fix: foreground commander queue concurrency
Foreground codex queues commander concurrency summary and fixtures.
This commit is contained in:
@@ -275,7 +275,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`:查看低噪声队列计数、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 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 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` 等价,不能被静默忽略。
|
||||
@@ -303,7 +303,7 @@ commander 视图的任务分类必须是确定性字段,至少区分 `business
|
||||
|
||||
stale-active 恢复和 `/api/scheduler/reconcile?staleMs=...` 诊断入口的 heartbeat stale 阈值必须按安全下限归一化:缺省和低于默认 5 分钟的值都按 5 分钟处理,过大值按 24 小时上限截断,并在结构化响应中返回 `requestedStaleMs*`、`staleMsAdjusted`、`staleMsAdjustmentReason`、`minStaleMs` 和 `maxStaleMs`。任何 `staleMs=0` 或过低阈值都不能把仍有 fresh scheduler heartbeat 的任务判成 stale/recoverable。
|
||||
|
||||
`codex queues`、`codex tasks --view commander` 和默认 supervisor 视图的 `activity` / `commanderConcurrency` 是指挥官并发治理的主读数。并发决策固定使用 `commanderConcurrency.activeRunnerCount` 或 commander `activeRunners.count`,它等于 `activity.effectiveActiveTaskCount`;15 并发策略的可补窗口按 `15 - activeRunnerCount` 计算,不能用 `activeQueueIds.length` 或 scheduler-local slot 数替代。`effectiveActiveTaskCount` 表示用于调度判断的有效活跃任务数;`databaseRunningTaskCount` 来自 PostgreSQL 中 `running` 状态计数;`databaseActiveTaskCount` 覆盖 running/judging 等数据库活跃任务;`heartbeatFreshActiveTaskCount` 表示 heartbeat-fresh 的有效 runner 数;`schedulerLocalActiveQueueCount` 和 `schedulerLocalActiveRunSlotCount` 只表示当前控制面本地可见 active run slots。`activeQueueIds` 与 `activeQueueCount` 是 scheduler-local 字段,可能在 `counts.running>0` 且 heartbeat 新鲜时为 0;看到这种组合时应按 `activity.effectiveActiveTaskCount`、`activity.heartbeatFreshActiveTaskCount` 和 `splitBrainLive` 决策,不得把空 `activeQueueIds` 当作零并发或停摆证据。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 仍是 live 且应计入 active runner;`attentionRequired=true` 表示需要人工看一眼或重新 poll,`interventionRequired=true` 才表示当前输出已经足以进入高风险介入路径。单次 heartbeat risk、stale recovery candidates 或 `recommendedAction=investigate-heartbeat-risk` 应先落到 `attentionRequired=true` 加 `re-poll supervisor before recovery`,不得直接等价为恢复授权。
|
||||
`codex queues`、`codex tasks --view commander` 和默认 supervisor 视图的 `activity` / `commanderConcurrency` 是指挥官并发治理的主读数。并发决策固定使用 `commanderConcurrency.activeRunnerCount`、`.data.queues.commander.activeRunnerCount` 或 commander `activeRunners.count`,它等于 `activity.effectiveActiveTaskCount`;15 并发策略的可补窗口按 `15 - activeRunnerCount` 计算,CLI 也会直接给出 `.data.queues.commander.slotDeficit`,不能用 `activeQueueIds.length` 或 scheduler-local slot 数替代。`effectiveActiveTaskCount` 表示用于调度判断的有效活跃任务数;`databaseRunningTaskCount` 来自 PostgreSQL 中 `running` 状态计数;`databaseActiveTaskCount` 覆盖 running/judging 等数据库活跃任务;`heartbeatFreshActiveTaskCount` 表示 heartbeat-fresh 的有效 runner 数;`schedulerLocalActiveQueueCount` 和 `schedulerLocalActiveRunSlotCount` 只表示当前控制面本地可见 active run slots。`activeQueueIds` 与 `activeQueueCount` 是 scheduler-local 字段,可能在 `counts.running>0` 且 heartbeat 新鲜时为 0;看到这种组合时应按 `activity.effectiveActiveTaskCount`、`activity.heartbeatFreshActiveTaskCount`、`.data.queues.commander.runningTasks` 和 `splitBrainLive` 决策,不得把空 `activeQueueIds` 当作零并发或停摆证据。`commanderConcurrency.splitBrainDisposition=live-count-as-active` 表示 split-brain 仍是 live 且应计入 active runner;`attentionRequired=true` 表示需要人工看一眼或重新 poll,`interventionRequired=true` 才表示当前输出已经足以进入高风险介入路径。单次 heartbeat risk、stale recovery candidates 或 `recommendedAction=investigate-heartbeat-risk` 应先落到 `attentionRequired=true` 加 `re-poll supervisor before recovery`,不得直接等价为恢复授权。
|
||||
|
||||
单次 `provider is not online`、SSH 超时、proxy 超时或 registry 请求失败只能证明“当前观察路径失败”,不能单独升级为 D601 全局离线、CI/CD 全局阻塞或业务任务不可推进。指挥官和 runner 必须用多信号裁决运行面状态,至少区分以下观察面:
|
||||
|
||||
|
||||
@@ -140,6 +140,224 @@ function splitBrainLiveResponse(): JsonRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function heartbeatRiskResponse(): JsonRecord {
|
||||
const staleTaskIds = manyIds("task-stale", 4);
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
total: 6,
|
||||
queueCount: 3,
|
||||
activeQueueIds: [],
|
||||
activeTaskIds: [],
|
||||
queuedTaskIds: ["task-queued-risk"],
|
||||
counts: { running: 4, queued: 1, retry_wait: 1 },
|
||||
unreadTerminal: 0,
|
||||
executionDiagnostics: {
|
||||
state: "stale-active",
|
||||
effectiveLiveness: "at-risk",
|
||||
recommendedAction: "investigate-heartbeat-risk",
|
||||
now: "2026-05-20T00:30:00.000Z",
|
||||
databaseActiveTaskCount: 4,
|
||||
databaseActiveTaskIds: staleTaskIds,
|
||||
schedulerActiveRunSlotCount: 0,
|
||||
schedulerActiveTaskIds: [],
|
||||
activeHeartbeatCount: 4,
|
||||
activeHeartbeatTaskIds: staleTaskIds,
|
||||
heartbeatFreshTaskIds: [],
|
||||
heartbeatExpiredTaskIds: staleTaskIds,
|
||||
heartbeatMissingTaskIds: [],
|
||||
staleRecoveryCandidateTaskIds: staleTaskIds,
|
||||
heartbeatRiskTaskIds: staleTaskIds,
|
||||
lastSchedulerHeartbeatAt: "2026-05-20T00:15:00.000Z",
|
||||
lastObservedAgentEventAt: "2026-05-20T00:14:30.000Z",
|
||||
reasons: ["heartbeat expired for database-active tasks"],
|
||||
},
|
||||
},
|
||||
queues: [
|
||||
{
|
||||
id: "risk-a",
|
||||
name: "Risk A",
|
||||
total: 3,
|
||||
counts: { running: 3 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: null,
|
||||
updatedAt: "2026-05-20T00:20:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "risk-b",
|
||||
name: "Risk B",
|
||||
total: 1,
|
||||
counts: { running: 1 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: null,
|
||||
updatedAt: "2026-05-20T00:19:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "risk-queued",
|
||||
name: "Risk Queued",
|
||||
total: 2,
|
||||
counts: { queued: 1, retry_wait: 1 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: "task-queued-risk",
|
||||
updatedAt: "2026-05-20T00:18:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function noActiveResponse(): JsonRecord {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
total: 2,
|
||||
queueCount: 2,
|
||||
activeQueueIds: [],
|
||||
activeTaskIds: [],
|
||||
queuedTaskIds: [],
|
||||
counts: { succeeded: 2 },
|
||||
unreadTerminal: 1,
|
||||
executionDiagnostics: {
|
||||
state: "healthy",
|
||||
effectiveLiveness: "healthy",
|
||||
recommendedAction: "none",
|
||||
databaseActiveTaskCount: 0,
|
||||
databaseActiveTaskIds: [],
|
||||
schedulerActiveRunSlotCount: 0,
|
||||
schedulerActiveTaskIds: [],
|
||||
activeHeartbeatCount: 0,
|
||||
activeHeartbeatTaskIds: [],
|
||||
heartbeatFreshTaskIds: [],
|
||||
heartbeatExpiredTaskIds: [],
|
||||
heartbeatMissingTaskIds: [],
|
||||
staleRecoveryCandidateTaskIds: [],
|
||||
heartbeatRiskTaskIds: [],
|
||||
},
|
||||
},
|
||||
queues: [
|
||||
{
|
||||
id: "done-a",
|
||||
name: "Done A",
|
||||
total: 1,
|
||||
counts: { succeeded: 1 },
|
||||
unreadTerminal: 1,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: null,
|
||||
updatedAt: "2026-05-20T00:05:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "done-b",
|
||||
name: "Done B",
|
||||
total: 1,
|
||||
counts: { succeeded: 1 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: null,
|
||||
updatedAt: "2026-05-20T00:04:00.000Z",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function activeBelowTargetResponse(): JsonRecord {
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
total: 8,
|
||||
queueCount: 3,
|
||||
activeQueueIds: ["feature-a", "feature-b"],
|
||||
activeTaskIds: ["task-active-a", "task-active-b"],
|
||||
queuedTaskIds: ["task-queued-a", "task-queued-b", "task-queued-c"],
|
||||
counts: { running: 2, queued: 3, retry_wait: 1, succeeded: 2 },
|
||||
unreadTerminal: 0,
|
||||
executionDiagnostics: {
|
||||
state: "healthy",
|
||||
effectiveLiveness: "healthy",
|
||||
recommendedAction: "none",
|
||||
databaseActiveTaskCount: 2,
|
||||
databaseActiveTaskIds: ["task-active-a", "task-active-b"],
|
||||
schedulerActiveRunSlotCount: 2,
|
||||
schedulerActiveTaskIds: ["task-active-a", "task-active-b"],
|
||||
activeHeartbeatCount: 2,
|
||||
activeHeartbeatTaskIds: ["task-active-a", "task-active-b"],
|
||||
heartbeatFreshTaskIds: ["task-active-a", "task-active-b"],
|
||||
heartbeatExpiredTaskIds: [],
|
||||
heartbeatMissingTaskIds: [],
|
||||
staleRecoveryCandidateTaskIds: [],
|
||||
heartbeatRiskTaskIds: [],
|
||||
lastSchedulerHeartbeatAt: "2026-05-20T01:00:00.000Z",
|
||||
lastObservedAgentEventAt: "2026-05-20T01:00:05.000Z",
|
||||
},
|
||||
},
|
||||
queues: [
|
||||
{
|
||||
id: "feature-a",
|
||||
name: "Feature A",
|
||||
total: 3,
|
||||
counts: { running: 1, queued: 1, succeeded: 1 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: "task-active-a",
|
||||
runnableTaskId: null,
|
||||
updatedAt: "2026-05-20T01:01:00.000Z",
|
||||
},
|
||||
{
|
||||
id: "feature-b",
|
||||
name: "Feature B",
|
||||
total: 3,
|
||||
counts: { running: 1, retry_wait: 1, succeeded: 1 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: "task-active-b",
|
||||
runnableTaskId: "task-queued-b",
|
||||
updatedAt: "2026-05-20T01:00:30.000Z",
|
||||
},
|
||||
{
|
||||
id: "feature-c",
|
||||
name: "Feature C",
|
||||
total: 2,
|
||||
counts: { queued: 2 },
|
||||
unreadTerminal: 0,
|
||||
activeTaskId: null,
|
||||
runnableTaskId: "task-queued-c",
|
||||
updatedAt: "2026-05-20T01:00:10.000Z",
|
||||
},
|
||||
],
|
||||
tasks: [
|
||||
{
|
||||
id: "task-active-a",
|
||||
queueId: "feature-a",
|
||||
status: "running",
|
||||
displayPrompt: "Implement feature A polish and contract tests",
|
||||
},
|
||||
{
|
||||
id: "task-active-b",
|
||||
queueId: "feature-b",
|
||||
status: "running",
|
||||
displayPrompt: "Fix feature B queue handling",
|
||||
},
|
||||
{
|
||||
id: "task-queued-a",
|
||||
queueId: "feature-a",
|
||||
status: "queued",
|
||||
displayPrompt: "Queued feature A follow-up",
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function assertQueuesShape(label: string, result: unknown, expectedView: string): void {
|
||||
const data = asRecord(result);
|
||||
const queues = asRecord(data.queues);
|
||||
@@ -175,6 +393,12 @@ function assertQueuesShape(label: string, result: unknown, expectedView: string)
|
||||
assertCondition(activity.activeQueueIdsScope === "scheduler-local-active-run-slots", `${label} activity should label activeQueueIds scope`, activity);
|
||||
assertCondition(Array.isArray(queues.activeTaskIds), `${label} activeTaskIds should be present`, queues);
|
||||
assertCondition(Array.isArray(queues.queuedTaskIds), `${label} queuedTaskIds should be present`, queues);
|
||||
const commander = asRecord(queues.commander);
|
||||
assertCondition(commander.activeRunnerCount === 1, `${label} commander block should be near the front and expose active runner count`, commander);
|
||||
assertCondition(commander.target === 15 && commander.slotDeficit === 14, `${label} commander block should expose 15 target slot deficit`, commander);
|
||||
assertCondition(commander.queuedCount === 2, `${label} commander block should expose queued count`, commander);
|
||||
assertCondition(asArray(asRecord(commander.runningTasks).items).length >= 1, `${label} commander runningTasks should expose active task ids`, commander);
|
||||
assertCondition(asArray(asRecord(asRecord(commander.heartbeat).fresh).items).includes("task-running"), `${label} commander heartbeat should foreground fresh ids`, commander);
|
||||
}
|
||||
|
||||
function assertSplitBrainLiveActivity(label: string, result: unknown): void {
|
||||
@@ -209,6 +433,50 @@ function assertSplitBrainLiveActivity(label: string, result: unknown): void {
|
||||
assertCondition(String(commanderConcurrency.decisionRule ?? "").includes("15 - activeRunnerCount"), `${label} should give 15-concurrency arithmetic`, commanderConcurrency);
|
||||
assertCondition(String(activity.activeQueueIdsNote ?? "").includes("zero local queue ids does not mean zero active runners"), `${label} activity note should prevent zero-active misread`, activity);
|
||||
assertCondition(String(activity.interpretation ?? "").includes("continue supervision"), `${label} activity interpretation should keep supervision action`, activity);
|
||||
const commander = asRecord(queues.commander);
|
||||
assertCondition(commander.activeRunnerCount === 8, `${label} commander should foreground heartbeat-effective active count`, commander);
|
||||
assertCondition(commander.slotDeficit === 7 && commander.target === 15, `${label} commander should compute below-target slot deficit`, commander);
|
||||
assertCondition(asRecord(commander.runningTasks).count === 8, `${label} commander runningTasks should preserve live active count`, commander);
|
||||
assertCondition(asRecord(asRecord(commander.heartbeat).fresh).count === 8, `${label} commander heartbeat should preserve fresh heartbeat count`, commander);
|
||||
assertCondition(asRecord(asRecord(commander.heartbeat).risk).count === 0, `${label} commander heartbeat risk should be zero for live split-brain`, commander);
|
||||
}
|
||||
|
||||
function assertHeartbeatRiskCommander(label: string, result: unknown): void {
|
||||
const queues = asRecord(asRecord(result).queues);
|
||||
const commander = asRecord(queues.commander);
|
||||
const heartbeat = asRecord(commander.heartbeat);
|
||||
const risk = asRecord(heartbeat.risk);
|
||||
const stale = asRecord(heartbeat.staleRecoveryCandidates);
|
||||
assertCondition(commander.activeRunnerCount === 4, `${label} should count database-active stale tasks as active before recovery`, commander);
|
||||
assertCondition(commander.slotDeficit === 11 && commander.target === 15, `${label} should compute slot deficit from target 15`, commander);
|
||||
assertCondition(commander.queuedCount === 2, `${label} should expose queued + retry_wait count`, commander);
|
||||
assertCondition(commander.attentionRequired === true && commander.interventionRequired === false, `${label} should require attention but not high-risk intervention from one snapshot`, commander);
|
||||
assertCondition(heartbeat.effectiveLiveness === "at-risk", `${label} heartbeat liveness should be at-risk`, heartbeat);
|
||||
assertCondition(risk.count === 4 && asArray(risk.items).length > 0, `${label} should expose bounded heartbeat risk ids`, risk);
|
||||
assertCondition(stale.count === 4 && String(stale.command ?? "").includes("dryRun=1"), `${label} should expose stale recovery candidates and dry-run command`, stale);
|
||||
}
|
||||
|
||||
function assertNoActiveCommander(label: string, result: unknown): void {
|
||||
const commander = asRecord(asRecord(asRecord(result).queues).commander);
|
||||
assertCondition(commander.activeRunnerCount === 0, `${label} should expose zero active runners`, commander);
|
||||
assertCondition(commander.slotDeficit === 15 && commander.slotDeficitState === "below-target", `${label} should expose full 15-slot deficit`, commander);
|
||||
assertCondition(commander.queuedCount === 0, `${label} queued count should be zero`, commander);
|
||||
assertCondition(asRecord(commander.runningTasks).count === 0, `${label} running task list should be empty`, commander);
|
||||
}
|
||||
|
||||
function assertBelowTargetCommander(label: string, result: unknown): void {
|
||||
const commander = asRecord(asRecord(asRecord(result).queues).commander);
|
||||
const runningTasks = asRecord(commander.runningTasks);
|
||||
const runningItems = asArray(runningTasks.items).map(asRecord);
|
||||
const activeQueues = asRecord(commander.activeQueues);
|
||||
const runnableQueues = asRecord(commander.runnableQueues);
|
||||
assertCondition(commander.activeRunnerCount === 2, `${label} should foreground active runner count`, commander);
|
||||
assertCondition(commander.slotDeficit === 13 && commander.target === 15, `${label} should expose deficit below target`, commander);
|
||||
assertCondition(commander.queuedCount === 4, `${label} should include queued and retry_wait`, commander);
|
||||
assertCondition(runningItems.some((item) => item.id === "task-active-a" && item.name === "Implement feature A polish and contract tests"), `${label} should include active task names when upstream task rows are available`, runningTasks);
|
||||
assertCondition(runningItems.some((item) => asArray(item.queueIds).includes("feature-a") && asArray(item.queueNames).includes("Feature A")), `${label} should include active task queues`, runningTasks);
|
||||
assertCondition(activeQueues.count === 2 && runnableQueues.count === 3, `${label} should keep active and queued queue rows separate`, commander);
|
||||
assertCondition(String(asRecord(commander.commands).running ?? "").includes("--status running,judging"), `${label} should expose running drill-down command`, commander);
|
||||
}
|
||||
|
||||
export function runCodeQueueQueuesShapeContract(): JsonRecord {
|
||||
@@ -220,11 +488,28 @@ export function runCodeQueueQueuesShapeContract(): JsonRecord {
|
||||
assertCondition(path === "/api/microservices/code-queue/proxy/api/queues", "codex queues should use stable proxy path", { path });
|
||||
return splitBrainLiveResponse();
|
||||
};
|
||||
const heartbeatRiskFetcher = (path: string): JsonRecord => {
|
||||
assertCondition(path === "/api/microservices/code-queue/proxy/api/queues", "codex queues should use stable proxy path", { path });
|
||||
return heartbeatRiskResponse();
|
||||
};
|
||||
const noActiveFetcher = (path: string): JsonRecord => {
|
||||
assertCondition(path === "/api/microservices/code-queue/proxy/api/queues", "codex queues should use stable proxy path", { path });
|
||||
return noActiveResponse();
|
||||
};
|
||||
const activeBelowTargetFetcher = (path: string): JsonRecord => {
|
||||
assertCondition(path === "/api/microservices/code-queue/proxy/api/queues", "codex queues should use stable proxy path", { path });
|
||||
return activeBelowTargetResponse();
|
||||
};
|
||||
|
||||
const summary = codexQueuesQueryForTest([], fetcher);
|
||||
assertQueuesShape("summary", summary, "summary");
|
||||
const summaryQueues = asRecord(asRecord(summary).queues);
|
||||
assertCondition(summaryQueues.deprecatedFullArray === undefined, "summary should not expose deprecated full array compatibility field", summaryQueues);
|
||||
assertCondition(summaryQueues.summaryMode === "commander-first", "summary should default to commander-first mode", summaryQueues);
|
||||
|
||||
const explicitCommander = codexQueuesQueryForTest(["--commander"], fetcher);
|
||||
const explicitCommanderQueues = asRecord(asRecord(explicitCommander).queues);
|
||||
assertCondition(explicitCommanderQueues.summaryMode === "commander-first", "--commander should keep commander-first mode", explicitCommanderQueues);
|
||||
|
||||
const full = codexQueuesQueryForTest(["--full"], fetcher);
|
||||
assertQueuesShape("full", full, "full");
|
||||
@@ -255,6 +540,12 @@ export function runCodeQueueQueuesShapeContract(): JsonRecord {
|
||||
assertSplitBrainLiveActivity("split-brain summary", splitSummary);
|
||||
const splitFull = codexQueuesQueryForTest(["--full"], splitBrainFetcher);
|
||||
assertSplitBrainLiveActivity("split-brain full", splitFull);
|
||||
const heartbeatRisk = codexQueuesQueryForTest([], heartbeatRiskFetcher);
|
||||
assertHeartbeatRiskCommander("heartbeat-risk summary", heartbeatRisk);
|
||||
const noActive = codexQueuesQueryForTest([], noActiveFetcher);
|
||||
assertNoActiveCommander("no-active summary", noActive);
|
||||
const belowTarget = codexQueuesQueryForTest([], activeBelowTargetFetcher);
|
||||
assertBelowTargetCommander("active-below-target summary", belowTarget);
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
@@ -268,6 +559,8 @@ export function runCodeQueueQueuesShapeContract(): JsonRecord {
|
||||
"offset pagination",
|
||||
"split-brain live activity counts distinguish scheduler-local queues, DB running tasks, and heartbeat-fresh runners",
|
||||
"commander concurrency block names the active runner count and 15-concurrency rule",
|
||||
"queues commander-first summary foregrounds active runner count, target 15 deficit, running task ids/names/queues, heartbeat risk, stale recovery candidates, and queued count",
|
||||
"queues commander fixtures cover split-brain live, heartbeat risk, no active, and active below target",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ const syntaxFiles = [
|
||||
"scripts/code-queue-cli-read-terminal-contract-test.ts",
|
||||
"scripts/code-queue-gh-auth-redaction-contract-test.ts",
|
||||
"scripts/d601-recovery-guardrails-contract-test.ts",
|
||||
"scripts/code-queue-queues-shape-contract-test.ts",
|
||||
"scripts/microservice-health-output-contract-test.ts",
|
||||
"scripts/code-queue-supervisor-disclosure-contract-test.ts",
|
||||
"scripts/code-queue-commander-view-contract-test.ts",
|
||||
@@ -354,6 +355,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
fileItem("scripts/code-queue-submit-summary-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-gh-auth-redaction-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-queues-shape-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-supervisor-disclosure-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-commander-view-contract-test.ts"),
|
||||
fileItem("scripts/host-codex-commander-skeleton-contract-test.ts"),
|
||||
@@ -408,6 +410,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
items.push(commandItem("code-queue:submit-summary-contract", ["bun", "scripts/code-queue-submit-summary-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:submit-routing-contract", ["bun", "scripts/code-queue-submit-routing-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:gh-auth-redaction-contract", ["bun", "scripts/code-queue-gh-auth-redaction-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:queues-shape-contract", ["bun", "scripts/code-queue-queues-shape-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:supervisor-disclosure-contract", ["bun", "scripts/code-queue-supervisor-disclosure-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("code-queue:commander-view-contract", ["bun", "scripts/code-queue-commander-view-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("host-codex-commander:skeleton-contract", ["bun", "scripts/host-codex-commander-skeleton-contract-test.ts"], 30_000));
|
||||
@@ -450,6 +453,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
items.push(skippedItem("code-queue:submit-summary-contract", "Code Queue submit summary contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:submit-routing-contract", "Code Queue submit routing contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:gh-auth-redaction-contract", "Code Queue GitHub auth output redaction contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:queues-shape-contract", "Code Queue queues shape contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:supervisor-disclosure-contract", "Code Queue supervisor disclosure contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("code-queue:commander-view-contract", "Code Queue commander view contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("host-codex-commander:skeleton-contract", "host Codex commander skeleton contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
|
||||
+251
-6
@@ -37,6 +37,7 @@ const commanderRecentCompletedLimit = 3;
|
||||
const commanderPromptPreviewChars = 96;
|
||||
const commanderBodyPreviewChars = 120;
|
||||
const commanderIssueTaskPreviewLimit = 4;
|
||||
const commanderConcurrencyTarget = 15;
|
||||
const unreadTriageCountLimit = 12;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
@@ -492,6 +493,7 @@ interface SupervisorStatusCounts {
|
||||
|
||||
interface CodexQueuesOptions {
|
||||
full: boolean;
|
||||
commander: boolean;
|
||||
limit: number;
|
||||
offset: number;
|
||||
page: number;
|
||||
@@ -2663,7 +2665,7 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
|
||||
function parseQueuesOptions(args: string[]): CodexQueuesOptions {
|
||||
assertKnownOptions(args, {
|
||||
flags: ["--full", "--all"],
|
||||
flags: ["--full", "--all", "--commander"],
|
||||
valueOptions: ["--limit", "--offset", "--page"],
|
||||
}, "codex queues");
|
||||
const limit = positiveIntegerOption(args, ["--limit"], defaultQueuesLimit, maxTasksLimit);
|
||||
@@ -2672,6 +2674,7 @@ function parseQueuesOptions(args: string[]): CodexQueuesOptions {
|
||||
const offset = offsetExplicit ? nonNegativeIntegerOption(args, ["--offset"], 0) : (page - 1) * limit;
|
||||
return {
|
||||
full: hasFlag(args, "--full") || hasFlag(args, "--all"),
|
||||
commander: hasFlag(args, "--commander") || !hasFlag(args, "--full") && !hasFlag(args, "--all"),
|
||||
limit,
|
||||
offset,
|
||||
page: Math.floor(offset / limit) + 1,
|
||||
@@ -4667,6 +4670,233 @@ function compactQueueRow(value: unknown): Record<string, unknown> {
|
||||
};
|
||||
}
|
||||
|
||||
function taskNameFromRecord(task: Record<string, unknown> | null): string | null {
|
||||
if (task === null) return null;
|
||||
for (const key of ["name", "title", "summary", "displayPrompt", "basePrompt", "prompt"]) {
|
||||
const value = asString(task[key]).replace(/\s+/gu, " ").trim();
|
||||
if (value.length > 0) return value.slice(0, 96);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function buildQueueLookup(queues: Record<string, unknown>[]): Map<string, Record<string, unknown>> {
|
||||
const lookup = new Map<string, Record<string, unknown>>();
|
||||
for (const queue of queues) {
|
||||
const id = asString(queue.id);
|
||||
if (id.length > 0) lookup.set(id, queue);
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
|
||||
function buildTaskLookupFromQueuesBody(body: Record<string, unknown>): Map<string, Record<string, unknown>> {
|
||||
const lookup = new Map<string, Record<string, unknown>>();
|
||||
for (const source of [body.tasks, asRecord(body.overview)?.tasks, asRecord(body.taskPage)?.tasks]) {
|
||||
for (const item of asArray(source)) {
|
||||
const task = asRecord(item);
|
||||
const id = asString(task?.id);
|
||||
if (task !== null && id.length > 0 && !lookup.has(id)) lookup.set(id, task);
|
||||
}
|
||||
}
|
||||
return lookup;
|
||||
}
|
||||
|
||||
function queueIdsForTaskId(taskId: string, queues: Record<string, unknown>[]): string[] {
|
||||
const ids: string[] = [];
|
||||
for (const queue of queues) {
|
||||
const queueId = asString(queue.id);
|
||||
if (queueId.length === 0) continue;
|
||||
if (asString(queue.activeTaskId) === taskId || asString(queue.runnableTaskId) === taskId) ids.push(queueId);
|
||||
}
|
||||
return ids;
|
||||
}
|
||||
|
||||
function compactCommanderTaskList(
|
||||
ids: string[],
|
||||
queues: Record<string, unknown>[],
|
||||
queueLookup: Map<string, Record<string, unknown>>,
|
||||
taskLookup: Map<string, Record<string, unknown>>,
|
||||
limit: number,
|
||||
source: string,
|
||||
totalCount?: number,
|
||||
): Record<string, unknown> {
|
||||
const all = orderedUniqueStringList(ids);
|
||||
const count = Math.max(all.length, totalCount ?? all.length);
|
||||
const items = all.slice(0, limit).map((id) => {
|
||||
const task = taskLookup.get(id) ?? null;
|
||||
const taskQueueId = asString(task?.queueId) || asString(task?.queue) || null;
|
||||
const inferredQueueIds = taskQueueId === null ? queueIdsForTaskId(id, queues) : [taskQueueId];
|
||||
const queueRecords = inferredQueueIds.map((queueId) => queueLookup.get(queueId) ?? null).filter((queue): queue is Record<string, unknown> => queue !== null);
|
||||
const queueNames = queueRecords.map((queue) => asString(queue.name)).filter((name) => name.length > 0);
|
||||
const name = taskNameFromRecord(task);
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
nameSource: name === null ? "not-returned-by-api-queues" : "task-row",
|
||||
queueIds: inferredQueueIds,
|
||||
queueNames,
|
||||
queueSource: taskQueueId !== null
|
||||
? "task-row"
|
||||
: inferredQueueIds.length > 0
|
||||
? "queue-row-active-or-runnable-task-id"
|
||||
: "not-returned-by-api-queues",
|
||||
commands: {
|
||||
show: `bun scripts/cli.ts codex task ${id}`,
|
||||
detail: `bun scripts/cli.ts codex task ${id} --detail`,
|
||||
trace: `bun scripts/cli.ts codex task ${id} --trace --tail --limit ${defaultTraceLimit}`,
|
||||
},
|
||||
};
|
||||
});
|
||||
return {
|
||||
count,
|
||||
returned: items.length,
|
||||
omitted: Math.max(0, count - items.length),
|
||||
truncated: count > items.length || all.length > items.length,
|
||||
idsUnavailable: count > 0 && items.length === 0,
|
||||
source,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function sortedQueueRowsForTaskStatus(queues: Record<string, unknown>[], statuses: string[]): Record<string, unknown>[] {
|
||||
return queues
|
||||
.filter((queue) => statuses.some((status) => countForStatus(asRecord(queue.counts) ?? {}, status) > 0))
|
||||
.sort((left, right) => {
|
||||
const leftUpdated = timestampMs(left.updatedAt) ?? 0;
|
||||
const rightUpdated = timestampMs(right.updatedAt) ?? 0;
|
||||
return rightUpdated - leftUpdated || asString(left.id).localeCompare(asString(right.id));
|
||||
});
|
||||
}
|
||||
|
||||
function compactCommanderQueueRows(queues: Record<string, unknown>[], limit: number, statuses: string[], source: string): Record<string, unknown> {
|
||||
const selected = sortedQueueRowsForTaskStatus(queues, statuses);
|
||||
const items = selected.slice(0, limit).map((queue) => ({
|
||||
id: queue.id ?? null,
|
||||
name: queue.name ?? null,
|
||||
counts: queue.counts ?? {},
|
||||
activeTaskId: queue.activeTaskId ?? null,
|
||||
runnableTaskId: queue.runnableTaskId ?? null,
|
||||
unreadTerminal: queue.unreadTerminal ?? 0,
|
||||
updatedAt: queue.updatedAt ?? null,
|
||||
commands: queue.commands ?? {},
|
||||
}));
|
||||
return {
|
||||
count: selected.length,
|
||||
returned: items.length,
|
||||
omitted: Math.max(0, selected.length - items.length),
|
||||
truncated: selected.length > items.length,
|
||||
source,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function compactCommanderHeartbeatList(ids: string[], totalCount: number, limit: number, source: string, command: string): Record<string, unknown> {
|
||||
const knownIds = orderedUniqueStringList(ids);
|
||||
const preview = compactIdPreview(knownIds, Math.max(totalCount, knownIds.length), limit, source);
|
||||
return {
|
||||
...preview,
|
||||
command,
|
||||
};
|
||||
}
|
||||
|
||||
function heartbeatIdsFromDiagnostics(rawDiagnostics: Record<string, unknown>, diagnostics: Record<string, unknown> | null, keys: string[]): string[] {
|
||||
const ids: string[] = [];
|
||||
for (const key of keys) {
|
||||
ids.push(...idPreviewInputItems(rawDiagnostics[key]));
|
||||
ids.push(...idPreviewInputItems(diagnostics?.[key]));
|
||||
}
|
||||
return orderedUniqueStringList(ids);
|
||||
}
|
||||
|
||||
function buildQueuesCommanderSummary(input: {
|
||||
queue: Record<string, unknown>;
|
||||
queues: Record<string, unknown>[];
|
||||
diagnostics: Record<string, unknown> | null;
|
||||
activity: Record<string, unknown>;
|
||||
commanderConcurrency: Record<string, unknown>;
|
||||
activeTaskIds: { items: string[]; count: number; omitted: number; truncated: boolean };
|
||||
queuedTaskIds: { items: string[]; count: number; omitted: number; truncated: boolean };
|
||||
options: CodexQueuesOptions;
|
||||
body: Record<string, unknown>;
|
||||
}): Record<string, unknown> {
|
||||
const rawDiagnostics = asRecord(input.queue.executionDiagnostics) ?? {};
|
||||
const liveness = asRecord(input.diagnostics?.liveness) ?? {};
|
||||
const recovery = asRecord(input.activity.recovery) ?? asRecord(input.diagnostics?.recovery) ?? {};
|
||||
const activeRunnerCount = asNumber(input.commanderConcurrency.activeRunnerCount, asNumber(input.activity.effectiveActiveTaskCount, 0));
|
||||
const target = commanderConcurrencyTarget;
|
||||
const slotDeficit = Math.max(0, target - activeRunnerCount);
|
||||
const slotDeficitState = activeRunnerCount >= target ? "at-or-above-target" : "below-target";
|
||||
const queuedCount = countForStatus(asRecord(input.queue.counts) ?? {}, "queued") + countForStatus(asRecord(input.queue.counts) ?? {}, "retry_wait");
|
||||
const queueLookup = buildQueueLookup(input.queues);
|
||||
const taskLookup = buildTaskLookupFromQueuesBody(input.body);
|
||||
const activeIds = orderedUniqueStringList([
|
||||
...input.activeTaskIds.items,
|
||||
...idPreviewInputItems(rawDiagnostics.databaseActiveTaskIds),
|
||||
...idPreviewInputItems(rawDiagnostics.heartbeatFreshTaskIds),
|
||||
...idPreviewInputItems(input.diagnostics?.heartbeatFreshTaskIds),
|
||||
]);
|
||||
const queuedIds = orderedUniqueStringList(input.queuedTaskIds.items);
|
||||
const heartbeatFreshIds = heartbeatIdsFromDiagnostics(rawDiagnostics, input.diagnostics, ["heartbeatFreshTaskIds", "activeHeartbeatTaskIds"]);
|
||||
const heartbeatRiskIds = heartbeatIdsFromDiagnostics(rawDiagnostics, input.diagnostics, ["heartbeatRiskTaskIds", "heartbeatExpiredTaskIds", "heartbeatMissingTaskIds"]);
|
||||
const staleCandidateIds = heartbeatIdsFromDiagnostics(rawDiagnostics, input.diagnostics, ["staleRecoveryCandidateTaskIds"]);
|
||||
const taskPreviewLimit = Math.min(input.options.limit, commanderSectionReturnedLimit);
|
||||
return {
|
||||
view: "commander",
|
||||
fieldOrder: [
|
||||
"activeRunnerCount",
|
||||
"source",
|
||||
"target",
|
||||
"slotDeficit",
|
||||
"runningTasks",
|
||||
"heartbeat",
|
||||
"queuedCount",
|
||||
"queueRows",
|
||||
"historyRows",
|
||||
],
|
||||
activeRunnerCount,
|
||||
source: input.commanderConcurrency.activeRunnerCountSource ?? input.activity.effectiveActiveSource ?? "activity.effectiveActiveTaskCount",
|
||||
activeRunnerCountField: input.commanderConcurrency.activeRunnerCountField ?? "activity.effectiveActiveTaskCount",
|
||||
target,
|
||||
slotDeficit,
|
||||
slotDeficitState,
|
||||
queuedCount,
|
||||
queuedSource: "queue.counts.queued + queue.counts.retry_wait",
|
||||
counts: input.queue.counts ?? {},
|
||||
runningTasks: compactCommanderTaskList(activeIds, input.queues, queueLookup, taskLookup, taskPreviewLimit, "activeTaskIds + executionDiagnostics.databaseActiveTaskIds + heartbeatFreshTaskIds", activeRunnerCount),
|
||||
queuedTasks: compactCommanderTaskList(queuedIds, input.queues, queueLookup, taskLookup, taskPreviewLimit, "queue.queuedTaskIds", queuedCount),
|
||||
activeQueues: compactCommanderQueueRows(input.queues, taskPreviewLimit, ["running", "judging"], "per-queue counts running/judging"),
|
||||
runnableQueues: compactCommanderQueueRows(input.queues, taskPreviewLimit, ["queued", "retry_wait"], "per-queue counts queued/retry_wait"),
|
||||
heartbeat: {
|
||||
effectiveLiveness: input.diagnostics?.effectiveLiveness ?? input.activity.effectiveLiveness ?? null,
|
||||
splitBrainLive: input.diagnostics?.splitBrainLive ?? input.activity.splitBrainLive ?? null,
|
||||
lastSchedulerHeartbeatAt: input.diagnostics?.lastSchedulerHeartbeatAt ?? recovery.lastSchedulerHeartbeatAt ?? null,
|
||||
lastObservedAgentEventAt: input.diagnostics?.lastObservedAgentEventAt ?? recovery.lastObservedAgentEventAt ?? null,
|
||||
fresh: compactCommanderHeartbeatList(heartbeatFreshIds, asNumber(input.activity.heartbeatFreshActiveTaskCount, heartbeatFreshIds.length), taskPreviewLimit, "executionDiagnostics.heartbeatFreshTaskIds + activeHeartbeatTaskIds", "bun scripts/cli.ts codex tasks --view supervisor --status running,judging --limit 20"),
|
||||
risk: compactCommanderHeartbeatList(heartbeatRiskIds, asNumber(input.activity.heartbeatRiskTaskCount, heartbeatRiskIds.length), taskPreviewLimit, "executionDiagnostics.heartbeatRiskTaskIds + heartbeatExpiredTaskIds + heartbeatMissingTaskIds", "bun scripts/cli.ts codex tasks --view supervisor --limit 20"),
|
||||
staleRecoveryCandidates: compactCommanderHeartbeatList(staleCandidateIds, asNumber(input.activity.staleRecoveryCandidateTaskCount, staleCandidateIds.length), taskPreviewLimit, "executionDiagnostics.staleRecoveryCandidateTaskIds", "bun scripts/cli.ts microservice proxy code-queue '/api/scheduler/reconcile?dryRun=1' --raw"),
|
||||
recoveryHint: input.commanderConcurrency.recoveryHint ?? recovery.hint ?? liveness.recoveryHint ?? null,
|
||||
},
|
||||
attentionRequired: input.commanderConcurrency.attentionRequired ?? false,
|
||||
interventionRequired: input.commanderConcurrency.interventionRequired ?? false,
|
||||
splitBrainDisposition: input.commanderConcurrency.splitBrainDisposition ?? input.activity.splitBrainDisposition ?? null,
|
||||
decisionRule: `remaining slots = ${target} - activeRunnerCount; slotDeficit is clamped at 0 when activeRunnerCount >= target.`,
|
||||
historyRows: {
|
||||
returned: Math.min(input.options.limit, input.queues.length),
|
||||
secondary: true,
|
||||
stablePath: "data.queues.items[]",
|
||||
note: "Queue history rows are paged and secondary; use commander.runningTasks, heartbeat, and queuedCount first for concurrency decisions.",
|
||||
},
|
||||
commands: {
|
||||
refresh: queueListCommand({ full: input.options.full, commander: input.options.commander, limit: input.options.limit, offset: input.options.offset }),
|
||||
supervisor: "bun scripts/cli.ts codex tasks --view supervisor --limit 20",
|
||||
running: "bun scripts/cli.ts codex tasks --view supervisor --status running,judging --limit 20",
|
||||
unread: "bun scripts/cli.ts codex unread --limit 20",
|
||||
fullQueues: queueListCommand({ full: true, limit: input.options.limit, offset: 0 }),
|
||||
rawQueues: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw --full",
|
||||
rawOverview: rawCodeQueueOverviewCommand,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function queueListCommand(options: Partial<CodexQueuesOptions> = {}): string {
|
||||
const full = options.full === true;
|
||||
const limit = options.limit ?? defaultQueuesLimit;
|
||||
@@ -4674,6 +4904,7 @@ function queueListCommand(options: Partial<CodexQueuesOptions> = {}): string {
|
||||
return [
|
||||
"bun scripts/cli.ts codex queues",
|
||||
full ? "--full" : "",
|
||||
!full && options.commander === true ? "--commander" : "",
|
||||
limit === defaultQueuesLimit ? "" : `--limit ${limit}`,
|
||||
offset > 0 ? `--offset ${offset}` : "",
|
||||
].filter(Boolean).join(" ");
|
||||
@@ -4698,16 +4929,30 @@ function compactQueuesResponse(body: Record<string, unknown>, options: CodexQueu
|
||||
const previousOffset = Math.max(0, options.offset - options.limit);
|
||||
const hasMore = nextOffset < selected.length;
|
||||
const hasPrevious = options.offset > 0;
|
||||
const commander = buildQueuesCommanderSummary({
|
||||
queue,
|
||||
queues,
|
||||
diagnostics,
|
||||
activity,
|
||||
commanderConcurrency,
|
||||
activeTaskIds,
|
||||
queuedTaskIds,
|
||||
options,
|
||||
body,
|
||||
});
|
||||
return {
|
||||
upstream,
|
||||
queues: {
|
||||
view: options.full ? "full" : "summary",
|
||||
summaryMode: options.commander ? "commander-first" : "full-paged",
|
||||
bounded: true,
|
||||
outputPolicy: {
|
||||
default: "paged-low-noise",
|
||||
default: options.commander ? "commander-first-paged-low-noise" : "paged-low-noise",
|
||||
stableItemsPath: "data.queues.items[]",
|
||||
commanderPath: "data.queues.commander",
|
||||
rawFullCommand: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw --full",
|
||||
},
|
||||
commander,
|
||||
count: selected.length,
|
||||
returned: visible.length,
|
||||
limit: options.limit,
|
||||
@@ -4757,10 +5002,10 @@ function compactQueuesResponse(body: Record<string, unknown>, options: CodexQueu
|
||||
}
|
||||
: {}),
|
||||
commands: {
|
||||
refresh: queueListCommand({ full: options.full, limit: options.limit, offset: options.offset }),
|
||||
next: hasMore ? queueListCommand({ full: options.full, limit: options.limit, offset: nextOffset }) : null,
|
||||
previous: hasPrevious ? queueListCommand({ full: options.full, limit: options.limit, offset: previousOffset }) : null,
|
||||
first: queueListCommand({ full: options.full, limit: options.limit, offset: 0 }),
|
||||
refresh: queueListCommand({ full: options.full, commander: options.commander, limit: options.limit, offset: options.offset }),
|
||||
next: hasMore ? queueListCommand({ full: options.full, commander: options.commander, limit: options.limit, offset: nextOffset }) : null,
|
||||
previous: hasPrevious ? queueListCommand({ full: options.full, commander: options.commander, limit: options.limit, offset: previousOffset }) : null,
|
||||
first: queueListCommand({ full: options.full, commander: options.commander, limit: options.limit, offset: 0 }),
|
||||
full: queueListCommand({ full: true, limit: options.limit, offset: 0 }),
|
||||
tasks: `bun scripts/cli.ts codex tasks --view supervisor --limit ${Math.min(options.limit, defaultTasksLimit)}`,
|
||||
unread: `bun scripts/cli.ts codex unread --limit ${Math.min(options.limit, defaultTasksLimit)}`,
|
||||
|
||||
+8
-1
@@ -284,7 +284,7 @@ function codexHelp(): unknown {
|
||||
"bun scripts/cli.ts codex resume <taskId> [prompt|--prompt-file path|--prompt-stdin] [--resume-id id] [--dry-run] [--full|--raw]",
|
||||
"bun scripts/cli.ts codex steer-confirm <taskId> --steer-id <id> [--raw]",
|
||||
"bun scripts/cli.ts codex interrupt|cancel <taskId>",
|
||||
"bun scripts/cli.ts codex queues [--full|--all] [--limit N] [--page N|--offset N] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>",
|
||||
"bun scripts/cli.ts codex queues [--commander] [--full|--all] [--limit N] [--page N|--offset N] | queue create <queueId> | queue merge <sourceQueueId> --into <targetQueueId> | move <taskId> --queue <queueId>",
|
||||
],
|
||||
promptInput: {
|
||||
recommended: ["--prompt-stdin", "--prompt-file"],
|
||||
@@ -313,6 +313,13 @@ function codexHelp(): unknown {
|
||||
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.",
|
||||
},
|
||||
queuesSummary: {
|
||||
default: "codex queues defaults to commander-first summary mode; --commander is an explicit alias, while --full keeps the same bounded queue rows with compatibility metadata.",
|
||||
commanderPath: "data.queues.commander",
|
||||
stableRowsPath: "data.queues.items[]",
|
||||
fields: ["activeRunnerCount", "source", "target=15", "slotDeficit", "runningTasks", "heartbeat.fresh", "heartbeat.risk", "heartbeat.staleRecoveryCandidates", "queuedCount"],
|
||||
rule: "Use data.queues.commander.activeRunnerCount and slotDeficit for quick capacity decisions; activeQueueIds are scheduler-local and can be empty during split-brain live.",
|
||||
},
|
||||
examples: {
|
||||
promptLint: "bun scripts/cli.ts codex prompt-lint --prompt-file /tmp/code-queue-prompt.md",
|
||||
stdin: [
|
||||
|
||||
Reference in New Issue
Block a user