fix: reduce code queue commander CLI noise

This commit is contained in:
Codex
2026-05-24 05:12:46 +00:00
parent a6a57a8276
commit 97d0f262b2
6 changed files with 217 additions and 51 deletions
@@ -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
View File
@@ -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
View File
@@ -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.",
},