
























@@ -44,9 +44,9 @@ async function writeTranscript(
4444await fs.writeFile(path.join(sessionsDir, `${sessionId}.jsonl`), `${lines}\n`);
4545}
464647-function cleanedLock(sessionsDir: string, sessionId: string): SessionLockInspection {
47+function cleanedLockForPath(lockPath: string): SessionLockInspection {
4848return {
49-lockPath: path.join(sessionsDir, `${sessionId}.jsonl.lock`),
49+ lockPath,
5050pid: 999_999,
5151pidAlive: false,
5252createdAt: new Date(Date.now() - 1_000).toISOString(),
@@ -57,6 +57,10 @@ function cleanedLock(sessionsDir: string, sessionId: string): SessionLockInspect
5757};
5858}
595960+function cleanedLock(sessionsDir: string, sessionId: string): SessionLockInspection {
61+return cleanedLockForPath(path.join(sessionsDir, `${sessionId}.jsonl.lock`));
62+}
63+6064describe("main-session-restart-recovery", () => {
6165it("marks only main running sessions whose transcript lock was cleaned", async () => {
6266const sessionsDir = await makeSessionsDir();
@@ -94,6 +98,123 @@ describe("main-session-restart-recovery", () => {
9498expect(store["agent:main:other"]?.abortedLastRun).toBeUndefined();
9599});
96100101+it("marks a running main session whose cleaned transcript lock is topic-suffixed", async () => {
102+const sessionsDir = await makeSessionsDir();
103+const sessionId = "main-session";
104+const sessionFile = `${sessionId}-topic-1234567890.jsonl`;
105+await writeStore(sessionsDir, {
106+"agent:main:discord:channel:123:thread:1234567890": {
107+ sessionId,
108+ sessionFile,
109+updatedAt: Date.now() - 10_000,
110+status: "running",
111+},
112+});
113+114+const result = await markRestartAbortedMainSessionsFromLocks({
115+ sessionsDir,
116+cleanedLocks: [cleanedLockForPath(path.join(sessionsDir, `${sessionFile}.lock`))],
117+});
118+119+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
120+expect(result).toEqual({ marked: 1, skipped: 0 });
121+expect(store["agent:main:discord:channel:123:thread:1234567890"]?.abortedLastRun).toBe(true);
122+});
123+124+it("does not mark a session for an unrelated topic lock that only shares its id prefix", async () => {
125+const sessionsDir = await makeSessionsDir();
126+await writeStore(sessionsDir, {
127+"agent:main:main": {
128+sessionId: "main-session",
129+sessionFile: "main-session.jsonl",
130+updatedAt: Date.now() - 10_000,
131+status: "running",
132+},
133+});
134+135+const result = await markRestartAbortedMainSessionsFromLocks({
136+ sessionsDir,
137+cleanedLocks: [
138+cleanedLockForPath(path.join(sessionsDir, "main-session-topic-unrelated.jsonl.lock")),
139+],
140+});
141+142+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
143+expect(result).toEqual({ marked: 0, skipped: 0 });
144+expect(store["agent:main:main"]?.abortedLastRun).toBeUndefined();
145+});
146+147+it("normalizes relative cleaned lock paths against the current working directory", async () => {
148+const sessionsDir = await makeSessionsDir();
149+const sessionId = "main-session";
150+const sessionFile = `${sessionId}-topic-1234567890.jsonl`;
151+await writeStore(sessionsDir, {
152+"agent:main:discord:channel:123:thread:1234567890": {
153+ sessionId,
154+ sessionFile,
155+updatedAt: Date.now() - 10_000,
156+status: "running",
157+},
158+});
159+160+const result = await markRestartAbortedMainSessionsFromLocks({
161+ sessionsDir,
162+cleanedLocks: [
163+cleanedLockForPath(
164+path.relative(process.cwd(), path.join(sessionsDir, `${sessionFile}.lock`)),
165+),
166+],
167+});
168+169+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
170+expect(result).toEqual({ marked: 1, skipped: 0 });
171+expect(store["agent:main:discord:channel:123:thread:1234567890"]?.abortedLastRun).toBe(true);
172+});
173+174+it("falls back to the session id transcript lock when persisted sessionFile is outside the sessions dir", async () => {
175+const sessionsDir = await makeSessionsDir();
176+await writeStore(sessionsDir, {
177+"agent:main:main": {
178+sessionId: "main-session",
179+sessionFile: "../stale/outside.jsonl",
180+updatedAt: Date.now() - 10_000,
181+status: "running",
182+},
183+});
184+185+const result = await markRestartAbortedMainSessionsFromLocks({
186+ sessionsDir,
187+cleanedLocks: [cleanedLock(sessionsDir, "main-session")],
188+});
189+190+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
191+expect(result).toEqual({ marked: 1, skipped: 0 });
192+expect(store["agent:main:main"]?.abortedLastRun).toBe(true);
193+});
194+195+it("falls back to the session id transcript lock when persisted sessionFile belongs to another generated session", async () => {
196+const sessionsDir = await makeSessionsDir();
197+const sessionId = "11111111-1111-4111-8111-111111111111";
198+const otherSessionId = "22222222-2222-4222-8222-222222222222";
199+await writeStore(sessionsDir, {
200+"agent:main:main": {
201+ sessionId,
202+sessionFile: `${otherSessionId}.jsonl`,
203+updatedAt: Date.now() - 10_000,
204+status: "running",
205+},
206+});
207+208+const result = await markRestartAbortedMainSessionsFromLocks({
209+ sessionsDir,
210+cleanedLocks: [cleanedLock(sessionsDir, sessionId)],
211+});
212+213+const store = loadSessionStore(path.join(sessionsDir, "sessions.json"));
214+expect(result).toEqual({ marked: 1, skipped: 0 });
215+expect(store["agent:main:main"]?.abortedLastRun).toBe(true);
216+});
217+97218it("resumes marked sessions with a tool-result transcript tail", async () => {
98219const sessionsDir = await makeSessionsDir();
99220await writeStore(sessionsDir, {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。