feat: add bounded codex unread triage
This commit is contained in:
+405
-5
@@ -20,6 +20,7 @@ const supervisorRecentCompletedLimit = 3;
|
||||
const supervisorPromptPreviewChars = 70;
|
||||
const supervisorBodyPreviewChars = 70;
|
||||
const supervisorRecentBodyPreviewChars = 50;
|
||||
const unreadTriageCountLimit = 12;
|
||||
const diagnosticsIdPreviewLimit = 3;
|
||||
const diagnosticsReasonPreviewLimit = 2;
|
||||
const mutationQueueIdPreviewLimit = 15;
|
||||
@@ -269,6 +270,21 @@ interface CodexTasksOptions {
|
||||
view: "supervisor" | "full";
|
||||
}
|
||||
|
||||
type CodexUnreadAction = "summary" | "mark-read";
|
||||
|
||||
interface CodexUnreadOptions {
|
||||
queueId: string | undefined;
|
||||
requestedLimit: number;
|
||||
limit: number;
|
||||
beforeId: string | undefined;
|
||||
statusFilter: string[] | null;
|
||||
repoFilter: string | undefined;
|
||||
issueFilter: string | undefined;
|
||||
action: CodexUnreadAction;
|
||||
confirm: boolean;
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
interface CodexTasksEntry {
|
||||
taskId: string;
|
||||
queueId: string | null;
|
||||
@@ -2180,6 +2196,64 @@ function parseTasksOptions(args: string[]): CodexTasksOptions {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRepoFilter(value: string | undefined): string | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
const trimmed = value.trim()
|
||||
.replace(/^https?:\/\/github\.com\//iu, "")
|
||||
.replace(/\.git$/iu, "")
|
||||
.replace(/^\/+|\/+$/gu, "");
|
||||
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(trimmed)) {
|
||||
throw new Error(`--repo must be owner/name; got ${value}`);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
function normalizeIssueFilter(value: string | undefined): string | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
const trimmed = value.trim().replace(/^#/u, "");
|
||||
const number = Number(trimmed);
|
||||
if (!Number.isInteger(number) || number <= 0) throw new Error(`--issue must be a positive issue number; got ${value}`);
|
||||
return `#${number}`;
|
||||
}
|
||||
|
||||
function parseUnreadOptions(args: string[]): CodexUnreadOptions {
|
||||
const first = args[0];
|
||||
const hasSubcommand = first !== undefined && !first.startsWith("--");
|
||||
const subcommand = hasSubcommand ? first : "summary";
|
||||
if (subcommand !== "summary" && subcommand !== "list" && subcommand !== "mark-read") {
|
||||
throw new Error(`codex unread subcommand must be summary, list, or mark-read; got ${subcommand}`);
|
||||
}
|
||||
const optionArgs = hasSubcommand ? args.slice(1) : args;
|
||||
assertKnownOptions(optionArgs, {
|
||||
flags: ["--mark-read", "--confirm", "--dry-run"],
|
||||
valueOptions: ["--queue", "--queue-id", "--limit", "--status", "--repo", "--issue", "--before-id", "--beforeId"],
|
||||
}, "codex unread");
|
||||
const statusRaw = optionValue(optionArgs, ["--status"]);
|
||||
const statusFilter = statusRaw === undefined
|
||||
? null
|
||||
: statusRaw.split(/[,\s]+/u).map((item) => item.trim()).filter(Boolean);
|
||||
if (statusFilter !== null) {
|
||||
const supported = new Set(["succeeded", "failed", "canceled"]);
|
||||
const unsupported = statusFilter.filter((status) => !supported.has(status));
|
||||
if (unsupported.length > 0) throw new Error(`unsupported --status value for codex unread: ${unsupported.join(", ")}; supported terminal statuses: ${Array.from(supported).join(", ")}`);
|
||||
}
|
||||
const requestedLimit = positiveIntegerOption(optionArgs, ["--limit"], defaultTasksLimit);
|
||||
const action: CodexUnreadAction = subcommand === "mark-read" || hasFlag(optionArgs, "--mark-read") ? "mark-read" : "summary";
|
||||
const explicitDryRun = hasFlag(optionArgs, "--dry-run");
|
||||
return {
|
||||
queueId: optionValue(optionArgs, ["--queue", "--queue-id"]),
|
||||
requestedLimit,
|
||||
limit: Math.min(requestedLimit, maxTasksLimit),
|
||||
beforeId: optionValue(optionArgs, ["--before-id", "--beforeId"]),
|
||||
statusFilter,
|
||||
repoFilter: normalizeRepoFilter(optionValue(optionArgs, ["--repo"])),
|
||||
issueFilter: normalizeIssueFilter(optionValue(optionArgs, ["--issue"])),
|
||||
action,
|
||||
confirm: hasFlag(optionArgs, "--confirm"),
|
||||
dryRun: explicitDryRun || action === "summary",
|
||||
};
|
||||
}
|
||||
|
||||
function parseQueuesOptions(args: string[]): CodexQueuesOptions {
|
||||
assertKnownOptions(args, {
|
||||
flags: ["--full", "--all"],
|
||||
@@ -2983,6 +3057,290 @@ function loadCodexTasks(taskArgs: CodexTasksOptions, fetcher: CodexResponseFetch
|
||||
return { upstream: response.upstream, page: { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks } };
|
||||
}
|
||||
|
||||
function unreadLoadOptions(options: CodexUnreadOptions): CodexTasksOptions {
|
||||
return {
|
||||
queueId: options.queueId,
|
||||
requestedLimit: maxTasksLimit,
|
||||
limit: maxTasksLimit,
|
||||
beforeId: undefined,
|
||||
unreadOnly: true,
|
||||
statusFilter: null,
|
||||
view: "full",
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageCommand(options: CodexUnreadOptions, action: CodexUnreadAction = "summary", extra: string[] = []): string {
|
||||
const args = ["codex", "unread"];
|
||||
if (action === "mark-read") args.push("mark-read");
|
||||
if (options.queueId !== undefined) args.push("--queue", options.queueId);
|
||||
if (options.repoFilter !== undefined) args.push("--repo", options.repoFilter);
|
||||
if (options.issueFilter !== undefined) args.push("--issue", options.issueFilter.replace(/^#/u, ""));
|
||||
if (options.statusFilter !== null) args.push("--status", options.statusFilter.join(","));
|
||||
if (options.requestedLimit !== defaultTasksLimit) args.push("--limit", String(options.requestedLimit));
|
||||
if (options.beforeId !== undefined) args.push("--before-id", options.beforeId);
|
||||
args.push(...extra);
|
||||
return `bun scripts/cli.ts ${args.join(" ")}`;
|
||||
}
|
||||
|
||||
function taskTriageSearchText(task: Record<string, unknown>): string {
|
||||
return [
|
||||
asString(task.displayPrompt),
|
||||
asString(task.basePrompt),
|
||||
asString(task.prompt),
|
||||
asString(task.cwd),
|
||||
asString(task.lastError),
|
||||
...stringList(task.referenceTaskIds),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function taskRepoRefs(task: Record<string, unknown>): string[] {
|
||||
const text = taskTriageSearchText(task);
|
||||
const repos = new Set<string>();
|
||||
for (const match of text.matchAll(/\b([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#\d{1,6}\b/gu)) {
|
||||
const repo = match[1] ?? "";
|
||||
if (repo.length > 0) repos.add(repo);
|
||||
}
|
||||
for (const match of text.matchAll(/\bgithub\.com[:/]([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+?)(?:\.git)?(?:[#/\s?]|$)/giu)) {
|
||||
const owner = match[1] ?? "";
|
||||
const name = (match[2] ?? "").replace(/\.git$/iu, "");
|
||||
if (owner.length > 0 && name.length > 0) repos.add(`${owner}/${name}`);
|
||||
}
|
||||
return Array.from(repos).sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
function taskIssueRefsForTriage(task: Record<string, unknown>): string[] {
|
||||
const text = taskTriageSearchText(task);
|
||||
const issues = new Set<string>();
|
||||
for (const match of text.matchAll(/#(\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]}`);
|
||||
return Array.from(issues)
|
||||
.sort((left, right) => Number(left.slice(1)) - Number(right.slice(1)));
|
||||
}
|
||||
|
||||
function lowerSet(values: string[]): Set<string> {
|
||||
return new Set(values.map((value) => value.toLowerCase()));
|
||||
}
|
||||
|
||||
function taskMatchesUnreadFilters(task: Record<string, unknown>, options: CodexUnreadOptions): boolean {
|
||||
if (!taskUnreadTerminal(task)) return false;
|
||||
if (!taskMatchesStatusFilter(task, options.statusFilter)) return false;
|
||||
if (options.queueId !== undefined && asString(task.queueId) !== options.queueId) return false;
|
||||
if (options.repoFilter !== undefined && !lowerSet(taskRepoRefs(task)).has(options.repoFilter.toLowerCase())) return false;
|
||||
if (options.issueFilter !== undefined && !taskIssueRefsForTriage(task).includes(options.issueFilter)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function unreadSortedCandidates(tasks: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown>[] {
|
||||
return sortCompletedWatchTasks(tasks.filter((task) => taskMatchesUnreadFilters(task, options)));
|
||||
}
|
||||
|
||||
function unreadPageCandidates(candidates: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown>[] {
|
||||
if (options.beforeId === undefined) return candidates;
|
||||
const beforeIndex = candidates.findIndex((task) => taskOverviewCandidateKey(task) === options.beforeId);
|
||||
return beforeIndex < 0 ? candidates : candidates.slice(beforeIndex + 1);
|
||||
}
|
||||
|
||||
function incrementCount(map: Map<string, number>, key: string): void {
|
||||
map.set(key, (map.get(key) ?? 0) + 1);
|
||||
}
|
||||
|
||||
function countBucket(map: Map<string, number>, limit = unreadTriageCountLimit): Record<string, unknown> {
|
||||
const rows = Array.from(map.entries())
|
||||
.map(([key, count]) => ({ key, count }))
|
||||
.sort((left, right) => right.count - left.count || left.key.localeCompare(right.key));
|
||||
return {
|
||||
totalDistinct: rows.length,
|
||||
returned: Math.min(rows.length, limit),
|
||||
truncated: rows.length > limit,
|
||||
items: rows.slice(0, limit),
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageCounts(candidates: Record<string, unknown>[]): Record<string, unknown> {
|
||||
const byRepo = new Map<string, number>();
|
||||
const byIssue = new Map<string, number>();
|
||||
const byStatus = new Map<string, number>();
|
||||
const byQueue = new Map<string, number>();
|
||||
for (const task of candidates) {
|
||||
incrementCount(byStatus, asString(task.status) || "unknown");
|
||||
incrementCount(byQueue, asString(task.queueId) || "unknown");
|
||||
const repos = taskRepoRefs(task);
|
||||
const issues = taskIssueRefsForTriage(task);
|
||||
if (repos.length === 0) incrementCount(byRepo, "unknown");
|
||||
for (const repo of repos) incrementCount(byRepo, repo);
|
||||
if (issues.length === 0) incrementCount(byIssue, "unknown");
|
||||
for (const issue of issues) incrementCount(byIssue, issue);
|
||||
}
|
||||
return {
|
||||
totalUnreadTerminal: candidates.length,
|
||||
byRepo: countBucket(byRepo),
|
||||
byIssue: countBucket(byIssue),
|
||||
byStatus: countBucket(byStatus),
|
||||
byQueue: countBucket(byQueue),
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageItem(task: Record<string, unknown>): Record<string, unknown> {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
return {
|
||||
id: taskId,
|
||||
queue: asString(task.queueId) || null,
|
||||
status: asString(task.status) || null,
|
||||
repos: taskRepoRefs(task),
|
||||
issues: taskIssueRefsForTriage(task),
|
||||
updatedAt: asString(task.updatedAt) || null,
|
||||
finishedAt: asString(task.finishedAt) || null,
|
||||
commands: {
|
||||
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}`,
|
||||
read: `bun scripts/cli.ts codex read ${taskId}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function unreadTriageSummary(upstream: { ok: unknown; status: unknown }, page: CodexTasksTaskPage, options: CodexUnreadOptions): {
|
||||
result: Record<string, unknown>;
|
||||
visibleCandidates: Record<string, unknown>[];
|
||||
} {
|
||||
const allCandidates = unreadSortedCandidates(page.tasks, options);
|
||||
const pagedCandidates = unreadPageCandidates(allCandidates, options);
|
||||
const visibleCandidates = pagedCandidates.slice(0, options.limit);
|
||||
const hasMore = pagedCandidates.length > visibleCandidates.length;
|
||||
const nextBeforeId = hasMore ? taskOverviewCandidateKey(visibleCandidates.at(-1) ?? {}) || null : null;
|
||||
const nextCommand = nextBeforeId === null ? null : unreadTriageCommand({ ...options, beforeId: nextBeforeId });
|
||||
const readCommand = unreadTriageCommand(options, "mark-read", ["--confirm"]);
|
||||
return {
|
||||
visibleCandidates,
|
||||
result: {
|
||||
ok: true,
|
||||
upstream,
|
||||
unreadTriage: {
|
||||
filters: {
|
||||
queueId: options.queueId ?? null,
|
||||
repo: options.repoFilter ?? null,
|
||||
issue: options.issueFilter ?? null,
|
||||
status: options.statusFilter,
|
||||
requestedLimit: options.requestedLimit,
|
||||
limit: options.limit,
|
||||
limitCapped: options.requestedLimit > options.limit,
|
||||
beforeId: options.beforeId ?? null,
|
||||
},
|
||||
readOnly: options.action === "summary" || options.dryRun,
|
||||
bounded: true,
|
||||
disclosure: {
|
||||
policy: "summary counts plus bounded task ids only; no raw prompt, final response, trace, or output is included by default",
|
||||
countBucketLimit: unreadTriageCountLimit,
|
||||
itemLimit: options.limit,
|
||||
mutationPolicy: "batch mark-read requires codex unread mark-read --confirm; per-task read remains codex read <taskId>",
|
||||
},
|
||||
counts: unreadTriageCounts(allCandidates),
|
||||
newest: {
|
||||
count: allCandidates.length,
|
||||
returned: visibleCandidates.length,
|
||||
hasMore,
|
||||
nextBeforeId,
|
||||
commands: {
|
||||
next: nextCommand,
|
||||
showTemplate: "bun scripts/cli.ts codex task <taskId>",
|
||||
detailTemplate: "bun scripts/cli.ts codex task <taskId> --detail",
|
||||
traceTemplate: `bun scripts/cli.ts codex task <taskId> --trace --tail --limit ${defaultTraceLimit}`,
|
||||
outputTemplate: `bun scripts/cli.ts codex output <taskId> --tail --limit ${defaultOutputLimit}`,
|
||||
readTemplate: "bun scripts/cli.ts codex read <taskId>",
|
||||
},
|
||||
items: visibleCandidates.map(unreadTriageItem),
|
||||
},
|
||||
commands: {
|
||||
refresh: unreadTriageCommand(options),
|
||||
tasksUnread: taskListCommand({
|
||||
queueId: options.queueId,
|
||||
requestedLimit: Math.min(options.requestedLimit, defaultTasksLimit),
|
||||
limit: Math.min(options.limit, defaultTasksLimit),
|
||||
beforeId: undefined,
|
||||
unreadOnly: true,
|
||||
statusFilter: options.statusFilter,
|
||||
view: "supervisor",
|
||||
}),
|
||||
byRepo: "bun scripts/cli.ts codex unread --repo owner/name",
|
||||
byIssue: "bun scripts/cli.ts codex unread --issue <number>",
|
||||
byStatus: "bun scripts/cli.ts codex unread --status succeeded|failed|canceled",
|
||||
byQueue: "bun scripts/cli.ts codex unread --queue <queueId>",
|
||||
perTaskRead: "bun scripts/cli.ts codex read <taskId>",
|
||||
batchReadDryRun: unreadTriageCommand(options, "mark-read", ["--dry-run"]),
|
||||
batchReadConfirm: readCommand,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function codexUnreadMutationGuard(result: Record<string, unknown>, visibleCandidates: Record<string, unknown>[], options: CodexUnreadOptions): Record<string, unknown> {
|
||||
const triage = asRecord(result.unreadTriage) ?? {};
|
||||
return {
|
||||
...result,
|
||||
ok: false,
|
||||
unreadTriage: {
|
||||
...triage,
|
||||
readOnly: true,
|
||||
mutation: {
|
||||
requested: true,
|
||||
confirmed: false,
|
||||
blocked: true,
|
||||
reason: "batch mark-read requires --confirm; default codex unread output is read-only",
|
||||
candidateTaskIds: visibleCandidates.map(taskOverviewCandidateKey),
|
||||
command: unreadTriageCommand(options, "mark-read", ["--confirm"]),
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function codexUnreadMutationResult(result: Record<string, unknown>, options: CodexUnreadOptions, results: Record<string, unknown>[]): Record<string, unknown> {
|
||||
const triage = asRecord(result.unreadTriage) ?? {};
|
||||
const failed = results.filter((item) => item.ok === false);
|
||||
return {
|
||||
...result,
|
||||
ok: failed.length === 0,
|
||||
unreadTriage: {
|
||||
...triage,
|
||||
readOnly: false,
|
||||
mutation: {
|
||||
requested: true,
|
||||
confirmed: true,
|
||||
action: "mark-read",
|
||||
limit: options.limit,
|
||||
attempted: results.length,
|
||||
succeeded: results.length - failed.length,
|
||||
failed: failed.length,
|
||||
results,
|
||||
commands: {
|
||||
refresh: unreadTriageCommand({ ...options, action: "summary", confirm: false, dryRun: true }),
|
||||
tasksUnread: `bun scripts/cli.ts codex unread --limit ${defaultTasksLimit}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function codexUnreadTriage(taskArgs: string[], fetcher: CodexResponseFetcher = coreInternalFetch): unknown {
|
||||
const options = parseUnreadOptions(taskArgs);
|
||||
const { upstream, page } = loadCodexTasks(unreadLoadOptions(options), fetcher);
|
||||
const { result, visibleCandidates } = unreadTriageSummary(upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const response = unwrapCodexResponse(fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
results.push({ taskId, ok: true, upstream: response.upstream });
|
||||
} catch (error) {
|
||||
results.push({ taskId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
return codexUnreadMutationResult(result, options, results);
|
||||
}
|
||||
|
||||
function codexTasksOverviewResult(
|
||||
taskPage: CodexTasksTaskPage,
|
||||
upstream: { ok: unknown; status: unknown },
|
||||
@@ -3163,6 +3521,10 @@ export function codexTasksQueryForTest(taskArgs: string[], fetcher: CodexRespons
|
||||
return codexTasksQuery(taskArgs, fetcher);
|
||||
}
|
||||
|
||||
export function codexUnreadTriageForTest(taskArgs: string[], fetcher: CodexResponseFetcher): unknown {
|
||||
return codexUnreadTriage(taskArgs, fetcher);
|
||||
}
|
||||
|
||||
async function codexTasksQueryAsync(taskArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const options = parseTasksOptions(taskArgs);
|
||||
const byId = new Map<string, Record<string, unknown>>();
|
||||
@@ -3186,6 +3548,41 @@ async function codexTasksQueryAsync(taskArgs: string[], fetcher: AsyncCodexRespo
|
||||
return codexTasksOverviewResult(page, response.upstream, options, summaries, degraded);
|
||||
}
|
||||
|
||||
async function codexUnreadTriageAsync(taskArgs: string[], fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const options = parseUnreadOptions(taskArgs);
|
||||
const loadOptions = unreadLoadOptions(options);
|
||||
const byId = new Map<string, Record<string, unknown>>();
|
||||
const response = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/overview${tasksListQueryString(loadOptions)}`)));
|
||||
const pageTasks = asArray(response.body.tasks).map((task) => asRecord(task)).filter((task): task is Record<string, unknown> => task !== null);
|
||||
for (const task of pageTasks) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
if (taskId.length === 0) continue;
|
||||
const existing = byId.get(taskId);
|
||||
if (existing === undefined || taskTimelineMs(task) >= taskTimelineMs(existing)) byId.set(taskId, task);
|
||||
}
|
||||
const tasks = Array.from(byId.values()).sort((left, right) => {
|
||||
const leftTime = taskTimelineMs(left);
|
||||
const rightTime = taskTimelineMs(right);
|
||||
if (leftTime !== rightTime) return rightTime - leftTime;
|
||||
return asString(left.id).localeCompare(asString(right.id));
|
||||
});
|
||||
const page: CodexTasksTaskPage = { queue: asRecord(response.body.queue), pagination: asRecord(response.body.pagination) ?? {}, tasks };
|
||||
const { result, visibleCandidates } = unreadTriageSummary(response.upstream, page, options);
|
||||
if (options.action !== "mark-read" || options.dryRun) return result;
|
||||
if (!options.confirm) return codexUnreadMutationGuard(result, visibleCandidates, options);
|
||||
const results: Record<string, unknown>[] = [];
|
||||
for (const task of visibleCandidates) {
|
||||
const taskId = taskOverviewCandidateKey(task);
|
||||
try {
|
||||
const readResponse = unwrapCodexResponse(await fetcher(codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/read`), { method: "POST", body: {} }));
|
||||
results.push({ taskId, ok: true, upstream: readResponse.upstream });
|
||||
} catch (error) {
|
||||
results.push({ taskId, ok: false, error: error instanceof Error ? error.message : String(error) });
|
||||
}
|
||||
}
|
||||
return codexUnreadMutationResult(result, options, results);
|
||||
}
|
||||
|
||||
async function codexTaskSummaryAsync(taskId: string, options: CodexTaskOptions, fetcher: AsyncCodexResponseFetcher): Promise<unknown> {
|
||||
const summaryPath = codeQueueProxyPath(`/api/tasks/${encodeURIComponent(taskId)}/summary${queryString({ toolLimit: options.toolLimit })}`);
|
||||
const summaryResponse = unwrapCodexResponse(await fetcher(summaryPath));
|
||||
@@ -3242,7 +3639,7 @@ export async function codexJudgeQueryAsync(taskId: string, optionArgs: string[],
|
||||
return codexTaskJudgeAsync(taskId, parseJudgeOptions(optionArgs), fetcher);
|
||||
}
|
||||
|
||||
export { codexQueuesQueryAsync, codexTasksQueryAsync };
|
||||
export { codexQueuesQueryAsync, codexTasksQueryAsync, codexUnreadTriageAsync };
|
||||
|
||||
function requireQueueId(args: string[], command: string): string {
|
||||
const index = args.indexOf("--queue");
|
||||
@@ -3337,7 +3734,7 @@ function compactQueueRow(value: unknown): Record<string, unknown> {
|
||||
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}`,
|
||||
unread: record.id === undefined || record.id === null ? null : `bun scripts/cli.ts codex unread --queue ${String(record.id)} --limit ${defaultTasksLimit}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -3438,7 +3835,7 @@ function compactQueuesResponse(body: Record<string, unknown>, options: CodexQueu
|
||||
first: queueListCommand({ full: options.full, limit: options.limit, offset: 0 }),
|
||||
full: queueListCommand({ full: true, limit: options.limit, offset: 0 }),
|
||||
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)}`,
|
||||
unread: `bun scripts/cli.ts codex unread --limit ${Math.min(options.limit, defaultTasksLimit)}`,
|
||||
raw: "bun scripts/cli.ts microservice proxy code-queue /api/queues --raw --full",
|
||||
},
|
||||
},
|
||||
@@ -4077,7 +4474,7 @@ function codexReadTaskWithFetcher(taskId: string, fetcher: CodexResponseFetcher)
|
||||
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}`,
|
||||
unread: `bun scripts/cli.ts codex tasks --unread --limit ${defaultTasksLimit}`,
|
||||
unread: `bun scripts/cli.ts codex unread --limit ${defaultTasksLimit}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -5633,6 +6030,9 @@ export async function runCodeQueueCommand(config: UniDeskConfig, args: string[])
|
||||
if (action === "tasks" || action === "overview") {
|
||||
return codexTasksQuery(args.slice(1));
|
||||
}
|
||||
if (action === "unread" || action === "terminal-unread") {
|
||||
return codexUnreadTriage(args.slice(1));
|
||||
}
|
||||
if (action === "dev-ready" || action === "health") {
|
||||
assertKnownOptions(args.slice(1), {}, `codex ${action}`);
|
||||
return codeQueueDevReady();
|
||||
@@ -5673,5 +6073,5 @@ export async function runCodeQueueCommand(config: UniDeskConfig, args: string[])
|
||||
const taskId = requireTaskId(taskIdArg, "codex steer");
|
||||
return codexSteerTask(taskId, args.slice(2));
|
||||
}
|
||||
throw new Error("codex command must be one of: prompt-lint, submit, enqueue, task, summary, show, tasks, overview, output, judge, read, mark-read, dev-ready, health, skills-sync, pr-preflight, runtime-preflight, queues, queue list, queue create, queue merge, move, steer, interrupt, cancel");
|
||||
throw new Error("codex command must be one of: prompt-lint, submit, enqueue, task, summary, show, tasks, overview, unread, terminal-unread, output, judge, read, mark-read, dev-ready, health, skills-sync, pr-preflight, runtime-preflight, queues, queue list, queue create, queue merge, move, steer, interrupt, cancel");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user