fix: 删除 session turn steer 兼容入口

This commit is contained in:
AgentRun Codex
2026-06-11 22:39:40 +08:00
parent 64b824911f
commit 90d4bc8cd2
3 changed files with 11 additions and 22 deletions
+1 -1
View File
@@ -39,7 +39,7 @@ AgentRun 是面向 UniDesk 与 HWLAB 的共享 Agent 执行基础设施。本仓
## Critical CLI Spec Rule
- P0: AgentRun CLI 和服务开发必须遵循 UniDesk `cli-spec` 原则:默认 JSON 输出、禁止空输出伪成功、禁止长阻塞 CLI、日志可见、配置显式校验、稳定跨服务边界优先使用 RESTful API。G14 非交互 route 优先使用 `./scripts/agentrun ...` launcher;它只负责定位 Bun 并转入 `scripts/agentrun-cli.ts`
- P0: 用户级 Session 续跑只使用 `sessions send <sessionId>`CLI 只做 render-only clientmanager REST `/api/v1/sessions/:sessionId/send` 按 durable session 状态自动决定内部 `steer` 或新 `turn``sessions turn` / `sessions steer` 能作为隐藏兼容 alias低层诊断入口,不能出现在默认调度、帮助文档或恢复建议
- P0: 用户级 Session 续跑只使用 `sessions send <sessionId>`CLI 只做 render-only clientmanager REST `/api/v1/sessions/:sessionId/send` 按 durable session 状态自动决定内部 `steer` 或新 `turn``sessions turn` / `sessions steer` 已删除且不兼容,不能作为隐藏 alias低层诊断入口默认调度、帮助文档或恢复建议重新出现
- P0: 一旦新增 CLI,入口文件必须保持轻量,具体实现拆入 `scripts/src/`;长任务必须快速返回,并提供 status/log/event 轮询。
## Critical v0.1 Implementation Stack Rule
+2 -2
View File
@@ -117,7 +117,7 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
- 需要提交较长 Queue task、dispatch body、run base 或 command payload 时,CLI 必须把 `--json-stdin` 作为首选入口,避免为了 heredoc/stdin 内容先写临时 dump 文件再传 `--json-file``--json-file` 只用于可复用、已受控的输入文件。`queue submit/dispatch --dry-run``next.confirm` 不得默认推荐 `--json-file`,有 JSON body 时应提示 `--json-stdin`;只有用户显式维护可复用文件时才使用 file fallback。`sessions send` 的 runner job override 也必须支持 `--runner-json-stdin`。所有 stdin JSON 仍必须解析为 object,并在 dry-run 中只展示有界 body 摘要、bytes 和 keys。
- `sessions ps` 默认只显示 running 和 unread session`--state all` 才显示历史 read session,避免旧 session 噪声淹没当前进度。
- `sessions send` 是异步 subagent 的唯一用户级受控 CLI 入口:短返回 manager 决策、内部 command type、run/command/runnerJob 摘要和后续 poll/read/cancel 命令,不等待模型完成。CLI 只做 render-only clientmanager 的 `/api/v1/sessions/:sessionId/send` 读取 durable session 状态后自动决定内部创建 `type=steer` 还是新 `type=turn` + runner job。`--profile M3``minimax-m3` 的 CLI aliasprofile 仍写入 canonical `backendProfile`,不得 fallback。
- `sessions send --dry-run` 必须全路径 non-mutating,只返回将提交给 manager 的有界计划和 manager 根据当前 session 状态可判断的 `decision`,不得创建 session、PVC、run、command 或 runner job。`sessions turn` / `sessions steer` 只能作为隐藏兼容 alias低层诊断入口;兼容 alias 也必须调用同一个 send REST 路径,不得强制内部 command type,不得出现在默认 help、恢复建议或调度者工作流
- `sessions send --dry-run` 必须全路径 non-mutating,只返回将提交给 manager 的有界计划和 manager 根据当前 session 状态可判断的 `decision`,不得创建 session、PVC、run、command 或 runner job。`sessions turn` / `sessions steer` 已删除且不兼容;不得作为隐藏 alias低层诊断入口默认 help、恢复建议或调度者工作流重新出现
- `sessions cancel` 通过 Session control 取消 active command 或 run`sessions read` 写入 reader cursor,使 terminal session 从默认 ps 中消失。
- `sessions output``sessions trace` 是输出和 trace 的唯一 CLI 查询入口;不得新增 `queue output``queue trace` 兼容命令。
- `sessions output``sessions trace` 默认必须按渐进披露输出低噪声 JSON:只展示 `assistant_message``tool_call`/`error` 摘要,`command_output``backend_status`、raw event、runnerTrace 和大 stdout/stderr 只进入 `suppressedEvents` 计数与 bytes,不得默认展开正文。需要查看工具输出、backend_status 或原始 event 时,必须通过默认摘要中的 `detailCommands`,或显式使用 `--seq <n>``--event-id <id>``--item-id <id>``--include-output``--full`/`--raw` 做定点展开;默认摘要生成的 `detailCommands` 必须带上能定位该 event 的最小 `--after-seq`/`--limit` hint,避免按 id 拉详情时重新扫描长 trace。这样保证默认不爆上下文,同时按 id/seq 可完整追溯。
@@ -175,7 +175,7 @@ CLI 官方 TypeScript 入口固定为 `scripts/agentrun-cli.ts`。在 G14 非交
| Queue CLI | 已实现/Q1 | 已提供 `queue submit/list/show/stats/commander/read/cancel`,通过 manager REST 访问 Queue task 和 stats,不直连 Postgres。 |
| Queue dispatch/refresh CLI | 已实现/Q2 | `queue dispatch` 受控创建 Core run/command/runner job`queue refresh` 从 Core run/command 终态回写 Queue task/latestAttempt。 |
| 本地 server 生命周期 CLI | 已实现/Q2 hardening | `server start` 默认后台短返回,`server status/stop` 提供 pid、port、logPath 和 readiness 可见性;`--foreground` 保留给容器/显式调试。 |
| Session CLI | 已实现/Q3 | 已提供 `sessions ps/show/send/cancel/trace/output/read`;默认 ps 只显示 running/unreadterminal 后自动 unreadread cursor 由 CLI 标记。`turn/steer` 仅保留隐藏兼容 alias 和低层 command type。 |
| Session CLI | 已实现/Q3 | 已提供 `sessions ps/show/send/cancel/trace/output/read`;默认 ps 只显示 running/unreadterminal 后自动 unreadread cursor 由 CLI 标记。用户级 CLI 只保留 `send``turn/steer` 只作为 manager 内部 command type,不再作为 CLI 入口或兼容 alias。 |
| CLI 测试规格 | 已定义/已验证主闭环 | 综合联调见 [spec-v01-validation.md](spec-v01-validation.md);每次发布仍按手动交互验收复跑。 |
| `deepseek` profile CLI | 已实现/已通过主闭环 | `secrets codex render --profile deepseek``backends list``runner start --backend``runner job` 和 JSON 错误可见性已实现;真实 CLI/RESTful 联调已通过 `codex -> deepseek -> codex` 切换主闭环。 |
| Provider profile 管理 CLI | 已实现 | `provider-profiles list/show/remove/set-key/validate` 调用 manager REST API,用于 HWLAB 委托和 operator 验收;输出必须持续保持 Secret/API Key 脱敏。 |
+8 -19
View File
@@ -82,8 +82,6 @@ async function dispatch(args: ParsedArgs): Promise<CliResult> {
if (group === "sessions" && command === "trace" && id) return sessionEvents(args, id, "trace");
if (group === "sessions" && command === "output" && id) return sessionEvents(args, id, "output");
if (group === "sessions" && command === "send") return sessionSend(args, id ?? null);
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);
@@ -677,7 +675,7 @@ function summarizeSessionMutationResult(action: "session-cancel" | "session-read
};
}
function summarizeSessionSendResult(result: JsonValue, sessionId: string, compatibilityAlias: "turn" | "steer" | null, profile: string, aipod?: string): JsonRecord {
function summarizeSessionSendResult(result: JsonValue, sessionId: string, profile: string, aipod?: string): JsonRecord {
const record = jsonRecordValue(result);
const run = jsonRecordValue(record?.run);
const command = jsonRecordValue(record?.command);
@@ -690,7 +688,6 @@ function summarizeSessionSendResult(result: JsonValue, sessionId: string, compat
sessionId,
profile,
...(aipod ? { aipod } : {}),
compatibilityAlias,
dryRun,
mutation: record?.mutation === true,
decision: stringValue(record?.decision),
@@ -1230,17 +1227,9 @@ async function sessionStorageDelete(args: ParsedArgs, sessionId: string): Promis
return (await client(args).delete(`/api/v1/sessions/${encodeURIComponent(sessionId)}/storage`)) as JsonRecord;
}
async function sessionTurn(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
return sessionSend(args, positionalSessionId, { compatibilityAlias: "turn" });
}
async function sessionSteer(args: ParsedArgs, sessionId: string): Promise<JsonRecord> {
return sessionSend(args, sessionId, { compatibilityAlias: "steer" });
}
async function sessionSend(args: ParsedArgs, positionalSessionId: string | null, options: { compatibilityAlias?: "turn" | "steer" } = {}): Promise<JsonRecord> {
async function sessionSend(args: ParsedArgs, positionalSessionId: string | null): Promise<JsonRecord> {
const aipod = optionalFlag(args, "aipod") ?? optionalFlag(args, "aipod-spec");
if (aipod) return sessionSendWithAipod(args, positionalSessionId, aipod, options);
if (aipod) return sessionSendWithAipod(args, positionalSessionId, aipod);
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");
@@ -1270,8 +1259,8 @@ async function sessionSend(args: ParsedArgs, positionalSessionId: string | null,
if (commandIdempotencyKey) sendBody.commandIdempotencyKey = commandIdempotencyKey;
if (args.flags.get("dry-run") !== true) await ensureSessionForSend(args, sessionId, body.tenantId as string, body.projectId as string, profile);
const result = await client(args).post(`/api/v1/sessions/${encodeURIComponent(sessionId)}/send`, sendBody);
if (wantsExpandedOutput(args)) return { action: "session-send", compatibilityAlias: options.compatibilityAlias ?? null, result: result as JsonValue, valuesPrinted: false };
return summarizeSessionSendResult(result, sessionId, options.compatibilityAlias ?? null, profile);
if (wantsExpandedOutput(args)) return { action: "session-send", result: result as JsonValue, valuesPrinted: false };
return summarizeSessionSendResult(result, sessionId, profile);
}
async function sessionCancel(args: ParsedArgs, sessionId: string): Promise<JsonRecord> {
@@ -1343,7 +1332,7 @@ async function submitQueueTaskWithAipod(args: ParsedArgs, aipod: string): Promis
return client(args).post("/api/v1/queue/tasks", body);
}
async function sessionSendWithAipod(args: ParsedArgs, positionalSessionId: string | null, aipod: string, options: { compatibilityAlias?: "turn" | "steer" } = {}): Promise<JsonRecord> {
async function sessionSendWithAipod(args: ParsedArgs, positionalSessionId: string | null, aipod: string): Promise<JsonRecord> {
const sessionId = positionalSessionId ?? optionalFlag(args, "session-id") ?? newSessionId();
const rendered = await renderAipodForCommand(args, aipod, positionalSessionId ? 3 : 2, { sessionId });
const task = rendered.queueTask;
@@ -1376,8 +1365,8 @@ async function sessionSendWithAipod(args: ParsedArgs, positionalSessionId: strin
if (commandIdempotencyKey) sendBody.commandIdempotencyKey = commandIdempotencyKey;
if (args.flags.get("dry-run") !== true) await ensureSessionForSend(args, sessionId, String(task.tenantId), String(task.projectId), profile);
const result = await client(args).post(`/api/v1/sessions/${encodeURIComponent(sessionId)}/send`, sendBody);
if (wantsExpandedOutput(args)) return { action: "session-send", compatibilityAlias: options.compatibilityAlias ?? null, aipod, result: result as JsonValue, valuesPrinted: false };
return summarizeSessionSendResult(result, sessionId, options.compatibilityAlias ?? null, profile, aipod);
if (wantsExpandedOutput(args)) return { action: "session-send", aipod, result: result as JsonValue, valuesPrinted: false };
return summarizeSessionSendResult(result, sessionId, profile, aipod);
}
async function submitQueueTask(args: ParsedArgs): Promise<JsonValue> {