fix: clarify codex supervisor active counts
This commit is contained in:
@@ -82,6 +82,14 @@ function fixtureResponse(path: string): JsonRecord {
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
counts: {
|
||||
running: 15,
|
||||
judging: 0,
|
||||
queued: 4,
|
||||
retry_wait: 1,
|
||||
succeeded: 13,
|
||||
},
|
||||
maxActiveQueues: 12,
|
||||
executionDiagnostics: {
|
||||
state: "split-brain",
|
||||
splitBrain: true,
|
||||
@@ -112,7 +120,7 @@ function fixtureResponse(path: string): JsonRecord {
|
||||
pagination: {
|
||||
limit: 200,
|
||||
returned: 15,
|
||||
total: 15,
|
||||
total: 33,
|
||||
hasMore: false,
|
||||
nextBeforeId: null,
|
||||
includeActive: true,
|
||||
@@ -152,6 +160,13 @@ function manyRunningFixtureResponse(path: string): JsonRecord {
|
||||
body: {
|
||||
ok: true,
|
||||
queue: {
|
||||
counts: {
|
||||
running: 40,
|
||||
judging: 0,
|
||||
queued: 7,
|
||||
retry_wait: 2,
|
||||
},
|
||||
maxActiveQueues: 50,
|
||||
executionDiagnostics: {
|
||||
state: "healthy",
|
||||
databaseActiveTaskCount: 40,
|
||||
@@ -227,7 +242,7 @@ function splitBrainLiveSupervisorFixtureResponse(path: string): JsonRecord {
|
||||
}
|
||||
|
||||
export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
|
||||
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "20"], fixtureResponse);
|
||||
const supervisor = codexTasksQueryForTest(["--view", "supervisor", "--limit", "260"], fixtureResponse);
|
||||
const full = codexTasksQueryForTest(["--view", "full", "--limit", "20"], fixtureResponse);
|
||||
const runningFiltered = codexTasksQueryForTest(["--status", "running", "--limit", "40"], manyRunningFixtureResponse);
|
||||
const unreadFiltered = codexTasksQueryForTest(["--unread", "--limit", "20"], fixtureResponse);
|
||||
@@ -251,6 +266,13 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
|
||||
const completedUnread = asRecord(supervisorView.completedUnread);
|
||||
const fullTasks = asRecord(asRecord(full).tasks);
|
||||
const diagnostics = asRecord(supervisorView.executionDiagnostics);
|
||||
const filters = asRecord(supervisorView.filters);
|
||||
const activeRunning = asRecord(supervisorView.activeRunning);
|
||||
const activeRunningRowPage = asRecord(activeRunning.rowPage);
|
||||
const activeRunningRedline = asRecord(activeRunning.redline);
|
||||
const activeRunningCommands = asRecord(activeRunning.commands);
|
||||
const counts = asRecord(supervisorView.counts);
|
||||
const outputBudget = asRecord(asRecord(disclosure.outputBudget));
|
||||
const listBudget = asRecord(diagnostics.listBudget);
|
||||
const omittedCounts = asRecord(listBudget.omittedCounts);
|
||||
const splitBrainLiveView = asRecord(asRecord(splitBrainLive).supervisor);
|
||||
@@ -286,8 +308,13 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
|
||||
assertCondition(fullItem.status === "running" && String(fullItem.statusLabel ?? "").includes("awaiting terminal/judge"), "full view should keep raw status while exposing derived closeout label", fullItem);
|
||||
assertCondition(fullItem.awaitingTerminalJudge === true && fullItem.closeoutState === "awaiting-terminal-or-judge", "full view should expose awaiting terminal/judge state", fullItem);
|
||||
assertCondition(fullTasks.returned === 15, "full view must not inherit supervisor recentCompleted cap", fullTasks);
|
||||
const budget = asRecord(disclosure.outputBudget);
|
||||
assertCondition(budget.recentCompletedReturnedLimit === 3 && budget.sectionReturnedLimit === 3, "supervisor must expose output budget metadata", disclosure);
|
||||
assertCondition(filters.requestedLimit === 260 && filters.limit === 100 && filters.limitCapped === true, "supervisor filters should disclose requested vs effective limit", filters);
|
||||
assertCondition(outputBudget.requestedLimit === 260 && outputBudget.effectiveLimit === 100 && outputBudget.sectionReturnedLimit === 3, "supervisor must expose output budget metadata", outputBudget);
|
||||
assertCondition(activeRunning.count === 15 && activeRunning.exact === true && activeRunning.source === "queue-summary-counts", "activeRunning should expose exact running+judging count from queue summary", activeRunning);
|
||||
assertCondition(activeRunningRowPage.returned === 1 && activeRunningRowPage.returnedLimit === 3 && activeRunningRowPage.distinction.includes("row page"), "activeRunning row page should distinguish returned rows from active count", activeRunningRowPage);
|
||||
assertCondition(activeRunningRedline.countField === "supervisor.activeRunning.count" && activeRunningRedline.hardRedline === 15 && activeRunningRedline.state === "at-or-over-hard-redline", "activeRunning redline should name count field and interpretation", activeRunningRedline);
|
||||
assertCondition(counts.activeRunningCount === 15 && counts.activeRunningExact === true && counts.activeRunningRowsReturned === 1, "supervisor counts should separate active count from returned running rows", counts);
|
||||
assertCondition(String(activeRunningCommands.running ?? "").includes("--status running,judging"), "activeRunning should provide running drilldown", activeRunningCommands);
|
||||
assertCondition(asArray(runningFilteredSection.items).length === 3, "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-03"), "running status filter should provide next page command", runningFilteredSection);
|
||||
@@ -324,6 +351,8 @@ export function runCodeQueueSupervisorDisclosureContract(): JsonRecord {
|
||||
"recentCompleted capped",
|
||||
"running/unread locally paged",
|
||||
"split-brain diagnostics capped",
|
||||
"active running exact count exposed",
|
||||
"requested/effective/returned limits disclosed",
|
||||
"prompt/body previews bounded",
|
||||
"running finalResponse rows labeled awaiting terminal/judge",
|
||||
"drill-down commands preserved",
|
||||
|
||||
+191
-3
@@ -189,6 +189,7 @@ interface CompactTaskMutationResponseOptions {
|
||||
|
||||
interface CodexTasksOptions {
|
||||
queueId: string | undefined;
|
||||
requestedLimit: number;
|
||||
limit: number;
|
||||
beforeId: string | undefined;
|
||||
unreadOnly: boolean;
|
||||
@@ -288,6 +289,14 @@ interface CodexTasksDegraded {
|
||||
reason: string;
|
||||
}
|
||||
|
||||
interface SupervisorStatusCounts {
|
||||
counts: Record<string, number>;
|
||||
exact: boolean;
|
||||
source: "queue-summary-counts" | "overview-page-fallback";
|
||||
scope: "all-queues" | "queue";
|
||||
queueId: string | null;
|
||||
}
|
||||
|
||||
interface CodexQueuesOptions {
|
||||
full: boolean;
|
||||
limit: number;
|
||||
@@ -1851,9 +1860,11 @@ function parseTasksOptions(args: string[]): CodexTasksOptions {
|
||||
const unsupported = statusFilter.filter((status) => !supported.has(status));
|
||||
if (unsupported.length > 0) throw new Error(`unsupported --status value: ${unsupported.join(", ")}; supported: ${Array.from(supported).join(", ")}`);
|
||||
}
|
||||
const requestedLimit = positiveIntegerOption(args, ["--limit"], defaultTasksLimit);
|
||||
return {
|
||||
queueId: optionValue(args, ["--queue", "--queue-id"]),
|
||||
limit: positiveIntegerOption(args, ["--limit"], defaultTasksLimit, maxTasksLimit),
|
||||
requestedLimit,
|
||||
limit: Math.min(requestedLimit, maxTasksLimit),
|
||||
beforeId: optionValue(args, ["--before-id", "--beforeId"]),
|
||||
unreadOnly: hasFlag(args, "--unread-only") || hasFlag(args, "--unread"),
|
||||
statusFilter,
|
||||
@@ -2376,13 +2387,25 @@ function filterTasksForOptions(tasks: Record<string, unknown>[], options: CodexT
|
||||
.filter((task) => !options.unreadOnly || taskUnreadTerminal(task));
|
||||
}
|
||||
|
||||
function baseTaskListOptions(options: CodexTasksOptions): CodexTasksOptions {
|
||||
return {
|
||||
queueId: options.queueId,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
beforeId: options.beforeId,
|
||||
unreadOnly: options.unreadOnly,
|
||||
statusFilter: options.statusFilter,
|
||||
view: options.view,
|
||||
};
|
||||
}
|
||||
|
||||
function taskListCommand(options: CodexTasksOptions, extra: string[] = []): string {
|
||||
const args = ["codex", "tasks"];
|
||||
if (options.view !== "supervisor") args.push("--view", options.view);
|
||||
if (options.queueId !== undefined) args.push("--queue", options.queueId);
|
||||
if (options.unreadOnly) args.push("--unread");
|
||||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||||
if (options.limit !== defaultTasksLimit) args.push("--limit", String(options.limit));
|
||||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||||
args.push(...extra);
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
@@ -2406,6 +2429,155 @@ function sectionNextCommand(
|
||||
return sourceNextCommand;
|
||||
}
|
||||
|
||||
function countRecordValues(value: unknown): Record<string, number> {
|
||||
const record = asRecord(value);
|
||||
const counts: Record<string, number> = {};
|
||||
if (record === null) return counts;
|
||||
for (const [status, rawCount] of Object.entries(record)) {
|
||||
const count = typeof rawCount === "number" ? rawCount : typeof rawCount === "string" ? Number(rawCount) : Number.NaN;
|
||||
if (Number.isFinite(count) && count >= 0) counts[status] = Math.floor(count);
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
function queueSummaryCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts | null {
|
||||
const queue = taskPage.queue;
|
||||
if (queue === null) return null;
|
||||
if (options.queueId !== undefined) {
|
||||
for (const row of asArray(queue.queues)) {
|
||||
const record = asRecord(row);
|
||||
if (record === null || asString(record.id) !== options.queueId) continue;
|
||||
if (asRecord(record.counts) === null) return null;
|
||||
return {
|
||||
counts: countRecordValues(record.counts),
|
||||
exact: true,
|
||||
source: "queue-summary-counts",
|
||||
scope: "queue",
|
||||
queueId: options.queueId,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (asRecord(queue.counts) === null) return null;
|
||||
return {
|
||||
counts: countRecordValues(queue.counts),
|
||||
exact: true,
|
||||
source: "queue-summary-counts",
|
||||
scope: "all-queues",
|
||||
queueId: null,
|
||||
};
|
||||
}
|
||||
|
||||
function fallbackStatusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
|
||||
const counts: Record<string, number> = {};
|
||||
for (const task of taskPage.tasks) {
|
||||
if (options.queueId !== undefined && asString(task.queueId) !== options.queueId) continue;
|
||||
const status = asString(task.status);
|
||||
if (status.length === 0) continue;
|
||||
counts[status] = (counts[status] ?? 0) + 1;
|
||||
}
|
||||
return {
|
||||
counts,
|
||||
exact: false,
|
||||
source: "overview-page-fallback",
|
||||
scope: options.queueId === undefined ? "all-queues" : "queue",
|
||||
queueId: options.queueId ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
function statusCountsForOptions(taskPage: CodexTasksTaskPage, options: CodexTasksOptions): SupervisorStatusCounts {
|
||||
return queueSummaryCountsForOptions(taskPage, options) ?? fallbackStatusCountsForOptions(taskPage, options);
|
||||
}
|
||||
|
||||
function positiveCount(value: unknown): number | null {
|
||||
const count = asNumber(value, Number.NaN);
|
||||
return Number.isFinite(count) && count >= 0 ? Math.floor(count) : null;
|
||||
}
|
||||
|
||||
function runtimeMaxActiveQueues(taskPage: CodexTasksTaskPage): number | null {
|
||||
const value = positiveCount(taskPage.queue?.maxActiveQueues);
|
||||
return value !== null && value > 0 ? value : null;
|
||||
}
|
||||
|
||||
function activeRunningRedlineState(activeCount: number, hardRedline: number, burstRedline: number, routineTarget: number): string {
|
||||
if (activeCount >= hardRedline) return "at-or-over-hard-redline";
|
||||
if (activeCount >= burstRedline) return "burst-redline";
|
||||
if (activeCount >= routineTarget) return "above-routine-target";
|
||||
return "below-routine-target";
|
||||
}
|
||||
|
||||
function supervisorActiveRunningSummary(
|
||||
taskPage: CodexTasksTaskPage,
|
||||
options: CodexTasksOptions,
|
||||
runningSection: CodexTasksSection<CodexTasksSupervisorEntry>,
|
||||
diagnostics: Record<string, unknown> | null,
|
||||
): Record<string, unknown> {
|
||||
const statusCounts = statusCountsForOptions(taskPage, options);
|
||||
const runningCount = statusCounts.counts.running ?? 0;
|
||||
const judgingCount = statusCounts.counts.judging ?? 0;
|
||||
const activeStatusCount = runningCount + judgingCount;
|
||||
const heartbeatActiveCount = positiveCount(diagnostics?.activeHeartbeatCount);
|
||||
const schedulerLocalActiveRunSlotCount = positiveCount(diagnostics?.schedulerActiveRunSlotCount);
|
||||
const effectiveActiveRunnerCount = Math.max(activeStatusCount, heartbeatActiveCount ?? 0, schedulerLocalActiveRunSlotCount ?? 0);
|
||||
const policy = submitPolicyContract().concurrency;
|
||||
const routineTarget = policy.gpt55Routine;
|
||||
const burstRedline = Math.max(policy.gpt55BurstMax, policy.minimaxSimpleMax);
|
||||
const hardRedline = 15;
|
||||
const runtimeLimit = runtimeMaxActiveQueues(taskPage);
|
||||
const redlineState = activeRunningRedlineState(activeStatusCount, hardRedline, burstRedline, routineTarget);
|
||||
const exactInterpretation = statusCounts.exact
|
||||
? "activeRunning.count is exact from queue status counts; row pagination does not change this count"
|
||||
: "activeRunning.count is page-scoped because queue status counts were absent; do not use it for commander redline decisions without a queue summary/raw cross-check";
|
||||
const baseOptions = baseTaskListOptions({ ...options, beforeId: undefined, unreadOnly: false, statusFilter: null });
|
||||
const runningOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["running", "judging"] };
|
||||
const queuedOptions: CodexTasksOptions = { ...baseOptions, statusFilter: ["queued", "retry_wait"] };
|
||||
return {
|
||||
count: activeStatusCount,
|
||||
exact: statusCounts.exact,
|
||||
source: statusCounts.source,
|
||||
scope: statusCounts.scope,
|
||||
queueId: statusCounts.queueId,
|
||||
statuses: {
|
||||
running: runningCount,
|
||||
judging: judgingCount,
|
||||
},
|
||||
effectiveActiveRunnerCount,
|
||||
effectiveActiveRunnerCountSources: {
|
||||
activeStatusCount,
|
||||
heartbeatActiveCount,
|
||||
schedulerLocalActiveRunSlotCount,
|
||||
},
|
||||
rowPage: {
|
||||
returned: runningSection.returned,
|
||||
availableInCurrentOverview: runningSection.count,
|
||||
returnedLimit: taskSectionLimit(options),
|
||||
truncated: runningSection.truncated,
|
||||
hasMore: runningSection.hasMore,
|
||||
distinction: "returned is the compact row page; count is the active running total used for supervision",
|
||||
},
|
||||
redline: {
|
||||
countField: "supervisor.activeRunning.count",
|
||||
routineTarget,
|
||||
burstRedline,
|
||||
hardRedline,
|
||||
runtimeMaxActiveQueues: runtimeLimit,
|
||||
state: redlineState,
|
||||
remainingToRoutineTarget: Math.max(0, routineTarget - activeStatusCount),
|
||||
remainingToBurstRedline: Math.max(0, burstRedline - activeStatusCount),
|
||||
remainingToHardRedline: Math.max(0, hardRedline - activeStatusCount),
|
||||
decisionReady: statusCounts.exact,
|
||||
interpretation: exactInterpretation,
|
||||
},
|
||||
commands: {
|
||||
running: taskListCommand(runningOptions),
|
||||
runningFull: taskListCommand({ ...runningOptions, view: "full" }),
|
||||
queued: taskListCommand(queuedOptions),
|
||||
queues: "bun scripts/cli.ts codex queues",
|
||||
nextRunningPage: runningSection.commands.next,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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>>();
|
||||
@@ -2504,6 +2676,7 @@ function codexTasksOverviewResult(
|
||||
const diagnostics = supervisorExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
|
||||
const activity = compactCodeQueueActivity(asRecord(taskPage.queue) ?? {}, diagnostics);
|
||||
const commanderConcurrency = asRecord(activity.commanderConcurrency) ?? {};
|
||||
const activeRunning = supervisorActiveRunningSummary(taskPage, options, runningSection, diagnostics);
|
||||
const visibleSupervisorItems = [...runningSection.items, ...unreadSection.items, ...recentSection.items, ...queuedSection.items];
|
||||
const classifierCounts = visibleSupervisorItems.reduce((counts, item) => {
|
||||
const key = item.kind;
|
||||
@@ -2517,7 +2690,10 @@ function codexTasksOverviewResult(
|
||||
filters: {
|
||||
view: options.view,
|
||||
queueId: options.queueId ?? null,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
effectiveLimit: options.limit,
|
||||
limitCapped: options.limit < options.requestedLimit,
|
||||
unreadOnly: options.unreadOnly,
|
||||
status: options.statusFilter,
|
||||
beforeId: options.beforeId ?? null,
|
||||
@@ -2526,6 +2702,7 @@ function codexTasksOverviewResult(
|
||||
endpoint: "/api/tasks/overview",
|
||||
queueId: options.queueId ?? null,
|
||||
limit: asNumber(pagination.limit, 0) || null,
|
||||
limitSemantics: "upstream overview fetch limit; separate from filters.requestedLimit/effectiveLimit and compact section row counts",
|
||||
returned: asNumber(pagination.returned, 0) || null,
|
||||
total: asNumber(pagination.total, 0) || null,
|
||||
hasMore: asBoolean(pagination.hasMore),
|
||||
@@ -2536,9 +2713,13 @@ function codexTasksOverviewResult(
|
||||
disclosure: {
|
||||
defaultView: "supervisor",
|
||||
policy: "bounded summary rows only; prompt/body are short previews and raw detail requires explicit task/full/trace/output commands",
|
||||
limitSemantics: "--limit is request/scanning/page budget; supervisor sections still return compact bounded row pages and expose counts separately",
|
||||
outputBudget: {
|
||||
promptPreviewChars: supervisorPromptPreviewChars,
|
||||
bodyPreviewChars: supervisorBodyPreviewChars,
|
||||
requestedLimit: options.requestedLimit,
|
||||
effectiveLimit: options.limit,
|
||||
limitCapped: options.limit < options.requestedLimit,
|
||||
sectionReturnedLimit: sectionLimit,
|
||||
recentCompletedReturnedLimit: recentLimit,
|
||||
recentCompletedBodyPreviewChars: supervisorRecentBodyPreviewChars,
|
||||
@@ -2551,6 +2732,9 @@ function codexTasksOverviewResult(
|
||||
counts: {
|
||||
scanned: allTasks.length,
|
||||
running: runningSection.count,
|
||||
activeRunningCount: activeRunning.count,
|
||||
activeRunningExact: activeRunning.exact,
|
||||
activeRunningRowsReturned: runningSection.returned,
|
||||
completedUnread: unreadSection.count,
|
||||
recentCompleted: recentSection.count,
|
||||
queued: queuedSection.count,
|
||||
@@ -2563,12 +2747,13 @@ function codexTasksOverviewResult(
|
||||
classifierCounts,
|
||||
commanderConcurrency,
|
||||
activity,
|
||||
activeRunning,
|
||||
executionDiagnostics: diagnostics,
|
||||
degraded,
|
||||
commands: {
|
||||
refresh: taskListCommand(options),
|
||||
unread: taskListCommand({ ...options, unreadOnly: true }),
|
||||
byStatus: `bun scripts/cli.ts codex tasks --status <status>${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.limit === defaultTasksLimit ? "" : ` --limit ${options.limit}`}`,
|
||||
byStatus: `bun scripts/cli.ts codex tasks --status <status>${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.requestedLimit === defaultTasksLimit ? "" : ` --limit ${options.requestedLimit}`}`,
|
||||
full: fullCommand,
|
||||
next: nextCommand,
|
||||
},
|
||||
@@ -2599,7 +2784,10 @@ function codexTasksFullResult(
|
||||
filters: {
|
||||
view: "full",
|
||||
queueId: options.queueId ?? null,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
effectiveLimit: options.limit,
|
||||
limitCapped: options.limit < options.requestedLimit,
|
||||
unreadOnly: options.unreadOnly,
|
||||
status: options.statusFilter,
|
||||
beforeId: options.beforeId ?? null,
|
||||
|
||||
@@ -291,6 +291,11 @@ function codexHelp(): unknown {
|
||||
schedulerLocalActiveQueueCount: "Only queues currently visible in this scheduler-local active-run slot view; zero does not override DB or heartbeat activity.",
|
||||
heartbeatFreshActiveTaskCount: "Heartbeat-effective active runner count used to avoid split-brain zero-active mistakes.",
|
||||
},
|
||||
supervisorView: {
|
||||
activeRunning: "Use data.supervisor.activeRunning.count for exact active running+judging count when exact=true; data.supervisor.running.returned is only the compact row page.",
|
||||
redline: "data.supervisor.activeRunning.redline names the count field, routine target, burst redline, hard redline, and decisionReady flag.",
|
||||
limitSemantics: "filters.requestedLimit preserves the user input; filters.limit/effectiveLimit shows the capped query budget; section outputBudget/rowPage show returned-row caps.",
|
||||
},
|
||||
description: "Operate Code Queue through the stable backend-core private proxy path with bounded activity summaries for queue and supervisor views. Real submit/steer success is a low-noise write confirmation and does not echo prompt text.",
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user