diff --git a/.agents/skills/unidesk-code-queue/SKILL.md b/.agents/skills/unidesk-code-queue/SKILL.md index dc624ff9..7b742486 100644 --- a/.agents/skills/unidesk-code-queue/SKILL.md +++ b/.agents/skills/unidesk-code-queue/SKILL.md @@ -56,6 +56,7 @@ bun scripts/cli.ts agentrun ack session/ bun scripts/cli.ts agentrun dispatch task/ bun scripts/cli.ts agentrun send session/ --aipod Artificer --prompt-stdin bun scripts/cli.ts agentrun cancel session/ --reason --dry-run +bun scripts/cli.ts agentrun cancel command/ --run --reason --dry-run ``` 日常 task manifest 优先使用 YAML heredoc:`agentrun apply -f -`;单 prompt 派单优先 `agentrun create task --aipod Artificer --prompt-stdin`;同 session 续跑只使用 `agentrun send session/`。UniDesk 客户端按 `config/agentrun.yaml` 直连 AgentRun REST API,不经过 HWLAB runtime、SSH official CLI 或旧 bridge wrapper;`send` 是唯一用户级 session follow-up 写入口,服务端按 durable session/run/command 状态自动决定内部 `steer` 或新 `turn`,旧 CLI `turn/steer` 路径不保留兼容。`--json-file`、`--prompt-file` 和 `--runner-json-file` 只是客户端输入来源,用于已审阅且可复用的受控文件。它不是旧 Code Queue adapter,不双写,也不迁移旧历史。 @@ -71,9 +72,9 @@ AgentRun queue 生命周期不是一个单独的 `queue lifecycle` 命令,而 1. 默认总览用 `get tasks --queue commander --limit 20`,只看 task state、queue/lane、run/cmd/rjob/session ref、age 和 attention。 2. 单任务用 `describe task/`,读取 `latestAttempt.runId`、`commandId`、`runnerJobId`、`sessionId/sessionPath` 和少量 `Next:`。 3. Run 级状态用 `events run/` 和 `result run/ --command `,判断 terminalClassification、failureKind、provider interruption、timeoutBudget 和 recoveryActions。 -4. Command 级状态用 `describe command/ --run ` 和 `result command/ --run `,确认 command state、ack、terminal status 和结果摘要。 +4. Command 级状态用 `describe command/ --run ` 和 `result command/ --run `,确认 command state、ack、terminal status 和结果摘要;确认为单个 active command 卡住时,用 `cancel command/ --run --reason ` 清理该 command,保留同一个 session 后再用 `send session/` 续跑。 5. Runner job 只读状态用 `describe runnerjob/ --run `,确认 env image reuse、jobName、namespace、phase、exitCode、retention 和 `valuesPrinted=false`。不要为了这些字段手动调用 `trans G14:k3s kubectl ...`。 -6. Session trace/output 只在 `describe task` 或 result 里有实际 `sessionId` 时使用 `logs|ack|send|cancel session/`;`sessionRef=null` 时不要猜 session 命令。 +6. Session trace/output 只在 `describe task` 或 result 里有实际 `sessionId` 时使用 `logs|ack|send|cancel session/`;`sessionRef=null` 时不要猜 session 命令。用户级 follow-up 一律使用 `send session/`,不要回到旧 `turn/steer` 或 `sessions ...` 兼容路径。 7. 已创建但尚未运行的 task 使用 `dispatch task/` 派发,不再退回旧 bridge `queue dispatch`。 默认视图必须低噪声且不是 JSON envelope,`-o json|yaml` 才输出稳定机器结构,`--raw` 才保留直连 AgentRun REST envelope;命令返回里的下一步应优先是 `bun scripts/cli.ts agentrun ...` 资源原语,不得把人工 k8s 查询作为日常下一步。 @@ -188,7 +189,7 @@ bun scripts/cli.ts codex interrupt bun scripts/cli.ts codex cancel ``` -仅用于停止旧 Code Queue 残留任务;新 AgentRun session 使用 `bun scripts/cli.ts agentrun cancel session/`。 +仅用于停止旧 Code Queue 残留任务;新 AgentRun session 使用 `bun scripts/cli.ts agentrun cancel session/`,单个卡住 command 使用 `bun scripts/cli.ts agentrun cancel command/ --run `。 --- diff --git a/scripts/src/agentrun.ts b/scripts/src/agentrun.ts index 3e2c7025..63408638 100644 --- a/scripts/src/agentrun.ts +++ b/scripts/src/agentrun.ts @@ -198,7 +198,7 @@ function agentRunHelpText(args: string[]): string { return "Usage: bun scripts/cli.ts agentrun ack task/|session/ [--reader-id cli]"; } if (verb === "cancel") { - return "Usage: bun scripts/cli.ts agentrun cancel task/|session/ --reason [--dry-run]"; + return "Usage: bun scripts/cli.ts agentrun cancel task/|session/|run/|command/ --reason [--run ] [--dry-run]"; } if (verb === "dispatch") { return "Usage: bun scripts/cli.ts agentrun dispatch task/"; @@ -576,16 +576,41 @@ async function resourceAck(config: UniDeskConfig | null, command: string, action async function resourceCancel(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise { const ref = parseResourceRef(action, args, "task"); - const cancelArgs = ref.kind === "task" ? ["cancel", ref.name] : ref.kind === "session" ? ["cancel", ref.name] : null; - if (cancelArgs === null) throw new Error("cancel supports task/ or session/"); - if (options.reason !== null && ref.kind === "task") cancelArgs.push("--reason", options.reason); - if (options.dryRun) cancelArgs.push("--dry-run"); + const cancelArgs = ["cancel", ref.name]; + if (options.reason !== null) cancelArgs.push("--reason", options.reason); + if (ref.kind === "command") cancelArgs.push("--run-id", options.runId ?? requiredContext("command cancel", "--run ")); + if (options.dryRun) { + const result = agentRunResourceCancelDryRunPlan(ref, options, rerunWithoutDryRun(command)); + return renderMutationSummary(command, result, options, `Planned cancel ${ref.kind}/${shortId(ref.name)}`, [rerunWithoutDryRun(command)]); + } const result = ref.kind === "task" ? await runAgentRunRestCommand(config, "queue", cancelArgs) - : await runAgentRunRestCommand(config, "sessions", cancelArgs); + : ref.kind === "session" + ? await runAgentRunRestCommand(config, "sessions", cancelArgs) + : ref.kind === "run" + ? await runAgentRunRestCommand(config, "runs", cancelArgs) + : ref.kind === "command" + ? await runAgentRunRestCommand(config, "commands", cancelArgs) + : null; + if (result === null) throw new Error("cancel supports task/, session/, run/, or command/"); return renderMutationSummary(command, result, options, `${options.dryRun ? "Planned cancel" : "Cancel requested"} ${ref.kind}/${shortId(ref.name)}`, options.dryRun ? [rerunWithoutDryRun(command)] : undefined); } +function agentRunResourceCancelDryRunPlan(ref: AgentRunResourceRef, options: AgentRunResourceOptions, confirmCommand: string): Record { + const body: Record = {}; + if (options.reason !== null) body.reason = options.reason; + if (ref.kind === "task") return agentRunDryRunPlan("task-cancel", `/api/v1/queue/tasks/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand); + if (ref.kind === "session") return agentRunDryRunPlan("session-cancel", `/api/v1/sessions/${encodeURIComponent(ref.name)}/control`, { action: "cancel", ...body }, confirmCommand); + if (ref.kind === "run") return agentRunDryRunPlan("run-cancel", `/api/v1/runs/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand); + if (ref.kind === "command") { + const runId = options.runId ?? requiredContext("command cancel", "--run "); + return agentRunDryRunPlan("command-cancel", `/api/v1/commands/${encodeURIComponent(ref.name)}/cancel`, body, confirmCommand, "POST", { + commandRef: { runId, commandId: ref.name, valuesPrinted: false }, + }); + } + throw new Error("cancel supports task/, session/, run/, or command/"); +} + async function resourceDispatch(config: UniDeskConfig | null, command: string, action: string | undefined, args: string[], options: AgentRunResourceOptions): Promise { const ref = parseResourceRef(action, args, "task"); if (ref.kind !== "task") throw new Error("dispatch supports task/");