import fsCore from "node:fs";
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "../../config/sessions.js";
import {
  clearMemoryPluginState,
  registerMemoryCapability,
  type MemoryFlushPlanResolver,
} from "../../plugins/memory-state.js";
import type { TemplateContext } from "../templating.js";
import {
  runMemoryFlushIfNeeded,
  runPreflightCompactionIfNeeded,
  setAgentRunnerMemoryTestDeps,
} from "./agent-runner-memory.js";
import { createTestFollowupRun, writeTestSessionStore } from "./agent-runner.test-fixtures.js";
import type { ReplyOperation } from "./reply-run-registry.js";

const compactEmbeddedAgentSessionMock = vi.fn();
const runWithModelFallbackMock = vi.fn();
const runEmbeddedAgentMock = vi.fn();
const refreshQueuedFollowupSessionMock = vi.fn();
const incrementCompactionCountMock = vi.fn();
const ensureSelectedAgentHarnessPluginMock = vi.fn();
const ensureMemoryFlushTargetFileMock = vi.fn();

function registerMemoryFlushPlanResolverForTest(resolver: MemoryFlushPlanResolver): void {
  registerMemoryCapability("memory-core", { flushPlanResolver: resolver });
}

type TestReplyOperation = ReplyOperation & {
  setPhase: ReturnType<typeof vi.fn<ReplyOperation["setPhase"]>>;
  updateSessionId: ReturnType<typeof vi.fn<ReplyOperation["updateSessionId"]>>;
};

function createReplyOperation(): TestReplyOperation {
  return {
    key: "test",
    sessionId: "session",
    abortSignal: new AbortController().signal,
    resetTriggered: false,
    phase: "queued",
    result: null,
    setPhase: vi.fn<ReplyOperation["setPhase"]>(),
    updateSessionId: vi.fn<ReplyOperation["updateSessionId"]>(),
    attachBackend: vi.fn(),
    detachBackend: vi.fn(),
    complete: vi.fn(),
    completeThen: vi.fn((afterClear: () => void) => {
      afterClear();
    }),
    fail: vi.fn(),
    abortByUser: vi.fn(),
    abortForRestart: vi.fn(),
  };
}

type RefreshQueuedFollowupSessionParams = {
  key?: string;
  previousSessionId?: string;
  nextSessionId?: string;
  nextSessionFile?: string;
};

type ModelFallbackParams = {
  provider?: string;
  model?: string;
  abortSignal?: AbortSignal;
  agentId?: string;
  sessionKey?: string;
  fallbacksOverride?: unknown[];
  resolveAgentHarnessRuntimeOverride?: (provider: string, model: string) => string | undefined;
  prepareAgentHarnessRuntime?: (params: {
    provider: string;
    model: string;
    agentHarnessRuntimeOverride?: string;
  }) => Promise<void> | void;
};

type EmbeddedAgentParams = {
  provider?: string;
  model?: string;
  authProfileId?: unknown;
  authProfileIdSource?: unknown;
  prompt?: string;
  transcriptPrompt?: string;
  memoryFlushWritePath?: string;
  silentExpected?: boolean;
  extraSystemPrompt?: string;
  bootstrapPromptWarningSignaturesSeen?: string[];
  bootstrapPromptWarningSignature?: string;
  abortSignal?: AbortSignal;
};

type CompactEmbeddedAgentSessionParams = {
  agentId?: string;
  authProfileId?: string;
  contextTokenBudget?: number;
  sessionKey?: string;
  sandboxSessionKey?: string;
  currentTokenCount?: number;
  cwd?: string;
  sessionFile?: string;
  sessionId?: string;
  trigger?: string;
};

function requireRefreshQueuedFollowupSessionCall(index = 0) {
  const call = refreshQueuedFollowupSessionMock.mock.calls[index]?.[0] as
    | RefreshQueuedFollowupSessionParams
    | undefined;
  if (!call) {
    throw new Error(`refreshQueuedFollowupSession call ${index} missing`);
  }
  return call;
}

function requireModelFallbackCall(index = 0) {
  const call = runWithModelFallbackMock.mock.calls[index]?.[0] as ModelFallbackParams | undefined;
  if (!call) {
    throw new Error(`runWithModelFallback call ${index} missing`);
  }
  return call;
}

function requireEmbeddedAgentCall(index = 0) {
  const call = runEmbeddedAgentMock.mock.calls[index]?.[0] as EmbeddedAgentParams | undefined;
  if (!call) {
    throw new Error(`runEmbeddedAgent call ${index} missing`);
  }
  return call;
}

function requireCompactEmbeddedAgentSessionCall(index = 0) {
  const call = compactEmbeddedAgentSessionMock.mock.calls[index]?.[0] as
    | CompactEmbeddedAgentSessionParams
    | undefined;
  if (!call) {
    throw new Error(`compactEmbeddedAgentSession call ${index} missing`);
  }
  return call;
}

