fix: unify agentrun session send cli
This commit is contained in:
+141
-93
@@ -39,7 +39,7 @@ const gitMirrorRepositories: readonly GitMirrorRepositorySpec[] = [
|
||||
|
||||
export function agentRunHelp(): unknown {
|
||||
return {
|
||||
command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|steer|send|explain",
|
||||
command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send|explain",
|
||||
output: "human by default; use -o json|yaml or --raw for machine/debug output",
|
||||
usage: [
|
||||
"bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
||||
@@ -54,7 +54,6 @@ export function agentRunHelp(): unknown {
|
||||
"bun scripts/cli.ts agentrun dispatch task/<taskId>",
|
||||
"bun scripts/cli.ts agentrun create task --aipod Artificer --prompt-stdin --idempotency-key <key>",
|
||||
"bun scripts/cli.ts agentrun apply -f - --dry-run",
|
||||
"bun scripts/cli.ts agentrun steer session/<sessionId> --prompt-stdin",
|
||||
"bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin",
|
||||
"bun scripts/cli.ts agentrun explain task",
|
||||
"bun scripts/cli.ts agentrun control-plane status",
|
||||
@@ -78,7 +77,7 @@ export function agentRunHelp(): unknown {
|
||||
],
|
||||
resources: ["task/qt", "run", "command/cmd", "runnerjob/rjob", "session/ses", "aipodspec/aps"],
|
||||
description: "Operate AgentRun v0.1 through Kubernetes-style resource verbs. Human output is compact by default; -o json|yaml returns the UniDesk render-only client schema, and --raw exposes the direct AgentRun REST envelope.",
|
||||
legacyCompatibility: "queue/runs/commands/runner/sessions/aipod-specs remain as compatibility groups backed by direct HTTP; new commander work should use get/describe/events/logs/result/ack/cancel/dispatch/create/apply/steer/send.",
|
||||
legacyCompatibility: "queue/runs/commands/runner/sessions/aipod-specs remain as compatibility groups backed by direct HTTP; new commander work should use get/describe/events/logs/result/ack/cancel/dispatch/create/apply/send. sessions turn/steer are removed; use send only.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,7 +149,6 @@ function isResourceVerb(value: string | undefined): value is AgentRunResourceVer
|
||||
|| value === "dispatch"
|
||||
|| value === "create"
|
||||
|| value === "apply"
|
||||
|| value === "steer"
|
||||
|| value === "send"
|
||||
|| value === "explain";
|
||||
}
|
||||
@@ -211,11 +209,8 @@ function agentRunHelpText(args: string[]): string {
|
||||
if (verb === "apply") {
|
||||
return "Usage: bun scripts/cli.ts agentrun apply -f task.yaml|json|- [--dry-run]\nTask manifests use kind: Task and spec: <AgentRun task payload>.";
|
||||
}
|
||||
if (verb === "steer") {
|
||||
return "Usage: bun scripts/cli.ts agentrun steer session/<sessionId> --prompt-stdin";
|
||||
}
|
||||
if (verb === "send") {
|
||||
return "Usage: bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin";
|
||||
return "Usage: bun scripts/cli.ts agentrun send session/<sessionId> --aipod Artificer --prompt-stdin\nThe server decides whether this becomes an internal steer or a new turn from session state.";
|
||||
}
|
||||
if (verb === "explain") return agentRunExplain(kind ?? "task");
|
||||
if (verb === "control-plane") {
|
||||
@@ -254,7 +249,7 @@ function agentRunHelpText(args: string[]): string {
|
||||
return [
|
||||
"Usage: bun scripts/cli.ts agentrun <verb> <resource> [options]",
|
||||
"",
|
||||
"Verbs: get, describe, events, logs, result, ack, cancel, dispatch, create, apply, steer, send, explain",
|
||||
"Verbs: get, describe, events, logs, result, ack, cancel, dispatch, create, apply, send, explain",
|
||||
"Resources: task/qt, run, command/cmd, runnerjob/rjob, session/ses, aipodspec/aps",
|
||||
"",
|
||||
"Common:",
|
||||
@@ -299,8 +294,7 @@ async function runAgentRunResourceCommand(config: UniDeskConfig | null, verb: Ag
|
||||
if (verb === "dispatch") return await resourceDispatch(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "create") return await resourceCreate(config, command, action, bridgeActionArgs, options);
|
||||
if (verb === "apply") return await resourceApply(config, command, bridgeActionArgs, options);
|
||||
if (verb === "steer") return await resourceSessionPromptCommand(config, command, "steer", action, bridgeActionArgs, options);
|
||||
if (verb === "send") return await resourceSessionPromptCommand(config, command, "turn", action, bridgeActionArgs, options);
|
||||
if (verb === "send") return await resourceSessionPromptCommand(config, command, action, bridgeActionArgs, options);
|
||||
} catch (error) {
|
||||
if (error instanceof AgentRunRestError) return renderAgentRunRestError(command, error, options);
|
||||
return renderedCliResult(false, command, `Error: ${error instanceof Error ? error.message : String(error)}`);
|
||||
@@ -620,12 +614,12 @@ async function resourceApply(config: UniDeskConfig | null, command: string, args
|
||||
return renderMutationSummary(command, result, options, `${options.dryRun ? "Dry-run applied" : "Applied"} task manifest`, options.dryRun ? [rerunWithoutDryRun(command)] : undefined);
|
||||
}
|
||||
|
||||
async function resourceSessionPromptCommand(config: UniDeskConfig | null, command: string, officialAction: "steer" | "turn", action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
async function resourceSessionPromptCommand(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise<RenderedCliResult> {
|
||||
const ref = parseResourceRef(action, args, "session");
|
||||
if (ref.kind !== "session") throw new Error(`${officialAction === "steer" ? "steer" : "send"} requires session/<sessionId>`);
|
||||
const sessionArgs = [officialAction, ref.name, ...stripLeadingResource(args, ref.name)];
|
||||
if (ref.kind !== "session") throw new Error("send requires session/<sessionId>");
|
||||
const sessionArgs = ["send", ref.name, ...stripLeadingResource(args, ref.name)];
|
||||
const result = await runAgentRunRestCommand(config, "sessions", sessionArgs);
|
||||
return renderMutationSummary(command, result, options, officialAction === "steer" ? "Steer submitted" : "Session turn submitted");
|
||||
return renderMutationSummary(command, result, options, options.dryRun ? "Session send plan" : "Session send submitted");
|
||||
}
|
||||
|
||||
function renderedCliResult(ok: boolean, command: string, renderedText: string, contentType: RenderedCliResult["contentType"] = "text/plain"): RenderedCliResult {
|
||||
@@ -675,7 +669,20 @@ function renderResultSummary(command: string, raw: Record<string, unknown>, opti
|
||||
`State: ${displayValue(data.state ?? data.status ?? data.terminalStatus ?? "-")}`,
|
||||
`OK: ${String(raw.ok !== false)}`,
|
||||
];
|
||||
const final = stringOrNull(data.finalResponse) ?? stringOrNull(data.output) ?? stringOrNull(data.summary) ?? stringOrNull(data.result);
|
||||
const authority = stringOrNull(data.finalResponseAuthority);
|
||||
const fallback = typeof data.finalResponseFallback === "boolean" ? data.finalResponseFallback : null;
|
||||
const needsContinuation = typeof data.needsContinuation === "boolean" ? data.needsContinuation : null;
|
||||
if (authority !== null) lines.push(`Authority: ${authority}`);
|
||||
if (fallback !== null) lines.push(`Fallback: ${String(fallback)}`);
|
||||
if (needsContinuation !== null) lines.push(`NeedsContinuation: ${String(needsContinuation)}`);
|
||||
const finalRecord = record(data.finalResponse);
|
||||
const final = stringOrNull(data.finalResponse)
|
||||
?? stringOrNull(finalRecord.content)
|
||||
?? stringOrNull(finalRecord.text)
|
||||
?? stringOrNull(finalRecord.message)
|
||||
?? stringOrNull(data.output)
|
||||
?? stringOrNull(data.summary)
|
||||
?? stringOrNull(data.result);
|
||||
if (final !== null) lines.push("", truncateMultiline(final, options.fullText ? 8000 : 1600));
|
||||
else {
|
||||
const failure = renderFailureLines(data);
|
||||
@@ -694,6 +701,12 @@ function renderMutationSummary(command: string, raw: Record<string, unknown>, op
|
||||
`OK: ${String(raw.ok !== false)}`,
|
||||
];
|
||||
if (id !== null) lines.push(`Name: ${id}`);
|
||||
const decision = stringOrNull(data.decision);
|
||||
const internalCommandType = stringOrNull(data.internalCommandType);
|
||||
if (data.dryRun !== undefined) lines.push(`DryRun: ${String(data.dryRun)}`);
|
||||
if (data.mutation !== undefined) lines.push(`Mutation: ${String(data.mutation)}`);
|
||||
if (decision !== null) lines.push(`Decision: ${decision}`);
|
||||
if (internalCommandType !== null) lines.push(`InternalCommandType: ${internalCommandType}`);
|
||||
const next = record(raw.next ?? data.next);
|
||||
const nextLines = (overrideNextLines ?? Object.values(next).map(String)).filter((line) => line.length > 0).slice(0, 5);
|
||||
if (nextLines.length > 0) lines.push("", "Next:", ...nextLines.map((line) => ` ${line}`));
|
||||
@@ -3342,8 +3355,7 @@ async function runAgentRunSessionsRest(action: string | undefined, id: string |
|
||||
if (agentRunHasFlag(args, "dry-run")) return agentRunDryRunPlan("session-cancel", `/api/v1/sessions/${encodeURIComponent(id)}/control`, body, `bun scripts/cli.ts agentrun sessions cancel ${id}`);
|
||||
return await agentRunRestRequest("agentrun sessions cancel", "POST", `/api/v1/sessions/${encodeURIComponent(id)}/control`, body);
|
||||
}
|
||||
if (action === "steer" && id) return await sessionSteerRest(id, args);
|
||||
if (action === "turn") return await sessionTurnRest(id ?? null, args);
|
||||
if (action === "send" && id) return await sessionSendRest(id, args);
|
||||
throw new AgentRunRestError("validation-failed", `unsupported sessions command: ${[action, id].filter(Boolean).join(" ") || "(empty)"}`);
|
||||
}
|
||||
|
||||
@@ -3436,64 +3448,85 @@ async function mutateQueueTaskRest(action: string, taskId: string, suffix: strin
|
||||
return await agentRunRestRequest(`agentrun queue ${suffix}`, "POST", pathValue, body);
|
||||
}
|
||||
|
||||
async function sessionSteerRest(sessionId: string, args: string[]): Promise<Record<string, unknown>> {
|
||||
const session = record(innerData(await agentRunRestRequest("agentrun sessions show", "GET", `/api/v1/sessions/${encodeURIComponent(sessionId)}${agentRunQuery(args, ["reader-id"])}`)));
|
||||
const runId = stringOrNull(session.activeRunId) ?? stringOrNull(session.lastRunId);
|
||||
if (runId === null) throw new AgentRunRestError("validation-failed", `session ${sessionId} has no run to steer`);
|
||||
const prompt = readPromptFromArgs(args, 2);
|
||||
const body: Record<string, unknown> = { type: "steer", payload: { prompt } };
|
||||
const idempotencyKey = agentRunOption(args, "idempotency-key");
|
||||
if (idempotencyKey) body.idempotencyKey = idempotencyKey;
|
||||
const command = await agentRunRestRequest("agentrun sessions steer", "POST", `/api/v1/runs/${encodeURIComponent(runId)}/commands`, body);
|
||||
return { ok: command.ok !== false, command: "agentrun sessions steer", data: { action: "session-steer", sessionId, runId, command: innerData(command) }, bridge: command.bridge };
|
||||
async function sessionSendRest(sessionId: string, args: string[]): Promise<Record<string, unknown>> {
|
||||
const aipod = agentRunOption(args, "aipod") ?? agentRunOption(args, "aipod-spec");
|
||||
if (aipod) return await sessionSendWithAipodRest(sessionId, aipod, args);
|
||||
const input = await optionalJsonBody(args);
|
||||
const prompt = optionalPromptFromArgs(args, 2);
|
||||
const sendBody = await sessionSendBodyFromRunInput(sessionId, args, input, sessionSendPayloadFromInput(input, prompt));
|
||||
const response = await agentRunRestRequest("agentrun sessions send", "POST", `/api/v1/sessions/${encodeURIComponent(sessionId)}/send`, sendBody);
|
||||
return { ok: response.ok !== false, command: "agentrun sessions send", data: innerData(response), bridge: response.bridge };
|
||||
}
|
||||
|
||||
async function sessionTurnRest(positionalSessionId: string | null, args: string[]): Promise<Record<string, unknown>> {
|
||||
const aipod = agentRunOption(args, "aipod") ?? agentRunOption(args, "aipod-spec");
|
||||
if (aipod) return await sessionTurnWithAipodRest(positionalSessionId, aipod, args);
|
||||
const sessionId = positionalSessionId ?? agentRunOption(args, "session-id") ?? newAgentRunSessionId();
|
||||
const body = await optionalJsonBody(args);
|
||||
const profile = agentRunOption(args, "profile") ?? agentRunOption(args, "backend-profile") ?? stringOrNull(body.backendProfile) ?? "codex";
|
||||
const prompt = readPromptFromArgs(args, positionalSessionId ? 2 : 1);
|
||||
body.tenantId = agentRunOption(args, "tenant-id") ?? stringOrNull(body.tenantId) ?? "unidesk";
|
||||
body.projectId = agentRunOption(args, "project-id") ?? stringOrNull(body.projectId) ?? "default";
|
||||
function sessionSendPayloadFromInput(input: Record<string, unknown>, prompt: string | null): Record<string, unknown> {
|
||||
const payload = record(input.payload);
|
||||
if (Object.keys(payload).length > 0) return payload;
|
||||
const text = prompt ?? stringOrNull(input.prompt) ?? stringOrNull(input.message) ?? stringOrNull(input.text);
|
||||
if (text === null) throw new AgentRunRestError("validation-failed", "send requires payload or non-empty prompt/message/text; use --prompt-stdin, --prompt-file, --prompt, a trailing prompt, or JSON payload");
|
||||
return { prompt: text };
|
||||
}
|
||||
|
||||
async function sessionSendBodyFromRunInput(sessionId: string, args: string[], input: Record<string, unknown>, payload: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
const fullSendBody = isExplicitSessionSendBody(input);
|
||||
const runInput = fullSendBody ? record(input.run ?? input.runBase) : input;
|
||||
const runBody = await sessionRunBodyFromArgs(sessionId, args, runInput);
|
||||
const runnerDefaults = fullSendBody ? record(input.runnerJob) : {};
|
||||
const runnerJob = await sessionRunnerJobBody(args, runnerDefaults);
|
||||
const body: Record<string, unknown> = fullSendBody ? { ...input } : {};
|
||||
body.run = runBody;
|
||||
body.payload = record(input.payload);
|
||||
if (Object.keys(record(body.payload)).length === 0) body.payload = payload;
|
||||
body.createRunnerJob = agentRunHasFlag(args, "no-runner-job") ? false : input.createRunnerJob !== false;
|
||||
body.runnerJob = runnerJob;
|
||||
body.dryRun = agentRunHasFlag(args, "dry-run");
|
||||
const commandIdempotencyKey = agentRunOption(args, "command-idempotency-key") ?? agentRunOption(args, "idempotency-key");
|
||||
if (commandIdempotencyKey) body.commandIdempotencyKey = commandIdempotencyKey;
|
||||
return body;
|
||||
}
|
||||
|
||||
function isExplicitSessionSendBody(input: Record<string, unknown>): boolean {
|
||||
return isRecord(input.run)
|
||||
|| isRecord(input.runBase)
|
||||
|| isRecord(input.payload)
|
||||
|| isRecord(input.runnerJob)
|
||||
|| input.createRunnerJob !== undefined
|
||||
|| input.commandIdempotencyKey !== undefined;
|
||||
}
|
||||
|
||||
async function sessionRunBodyFromArgs(sessionId: string, args: string[], input: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
const existing = await fetchAgentRunSessionOrNull(sessionId, args);
|
||||
const profile = agentRunOption(args, "profile")
|
||||
?? agentRunOption(args, "backend-profile")
|
||||
?? stringOrNull(input.backendProfile)
|
||||
?? stringOrNull(existing?.backendProfile)
|
||||
?? "codex";
|
||||
const body: Record<string, unknown> = { ...input };
|
||||
body.tenantId = agentRunOption(args, "tenant-id") ?? stringOrNull(body.tenantId) ?? stringOrNull(existing?.tenantId) ?? "unidesk";
|
||||
body.projectId = agentRunOption(args, "project-id") ?? stringOrNull(body.projectId) ?? stringOrNull(existing?.projectId) ?? "default";
|
||||
body.providerId = agentRunOption(args, "provider-id") ?? stringOrNull(body.providerId) ?? "G14";
|
||||
body.backendProfile = profile;
|
||||
body.workspaceRef = jsonObjectOption(args, "workspace-json") ?? record(body.workspaceRef);
|
||||
if (Object.keys(record(body.workspaceRef)).length === 0) body.workspaceRef = { kind: "opaque", path: "." };
|
||||
body.executionPolicy = jsonObjectOption(args, "execution-policy-json") ?? record(body.executionPolicy);
|
||||
if (Object.keys(record(body.executionPolicy)).length === 0) body.executionPolicy = defaultAgentRunExecutionPolicy(profile);
|
||||
const sessionRef = record(body.sessionRef);
|
||||
const metadata = record(sessionRef.metadata);
|
||||
const inheritedSessionRef = existing === null ? {} : {
|
||||
conversationId: existing.conversationId,
|
||||
threadId: existing.threadId,
|
||||
metadata: existing.metadata,
|
||||
};
|
||||
const sessionRef = { ...inheritedSessionRef, ...record(body.sessionRef) };
|
||||
const metadata = { ...record(inheritedSessionRef.metadata), ...record(sessionRef.metadata) };
|
||||
const title = agentRunOption(args, "title");
|
||||
if (title) metadata.title = title;
|
||||
body.sessionRef = { ...sessionRef, sessionId, metadata };
|
||||
await ensureAgentRunSession(sessionId, body);
|
||||
const run = await agentRunRestRequest("agentrun runs create", "POST", "/api/v1/runs", body);
|
||||
const runData = record(innerData(run));
|
||||
const runId = stringOrNull(runData.id) ?? requiredContext("session turn", "run id returned by AgentRun server");
|
||||
const commandBody: Record<string, unknown> = { type: "turn", payload: { prompt } };
|
||||
const commandIdempotencyKey = agentRunOption(args, "command-idempotency-key") ?? agentRunOption(args, "idempotency-key");
|
||||
if (commandIdempotencyKey) commandBody.idempotencyKey = commandIdempotencyKey;
|
||||
const command = await agentRunRestRequest("agentrun commands create", "POST", `/api/v1/runs/${encodeURIComponent(runId)}/commands`, commandBody);
|
||||
const commandId = stringOrNull(record(innerData(command)).id);
|
||||
let runnerJob: Record<string, unknown> | null = null;
|
||||
if (!agentRunHasFlag(args, "no-runner-job") && commandId !== null) {
|
||||
const runnerBody = await optionalRunnerJsonBody(args);
|
||||
runnerBody.commandId = commandId;
|
||||
copyAgentRunOptions(args, runnerBody, ["image", "namespace", "attempt-id", "runner-id", "source-commit", "service-account-name"]);
|
||||
runnerJob = await agentRunRestRequest("agentrun runner job", "POST", `/api/v1/runs/${encodeURIComponent(runId)}/runner-jobs`, runnerBody);
|
||||
}
|
||||
return { ok: true, command: "agentrun sessions turn", data: { action: "session-turn", sessionId, profile, run: innerData(run), command: innerData(command), runnerJob: runnerJob ? innerData(runnerJob) : null, valuesPrinted: false }, bridge: runnerJob?.bridge ?? command.bridge ?? run.bridge };
|
||||
return body;
|
||||
}
|
||||
|
||||
async function sessionTurnWithAipodRest(positionalSessionId: string | null, aipod: string, args: string[]): Promise<Record<string, unknown>> {
|
||||
const sessionId = positionalSessionId ?? agentRunOption(args, "session-id") ?? newAgentRunSessionId();
|
||||
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, await aipodRenderInputFromArgs(args, positionalSessionId ? 2 : 1, { sessionId }));
|
||||
async function sessionSendWithAipodRest(sessionId: string, aipod: string, args: string[]): Promise<Record<string, unknown>> {
|
||||
const rendered = await agentRunRestRequest("agentrun aipod-specs render", "POST", `/api/v1/aipod-specs/${encodeURIComponent(aipod)}/render`, await aipodRenderInputFromArgs(args, 2, { sessionId }));
|
||||
const renderedData = record(innerData(rendered));
|
||||
const task = record(renderedData.queueTask);
|
||||
if (Object.keys(task).length === 0) throw new AgentRunRestError("schema-mismatch", `aipod-spec ${aipod} render did not return queueTask`);
|
||||
await ensureAgentRunSession(sessionId, task);
|
||||
const sessionRef = record(task.sessionRef);
|
||||
const metadata = record(sessionRef.metadata);
|
||||
const title = agentRunOption(args, "title") ?? stringOrNull(task.title);
|
||||
@@ -3509,37 +3542,38 @@ async function sessionTurnWithAipodRest(positionalSessionId: string | null, aipo
|
||||
resourceBundleRef: task.resourceBundleRef,
|
||||
traceSink: { kind: "aipod-session", aipod, sessionId, valuesPrinted: false },
|
||||
};
|
||||
const run = await agentRunRestRequest("agentrun runs create", "POST", "/api/v1/runs", runBody);
|
||||
const runId = stringOrNull(record(innerData(run)).id) ?? requiredContext("session turn", "run id returned by AgentRun server");
|
||||
const commandBody: Record<string, unknown> = { type: "turn", payload: task.payload };
|
||||
const runnerDefaults = record(record(renderedData.dispatchDefaults).runnerJob);
|
||||
const sendBody: Record<string, unknown> = {
|
||||
run: runBody,
|
||||
payload: task.payload,
|
||||
createRunnerJob: !agentRunHasFlag(args, "no-runner-job"),
|
||||
runnerJob: await sessionRunnerJobBody(args, runnerDefaults),
|
||||
dryRun: agentRunHasFlag(args, "dry-run"),
|
||||
};
|
||||
const commandIdempotencyKey = agentRunOption(args, "command-idempotency-key") ?? agentRunOption(args, "idempotency-key");
|
||||
if (commandIdempotencyKey) commandBody.idempotencyKey = commandIdempotencyKey;
|
||||
const command = await agentRunRestRequest("agentrun commands create", "POST", `/api/v1/runs/${encodeURIComponent(runId)}/commands`, commandBody);
|
||||
const commandId = stringOrNull(record(innerData(command)).id);
|
||||
let runnerJob: Record<string, unknown> | null = null;
|
||||
if (!agentRunHasFlag(args, "no-runner-job") && commandId !== null) {
|
||||
const runnerDefaults = record(record(renderedData.dispatchDefaults).runnerJob);
|
||||
const runnerBody = { ...runnerDefaults, ...(await optionalRunnerJsonBody(args)), commandId };
|
||||
copyAgentRunOptions(args, runnerBody, ["image", "namespace", "attempt-id", "runner-id", "source-commit", "service-account-name"]);
|
||||
runnerJob = await agentRunRestRequest("agentrun runner job", "POST", `/api/v1/runs/${encodeURIComponent(runId)}/runner-jobs`, runnerBody);
|
||||
}
|
||||
return { ok: true, command: "agentrun sessions turn", data: { action: "session-turn", aipod, sessionId, profile: String(task.backendProfile ?? ""), run: innerData(run), command: innerData(command), runnerJob: runnerJob ? innerData(runnerJob) : null, valuesPrinted: false }, bridge: runnerJob?.bridge ?? command.bridge ?? run.bridge };
|
||||
if (commandIdempotencyKey) sendBody.commandIdempotencyKey = commandIdempotencyKey;
|
||||
const response = await agentRunRestRequest("agentrun sessions send", "POST", `/api/v1/sessions/${encodeURIComponent(sessionId)}/send`, sendBody);
|
||||
const data = record(innerData(response));
|
||||
return { ok: response.ok !== false, command: "agentrun sessions send", data: { ...data, aipod, profile: String(task.backendProfile ?? ""), valuesPrinted: false }, bridge: response.bridge };
|
||||
}
|
||||
|
||||
async function ensureAgentRunSession(sessionId: string, source: Record<string, unknown>): Promise<void> {
|
||||
async function fetchAgentRunSessionOrNull(sessionId: string, args: string[]): Promise<Record<string, unknown> | null> {
|
||||
try {
|
||||
await agentRunRestRequest("agentrun sessions storage", "GET", `/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`);
|
||||
return;
|
||||
} catch {
|
||||
// Missing storage is recovered by creating the session; other failures will surface on create.
|
||||
return record(innerData(await agentRunRestRequest("agentrun sessions show", "GET", `/api/v1/sessions/${encodeURIComponent(sessionId)}${agentRunQuery(args, ["reader-id"])}`)));
|
||||
} catch (error) {
|
||||
if (error instanceof AgentRunRestError && error.httpStatus === 404) return null;
|
||||
throw error;
|
||||
}
|
||||
await agentRunRestRequest("agentrun sessions create", "POST", "/api/v1/sessions", {
|
||||
sessionId,
|
||||
tenantId: source.tenantId ?? "unidesk",
|
||||
projectId: source.projectId ?? "default",
|
||||
backendProfile: source.backendProfile ?? "codex",
|
||||
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(),
|
||||
});
|
||||
}
|
||||
|
||||
async function sessionRunnerJobBody(args: string[], defaults: Record<string, unknown> = {}): Promise<Record<string, unknown>> {
|
||||
const runnerBody = { ...defaults, ...(await optionalRunnerJsonBody(args)) };
|
||||
copyAgentRunOptions(args, runnerBody, ["image", "namespace", "attempt-id", "runner-id", "source-commit", "service-account-name"]);
|
||||
const managerUrl = agentRunOption(args, "runner-manager-url");
|
||||
if (managerUrl !== null) runnerBody.managerUrl = managerUrl;
|
||||
const runnerIdempotencyKey = agentRunOption(args, "runner-idempotency-key");
|
||||
if (runnerIdempotencyKey !== null) runnerBody.idempotencyKey = runnerIdempotencyKey;
|
||||
return runnerBody;
|
||||
}
|
||||
|
||||
async function agentRunRestRequest(command: string, method: AgentRunHttpMethod, pathValue: string, body?: unknown): Promise<Record<string, unknown>> {
|
||||
@@ -3967,12 +4001,26 @@ function jsonInputDisclosureFromArgs(args: string[]): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function newAgentRunSessionId(): string {
|
||||
return `sess_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
function defaultAgentRunExecutionPolicy(profile: string): Record<string, unknown> {
|
||||
return { backendProfile: profile, valuesPrinted: false };
|
||||
const keys = profile === "sub2api" || profile === "codex" ? ["auth.json", "config.toml"] : ["auth.json", "config.toml"];
|
||||
return {
|
||||
sandbox: "workspace-write",
|
||||
approval: "never",
|
||||
timeoutMs: 900000,
|
||||
network: "enabled",
|
||||
secretScope: {
|
||||
allowCredentialEcho: false,
|
||||
providerCredentials: [
|
||||
{
|
||||
profile,
|
||||
secretRef: {
|
||||
name: `agentrun-v01-provider-${profile}`,
|
||||
keys,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function safeAgentRunEnvelope(envelope: Record<string, unknown>): Record<string, unknown> {
|
||||
@@ -4046,7 +4094,7 @@ type AgentRunHttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
||||
type AgentRunFailureKind = "auth-missing" | "auth-failed" | "agentrun-unreachable" | "schema-mismatch" | "unsupported-version" | "validation-failed";
|
||||
|
||||
type AgentRunRestCompatGroup = "queue" | "sessions" | "aipod-specs" | "aipods" | "runs" | "commands" | "runner";
|
||||
type AgentRunResourceVerb = "get" | "describe" | "events" | "logs" | "result" | "ack" | "cancel" | "dispatch" | "create" | "apply" | "steer" | "send" | "explain";
|
||||
type AgentRunResourceVerb = "get" | "describe" | "events" | "logs" | "result" | "ack" | "cancel" | "dispatch" | "create" | "apply" | "send" | "explain";
|
||||
type AgentRunResourceKind = "task" | "run" | "command" | "runnerjob" | "session" | "aipodspec";
|
||||
type AgentRunOutputMode = "human" | "wide" | "name" | "json" | "yaml";
|
||||
|
||||
@@ -4471,7 +4519,7 @@ function unsupported(args: string[]): RenderedCliResult {
|
||||
`Error: unsupported AgentRun command: ${command}`,
|
||||
"",
|
||||
"Supported resource commands:",
|
||||
" agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|steer|send",
|
||||
" agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send",
|
||||
"",
|
||||
"Compatibility bridge groups:",
|
||||
" agentrun aipod-specs|queue|runs|commands|runner|sessions",
|
||||
|
||||
+6
-6
@@ -57,14 +57,14 @@ export function rootHelp(): unknown {
|
||||
{ command: "commander contract|plan --dry-run|smoke --dry-run|approval request --dry-run", description: "Host Codex commander skeleton contract, no-daemon smoke plan, and dry-run approval preview without live bridges or message sends." },
|
||||
{ command: "hwlab nodes control-plane|git-mirror|secret --node G14 --lane v03", description: "Manage HWLAB node/lane runtime prerequisites for v0.3+ with the node identity passed as data instead of a command family." },
|
||||
{ command: "hwlab g14 monitor-prs | hwlab g14 control-plane status|apply|trigger-current|runtime-migration|cleanup-runs|cleanup-released-pvs | hwlab g14 git-mirror status|apply|sync|flush | hwlab g14 tools-image status|build", description: "Start the legacy G14 PR monitor, run bounded v0.2 Tekton/Argo control-plane, manual PipelineRun trigger, runtime migration, CI workspace retention, manual devops-infra git mirror/relay maintenance, or fixed HWLAB CI tools image actions; long confirmed trigger/sync/flush actions return async jobs by default." },
|
||||
{ command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|steer|send|control-plane|git-mirror", description: "Use AgentRun v0.1 resource primitives with low-noise human output by default; legacy bridge groups remain available for raw compatibility." },
|
||||
{ command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send|control-plane|git-mirror", description: "Use AgentRun v0.1 resource primitives with low-noise human output by default; session follow-up uses send only and the server decides internal steer vs turn." },
|
||||
{ command: "platform-infra sub2api plan|apply|status|validate|codex-pool", description: "Deploy Sub2API in G14 platform-infra, manage the YAML-controlled Codex upstream pool, expose the unified API, and inspect marker sentinel state with low-noise reports without printing API keys." },
|
||||
{ command: "hwlab cd audit --env dev | hwlab cd status --env dev | hwlab cd apply --env dev --dry-run", description: "Legacy D601 HWLAB DEV CD wrapper kept for explicit old-path diagnostics; current HWLAB rollout uses G14 GitOps." },
|
||||
{ command: "code-agent-sandbox", description: "Independent Code Agent Sandbox service skeleton for adapter, mode, and credential-boundary diagnostics." },
|
||||
{ command: "schedule list|get|runs|run|retry-run|delete", description: "Manage backend-core scheduled tasks and run history; schedule run <id> supports --wait-ms N and retry-run reuses the failed run's schedule." },
|
||||
{ command: "schedule upsert-pgdata-backup [--time HH:MM] [--remote-base /SERVER_DATA/UNIDESK_PG_DATA]", description: "Create or update the daily PGDATA physical backup task that uploads monthly rotated archives to Baidu Netdisk." },
|
||||
{ command: "codex deploy <commitId> [--provider-id D601] [--timeout-ms N]", description: "Disabled legacy Code Queue deploy path; use the dev-only artifact consumer instead." },
|
||||
{ command: "codex submit|steer|resume|queue create|queue merge|move", description: "Frozen legacy Code Queue write commands; use agentrun create/apply/steer/send for new commander work. Historical codex task/tasks/output/read/unread/queues remain available for archive troubleshooting." },
|
||||
{ command: "codex submit|steer|resume|queue create|queue merge|move", description: "Frozen legacy Code Queue write commands; use agentrun create/apply/send for new commander work. Historical codex task/tasks/output/read/unread/queues remain available for archive troubleshooting." },
|
||||
{ command: "codex skills-sync --dry-run [--full]", description: "Inspect the controlled runner skills hostPath lifecycle contract without copying files, restarting services, reading secrets, or mutating live runner paths." },
|
||||
{ command: "codex execution-plane [--full|--raw]", description: "Read-only D601 native k3s Code Queue execution-plane inspection; compares formal deployments, deprecated Compose residuals, commit markers, pod digest, and mounted worktree HEAD." },
|
||||
{ command: "codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]", description: "Read-only PR admission check with compact commander output by default; use --full or --raw to expand the full runtime preflight, tool, and observation payload." },
|
||||
@@ -75,7 +75,7 @@ export function rootHelp(): unknown {
|
||||
{ command: "codex read <taskId>", description: "Mark one reviewed terminal task read and return terminal metadata plus final response; prompt/tool logs stay behind drill-down commands." },
|
||||
{ command: "codex dev-ready", description: "Fetch execution-container readiness, including sanitized skill injection status from /api/dev-ready." },
|
||||
{ command: "codex judge <taskId> --attempt N [--dry-run] [--include-prompt]", description: "Replay one stored Code Queue attempt through the same judge context builder and MiniMax judge call path used by the live queue worker." },
|
||||
{ command: "codex steer <taskId> / codex resume <taskId>", description: "Frozen legacy execution mutation entries; use agentrun steer/send/logs/events/result/ack/cancel against AgentRun resources instead." },
|
||||
{ command: "codex steer <taskId> / codex resume <taskId>", description: "Frozen legacy execution mutation entries; use agentrun send/logs/events/result/ack/cancel against AgentRun resources instead." },
|
||||
{ command: "codex steer-confirm <taskId> --steer-id <id> [--raw]", description: "Read-only lookup for a steerId in task trace so deliveryUnconfirmed can be resolved without resending the corrective prompt." },
|
||||
{ command: "codex interrupt|cancel <taskId>", description: "Request interrupt for a running Code Queue task, or cancel a queued/retry_wait task, through the same private proxy." },
|
||||
{ command: "codex queues [--full|--all] [--limit N] [--page N|--offset N]", description: "Read legacy Code Queue archive summaries. Legacy queue create/merge and move are frozen; use agentrun create/apply/get/cancel for new work." },
|
||||
@@ -391,7 +391,7 @@ function codexHelp(): unknown {
|
||||
"bun scripts/cli.ts codex execution-plane [--full|--raw]",
|
||||
"bun scripts/cli.ts codex pr-preflight [--remote] [--push-dry-run --push-dry-run-ref refs/heads/probe/<name>] [--pr-create-dry-run --pr-create-dry-run-head <head>] [--issue N] [--full|--raw]",
|
||||
"bun scripts/cli.ts codex judge <taskId> --attempt N [--dry-run] [--include-prompt]",
|
||||
"bun scripts/cli.ts agentrun steer session/<sessionId> --prompt-stdin",
|
||||
"bun scripts/cli.ts agentrun send session/<sessionId> --prompt-stdin",
|
||||
"bun scripts/cli.ts codex steer <taskId> # frozen legacy write entry",
|
||||
"bun scripts/cli.ts codex resume <taskId> # frozen legacy write entry",
|
||||
"bun scripts/cli.ts codex steer-confirm <taskId> --steer-id <id> [--raw]",
|
||||
@@ -463,7 +463,7 @@ function codexHelp(): unknown {
|
||||
redline: "data.supervisor.activeRunning.redline names the count field, routine target, burst redline, hard redline, and decisionReady flag.",
|
||||
limitSemantics: "filters.requestedLimit preserves the user input; filters.limit/effectiveLimit shows the capped query budget; section outputBudget/rowPage show returned-row caps.",
|
||||
},
|
||||
description: "Operate legacy Code Queue as a read-only archive through bounded task/output/read/unread/queues views. New task dispatch, retry/resume, steer, queue mutation, move, and workdir mutation are frozen and replaced by AgentRun resource primitives via bun scripts/cli.ts agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|steer|send.",
|
||||
description: "Operate legacy Code Queue as a read-only archive through bounded task/output/read/unread/queues views. New task dispatch, retry/resume, queue mutation, move, and workdir mutation are frozen and replaced by AgentRun resource primitives via bun scripts/cli.ts agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send.",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -578,7 +578,7 @@ function artifactRegistryHelp(): unknown {
|
||||
|
||||
function agentRunHelpSummary(): unknown {
|
||||
return {
|
||||
command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|steer|send|control-plane|git-mirror",
|
||||
command: "agentrun get|describe|events|logs|result|ack|cancel|dispatch|create|apply|send|control-plane|git-mirror",
|
||||
output: "human by default; use -o json|yaml or --raw",
|
||||
usage: [
|
||||
"bun scripts/cli.ts agentrun get tasks --queue commander --limit 20",
|
||||
|
||||
Reference in New Issue
Block a user