fix: bound codex task supervisor output
This commit is contained in:
@@ -138,59 +138,106 @@ function fixtureResponse(path: string): JsonRecord {
|
||||
};
|
||||
}
|
||||
|
||||
function manyRunningFixtureResponse(path: string): JsonRecord {
|
||||
if (path.includes("/summary")) return fixtureResponse(path);
|
||||
assertCondition(path.startsWith("/api/microservices/code-queue/proxy/api/tasks/overview"), "unexpected path", { path });
|
||||
const tasks = Array.from({ length: 40 }, (_, index) => task(
|
||||
`task-running-${String(index + 1).padStart(2, "0")}`,
|
||||
"running",
|
||||
`2026-05-22T00:${String(59 - index).padStart(2, "0")}:00.000Z`,
|
||||
));
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
executionDiagnostics: {
|
||||
state: "healthy",
|
||||
databaseActiveTaskCount: 40,
|
||||
databaseActiveTaskIds: manyIds("running-active", 40),
|
||||
activeHeartbeatCount: 40,
|
||||
activeHeartbeatTaskIds: manyIds("running-heartbeat", 40),
|
||||
heartbeatFreshTaskIds: manyIds("running-fresh", 40),
|
||||
},
|
||||
},
|
||||
pagination: {
|
||||
limit: 200,
|
||||
returned: 40,
|
||||
total: 40,
|
||||
hasMore: false,
|
||||
nextBeforeId: null,
|
||||
includeActive: true,
|
||||
},
|
||||
tasks,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
|
||||
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "20"], fixtureResponse);
|
||||
const full = codexTasksQueryForTest(["--view", "full", "--limit", "20"], fixtureResponse);
|
||||
const runningFiltered = codexTasksQueryForTest(["--status", "running", "--limit", "40"], manyRunningFixtureResponse);
|
||||
const unreadFiltered = codexTasksQueryForTest(["--unread", "--limit", "20"], fixtureResponse);
|
||||
|
||||
const supervisorBody = JSON.stringify(supervisor);
|
||||
const fullBody = JSON.stringify(full);
|
||||
const runningFilteredBody = JSON.stringify(runningFiltered);
|
||||
const unreadFilteredBody = JSON.stringify(unreadFiltered);
|
||||
const supervisorData = asRecord(supervisor);
|
||||
const supervisorView = asRecord(supervisorData.supervisor);
|
||||
const runningFilteredView = asRecord(asRecord(runningFiltered).supervisor);
|
||||
const runningFilteredSection = asRecord(runningFilteredView.running);
|
||||
const unreadFilteredView = asRecord(asRecord(unreadFiltered).supervisor);
|
||||
const unreadFilteredSection = asRecord(unreadFilteredView.completedUnread);
|
||||
const disclosure = asRecord(supervisorView.disclosure);
|
||||
const runningItem = asRecord(asArray(asRecord(supervisorView.running).items)[0]);
|
||||
const recentCompleted = asRecord(supervisorView.recentCompleted);
|
||||
const recentItems = asArray(recentCompleted.items);
|
||||
const prompt = asRecord(runningItem.prompt);
|
||||
const lastMessage = asRecord(runningItem.lastMessage);
|
||||
const commands = asRecord(runningItem.commands);
|
||||
const fullItem = asRecord(asArray(asRecord(asRecord(full).tasks).items)[0]);
|
||||
const completedUnread = asRecord(supervisorView.completedUnread);
|
||||
const fullTasks = asRecord(asRecord(full).tasks);
|
||||
const diagnostics = asRecord(supervisorView.executionDiagnostics);
|
||||
const liveness = asRecord(diagnostics.liveness);
|
||||
const listBudget = asRecord(diagnostics.listBudget);
|
||||
const omittedCounts = asRecord(listBudget.omittedCounts);
|
||||
|
||||
assertCondition(supervisorBody.length < fullBody.length * 0.55, "supervisor output should be materially smaller than full output", { supervisorChars: supervisorBody.length, fullChars: fullBody.length });
|
||||
assertCondition(supervisorBody.length < 70_000, "supervisor output should remain bounded even with large diagnostics", { supervisorChars: supervisorBody.length });
|
||||
assertCondition(supervisorBody.length < 45_000, "supervisor output should remain bounded even with large diagnostics", { supervisorChars: supervisorBody.length });
|
||||
assertCondition(recentItems.length === 5, "recentCompleted should be capped below --limit by default", { returned: recentItems.length });
|
||||
assertCondition(asArray(completedUnread.items).length === 7, "completedUnread should keep unread terminal tasks separate from recentCompleted", completedUnread);
|
||||
assertCondition(asArray(completedUnread.items).length === 5, "completedUnread should be locally paged and kept separate from recentCompleted", completedUnread);
|
||||
assertCondition(recentItems.every((item) => asRecord(item).unreadTerminal === false), "recentCompleted should not duplicate unread terminal tasks", { recentItems });
|
||||
assertCondition(asArray(diagnostics.databaseActiveTaskIds).length === 12, "diagnostic task id lists should be capped", diagnostics);
|
||||
assertCondition(omittedCounts.databaseActiveTaskIds === 68, "diagnostic omitted counts should preserve full visibility metadata", omittedCounts);
|
||||
assertCondition(liveness.effectiveLiveness === "live", "supervisor liveness summary should keep split-brain live explicit", liveness);
|
||||
assertCondition(liveness.recommendedAction === "continue-supervision", "supervisor liveness summary should recommend continued supervision", liveness);
|
||||
assertCondition(liveness.splitBrainLive === true, "supervisor liveness summary should mark splitBrainLive", liveness);
|
||||
assertCondition(liveness.activeHeartbeatCount === 80, "supervisor liveness summary should foreground active heartbeat count", liveness);
|
||||
assertCondition(asArray(liveness.heartbeatFreshTaskIds).length === 12, "supervisor liveness summary should keep heartbeatFreshTaskIds bounded", liveness);
|
||||
assertCondition(String(liveness.interpretation ?? "").includes("continue supervision"), "supervisor liveness interpretation should not imply scheduler stoppage", liveness);
|
||||
assertCondition(asArray(diagnostics.reasons).length === 6, "diagnostic reasons should be capped", diagnostics);
|
||||
assertCondition(diagnostics.livenessSummaryTruncated === true, "long diagnostic liveness summary should be previewed", diagnostics);
|
||||
assertCondition(diagnostics.databaseActiveTaskIds === undefined, "supervisor diagnostics should not expose verbose databaseActiveTaskIds by default", diagnostics);
|
||||
assertCondition(omittedCounts.databaseActiveTaskIds === 77, "diagnostic omitted counts should preserve full visibility metadata", omittedCounts);
|
||||
assertCondition(diagnostics.effectiveLiveness === "live", "supervisor liveness summary should keep split-brain live explicit", diagnostics);
|
||||
assertCondition(diagnostics.recommendedAction === "continue-supervision", "supervisor liveness summary should recommend continued supervision", diagnostics);
|
||||
assertCondition(diagnostics.splitBrainLive === true, "supervisor liveness summary should mark splitBrainLive", diagnostics);
|
||||
assertCondition(diagnostics.activeHeartbeatCount === 80, "supervisor liveness summary should foreground active heartbeat count", diagnostics);
|
||||
assertCondition(asArray(diagnostics.heartbeatFreshTaskIds).length === 3, "supervisor diagnostics should keep heartbeatFreshTaskIds bounded", diagnostics);
|
||||
assertCondition(String(diagnostics.interpretation ?? "").includes("continue supervision"), "supervisor liveness interpretation should not imply scheduler stoppage", diagnostics);
|
||||
assertCondition(asArray(diagnostics.reasons).length === 2, "diagnostic reasons should be capped", diagnostics);
|
||||
assertCondition(diagnostics.livenessSummary === undefined, "supervisor diagnostics should omit liveness summary preview by default", diagnostics);
|
||||
assertCondition(listBudget.truncated === true && typeof listBudget.rawCommand === "string", "diagnostic list budget should disclose raw command", listBudget);
|
||||
assertCondition(asArray(runningItem.issueRefs).includes("#132"), "supervisor row should expose issue refs for triage", runningItem);
|
||||
assertCondition(Number(prompt.chars) > String(prompt.text ?? "").length && prompt.truncated === true, "supervisor prompt must be a short preview with original char count", prompt);
|
||||
assertCondition(Number(lastMessage.chars) > String(lastMessage.text ?? "").length && lastMessage.truncated === true, "supervisor body must be a short preview with original char count", lastMessage);
|
||||
assertCondition(commands.show !== undefined && commands.trace !== undefined && commands.output !== undefined && commands.full !== undefined, "supervisor row must keep progressive drill-down commands", commands);
|
||||
assertCondition(runningItem.promptPreview === undefined && runningItem.lastAssistantMessage === undefined, "supervisor rows must not expose legacy long list fields", runningItem);
|
||||
assertCondition(asArray(runningItem.issues).includes("#132"), "supervisor row should expose issue refs for triage", runningItem);
|
||||
assertCondition(Number(runningItem.promptChars) > String(runningItem.prompt ?? "").length && runningItem.promptTruncated === true, "supervisor prompt must be a short flat preview with original char count", runningItem);
|
||||
assertCondition(Number(runningItem.lastChars) > String(runningItem.last ?? "").length && runningItem.lastTruncated === true, "supervisor body must be a short flat preview with original char count", runningItem);
|
||||
assertCondition(runningItem.commands === undefined && runningItem.promptPreview === undefined && runningItem.lastAssistantMessage === undefined, "supervisor rows must not expose repeated commands or legacy long list fields", runningItem);
|
||||
assertCondition(asRecord(fullItem.promptPreview).chars !== undefined && fullItem.lastAssistantMessage !== undefined, "full view must retain detailed task row fields", fullItem);
|
||||
assertCondition(fullTasks.returned === 15, "full view must not inherit supervisor recentCompleted cap", fullTasks);
|
||||
assertCondition(asRecord(disclosure.outputBudget).recentCompletedReturnedLimit === 5, "supervisor must expose output budget metadata", disclosure);
|
||||
const budget = asRecord(disclosure.outputBudget);
|
||||
assertCondition(budget.recentCompletedReturnedLimit === 5 && budget.sectionReturnedLimit === 5, "supervisor must expose output budget metadata", disclosure);
|
||||
assertCondition(asArray(runningFilteredSection.items).length === 5, "running status filter should be locally paged below --limit", runningFilteredSection);
|
||||
assertCondition(runningFilteredSection.count === 40 && runningFilteredSection.hasMore === true, "running status filter should preserve count and hasMore", runningFilteredSection);
|
||||
assertCondition(String(asRecord(runningFilteredSection.commands).next ?? "").includes("--before-id task-running-05"), "running status filter should provide next page command", runningFilteredSection);
|
||||
assertCondition(runningFilteredBody.length < 14_000, "running status filter output should remain bounded", { chars: runningFilteredBody.length });
|
||||
assertCondition(asArray(unreadFilteredSection.items).length <= 5, "unread list should be locally paged below --limit", unreadFilteredSection);
|
||||
assertCondition(unreadFilteredBody.length < 14_000, "unread output should remain bounded", { chars: unreadFilteredBody.length });
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
checks: [
|
||||
"supervisor output materially smaller than full",
|
||||
"recentCompleted capped",
|
||||
"running/unread locally paged",
|
||||
"split-brain diagnostics capped",
|
||||
"prompt/body previews bounded",
|
||||
"drill-down commands preserved",
|
||||
|
||||
+155
-84
@@ -12,12 +12,13 @@ const defaultOutputLimit = 20;
|
||||
const defaultTextPreviewChars = 12_000;
|
||||
const defaultTasksLimit = 20;
|
||||
const maxTasksLimit = 100;
|
||||
const supervisorSectionReturnedLimit = 5;
|
||||
const supervisorRecentCompletedLimit = 5;
|
||||
const supervisorPromptPreviewChars = 160;
|
||||
const supervisorBodyPreviewChars = 180;
|
||||
const supervisorRecentBodyPreviewChars = 80;
|
||||
const diagnosticsIdPreviewLimit = 12;
|
||||
const diagnosticsReasonPreviewLimit = 6;
|
||||
const supervisorPromptPreviewChars = 90;
|
||||
const supervisorBodyPreviewChars = 90;
|
||||
const supervisorRecentBodyPreviewChars = 60;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
const steerPromptPreviewChars = 320;
|
||||
const minimaxSubmitModel = "minimax-m2.7";
|
||||
const deepseekSubmitModel = "deepseek-chat";
|
||||
@@ -193,32 +194,37 @@ interface CodexTasksEntry {
|
||||
};
|
||||
}
|
||||
|
||||
interface SupervisorTextSummary {
|
||||
text: string;
|
||||
chars: number;
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
interface SupervisorMessageSummary extends SupervisorTextSummary {
|
||||
at: unknown;
|
||||
source: unknown;
|
||||
}
|
||||
|
||||
interface CodexTasksSupervisorEntry {
|
||||
taskId: string;
|
||||
queueId: string | null;
|
||||
id: string;
|
||||
queue: string | null;
|
||||
status: string | null;
|
||||
currentAttempt: number | null;
|
||||
attempt: number | null;
|
||||
updatedAt: string | null;
|
||||
finishedAt: string | null;
|
||||
unreadTerminal: boolean;
|
||||
issueRefs: string[];
|
||||
classification: {
|
||||
kind: "direct-progress" | "deployment-fix" | "verification" | "management-noise" | "documentation" | "unknown";
|
||||
labels: string[];
|
||||
managementNoise: boolean;
|
||||
reason: string;
|
||||
};
|
||||
prompt: Record<string, unknown>;
|
||||
lastMessage: Record<string, unknown> | null;
|
||||
queuedReason: Record<string, unknown> | null;
|
||||
commands: {
|
||||
show: string;
|
||||
detail: string;
|
||||
trace: string;
|
||||
output: string;
|
||||
full: string;
|
||||
read: string;
|
||||
};
|
||||
finishedAt?: string | null;
|
||||
unreadTerminal?: boolean;
|
||||
issues: string[];
|
||||
kind: "direct-progress" | "deployment-fix" | "verification" | "management-noise" | "documentation" | "unknown";
|
||||
noise?: boolean;
|
||||
prompt: string;
|
||||
promptChars: number;
|
||||
promptTruncated?: boolean;
|
||||
last?: string;
|
||||
lastAt?: unknown;
|
||||
lastChars?: number;
|
||||
lastTruncated?: boolean;
|
||||
queuedReason?: Record<string, unknown>;
|
||||
read?: string;
|
||||
}
|
||||
|
||||
interface CodexTasksSection<T = CodexTasksEntry> {
|
||||
@@ -229,9 +235,12 @@ interface CodexTasksSection<T = CodexTasksEntry> {
|
||||
commands: {
|
||||
next: string | null;
|
||||
full: string;
|
||||
showTemplate?: string;
|
||||
detailTemplate?: string;
|
||||
traceTemplate?: string;
|
||||
outputTemplate?: string;
|
||||
taskFullTemplate?: string;
|
||||
readTemplate?: string;
|
||||
};
|
||||
items: T[];
|
||||
}
|
||||
@@ -404,6 +413,16 @@ function compactInlinePreview(value: string, maxChars: number): Record<string, u
|
||||
return textPreview(value.replace(/\s+/gu, " ").trim(), maxChars);
|
||||
}
|
||||
|
||||
function supervisorTextSummary(value: string, maxChars: number): SupervisorTextSummary {
|
||||
const compact = value.replace(/\s+/gu, " ").trim();
|
||||
const truncated = compact.length > maxChars;
|
||||
return {
|
||||
text: truncated ? compact.slice(0, maxChars) : compact,
|
||||
chars: compact.length,
|
||||
truncated,
|
||||
};
|
||||
}
|
||||
|
||||
function fmtDuration(ms: unknown): string {
|
||||
const value = Number(ms);
|
||||
if (!Number.isFinite(value) || value < 0) return "--";
|
||||
@@ -1157,6 +1176,35 @@ function compactExecutionDiagnostics(value: unknown): Record<string, unknown> |
|
||||
};
|
||||
}
|
||||
|
||||
function supervisorExecutionDiagnostics(value: unknown): Record<string, unknown> | null {
|
||||
const diagnostics = compactExecutionDiagnostics(value);
|
||||
if (diagnostics === null) return null;
|
||||
const liveness = asRecord(diagnostics.liveness) ?? {};
|
||||
const listBudget = asRecord(diagnostics.listBudget) ?? {};
|
||||
return {
|
||||
state: diagnostics.state ?? null,
|
||||
effectiveLiveness: diagnostics.effectiveLiveness ?? null,
|
||||
recommendedAction: diagnostics.recommendedAction ?? null,
|
||||
splitBrainLive: diagnostics.splitBrainLive ?? null,
|
||||
activeHeartbeatCount: diagnostics.activeHeartbeatCount ?? null,
|
||||
databaseActiveTaskCount: diagnostics.databaseActiveTaskCount ?? null,
|
||||
schedulerActiveRunSlotCount: diagnostics.schedulerActiveRunSlotCount ?? null,
|
||||
lastObservedAgentEventAt: diagnostics.lastObservedAgentEventAt ?? null,
|
||||
heartbeatFreshTaskIds: diagnostics.heartbeatFreshTaskIds ?? [],
|
||||
heartbeatRiskTaskIds: diagnostics.heartbeatRiskTaskIds ?? [],
|
||||
traceGapTaskIds: diagnostics.traceGapTaskIds ?? [],
|
||||
reasons: diagnostics.reasons ?? [],
|
||||
interpretation: liveness.interpretation ?? null,
|
||||
listBudget: {
|
||||
idPreviewLimit: listBudget.idPreviewLimit ?? diagnosticsIdPreviewLimit,
|
||||
reasonPreviewLimit: listBudget.reasonPreviewLimit ?? diagnosticsReasonPreviewLimit,
|
||||
truncated: listBudget.truncated ?? false,
|
||||
omittedCounts: listBudget.omittedCounts ?? {},
|
||||
rawCommand: listBudget.rawCommand ?? "bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview?limit=30 --raw --full",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function compactToolSummary(value: unknown, full: boolean): Record<string, unknown> {
|
||||
const record = asRecord(value) ?? {};
|
||||
const items = asArray(record.items).map((item) => {
|
||||
@@ -1730,7 +1778,12 @@ function taskIssueRefs(task: Record<string, unknown>, summary: Record<string, un
|
||||
return Array.from(new Set(Array.from(text.matchAll(/#(\d{1,6})\b/gu)).map((match) => `#${match[1]}`))).slice(0, 8);
|
||||
}
|
||||
|
||||
function taskClassification(task: Record<string, unknown>, summary: Record<string, unknown> | null): CodexTasksSupervisorEntry["classification"] {
|
||||
function taskClassification(task: Record<string, unknown>, summary: Record<string, unknown> | null): {
|
||||
kind: CodexTasksSupervisorEntry["kind"];
|
||||
labels: string[];
|
||||
managementNoise: boolean;
|
||||
reason: string;
|
||||
} {
|
||||
const text = [
|
||||
asString(task.displayPrompt),
|
||||
asString(task.basePrompt),
|
||||
@@ -1765,23 +1818,14 @@ function taskClassification(task: Record<string, unknown>, summary: Record<strin
|
||||
return { kind: "unknown", labels, managementNoise: false, reason: "no strong classifier term matched" };
|
||||
}
|
||||
|
||||
function supervisorLastMessage(summaryLastAssistant: unknown, maxChars: number): Record<string, unknown> | null {
|
||||
function supervisorLastMessage(summaryLastAssistant: unknown, maxChars: number): SupervisorMessageSummary | null {
|
||||
if (summaryLastAssistant === undefined || summaryLastAssistant === null) return null;
|
||||
const record = asRecord(summaryLastAssistant) ?? {};
|
||||
const text = asString(record.text);
|
||||
if (text.length === 0) {
|
||||
return {
|
||||
at: record.at ?? null,
|
||||
seq: record.seq ?? null,
|
||||
source: record.source ?? "none",
|
||||
...compactInlinePreview("", maxChars),
|
||||
};
|
||||
}
|
||||
return {
|
||||
at: record.at ?? null,
|
||||
seq: record.seq ?? null,
|
||||
source: record.source ?? "none",
|
||||
...compactInlinePreview(text, maxChars),
|
||||
...supervisorTextSummary(text, maxChars),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1825,35 +1869,34 @@ function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, u
|
||||
|
||||
function taskSupervisorEntry(task: Record<string, unknown>, summary: Record<string, unknown> | null, bodyPreviewChars = supervisorBodyPreviewChars): CodexTasksSupervisorEntry {
|
||||
const taskId = asString(task.id);
|
||||
const summaryCommands = summary === null ? null : asRecord(summary.commands);
|
||||
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
|
||||
const showCommand = typeof summary?.cliHint === "string" && summary.cliHint.length > 0
|
||||
? summary.cliHint
|
||||
: `bun scripts/cli.ts codex task ${taskId}`;
|
||||
const traceCommand = typeof summary?.traceHint === "string" && summary.traceHint.length > 0
|
||||
? summary.traceHint
|
||||
: `bun scripts/cli.ts codex task ${taskId} --trace --tail --limit ${defaultTraceLimit}`;
|
||||
const status = asString(task.status) || null;
|
||||
const unreadTerminal = taskUnreadTerminal(task);
|
||||
const classification = taskClassification(task, summary);
|
||||
const queuedReason = compactQueuedReason(task.queuedReason);
|
||||
const prompt = supervisorTextSummary(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), supervisorPromptPreviewChars);
|
||||
const lastMessage = supervisorLastMessage(summaryLastAssistant, bodyPreviewChars);
|
||||
return {
|
||||
taskId,
|
||||
queueId: asString(task.queueId) || null,
|
||||
status: asString(task.status) || null,
|
||||
currentAttempt: typeof task.currentAttempt === "number" && Number.isFinite(task.currentAttempt) ? task.currentAttempt : null,
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status,
|
||||
attempt: typeof task.currentAttempt === "number" && Number.isFinite(task.currentAttempt) ? task.currentAttempt : null,
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
finishedAt: asString(task.finishedAt) || null,
|
||||
unreadTerminal: taskUnreadTerminal(task),
|
||||
issueRefs: taskIssueRefs(task, summary),
|
||||
classification: taskClassification(task, summary),
|
||||
prompt: compactInlinePreview(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), supervisorPromptPreviewChars),
|
||||
lastMessage: supervisorLastMessage(summaryLastAssistant, bodyPreviewChars),
|
||||
queuedReason: compactQueuedReason(task.queuedReason),
|
||||
commands: {
|
||||
show: typeof summaryCommands?.show === "string" && summaryCommands.show.length > 0 ? summaryCommands.show : showCommand,
|
||||
detail: `bun scripts/cli.ts codex task ${taskId} --detail`,
|
||||
trace: typeof summaryCommands?.trace === "string" && summaryCommands.trace.length > 0 ? summaryCommands.trace : traceCommand,
|
||||
output: `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`,
|
||||
full: `bun scripts/cli.ts codex task ${taskId} --full`,
|
||||
read: `bun scripts/cli.ts codex read ${taskId}`,
|
||||
},
|
||||
...(isTerminalTaskStatus(status) ? { finishedAt: asString(task.finishedAt) || null, unreadTerminal } : {}),
|
||||
issues: taskIssueRefs(task, summary),
|
||||
kind: classification.kind,
|
||||
...(classification.managementNoise ? { noise: true } : {}),
|
||||
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 } : {}),
|
||||
}),
|
||||
...(queuedReason === null ? {} : { queuedReason }),
|
||||
...(unreadTerminal ? { read: `bun scripts/cli.ts codex read ${taskId}` } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1891,18 +1934,24 @@ function buildSupervisorTaskSection(
|
||||
const visibleTasks = tasks.slice(0, limit);
|
||||
const items = visibleTasks.map((task) => taskSupervisorEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null, bodyPreviewChars));
|
||||
const truncated = tasks.length > limit;
|
||||
const commands = {
|
||||
next: truncated || nextCommand !== null ? nextCommand : null,
|
||||
full: fullCommand,
|
||||
...(items.length === 0 ? {} : {
|
||||
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}`,
|
||||
taskFullTemplate: "bun scripts/cli.ts codex task <taskId> --full",
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
}),
|
||||
};
|
||||
return {
|
||||
count: tasks.length,
|
||||
returned: items.length,
|
||||
truncated,
|
||||
hasMore: truncated,
|
||||
commands: {
|
||||
next: truncated ? nextCommand : null,
|
||||
full: fullCommand,
|
||||
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}`,
|
||||
},
|
||||
hasMore: truncated || nextCommand !== null,
|
||||
commands,
|
||||
items,
|
||||
};
|
||||
}
|
||||
@@ -1976,6 +2025,24 @@ function taskListCommand(options: CodexTasksOptions, extra: string[] = []): stri
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function taskSectionLimit(options: CodexTasksOptions, maxReturned = supervisorSectionReturnedLimit): number {
|
||||
return Math.min(options.limit, maxReturned);
|
||||
}
|
||||
|
||||
function sectionNextCommand(
|
||||
tasks: Record<string, unknown>[],
|
||||
limit: number,
|
||||
options: CodexTasksOptions,
|
||||
sourceNextCommand: string | null,
|
||||
): string | null {
|
||||
if (tasks.length > limit) {
|
||||
const lastVisibleTask = tasks[limit - 1];
|
||||
const beforeId = lastVisibleTask === undefined ? "" : taskOverviewCandidateKey(lastVisibleTask);
|
||||
if (beforeId.length > 0) return taskListCommand({ ...options, beforeId });
|
||||
}
|
||||
return sourceNextCommand;
|
||||
}
|
||||
|
||||
function fetchTaskSummaries(taskIds: string[], fetcher: CodexResponseFetcher): { summaries: Map<string, Record<string, unknown>>; degraded: CodexTasksDegraded | null } {
|
||||
const boundedIds = taskIds.slice(0, maxTasksLimit);
|
||||
const summaries = new Map<string, Record<string, unknown>>();
|
||||
@@ -2064,18 +2131,19 @@ function codexTasksOverviewResult(
|
||||
const sourceHasMore = asBoolean(taskPage.pagination.hasMore);
|
||||
const nextCommand = sourceHasMore && nextBeforeId !== null ? taskListCommand({ ...options, beforeId: nextBeforeId }) : null;
|
||||
const fullCommand = taskListCommand({ ...options, view: "full" });
|
||||
const sectionLimit = taskSectionLimit(options);
|
||||
const recentLimit = Math.min(options.limit, supervisorRecentCompletedLimit);
|
||||
const runningSection = buildSupervisorTaskSection(runningTasks, summaries, options.limit, nextCommand, fullCommand);
|
||||
const unreadSection = buildSupervisorTaskSection(unreadCompletedTasks, summaries, options.limit, nextCommand, fullCommand);
|
||||
const recentSection = buildSupervisorTaskSection(recentCompletedTasks, summaries, recentLimit, nextCommand, fullCommand, supervisorRecentBodyPreviewChars);
|
||||
const queuedSection = buildSupervisorTaskSection(queuedTasks, summaries, options.limit, nextCommand, fullCommand);
|
||||
const runningSection = buildSupervisorTaskSection(runningTasks, summaries, sectionLimit, sectionNextCommand(runningTasks, sectionLimit, options, nextCommand), fullCommand);
|
||||
const unreadSection = buildSupervisorTaskSection(unreadCompletedTasks, summaries, sectionLimit, sectionNextCommand(unreadCompletedTasks, sectionLimit, options, nextCommand), fullCommand);
|
||||
const recentSection = buildSupervisorTaskSection(recentCompletedTasks, summaries, recentLimit, sectionNextCommand(recentCompletedTasks, recentLimit, options, nextCommand), fullCommand, supervisorRecentBodyPreviewChars);
|
||||
const queuedSection = buildSupervisorTaskSection(queuedTasks, summaries, sectionLimit, sectionNextCommand(queuedTasks, sectionLimit, options, nextCommand), fullCommand);
|
||||
const pagination = taskPage.pagination;
|
||||
const diagnostics = compactExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
|
||||
const diagnostics = supervisorExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
|
||||
const visibleSupervisorItems = [...runningSection.items, ...unreadSection.items, ...recentSection.items, ...queuedSection.items];
|
||||
const classifierCounts = visibleSupervisorItems.reduce((counts, item) => {
|
||||
const key = item.classification.kind;
|
||||
const key = item.kind;
|
||||
counts[key] = (counts[key] ?? 0) + 1;
|
||||
if (item.classification.managementNoise) counts.managementNoise = (counts.managementNoise ?? 0) + 1;
|
||||
if (item.noise) counts.managementNoise = (counts.managementNoise ?? 0) + 1;
|
||||
return counts;
|
||||
}, {} as Record<string, number>);
|
||||
return {
|
||||
@@ -2106,8 +2174,10 @@ function codexTasksOverviewResult(
|
||||
outputBudget: {
|
||||
promptPreviewChars: supervisorPromptPreviewChars,
|
||||
bodyPreviewChars: supervisorBodyPreviewChars,
|
||||
sectionReturnedLimit: sectionLimit,
|
||||
recentCompletedReturnedLimit: recentLimit,
|
||||
recentCompletedBodyPreviewChars: supervisorRecentBodyPreviewChars,
|
||||
rowCommands: "task id is the row drill-down key; show/detail/trace/output/full/read are section templates to avoid repeated noise",
|
||||
},
|
||||
fullCommand,
|
||||
next: nextCommand,
|
||||
@@ -2188,11 +2258,12 @@ function visibleTaskIdsForOverview(tasks: Record<string, unknown>[], options: Co
|
||||
if (options.view === "full") {
|
||||
return filtered.slice(0, options.limit).map((task) => taskOverviewCandidateKey(task)).filter((taskId) => taskId.length > 0);
|
||||
}
|
||||
const sectionLimit = taskSectionLimit(options);
|
||||
return Array.from(new Set([
|
||||
...sortRunningWatchTasks(filtered).slice(0, options.limit),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)).slice(0, options.limit),
|
||||
...sortRunningWatchTasks(filtered).slice(0, sectionLimit),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)).slice(0, sectionLimit),
|
||||
...sortCompletedWatchTasks(filtered).filter((task) => !taskUnreadTerminal(task)).slice(0, Math.min(options.limit, supervisorRecentCompletedLimit)),
|
||||
...sortQueuedWatchTasks(filtered).slice(0, options.limit),
|
||||
...sortQueuedWatchTasks(filtered).slice(0, sectionLimit),
|
||||
].map((task) => taskOverviewCandidateKey(task))))
|
||||
.filter((taskId) => taskId.length > 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user