import assert from "node:assert/strict"; import test from "node:test"; import { normalizeInspection, normalizeStatus, parseStatusChangedNotification, type ReadMethod, type ThreadSummary, } from "../src/domain.js"; const allowedMethod = "thread/read" satisfies ReadMethod; // @ts-expect-error Mutating methods must not satisfy the compile-time allowlist. const rejectedMethod: ReadMethod = "thread/archive"; void allowedMethod; void rejectedMethod; const baseThread = { id: "thread-1", sessionId: "thread-1", parentThreadId: null, forkedFromId: null, preview: "Investigate", ephemeral: false, modelProvider: "openai", createdAt: 10, updatedAt: 20, recencyAt: 30, cwd: "/work/project", source: "cli", status: { type: "active", activeFlags: ["waitingOnApproval"] }, name: "Investigation", agentNickname: null, agentRole: null, turns: [], }; test("status normalization preserves all documented states and active flags", () => { assert.deepEqual(normalizeStatus({ type: "active", activeFlags: ["waitingOnApproval"] }), { type: "active", activeFlags: ["waitingOnApproval"], }); for (const type of ["idle", "notLoaded", "systemError"] as const) { assert.deepEqual(normalizeStatus({ type }), { type, activeFlags: [] }); } }); test("status notifications preserve thread identity and semantics", () => { assert.deepEqual( parseStatusChangedNotification({ method: "thread/status/changed", params: { threadId: "thread-1", status: { type: "idle" } }, }), { threadId: "thread-1", status: { type: "idle", activeFlags: [] } }, ); assert.equal(parseStatusChangedNotification({ method: "turn/completed", params: {} }), null); }); test("inspection returns bounded recent conversation context", () => { const result = normalizeInspection( { thread: { ...baseThread, turns: [ { id: "turn-1", items: [ { id: "user-1", type: "userMessage", content: [{ type: "text", text: "older" }] }, { id: "agent-1", type: "agentMessage", text: "recent-answer" }, ], }, ], }, }, { maxItems: 1, maxCharacters: 6 }, ); assert.deepEqual(result.context.items.map((item) => item.text), ["answer"]); assert.equal(result.context.truncated, true); assert.equal(result.context.omittedItems, 1); }); test("thread summary type contains no conversation body field", () => { const summary = normalizeInspection({ thread: baseThread }, { maxItems: 1, maxCharacters: 10 }).thread; assert.equal("turns" in (summary as ThreadSummary), false); }); test("malformed thread response is rejected", () => { assert.throws( () => normalizeInspection({ thread: { ...baseThread, turns: "bad" } }, { maxItems: 1, maxCharacters: 10 }), /turns must be an array/, ); });