import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import test from "node:test";

import { Kind } from "@sinclair/typebox";

import remnicPiExtension, {
  buildCompactionSummary,
  createRemnicPiExtension,
  isDaemonUnreachableError,
  observeMessages,
  stripSessionOwnedSchemaFields,
  stripSessionOwnedRuntimeFields,
  toPiToolParametersSchema,
} from "./index.js";
import type { RemnicPiConfig } from "./config.js";

test("stripSessionOwnedSchemaFields hides session routing fields from Pi tools", () => {
  const schema = stripSessionOwnedSchemaFields({
    type: "object",
    properties: {
      sessionKey: { type: "string" },
      namespace: { type: "string" },
      cwd: { type: "string" },
      query: { type: "string" },
    },
    required: ["sessionKey", "query"],
    additionalProperties: false,
  });

  assert.deepEqual(schema.properties, {
    query: { type: "string" },
  });
  assert.deepEqual(schema.required, ["query"]);
  assert.equal(schema.additionalProperties, false);
});

test("stripSessionOwnedSchemaFields hides nested session routing fields from Pi tools", () => {
  const schema = stripSessionOwnedSchemaFields({
    type: "object",
    properties: {
      filter: {
        type: "object",
        properties: {
          sessionKey: { type: "string" },
          namespace: { type: "string" },
          cwd: { type: "string" },
          query: { type: "string" },
        },
        required: ["sessionKey", "namespace", "cwd", "query"],
      },
    },
    required: ["filter"],
  });

  assert.deepEqual(schema, {
    type: "object",
    properties: {
      filter: {
        type: "object",
        properties: {
          query: { type: "string" },
        },
        required: ["query"],
      },
    },
    required: ["filter"],
  });
});

test("stripSessionOwnedRuntimeFields removes nested session routing values", () => {
  assert.deepEqual(
    stripSessionOwnedRuntimeFields({
      query: "keep",
      cwd: "/attacker",
      filter: {
        sessionKey: "attacker",
        namespace: "attacker",
        keep: true,
        nested: [{ cwd: "/other", value: 1 }],
      },
    }),
    {
      query: "keep",
      filter: {
        keep: true,
        nested: [{ value: 1 }],
      },
    },
  );
});

test("toPiToolParametersSchema wraps stripped MCP schemas as TypeBox schemas", () => {
  const schema = toPiToolParametersSchema({
    type: "object",
    properties: {
      sessionKey: { type: "string" },
      namespace: { type: "string" },
      cwd: { type: "string" },
      query: { type: "string" },
    },
    required: ["sessionKey", "query"],
    additionalProperties: false,
  }) as any;

  assert.equal(schema[Kind], "Unsafe");
  assert.deepEqual(schema.properties, {
    query: { type: "string" },
  });
  assert.deepEqual(schema.required, ["query"]);
  assert.equal(schema.additionalProperties, false);
});

test("module import does not load the default Pi config eagerly", async (t) => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "remnic-pi-import-"));
  const configPath = path.join(root, "remnic.config.json");
  const previousConfig = process.env.REMNIC_PI_CONFIG;
  try {
    fs.writeFileSync(configPath, "{not-json");
    process.env.REMNIC_PI_CONFIG = configPath;

    const moduleUrl = new URL(`./index.ts?bad-config-import=${Date.now()}`, import.meta.url).href;
    const mod = await import(moduleUrl);

    assert.equal(typeof mod.createRemnicPiExtension, "function");
    assert.equal(typeof mod.default, "function");
  } finally {
    if (previousConfig === undefined) {
      delete process.env.REMNIC_PI_CONFIG;
    } else {
      process.env.REMNIC_PI_CONFIG = previousConfig;
    }
    fs.rmSync(root, { recursive: true, force: true });
  }
});

test("default Pi extension creates isolated state for each host invocation", async (t) => {
  const root = fs.mkdtempSync(path.join(os.tmpdir(), "remnic-pi-default-isolation-"));
  const configPath = path.join(root, "remnic.config.json");
  fs.writeFileSync(configPath, JSON.stringify({
    authToken: "test-token",
    observeEnabled: false,
    compactionEnabled: false,
    mcpToolsEnabled: false,
    statusEnabled: false,
  }));

  const previousConfig = process.env.REMNIC_PI_CONFIG;
  const originalFetch = globalThis.fetch;
  const recallBodies: unknown[] = [];
  process.env.REMNIC_PI_CONFIG = configPath;
  globalThis.fetch = async (_input, init) => {
    recallBodies.push(JSON.parse(String(init?.body ?? "{}")));
    return new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  };
  t.after(() => {
    if (previousConfig === undefined) delete process.env.REMNIC_PI_CONFIG;
    else process.env.REMNIC_PI_CONFIG = previousConfig;
    globalThis.fetch = originalFetch;
    fs.rmSync(root, { recursive: true, force: true });
  });

  const first = makePiHarness();
  const second = makePiHarness();
  await remnicPiExtension(first.pi as any);
  await remnicPiExtension(second.pi as any);

  const event = { messages: [{ role: "user", content: "same prompt" }] };
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "shared-session" },
  };

  await first.emit("context", event, ctx);
  await second.emit("context", event, ctx);

  assert.equal(recallBodies.length, 2);
});

