






















@@ -9,6 +9,7 @@ import {
99INTERNAL_RUNTIME_CONTEXT_END,
1010} from "./internal-runtime-context.js";
1111import {
12+markRestartAbortedMainSessions,
1213markRestartAbortedMainSessionsFromLocks,
1314recoverRestartAbortedMainSessions,
1415} from "./main-session-restart-recovery.js";
@@ -78,6 +79,161 @@ function firstGatewayParams(): Record<string, unknown> {
7879}
79808081describe("main-session-restart-recovery", () => {
82+it("marks only matching running main sessions by active session key", async () => {
83+const sessionsDir = await makeSessionsDir();
84+await writeStore(sessionsDir, {
85+"agent:main:main": {
86+sessionId: "main-session",
87+updatedAt: Date.now() - 10_000,
88+status: "running",
89+},
90+"agent:main:completed": {
91+sessionId: "completed-session",
92+updatedAt: Date.now() - 10_000,
93+status: "done",
94+},
95+"agent:main:subagent:child": {
96+sessionId: "child-session",
97+updatedAt: Date.now() - 10_000,
98+status: "running",
99+spawnDepth: 1,
100+},
101+"cron:nightly": {
102+sessionId: "cron-session",
103+updatedAt: Date.now() - 10_000,
104+status: "running",
105+},
106+"agent:main:other": {
107+sessionId: "other-session",
108+updatedAt: Date.now() - 10_000,
109+status: "running",
110+},
111+});
112+113+const result = await markRestartAbortedMainSessions({
114+stateDir: tmpDir,
115+sessionKeys: ["agent:main:main", "agent:main:completed", "agent:main:subagent:child"],
116+});
117+118+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
119+expect(result).toEqual({ marked: 1, skipped: 1 });
120+expect(store["agent:main:main"]?.abortedLastRun).toBe(true);
121+expect(store["agent:main:completed"]?.abortedLastRun).toBeUndefined();
122+expect(store["agent:main:subagent:child"]?.abortedLastRun).toBeUndefined();
123+expect(store["cron:nightly"]?.abortedLastRun).toBeUndefined();
124+expect(store["agent:main:other"]?.abortedLastRun).toBeUndefined();
125+});
126+127+it("marks active sessions in a configured custom session store", async () => {
128+const storePath = path.join(tmpDir, "custom", "sessions.json");
129+await fs.mkdir(path.dirname(storePath), { recursive: true });
130+await fs.writeFile(
131+storePath,
132+JSON.stringify(
133+{
134+"agent:main:issue-82433": {
135+sessionId: "custom-session",
136+updatedAt: Date.now() - 10_000,
137+status: "running",
138+},
139+} satisfies Record<string, SessionEntry>,
140+null,
141+2,
142+),
143+);
144+await writeTranscript(path.dirname(storePath), "custom-session", [
145+{ role: "user", content: "continue this custom-store turn" },
146+{ role: "toolResult", content: "custom result" },
147+]);
148+149+const result = await markRestartAbortedMainSessions({
150+cfg: { session: { store: storePath } },
151+stateDir: tmpDir,
152+sessionKeys: ["agent:main:issue-82433"],
153+});
154+155+const store = loadSessionStore(storePath);
156+expect(result).toEqual({ marked: 1, skipped: 0 });
157+expect(store["agent:main:issue-82433"]?.abortedLastRun).toBe(true);
158+159+const recovery = await recoverRestartAbortedMainSessions({
160+cfg: { session: { store: storePath } },
161+stateDir: tmpDir,
162+});
163+164+expect(recovery).toEqual({ recovered: 1, failed: 0, skipped: 0 });
165+});
166+167+it("uses active session ids to avoid marking stale duplicate keys in another store", async () => {
168+const defaultSessionsDir = await makeSessionsDir();
169+await writeStore(defaultSessionsDir, {
170+"agent:main:issue-82433": {
171+sessionId: "stale-default-session",
172+updatedAt: Date.now() - 10_000,
173+status: "running",
174+},
175+});
176+177+const storePath = path.join(tmpDir, "custom-duplicate-key", "sessions.json");
178+await fs.mkdir(path.dirname(storePath), { recursive: true });
179+await fs.writeFile(
180+storePath,
181+JSON.stringify(
182+{
183+"agent:main:issue-82433": {
184+sessionId: "active-custom-session",
185+updatedAt: Date.now() - 10_000,
186+status: "running",
187+},
188+} satisfies Record<string, SessionEntry>,
189+null,
190+2,
191+),
192+);
193+194+const result = await markRestartAbortedMainSessions({
195+cfg: { session: { store: storePath } },
196+stateDir: tmpDir,
197+sessionIds: ["active-custom-session"],
198+sessionKeys: ["agent:main:issue-82433"],
199+});
200+201+const defaultStore = loadSessionStore(path.join(defaultSessionsDir, "sessions.json"));
202+const customStore = loadSessionStore(storePath);
203+expect(result).toEqual({ marked: 1, skipped: 0 });
204+expect(defaultStore["agent:main:issue-82433"]?.abortedLastRun).toBeUndefined();
205+expect(customStore["agent:main:issue-82433"]?.abortedLastRun).toBe(true);
206+});
207+208+it("marks custom-store sessions by session id when no session key is available", async () => {
209+const storePath = path.join(tmpDir, "custom-by-id", "sessions.json");
210+await fs.mkdir(path.dirname(storePath), { recursive: true });
211+await fs.writeFile(
212+storePath,
213+JSON.stringify(
214+{
215+"agent:main:custom-by-id": {
216+sessionId: "custom-session-id-only",
217+updatedAt: Date.now() - 10_000,
218+status: "running",
219+},
220+} satisfies Record<string, SessionEntry>,
221+null,
222+2,
223+),
224+);
225+226+const result = await markRestartAbortedMainSessions({
227+cfg: { session: { store: storePath } },
228+stateDir: tmpDir,
229+sessionIds: ["custom-session-id-only"],
230+});
231+232+const store = loadSessionStore(storePath);
233+expect(result).toEqual({ marked: 1, skipped: 0 });
234+expect(store["agent:main:custom-by-id"]?.abortedLastRun).toBe(true);
235+});
236+81237it("marks only main running sessions whose transcript lock was cleaned", async () => {
82238const sessionsDir = await makeSessionsDir();
83239await writeStore(sessionsDir, {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。