






















@@ -562,6 +562,136 @@ describe("gateway agent handler", () => {
562562expect(capturedEntry?.sessionFile).toBeUndefined();
563563});
564564565+it("rotates a failed session instead of resuming when its transcript is missing", async () => {
566+const now = Date.parse("2026-05-18T09:45:00.000Z");
567+vi.useFakeTimers({ toFake: ["Date"] });
568+dateOnlyFakeClockActive = true;
569+vi.setSystemTime(now);
570+const missingTranscriptEntry = {
571+sessionId: "failed-missing-session-id",
572+sessionFile: "/tmp/openclaw/missing/failed-missing-session-id.jsonl",
573+status: "failed",
574+updatedAt: now,
575+sessionStartedAt: now,
576+lastInteractionAt: now,
577+startedAt: now - 2_000,
578+endedAt: now - 1_000,
579+runtimeMs: 1_000,
580+abortedLastRun: true,
581+};
582+mockMainSessionEntry(missingTranscriptEntry);
583+584+const capturedEntry = await runMainAgentAndCaptureEntry("test-idem-failed-missing-transcript");
585+586+const call = await waitForAgentCommandCall<{ sessionId?: string }>();
587+expect(call.sessionId).not.toBe("failed-missing-session-id");
588+expect(capturedEntry?.sessionId).not.toBe("failed-missing-session-id");
589+expect(capturedEntry?.status).toBeUndefined();
590+expect(capturedEntry?.startedAt).toBeUndefined();
591+expect(capturedEntry?.endedAt).toBeUndefined();
592+expect(capturedEntry?.runtimeMs).toBeUndefined();
593+expect(capturedEntry?.abortedLastRun).toBeUndefined();
594+expect(capturedEntry?.sessionFile).toBeUndefined();
595+});
596+597+it("rotates a failed session when its default transcript is missing", async () => {
598+const now = Date.parse("2026-05-18T09:48:00.000Z");
599+vi.useFakeTimers({ toFake: ["Date"] });
600+dateOnlyFakeClockActive = true;
601+vi.setSystemTime(now);
602+const missingDefaultTranscriptEntry = {
603+sessionId: "failed-missing-default-session-id",
604+status: "failed",
605+updatedAt: now,
606+sessionStartedAt: now,
607+lastInteractionAt: now,
608+};
609+mockMainSessionEntry(missingDefaultTranscriptEntry);
610+611+const capturedEntry = await runMainAgentAndCaptureEntry(
612+"test-idem-failed-missing-default-transcript",
613+);
614+615+const call = await waitForAgentCommandCall<{ sessionId?: string }>();
616+expect(call.sessionId).not.toBe("failed-missing-default-session-id");
617+expect(capturedEntry?.sessionId).not.toBe("failed-missing-default-session-id");
618+expect(capturedEntry?.status).toBeUndefined();
619+expect(capturedEntry?.sessionFile).toBeUndefined();
620+});
621+622+it("keeps a failed session reusable when its default transcript exists", async () => {
623+const now = Date.parse("2026-05-18T09:49:00.000Z");
624+vi.useFakeTimers({ toFake: ["Date"] });
625+dateOnlyFakeClockActive = true;
626+vi.setSystemTime(now);
627+628+await withTempDir({ prefix: "openclaw-gateway-failed-default-session-file-" }, async (root) => {
629+const sessionsDir = `${root}/sessions`;
630+await fs.mkdir(sessionsDir, { recursive: true });
631+await fs.writeFile(`${sessionsDir}/failed-present-default-session-id.jsonl`, "", "utf8");
632+const failedEntryWithDefaultTranscript = {
633+sessionId: "failed-present-default-session-id",
634+status: "failed",
635+updatedAt: now,
636+sessionStartedAt: now,
637+lastInteractionAt: now,
638+};
639+mocks.loadSessionEntry.mockReturnValue({
640+cfg: {},
641+storePath: `${sessionsDir}/sessions.json`,
642+entry: failedEntryWithDefaultTranscript,
643+canonicalKey: "agent:main:main",
644+});
645+646+const capturedEntry = await runMainAgentAndCaptureEntry(
647+"test-idem-failed-present-default-transcript",
648+);
649+650+const call = await waitForAgentCommandCall<{ sessionId?: string }>();
651+expect(call.sessionId).toBe("failed-present-default-session-id");
652+expect(capturedEntry?.sessionId).toBe("failed-present-default-session-id");
653+expect(capturedEntry?.status).toBe("failed");
654+expect(capturedEntry?.sessionFile).toBeUndefined();
655+});
656+});
657+658+it("keeps a failed session reusable when its relative transcript resolves and exists", async () => {
659+const now = Date.parse("2026-05-18T09:50:00.000Z");
660+vi.useFakeTimers({ toFake: ["Date"] });
661+dateOnlyFakeClockActive = true;
662+vi.setSystemTime(now);
663+664+await withTempDir({ prefix: "openclaw-gateway-failed-session-file-" }, async (root) => {
665+const sessionsDir = `${root}/sessions`;
666+await fs.mkdir(sessionsDir, { recursive: true });
667+await fs.writeFile(`${sessionsDir}/relative-present.jsonl`, "", "utf8");
668+const failedEntryWithResolvedTranscript = {
669+sessionId: "failed-present-session-id",
670+sessionFile: "relative-present.jsonl",
671+status: "failed",
672+updatedAt: now,
673+sessionStartedAt: now,
674+lastInteractionAt: now,
675+};
676+mocks.loadSessionEntry.mockReturnValue({
677+cfg: {},
678+storePath: `${sessionsDir}/sessions.json`,
679+entry: failedEntryWithResolvedTranscript,
680+canonicalKey: "agent:main:main",
681+});
682+683+const capturedEntry = await runMainAgentAndCaptureEntry(
684+"test-idem-failed-present-transcript",
685+);
686+687+const call = await waitForAgentCommandCall<{ sessionId?: string }>();
688+expect(call.sessionId).toBe("failed-present-session-id");
689+expect(capturedEntry?.sessionId).toBe("failed-present-session-id");
690+expect(capturedEntry?.status).toBe("failed");
691+expect(capturedEntry?.sessionFile).toBe("relative-present.jsonl");
692+});
693+});
694+565695it("keeps stored group metadata when a trusted group session receives caller-supplied selectors", async () => {
566696const sessionKey = "agent:main:slack:group:C123";
567697const existingEntry = buildExistingMainStoreEntry({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。