test("MCP tool registration uses the startup timeout instead of the general request timeout", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async (_input, init) =>
    new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () => reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })));
    });
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const registeredTools: Record<string, unknown>[] = [];
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      compactionEnabled: false,
      statusEnabled: false,
      mcpToolsEnabled: true,
      requestTimeoutMs: 60000,
      startupRequestTimeoutMs: 2,
    },
  });

  await extension({
    on: () => undefined,
    registerCommand: () => undefined,
    registerTool: (tool: Record<string, unknown>) => {
      registeredTools.push(tool);
    },
    appendEntry: () => undefined,
  });

  assert.deepEqual(registeredTools, []);
});

test("session_start status probe uses the startup timeout", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async (_input, init) =>
    new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () => reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })));
    });
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const statuses: Array<[string, string]> = [];
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: true,
      requestTimeoutMs: 60000,
      startupRequestTimeoutMs: 2,
    },
  });
  await extension(pi as any);

  await emit("session_start", {}, {
    cwd: "/tmp/remnic-pi",
    ui: {
      setStatus: (key: string, value: string) => statuses.push([key, value]),
    },
    sessionManager: { getSessionId: () => "startup-timeout-test", getEntries: () => [] },
  });

  assert.deepEqual(statuses, [["remnic", "Remnic offline"]]);
});

test("observeMessages only records dedupe hashes after a successful observe", async () => {
  const observedHashes = new Set<string>();
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "retry-test" },
  };
  let calls = 0;
  const client: { observe: () => Promise<void> } = {
    observe: async () => {
      calls += 1;
      throw new Error("offline");
    },
  };

  await observeMessages(ctx, client as any, [{ id: "same-1", role: "user", content: "same prompt" }], observedHashes);

  assert.equal(calls, 1);
  assert.equal(observedHashes.size, 0);

  client.observe = async () => {
    calls += 1;
  };

  await observeMessages(ctx, client as any, [{ id: "same-1", role: "user", content: "same prompt" }], observedHashes);
  await observeMessages(ctx, client as any, [{ id: "same-1", role: "user", content: "same prompt" }], observedHashes);

  assert.equal(calls, 2);
  assert.equal(observedHashes.size, 1);
});

test("observeMessages caps persisted dedupe hashes during long sessions", async () => {
  const observedHashes = new Set<string>();
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "cap-test" },
  };
  const client: { observe: () => Promise<void> } = {
    observe: async () => undefined,
  };

  for (let index = 0; index < 2005; index++) {
    await observeMessages(ctx, client as any, [{ id: `message-${index}`, role: "user", content: `message ${index}` }], observedHashes);
  }

  assert.equal(observedHashes.size, 2000);
});

test("observeMessages preserves repeated turns without stable Pi identity", async () => {
  const observedHashes = new Set<string>();
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "repeat-test" },
  };
  const batches: unknown[][] = [];
  const client: { observe: (_sessionKey: string, _cwd: string, messages: unknown[]) => Promise<void> } = {
    observe: async (_sessionKey, _cwd, messages) => {
      batches.push(messages);
    },
  };

  await observeMessages(ctx, client as any, [{ role: "user", content: "yes" }], observedHashes);
  await observeMessages(ctx, client as any, [{ role: "user", content: "yes" }], observedHashes);

  assert.equal(batches.length, 2);
  assert.equal(observedHashes.size, 0);
});

test("observeMessages preserves repeated multi-message turns without stable Pi identity", async () => {
  const observedHashes = new Set<string>();
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "repeat-multi-message-test" },
  };
  const batches: unknown[][] = [];
  const client: { observe: (_sessionKey: string, _cwd: string, messages: unknown[]) => Promise<void> } = {
    observe: async (_sessionKey, _cwd, messages) => {
      batches.push(messages);
    },
  };
  const turn = [
    { role: "assistant", content: "done" },
    { role: "bashExecution", command: "npm test", output: "passed" },
  ];

  await observeMessages(ctx, client as any, turn, observedHashes);
  await observeMessages(ctx, client as any, turn, observedHashes);

  assert.equal(batches.length, 2);
  assert.deepEqual(batches.map((batch) => batch.length), [2, 2]);
  assert.equal(observedHashes.size, 0);
});

test("observeMessages dedupes replayed Pi entries with stable identity", async () => {
  const observedHashes = new Set<string>();
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "entry-test" },
  };
  let calls = 0;
  const client: { observe: () => Promise<void> } = {
    observe: async () => {
      calls += 1;
    },
  };
  const raw = [{ id: "entry-1", role: "user", content: "yes" }];

  await observeMessages(ctx, client as any, raw, observedHashes);
  await observeMessages(ctx, client as any, raw, observedHashes);

  assert.equal(calls, 1);
  assert.equal(observedHashes.size, 1);
});

