feat: add bounded codex unread triage
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
import { codexUnreadTriageForTest } from "./src/code-queue";
|
||||
|
||||
type JsonRecord = Record<string, unknown>;
|
||||
type RequestRecord = { path: string; method: string; body: unknown };
|
||||
|
||||
function assertCondition(condition: unknown, message: string, detail: JsonRecord = {}): void {
|
||||
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
||||
}
|
||||
|
||||
function asRecord(value: unknown): JsonRecord {
|
||||
assertCondition(typeof value === "object" && value !== null && !Array.isArray(value), "expected JSON object", { value });
|
||||
return value as JsonRecord;
|
||||
}
|
||||
|
||||
function asArray(value: unknown): unknown[] {
|
||||
assertCondition(Array.isArray(value), "expected JSON array", { value });
|
||||
return value as unknown[];
|
||||
}
|
||||
|
||||
function task(id: string, queueId: string, status: string, updatedAt: string, readAt: string | null, prompt: string): JsonRecord {
|
||||
return {
|
||||
id,
|
||||
queueId,
|
||||
status,
|
||||
currentAttempt: 1,
|
||||
updatedAt,
|
||||
finishedAt: status === "running" ? null : updatedAt,
|
||||
readAt,
|
||||
displayPrompt: prompt,
|
||||
basePrompt: prompt,
|
||||
prompt,
|
||||
cwd: "/root/unidesk",
|
||||
referenceTaskIds: [],
|
||||
lastError: null,
|
||||
};
|
||||
}
|
||||
|
||||
function fixtureResponse(path: string, init?: { method?: string; body?: unknown }, requests: RequestRecord[] = []): JsonRecord {
|
||||
const method = init?.method ?? "GET";
|
||||
requests.push({ path, method, body: init?.body });
|
||||
if (path.endsWith("/read")) {
|
||||
const taskId = decodeURIComponent(path.split("/api/tasks/")[1]?.split("/")[0] ?? "unknown");
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
task: task(taskId, "default", "succeeded", "2026-05-22T00:10:00.000Z", "2026-05-22T00:10:01.000Z", "read response prompt should stay compact"),
|
||||
queue: { counts: { succeeded: 1 }, unreadTerminal: 0 },
|
||||
},
|
||||
};
|
||||
}
|
||||
assertCondition(path.startsWith("/api/microservices/code-queue/proxy/api/tasks/overview"), "unexpected path", { path, method });
|
||||
return {
|
||||
ok: true,
|
||||
status: 200,
|
||||
body: {
|
||||
ok: true,
|
||||
queue: { unreadTerminal: 4 },
|
||||
pagination: {
|
||||
limit: 200,
|
||||
returned: 6,
|
||||
total: 6,
|
||||
hasMore: false,
|
||||
nextBeforeId: null,
|
||||
includeActive: true,
|
||||
},
|
||||
tasks: [
|
||||
task("task-new-1", "default", "succeeded", "2026-05-22T00:05:00.000Z", null, "pikasTech/unidesk#20 closeout RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
task("task-new-2", "review", "failed", "2026-05-22T00:04:00.000Z", null, "https://github.com/pikasTech/unidesk/issues/20 failed task RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
task("task-new-3", "review", "canceled", "2026-05-22T00:03:00.000Z", null, "pikasTech/unidesk#21 canceled task RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
task("task-unknown", "misc", "succeeded", "2026-05-22T00:02:00.000Z", null, "no repo marker RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
task("task-read", "default", "succeeded", "2026-05-22T00:01:00.000Z", "2026-05-22T00:01:01.000Z", "pikasTech/unidesk#20 already read RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
task("task-running", "default", "running", "2026-05-22T00:00:00.000Z", null, "pikasTech/unidesk#20 running RAW_PROMPT_SHOULD_NOT_LEAK"),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function countItems(bucket: JsonRecord): JsonRecord[] {
|
||||
return asArray(bucket.items).map(asRecord);
|
||||
}
|
||||
|
||||
function itemCount(bucket: JsonRecord, key: string): number {
|
||||
const row = countItems(bucket).find((item) => item.key === key);
|
||||
return typeof row?.count === "number" ? row.count : 0;
|
||||
}
|
||||
|
||||
export function runCodeQueueUnreadTriageContract(): JsonRecord {
|
||||
const requests: RequestRecord[] = [];
|
||||
const fetcher = (path: string, init?: { method?: string; body?: unknown }): JsonRecord => fixtureResponse(path, init, requests);
|
||||
|
||||
const summary = codexUnreadTriageForTest(["--limit", "2"], fetcher);
|
||||
const summaryBody = JSON.stringify(summary);
|
||||
const triage = asRecord(asRecord(summary).unreadTriage);
|
||||
const counts = asRecord(triage.counts);
|
||||
const newest = asRecord(triage.newest);
|
||||
const commands = asRecord(triage.commands);
|
||||
|
||||
assertCondition(asRecord(summary).ok === true, "default unread triage should succeed", asRecord(summary));
|
||||
assertCondition(triage.readOnly === true && triage.bounded === true, "default unread triage must be read-only and bounded", triage);
|
||||
assertCondition(counts.totalUnreadTerminal === 4, "counts should include only unread terminal tasks", counts);
|
||||
assertCondition(itemCount(asRecord(counts.byRepo), "pikasTech/unidesk") === 3, "repo counts should group owner/name refs", counts);
|
||||
assertCondition(itemCount(asRecord(counts.byIssue), "#20") === 2, "issue counts should group issue refs", counts);
|
||||
assertCondition(itemCount(asRecord(counts.byStatus), "succeeded") === 2, "status counts should include terminal statuses", counts);
|
||||
assertCondition(itemCount(asRecord(counts.byQueue), "review") === 2, "queue counts should include queues", counts);
|
||||
assertCondition(newest.returned === 2 && newest.hasMore === true, "newest items should obey --limit and expose pagination", newest);
|
||||
assertCondition(typeof commands.perTaskRead === "string" && String(commands.perTaskRead).includes("codex read <taskId>"), "triage should preserve per-task read drill-down", commands);
|
||||
assertCondition(!summaryBody.includes("RAW_PROMPT_SHOULD_NOT_LEAK"), "triage output must not dump raw prompt text", { summaryBody });
|
||||
assertCondition(!requests.some((request) => request.path.includes("/summary")), "triage must not fetch per-task summaries by default", { requests });
|
||||
assertCondition(!requests.some((request) => request.method === "POST"), "default triage must not mutate", { requests });
|
||||
|
||||
const guardStart = requests.length;
|
||||
const guarded = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2"], fetcher);
|
||||
const guardedTriage = asRecord(asRecord(guarded).unreadTriage);
|
||||
const guardedMutation = asRecord(guardedTriage.mutation);
|
||||
assertCondition(asRecord(guarded).ok === false, "batch mark-read without --confirm should fail closed", asRecord(guarded));
|
||||
assertCondition(guardedMutation.blocked === true && guardedMutation.confirmed === false, "confirm guard should describe blocked mutation", guardedMutation);
|
||||
assertCondition(!requests.slice(guardStart).some((request) => request.method === "POST"), "missing --confirm must not POST read calls", { requests: requests.slice(guardStart) });
|
||||
|
||||
const confirmStart = requests.length;
|
||||
const confirmed = codexUnreadTriageForTest(["mark-read", "--repo", "pikasTech/unidesk", "--issue", "20", "--limit", "2", "--confirm"], fetcher);
|
||||
const confirmedTriage = asRecord(asRecord(confirmed).unreadTriage);
|
||||
const confirmedMutation = asRecord(confirmedTriage.mutation);
|
||||
const readPosts = requests.slice(confirmStart).filter((request) => request.method === "POST" && request.path.endsWith("/read"));
|
||||
assertCondition(asRecord(confirmed).ok === true, "confirmed batch mark-read should succeed", asRecord(confirmed));
|
||||
assertCondition(confirmedTriage.readOnly === false && confirmedMutation.confirmed === true, "confirmed mutation should be explicit", confirmedMutation);
|
||||
assertCondition(confirmedMutation.attempted === 2 && confirmedMutation.succeeded === 2, "confirmed mutation should respect filters and limit", confirmedMutation);
|
||||
assertCondition(readPosts.length === 2 && readPosts[0]?.path.includes("task-new-1") && readPosts[1]?.path.includes("task-new-2"), "confirmed mutation should POST newest filtered task reads only", { readPosts });
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
checks: [
|
||||
"default unread triage is read-only",
|
||||
"repo/issue/status/queue counts are present",
|
||||
"newest items are bounded",
|
||||
"raw prompt text is omitted",
|
||||
"batch mark-read requires --confirm",
|
||||
"confirmed batch read respects filters and limit",
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
if (import.meta.main) {
|
||||
process.stdout.write(`${JSON.stringify(runCodeQueueUnreadTriageContract(), null, 2)}\n`);
|
||||
}
|
||||
Reference in New Issue
Block a user