describe("runMemoryFlushIfNeeded", () => {
  let rootDir = "";

  beforeEach(async () => {
    rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-memory-unit-"));
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 20_000,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    runWithModelFallbackMock.mockReset().mockImplementation(async ({ provider, model, run }) => ({
      result: await run(provider, model),
      provider,
      model,
      attempts: [],
    }));
    compactEmbeddedAgentSessionMock.mockReset().mockResolvedValue({
      ok: true,
      compacted: true,
      result: { tokensAfter: 42 },
    });
    runEmbeddedAgentMock.mockReset().mockResolvedValue({ payloads: [], meta: {} });
    refreshQueuedFollowupSessionMock.mockReset();
    ensureMemoryFlushTargetFileMock.mockReset().mockResolvedValue(undefined);
    ensureSelectedAgentHarnessPluginMock.mockReset().mockResolvedValue(undefined);
    incrementCompactionCountMock.mockReset().mockImplementation(async (params) => {
      const sessionKey = String(params.sessionKey ?? "");
      if (!sessionKey || !params.sessionStore?.[sessionKey]) {
        return undefined;
      }
      const previous = params.sessionStore[sessionKey] as SessionEntry;
      const nextEntry: SessionEntry = {
        ...previous,
        compactionCount: (previous.compactionCount ?? 0) + 1,
      };
      if (typeof params.newSessionId === "string" && params.newSessionId) {
        nextEntry.sessionId = params.newSessionId;
        if (typeof params.newSessionFile === "string" && params.newSessionFile) {
          nextEntry.sessionFile = params.newSessionFile;
        } else {
          const storePath = typeof params.storePath === "string" ? params.storePath : rootDir;
          nextEntry.sessionFile = path.join(
            path.dirname(storePath),
            `${params.newSessionId}.jsonl`,
          );
        }
      }
      params.sessionStore[sessionKey] = nextEntry;
      if (typeof params.storePath === "string") {
        await writeTestSessionStore(params.storePath, sessionKey, nextEntry);
      }
      return nextEntry.compactionCount;
    });
    setAgentRunnerMemoryTestDeps({
      compactEmbeddedAgentSession: compactEmbeddedAgentSessionMock as never,
      runWithModelFallback: runWithModelFallbackMock as never,
      runEmbeddedAgent: runEmbeddedAgentMock as never,
      ensureMemoryFlushTargetFile: ensureMemoryFlushTargetFileMock as never,
      refreshQueuedFollowupSession: refreshQueuedFollowupSessionMock as never,
      incrementCompactionCount: incrementCompactionCountMock as never,
      ensureSelectedAgentHarnessPlugin: ensureSelectedAgentHarnessPluginMock as never,
      registerAgentRunContext: vi.fn() as never,
      randomUUID: () => "00000000-0000-0000-0000-000000000001",
      now: () => 1_700_000_000_000,
    });
  });

  afterEach(async () => {
    setAgentRunnerMemoryTestDeps();
    clearMemoryPluginState();
    await fs.rm(rootDir, { recursive: true, force: true });
  });

  it("runs a memory flush turn, rotates after compaction, and persists metadata", async () => {
    const storePath = path.join(rootDir, "sessions.json");
    const sessionKey = "main";
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const sessionStore = { [sessionKey]: sessionEntry };
    await writeTestSessionStore(storePath, sessionKey, sessionEntry);

    runEmbeddedAgentMock.mockImplementationOnce(
      async (params: {
        onAgentEvent?: (evt: { stream: string; data: { phase: string } }) => void;
      }) => {
        params.onAgentEvent?.({ stream: "compaction", data: { phase: "end" } });
        return {
          payloads: [],
          meta: { agentMeta: { sessionId: "session-rotated" } },
        };
      },
    );

    const followupRun = createTestFollowupRun();
    const entry = await runMemoryFlushIfNeeded({
      cfg: {
        agents: {
          defaults: {
            compaction: {
              memoryFlush: {},
            },
          },
        },
      },
      followupRun,
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore,
      sessionKey,
      storePath,
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry?.sessionId).toBe("session-rotated");
    expect(followupRun.run.sessionId).toBe("session-rotated");
    expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1);
    const flushCall = requireEmbeddedAgentCall();
    expect(flushCall.prompt).toContain("Pre-compaction memory flush.");
    expect(flushCall.transcriptPrompt).toBe("");
    expect(flushCall.prompt).not.toBe(flushCall.transcriptPrompt);
    expect(flushCall.memoryFlushWritePath).toMatch(/^memory\/\d{4}-\d{2}-\d{2}\.md$/);
    expect(flushCall.silentExpected).toBe(true);
    expect(ensureMemoryFlushTargetFileMock).toHaveBeenCalledWith({
      workspaceDir: followupRun.run.workspaceDir,
      relativePath: flushCall.memoryFlushWritePath,
    });
    expect(ensureMemoryFlushTargetFileMock.mock.invocationCallOrder[0]).toBeLessThan(
      runEmbeddedAgentMock.mock.invocationCallOrder[0] ?? 0,
    );
    expect(refreshQueuedFollowupSessionMock).toHaveBeenCalledTimes(1);
    const refreshCall = requireRefreshQueuedFollowupSessionCall();
    expect(refreshCall.key).toBe(sessionKey);
    expect(refreshCall.previousSessionId).toBe("session");
    expect(refreshCall.nextSessionId).toBe("session-rotated");
    expect(refreshCall.nextSessionFile).toContain("session-rotated.jsonl");

    const persisted = JSON.parse(await fs.readFile(storePath, "utf8")) as {
      main: SessionEntry;
    };
    expect(persisted.main.sessionId).toBe("session-rotated");
    expect(persisted.main.compactionCount).toBe(2);
    expect(persisted.main.memoryFlushCompactionCount).toBe(1);
    expect(persisted.main.memoryFlushAt).toBe(1_700_000_000_000);
  });

  it("reports memory-flush error payloads for visible delivery", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const visibleErrorPayloads: Array<{ text?: string; isError?: boolean }> = [];
    runEmbeddedAgentMock.mockResolvedValueOnce({
      payloads: [
        { text: "normal silent maintenance reply" },
        {
          text: "⚠️ write failed: Memory flush writes are restricted to memory/2023-11-14.md; use that path only.",
          isError: true,
        },
      ],
      meta: {},
    });

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun(),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
      onVisibleErrorPayloads: (payloads) => {
        visibleErrorPayloads.push(...payloads);
      },
    });

    expect(visibleErrorPayloads).toEqual([
      {
        text: "⚠️ write failed: Memory flush writes are restricted to memory/2023-11-14.md; use that path only.",
        isError: true,
      },
    ]);
  });

  it("reports restricted memory-flush write failures for visible delivery", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const visibleErrorPayloads: Array<{ text?: string; isError?: boolean }> = [];
    runWithModelFallbackMock.mockRejectedValueOnce(
      new Error(
        "write failed: Memory flush writes are restricted to memory/2023-11-14.md; use that path only.",
      ),
    );

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun(),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
      onVisibleErrorPayloads: (payloads) => {
        visibleErrorPayloads.push(...payloads);
      },
    });

    expect(visibleErrorPayloads).toEqual([
      {
        text: "⚠️ write failed: Memory flush writes are restricted to memory/2023-11-14.md; use that path only.",
        isError: true,
      },
    ]);
  });

  it("surfaces generic non-abort memory-flush failures so cron meta.error is populated (regression: #80755)", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const visibleErrorPayloads: Array<{ text?: string; isError?: boolean }> = [];
    runWithModelFallbackMock.mockRejectedValueOnce(
      new Error("provider timed out after 60s while flushing memory"),
    );

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun(),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-7",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
      onVisibleErrorPayloads: (payloads) => {
        visibleErrorPayloads.push(...payloads);
      },
    });

    expect(visibleErrorPayloads).toEqual([
      {
        text: "⚠️ provider timed out after 60s while flushing memory",
        isError: true,
      },
    ]);
  });

  it("redacts and caps generic visible memory-flush failures before delivery", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const visibleErrorPayloads: Array<{ text?: string; isError?: boolean }> = [];
    const token = "sk-abcdefghijklmnopqrstuv";
    runWithModelFallbackMock.mockRejectedValueOnce(
      new Error(`provider failed with Authorization: Bearer ${token} ${"x".repeat(800)}`),
    );

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun(),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-7",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
      onVisibleErrorPayloads: (payloads) => {
        visibleErrorPayloads.push(...payloads);
      },
    });

    const [payload] = visibleErrorPayloads;
    expect(payload?.isError).toBe(true);
    expect(payload?.text).toMatch(/^⚠️ provider failed with Authorization: Bearer /);
    expect(payload?.text).not.toContain(token);
    expect(payload?.text?.length).toBeLessThanOrEqual(600);
    expect(payload?.text?.endsWith("…")).toBe(true);
  });

  it("does not surface user-abort errors as visible payloads (regression: #80755)", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };
    const visibleErrorPayloads: Array<{ text?: string; isError?: boolean }> = [];
    const abortErr = new Error("operation aborted by user");
    abortErr.name = "AbortError";
    runWithModelFallbackMock.mockRejectedValueOnce(abortErr);

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun(),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-7",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
      onVisibleErrorPayloads: (payloads) => {
        visibleErrorPayloads.push(...payloads);
      },
    });

    expect(visibleErrorPayloads).toEqual([]);
  });

  it("runs memory flush on the configured maintenance model without active fallbacks", async () => {
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 20_000,
      model: "ollama/qwen3:8b",
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };

    const replyOperation = createReplyOperation();
    await runMemoryFlushIfNeeded({
      cfg: {
        agents: {
          defaults: {
            model: {
              primary: "anthropic/claude",
              fallbacks: ["openai/gpt-5.4"],
            },
            compaction: {
              memoryFlush: {
                model: "ollama/qwen3:8b",
              },
            },
          },
        },
      },
      followupRun: createTestFollowupRun({ provider: "anthropic", model: "claude" }),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation,
    });

    expect(runWithModelFallbackMock).toHaveBeenCalledTimes(1);
    const fallbackCall = requireModelFallbackCall();
    expect(fallbackCall.provider).toBe("ollama");
    expect(fallbackCall.model).toBe("qwen3:8b");
    expect(fallbackCall.abortSignal).toBe(replyOperation.abortSignal);
    expect(fallbackCall.fallbacksOverride).toEqual([]);
    expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1);
    const agentCall = requireEmbeddedAgentCall();
    expect(agentCall.provider).toBe("ollama");
    expect(agentCall.model).toBe("qwen3:8b");
    expect(agentCall.abortSignal).toBe(replyOperation.abortSignal);
    expect(agentCall.authProfileId).toBeUndefined();
    expect(agentCall.authProfileIdSource).toBeUndefined();
  });

  it("loads the selected harness before memory-flush fallback preflight", async () => {
    const cfg = {
      agents: {
        defaults: {
          compaction: {
            memoryFlush: {},
          },
        },
      },
    };
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
      agentRuntimeOverride: "codex",
    };
    const runtimePolicySessionKey = "agent:main:telegram:default:direct:12345";

    await runMemoryFlushIfNeeded({
      cfg,
      followupRun: createTestFollowupRun({
        agentId: "main",
        sessionKey: "main",
        runtimePolicySessionKey,
        workspaceDir: "/workspace",
        provider: "openai",
        model: "gpt-5.4",
      }),
      sessionCtx: { Provider: "telegram" } as unknown as TemplateContext,
      defaultModel: "openai/gpt-5.4",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      runtimePolicySessionKey,
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const fallbackCall = requireModelFallbackCall();
    expect(fallbackCall.agentId).toBe("main");
    expect(fallbackCall.sessionKey).toBe(runtimePolicySessionKey);
    expect(fallbackCall.resolveAgentHarnessRuntimeOverride?.("openai", "gpt-5.4")).toBe("codex");

    await fallbackCall.prepareAgentHarnessRuntime?.({
      provider: "openai",
      model: "gpt-5.4",
      agentHarnessRuntimeOverride: "codex",
    });

    expect(ensureSelectedAgentHarnessPluginMock).toHaveBeenCalledWith({
      config: cfg,
      provider: "openai",
      modelId: "gpt-5.4",
      agentId: "main",
      sessionKey: runtimePolicySessionKey,
      agentHarnessRuntimeOverride: "codex",
      workspaceDir: "/workspace",
    });
  });

  it("ignores stale runtime pins before memory-flush fallback preflight", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
      agentRuntimeOverride: "unsupported-runtime",
    };

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        provider: "openai",
        model: "gpt-5.4",
      }),
      sessionCtx: { Provider: "telegram" } as unknown as TemplateContext,
      defaultModel: "openai/gpt-5.4",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(
      requireModelFallbackCall().resolveAgentHarnessRuntimeOverride?.("openai", "gpt-5.4"),
    ).toBeUndefined();
  });

  it("skips memory flush for CLI providers", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };

    const entry = await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { cliBackends: { "codex-cli": { command: "codex" } } } } },
      followupRun: createTestFollowupRun({ provider: "codex-cli" }),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "codex-cli/gpt-5.5",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(runEmbeddedAgentMock).not.toHaveBeenCalled();
  });

  it("uses runtime policy session key when checking memory-flush sandbox writability", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
    };

    const entry = await runMemoryFlushIfNeeded({
      cfg: {
        agents: {
          defaults: {
            sandbox: {
              mode: "non-main",
              scope: "agent",
              workspaceAccess: "ro",
            },
            compaction: {
              memoryFlush: {},
            },
          },
        },
      },
      followupRun: createTestFollowupRun({
        sessionKey: "agent:main:main",
        runtimePolicySessionKey: "agent:main:telegram:default:direct:12345",
      }),
      sessionCtx: { Provider: "telegram" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { "agent:main:main": sessionEntry },
      sessionKey: "agent:main:main",
      runtimePolicySessionKey: "agent:main:telegram:default:direct:12345",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(runEmbeddedAgentMock).not.toHaveBeenCalled();
  });

  it("continues when preflight compaction reports the session is already under target", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: true,
      compacted: false,
      reason: "already under target",
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 120,
      totalTokensFresh: true,
    };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "agent:main:main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100,
      sessionEntry,
      sessionStore: { "agent:main:main": sessionEntry },
      sessionKey: "agent:main:main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    expect(requireCompactEmbeddedAgentSessionCall()).toMatchObject({
      trigger: "budget",
      deferOwningContextEngineCompaction: false,
      contextTokenBudget: 100,
    });
    expect(incrementCompactionCountMock).not.toHaveBeenCalled();
  });

  it("fails when required preflight context-engine compaction is deferred to background maintenance", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: true,
      compacted: false,
      reason: "deferred to background context-engine maintenance",
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 120,
      totalTokensFresh: true,
    };

    await expect(
      runPreflightCompactionIfNeeded({
        cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
        followupRun: createTestFollowupRun({
          sessionId: "session",
          sessionFile,
          sessionKey: "agent:main:main",
        }),
        defaultModel: "anthropic/claude-opus-4-6",
        agentCfgContextTokens: 100,
        sessionEntry,
        sessionStore: { "agent:main:main": sessionEntry },
        sessionKey: "agent:main:main",
        storePath: path.join(rootDir, "sessions.json"),
        isHeartbeat: false,
        replyOperation: createReplyOperation(),
      }),
    ).rejects.toThrow(
      "Preflight compaction required but failed: deferred to background context-engine maintenance",
    );

    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    expect(incrementCompactionCountMock).not.toHaveBeenCalled();
  });

  it("passes runtime policy session key to preflight compaction sandbox resolution", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 120,
      totalTokensFresh: true,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "agent:main:main",
        cwd: "/tmp/task-repo",
        runtimePolicySessionKey: "agent:main:telegram:default:direct:12345",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100,
      sessionEntry,
      sessionStore: { "agent:main:main": sessionEntry },
      sessionKey: "agent:main:main",
      runtimePolicySessionKey: "agent:main:telegram:default:direct:12345",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.sessionKey).toBe("agent:main:main");
    expect(compactCall.cwd).toBe("/tmp/task-repo");
    expect(compactCall.sandboxSessionKey).toBe("agent:main:telegram:default:direct:12345");
  });

  it.each([
    ["stale_thread_binding", "thread not found: <codex-thread-id>"],
    ["missing_thread_binding", "no thread binding for session"],
  ])(
    "continues after recoverable native harness %s failure during preflight compaction",
    async (failureReason, reason) => {
      const sessionFile = path.join(rootDir, "session.jsonl");
      await fs.writeFile(
        sessionFile,
        `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
        "utf8",
      );
      registerMemoryFlushPlanResolverForTest(() => ({
        softThresholdTokens: 1,
        forceFlushTranscriptBytes: 1_000_000_000,
        reserveTokensFloor: 0,
        prompt: "Pre-compaction memory flush.\nNO_REPLY",
        systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
        relativePath: "memory/2023-11-14.md",
      }));
      compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
        ok: false,
        compacted: false,
        reason,
        failure: { reason: failureReason },
      });
      const sessionEntry: SessionEntry = {
        sessionId: "session",
        sessionFile,
        updatedAt: Date.now(),
        totalTokens: 120,
        totalTokensFresh: true,
      };
      const sessionStore = { "agent:main:telegram:group:redacted": sessionEntry };

      const entry = await runPreflightCompactionIfNeeded({
        cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
        followupRun: createTestFollowupRun({
          sessionId: "session",
          sessionFile,
          sessionKey: "agent:main:telegram:group:redacted",
        }),
        defaultModel: "anthropic/claude-opus-4-6",
        agentCfgContextTokens: 100,
        sessionEntry,
        sessionStore,
        sessionKey: "agent:main:telegram:group:redacted",
        storePath: path.join(rootDir, "sessions.json"),
        isHeartbeat: false,
        replyOperation: createReplyOperation(),
      });

      expect(entry).toBe(sessionEntry);
      expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
      expect(incrementCompactionCountMock).not.toHaveBeenCalled();
    },
  );

  it("continues after an unstructured thread-not-found preflight compaction failure", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: false,
      compacted: false,
      reason: "thread not found: <codex-thread-id>",
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 120,
      totalTokensFresh: true,
    };
    const sessionStore = { "agent:main:telegram:group:redacted": sessionEntry };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "agent:main:telegram:group:redacted",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100,
      sessionEntry,
      sessionStore,
      sessionKey: "agent:main:telegram:group:redacted",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    expect(incrementCompactionCountMock).not.toHaveBeenCalled();
  });

  it("still fails preflight compaction for non-binding native harness failures", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: false,
      compacted: false,
      reason: "auth profile mismatch",
      failure: { reason: "auth_profile_mismatch" },
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 120,
      totalTokensFresh: true,
    };
    const sessionStore = { "agent:main:telegram:group:redacted": sessionEntry };

    await expect(
      runPreflightCompactionIfNeeded({
        cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
        followupRun: createTestFollowupRun({
          sessionId: "session",
          sessionFile,
          sessionKey: "agent:main:telegram:group:redacted",
        }),
        defaultModel: "anthropic/claude-opus-4-6",
        agentCfgContextTokens: 100,
        sessionEntry,
        sessionStore,
        sessionKey: "agent:main:telegram:group:redacted",
        storePath: path.join(rootDir, "sessions.json"),
        isHeartbeat: false,
        replyOperation: createReplyOperation(),
      }),
    ).rejects.toThrow("Preflight compaction required but failed: auth profile mismatch");

    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    expect(incrementCompactionCountMock).not.toHaveBeenCalled();
  });

  it("passes resolved context budget and auth profile to preflight compaction", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 245_000,
      totalTokensFresh: true,
      compactionCount: 0,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        authProfileId: "anthropic:claude@martian.engineering",
        provider: "anthropic",
        model: "claude-opus-4-6",
        sessionKey: "agent:main:main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 258_000,
      sessionEntry,
      sessionStore: { "agent:main:main": sessionEntry },
      sessionKey: "agent:main:main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.authProfileId).toBe("anthropic:claude@martian.engineering");
    expect(compactCall.contextTokenBudget).toBe(258_000);
  });
  it("updates the active preflight run after transcript rotation", async () => {
    const sessionFile = path.join(rootDir, "session.jsonl");
    const successorFile = path.join(rootDir, "session-rotated.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 1,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: true,
      compacted: true,
      result: {
        tokensAfter: 42,
        sessionId: "session-rotated",
        sessionFile: successorFile,
      },
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };
    const sessionStore = { "agent:main:main": sessionEntry };
    const followupRun = createTestFollowupRun({
      sessionId: "session",
      sessionFile,
      sessionKey: "agent:main:main",
    });
    const replyOperation = createReplyOperation();

    const entry = await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100,
      sessionEntry,
      sessionStore,
      sessionKey: "agent:main:main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation,
    });

    expect(entry?.sessionId).toBe("session-rotated");
    expect(entry?.sessionFile).toBe(successorFile);
    expect(followupRun.run.sessionId).toBe("session-rotated");
    expect(followupRun.run.sessionFile).toBe(successorFile);
    expect(replyOperation.updateSessionId).toHaveBeenCalledWith("session-rotated");
    expect(refreshQueuedFollowupSessionMock).toHaveBeenCalledWith({
      key: "agent:main:main",
      previousSessionId: "session",
      nextSessionId: "session-rotated",
      nextSessionFile: successorFile,
    });
  });

  it("includes recent output tokens when deciding preflight compaction", async () => {
    const sessionFile = path.join(rootDir, "session-usage.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({
        message: {
          role: "assistant",
          content: "large answer",
          usage: { input: 90_000, output: 10_000 },
        },
      })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.currentTokenCount).toBeGreaterThanOrEqual(100_000);
  });

  it("reuses the transcript tail scan stat when memory flush needs usage and byte size", async () => {
    const sessionFile = path.join(rootDir, "memory-flush-usage-and-size.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({
        message: {
          role: "assistant",
          content: "large answer",
          usage: { input: 80_000, output: 4_000 },
        },
      })}\n`,
      "utf8",
    );
    const originalStat = fsCore.promises.stat.bind(fsCore.promises);
    const statSpy = vi
      .spyOn(fsCore.promises, "stat")
      .mockImplementation(async (target, options) => originalStat(target, options));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };

    let directTranscriptStats: unknown[] = [];
    try {
      await runMemoryFlushIfNeeded({
        cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
        followupRun: createTestFollowupRun({
          sessionId: "session",
          sessionFile,
          sessionKey: "main",
        }),
        sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
        defaultModel: "anthropic/claude-opus-4-6",
        agentCfgContextTokens: 100_000,
        resolvedVerboseLevel: "off",
        sessionEntry,
        sessionStore: { main: sessionEntry },
        sessionKey: "main",
        storePath: path.join(rootDir, "sessions.json"),
        isHeartbeat: false,
        replyOperation: createReplyOperation(),
      });
      directTranscriptStats = statSpy.mock.calls.filter(
        ([target]) => String(target) === sessionFile,
      );
    } finally {
      statSpy.mockRestore();
    }

    expect(directTranscriptStats).toEqual([]);
    expect(runEmbeddedAgentMock).toHaveBeenCalledTimes(1);
  });

  it("continues when preflight compaction returns a successful no-op", async () => {
    compactEmbeddedAgentSessionMock.mockResolvedValueOnce({
      ok: true,
      compacted: false,
      reason: "plugin already stored this turn",
    });
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 180_499,
      totalTokensFresh: true,
      compactionCount: 0,
    };
    const sessionStore = { main: sessionEntry };
    const replyOperation = createReplyOperation();

    const entry = await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 200_000,
      sessionEntry,
      sessionStore,
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation,
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.contextTokenBudget).toBe(200_000);
    expect(replyOperation.setPhase).toHaveBeenCalledWith("preflight_compacting");
    expect(replyOperation.updateSessionId).not.toHaveBeenCalled();
    expect(incrementCompactionCountMock).not.toHaveBeenCalled();
    expect(refreshQueuedFollowupSessionMock).not.toHaveBeenCalled();
  });

  it("skips OpenClaw preflight compaction for persisted Codex runtime sessions", async () => {
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 347_000,
      totalTokensFresh: false,
      agentHarnessId: "codex",
    };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: {
        models: {
          providers: {
            openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] },
            "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
          },
        },
        agents: { defaults: { compaction: { memoryFlush: {} } } },
      } as never,
      followupRun: createTestFollowupRun({
        provider: "openai",
        model: "gpt-5.5",
        sessionId: "session",
        sessionKey: "main",
      }),
      defaultModel: "gpt-5.5",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
  });

  it("skips fresh persisted token totals for persisted Codex runtime sessions", async () => {
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 347_000,
      totalTokensFresh: true,
      agentHarnessId: "codex",
    };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: {
        models: {
          providers: {
            openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] },
            "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
          },
        },
        agents: { defaults: { compaction: { memoryFlush: {} } } },
      } as never,
      followupRun: createTestFollowupRun({
        provider: "openai",
        model: "gpt-5.5",
        sessionId: "session",
        sessionKey: "main",
      }),
      defaultModel: "gpt-5.5",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
  });

  it("keeps the OpenAI API context window for persisted OpenClaw runtime overrides", async () => {
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 347_000,
      totalTokensFresh: false,
      agentRuntimeOverride: "openclaw",
    };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: {
        models: {
          providers: {
            openai: { models: [{ id: "gpt-5.5", contextWindow: 1_000_000 }] },
            "openai-codex": { models: [{ id: "gpt-5.5", contextWindow: 350_000 }] },
          },
        },
        agents: { defaults: { compaction: { memoryFlush: {} } } },
      } as never,
      followupRun: createTestFollowupRun({
        provider: "openai",
        model: "gpt-5.5",
        sessionId: "session",
        sessionKey: "main",
      }),
      defaultModel: "gpt-5.5",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
  });

  it("uses the active run sessionFile when the session entry has no transcript path", async () => {
    const sessionFile = path.join(rootDir, "active-run-session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({
        message: {
          role: "assistant",
          content: "large answer",
          usage: { input: 90_000, output: 8_000 },
        },
      })}\n`,
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(compactEmbeddedAgentSessionMock).toHaveBeenCalledTimes(1);
    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.sessionId).toBe("session");
    expect(compactCall.sessionFile).toContain("active-run-session.jsonl");
  });

  it("keeps preflight compaction conservative for content appended after latest usage", async () => {
    const sessionFile = path.join(rootDir, "post-usage-tail-session.jsonl");
    await fs.writeFile(
      sessionFile,
      [
        JSON.stringify({
          message: {
            role: "assistant",
            content: "small answer",
            usage: { input: 40_000, output: 2_000 },
          },
        }),
        JSON.stringify({
          message: {
            role: "tool",
            content: `large interrupted tool output ${"x".repeat(450_000)}`,
          },
        }),
      ].join("\n"),
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.currentTokenCount).toBeGreaterThan(100_000);
  });

  it("combines latest usage with post-usage tail pressure for preflight compaction", async () => {
    const sessionFile = path.join(rootDir, "combined-tail-pressure-session.jsonl");
    await fs.writeFile(
      sessionFile,
      [
        JSON.stringify({
          message: {
            role: "assistant",
            content: "small answer",
            usage: { input: 86_000, output: 2_000 },
          },
        }),
        JSON.stringify({
          message: {
            role: "tool",
            content: `moderate interrupted tool output ${"x".repeat(36_000)}`,
          },
        }),
      ].join("\n"),
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };

    await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.currentTokenCount).toBeGreaterThanOrEqual(96_000);
  });

  it("does not count bytes from a large latest usage record as post-usage tail pressure", async () => {
    const sessionFile = path.join(rootDir, "large-usage-record-session.jsonl");
    await fs.writeFile(
      sessionFile,
      [
        JSON.stringify({
          type: "session",
          id: "session",
        }),
        JSON.stringify({
          message: {
            role: "assistant",
            content: `large answer ${"x".repeat(300_000)}`,
            usage: { input: 40_000, output: 2_000 },
          },
        }),
      ].join("\n") + "\n",
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };
    const entry = await runPreflightCompactionIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
  });

  it("does not treat raw transcript metadata bytes as token pressure", async () => {
    const sessionFile = path.join(rootDir, "metadata-heavy-session.jsonl");
    await fs.writeFile(
      sessionFile,
      [
        JSON.stringify({
          type: "session",
          id: "session",
        }),
        JSON.stringify({
          type: "custom",
          payload: "x".repeat(450_000),
        }),
        JSON.stringify({
          message: {
            role: "assistant",
            content: "small answer",
            usage: { input: 40_000, output: 2_000 },
          },
        }),
      ].join("\n") + "\n",
      "utf8",
    );
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 0,
      prompt: "Pre-compaction memory flush.\nNO_REPLY",
      systemPrompt: "Write memory to memory/YYYY-MM-DD.md.",
      relativePath: "memory/2023-11-14.md",
    }));
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokensFresh: false,
    };
    const originalStat = fsCore.promises.stat.bind(fsCore.promises);
    const statSpy = vi
      .spyOn(fsCore.promises, "stat")
      .mockImplementation(async (target, options) => originalStat(target, options));

    let entry: SessionEntry | undefined;
    let directTranscriptStats: unknown[] = [];
    try {
      entry = await runPreflightCompactionIfNeeded({
        cfg: {
          agents: {
            defaults: {
              compaction: {
                memoryFlush: {},
                truncateAfterCompaction: true,
                maxActiveTranscriptBytes: "10mb",
              },
            },
          },
        },
        followupRun: createTestFollowupRun({
          sessionId: "session",
          sessionFile,
          sessionKey: "main",
        }),
        defaultModel: "anthropic/claude-opus-4-6",
        agentCfgContextTokens: 100_000,
        sessionEntry,
        sessionStore: { main: sessionEntry },
        sessionKey: "main",
        storePath: path.join(rootDir, "sessions.json"),
        isHeartbeat: false,
        replyOperation: createReplyOperation(),
      });
      directTranscriptStats = statSpy.mock.calls.filter(
        ([target]) => String(target) === sessionFile,
      );
    } finally {
      statSpy.mockRestore();
    }

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
    expect(directTranscriptStats).toEqual([]);
  });

  it("triggers preflight compaction when the active transcript exceeds the configured byte threshold", async () => {
    const sessionFile = path.join(rootDir, "large-session.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(256) } })}\n`,
      "utf8",
    );
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 10,
      totalTokensFresh: true,
      compactionCount: 0,
    };
    const sessionStore = { main: sessionEntry };
    const replyOperation = createReplyOperation();

    const entry = await runPreflightCompactionIfNeeded({
      cfg: {
        agents: {
          defaults: {
            compaction: {
              truncateAfterCompaction: true,
              maxActiveTranscriptBytes: "10b",
            },
          },
        },
      },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore,
      sessionKey: "main",
      storePath: path.join(rootDir, "sessions.json"),
      isHeartbeat: false,
      replyOperation,
    });

    expect(entry?.compactionCount).toBe(1);
    expect(replyOperation.setPhase).toHaveBeenCalledWith("preflight_compacting");
    const compactCall = requireCompactEmbeddedAgentSessionCall();
    expect(compactCall.sessionId).toBe("session");
    expect(compactCall.trigger).toBe("budget");
    expect(compactCall.currentTokenCount).toBe(10);
    expect(compactCall.sessionFile).toContain("large-session.jsonl");
  });

  it("keeps the active transcript byte threshold inactive unless transcript rotation is enabled", async () => {
    const sessionFile = path.join(rootDir, "large-session-no-rotation.jsonl");
    await fs.writeFile(
      sessionFile,
      `${JSON.stringify({ message: { role: "user", content: "x".repeat(256) } })}\n`,
      "utf8",
    );
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      sessionFile,
      updatedAt: Date.now(),
      totalTokens: 10,
      totalTokensFresh: true,
      compactionCount: 0,
    };

    const entry = await runPreflightCompactionIfNeeded({
      cfg: {
        agents: {
          defaults: {
            compaction: {
              maxActiveTranscriptBytes: "10b",
            },
          },
        },
      },
      followupRun: createTestFollowupRun({
        sessionId: "session",
        sessionFile,
        sessionKey: "main",
      }),
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    expect(entry).toBe(sessionEntry);
    expect(compactEmbeddedAgentSessionMock).not.toHaveBeenCalled();
  });

  it("uses configured prompts and stored bootstrap warning signatures", async () => {
    const sessionEntry: SessionEntry = {
      sessionId: "session",
      updatedAt: Date.now(),
      totalTokens: 80_000,
      compactionCount: 1,
      systemPromptReport: {
        source: "run",
        generatedAt: Date.now(),
        systemPrompt: { chars: 1, projectContextChars: 0, nonProjectContextChars: 1 },
        injectedWorkspaceFiles: [],
        skills: { promptChars: 0, entries: [] },
        tools: { listChars: 0, schemaChars: 0, entries: [] },
        bootstrapTruncation: {
          warningMode: "once",
          warningShown: true,
          promptWarningSignature: "sig-b",
          warningSignaturesSeen: ["sig-a", "sig-b"],
          truncatedFiles: 1,
          nearLimitFiles: 0,
          totalNearLimit: false,
        },
      },
    };
    registerMemoryFlushPlanResolverForTest(() => ({
      softThresholdTokens: 4_000,
      forceFlushTranscriptBytes: 1_000_000_000,
      reserveTokensFloor: 20_000,
      prompt: "Write notes.\nNO_REPLY to memory/2023-11-14.md and MEMORY.md",
      systemPrompt: "Flush memory now. NO_REPLY memory/YYYY-MM-DD.md MEMORY.md",
      relativePath: "memory/2023-11-14.md",
    }));

    await runMemoryFlushIfNeeded({
      cfg: { agents: { defaults: { compaction: { memoryFlush: {} } } } },
      followupRun: createTestFollowupRun({ extraSystemPrompt: "extra system" }),
      sessionCtx: { Provider: "whatsapp" } as unknown as TemplateContext,
      defaultModel: "anthropic/claude-opus-4-6",
      agentCfgContextTokens: 100_000,
      resolvedVerboseLevel: "off",
      sessionEntry,
      sessionStore: { main: sessionEntry },
      sessionKey: "main",
      isHeartbeat: false,
      replyOperation: createReplyOperation(),
    });

    const flushCall = requireEmbeddedAgentCall();
    expect(flushCall.prompt).toContain("Write notes.");
    expect(flushCall.prompt).toContain("NO_REPLY");
    expect(flushCall.prompt).toContain("MEMORY.md");
    expect(flushCall.transcriptPrompt).toBe("");
    expect(flushCall.extraSystemPrompt).toContain("extra system");
    expect(flushCall.extraSystemPrompt).toContain("Flush memory now.");
    expect(flushCall.memoryFlushWritePath).toBe("memory/2023-11-14.md");
    expect(flushCall.silentExpected).toBe(true);
    expect(flushCall.bootstrapPromptWarningSignaturesSeen).toEqual(["sig-a", "sig-b"]);
    expect(flushCall.bootstrapPromptWarningSignature).toBe("sig-b");
  });
});