test("session_shutdown preserves Pi branch entry identity before observing", async (t) => {
  const originalFetch = globalThis.fetch;
  const observeBodies: Array<Record<string, any>> = [];
  globalThis.fetch = async (input, init) => {
    if (String(input).endsWith("/engram/v1/observe")) {
      observeBodies.push(JSON.parse(String(init?.body ?? "{}")));
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: {
      getSessionId: () => "branch-entry-test",
      getEntries: () => [],
      getBranch: () => [
        {
          id: "entry-1",
          timestamp: 1710000000000,
          message: { role: "user", content: "remember this" },
        },
      ],
    },
  };

  await emit("session_shutdown", {}, ctx);

  assert.equal(observeBodies.length, 1);
  const rawContent = observeBodies[0].messages?.[0]?.rawContent as Record<string, unknown>;
  assert.equal(rawContent.entryId, "entry-1");
  assert.equal(rawContent.timestamp, 1710000000000);
});

test("session_shutdown skips branch messages already observed at turn_end", async (t) => {
  const originalFetch = globalThis.fetch;
  const observeBodies: Array<Record<string, any>> = [];
  globalThis.fetch = async (input, init) => {
    if (String(input).endsWith("/engram/v1/observe")) {
      observeBodies.push(JSON.parse(String(init?.body ?? "{}")));
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const message = { role: "assistant", content: "done" };
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: {
      getSessionId: () => "shutdown-live-observed-test",
      getEntries: () => [],
      getBranch: () => [{ id: "entry-1", message }],
    },
  };

  await emit("turn_end", { message }, ctx);
  await emit("session_shutdown", {}, ctx);

  assert.equal(observeBodies.length, 1);
  assert.equal(observeBodies[0].messages?.[0]?.rawContent?.entryId, undefined);
});

test("message_end observes user prompts before shutdown replay", async (t) => {
  const originalFetch = globalThis.fetch;
  const observeBodies: Array<Record<string, any>> = [];
  globalThis.fetch = async (input, init) => {
    if (String(input).endsWith("/engram/v1/observe")) {
      observeBodies.push(JSON.parse(String(init?.body ?? "{}")));
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const message = { role: "user", content: "remember my preference" };
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: {
      getSessionId: () => "message-end-live-observed-test",
      getEntries: () => [],
      getBranch: () => [{ id: "entry-1", message }],
    },
  };

  await emit("message_end", { message }, ctx);
  await emit("session_shutdown", {}, ctx);

  assert.equal(observeBodies.length, 1);
  assert.equal(observeBodies[0].messages?.[0]?.content, "remember my preference");
});

test("agent_end does not duplicate turn_end observation", async (t) => {
  const originalFetch = globalThis.fetch;
  const observeBodies: Array<Record<string, unknown>> = [];
  globalThis.fetch = async (input, init) => {
    if (String(input).endsWith("/engram/v1/observe")) {
      observeBodies.push(JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>);
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "agent-end-duplicate-test" },
  };
  const message = { role: "assistant", content: "done" };

  await emit("turn_end", { message }, ctx);
  await emit("agent_end", { messages: [message] }, ctx);

  assert.equal(observeBodies.length, 1);
});

test("buildCompactionSummary returns empty content for empty compaction preparations", () => {
  assert.equal(buildCompactionSummary({}), "");
});

test("buildCompactionSummary includes only meaningful compaction content", () => {
  const summary = buildCompactionSummary({
    messagesToSummarize: [
      { role: "user", content: "keep this" },
      { role: "bashExecution", command: "private", output: "secret", excludeFromContext: true },
    ],
  });

  assert.ok(summary.includes("## Remnic Pi Context Checkpoint"));
  assert.ok(summary.includes("[user] keep this"));
  assert.equal(summary.includes("private"), false);
  assert.equal(summary.includes("secret"), false);
});

test("session_before_compact records token counts only when Pi supplies both counts", async (t) => {
  const originalFetch = globalThis.fetch;
  const compactionRecords: Array<Record<string, unknown>> = [];
  globalThis.fetch = async (input, init) => {
    const url = String(input);
    const body = init?.body ? JSON.parse(String(init.body)) as Record<string, unknown> : {};
    if (url.endsWith("/engram/v1/lcm/compaction/record")) {
      compactionRecords.push(body);
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "compact-token-count-test" },
  };
  const messagesToSummarize = [{ role: "user", content: "compact this" }];

  await emit("session_before_compact", { preparation: { tokensBefore: 100, messagesToSummarize } }, ctx);
  await new Promise((resolve) => setTimeout(resolve, 0));
  assert.equal(compactionRecords.length, 0);

  await emit("session_before_compact", { preparation: { tokensBefore: 100, tokensAfter: 42 } }, ctx);
  assert.equal(compactionRecords.length, 1);
  assert.equal(compactionRecords[0].tokensBefore, 100);
  assert.equal(compactionRecords[0].tokensAfter, 42);
});

test("session_before_compact surfaces checkpoint write failures without dropping compaction result", async (t) => {
  const originalFetch = globalThis.fetch;
  const notifications: Array<{ message: string; level: string }> = [];
  globalThis.fetch = async (input) => {
    const url = String(input);
    if (url.endsWith("/mcp")) {
      return new Response(JSON.stringify({ error: { message: "checkpoint unavailable" } }), { status: 200 });
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const result = await emit(
    "session_before_compact",
    {
      preparation: {
        tokensBefore: 100,
        tokensAfter: 42,
        messagesToSummarize: [{ role: "user", content: "compact this" }],
      },
    },
    {
      cwd: "/tmp/remnic-pi",
      sessionManager: { getSessionId: () => "compact-checkpoint-failure-test" },
      ui: {
        notify(message: string, level: string) {
          notifications.push({ message, level });
        },
      },
    },
  );

  assert.ok(result && typeof result === "object" && "compaction" in result);
  assert.ok(
    notifications.some(
      (notification) =>
        notification.level === "warning" &&
        notification.message.includes("Remnic context checkpoint failed"),
    ),
  );
});

test("singleton extension clears per-session recall suppression on shutdown", async (t) => {
  const originalFetch = globalThis.fetch;
  const recallBodies: unknown[] = [];
  globalThis.fetch = async (_input, init) => {
    recallBodies.push(JSON.parse(String(init?.body ?? "{}")));
    return new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const firstCtx = { cwd: "/tmp/remnic-pi" };
  const secondCtx = { cwd: "/tmp/remnic-pi" };
  const event = { messages: [{ role: "user", content: "same prompt" }] };

  await emit("context", event, firstCtx);
  await emit("context", event, firstCtx);
  await emit("session_shutdown", {}, firstCtx);
  await emit("context", event, secondCtx);

  assert.equal(recallBodies.length, 2);
});

test("recall suppression distinguishes repeated user text with different message ids", async (t) => {
  const originalFetch = globalThis.fetch;
  const recallBodies: unknown[] = [];
  globalThis.fetch = async (_input, init) => {
    recallBodies.push(JSON.parse(String(init?.body ?? "{}")));
    return new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "repeat-user-text-recall" },
  };

  await emit("context", { messages: [{ id: "m1", role: "user", content: "continue" }] }, ctx);
  await emit("context", { messages: [{ id: "m2", role: "user", content: "continue" }] }, ctx);
  await emit("context", { messages: [{ id: "m2", role: "user", content: "continue" }] }, ctx);

  assert.equal(recallBodies.length, 2);
});

test("empty recall responses do not suppress retry for same query", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async () => {
    calls += 1;
    return new Response(JSON.stringify({ context: calls === 1 ? "" : "remembered context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "empty-recall-retry" },
  };
  const event = { messages: [{ role: "user", content: "same prompt" }] };

  assert.equal(await emit("context", event, ctx), undefined);
  const result = await emit("context", event, ctx) as { messages?: Array<{ content?: Array<{ text?: string }> }> };

  assert.equal(calls, 2);
  const injected = result.messages?.at(-1);
  assert.ok(injected?.content?.[0]?.text?.includes("remembered context"));
  assert.equal(
    (injected as { excludeFromContext?: unknown } | undefined)?.excludeFromContext,
    undefined,
  );
  assert.equal(
    (injected as { remnicInjected?: unknown } | undefined)?.remnicInjected,
    true,
  );
});

test("context recall appends injected context after stable conversation history", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async () =>
    new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const firstMessage = { role: "system", content: "stable prefix" };
  const userMessage = { role: "user", content: "same prompt" };
  const result = await emit("context", { messages: [firstMessage, userMessage] }, {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "append-recall-test" },
  }) as { messages?: Array<Record<string, unknown>> };

  assert.equal(result.messages?.[0], firstMessage);
  assert.equal(result.messages?.[1], userMessage);
  assert.equal(result.messages?.[2]?.remnicInjected, true);
});

test("context recall does not load full session history", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async () =>
    new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const sessionManager = {
    getSessionId: () => "history-free-context",
    getEntries: () => {
      throw new Error("getEntries should not be called");
    },
    getBranch: () => {
      throw new Error("getBranch should not be called");
    },
  };

  const result = await emit("context", { messages: [{ role: "user", content: "same prompt" }] }, {
    cwd: "/tmp/remnic-pi",
    sessionManager,
  }) as { messages?: Array<Record<string, unknown>> };

  assert.equal(result.messages?.at(-1)?.remnicInjected, true);
});

test("context recall skips stale Pi ctx before snapshot", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async () => {
    calls += 1;
    return new Response(JSON.stringify({ context: "should not be used" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const stale = makeStaleCtx({ sessionId: "stale-before-snapshot" });
  stale.markStale();

  const result = await emit("context", { messages: [{ role: "user", content: "same prompt" }] }, stale.ctx);

  assert.equal(result, undefined);
  assert.equal(calls, 0);
  assert.deepEqual(stale.notifications, []);
});

test("context recall keeps pi:default fallback when session manager is missing", async (t) => {
  const originalFetch = globalThis.fetch;
  const recallBodies: Array<Record<string, unknown>> = [];
  globalThis.fetch = async (_input, init) => {
    recallBodies.push(JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>);
    return new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const result = await emit("context", { messages: [{ role: "user", content: "same prompt" }] }, {
    cwd: "/tmp/remnic-pi",
  }) as { messages?: Array<Record<string, unknown>> };

  assert.equal(result.messages?.at(-1)?.remnicInjected, true);
  assert.equal(recallBodies[0]?.sessionKey, "pi:default");
  assert.equal(recallBodies[0]?.cwd, "/tmp/remnic-pi");
});

test("recall context truncation stays within the configured budget", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async () =>
    new Response(JSON.stringify({ context: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" }), { status: 200 });
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      recallBudgetChars: 40,
    },
  });
  await extension(pi as any);

  const result = await emit("context", { messages: [{ role: "user", content: "same prompt" }] }, {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "recall-budget-test" },
  }) as { messages?: Array<{ content?: Array<{ text?: string }> }> };

  const text = result.messages?.at(-1)?.content?.[0]?.text ?? "";
  const context = text.split("Remnic recalled context for this turn:\n\n")[1] ?? "";
  assert.equal(context.length, 40);
  assert.ok(context.endsWith("[Remnic context truncated]"));
});

test("failed recall does not suppress retry for same query", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async () => {
    calls += 1;
    if (calls === 1) throw new Error("offline");
    return new Response(JSON.stringify({ context: "remembered context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "retry-recall" },
  };
  const event = { messages: [{ role: "user", content: "same prompt" }] };

  await emit("context", event, ctx);
  await emit("context", event, ctx);
  await emit("context", event, ctx);

  assert.equal(calls, 2);
});

test("context recall failure notification survives stale Pi ctx after await", async (t) => {
  const originalFetch = globalThis.fetch;
  const stale = makeStaleCtx({ sessionId: "stale-context-recall" });
  globalThis.fetch = async () => {
    stale.markStale();
    throw new Error("offline");
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  await assert.doesNotReject(() =>
    emit("context", { messages: [{ role: "user", content: "same prompt" }] }, stale.ctx)
  );
  assert.deepEqual(stale.notifications, [
    { message: "Remnic recall unavailable: offline", level: "warning" },
  ]);
});

test("observe failure notification survives stale Pi ctx after await", async (t) => {
  const originalFetch = globalThis.fetch;
  const stale = makeStaleCtx({ sessionId: "stale-observe" });
  globalThis.fetch = async () => {
    stale.markStale();
    throw new Error("observe offline");
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  await assert.doesNotReject(() =>
    emit("message_end", { message: { id: "m1", role: "user", content: "remember this" } }, stale.ctx)
  );
  assert.deepEqual(stale.notifications, [
    { message: "Remnic observe failed: observe offline", level: "warning" },
  ]);
});

test("compaction failure notification survives stale Pi ctx after await", async (t) => {
  const originalFetch = globalThis.fetch;
  const stale = makeStaleCtx({ sessionId: "stale-compaction" });
  globalThis.fetch = async () => {
    stale.markStale();
    throw new Error("flush offline");
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  await assert.doesNotReject(() => emit("session_before_compact", { preparation: {} }, stale.ctx));
  assert.deepEqual(stale.notifications, [
    { message: "Remnic LCM flush failed: flush offline", level: "warning" },
  ]);
});

test("command handlers notify with captured context after stale Pi ctx", async (t) => {
  const originalFetch = globalThis.fetch;
  const stale = makeStaleCtx({ sessionId: "stale-command" });
  const recallBodies: Array<Record<string, unknown>> = [];
  globalThis.fetch = async (_input, init) => {
    recallBodies.push(JSON.parse(String(init?.body ?? "{}")) as Record<string, unknown>);
    stale.markStale();
    return new Response(JSON.stringify({ context: "remembered command context" }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const { pi, runCommand } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  await assert.doesNotReject(() => runCommand("remnic-recall", "same prompt", stale.ctx));
  assert.equal(recallBodies[0].sessionKey, "pi:stale-command");
  assert.equal(recallBodies[0].cwd, "/tmp/remnic-pi");
  assert.deepEqual(stale.notifications, [
    { message: "remembered command context", level: "info" },
  ]);
});

test("registered MCP tools strip nested session-owned params before forwarding", async (t) => {
  const originalFetch = globalThis.fetch;
  const registeredTools: any[] = [];
  const forwardedArguments: unknown[] = [];
  globalThis.fetch = async (_input, init) => {
    const body = JSON.parse(String(init?.body ?? "{}"));
    if (body.method === "tools/list") {
      return new Response(JSON.stringify({
        result: {
          tools: [
            {
              name: "remnic.search",
              description: "Search",
              inputSchema: {
                type: "object",
                properties: {
                  filter: {
                    type: "object",
                    properties: {
                      sessionKey: { type: "string" },
                      namespace: { type: "string" },
                      cwd: { type: "string" },
                      query: { type: "string" },
                    },
                    required: ["sessionKey", "namespace", "cwd", "query"],
                  },
                },
                required: ["filter"],
              },
            },
          ],
        },
      }), { status: 200 });
    }
    if (body.method === "tools/call") {
      forwardedArguments.push(body.params.arguments);
      return new Response(JSON.stringify({ result: { ok: true } }), { status: 200 });
    }
    return new Response(JSON.stringify({ result: {} }), { status: 200 });
  };
  t.after(() => {
    globalThis.fetch = originalFetch;
  });

  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      namespace: "configured-namespace",
      recallEnabled: false,
      observeEnabled: false,
      compactionEnabled: false,
      statusEnabled: false,
      mcpToolsEnabled: true,
    },
  });
  await extension({
    on: () => undefined,
    registerCommand: () => undefined,
    registerTool: (tool: Record<string, unknown>) => {
      registeredTools.push(tool);
    },
    appendEntry: () => undefined,
  });

  assert.equal(registeredTools.length, 1);
  assert.deepEqual(registeredTools[0].parameters.properties.filter.properties, {
    query: { type: "string" },
  });
  assert.deepEqual(registeredTools[0].parameters.properties.filter.required, ["query"]);

  await registeredTools[0].execute(
    "tool-call-1",
    {
      filter: {
        sessionKey: "attacker-session",
        namespace: "attacker-namespace",
        cwd: "/attacker",
        query: "keep",
        nested: [{ cwd: "/nested-attacker", value: 1 }],
      },
    },
    undefined,
    undefined,
    {
      cwd: "/safe/project",
      sessionManager: { getSessionId: () => "safe-session" },
    },
  );

  assert.deepEqual(forwardedArguments, [
    {
      filter: {
        query: "keep",
        nested: [{ value: 1 }],
      },
      sessionKey: "pi:safe-session",
      namespace: "configured-namespace",
      cwd: "/safe/project",
    },
  ]);
});

function baseConfig(): RemnicPiConfig {
  return {
    remnicDaemonUrl: "http://127.0.0.1:4318",
    recallMode: "auto",
    recallTopK: 8,
    recallBudgetChars: 12000,
    recallEnabled: true,
    observeEnabled: true,
    observeSkipExtraction: false,
    compactionEnabled: true,
    mcpToolsEnabled: true,
    statusEnabled: true,
    requestTimeoutMs: 60000,
    startupRequestTimeoutMs: 1000,
    turnRequestTimeoutMs: 20000,
    observeMaxBytes: 102400,
    observeMaxRetries: 2,
    daemonCooldownMs: 5000,
  };
}

function makePiHarness(): {
  pi: Record<string, unknown>;
  emit: (event: string, payload: unknown, ctx: unknown) => Promise<unknown>;
  runCommand: (name: string, args: string, ctx: unknown) => Promise<void>;
} {
  const handlers = new Map<string, Array<(event: unknown, ctx: unknown) => unknown | Promise<unknown>>>();
  const commands = new Map<string, (args: string, ctx: unknown) => Promise<void>>();
  const pi = {
    on: (event: string, handler: (event: unknown, ctx: unknown) => unknown | Promise<unknown>) => {
      handlers.set(event, [...(handlers.get(event) ?? []), handler]);
    },
    registerCommand: (name: string, options: { handler: (args: string, ctx: unknown) => Promise<void> }) => {
      commands.set(name, options.handler);
    },
    registerTool: () => undefined,
    appendEntry: () => undefined,
  };
  return {
    pi,
    emit: async (event, payload, ctx) => {
      let result: unknown;
      for (const handler of handlers.get(event) ?? []) {
        result = await handler(payload, ctx);
      }
      return result;
    },
    runCommand: async (name, args, ctx) => {
      const handler = commands.get(name);
      assert.ok(handler, `missing command ${name}`);
      await handler(args, ctx);
    },
  };
}

function makeStaleCtx(options: {
  sessionId: string;
  cwd?: string;
  entries?: unknown[];
  branch?: unknown[];
}): {
  ctx: unknown;
  markStale: () => void;
  notifications: Array<{ message: string; level: string }>;
  statuses: Array<{ key: string; value: string }>;
} {
  let stale = false;
  const notifications: Array<{ message: string; level: string }> = [];
  const statuses: Array<{ key: string; value: string }> = [];
  const assertActive = () => {
    if (stale) {
      throw new Error("This extension ctx is stale after session replacement or reload.");
    }
  };
  const sessionManager = {
    getSessionId: () => options.sessionId,
    getEntries: () => options.entries ?? [],
    getBranch: () => options.branch ?? [],
  };
  const ui = {
    notify(message: string, level: string) {
      notifications.push({ message, level });
    },
    setStatus(key: string, value: string) {
      statuses.push({ key, value });
    },
  };

  return {
    ctx: {
      get cwd() {
        assertActive();
        return options.cwd ?? "/tmp/remnic-pi";
      },
      get hasUI() {
        assertActive();
        return true;
      },
      get ui() {
        assertActive();
        return ui;
      },
      get sessionManager() {
        assertActive();
        return sessionManager;
      },
      compact() {
        assertActive();
      },
    },
    markStale: () => {
      stale = true;
    },
    notifications,
    statuses,
  };
}


test("recall circuit breaker skips subsequent turns after a timeout against an unreachable daemon (#1626)", async (t) => {
  const originalFetch = globalThis.fetch;
  let fetchCalls = 0;
  // fetch hangs until the AbortController fires, simulating an unreachable host.
  globalThis.fetch = async (_input, init) => {
    fetchCalls += 1;
    return new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () =>
        reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })),
      );
    });
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      turnRequestTimeoutMs: 30,
      daemonCooldownMs: 60000,
    },
  });
  await extension(pi as unknown as Parameters<typeof extension>[0]);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "cb-session" },
  };
  const event = { messages: [{ role: "user", content: "any prompt" }] };

  // First turn: recall times out → daemon enters cooldown.
  await emit("context", event, ctx);
  assert.equal(fetchCalls, 1, "first turn issued a recall that timed out");

  // Second turn: daemon is known-down → recall is skipped fast, no fetch.
  await emit("context", event, ctx);
  assert.equal(fetchCalls, 1, "circuit breaker skipped recall during cooldown");
});

test("session_shutdown replays the branch even when the daemon breaker is tripped (#1626, review codex)", async (t) => {
  const originalFetch = globalThis.fetch;
  const observeBodies: Array<Record<string, any>> = [];
  let call = 0;
  globalThis.fetch = async (input, init) => {
    call += 1;
    if (String(input).endsWith("/engram/v1/observe")) {
      // Fail the ENTIRE turn_end retry chain (1 initial + observeMaxRetries=2
      // retries = 3 calls) so the breaker actually trips; the next observe
      // (shutdown) then succeeds, proving shutdown bypasses the breaker.
      if (call <= 3) {
        throw new Error("The socket connection was closed unexpectedly.");
      }
      observeBodies.push(JSON.parse(String(init?.body ?? "{}")));
    }
    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
    },
  });
  await extension(pi as any);

  const message = { role: "assistant", content: "done" };
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: {
      getSessionId: () => "shutdown-breaker-test",
      getEntries: () => [],
      getBranch: () => [{ id: "entry-1", message }],
    },
  };

  // turn_end observe fails transiently -> breaker tripped, nothing recorded.
  await emit("turn_end", { message }, ctx);
  assert.equal(observeBodies.length, 0, "turn_end observe failed and recorded nothing");

  // shutdown runs while the breaker is in cooldown; forceAttempt must still
  // replay the branch (the last chance to observe before teardown).
  await emit("session_shutdown", {}, ctx);
  assert.equal(observeBodies.length, 1, "shutdown bypassed the breaker and observed the branch");
});

