import { mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, it, vi } from "vitest";
import { PiSessionService, type PiAgentSession } from "./piSessionService.js";
import type { SpawnTargetDecision } from "./spawnTargetResolver.js";
import { CapturingSessionEventHub, emptyArchiveStore, fakeRuntime, fakeSessionManager, runtimeCreator, sessionGateway, sessionRecord, sessionRef, testModel, testModelRuntime, type RuntimeCreator } from "./piSessionService.testSupport.js";

const TEST_AGENT_DIR = "/tmp/pi-web-test-agent";

describe("PiSessionService", () => {
  describe("spawnSubsession", () => {
    function subsessionService(decision: SpawnTargetDecision, heartbeatIntervalMs = 60_000, childIds = ["child-1"]) {
      const parent = fakeRuntime("parent-1", { sessionFile: "/tmp/parent-1.jsonl" });
      const children = childIds.map((childId) => fakeRuntime(childId, {
        sessionFile: `/tmp/${childId}.jsonl`,
        sessionManager: fakeSessionManager("/workspace-feature"),
      }));
      const child = children[0];
      if (child === undefined) throw new Error("At least one child fixture is required");
      const created = [parent.runtime, ...children.map(({ runtime }) => runtime)];
      let index = 0;
      const createAgentRuntime: RuntimeCreator = async () => {
        await Promise.resolve();
        const runtime = created[Math.min(index, created.length - 1)] ?? child.runtime;
        index += 1;
        return runtime;
      };
      const archived = new Map<string, { sessionId: string; cwd: string; archivedAt: string }>();
      const archiveStore = {
        list: () => Promise.resolve([...archived.values()]),
        get: (sessionId: string) => Promise.resolve(archived.get(sessionId)),
        archive: (input: { sessionId: string; cwd: string }) => {
          const record = { sessionId: input.sessionId, cwd: input.cwd, archivedAt: "2026-01-01T00:00:00.000Z" };
          archived.set(input.sessionId, record);
          return Promise.resolve(record);
        },
        restore: (sessionId: string) => { archived.delete(sessionId); return Promise.resolve(); },
        isArchived: (sessionId: string) => Promise.resolve(archived.has(sessionId)),
      };
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime,
        sessionManager: sessionGateway([]),
        archiveStore,
        spawnTargets: { resolveSpawnTarget: () => Promise.resolve(decision) },
        heartbeatIntervalMs,
      });
      return { parent, child, children, service };
    }

    it("records the parent, delivers the prompt, and lists the tracked child", async () => {
      const { child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      await service.start("/workspace"); // bring the parent online so it can be notified

      const result = await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "do the slice", cwd: "/workspace-feature" });

      expect(result).toEqual({ sessionId: "child-1", cwd: "/workspace-feature" });
      expect(child.calls.prompt).toEqual([{ text: "do the slice", options: undefined }]);
      await expect(service.listSubsessions("parent-1")).resolves.toEqual([
        { sessionId: "child-1", cwd: "/workspace-feature", status: "idle" },
      ]);
      await service.dispose();
    });

    it("uses the parent model and disables delegation before creating the tracked child runtime", async () => {
      const parent = fakeRuntime("parent-1", { sessionFile: "/tmp/parent-1.jsonl" });
      const child = fakeRuntime("child-1", { sessionFile: "/tmp/child-1.jsonl", sessionManager: fakeSessionManager("/workspace-feature") });
      const model = testModel();
      const initialModels: PiAgentSession["model"][] = [];
      const delegationCapabilities: boolean[] = [];
      const runtimes = [parent.runtime, child.runtime];
      let index = 0;
      const createAgentRuntime: RuntimeCreator = async (_createRuntime, options) => {
        await Promise.resolve();
        initialModels.push(options.initialModel);
        delegationCapabilities.push(options.delegationToolsEnabled);
        const runtime = runtimes[index] ?? child.runtime;
        index += 1;
        return runtime;
      };
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime,
        sessionManager: sessionGateway([]),
        archiveStore: emptyArchiveStore(),
        spawnTargets: { resolveSpawnTarget: () => Promise.resolve({ allowed: true, cwd: "/workspace-feature" }) },
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "do the slice", cwd: "/workspace-feature", model });

      expect(initialModels).toEqual([undefined, model]);
      expect(delegationCapabilities).toEqual([true, false]);
      await service.dispose();
    });

    it("persists tracked child links in the parent and child sessions", async () => {
      const parentPersisted: { customType: string; data?: unknown }[] = [];
      const childPersisted: { customType: string; data?: unknown }[] = [];
      const parent = fakeRuntime("parent-1", {
        sessionFile: "/tmp/parent-1.jsonl",
        sessionManager: fakeSessionManager("/workspace", {
          appendCustomEntry: (customType, data) => {
            parentPersisted.push({ customType, data });
            return "parent-entry-1";
          },
        }),
      });
      const child = fakeRuntime("child-1", {
        sessionFile: "/tmp/child-1.jsonl",
        sessionManager: fakeSessionManager("/workspace-feature", {
          appendCustomEntry: (customType, data) => {
            childPersisted.push({ customType, data });
            return "child-entry-1";
          },
        }),
      });
      const runtimes = [parent.runtime, child.runtime];
      let index = 0;
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: () => {
          const runtime = runtimes[index] ?? child.runtime;
          index += 1;
          return Promise.resolve(runtime);
        },
        sessionManager: sessionGateway([]),
        archiveStore: emptyArchiveStore(),
        spawnTargets: { resolveSpawnTarget: () => Promise.resolve({ allowed: true, cwd: "/workspace-feature" }) },
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "do the slice", cwd: "/workspace-feature" });

      expect(parentPersisted).toEqual([
        {
          customType: "pi-web.subsession.link",
          data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: "/tmp/child-1.jsonl", cwd: "/workspace-feature" },
        },
      ]);
      expect(childPersisted).toEqual([
        {
          customType: "pi-web.subsession.spawned",
          data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" },
        },
      ]);
      await service.dispose();
    });

    it("hydrates persisted child links after a service restart so the parent can inspect them", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getBranch: () => [{ type: "message", message: { role: "assistant", content: "finished" } }],
        });
        const parent = fakeRuntime("parent-1", {
          sessionFile: parentFile,
          sessionManager: fakeSessionManager("/workspace", {
            getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
          }),
        });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const runtimes = [parent.runtime, child.runtime];
        let index = 0;
        const open = vi.fn(() => childManager);
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: () => {
            const runtime = runtimes[index] ?? child.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: { create: () => parent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([]), open },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.start("/workspace");

        await expect(service.checkSubsession("parent-1", "child-1")).resolves.toEqual({
          sessionId: "child-1",
          cwd: "/workspace-feature",
          status: "idle",
          finalText: "finished",
          messageCount: 1,
        });
        expect(open).toHaveBeenCalledWith(childFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("ignores stale persisted child links when the child no longer records the parent", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-stale-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature" })}\n`, "utf8");

      try {
        const parent = fakeRuntime("parent-1", {
          sessionFile: parentFile,
          sessionManager: fakeSessionManager("/workspace", {
            getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
          }),
        });
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: runtimeCreator(parent.runtime),
          sessionManager: { create: () => parent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([]), open: () => fakeSessionManager() },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.start("/workspace");

        await expect(service.listSubsessions("parent-1")).resolves.toEqual([]);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("does not hydrate persisted links when the exact child file is unavailable", async () => {
      const parentFile = "/sessions/parent-1.jsonl";
      const parent = fakeRuntime("parent-1", {
        sessionFile: parentFile,
        sessionManager: fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: "/sessions/child-1.jsonl", cwd: "/workspace-feature" } }],
        }),
      });
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(parent.runtime),
        sessionManager: { create: () => parent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([]), open: () => fakeSessionManager() },
        archiveStore: emptyArchiveStore(),
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");

      await expect(service.listSubsessions("parent-1")).resolves.toEqual([]);
      await service.dispose();
    });

    it("does not hydrate parent links without a child file", async () => {
      const parentFile = "/sessions/parent-1.jsonl";
      const parent = fakeRuntime("parent-1", {
        sessionFile: parentFile,
        sessionManager: fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child", cwd: "/workspace-feature" } }],
        }),
      });
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(parent.runtime),
        sessionManager: { create: () => parent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([]), open: () => fakeSessionManager() },
        archiveStore: emptyArchiveStore(),
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");

      await expect(service.listSubsessions("parent-1")).resolves.toEqual([]);
      await service.dispose();
    });

    it("does not invent subsession links from existing child session headers", async () => {
      const parentFile = "/sessions/parent-1.jsonl";
      const childRecord = { ...sessionRecord("child-1", "/workspace-feature"), path: "/sessions/child-1.jsonl", parentSessionPath: parentFile };
      const parent = fakeRuntime("parent-1", {
        sessionFile: parentFile,
        sessionManager: fakeSessionManager("/workspace", { getEntries: () => [] }),
      });
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(parent.runtime),
        sessionManager: { create: () => parent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([childRecord]), open: () => fakeSessionManager() },
        archiveStore: emptyArchiveStore(),
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");

      await expect(service.listSubsessions("parent-1")).resolves.toEqual([]);
      await service.dispose();
    });

    it("does not hydrate copied parent links when the opened parent has a different id", async () => {
      const forkedParent = fakeRuntime("parent-fork-1", {
        sessionFile: "/sessions/parent-fork-1.jsonl",
        sessionManager: fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: "/sessions/child-1.jsonl", cwd: "/workspace-feature" } }],
        }),
      });
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(forkedParent.runtime),
        sessionManager: { create: () => forkedParent.session.sessionManager, list: () => Promise.resolve([]), listAll: () => Promise.resolve([]), open: () => fakeSessionManager() },
        archiveStore: emptyArchiveStore(),
        heartbeatIntervalMs: 60_000,
      });

      await service.start("/workspace");

      await expect(service.listSubsessions("parent-fork-1")).resolves.toEqual([]);
      await service.dispose();
    });

    it("relinks a spawned child when the child session is opened after restart", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-open-child-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getSessionId: () => "child-1",
          getSessionFile: () => childFile,
          getHeader: () => ({ parentSession: parentFile }),
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getSessionId: () => "parent-1",
          getSessionFile: () => parentFile,
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
        });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const runtimes = [child.runtime, parent.runtime];
        const delegationCapabilities: boolean[] = [];
        let index = 0;
        const open = vi.fn((path: string) => path === parentFile ? parentManager : childManager);
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: (_createRuntime, options) => {
            delegationCapabilities.push(options.delegationToolsEnabled);
            const runtime = runtimes[index] ?? parent.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: {
            create: () => childManager,
            list: () => Promise.resolve([{ ...sessionRecord("child-1", "/workspace-feature"), path: childFile, parentSessionPath: parentFile }]),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // The completion notice is delivered via the async custom-message path;
        // wait for it rather than sleeping a fixed interval.
        await vi.waitFor(() => {
          expect(parent.calls.sendCustomMessage).toHaveLength(1);
        });

        expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("Subsession child-1 stopped working");
        expect(delegationCapabilities).toEqual([false, true]);
        expect(open).toHaveBeenCalledWith(parentFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("notifies the validated parent file instead of an active prefix-matched parent id", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-prefix-parent-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const forkParentFile = join(tempDir, "parent-fork.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(forkParentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1-fork", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getHeader: () => ({ parentSession: parentFile }),
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
        });
        const forkManager = fakeSessionManager("/workspace");
        const fork = fakeRuntime("parent-1-fork", { sessionFile: forkParentFile, sessionManager: forkManager });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const runtimes = [fork.runtime, child.runtime, parent.runtime];
        let index = 0;
        const open = vi.fn((path: string) => {
          if (path === parentFile) return parentManager;
          if (path === forkParentFile) return forkManager;
          return childManager;
        });
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: () => {
            const runtime = runtimes[index] ?? parent.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: {
            create: () => forkManager,
            list: (cwd: string) => Promise.resolve(cwd === "/workspace"
              ? [{ ...sessionRecord("parent-1-fork", "/workspace"), path: forkParentFile }]
              : [{ ...sessionRecord("child-1", "/workspace-feature"), path: childFile, parentSessionPath: parentFile }]),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("parent-1-fork", "/workspace"));
        await service.status(sessionRef("child-1", "/workspace-feature"));
        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // Wait for the validated parent to be notified via the async path.
        await vi.waitFor(() => {
          expect(parent.calls.sendCustomMessage).toHaveLength(1);
        });

        expect(fork.calls.sendCustomMessage).toHaveLength(0);
        expect(open).toHaveBeenCalledWith(parentFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("does not relink a copied child with the original session id unless the parent link names the current child file", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-copied-child-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const originalChildFile = join(tempDir, "original-child.jsonl");
      const copiedChildFile = join(tempDir, "copied-child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(originalChildFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");
      await writeFile(copiedChildFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getHeader: () => ({ parentSession: parentFile }),
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: originalChildFile, cwd: "/workspace-feature" } }],
        });
        const child = fakeRuntime("child-1", { sessionFile: copiedChildFile, sessionManager: childManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const runtimes = [child.runtime, parent.runtime];
        let index = 0;
        const open = vi.fn((path: string) => path === parentFile ? parentManager : childManager);
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: () => {
            const runtime = runtimes[index] ?? parent.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: {
            create: () => childManager,
            list: () => Promise.resolve([{ ...sessionRecord("child-1", "/workspace-feature"), path: copiedChildFile, parentSessionPath: parentFile }]),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // Let the async relink path settle, then confirm it stayed inert and
        // never notified the parent.
        await new Promise<void>((resolve) => setImmediate(resolve));

        expect(parent.calls.sendCustomMessage).toHaveLength(0);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("uses the verified child file instead of an active copied child with the same id", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-active-copy-child-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const originalChildFile = join(tempDir, "original-child.jsonl");
      const copiedChildFile = join(tempDir, "copied-child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(originalChildFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");
      await writeFile(copiedChildFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const copiedManager = fakeSessionManager("/workspace-feature", {
          getBranch: () => [{ type: "message", message: { role: "assistant", content: "copied child result" } }],
        });
        const originalManager = fakeSessionManager("/workspace-feature", {
          getBranch: () => [{ type: "message", message: { role: "assistant", content: "original child result" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: originalChildFile, cwd: "/workspace-feature" } }],
        });
        const copiedChild = fakeRuntime("child-1", { sessionFile: copiedChildFile, sessionManager: copiedManager, isStreaming: true });
        const originalChild = fakeRuntime("child-1", { sessionFile: originalChildFile, sessionManager: originalManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const createAgentRuntime: RuntimeCreator = (_createRuntime, options) => {
          if (options.sessionManager === copiedManager) return Promise.resolve(copiedChild.runtime);
          if (options.sessionManager === originalManager) return Promise.resolve(originalChild.runtime);
          if (options.sessionManager === parentManager) return Promise.resolve(parent.runtime);
          throw new Error("unexpected session manager");
        };
        const open = vi.fn((path: string) => {
          if (path === copiedChildFile) return copiedManager;
          if (path === originalChildFile) return originalManager;
          if (path === parentFile) return parentManager;
          throw new Error(`unexpected open path ${path}`);
        });
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime,
          sessionManager: {
            create: () => parentManager,
            list: (cwd: string) => Promise.resolve(cwd === "/workspace-feature" ? [{ ...sessionRecord("child-1", "/workspace-feature"), path: copiedChildFile, parentSessionPath: parentFile }] : []),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        await service.start("/workspace");

        await expect(service.listSubsessions("parent-1", parentFile)).resolves.toEqual([
          { sessionId: "child-1", cwd: "/workspace-feature", status: "idle" },
        ]);

        copiedChild.session.isStreaming = true;
        copiedChild.emit({ type: "agent_start" });
        copiedChild.session.isStreaming = false;
        copiedChild.emit({ type: "agent_end" });
        // Let the async relink path settle, then confirm the copied child never
        // notified the verified parent.
        await new Promise<void>((resolve) => setImmediate(resolve));
        expect(parent.calls.sendCustomMessage).toHaveLength(0);

        await expect(service.checkSubsession("parent-1", "child-1", parentFile)).resolves.toMatchObject({
          sessionId: "child-1",
          cwd: "/workspace-feature",
          status: "idle",
          finalText: "original child result",
          messageCount: 1,
        });
        const read = await service.readSubsession("parent-1", "child-1", { roles: ["assistant"] }, parentFile);
        expect(read.entries[0]?.parts[0]).toMatchObject({ kind: "text", text: "original child result" });
        expect(open).toHaveBeenCalledWith(originalChildFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("uses the verified parent file instead of an active copied parent with the same id", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-active-copy-parent-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const copiedParentFile = join(tempDir, "copied-parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(copiedParentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: parentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
          getBranch: () => [{ type: "message", message: { role: "assistant", content: "child result" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
        });
        const copiedParentManager = fakeSessionManager("/workspace", { getEntries: () => [] });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const copiedParent = fakeRuntime("parent-1", { sessionFile: copiedParentFile, sessionManager: copiedParentManager });
        const createAgentRuntime: RuntimeCreator = (_createRuntime, options) => {
          if (options.sessionManager === childManager) return Promise.resolve(child.runtime);
          if (options.sessionManager === parentManager) return Promise.resolve(parent.runtime);
          if (options.sessionManager === copiedParentManager) return Promise.resolve(copiedParent.runtime);
          throw new Error("unexpected session manager");
        };
        const open = vi.fn((path: string) => {
          if (path === childFile) return childManager;
          if (path === parentFile) return parentManager;
          if (path === copiedParentFile) return copiedParentManager;
          throw new Error(`unexpected open path ${path}`);
        });
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime,
          sessionManager: {
            create: () => copiedParentManager,
            list: (cwd: string) => Promise.resolve(cwd === "/workspace"
              ? [{ ...sessionRecord("parent-1", "/workspace"), path: copiedParentFile }]
              : [{ ...sessionRecord("child-1", "/workspace-feature"), path: childFile, parentSessionPath: parentFile }]),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        await service.status(sessionRef("parent-1", "/workspace"));

        await expect(service.listSubsessions("parent-1", copiedParentFile)).resolves.toEqual([]);
        await expect(service.checkSubsession("parent-1", "child-1", copiedParentFile)).rejects.toThrow("not one of your subsessions");
        await expect(service.readSubsession("parent-1", "child-1", {}, copiedParentFile)).rejects.toThrow("not one of your subsessions");

        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // Wait for the verified parent to be notified via the async path.
        await vi.waitFor(() => {
          expect(parent.calls.sendCustomMessage).toHaveLength(1);
        });

        expect(copiedParent.calls.sendCustomMessage).toHaveLength(0);
        expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("Subsession child-1 stopped working");
        expect(open).toHaveBeenCalledWith(parentFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("does not relink a child marker when the current child file header no longer records the parent", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-stale-child-header-"));
      const parentFile = join(tempDir, "parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(parentFile, `${JSON.stringify({ type: "session", version: 3, id: "parent-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature" })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getHeader: () => ({ parentSession: parentFile }),
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
        });
        const parentManager = fakeSessionManager("/workspace", {
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.link", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1", spawnedSessionFile: childFile, cwd: "/workspace-feature" } }],
        });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const parent = fakeRuntime("parent-1", { sessionFile: parentFile, sessionManager: parentManager });
        const runtimes = [child.runtime, parent.runtime];
        let index = 0;
        const open = vi.fn((path: string) => path === parentFile ? parentManager : childManager);
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: () => {
            const runtime = runtimes[index] ?? parent.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: {
            create: () => childManager,
            list: () => Promise.resolve([{ ...sessionRecord("child-1", "/workspace-feature"), path: childFile, parentSessionPath: parentFile }]),
            listAll: () => Promise.resolve([]),
            open,
          },
          archiveStore: {
            ...emptyArchiveStore(),
            get: (sessionId) => Promise.resolve(sessionId === "child-1" ? { sessionId: "child-1", cwd: "/workspace-feature", archivedAt: "2026-01-01T00:00:00.000Z", parentSessionPath: parentFile } : undefined),
          },
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // Let the async relink path settle, then confirm it stayed inert.
        await new Promise<void>((resolve) => setImmediate(resolve));

        expect(parent.calls.sendCustomMessage).toHaveLength(0);
        expect(open).not.toHaveBeenCalledWith(parentFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("does not relink a child marker when the child header points at a different parent id", async () => {
      const tempDir = await mkdtemp(join(tmpdir(), "pi-web-subsession-wrong-parent-"));
      const mismatchedParentFile = join(tempDir, "other-parent.jsonl");
      const actualParentFile = join(tempDir, "parent.jsonl");
      const childFile = join(tempDir, "child.jsonl");
      await writeFile(mismatchedParentFile, `${JSON.stringify({ type: "session", version: 3, id: "other-parent", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace" })}\n`, "utf8");
      await writeFile(childFile, `${JSON.stringify({ type: "session", version: 3, id: "child-1", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/workspace-feature", parentSession: mismatchedParentFile })}\n`, "utf8");

      try {
        const childManager = fakeSessionManager("/workspace-feature", {
          getHeader: () => ({ parentSession: mismatchedParentFile }),
          getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
        });
        const parent = fakeRuntime("parent-1", { sessionFile: actualParentFile, sessionManager: fakeSessionManager("/workspace") });
        const child = fakeRuntime("child-1", { sessionFile: childFile, sessionManager: childManager });
        const runtimes = [child.runtime, parent.runtime];
        let index = 0;
        const open = vi.fn((path: string) => path === actualParentFile ? parent.session.sessionManager : childManager);
        const service = new PiSessionService(new CapturingSessionEventHub(), {
          agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
          createAgentRuntime: () => {
            const runtime = runtimes[index] ?? parent.runtime;
            index += 1;
            return Promise.resolve(runtime);
          },
          sessionManager: {
            create: () => childManager,
            list: () => Promise.resolve([{ ...sessionRecord("child-1", "/workspace-feature"), path: childFile, parentSessionPath: mismatchedParentFile }]),
            listAll: () => Promise.resolve([{ ...sessionRecord("parent-1", "/workspace"), path: actualParentFile }]),
            open,
          },
          archiveStore: emptyArchiveStore(),
          heartbeatIntervalMs: 60_000,
        });

        await service.status(sessionRef("child-1", "/workspace-feature"));
        child.session.isStreaming = true;
        child.emit({ type: "agent_start" });
        child.session.isStreaming = false;
        child.emit({ type: "agent_end" });
        // Let the async relink path settle, then confirm it stayed inert.
        await new Promise<void>((resolve) => setImmediate(resolve));

        expect(parent.calls.sendCustomMessage).toHaveLength(0);
        expect(open).not.toHaveBeenCalledWith(actualParentFile);
        await service.dispose();
      } finally {
        await rm(tempDir, { recursive: true, force: true });
      }
    });

    it("does not relink copied child markers when the opened child has a different id", async () => {
      const parentFile = "/sessions/parent-1.jsonl";
      const childFile = "/sessions/child-fork-1.jsonl";
      const childManager = fakeSessionManager("/workspace-feature", {
        getHeader: () => ({ parentSession: parentFile }),
        getEntries: () => [{ type: "custom", customType: "pi-web.subsession.spawned", data: { version: 1, spawnedBySessionId: "parent-1", spawnedSessionId: "child-1" } }],
      });
      const child = fakeRuntime("child-fork-1", { sessionFile: childFile, sessionManager: childManager });
      const open = vi.fn(() => childManager);
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(child.runtime),
        sessionManager: {
          create: () => childManager,
          list: () => Promise.resolve([{ ...sessionRecord("child-fork-1", "/workspace-feature"), path: childFile, parentSessionPath: parentFile }]),
          listAll: () => Promise.resolve([]),
          open,
        },
        archiveStore: emptyArchiveStore(),
        heartbeatIntervalMs: 60_000,
      });

      await service.status(sessionRef("child-fork-1", "/workspace-feature"));
      child.session.isStreaming = true;
      child.emit({ type: "agent_start" });
      child.session.isStreaming = false;
      child.emit({ type: "agent_end" });
      // Let the async relink path settle, then confirm it stayed inert.
      await new Promise<void>((resolve) => setImmediate(resolve));

      expect(open).not.toHaveBeenCalledWith(parentFile);
      await expect(service.listSubsessions("parent-1")).resolves.toEqual([]);
      await service.dispose();
    });

    it("notifies the parent once when the tracked child stops working", async () => {
      const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      child.session.sessionManager.getBranch = () => [
        { type: "message", message: { role: "assistant", content: "all done" } },
      ];
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });
      parent.calls.prompt.length = 0; // ignore the spawn prompt to the child; focus on the parent notification

      child.session.isStreaming = true;
      child.emit({ type: "agent_start" }); // arm the notification
      child.session.isStreaming = false;
      child.emit({ type: "agent_end" }); // fire once
      child.emit({ type: "turn_end" }); // must not re-notify
      // The parent notification is delivered via the async custom-message path;
      // wait for the single delivery (turn_end must not add a second).
      await vi.waitFor(() => {
        expect(parent.calls.sendCustomMessage).toHaveLength(1);
      });
      expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("Subsession child-1 stopped working");
      expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("--- SUBSESSION OUTPUT: child-1 ---\nall done");
      expect(parent.calls.sendCustomMessage[0]?.message.customType).toBe("subsession.completion");
      expect(parent.calls.sendCustomMessage[0]?.options).toEqual({ triggerTurn: true, deliverAs: "followUp" });
      expect(parent.calls.prompt).toHaveLength(0); // not a user-authored message
      await service.dispose();
    });

    it("omits oversized output from the completion notice while keeping it available for inspection", async () => {
      const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      const longOutput = `BEGIN_LONG_OUTPUT\n${"x".repeat(2100)}\nEND_LONG_OUTPUT`;
      child.session.sessionManager.getBranch = () => [
        { type: "message", message: { role: "assistant", content: longOutput } },
      ];
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });

      child.session.isStreaming = true;
      child.emit({ type: "agent_start" });
      child.session.isStreaming = false;
      child.emit({ type: "agent_end" });
      // Wait for the completion notice to be delivered via the async path.
      await vi.waitFor(() => {
        expect(parent.calls.sendCustomMessage).toHaveLength(1);
      });

      expect(parent.calls.sendCustomMessage[0]?.message.content).toBe(
        "Subsession child-1 stopped working (idle).\nNo other tracked subsessions are working.\n\nOutput from subsession child-1 was too long for this completion notice and was omitted. Call check_subsession with sessionId \"child-1\" to retrieve the final output.",
      );
      expect(parent.calls.sendCustomMessage[0]?.message.content).not.toContain("BEGIN_LONG_OUTPUT");
      await expect(service.checkSubsession("parent-1", "child-1", "/tmp/parent-1.jsonl")).resolves.toMatchObject({ finalText: longOutput });
      await service.dispose();
    });

    it("reports other working children in each completion notice", async () => {
      const { parent, children, service } = subsessionService(
        { allowed: true, cwd: "/workspace-feature" },
        60_000,
        ["child-1", "child-2"],
      );
      const [first, second] = children;
      if (first === undefined || second === undefined) throw new Error("Expected two child fixtures");
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "first", cwd: "/workspace-feature" });
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "second", cwd: "/workspace-feature" });

      first.session.isStreaming = true;
      first.emit({ type: "agent_start" });
      second.session.isStreaming = true;
      second.emit({ type: "agent_start" });

      first.session.isStreaming = false;
      first.emit({ type: "agent_end" });
      // Wait for the first completion notice before asserting its content.
      await vi.waitFor(() => {
        expect(parent.calls.sendCustomMessage).toHaveLength(1);
      });

      expect(parent.calls.sendCustomMessage[0]?.message.content).toBe(
        "Subsession child-1 stopped working (idle).\nStill working: child-2. Continue working, or call yield_to_subsessions alone and last at the next join point. Further completion notices arrive automatically; do not poll.\n\n--- SUBSESSION OUTPUT: child-1 ---\n(no output)",
      );

      second.session.isStreaming = false;
      second.emit({ type: "agent_end" });
      // Wait for the second completion notice before asserting its content.
      await vi.waitFor(() => {
        expect(parent.calls.sendCustomMessage).toHaveLength(2);
      });

      expect(parent.calls.sendCustomMessage[1]?.message.content).toBe(
        "Subsession child-2 stopped working (idle).\nNo other tracked subsessions are working.\n\n--- SUBSESSION OUTPUT: child-2 ---\n(no output)",
      );
      await service.dispose();
    });

    it("notifies via the heartbeat when the child settles without a further event", async () => {
      const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" }, 10);
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });
      parent.calls.prompt.length = 0;

      // The child works, then settles silently: agent_end arrives while it still
      // reports active work, so the event-driven latch does not fire here.
      child.session.isStreaming = true;
      child.emit({ type: "agent_start" });
      child.emit({ type: "agent_end" });
      expect(parent.calls.sendCustomMessage).toHaveLength(0);

      // Once the session settles, the periodic heartbeat re-check notifies.
      child.session.isStreaming = false;
      // The periodic heartbeat (heartbeatIntervalMs: 10) re-checks and notifies
      // once the child settles; wait for that delivery rather than sleeping.
      await vi.waitFor(() => {
        expect(parent.calls.sendCustomMessage).toHaveLength(1);
      });

      expect(parent.calls.sendCustomMessage[0]?.message.content).toContain("Subsession child-1 stopped working");
      await service.dispose();
    });

    it("does not notify the parent when a tracked child is archived", async () => {
      const { parent, child, service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });
      // Arm the notification, as a real working child would.
      child.session.isStreaming = true;
      child.emit({ type: "agent_start" });
      child.session.isStreaming = false;
      parent.calls.sendCustomMessage.length = 0;

      await service.archive("child-1");
      // Let any pending notification settle, then confirm the archived child
      // never notified the parent.
      await new Promise<void>((resolve) => setImmediate(resolve));

      expect(parent.calls.sendCustomMessage).toHaveLength(0);
      await service.dispose();
    });

    it("reports a missing tracked child file as unknown in the subsession list", async () => {
      const { service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });

      await service.archive("child-1");

      await expect(service.listSubsessions("parent-1")).resolves.toEqual([
        { sessionId: "child-1", cwd: "/workspace-feature", status: "unknown" },
      ]);
      await service.dispose();
    });

    it("check_subsession and read_subsession refuse sessions that are not the caller's children", async () => {
      const { service } = subsessionService({ allowed: true, cwd: "/workspace-feature" });
      await service.start("/workspace");
      await service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "parent-1", parentSessionFile: "/tmp/parent-1.jsonl", prompt: "go", cwd: "/workspace-feature" });

      await expect(service.checkSubsession("someone-else", "child-1")).rejects.toThrow("not one of your subsessions");
      await expect(service.readSubsession("someone-else", "child-1", {})).rejects.toThrow("not one of your subsessions");
      await service.dispose();
    });

    it("is disabled when no spawn target resolver is configured", async () => {
      const fake = fakeRuntime("nope");
      const service = new PiSessionService(new CapturingSessionEventHub(), {
        agentDir: TEST_AGENT_DIR,
      modelRuntime: testModelRuntime,
        createAgentRuntime: runtimeCreator(fake.runtime),
        sessionManager: sessionGateway([]),
        heartbeatIntervalMs: 60_000,
      });
      await expect(service.spawnSubsession({ spawningCwd: "/workspace", parentSessionId: "p", parentSessionFile: undefined, prompt: "go", cwd: undefined }))
        .rejects.toThrow("Spawning sessions is disabled");
      await service.dispose();
    });
  });
});
