fix: reduce code queue commander CLI noise
This commit is contained in:
+88
-40
@@ -38,6 +38,7 @@ const commanderRecentCompletedLimit = 3;
|
||||
const commanderIssueTaskPreviewLimit = 4;
|
||||
const commanderConcurrencyTarget = 15;
|
||||
const unreadTriageCountLimit = 12;
|
||||
const unreadTriageDefaultItemLimit = 6;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
const mutationQueueIdPreviewLimit = 15;
|
||||
@@ -358,6 +359,7 @@ interface CodexUnreadOptions {
|
||||
statusFilter: string[] | null;
|
||||
repoFilter: string | undefined;
|
||||
issueFilter: string | undefined;
|
||||
view: "summary" | "full";
|
||||
action: CodexUnreadAction;
|
||||
confirm: boolean;
|
||||
dryRun: boolean;
|
||||
@@ -431,11 +433,11 @@ interface CodexTasksSupervisorEntry {
|
||||
}
|
||||
|
||||
type CommanderTaskCategory =
|
||||
| "business-user-facing"
|
||||
| "deployment-artifact"
|
||||
| "ci-e2e-evidence"
|
||||
| "diagnostics-gate-report"
|
||||
| "docs-governance"
|
||||
| "user-facing"
|
||||
| "workflow"
|
||||
| "cd-artifact"
|
||||
| "infra-governance"
|
||||
| "noise-report"
|
||||
| "infrastructure-blocker"
|
||||
| "unknown";
|
||||
|
||||
@@ -444,7 +446,7 @@ type CommanderAttentionSeverity = "critical" | "high" | "medium";
|
||||
interface CommanderTaskClassification {
|
||||
category: CommanderTaskCategory;
|
||||
labels: string[];
|
||||
noiseClass: "delivery" | "evidence" | "governance" | "blocker" | "unknown";
|
||||
noiseClass: "delivery" | "workflow" | "noise" | "governance" | "blocker" | "unknown";
|
||||
reason: string;
|
||||
}
|
||||
|
||||
@@ -2779,8 +2781,8 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
}
|
||||
const optionArgs = hasSubcommand ? args.slice(1) : args;
|
||||
assertKnownOptions(optionArgs, {
|
||||
flags: ["--mark-read", "--confirm", "--dry-run"],
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId"],
|
||||
flags: ["--mark-read", "--confirm", "--dry-run", "--full"],
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId", "--view"],
|
||||
}, "codex unread");
|
||||
const statusRaw = optionValue(optionArgs, ["--status"]);
|
||||
const statusFilter = statusRaw === undefined
|
||||
@@ -2788,6 +2790,9 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
: normalizeCodexStatusFilter(statusRaw, codexTerminalTaskStatuses, "codex unread");
|
||||
const requestedLimit = positiveIntegerOption(optionArgs, ["--limit"], defaultTasksLimit);
|
||||
const action: CodexUnreadAction = subcommand === "mark-read" || hasFlag(optionArgs, "--mark-read") ? "mark-read" : "summary";
|
||||
const viewRaw = optionValue(optionArgs, ["--view"]);
|
||||
if (viewRaw !== undefined && viewRaw !== "summary" && viewRaw !== "full") throw new Error(`codex unread --view must be summary or full; got ${viewRaw}`);
|
||||
const view: CodexUnreadOptions["view"] = hasFlag(optionArgs, "--full") || subcommand === "list" || viewRaw === "full" ? "full" : "summary";
|
||||
const explicitDryRun = hasFlag(optionArgs, "--dry-run");
|
||||
return {
|
||||
queueId: optionValue(optionArgs, ["--queue", "--queue-id"]),
|
||||
@@ -2797,6 +2802,7 @@ function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
statusFilter,
|
||||
repoFilter: normalizeRepoFilter(optionValue(optionArgs, ["--repo"])),
|
||||
issueFilter: normalizeIssueFilter(optionValue(optionArgs, ["--issue"])),
|
||||
view,
|
||||
action,
|
||||
confirm: hasFlag(optionArgs, "--confirm"),
|
||||
dryRun: explicitDryRun || action === "summary",
|
||||
@@ -3074,6 +3080,15 @@ function taskSearchText(task: Record<string, unknown>, summary: Record<string, u
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function hasStrongInfrastructureBlockerSignal(text: string): boolean {
|
||||
const negatedBlockerMention = /\briskcounts\.infrastructureblocker\s*[:=]\s*0\b|\b(?:no|not|without|avoid|must not|should not|do not|don't)\b.{0,60}\b(?:infrastructure[- ]?blocker|infra[- ]?blocker|blocker)\b|(?:不应|不要|不能|避免|无|没有).{0,60}(?:基础设施阻塞|误报|blocker)/iu;
|
||||
const infraContext = /\b(?:provider|gateway|k3s|k3sctl|scheduler|postgres|database|db|storage|heartbeat|proxy|tunnel|auth|token|secret|credential|github|gh auth|rate limit|429|socket\.write|connection_closed)\b|数据库|调度|鉴权|令牌|凭证|不可达|存储/iu;
|
||||
const blockerSignal = /\b(?:infrastructure[- ]?blocker|infra[- ]?blocker|blocked|blocker|cannot proceed|can't proceed|stuck|failed|failure|crash|degraded|unavailable|offline|unreachable|timeout|timed out|connection_closed|null is not an object|permission denied|missing token|auth failed|retry exhausted|rate limit|429)\b|基础设施阻塞|阻塞|故障|失败|崩溃|降级|不可用|离线|不可达|超时|鉴权失败|权限不足/iu;
|
||||
const explicitCodeQueueStorage = /\b(?:postgres|database|db|storage|socket\.write|connection_closed)\b.{0,80}\b(?:failed|failure|crash|degraded|unavailable|timeout|blocked|null is not an object)\b|\b(?:failed|failure|crash|degraded|unavailable|timeout|blocked|null is not an object)\b.{0,80}\b(?:postgres|database|db|storage|socket\.write|connection_closed)\b|数据库.{0,40}(?:故障|失败|崩溃|降级|不可用|超时|阻塞)/iu;
|
||||
if (negatedBlockerMention.test(text) && !explicitCodeQueueStorage.test(text)) return false;
|
||||
return explicitCodeQueueStorage.test(text) || (infraContext.test(text) && blockerSignal.test(text));
|
||||
}
|
||||
|
||||
function issueRefsFromText(text: string): string[] {
|
||||
const issues = new Set<string>();
|
||||
const knownPrefix = (value: string): "HWLAB" | "UniDesk" | null => {
|
||||
@@ -3109,26 +3124,27 @@ function commanderTaskClassification(task: Record<string, unknown>, summary: Rec
|
||||
const text = taskSearchText(task, summary).toLowerCase();
|
||||
const matches = (pattern: RegExp): boolean => pattern.test(text);
|
||||
const labels: string[] = [];
|
||||
if (matches(/\b(?:provider|gateway|k3s|k3sctl|backend-core|scheduler|runner|runtime|heartbeat|stale|tunnel|proxy|auth|token|secret|postgres|database|db|gh auth|github transient|429|rate limit|blocked|blocker|offline|unreachable|timeout)\b|阻塞|故障|超时|鉴权|数据库|调度|运行时/iu)) labels.push("infrastructure-blocker");
|
||||
if (matches(/\b(?:deploy|deployment|rollout|artifact|image|digest|registry|publish|release|ci\/cd|cd)\b|部署|发布|镜像|制品|digest|回滚/iu)) labels.push("deployment-artifact");
|
||||
if (matches(/\b(?:ci|e2e|playwright|smoke|test|tests|typecheck|syntax|lint)\b|自测|冒烟|测试/iu)) labels.push("ci-e2e-evidence");
|
||||
if (matches(/\b(?:diagnostic|diagnostics|gate|report|audit|triage|preflight|observability|summary|brief|board|supervisor|commander|visibility|verification|validate|validation|evidence|proof|check)\b|诊断|报告|审查|汇总|观测|看板|指挥|监督|验证|证据|门禁/iu)) labels.push("diagnostics-gate-report");
|
||||
if (matches(/\b(?:doc|docs|documentation|reference|governance|policy|runbook|ag\.?ents|readme|markdown)\b|文档|参考|治理|规范|策略/iu)) labels.push("docs-governance");
|
||||
if (matches(/\b(?:fix|bug|repair|implement|feature|ui|frontend|backend|api|workbench|patch-panel|box-?simu|gateway-?simu|m3|hardware|hwlab|user-facing|business|用户|工作台|接线|仿真|硬件|业务)\b|修复|实现|功能|用户可见/iu)) labels.push("business-user-facing");
|
||||
if (hasStrongInfrastructureBlockerSignal(text)) labels.push("infrastructure-blocker");
|
||||
if (matches(/\b(?:deploy|deployment|rollout|artifact|image|digest|registry|publish|release|ci\/cd|cd)\b|部署|发布|镜像|制品|digest|回滚/iu)) labels.push("cd-artifact");
|
||||
if (matches(/\b(?:ui|frontend|workbench|patch-panel|box-?simu|gateway-?simu|m3|hardware|hwlab|user-facing|business|customer|product|用户|工作台|接线|仿真|硬件|业务|用户可见)\b/iu)) labels.push("user-facing");
|
||||
if (matches(/\b(?:doc|docs|documentation|reference|governance|policy|runbook|ag\.?ents|readme|markdown|commander|supervisor|scheduler|runner|runtime|queue|code queue|cli|guardrail|guard)\b|文档|参考|治理|规范|策略|指挥|监督|调度|运行时|队列|守卫|边界/iu)) labels.push("infra-governance");
|
||||
if (matches(/\b(?:ci|e2e|playwright|smoke|test|tests|typecheck|syntax|lint|diagnostic|diagnostics|gate|report|audit|triage|preflight|observability|summary|brief|board|visibility|verification|validate|validation|evidence|proof|check)\b|自测|冒烟|测试|诊断|报告|审查|汇总|观测|看板|验证|证据|门禁/iu)) labels.push("noise-report");
|
||||
if (matches(/\b(?:fix|bug|repair|implement|implementation|feature|backend|api|refactor|workflow|flow|state machine)\b|修复|实现|功能|流程/iu)) labels.push("workflow");
|
||||
const ordered: CommanderTaskCategory[] = [
|
||||
"infrastructure-blocker",
|
||||
"deployment-artifact",
|
||||
"business-user-facing",
|
||||
"docs-governance",
|
||||
"ci-e2e-evidence",
|
||||
"diagnostics-gate-report",
|
||||
"cd-artifact",
|
||||
"user-facing",
|
||||
"workflow",
|
||||
"infra-governance",
|
||||
"noise-report",
|
||||
];
|
||||
const category = ordered.find((item) => labels.includes(item)) ?? "unknown";
|
||||
const noiseClass: CommanderTaskClassification["noiseClass"] =
|
||||
category === "infrastructure-blocker" ? "blocker"
|
||||
: category === "business-user-facing" || category === "deployment-artifact" ? "delivery"
|
||||
: category === "ci-e2e-evidence" || category === "diagnostics-gate-report" ? "evidence"
|
||||
: category === "docs-governance" ? "governance"
|
||||
: category === "user-facing" || category === "cd-artifact" ? "delivery"
|
||||
: category === "workflow" ? "workflow"
|
||||
: category === "noise-report" ? "noise"
|
||||
: category === "infra-governance" ? "governance"
|
||||
: "unknown";
|
||||
return {
|
||||
category,
|
||||
@@ -3146,16 +3162,16 @@ function taskClassification(task: Record<string, unknown>, summary: Record<strin
|
||||
} {
|
||||
const commander = commanderTaskClassification(task, summary);
|
||||
const labels = commander.labels;
|
||||
if (commander.category === "deployment-artifact") {
|
||||
return { kind: "deployment-fix", labels, managementNoise: commander.noiseClass === "evidence", reason: commander.reason };
|
||||
if (commander.category === "cd-artifact") {
|
||||
return { kind: "deployment-fix", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "business-user-facing" || commander.category === "infrastructure-blocker") {
|
||||
if (commander.category === "user-facing" || commander.category === "workflow" || commander.category === "infrastructure-blocker") {
|
||||
return { kind: "direct-progress", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "ci-e2e-evidence" || commander.category === "diagnostics-gate-report") {
|
||||
if (commander.category === "noise-report") {
|
||||
return { kind: "verification", labels, managementNoise: true, reason: commander.reason };
|
||||
}
|
||||
if (commander.category === "docs-governance") {
|
||||
if (commander.category === "infra-governance") {
|
||||
return { kind: "documentation", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
return { kind: "unknown", labels, managementNoise: false, reason: commander.reason };
|
||||
@@ -3705,10 +3721,15 @@ function unreadTriageCommand(options: CodexUnreadOptions, action: CodexUnreadAct
|
||||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||||
if (options.view === "full") args.push("--full");
|
||||
args.push(...extra);
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function unreadTriageFullCommand(options: CodexUnreadOptions): string {
|
||||
return unreadTriageCommand({ ...options, view: "full" });
|
||||
}
|
||||
|
||||
function taskTriageSearchText(task: Record<string, unknown>): string {
|
||||
return [
|
||||
asString(task.displayPrompt),
|
||||
@@ -3807,9 +3828,16 @@ function unreadTriageCounts(candidates: Record<string, unknown>[]): Record<strin
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown> {
|
||||
function unreadNextStep(task: Record<string, unknown>): string {
|
||||
const status = asString(task.status);
|
||||
if (status === "failed") return "inspect failure, close out, then read";
|
||||
if (status === "canceled") return "confirm cancellation outcome, then read";
|
||||
return "scan final response, close out, then read";
|
||||
}
|
||||
|
||||
function unreadTriageItem(task: Record<string, unknown>, view: CodexUnreadOptions["view"]): Record<string, unknown> {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
return {
|
||||
const base = {
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status: asString(task.status) || null,
|
||||
@@ -3817,6 +3845,11 @@ function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown
|
||||
issues: taskIssueRefsForTriage(task),
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
finishedAt: asString(task.finishedAt) || null,
|
||||
nextStep: unreadNextStep(task),
|
||||
};
|
||||
if (view !== "full") return base;
|
||||
return {
|
||||
...base,
|
||||
commands: {
|
||||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||||
@@ -3830,16 +3863,20 @@ function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown
|
||||
function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: CodexTasksTaskPage, options: CodexUnreadOptions): {
|
||||
result: Record<string, unknown>;
|
||||
visibleCandidates: Record<string, unknown>[];
|
||||
mutationCandidates: Record<string, unknown>[];
|
||||
} {
|
||||
const allCandidates = unreadSortedCandidates(page.tasks, options);
|
||||
const pagedCandidates = unreadPageCandidates(allCandidates, options);
|
||||
const visibleCandidates = pagedCandidates.slice(0, options.limit);
|
||||
const mutationCandidates = pagedCandidates.slice(0, options.limit);
|
||||
const itemLimit = options.view === "full" || options.action === "mark-read" ? options.limit : Math.min(options.limit, unreadTriageDefaultItemLimit);
|
||||
const visibleCandidates = pagedCandidates.slice(0, itemLimit);
|
||||
const hasMore = pagedCandidates.length > visibleCandidates.length;
|
||||
const nextBeforeId = hasMore ? taskOverviewCandidateKey(visibleCandidates.at(-1) ?? {}) || null : null;
|
||||
const nextCommand = nextBeforeId === null ? null : unreadTriageCommand({ ...options, beforeId: nextBeforeId });
|
||||
const readCommand = unreadTriageCommand(options, "mark-read", ["--confirm"]);
|
||||
const readCommand = unreadTriageCommand({ ...options, view: "summary" }, "mark-read", ["--confirm"]);
|
||||
return {
|
||||
visibleCandidates,
|
||||
mutationCandidates,
|
||||
result: {
|
||||
ok: true,
|
||||
upstream,
|
||||
@@ -3851,35 +3888,43 @@ function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: C
|
||||
status: options.statusFilter,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
view: options.view,
|
||||
limitCapped: options.requestedLimit > options.limit,
|
||||
beforeId: options.beforeId ?? null,
|
||||
},
|
||||
readOnly: options.action === "summary" || options.dryRun,
|
||||
bounded: true,
|
||||
disclosure: {
|
||||
policy: "summary counts plus bounded task ids only; no raw prompt, final response, trace, or output is included by default",
|
||||
policy: options.view === "full"
|
||||
? "explicit full unread view: bounded task rows include per-task drill-down commands; raw prompt, final response, trace, and output still require codex task/output drill-down"
|
||||
: "progressive disclosure: default shows counts, buckets, and a small compact task page only; no raw prompt, final response, trace, output, or repeated per-task command blocks",
|
||||
countBucketLimit: unreadTriageCountLimit,
|
||||
itemLimit: options.limit,
|
||||
itemLimit,
|
||||
defaultItemLimit: unreadTriageDefaultItemLimit,
|
||||
mutationPolicy: "batch mark-read requires codex unread mark-read --confirm; per-task read remains codex read <taskId>",
|
||||
},
|
||||
counts: unreadTriageCounts(allCandidates),
|
||||
newest: {
|
||||
count: allCandidates.length,
|
||||
returned: visibleCandidates.length,
|
||||
fullCandidateLimit: mutationCandidates.length,
|
||||
rowsOmittedByDefault: options.view === "full" ? 0 : Math.max(0, pagedCandidates.length - visibleCandidates.length),
|
||||
hasMore,
|
||||
nextBeforeId,
|
||||
commands: {
|
||||
next: nextCommand,
|
||||
full: unreadTriageFullCommand(options),
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||||
traceTemplate: `bun scripts/cli.ts codex task <taskId> --trace --tail --limit ${defaultTraceLimit}`,
|
||||
outputTemplate: `bun scripts/cli.ts codex output <taskId> --tail --limit ${defaultOutputLimit}`,
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
},
|
||||
items: visibleCandidates.map(unreadTriageItem),
|
||||
items: visibleCandidates.map((task) => unreadTriageItem(task, options.view)),
|
||||
},
|
||||
commands: {
|
||||
refresh: unreadTriageCommand(options),
|
||||
full: unreadTriageFullCommand(options),
|
||||
tasksUnread: taskListCommand({
|
||||
queueId: options.queueId,
|
||||
requestedLimit: Math.min(options.requestedLimit, defaultTasksLimit),
|
||||
@@ -3893,6 +3938,9 @@ function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: C
|
||||
byIssue: "bun scripts/cli.ts codex unread --issue <number>",
|
||||
byStatus: "bun scripts/cli.ts codex unread --status succeeded|failed|canceled",
|
||||
byQueue: "bun scripts/cli.ts codex unread --queue <queueId>",
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
perTaskRead: "bun scripts/cli.ts codex read <taskId>",
|
||||
batchReadDryRun: unreadTriageCommand(options, "mark-read", ["--dry-run"]),
|
||||
batchReadConfirm: readCommand,
|
||||
@@ -3952,11 +4000,11 @@ function codexUnreadMutationResult(result: Record<string, unknown>, options: Cod
|
||||
function codexUnreadTriage(taskArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
const options = parseUnreadOptions(taskArgs);
|
||||
const { upstream, page } = loadCodexTasks(unreadLoadOptions(options), fetcher);
|
||||
const { result, visibleCandidates } = unreadTriageSummary(upstream, page, options);
|
||||
const { result, mutationCandidates } = unreadTriageSummary(upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, mutationCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
for (const task of mutationCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
@@ -4768,11 +4816,11 @@ async function codexUnreadTriageAsync(taskArgs: string[], fetcher: AsyncCodexRes
|
||||
return asString(left.id).localeCompare(asString(right.id));
|
||||
});
|
||||
const page: CodexTasksTaskPage = { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks };
|
||||
const { result, visibleCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||||
const { result, mutationCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, mutationCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
for (const task of mutationCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const readResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
|
||||
Reference in New Issue
Block a user