test("session_shutdown observe uses the general request budget, not the per-turn budget (review cursor)", async (t) => {
  const originalFetch = globalThis.fetch;
  globalThis.fetch = async (_input, init) =>
    new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () =>
        reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })),
      );
    });
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      turnRequestTimeoutMs: 30,
      requestTimeoutMs: 150,
    },
  });
  await extension(pi as any);

  const stale = makeStaleCtx({
    sessionId: "shutdown-budget-test",
    branch: [{ id: "entry-1", message: { role: "user", content: "x".repeat(50) } }],
  });

  await emit("session_shutdown", {}, stale.ctx);

  // Shutdown replay is teardown (no host handler window), so it must use the
  // general request budget (150ms), NOT the per-turn budget (30ms). The timeout
  // error embeds the budget actually used, so this deterministically proves
  // which budget governed the forced replay.
  const observeFail = stale.notifications.find((n) => /Remnic observe failed/.test(n.message));
  assert.ok(observeFail, "shutdown observe ran and timed out");
  assert.match(observeFail!.message, /timed out after 150ms/);
});

test("retry-budget exhaustion trips the circuit breaker so the next turn cools down (review codex)", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async () => {
    calls += 1;
    throw new Error("The socket connection was closed unexpectedly.");
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      // Per-turn budget below the first 200ms backoff, so a transient failure
      // exhausts the retry budget and throws the budget-exceeded error.
      turnRequestTimeoutMs: 30,
      observeMaxRetries: 2,
      daemonCooldownMs: 60000,
    },
  });
  await extension(pi as any);

  const message = { role: "assistant", content: "done" };
  const ctx = {
    cwd: "/tmp/remnic-pi",
    sessionManager: { getSessionId: () => "budget-breaker-test", getEntries: () => [], getBranch: () => [] },
  };

  // First turn: transient socket close exhausts the retry budget. The
  // budget-exceeded error must trip the breaker (not fall through silently).
  await emit("turn_end", { message }, ctx);
  const callsAfterFirst = calls;
  assert.ok(callsAfterFirst >= 1, "first turn attempted an observe");

  // Second turn: breaker is in cooldown -> observe is skipped, no new fetch.
  await emit("turn_end", { message }, ctx);
  assert.equal(calls, callsAfterFirst, "breaker skipped the second turn after budget exhaustion");
});

