feat(v0.1): CLI sessions create / storage / storage-delete + session-turn auto-ensure PVC
PR C 收尾附带 CLI 能力: - 新增 sessions create [sessionId] 调 POST /api/v1/sessions 创建 session+PVC - 新增 sessions storage <sessionId> 调 GET /api/v1/sessions/:id/storage - 新增 sessions storage <sessionId> --delete 调 DELETE - sessions turn <sessionId> 也会先 GET storage 探活,不存在则 POST /api/v1/sessions 补建 (之前 sessions turn 只在 store 里隐式建 session record 但 storageKind=none, 现在用显式 session create 入口保证 storageKind=pvc 提前建好) - ManagerClient 新增 delete() 方法
This commit is contained in:
@@ -40,6 +40,9 @@ async function dispatch(args: ParsedArgs): Promise<JsonValue> {
|
||||
if (group === "backends" && command === "list") return client(args).get("/api/v1/backends");
|
||||
if (group === "secrets" && command === "codex" && id === "render") return renderCodexSecret(args);
|
||||
if (group === "sessions" && command === "ps") return listSessions(args);
|
||||
if (group === "sessions" && command === "create") return sessionCreate(args, id ?? null);
|
||||
if (group === "sessions" && command === "storage" && id) return sessionStorageGet(args, id);
|
||||
if (group === "sessions" && command === "storage" && !id) throw new AgentRunError("schema-invalid", "sessions storage requires a sessionId", { httpStatus: 2 });
|
||||
if (group === "sessions" && command === "show" && id) return client(args).get(`/api/v1/sessions/${encodeURIComponent(id)}${readerQuery(args)}`);
|
||||
if (group === "sessions" && command === "read" && id) return client(args).post(`/api/v1/sessions/${encodeURIComponent(id)}/read`, { readerId: optionalFlag(args, "reader-id") ?? "cli" });
|
||||
if (group === "sessions" && command === "trace" && id) return sessionEvents(args, id, "trace");
|
||||
@@ -47,6 +50,8 @@ async function dispatch(args: ParsedArgs): Promise<JsonValue> {
|
||||
if (group === "sessions" && command === "turn") return sessionTurn(args, id ?? null);
|
||||
if (group === "sessions" && command === "steer" && id) return sessionSteer(args, id);
|
||||
if (group === "sessions" && command === "cancel" && id) return sessionCancel(args, id);
|
||||
const sessionStorageCmd = group === "sessions" && (command === "storage-delete" || (command === "storage" && id && optionalFlag(args, "delete") === "true"));
|
||||
if (sessionStorageCmd && id) return sessionStorageDelete(args, id);
|
||||
if (group === "queue" && command === "submit") return submitQueueTask(args);
|
||||
if (group === "queue" && command === "list") return listQueueTasks(args);
|
||||
if (group === "queue" && command === "show" && id) return client(args).get(`/api/v1/queue/tasks/${encodeURIComponent(id)}`);
|
||||
@@ -148,11 +153,63 @@ async function sessionEvents(args: ParsedArgs, sessionId: string, kind: "trace"
|
||||
return client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/${kind}${query ? `?${query}` : ""}`);
|
||||
}
|
||||
|
||||
async function sessionCreate(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
|
||||
const sessionId = positionalSessionId ?? optionalFlag(args, "session-id") ?? newSessionId();
|
||||
const profile = normalizeProfile(optionalFlag(args, "profile") ?? optionalFlag(args, "backend-profile") ?? "codex");
|
||||
const tenantId = optionalFlag(args, "tenant-id") ?? "unidesk";
|
||||
const projectId = optionalFlag(args, "project-id") ?? "default";
|
||||
const providerId = optionalFlag(args, "provider-id") ?? "G14";
|
||||
const expiresInDays = Number(optionalFlag(args, "expires-in-days") ?? 30);
|
||||
const expiresAt = new Date(Date.now() + Math.max(1, expiresInDays) * 24 * 60 * 60 * 1000).toISOString();
|
||||
const created = await client(args).post("/api/v1/sessions", {
|
||||
sessionId,
|
||||
tenantId,
|
||||
projectId,
|
||||
backendProfile: profile,
|
||||
expiresAt,
|
||||
}) as { action: string; pvc: { pvcName: string; pvcPhase: string }; session: { sessionId: string; storageKind: string; codexRolloutSubdir: string } };
|
||||
const storage = await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`) as { pvcName: string; pvcPhase: string; storageSizeBytes: number | null };
|
||||
return {
|
||||
action: created.action,
|
||||
session: created.session,
|
||||
pvc: created.pvc,
|
||||
storage,
|
||||
pollCommands: {
|
||||
show: `./scripts/agentrun sessions show ${sessionId} --reader-id cli`,
|
||||
storage: `./scripts/agentrun sessions storage ${sessionId}`,
|
||||
trace: `./scripts/agentrun sessions trace ${sessionId} --after-seq 0 --limit 100`,
|
||||
turn: `./scripts/agentrun sessions turn ${sessionId} --prompt "..."`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async function sessionStorageGet(args: ParsedArgs, sessionId: string): Promise<JsonRecord> {
|
||||
return (await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`)) as JsonRecord;
|
||||
}
|
||||
|
||||
async function sessionStorageDelete(args: ParsedArgs, sessionId: string): Promise<JsonRecord> {
|
||||
return (await client(args).delete(`/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`)) as JsonRecord;
|
||||
}
|
||||
|
||||
async function sessionTurn(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
|
||||
const body = await optionalJsonFile(args);
|
||||
const sessionId = positionalSessionId ?? optionalFlag(args, "session-id") ?? newSessionId();
|
||||
const requestedProfile = optionalFlag(args, "profile") ?? optionalFlag(args, "backend-profile") ?? (typeof body.backendProfile === "string" ? String(body.backendProfile) : "codex");
|
||||
const profile = normalizeProfile(requestedProfile);
|
||||
if (positionalSessionId || optionalFlag(args, "session-id")) {
|
||||
try {
|
||||
await client(args).get(`/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`);
|
||||
} catch (error) {
|
||||
const expiresInDays = Number(optionalFlag(args, "expires-in-days") ?? 30);
|
||||
await client(args).post("/api/v1/sessions", {
|
||||
sessionId,
|
||||
tenantId: optionalFlag(args, "tenant-id") ?? "unidesk",
|
||||
projectId: optionalFlag(args, "project-id") ?? "default",
|
||||
backendProfile: profile,
|
||||
expiresAt: new Date(Date.now() + Math.max(1, expiresInDays) * 24 * 60 * 60 * 1000).toISOString(),
|
||||
});
|
||||
}
|
||||
}
|
||||
const prompt = await readPrompt(args);
|
||||
body.tenantId = optionalFlag(args, "tenant-id") ?? stringField(body, "tenantId", "unidesk");
|
||||
body.projectId = optionalFlag(args, "project-id") ?? stringField(body, "projectId", "default");
|
||||
@@ -625,6 +682,9 @@ function help(): JsonRecord {
|
||||
"runs result <runId> [--command-id <commandId>]",
|
||||
"runs cancel <runId> [--reason <text>]",
|
||||
"sessions ps [--state default|running|unread|terminal|idle|all] [--profile codex|deepseek|minimax-m3|M3] [--reader-id <reader>]",
|
||||
"sessions create [sessionId] [--profile codex|deepseek|minimax-m3|M3] [--expires-in-days <n>]",
|
||||
"sessions storage <sessionId>",
|
||||
"sessions storage <sessionId> --delete",
|
||||
"sessions show <sessionId> [--reader-id <reader>]",
|
||||
"sessions turn [sessionId] --json-file <run-base.json> --prompt-file <file> [--profile minimax-m3|M3] [--runner-json-file <job.json>]",
|
||||
"sessions steer <sessionId> --prompt-file <file>",
|
||||
|
||||
@@ -16,6 +16,10 @@ export class ManagerClient {
|
||||
return this.request("PATCH", path, body);
|
||||
}
|
||||
|
||||
async delete(path: string): Promise<JsonValue> {
|
||||
return this.request("DELETE", path);
|
||||
}
|
||||
|
||||
private async request(method: string, path: string, body?: JsonValue): Promise<JsonValue> {
|
||||
const init: RequestInit = { method };
|
||||
if (body !== undefined) {
|
||||
|
||||
Reference in New Issue
Block a user