import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "fs"; import { tmpdir } from "os"; import { join } from "path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { createCompactionRuntime } from "../src/compaction"; import { DEFERRED_REINJECT_CUSTOM_TYPE, deliverDeferredReinjectSteer, tryConsumeDeferredReinject } from "../src/reinject"; import { createDefaultSettings } from "../src/settings"; import { createInitialState, trackSkill } from "../src/state"; const tempDirs: string[] = []; afterEach(() => { for (const dir of tempDirs.splice(0)) { rmSync(dir, { recursive: true, force: true }); } }); function tempSkillDir(name: string): { filePath: string; baseDir: string } { const root = mkdtempSync(join(tmpdir(), "pi-skill-reinject-mid-turn-")); tempDirs.push(root); const baseDir = join(root, name); mkdirSync(baseDir, { recursive: true }); const filePath = join(baseDir, "SKILL.md"); writeFileSync(filePath, `---\nname: ${name}\n---\n# body\n`, "utf8"); return { filePath, baseDir }; } describe("deliverDeferredReinjectSteer", () => { it("sends steer message and clears pending when skills resolve", () => { const { filePath, baseDir } = tempSkillDir("alpha"); const runtime = createCompactionRuntime(); const state = createInitialState(); trackSkill(state, { name: "alpha", filePath, baseDir, source: "slash" }); state.pendingReinject = ["alpha"]; const sendMessage = vi.fn(); const pi = { sendMessage } as never; const registered = [{ name: "alpha", filePath, baseDir }]; const delivered = deliverDeferredReinjectSteer( pi, state, createDefaultSettings(), registered, runtime, "compact-1", ); expect(delivered).toBe(true); expect(state.pendingReinject).toEqual([]); expect(runtime.deferredDeliveredForCompactionId).toBe("compact-1"); expect(sendMessage).toHaveBeenCalledOnce(); expect(sendMessage).toHaveBeenCalledWith( expect.objectContaining({ customType: DEFERRED_REINJECT_CUSTOM_TYPE, display: true, content: expect.stringContaining(' { const runtime = createCompactionRuntime(); const state = createInitialState(); const sendMessage = vi.fn(); const pi = { sendMessage } as never; expect( deliverDeferredReinjectSteer(pi, state, createDefaultSettings(), [], runtime, "compact-1"), ).toBe(false); expect(sendMessage).not.toHaveBeenCalled(); }); }); describe("tryConsumeDeferredReinject after mid-turn steer", () => { it("skips before_agent_start inject when steer already delivered for compaction", () => { const { filePath, baseDir } = tempSkillDir("beta"); const runtime = createCompactionRuntime(); runtime.lastCompactionEntryId = "compact-2"; runtime.deferredDeliveredForCompactionId = "compact-2"; const state = createInitialState(); trackSkill(state, { name: "beta", filePath, baseDir, source: "slash" }); state.pendingReinject = ["beta"]; expect( tryConsumeDeferredReinject( state, createDefaultSettings(), [{ name: "beta", filePath, baseDir }], undefined, runtime, ), ).toBeUndefined(); }); it("consumes pending on before_agent_start when steer did not run (idle path)", () => { const { filePath, baseDir } = tempSkillDir("gamma"); const runtime = createCompactionRuntime(); runtime.lastCompactionEntryId = "compact-3"; const state = createInitialState(); trackSkill(state, { name: "gamma", filePath, baseDir, source: "slash" }); state.pendingReinject = ["gamma"]; const result = tryConsumeDeferredReinject( state, createDefaultSettings(), [{ name: "gamma", filePath, baseDir }], undefined, runtime, ); expect(result?.message.customType).toBe(DEFERRED_REINJECT_CUSTOM_TYPE); expect(state.pendingReinject).toEqual([]); expect(runtime.deferredDeliveredForCompactionId).toBeNull(); }); });