test("isDaemonUnreachableError recognizes both budget-exceeded wordings so the breaker trips (review cursor)", () => {
  // requestWithRetry retry-budget exhaustion:
  assert.ok(
    isDaemonUnreachableError(new Error("Remnic request exceeded the 50ms budget before retry 1 (POST /engram/v1/observe)")),
    "retry-budget exhaustion is unreachable",
  );
  // Multi-chunk observe per-turn budget exhaustion:
  assert.ok(
    isDaemonUnreachableError(new Error("Remnic observe exceeded the per-turn budget of 20ms across 3 chunks (completed 1)")),
    "multi-chunk observe budget exhaustion is unreachable",
  );
  // A semantic HTTP-style error is NOT classified as unreachable:
  assert.ok(!isDaemonUnreachableError(new Error("Internal Server Error")), "HTTP errors stay reachable");
});

test("a successful startup health probe clears a stale circuit breaker (review codex)", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async (input, init) => {
    calls += 1;
    const url = String(input);
    if (url.endsWith("/engram/v1/health")) {
      return new Response(JSON.stringify({ ok: true }), { status: 200 });
    }
    // recall hangs until the AbortController fires, simulating an unreachable daemon.
    return new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () =>
        reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })),
      );
    });
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: true,
      turnRequestTimeoutMs: 30,
      daemonCooldownMs: 60000,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    ui: { setStatus: () => {}, notify: () => {} },
    sessionManager: { getSessionId: () => "status-clear-breaker" },
  };
  const event = { messages: [{ role: "user", content: "hi" }] };

  // 1) recall times out -> breaker tripped.
  await emit("context", event, ctx);
  // 2) session_start health succeeds -> clears the stale breaker.
  await emit("session_start", {}, ctx);
  // 3) recall now runs instead of fast-skipping.
  const callsBeforeSecondRecall = calls;
  await emit("context", event, ctx);
  assert.ok(calls > callsBeforeSecondRecall, "recall ran after the health probe cleared the breaker");
});

