
























@@ -461,17 +461,20 @@ describe("maybeCompactCodexAppServerSession", () => {
461461expect(details.codexThreadBindingInvalidated).toBe(true);
462462expect(await readCodexAppServerBinding(sessionFile)).toBeUndefined();
463463expect(compact).toHaveBeenCalledTimes(1);
464-expect(compact).toHaveBeenCalledWith({
465-sessionId: "session-1",
466-sessionKey: "agent:main:session-1",
467- sessionFile,
468-tokenBudget: 777,
469-currentTokenCount: 123,
470-compactionTarget: "threshold",
471-customInstructions: undefined,
472-force: true,
473-runtimeContext: { workspaceDir: tempDir, provider: "codex" },
474-});
464+expect(compact).toHaveBeenCalledWith(
465+expect.objectContaining({
466+sessionId: "session-1",
467+sessionKey: "agent:main:session-1",
468+ sessionFile,
469+tokenBudget: 777,
470+currentTokenCount: 123,
471+compactionTarget: "threshold",
472+customInstructions: undefined,
473+force: true,
474+runtimeContext: { workspaceDir: tempDir, provider: "codex" },
475+abortSignal: expect.any(AbortSignal),
476+}),
477+);
475478expect(maintain).toHaveBeenCalledTimes(1);
476479const [maintainCall] = maintain.mock.calls[0] ?? [];
477480const maintainParams = maintainCall as
@@ -683,6 +686,100 @@ describe("maybeCompactCodexAppServerSession", () => {
683686expect(compactResult.reason).toBe("below threshold");
684687expect(maintain).not.toHaveBeenCalled();
685688});
689+690+describe("owning context-engine compaction safety timeout", () => {
691+afterEach(() => {
692+vi.useRealTimers();
693+});
694+695+it("bounds a hung owning context-engine compact() and reports a clean ok:false", async () => {
696+const sessionFile = await writeTestBinding();
697+const compact = vi.fn<ContextEngine["compact"]>(() => new Promise(() => {}));
698+const contextEngine: ContextEngine = {
699+info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
700+assemble: vi.fn() as never,
701+ingest: vi.fn() as never,
702+ compact,
703+};
704+705+vi.useFakeTimers();
706+const pendingResult = maybeCompactCodexAppServerSession({
707+sessionId: "session-1",
708+sessionKey: "agent:main:session-1",
709+ sessionFile,
710+workspaceDir: tempDir,
711+ contextEngine,
712+// 1 s host-resolved compaction timeout.
713+config: { agents: { defaults: { compaction: { timeoutSeconds: 1 } } } },
714+});
715+716+await vi.advanceTimersByTimeAsync(1_000);
717+const result = requireCompactResult(await pendingResult);
718+719+expect(result.ok).toBe(false);
720+expect(result.compacted).toBe(false);
721+expect(result.reason).toContain("timed out");
722+expect(compact).toHaveBeenCalledTimes(1);
723+expect(vi.getTimerCount()).toBe(0);
724+});
725+726+it("threads a composed caller abort signal into the owning context-engine compact()", async () => {
727+const sessionFile = await writeTestBinding();
728+const controller = new AbortController();
729+const compact = vi.fn<ContextEngine["compact"]>(async () => ({
730+ok: true,
731+compacted: false,
732+reason: "below threshold",
733+}));
734+const contextEngine: ContextEngine = {
735+info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
736+assemble: vi.fn() as never,
737+ingest: vi.fn() as never,
738+ compact,
739+};
740+741+await maybeCompactCodexAppServerSession({
742+sessionId: "session-1",
743+sessionKey: "agent:main:session-1",
744+ sessionFile,
745+workspaceDir: tempDir,
746+ contextEngine,
747+abortSignal: controller.signal,
748+});
749+750+expect(compact).toHaveBeenCalledTimes(1);
751+expect(compact.mock.calls[0]?.[0]?.abortSignal).toBeInstanceOf(AbortSignal);
752+});
753+754+it("aborts a hung owning context-engine compact() when the caller signal fires", async () => {
755+const sessionFile = await writeTestBinding();
756+const controller = new AbortController();
757+const compact = vi.fn<ContextEngine["compact"]>(() => new Promise(() => {}));
758+const contextEngine: ContextEngine = {
759+info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
760+assemble: vi.fn() as never,
761+ingest: vi.fn() as never,
762+ compact,
763+};
764+765+const pendingResult = maybeCompactCodexAppServerSession({
766+sessionId: "session-1",
767+sessionKey: "agent:main:session-1",
768+ sessionFile,
769+workspaceDir: tempDir,
770+ contextEngine,
771+abortSignal: controller.signal,
772+});
773+774+controller.abort(new Error("run aborted"));
775+const result = requireCompactResult(await pendingResult);
776+777+expect(result.ok).toBe(false);
778+expect(result.compacted).toBe(false);
779+expect(result.reason).toContain("run aborted");
780+expect(compact).toHaveBeenCalledTimes(1);
781+});
782+});
686783});
687784688785function createFakeCodexClient(): {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。