返回 DeepSeek-Reasonix
maintenance-submit.test.ts
根目录 / desktop / frontend / src / __tests__ / maintenance-submit.test.ts
1 import assert from "node:assert/strict";
2 import test from "node:test";
3 import { createTurnSubmissionId, historyMessagesToItems, initialState, reducer, type State } from "../lib/useController";
4 import { submitTurn, type ManagementReceipt } from "../lib/turnSubmit";
5 import type { AppBindings } from "../lib/bridge";
6 import type { RuntimeState } from "../lib/runtimeStateStore";
7 import { canonicalMessage } from "../lib/canonicalTranscriptBackend";
8 import { isCompactCommand, isCompactSubmission } from "../lib/sessionMaintenanceOperation";
9 import { TranscriptStore, type TranscriptBackend } from "../lib/transcriptStore";
10 import type { TranscriptSnapshot } from "../lib/transcriptProtocol";
11
12 const operation = { operationId: "compact-A", kind: "compact", activity: "running", status: "running", operationRevision: 1, runtimeEpoch: "epoch-A" };
13 const runtime = (revision: number, maintenance: RuntimeState["maintenance"]): RuntimeState => ({
14 schemaVersion: 1, projectionEpoch: "projection-A", runtimeEpoch: "epoch-A", activityRevision: revision, revision,
15 phase: maintenance ? "executing" : "idle", running: Boolean(maintenance), turnId: "", turnStatus: "", turnEventSeq: 0,
16 pendingPrompt: false, cancelRequested: false, cancellable: Boolean(maintenance), backgroundJobs: 0, activity: maintenance ? "maintenance" : "", maintenance,
17 });
18 const pending = (state = initialState, submissionId = "submit") => reducer(state, { type: "user", text: "/compact", seq: state.seq, submissionId });
19 const confirm = (state: State, receipt?: ManagementReceipt, submissionId = "submit") => reducer(state, { type: "management_confirmed", submissionId, receipt });
20 const running = () => reducer(initialState, { type: "runtime_snapshot", snapshot: runtime(1, operation) });
21 const terminal = { ...operation, status: "completed", activity: "finalizing", operationRevision: 3, applied: true, summary: "durable summary" };
22 const busyReceipt = { operationId: operation.operationId, errorCode: "maintenance_busy" };
23
24 test("management refusal retains operation identity through the submit adapter; legacy success and transport errors keep their meaning", async () => {
25 for (const managementErrorCode of [undefined, "maintenance_busy", "maintenance_recovery_required", "future_code"]) {
26 const bridge = { StartTurnForTab: async () => ({ disposition: "management_handled", operationId: operation.operationId, managementErrorCode }) } as unknown as AppBindings;
27 assert.deepEqual(await submitTurn(bridge, "A", "submit", "/compact", "/compact", ""), [2, { operationId: operation.operationId, errorCode: managementErrorCode }]);
28 }
29 const bridge = { StartTurnForTab: async () => { throw new Error("connection closed"); } } as unknown as AppBindings;
30 await assert.rejects(submitTurn(bridge, "A", "submit", "/compact", "/compact", ""), /connection closed/);
31 });
32
33 test("duplicate compact has no chat echo, preserves runtime-owned Stop state and deduplicates the busy notice", () => {
34 let state = confirm(running(), busyReceipt);
35 assert.deepEqual(state.localSubmissionOrder, []);
36 assert.equal(state.running, false, "management does not fabricate a conversational turn");
37 assert.equal(state.turnActive, false);
38 assert.equal(state.runtimeStateSnapshot?.cancellable, true);
39 assert.equal(state.items.filter(item => item.kind === "compaction").length, 1);
40 assert.equal(state.items.some(item => item.kind === "notice" && item.level === "warn"), false);
41 state = confirm(state, busyReceipt);
42 assert.equal(state.items.filter(item => item.kind === "notice").length, 1);
43 });
44
45 test("a late management receipt cannot clear a newer submission or reopen completed compaction", () => {
46 let state = running();
47 state = reducer(state, { type: "event", e: { kind: "session_operation", sessionOperation: terminal } });
48 state = reducer(state, { type: "runtime_snapshot", snapshot: runtime(3, undefined) });
49 state = confirm(state, busyReceipt);
50 assert.equal(state.running, false);
51 assert.equal(state.items.some(item => item.kind === "notice"), false);
52 assert.equal(state.items.find(item => item.kind === "compaction")?.status, "completed");
53 state = pending(pending(running()), "newer");
54 const later = confirm(state, busyReceipt);
55 assert.equal(later.pendingSubmissionId, "newer");
56 assert.equal(later.localSubmissions.submit, undefined);
57 assert.equal(later.localSubmissions.newer.status, "sending");
58 });
59
60 test("recovery and unknown command refusals remain visible without a failed conversational bubble", () => {
61 for (const errorCode of ["maintenance_recovery_required", "future_code"]) {
62 const state = confirm(running(), { operationId: operation.operationId, errorCode });
63 assert.deepEqual(state.localSubmissionOrder, []);
64 assert.equal(state.items.filter(item => item.kind === "notice" && item.level === "warn").length, 1);
65 }
66 });
67
68 test("switching away during compaction and restoring durable history rebuilds one completed summary", () => {
69 const background = confirm(running(), busyReceipt);
70 const other = reducer(initialState, { type: "history", messages: [{ role: "user", content: "unrelated session", messageId: "B" }] });
71 const completed = reducer(background, { type: "event", e: { kind: "session_operation", sessionOperation: terminal } });
72 const body = { role: "compaction", id: `maintenance:${operation.operationId}`, content: JSON.stringify(terminal) };
73 const message = canonicalMessage({ messageId: body.id, role: body.role, position: 2, eventSequence: 3, version: 1, visibleTurn: 1 }, body);
74 const snapshot: TranscriptSnapshot = { protocolVersion: 1, snapshotId: "completed-A", identity: {
75 sessionId: "A", runtimeEpoch: "epoch-A", rewriteEpoch: 0, headId: "" }, projectionRevision: 3,
76 coveredThroughSeq: 3, records: [{ id: body.id, order: 0, message, refs: [] }], activeRecords: [], activeAttempts: [],
77 runtime: { status: "completed", pendingEvents: [] }, before: 0, hasOlder: false, totalRecords: 1, totalTurns: 1, stale: false };
78 const projection = { items: historyMessagesToItems([message], "snapshot:").items, removeIds: [], startTurn: 0, endTurn: 1,
79 totalTurns: 1, hasOlder: false, hasNewer: false, revision: 3, revisionKnown: true, digest: "completed-A" };
80 for (const state of [completed, reducer(completed, { type: "reset" })]) {
81 let restored = reducer(state, { type: "transcript_v2_snapshot", snapshot, projection });
82 restored = reducer(restored, { type: "runtime_snapshot", snapshot: runtime(3, undefined) });
83 const cards = restored.items.filter(item => item.kind === "compaction");
84 assert.equal(cards.length, 1);
85 assert.equal(cards[0].status, "completed");
86 assert.equal(cards[0].summary, terminal.summary);
87 assert.equal(cards[0].pending, false);
88 assert.deepEqual(restored.localSubmissionOrder, []);
89 }
90 assert.equal(other.items.some(item => item.kind === "compaction"), false);
91 });
92
93 test("only compact and its focus form use the management route", () => {
94 for (const text of ["/compact", " /compact keep decisions "]) assert.equal(isCompactCommand(text), true);
95 for (const text of ["/compactly", "continue", "explain /compact", "/context"]) assert.equal(isCompactCommand(text), false);
96 });
97
98 test("expanded and edited compact focus uses the typed command route without rewriting instructions", async () => {
99 const calls: unknown[][] = [];
100 const bridge = {
101 StartTurnForTab: async (...args: unknown[]) => { calls.push(args); return { disposition: "management_handled", operationId: operation.operationId, managementErrorCode: "maintenance_busy" }; },
102 SubmitDisplayToTabWithID: async () => { throw Error("display route bypassed management admission"); },
103 SubmitEditedDisplayToTabWithID: async () => { throw Error("edit route bypassed management admission"); },
104 } as unknown as AppBindings;
105 const focus = `/compact preserve decisions\n${"source details ".repeat(200)}`;
106 for (const original of ["", "previous draft"]) {
107 assert.equal(isCompactSubmission(focus), true);
108 assert.deepEqual(await submitTurn(bridge, "A", "request", "/compact [Pasted text #1]", focus, original), [2, busyReceipt]);
109 }
110 assert.deepEqual(calls, [["A", focus, "request"], ["A", focus, "request"]]);
111 assert.equal(isCompactSubmission(focus, { display: focus, input: focus, invocations: [] }), false);
112 assert.equal(isCompactSubmission(focus, undefined, { goal: focus }), false);
113 });
114
115 test("every receipt/completion/idle ordering settles without pinning a phantom turn", () => {
116 const permutations = [["receipt", "terminal", "idle"], ["receipt", "idle", "terminal"],
117 ["terminal", "receipt", "idle"], ["terminal", "idle", "receipt"],
118 ["idle", "receipt", "terminal"], ["idle", "terminal", "receipt"]];
119 for (const order of permutations) {
120 let state: State = { ...running(), transcriptProtocol: 2 };
121 for (const step of order) {
122 if (step === "receipt") state = confirm(state, busyReceipt);
123 if (step === "terminal") {
124 state = reducer(state, { type: "event", e: { kind: "session_operation", sessionOperation: terminal } });
125 state = reducer(state, { type: "transcript_runtime", runtime: { status: "completed", turnId: "previous-turn", submissionId: "previous-submission", pendingEvents: [], samplingCount: 0, toolCount: 0 } });
126 }
127 if (step === "idle") state = reducer(state, { type: "runtime_snapshot", snapshot: runtime(3, undefined) });
128 }
129 assert.equal(state.running, false, order.join(" -> "));
130 assert.equal(state.cancellable, false);
131 assert.equal(state.pendingSubmissionId, undefined);
132 assert.equal(state.runtimeStateSnapshot?.running, false);
133 assert.equal(state.items.find(item => item.kind === "compaction")?.status, "completed");
134 const store = new TranscriptStore({} as TranscriptBackend);
135 store.setPinned("A", Boolean(state.running || state.turnActive || state.live));
136 assert.equal(store.tabIsPinned("A"), false);
137 }
138 });
139
140 test("a late compact receipt preserves a newer active chat and pending prompt", () => {
141 const state: State = { ...pending(initialState, "new-chat"), running: true, turnActive: true,
142 pendingPrompt: true, activeTurnId: "new-turn", cancellable: true, cancelRequested: true };
143 const next = confirm(state, busyReceipt, "old-compact");
144 for (const key of ["running", "turnActive", "pendingPrompt", "activeTurnId", "cancellable", "cancelRequested", "pendingSubmissionId", "localSubmissions"] as const) {
145 assert.equal(next[key], state[key], key);
146 }
147 });
148
149 test("management requests reserve distinct submission identities without starting chat lifecycle", () => {
150 let state = initialState;
151 const ids: string[] = [];
152 for (let i = 0; i < 3; i++) {
153 ids.push(createTurnSubmissionId("A", state.sessionGen, state.seq));
154 state = reducer(state, { type: "management_requested" });
155 assert.equal(state.running, false);
156 assert.deepEqual(state.localSubmissionOrder, []);
157 }
158 const chatId = createTurnSubmissionId("A", state.sessionGen, state.seq);
159 assert.equal(new Set([...ids, chatId]).size, 4);
160 state = pending(state, chatId);
161 for (const id of ids) state = confirm(state, busyReceipt, id);
162 assert.equal(state.pendingSubmissionId, chatId);
163 assert.equal(state.running, true);
164 });
165
166 test("legacy compact bindings and conversational display submissions retain their contracts", async () => {
167 const calls: unknown[][] = [];
168 const bridge = {
169 SubmitToTabWithID: async (...args: unknown[]) => { calls.push(["legacy", ...args]); },
170 SubmitDisplayToTabWithID: async (...args: unknown[]) => { calls.push(["display", ...args]); },
171 SubmitInvocationsToTabWithID: async (...args: unknown[]) => { calls.push(["structured", ...args]); },
172 SubmitInitialGoalToTabWithID: async (...args: unknown[]) => { calls.push(["goal", ...args]); return ["approval"]; },
173 } as unknown as AppBindings;
174 assert.deepEqual(await submitTurn(bridge, "A", "legacy", "/compact [paste]", "/compact decisions", ""), [0]);
175 await submitTurn(bridge, "A", "chat", "visible", "hidden context\nvisible", "");
176 await submitTurn(bridge, "A", "structured", "/compact", "/compact", "", { display: "tool task", input: "tool input", invocations: [] });
177 assert.deepEqual(await submitTurn(bridge, "A", "goal", "task", "/compact", "", undefined,
178 { goal: "task", collaborationMode: "goal", toolApprovalMode: "ask" }), [1, ["approval"]]);
179 assert.deepEqual(calls.map(call => call[0]), ["legacy", "display", "structured", "goal"]);
180 assert.deepEqual(calls[0], ["legacy", "A", "/compact decisions", "legacy"]);
181 assert.deepEqual(calls[1], ["display", "A", "visible", "hidden context\nvisible", "chat"]);
182 });
183
183 lines TYPESCRIPT