test("an offline startup health probe trips the circuit breaker so the first turn fast-skips (review codex)", async (t) => {
  const originalFetch = globalThis.fetch;
  let recallCalls = 0;
  globalThis.fetch = async (input, init) => {
    const url = String(input);
    if (url.endsWith("/engram/v1/health")) {
      // Daemon is offline: a connection-level failure fails fast (health() does not retry).
      throw new Error("The socket connection was closed unexpectedly.");
    }
    // If the breaker let recall through, this would hang until the AbortController fires.
    recallCalls += 1;
    return new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () =>
        reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })),
      );
    });
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: true,
      turnRequestTimeoutMs: 30,
      daemonCooldownMs: 60000,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    ui: { setStatus: () => {}, notify: () => {} },
    sessionManager: { getSessionId: () => "status-trip-breaker" },
  };
  const event = { messages: [{ role: "user", content: "hi" }] };

  // 1) session_start health fails because the daemon is offline -> trips the breaker.
  await emit("session_start", {}, ctx);
  // 2) context (recall) fast-skips because the breaker is tripped, so the doomed
  //    recall never costs the full turn budget.
  await emit("context", event, ctx);
  assert.equal(recallCalls, 0, "recall fast-skipped after the offline startup probe tripped the breaker");
});

test("/remnic-recall bounds retry to the general request budget instead of unbounded retries (review cursor)", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async () => {
    calls += 1;
    throw new Error("The socket connection was closed unexpectedly.");
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, runCommand } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      recallEnabled: false,
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      requestTimeoutMs: 40,
      observeMaxRetries: 2,
    },
  });
  await extension(pi as any);

  const notifications: Array<{ message: string; level: string }> = [];
  const ctx = {
    cwd: "/tmp/remnic-pi",
    ui: { notify: (m: string, l: string) => notifications.push({ message: m, level: l }) },
    sessionManager: { getSessionId: () => "recall-command-budget" },
  };

  await runCommand("remnic-recall", "any query", ctx);
  // The command passes the general request budget as a shared deadline, so the
  // 40ms budget (below the first 200ms backoff) stops retries after one call
  // instead of looping through the full retry chain unbounded.
  assert.equal(calls, 1, "manual recall did not loop through unbounded retries");
  assert.ok(
    notifications.some((n) => /Remnic command failed/.test(n.message)),
    "command reported the bounded failure",
  );
});

