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:
Codex
2026-06-03 20:56:27 +08:00
parent 7ccea67391
commit 78513aa4c7
2 changed files with 64 additions and 0 deletions
+60
View File
@@ -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>",
+4
View 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) {