feat: tighten code queue supervision cli

This commit is contained in:
Codex
2026-05-20 04:15:18 +00:00
parent 6e86d3600c
commit f9b190945d
11 changed files with 531 additions and 40 deletions
+401 -29
View File
@@ -60,7 +60,10 @@ interface CompactTaskMutationResponseOptions {
interface CodexTasksOptions {
queueId: string | undefined;
limit: number;
beforeId: string | undefined;
unreadOnly: boolean;
statusFilter: string[] | null;
view: "supervisor" | "full";
}
interface CodexTasksEntry {
@@ -73,10 +76,15 @@ interface CodexTasksEntry {
readAt: string | null;
unread: boolean;
unreadTerminal: boolean;
promptPreview: Record<string, unknown>;
queuedReason: Record<string, unknown> | null;
lastAssistantMessage: Record<string, unknown> | null;
commands: {
show: string;
trace: string;
output: string;
read: string;
full: string;
};
}
@@ -84,15 +92,26 @@ interface CodexTasksSection {
count: number;
returned: number;
truncated: boolean;
hasMore: boolean;
commands: {
next: string | null;
full: string;
};
items: CodexTasksEntry[];
}
interface CodexTasksDegraded {
summaryFetchFailedTaskIds: string[];
summaryFetchErrorCount: number;
summaryFetchOmittedTaskCount: number;
reason: string;
}
interface CodexQueuesOptions {
full: boolean;
limit: number;
}
type CodexRequestInit = { method?: string; body?: unknown };
type CodexResponseFetcher = (path: string, init?: CodexRequestInit) => unknown;
type AsyncCodexResponseFetcher = (path: string, init?: CodexRequestInit) => Promise<unknown>;
@@ -300,6 +319,23 @@ function hasFlag(args: string[], name: string): boolean {
return args.includes(name);
}
function assertKnownOptions(args: string[], spec: { flags?: string[]; valueOptions?: string[] }, command: string): void {
const flags = new Set(spec.flags ?? []);
const valueOptions = new Set(spec.valueOptions ?? []);
for (let index = 0; index < args.length; index += 1) {
const arg = args[index] ?? "";
if (!arg.startsWith("--")) continue;
if (flags.has(arg)) continue;
if (valueOptions.has(arg)) {
const value = args[index + 1];
if (value === undefined || value.startsWith("--")) throw new Error(`${arg} requires a value`);
index += 1;
continue;
}
throw new Error(`unsupported ${command} option: ${arg}`);
}
}
function textView(text: string, full: boolean, maxChars: number): Record<string, unknown> {
const truncated = !full && text.length > maxChars;
return {
@@ -324,6 +360,21 @@ function compactLastAssistant(value: unknown, full: boolean): Record<string, unk
};
}
function compactQueuedReason(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
code: record.code ?? null,
label: record.label ?? null,
message: textPreview(asString(record.message), 280),
blockerTaskId: record.blockerTaskId ?? null,
blockerQueueId: record.blockerQueueId ?? null,
waitPosition: record.waitPosition ?? null,
activeRunSlotCount: record.activeRunSlotCount ?? null,
maxActiveQueues: record.maxActiveQueues ?? null,
};
}
function compactSchedulerHeartbeat(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
@@ -667,6 +718,10 @@ function compactTracePage(body: Record<string, unknown>, taskId: string, limit:
}
function parseTaskOptions(args: string[]): CodexTaskOptions {
assertKnownOptions(args, {
flags: ["--trace", "--tail", "--from-start", "--first", "--full", "--raw-summary"],
valueOptions: ["--before-seq", "--beforeSeq", "--after-seq", "--afterSeq", "--trace-limit", "--limit", "--tool-limit"],
}, "codex task");
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
@@ -685,6 +740,10 @@ function parseTaskOptions(args: string[]): CodexTaskOptions {
}
function parseOutputOptions(args: string[]): CodexOutputOptions {
assertKnownOptions(args, {
flags: ["--tail", "--from-start", "--first", "--full-text", "--raw"],
valueOptions: ["--before-seq", "--beforeSeq", "--after-seq", "--afterSeq", "--limit", "--max-text-chars"],
}, "codex output");
const beforeSeq = nullablePositiveNumberOption(args, ["--before-seq", "--beforeSeq"]);
const afterSeq = nonNegativeNumberOption(args, ["--after-seq", "--afterSeq"], 0);
const fromStart = hasFlag(args, "--from-start") || hasFlag(args, "--first");
@@ -700,14 +759,47 @@ function parseOutputOptions(args: string[]): CodexOutputOptions {
}
function parseTasksOptions(args: string[]): CodexTasksOptions {
assertKnownOptions(args, {
flags: ["--unread-only", "--unread", "--full", "--all"],
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}`);
const statusRaw = optionValue(args, ["--status"]);
const statusFilter = statusRaw === undefined
? null
: statusRaw.split(/[,\s]+/u).map((item) => item.trim()).filter(Boolean);
if (statusFilter !== null) {
const supported = new Set(["queued", "running", "judging", "retry_wait", "succeeded", "failed", "canceled"]);
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(", ")}`);
}
return {
queueId: optionValue(args, ["--queue", "--queue-id"]),
limit: positiveIntegerOption(args, ["--limit"], defaultTasksLimit, maxTasksLimit),
unreadOnly: hasFlag(args, "--unread-only"),
beforeId: optionValue(args, ["--before-id", "--beforeId"]),
unreadOnly: hasFlag(args, "--unread-only") || hasFlag(args, "--unread"),
statusFilter,
view: hasFlag(args, "--full") || hasFlag(args, "--all") ? "full" : viewValue,
};
}
function parseQueuesOptions(args: string[]): CodexQueuesOptions {
assertKnownOptions(args, {
flags: ["--full", "--all"],
valueOptions: ["--limit"],
}, "codex queues");
return {
full: hasFlag(args, "--full") || hasFlag(args, "--all"),
limit: positiveIntegerOption(args, ["--limit"], defaultTasksLimit, maxTasksLimit),
};
}
function parseJudgeOptions(args: string[]): CodexJudgeOptions {
assertKnownOptions(args, {
flags: ["--dry-run", "--no-call", "--include-prompt"],
valueOptions: ["--attempt", "--attempt-id", "--attemptIndex"],
}, "codex judge");
const rawAttempt = optionValue(args, ["--attempt", "--attempt-id", "--attemptIndex"]) ?? positionalArgs(args)[0];
let attempt: number | null = null;
if (rawAttempt !== undefined) {
@@ -823,7 +915,7 @@ function isTerminalTaskStatus(status: unknown): boolean {
}
function isActiveTaskStatus(status: unknown): boolean {
return status === "running" || status === "judging" || status === "retry_wait" || status === "queued";
return status === "running" || status === "judging";
}
function taskStatusRank(status: unknown): number {
@@ -855,16 +947,24 @@ function taskUnreadTerminal(task: Record<string, unknown>): boolean {
return isTerminalTaskStatus(status) && (readAt === null || readAt === undefined || readAt === "");
}
function taskQueuedRunnable(task: Record<string, unknown>): boolean {
const status = asString(task.status);
if (status === "queued" || status === "retry_wait") return true;
return asRecord(task.queuedReason)?.code === "ready";
}
function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, unknown> | null): CodexTasksEntry {
const taskId = asString(task.id);
const summaryCommands = summary === null ? null : asRecord(summary.commands);
const summaryLastAssistant = summary?.lastAssistantMessage ?? task.lastAssistantMessage;
const promptPreview = textPreview(asString(task.displayPrompt ?? task.basePrompt ?? task.prompt), 360);
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 outputCommand = `bun scripts/cli.ts codex output ${taskId} --tail --limit ${defaultOutputLimit}`;
return {
taskId,
queueId: asString(task.queueId) || null,
@@ -875,31 +975,51 @@ function taskWatchEntry(task: Record<string, unknown>, summary: Record<string, u
readAt: asString(task.readAt) || null,
unread: taskUnreadTerminal(task),
unreadTerminal: taskUnreadTerminal(task),
promptPreview,
queuedReason: compactQueuedReason(task.queuedReason),
lastAssistantMessage: summaryLastAssistant === undefined || summaryLastAssistant === null ? null : compactLastAssistant(summaryLastAssistant, false),
commands: {
show: typeof summaryCommands?.show === "string" && summaryCommands.show.length > 0 ? summaryCommands.show : showCommand,
trace: typeof summaryCommands?.trace === "string" && summaryCommands.trace.length > 0 ? summaryCommands.trace : traceCommand,
output: outputCommand,
read: `bun scripts/cli.ts codex read ${taskId}`,
full: `bun scripts/cli.ts codex task ${taskId} --full`,
},
};
}
function buildTaskWatchSection(tasks: Record<string, unknown>[], summaries: Map<string, Record<string, unknown>>, limit: number): CodexTasksSection {
function buildTaskWatchSection(
tasks: Record<string, unknown>[],
summaries: Map<string, Record<string, unknown>>,
limit: number,
nextCommand: string | null,
fullCommand: string,
): CodexTasksSection {
const visibleTasks = tasks.slice(0, limit);
const items = visibleTasks.map((task) => taskWatchEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null));
const truncated = tasks.length > limit;
return {
count: tasks.length,
returned: items.length,
truncated: tasks.length > limit,
truncated,
hasMore: truncated,
commands: {
next: truncated ? nextCommand : null,
full: fullCommand,
},
items,
};
}
function collectTaskWatchDegraded(summaryErrors: Array<{ taskId: string; message: string }>): CodexTasksDegraded | null {
if (summaryErrors.length === 0) return null;
function collectTaskWatchDegraded(summaryErrors: Array<{ taskId: string; message: string }>, omittedTaskCount = 0): CodexTasksDegraded | null {
if (summaryErrors.length === 0 && omittedTaskCount === 0) return null;
return {
summaryFetchFailedTaskIds: summaryErrors.map((error) => error.taskId),
summaryFetchErrorCount: summaryErrors.length,
reason: "task summary fetch failed for one or more entries; unread state still comes from task-level overview data",
summaryFetchOmittedTaskCount: omittedTaskCount,
reason: summaryErrors.length > 0
? "task summary fetch failed for one or more entries; unread state still comes from task-level overview data"
: "task summary fetch was intentionally omitted for bounded full view; use commands.show for per-task detail",
};
}
@@ -925,10 +1045,46 @@ function sortCompletedWatchTasks(tasks: Record<string, unknown>[]): Record<strin
});
}
function sortQueuedWatchTasks(tasks: Record<string, unknown>[]): Record<string, unknown>[] {
return [...tasks]
.filter((task) => taskQueuedRunnable(task))
.sort((left, right) => {
const rankDelta = taskStatusRank(asString(left.status)) - taskStatusRank(asString(right.status));
if (rankDelta !== 0) return rankDelta;
const timeDelta = taskTimelineMs(left) - taskTimelineMs(right);
if (timeDelta !== 0) return timeDelta;
return asString(left.id).localeCompare(asString(right.id));
});
}
function taskMatchesStatusFilter(task: Record<string, unknown>, statusFilter: string[] | null): boolean {
return statusFilter === null || statusFilter.includes(asString(task.status));
}
function filterTasksForOptions(tasks: Record<string, unknown>[], options: CodexTasksOptions): Record<string, unknown>[] {
return tasks
.filter((task) => options.queueId === undefined || asString(task.queueId) === options.queueId)
.filter((task) => taskMatchesStatusFilter(task, options.statusFilter))
.filter((task) => !options.unreadOnly || taskUnreadTerminal(task));
}
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.beforeId !== undefined) args.push("--before-id", options.beforeId);
args.push(...extra);
return `bun scripts/cli.ts ${args.join(" ")}`;
}
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>>();
const errors: Array<{ taskId: string; message: string }> = [];
for (const taskId of taskIds) {
for (const taskId of boundedIds) {
try {
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: 1 })}`)));
const summary = asRecord(response.body.summary) ?? {};
@@ -937,13 +1093,14 @@ function fetchTaskSummaries(taskIds: string[], fetcher: CodexResponseFetcher): {
errors.push({ taskId, message: error instanceof Error ? error.message : String(error) });
}
}
return { summaries, degraded: collectTaskWatchDegraded(errors) };
return { summaries, degraded: collectTaskWatchDegraded(errors, Math.max(0, taskIds.length - boundedIds.length)) };
}
async function fetchTaskSummariesAsync(taskIds: string[], fetcher: AsyncCodexResponseFetcher): Promise<{ summaries: Map<string, Record<string, unknown>>; degraded: CodexTasksDegraded | null }> {
const boundedIds = taskIds.slice(0, maxTasksLimit);
const summaries = new Map<string, Record<string, unknown>>();
const errors: Array<{ taskId: string; message: string }> = [];
await Promise.all(taskIds.map(async (taskId) => {
await Promise.all(boundedIds.map(async (taskId) => {
try {
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: 1 })}`)));
const summary = asRecord(response.body.summary) ?? {};
@@ -952,7 +1109,7 @@ async function fetchTaskSummariesAsync(taskIds: string[], fetcher: AsyncCodexRes
errors.push({ taskId, message: error instanceof Error ? error.message : String(error) });
}
}));
return { summaries, degraded: collectTaskWatchDegraded(errors) };
return { summaries, degraded: collectTaskWatchDegraded(errors, Math.max(0, taskIds.length - boundedIds.length)) };
}
type CodexTasksTaskPage = {
@@ -966,6 +1123,7 @@ function tasksListQueryString(options: CodexTasksOptions): string {
limit: 200,
priorityLimit: 200,
queueId: options.queueId,
beforeId: options.beforeId,
includeActive: 1,
selected: 0,
compact: 1,
@@ -1000,21 +1158,32 @@ function codexTasksOverviewResult(
summaries: Map<string, Record<string, unknown>>,
degraded: CodexTasksDegraded | null,
): Record<string, unknown> {
const allTasks = options.queueId === undefined ? taskPage.tasks : taskPage.tasks.filter((task) => asString(task.queueId) === options.queueId);
if (options.view === "full") return codexTasksFullResult(taskPage, upstream, options, summaries, degraded);
const allTasks = filterTasksForOptions(taskPage.tasks, options);
const runningTasks = sortRunningWatchTasks(allTasks);
const unreadCompletedTasks = sortCompletedWatchTasks(allTasks).filter((task) => taskUnreadTerminal(task));
const recentCompletedTasks = options.unreadOnly ? [] : sortCompletedWatchTasks(allTasks);
const runningSection = buildTaskWatchSection(runningTasks, summaries, options.limit);
const unreadSection = buildTaskWatchSection(unreadCompletedTasks, summaries, options.limit);
const recentSection = buildTaskWatchSection(recentCompletedTasks, summaries, options.limit);
const queuedTasks = 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 = taskListCommand({ ...options, view: "full" });
const runningSection = buildTaskWatchSection(runningTasks, summaries, options.limit, nextCommand, fullCommand);
const unreadSection = buildTaskWatchSection(unreadCompletedTasks, summaries, options.limit, nextCommand, fullCommand);
const recentSection = buildTaskWatchSection(recentCompletedTasks, summaries, options.limit, nextCommand, fullCommand);
const queuedSection = buildTaskWatchSection(queuedTasks, summaries, options.limit, nextCommand, fullCommand);
const pagination = taskPage.pagination;
const diagnostics = compactExecutionDiagnostics(asRecord(taskPage.queue)?.executionDiagnostics);
return {
upstream,
overview: {
supervisor: {
filters: {
view: options.view,
queueId: options.queueId ?? null,
limit: options.limit,
unreadOnly: options.unreadOnly,
status: options.statusFilter,
beforeId: options.beforeId ?? null,
},
source: {
endpoint: "/api/tasks/overview",
@@ -1026,29 +1195,92 @@ function codexTasksOverviewResult(
nextBeforeId: asString(pagination.nextBeforeId) || null,
includeActive: asBoolean(pagination.includeActive),
},
queue: taskPage.queue,
bounded: true,
disclosure: {
defaultView: "supervisor",
fullCommand,
next: nextCommand,
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
},
counts: {
scanned: allTasks.length,
running: runningSection.count,
completedUnread: unreadSection.count,
recentCompleted: recentSection.count,
queued: queuedSection.count,
},
executionDiagnostics: diagnostics,
degraded,
commands: {
refresh: `bun scripts/cli.ts codex tasks${options.queueId === undefined ? "" : ` --queue ${options.queueId}`}${options.unreadOnly ? " --unread-only" : ""}${options.limit === defaultTasksLimit ? "" : ` --limit ${options.limit}`}`,
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}`}`,
full: fullCommand,
next: nextCommand,
},
running: runningSection,
completedUnread: unreadSection,
recentCompleted: recentSection,
queued: queuedSection,
},
};
}
function codexTasksFullResult(
taskPage: CodexTasksTaskPage,
upstream: { ok: unknown; status: unknown },
options: CodexTasksOptions,
summaries: Map<string, Record<string, unknown>>,
degraded: CodexTasksDegraded | null,
): Record<string, unknown> {
const filtered = filterTasksForOptions(taskPage.tasks, options);
const visible = filtered.slice(0, options.limit);
const pagination = taskPage.pagination;
const nextBeforeId = asString(pagination.nextBeforeId) || null;
const sourceHasMore = asBoolean(pagination.hasMore);
const nextCommand = sourceHasMore && nextBeforeId !== null ? taskListCommand({ ...options, beforeId: nextBeforeId }) : null;
return {
upstream,
tasks: {
filters: {
view: "full",
queueId: options.queueId ?? null,
limit: options.limit,
unreadOnly: options.unreadOnly,
status: options.statusFilter,
beforeId: options.beforeId ?? null,
},
source: {
endpoint: "/api/tasks/overview",
returned: asNumber(pagination.returned, 0) || null,
total: asNumber(pagination.total, 0) || null,
hasMore: asBoolean(pagination.hasMore),
nextBeforeId,
},
count: filtered.length,
returned: visible.length,
hasMore: filtered.length > visible.length || sourceHasMore,
degraded,
commands: {
supervisor: taskListCommand({ ...options, view: "supervisor" }),
next: nextCommand,
rawOverview: `bun scripts/cli.ts microservice proxy code-queue /api/tasks/overview${tasksListQueryString(options)} --raw`,
},
items: visible.map((task) => taskWatchEntry(task, summaries.get(taskOverviewCandidateKey(task)) ?? null)),
},
};
}
function visibleTaskIdsForOverview(tasks: Record<string, unknown>[], options: CodexTasksOptions): string[] {
const filtered = filterTasksForOptions(tasks, options);
if (options.view === "full") {
return filtered.slice(0, options.limit).map((task) => taskOverviewCandidateKey(task)).filter((taskId) => taskId.length > 0);
}
return Array.from(new Set([
...sortRunningWatchTasks(tasks).slice(0, options.limit),
...sortCompletedWatchTasks(tasks).filter((task) => taskUnreadTerminal(task)).slice(0, options.limit),
...sortCompletedWatchTasks(tasks).slice(0, options.limit),
...sortRunningWatchTasks(filtered).slice(0, options.limit),
...sortCompletedWatchTasks(filtered).filter((task) => taskUnreadTerminal(task)).slice(0, options.limit),
...sortCompletedWatchTasks(filtered).slice(0, options.limit),
...sortQueuedWatchTasks(filtered).slice(0, options.limit),
].map((task) => taskOverviewCandidateKey(task))))
.filter((taskId) => taskId.length > 0);
}
@@ -1211,15 +1443,77 @@ function requireMergeTargetQueueId(args: string[], command: string): string {
return raw.trim();
}
function codeQueues(): unknown {
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/queues")));
function compactQueueRow(value: unknown): Record<string, unknown> {
const record = asRecord(value) ?? {};
return {
upstream: response.upstream,
queues: response.body.queues ?? [],
queue: compactQueueMutationSummary(response.body.queue),
id: record.id ?? null,
name: record.name ?? null,
total: record.total ?? null,
counts: record.counts ?? {},
unreadTerminal: record.unreadTerminal ?? 0,
activeTaskId: record.activeTaskId ?? null,
runnableTaskId: record.runnableTaskId ?? null,
processing: record.processing ?? null,
updatedAt: record.updatedAt ?? null,
commands: {
tasks: record.id === undefined || record.id === null ? null : `bun scripts/cli.ts codex tasks --queue ${String(record.id)} --limit ${defaultTasksLimit}`,
unread: record.id === undefined || record.id === null ? null : `bun scripts/cli.ts codex tasks --queue ${String(record.id)} --unread --limit ${defaultTasksLimit}`,
},
};
}
function compactQueuesResponse(body: Record<string, unknown>, options: CodexQueuesOptions, upstream: { ok: unknown; status: unknown }): Record<string, unknown> {
const queue = asRecord(body.queue) ?? asRecord(body.summary) ?? {};
const queues = asArray(body.queues).map(compactQueueRow);
const activeIds = stringList(queue.activeQueueIds);
const nonemptyQueues = queues.filter((row) => asNumber(row.total, 0) > 0);
const unreadQueues = queues.filter((row) => asNumber(row.unreadTerminal, 0) > 0);
const runnableQueues = queues.filter((row) => row.runnableTaskId !== null && row.runnableTaskId !== undefined);
const activeQueues = queues.filter((row) => typeof row.id === "string" && activeIds.includes(row.id));
const selected = options.full ? queues : Array.from(new Map([...activeQueues, ...unreadQueues, ...runnableQueues, ...nonemptyQueues].map((row) => [String(row.id), row])).values());
const visible = selected.slice(0, options.limit);
const diagnostics = compactExecutionDiagnostics(queue.executionDiagnostics);
return {
upstream,
queues: {
view: options.full ? "full" : "summary",
bounded: true,
count: selected.length,
returned: visible.length,
hasMore: selected.length > visible.length,
totals: {
totalTasks: queue.total ?? null,
queueCount: queue.queueCount ?? queues.length,
activeQueueCount: activeIds.length,
nonemptyQueueCount: nonemptyQueues.length,
unreadQueueCount: unreadQueues.length,
runnableQueueCount: runnableQueues.length,
},
activeQueueIds: queue.activeQueueIds ?? [],
activeTaskIds: queue.activeTaskIds ?? [],
queuedTaskIds: queue.queuedTaskIds ?? [],
counts: queue.counts ?? {},
unreadTerminal: queue.unreadTerminal ?? 0,
executionDiagnostics: diagnostics,
items: visible,
commands: {
refresh: `bun scripts/cli.ts codex queues${options.limit === defaultTasksLimit ? "" : ` --limit ${options.limit}`}`,
full: `bun scripts/cli.ts codex queues --full${options.limit === defaultTasksLimit ? "" : ` --limit ${options.limit}`}`,
tasks: `bun scripts/cli.ts codex tasks --view supervisor --limit ${Math.min(options.limit, defaultTasksLimit)}`,
unread: `bun scripts/cli.ts codex tasks --unread --limit ${Math.min(options.limit, defaultTasksLimit)}`,
raw: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw",
},
},
};
}
function codeQueues(optionArgs: string[] = []): unknown {
const options = parseQueuesOptions(optionArgs);
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/queues")));
if (options.full) return { upstream: response.upstream, queues: response.body.queues ?? [], queue: compactQueueMutationSummary(response.body.queue), commands: { summary: "bun scripts/cli.ts codex queues" } };
return compactQueuesResponse(response.body, options, response.upstream);
}
function codexCreateQueue(queueId: string): unknown {
return unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/queues"), { method: "POST", body: { queueId } }));
}
@@ -1275,6 +1569,27 @@ function referenceTaskIdsFromOptions(args: string[]): string[] {
}
function parseSubmitOptions(args: string[]): CodexSubmitOptions {
assertKnownOptions(args, {
flags: ["--prompt-stdin", "--stdin", "--dry-run"],
valueOptions: [
"--prompt-file",
"--file",
"--queue",
"--queue-id",
"--provider-id",
"--provider",
"--cwd",
"--workdir",
"--model",
"--reasoning-effort",
"--execution-mode",
"--mode",
"--max-attempts",
"--reference-task-id",
"--reference",
"--ref",
],
}, "codex submit");
const maxAttempts = args.some((arg) => arg === "--max-attempts")
? positiveIntegerOption(args, ["--max-attempts"], 99, 99)
: undefined;
@@ -1339,6 +1654,19 @@ function compactTaskMutationResponse(task: unknown, options: CompactTaskMutation
};
}
function codexReadTask(taskId: string): unknown {
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
return {
upstream: response.upstream,
task: compactTaskMutationResponse(response.body.task),
queue: compactQueueMutationSummary(response.body.queue),
commands: {
show: `bun scripts/cli.ts codex task ${taskId}`,
unread: `bun scripts/cli.ts codex tasks --unread --limit ${defaultTasksLimit}`,
},
};
}
function compactQueueMutationSummary(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
@@ -1355,6 +1683,42 @@ function compactQueueMutationSummary(value: unknown): Record<string, unknown> |
};
}
function compactSkillsStatus(value: unknown): Record<string, unknown> | null {
const record = asRecord(value);
if (record === null) return null;
return {
path: record.path ?? null,
mountPoint: record.mountPoint ?? null,
exists: record.exists ?? false,
available: record.available ?? false,
readonly: record.readonly ?? false,
skillCount: record.skillCount ?? 0,
cliSpecAvailable: record.cliSpecAvailable ?? false,
repairHint: record.repairHint ?? null,
};
}
function codeQueueDevReady(): unknown {
const response = unwrapCodexResponse(coreInternalFetch(codeQueueProxyPath("/api/dev-ready")));
const devReady = asRecord(response.body.devReady) ?? {};
return {
upstream: response.upstream,
devReady: {
ok: devReady.ok ?? false,
missingTools: devReady.missingTools ?? [],
workdir: devReady.workdir ?? null,
docker: devReady.docker ?? null,
codexConfig: devReady.codexConfig ?? null,
ssh: devReady.ssh ?? null,
skills: compactSkillsStatus(devReady.skills),
},
commands: {
health: "bun scripts/cli.ts codex dev-ready",
raw: "bun scripts/cli.ts microservice proxy code-queue /api/dev-ready --raw",
},
};
}
function codexSubmitTask(args: string[]): unknown {
const options = parseSubmitOptions(args);
const payload = submitPayload(options);
@@ -1399,6 +1763,10 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
if (action === "tasks" || action === "overview") {
return codexTasksQuery(args.slice(1));
}
if (action === "dev-ready" || action === "health") {
assertKnownOptions(args.slice(1), {}, `codex ${action}`);
return codeQueueDevReady();
}
if (action === "output") {
const taskId = requireTaskId(taskIdArg, "codex output");
return codexOutputQuery(taskId, args.slice(2));
@@ -1407,10 +1775,14 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
const taskId = requireTaskId(taskIdArg, "codex judge");
return codexJudgeQuery(taskId, args.slice(2));
}
if (action === "queues") return codeQueues();
if (action === "read" || action === "mark-read") {
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
return codexReadTask(taskId);
}
if (action === "queues") return codeQueues(args.slice(1));
if (action === "queue") {
const sub = taskIdArg ?? "list";
if (sub === "list") return codeQueues();
if (sub === "list") return codeQueues(args.slice(2));
if (sub === "create") return codexCreateQueue(requireQueueId(args.slice(2), "queue create"));
if (sub === "merge") {
const mergeArgs = args.slice(2);
@@ -1425,5 +1797,5 @@ export async function runCodeQueueCommand(_config: UniDeskConfig, args: string[]
const taskId = requireTaskId(taskIdArg, `codex ${action}`);
return codexInterruptTask(taskId);
}
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, queues, queue list, queue create, queue merge, move, interrupt, cancel");
throw new Error("codex command must be one of: submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, queues, queue list, queue create, queue merge, move, interrupt, cancel");
}