test("a successful /remnic-recall clears a stale circuit breaker (review cursor)", async (t) => {
  const originalFetch = globalThis.fetch;
  let calls = 0;
  globalThis.fetch = async (input, init) => {
    calls += 1;
    const url = String(input);
    // Manual recall responds OK; automatic context recall hangs until abort.
    if (url.endsWith("/engram/v1/recall") && init?.body && JSON.parse(String(init.body)).query === "manual") {
      return new Response(JSON.stringify({ context: "manual context" }), { status: 200 });
    }
    return new Promise<Response>((_resolve, reject) => {
      init?.signal?.addEventListener("abort", () =>
        reject(Object.assign(new Error("This operation was aborted"), { name: "AbortError" })),
      );
    });
  };
  t.after(() => { globalThis.fetch = originalFetch; });

  const { pi, emit, runCommand } = makePiHarness();
  const extension = createRemnicPiExtension({
    config: {
      ...baseConfig(),
      authToken: "test-token",
      observeEnabled: false,
      compactionEnabled: false,
      mcpToolsEnabled: false,
      statusEnabled: false,
      turnRequestTimeoutMs: 30,
      daemonCooldownMs: 60000,
    },
  });
  await extension(pi as any);

  const ctx = {
    cwd: "/tmp/remnic-pi",
    ui: { setStatus: () => {}, notify: () => {} },
    sessionManager: { getSessionId: () => "manual-recall-clears-breaker" },
  };
  const autoEvent = { messages: [{ role: "user", content: "auto" }] };

  // 1) automatic context recall times out -> breaker tripped.
  await emit("context", autoEvent, ctx);
  // 2) a successful manual /remnic-recall clears the stale breaker.
  await runCommand("remnic-recall", "manual", ctx);
  // 3) automatic context recall now runs instead of fast-skipping.
  const callsBeforeSecondAuto = calls;
  await emit("context", { messages: [{ role: "user", content: "auto2" }] }, ctx);
  assert.ok(calls > callsBeforeSecondAuto, "automatic recall ran after the manual recall cleared the breaker");
});
