fix: reduce code queue commander CLI noise
This commit is contained in:
@@ -52,8 +52,8 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
|
||||
- `codex resume <taskId> [prompt|--prompt-file path|--prompt-stdin] [--resume-id id] [--dry-run] [--full|--raw]` 对已终态或 awaiting-closeout 的原 Code Queue task 创建后续 turn,优先用于 PR 小修、冲突、rebase、补测和 reviewer feedback,保留原 task、attempt、branch/PR 上下文和 `codexThreadId`/OpenCode session。CLI 会为同一 task/prompt 生成稳定 `resumeId`,也允许显式传入;同一 `resumeId` 加同 prompt 返回 `duplicate_suppressed` 且不重复注入,同一 `resumeId` 加不同 prompt 返回 409 conflict。真实成功只返回 taskId、resumeId/turnId、`deliveryState`、是否复用原 `codexThreadId`、有界 trace confirmation 和 `codex task/detail/trace/output` 后续命令,不回显 prompt 或完整 task state。running/judging task 必须 fail closed 并给出 `disposition=use-steer-for-active-task` 与 `codex steer` 命令,不把 resume 伪装成新 task;不存在 task 返回结构化 not accepted。若 delivery timeout 或 trace 未确认,输出 `deliveryUnconfirmed` 和确认命令,调用方先查 `codex task <taskId> --trace` 再用同一 `resumeId` 重试。
|
||||
- `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]` 通过稳定 `code-queue` proxy 请求 D601 scheduler `/api/runtime-preflight`,用于 PR 型派单 admission。默认输出是紧凑 commander 视图,显式分出 `schedulerPreflight` 与 `activeRunnerPrCapability`,并附带 `commands` 和 `disclosure`,方便先看 scheduler auth 缺口、再看当前 runner/dev container 的 `gh auth status` 与 `gh pr create --dry-run` 能力;`--full` 或 `--raw` 才展开完整 `preflight`、工具、agent port、Git worktree、GitHub egress、repo/issue/PR 只读探测和观测原文。只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在和来源 key,不打印值。当 auth-broker 配置存在时,`tokenCoverage.source="auth-broker"`、`credentialSource="broker-issued-token"` 且 runner env token 不是成功前提;当仅 env token 存在时,`credentialSource="env-token"` 且 `authBroker.nextAction="use-env-token-until-auth-broker-live"`;两者都缺失时顶层 `ok=false`、`runnerDisposition=infra-blocked`、`degradedReason=auth-broker-needed`,`tokenCoverage.missing` 同时列出 `GH_TOKEN` 与 `GITHUB_TOKEN`,并输出 `authBroker.source="broker/auth-broker-needed"`、`capability.source="missing-token"`。该 `auth-missing` 的 scope 是 `scheduler-runner-env`,不能简化成“当前 active runner/dev container 不能创建 PR”;默认视图必须带 `scopeBoundary` 和 `activeRunnerPrCapability`。GitHub DNS/API 连接失败应归类为 `failureKind=github-transient`、`degradedReason=github-dns-api-transient`,并带 `retryable=true`、`commanderAction=retry-backoff-or-keep-running-if-heartbeat-fresh` 和有界 `githubTransient.failedProbes`;调用方应重试/退避,且在任务 heartbeat/trace 新鲜时继续监督,不把它当成 auth 缺失或 PR 语义失败。`prCapability` 是 runner-facing 合同摘要,必须包含目标分支、token/auth 来源、`systemGhBinaryRequiredForWrites=false`、UniDesk REST `bun scripts/cli.ts gh` 可用性、push dry-run/PR create dry-run 的 `writesRemote=false`、expected PR handoff、真实 PR 创建需要 commander 授权和 `gh pr merge` 的 `unsupported-command` 边界;系统 `gh` binary 缺失只进入 `tools.systemGhBinary`,不得误判为 UniDesk REST `gh` CLI 不可用。`--remote` 在 runner-like 环境里不再依赖本地 `unidesk-backend-core`、`unidesk-database`、`baidu-netdisk-backend` 容器存在;这些缺失只作为本地观测证据。若远程控制面可达,则继续走远程控制面结果;若远程控制面不可达,则结构化返回 `failureKind=control-plane-missing` / `degradedReason=remote-control-plane-unreachable`,而不是把本地 `backend-core-container-missing` 当作最终阻塞。`--pr-create-dry-run` 不 POST GitHub,只证明 runner 内 PR body 生成、`scripts/cli.ts gh pr create --dry-run` 和 branch 参数形态可用;服务端创建权限仍以 token/auth broker、repo/issue/PR read、push dry-run 和最终授权后的真实 PR 创建结果为准。
|
||||
- `codex task <taskId>` 通过 Code Queue 私有代理按任务 ID 查询结构化审阅摘要;默认只返回任务身份、执行 Provider、工作目录、attempt 计数、原始 prompt、最终 response、最后错误和渐进披露命令,适合指挥官审阅完成未读任务且避免上下文爆炸。`--detail` 仍是有界详细摘要:默认只返回少量 attempt/tool 行、短 prompt/response/stderr/feedback 预览和 omitted/truncated 元数据;需要完整 prompt/response 文本或更多 tool/attempt 细节时再显式加 `--full`、`--tool-limit N`、`--trace` 或 `codex output`。该摘要读取默认由主 server `code-queue-mgr` 从 PostgreSQL 返回,不依赖 D601 `code-queue-read` Service 可用。
|
||||
- `codex tasks [--view commander|supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。host commander 轮询应优先使用 `--view commander`:它是低噪声 polling 入口,只返回有界 action map,包含 `activeRunners.count` 及来源/处置、少量 active item、queued/retry_wait 精确计数、terminal-unread 总数和省略行数、关键风险计数、HWLAB#7/#99/#116/#164/#317 与 UniDesk#20/#118 命中、确定性分类计数和集中式 `codex task/trace/output/read` drill-down 命令。默认 commander 不展开历史 terminal unread item details,也不嵌入 prompt preview、final response preview、trace、output 或 raw overview;terminal unread 详情必须通过 `codex unread`、`codex tasks --unread --view supervisor`、`--view full`、`--full` 或 per-task `codex read <taskId>` 获取。默认 `supervisor` 保持旧低噪声分区视图,只返回 `activeRunning`、`running`、`completedUnread`、`recentCompleted`、`queued`、`activity`、`commanderConcurrency` 和 `executionDiagnostics` 的紧凑行;`activeRunning.count` 是 running+judging 的状态计数,`exact=true` 时来自 queue summary counts,`running.returned` 和 `activeRunning.rowPage.returned` 只是本次返回的紧凑行数。`commanderConcurrency.activeRunnerCount` 是并发策略应使用的 active/running 计数,等于 `activity.effectiveActiveTaskCount`;15 并发策略按 `15 - activeRunnerCount` 计算剩余窗口。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 有 fresh heartbeat 证据,应继续监督并计入 active;`interventionRequired=true` 才提示介入。`supervisor` prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 commander/supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;CLI 安全上限是 100,输出会在 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped` 和 disclosure 中说明显式请求是否被 capped;底层 overview 拉取预算独立显示在 `source.requestedLimit` / `source.effectiveLimit`,所以 `--limit 260` 应显示 requested=260、effective=100、source requested/effective=200,而不是只露出一个含糊的 `limit`。`--unread` 是 `--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,并接受常见 alias:`completed|complete|success|successful -> succeeded`,`cancelled -> canceled`,`retry-wait|retrying -> retry_wait`,`pending -> queued`。未知参数或未知状态必须结构化失败并给出支持值和 alias 建议,预期参数错误默认不输出 stack trace;显式 `UNIDESK_CLI_DEBUG=1` 可保留完整诊断。需要更详细当前页任务行时显式使用 `--view full` 或 `--full`,仍受 `--limit` 和 `--before-id` 分页约束。
|
||||
- `codex unread [summary|mark-read] [--queue id] [--repo owner/name] [--issue N] [--status succeeded|failed|canceled[,..]] [--limit N] [--before-id id] [--confirm]` 是完成未读积压的默认低噪声 triage 入口。默认只读返回 repo/issue/status/queue 计数和最新任务 id 小页,不拉取 per-task summary,不输出 raw prompt、final response、trace 或 output;每行只给 `codex task/detail/trace/output/read` drill-down 命令。批量已读必须使用 `codex unread mark-read ... --confirm`,缺少 `--confirm` 时结构化失败且不 POST `/read`;单任务审阅仍优先 `codex read <taskId>`。
|
||||
- `codex tasks [--view commander|supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。host commander 轮询应优先使用 `--view commander`:它是低噪声 polling 入口,只返回有界 action map,包含 `activeRunners.count` 及来源/处置、少量 active item、queued/retry_wait 精确计数、terminal-unread 总数和省略行数、关键风险计数、HWLAB#7/#99/#116/#164/#317 与 UniDesk#20/#118 命中、确定性分类计数和集中式 `codex task/trace/output/read` drill-down 命令。默认 commander 不展开历史 terminal unread item details,也不嵌入 prompt preview、final response preview、trace、output 或 raw overview;terminal unread 详情必须通过 `codex unread`、`codex tasks --unread --view supervisor`、`--view full`、`--full` 或 per-task `codex read <taskId>` 获取。默认 `supervisor` 保持旧低噪声分区视图,只返回 `activeRunning`、`running`、`completedUnread`、`recentCompleted`、`queued`、`activity`、`commanderConcurrency` 和 `executionDiagnostics` 的紧凑行;`activeRunning.count` 是 running+judging 的状态计数,`exact=true` 时来自 queue summary counts,`running.returned` 和 `activeRunning.rowPage.returned` 只是本次返回的紧凑行数。`commanderConcurrency.activeRunnerCount` 是并发策略应使用的 active/running 计数,等于 `activity.effectiveActiveTaskCount`;15 并发策略按 `15 - activeRunnerCount` 计算剩余窗口。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 有 fresh heartbeat 证据,应继续监督并计入 active;`interventionRequired=true` 才提示介入。确定性分类只在有强基础设施故障信号时输出 `infrastructure-blocker`;普通 runner/CLI/治理上下文归入 `infra-governance`、`workflow` 或 `unknown`,避免把历史任务误报为基础设施阻塞。`supervisor` prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 commander/supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;CLI 安全上限是 100,输出会在 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped` 和 disclosure 中说明显式请求是否被 capped;底层 overview 拉取预算独立显示在 `source.requestedLimit` / `source.effectiveLimit`,所以 `--limit 260` 应显示 requested=260、effective=100、source requested/effective=200,而不是只露出一个含糊的 `limit`。`--unread` 是 `--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,并接受常见 alias:`completed|complete|success|successful -> succeeded`,`cancelled -> canceled`,`retry-wait|retrying -> retry_wait`,`pending -> queued`。未知参数或未知状态必须结构化失败并给出支持值和 alias 建议,预期参数错误默认不输出 stack trace;显式 `UNIDESK_CLI_DEBUG=1` 可保留完整诊断。需要更详细当前页任务行时显式使用 `--view full` 或 `--full`,仍受 `--limit` 和 `--before-id` 分页约束。
|
||||
- `codex unread [summary|list|mark-read] [--queue id] [--repo owner/name] [--issue N] [--status succeeded|failed|canceled[,..]] [--limit N] [--before-id id] [--view summary|full] [--full] [--confirm]` 是完成未读积压的默认低噪声 triage 入口。默认只读返回总数、repo/issue/status/queue 计数、最新任务 id 小页和每行一条紧凑 `nextStep`,不拉取 per-task summary,不输出 raw prompt、final response、trace、output,也不为每个任务重复 `show/detail/trace/output/read` command block;完整 per-task 命令只在显式 `--full`、`--view full` 或 `list` 中展开。默认输出仍保留一次性的 `codex task <taskId>`、`codex read <taskId>`、分页和 full 展开模板命令。批量已读必须使用 `codex unread mark-read ... --confirm`,缺少 `--confirm` 时结构化失败且不 POST `/read`;单任务审阅仍优先 `codex read <taskId>`。
|
||||
- `codex task <taskId> --trace --tail|--from-start|--after-seq N|--before-seq N --limit N` 按页拉取 Code Queue 的逻辑 trace;响应会返回 `nextAfterSeq`、`previousBeforeSeq`、`hasMore`、`hasBefore` 和下一页/上一页命令,默认 `--trace` 取最新一页,且仍以分页 trace 为主;需要完整 prompt/最终 response 时加 `--full`,需要详细 task 摘要时加 `--detail`。
|
||||
- `codex output <taskId> --tail|--from-start|--after-seq N|--before-seq N --limit N [--full-text]` 按原始 output seq 分页读取底层记录;当 trace 行提示 `commandOmittedLines`、`bodyOmittedLines` 或 `rawSeqs` 时,用该命令按 seq 补取信息。默认是低噪声 raw-output 摘要:即使传入很大的 `--limit`,非 `--full-text` 也会限制返回行数和单条文本预览,并在 `disclosure.limitCapped`、`requestedLimit`、`effectiveLimit` 和 `commands.fullText` 中说明如何继续展开;显式 `--full-text` 才返回该页全文。
|
||||
- `codex read <taskId>` 在人工审阅后标记单个终态任务已读,并在同一次响应中返回稳定任务身份、执行元数据、终态 attempt 摘要、最后错误或 judge 信息和最终 response,避免标记已读后还要额外 drill-down 才能确认结果。该命令不返回完整 prompt、tool logs 或 feedback prompt,只返回字符数、计数和 `codex task/detail/trace/output` 渐进披露命令;列表、overview 和 supervisor 视图只返回这个命令字段,不得自动执行,也不得批量清空未读状态。
|
||||
|
||||
@@ -278,7 +278,7 @@ replacement runner 只用于方向明显错误、质量不可接受、原 task
|
||||
- `bun scripts/cli.ts codex tasks --view commander --limit N`:host commander 轮询的推荐入口。输出是有界 action map,必须直接显示 `activeRunners.count`、计数来源、split-brain/heartbeat 处置、queued/retry_wait 精确计数、terminal-unread 总数和已省略行数、active 风险数、stale/heartbeat/trace gap、`finalResponse` 已出现但仍非终态的 awaiting terminal/judge、blocker-like final response、HWLAB#7/#99/#116/#164/#317 与 UniDesk#20/#118 命中、任务分类和下一步 drill-down 命令。默认不得输出完整 prompt、完整 final response、raw output、完整 trace 或 raw overview;需要详情只能按 task id 使用 `codex task`、`codex task --trace`、`codex output`、`codex read` 或 `rawOverview` 命令渐进展开。
|
||||
- `bun scripts/cli.ts codex tasks --view supervisor --limit N`:查看默认低噪声监督视图,包括 `activeRunning`、running、完成未读、少量最近完成、queued/runnable、activity、commanderConcurrency、execution diagnostics、任务分类和下一步 drill-down 命令。默认行只保留 task id、队列、短 prompt/body 预览和原始字符数;`--limit` 是扫描/分页预算,不是返回几十条肥行的开关,CLI effective limit 安全上限为 100,输出必须用 `filters.requestedLimit`、`filters.effectiveLimit`、`filters.limitCapped`、`source.requestedLimit` 和 `source.effectiveLimit` 区分用户请求、CLI cap 和 overview 源拉取预算;例如 `--limit 260` 应明确显示 requested=260、effective=100、source=200,`running.returned` 只是低噪声返回行数。`show/detail/trace/output/full/read` 放在 section template 中,避免每条任务重复刷屏,需要更多内容再按 taskId 展开。刚执行 `codex submit` 后也可以先读 submit 返回的 `submitted.taskStates[]`、`queue.countContext`、`queue.activity.effectiveActiveTaskCount` 和 `queue.stateDisclosure`;若某个 id preview 有 `idsUnavailable=true`,不要把它当成空队列,按 `queue.listPreviewPolicy.rawCommand` 或本 supervisor 命令继续查。
|
||||
- `bun scripts/cli.ts codex queues`:默认是 commander-first 队列态势摘要,`--commander` 是显式同义开关。输出前部固定使用 `.data.queues.commander`,先给出 `activeRunnerCount`、`source`、`target=15`、`slotDeficit`、`queuedCount`、`runningTasks`、`heartbeat.fresh`、`heartbeat.risk`、`heartbeat.staleRecoveryCandidates`、active/runnable queue 小页和 drill-down 命令;历史 queue item 列表保留在 `.data.queues.items[]`,但只是分页的次要行。需要完整队列行视图时加 `--full`,但 `--full` 仍默认分页,继续用 `--limit N`、`--page N` 或 `--offset N` 渐进展开。summary 和 full 都使用稳定 JSON path `.data.queues.items[]` 读取队列行,并从 `.data.queues.commander`、`.data.queues.commanderConcurrency`、`.data.queues.activity`、`.data.queues.counts` 与 `.data.queues.executionDiagnostics` 读取全局活跃计数和执行诊断;完整 upstream 只通过输出中的 raw command 显式获取。若 `/api/queues` 没有返回 task row,`runningTasks.items[].name` 会是 `null` 且 `nameSource=not-returned-by-api-queues`,此时按返回的 `codex task <taskId>` 或 supervisor 命令展开,不要假设任务没有名称。
|
||||
- `bun scripts/cli.ts codex unread --limit N`:查看完成未读审阅积压的默认 triage,按 repo、issue、status 和 queue 汇总,并给出有界最新任务和 drill-down/read 命令;默认不输出 raw prompt、final response、trace 或 output。
|
||||
- `bun scripts/cli.ts codex unread --limit N`:查看完成未读审阅积压的默认 triage,按 repo、issue、status 和 queue 汇总,并给出有界最新任务紧凑行;默认行只包含 task id、状态、queue、issues、updatedAt/finishedAt 和一条 `nextStep`,不重复每任务 `show/detail/trace/output/read` 命令,也不输出 raw prompt、final response、trace 或 output。完整 per-task 命令必须显式使用 `codex unread --full`、`codex unread --view full`、`codex unread list` 或单任务 `codex task <taskId>`/`codex read <taskId>` 展开;默认输出必须保留一次性的模板命令和分页命令。
|
||||
- `bun scripts/cli.ts codex unread mark-read --repo owner/name --issue N --limit N --confirm`:批量已读入口,必须显式 `mark-read` 和 `--confirm`,否则结构化失败且不 POST `/read`。
|
||||
- `bun scripts/cli.ts codex tasks --unread --limit N`:兼容查看完成未读审阅积压;`--unread` 与 `--unread-only` 等价,不能被静默忽略。
|
||||
- `bun scripts/cli.ts codex tasks --status succeeded --unread --limit N`:按具体终态过滤监督结果;不支持的 status filter 必须显式失败,不能扩大为未过滤结果。
|
||||
@@ -289,7 +289,7 @@ replacement runner 只用于方向明显错误、质量不可接受、原 task
|
||||
|
||||
默认 commander/supervisor 视图必须保持低噪声。commander 视图用于回答“现在需要处理什么”,supervisor 视图用于看分区小页和红线细节。commander 的 `activeRunners.count` 是指挥官 active runner 计数,supervisor 的 `activeRunning.count` 是 running+judging 状态计数;两者都必须标明 exact/source,不能把返回行数当成并发总数。`activeRunning.count` 来源是 queue summary 的 status counts 时 `activeRunning.exact=true`,用于 redline 判断;`activeRunning.rowPage.returned` / `running.returned` 只表示本次返回的紧凑任务行。`activeRunning.redline` 必须写明 `countField`、routine target、burst redline、hard redline、`state` 和 `decisionReady`;只有 `decisionReady=true` 时,才能直接用该 count 做红线/补派判断。commander 的 `attention.items` 只返回最需要处理的有界任务,`attention.total/returned/omitted` 必须保留省略计数;`sections.recentCompleted` 不得重复 `sections.terminalUnread` 的未读终态。`running`、`completedUnread` 和 `queued` 即使传入较大的 `--limit`,默认也只返回一个很小的有界页,并通过 section `commands.next` 继续分页;`--limit` 保留为扫描/分页预算和 full view 返回预算,不得让一次 commander/supervisor 调用输出几十条肥行。每个任务行只应带 task id 和必要摘要,`show`、`detail`、`trace`、`output`、`full`、`read` 使用 section template 或 row commands 表达,让下一步渐进披露动作明确且不重复;默认不得嵌入完整 queue 列表、完整 final response、raw output 页或完整 trace 行。`recentCompleted` 必须默认限量,且不得重复 `completedUnread` 里的未读终态,避免完成历史把当前 running、阻塞和未读审阅挤出视野;需要完整当前页时显式使用 `--view full`。`executionDiagnostics` 只能展示有界 task-id/reason 预览、总数、截断标记和 omitted counts;需要全量诊断时使用输出中的 raw command。`commands.read` 只是在人工审阅后的建议命令,listing 命令绝不能自动执行。
|
||||
|
||||
commander 视图的任务分类必须是确定性字段,至少区分 `business-user-facing`、`deployment-artifact`、`ci-e2e-evidence`、`diagnostics-gate-report`、`docs-governance`、`infrastructure-blocker` 和 `unknown`。分类只用于监督优先级和噪声折叠,不替代任务验收;当 final response 带 blocker-like 语言、failed/terminal-unread、heartbeat/stale risk、trace gap 或 awaiting terminal/judge 时,分类再低噪声也必须进入 attention 或风险计数。
|
||||
commander 视图的任务分类必须是确定性字段,至少区分 `user-facing`、`cd-artifact`、`infra-governance`、`noise-report`、`workflow`、`infrastructure-blocker` 和 `unknown`。`infrastructure-blocker` 只能由强基础设施故障信号触发,例如 storage/PostgreSQL degraded、provider/k3s/proxy/auth 等基础设施上下文同时出现 blocked/failed/unreachable/timeout 等阻塞信号;普通 prompt 中的 runner、CLI、commander、supervisor、治理或历史任务上下文不得单独升级为 blocker,没有把握时使用 `workflow` 或 `unknown`。分类只用于监督优先级和噪声折叠,不替代任务验收;当 final response 带 blocker-like 语言、failed/terminal-unread、heartbeat/stale risk、trace gap 或 awaiting terminal/judge 时,分类再低噪声也必须进入 attention 或风险计数。
|
||||
|
||||
`codex tasks` 中的 `status` 永远是 scheduler/control-plane 原始状态,不因为看到 worker final response 而改写。若某个非终态任务的最后 assistant 文本来自 `finalResponse`,CLI 会额外显示 `statusLabel`、`awaitingTerminalJudge=true`、`closeoutState=awaiting-terminal-or-judge` 或 `awaiting-judge`,并附带 closeout hint。指挥官应把这类行理解为“worker 已经产出最终回复文本,但 Code Queue 还在等待 agent terminal event、scheduler 写回或 judge 结果”;它仍占用 active/running 监督窗口,不能按完成任务 `read` 或验收,直到 `status` 进入 `succeeded`、`failed` 或 `canceled` 并可审阅 judge/terminal 记录。
|
||||
|
||||
|
||||
@@ -142,6 +142,97 @@ function noisyCommanderFixture(path: string, requests: RequestRecord[] = []): Js
|
||||
};
|
||||
}
|
||||
|
||||
function readyCommanderFixture(path: string): JsonRecord {
|
||||
if (path.includes("/summary")) {
|
||||
const taskId = decodeURIComponent(path.split("/api/tasks/")[1]?.split("/")[0] ?? "unknown");
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
summary: {
|
||||
id: taskId,
|
||||
queueId: "default",
|
||||
status: "succeeded",
|
||||
currentAttempt: 1,
|
||||
maxAttempts: 1,
|
||||
prompt: "D601 Code Queue GPT-5.5 runner completed normal workflow with ready platform status.",
|
||||
lastAssistantMessage: {
|
||||
at: "2026-05-22T00:59:00.000Z",
|
||||
seq: 12,
|
||||
source: "finalResponse",
|
||||
text: "Completed routine workflow with ready platform status and no follow-up incident.",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
assertCondition(path.startsWith("/api/microservices/code-queue/proxy/api/tasks/overview"), "unexpected ready fixture path", { path });
|
||||
const tasks = Array.from({ length: 12 }, (_, index) => task(
|
||||
`task-ready-${index + 1}`,
|
||||
"succeeded",
|
||||
`2026-05-22T00:${String(40 - index).padStart(2, "0")}:00.000Z`,
|
||||
index === 2
|
||||
? "D601 Code Queue GPT-5.5 runner commander audit: infrastructure.status=ready riskCounts.infrastructureBlocker=0; do not classify all history as infrastructure-blocker"
|
||||
: index % 3 === 0
|
||||
? "D601 Code Queue GPT-5.5 runner workflow fix for UniDesk#20 commander CLI behavior"
|
||||
: index % 3 === 1
|
||||
? "D601 Code Queue GPT-5.5 runner user-facing HWLAB workbench implementation"
|
||||
: "D601 Code Queue GPT-5.5 runner routine unknown historical task",
|
||||
"2026-05-22T01:00:00.000Z",
|
||||
"Routine final response.",
|
||||
));
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
counts: {
|
||||
running: 0,
|
||||
judging: 0,
|
||||
queued: 0,
|
||||
retry_wait: 0,
|
||||
succeeded: tasks.length,
|
||||
failed: 0,
|
||||
canceled: 0,
|
||||
},
|
||||
unreadTerminal: 0,
|
||||
maxActiveQueues: 15,
|
||||
storage: {
|
||||
postgresReady: true,
|
||||
health: {
|
||||
status: "ready",
|
||||
degraded: false,
|
||||
signals: [],
|
||||
},
|
||||
},
|
||||
executionDiagnostics: {
|
||||
now: "2026-05-22T01:00:00.000Z",
|
||||
state: "ready",
|
||||
effectiveLiveness: "idle",
|
||||
recommendedAction: "continue-supervision",
|
||||
databaseActiveTaskCount: 0,
|
||||
schedulerActiveRunSlotCount: 0,
|
||||
activeHeartbeatCount: 0,
|
||||
heartbeatRiskTaskIds: [],
|
||||
staleRecoveryCandidateTaskIds: [],
|
||||
traceGapTaskIds: [],
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
limit: 200,
|
||||
returned: tasks.length,
|
||||
total: tasks.length,
|
||||
hasMore: false,
|
||||
nextBeforeId: null,
|
||||
includeActive: true,
|
||||
},
|
||||
tasks,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
const commanderRequests: RequestRecord[] = [];
|
||||
const commanderLimit8Requests: RequestRecord[] = [];
|
||||
@@ -153,6 +244,7 @@ export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "260"], fetchNoisy);
|
||||
const full = codexTasksQueryForTest(["--view", "full", "--limit", "260"], fetchNoisy);
|
||||
const commanderLimit8 = codexTasksQueryForTest(["--view", "commander", "--limit", "8"], fetchCommanderLimit8);
|
||||
const readyCommander = codexTasksQueryForTest(["--view", "commander", "--limit", "120"], readyCommanderFixture);
|
||||
const fullLimit8 = codexTasksQueryForTest(["--view", "full", "--limit", "8"], fetchNoisy);
|
||||
const unreadLimit8 = codexTasksQueryForTest(["--unread", "--limit", "8"], fetchNoisy);
|
||||
const commanderBody = JSON.stringify(commander);
|
||||
@@ -164,6 +256,7 @@ export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
const commanderView = asRecord(asRecord(commander).commander);
|
||||
const commanderTerminalAliasView = asRecord(asRecord(commanderTerminalAliases).commander);
|
||||
const commanderLimit8View = asRecord(asRecord(commanderLimit8).commander);
|
||||
const readyCommanderView = asRecord(asRecord(readyCommander).commander);
|
||||
const supervisorView = asRecord(asRecord(supervisor).supervisor);
|
||||
const filters = asRecord(commanderView.filters);
|
||||
const activeRunners = asRecord(commanderView.activeRunners);
|
||||
@@ -174,6 +267,10 @@ export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
const highPriorityIssues = asRecord(commanderView.highPriorityIssues);
|
||||
const classification = asRecord(commanderView.classification);
|
||||
const byCategory = asRecord(classification.byCategory);
|
||||
const readyRiskCounts = asRecord(readyCommanderView.riskCounts);
|
||||
const readyClassification = asRecord(readyCommanderView.classification);
|
||||
const readyByCategory = asRecord(readyClassification.byCategory);
|
||||
const readyInfrastructure = asRecord(readyCommanderView.infrastructure);
|
||||
const commands = asRecord(commanderView.commands);
|
||||
const attention = asRecord(commanderView.attention);
|
||||
const attentionItems = asArray(attention.items).map(asRecord);
|
||||
@@ -204,13 +301,16 @@ export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
assertCondition(activeItems.some((item) => item.id === "task-running-risk") && activeItems.some((item) => item.id === "task-running-watch"), "commander activeRunners should include compact active task items", activeRunners);
|
||||
assertCondition(attentionCounts.total === 4 && attentionCounts.returned === 4 && attentionCounts.omitted === 0, "commander attention counts should preserve non-terminal attention totals", attentionCounts);
|
||||
assertCondition(highPriorityIssues.present === true && highPriorityIssues.matchedCount === 7, "commander should surface tracked high-priority issues", highPriorityIssues);
|
||||
assertCondition(Number(byCategory["business-user-facing"] ?? 0) >= 1
|
||||
&& Number(byCategory["deployment-artifact"] ?? 0) >= 1
|
||||
&& Number(byCategory["ci-e2e-evidence"] ?? 0) >= 1
|
||||
&& Number(byCategory["diagnostics-gate-report"] ?? 0) >= 1
|
||||
&& Number(byCategory["docs-governance"] ?? 0) >= 1
|
||||
assertCondition(Number(byCategory["user-facing"] ?? 0) >= 1
|
||||
&& Number(byCategory["cd-artifact"] ?? 0) >= 1
|
||||
&& Number(byCategory["noise-report"] ?? 0) >= 1
|
||||
&& Number(byCategory["infra-governance"] ?? 0) >= 1
|
||||
&& Number(byCategory["infrastructure-blocker"] ?? 0) >= 1, "deterministic classifier should cover requested categories", byCategory);
|
||||
assertCondition(classification.deterministic === true, "classification metadata should be deterministic", classification);
|
||||
assertCondition(Number(readyRiskCounts.infrastructureBlocker ?? 0) === 0, "ready commander page should not report infrastructure blocker risk", readyRiskCounts);
|
||||
assertCondition(readyInfrastructure.infrastructureBlocker === false && readyInfrastructure.status === "ready", "ready commander page should surface ready infrastructure", readyInfrastructure);
|
||||
assertCondition(Number(readyByCategory["infrastructure-blocker"] ?? 0) === 0, "runner/governance boilerplate must not classify historical tasks as infrastructure-blocker", readyByCategory);
|
||||
assertCondition(Number(readyByCategory["workflow"] ?? 0) + Number(readyByCategory["user-facing"] ?? 0) + Number(readyByCategory["infra-governance"] ?? 0) + Number(readyByCategory["unknown"] ?? 0) === 12, "ready fixture tasks should be split without blocker overreporting", readyByCategory);
|
||||
assertCondition(String(commands.refresh ?? "").includes("--view commander"), "commander refresh command should preserve explicit commander view", commands);
|
||||
assertCondition(String(commands.supervisor ?? "").startsWith("bun scripts/cli.ts codex tasks") && !String(commands.supervisor ?? "").includes("--view commander"), "commander should keep supervisor drilldown command", commands);
|
||||
assertCondition(String(commands.full ?? "").includes("--view full"), "commander should keep full drilldown command", commands);
|
||||
@@ -266,6 +366,7 @@ export function runCodeQueueCommanderViewContract(): JsonRecord {
|
||||
"attention rows expose active, queued/retry_wait and blocker signals",
|
||||
"high-priority issue refs are surfaced",
|
||||
"deterministic classifier emits requested categories",
|
||||
"ready infrastructure pages do not classify all historical runner tasks as infrastructure-blocker",
|
||||
"drilldown commands are present without prompt/final-response flood",
|
||||
"commander --limit 8 omits terminal unread details and prompt previews",
|
||||
"codex tasks --status completed,cancelled aliases normalize to succeeded,canceled",
|
||||
|
||||
@@ -95,6 +95,7 @@ export function runCodeQueueUnreadTriageContract(): JsonRecord {
|
||||
const triage = asRecord(asRecord(summary).unreadTriage);
|
||||
const counts = asRecord(triage.counts);
|
||||
const newest = asRecord(triage.newest);
|
||||
const newestItems = asArray(newest.items).map(asRecord);
|
||||
const commands = asRecord(triage.commands);
|
||||
|
||||
assertCondition(asRecord(summary).ok === true, "default unread triage should succeed", asRecord(summary));
|
||||
@@ -105,11 +106,23 @@ export function runCodeQueueUnreadTriageContract(): JsonRecord {
|
||||
assertCondition(itemCount(asRecord(counts.byStatus), "succeeded") === 2, "status counts should include terminal statuses", counts);
|
||||
assertCondition(itemCount(asRecord(counts.byQueue), "review") === 2, "queue counts should include queues", counts);
|
||||
assertCondition(newest.returned === 2 && newest.hasMore === true, "newest items should obey --limit and expose pagination", newest);
|
||||
assertCondition(newestItems.every((item) => item.commands === undefined && typeof item.nextStep === "string"), "default unread rows must stay compact without repeated per-task command blocks", { newestItems });
|
||||
assertCondition(typeof commands.perTaskRead === "string" && String(commands.perTaskRead).includes("codex read <taskId>"), "triage should preserve per-task read drill-down", commands);
|
||||
assertCondition(typeof commands.full === "string" && String(commands.full).includes("codex unread") && String(commands.full).includes("--full"), "triage should expose one full-view expansion command", commands);
|
||||
assertCondition(!summaryBody.includes("RAW_PROMPT_SHOULD_NOT_LEAK"), "triage output must not dump raw prompt text", { summaryBody });
|
||||
assertCondition(!requests.some((request) => request.path.includes("/summary")), "triage must not fetch per-task summaries by default", { requests });
|
||||
assertCondition(!requests.some((request) => request.method === "POST"), "default triage must not mutate", { requests });
|
||||
|
||||
const full = codexUnreadTriageForTest(["--view", "full", "--limit", "2"], fetcher);
|
||||
const fullBody = JSON.stringify(full);
|
||||
const fullTriage = asRecord(asRecord(full).unreadTriage);
|
||||
const fullNewest = asRecord(fullTriage.newest);
|
||||
const fullItems = asArray(fullNewest.items).map(asRecord);
|
||||
const fullItemCommands = fullItems.map((item) => asRecord(item.commands));
|
||||
assertCondition(asRecord(fullTriage.filters).view === "full", "codex unread --view full should disclose full view", fullTriage);
|
||||
assertCondition(fullItems.length === 2 && fullItemCommands.every((item) => typeof item.detail === "string" && typeof item.read === "string"), "explicit full unread view should expand per-task commands", { fullItems });
|
||||
assertCondition(summaryBody.length < fullBody.length, "default unread summary should stay smaller than explicit full view", { summaryChars: summaryBody.length, fullChars: fullBody.length });
|
||||
|
||||
const guardStart = requests.length;
|
||||
const guarded = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2"], fetcher);
|
||||
const guardedTriage = asRecord(asRecord(guarded).unreadTriage);
|
||||
@@ -134,10 +147,14 @@ export function runCodeQueueUnreadTriageContract(): JsonRecord {
|
||||
"default unread triage is read-only",
|
||||
"repo/issue/status/queue counts are present",
|
||||
"newest items are bounded",
|
||||
"default rows avoid repeated per-task command blocks",
|
||||
"--view full expands per-task commands",
|
||||
"raw prompt text is omitted",
|
||||
"batch mark-read requires --confirm",
|
||||
"confirmed batch read respects filters and limit",
|
||||
],
|
||||
summaryChars: summaryBody.length,
|
||||
fullChars: fullBody.length,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+88
-40
@@ -38,6 +38,7 @@ const commanderRecentCompletedLimit = 3;
|
||||
const commanderIssueTaskPreviewLimit = 4;
|
||||
const commanderConcurrencyTarget = 15;
|
||||
const unreadTriageCountLimit = 12;
|
||||
const unreadTriageDefaultItemLimit = 6;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
const mutationQueueIdPreviewLimit = 15;
|
||||
@@ -358,6 +359,7 @@ interface CodexUnreadOptions {
|
||||
statusFilter: string[] | null;
|
||||
repoFilter: string | undefined;
|
||||
issueFilter: string | undefined;
|
||||
view: "summary" | "full";
|
||||
action: CodexUnreadAction;
|
||||
confirm: boolean;
|
||||
dryRun: boolean;
|
||||
@@ -431,11 +433,11 @@ interface CodexTasksSupervisorEntry {
|
||||
}
|
||||
|
||||
type CommanderTaskCategory =
|
||||
| "business-user-facing"
|
||||
| "deployment-artifact"
|
||||
| "ci-e2e-evidence"
|
||||
| "diagnostics-gate-report"
|
||||
| "docs-governance"
|
||||
| "user-facing"
|
||||
| "workflow"
|
||||
| "cd-artifact"
|
||||
| "infra-governance"
|
||||
| "noise-report"
|
||||
| "infrastructure-blocker"
|
||||
| "unknown";
|
||||
|
||||
@@ -444,7 +446,7 @@ type CommanderAttentionSeverity = "critical" | "high" | "medium";
|
||||
interface CommanderTaskClassification {
|
||||
category: CommanderTaskCategory;
|
||||
labels: string[];
|
||||
noiseClass: "delivery" | "evidence" | "governance" | "blocker" | "unknown";
|
||||
noiseClass: "delivery" | "workflow" | "noise" | "governance" | "blocker" | "unknown";
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@@ -2779,8 +2781,8 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
}
|
||||
const optionArgs = hasSubcommand ? args.slice(1) : args;
|
||||
assertKnownOptions(optionArgs, {
|
||||
flags: ["--mark-read", "--confirm", "--dry-run"],
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId"],
|
||||
flags: ["--mark-read", "--confirm", "--dry-run", "--full"],
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId", "--view"],
|
||||
}, "codex unread");
|
||||
const statusRaw = optionValue(optionArgs, ["--status"]);
|
||||
const statusFilter = statusRaw === undefined
|
||||
@@ -2788,6 +2790,9 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
: normalizeCodexStatusFilter(statusRaw, codexTerminalTaskStatuses, "codex unread");
|
||||
const requestedLimit = positiveIntegerOption(optionArgs, ["--limit"], defaultTasksLimit);
|
||||
const action: CodexUnreadAction = subcommand === "mark-read" || hasFlag(optionArgs, "--mark-read") ? "mark-read" : "summary";
|
||||
const viewRaw = optionValue(optionArgs, ["--view"]);
|
||||
if (viewRaw !== undefined && viewRaw !== "summary" && viewRaw !== "full") throw new Error(`codex unread --view must be summary or full; got ${viewRaw}`);
|
||||
const view: CodexUnreadOptions["view"] = hasFlag(optionArgs, "--full") || subcommand === "list" || viewRaw === "full" ? "full" : "summary";
|
||||
const explicitDryRun = hasFlag(optionArgs, "--dry-run");
|
||||
return {
|
||||
queueId: optionValue(optionArgs, ["--queue", "--queue-id"]),
|
||||
@@ -2797,6 +2802,7 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
statusFilter,
|
||||
repoFilter: normalizeRepoFilter(optionValue(optionArgs, ["--repo"])),
|
||||
issueFilter: normalizeIssueFilter(optionValue(optionArgs, ["--issue"])),
|
||||
view,
|
||||
action,
|
||||
confirm: hasFlag(optionArgs, "--confirm"),
|
||||
dryRun: explicitDryRun || action === "summary",
|
||||
@@ -3074,6 +3080,15 @@ function taskSearchText(task: Record<string, unknown>, summary: Record<string, u
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function hasStrongInfrastructureBlockerSignal(text: string): boolean {
|
||||
const negatedBlockerMention = /\briskcounts\.infrastructureblocker\s*[:=]\s*0\b|\b(?:no|not|without|avoid|must not|should not|do not|don't)\b.{0,60}\b(?:infrastructure[- ]?blocker|infra[- ]?blocker|blocker)\b|(?:不应|不要|不能|避免|无|没有).{0,60}(?:基础设施阻塞|误报|blocker)/iu;
|
||||
const infraContext = /\b(?:provider|gateway|k3s|k3sctl|scheduler|postgres|database|db|storage|heartbeat|proxy|tunnel|auth|token|secret|credential|github|gh auth|rate limit|429|socket\.write|connection_closed)\b|数据库|调度|鉴权|令牌|凭证|不可达|存储/iu;
|
||||
const blockerSignal = /\b(?:infrastructure[- ]?blocker|infra[- ]?blocker|blocked|blocker|cannot proceed|can't proceed|stuck|failed|failure|crash|degraded|unavailable|offline|unreachable|timeout|timed out|connection_closed|null is not an object|permission denied|missing token|auth failed|retry exhausted|rate limit|429)\b|基础设施阻塞|阻塞|故障|失败|崩溃|降级|不可用|离线|不可达|超时|鉴权失败|权限不足/iu;
|
||||
const explicitCodeQueueStorage = /\b(?:postgres|database|db|storage|socket\.write|connection_closed)\b.{0,80}\b(?:failed|failure|crash|degraded|unavailable|timeout|blocked|null is not an object)\b|\b(?:failed|failure|crash|degraded|unavailable|timeout|blocked|null is not an object)\b.{0,80}\b(?:postgres|database|db|storage|socket\.write|connection_closed)\b|数据库.{0,40}(?:故障|失败|崩溃|降级|不可用|超时|阻塞)/iu;
|
||||
if (negatedBlockerMention.test(text) && !explicitCodeQueueStorage.test(text)) return false;
|
||||
return explicitCodeQueueStorage.test(text) || (infraContext.test(text) && blockerSignal.test(text));
|
||||
}
|
||||
|
||||
function issueRefsFromText(text: string): string[] {
|
||||
const issues = new Set<string>();
|
||||
const knownPrefix = (value: string): "HWLAB" | "UniDesk" | null => {
|
||||
@@ -3109,26 +3124,27 @@ function commanderTaskClassification(task: Record<string, unknown>, summary: Rec
|
||||
const text = taskSearchText(task, summary).toLowerCase();
|
||||
const matches = (pattern: RegExp): boolean => pattern.test(text);
|
||||
const labels: string[] = [];
|
||||
if (matches(/\b(?:provider|gateway|k3s|k3sctl|backend-core|scheduler|runner|runtime|heartbeat|stale|tunnel|proxy|auth|token|secret|postgres|database|db|gh auth|github transient|429|rate limit|blocked|blocker|offline|unreachable|timeout)\b|阻塞|故障|超时|鉴权|数据库|调度|运行时/iu)) labels.push("infrastructure-blocker");
|
||||
if (matches(/\b(?:deploy|deployment|rollout|artifact|image|digest|registry|publish|release|ci\/cd|cd)\b|部署|发布|镜像|制品|digest|回滚/iu)) labels.push("deployment-artifact");
|
||||
if (matches(/\b(?:ci|e2e|playwright|smoke|test|tests|typecheck|syntax|lint)\b|自测|冒烟|测试/iu)) labels.push("ci-e2e-evidence");
|
||||
if (matches(/\b(?:diagnostic|diagnostics|gate|report|audit|triage|preflight|observability|summary|brief|board|supervisor|commander|visibility|verification|validate|validation|evidence|proof|check)\b|诊断|报告|审查|汇总|观测|看板|指挥|监督|验证|证据|门禁/iu)) labels.push("diagnostics-gate-report");
|
||||
if (matches(/\b(?:doc|docs|documentation|reference|governance|policy|runbook|ag\.?ents|readme|markdown)\b|文档|参考|治理|规范|策略/iu)) labels.push("docs-governance");
|
||||
if (matches(/\b(?:fix|bug|repair|implement|feature|ui|frontend|backend|api|workbench|patch-panel|box-?simu|gateway-?simu|m3|hardware|hwlab|user-facing|business|用户|工作台|接线|仿真|硬件|业务)\b|修复|实现|功能|用户可见/iu)) labels.push("business-user-facing");
|
||||
if (hasStrongInfrastructureBlockerSignal(text)) labels.push("infrastructure-blocker");
|
||||
if (matches(/\b(?:deploy|deployment|rollout|artifact|image|digest|registry|publish|release|ci\/cd|cd)\b|部署|发布|镜像|制品|digest|回滚/iu)) labels.push("cd-artifact");
|
||||
if (matches(/\b(?:ui|frontend|workbench|patch-panel|box-?simu|gateway-?simu|m3|hardware|hwlab|user-facing|business|customer|product|用户|工作台|接线|仿真|硬件|业务|用户可见)\b/iu)) labels.push("user-facing");
|
||||
if (matches(/\b(?:doc|docs|documentation|reference|governance|policy|runbook|ag\.?ents|readme|markdown|commander|supervisor|scheduler|runner|runtime|queue|code queue|cli|guardrail|guard)\b|文档|参考|治理|规范|策略|指挥|监督|调度|运行时|队列|守卫|边界/iu)) labels.push("infra-governance");
|
||||
if (matches(/\b(?:ci|e2e|playwright|smoke|test|tests|typecheck|syntax|lint|diagnostic|diagnostics|gate|report|audit|triage|preflight|observability|summary|brief|board|visibility|verification|validate|validation|evidence|proof|check)\b|自测|冒烟|测试|诊断|报告|审查|汇总|观测|看板|验证|证据|门禁/iu)) labels.push("noise-report");
|
||||
if (matches(/\b(?:fix|bug|repair|implement|implementation|feature|backend|api|refactor|workflow|flow|state machine)\b|修复|实现|功能|流程/iu)) labels.push("workflow");
|
||||
const ordered: CommanderTaskCategory[] = [
|
||||
"infrastructure-blocker",
|
||||
"deployment-artifact",
|
||||
"business-user-facing",
|
||||
"docs-governance",
|
||||
"ci-e2e-evidence",
|
||||
"diagnostics-gate-report",
|
||||
"cd-artifact",
|
||||
"user-facing",
|
||||
"workflow",
|
||||
"infra-governance",
|
||||
"noise-report",
|
||||
];
|
||||
const category = ordered.find((item) => labels.includes(item)) ?? "unknown";
|
||||
const noiseClass: CommanderTaskClassification["noiseClass"] =
|
||||
category === "infrastructure-blocker" ? "blocker"
|
||||
: category === "business-user-facing" || category === "deployment-artifact" ? "delivery"
|
||||
: category === "ci-e2e-evidence" || category === "diagnostics-gate-report" ? "evidence"
|
||||
: category === "docs-governance" ? "governance"
|
||||
: category === "user-facing" || category === "cd-artifact" ? "delivery"
|
||||
: category === "workflow" ? "workflow"
|
||||
: category === "noise-report" ? "noise"
|
||||
: category === "infra-governance" ? "governance"
|
||||
: "unknown";
|
||||
return {
|
||||
category,
|
||||
@@ -3146,16 +3162,16 @@ function taskClassification(task: Record<string, unknown>, summary: Record<strin
|
||||
} {
|
||||
const commander = commanderTaskClassification(task, summary);
|
||||
const labels = commander.labels;
|
||||
if (commander.category === "deployment-artifact") {
|
||||
return { kind: "deployment-fix", labels, managementNoise: commander.noiseClass === "evidence", reason: commander.reason };
|
||||
if (commander.category === "cd-artifact") {
|
||||
return { kind: "deployment-fix", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "business-user-facing" || commander.category === "infrastructure-blocker") {
|
||||
if (commander.category === "user-facing" || commander.category === "workflow" || commander.category === "infrastructure-blocker") {
|
||||
return { kind: "direct-progress", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "ci-e2e-evidence" || commander.category === "diagnostics-gate-report") {
|
||||
if (commander.category === "noise-report") {
|
||||
return { kind: "verification", labels, managementNoise: true, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "docs-governance") {
|
||||
if (commander.category === "infra-governance") {
|
||||
return { kind: "documentation", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
return { kind: "unknown", labels, managementNoise: false, reason: commander.reason };
|
||||
@@ -3705,10 +3721,15 @@ function unreadTriageCommand(options: CodexUnreadOptions, action: CodexUnreadAct
|
||||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||||
if (options.view === "full") args.push("--full");
|
||||
args.push(...extra);
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function unreadTriageFullCommand(options: CodexUnreadOptions): string {
|
||||
return unreadTriageCommand({ ...options, view: "full" });
|
||||
}
|
||||
|
||||
function taskTriageSearchText(task: Record<string, unknown>): string {
|
||||
return [
|
||||
asString(task.displayPrompt),
|
||||
@@ -3807,9 +3828,16 @@ function unreadTriageCounts(candidates: Record<string, unknown>[]): Record<strin
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown> {
|
||||
function unreadNextStep(task: Record<string, unknown>): string {
|
||||
const status = asString(task.status);
|
||||
if (status === "failed") return "inspect failure, close out, then read";
|
||||
if (status === "canceled") return "confirm cancellation outcome, then read";
|
||||
return "scan final response, close out, then read";
|
||||
}
|
||||
|
||||
function unreadTriageItem(task: Record<string, unknown>, view: CodexUnreadOptions["view"]): Record<string, unknown> {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
return {
|
||||
const base = {
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status: asString(task.status) || null,
|
||||
@@ -3817,6 +3845,11 @@ function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown
|
||||
issues: taskIssueRefsForTriage(task),
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
finishedAt: asString(task.finishedAt) || null,
|
||||
nextStep: unreadNextStep(task),
|
||||
};
|
||||
if (view !== "full") return base;
|
||||
return {
|
||||
...base,
|
||||
commands: {
|
||||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||||
@@ -3830,16 +3863,20 @@ function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown
|
||||
function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: CodexTasksTaskPage, options: CodexUnreadOptions): {
|
||||
result: Record<string, unknown>;
|
||||
visibleCandidates: Record<string, unknown>[];
|
||||
mutationCandidates: Record<string, unknown>[];
|
||||
} {
|
||||
const allCandidates = unreadSortedCandidates(page.tasks, options);
|
||||
const pagedCandidates = unreadPageCandidates(allCandidates, options);
|
||||
const visibleCandidates = pagedCandidates.slice(0, options.limit);
|
||||
const mutationCandidates = pagedCandidates.slice(0, options.limit);
|
||||
const itemLimit = options.view === "full" || options.action === "mark-read" ? options.limit : Math.min(options.limit, unreadTriageDefaultItemLimit);
|
||||
const visibleCandidates = pagedCandidates.slice(0, itemLimit);
|
||||
const hasMore = pagedCandidates.length > visibleCandidates.length;
|
||||
const nextBeforeId = hasMore ? taskOverviewCandidateKey(visibleCandidates.at(-1) ?? {}) || null : null;
|
||||
const nextCommand = nextBeforeId === null ? null : unreadTriageCommand({ ...options, beforeId: nextBeforeId });
|
||||
const readCommand = unreadTriageCommand(options, "mark-read", ["--confirm"]);
|
||||
const readCommand = unreadTriageCommand({ ...options, view: "summary" }, "mark-read", ["--confirm"]);
|
||||
return {
|
||||
visibleCandidates,
|
||||
mutationCandidates,
|
||||
result: {
|
||||
ok: true,
|
||||
upstream,
|
||||
@@ -3851,35 +3888,43 @@ function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: C
|
||||
status: options.statusFilter,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
view: options.view,
|
||||
limitCapped: options.requestedLimit > options.limit,
|
||||
beforeId: options.beforeId ?? null,
|
||||
},
|
||||
readOnly: options.action === "summary" || options.dryRun,
|
||||
bounded: true,
|
||||
disclosure: {
|
||||
policy: "summary counts plus bounded task ids only; no raw prompt, final response, trace, or output is included by default",
|
||||
policy: options.view === "full"
|
||||
? "explicit full unread view: bounded task rows include per-task drill-down commands; raw prompt, final response, trace, and output still require codex task/output drill-down"
|
||||
: "progressive disclosure: default shows counts, buckets, and a small compact task page only; no raw prompt, final response, trace, output, or repeated per-task command blocks",
|
||||
countBucketLimit: unreadTriageCountLimit,
|
||||
itemLimit: options.limit,
|
||||
itemLimit,
|
||||
defaultItemLimit: unreadTriageDefaultItemLimit,
|
||||
mutationPolicy: "batch mark-read requires codex unread mark-read --confirm; per-task read remains codex read <taskId>",
|
||||
},
|
||||
counts: unreadTriageCounts(allCandidates),
|
||||
newest: {
|
||||
count: allCandidates.length,
|
||||
returned: visibleCandidates.length,
|
||||
fullCandidateLimit: mutationCandidates.length,
|
||||
rowsOmittedByDefault: options.view === "full" ? 0 : Math.max(0, pagedCandidates.length - visibleCandidates.length),
|
||||
hasMore,
|
||||
nextBeforeId,
|
||||
commands: {
|
||||
next: nextCommand,
|
||||
full: unreadTriageFullCommand(options),
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||||
traceTemplate: `bun scripts/cli.ts codex task <taskId> --trace --tail --limit ${defaultTraceLimit}`,
|
||||
outputTemplate: `bun scripts/cli.ts codex output <taskId> --tail --limit ${defaultOutputLimit}`,
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
},
|
||||
items: visibleCandidates.map(unreadTriageItem),
|
||||
items: visibleCandidates.map((task) => unreadTriageItem(task, options.view)),
|
||||
},
|
||||
commands: {
|
||||
refresh: unreadTriageCommand(options),
|
||||
full: unreadTriageFullCommand(options),
|
||||
tasksUnread: taskListCommand({
|
||||
queueId: options.queueId,
|
||||
requestedLimit: Math.min(options.requestedLimit, defaultTasksLimit),
|
||||
@@ -3893,6 +3938,9 @@ function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: C
|
||||
byIssue: "bun scripts/cli.ts codex unread --issue <number>",
|
||||
byStatus: "bun scripts/cli.ts codex unread --status succeeded|failed|canceled",
|
||||
byQueue: "bun scripts/cli.ts codex unread --queue <queueId>",
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
perTaskRead: "bun scripts/cli.ts codex read <taskId>",
|
||||
batchReadDryRun: unreadTriageCommand(options, "mark-read", ["--dry-run"]),
|
||||
batchReadConfirm: readCommand,
|
||||
@@ -3952,11 +4000,11 @@ function codexUnreadMutationResult(result: Record<string, unknown>, options: Cod
|
||||
function codexUnreadTriage(taskArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
const options = parseUnreadOptions(taskArgs);
|
||||
const { upstream, page } = loadCodexTasks(unreadLoadOptions(options), fetcher);
|
||||
const { result, visibleCandidates } = unreadTriageSummary(upstream, page, options);
|
||||
const { result, mutationCandidates } = unreadTriageSummary(upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, mutationCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
for (const task of mutationCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
@@ -4768,11 +4816,11 @@ async function codexUnreadTriageAsync(taskArgs: string[], fetcher: AsyncCodexRes
|
||||
return asString(left.id).localeCompare(asString(right.id));
|
||||
});
|
||||
const page: CodexTasksTaskPage = { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks };
|
||||
const { result, visibleCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||||
const { result, mutationCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, mutationCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
for (const task of mutationCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const readResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ export function rootHelp(): unknown {
|
||||
{ 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." },
|
||||
{ command: "codex task <taskId> [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]", description: "Fetch the bounded review view by default; --detail is still capped, while --full/trace/output explicitly expand evidence." },
|
||||
{ command: "codex tasks [--view commander|supervisor|full] [--queue id] [--status status[,status]] [--unread|--unread-only] [--limit N] [--before-id id]", description: "Show Code Queue task state with progressive disclosure; --view commander is the recommended bounded host-commander loop, supervisor keeps compact sections, and full returns detailed rows." },
|
||||
{ command: "codex unread [summary|mark-read] [--queue id] [--repo owner/name] [--issue N] [--status succeeded,failed,canceled] [--limit N] [--confirm]", description: "Summarize unread terminal backlog by repo, issue, status and queue without raw prompts; batch mark-read requires the explicit mark-read subcommand plus --confirm." },
|
||||
{ command: "codex unread [summary|list|mark-read] [--queue id] [--repo owner/name] [--issue N] [--status succeeded,failed,canceled] [--limit N] [--view summary|full] [--full] [--confirm]", description: "Summarize unread terminal backlog by repo, issue, status and queue with compact rows by default; per-task command blocks require --full/list, and batch mark-read requires --confirm." },
|
||||
{ command: "codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]", description: "Fetch paged raw Code Queue output records; default caps large limits/text previews, --full-text explicitly expands one seq window." },
|
||||
{ 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." },
|
||||
@@ -311,7 +311,7 @@ function codexHelp(): unknown {
|
||||
disclosure: "Full prompt, tool logs, and feedback prompts are not printed by codex read; use codex task/detail/trace/output for progressive disclosure.",
|
||||
},
|
||||
unreadTriage: {
|
||||
default: "codex unread is read-only by default and returns counts plus bounded task ids grouped by repo, issue, status and queue.",
|
||||
default: "codex unread is read-only by default and returns counts, buckets, compact task rows, and one-time drill-down templates; per-task command blocks require --full or list.",
|
||||
mutationGuard: "Batch mark-read is blocked unless the explicit mark-read subcommand is used with --confirm; use codex read <taskId> for per-task review.",
|
||||
disclosure: "Raw prompt, final response, trace and output are omitted; use the returned task/detail/trace/output/read commands for drill-down.",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user