fix: clarify codex supervisor active counts

This commit is contained in:
Codex
2026-05-23 02:49:09 +00:00
parent 982f21ec62
commit 8393bc531a
5 changed files with 233 additions and 11 deletions
+1 -1
View File
@@ -47,7 +47,7 @@ CLI 可以从 `master` 快速演进,但必须兼容 `deploy.json` 固定的 CI
- `codex submit [prompt] [--prompt-file path|--prompt-stdin] [--queue queueId] [--provider-id id] [--cwd path] [--model model] [--reasoning-effort effort] [--execution-mode mode] [--max-attempts N] [--reference-task-id id] [--dry-run]` 通过 backend-core 私有代理向稳定 `code-queue` 用户服务路径提交任务;prompt 必须且只能来自位置参数、文件或 stdin 之一,`--dry-run` 只返回结构化请求且不实际入队。长 prompt、多行 prompt、含引号/反引号/Markdown 表格/JSON/反斜杠的 prompt 必须优先用 `--prompt-stdin``--prompt-file`,不要拼进 shell 单个参数;位置参数只适合短单行 smoke prompt。stdin 推荐用 quoted heredoc`cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run`,文件路径推荐 `bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run`,确认 dry-run 后移除 `--dry-run` 提交同一 payload。dry-run 会额外输出 `routingRecommendation`,包含推荐 route、runner、model、风险信号、prompt 自包含/issue 非唯一来源/prod-secret-DB 禁止/运行态或 release 禁止/证据要求/中等复杂度候选等 guard 状态;同时输出 `policyContract`,固定暴露 GPT-5.5、DeepSeek、MiniMax 的风险分层、并发上限和外部 provider 429 退避处置。该建议只用于指挥官 preflight,不会改写 payload,不改变 runtime admission,也不假设生产 MiniMax 或 DeepSeek 可用。`--dry-run` 必须返回完整 prompt、字符数和 `truncated=false` 用于人工验收;真实提交是写入操作,默认只返回 `accepted=true`、task id、队列、写入保护摘要和后续查看命令,必须标记 `promptOmitted=true` 且不得回显 prompt 或 promptPreview。真实提交会经过本机本地串行化保护和短节流,避免同一指挥端并发 submit 把低内存主机或 `code-queue-mgr` 控制面打抖;返回值会附带低噪声 `submitConcurrencyGuard` 说明本次提交的锁与等待信息。backend-core 默认把提交、队列 CRUD、已读状态、历史摘要和轻量 Trace 读取分流到主 server `code-queue-mgr`,由它写入主 PostgreSQLD601 scheduler 只轮询并执行已入库任务。
- `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]` 通过稳定 `code-queue` proxy 请求 D601 scheduler `/api/runtime-preflight`,用于 PR 型派单 admission。输出会压缩展示 scheduler/runner 的 token 覆盖、Auth Broker source/capability/nextAction、工具、agent port、Git worktree、GitHub egress、repo/issue/PR 只读探测、可选 push dry-run,以及可选 PR body/create dry-run guard;只报告 `GH_TOKEN`/`GITHUB_TOKEN` 是否存在和来源 key,不打印值。当 auth-broker 配置存在时,`tokenCoverage.source="auth-broker"``credentialSource="broker-issued-token"` 且 runner env token 不是成功前提;当仅 env token 存在时,`credentialSource="env-token"``preflight.authBroker.nextAction="use-env-token-until-auth-broker-live"`;两者都缺失时顶层 `ok=false``runnerDisposition=infra-blocked``degradedReason=auth-broker-needed``tokenCoverage.missing` 同时列出 `GH_TOKEN``GITHUB_TOKEN`,并输出 `preflight.authBroker.source="broker/auth-broker-needed"``capability.source="missing-token"`。该 `auth-missing` 的 scope 是 `scheduler-runner-env`,不能简化成“当前 active runner/dev container 不能创建 PR”;输出必须带 `scopeBoundary``activeRunnerDevContainer`,要求调用方另跑 `bun scripts/cli.ts gh auth status --repo pikasTech/unidesk` 与 PR dry-run 来确认当前 dev container 能力。`preflight.prCapabilityContract` 是 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 supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。默认 `supervisor` 是低噪声指挥官视图,只返回 `running``completedUnread``recentCompleted``queued``activity``commanderConcurrency``executionDiagnostics` 的紧凑行;prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview`commanderConcurrency.activeRunnerCount` 是并发策略应使用的 active/running 计数,等于 `activity.effectiveActiveTaskCount`15 并发策略按 `15 - activeRunnerCount` 计算剩余窗口。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 有 fresh heartbeat 证据,应继续监督并计入 active;`interventionRequired=true` 才提示介入。每个条目只保留 task id、队列、状态、issue、分类和短摘要,`show/detail/trace/output/full/read` 放在 section template 中避免重复噪声,并带 `kind` 标记直接推进、部署修复、验证/报告噪声等类别。`--limit` 在 supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;`--unread``--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,未知参数或未知状态必须结构化失败,不能静默忽略。需要更详细当前页任务行时显式使用 `--view full``--full`,仍受 `--limit``--before-id` 分页约束。
- `codex tasks [--view supervisor|full] [--queue id] [--status succeeded|running|queued|failed|canceled|judging|retry_wait[,..]] [--unread|--unread-only] [--limit N] [--before-id id]` 通过同一私有代理输出渐进式披露视图。默认 `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` 才提示介入。prompt/body 只给短预览和原始字符数,`running`/`completedUnread`/`queued` 默认只返回一个有界小页并通过 section `commands.next` 继续分页,`recentCompleted` 默认限量且不重复 `completedUnread` 未读终态,不嵌入完整 Trace、final response 或全量 overview。`--limit` 在 supervisor 中主要是扫描/分页预算,不是返回几十条肥行的开关;`filters.requestedLimit` 保留用户输入,`filters.limit`/`effectiveLimit` 显示实际 cap。`--unread``--unread-only` 的别名,必须只保留未读终态;`--status` 必须真实过滤支持的状态,未知参数或未知状态必须结构化失败。需要更详细当前页任务行时显式使用 `--view full``--full`,仍受 `--limit``--before-id` 分页约束。
- `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>` 在人工审阅后标记单个终态任务已读;列表、overview 和 supervisor 视图只返回这个命令字段,不得自动执行,也不得批量清空未读状态。
+3 -3
View File
@@ -220,15 +220,15 @@ bun scripts/cli.ts codex pr-preflight --remote --issue <issue-number>
常用入口:
- `bun scripts/cli.ts codex tasks --view supervisor --limit N`:查看默认低噪声监督视图,包括 running、完成未读、少量最近完成、queued/runnable、activity、execution diagnostics、任务分类和下一步 drill-down 命令。默认行只保留 task id、队列、短 prompt/body 预览和原始字符数;`--limit` 是扫描/分页预算,不是返回几十条肥行的开关;`show/detail/trace/output/full/read` 放在 section template 中,避免每条任务重复刷屏,需要更多内容再按 taskId 展开。
- `bun scripts/cli.ts codex queues`:查看低噪声队列计数、activity、active task id、完成未读队列、runnable 队列和控制面诊断;需要完整队列行视图时加 `--full`,但 `--full` 仍默认分页,继续用 `--limit N``--page N``--offset N` 渐进展开。summary 和 full 都使用稳定 JSON path `.data.queues.items[]` 读取队列行,并从 `.data.queues.activity``.data.queues.counts``.data.queues.executionDiagnostics` 读取全局活跃计数和执行诊断;完整 upstream 只通过输出中的 raw command 显式获取。
- `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` 是扫描/分页预算,不是返回几十条肥行的开关`filters.requestedLimit` 保留用户输入,`filters.limit`/`effectiveLimit` 是实际上限,`running.returned` 只是低噪声返回行数`show/detail/trace/output/full/read` 放在 section template 中,避免每条任务重复刷屏,需要更多内容再按 taskId 展开。
- `bun scripts/cli.ts codex queues`:查看低噪声队列计数、activity、commanderConcurrency、active task id、完成未读队列、runnable 队列和控制面诊断;需要完整队列行视图时加 `--full`,但 `--full` 仍默认分页,继续用 `--limit N``--page N``--offset N` 渐进展开。summary 和 full 都使用稳定 JSON path `.data.queues.items[]` 读取队列行,并从 `.data.queues.commanderConcurrency``.data.queues.activity``.data.queues.counts``.data.queues.executionDiagnostics` 读取全局活跃计数和执行诊断;完整 upstream 只通过输出中的 raw command 显式获取。
- `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 必须显式失败,不能扩大为未过滤结果。
- `bun scripts/cli.ts codex task <taskId>`:默认只查看原始 prompt、最终 response、最后错误和 drill-down 命令,这是完成未读任务审阅的第一步。
- 当默认审阅摘要不足时,再逐级使用 `bun scripts/cli.ts codex task <taskId> --detail``bun scripts/cli.ts codex task <taskId> --trace --limit N``codex output`
- 当 master 控制面状态和 D601 scheduler 状态看起来分裂时,使用 `docs/reference/observability.md` 中的活性规则判断。
默认 supervisor 视图必须保持低噪声。`running``completedUnread``queued` 即使传入较大的 `--limit`,默认也只返回一个很小的有界页,并通过 section `commands.next` 继续分页;`--limit` 保留为扫描/分页预算和 full view 返回预算,不得让一次 supervisor 调用输出几十条肥行。每个任务行只应带 task id 和必要摘要,`show``detail``trace``output``full``read` 使用 section template 表达,让下一步渐进披露动作明确且不重复;默认不得嵌入完整 queue 列表、完整 final response、raw output 页或完整 trace 行。`recentCompleted` 必须默认限量,且不得重复 `completedUnread` 里的未读终态,避免完成历史把当前 running、阻塞和未读审阅挤出视野;需要完整当前页时显式使用 `--view full``executionDiagnostics` 只能展示有界 task-id/reason 预览、总数、截断标记和 omitted counts;需要全量诊断时使用输出中的 raw command。`commands.read` 只是在人工审阅后的建议命令,listing 命令绝不能自动执行。
默认 supervisor 视图必须保持低噪声。`activeRunning.count` 是指挥官 active running 计数,来源是 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 做红线/补派判断。`running``completedUnread``queued` 即使传入较大的 `--limit`,默认也只返回一个很小的有界页,并通过 section `commands.next` 继续分页;`--limit` 保留为扫描/分页预算和 full view 返回预算,不得让一次 supervisor 调用输出几十条肥行。每个任务行只应带 task id 和必要摘要,`show``detail``trace``output``full``read` 使用 section template 表达,让下一步渐进披露动作明确且不重复;默认不得嵌入完整 queue 列表、完整 final response、raw output 页或完整 trace 行。`recentCompleted` 必须默认限量,且不得重复 `completedUnread` 里的未读终态,避免完成历史把当前 running、阻塞和未读审阅挤出视野;需要完整当前页时显式使用 `--view full``executionDiagnostics` 只能展示有界 task-id/reason 预览、总数、截断标记和 omitted counts;需要全量诊断时使用输出中的 raw command。`commands.read` 只是在人工审阅后的建议命令,listing 命令绝不能自动执行。
`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 记录。
@@ -82,6 +82,14 @@ function fixtureResponse(path: string): JsonRecord {
body: {
ok: true,
queue: {
counts: {
running: 15,
judging: 0,
queued: 4,
retry_wait: 1,
succeeded: 13,
},
maxActiveQueues: 12,
executionDiagnostics: {
state: "split-brain",
splitBrain: true,
@@ -112,7 +120,7 @@ function fixtureResponse(path: string): JsonRecord {
pagination: {
limit: 200,
returned: 15,
total: 15,
total: 33,
hasMore: false,
nextBeforeId: null,
includeActive: true,
@@ -152,6 +160,13 @@ function manyRunningFixtureResponse(path: string): JsonRecord {
body: {
ok: true,
queue: {
counts: {
running: 40,
judging: 0,
queued: 7,
retry_wait: 2,
},
maxActiveQueues: 50,
executionDiagnostics: {
state: "healthy",
databaseActiveTaskCount: 40,
@@ -227,7 +242,7 @@ function splitBrainLiveSupervisorFixtureResponse(path: string): JsonRecord {
}
export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "20"], fixtureResponse);
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "260"], fixtureResponse);
const full = codexTasksQueryForTest(["--view", "full", "--limit", "20"], fixtureResponse);
const runningFiltered = codexTasksQueryForTest(["--status", "running", "--limit", "40"], manyRunningFixtureResponse);
const unreadFiltered = codexTasksQueryForTest(["--unread", "--limit", "20"], fixtureResponse);
@@ -251,6 +266,13 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
const completedUnread = asRecord(supervisorView.completedUnread);
const fullTasks = asRecord(asRecord(full).tasks);
const diagnostics = asRecord(supervisorView.executionDiagnostics);
const filters = asRecord(supervisorView.filters);
const activeRunning = asRecord(supervisorView.activeRunning);
const activeRunningRowPage = asRecord(activeRunning.rowPage);
const activeRunningRedline = asRecord(activeRunning.redline);
const activeRunningCommands = asRecord(activeRunning.commands);
const counts = asRecord(supervisorView.counts);
const outputBudget = asRecord(asRecord(disclosure.outputBudget));
const listBudget = asRecord(diagnostics.listBudget);
const omittedCounts = asRecord(listBudget.omittedCounts);
const splitBrainLiveView = asRecord(asRecord(splitBrainLive).supervisor);
@@ -286,8 +308,13 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
assertCondition(fullItem.status === "running" && String(fullItem.statusLabel ?? "").includes("awaiting terminal/judge"), "full view should keep raw status while exposing derived closeout label", fullItem);
assertCondition(fullItem.awaitingTerminalJudge === true && fullItem.closeoutState === "awaiting-terminal-or-judge", "full view should expose awaiting terminal/judge state", fullItem);
assertCondition(fullTasks.returned === 15, "full view must not inherit supervisor recentCompleted cap", fullTasks);
const budget = asRecord(disclosure.outputBudget);
assertCondition(budget.recentCompletedReturnedLimit === 3 && budget.sectionReturnedLimit === 3, "supervisor must expose output budget metadata", disclosure);
assertCondition(filters.requestedLimit === 260 && filters.limit === 100 && filters.limitCapped === true, "supervisor filters should disclose requested vs effective limit", filters);
assertCondition(outputBudget.requestedLimit === 260 && outputBudget.effectiveLimit === 100 && outputBudget.sectionReturnedLimit === 3, "supervisor must expose output budget metadata", outputBudget);
assertCondition(activeRunning.count === 15 && activeRunning.exact === true && activeRunning.source === "queue-summary-counts", "activeRunning should expose exact running+judging count from queue summary", activeRunning);
assertCondition(activeRunningRowPage.returned === 1 && activeRunningRowPage.returnedLimit === 3 && activeRunningRowPage.distinction.includes("row page"), "activeRunning row page should distinguish returned rows from active count", activeRunningRowPage);
assertCondition(activeRunningRedline.countField === "supervisor.activeRunning.count" && activeRunningRedline.hardRedline === 15 && activeRunningRedline.state === "at-or-over-hard-redline", "activeRunning redline should name count field and interpretation", activeRunningRedline);
assertCondition(counts.activeRunningCount === 15 && counts.activeRunningExact === true && counts.activeRunningRowsReturned === 1, "supervisor counts should separate active count from returned running rows", counts);
assertCondition(String(activeRunningCommands.running ?? "").includes("--status running,judging"), "activeRunning should provide running drilldown", activeRunningCommands);
assertCondition(asArray(runningFilteredSection.items).length === 3, "running status filter should be locally paged below --limit", runningFilteredSection);
assertCondition(runningFilteredSection.count === 40 && runningFilteredSection.hasMore === true, "running status filter should preserve count and hasMore", runningFilteredSection);
assertCondition(String(asRecord(runningFilteredSection.commands).next ?? "").includes("--before-id task-running-03"), "running status filter should provide next page command", runningFilteredSection);
@@ -324,6 +351,8 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
"recentCompleted capped",
"running/unread locally paged",
"split-brain diagnostics capped",
"active running exact count exposed",
"requested/effective/returned limits disclosed",
"prompt/body previews bounded",
"running finalResponse rows labeled awaiting terminal/judge",
"drill-down commands preserved",
+191 -3
View File
@@ -189,6 +189,7 @@ interface CompactTaskMutationResponseOptions {
interface CodexTasksOptions {
queueId: string | undefined;
requestedLimit: number;
limit: number;
beforeId: string | undefined;
unreadOnly: boolean;
@@ -288,6 +289,14 @@ interface CodexTasksDegraded {
reason: string;
}
interface SupervisorStatusCounts {
counts: Record<string, number>;
exact: boolean;
source: "queue-summary-counts" | "overview-page-fallback";
scope: "all-queues" | "queue";
queueId: string | null;
}
interface CodexQueuesOptions {
full: boolean;
limit: number;
@@ -1851,9 +1860,11 @@ function parseTasksOptions(args: string[]): CodexTasksOptions {
const unsupported = statusFilter.filter((status) => !supported.has(status));
if (unsupported.length > 0) throw new Error(`unsupported --status value: ${unsupported.join(", ")}; supported: ${Array.from(supported).join(", ")}`);
}
const requestedLimit = positiveIntegerOption(args, ["--limit"], defaultTasksLimit);
return {
queueId: optionValue(args, ["--queue", "--queue-id"]),
limit: positiveIntegerOption(args, ["--limit"], defaultTasksLimit, maxTasksLimit),
requestedLimit,
limit: Math.min(requestedLimit, maxTasksLimit),
beforeId: optionValue(args, ["--before-id", "--beforeId"]),
unreadOnly: hasFlag(args, "--unread-only") || hasFlag(args, "--unread"),
statusFilter,
@@ -2376,13 +2387,25 @@ function filterTasksForOptions(tasks: Record<string, unknown>[], options: CodexT
.filter((task) => !options.unreadOnly || taskUnreadTerminal(task));
}
function baseTaskListOptions(options: CodexTasksOptions): CodexTasksOptions {
return {
queueId: options.queueId,
requestedLimit: options.requestedLimit,
limit: options.limit,
beforeId: options.beforeId,
unreadOnly: options.unreadOnly,
statusFilter: options.statusFilter,
view: options.view,
};
}
function taskListCommand(options: CodexTasksOptions, extra: string[] = []): string {
const args = ["codex", "tasks"];
if (options.view !== "supervisor") args.push("--view", options.view);
if (options.queueId !== undefined) args.push("--queue", options.queueId);
if (options.unreadOnly) args.push("--unread");
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
if (options.limit !== defaultTasksLimit) args.push("--limit", String(options.limit));
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
args.push(...extra);
return `bun scripts/cli.ts ${args.join(" ")}`;
@@ -2406,6 +2429,155 @@ function sectionNextCommand(
return sourceNextCommand;
}
function countRecordValues(value: unknown): Record<string, number> {
const record = asRecord(value);
const counts: Record<string, number> = {};
if (record === null) return counts;
for (const [status, rawCount] of Object.entries(record)) {
const count = typeof rawCount === "number" ? rawCount : typeof rawCount === "string" ? Number(rawCount) : Number.NaN;
if (Number.isFinite(count) && count >= 0) counts[status] = Math.floor(count);
}
return counts;
}
function queueSummaryCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts | null {
const queue = taskPage.queue;
if (queue === null) return null;
if (options.queueId !== undefined) {
for (const row of asArray(queue.queues)) {
const record = asRecord(row);
if (record === null || asString(record.id) !== options.queueId) continue;
if (asRecord(record.counts) === null) return null;
return {
counts: countRecordValues(record.counts),
exact: true,
source: "queue-summary-counts",
scope: "queue",
queueId: options.queueId,
};
}
return null;
}
if (asRecord(queue.counts) === null) return null;
return {
counts: countRecordValues(queue.counts),
exact: true,
source: "queue-summary-counts",
scope: "all-queues",
queueId: null,
};
}
function fallbackStatusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
const counts: Record<string, number> = {};
for (const task of taskPage.tasks) {
if (options.queueId !== undefined && asString(task.queueId) !== options.queueId) continue;
const status = asString(task.status);
if (status.length === 0) continue;
counts[status] = (counts[status] ?? 0) + 1;
}
return {
counts,
exact: false,
source: "overview-page-fallback",
scope: options.queueId === undefined ? "all-queues" : "queue",
queueId: options.queueId ?? null,
};
}
function statusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
return queueSummaryCountsForOptions(taskPage, options) ?? fallbackStatusCountsForOptions(taskPage, options);
}
function positiveCount(value: unknown): number | null {
const count = asNumber(value, Number.NaN);
return Number.isFinite(count) && count >= 0 ? Math.floor(count) : null;
}
function runtimeMaxActiveQueues(taskPage: CodexTasksTaskPage): number | null {
const value = positiveCount(taskPage.queue?.maxActiveQueues);
return value !== null && value > 0 ? value : null;
}
function activeRunningRedlineState(activeCount: number, hardRedline: number, burstRedline: number, routineTarget: number): string {
if (activeCount >= hardRedline) return "at-or-over-hard-redline";
if (activeCount >= burstRedline) return "burst-redline";
if (activeCount >= routineTarget) return "above-routine-target";
return "below-routine-target";
}
function supervisorActiveRunningSummary(
taskPage: CodexTasksTaskPage,
options: CodexTasksOptions,
runningSection: CodexTasksSection<CodexTasksSupervisorEntry>,
diagnostics: Record<string, unknown> | null,
): Record<string, unknown> {
const statusCounts = statusCountsForOptions(taskPage, options);
const runningCount = statusCounts.counts.running ?? 0;
const judgingCount = statusCounts.counts.judging ?? 0;
const activeStatusCount = runningCount + judgingCount;
const heartbeatActiveCount = positiveCount(diagnostics?.activeHeartbeatCount);
const schedulerLocalActiveRunSlotCount = positiveCount(diagnostics?.schedulerActiveRunSlotCount);
const effectiveActiveRunnerCount = Math.max(activeStatusCount, heartbeatActiveCount ?? 0, schedulerLocalActiveRunSlotCount ?? 0);
const policy = submitPolicyContract().concurrency;
const routineTarget = policy.gpt55Routine;
const burstRedline = Math.max(policy.gpt55BurstMax, policy.minimaxSimpleMax);
const hardRedline = 15;
const runtimeLimit = runtimeMaxActiveQueues(taskPage);
const redlineState = activeRunningRedlineState(activeStatusCount, hardRedline, burstRedline, routineTarget);
const exactInterpretation = statusCounts.exact
? "activeRunning.count is exact from queue status counts; row pagination does not change this count"
: "activeRunning.count is page-scoped because queue status counts were absent; do not use it for commander redline decisions without a queue summary/raw cross-check";
const baseOptions = baseTaskListOptions({ ...options, beforeId: undefined, unreadOnly: false, statusFilter: null });
const runningOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["running", "judging"] };
const queuedOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["queued", "retry_wait"] };
return {
count: activeStatusCount,
exact: statusCounts.exact,
source: statusCounts.source,
scope: statusCounts.scope,
queueId: statusCounts.queueId,
statuses: {
running: runningCount,
judging: judgingCount,
},
effectiveActiveRunnerCount,
effectiveActiveRunnerCountSources: {
activeStatusCount,
heartbeatActiveCount,
schedulerLocalActiveRunSlotCount,
},
rowPage: {
returned: runningSection.returned,
availableInCurrentOverview: runningSection.count,
returnedLimit: taskSectionLimit(options),
truncated: runningSection.truncated,
hasMore: runningSection.hasMore,
distinction: "returned is the compact row page; count is the active running total used for supervision",
},
redline: {
countField: "supervisor.activeRunning.count",
routineTarget,
burstRedline,
hardRedline,
runtimeMaxActiveQueues: runtimeLimit,
state: redlineState,
remainingToRoutineTarget: Math.max(0, routineTarget - activeStatusCount),
remainingToBurstRedline: Math.max(0, burstRedline - activeStatusCount),
remainingToHardRedline: Math.max(0, hardRedline - activeStatusCount),
decisionReady: statusCounts.exact,
interpretation: exactInterpretation,
},
commands: {
running: taskListCommand(runningOptions),
runningFull: taskListCommand({ ...runningOptions, view: "full" }),
queued: taskListCommand(queuedOptions),
queues: "bun scripts/cli.ts codex queues",
nextRunningPage: runningSection.commands.next,
},
};
}
function fetchTaskSummaries(taskIds: string[], fetcher: CodexResponseFetcher): { summaries: Map<string, Record<string, unknown>>; degraded: CodexTasksDegraded | null } {
const boundedIds = taskIds.slice(0, maxTasksLimit);
const summaries = new Map<string, Record<string, unknown>>();
@@ -2504,6 +2676,7 @@ function codexTasksOverviewResult(
const diagnostics = supervisorExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
const activity = compactCodeQueueActivity(asRecord(taskPage.queue) ?? {}, diagnostics);
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
const activeRunning = supervisorActiveRunningSummary(taskPage, options, runningSection, diagnostics);
const visibleSupervisorItems = [...runningSection.items, ...unreadSection.items, ...recentSection.items, ...queuedSection.items];
const classifierCounts = visibleSupervisorItems.reduce((counts, item) => {
const key = item.kind;
@@ -2517,7 +2690,10 @@ function codexTasksOverviewResult(
filters: {
view: options.view,
queueId: options.queueId ?? null,
requestedLimit: options.requestedLimit,
limit: options.limit,
effectiveLimit: options.limit,
limitCapped: options.limit < options.requestedLimit,
unreadOnly: options.unreadOnly,
status: options.statusFilter,
beforeId: options.beforeId ?? null,
@@ -2526,6 +2702,7 @@ function codexTasksOverviewResult(
endpoint: "/api/tasks/overview",
queueId: options.queueId ?? null,
limit: asNumber(pagination.limit, 0) || null,
limitSemantics: "upstream overview fetch limit; separate from filters.requestedLimit/effectiveLimit and compact section row counts",
returned: asNumber(pagination.returned, 0) || null,
total: asNumber(pagination.total, 0) || null,
hasMore: asBoolean(pagination.hasMore),
@@ -2536,9 +2713,13 @@ function codexTasksOverviewResult(
disclosure: {
defaultView: "supervisor",
policy: "bounded summary rows only; prompt/body are short previews and raw detail requires explicit task/full/trace/output commands",
limitSemantics: "--limit is request/scanning/page budget; supervisor sections still return compact bounded row pages and expose counts separately",
outputBudget: {
promptPreviewChars: supervisorPromptPreviewChars,
bodyPreviewChars: supervisorBodyPreviewChars,
requestedLimit: options.requestedLimit,
effectiveLimit: options.limit,
limitCapped: options.limit < options.requestedLimit,
sectionReturnedLimit: sectionLimit,
recentCompletedReturnedLimit: recentLimit,
recentCompletedBodyPreviewChars: supervisorRecentBodyPreviewChars,
@@ -2551,6 +2732,9 @@ function codexTasksOverviewResult(
counts: {
scanned: allTasks.length,
running: runningSection.count,
activeRunningCount: activeRunning.count,
activeRunningExact: activeRunning.exact,
activeRunningRowsReturned: runningSection.returned,
completedUnread: unreadSection.count,
recentCompleted: recentSection.count,
queued: queuedSection.count,
@@ -2563,12 +2747,13 @@ function codexTasksOverviewResult(
classifierCounts,
commanderConcurrency,
activity,
activeRunning,
executionDiagnostics: diagnostics,
degraded,
commands: {
refresh: taskListCommand(options),
unread: taskListCommand({ ...options, unreadOnly: true }),
byStatus: `bun scripts/cli.ts codex tasks --status <status>${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.limit === defaultTasksLimit ? "" : ` --limit ${options.limit}`}`,
byStatus: `bun scripts/cli.ts codex tasks --status <status>${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.requestedLimit === defaultTasksLimit ? "" : ` --limit ${options.requestedLimit}`}`,
full: fullCommand,
next: nextCommand,
},
@@ -2599,7 +2784,10 @@ function codexTasksFullResult(
filters: {
view: "full",
queueId: options.queueId ?? null,
requestedLimit: options.requestedLimit,
limit: options.limit,
effectiveLimit: options.limit,
limitCapped: options.limit < options.requestedLimit,
unreadOnly: options.unreadOnly,
status: options.statusFilter,
beforeId: options.beforeId ?? null,
+5
View File
@@ -291,6 +291,11 @@ function codexHelp(): unknown {
schedulerLocalActiveQueueCount: "Only queues currently visible in this scheduler-local active-run slot view; zero does not override DB or heartbeat activity.",
heartbeatFreshActiveTaskCount: "Heartbeat-effective active runner count used to avoid split-brain zero-active mistakes.",
},
supervisorView: {
activeRunning: "Use data.supervisor.activeRunning.count for exact active running+judging count when exact=true; data.supervisor.running.returned is only the compact row page.",
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 Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text.",
};
}