feat(code-queue): add commander tasks view
This commit is contained in:
@@ -44,6 +44,7 @@ const syntaxFiles = [
|
||||
"scripts/code-queue-gh-auth-redaction-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",
|
||||
"src/components/frontend/src/index.ts",
|
||||
"src/components/frontend/src/app.tsx",
|
||||
"src/components/frontend/src/decision-center.tsx",
|
||||
@@ -329,6 +330,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
fileItem("scripts/code-queue-submit-routing-contract-test.ts"),
|
||||
fileItem("scripts/code-queue-gh-auth-redaction-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"),
|
||||
fileItem("scripts/host-codex-commander-no-daemon-smoke-contract-test.ts"),
|
||||
fileItem("scripts/provider-runner-triage-contract-test.ts"),
|
||||
@@ -378,6 +380,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
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: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));
|
||||
items.push(commandItem("host-codex-commander:no-daemon-smoke-contract", ["bun", "scripts/host-codex-commander-no-daemon-smoke-contract-test.ts"], 30_000));
|
||||
items.push(commandItem("provider:runner-triage-contract", ["bun", "scripts/provider-runner-triage-contract-test.ts"], 30_000));
|
||||
@@ -416,6 +419,7 @@ export function runChecks(config: UniDeskConfig, options: CheckOptions = default
|
||||
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: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"));
|
||||
items.push(skippedItem("host-codex-commander:no-daemon-smoke-contract", "host Codex commander no-daemon smoke contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
items.push(skippedItem("provider:runner-triage-contract", "Provider runner triage contract is opt-in with script checks", "--scripts-typecheck or --full"));
|
||||
|
||||
+602
-31
@@ -31,6 +31,12 @@ const supervisorRecentCompletedLimit = 3;
|
||||
const supervisorPromptPreviewChars = 70;
|
||||
const supervisorBodyPreviewChars = 70;
|
||||
const supervisorRecentBodyPreviewChars = 50;
|
||||
const commanderAttentionLimit = 10;
|
||||
const commanderSectionReturnedLimit = 5;
|
||||
const commanderRecentCompletedLimit = 3;
|
||||
const commanderPromptPreviewChars = 96;
|
||||
const commanderBodyPreviewChars = 120;
|
||||
const commanderIssueTaskPreviewLimit = 4;
|
||||
const unreadTriageCountLimit = 12;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
@@ -303,7 +309,7 @@ interface CodexTasksOptions {
|
||||
beforeId: string | undefined;
|
||||
unreadOnly: boolean;
|
||||
statusFilter: string[] | null;
|
||||
view: "supervisor" | "full";
|
||||
view: "commander" | "supervisor" | "full";
|
||||
}
|
||||
|
||||
type CodexUnreadAction = "summary" | "mark-read";
|
||||
@@ -388,6 +394,58 @@ interface CodexTasksSupervisorEntry {
|
||||
read?: string;
|
||||
}
|
||||
|
||||
type CommanderTaskCategory =
|
||||
| "business-user-facing"
|
||||
| "deployment-artifact"
|
||||
| "ci-e2e-evidence"
|
||||
| "diagnostics-gate-report"
|
||||
| "docs-governance"
|
||||
| "infrastructure-blocker"
|
||||
| "unknown";
|
||||
|
||||
type CommanderAttentionSeverity = "critical" | "high" | "medium";
|
||||
|
||||
interface CommanderTaskClassification {
|
||||
category: CommanderTaskCategory;
|
||||
labels: string[];
|
||||
noiseClass: "delivery" | "evidence" | "governance" | "blocker" | "unknown";
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface CommanderAttentionItem {
|
||||
id: string;
|
||||
queue: string | null;
|
||||
status: string | null;
|
||||
statusLabel?: string;
|
||||
severity: CommanderAttentionSeverity;
|
||||
action: "read-closeout" | "inspect-active" | "watch-active" | "inspect-blocker";
|
||||
reasons: string[];
|
||||
riskSignals: string[];
|
||||
issues: string[];
|
||||
highPriorityIssues: string[];
|
||||
classification: CommanderTaskClassification;
|
||||
attempt: number | null;
|
||||
updatedAt: string | null;
|
||||
finishedAt?: string | null;
|
||||
unreadTerminal?: boolean;
|
||||
finalResponseAt?: unknown;
|
||||
prompt: string;
|
||||
promptChars: number;
|
||||
promptTruncated?: boolean;
|
||||
last?: string;
|
||||
lastAt?: unknown;
|
||||
lastChars?: number;
|
||||
lastTruncated?: boolean;
|
||||
commands: {
|
||||
show: string;
|
||||
detail: string;
|
||||
trace: string;
|
||||
output: string;
|
||||
read?: string;
|
||||
full: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CodexTasksSection<T = CodexTasksEntry> {
|
||||
count: number;
|
||||
returned: number;
|
||||
@@ -2343,7 +2401,7 @@ function parseTasksOptions(args: string[]): CodexTasksOptions {
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--view", "--before-id", "--beforeId"],
|
||||
}, "codex tasks");
|
||||
const viewValue = optionValue(args, ["--view"]) ?? "supervisor";
|
||||
if (viewValue !== "supervisor" && viewValue !== "full") throw new Error(`--view must be supervisor or full; got ${viewValue}`);
|
||||
if (viewValue !== "commander" && viewValue !== "supervisor" && viewValue !== "full") throw new Error(`--view must be commander, supervisor, or full; got ${viewValue}`);
|
||||
const statusRaw = optionValue(args, ["--status"]);
|
||||
const statusFilter = statusRaw === undefined
|
||||
? null
|
||||
@@ -2672,7 +2730,89 @@ function taskIssueRefs(task: Record<string, unknown>, summary: Record<string, un
|
||||
asString(summary?.lastError),
|
||||
asString(task.lastError),
|
||||
].join("\n");
|
||||
return Array.from(new Set(Array.from(text.matchAll(/#(\d{1,6})\b/gu)).map((match) => `#${match[1]}`))).slice(0, 8);
|
||||
return issueRefsFromText(text).slice(0, 8);
|
||||
}
|
||||
|
||||
function taskSearchText(task: Record<string, unknown>, summary: Record<string, unknown> | null): string {
|
||||
return [
|
||||
asString(task.id),
|
||||
asString(task.queueId),
|
||||
asString(task.status),
|
||||
asString(task.displayPrompt),
|
||||
asString(task.basePrompt),
|
||||
asString(task.prompt),
|
||||
asString(task.cwd),
|
||||
asString(task.model),
|
||||
asString(task.providerId),
|
||||
asString(task.lastError),
|
||||
asString(summary?.lastError),
|
||||
asString(asRecord(summary?.lastAssistantMessage)?.text),
|
||||
...stringList(task.referenceTaskIds),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function issueRefsFromText(text: string): string[] {
|
||||
const issues = new Set<string>();
|
||||
const knownPrefix = (value: string): "HWLAB" | "UniDesk" | null => {
|
||||
const lower = value.toLowerCase();
|
||||
if (lower === "hwlab") return "HWLAB";
|
||||
if (lower === "unidesk") return "UniDesk";
|
||||
return null;
|
||||
};
|
||||
for (const match of text.matchAll(/\b(HWLAB|UniDesk)#(\d{1,6})\b/giu)) {
|
||||
const prefix = knownPrefix(String(match[1] ?? ""));
|
||||
if (prefix !== null) issues.add(`${prefix}#${match[2]}`);
|
||||
}
|
||||
for (const match of text.matchAll(/\b[A-Za-z0-9_.-]+\/(HWLAB|unidesk)#(\d{1,6})\b/giu)) {
|
||||
const prefix = knownPrefix(String(match[1] ?? ""));
|
||||
if (prefix !== null) issues.add(`${prefix}#${match[2]}`);
|
||||
}
|
||||
for (const match of text.matchAll(/\bgithub\.com[:/][A-Za-z0-9_.-]+\/(HWLAB|unidesk)(?:\.git)?\/issues\/(\d{1,6})\b/giu)) {
|
||||
const prefix = knownPrefix(String(match[1] ?? ""));
|
||||
if (prefix !== null) issues.add(`${prefix}#${match[2]}`);
|
||||
}
|
||||
for (const match of text.matchAll(/(?:^|[^A-Za-z0-9_/-])#(\d{1,6})\b/gu)) issues.add(`#${match[1]}`);
|
||||
for (const match of text.matchAll(/\/issues\/(\d{1,6})\b/gu)) issues.add(`#${match[1]}`);
|
||||
const issueNumber = (value: string): number => Number(value.match(/#(\d{1,6})\b/u)?.[1] ?? Number.MAX_SAFE_INTEGER);
|
||||
return Array.from(issues).sort((left, right) => issueNumber(left) - issueNumber(right) || left.localeCompare(right));
|
||||
}
|
||||
|
||||
function highPriorityIssueRefs(issues: string[]): string[] {
|
||||
const priority = new Set(["HWLAB#7", "HWLAB#99", "HWLAB#116", "HWLAB#164", "HWLAB#317", "UniDesk#20", "UniDesk#118", "#7", "#20", "#99", "#116", "#118", "#164", "#317"]);
|
||||
return issues.filter((issue) => priority.has(issue));
|
||||
}
|
||||
|
||||
function commanderTaskClassification(task: Record<string, unknown>, summary: Record<string, unknown> | null): CommanderTaskClassification {
|
||||
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");
|
||||
const ordered: CommanderTaskCategory[] = [
|
||||
"infrastructure-blocker",
|
||||
"deployment-artifact",
|
||||
"business-user-facing",
|
||||
"docs-governance",
|
||||
"ci-e2e-evidence",
|
||||
"diagnostics-gate-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"
|
||||
: "unknown";
|
||||
return {
|
||||
category,
|
||||
labels: labels.length > 0 ? labels : ["uncategorized"],
|
||||
noiseClass,
|
||||
reason: labels.length > 0 ? `matched ${labels.slice(0, 3).join(", ")}` : "no strong classifier term matched",
|
||||
};
|
||||
}
|
||||
|
||||
function taskClassification(task: Record<string, unknown>, summary: Record<string, unknown> | null): {
|
||||
@@ -2681,38 +2821,21 @@ function taskClassification(task: Record<string, unknown>, summary: Record<strin
|
||||
managementNoise: boolean;
|
||||
reason: string;
|
||||
} {
|
||||
const text = [
|
||||
asString(task.displayPrompt),
|
||||
asString(task.basePrompt),
|
||||
asString(task.prompt),
|
||||
asString(task.lastError),
|
||||
asString(summary?.lastError),
|
||||
asString(asRecord(summary?.lastAssistantMessage)?.text),
|
||||
].join("\n").toLowerCase();
|
||||
const matches = (pattern: RegExp): boolean => pattern.test(text);
|
||||
const labels: string[] = [];
|
||||
if (matches(/\b(?:gate|report|aggregator|runbook|contract|audit|review|brief|evidence|diagnostic|observability|visibility|preflight|smoke)\b|报告|审查|汇总|观测|诊断|预检|门禁/iu)) labels.push("management-or-verification");
|
||||
if (matches(/\b(?:deploy|deployment|prod|dev|release|artifact|ci|cd)\b|部署|发布|上线/iu)) labels.push("deployment");
|
||||
if (matches(/\b(?:fix|bug|repair|implement|feature|ui|frontend|backend|api|database|db|workbench|patch-panel|box-simu|gateway-simu)\b|修复|实现|用户|工作台|接线|仿真|数据库/iu)) labels.push("direct-work");
|
||||
if (matches(/\b(?:doc|docs|reference|markdown)\b|文档|参考/iu)) labels.push("documentation");
|
||||
if (labels.length === 0) labels.push("uncategorized");
|
||||
|
||||
if (labels.includes("management-or-verification") && !labels.includes("direct-work") && !labels.includes("deployment")) {
|
||||
return { kind: "management-noise", labels, managementNoise: true, reason: "matched report/gate/review/diagnostic terms without direct implementation or deployment terms" };
|
||||
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 (labels.includes("deployment")) {
|
||||
return { kind: "deployment-fix", labels, managementNoise: labels.includes("management-or-verification") && !labels.includes("direct-work"), reason: "matched deployment or artifact terms" };
|
||||
if (commander.category === "business-user-facing" || commander.category === "infrastructure-blocker") {
|
||||
return { kind: "direct-progress", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (labels.includes("direct-work")) {
|
||||
return { kind: "direct-progress", labels, managementNoise: false, reason: "matched implementation, user-visible, runtime, or repair terms" };
|
||||
if (commander.category === "ci-e2e-evidence" || commander.category === "diagnostics-gate-report") {
|
||||
return { kind: "verification", labels, managementNoise: true, reason: commander.reason };
|
||||
}
|
||||
if (labels.includes("management-or-verification")) {
|
||||
return { kind: "verification", labels, managementNoise: true, reason: "matched verification/report terms; keep folded unless it blocks real work" };
|
||||
if (commander.category === "docs-governance") {
|
||||
return { kind: "documentation", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
if (labels.includes("documentation")) {
|
||||
return { kind: "documentation", labels, managementNoise: false, reason: "matched documentation terms" };
|
||||
}
|
||||
return { kind: "unknown", labels, managementNoise: false, reason: "no strong classifier term matched" };
|
||||
return { kind: "unknown", labels, managementNoise: false, reason: commander.reason };
|
||||
}
|
||||
|
||||
function supervisorLastMessage(summaryLastAssistant: unknown, maxChars: number): SupervisorMessageSummary | null {
|
||||
@@ -2970,6 +3093,10 @@ function taskListCommand(options: CodexTasksOptions, extra: string[] = []): stri
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function taskListCommandWithView(options: CodexTasksOptions, view: CodexTasksOptions["view"], extra: string[] = []): string {
|
||||
return taskListCommand({ ...options, view }, extra);
|
||||
}
|
||||
|
||||
function codexTasksLimitDisclosure(options: CodexTasksOptions): Record<string, unknown> {
|
||||
return {
|
||||
requestedLimit: options.requestedLimit,
|
||||
@@ -3518,6 +3645,439 @@ function codexUnreadTriage(taskArgs: string[], fetcher: CodexResponseFetcher = c
|
||||
return codexUnreadMutationResult(result, options, results);
|
||||
}
|
||||
|
||||
function taskDrilldownCommands(taskId: string, includeRead: boolean): CommanderAttentionItem["commands"] {
|
||||
return {
|
||||
show: `bun scripts/cli.ts codex task ${taskId}`,
|
||||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||||
trace: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`,
|
||||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||||
...(includeRead ? { read: `bun scripts/cli.ts codex read ${taskId}` } : {}),
|
||||
full: `bun scripts/cli.ts codex task ${taskId} --full`,
|
||||
};
|
||||
}
|
||||
|
||||
function diagnosticTaskSet(record: Record<string, unknown>, keys: string[]): Set<string> {
|
||||
return new Set(keys.flatMap((key) => stringList(record[key])));
|
||||
}
|
||||
|
||||
function taskAgeMsAt(task: Record<string, unknown>, nowIsoValue: unknown): number | null {
|
||||
const nowMs = Date.parse(asString(nowIsoValue));
|
||||
const updatedMs = Date.parse(asString(task.updatedAt));
|
||||
if (!Number.isFinite(nowMs) || !Number.isFinite(updatedMs)) return null;
|
||||
return Math.max(0, nowMs - updatedMs);
|
||||
}
|
||||
|
||||
function blockerLikeFinalResponseSignals(task: Record<string, unknown>, summary: Record<string, unknown> | null): string[] {
|
||||
const lastAssistant = asRecord(summary?.lastAssistantMessage ?? task.lastAssistantMessage);
|
||||
const text = [
|
||||
asString(lastAssistant?.text),
|
||||
asString(task.lastError),
|
||||
asString(summary?.lastError),
|
||||
asString(asRecord(task.lastJudge)?.reason),
|
||||
asString(asRecord(summary?.lastJudge)?.reason),
|
||||
].join("\n").toLowerCase();
|
||||
const signals: string[] = [];
|
||||
const add = (id: string, pattern: RegExp): void => {
|
||||
if (pattern.test(text) && !signals.includes(id)) signals.push(id);
|
||||
};
|
||||
add("blocked", /\b(blocked|blocker|cannot proceed|can't proceed|stuck|waiting for commander|needs authorization|need authorization|requires approval|permission denied)\b|阻塞|卡住|等待授权|需要授权|权限不足/iu);
|
||||
add("infra-auth-network", /\b(auth|token|credential|github transient|dns|rate limit|429|timeout|timed out|unreachable|offline|proxy|tunnel|provider unavailable)\b|鉴权|令牌|网络|超时|不可达/iu);
|
||||
add("merge-or-test-failure", /\b(conflict|merge failed|tests? failed|typecheck failed|syntax failed|build failed|ci failed|e2e failed)\b|冲突|测试失败|构建失败|检查失败/iu);
|
||||
add("not-deployed", /\b(not deployed|not rebuilt|not rolled out|deploy skipped|rollout skipped|needs rollout|requires deploy)\b|未部署|未重建|未上线|需要部署/iu);
|
||||
return signals;
|
||||
}
|
||||
|
||||
function commanderAttentionReasons(
|
||||
task: Record<string, unknown>,
|
||||
summary: Record<string, unknown> | null,
|
||||
diagnostics: Record<string, unknown>,
|
||||
): {
|
||||
reasons: string[];
|
||||
riskSignals: string[];
|
||||
severity: CommanderAttentionSeverity;
|
||||
action: CommanderAttentionItem["action"];
|
||||
} | null {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
const status = asString(task.status);
|
||||
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
|
||||
const awaitingStatus = finalResponseAwaitingTerminalStatus(status || null, summaryLastAssistant);
|
||||
const unreadTerminal = taskUnreadTerminal(task);
|
||||
const issues = taskIssueRefs(task, summary);
|
||||
const priorityIssues = highPriorityIssueRefs(issues);
|
||||
const heartbeatRiskTaskIds = diagnosticTaskSet(diagnostics, ["heartbeatRiskTaskIds", "heartbeatExpiredTaskIds", "heartbeatMissingTaskIds", "staleRecoveryCandidateTaskIds"]);
|
||||
const traceGapTaskIds = diagnosticTaskSet(diagnostics, ["traceGapTaskIds"]);
|
||||
const staleRecoveryTaskIds = diagnosticTaskSet(diagnostics, ["staleRecoveryCandidateTaskIds"]);
|
||||
const blockerSignals = blockerLikeFinalResponseSignals(task, summary);
|
||||
const staleAgeMs = taskAgeMsAt(task, diagnostics.now);
|
||||
const staleActiveByAge = isActiveTaskStatus(status) && staleAgeMs !== null && staleAgeMs >= 45 * 60 * 1000;
|
||||
const reasons: string[] = [];
|
||||
const riskSignals: string[] = [];
|
||||
if (unreadTerminal) {
|
||||
reasons.push(status === "failed" ? "failed terminal unread; read and close out" : "terminal unread; read and close out");
|
||||
riskSignals.push(status === "failed" ? "failed-terminal-unread" : "terminal-unread");
|
||||
}
|
||||
if (isActiveTaskStatus(status) && heartbeatRiskTaskIds.has(taskId)) {
|
||||
reasons.push("heartbeat/stale-recovery risk is present for this active task");
|
||||
riskSignals.push(staleRecoveryTaskIds.has(taskId) ? "stale-recovery-candidate" : "heartbeat-risk");
|
||||
}
|
||||
if (isActiveTaskStatus(status) && traceGapTaskIds.has(taskId)) {
|
||||
reasons.push("trace progress gap is reported for this active task");
|
||||
riskSignals.push("trace-gap");
|
||||
}
|
||||
if (staleActiveByAge) {
|
||||
reasons.push("active task updatedAt is older than 45 minutes relative to execution diagnostics");
|
||||
riskSignals.push("updatedAt-stale");
|
||||
}
|
||||
if (awaitingStatus !== null) {
|
||||
reasons.push(awaitingStatus.state === "awaiting-judge" ? "final response is visible while judge is still pending" : "final response is visible while task is still running");
|
||||
riskSignals.push(awaitingStatus.state);
|
||||
}
|
||||
if (isActiveTaskStatus(status) && blockerSignals.length > 0) {
|
||||
reasons.push("latest assistant/final response has blocker-like language");
|
||||
riskSignals.push(...blockerSignals);
|
||||
}
|
||||
if ((status === "queued" || status === "retry_wait") && priorityIssues.length > 0) {
|
||||
reasons.push("queued/retry_wait task references a tracked high-priority issue");
|
||||
riskSignals.push("high-priority-queued");
|
||||
}
|
||||
if (isActiveTaskStatus(status) && priorityIssues.length > 0) {
|
||||
reasons.push("active task references a tracked high-priority issue");
|
||||
riskSignals.push("high-priority-active");
|
||||
}
|
||||
if (reasons.length === 0) return null;
|
||||
const uniqueRiskSignals = Array.from(new Set(riskSignals));
|
||||
const severity: CommanderAttentionSeverity = uniqueRiskSignals.some((signal) => signal === "failed-terminal-unread" || signal === "heartbeat-risk" || signal === "stale-recovery-candidate")
|
||||
? "critical"
|
||||
: uniqueRiskSignals.some((signal) => signal === "terminal-unread" || signal === "blocked" || signal === "infra-auth-network" || signal === "merge-or-test-failure" || signal === "not-deployed" || signal === "updatedAt-stale")
|
||||
? "high"
|
||||
: "medium";
|
||||
const action: CommanderAttentionItem["action"] = unreadTerminal
|
||||
? "read-closeout"
|
||||
: isActiveTaskStatus(status)
|
||||
? severity === "medium" ? "watch-active" : "inspect-active"
|
||||
: "inspect-blocker";
|
||||
return { reasons: Array.from(new Set(reasons)), riskSignals: uniqueRiskSignals, severity, action };
|
||||
}
|
||||
|
||||
function commanderAttentionItem(
|
||||
task: Record<string, unknown>,
|
||||
summary: Record<string, unknown> | null,
|
||||
diagnostics: Record<string, unknown>,
|
||||
): CommanderAttentionItem | null {
|
||||
const attention = commanderAttentionReasons(task, summary, diagnostics);
|
||||
if (attention === null) return null;
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
const status = asString(task.status) || null;
|
||||
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
|
||||
const awaitingStatus = finalResponseAwaitingTerminalStatus(status, summaryLastAssistant);
|
||||
const prompt = supervisorTextSummary(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), commanderPromptPreviewChars);
|
||||
const lastMessage = supervisorLastMessage(summaryLastAssistant, commanderBodyPreviewChars);
|
||||
const issues = taskIssueRefs(task, summary);
|
||||
const unreadTerminal = taskUnreadTerminal(task);
|
||||
return {
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status,
|
||||
...(awaitingStatus === null ? {} : {
|
||||
statusLabel: awaitingStatus.label,
|
||||
finalResponseAt: awaitingStatus.finalResponseAt,
|
||||
}),
|
||||
severity: attention.severity,
|
||||
action: attention.action,
|
||||
reasons: attention.reasons,
|
||||
riskSignals: attention.riskSignals,
|
||||
issues,
|
||||
highPriorityIssues: highPriorityIssueRefs(issues),
|
||||
classification: commanderTaskClassification(task, summary),
|
||||
attempt: typeof task.currentAttempt === "number" && Number.isFinite(task.currentAttempt) ? task.currentAttempt : null,
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
...(isTerminalTaskStatus(status) ? { finishedAt: asString(task.finishedAt) || null, unreadTerminal } : {}),
|
||||
prompt: prompt.text,
|
||||
promptChars: prompt.chars,
|
||||
...(prompt.truncated ? { promptTruncated: true } : {}),
|
||||
...(lastMessage === null ? {} : {
|
||||
last: lastMessage.text,
|
||||
lastAt: lastMessage.at,
|
||||
lastChars: lastMessage.chars,
|
||||
...(lastMessage.truncated ? { lastTruncated: true } : {}),
|
||||
}),
|
||||
commands: taskDrilldownCommands(taskId, unreadTerminal),
|
||||
};
|
||||
}
|
||||
|
||||
function commanderAttentionRank(item: CommanderAttentionItem): number {
|
||||
const severityRank: Record<CommanderAttentionSeverity, number> = { critical: 0, high: 1, medium: 2 };
|
||||
const actionRank: Record<CommanderAttentionItem["action"], number> = {
|
||||
"read-closeout": 0,
|
||||
"inspect-active": 1,
|
||||
"inspect-blocker": 2,
|
||||
"watch-active": 3,
|
||||
};
|
||||
return severityRank[item.severity] * 10 + actionRank[item.action];
|
||||
}
|
||||
|
||||
function commanderIdSection(tasks: Record<string, unknown>[], summaries: Map<string, Record<string, unknown>>, limit: number, nextCommand: string | null, fullCommand: string): Record<string, unknown> {
|
||||
const visible = tasks.slice(0, limit);
|
||||
return {
|
||||
count: tasks.length,
|
||||
returned: visible.length,
|
||||
omitted: Math.max(0, tasks.length - visible.length),
|
||||
truncated: tasks.length > visible.length,
|
||||
hasMore: tasks.length > visible.length || nextCommand !== null,
|
||||
commands: {
|
||||
next: tasks.length > visible.length || nextCommand !== null ? nextCommand : null,
|
||||
full: fullCommand,
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
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: visible.map((task) => {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
const summary = summaries.get(taskId) ?? null;
|
||||
const issues = taskIssueRefs(task, summary);
|
||||
return {
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status: asString(task.status) || null,
|
||||
issues,
|
||||
highPriorityIssues: highPriorityIssueRefs(issues),
|
||||
category: commanderTaskClassification(task, summary).category,
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
finishedAt: asString(task.finishedAt) || null,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function commanderClassificationCounts(tasks: Record<string, unknown>[], summaries: Map<string, Record<string, unknown>>): Record<string, unknown> {
|
||||
const byCategory: Record<string, number> = {};
|
||||
const byNoiseClass: Record<string, number> = {};
|
||||
for (const task of tasks) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
const classification = commanderTaskClassification(task, summaries.get(taskId) ?? null);
|
||||
byCategory[classification.category] = (byCategory[classification.category] ?? 0) + 1;
|
||||
byNoiseClass[classification.noiseClass] = (byNoiseClass[classification.noiseClass] ?? 0) + 1;
|
||||
}
|
||||
return {
|
||||
byCategory,
|
||||
byNoiseClass,
|
||||
categories: ["business-user-facing", "deployment-artifact", "ci-e2e-evidence", "diagnostics-gate-report", "docs-governance", "infrastructure-blocker", "unknown"],
|
||||
deterministic: true,
|
||||
sourceFields: ["task prompt previews", "task metadata", "summary lastAssistantMessage preview when fetched"],
|
||||
};
|
||||
}
|
||||
|
||||
function commanderHighPriorityIssues(tasks: Record<string, unknown>[], summaries: Map<string, Record<string, unknown>>): Record<string, unknown> {
|
||||
const tracked = ["HWLAB#7", "HWLAB#99", "HWLAB#116", "HWLAB#164", "HWLAB#317", "UniDesk#20", "UniDesk#118"];
|
||||
const byIssue = new Map<string, string[]>();
|
||||
for (const task of tasks) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
const issues = highPriorityIssueRefs(taskIssueRefs(task, summaries.get(taskId) ?? null));
|
||||
for (const issue of issues) {
|
||||
const existing = byIssue.get(issue) ?? [];
|
||||
if (!existing.includes(taskId)) existing.push(taskId);
|
||||
byIssue.set(issue, existing);
|
||||
}
|
||||
}
|
||||
const issueNumber = (value: string): number => Number(value.match(/#(\d{1,6})\b/u)?.[1] ?? Number.MAX_SAFE_INTEGER);
|
||||
const items = Array.from(byIssue.entries())
|
||||
.sort(([left], [right]) => issueNumber(left) - issueNumber(right) || left.localeCompare(right))
|
||||
.map(([issue, taskIds]) => ({
|
||||
issue,
|
||||
taskCount: taskIds.length,
|
||||
returnedTaskIds: taskIds.slice(0, commanderIssueTaskPreviewLimit),
|
||||
omittedTaskIds: Math.max(0, taskIds.length - commanderIssueTaskPreviewLimit),
|
||||
}));
|
||||
return {
|
||||
tracked,
|
||||
present: items.length > 0,
|
||||
matchedCount: items.length,
|
||||
returned: items.length,
|
||||
items,
|
||||
};
|
||||
}
|
||||
|
||||
function attentionCounts(items: CommanderAttentionItem[], returnedItems: CommanderAttentionItem[]): Record<string, unknown> {
|
||||
const countBy = (source: CommanderAttentionItem[], key: "severity" | "action"): Record<string, number> => source.reduce((counts, item) => {
|
||||
const value = item[key];
|
||||
counts[value] = (counts[value] ?? 0) + 1;
|
||||
return counts;
|
||||
}, {} as Record<string, number>);
|
||||
return {
|
||||
total: items.length,
|
||||
returned: returnedItems.length,
|
||||
omitted: Math.max(0, items.length - returnedItems.length),
|
||||
bySeverity: countBy(items, "severity"),
|
||||
returnedBySeverity: countBy(returnedItems, "severity"),
|
||||
byAction: countBy(items, "action"),
|
||||
};
|
||||
}
|
||||
|
||||
function terminalUnreadAggregateCount(taskPage: CodexTasksTaskPage, options: CodexTasksOptions, fallback: number): { total: number; exact: boolean; source: string } {
|
||||
const queue = taskPage.queue;
|
||||
if (queue !== null && options.queueId === undefined) {
|
||||
const count = positiveCount(queue.unreadTerminal);
|
||||
if (count !== null) return { total: count, exact: true, source: "queue-summary-unreadTerminal" };
|
||||
}
|
||||
if (queue !== null && options.queueId !== undefined) {
|
||||
for (const item of asArray(queue.queues)) {
|
||||
const record = asRecord(item);
|
||||
if (record === null || asString(record.id) !== options.queueId) continue;
|
||||
const count = positiveCount(record.unreadTerminal);
|
||||
if (count !== null) return { total: count, exact: true, source: "queue-row-unreadTerminal" };
|
||||
}
|
||||
}
|
||||
return { total: fallback, exact: false, source: "overview-page-fallback" };
|
||||
}
|
||||
|
||||
function codexTasksCommanderResult(
|
||||
taskPage: CodexTasksTaskPage,
|
||||
upstream: { ok: unknown; status: unknown },
|
||||
options: CodexTasksOptions,
|
||||
summaries: Map<string, Record<string, unknown>>,
|
||||
degraded: CodexTasksDegraded | null,
|
||||
): Record<string, unknown> {
|
||||
const allTasks = filterTasksForOptions(taskPage.tasks, options);
|
||||
const statusCounts = statusCountsForOptions(taskPage, options);
|
||||
const rawQueue = asRecord(taskPage.queue) ?? {};
|
||||
const rawDiagnostics = asRecord(rawQueue.executionDiagnostics) ?? {};
|
||||
const diagnostics = supervisorExecutionDiagnostics(rawDiagnostics);
|
||||
const activity = compactCodeQueueActivity(rawQueue, diagnostics);
|
||||
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
|
||||
const runningTasks = sortRunningWatchTasks(allTasks);
|
||||
const unreadCompletedTasks = sortCompletedWatchTasks(allTasks).filter((task) => taskUnreadTerminal(task));
|
||||
const recentCompletedTasks = options.unreadOnly ? [] : sortCompletedWatchTasks(allTasks).filter((task) => !taskUnreadTerminal(task));
|
||||
const queuedRetryTasks = options.unreadOnly ? [] : sortQueuedWatchTasks(allTasks);
|
||||
const nextBeforeId = asString(taskPage.pagination.nextBeforeId) || null;
|
||||
const sourceHasMore = asBoolean(taskPage.pagination.hasMore);
|
||||
const nextCommand = sourceHasMore && nextBeforeId !== null ? taskListCommand({ ...options, beforeId: nextBeforeId }) : null;
|
||||
const fullCommand = taskListCommandWithView(options, "full");
|
||||
const supervisorCommand = taskListCommandWithView(options, "supervisor");
|
||||
const activeSection = buildSupervisorTaskSection(runningTasks, summaries, taskSectionLimit(options), sectionNextCommand(runningTasks, taskSectionLimit(options), options, nextCommand), fullCommand);
|
||||
const activeRunning = supervisorActiveRunningSummary(taskPage, options, activeSection, diagnostics);
|
||||
const attentionItems = allTasks
|
||||
.map((task) => commanderAttentionItem(task, summaries.get(taskOverviewCandidateKey(task)) ?? null, rawDiagnostics))
|
||||
.filter((item): item is CommanderAttentionItem => item !== null)
|
||||
.sort((left, right) => {
|
||||
const rankDelta = commanderAttentionRank(left) - commanderAttentionRank(right);
|
||||
if (rankDelta !== 0) return rankDelta;
|
||||
const timeDelta = Date.parse(right.updatedAt ?? "") - Date.parse(left.updatedAt ?? "");
|
||||
if (Number.isFinite(timeDelta) && timeDelta !== 0) return timeDelta;
|
||||
return left.id.localeCompare(right.id);
|
||||
});
|
||||
const returnedAttention = attentionItems.slice(0, commanderAttentionLimit);
|
||||
const activeRiskTasks = runningTasks.filter((task) => commanderAttentionReasons(task, summaries.get(taskOverviewCandidateKey(task)) ?? null, rawDiagnostics) !== null);
|
||||
const queued = statusCounts.counts.queued ?? 0;
|
||||
const retryWait = statusCounts.counts.retry_wait ?? 0;
|
||||
const failedUnread = unreadCompletedTasks.filter((task) => asString(task.status) === "failed").length;
|
||||
const canceledUnread = unreadCompletedTasks.filter((task) => asString(task.status) === "canceled").length;
|
||||
const succeededUnread = unreadCompletedTasks.filter((task) => asString(task.status) === "succeeded").length;
|
||||
const terminalUnreadAggregate = terminalUnreadAggregateCount(taskPage, options, unreadCompletedTasks.length);
|
||||
return {
|
||||
upstream,
|
||||
commander: {
|
||||
filters: {
|
||||
view: "commander",
|
||||
queueId: options.queueId ?? null,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
...codexTasksLimitDisclosure(options),
|
||||
unreadOnly: options.unreadOnly,
|
||||
status: options.statusFilter,
|
||||
beforeId: options.beforeId ?? null,
|
||||
},
|
||||
source: { queueId: options.queueId ?? null, ...codexTasksSourceDisclosure(taskPage.pagination) },
|
||||
bounded: true,
|
||||
disclosure: {
|
||||
recommendedFor: "host commander supervision loops",
|
||||
policy: "bounded action map only; no full prompt, final response, trace, output, or raw overview body is included by default",
|
||||
attentionLimit: commanderAttentionLimit,
|
||||
sectionReturnedLimit: commanderSectionReturnedLimit,
|
||||
promptPreviewChars: commanderPromptPreviewChars,
|
||||
bodyPreviewChars: commanderBodyPreviewChars,
|
||||
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
|
||||
},
|
||||
activeRunners: {
|
||||
count: asNumber(commanderConcurrency.activeRunnerCount, asNumber(activity.effectiveActiveTaskCount, activeRunning.effectiveActiveRunnerCount as number)),
|
||||
exact: activeRunning.exact,
|
||||
countField: commanderConcurrency.activeRunnerCountField ?? "activity.effectiveActiveTaskCount",
|
||||
source: activity.effectiveActiveSource ?? "unknown",
|
||||
disposition: commanderConcurrency.splitBrainDisposition ?? activity.splitBrainDisposition ?? null,
|
||||
interventionRequired: commanderConcurrency.interventionRequired ?? null,
|
||||
interventionReason: commanderConcurrency.interventionReason ?? null,
|
||||
statusCounts: {
|
||||
running: statusCounts.counts.running ?? 0,
|
||||
judging: statusCounts.counts.judging ?? 0,
|
||||
source: statusCounts.source,
|
||||
exact: statusCounts.exact,
|
||||
},
|
||||
databaseRunning: activity.databaseRunningTaskCount,
|
||||
databaseActive: activity.databaseActiveTaskCount,
|
||||
heartbeatFreshActive: activity.heartbeatFreshActiveTaskCount,
|
||||
schedulerLocalActiveQueues: activity.schedulerLocalActiveQueueCount,
|
||||
schedulerLocalActiveRunSlots: activity.schedulerLocalActiveRunSlotCount,
|
||||
},
|
||||
queueBacklog: {
|
||||
queued,
|
||||
retryWait,
|
||||
total: queued + retryWait,
|
||||
exact: statusCounts.exact,
|
||||
source: statusCounts.source,
|
||||
},
|
||||
terminalUnread: {
|
||||
total: terminalUnreadAggregate.total,
|
||||
failed: failedUnread,
|
||||
canceled: canceledUnread,
|
||||
succeeded: succeededUnread,
|
||||
rowsReturned: unreadCompletedTasks.length,
|
||||
rowsOmitted: Math.max(0, terminalUnreadAggregate.total - unreadCompletedTasks.length),
|
||||
exact: terminalUnreadAggregate.exact,
|
||||
source: terminalUnreadAggregate.source,
|
||||
statusBreakdownSource: "fetched-priority-rows",
|
||||
},
|
||||
riskCounts: {
|
||||
attention: attentionCounts(attentionItems, returnedAttention),
|
||||
activeRisks: activeRiskTasks.length,
|
||||
heartbeatRiskTaskIds: stringList(rawDiagnostics.heartbeatRiskTaskIds).length,
|
||||
staleRecoveryCandidateTaskIds: stringList(rawDiagnostics.staleRecoveryCandidateTaskIds).length,
|
||||
traceGapTaskIds: stringList(rawDiagnostics.traceGapTaskIds).length,
|
||||
awaitingTerminalOrJudge: attentionItems.filter((item) => item.riskSignals.includes("awaiting-terminal-or-judge") || item.riskSignals.includes("awaiting-judge")).length,
|
||||
blockerLikeFinalResponse: attentionItems.filter((item) => item.riskSignals.some((signal) => signal === "blocked" || signal === "infra-auth-network" || signal === "merge-or-test-failure" || signal === "not-deployed")).length,
|
||||
},
|
||||
highPriorityIssues: commanderHighPriorityIssues(allTasks, summaries),
|
||||
classification: commanderClassificationCounts(allTasks, summaries),
|
||||
executionDiagnostics: diagnostics,
|
||||
degraded,
|
||||
commands: {
|
||||
refresh: taskListCommand(options),
|
||||
supervisor: supervisorCommand,
|
||||
full: fullCommand,
|
||||
unread: `bun scripts/cli.ts codex unread --limit ${Math.min(options.requestedLimit, defaultTasksLimit)}`,
|
||||
running: taskListCommandWithView({ ...baseTaskListOptions({ ...options, unreadOnly: false, beforeId: undefined }), statusFilter: ["running", "judging"] }, "supervisor"),
|
||||
queued: taskListCommandWithView({ ...baseTaskListOptions({ ...options, unreadOnly: false, beforeId: undefined }), statusFilter: ["queued", "retry_wait"] }, "supervisor"),
|
||||
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
|
||||
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>",
|
||||
},
|
||||
attention: {
|
||||
...attentionCounts(attentionItems, returnedAttention),
|
||||
truncated: attentionItems.length > returnedAttention.length,
|
||||
items: returnedAttention,
|
||||
},
|
||||
sections: {
|
||||
activeNeedsAttention: commanderIdSection(activeRiskTasks, summaries, commanderSectionReturnedLimit, taskListCommandWithView({ ...options, statusFilter: ["running", "judging"] }, "supervisor"), fullCommand),
|
||||
terminalUnread: commanderIdSection(unreadCompletedTasks, summaries, commanderSectionReturnedLimit, taskListCommandWithView({ ...options, unreadOnly: true }, "supervisor"), fullCommand),
|
||||
queuedRetryWait: commanderIdSection(queuedRetryTasks, summaries, commanderSectionReturnedLimit, taskListCommandWithView({ ...options, statusFilter: ["queued", "retry_wait"] }, "supervisor"), fullCommand),
|
||||
recentCompleted: commanderIdSection(recentCompletedTasks, summaries, commanderRecentCompletedLimit, nextCommand, fullCommand),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function codexTasksOverviewResult(
|
||||
taskPage: CodexTasksTaskPage,
|
||||
upstream: { ok: unknown; status: unknown },
|
||||
@@ -3525,6 +4085,7 @@ function codexTasksOverviewResult(
|
||||
summaries: Map<string, Record<string, unknown>>,
|
||||
degraded: CodexTasksDegraded | null,
|
||||
): Record<string, unknown> {
|
||||
if (options.view === "commander") return codexTasksCommanderResult(taskPage, upstream, options, summaries, degraded);
|
||||
if (options.view === "full") return codexTasksFullResult(taskPage, upstream, options, summaries, degraded);
|
||||
const allTasks = filterTasksForOptions(taskPage.tasks, options);
|
||||
const runningTasks = sortRunningWatchTasks(allTasks);
|
||||
@@ -3677,6 +4238,16 @@ function visibleTaskIdsForOverview(tasks: Record<string, unknown>[], options: Co
|
||||
return filtered.slice(0, options.limit).map((task) => taskOverviewCandidateKey(task)).filter((taskId) => taskId.length > 0);
|
||||
}
|
||||
const sectionLimit = taskSectionLimit(options);
|
||||
if (options.view === "commander") {
|
||||
return Array.from(new Set([
|
||||
...sortRunningWatchTasks(filtered),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)),
|
||||
...sortQueuedWatchTasks(filtered).slice(0, commanderSectionReturnedLimit),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => !taskUnreadTerminal(task)).slice(0, commanderRecentCompletedLimit),
|
||||
].map((task) => taskOverviewCandidateKey(task))))
|
||||
.filter((taskId) => taskId.length > 0)
|
||||
.slice(0, maxTasksLimit);
|
||||
}
|
||||
return Array.from(new Set([
|
||||
...sortRunningWatchTasks(filtered).slice(0, sectionLimit),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)).slice(0, sectionLimit),
|
||||
|
||||
+2
-2
@@ -57,7 +57,7 @@ export function rootHelp(): unknown {
|
||||
{ command: "codex skills-sync --dry-run [--full]", description: "Inspect the controlled runner skills hostPath lifecycle contract without copying files, restarting services, reading secrets, or mutating live runner paths." },
|
||||
{ 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 supervisor|full] [--queue id] [--status status[,status]] [--unread|--unread-only] [--limit N] [--before-id id]", description: "Show the low-noise supervisor view by default: compact task rows, tiny local sections, activity counts, diagnostics, and drill-down commands; use --view full for detailed rows." },
|
||||
{ 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 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." },
|
||||
@@ -259,7 +259,7 @@ function codexHelp(): unknown {
|
||||
"cat <<'PROMPT' | bun scripts/cli.ts codex submit --prompt-stdin --queue <id> --dry-run",
|
||||
"bun scripts/cli.ts codex submit --prompt-file /tmp/code-queue-prompt.md --queue <id> --dry-run",
|
||||
"bun scripts/cli.ts codex task <taskId> [--detail] [--trace --tail|--from-start|--after-seq N|--before-seq N --limit N] [--full]",
|
||||
"bun scripts/cli.ts codex tasks [--view supervisor|full] [--queue id] [--status succeeded,running] [--unread|--unread-only] [--limit N] [--before-id id]",
|
||||
"bun scripts/cli.ts codex tasks [--view commander|supervisor|full] [--queue id] [--status succeeded,running] [--unread|--unread-only] [--limit N] [--before-id id]",
|
||||
"bun scripts/cli.ts codex unread [--repo owner/name] [--issue N] [--limit N]",
|
||||
"bun scripts/cli.ts codex unread mark-read [--repo owner/name] [--issue N] [--limit N] --confirm",
|
||||
"bun scripts/cli.ts codex output <taskId> [--tail|--from-start|--after-seq N|--before-seq N --limit N] [--full-text]",
|
||||
|
||||
Reference in New Issue
Block a user