billion-context-dsh 0.2.19 → 0.2.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +6 -6
- package/README.md +6 -6
- package/dist/host-tokens.d.ts +24 -5
- package/dist/index.d.ts +25 -1
- package/dist/index.js +132 -19
- package/dist/index.js.map +1 -1
- package/dist/tool-pairing.d.ts +36 -0
- package/dist/window.d.ts +43 -5
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../node_modules/acp-kernel/src/refs.ts","../node_modules/acp-kernel/src/state.ts","../node_modules/acp-kernel/src/prune.ts","../node_modules/acp-kernel/src/sync.ts","../node_modules/acp-kernel/src/tokenize.ts","../node_modules/acp-kernel/src/config.ts","../node_modules/acp-kernel/src/boundaries.ts","../node_modules/acp-kernel/src/truncate-tools.ts","../node_modules/acp-kernel/src/hide-consumed.ts","../node_modules/acp-kernel/src/filter/registry.ts","../node_modules/acp-kernel/src/filter/apply.ts","../node_modules/acp-kernel/src/render-refs.ts","../node_modules/acp-kernel/src/protected.ts","../node_modules/acp-kernel/src/tool-pairs.ts","../node_modules/acp-kernel/src/reasoning-pairs.ts","../node_modules/acp-kernel/src/recommend.ts","../node_modules/acp-kernel/src/pipeline.ts","../node_modules/acp-kernel/src/compress.ts","../node_modules/acp-kernel/src/compression-rules.ts","../node_modules/acp-kernel/src/prompts.ts","../node_modules/acp-kernel/src/nudge-text.ts","../node_modules/acp-kernel/src/decompress.ts","../node_modules/acp-kernel/src/report.ts","../node_modules/acp-kernel/src/rebuild.ts","../node_modules/acp-kernel/src/transform-channel.ts","../node_modules/acp-kernel/src/search/stemmer.ts","../node_modules/acp-kernel/src/search/tokenizer.ts","../node_modules/acp-kernel/src/search/doc-cache.ts","../node_modules/acp-kernel/src/search/algorithms/substring.ts","../node_modules/acp-kernel/src/search/algorithms/bm25.ts","../node_modules/acp-kernel/src/search/algorithms/fuzzy.ts","../node_modules/acp-kernel/src/search/algorithms/hybrid.ts","../node_modules/acp-kernel/src/search/registry.ts","../node_modules/acp-kernel/src/search/types.ts","../node_modules/acp-kernel/src/search/index.ts","../src/region.ts","../src/session-events.ts","../src/messages.ts","../src/host-tokens.ts","../src/state.ts","../src/tools.ts","../src/config.ts","../src/nudge.ts","../src/prompts.ts","../src/window.ts","../src/commands.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nimport { renderSystemPrompt, resolvePrompts, type AcpPrompts, type ResolvedPrompts } from './prompts.ts'\nimport { DEFAULT_CONTEXT_WINDOW, detectContextWindow, projectedContextWindow, type AcpWindow } from './window.ts'\nimport { deferCompressPairHide, stripOrphanedSurfaceToolMessages } from './region.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport {\n DEFAULT_PROMPTS,\n DEFAULT_RESOLVED,\n renderSystemPrompt,\n renderTemplate,\n resolvePrompts,\n type AcpPrompts,\n type NudgePrompts,\n type PromptInput,\n type PromptOverride,\n type RangeTablePrompts,\n type ResolvedPrompts,\n type ToolPrompts,\n} from './prompts.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, resolveTokenCount, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n DEFAULT_CONTEXT_WINDOW,\n detectContextWindow,\n projectedContextWindow,\n windowSourceLabel,\n type AcpWindow,\n} from './window.ts'\nexport {\n AlreadyCompressedRangeError,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n blockRegistry,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n summarySeqOfKernelBlock,\n expandShadowedSeqs,\n hideCompressToolPair,\n stripOrphanedSurfaceToolMessages,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n type ResolvedSurfaceRange,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /**\n * The context window used for pressure decisions, in tokens. When omitted,\n * `autoModelContextLimit` (default true) resolves it automatically: the live\n * host session projection (`contextPressure.contextWindow`) is preferred,\n * then the model's real window is probed via\n * `agent.ctx.llm.resolveModelInfo(provider, model)`; an explicit value\n * always wins and disables both.\n */\n readonly modelContextLimit?: number\n /** Auto-resolve the real context window: host session projection first, then the LLM runtime probe. Default true. */\n readonly autoModelContextLimit: boolean\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /**\n * Nudge window upper bound — over-limit guarantee line: above this the\n * kernel injects a nudge regardless of growth or cadence. Engine default\n * 0.70 (deliberately BELOW the kernel/billion-context-pi default 0.75 and\n * the host compaction-basic auto-compaction line 0.80, so the forced nudge\n * always fires first); an explicit value wins over this default — a\n * same-name key in `coreOverrides.nudge` wins over both (it merges last).\n */\n readonly nudgeMaxContextLimitPct?: number\n /**\n * Emergency nudge threshold (bypasses the per-turn dedup). Engine default\n * 0.85 (down from the kernel/billion-context-pi default 0.95: 95% leaves\n * the model no room to act before the API rejects, and the host's 80%\n * compaction-basic line shadows it in standard/code/cordis modes).\n */\n readonly nudgeEmergencyThresholdPct?: number\n /**\n * Any other acp-kernel Config override (billion-context-pi's `coreOverrides`\n * escape hatch). Merge order per section: kernel defaults → the engine pct\n * knobs above → these keys land LAST, so a same-name key here wins.\n */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /**\n * Custom token-count function for the kernel's internal estimation.\n * Defaults to the kernel's `defaultCountTokens` (CJK: 1 char = 1 token,\n * other: 4 chars = 1 token — aligns with billion-context-pi).\n * Can be overridden for provider-specific tokenization, e.g. DeepSeek's\n * official coefficient: 1 CJK char ≈ 0.6 tokens, 1 other char ≈ 0.3 tokens.\n * Only affects the kernel's internal estimation (compressible range sizing,\n * nudge text, growth branch pending); the `projectedTokens` reading from\n * `sessionProjections` (used for nudge pressure decisions and acp_status)\n * is provider-anchored and unaffected by this function.\n */\n readonly countTokens?: (text: string) => number\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n /** Per-stage prompt template overrides (nudge / range table / system prompt / tool descriptions). See docs/configurable-prompts-design.md. */\n readonly prompts?: AcpPrompts\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n autoModelContextLimit: true,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n // Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the\n // kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before\n // the API rejects, and the host's compaction-basic line (thresholdRatio\n // 0.80) shadows it in standard/code/cordis modes; 0.70 keeps the forced\n // over-limit nudge ahead of that 80% line. Explicit values always win\n // against these defaults — `coreOverrides` merges last and beats them on\n // same-name keys.\n nudgeMaxContextLimitPct: 0.7,\n nudgeEmergencyThresholdPct: 0.85,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n /** Resolved prompt templates (validated at construction — fail-fast on template typos). */\n readonly prompts: ResolvedPrompts\n /**\n * The environment wired into tools / command / nudge. Exposed so tests (and\n * introspection) can assert the forwarding actually happened: the config\n * chain user config → this.config → env → kernelConfigFor is all OPTIONAL\n * fields, so a dropped forwarding line fails typecheck silently and would\n * revive lost-config bugs with every unit test green.\n */\n readonly env: ToolEnvironment\n\n private readonly lastNudgeTurn = new Map<string, number>()\n /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */\n private readonly compressCallIdsToHide = new Set<string>()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map<string, AcpWindow>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n // Resolve + validate prompt templates BEFORE building env: a template typo\n // must fail engine construction, never silently leak into model context.\n this.prompts = resolvePrompts(config.prompts)\n const ports = this.config.countTokens !== undefined ? { countTokens: this.config.countTokens } : {}\n this.kernel = createCore(ports)\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n // Initial value before any probe; windowFor() replaces it per pre-step.\n modelContextLimit: this.config.modelContextLimit ?? DEFAULT_CONTEXT_WINDOW,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n windowFor: (agent) => this.windowFor(agent),\n prompts: this.prompts,\n compressCallIdsToHide: this.compressCallIdsToHide,\n }\n this.env = env\n\n // Tools and commands may not be registered yet on cold start: cordis\n // starts unrelated composition rows concurrently, so the first\n // `ctx.get('tools')` can legitimately be undefined even though the row\n // ships later in the file. HMR-style reloads always see them (already\n // present), but a fresh process races — the tools silently vanished on\n // restart. Register eagerly, then re-attempt when the service appears\n // (`internal/service`) or the app finishes booting (`ready`); guard so a\n // late callback never double-registers.\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n } else {\n let done = false\n const registerTools = (): void => {\n if (done) return\n const registry = ctx.get('tools')\n if (registry === undefined) return\n done = true\n for (const tool of makeTools(env)) registry.register(tool)\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'tools') registerTools()\n })\n }\n const commands = ctx.get('commands')\n if (commands !== undefined) {\n commands.register(acpCommand(env))\n } else {\n let done = false\n const registerCommand = (): void => {\n if (done) return\n const registry = ctx.get('commands')\n if (registry === undefined) return\n done = true\n registry.register(acpCommand(env))\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'commands') registerCommand()\n })\n }\n // After a successful compress tool result is appended, hide its\n // call/result pair. The durable summary node was inserted mid-turn (before\n // the result), so leaving the pair visible would put a user message between\n // an assistant tool_calls block and its tool response — strict providers\n // reject that request with HTTP 400 (issue #18).\n ctx.on('session/event', (session, event) => {\n if (event.type !== 'tool/result') return\n const message = event.data.message\n const block = message.content[0]\n const callId = block?.toolCallId ?? message.source.callId\n if (typeof callId !== 'string' || !this.compressCallIdsToHide.has(callId)) return\n this.compressCallIdsToHide.delete(callId)\n // session.append is NOT reentrant: calling it synchronously inside this\n // session/event dispatch (the outer append still holds the reentry lock)\n // throws \"session append cannot reenter while another append is being\n // published\" on live, store-attached sessions, and the dispatcher\n // silently swallows the error — the hide would be a no-op. Defer it to a\n // microtask: microtasks drain after the append fully publishes and\n // before the agent loop resumes, so the pair is hidden before the next\n // request is built.\n deferCompressPairHide(session, callId, event.seq, (error) => {\n ctx.logger.warn(`billion-context-dsh: hide compress call/result pair failed: ${String(error)}`)\n })\n })\n ctx.on('agent/pre-step', async (payload, next) => {\n // A crash-interrupted tool leaves an orphan call/result on the surface:\n // it corrupts the pairing balance cache AND can 400 the next request\n // (strict providers reject tool messages without their call/response).\n // Clean them before EVERY step — not only when a nudge fires — so a\n // low-pressure session never hits the orphan 400 (issue #18). No call is\n // in flight at pre-step (the previous step's tools all landed), so the\n // default empty in-flight set is safe.\n stripOrphanedSurfaceToolMessages(payload.agent.session)\n if (!this.config.autoNudge) return next()\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const window = await this.windowFor(payload.agent)\n const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides). The\n // systemPrompt service may not be registered yet on cold start (cordis\n // starts unrelated composition rows concurrently), so apply the same\n // retry pattern as tools and commands: eager registration, then\n // re-attempt when the service appears via `internal/service`; guard so a\n // late callback never double-registers.\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: renderSystemPrompt(this.prompts),\n })\n } else {\n let done = false\n const registerSystemPrompt = (): void => {\n if (done) return\n const registry = ctx.get('systemPrompt')\n if (registry === undefined) return\n done = true\n registry.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: renderSystemPrompt(this.prompts),\n })\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'systemPrompt') registerSystemPrompt()\n })\n }\n }\n\n /**\n * Resolve the effective context window for an agent. An explicitly\n * configured `modelContextLimit` always wins (no probe). Otherwise the live\n * session projection (`contextPressure.contextWindow`) is preferred when it\n * discloses one — it tracks the session's CURRENT route, so a mid-session\n * model switch repairs itself without a restart or config (see\n * projectedContextWindow). Falls back to probing the model's real window\n * via `agent.ctx.llm.resolveModelInfo` (cached per provider/model route,\n * probe failures cached too) and finally to DEFAULT_CONTEXT_WINDOW when\n * auto-detection is disabled or unavailable.\n */\n async windowFor(agent: Agent): Promise<AcpWindow> {\n if (this.config.modelContextLimit !== undefined) {\n return { limit: this.config.modelContextLimit, source: 'explicit' }\n }\n const provider = agent.options.provider ?? ''\n const model = agent.options.model ?? ''\n const key = `${provider}\\0${model}`\n // Projection source first: it reflects the live route (agent.options is a\n // stale snapshot after a model switch), and it is not cached here because\n // the projection itself refreshes on every request — caching would freeze\n // the old model's window for the whole process (the false-EMERGENCY trap).\n // Only consulted when auto detection is enabled (same gate as the probe).\n if (this.config.autoModelContextLimit) {\n const projected = projectedContextWindow(agent)\n if (projected !== null) {\n return { limit: projected, source: 'projection', provider, model }\n }\n }\n const cached = this.windowCache.get(key)\n if (cached !== undefined) return cached\n let window: AcpWindow\n if (!this.config.autoModelContextLimit) {\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n } else {\n const detected = await detectContextWindow(agent, provider, model)\n if (detected === null) {\n // Probe failures are cached below too, so the 128K fallback sticks for\n // the whole process lifetime — a gateway operator who fixes the model\n // API must restart (or set modelContextLimit) before the probe retries.\n // Warn loudly instead of failing silently: pressure numbers computed\n // against the fallback are what issue #63's false emergency nudges\n // came from (a gateway that disclosed no window read as ~55% of 128K\n // when the real window was 1M).\n this.ctx.logger.warn(\n `billion-context-dsh: context-window auto-detection failed for ${provider}/${model} — using the ${DEFAULT_CONTEXT_WINDOW} fallback (restart to re-probe, or set modelContextLimit explicitly)`,\n )\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model, probeFailed: true }\n } else {\n window = { limit: detected, source: 'auto', provider, model }\n }\n }\n this.windowCache.set(key, window)\n return window\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","import type { CoreMessage, MessageRefMap } from \"./types.js\";\n\nconst REF_WIDTH = 5;\nconst MIN_INDEX = 1;\nconst MAX_INDEX = 99999;\nconst REF_PATTERN = /^m0*(\\d{1,5})$/;\n\nexport const BLOCKED_REF = \"BLOCKED\";\n\nexport function emptyRefMap(): MessageRefMap {\n return { byRaw: {}, byRef: {} };\n}\n\nexport function indexToRef(index: number): string {\n if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {\n throw new RangeError(\n `ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`,\n );\n }\n return `m${String(index).padStart(REF_WIDTH, \"0\")}`;\n}\n\nexport function refToIndex(ref: string): number | null {\n const match = REF_PATTERN.exec(ref.trim().toLowerCase());\n if (!match) return null;\n const index = Number(match[1]);\n if (index < MIN_INDEX || index > MAX_INDEX) return null;\n return index;\n}\n\nexport function refForRaw(map: MessageRefMap, rawId: string): string | null {\n return map.byRaw[rawId] ?? null;\n}\n\nexport function rawForRef(map: MessageRefMap, ref: string): string | null {\n return map.byRef[ref] ?? null;\n}\n\nexport interface AssignRefsResult {\n map: MessageRefMap;\n nextIndex: number;\n newlyAssigned: number;\n}\n\nexport interface AssignRefsOptions {\n existing: MessageRefMap;\n nextIndex: number;\n isProtected?: (message: CoreMessage) => boolean;\n shouldSkip?: (message: CoreMessage) => boolean;\n}\n\nexport function assignRefs(\n messages: CoreMessage[],\n options: AssignRefsOptions,\n): AssignRefsResult {\n const map: MessageRefMap = {\n byRaw: { ...options.existing.byRaw },\n byRef: { ...options.existing.byRef },\n };\n let cursor =\n Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX\n ? options.nextIndex\n : MIN_INDEX;\n let newlyAssigned = 0;\n\n for (const message of messages) {\n if (!message.id || options.shouldSkip?.(message)) continue;\n\n if (map.byRaw[message.id]) continue;\n\n if (options.isProtected?.(message)) {\n map.byRaw[message.id] = BLOCKED_REF;\n continue;\n }\n\n const ref = allocateFreeRef(map, cursor);\n cursor = ref.index + 1;\n map.byRaw[message.id] = ref.text;\n map.byRef[ref.text] = message.id;\n newlyAssigned++;\n }\n\n return { map, nextIndex: cursor, newlyAssigned };\n}\n\nfunction allocateFreeRef(\n map: MessageRefMap,\n start: number,\n): { text: string; index: number } {\n let candidate = Math.max(start, MIN_INDEX);\n while (candidate <= MAX_INDEX) {\n const text = indexToRef(candidate);\n if (!map.byRef[text]) {\n return { text, index: candidate };\n }\n candidate++;\n }\n throw new Error(\n `ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`,\n );\n}\n\nexport function rebuildRefIndex(map: MessageRefMap): MessageRefMap {\n const byRef: Record<string, string> = {};\n for (const [rawId, ref] of Object.entries(map.byRaw)) {\n if (ref !== BLOCKED_REF) byRef[ref] = rawId;\n }\n return { byRaw: { ...map.byRaw }, byRef };\n}\n\nexport function highestUsedIndex(map: MessageRefMap): number {\n let highest = 0;\n for (const ref of Object.values(map.byRaw)) {\n const index = ref === BLOCKED_REF ? null : refToIndex(ref);\n if (index !== null && index > highest) highest = index;\n }\n return highest;\n}\n","import type { CompressionBlock, CompressionState } from \"./types.js\";\n\nexport function createInitialState(): CompressionState {\n return {\n blocks: [],\n messageRefs: { byRaw: {}, byRef: {} },\n tokenSnapshot: {},\n nudge: {\n lastPerMessageNudgeTokens: 0,\n lastNudgeShownTokens: 0,\n baselineTokens: 0,\n anchors: {},\n lastShownByTier: {},\n },\n stats: { tokensCompressed: 0, compressionCount: 0 },\n nextBlockId: 1,\n nextRunId: 1,\n };\n}\n\nexport function allocateBlockId(state: CompressionState): string {\n const id = state.nextBlockId;\n state.nextBlockId = Math.max(1, id) + 1;\n return `b${id}`;\n}\n\nexport function allocateRunId(state: CompressionState): string {\n const id = state.nextRunId;\n state.nextRunId = Math.max(1, id) + 1;\n return `r${id}`;\n}\n\nexport function blockById(\n state: CompressionState,\n blockId: string,\n): CompressionBlock | undefined {\n return state.blocks.find((block) => block.blockId === blockId);\n}\n\nexport function activeBlocks(state: CompressionState): CompressionBlock[] {\n return state.blocks.filter((block) => block.active);\n}\n\nexport function coveredMessageIds(state: CompressionState): Set<string> {\n const covered = new Set<string>();\n for (const block of state.blocks) {\n if (!block.active) continue;\n for (const id of block.effectiveMessageIds) covered.add(id);\n }\n return covered;\n}\n\nexport function highestActiveTier(state: CompressionState): 0 | 1 | 2 | 3 {\n let highest: 0 | 1 | 2 | 3 = 0;\n for (const block of state.blocks) {\n if (block.active && block.tier > highest) highest = block.tier;\n }\n return highest;\n}\n\nexport function advanceSurvival(\n state: CompressionState,\n promotionThreshold: number,\n): void {\n for (const block of state.blocks) {\n if (!block.active) continue;\n block.survivedCount += 1;\n if (block.survivedCount >= promotionThreshold) {\n block.generation = \"old\";\n }\n }\n}\n","import { activeBlocks, coveredMessageIds } from \"./state.js\";\nimport type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport const SUMMARY_HEADER = \"[Compressed conversation section]\";\n\nexport interface PruneOptions {\n injectSummaries?: boolean;\n}\n\nexport function prune(\n messages: CoreMessage[],\n state: CompressionState,\n options: PruneOptions = {},\n): CoreMessage[] {\n const covered = coveredMessageIds(state);\n if (covered.size === 0) return [...messages];\n\n const inject = options.injectSummaries ?? true;\n const firstUserIndex = messages.findIndex(\n (message) => message.role === \"user\",\n );\n\n const indexById = new Map<string, number>();\n messages.forEach((message, index) => indexById.set(message.id, index));\n\n const anchors = inject ? collectSummaryAnchors(state, indexById) : [];\n\n return stripOrphanedReasoning(\n stripOrphanedToolResults(\n stripOrphanedToolCalls(\n rebuildMessages(messages, covered, firstUserIndex, anchors),\n ),\n ),\n );\n}\n\ninterface SummaryAnchor {\n blockId: string;\n summary: string;\n topic?: string;\n insertAt: number;\n}\n\nfunction collectSummaryAnchors(\n state: CompressionState,\n indexById: Map<string, number>,\n): SummaryAnchor[] {\n const anchors: SummaryAnchor[] = [];\n for (const block of activeBlocks(state)) {\n let earliest: number | null = null;\n for (const id of block.effectiveMessageIds) {\n const index = indexById.get(id);\n if (index !== undefined && (earliest === null || index < earliest)) {\n earliest = index;\n }\n }\n anchors.push({\n blockId: block.blockId,\n summary: block.summary,\n topic: block.topic,\n insertAt: earliest ?? 0,\n });\n }\n anchors.sort((left, right) => left.insertAt - right.insertAt);\n return anchors;\n}\n\nfunction rebuildMessages(\n messages: CoreMessage[],\n covered: Set<string>,\n firstUserIndex: number,\n anchors: SummaryAnchor[],\n): CoreMessage[] {\n const result: CoreMessage[] = [];\n const pending = [...anchors];\n\n for (let index = 0; index < messages.length; index++) {\n while (pending.length > 0 && pending[0]!.insertAt === index) {\n result.push(renderSummary(pending.shift()!));\n }\n if (index === firstUserIndex && firstUserIndex >= 0) {\n result.push(messages[index]!);\n continue;\n }\n if (covered.has(messages[index]!.id)) continue;\n result.push(messages[index]!);\n }\n\n while (pending.length > 0) {\n result.push(renderSummary(pending.shift()!));\n }\n\n return result;\n}\n\nfunction renderSummary(anchor: SummaryAnchor): CoreMessage {\n const body = anchor.summary.trim();\n const topicLine = anchor.topic\n ? `${SUMMARY_HEADER} — ${anchor.topic}`\n : SUMMARY_HEADER;\n const text = body.length === 0 ? topicLine : `${topicLine}\\n${body}`;\n return {\n id: `acp_summary_${anchor.blockId}`,\n role: \"system\",\n contentType: \"text\",\n text,\n };\n}\n\nfunction stripOrphanedToolResults(messages: CoreMessage[]): CoreMessage[] {\n const knownCallIds = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-call\" && m.toolCallId) {\n knownCallIds.add(m.toolCallId);\n }\n }\n return messages.filter(\n (m) =>\n m.contentType !== \"tool-result\" ||\n !m.toolCallId ||\n knownCallIds.has(m.toolCallId),\n );\n}\n\nfunction stripOrphanedToolCalls(messages: CoreMessage[]): CoreMessage[] {\n const knownResultIds = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-result\" && m.toolCallId) {\n knownResultIds.add(m.toolCallId);\n }\n }\n return messages.filter(\n (m) =>\n m.contentType !== \"tool-call\" ||\n !m.toolCallId ||\n m.toolName === \"compress\" ||\n knownResultIds.has(m.toolCallId),\n );\n}\n\n/**\n * Defense-in-depth for reasoning/text pairing (analogue of\n * {@link stripOrphanedToolCalls}). A `reasoning` message is only meaningful\n * when immediately followed — after any same-run reasoning — by its companion\n * assistant text/tool-call; strict thinking models (DeepSeek et al.) reject\n * reasoning_content that has lost its response with HTTP 400. Compress-time\n * boundary expansion normally keeps the pair in one block, so this only fires\n * for degenerate straddles (block-boundary ranges, malformed input, or a\n * reasoning that never had a companion): drop the dangling run rather than\n * ship a 400-triggering half-pair. Runs AFTER tool stripping, since removing\n * an orphaned tool-call can leave its preceding reasoning dangling too.\n */\nfunction stripOrphanedReasoning(messages: CoreMessage[]): CoreMessage[] {\n const drop = new Set<number>();\n for (let i = 0; i < messages.length; i++) {\n if (drop.has(i)) continue;\n if (messages[i]!.contentType !== \"reasoning\") continue;\n let j = i;\n while (\n j + 1 < messages.length &&\n messages[j + 1]!.contentType === \"reasoning\"\n ) {\n j++;\n }\n const companion = messages[j + 1];\n const hasCompanion =\n companion !== undefined &&\n companion.role === \"assistant\" &&\n (companion.contentType === \"text\" ||\n companion.contentType === \"tool-call\");\n if (!hasCompanion) {\n for (let k = i; k <= j; k++) drop.add(k);\n }\n }\n if (drop.size === 0) return messages;\n return messages.filter((_, i) => !drop.has(i));\n}\n","import type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport interface SyncResult {\n state: CompressionState;\n deactivated: string[];\n}\n\nexport function syncBlocks(\n messages: CoreMessage[],\n state: CompressionState,\n): SyncResult {\n const presentIds = new Set(messages.map((message) => message.id));\n const deactivated: string[] = [];\n // Deep-clone (not just `{...state}`) so the caller's input state is never\n // mutated: processTurn stamps `state.nudge.*` and reassigns `messageRefs`,\n // and block sub-arrays must not alias the input. Previously nudge/stats/\n // messageRefs were shared references → input-state mutation leak.\n const result: CompressionState = {\n blocks: state.blocks.map((block) => ({\n ...block,\n directMessageIds: [...block.directMessageIds],\n effectiveMessageIds: [...block.effectiveMessageIds],\n directBlockIds: [...block.directBlockIds],\n })),\n messageRefs: {\n byRaw: { ...state.messageRefs.byRaw },\n byRef: { ...state.messageRefs.byRef },\n },\n // Snapshot is keyed by ref with primitive values — shallow copy suffices.\n tokenSnapshot: { ...(state.tokenSnapshot ?? {}) },\n nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },\n stats: { ...state.stats },\n nextBlockId: state.nextBlockId,\n nextRunId: state.nextRunId,\n };\n\n // Refs are additive (assignRefs never removes them from messageRefs), so\n // prune the snapshot by currently-present message refs — otherwise it grows\n // unboundedly as messages are compressed/deleted across a long session.\n const liveRefs = new Set(\n messages\n .map((m) => result.messageRefs.byRaw[m.id])\n .filter((r): r is string => typeof r === \"string\"),\n );\n if (Object.keys(result.tokenSnapshot).length !== liveRefs.size) {\n const pruned: Record<string, number> = {};\n for (const [ref, n] of Object.entries(result.tokenSnapshot)) {\n if (liveRefs.has(ref)) pruned[ref] = n;\n }\n result.tokenSnapshot = pruned;\n }\n\n const consumedBlockIds = new Set<string>();\n for (const block of result.blocks) {\n for (const consumedId of block.directBlockIds) {\n consumedBlockIds.add(consumedId);\n }\n }\n\n for (const block of result.blocks) {\n if (consumedBlockIds.has(block.blockId)) {\n block.active = false;\n continue;\n }\n block.active = true;\n const stillPresent = block.effectiveMessageIds.some((id) =>\n presentIds.has(id),\n );\n if (!stillPresent) {\n block.active = false;\n deactivated.push(block.blockId);\n }\n }\n\n return { state: result, deactivated };\n}\n","import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\nexport function defaultCountTokens(text: string): number {\n if (!text) return 0;\n // CJK chars tokenize ~1:1 (chars/4 badly underestimates them). Count them\n // directly, then estimate the non-CJK remainder with chars/4 so digits,\n // punctuation, and symbols in code/JSON are not dropped to zero.\n const cjk = text.match(/[\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/g);\n const cjkCount = cjk?.length ?? 0;\n return cjkCount + Math.ceil((text.length - cjkCount) / 4);\n}\n\nexport function estimateMessageTokens(text: string | undefined): number {\n return defaultCountTokens(text ?? \"\");\n}\n\nexport function estimateTokensFast(text: string): number {\n if (!text) return 0;\n return Math.ceil(text.length / 4);\n}\n\nexport type TokenCountFn = (text: string) => number;\n\nconst BPE_SIZE_GUARD = 100_000;\n\nexport function createBpeTokenizer(): TokenCountFn {\n try {\n const mod = require(\"@anthropic-ai/tokenizer\");\n const bpeCount = mod.countTokens ?? mod.default?.countTokens;\n if (typeof bpeCount !== \"function\") return defaultCountTokens;\n return (text: string) => {\n if (text.length > BPE_SIZE_GUARD) return defaultCountTokens(text);\n try {\n return bpeCount(text);\n } catch {\n return defaultCountTokens(text);\n }\n };\n } catch {\n return defaultCountTokens;\n }\n}\n","import type { Config } from \"./types.js\";\n\nexport function defaultConfig(\n modelContextLimit: number,\n overrides: Partial<Config> = {},\n): Config {\n const base: Config = {\n tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },\n nudge: {\n maxContextLimitPct: 0.75,\n minContextLimitPct: 0.45,\n frequency: 5,\n iterationThreshold: 15,\n force: \"soft\",\n growthRatio: 0.05,\n growthFloor: 50000,\n growthCap: 50000,\n minGrowthFloor: 20000,\n minGrowthRatio: 0.45,\n emergencyThresholdPct: 0.95,\n tier2GrowthMultiplier: 1.5,\n },\n promotionThreshold: 5,\n truncate: { threshold: 0.95 },\n compress: {\n minCompressRange: 5000,\n maxSummaryLength: 20000,\n minSummaryLength: 50,\n },\n protectedTools: [],\n preserveRecentMessages: 5,\n preserveRecentTokens: 5000,\n modelContextLimit,\n };\n return {\n ...base,\n ...overrides,\n tiers: { ...base.tiers, ...overrides.tiers },\n nudge: { ...base.nudge, ...overrides.nudge },\n truncate: { ...base.truncate, ...overrides.truncate },\n compress: { ...base.compress, ...overrides.compress },\n };\n}\n\nexport function validateConfig(config: Config): string[] {\n const errors: string[] = [];\n if (\n !Number.isFinite(config.modelContextLimit) ||\n config.modelContextLimit <= 0\n ) {\n errors.push(\"modelContextLimit must be a positive number\");\n }\n if (config.nudge.minContextLimitPct > config.nudge.maxContextLimitPct) {\n errors.push(\n \"nudge.minContextLimitPct must not exceed nudge.maxContextLimitPct\",\n );\n }\n if (config.nudge.maxContextLimitPct > config.nudge.emergencyThresholdPct) {\n errors.push(\n \"nudge.maxContextLimitPct must not exceed nudge.emergencyThresholdPct\",\n );\n }\n if (config.promotionThreshold < 1) {\n errors.push(\"promotionThreshold must be >= 1\");\n }\n if (config.truncate.threshold <= 0 || config.truncate.threshold > 1) {\n errors.push(\"truncate.threshold must be in (0, 1]\");\n }\n for (const tier of [config.tiers.tier2Trigger, config.tiers.tier3Trigger]) {\n if (tier < 1) errors.push(\"tier triggers must be >= 1\");\n }\n if (config.tiers.tier3Trigger <= config.tiers.tier2Trigger) {\n errors.push(\"tiers.tier3Trigger must be greater than tiers.tier2Trigger\");\n }\n return errors;\n}\n","import { activeBlocks, blockById } from \"./state.js\";\nimport type {\n CompressionState,\n CoreMessage,\n ResolvedBoundary,\n} from \"./types.js\";\n\nexport type BoundaryKind = \"message\" | \"block\";\n\nexport interface ParsedBoundary {\n kind: BoundaryKind;\n numericId: number;\n raw: string;\n}\n\nconst MESSAGE_REF_PATTERN = /^m0*(\\d{1,5})$/;\nconst BLOCK_REF_PATTERN = /^b(\\d{1,9})$/;\n\nexport function parseBoundary(ref: string): ParsedBoundary | null {\n const normalized = ref.trim().toLowerCase();\n const messageMatch = MESSAGE_REF_PATTERN.exec(normalized);\n if (messageMatch) {\n const numericId = Number(messageMatch[1]);\n if (numericId >= 1 && numericId <= 99999) {\n return { kind: \"message\", numericId, raw: normalized };\n }\n }\n const blockMatch = BLOCK_REF_PATTERN.exec(normalized);\n if (blockMatch) {\n const numericId = Number(blockMatch[1]);\n if (numericId >= 1) return { kind: \"block\", numericId, raw: normalized };\n }\n return null;\n}\n\n/**\n * Thrown when a boundary ref parses but cannot be anchored in the visible\n * context. `kind` distinguishes a ref that never existed (\"unknown\", e.g. a\n * typo or a ref from another session) from one that was consumed by an\n * existing block (\"consumed\", messages hidden by prune). `endpoint` names the\n * failing side of the range so callers can attribute the error precisely.\n */\nexport class BoundaryNotFoundError extends Error {\n readonly code = \"BOUNDARY_NOT_FOUND\";\n readonly kind: \"unknown\" | \"consumed\";\n readonly endpoint: \"start\" | \"end\";\n\n constructor(\n kind: \"unknown\" | \"consumed\",\n endpoint: \"start\" | \"end\",\n message: string,\n ) {\n super(message);\n this.name = \"BoundaryNotFoundError\";\n this.code = \"BOUNDARY_NOT_FOUND\";\n this.kind = kind;\n this.endpoint = endpoint;\n }\n}\n\nexport interface ResolveBoundariesInput {\n startRef: string;\n endRef: string;\n messages: CoreMessage[];\n state: CompressionState;\n}\n\nexport interface ResolvedRange {\n startIndex: number;\n endIndex: number;\n messageIds: string[];\n nestedBlockIds: string[];\n boundaryKind: BoundaryKind;\n protectedGaps: number[];\n}\n\nexport function resolveBoundaries(\n input: ResolveBoundariesInput,\n): ResolvedRange {\n const start = parseBoundary(input.startRef);\n const end = parseBoundary(input.endRef);\n if (!start || !end) {\n throw new Error(\n `Invalid boundary ref(s): startId=\"${input.startRef}\", endId=\"${input.endRef}\". Use mNNNNN or bN.`,\n );\n }\n\n const indexByRawId = new Map<string, number>();\n input.messages.forEach((message, index) =>\n indexByRawId.set(message.id, index),\n );\n\n let startIndex = resolveAnchorIndex(start, input.state, indexByRawId, \"start\");\n let endIndex = resolveAnchorIndex(end, input.state, indexByRawId, \"end\");\n\n if (startIndex > endIndex) {\n [startIndex, endIndex] = [endIndex, startIndex];\n }\n\n const messageIds: string[] = [];\n for (let index = startIndex; index <= endIndex; index++) {\n const message = input.messages[index];\n if (message) messageIds.push(message.id);\n }\n\n const boundaryKind: BoundaryKind =\n start.kind === \"block\" || end.kind === \"block\" ? \"block\" : \"message\";\n\n const nestedBlockIds: string[] = [];\n const nestedSeen = new Set<string>();\n for (const block of activeBlocks(input.state)) {\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor !== null && anchor >= startIndex && anchor <= endIndex) {\n if (!nestedSeen.has(block.blockId)) {\n nestedSeen.add(block.blockId);\n nestedBlockIds.push(block.blockId);\n }\n }\n }\n\n const protectedGaps: number[] = [];\n\n return {\n startIndex,\n endIndex,\n messageIds,\n nestedBlockIds,\n boundaryKind,\n protectedGaps,\n };\n}\n\nfunction resolveAnchorIndex(\n boundary: ParsedBoundary,\n state: CompressionState,\n indexByRawId: Map<string, number>,\n endpoint: \"start\" | \"end\",\n): number {\n const label = endpoint === \"start\" ? \"startId\" : \"endId\";\n if (boundary.kind === \"message\") {\n const rawId =\n state.messageRefs.byRef[boundary.raw] ??\n state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];\n if (!rawId) {\n throw new BoundaryNotFoundError(\n \"unknown\",\n endpoint,\n `${label}=\"${boundary.raw}\" does not exist in this session (typo or wrong session) — run acp_status for current refs.`,\n );\n }\n const index = indexByRawId.get(rawId);\n if (index === undefined) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"${boundary.raw}\" not found in visible context (likely consumed by an existing block).`,\n );\n }\n return index;\n }\n\n const block = blockById(state, `b${boundary.numericId}`);\n if (!block) {\n throw new BoundaryNotFoundError(\n \"unknown\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" does not exist in this session (typo or wrong session) — run acp_status for current refs.`,\n );\n }\n if (!block.active) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" not found in visible context (block distilled/consumed by a higher-tier block).`,\n );\n }\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor === null) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" not found in visible context (block messages consumed by a higher-tier block).`,\n );\n }\n return anchor;\n}\n\nfunction formatPaddedRef(index: number): string {\n return `m${String(index).padStart(5, \"0\")}`;\n}\n\nexport function earliestIndexOfIds(\n ids: string[],\n indexByRawId: Map<string, number>,\n): number | null {\n let earliest: number | null = null;\n for (const id of ids) {\n const index = indexByRawId.get(id);\n if (index !== undefined && (earliest === null || index < earliest)) {\n earliest = index;\n }\n }\n return earliest;\n}\n\nexport function toResolvedBoundary(range: ResolvedRange): ResolvedBoundary {\n return {\n startIndex: range.startIndex,\n endIndex: range.endIndex,\n protectedGaps: range.protectedGaps,\n };\n}\n","import type { Config, CoreMessage } from \"./types.js\";\n\nexport interface TruncateOptions {\n minOutputTokens?: number;\n keepPrefixChars?: number;\n keepSuffixChars?: number;\n protectRecentMessages?: number;\n}\n\nexport interface TruncateResult {\n messages: CoreMessage[];\n truncatedCount: number;\n savedTokens: number;\n}\n\nconst TRUNCATION_MARKER = \"[truncated for context space]\";\nconst DEFAULTS = {\n minOutputTokens: 1000,\n keepPrefixChars: 2000,\n keepSuffixChars: 2000,\n protectRecentMessages: 3,\n} as const;\n\nexport function truncateLargeToolOutputs(\n messages: CoreMessage[],\n tokenCount: number,\n config: Config,\n countTokens: (text: string) => number,\n options: TruncateOptions = {},\n): TruncateResult {\n const opts = { ...DEFAULTS, ...options };\n if (config.modelContextLimit <= 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const threshold = config.truncate.threshold * config.modelContextLimit;\n if (tokenCount < threshold) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const protectedIndex = messages.length - opts.protectRecentMessages;\n const candidates: Array<{ index: number; tokens: number }> = [];\n\n for (let index = 0; index < messages.length; index++) {\n if (index >= protectedIndex) break;\n const message = messages[index]!;\n if (message.contentType !== \"tool-result\") continue;\n const text = message.text ?? \"\";\n if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;\n const tokens = countTokens(text);\n if (tokens < opts.minOutputTokens) continue;\n candidates.push({ index, tokens });\n }\n\n if (candidates.length === 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n candidates.sort((left, right) => right.tokens - left.tokens);\n\n const targetTokens = threshold * 0.9;\n let savedTokens = 0;\n const edits = new Map<number, string>();\n let truncatedCount = 0;\n\n for (const candidate of candidates) {\n if (tokenCount - savedTokens <= targetTokens) break;\n const original = messages[candidate.index]!.text ?? \"\";\n if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) continue;\n\n const prefix = original.slice(0, opts.keepPrefixChars);\n const suffix = original.slice(-opts.keepSuffixChars);\n const replacement =\n prefix +\n `\\n\\n...${TRUNCATION_MARKER} — original ~${candidate.tokens} tokens]...\\n\\n` +\n suffix;\n edits.set(candidate.index, replacement);\n savedTokens += candidate.tokens - countTokens(replacement);\n truncatedCount++;\n }\n\n if (truncatedCount === 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const updated = messages.map((message, index) =>\n edits.has(index) ? { ...message, text: edits.get(index)! } : message,\n );\n return { messages: updated, truncatedCount, savedTokens };\n}\n","import type { CompressionState, CoreMessage } from \"./types.js\";\n\nconst KEEP_LAST_ORPHANED = 0;\n\nexport interface HideConsumedResult {\n messages: CoreMessage[];\n hidden: number;\n}\n\nfunction rangeKey(startRef: string, endRef: string): string {\n return `${startRef}::${endRef}`;\n}\n\nfunction rewriteCompressText(text: string | undefined, liveKeys: Set<string>): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text ?? \"\");\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\") return null;\n const obj = parsed as { content?: unknown };\n const content = obj.content;\n if (!Array.isArray(content) || content.length === 0) return null;\n\n const kept = content.filter((entry): entry is Record<string, unknown> => {\n if (!entry || typeof entry !== \"object\") return false;\n const s = typeof entry.startId === \"string\" ? entry.startId : typeof entry.messageId === \"string\" ? entry.messageId : \"\";\n const e = typeof entry.endId === \"string\" ? entry.endId : typeof entry.messageId === \"string\" ? entry.messageId : \"\";\n return liveKeys.has(rangeKey(s, e));\n });\n\n if (kept.length === content.length || kept.length === 0) return null;\n\n return JSON.stringify({ ...obj, content: kept });\n}\n\nexport function hideConsumedCompressCalls(\n state: CompressionState,\n messages: CoreMessage[],\n): HideConsumedResult {\n const allBlockCallIds = new Set<string>();\n const activeCallIds = new Set<string>();\n const liveRangeKeysByCallId = new Map<string, Set<string>>();\n const legacyLiveByCallId = new Set<string>();\n for (const block of state.blocks) {\n if (!block.compressCallId) continue;\n allBlockCallIds.add(block.compressCallId);\n if (!block.active) continue;\n activeCallIds.add(block.compressCallId);\n if (block.startRef === undefined || block.endRef === undefined) {\n legacyLiveByCallId.add(block.compressCallId);\n continue;\n }\n let keys = liveRangeKeysByCallId.get(block.compressCallId);\n if (!keys) {\n keys = new Set<string>();\n liveRangeKeysByCallId.set(block.compressCallId, keys);\n }\n keys.add(rangeKey(block.startRef, block.endRef));\n }\n\n const lastOrphanedCallIds: string[] = [];\n for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {\n const message = messages[i]!;\n if (message.toolName !== \"compress\" || message.contentType !== \"tool-call\") continue;\n const callId = message.toolCallId;\n if (callId && !allBlockCallIds.has(callId)) {\n lastOrphanedCallIds.push(callId);\n }\n }\n\n const keepCallIds = new Set([...activeCallIds, ...lastOrphanedCallIds]);\n\n const hiddenCallIds = new Set<string>();\n for (const message of messages) {\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n (!message.toolCallId || !keepCallIds.has(message.toolCallId))\n ) {\n if (message.toolCallId) hiddenCallIds.add(message.toolCallId);\n }\n }\n\n let hidden = 0;\n const result: CoreMessage[] = [];\n for (const message of messages) {\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n (!message.toolCallId || !keepCallIds.has(message.toolCallId))\n ) {\n hidden++;\n continue;\n }\n if (\n message.contentType === \"tool-result\" &&\n message.toolCallId &&\n hiddenCallIds.has(message.toolCallId)\n ) {\n hidden++;\n continue;\n }\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n message.toolCallId &&\n keepCallIds.has(message.toolCallId)\n ) {\n const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);\n if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {\n const rewritten = rewriteCompressText(message.text, liveKeys);\n if (rewritten !== null) {\n result.push({ ...message, text: rewritten });\n continue;\n }\n }\n }\n result.push(message);\n }\n\n return { messages: result, hidden };\n}\n","import type { MessageFilter } from \"./types.js\";\n\nconst registry = new Map<string, MessageFilter>();\n\nexport function registerMessageFilter(filter: MessageFilter): void {\n const existing = registry.get(filter.name);\n if (existing && existing.version !== filter.version) {\n throw new Error(\n `Message filter \"${filter.name}\" already registered with version ${existing.version}, cannot register version ${filter.version}.`,\n );\n }\n registry.set(filter.name, filter);\n}\n\nexport function getMessageFilter(name: string): MessageFilter | undefined {\n return registry.get(name);\n}\n\nexport function listMessageFilters(): MessageFilter[] {\n return [...registry.values()];\n}\n\nexport function clearMessageFilters(): void {\n registry.clear();\n}\n","import { listMessageFilters } from \"./registry.js\";\nimport type { CoreMessage } from \"../types.js\";\nimport type { FilterResult, MessageFilterContext, MessageFiltersConfig } from \"./types.js\";\n\nexport interface ApplyResult {\n messages: CoreMessage[];\n partsFiltered: number;\n partsDropped: number;\n partsModified: number;\n}\n\nexport function applyMessageFilters(\n messages: CoreMessage[],\n config: MessageFiltersConfig | undefined,\n): ApplyResult {\n if (!config?.enabled) {\n return { messages, partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n }\n\n const active = listMessageFilters().filter(\n (filter) => config.filters?.[filter.name]?.enabled !== false,\n );\n if (active.length === 0) {\n return { messages, partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n }\n\n let working = messages.map((message) => ({ ...message }));\n const tally = { partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n const total = working.length;\n\n const immediate = active.filter((filter) => !filter.keepLastOnly);\n for (let index = 0; index < working.length; index++) {\n const message = working[index]!;\n const text = message.text ?? \"\";\n if (text.length === 0) continue;\n let current = text;\n const baseCtx: MessageFilterContext = {\n text: current,\n role: message.role,\n messageIndex: index,\n totalMessages: total,\n toolName: message.toolName,\n };\n for (const filter of immediate) {\n let decision: FilterResult;\n try {\n decision = filter.filter(baseCtx);\n } catch {\n continue;\n }\n if (decision.action === \"keep\") continue;\n tally.partsFiltered++;\n if (decision.action === \"drop\") {\n current = \"\";\n tally.partsDropped++;\n } else if (decision.action === \"modify\" && decision.text !== undefined) {\n current = decision.text;\n tally.partsModified++;\n }\n baseCtx.text = current;\n }\n if (current !== text) working[index] = { ...message, text: current };\n }\n\n const keepLast = active.filter((filter) => filter.keepLastOnly);\n for (const filter of keepLast) {\n let foundLast = false;\n for (let index = working.length - 1; index >= 0; index--) {\n const message = working[index]!;\n const text = message.text ?? \"\";\n if (text.length === 0) continue;\n const ctx: MessageFilterContext = {\n text,\n role: message.role,\n messageIndex: index,\n totalMessages: total,\n toolName: message.toolName,\n };\n let decision: FilterResult;\n try {\n decision = filter.filter(ctx);\n } catch {\n continue;\n }\n if (decision.action !== \"drop\" && decision.action !== \"modify\") continue;\n if (foundLast) {\n tally.partsFiltered++;\n tally.partsDropped++;\n working[index] = { ...message, text: \"\" };\n } else {\n foundLast = true;\n if (decision.action === \"modify\" && decision.text !== undefined) {\n tally.partsFiltered++;\n tally.partsModified++;\n working[index] = { ...message, text: decision.text };\n }\n }\n }\n }\n\n return { messages: working, ...tally };\n}\n","import type { CoreMessage, CompressionState, MessageRefMap } from \"./types.js\";\nimport { refForRaw, BLOCKED_REF } from \"./refs.js\";\nimport type { PipelineNode, PipelineContext, NodeIO } from \"./pipeline.js\";\n\n/**\n * Controls which messages get an <acp> ref tag injected into their text.\n * Ref assignment (assignRefsNode) is unconditional — every message always\n * receives a ref in state.messageRefs regardless of this setting. This only\n * governs text rendering:\n * - \"all\": tag every mapped message (in-process hosts like pai-acp)\n * - \"text-only\": tag only user/assistant text; leave tool-call args and\n * tool-result content pristine (proxy hosts — structured content must not\n * be polluted)\n * - \"none\": leave all text untouched (hosts that read the ref map directly)\n */\nexport type RenderStrategy = \"all\" | \"text-only\" | \"none\";\n\n/** Format token count: <1K raw, <10K \"X.YK\", >=10K \"XK\". */\nfunction formatTokens(tokens: number): string {\n if (tokens < 1000) return String(tokens);\n if (tokens < 10000) return (tokens / 1000).toFixed(1) + \"K\";\n return Math.round(tokens / 1000) + \"K\";\n}\n\nfunction classifyType(message: CoreMessage): string {\n if (\n message.contentType === \"tool-call\" ||\n message.contentType === \"tool-result\"\n ) {\n return message.toolName || \"tool\";\n }\n return message.contentType;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst LT = \"\\x3c\";\nconst GT = \"\\x3e\";\nconst TAG_OPEN = LT + \"acp \";\nconst TAG_CLOSE = LT + \"/acp\" + GT;\n\nfunction acpTag(ref: string, tokens: number, type: string): string {\n return TAG_OPEN + 'tokens=\"' + formatTokens(tokens) + '\" type=\"' + type + '\"' + GT + ref + TAG_CLOSE;\n}\n\nfunction renderMessage(\n message: CoreMessage,\n map: MessageRefMap,\n countTokens: (text: string) => number,\n strategy: RenderStrategy,\n snapshot: Record<string, number> | null = null,\n): CoreMessage {\n const ref = refForRaw(map, message.id);\n if (!ref || ref === BLOCKED_REF) return message;\n\n // \"none\": host reads the ref map directly — never pollute text.\n if (strategy === \"none\") return message;\n\n // text-only: never tag structured tool content. Refs are still assigned.\n if (strategy === \"text-only\" && message.contentType !== \"text\") {\n return message;\n }\n\n // Strip own stale tag BEFORE computing tokens (idempotency).\n // Match the message's own ref only — foreign tags survive (content-corruption fix).\n const ownTagRe = new RegExp(\n \"^\" + escapeRegex(TAG_OPEN) + \"[^>]*\" + GT + escapeRegex(ref) + escapeRegex(TAG_CLOSE) + \"\\\\n?\",\n );\n const cleanText = (message.text || \"\").replace(ownTagRe, \"\");\n\n // Snapshot mode: token count is fixed at first render (stable prefix cache).\n // Live mode (snapshot = null): recompute every render — legacy behavior.\n const tokens = snapshot\n ? (snapshot[ref] ?? (snapshot[ref] = countTokens(cleanText)))\n : countTokens(cleanText);\n const type = classifyType(message);\n const prefix = acpTag(ref, tokens, type) + \"\\n\";\n\n if (!cleanText) return { ...message, text: prefix };\n return { ...message, text: prefix + cleanText };\n}\n\nexport function renderVisibleRefs(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (text: string) => number = (text) =>\n Math.ceil(text.length / 4),\n strategy: RenderStrategy = \"all\",\n): CoreMessage[] {\n // Legacy behavior: recompute tokens every render (snapshot = null).\n const map = state.messageRefs;\n return messages.map((message) =>\n renderMessage(message, map, countTokens, strategy),\n );\n}\n\nexport interface RenderWithSnapshotResult {\n messages: CoreMessage[];\n tokenSnapshot: Record<string, number>;\n}\n\n/** Render with a stable token snapshot: token counts are written on first\n * render and reused forever (keyed by ref). The snapshot starts as a shallow\n * copy of the persisted state so old entries survive; new entries are added\n * during this render. */\nexport function renderWithSnapshot(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (text: string) => number = (text) => Math.ceil(text.length / 4),\n strategy: RenderStrategy = \"all\",\n): RenderWithSnapshotResult {\n const map = state.messageRefs;\n const snapshot = { ...(state.tokenSnapshot ?? {}) };\n const rendered = messages.map((message) =>\n renderMessage(message, map, countTokens, strategy, snapshot),\n );\n return { messages: rendered, tokenSnapshot: snapshot };\n}\n\n/** Factory: build a render-refs node bound to a specific render strategy. */\nexport function createRenderRefsNode(strategy: RenderStrategy): PipelineNode {\n return {\n name: \"render-refs\",\n run(io: NodeIO, ctx: PipelineContext): NodeIO {\n const { messages, tokenSnapshot } = renderWithSnapshot(\n io.messages,\n io.state,\n ctx.countTokens,\n strategy,\n );\n // Write the snapshot back only when it grew: steady-state (all hits)\n // must not churn the state object and force an adapter save every turn.\n const prev = io.state.tokenSnapshot;\n const changed =\n !prev || Object.keys(tokenSnapshot).length !== Object.keys(prev).length;\n return changed\n ? { ...io, messages, state: { ...io.state, tokenSnapshot } }\n : { ...io, messages };\n },\n };\n}\n\n/** Backward compat: default render-refs node using strategy \"all\". */\nexport const renderRefsNode: PipelineNode = createRenderRefsNode(\"all\");\n","import type { Config, CoreMessage } from \"./types.js\";\n\n/** Tools that are ALWAYS protected, regardless of user config. These are ACP's\n * own metadata tools whose records must remain in context: compress calls\n * carry the summaries that decompress/search rely on, and the system prompt\n * treats past compress calls as load-bearing metadata. Letting them be\n * compressed away breaks decompress and the \"summary is historical\" contract. */\nexport const ALWAYS_PROTECTED_TOOLS = [\"compress\"] as const;\n\n/** Tool results that must NEVER participate in the soft-protected recent zone\n * (preserveRecentMessages / preserveRecentTokens / last user message).\n *\n * These tools return large content (restored blocks, search hits, file bodies,\n * command output). If such a result lands in the last-N window it becomes\n * un-compressible: the model cannot reclaim that context, and it never appears\n * in the compressible-ranges recommendation list. Excluding these tools from\n * the protected zone lets the model compress them again immediately, while\n * still leaving them visible (the host's preserveRecent is about not\n * compressing the active working set, not about which tool results are in\n * scope).\n *\n * - `decompress`: large restored content as an inline tool result.\n * - `search_context`: large result lists (10 ranked hits with previews).\n * - `read`: file/image contents — the largest common source of context bloat.\n * - `bash`: command output (build/test/logs) — frequently large and spent.\n *\n * Note: this only affects the recent-zone computation. Such messages remain\n * fully visible and compressible like any ordinary message. */\nexport const NEVER_PRESERVE_RECENT_TOOLS = [\n \"decompress\",\n \"search_context\",\n \"read\",\n \"bash\",\n] as const;\n\n/** True for tool-call / tool-result messages whose toolName is in the\n * NEVER_PRESERVE_RECENT_TOOLS list — i.e. tool results (like decompress)\n * that should be excluded from the soft-protected recent zone. */\nexport function isNeverPreserveRecent(msg: CoreMessage): boolean {\n if (msg.contentType !== \"tool-call\" && msg.contentType !== \"tool-result\") {\n return false;\n }\n if (!msg.toolName) return false;\n return (NEVER_PRESERVE_RECENT_TOOLS as readonly string[]).includes(msg.toolName);\n}\n\nexport function matchToolPattern(toolName: string, pattern: string): boolean {\n if (pattern.endsWith(\"*\")) {\n return toolName.startsWith(pattern.slice(0, -1));\n }\n return toolName === pattern;\n}\n\nexport function isMessageProtected(\n msg: CoreMessage,\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n): boolean {\n // tool-result carries the same toolName as its tool-call (the host projects\n // it), so checking toolName covers both sides of a tool exchange.\n if (\n (msg.contentType !== \"tool-call\" && msg.contentType !== \"tool-result\") ||\n !msg.toolName\n ) {\n return false;\n }\n\n // Hard-coded protection: ACP metadata tools are never compressible.\n if ((ALWAYS_PROTECTED_TOOLS as readonly string[]).includes(msg.toolName)) {\n return true;\n }\n\n for (const pattern of config.protectedTools) {\n if (matchToolPattern(msg.toolName, pattern)) return true;\n }\n\n if (config.isToolProtected?.(msg.toolName, msg.text)) return true;\n\n return false;\n}\n\n/** Build the set of toolCallIds whose tool-call is protected. Use this to also\n * protect tool-results that lack a toolName (common when the host projects a\n * tool-result with only toolCallId). Without it, the result half of a\n * protected tool exchange leaks into compressible ranges. */\nexport function collectProtectedToolCallIds(\n messages: CoreMessage[],\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n): Set<string> {\n const ids = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-call\" && m.toolCallId && isMessageProtected(m, config)) {\n ids.add(m.toolCallId);\n }\n }\n return ids;\n}\n\n/** Like isMessageProtected, but also matches tool-results by toolCallId against\n * the protected call set. Use when you have the full message list available. */\nexport function isMessageProtectedWithPairing(\n msg: CoreMessage,\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n protectedCallIds: Set<string>,\n): boolean {\n if (isMessageProtected(msg, config)) return true;\n if (\n msg.contentType === \"tool-result\" &&\n msg.toolCallId &&\n protectedCallIds.has(msg.toolCallId)\n ) {\n return true;\n }\n return false;\n}\n","import type { CoreMessage } from \"./types.js\";\n\n/**\n * Adjust compression range boundaries to include tool-call/result pairs.\n *\n * PREVENTIVE approach (adapted from opencode-acp PR #248): before compression\n * is applied, scan for tool-call or tool-result messages whose matching half\n * (the result for a call in range, or the call for a result in range) sits\n * outside the requested range. Pull the orphan half INTO the range so the\n * pair is compressed together — zero information loss.\n *\n * Only MESSAGE-boundary ranges are adjusted. Block-boundary ranges (bN) are\n * left untouched to preserve tier-detection correctness.\n *\n * @returns Adjusted { startIndex, endIndex } — may be wider than input.\n */\nexport function adjustBoundariesForToolPairs(\n startIndex: number,\n endIndex: number,\n messages: CoreMessage[],\n maxScan: number = 20,\n): { startIndex: number; endIndex: number } {\n // Collect all toolCallIds in range (both tool-call and tool-result messages).\n // Skip compress tool — it's force-protected and always survives pruning.\n const callIdsInRange = new Set<string>();\n for (let i = startIndex; i <= endIndex; i++) {\n const msg = messages[i];\n if (!msg || !msg.toolCallId) continue;\n if (msg.toolName === \"compress\") continue;\n callIdsInRange.add(msg.toolCallId);\n }\n\n if (callIdsInRange.size === 0) {\n return { startIndex, endIndex };\n }\n\n // Extend FORWARD: tool-results typically follow their tool-call.\n // Stop at the first gap after finding at least one matching message.\n let newEndIndex = endIndex;\n for (let i = endIndex + 1; i < messages.length && i <= endIndex + maxScan; i++) {\n const msg = messages[i];\n if (!msg) break;\n if (msg.toolCallId && callIdsInRange.has(msg.toolCallId)) {\n newEndIndex = i;\n } else if (newEndIndex > endIndex) {\n break;\n }\n }\n\n // Extend BACKWARD: tool-calls typically precede their tool-result.\n let newStartIndex = startIndex;\n for (let i = startIndex - 1; i >= 0 && i >= startIndex - maxScan; i--) {\n const msg = messages[i];\n if (!msg) break;\n if (msg.toolCallId && callIdsInRange.has(msg.toolCallId)) {\n newStartIndex = i;\n } else if (newStartIndex < startIndex) {\n break;\n }\n }\n\n return { startIndex: newStartIndex, endIndex: newEndIndex };\n}\n","import type { CoreMessage } from \"./types.js\";\n\n/**\n * Adjust compression range boundaries to keep a `reasoning` message together\n * with the assistant text/tool-call it belongs to.\n *\n * Reasoning models (DeepSeek-R1, GLM-4.6 thinking, Qwen-QwQ, Anthropic\n * thinking) emit a `reasoning_content` / thinking block that strict providers\n * require to be echoed back alongside the response on every subsequent\n * request. In acp-kernel that block is a separate `contentType: \"reasoning\"`\n * message immediately preceding the assistant text/tool-call of the same turn.\n * If a compression range covers only one half of the pair, the rebuilt\n * conversation ships reasoning without its response (or vice versa) and the\n * provider returns HTTP 400 (DeepSeek: \"reasoning_content in the thinking mode\n * must be passed back to the API\").\n *\n * This is the reasoning analogue of {@link adjustBoundariesForToolPairs}:\n * before a range is applied, pull the orphan half INTO the range so the pair\n * compresses together — zero information loss. Only MESSAGE-boundary ranges\n * are adjusted (block-boundary ranges are left untouched, like tool pairs).\n *\n * Pairing is adjacency-based — there is no shared id (unlike toolCallId). A\n * `reasoning` message pairs with the assistant text/tool-call immediately\n * following its reasoning run, and an assistant text/tool-call pairs with the\n * reasoning run immediately preceding it. This matches the round-trip contract\n * every adapter relies on when reconstructing reasoning_content.\n *\n * @returns Adjusted { startIndex, endIndex } — may be wider than input.\n */\nexport function adjustBoundariesForReasoningPairs(\n startIndex: number,\n endIndex: number,\n messages: CoreMessage[],\n): { startIndex: number; endIndex: number } {\n if (startIndex > endIndex) {\n return { startIndex, endIndex };\n }\n let newStartIndex = startIndex;\n let newEndIndex = endIndex;\n\n for (let i = startIndex; i <= endIndex && i < messages.length; i++) {\n const msg = messages[i];\n if (!msg) continue;\n\n if (msg.contentType === \"reasoning\") {\n // Forward: pull the companion assistant text/tool-call that follows\n // this reasoning run into the range.\n let j = i;\n while (\n j + 1 < messages.length &&\n messages[j + 1]!.contentType === \"reasoning\"\n ) {\n j++;\n }\n const companion = messages[j + 1];\n if (\n companion !== undefined &&\n companion.role === \"assistant\" &&\n (companion.contentType === \"text\" ||\n companion.contentType === \"tool-call\") &&\n j + 1 > newEndIndex\n ) {\n newEndIndex = j + 1;\n }\n }\n\n if (\n msg.role === \"assistant\" &&\n (msg.contentType === \"text\" || msg.contentType === \"tool-call\")\n ) {\n // Backward: pull the reasoning run immediately preceding this assistant\n // message into the range.\n let k = i - 1;\n while (k >= 0 && messages[k]!.contentType === \"reasoning\") {\n k--;\n }\n const runStart = k + 1;\n if (\n runStart < i &&\n runStart >= 0 &&\n messages[runStart]!.contentType === \"reasoning\" &&\n runStart < newStartIndex\n ) {\n newStartIndex = runStart;\n }\n }\n }\n\n return { startIndex: newStartIndex, endIndex: newEndIndex };\n}\n","/**\n * Recommendation engine — compression protection + recommendation.\n *\n * Clean-room reimplementation of the recommendation algorithm (MIT, ours).\n * These pure functions answer two questions every turn:\n *\n * 1. **Protection** — which messages must NOT be compressed? (protected tools,\n * recent messages, recent tokens)\n * 2. **Recommendation** — which remaining ranges are actually WORTH compressing?\n * (growth-aware threshold; suppress nudges when ranges are too small)\n *\n * Called by the `recommend` pipeline node. No side effects, no state mutation.\n */\n\nimport type {\n CompressibleRange,\n Config,\n ContextRanges,\n CoreMessage,\n ProtectedRange,\n} from \"./types.js\";\nimport type { CompressionState } from \"./types.js\";\nimport {\n collectProtectedToolCallIds,\n isMessageProtectedWithPairing,\n isNeverPreserveRecent,\n} from \"./protected.js\";\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction refNum(ref: string): number {\n const n = parseInt(ref.slice(1), 10);\n return Number.isNaN(n) ? -1 : n;\n}\n\n/** Default token estimate (chars/4) used when the caller doesn't inject a\n * countTokens — preserves the historical behavior for backwards compat. */\nfunction estimateTextTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction isToolMessage(message: CoreMessage): boolean {\n return message.contentType === \"tool-call\" || message.contentType === \"tool-result\";\n}\n\n\nfunction isSyntheticOrPruned(\n message: CoreMessage,\n state: CompressionState,\n): boolean {\n if (message.text?.startsWith(\"[Compressed conversation section]\")) return true;\n for (const block of state.blocks) {\n if (block.active && block.effectiveMessageIds.includes(message.id)) return true;\n }\n return false;\n}\n\n// ─── 1. Protected Refs (soft protection zone) ─────────────────────────────────\n\n/**\n * Compute the set of protected message refs (mNNNNN) that form the\n * \"soft-protected zone\" at the tail of the conversation.\n *\n * Combines two rules:\n * 1. Last N messages (`config.preserveRecentMessages`)\n * 2. Last N tokens expanding backward (`config.preserveRecentTokens`)\n *\n * Only considers visible, non-synthetic, non-pruned messages that have refs.\n */\nexport function computeProtectedRefs(\n messages: CoreMessage[],\n state: CompressionState,\n config: Config,\n countTokens: (text: string) => number = estimateTextTokens,\n): Set<string> {\n const preserveN = config.preserveRecentMessages;\n const preserveTokens = config.preserveRecentTokens;\n\n const result = new Set<string>();\n const visible: { ref: string; tokens: number }[] = [];\n\n for (const msg of messages) {\n if (isSyntheticOrPruned(msg, state)) continue;\n // Exclude decompress-style tool results from the recent-zone window.\n // These are large inline restorations that the model should be free to\n // compress again immediately; counting them toward the last-N window\n // would make them un-compressible and hide them from recommendations.\n // The message stays fully visible — this only affects protection scope.\n if (isNeverPreserveRecent(msg)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (!ref || ref === \"BLOCKED\") continue;\n visible.push({ ref, tokens: countTokens(msg.text ?? \"\") });\n }\n\n // Rule 1: last N messages\n if (preserveN > 0) {\n for (const m of visible.slice(-preserveN)) {\n result.add(m.ref);\n }\n }\n\n // Rule 2: last N tokens (expand backward from tail)\n if (preserveTokens > 0) {\n let tokenAccum = 0;\n for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {\n result.add(visible[i]!.ref);\n tokenAccum += visible[i]!.tokens;\n }\n }\n\n // Rule 3: last visible user message. Protected whenever recent-message\n // protection is on (preserveRecentMessages > 0) — this couples it to the\n // same switch as Rule 1, so setting preserveRecentMessages = 0 fully opts\n // out (needed by tests that compress the tail). Production defaultConfig\n // uses 5, so the last user message is always protected in practice.\n // Note: we scan the raw messages array (not `visible`) here so the last\n // user message is still found even when a decompress tool result was\n // skipped above — user intent is always protected regardless of recent\n // tool results.\n if (preserveN > 0) {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role !== \"user\" || isSyntheticOrPruned(msg, state)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (ref && ref !== \"BLOCKED\") result.add(ref);\n break;\n }\n }\n\n return result;\n}\n\n// ─── 2. Build Compressible + Protected Ranges ────────────────────────────────\n\n/**\n * Build compressible and protected range groups from the message list.\n *\n * Messages are classified into:\n * - **compressible**: normal messages outside the protected zone\n * - **protected**: messages from protected tools (e.g., skill, task)\n * - **skipped**: covered by blocks, synthetic, or in the protected zone\n *\n * Compressible messages are grouped into contiguous ranges. The protected\n * zone (from `computeProtectedRefs`) splits groups — the unprotected head\n * survives as its own range.\n */\nexport function buildCompressibleRanges(\n messages: CoreMessage[],\n state: CompressionState,\n config: Config,\n protectedZoneRefs?: Set<string>,\n countTokens: (text: string) => number = estimateTextTokens,\n): ContextRanges {\n const compressibleMsgs: {\n ref: string;\n refNum: number;\n tokens: number;\n chars: number;\n isTool: boolean;\n isUser: boolean;\n }[] = [];\n const protectedMsgs: {\n ref: string;\n refNum: number;\n tokens: number;\n tools: string[];\n }[] = [];\n\n // Pairing: a tool-result may carry only toolCallId (no toolName). Collect the\n // callIds of protected tool-calls first, then protect matching results too.\n const protectedCallIds = collectProtectedToolCallIds(messages, config);\n\n for (const msg of messages) {\n if (isSyntheticOrPruned(msg, state)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (!ref || ref === \"BLOCKED\") continue;\n\n const rn = refNum(ref);\n\n if (isMessageProtectedWithPairing(msg, config, protectedCallIds)) {\n protectedMsgs.push({\n ref,\n refNum: rn,\n tokens: countTokens(msg.text ?? \"\"),\n tools: msg.toolName ? [msg.toolName] : [],\n });\n continue;\n }\n\n if (protectedZoneRefs?.has(ref)) {\n continue;\n }\n\n compressibleMsgs.push({\n ref,\n refNum: rn,\n tokens: countTokens(msg.text ?? \"\"),\n chars: (msg.text ?? \"\").length,\n isTool: isToolMessage(msg),\n isUser: msg.role === \"user\",\n });\n }\n\n // Build compressible groups (contiguous, split at ref gaps and at user\n // messages once a group has >= 3 messages). Splitting at user boundaries\n // keeps each compressible range aligned to roughly one user turn, instead\n // of producing one giant range spanning many turns (or, conversely, a\n // fragment per message when ref gaps appear). Mirrors opencode-acp's\n // buildCompressibleRanges condition.\n const compressible: CompressibleRange[] = [];\n let cur: CompressibleRange | null = null;\n let prevRefNum = -2;\n\n for (const info of compressibleMsgs) {\n const hasGap = info.refNum > prevRefNum + 1;\n if (cur && ((info.isUser && cur.count >= 3) || hasGap)) {\n compressible.push(cur);\n cur = null;\n }\n prevRefNum = info.refNum;\n if (!cur) {\n cur = {\n startRef: info.ref,\n endRef: info.ref,\n count: 1,\n tokens: info.tokens,\n chars: info.chars,\n toolPct: info.isTool ? 100 : 0,\n textPct: info.isTool ? 0 : 100,\n };\n } else {\n cur.endRef = info.ref;\n cur.count++;\n cur.tokens += info.tokens;\n cur.chars = (cur.chars ?? 0) + info.chars;\n if (info.isTool) {\n cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);\n } else {\n cur.toolPct = Math.round((cur.toolPct * (cur.count - 1)) / cur.count);\n }\n cur.textPct = 100 - cur.toolPct;\n }\n }\n if (cur) compressible.push(cur);\n\n // Build protected groups (contiguous)\n const protectedRanges: ProtectedRange[] = [];\n let pcur: ProtectedRange | null = null;\n let pPrevRefNum = -2;\n\n for (const info of protectedMsgs) {\n const hasGap = info.refNum > pPrevRefNum + 1;\n if (pcur && hasGap) {\n protectedRanges.push(pcur);\n pcur = null;\n }\n pPrevRefNum = info.refNum;\n if (!pcur) {\n pcur = {\n startRef: info.ref,\n endRef: info.ref,\n count: 1,\n tokens: info.tokens,\n tools: [...info.tools],\n };\n } else {\n pcur.endRef = info.ref;\n pcur.count++;\n pcur.tokens += info.tokens;\n for (const t of info.tools) {\n if (!pcur!.tools.includes(t)) pcur!.tools.push(t);\n }\n }\n }\n if (pcur) protectedRanges.push(pcur);\n\n return {\n compressible: compressible.filter((g) => g.tokens > 0),\n protected: protectedRanges,\n };\n}\n\nfunction mergeBatch(batch: CompressibleRange[]): CompressibleRange {\n const first = batch[0]!;\n const last = batch[batch.length - 1]!;\n const count = batch.reduce((s, r) => s + r.count, 0);\n const tokens = batch.reduce((s, r) => s + r.tokens, 0);\n const chars = batch.reduce((s, r) => s + rangeChars(r), 0);\n const toolPct = Math.round(\n batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count,\n );\n const merged: CompressibleRange = {\n startRef: first.startRef,\n endRef: last.endRef,\n count,\n tokens,\n chars,\n toolPct,\n textPct: 100 - toolPct,\n };\n if (batch.some((r) => r.dangerous === true)) {\n merged.dangerous = true;\n }\n return merged;\n}\n\n/** Effective size of a range in characters — the unit the apply-side\n * minCompressRange gate uses. Falls back to the historical tokens*4\n * estimate only for hand-built ranges that predate the `chars` field. */\nfunction rangeChars(r: CompressibleRange): number {\n return r.chars ?? r.tokens * 4;\n}\n\n/** Merge adjacent ranges into batches that clear `minChars` of REAL text —\n * the same accounting `applyCompression` uses — so a recommended range is\n * never below the threshold the kernel would atomically reject. Batching by\n * token estimates (tokens*4) instead broke whenever the host injected a\n * tokenizer where tokens != chars/4 (CJK-aware estimators are ~1:1, so\n * tokens*4 overestimated size ~4x and nudge recommended ranges the apply\n * side then refused). A sub-threshold tail batch is still emitted — callers\n * filter by effectiveness separately (see pendingByTier). */\nexport function mergeRangesToThreshold(\n ranges: CompressibleRange[],\n minChars: number,\n): CompressibleRange[] {\n if (minChars <= 0 || ranges.length === 0) return ranges;\n const result: CompressibleRange[] = [];\n let batch: CompressibleRange[] = [];\n let batchChars = 0;\n for (const r of ranges) {\n batch.push(r);\n batchChars += rangeChars(r);\n if (batchChars >= minChars) {\n result.push(mergeBatch(batch));\n batch = [];\n batchChars = 0;\n }\n }\n if (batch.length > 0) {\n result.push(mergeBatch(batch));\n }\n return result;\n}\n","import type { CompressionState, CoreMessage, NudgeDecision } from \"./types.js\";\n\nexport interface PipelineContext {\n readonly config: import(\"./types.js\").Config;\n readonly tokenCount: number;\n readonly countTokens: (text: string) => number;\n}\n\nexport interface NodeEffects {\n nudge?: NudgeDecision;\n recommendation?: import(\"./types.js\").Recommendation;\n truncatedCount?: number;\n readonly [key: string]: unknown;\n}\n\nexport interface NodeIO {\n messages: CoreMessage[];\n state: CompressionState;\n effects: NodeEffects;\n}\n\nexport interface PipelineNode {\n readonly name: string;\n run(io: NodeIO, ctx: PipelineContext): NodeIO;\n enabled?: (io: NodeIO, ctx: PipelineContext) => boolean;\n}\n\nexport function makeIO(\n messages: CoreMessage[],\n state: CompressionState,\n effects: NodeEffects = {},\n): NodeIO {\n return { messages, state, effects };\n}\n\nexport function runPipeline(\n nodes: readonly PipelineNode[],\n initial: NodeIO,\n ctx: PipelineContext,\n): NodeIO {\n let io = initial;\n for (const node of nodes) {\n if (node.enabled && !node.enabled(io, ctx)) continue;\n io = node.run(io, ctx);\n }\n return io;\n}\n","import { assignRefs, highestUsedIndex } from \"./refs.js\";\nimport { prune } from \"./prune.js\";\nimport { syncBlocks } from \"./sync.js\";\nimport { advanceSurvival, activeBlocks, blockById } from \"./state.js\";\nimport {\n allocateBlockId,\n allocateRunId,\n createInitialState,\n} from \"./state.js\";\nimport { defaultCountTokens } from \"./tokenize.js\";\nimport { validateConfig } from \"./config.js\";\nimport {\n BoundaryNotFoundError,\n resolveBoundaries,\n earliestIndexOfIds,\n} from \"./boundaries.js\";\nimport type { ResolvedRange } from \"./boundaries.js\";\nimport { truncateLargeToolOutputs } from \"./truncate-tools.js\";\nimport { hideConsumedCompressCalls } from \"./hide-consumed.js\";\nimport { applyMessageFilters, listMessageFilters } from \"./filter/index.js\";\nimport { createRenderRefsNode } from \"./render-refs.js\";\nimport type { RenderStrategy } from \"./render-refs.js\";\nimport { isMessageProtected } from \"./protected.js\";\nimport { adjustBoundariesForToolPairs } from \"./tool-pairs.js\";\nimport { adjustBoundariesForReasoningPairs } from \"./reasoning-pairs.js\";\nimport {\n computeProtectedRefs,\n buildCompressibleRanges,\n mergeRangesToThreshold,\n} from \"./recommend.js\";\nimport {\n runPipeline,\n type PipelineContext,\n type PipelineNode,\n type NodeIO,\n} from \"./pipeline.js\";\nimport type {\n ApplyCompressionResult,\n CompressionBlock,\n CompressionState,\n CompressionTier,\n Config,\n ContextBreakdown,\n CoreMessage,\n NudgeConfig,\n NudgeDecision,\n ProcessTurnResult,\n Recommendation,\n StatusReport,\n} from \"./types.js\";\n\nexport interface Ports {\n countTokens?: (text: string) => number;\n}\n\nexport interface CompressionCore {\n processTurn(input: ProcessTurnInput): ProcessTurnResult;\n applyCompression(input: ApplyCompressionInput): ApplyCompressionResult;\n defaultNodes(): PipelineNode[];\n decompress(\n blockId: string,\n state: CompressionState,\n ): CompressionBlock | undefined;\n search(query: string, state: CompressionState): CompressionBlock[];\n status(\n state: CompressionState,\n tokenCount: number,\n config: Config,\n ): StatusReport;\n}\n\nexport interface ProcessTurnInput {\n messages: CoreMessage[];\n state: CompressionState;\n config: Config;\n tokenCount: number;\n /**\n * Which messages get an <acp> ref tag injected into their text\n * (the render-refs pipeline node). Refs are ALWAYS assigned regardless\n * (assign-refs node runs unconditionally).\n * - \"all\" (default): tag every mapped message — in-process hosts\n * like pai-acp want tags for the LLM to reference compress ranges.\n * - \"text-only\": tag only user/assistant text; leave tool-call args\n * and tool-result content pristine — proxy hosts where structured\n * content must not be polluted.\n * - \"none\": leave all text untouched — hosts that read the ref map\n * directly from result.state.messageRefs.\n */\n renderTags?: RenderStrategy;\n}\n\nexport interface ApplyCompressionInput {\n ranges: {\n startRef: string;\n endRef: string;\n summary: string;\n topic?: string;\n compressCallId?: string;\n summaryMaxChars?: number;\n }[];\n messages: CoreMessage[];\n state: CompressionState;\n config: Config;\n protectedMessageIds?: Set<string>;\n}\n\n/**\n * Per-range classification from a single resolveBoundaries pass. \"ok\" ranges\n * go on to applySingleRange (which re-resolves internally for tool-pair\n * adjustment); \"consumed\" means the refs existed but their messages were\n * hidden by an existing block; \"unknown\" means a ref never existed in this\n * session; \"invalid\" means a ref failed to parse (e.g. \"foo\").\n */\ntype RangeResolution =\n | { status: \"ok\"; resolved: ResolvedRange }\n | { status: \"consumed\"; error: BoundaryNotFoundError }\n | { status: \"unknown\"; error: BoundaryNotFoundError }\n | { status: \"invalid\"; error: Error };\n\nfunction rangeError(\n spec: { startRef: string; endRef: string },\n message: string,\n): string {\n return `range ${spec.startRef}..${spec.endRef}: ${message}`;\n}\n\nexport function createCore(ports: Ports = {}): CompressionCore {\n const countTokens = ports.countTokens ?? defaultCountTokens;\n\n function applyCompression(\n input: ApplyCompressionInput,\n ): ApplyCompressionResult {\n const state: CompressionState = cloneState(input.state);\n const runId = allocateRunId(state);\n let blocksCreated = 0;\n let tokensCompressed = 0;\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Default to the soft-protected zone (recent-N + last user message) when the\n // caller doesn't pass an explicit set. This makes applyCompression safe by\n // default; applySingleRange enforces it as a hard backstop.\n const protectedMessageIds =\n input.protectedMessageIds ??\n computeProtectedRefs(input.messages, input.state, input.config, countTokens);\n\n const preExistingCoverage = collectCoverage(state);\n\n // Classify every requested range ONCE. The result feeds overlap\n // skipSpecs, the minCompressRange pre-check, and the per-range loop —\n // previously each re-resolved and silently swallowed failures, so\n // consumed/unknown ranges produced misleading \"too small\" errors.\n const classifications = new Map<typeof input.ranges[number], RangeResolution>();\n const classificationErrors: string[] = [];\n const consumedRanges: typeof input.ranges = [];\n for (const spec of input.ranges) {\n try {\n const resolved = resolveBoundaries({\n startRef: spec.startRef,\n endRef: spec.endRef,\n messages: input.messages,\n state,\n });\n classifications.set(spec, { status: \"ok\", resolved });\n } catch (error) {\n if (error instanceof BoundaryNotFoundError) {\n classifications.set(\n spec,\n error.kind === \"unknown\"\n ? { status: \"unknown\", error }\n : { status: \"consumed\", error },\n );\n if (error.kind === \"consumed\") {\n consumedRanges.push(spec);\n } else {\n classificationErrors.push(rangeError(spec, error.message));\n }\n } else {\n classifications.set(spec, {\n status: \"invalid\",\n error: error instanceof Error ? error : new Error(String(error)),\n });\n classificationErrors.push(\n rangeError(spec, error instanceof Error ? error.message : String(error)),\n );\n }\n }\n }\n\n const rangeIndexSets: { spec: typeof input.ranges[number]; indices: number[] }[] = [];\n for (const [spec, resolution] of classifications) {\n if (resolution.status !== \"ok\") continue;\n const indices = resolution.resolved.messageIds.map((id) =>\n input.messages.findIndex((m) => m.id === id),\n ).filter((i) => i >= 0);\n rangeIndexSets.push({ spec, indices });\n }\n const sortedRanges = [...rangeIndexSets].sort((a, b) => {\n const aMin = a.indices.length > 0 ? Math.min(...a.indices) : Infinity;\n const bMin = b.indices.length > 0 ? Math.min(...b.indices) : Infinity;\n return aMin - bMin;\n });\n // Overlapping ranges warn+skip (earliest wins) rather than aborting the\n // whole batch — see ISSUE-42 / dog/billion-context-pi#21.\n const skipSpecs = new Set<typeof input.ranges[number]>();\n let acceptedMaxIndex = -1;\n for (const entry of sortedRanges) {\n const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;\n const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;\n if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {\n skipSpecs.add(entry.spec);\n warnings.push(\n `Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) — overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`,\n );\n continue;\n }\n if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;\n }\n\n if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {\n let totalRangeChars = 0;\n let hasBlockBoundaryRange = false;\n let countedRanges = 0;\n for (const [spec, resolution] of classifications) {\n if (resolution.status !== \"ok\" || skipSpecs.has(spec)) continue;\n if (resolution.resolved.boundaryKind === \"block\") {\n hasBlockBoundaryRange = true;\n continue;\n }\n countedRanges++;\n for (const id of resolution.resolved.messageIds) {\n const msg = input.messages.find((m) => m.id === id);\n totalRangeChars += msg?.text?.length ?? 0;\n }\n }\n if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {\n const gateMessage =\n consumedRanges.length > 0\n ? `Requested range(s) already compressed (e.g. ${consumedRanges[0]!.startRef}..${consumedRanges[0]!.endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do — run acp_status to see current compressible ranges.`\n : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;\n return {\n state: input.state,\n result: {\n blocksCreated: 0,\n tokensCompressed: 0,\n errors: [gateMessage, ...classificationErrors],\n warnings: [],\n },\n };\n }\n }\n\n for (const spec of input.ranges) {\n if (skipSpecs.has(spec)) continue;\n const resolution = classifications.get(spec);\n if (resolution === undefined) continue;\n if (resolution.status === \"consumed\") {\n warnings.push(\n `Skipped range (${spec.startRef}..${spec.endRef}) — already compressed (messages consumed by existing block(s)); nothing to compress.`,\n );\n continue;\n }\n if (resolution.status === \"unknown\" || resolution.status === \"invalid\") {\n errors.push(rangeError(spec, resolution.error.message));\n continue;\n }\n try {\n const outcome = applySingleRange({\n spec,\n messages: input.messages,\n state,\n runId,\n config: input.config,\n protectedMessageIds,\n countTokens,\n preExistingCoverage,\n });\n blocksCreated++;\n tokensCompressed += outcome.tokens;\n warnings.push(...outcome.warnings);\n } catch (error) {\n errors.push(rangeError(spec, error instanceof Error ? error.message : String(error)));\n }\n }\n\n state.stats.compressionCount += blocksCreated;\n state.stats.tokensCompressed += tokensCompressed;\n\n if (blocksCreated > 0) {\n // Compress succeeded: clear the growth baseline so the next turn\n // re-establishes it at the new (lower) token count. Without this the\n // nudge re-fires in a feedback loop (the §5.7 baseline-reset bug).\n state.nudge.lastPerMessageNudgeTokens = 0;\n state.nudge.lastNudgeShownTokens = 0;\n // Clearing the per-tier cadence too: after a successful compression\n // (which may have consumed blocks of tier N to produce tier N+1), every\n // tier should be eligible to re-evaluate from the new token count.\n state.nudge.lastShownByTier = {};\n }\n\n return { state, result: { blocksCreated, tokensCompressed, errors, warnings } };\n }\n\n function processTurn(input: ProcessTurnInput): ProcessTurnResult {\n const configErrors = validateConfig(input.config);\n if (configErrors.length > 0) {\n console.warn(`[acp-kernel] Config validation warnings: ${configErrors.join(\"; \")}. Thresholds may not fire correctly.`);\n }\n const ctx: PipelineContext = {\n config: input.config,\n tokenCount: input.tokenCount,\n countTokens,\n };\n const initial: NodeIO = {\n messages: input.messages,\n state: input.state,\n effects: {},\n };\n // Conversion (assign-refs) and rendering (render-refs) are separate\n // concerns. Refs are always assigned; renderTags only controls which\n // message texts receive an <acp> tag.\n const strategy: RenderStrategy = input.renderTags ?? \"all\";\n const nodes = buildNodes(strategy);\n const result = runPipeline(nodes, initial, ctx);\n return {\n messages: result.messages,\n state: result.state,\n nudge: result.effects.nudge,\n };\n }\n\n function decompress(blockId: string, state: CompressionState) {\n return blockById(state, blockId);\n }\n\n function search(query: string, state: CompressionState): CompressionBlock[] {\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 0);\n if (terms.length === 0) return [];\n const scored = activeBlocks(state)\n .map((block) => ({ block, score: scoreRelevance(block, terms) }))\n .filter((entry) => entry.score > 0.1)\n .sort((left, right) => right.score - left.score);\n return scored.map((entry) => entry.block);\n }\n\n function status(\n state: CompressionState,\n tokenCount: number,\n config: Config,\n ): StatusReport {\n const active = activeBlocks(state);\n const usage =\n config.modelContextLimit > 0 ? tokenCount / config.modelContextLimit : 0;\n return {\n contextUsage: usage,\n tokenCount,\n modelContextLimit: config.modelContextLimit,\n activeBlocks: active.length,\n totalBlocks: state.blocks.length,\n tokensCompressed: state.stats.tokensCompressed,\n breakdown: { active: active.length, total: state.blocks.length },\n };\n }\n\n function defaultNodes(): PipelineNode[] {\n return buildNodes(\"all\");\n }\n\n /** Build the pipeline node list for a given render strategy. \"none\" omits\n * the render-refs node entirely; \"all\"/\"text-only\" append a render-refs\n * node bound to that strategy. */\n function buildNodes(strategy: RenderStrategy): PipelineNode[] {\n const base: PipelineNode[] = [\n assignRefsNode,\n syncBlocksNode,\n pruneNode,\n filterNode,\n hideCompressCallsNode,\n recommendNode,\n nudgeNode,\n emergencyTruncateNode,\n ];\n if (strategy === \"none\") return base;\n return [...base, createRenderRefsNode(strategy)];\n }\n\n return { processTurn, applyCompression, defaultNodes, decompress, search, status };\n}\n\n// --- Pipeline nodes -------------------------------------------------------\n// Each node owns ONE concern. The ref map has a SINGLE writer (assignRefsNode);\n// tags are DERIVED at the end (renderRefsNode) — no dual source of truth, so\n// the old stripHallucinations band-aid is gone. Truncation is the LAST\n// token-reducing safety valve; render-refs is the final annotation pass.\n\nconst assignRefsNode: PipelineNode = {\n name: \"assign-refs\",\n run(io, ctx) {\n const hasProtection =\n ctx.config.protectedTools.length > 0 || !!ctx.config.isToolProtected;\n const protectedFn = hasProtection\n ? (m: CoreMessage) => isMessageProtected(m, ctx.config)\n : undefined;\n const refResult = assignRefs(io.messages, {\n existing: io.state.messageRefs,\n nextIndex: highestUsedIndex(io.state.messageRefs) + 1,\n isProtected: protectedFn,\n });\n return { ...io, state: { ...io.state, messageRefs: refResult.map } };\n },\n};\n\nconst syncBlocksNode: PipelineNode = {\n name: \"sync-blocks\",\n run(io, ctx) {\n const synced = syncBlocks(io.messages, io.state);\n advanceSurvival(synced.state, ctx.config.promotionThreshold);\n return { ...io, state: synced.state };\n },\n};\n\nconst pruneNode: PipelineNode = {\n name: \"prune\",\n run(io) {\n return { ...io, messages: prune(io.messages, io.state) };\n },\n};\n\nconst filterNode: PipelineNode = {\n name: \"filter\",\n enabled: (_io, ctx) =>\n !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,\n run(io, ctx) {\n const applied = applyMessageFilters(io.messages, ctx.config.messageFilters);\n return { ...io, messages: applied.messages };\n },\n};\n\nconst hideCompressCallsNode: PipelineNode = {\n name: \"hide-compress-calls\",\n run(io) {\n const hidden = hideConsumedCompressCalls(io.state, io.messages);\n return { ...io, messages: hidden.messages };\n },\n};\n\nconst recommendNode: PipelineNode = {\n name: \"recommend\",\n run(io, ctx) {\n const protectedRefs = computeProtectedRefs(\n io.messages,\n io.state,\n ctx.config,\n ctx.countTokens,\n );\n const contextRanges = buildCompressibleRanges(\n io.messages,\n io.state,\n ctx.config,\n protectedRefs,\n ctx.countTokens,\n );\n const nothingToCompress = contextRanges.compressible.length === 0;\n const recommendation: Recommendation = {\n contextRanges,\n recommendedRanges: mergeRangesToThreshold(\n contextRanges.compressible,\n ctx.config.compress.minCompressRange,\n ),\n nothingToCompress,\n };\n return { ...io, effects: { ...io.effects, recommendation } };\n },\n};\n\nconst nudgeNode: PipelineNode = {\n name: \"nudge-inject\",\n run(io, ctx) {\n const nudge = decideNudge({\n tokenCount: ctx.tokenCount,\n config: ctx.config,\n state: io.state,\n messages: io.messages,\n recommendation: io.effects.recommendation,\n countTokens: ctx.countTokens,\n });\n\n const baseline = io.state.nudge.lastPerMessageNudgeTokens;\n const nudgeGrowthTokens = resolveAdaptiveGrowth(\n ctx.config.modelContextLimit,\n ctx.config.nudge,\n );\n\n let stamped = { ...io.state.nudge };\n\n if (\n baseline > 0 &&\n ctx.tokenCount < baseline - nudgeGrowthTokens\n ) {\n stamped.lastPerMessageNudgeTokens = ctx.tokenCount;\n stamped.lastNudgeShownTokens = 0;\n // The context shrank dramatically — host compaction, or a tokenCount\n // scale switch (an adapter moving from session-tree accounting to\n // sent-view estimation). Per-tier cadence stamps recorded at the old\n // scale would otherwise make `tokenCount - lastShownByTier[t] >=\n // growthFloor` unreachable (a stamp above the window never re-arms),\n // suppressing mid-band nudges until the absolute overLimit band fires.\n // Restart tier cadence from the new baseline, mirroring the full stamp\n // reset a successful applyCompression performs.\n stamped.lastShownByTier = {};\n }\n\n if (stamped.lastPerMessageNudgeTokens === 0) {\n stamped.lastPerMessageNudgeTokens = ctx.tokenCount;\n }\n\n if (nudge.shouldInject) {\n stamped.lastNudgeShownTokens = ctx.tokenCount;\n // Record the injected tier's own cadence baseline. Shared baseline\n // (lastNudgeShownTokens) suppresses lower-priority tiers within this\n // turn; the per-tier entry throttles re-firing of the SAME tier.\n if (nudge.tier !== null) {\n stamped.lastShownByTier = { ...stamped.lastShownByTier, [nudge.tier]: ctx.tokenCount };\n }\n }\n\n return {\n ...io,\n state: { ...io.state, nudge: stamped },\n effects: { ...io.effects, nudge },\n };\n },\n};\n\nconst emergencyTruncateNode: PipelineNode = {\n name: \"emergency-truncate\",\n run(io, ctx) {\n const usage =\n ctx.config.modelContextLimit > 0\n ? ctx.tokenCount / ctx.config.modelContextLimit\n : 0;\n if (usage < ctx.config.truncate.threshold) return io;\n const trunc = truncateLargeToolOutputs(\n io.messages,\n ctx.tokenCount,\n ctx.config,\n ctx.countTokens,\n { protectRecentMessages: ctx.config.preserveRecentMessages },\n );\n return {\n ...io,\n messages: trunc.messages,\n effects: { ...io.effects, truncatedCount: trunc.truncatedCount },\n };\n },\n};\n\ninterface SingleRangeInput {\n spec: { startRef: string; endRef: string; summary: string; topic?: string; compressCallId?: string; summaryMaxChars?: number };\n messages: CoreMessage[];\n state: CompressionState;\n runId: string;\n config: Config;\n protectedMessageIds?: Set<string>;\n countTokens: (text: string) => number;\n preExistingCoverage: Set<string>;\n}\n\ninterface SingleRangeOutcome {\n tokens: number;\n warnings: string[];\n}\n\nfunction applySingleRange(input: SingleRangeInput): SingleRangeOutcome {\n const warnings: string[] = [];\n const resolved = resolveBoundaries({\n startRef: input.spec.startRef,\n endRef: input.spec.endRef,\n messages: input.messages,\n state: input.state,\n });\n\n const rangeMessageIds = applyPairBoundaryAdjustments(\n resolved,\n input.messages,\n );\n\n // Re-scan for nested blocks in the ADJUSTED range (tool-pair extension may\n // have pulled in messages that are anchors of existing blocks).\n if (rangeMessageIds.length > resolved.messageIds.length) {\n const indexByRawId = new Map<string, number>();\n input.messages.forEach((m, i) => indexByRawId.set(m.id, i));\n const adjustedStart = indexByRawId.get(rangeMessageIds[0]!) ?? resolved.startIndex;\n const adjustedEnd = indexByRawId.get(rangeMessageIds[rangeMessageIds.length - 1]!) ?? resolved.endIndex;\n const nestedSeen = new Set(resolved.nestedBlockIds);\n for (const block of activeBlocks(input.state)) {\n if (nestedSeen.has(block.blockId)) continue;\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor !== null && anchor >= adjustedStart && anchor <= adjustedEnd) {\n nestedSeen.add(block.blockId);\n resolved.nestedBlockIds.push(block.blockId);\n }\n }\n }\n\n const isBlockBoundary = resolved.boundaryKind === \"block\";\n const targetTier = resolveTargetTier(\n input.state,\n resolved.nestedBlockIds,\n isBlockBoundary,\n );\n const outputTier = isBlockBoundary\n ? (Math.min(3, targetTier + 1) as CompressionTier)\n : 1;\n\n const consumedBlockIds = resolved.nestedBlockIds.filter((id) => {\n const block = blockById(input.state, id);\n return block?.active && block.tier === targetTier;\n });\n\n const effectiveMessageIds = new Set<string>(rangeMessageIds);\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) {\n for (const id of consumed.effectiveMessageIds)\n effectiveMessageIds.add(id);\n }\n }\n\n const directMessageIds = [...effectiveMessageIds].filter(\n (id) => !input.preExistingCoverage.has(id),\n );\n\n let filteredIds = filterProtectedToolMessages(\n directMessageIds,\n input.messages,\n input.config,\n );\n\n // filterProtectedToolMessages drops protected tool calls (and their paired\n // results) from the compressible set. They must also leave effectiveMessageIds,\n // otherwise the block would record them as covered and hide them from view.\n // (Bug 39: protected tool messages folded into a block.)\n if (filteredIds.length < directMessageIds.length) {\n const kept = new Set(filteredIds);\n for (const id of directMessageIds) {\n if (!kept.has(id)) effectiveMessageIds.delete(id);\n }\n }\n\n // SOFT PROTECTION: the recent-N / last-user-message zone is advisory-only at\n // compress time. Instead of failing the whole range when it brushes protected\n // messages, exclude those messages and proceed with the rest (so the model\n // isn't blocked when it picks a range that slightly overlaps the recent\n // window). If excluding them empties the range entirely AND there are no\n // consumed blocks to merge, we still fail — there is genuinely nothing to\n // compress. `protectedMessageIds` holds REF ids (mNNNNN) from\n // computeProtectedRefs; filteredIds holds RAW message ids, so convert via\n // state.messageRefs.byRaw before testing membership.\n const protectedRefs = input.protectedMessageIds;\n const hitProtectedRaw = protectedRefs\n ? filteredIds.filter((id) => {\n const ref = input.state.messageRefs.byRaw[id];\n return ref !== undefined && protectedRefs.has(ref);\n })\n : [];\n if (hitProtectedRaw.length > 0) {\n const protectedSet = new Set(hitProtectedRaw);\n filteredIds = filteredIds.filter((id) => !protectedSet.has(id));\n // Remove protected messages from effective coverage too, so they are NOT\n // hidden by the new block (they must stay fully visible).\n for (const id of hitProtectedRaw) effectiveMessageIds.delete(id);\n\n const hitRefs = hitProtectedRaw\n .map((id) => input.state.messageRefs.byRaw[id])\n .filter((v): v is string => typeof v === \"string\");\n\n if (filteredIds.length === 0 && consumedBlockIds.length === 0) {\n const recentN = input.config.preserveRecentMessages;\n throw new Error(\n `Range is entirely within the protected zone (the last ${recentN} messages and/or the most recent user message): ${hitRefs.join(\n \", \",\n )}. Adjust startId/endId to older messages.`,\n );\n }\n warnings.push(\n `Excluded ${hitProtectedRaw.length} protected message(s) ${hitRefs.join(\n \", \",\n )} from compression range (recent/last-user zone).`,\n );\n }\n\n validateCompressionRange(input, filteredIds, consumedBlockIds.length);\n\n let compressedTokens = 0;\n for (const id of filteredIds) {\n const message = input.messages.find((entry) => entry.id === id);\n compressedTokens += input.countTokens(message?.text ?? \"\");\n }\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) {\n compressedTokens += input.countTokens(consumed.summary);\n }\n }\n\n const blockId = allocateBlockId(input.state);\n const block: CompressionBlock = {\n blockId,\n runId: input.runId,\n tier: outputTier,\n topic: input.spec.topic,\n summary: input.spec.summary,\n directMessageIds: filteredIds,\n effectiveMessageIds: [...effectiveMessageIds],\n directBlockIds: [...consumedBlockIds],\n compressedTokens,\n createdAt: Date.now(),\n survivedCount: 0,\n generation: \"young\",\n active: true,\n compressCallId: input.spec.compressCallId,\n startRef: input.spec.startRef,\n endRef: input.spec.endRef,\n };\n input.state.blocks.push(block);\n\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) consumed.active = false;\n }\n\n return { tokens: compressedTokens, warnings };\n}\n\nfunction applyPairBoundaryAdjustments(\n resolved: { startIndex: number; endIndex: number; messageIds: string[]; boundaryKind: string },\n messages: CoreMessage[],\n): string[] {\n if (resolved.boundaryKind === \"block\") {\n return resolved.messageIds;\n }\n // Compose tool-pair and reasoning-pair boundary adjustments to a fixpoint\n // (≤2 passes). Reasoning may pull in a tool-call whose result tool-pairs\n // then extends for; tool-pairs may pull in a tool-call whose preceding\n // reasoning is then drawn in. Both only ever WIDEN the range.\n let startIndex = resolved.startIndex;\n let endIndex = resolved.endIndex;\n for (let pass = 0; pass < 2; pass++) {\n const reasoningAdjusted = adjustBoundariesForReasoningPairs(\n startIndex,\n endIndex,\n messages,\n );\n const toolAdjusted = adjustBoundariesForToolPairs(\n reasoningAdjusted.startIndex,\n reasoningAdjusted.endIndex,\n messages,\n );\n const changed =\n toolAdjusted.startIndex !== startIndex ||\n toolAdjusted.endIndex !== endIndex;\n startIndex = toolAdjusted.startIndex;\n endIndex = toolAdjusted.endIndex;\n if (!changed) break;\n }\n if (\n startIndex === resolved.startIndex &&\n endIndex === resolved.endIndex\n ) {\n return resolved.messageIds;\n }\n const ids: string[] = [];\n for (let i = startIndex; i <= endIndex; i++) {\n const msg = messages[i];\n if (msg) ids.push(msg.id);\n }\n return ids;\n}\n\nfunction validateCompressionRange(\n input: SingleRangeInput,\n directMessageIds: string[],\n consumedBlockCount: number,\n): void {\n const cfg = input.config.compress;\n const summary = input.spec.summary?.trim() ?? \"\";\n\n if (summary.length === 0) {\n throw new Error(\n \"Summary is empty — provide a meaningful summary of the compressed range.\",\n );\n }\n\n if (cfg.minSummaryLength > 0 && summary.length < cfg.minSummaryLength) {\n throw new Error(\n `Summary too short (${summary.length} chars, min ${cfg.minSummaryLength}). The summary must capture the compressed range's key information.`,\n );\n }\n\n const effectiveMax = input.spec.summaryMaxChars ?? cfg.maxSummaryLength;\n if (\n effectiveMax > 0 &&\n summary.length > effectiveMax\n ) {\n throw new Error(\n `Summary too long (${summary.length} chars, max ${effectiveMax}). Strip noise — keep critical paths, decisions, errors, and code references. Or pass summaryMaxChars to increase the limit — don't lose critical info just to fit.`,\n );\n }\n\n if (directMessageIds.length === 0 && consumedBlockCount === 0) {\n throw new Error(\n \"Range contains no compressible messages — all are already covered by active blocks or protected.\",\n );\n }\n}\n\nfunction filterProtectedToolMessages(\n directMessageIds: string[],\n messages: CoreMessage[],\n config: Config,\n): string[] {\n // Protected tool calls (and their results, paired by toolCallId) stay in\n // visible context and are simply dropped from the compressible set. They are\n // NOT folded into the summary — the summary reflects what the author wrote,\n // nothing auto-appended.\n const protectedCallIds = new Set<string>();\n const removedIds = new Set<string>();\n for (const msg of messages) {\n if (isMessageProtected(msg, config) && msg.toolCallId) {\n protectedCallIds.add(msg.toolCallId);\n }\n }\n\n for (const id of directMessageIds) {\n const msg = messages.find((m) => m.id === id);\n if (!msg) continue;\n if (isMessageProtected(msg, config)) {\n removedIds.add(id);\n if (msg.toolCallId) protectedCallIds.add(msg.toolCallId);\n }\n }\n\n for (const id of directMessageIds) {\n if (removedIds.has(id)) continue;\n const msg = messages.find((m) => m.id === id);\n if (!msg) continue;\n if (\n msg.contentType === \"tool-result\" &&\n msg.toolCallId &&\n protectedCallIds.has(msg.toolCallId)\n ) {\n removedIds.add(id);\n }\n }\n\n return directMessageIds.filter((id) => !removedIds.has(id));\n}\n\nfunction resolveTargetTier(\n state: CompressionState,\n nestedBlockIds: string[],\n isBlockBoundary: boolean,\n): CompressionTier {\n if (!isBlockBoundary) return 1;\n if (nestedBlockIds.length === 0) return 1;\n let minTier: CompressionTier = 3;\n for (const id of nestedBlockIds) {\n const block = blockById(state, id);\n if (block && block.tier < minTier) minTier = block.tier;\n }\n return minTier;\n}\n\nfunction collectCoverage(state: CompressionState): Set<string> {\n const coverage = new Set<string>();\n for (const block of activeBlocks(state)) {\n for (const id of block.effectiveMessageIds) coverage.add(id);\n }\n return coverage;\n}\n\ninterface NudgeInput {\n tokenCount: number;\n config: Config;\n state: CompressionState;\n messages: CoreMessage[];\n recommendation?: Recommendation;\n countTokens: (t: string) => number;\n}\n\nfunction resolveAdaptiveGrowth(\n modelContextLimit: number,\n nudge: NudgeConfig,\n): number {\n if (!modelContextLimit || modelContextLimit <= 0) return nudge.growthFloor;\n return Math.min(\n nudge.growthCap,\n Math.max(\n nudge.growthFloor,\n Math.round(modelContextLimit * nudge.growthRatio),\n ),\n );\n}\n\n/** Compressible amount for each tier. T1 = EFFECTIVE merged-range tokens —\n * only ranges whose real char count >= minCompressRange count (avoids\n * inflation from fragmentation; matches the apply-side gate, which counts\n * raw `msg.text.length`, so a nudge never offers a range the kernel would\n * atomically reject — see CompressibleRange.chars); T2 = total summary\n * tokens of all active tier-1 blocks; T3 = total summary tokens of all\n * active tier-2 blocks. */\nfunction pendingByTier(\n state: CompressionState,\n recommendation: Recommendation | undefined,\n countTokens: (t: string) => number,\n minCompressRange: number,\n): Record<number, { pending: number; targetBlocks: CompressionBlock[] }> {\n const out: Record<number, { pending: number; targetBlocks: CompressionBlock[] }> = {};\n const merged = recommendation?.recommendedRanges ?? [];\n const effective =\n minCompressRange > 0\n ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange)\n : merged;\n out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };\n const active = activeBlocks(state);\n const t1 = active.filter((b) => b.tier === 1);\n const t2 = active.filter((b) => b.tier === 2);\n out[2] = { pending: t1.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t1 };\n out[3] = { pending: t2.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t2 };\n return out;\n}\n\nfunction decideNudge(input: NudgeInput): NudgeDecision {\n const { config, state, tokenCount, recommendation, countTokens } = input;\n const limit = config.modelContextLimit;\n const usage = limit > 0 ? tokenCount / limit : 0;\n\n const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);\n\n const overLimit = usage >= config.nudge.maxContextLimitPct;\n const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;\n // High-pressure band: over maxContextLimitPct (subsumes the emergency\n // threshold). Bypasses growth gate + cadence; gated on effective pending.\n const pressure = overLimit || emergencyOverride;\n\n const baseline = state.nudge.lastPerMessageNudgeTokens;\n const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;\n\n const hasPendingNudge = hadPendingNudge;\n const effectiveThreshold = hasPendingNudge\n ? Math.floor(nudgeGrowthTokens / 2)\n : nudgeGrowthTokens;\n\n const growthReference =\n state.nudge.lastNudgeShownTokens > 0\n ? state.nudge.lastNudgeShownTokens\n : baseline > 0\n ? baseline\n : tokenCount;\n\n const growthFloor = Math.max(\n config.nudge.minGrowthFloor,\n config.nudge.minGrowthRatio * nudgeGrowthTokens,\n );\n\n const growthSinceReference = tokenCount - growthReference;\n\n const rec = recommendation;\n const tiers = pendingByTier(\n state,\n rec,\n countTokens,\n config.compress.minCompressRange,\n );\n\n // Tier arbitration. Emergency (usage >= emergencyThresholdPct) ignores tier\n // priority and picks the tier with the MAX pending. Non-emergency defaults to\n // T1; T2 and T3 override when each crossed the shared 1.5x threshold AND\n // exceeds the effective pending of every lower tier (T2 > T1 effective;\n // T3 > T2 and > T1 effective).\n const tier2Threshold = Math.round(\n nudgeGrowthTokens * (config.nudge.tier2GrowthMultiplier ?? 1.5),\n );\n let injectedTier: CompressionTier | null = null;\n let injectedReason = \"\";\n const growthReady = growthSinceReference >= growthFloor;\n const t1Eff = tiers[1]?.pending ?? 0;\n const t2Pen = tiers[2]?.pending ?? 0;\n const t3Pen = tiers[3]?.pending ?? 0;\n\n if (pressure) {\n // High pressure: pick the tier with the MAX pending so pressure can route\n // to distillation when that reclaims the most tokens. Gated on effective\n // pending (real chars >= minCompressRange for T1) so we never offer ranges\n // the kernel would atomically reject. emergency vs over-limit only\n // changes the reason label/voice; truncate.threshold remains the\n // independent last resort when there is genuinely nothing to compress.\n const candidates: CompressionTier[] = [1];\n if (config.tiers.enabled) {\n candidates.push(2, 3);\n }\n let best: CompressionTier | null = null;\n let bestPending = 0;\n for (const t of candidates) {\n const p = tiers[t]?.pending ?? 0;\n if (p > bestPending) {\n bestPending = p;\n best = t;\n }\n }\n if (best !== null && bestPending > 0) {\n injectedTier = best;\n const label = emergencyOverride ? \"EMERGENCY\" : \"OVER-LIMIT\";\n injectedReason =\n best === 1\n ? `${label} T1: max effective pending ${bestPending}, usage ${Math.round(usage * 100)}%`\n : `${label} T${best} distill: max pending ${bestPending} (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}), usage ${Math.round(usage * 100)}%`;\n }\n } else if (growthReady) {\n if (t1Eff >= nudgeGrowthTokens) {\n injectedTier = 1;\n injectedReason = `T1 effective ${t1Eff} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%`;\n } else if (\n config.tiers.enabled &&\n t2Pen >= tier2Threshold &&\n t2Pen > t1Eff\n ) {\n const lastShown = state.nudge.lastShownByTier[2] ?? 0;\n const cadenceMet =\n lastShown === 0 || tokenCount - lastShown >= growthFloor;\n if (cadenceMet) {\n injectedTier = 2;\n injectedReason = `T2 distill ready: ${tiers[2]!.targetBlocks.length} tier-1 blocks (${t2Pen} tokens) >= ${tier2Threshold} (1.5x) and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;\n }\n } else if (\n config.tiers.enabled &&\n t3Pen >= tier2Threshold &&\n t3Pen > t2Pen &&\n t3Pen > t1Eff\n ) {\n const lastShown = state.nudge.lastShownByTier[3] ?? 0;\n const cadenceMet =\n lastShown === 0 || tokenCount - lastShown >= growthFloor;\n if (cadenceMet) {\n injectedTier = 3;\n injectedReason = `T3 condense ready: ${tiers[3]!.targetBlocks.length} tier-2 blocks (${t3Pen} tokens) >= ${tier2Threshold} (1.5x) and > T2 ${t2Pen} and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;\n }\n }\n }\n\n const shouldInject = injectedTier !== null;\n\n let reason: string;\n if (injectedTier !== null) {\n reason = injectedReason;\n } else if (pressure) {\n const label = emergencyOverride ? \"EMERGENCY\" : \"OVER-LIMIT\";\n reason = `${label}: usage ${Math.round(usage * 100)}% but no tier has effective compressible content (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}) — nudge suppressed to avoid offering ranges below minCompressRange`;\n } else {\n const tiersList = [1, 2, 3] as const;\n const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);\n const ready = eligible\n .filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens)\n .map((t) => `T${t} ${tiers[t]!.pending}`);\n const readyHint = ready.length > 0 ? `, ready: ${ready.join(\", \")}` : \"\";\n const blocked = eligible\n .filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor)\n .map((t) => `T${t} (cadence)`);\n const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(\", \")}` : \"\";\n const maxPending = Math.max(0, ...Object.values(tiers).map((t) => t.pending));\n // Report the ACTUAL blocking condition, not a fixed template. A session\n // can have plenty to compress (pending >= threshold) but still not\n // inject because growth/floor/cadence isn't met — the old fixed\n // \"< threshold\" string lied in that case.\n const pendingShort = maxPending < nudgeGrowthTokens;\n const growthShort = growthSinceReference < growthFloor;\n const parts: string[] = [];\n if (pendingShort) parts.push(`max compressible ${maxPending} < threshold ${nudgeGrowthTokens}`);\n if (growthShort) parts.push(`growth ${growthSinceReference} < floor ${growthFloor}`);\n if (parts.length === 0) parts.push(`max compressible ${maxPending}, growth ${growthSinceReference}`);\n reason = `${parts.join(\"; \")}${readyHint}${blockedHint}`;\n }\n\n const ctxBreakdown = computeContextBreakdown(input.messages, tokenCount, growthSinceReference, countTokens);\n\n return {\n shouldInject,\n reason,\n compressibleRanges: rec?.recommendedRanges ?? [],\n protectedRanges: rec?.contextRanges.protected ?? [],\n tierTargetBlocks: injectedTier ? tiers[injectedTier]!.targetBlocks : [],\n contextUsage: usage,\n tier: injectedTier,\n breakdown: {\n usage,\n growth: growthSinceReference,\n growthReference,\n effectiveThreshold,\n nudgeGrowthTokens,\n growthFloor,\n hasPendingNudge: hasPendingNudge ? 1 : 0,\n overLimit: overLimit ? 1 : 0,\n emergencyOverride: emergencyOverride ? 1 : 0,\n pendingT1: tiers[1]!.pending,\n pendingT2: tiers[2]!.pending,\n pendingT3: tiers[3]!.pending,\n },\n contextBreakdown: ctxBreakdown,\n };\n}\n\nfunction computeContextBreakdown(messages: CoreMessage[], total: number, growth: number, countTokens: (t: string) => number): ContextBreakdown {\n const count = countTokens ?? ((t: string) => Math.ceil(t.length / 4));\n let system = 0, tool = 0, summaries = 0, code = 0, text = 0;\n for (const msg of messages) {\n const tokens = count(msg.text ?? \"\");\n if (msg.text?.startsWith(\"[Compressed conversation section]\")) {\n summaries += tokens;\n } else if (msg.contentType === \"tool-call\" || msg.contentType === \"tool-result\") {\n tool += tokens;\n } else if (msg.role === \"system\") {\n system += tokens;\n } else if (msg.text?.includes(\"```\")) {\n code += tokens;\n } else {\n text += tokens;\n }\n }\n return { system, tool, summaries, code, text, total, growth };\n}\n\nfunction cloneState(state: CompressionState): CompressionState {\n return {\n blocks: state.blocks.map((block) => ({\n ...block,\n directMessageIds: [...block.directMessageIds],\n effectiveMessageIds: [...block.effectiveMessageIds],\n directBlockIds: [...block.directBlockIds],\n })),\n messageRefs: {\n byRaw: { ...state.messageRefs.byRaw },\n byRef: { ...state.messageRefs.byRef },\n },\n tokenSnapshot: { ...(state.tokenSnapshot ?? {}) },\n nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },\n stats: { ...state.stats },\n nextBlockId: state.nextBlockId,\n nextRunId: state.nextRunId,\n };\n}\n\nfunction scoreRelevance(block: CompressionBlock, terms: string[]): number {\n const topic = (block.topic ?? \"\").toLowerCase();\n const summary = block.summary.toLowerCase();\n let score = 0;\n for (const term of terms) {\n const topicHits = countOccurrences(topic, term);\n if (topicHits > 0) score += Math.min(topicHits * 0.15, 0.45);\n const summaryHits = countOccurrences(summary, term);\n if (summaryHits > 0) score += Math.min(summaryHits * 0.04, 0.2);\n }\n return Math.min(score, 1);\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!haystack || !needle) return 0;\n let count = 0;\n let position = 0;\n while ((position = haystack.indexOf(needle, position)) !== -1) {\n count++;\n position += needle.length;\n }\n return count;\n}\n\nexport { createInitialState };\n","/**\n * Compression rule texts — VERBATIM copy from context-compress-algorithms (MIT, ours).\n * These were tuned over months of production use.\n *\n * DO NOT modify the wording — it is the result of extensive tuning.\n */\n\nexport const COMPRESS_PHILOSOPHY = `Compression Philosophy:\n- All compression serves the primary task, but be frugal.\n- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.\n- Compress by need, not by percentage.\n- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format — the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;\n\nexport const HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS\n\nWhen you call \\`compress\\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.\n\nKEEP VERBATIM — never paraphrase or abbreviate these:\n- Full file paths with line numbers, directory prefix on every mention (\\`lib/hooks.ts:347\\`, \\`src/index.ts:12-18\\`, \\`gatenet_v3/model.py:45\\`). Never abbreviate to a bare filename (\\`hooks.ts\\`, \\`model.py\\`) — they are ambiguous and cannot be grepped or decompressed-to later.\n- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic — the line that IS the finding, not just the function name (e.g. \\`kv_keys += define_gate * a_key[i](emb)\\` is more useful than \"see model_kvnet.py\").\n- Error messages and stack traces (exact text — you need the literal string to grep for it later).\n- Key details from reports and analyses — not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76× PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing; without it the decision looks arbitrary).\n- Constraints discovered (\"must support Node 22\", \"no new dependencies\", \"AGENTS.md forbids \\`as any\\`\").\n- Exact values: versions, config keys, thresholds, magic numbers.\n- User intent — quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., \"User said: ...\"), not as current directives. Losing these changes the task itself.\n- The user's overall goal and any changes to it — the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., \"initially: fix bug X → pivoted to: refactor module Y after discovering root cause\"). Losing the goal or its evolution makes all subsequent work appear unmotivated.\n- Purpose behind each significant action — preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.\n- Open questions and unresolved TODOs — losing these changes what work appears to remain.\n- Message refs of key anchors (\\`m00420\\`, \\`m00510–m00520\\`) — they let you or a later reader jump back via decompress to the exact original.\n\nDROP — extract the signal, discard the vessel:\n- Verbose logs (build/test/\\`npm\\` output) once you have captured the error line or the result.\n- Duplicate file reads once the needed content is recorded.\n- Consumed exploration — search hits, agent return values, successful tool outputs — once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).\n- Dead-end exploration — but PRESERVE the lesson in one line: \"tried X, failed because Y\".\n- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).\n- Repeated status checks (\\`git status\\`, \\`ls\\`) once state is known.\n\nFor each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers — not where it lives. Bad: \"probe script at /path/probe_kvnet.py\". Good: \"probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention.\" This lets a later decompress target the right block by relevance, not by guessing locations.\n\nPRIORITY — when the summary must be compact, preserve in this order:\n1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).\n2. Decisions and rationale.\n3. Exact technical artifacts: paths, signatures, errors, values.\n4. Conclusions and key findings.\n5. Lessons learned: what failed and why.\n\nWrite dense, scannable bullets — not narrative prose. If the range spans distinct concerns (request → findings → decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;\n\nexport const TIER2_DISTILL_RULES = `TIER 2 COMPRESSION — DISTILLATION\n\nYou are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.\n\nKEEP — these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing).\n- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.\n- Key lessons: what failed and why (\"tried X, failed because Y\"). These prevent repeating mistakes.\n- Critical constraints discovered (\"must support Node 22\", \"AGENTS.md forbids as any\").\n- Design decisions with architectural impact (\"chose compress-as-anchor over synthetic messages because prefix cache\").\n- Whether content is OBSOLETE or SUPERSEDED — mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details — just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work — e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures — just enough to LOCATE the code without searching.\n- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line (\"explored X, not viable because Y\"). Do not keep the exploration process.\n\nDROP — these were useful during the work but are no longer needed:\n- Exact line numbers, diffs, verbose function signatures, full code listings.\n- Build/deploy process details, test execution steps.\n- Review process details (who reviewed, what rounds, test counts).\n- Verbose logs, command output, intermediate debugging steps.\n\nFORMAT:\n- Start each distilled block with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n Example: \\`Source: b5+b7 (56K+44K→268 tok, 375x). [Tool-result recap + publish]\\`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable — no narrative prose.\n- Start with the outcome, not the process: \"v1.13.0 shipped (7 PRs bundled)\" not \"implemented 7 PRs then reviewed then merged\".\n- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks — keep it once under the most relevant source header.\n\nSIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by \"[no actionable content].\"`;\n\nexport const TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION — ULTRA-CONDENSATION\n\nYou are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.\n\nPRIORITY — when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) — these are permanent record.\n2. Open work (PRs/issues still pending) — these may need follow-up.\n3. Key decisions with architectural impact (\"chose X over Y because Z\").\n4. Critical constraints (\"must support Node 22\").\nDrop everything else. Tier 3 is a lookup index, not a knowledge base.\n\nFORMAT:\n- Start with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.\n- No explanations, no rationale, no process — just the fact.\n- Format: \"[PR/Issue/Version] — [outcome in ≤8 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped — quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged — preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed — compress consumed all user messages\"\n- \"Chose compress-as-anchor — prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any — never suppress types\"\n\nDROP:\n- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.\n- Lessons learned (\"tried X, failed because Y\") — drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details — keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] — drop entirely, note \"[N blocks obsolete]\" in the summary.\n\nSIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output ≈ N × 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;\n","import {\n COMPRESS_PHILOSOPHY,\n HOW_TO_COMPRESS_RULES,\n TIER2_DISTILL_RULES,\n TIER3_CONDENSE_RULES,\n} from \"./compression-rules.js\";\n\n/**\n * Overridable prompt text consumed by the kernel's nudge renderer and, via the\n * adapter, the system prompt. Every field here is LOAD-BEARING: these rules\n * were tuned over months of production use and are quality-critical. Overriding\n * them can degrade summary quality (loss of paths / signatures / decisions →\n * broken retrieval), so {@link resolvePrompts} requires `{ acknowledgeRisk: true }`.\n *\n * Surface-level text (summary section headers, status-report chrome, tool\n * descriptions) is intentionally NOT part of this interface — it is owned by\n * the adapter or a later \"prompt-set format\" layer and is safe to customize\n * freely. See DESIGN.md for the load-bearing vs surface classification.\n */\nexport interface Prompts {\n /** Core compression philosophy. Embedded in the system prompt + every nudge. */\n compressPhilosophy: string;\n /** Rules the model follows when writing a tier-1 summary. */\n howToCompressRules: string;\n /** Rules for tier-2 distillation of existing summaries. */\n tier2DistillRules: string;\n /** Rules for tier-3 ultra-condensation of distilled summaries. */\n tier3CondenseRules: string;\n}\n\n/**\n * The kernel's canonical prompt values (verbatim from compression-rules.ts).\n * Frozen so a buggy caller cannot mutate the shared singleton and corrupt\n * every other consumer of {@link defaultPrompts}.\n */\nexport const defaultPrompts: Prompts = Object.freeze({\n compressPhilosophy: COMPRESS_PHILOSOPHY,\n howToCompressRules: HOW_TO_COMPRESS_RULES,\n tier2DistillRules: TIER2_DISTILL_RULES,\n tier3CondenseRules: TIER3_CONDENSE_RULES,\n}) as Prompts;\n\nexport interface ResolvePromptsOptions {\n /**\n * Must be `true` to override any prompt field. Every {@link Prompts} field is\n * load-bearing; overriding without acknowledging the quality risk is a\n * programming error and throws.\n */\n acknowledgeRisk?: boolean;\n}\n\n/**\n * Merge prompt overrides onto the kernel defaults. All fields are load-bearing,\n * so ANY override requires `{ acknowledgeRisk: true }`.\n *\n * Only `string`-valued overrides take effect: an explicit `undefined`/`null` or\n * a wrong type is silently dropped (never clobbers a good default), so a\n * malformed partial never degrades the canonical rules. Resolve once at host\n * startup, then pass the resulting {@link Prompts} to {@link renderNudgeText}\n * and to the adapter's system-prompt composition so both layers stay consistent.\n */\nexport function resolvePrompts(\n overrides?: Partial<Prompts>,\n options: ResolvePromptsOptions = {},\n): Prompts {\n const clean: Partial<Prompts> = {};\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n if (typeof value === \"string\") {\n (clean as Record<string, unknown>)[key] = value;\n }\n }\n }\n const keys = Object.keys(clean) as (keyof Prompts)[];\n if (keys.length > 0 && !options.acknowledgeRisk) {\n throw new Error(\n `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. ` +\n `Overridden keys: ${keys.join(\", \")}. These rules are quality-critical (tuned over months of production use); ` +\n `changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`,\n );\n }\n return { ...defaultPrompts, ...clean };\n}\n","import type { NudgeDecision, CompressibleRange, ProtectedRange, ContextBreakdown, CompressionBlock } from \"./types.js\";\nimport { defaultPrompts } from \"./prompts.js\";\nimport type { Prompts } from \"./prompts.js\";\n\nexport type NudgeVoice = \"gentle\" | \"emergency\";\n\nexport interface RenderedNudge {\n voice: NudgeVoice;\n text: string;\n}\n\nfunction efficiencyNote(prompts: Prompts): string {\n return `This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction emergencyHeader(prompts: Prompts): string {\n return `⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction formatK(n: number): string {\n if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;\n return `${n}`;\n}\n\nfunction formatBreakdown(bd?: ContextBreakdown): string {\n if (!bd) return \"\";\n const parts: string[] = [];\n if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);\n if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);\n if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);\n if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);\n if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);\n const growth = bd.growth > 0 ? `\\n+${formatK(bd.growth)} since last nudge` : \"\";\n return `Context breakdown: ${parts.join(\" | \")}${growth}`;\n}\n\n\n\nfunction formatTierTargetBlocks(blocks: CompressionBlock[]): string {\n if (blocks.length === 0) {\n return \"Target blocks: (none — no tier blocks found)\";\n }\n const lines = blocks.map((b) => {\n const summaryTokens = Math.ceil((b.summary ?? \"\").length / 4);\n const topic = b.topic ? ` \"${b.topic}\"` : \"\";\n return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}→${formatK(summaryTokens)}${topic}`;\n });\n return `Target ${blocks[0]!.tier === 1 ? \"tier-1\" : \"tier-2\"} blocks to distill (${blocks.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nexport function formatRanges(compressible: CompressibleRange[], protectedRanges: ProtectedRange[]): string {\n if (compressible.length === 0 && protectedRanges.length === 0) {\n return \"[No specific ranges detected — compress any consumed content.]\";\n }\n\n // Merge compressible + protected into a single oldest-first list, mirroring\n // opencode-acp's formatCompressibleRanges. Splitting them into two sections\n // lost the time order and hid overlaps; a range can be partly compressible\n // and partly protected, which only the merged view shows correctly.\n interface Merged {\n startRef: string; endRef: string; startNum: number; endNum: number;\n count: number; tokens: number;\n compressibleTokens: number; compressibleCount: number;\n protectedTokens: number; protectedCount: number; protectedTools: string[];\n toolPct: number; textPct: number; dangerous: boolean;\n }\n const refNum = (ref: string): number => {\n const m = ref.match(/\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n };\n const entries: Merged[] = [];\n for (const r of compressible) {\n entries.push({\n startRef: r.startRef, endRef: r.endRef, startNum: refNum(r.startRef), endNum: refNum(r.endRef),\n count: r.count, tokens: r.tokens, toolPct: r.toolPct, textPct: r.textPct,\n compressibleTokens: r.tokens, compressibleCount: r.count,\n protectedTokens: 0, protectedCount: 0, protectedTools: [], dangerous: r.dangerous ?? false,\n });\n }\n for (const r of protectedRanges) {\n entries.push({\n startRef: r.startRef, endRef: r.endRef, startNum: refNum(r.startRef), endNum: refNum(r.endRef),\n count: r.count, tokens: r.tokens, toolPct: 0, textPct: 0,\n compressibleTokens: 0, compressibleCount: 0,\n protectedTokens: r.tokens, protectedCount: r.count, protectedTools: [...r.tools], dangerous: false,\n });\n }\n entries.sort((a, b) => a.startNum - b.startNum);\n // Merge adjacent/overlapping ranges (gap ≤ 1 ref).\n const merged: Merged[] = [];\n for (const e of entries) {\n const last = merged[merged.length - 1];\n if (last && e.startNum <= last.endNum + 1) {\n last.endRef = e.endRef;\n last.endNum = Math.max(last.endNum, e.endNum);\n last.count += e.count;\n last.tokens += e.tokens;\n last.compressibleTokens += e.compressibleTokens;\n last.compressibleCount += e.compressibleCount;\n last.protectedTokens += e.protectedTokens;\n last.protectedCount += e.protectedCount;\n if (e.dangerous) last.dangerous = true;\n for (const t of e.protectedTools) {\n if (!last.protectedTools.includes(t)) last.protectedTools.push(t);\n }\n } else {\n merged.push({ ...e });\n }\n }\n const lines = merged.map((e) => {\n const suffix = e.dangerous && e.compressibleTokens > 0 ? \" ⚠️ NOT recommended unless you are certain.\" : \"\";\n if (e.protectedTokens > 0 && e.compressibleTokens === 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(\", \")} — not compressible]${suffix}`;\n }\n if (e.protectedTokens > 0 && e.compressibleTokens > 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(\", \")}]${suffix}`;\n }\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;\n });\n return `Compressible ranges (${merged.length}, oldest first):\\n${lines.join(\"\\n\")}`;\n}\n\nexport function renderNudgeText(decision: NudgeDecision, prompts: Prompts = defaultPrompts): RenderedNudge {\n const breakdownStr = formatBreakdown(decision.contextBreakdown);\n const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);\n const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;\n\n if (decision.tier !== null && decision.tier >= 2) {\n const isT2 = decision.tier === 2;\n const targets = decision.tierTargetBlocks ?? [];\n const blockList = formatTierTargetBlocks(targets);\n const startId = targets[0]?.blockId ?? \"b1\";\n const endId = targets[targets.length - 1]?.blockId ?? \"b5\";\n const voice: NudgeVoice = isEmergency ? \"emergency\" : \"gentle\";\n const triggerLine = isEmergency\n ? `[EMERGENCY — TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"}] Context limit reached — distill NOW into a denser summary to reclaim tokens.`\n : `[TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"} TRIGGER]`;\n return {\n voice,\n text: [\n efficiencyNote(prompts),\n \"\",\n breakdownStr,\n \"\",\n triggerLine,\n isT2\n ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.`\n : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,\n blockList,\n `Example: compress({ content: [{ startId: \"${startId}\", endId: \"${endId}\", summary: \"...\" }] })`,\n \"\",\n prompts.howToCompressRules,\n \"\",\n isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules,\n ].join(\"\\n\"),\n };\n }\n\n if (isEmergency) {\n return {\n voice: \"emergency\",\n text: [\n emergencyHeader(prompts),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n `{ \"topic\": \"...\", \"content\": [{ \"startId\": \"<ID>\", \"endId\": \"<ID>\", \"summary\": \"...\" }] }`,\n \"Only use IDs from visible messages above. Compress older work first.\",\n \"\",\n rangesStr,\n ].join(\"\\n\"),\n };\n }\n\n return {\n voice: \"gentle\",\n text: [\n efficiencyNote(prompts),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n rangesStr,\n \"\",\n `💡 Compress all ranges in one call (pass multiple content entries: \\`content: [{...}, {...}]\\`).`,\n ].join(\"\\n\"),\n };\n}\n","import { SUMMARY_HEADER } from \"./prune.js\";\nimport type { CompressionBlock, CompressionState, CoreMessage } from \"./types.js\";\n\nexport function parseBlockIdArg(arg: string): string | null {\n const normalized = arg.trim().toLowerCase();\n const refMatch = /^b0*(\\d+)$/.exec(normalized);\n if (refMatch && refMatch[1] !== undefined) return `b${refMatch[1]}`;\n const numMatch = /^(\\d+)$/.exec(normalized);\n if (numMatch && numMatch[1] !== undefined) return `b${numMatch[1]}`;\n return null;\n}\n\nexport function findBlocksOverlappingMessages(\n state: CompressionState,\n messageIds: Set<string>,\n): CompressionBlock[] {\n if (messageIds.size === 0) return [];\n const matched: CompressionBlock[] = [];\n for (const block of state.blocks) {\n if (!block.active) continue;\n if (block.effectiveMessageIds.some((id) => messageIds.has(id))) {\n matched.push(block);\n }\n }\n return matched.sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n}\n\nexport function findActiveAncestor(state: CompressionState, blockId: string): string | null {\n const start = state.blocks.find((b) => b.blockId === blockId);\n if (!start) return null;\n const queue: string[] = [...start.directBlockIds];\n const visited = new Set<string>();\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n if (visited.has(currentId)) continue;\n visited.add(currentId);\n const current = state.blocks.find((b) => b.blockId === currentId);\n if (!current) continue;\n if (current.active) return current.blockId;\n for (const ancestorId of current.directBlockIds) {\n if (!visited.has(ancestorId)) queue.push(ancestorId);\n }\n }\n return null;\n}\n\nexport interface DeactivateOptions {\n deep?: boolean;\n}\n\nexport function deactivateBlock(\n state: CompressionState,\n blockIds: string[],\n options: DeactivateOptions = {},\n): CompressionState {\n const targets = new Set(blockIds);\n\n const updated = state.blocks.map((block) => {\n if (!targets.has(block.blockId) || !block.active) return block;\n return {\n ...block,\n active: false,\n durationMs: block.durationMs,\n createdAt: block.createdAt,\n };\n });\n\n let final = updated;\n if (options.deep) {\n const visited = new Set<string>();\n const queue: string[] = [];\n for (const id of blockIds) {\n const block = updated.find((b) => b.blockId === id);\n if (block) queue.push(...block.directBlockIds);\n }\n while (queue.length > 0) {\n const id = queue.shift()!;\n if (visited.has(id)) continue;\n visited.add(id);\n final = final.map((block) => {\n if (block.blockId !== id) return block;\n queue.push(...block.directBlockIds);\n return block.active ? { ...block, active: false } : block;\n });\n }\n }\n\n return { ...state, blocks: final };\n}\n\nexport interface RestoredPreviewResult {\n preview: string;\n restoredCount: number;\n}\n\nexport function buildRestoredContentPreview(\n messages: CoreMessage[],\n beforeActiveMessageIds: Set<string>,\n state: CompressionState,\n): RestoredPreviewResult {\n const restored: CoreMessage[] = [];\n for (const message of messages) {\n if (!beforeActiveMessageIds.has(message.id)) continue;\n const stillCovered = state.blocks.some(\n (b) => b.active && b.effectiveMessageIds.includes(message.id),\n );\n if (!stillCovered) restored.push(message);\n }\n\n if (restored.length === 0) return { preview: \"\", restoredCount: 0 };\n\n const lines: string[] = [];\n let totalLength = 0;\n const MAX_PREVIEW = 2000;\n const MAX_PER_MESSAGE = 200;\n\n for (const message of restored) {\n if (totalLength >= MAX_PREVIEW) break;\n const text = message.text ?? \"\";\n const truncated = text.length > MAX_PER_MESSAGE ? text.slice(0, MAX_PER_MESSAGE) + \"...\" : text;\n const label =\n message.toolName && message.contentType !== \"text\"\n ? `${message.toolName}: ${truncated}`\n : `[${message.role}] ${truncated}`;\n lines.push(label);\n totalLength += label.length + 1;\n }\n\n return { preview: lines.join(\"\\n\"), restoredCount: restored.length };\n}\n\nexport interface CollectedContentResult {\n /** Rendered, human-readable content string (empty when count is 0). */\n text: string;\n /** Number of items rendered: direct messages + nested summaries (full=false) or all messages (full=true). */\n count: number;\n}\n\nexport interface CollectContentOptions {\n /** When true, recurse through all nested tiers to original messages. Default: false (one tier up — nested active children stay folded, their summaries shown). */\n full?: boolean;\n}\n\n/**\n * Collect a block's content as a readable string WITHOUT modifying state.\n *\n * This is the cache-safe decompress primitive: the block stays compressed\n * (folded), its summary stays in place, and the full content is returned as\n * text for the caller to surface (e.g. as a tool result appended to the\n * conversation). Unlike deactivateBlock + prune, this does not mutate the\n * message-array prefix, so prompt cache is preserved.\n *\n * full=false (default): one tier up. Nested ACTIVE children of this block\n * stay folded; their summaries are rendered in place of their messages.\n * The block's own direct messages (not covered by any active child) are\n * rendered in full.\n * full=true: recurse through all nested tiers; every effective message is\n * rendered in full.\n *\n * Returns { text: \"\", count: 0 } when the block covers no messages.\n */\nexport function collectBlockContent(\n state: CompressionState,\n block: CompressionBlock,\n messages: CoreMessage[],\n options: CollectContentOptions = {},\n): CollectedContentResult {\n const full = options.full ?? false;\n const targetIds = new Set(block.effectiveMessageIds);\n\n if (full) {\n const msgs = messages.filter((m) => targetIds.has(m.id));\n if (msgs.length === 0) return { text: \"\", count: 0 };\n return { text: msgs.map(formatMessage).join(\"\\n\\n\"), count: msgs.length };\n }\n\n // One tier up: messages covered by nested ACTIVE children stay folded\n // (their summaries shown); the block's own direct messages shown in full.\n const nestedChildren: CompressionBlock[] = [];\n const nestedCovered = new Set<string>();\n for (const childId of block.directBlockIds) {\n const child = state.blocks.find((b) => b.blockId === childId);\n if (!child?.active) continue;\n nestedChildren.push(child);\n for (const id of child.effectiveMessageIds) nestedCovered.add(id);\n }\n\n const parts: string[] = [];\n for (const child of nestedChildren) {\n const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;\n parts.push(`${SUMMARY_HEADER} — ${label}\\n${child.summary}`);\n }\n\n let directCount = 0;\n for (const m of messages) {\n if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {\n parts.push(formatMessage(m));\n directCount++;\n }\n }\n\n const count = directCount + nestedChildren.length;\n if (count === 0) return { text: \"\", count: 0 };\n return { text: parts.join(\"\\n\\n\"), count };\n}\n\nfunction formatMessage(message: CoreMessage): string {\n const text = message.text ?? \"\";\n if (message.toolName && message.contentType !== \"text\") {\n return `[${message.role} • ${message.toolName}]\\n${text}`;\n }\n return `[${message.role}]\\n${text}`;\n}\n\nfunction numericPart(blockId: string): number {\n const match = /^b(\\d+)$/.exec(blockId);\n return match && match[1] !== undefined ? Number(match[1]) : 0;\n}\n","import { refForRaw } from \"./refs.js\";\nimport type { CompressionBlock, CompressionState, CoreMessage } from \"./types.js\";\n\nfunction formatTokens(n: number): string {\n if (!Number.isFinite(n) || n <= 0) return \"0\";\n return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);\n}\n\nfunction pct(n: number, total: number): number {\n if (n <= 0 || total <= 0) return 0;\n return Math.max(1, Math.round((n / total) * 100));\n}\n\nfunction numericPart(blockId: string): number {\n const match = /^b(\\d+)$/.exec(blockId);\n return match && match[1] !== undefined ? Number(match[1]) : 0;\n}\n\nfunction summaryTokensOf(block: CompressionBlock, countTokens: (t: string) => number): number {\n return countTokens(block.summary);\n}\n\nfunction effectiveCompressedTokens(\n block: CompressionBlock,\n _state: CompressionState,\n _countTokens: (t: string) => number,\n): number {\n // block.compressedTokens already records the full input token count of the\n // operation that created this block: for a tier-1 block that is the raw\n // messages; for a tier-2 block it is the tier-1 summaries + the new\n // messages it spans. Recursing into directBlockIds and summing children's\n // compressedTokens double-counts the consumed children, so we return the\n // block's own value directly. (The previous recursion inflated tier-2+\n // \"original\" figures and mis-ordered the status report.)\n return block.compressedTokens;\n}\n\nfunction tierLabel(block: CompressionBlock): string {\n return `T${block.tier}`;\n}\n\nfunction tierBreakdown(\n blocks: CompressionBlock[],\n countTokens: (t: string) => number,\n): string | null {\n const tierTokens: Record<number, number> = {};\n for (const block of blocks) {\n tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);\n }\n const tiers = Object.keys(tierTokens).map(Number);\n if (tiers.length <= 1) return null;\n const parts: string[] = [];\n for (const tier of [1, 2, 3]) {\n if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens(tierTokens[tier])}`);\n }\n return parts.join(\" | \");\n}\n\ninterface VisibleMessageInfo {\n ref: string;\n tokens: number;\n tool: string;\n index: number;\n}\n\nfunction collectVisible(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (t: string) => number,\n): { visible: VisibleMessageInfo[]; summaryTokens: number } {\n const coveredIds = new Set<string>();\n for (const block of state.blocks) {\n if (!block.active) continue;\n for (const id of block.effectiveMessageIds) coveredIds.add(id);\n }\n let summaryTokens = 0;\n for (const block of state.blocks) {\n if (block.active) summaryTokens += summaryTokensOf(block, countTokens);\n }\n const visible: VisibleMessageInfo[] = [];\n messages.forEach((message, index) => {\n if (coveredIds.has(message.id)) return;\n const ref = refForRaw(state.messageRefs, message.id);\n if (!ref) return;\n const tokens = countTokens(message.text ?? \"\");\n const tool = message.toolName ?? \"text\";\n if (tokens > 0) visible.push({ ref, tokens, tool, index });\n });\n return { visible, summaryTokens };\n}\n\nexport interface StatusReportOptions {\n scope?: \"compressed\" | \"uncompressed\";\n view?: \"ranges\" | \"messages\";\n tool?: string;\n sort?: \"size\" | \"time\" | \"tool\" | \"age\";\n limit?: number;\n}\n\nexport function buildStatusReport(\n state: CompressionState,\n messages: CoreMessage[],\n countTokens: (t: string) => number,\n options: StatusReportOptions = {},\n): string {\n const scope = options.scope;\n const view = options.view ?? \"ranges\";\n const toolFilter = options.tool;\n const sort = options.sort ?? \"size\";\n const limit = options.limit ?? 30;\n\n const activeBlocks = state.blocks\n .filter((b) => b.active)\n .sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n\n if (scope === \"compressed\") {\n return renderCompressedDrilldown(activeBlocks, state, sort, limit, countTokens);\n }\n\n const { visible, summaryTokens } = collectVisible(messages, state, countTokens);\n\n if (scope === \"uncompressed\") {\n if (view === \"messages\") {\n return renderMessageDrilldown(visible, toolFilter, sort, limit);\n }\n return renderUncompressedRanges(visible);\n }\n\n return renderOverview(visible, summaryTokens, activeBlocks, state, countTokens, limit);\n}\n\nfunction renderOverview(\n visible: VisibleMessageInfo[],\n summaryTokens: number,\n blocks: CompressionBlock[],\n state: CompressionState,\n countTokens: (t: string) => number,\n limit: number,\n): string {\n const lines: string[] = [];\n const toolTypeMap = new Map<string, number>();\n for (const message of visible) {\n toolTypeMap.set(message.tool, (toolTypeMap.get(message.tool) ?? 0) + message.tokens);\n }\n const topTool = [...toolTypeMap.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];\n\n const totalTool = visible\n .filter((m) => m.tool !== \"text\")\n .reduce((sum, m) => sum + m.tokens, 0);\n const totalText = visible\n .filter((m) => m.tool === \"text\")\n .reduce((sum, m) => sum + m.tokens, 0);\n const total = summaryTokens + totalTool + totalText;\n\n lines.push(\"CONTEXT BREAKDOWN\");\n lines.push(\n ` ${formatTokens(totalTool)} tool (${pct(totalTool, total)}%) | ${formatTokens(totalText)} text (${pct(totalText, total)}%) | ${formatTokens(summaryTokens)} summaries (${pct(summaryTokens, total)}%)`,\n );\n const topTypes = [...toolTypeMap.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n if (topTypes.length > 0) {\n lines.push(` Top tools: ${topTypes.map(([t, n]) => `${t} (${pct(n, total)}%)`).join(\", \")}`);\n }\n\n lines.push(\"\");\n if (blocks.length === 0) {\n lines.push(\"COMPRESSED BLOCKS\");\n lines.push(\" No compressed blocks.\");\n } else {\n const totalSummary = blocks.reduce((s, b) => s + summaryTokensOf(b, countTokens), 0);\n const totalEffective = blocks.reduce(\n (s, b) => s + effectiveCompressedTokens(b, state, countTokens),\n 0,\n );\n lines.push(\n `COMPRESSED BLOCKS — ${blocks.length} active (${formatTokens(totalSummary)} summary, ${formatTokens(totalEffective)} original)`,\n );\n const breakdown = tierBreakdown(blocks, countTokens);\n if (breakdown) lines.push(` Tier usage: ${breakdown}`);\n lines.push(\"\");\n const sorted = [...blocks].sort(\n (a, b) =>\n effectiveCompressedTokens(b, state, countTokens) -\n effectiveCompressedTokens(a, state, countTokens) ||\n b.createdAt - a.createdAt,\n );\n for (const block of sorted.slice(0, limit)) {\n const topic = block.topic ?? \"(no topic)\";\n const eff = effectiveCompressedTokens(block, state, countTokens);\n lines.push(\n ` ${block.blockId} (${tierLabel(block)}) ${formatTokens(eff)}→${formatTokens(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs \"${topic}\"`,\n );\n }\n }\n\n lines.push(\"\");\n lines.push(\n `Tip: buildStatusReport({scope:\"uncompressed\", view:\"messages\", tool:\"${topTool ?? \"bash\"}\"}) for per-message listing`,\n );\n return lines.join(\"\\n\");\n}\n\nfunction renderUncompressedRanges(visible: VisibleMessageInfo[]): string {\n const lines: string[] = [];\n const totalTokens = visible.reduce((s, m) => s + m.tokens, 0);\n lines.push(`UNCOMPRESSED — ${formatTokens(totalTokens)} | ${visible.length} visible messages`);\n lines.push(\"\");\n if (visible.length === 0) {\n lines.push(\" (no uncompressed messages)\");\n return lines.join(\"\\n\");\n }\n // Merge consecutive messages into ranges (by numeric ref), aggregating\n // token counts and dominant tool so the view reads as blocks, not a\n // per-message firehose — mirroring the Compressible Ranges output.\n interface Merged { startRef: string; endRef: string; startNum: number; count: number; tokens: number; tool: string; }\n const refNum = (ref: string): number => {\n const m = ref.match(/\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n };\n const merged: Merged[] = [];\n for (const m of visible) {\n const num = refNum(m.ref);\n const last = merged[merged.length - 1];\n if (last && num === last.startNum + last.count) {\n last.endRef = m.ref;\n last.count += 1;\n last.tokens += m.tokens;\n } else {\n merged.push({ startRef: m.ref, endRef: m.ref, startNum: num, count: 1, tokens: m.tokens, tool: m.tool });\n }\n }\n for (const r of merged.slice(0, 30)) {\n const range = r.count === 1 ? r.startRef : `${r.startRef}–${r.endRef}`;\n lines.push(` ${range} (${r.count} msgs, ${formatTokens(r.tokens)}${r.count > 1 ? ` (${Math.round(r.tokens / r.count)}/msg)` : \"\"}) ${r.tool}`);\n }\n if (merged.length > 30) {\n lines.push(` ... and ${merged.length - 30} more ranges`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction renderMessageDrilldown(\n visible: VisibleMessageInfo[],\n toolFilter: string | undefined,\n sort: string,\n limit: number,\n): string {\n let filtered = visible;\n if (toolFilter) filtered = filtered.filter((m) => m.tool === toolFilter);\n\n if (sort === \"time\") filtered.sort((a, b) => a.index - b.index);\n else if (sort === \"tool\") filtered.sort((a, b) => a.tool.localeCompare(b.tool) || b.tokens - a.tokens);\n else filtered.sort((a, b) => b.tokens - a.tokens);\n\n const totalTokens = filtered.reduce((s, m) => s + m.tokens, 0);\n const allTokens = visible.reduce((s, m) => s + m.tokens, 0);\n const header = toolFilter\n ? `UNCOMPRESSED — ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible`\n : `UNCOMPRESSED — ${formatTokens(totalTokens)} | ${filtered.length} msgs`;\n const lines = [header, `Sorted by ${sort}`, \"\"];\n const shown = filtered.slice(0, limit);\n for (const message of shown) {\n lines.push(` ${message.ref} (${formatTokens(message.tokens)}) ${message.tool}`);\n }\n if (filtered.length > shown.length) {\n lines.push(\"\");\n lines.push(`${shown.length} of ${filtered.length} shown.`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction renderCompressedDrilldown(\n blocks: CompressionBlock[],\n state: CompressionState,\n sort: string,\n limit: number,\n countTokens: (t: string) => number,\n): string {\n let sorted = [...blocks];\n if (sort === \"time\") sorted.sort((a, b) => a.createdAt - b.createdAt);\n else if (sort === \"age\") sorted.sort((a, b) => b.survivedCount - a.survivedCount);\n else\n sorted.sort(\n (a, b) =>\n effectiveCompressedTokens(b, state, countTokens) -\n effectiveCompressedTokens(a, state, countTokens) ||\n b.createdAt - a.createdAt,\n );\n\n const totalSummary = sorted.reduce((s, b) => s + summaryTokensOf(b, countTokens), 0);\n const totalEffective = sorted.reduce(\n (s, b) => s + effectiveCompressedTokens(b, state, countTokens),\n 0,\n );\n const lines = [\n `COMPRESSED — ${sorted.length} blocks | ${formatTokens(totalEffective)} original → ${formatTokens(totalSummary)} summary`,\n ];\n const breakdown = tierBreakdown(sorted, countTokens);\n if (breakdown) lines.push(`Tier usage: ${breakdown}`);\n lines.push(\"\");\n const shown = sorted.slice(0, limit);\n for (const block of shown) {\n const nested = block.directBlockIds.length > 0 ? ` nested=[${block.directBlockIds.join(\",\")}]` : \"\";\n const topic = block.topic ?? \"(no topic)\";\n const eff = effectiveCompressedTokens(block, state, countTokens);\n lines.push(\n ` ${block.blockId} (${tierLabel(block)}) ${formatTokens(eff)}→${formatTokens(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs age=${block.survivedCount} ${block.generation}${nested}`,\n );\n lines.push(` \"${topic}\"`);\n }\n if (sorted.length > shown.length) {\n lines.push(\"\");\n lines.push(`${shown.length} of ${sorted.length} shown.`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRecap(\n state: CompressionState,\n blockId?: string,\n): string {\n const activeBlocks = state.blocks\n .filter((b) => b.active)\n .sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n\n if (blockId !== undefined) {\n const block = state.blocks.find((b) => b.blockId === blockId);\n if (!block) {\n const activeList = activeBlocks.map((b) => b.blockId).join(\", \");\n return `Block ${blockId} not found. Active blocks: ${activeList}`;\n }\n if (!block.active) {\n return `Block ${blockId} is inactive (deactivated by nested compression).`;\n }\n const range = `${block.effectiveMessageIds.length} messages`;\n return `[Compressed conversation section]\\n${block.summary}\\n\\n[${blockId} | ${range} | topic: \"${block.topic ?? \"(none)\"}\"]`;\n }\n\n if (activeBlocks.length === 0) return \"No active compression blocks.\";\n\n const lines = [`Active compression blocks (${activeBlocks.length}):`];\n for (const block of activeBlocks) {\n const range = `${block.effectiveMessageIds.length} messages`;\n const preview = block.summary.slice(0, 200);\n lines.push(`\\n${block.blockId} | ${range} | \"${block.topic ?? \"(none)\"}\"`);\n lines.push(` ${preview}${block.summary.length > 200 ? \"...\" : \"\"}`);\n }\n lines.push(`\\nCall with blockId to get the full summary.`);\n return lines.join(\"\\n\");\n}\n","import { createCore } from \"./compress.js\";\nimport { assignRefs, highestUsedIndex } from \"./refs.js\";\nimport { defaultCountTokens } from \"./tokenize.js\";\nimport type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport interface CompressInputEntry {\n startId?: string;\n endId?: string;\n messageId?: string;\n summary: string;\n topic?: string;\n}\n\nexport interface RebuildResult {\n state: CompressionState;\n blocksRebuilt: number;\n}\n\nexport interface RebuildPorts {\n countTokens?: (text: string) => number;\n}\n\n/**\n * Fork-recovery: reconstruct compression state by replaying historical\n * `compress` tool-call messages. Message refs (mNNNNN) are assigned by\n * message order, so they are fork-stable — a ref in a historical compress\n * input points to the same logical message after a fork regenerates IDs.\n * The rebuilt state is an approximation: only raw model summaries are\n * replayed (no protected-content enrichments).\n */\nexport function rebuildCompressionState(\n state: CompressionState,\n messages: CoreMessage[],\n config: import(\"./types.js\").Config,\n ports: RebuildPorts = {},\n): RebuildResult {\n const core = createCore({ countTokens: ports.countTokens ?? defaultCountTokens });\n const refResult = assignRefs(messages, {\n existing: state.messageRefs,\n nextIndex: highestUsedIndex(state.messageRefs) + 1,\n });\n let working: CompressionState = { ...state, messageRefs: refResult.map };\n\n const invocations = collectCompressInvocations(messages);\n let blocksRebuilt = 0;\n\n for (const invocation of invocations) {\n const ranges = extractRanges(invocation.input, invocation.callId);\n if (ranges.length === 0) continue;\n const result = core.applyCompression({ ranges, messages, state: working, config });\n working = result.state;\n blocksRebuilt += result.result.blocksCreated;\n }\n\n return { state: working, blocksRebuilt };\n}\n\ninterface CompressInvocation {\n callId: string | undefined;\n input: unknown;\n}\n\nfunction collectCompressInvocations(messages: CoreMessage[]): CompressInvocation[] {\n const invocations: CompressInvocation[] = [];\n for (const message of messages) {\n if (message.toolName !== \"compress\" || message.contentType !== \"tool-call\") continue;\n let input: unknown;\n try {\n input = JSON.parse(message.text ?? \"\");\n } catch {\n continue;\n }\n invocations.push({ callId: message.toolCallId, input });\n }\n return invocations;\n}\n\nfunction extractRanges(\n input: unknown,\n callId: string | undefined,\n): Array<{\n startRef: string;\n endRef: string;\n summary: string;\n topic?: string;\n compressCallId?: string;\n}> {\n const content = (input as { content?: unknown[] })?.content;\n if (!Array.isArray(content)) return [];\n const ranges = [];\n for (const entry of content) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as CompressInputEntry;\n if (typeof e.summary !== \"string\") continue;\n const start = e.startId ?? e.messageId;\n const end = e.endId ?? e.messageId;\n if (typeof start !== \"string\" || typeof end !== \"string\") continue;\n ranges.push({\n startRef: start,\n endRef: end,\n summary: e.summary,\n topic: typeof e.topic === \"string\" ? e.topic : undefined,\n compressCallId: callId,\n });\n }\n return ranges;\n}\n","export type TransformChannel = \"message\" | \"wire\";\n\n/**\n * Pick the transform channel: an explicit preference always wins; otherwise\n * the wire channel is used only when the caller's host actually applies the\n * wire-payload replacement (adapters pass `wireViable` — e.g. the body format\n * is in WIRE_FORMATS and the host honors the hook's return value).\n */\nexport function resolveTransformChannel(\n explicit: TransformChannel | undefined,\n wireViable: boolean,\n): TransformChannel {\n return explicit ?? (wireViable ? \"wire\" : \"message\");\n}\n","/**\n * Lightweight English stemmer (suffix stripping, Porter-inspired).\n * Zero dependencies. Good enough for IR morphology normalization:\n * tokens → token, running → runn, compressed → compress,\n * authentication → authentic, handling → handl, subagents → subagent\n *\n * Not a full Porter stemmer — intentionally simpler and faster. CJK is\n * untouched (handled by bigram tokenization, not stemming).\n */\nexport function stem(word: string): string {\n let w = word;\n if (w.length <= 3) return w;\n if (w.endsWith(\"ies\")) w = w.slice(0, -3) + \"y\";\n else if (w.endsWith(\"ses\") || w.endsWith(\"xes\") || w.endsWith(\"zes\")) w = w.slice(0, -2);\n else if (w.endsWith(\"ches\") || w.endsWith(\"shes\")) w = w.slice(0, -2);\n else if (w.endsWith(\"s\") && !w.endsWith(\"ss\")) w = w.slice(0, -1);\n if (w.endsWith(\"ing\") && w.length > 5) w = w.slice(0, -3);\n if (w.endsWith(\"ed\") && w.length > 4) w = w.slice(0, -2);\n if (w.endsWith(\"ation\") && w.length > 6) w = w.slice(0, -3);\n else if (w.endsWith(\"tion\") && w.length > 5) w = w.slice(0, -4) + \"t\";\n else if (w.endsWith(\"ion\") && w.length > 4) w = w.slice(0, -3);\n if (w.endsWith(\"ment\") && w.length > 6) w = w.slice(0, -4);\n if (w.endsWith(\"ness\") && w.length > 6) w = w.slice(0, -4);\n if (w.endsWith(\"ly\") && w.length > 4) w = w.slice(0, -2);\n return w;\n}\n","/**\n * Search tokenizer.\n *\n * Handles mixed Latin + CJK content — the single biggest quality lever\n * over plain substring search. Latin is split on non-word boundaries;\n * CJK (no spaces) is word-segmented via Intl.Segmenter (CLDR dictionary),\n * falling back to overlapping bigrams on out-of-vocabulary text so a query\n * like \"身份验证\" still scores against doc text \"身份验证流程\".\n *\n * CJK segmentation is a SINGLE segment() pass over the whole text, not one\n * call per CJK run: a segment() call has fixed overhead (~3µs), and\n * run-heavy text (logs: dozens of short runs per line) made per-run calls\n * 10-16× slower than one bulk pass. ICU never merges CJK words across\n * non-CJK boundaries, so bulk segmentation yields the same words per run\n * (differential-verified against the per-run implementation across a\n * mixed-script stress corpus); run boundaries are re-derived below to keep\n * the all-OOV bigram fallback.\n */\n\n/**\n * CJK ideograph/kana/hangul class — the one shared definition of \"non-Latin\n * script that must be handled specially\". Exported so fuzzy.ts relaxes its\n * short-query gate for the SAME range tokenizer.ts segments: two hand-copied\n * regexes would silently drift apart. Latin is deliberately absent — 2-char\n * English tokens (\"to\", \"of\") carry no meaning, while nearly all CJK words\n * are 2-char atomic units (登录/缓存), so the two scripts need opposite rules.\n */\nimport { stem } from \"./stemmer.js\";\n\nexport const CJK = /[\\u3400-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af]/;\nconst LATIN_WORD = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;\n\nconst cjkSegmenter = new Intl.Segmenter(\"zh\", { granularity: \"word\" });\n\n/**\n * CJK segment groups → tokens, with the all-OOV fallback.\n *\n * `segs` are the word segments the segmenter produced for ONE contiguous\n * CJK run. Multi-char words are kept as whole terms, so \"国际化\" matches\n * \"国际化\" and \"试验证明\" no longer scores against \"验证\" through accidental\n * char runs. When the dictionary finds no multi-char word at all (all-OOV\n * text) we fall back to overlapping bigrams + single chars so recall is\n * preserved — this also covers single-char queries like \"验\".\n */\nfunction cjkRunTokens(segs: string[]): string[] {\n const words = segs.filter((w) => w.length >= 2);\n if (words.length > 0) return words;\n const run = segs.join(\"\");\n const out: string[] = [];\n for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));\n for (const ch of run) out.push(ch);\n return out;\n}\n\nexport interface TokenizeOptions {\n stem?: boolean;\n}\n\nexport function tokenize(text: string, opts: TokenizeOptions = {}): string[] {\n const lower = text.toLowerCase();\n const tokens: string[] = [];\n\n const latin = lower.match(LATIN_WORD) ?? [];\n for (let w of latin) {\n if (w.length >= 2) {\n if (opts.stem) w = stem(w);\n tokens.push(w);\n }\n }\n\n // CJK: one segmenter pass over the whole text instead of one\n // segment() call per CJK run. A segment() call has fixed overhead\n // (~3µs), and run-heavy text (logs: dozens of short runs per line) made\n // per-run calls 10-16× slower than one bulk pass. ICU never merges CJK\n // words across non-CJK boundaries, so bulk segmentation yields the same\n // words per run (differential-verified against the per-run\n // implementation across a mixed-script stress corpus); run boundaries\n // are re-derived below to keep the all-OOV bigram fallback.\n //\n // Guard: skip the segmenter entirely when the text has no CJK at all —\n // the old code never called it for pure-Latin text, and a bulk pass\n // would pay a full-text scan (12ms → 33ms per MB of English) for nothing.\n if (!CJK.test(lower)) return tokens;\n\n // Group the bulk segments back into CJK runs: a non-CJK segment is a run\n // boundary (the segmenter never puts non-CJK inside a CJK word segment).\n const runSegs: string[][] = [];\n let cur: string[] | null = null;\n for (const s of cjkSegmenter.segment(lower)) {\n const t = s.segment;\n if (t.length === 0) continue;\n if (CJK.test(t)) {\n (cur ??= []).push(t);\n } else if (cur) {\n runSegs.push(cur);\n cur = null;\n }\n }\n if (cur) runSegs.push(cur);\n\n for (const segs of runSegs) {\n tokens.push(...cjkRunTokens(segs));\n }\n\n return tokens;\n}\n\n/** Character bigrams over arbitrary text — used by fuzzy matching. */\nexport function charBigrams(text: string): string[] {\n const grams: string[] = [];\n for (let i = 0; i < text.length - 1; i++) {\n const pair = text.slice(i, i + 2);\n if (pair.trim().length === pair.length) grams.push(pair);\n }\n return grams;\n}\n\n/** Term-frequency map. */\nexport function tfMap(text: string, stem: boolean): Map<string, number> {\n const m = new Map<string, number>();\n for (const t of tokenize(text, { stem })) m.set(t, (m.get(t) ?? 0) + 1);\n return m;\n}\n","/**\n * Per-doc derived features, memoized across search calls.\n *\n * A search over the compressed history re-scores the SAME immutable docs on\n * every call — compressed block summaries and folded message text never\n * change. Without this cache, every search_context call re-tokenized the\n * entire corpus (segmenter CJK pass ≈ 0.3s/MB cold) plus re-lowercased it\n * and rebuilt the bigram set for each channel: a 5MB session cost ~3s PER\n * CALL, growing linearly with session length. With the cache the corpus is\n * processed once; later searches are O(docs × query-terms).\n *\n * Keyed by doc text (immutable). Bounded by total cached source chars —\n * oldest docs are evicted when the cap is exceeded, so a long-lived\n * process serving many sessions cannot grow unboundedly. Hosts that want to\n * release the memory eagerly on session shutdown/switch can call\n * clearDocFeatures() (optional: the cap already bounds it).\n */\n\nimport { charBigrams, tfMap } from \"./tokenizer.js\";\n\nexport interface DocFeatures {\n /** Stemmed term frequencies (BM25 channel). */\n tf: Map<string, number>;\n /** Total term count (BM25 length normalization). */\n len: number;\n /** Lower-cased text (substring + fuzzy channels). */\n lower: string;\n /** Unique char bigrams of `lower` (fuzzy channel). */\n grams: Set<string>;\n}\n\nconst DEFAULT_CAP_CHARS = 8 * 1024 * 1024;\nlet capChars = DEFAULT_CAP_CHARS;\nconst cache = new Map<string, DocFeatures>();\nlet cachedChars = 0;\n\nfunction build(text: string): DocFeatures {\n const tf = tfMap(text, true);\n let len = 0;\n for (const v of tf.values()) len += v;\n const lower = text.toLowerCase();\n return { tf, len, lower, grams: new Set(charBigrams(lower)) };\n}\n\nexport function docFeatures(text: string): DocFeatures {\n const hit = cache.get(text);\n if (hit) return hit;\n const f = build(text);\n if (text.length > 0 && text.length <= capChars) {\n while (cachedChars + text.length > capChars && cache.size > 0) {\n const k = cache.keys().next().value as string;\n cachedChars -= k.length;\n cache.delete(k);\n }\n cache.set(text, f);\n cachedChars += text.length;\n }\n return f;\n}\n\n/** Drop all cached features (e.g. on session shutdown/switch). */\nexport function clearDocFeatures(): void {\n cache.clear();\n cachedChars = 0;\n}\n\n/**\n * Set the cache cap in source chars. Docs larger than the cap are never\n * cached. Also used by tests to exercise eviction.\n */\nexport function setDocCacheCap(chars: number): void {\n capChars = Math.max(1, chars);\n while (cachedChars > capChars && cache.size > 0) {\n const k = cache.keys().next().value as string;\n cachedChars -= k.length;\n cache.delete(k);\n }\n}\n\n/** Cache occupancy — for diagnostics. */\nexport function docCacheInfo(): { entries: number; chars: number } {\n return { entries: cache.size, chars: cachedChars };\n}\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * Substring counting — the original baseline algorithm.\n * Exact, lowercased substring occurrence counts. Predictable but blind to\n * morphology, typos, and CJK word boundaries. Kept for backward compat and\n * as a deterministic reference.\n */\nexport const substringAlgorithm: SearchAlgorithm = {\n name: \"substring\",\n description: \"Exact substring counting (original baseline). Predictable, no normalization.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const terms = query.toLowerCase().trim().split(/\\s+/).filter((t) => t.length > 0);\n if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n return docs.map((d) => {\n const haystack = docFeatures(d.text).lower; // memoized across calls\n let score = 0;\n for (const term of terms) score += countOccurrences(haystack, term);\n return { ref: d.ref, score };\n });\n },\n};\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!needle) return 0;\n return haystack.split(needle).length - 1;\n}\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { tokenize } from \"../tokenizer.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * BM25 with stemming + CJK bigram tokenization.\n *\n * k1=1.2, b=0.75 (standard IR). IDF down-weights terms common across the\n * corpus; length normalization prevents long summaries from dominating by\n * raw term count. Stemming collapses English morphology\n * (compress/compressed/compression → ~compress).\n *\n * On the 32-block mixed EN/CJK benchmark: MRR 0.833 / R@1 0.833 / R@3 0.833\n * vs 0.797 / 0.792 / 0.792 for substring — better in isolation on every\n * metric, and the precision component of the hybrid default (see hybrid.ts).\n */\nexport const bm25Algorithm: SearchAlgorithm = {\n name: \"bm25\",\n description: \"BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const N = docs.length;\n const k1 = 1.2;\n const b = 0.75;\n const parsed = docs.map((d) => {\n const f = docFeatures(d.text); // memoized: tf + length, cached across calls\n return { id: d.ref, tf: f.tf, len: f.len };\n });\n const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);\n\n const qTerms = tokenize(query, { stem: true });\n if (qTerms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n const idf = new Map<string, number>();\n for (const t of new Set(qTerms)) {\n let df = 0;\n for (const d of parsed) if (d.tf.has(t)) df++;\n idf.set(t, Math.log(1 + (N - df + 0.5) / (df + 0.5)));\n }\n\n return parsed.map((d) => {\n let score = 0;\n for (const t of qTerms) {\n const f = d.tf.get(t) ?? 0;\n if (f === 0) continue;\n const idfT = idf.get(t) ?? 0;\n score += (idfT * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / (avgdl || 1)));\n }\n return { ref: d.id, score };\n });\n },\n};\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { charBigrams, CJK } from \"../tokenizer.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * Fuzzy character-bigram matching (Jaccard-style).\n *\n * Decomposes the query into character bigrams and measures overlap with\n * each doc. Robust to typos (tokan≈token), partial words, and works\n * uniformly across all scripts (CJK benefits most).\n *\n * Query-token gate — CJK gets its own length rule; Latin is frozen:\n * length >= 4 (any script) typo-tolerant bigram rescue needs a couple of\n * chars before it means anything; 2-3-char Latin tokens (\"to\", \"of\",\n * \"us\") are stop-word noise whose bigrams overlap nearly every doc.\n * length >= 2 && CJK Chinese/Japanese/Korean words are mostly\n * 2-character atomic units (登录/缓存/図表), so the Latin-style >= 4 rule\n * would lock the whole CJK query space out of this recall channel\n * (that gap is what bench \"缓存 → nothing\" exposed). Single CJK chars\n * stay excluded — one char cannot form a bigram, nothing to compare.\n *\n * On benchmark: lowest MRR of any single algorithm (0.795 — a hair under\n * substring's 0.797) — precision is weak, but it is the recall boost in the\n * hybrid default.\n */\nexport const fuzzyAlgorithm: SearchAlgorithm = {\n name: \"fuzzy\",\n description: \"Character bigram overlap. Typo-tolerant, script-agnostic, high recall.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n // Gate (see header): Latin short tokens are noise, 2-char CJK words\n // are real terms — admit the latter so 缓存/登录 reach the scorer.\n const qTokens = query.toLowerCase().split(/[\\s,]+/).filter((t) => t.length >= 4 || (t.length >= 2 && CJK.test(t)));\n if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n const qGrams = new Set<string>();\n for (const t of qTokens) for (const g of charBigrams(t)) qGrams.add(g);\n if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n return docs.map((d) => {\n const docGrams = docFeatures(d.text).grams; // memoized bigram set\n let hits = 0;\n for (const g of qGrams) if (docGrams.has(g)) hits++;\n return { ref: d.ref, score: hits / qGrams.size };\n });\n },\n};\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { bm25Algorithm } from \"./bm25.js\";\nimport { fuzzyAlgorithm } from \"./fuzzy.js\";\n\n/**\n * Hybrid: normalized BM25(stem) + fuzzy n-gram, weighted 0.7 / 0.3.\n *\n * BM25 supplies precision on real terms (with morphology + IDF + length\n * norm); fuzzy supplies recall on typos, partials, and cross-script.\n * Each component is max-normalized to [0,1] before weighting so their\n * scales are comparable regardless of corpus size.\n *\n * Benchmark (32 blocks, 48 mixed EN/CJK queries, final code — segmenter\n * tokenizer + CJK fuzzy gate):\n * substring MRR 0.797 R@1 0.792 R@3 0.792\n * bm25 MRR 0.833 R@1 0.833 R@3 0.833\n * fuzzy MRR 0.795 R@1 0.708 R@3 0.875\n * hybrid MRR 0.898 R@1 0.875 R@3 0.917 ← best on every metric\n * The weight ratio is robust: 0.6–0.8 for BM25 all score within 0.001 MRR.\n */\n\nconst W_BM25 = 0.7;\nconst W_FUZZY = 0.3;\n\nexport const hybridAlgorithm: SearchAlgorithm = {\n name: \"hybrid\",\n description: \"Weighted BM25(stem) + fuzzy n-gram. Default — best precision + recall.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const bm = bm25Algorithm.score(docs, query);\n const fz = fuzzyAlgorithm.score(docs, query);\n const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);\n const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);\n const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));\n const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));\n return docs.map((d) => ({\n ref: d.ref,\n score: W_BM25 * (bmMap.get(d.ref) ?? 0) + W_FUZZY * (fzMap.get(d.ref) ?? 0),\n }));\n },\n};\n","/**\n * Algorithm registry. Builtins are pre-registered; hosts may register\n * additional algorithms (e.g. an embedding-based semantic provider) via\n * registerSearchAlgorithm and reference them by name in SearchOptions.\n */\nimport type { AnySearchAlgorithm } from \"./types.js\";\nimport { substringAlgorithm } from \"./algorithms/substring.js\";\nimport { bm25Algorithm } from \"./algorithms/bm25.js\";\nimport { fuzzyAlgorithm } from \"./algorithms/fuzzy.js\";\nimport { hybridAlgorithm } from \"./algorithms/hybrid.js\";\n\nconst registry = new Map<string, AnySearchAlgorithm>();\n\nexport function registerSearchAlgorithm(algo: AnySearchAlgorithm): void {\n registry.set(algo.name, algo);\n}\n\nexport function getSearchAlgorithm(name: string): AnySearchAlgorithm | undefined {\n return registry.get(name);\n}\n\nexport function listSearchAlgorithms(): AnySearchAlgorithm[] {\n return [...registry.values()];\n}\n\n// Pre-register builtins. Hybrid is the default (see types.ts DEFAULT_ALGORITHM).\nregisterSearchAlgorithm(substringAlgorithm);\nregisterSearchAlgorithm(bm25Algorithm);\nregisterSearchAlgorithm(fuzzyAlgorithm);\nregisterSearchAlgorithm(hybridAlgorithm);\n","/**\n * Search type definitions.\n *\n * Two data sources are searchable:\n * - Compressed blocks (summary text; ref = \"b{id}\")\n * - Historical messages (original text from the append-only session log;\n * ref = \"m{NNNNN}\"). These let the model locate detail that compression\n * turned into a short summary — search to pinpoint, then decompress the\n * owning block for the full content.\n *\n * A SearchAlgorithm is a stateless scorer over a unified SearchDoc[]. Roles\n * carry a configurable weight (user intent > assistant reasoning > tool noise).\n */\n\n/** Where a searchable document came from. */\nexport type SearchDocKind = \"block\" | \"message\";\n\nexport type MessageRole = \"user\" | \"assistant\" | \"tool\";\n\n/** A unified searchable document — either a block summary or a message. */\nexport interface SearchDoc {\n kind: SearchDocKind;\n /** Stable ref for decompress: \"b3\" for a block, \"m00350\" for a message. */\n ref: string;\n /** Text this doc is scored against (topic+summary for blocks; content for messages). */\n text: string;\n /** For preview/title display. */\n title: string;\n /** Message role (messages only); undefined for blocks. Drives role weighting. */\n role?: MessageRole;\n /** Block owning this doc. For blocks: the block itself. For messages: the block\n * that compressed it (so the model knows which block to decompress for detail). */\n blockId?: string;\n /** Tier of the owning block (display + grouping). */\n tier?: number;\n /** Approx token size (for \"how big is this\" display). */\n tokens?: number;\n}\n\n/** Per-role score multipliers. Defaults favor user intent over tool noise. */\nexport interface RoleWeights {\n user?: number;\n assistant?: number;\n tool?: number;\n block?: number;\n}\n\nexport const DEFAULT_ROLE_WEIGHTS: Required<RoleWeights> = {\n user: 1.5,\n assistant: 1.0,\n tool: 0.6,\n block: 1.0,\n};\n\nexport interface ScoredBlock {\n ref: string;\n score: number;\n}\n\nexport interface SearchAlgorithm {\n name: string;\n description: string;\n score(docs: SearchDoc[], query: string): ScoredBlock[];\n}\n\nexport interface AsyncSearchAlgorithm {\n name: string;\n description: string;\n score(docs: SearchDoc[], query: string): Promise<ScoredBlock[]>;\n}\n\nexport type AnySearchAlgorithm = SearchAlgorithm | AsyncSearchAlgorithm;\n\nexport interface SearchResult {\n /** \"block\" or \"message\". */\n kind: SearchDocKind;\n /** Ref to pass to decompress: \"b3\" or \"m00350\". */\n ref: string;\n /** Owning block id (for messages: the block that compressed it). */\n blockId?: string;\n tier: number;\n score: number;\n title: string;\n preview: string;\n role?: MessageRole;\n tokens?: number;\n}\n\nexport interface SearchOptions {\n algorithm?: string;\n limit?: number;\n previewLength?: number;\n minScore?: number;\n /** Per-role weights (default DEFAULT_ROLE_WEIGHTS). */\n roleWeights?: RoleWeights;\n}\n\n/** Host-supplied historical message, turned into a message SearchDoc. */\nexport interface MessageInput {\n ref: string;\n role: MessageRole;\n text: string;\n tokens?: number;\n /** Block id that compressed this message (undefined if still visible). */\n blockId?: string;\n tier?: number;\n}\n\nexport const DEFAULT_ALGORITHM = \"hybrid\";\n","/**\n * searchBlocks — public search entry point.\n *\n * Scores a unified document set (block summaries + historical messages)\n * and returns ranked results. The model uses search to cheaply locate\n * detail that compression folded into summaries, then decompresses the\n * owning block for the full content.\n *\n * Two entry points:\n * - searchBlocks() — sync. Works for all lexical algorithms.\n * - searchBlocksAsync() — async. Also supports embedding-based semantic\n * algorithms whose score() returns a Promise.\n */\n\nimport type { CompressionState, CompressionBlock } from \"../types.js\";\nimport { getSearchAlgorithm } from \"./registry.js\";\nimport type { SearchDoc, ScoredBlock, MessageInput } from \"./types.js\";\nimport type { SearchResult, SearchOptions, RoleWeights } from \"./types.js\";\nimport { DEFAULT_ALGORITHM, DEFAULT_ROLE_WEIGHTS } from \"./types.js\";\n\n/** Build SearchDoc[] from all blocks (active AND inactive) of the state. */\nexport function blockDocs(state: CompressionState): SearchDoc[] {\n return state.blocks.map((b: CompressionBlock): SearchDoc => ({\n kind: \"block\",\n ref: b.blockId,\n text: `${b.topic ?? \"\"} ${b.summary ?? \"\"}`,\n title: b.topic ?? b.blockId,\n blockId: b.blockId,\n tier: b.tier ?? 1,\n tokens: b.compressedTokens,\n }));\n}\n\n/**\n * Build SearchDoc[] from historical messages supplied by the host. The host\n * (pai-acp) reads these from the append-only session log — they include the\n * original text of messages that compression later folded into block summaries.\n *\n * `ownerOf(ref)` maps a message ref to the block id that compressed it, so a\n * message hit tells the model exactly which block to decompress for detail.\n */\nexport function messageDocs(msgs: MessageInput[]): SearchDoc[] {\n return msgs.map((m): SearchDoc => ({\n kind: \"message\",\n ref: m.ref,\n text: m.text,\n title: `${m.role}: ${m.text.slice(0, 60)}`,\n role: m.role,\n blockId: m.blockId,\n tier: m.tier,\n tokens: m.tokens,\n }));\n}\n\nfunction applyRoleWeight(scored: ScoredBlock[], docs: SearchDoc[], rw: Required<RoleWeights>): ScoredBlock[] {\n if (docs.length === 0) return scored;\n const docByRef = new Map(docs.map((d) => [d.ref, d]));\n return scored.map((s) => {\n const doc = docByRef.get(s.ref);\n if (!doc) return s;\n const w =\n doc.kind === \"message\"\n ? doc.role === \"user\"\n ? rw.user\n : doc.role === \"assistant\"\n ? rw.assistant\n : rw.tool\n : rw.block;\n return { ref: s.ref, score: s.score * w };\n });\n}\n\nfunction runSearch(\n docs: SearchDoc[],\n query: string,\n options: SearchOptions,\n): SearchResult[] | Promise<SearchResult[]> {\n const limit = options.limit ?? 10;\n const previewLength = options.previewLength ?? 200;\n const minScore = options.minScore ?? 0.01;\n const algoName = options.algorithm ?? DEFAULT_ALGORITHM;\n const rw = { ...DEFAULT_ROLE_WEIGHTS, ...options.roleWeights };\n\n const algo = getSearchAlgorithm(algoName);\n if (!algo) return [];\n if (docs.length === 0) return [];\n\n const scoredOrPromise = algo.score(docs, query);\n\n const buildResults = (weighted: ScoredBlock[]): SearchResult[] => {\n const byRef = new Map(docs.map((d) => [d.ref, d]));\n return weighted\n .map((s): SearchResult | null => {\n const doc = byRef.get(s.ref);\n if (!doc) return null;\n return {\n kind: doc.kind,\n ref: doc.ref,\n blockId: doc.blockId,\n tier: doc.tier ?? 1,\n score: s.score,\n title: doc.title,\n preview: makePreview(doc.text, query, previewLength),\n role: doc.role,\n tokens: doc.tokens,\n };\n })\n .filter((r): r is SearchResult => r !== null && r.score >= minScore)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n };\n\n if (scoredOrPromise instanceof Promise) {\n return scoredOrPromise.then((raw) => buildResults(applyRoleWeight(raw, docs, rw)));\n }\n return buildResults(applyRoleWeight(scoredOrPromise, docs, rw));\n}\n\n/** Sync entry — throws for async algorithms. Pass docs from blockDocs() + messageDocs(). */\nexport function searchBlocks(docs: SearchDoc[], query: string, options: SearchOptions = {}): SearchResult[] {\n const result = runSearch(docs, query, options);\n if (result instanceof Promise) {\n throw new Error(\n `searchBlocks: algorithm \"${options.algorithm ?? DEFAULT_ALGORITHM}\" is async (e.g. semantic). Use searchBlocksAsync() instead.`,\n );\n }\n return result;\n}\n\nexport { clearDocFeatures, docCacheInfo, docFeatures, setDocCacheCap } from \"./doc-cache.js\";\nexport type { DocFeatures } from \"./doc-cache.js\";\n\nexport async function searchBlocksAsync(docs: SearchDoc[], query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n return await runSearch(docs, query, options);\n}\n\n/**\n * Preview centered on the first query-term hit (case-insensitive).\n * Falls back to the head when no term hits.\n */\nfunction makePreview(text: string, query: string, len: number): string {\n if (!text) return \"\";\n const terms = query.toLowerCase().trim().split(/\\s+/).filter((t) => t.length > 1);\n if (terms.length === 0) return text.slice(0, len);\n\n const lower = text.toLowerCase();\n let hitIdx = -1;\n for (const term of terms) {\n const idx = lower.indexOf(term);\n if (idx >= 0) {\n hitIdx = idx;\n break;\n }\n }\n\n if (hitIdx < 0) return text.slice(0, len);\n\n const half = Math.max(0, Math.floor(len / 2) - 10);\n const start = Math.max(0, hitIdx - half);\n const end = Math.min(text.length, start + len);\n const prefix = start > 0 ? \"…\" : \"\";\n const suffix = end < text.length ? \"…\" : \"\";\n return prefix + text.slice(start, end).trim() + suffix;\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'\nimport {\n CompactionId,\n compactCheckpointSource,\n toolPairingBalancedAfter,\n toolPairingBalancedBefore,\n} from '@deepseek-ai/dsh-compaction'\nimport { createAssistantMessage, createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { defaultCountTokens } from 'acp-kernel'\nimport { extractEventText, extractText, toolCallIdOfResultEvent } from './messages.ts'\nimport { hostPriceEvent } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\n\n/**\n * A surface sequence number as the INSTALLED `dsh-session` sees it. On the\n * alpha line dsh-session brands these as `SessionSeq` (a branded `number`,\n * see dsh-session types.d.ts); on the rc.6 baseline they are plain `number`.\n * Deriving the element type from `Session['surface']` keeps this module\n * type-correct against BOTH without naming the alpha-only brand — which does\n * not exist on rc.6, so naming it would break the rc.6 baseline typecheck.\n * `as SurfaceSeq` below is the single admission point: a plain `number` that a\n * caller (model ref, ledger field) produces is admitted as a surface seq only\n * at the exact write/index site that the installed dsh-session brands.\n */\ntype SurfaceSeq = Session['surface']['nodes'][number]\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n /** The block's short label (kernel `CompressionBlock.topic`), when the compress request carried one. */\n readonly topic?: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n /** Compression tier: 1 (message range), 2 (distills tier-1 blocks), 3 (distills tier-2 blocks). Legacy blocks default to 1. */\n readonly tier: 1 | 2 | 3\n /** Compaction ids of the blocks this block distilled (parents). Empty for tier-1 blocks. */\n readonly parentBlockIds: readonly string[]\n /** The acp-kernel block id (`bN`) created for this transaction — absent for legacy blocks (synthesised by order). */\n readonly kernelBlockId?: string\n /** The surface seq of this block's checkpoint summary node (derived from the log; null when the node is gone). */\n readonly summarySeq?: number\n /** The kernel block's raw direct/effective message ids at creation (recorded since the tier feature; absent for legacy). */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n /** Unix epoch ms of the compaction/summary event. */\n readonly createdAt: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/**\n * Reject a second concurrent compaction for the same session.\n *\n * Compaction is synchronous and a session is single-writer, so a\n * `compaction/start` with NO matching `compaction/end` in the durable log can\n * only be a stale leftover from a prior run that died mid-write (a hard kill,\n * not a caught throw — every caught throw is paired with a compensating\n * `compaction/end` in runCompactionTransaction). Such a leftover must NOT\n * permanently block every later compress call: this treats it as stale,\n * surfaces it once, and lets a new compaction proceed. The old \"already\n * active\" throw only fired when a genuine concurrent compaction existed,\n * which the synchronous single-writer premise makes impossible.\n */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n console.warn('billion-context-dsh: clearing stale compaction flag — found a compaction/start with no matching compaction/end')\n }\n}\n\n/**\n * Whether the surface node at `seq` projects to CoreMessage(s) whose ref key\n * is the bare seq — user messages, tool results, and text-only or SINGLE\n * tool-call assistant messages all do. Multi-tool-call assistant messages\n * project to `${seq}#${callId}` ids (projectEvent) and therefore carry NO\n * bare-`${seq}` ref, so compress's byRaw lookup can never resolve them as\n * range edges. resolveSurfaceRange treats such edges as unbalanced and shifts\n * them to the nearest clean cut.\n */\nfunction hasPlainRef(session: Session, seq: number): boolean {\n const event = eventAtOf(session, seq)\n if (event === undefined) return false\n switch (event.type) {\n case 'user/message':\n case 'tool/result':\n return extractEventText(event).trim().length > 0\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = Array.isArray(content)\n ? content.filter(\n (block) => block !== null && typeof block === 'object' && (block as { type?: string }).type === 'tool-call',\n )\n : []\n if (calls.length > 1) return false\n // One tool-call: projectEvent emits a bare-seq CoreMessage unconditionally.\n // Zero: only when the text is non-empty.\n return calls.length === 1 || extractEventText(event).trim().length > 0\n }\n default:\n return false\n }\n}\n\n/**\n * A requested range whose EVERY live message was already shadowed by one or\n * more blocks. The compress tool catches this and reports the range as already\n * compressed (with the covering block ids) instead of folding block summary\n * nodes as plain messages or erroring out. Distillation stays an explicit act:\n * target a LIVE checkpoint seq directly to distill (tier 2/3).\n */\nexport class AlreadyCompressedRangeError extends Error {\n constructor(\n readonly start: number,\n readonly end: number,\n readonly coveringBlockIds: readonly string[],\n ) {\n super(\n `billion-context-dsh: seq ${start}..${end} already compressed — `\n + 'no live content remains in that span',\n )\n this.name = 'AlreadyCompressedRangeError'\n }\n}\n\ntype StaleRangeRecovery =\n | { kind: 'ok'; start: number; end: number }\n | { kind: 'already-compressed'; coveringBlockIds: string[] }\n | { kind: 'unresolvable'; failedEdge: number }\n\n/**\n * Rebuild a requested range whose edges are no longer on the current surface.\n * The dominant cause is staleness: the seqs came from an older nudge table or\n * a previous compress result, and an earlier compression SHADOWED them (they\n * stay in the append-only log, but are gone from the surface). The recovery:\n *\n * 1. An edge that does not exist in the log at all (invented, or from another\n * session) is unresolvable — there is no way to guess what it meant.\n * 2. The still-LIVE surface nodes inside the requested span, in VALUE order\n * (the surface can be locally non-monotonic after replacements, so value\n * order is the only coherent span). If there are none, the whole span was\n * already compressed → 'already-compressed' with the covering block ids.\n * 3. Otherwise the range snaps to the first..last live PLAIN node in the\n * span. Block checkpoint nodes are deliberately excluded: distilling a\n * block on a STALE reference would silently change block structure the\n * model never intended to touch — distillation requires targeting a live\n * checkpoint seq directly.\n */\nfunction recoverStaleRange(session: Session, start: number, end: number): StaleRangeRecovery {\n if (eventAtOf(session, start) === undefined || eventAtOf(session, end) === undefined) {\n const failedEdge = eventAtOf(session, start) === undefined ? start : end\n return { kind: 'unresolvable', failedEdge }\n }\n const liveInside = session.surface.nodes\n .filter((seq) => seq >= start && seq <= end)\n .sort((a, b) => a - b)\n const plain = liveInside.filter((seq) => !isCheckpointNode(eventAtOf(session, seq)!))\n if (plain.length === 0) {\n const coveringBlockIds = rebuildBlockLedger(sessionEventsOf(session))\n .filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end))\n .map((entry) => entry.blockId)\n return { kind: 'already-compressed', coveringBlockIds }\n }\n return { kind: 'ok', start: plain[0]!, end: plain[plain.length - 1]! }\n}\n\nexport interface ResolvedSurfaceRange {\n readonly start: number\n readonly end: number\n /**\n * True when the requested edges were not on the current surface and were\n * remapped to the still-live content of the requested span (an earlier\n * compression shadowed them). Callers surface this so the model sees what\n * was actually compressed instead of silently shadowing a different span.\n */\n readonly recovered?: boolean\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to a\n * tool-pairing-balanced range whose boundaries carry a bare-seq ref. Reversed\n * ranges throw. An edge that sits inside a tool-call/result pair — or on a\n * multi-tool-call assistant message that has no bare-seq ref — is first nudged\n * inward to the nearest clean cut; if that collapses the range (e.g. the model\n * asked for a SINGLE tool result, which can never be balanced alone), the\n * range EXPANDS outward to the enclosing clean pair instead — a lone tool\n * message is almost always a \"consumed output\" the model genuinely wants to\n * compress. The returned range is what a caller should actually shadow.\n *\n * Missing edges are NOT an immediate error: the seqs were probably shadowed by\n * an earlier compression (stale nudge table / old compress result). The span\n * is rebuilt from its still-live remainder via recoverStaleRange — a fully\n * shadowed span throws AlreadyCompressedRangeError, a genuinely unknown edge\n * throws the not-in-surface guidance error. The returned range is what a\n * caller should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): ResolvedSurfaceRange {\n const nodes = session.surface.nodes\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n let requestedStartIdx = nodes.indexOf(start as SurfaceSeq)\n let requestedEndIdx = nodes.indexOf(end as SurfaceSeq)\n let recovered = false\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n const stale = recoverStaleRange(session, start, end)\n if (stale.kind === 'unresolvable') {\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + `edge seq ${stale.failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n if (stale.kind === 'already-compressed') {\n throw new AlreadyCompressedRangeError(start, end, stale.coveringBlockIds)\n }\n start = stale.start\n end = stale.end\n recovered = true\n requestedStartIdx = nodes.indexOf(start as SurfaceSeq)\n requestedEndIdx = nodes.indexOf(end as SurfaceSeq)\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n // Unreachable in practice (recovery returns live nodes), but never let\n // a negative index reach the balancing passes.\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + 'consult acp_status for the current surface range',\n )\n }\n }\n if (requestedStartIdx > requestedEndIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // Belt-and-braces: the surface can be locally out of order after surface\n // replacements, so index order alone does not guarantee value order.\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // A boundary must be BOTH tool-pairing-balanced AND carry a bare-seq ref.\n const cleanBefore = (index: number): boolean =>\n toolPairingBalancedBefore(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n const cleanAfter = (index: number): boolean =>\n toolPairingBalancedAfter(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n let startIdx = requestedStartIdx\n let endIdx = requestedEndIdx\n // First pass: nudge inward to the nearest clean cuts.\n while (startIdx <= endIdx && !cleanBefore(startIdx)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !cleanAfter(endIdx)) {\n endIdx -= 1\n }\n if (startIdx <= endIdx && nodes[startIdx]! <= nodes[endIdx]!) {\n return recovered\n ? { start: nodes[startIdx]!, end: nodes[endIdx]!, recovered: true }\n : { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n // A recovered span NEVER expands across block checkpoints: the model's\n // requested edges were stale, so growing the span into block territory could\n // fold content it never intended to touch. If the live remainder cannot be\n // balanced by shrinking alone, give up with guidance instead.\n if (recovered) {\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced live remainder around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n }\n // Second pass: the inward pass collapsed (a lone tool message) — expand\n // outward from the REQUESTED span to the smallest clean enclosing pair.\n startIdx = requestedStartIdx\n endIdx = requestedEndIdx\n while (startIdx > 0 && !cleanBefore(startIdx)) {\n startIdx -= 1\n }\n while (endIdx < nodes.length - 1 && !cleanAfter(endIdx)) {\n endIdx += 1\n }\n // Value order guard: the surface is locally non-monotonic after replacements\n // (a checkpoint seq inserted ahead of older residual nodes), so index order\n // alone is not enough — never return a span whose end seq is numerically\n // BEFORE its start seq. The caller (nudge / compress) skips such a span.\n if (cleanBefore(startIdx) && cleanAfter(endIdx) && nodes[startIdx]! <= nodes[endIdx]!) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start as SurfaceSeq)\n const endIdx = nodes.indexOf(end as SurfaceSeq)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n /** Short block label (kernel `CompressionBlock.topic`) — persisted so a restarted engine rehydrates it. */\n readonly topic?: string\n /** Compression tier of this block (default 1). */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created by the kernel for this transaction. */\n readonly kernelBlockId?: string\n /** Compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /** The kernel block's direct/effective message ids (raw CoreMessage ids) — recorded for faithful rehydration. */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n}\n\n/**\n * ACP tier extension fields carried on `compaction/summary` events. The\n * upstream dsh-compaction event type does not know them, so reads and writes\n * go through this precise intersection (never `any`).\n */\nexport interface AcpCompactionSummaryFields {\n /** Compression tier (1/2/3) — 1 = message range, 2 = distills tier-1, 3 = distills tier-2. */\n readonly tier?: 1 | 2 | 3\n /** Short block label (kernel `CompressionBlock.topic`) — the acp_status block title. */\n readonly topic?: string\n /** The acp-kernel block id (`bN`) created for this transaction. */\n readonly kernelBlockId?: string\n /** Durable compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /**\n * The kernel block's direct message ids (raw CoreMessage ids) at creation —\n * recorded so a restarted engine rehydrates the SAME coverage (a tier-2\n * block's coverage is its parents' originals, not the checkpoint node).\n */\n readonly directMessageIds?: readonly string[]\n /** The kernel block's effective message ids (raw CoreMessage ids) at creation. */\n readonly effectiveMessageIds?: readonly string[]\n}\n\ntype CompactionSummaryData = SessionEventMap['compaction/summary']\n\n/** Read a `compaction/summary` event's data including the ACP tier extension fields. */\nexport function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpCompactionSummaryFields {\n return event.data as CompactionSummaryData & AcpCompactionSummaryFields\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(sessionEventsOf(session))\n const turn = findOpenTurn(sessionEventsOf(session))\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n // Fail fast on an unresolvable range BEFORE writing any durable event. If we\n // let the host's surfaceOp replace throw below, we would first have recorded\n // compaction/start and compaction/summary and then leave a dangling start\n // (poisoning every later compress call) plus an orphan summary in the ledger.\n // Validating the edges up front keeps a bad range a clean, zero-write no-op.\n if (input.start > input.end) {\n throw new Error(`billion-context-dsh: reversed range ${input.start}..${input.end}`)\n }\n if (eventAtOf(session, input.start) === undefined || eventAtOf(session, input.end) === undefined) {\n const failedEdge = eventAtOf(session, input.start) === undefined ? input.start : input.end\n throw new Error(\n `billion-context-dsh: seq ${input.start}..${input.end} not in the current surface — `\n + `edge seq ${failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n\n try {\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n tier: input.tier ?? 1,\n ...(input.kernelBlockId === undefined ? {} : { kernelBlockId: input.kernelBlockId }),\n ...(input.topic === undefined ? {} : { topic: input.topic }),\n ...(input.parentBlockIds === undefined || input.parentBlockIds.length === 0\n ? {}\n : { parentBlockIds: [...input.parentBlockIds] }),\n ...(input.directMessageIds === undefined ? {} : { directMessageIds: [...input.directMessageIds] }),\n ...(input.effectiveMessageIds === undefined ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }),\n } as CompactionSummaryData & AcpCompactionSummaryFields).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start as SurfaceSeq, end: input.end as SurfaceSeq },\n sourceEventSeqs: [...input.shadowedSeqs] as SurfaceSeq[],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n } catch (error) {\n // Backstop: if any append AFTER compaction/start throws (the host rejects\n // the surfaceOp replace for a reason we did not pre-validate, the summary\n // serialization fails, …), write a compensating compaction/end so the\n // durable log never holds a dangling start that would block every later\n // compress call. A leftover compaction/summary with no applied replace is\n // surfaced as an orphan ledger block, which is preferable to a hard\n // permanent block.\n try {\n session.append('compaction/end', { compactionId, turn })\n } catch (compensateError) {\n // The durable log may now hold a dangling compaction/start; the next\n // assertNoActiveCompaction call heals it. Never mask the original error.\n console.warn('billion-context-dsh: failed to write a compensating compaction/end', compensateError)\n }\n throw error\n }\n return { compactionId, seqs }\n}\n\n/** The seq of a compaction's checkpoint summary node in the log (visible or shadowed). */\nfunction summarySeqOfCompaction(events: readonly SessionEvent[], compactionId: string): number | null {\n for (const event of events) {\n if (event.type !== 'user/message') continue\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin === 'compact' && source.compactionId === compactionId) return event.seq\n }\n return null\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = readCompactionSummary(event)\n // Blocks written before the token-accounting fix carry shadowedTokenCount\n // 0; backfill from the shadowed originals still in the log so acp_status\n // reports real reclaimed tokens.\n let shadowedTokenCount = data.shadowedTokenCount\n if (shadowedTokenCount === 0) {\n shadowedTokenCount = 0\n for (const seq of data.shadowedSeqs) {\n const original = events[seq]\n if (original !== undefined) shadowedTokenCount += defaultCountTokens(extractEventText(original))\n }\n }\n const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1\n const parentBlockIds: string[] = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : []\n const directMessageIds: string[] | undefined = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : undefined\n const effectiveMessageIds: string[] | undefined = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : undefined\n const summarySeq = summarySeqOfCompaction(events, data.compactionId)\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n ...(typeof data.topic === 'string' ? { topic: data.topic } : {}),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n tier,\n parentBlockIds,\n ...(typeof data.kernelBlockId === 'string' ? { kernelBlockId: data.kernelBlockId } : {}),\n ...(summarySeq === null ? {} : { summarySeq }),\n ...(directMessageIds === undefined ? {} : { directMessageIds }),\n ...(effectiveMessageIds === undefined ? {} : { effectiveMessageIds }),\n createdAt: event.time,\n })\n }\n return ledger\n}\n\n/** One self-computed compressible span of the current surface. */\nexport interface SeqCompressibleRange {\n readonly start: number\n readonly end: number\n readonly count: number\n readonly tokens: number\n /** Share of messages that are tool messages (tool-call or tool-result), 0-100 — kernel `toolPct` parity. */\n readonly toolPct: number\n}\n\n/** Whether a surface message event is a tool message (tool-call or tool-result) — kernel `isToolMessage` parity. */\nfunction isToolEvent(event: SessionEvent): boolean {\n if (event.type === 'tool/result') return true\n if (event.type !== 'assistant/message') return false\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n return Array.isArray(content) && content.some((block) => (block as { type?: unknown })?.type === 'tool-call')\n}\n\n/** Whether a surface user message is a compaction checkpoint node (already compressed). */\nfunction isCheckpointNode(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\n/** Tool-call ids carried by one assistant surface message. */\nfunction toolCallIdsOfEvent(event: SessionEvent): string[] {\n if (event.type !== 'assistant/message') return []\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n if (!Array.isArray(content)) return []\n const ids: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; id?: unknown }\n if (b.type === 'tool-call' && typeof b.id === 'string') ids.push(b.id)\n }\n return ids\n}\n\n/**\n * Provider/model to stamp on a synthetic empty assistant pruning node.\n */\nfunction assistantProviderModel(event: SessionEvent): { provider: string; model: string } {\n if (event.type === 'assistant/message') {\n const message = (event.data as { message?: { source?: { provider?: unknown; model?: unknown } } }).message\n return {\n provider: typeof message?.source?.provider === 'string' ? message.source.provider : 'billion-context-dsh',\n model: typeof message?.source?.model === 'string' ? message.source.model : 'surface-prune',\n }\n }\n return { provider: 'billion-context-dsh', model: 'surface-prune' }\n}\n\n/**\n * Durable model-free prune: append `compaction/prune` as the shadow price, then\n * replace the given surface seqs with either a user message carrying `text`\n * (used for compress call/result hiding, so the model still sees the tool\n * outcome) or an EMPTY assistant message (used for orphan cleanup, which DSH\n * derives to nothing). The originals remain in the append-only log.\n */\nfunction hideSurfaceSeqs(\n session: Session,\n seqs: readonly number[],\n provider: string,\n model: string,\n text?: string,\n priceEvent: (event: SessionEvent) => number = hostPriceEvent,\n): void {\n if (seqs.length === 0) return\n const start = seqs[0]!\n const end = seqs[seqs.length - 1]!\n let shadowedTokenCount = 0\n for (const seq of seqs) {\n const event = eventAtOf(session, seq)\n // The prune claim MUST speak the host's token vocabulary (rule 12): the\n // default `hostPriceEvent` is the exact mirror of the host estimator.\n // NEVER defaultCountTokens — that overdraws the meter on CJK (#54).\n if (event !== undefined) shadowedTokenCount += priceEvent(event)\n }\n session.append('compaction/prune', {\n shadowedRange: { start: start as SurfaceSeq, end: end as SurfaceSeq },\n shadowedSeqs: [...seqs] as SurfaceSeq[],\n shadowedTokenCount,\n })\n if (text !== undefined) {\n session.append('user/message', createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'billion-context-dsh' },\n }), {\n surfaceOp: { op: 'replace', start: start as SurfaceSeq, end: end as SurfaceSeq },\n sourceEventSeqs: [...seqs] as SurfaceSeq[],\n })\n return\n }\n session.append('assistant/message', {\n turn: findOpenTurn(sessionEventsOf(session)) ?? 0,\n step: 0,\n message: createAssistantMessage({ content: [], source: { provider, model } }),\n }, {\n surfaceOp: { op: 'replace', start: start as SurfaceSeq, end: end as SurfaceSeq },\n sourceEventSeqs: [...seqs] as SurfaceSeq[],\n })\n}\n\n/**\n * Hide one successful `compress` tool's call/result pair after its tool/result\n * has been logged. The durable compaction summary is inserted BEFORE the\n * current tool result (the compress tool runs mid-turn), so leaving the pair on\n * the surface would produce `assistant(tool_calls) → user(summary) →\n * tool(result)` — rejected by strict providers. Replacing both nodes with a\n * plain user message (the result text) removes the pair from the derived\n * surface without touching the compaction block.\n */\nexport function hideCompressToolPair(session: Session, callId: string, resultSeq?: number): boolean {\n let callSeq: number | null = null\n const events = sessionEventsOf(session)\n for (const event of events) {\n if (event.type !== 'assistant/message') continue\n if (toolCallIdsOfEvent(event).includes(callId)) {\n callSeq = event.seq\n break\n }\n }\n if (callSeq === null) return false\n // Only hide a node that carries EXACTLY the compress call. Hiding a\n // multi-call node replaces the whole assistant message, which would orphan\n // the sibling calls' results (their call ids vanish with the node).\n const callNodeIds = toolCallIdsOfEvent(events[callSeq]!)\n if (callNodeIds.length !== 1 || callNodeIds[0] !== callId) return false\n let resolvedResultSeq = resultSeq ?? null\n if (resolvedResultSeq === null) {\n for (const event of events) {\n if (event.type === 'tool/result' && toolCallIdOfResultEvent(event) === callId) {\n resolvedResultSeq = event.seq\n break\n }\n }\n }\n if (resolvedResultSeq === null) return false\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(callSeq as SurfaceSeq)\n const endIdx = nodes.indexOf(resolvedResultSeq as SurfaceSeq)\n // Only hide an actually adjacent pair; never shadow unrelated messages that\n // happen to sit between a stale call and result.\n if (startIdx < 0 || endIdx < 0 || endIdx - startIdx !== 1) return false\n const { provider, model } = assistantProviderModel(events[callSeq]!)\n const resultEvent = events[resolvedResultSeq]\n const resultText = resultEvent === undefined ? '' : extractEventText(resultEvent)\n hideSurfaceSeqs(session, [callSeq, resolvedResultSeq], provider, model, resultText.trim().length > 0 ? resultText : undefined)\n return true\n}\n\n/**\n * Surface-level orphan cleanup: hide tool/result nodes with no matching call,\n * assistant tool-call nodes whose calls all lack results, and \"broken pairs\"\n * whose result is NOT adjacent to the call node on the surface (a\n * non-tool/result node — typically the compaction summary a buggy older\n * version inserted between a compress call and its result — sits between\n * them). A single orphan result corrupts the whole tool-pairing balance cache\n * (every range resolve throws), orphan calls fragment large ranges into tiny\n * uncompressed fragments, and a broken pair cannot serialize for strict\n * providers — the mechanisms behind issue #18's \"only ~28 tokens visible\".\n * Uses the same durable prune protocol as `hideSurfaceSeqs`, so the removed\n * nodes stay recoverable from the append-only log.\n */\nexport function stripOrphanedSurfaceToolMessages(\n session: Session,\n inFlightCallIds: ReadonlySet<string> = new Set(),\n): number {\n const nodes = session.surface.nodes\n const callIdsBySeq = new Map<number, string[]>()\n // callId -> surface position of the assistant node carrying it, for calls\n // whose result has not been decided yet.\n const open = new Map<string, { seq: number; index: number }>()\n const orphanResultSeqs: number[] = []\n // result seq -> call node seq, for pairs whose result landed but is not\n // adjacent to the call node on the surface.\n const brokenResults = new Map<number, number>()\n for (let index = 0; index < nodes.length; index += 1) {\n const seq = nodes[index]!\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n if (event.type === 'assistant/message') {\n const ids = toolCallIdsOfEvent(event)\n if (ids.length === 0) continue\n callIdsBySeq.set(seq, ids)\n for (const id of ids) {\n if (!open.has(id)) open.set(id, { seq, index })\n }\n } else if (event.type === 'tool/result') {\n const id = toolCallIdOfResultEvent(event)\n if (id === null) continue\n const call = open.get(id)\n if (call === undefined) {\n orphanResultSeqs.push(seq)\n continue\n }\n // A pair is healthy only when every node between the call and this\n // result is a tool/result of the SAME call node (multi-call messages).\n // Any other node in between makes the pair unserializable for strict\n // providers: prune both ends.\n const callNodeIds = callIdsBySeq.get(call.seq)\n let adjacent = false\n if (callNodeIds !== undefined) {\n adjacent = true\n for (let mid = call.index + 1; mid < index; mid += 1) {\n const midEvent = eventAtOf(session, nodes[mid]!)\n if (midEvent === undefined || midEvent.type !== 'tool/result') {\n adjacent = false\n break\n }\n const midId = toolCallIdOfResultEvent(midEvent)\n if (midId === null || !callNodeIds.includes(midId)) {\n adjacent = false\n break\n }\n }\n }\n open.delete(id)\n if (!adjacent) brokenResults.set(seq, call.seq)\n }\n }\n // call node seq -> ids of that node whose result is broken (non-adjacent).\n const brokenIdsByCallSeq = new Map<number, string[]>()\n for (const [resultSeq, callSeq] of brokenResults) {\n const id = toolCallIdOfResultEvent(eventAtOf(session, resultSeq)!)\n if (id !== null) {\n const list = brokenIdsByCallSeq.get(callSeq) ?? []\n list.push(id)\n brokenIdsByCallSeq.set(callSeq, list)\n }\n }\n const hiddenSet = new Set<number>(orphanResultSeqs)\n for (const resultSeq of brokenResults.keys()) hiddenSet.add(resultSeq)\n for (const [callSeq, ids] of callIdsBySeq) {\n const brokenIds = brokenIdsByCallSeq.get(callSeq)\n // Only hide an assistant node when NONE of its calls are usable: every id\n // must lack a result (open) or have a broken result. A mixed node (some\n // healthy results) must stay so its valid results are not orphaned by\n // hiding the call — and a node carrying an in-flight call can never be\n // pruned, or the pending result lands orphaned.\n const allUnpaired = !ids.some((candidate) => inFlightCallIds.has(candidate))\n && ids.every((candidate) => open.has(candidate) || brokenIds?.includes(candidate) === true)\n if (allUnpaired) hiddenSet.add(callSeq)\n }\n const hidden = [...hiddenSet].sort((a, b) => a - b)\n let count = 0\n for (const seq of hidden) {\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n const { provider, model } = assistantProviderModel(event)\n hideSurfaceSeqs(session, [seq], provider, model)\n count += 1\n }\n return count\n}\n\n/**\n * All tool-call ids currently visible on the surface with no matching\n * tool/result yet — the in-flight calls of the current step. Sibling tools\n * called in the same assistant message as `compress` are in-flight too, so\n * `handleCompress` must protect the whole set (not just its own call id) or\n * the sibling call would be pruned as an orphan and its result would land\n * orphaned (HTTP 400 until the next cleanup).\n */\nexport function openToolCallIds(session: Session): Set<string> {\n const open = new Set<string>()\n for (const seq of session.surface.nodes) {\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n if (event.type === 'assistant/message') {\n for (const id of toolCallIdsOfEvent(event)) open.add(id)\n } else if (event.type === 'tool/result') {\n const id = toolCallIdOfResultEvent(event)\n if (id !== null) open.delete(id)\n }\n }\n return open\n}\n\n/**\n * Schedule `hideCompressToolPair` on the microtask queue. `session.append`\n * is NOT reentrant: running it synchronously inside a `session/event`\n * listener (while the outer append is still publishing) throws \"session\n * append cannot reenter while another append is being published\" on live,\n * store-attached sessions, and the dispatcher silently swallows the error —\n * so a synchronous hide is a silent no-op in production. A microtask drains\n * after the current append fully publishes and before the agent loop resumes,\n * so the pair is hidden before the next request is built.\n */\nexport function deferCompressPairHide(\n session: Session,\n callId: string,\n resultSeq: number,\n onError?: (error: unknown) => void,\n): void {\n queueMicrotask(() => {\n try {\n hideCompressToolPair(session, callId, resultSeq)\n } catch (error) {\n onError?.(error)\n }\n })\n}\n\n/**\n * Compute compressible spans directly from the surface — independent of the\n * kernel's ref map, which can drift after surface replacements in long\n * sessions and hide large tool results from the nudge range table. Skips the\n * recent protected tail, the last user message, and compaction checkpoints;\n * edges are then balanced through resolveSurfaceRange. Ranges are ordered\n * oldest-first (stable across turns — matches the kernel's `oldest first`).\n * UPSTREAM: this self-computation is a labeled workaround for kernel\n * ref-map drift after surface replacements (AGENTS.md rule 11) — drop it and\n * use kernel compressibleRanges once the drift is fixed upstream.\n */\nexport function buildCompressibleSeqRanges(\n session: Session,\n opts: { preserveRecent?: number } = {},\n): SeqCompressibleRange[] {\n // Orphan tool messages corrupt the pairing balance cache and fragment every\n // large span. Prune them before scanning so the range table reflects the\n // actually compressible surface (issue #18).\n stripOrphanedSurfaceToolMessages(session)\n const nodes = session.surface.nodes\n const preserve = opts.preserveRecent ?? 5\n const protectedSeqs = new Set<number>()\n // `nodes.slice(-preserve)` would protect EVERYTHING when preserve is 0\n // (`slice(-0) === slice(0)`) — guard so 0 means \"no recent protection\".\n if (preserve > 0) {\n for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq)\n }\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const event = eventAtOf(session, nodes[index]!)\n if (event?.type === 'user/message' && !isCheckpointNode(event)) {\n protectedSeqs.add(nodes[index]!)\n break\n }\n }\n const raw: Array<{ start: number; end: number; count: number; tokens: number; toolCount: number }> = []\n let cur: { start: number; end: number; count: number; tokens: number; toolCount: number } | null = null\n const flush = (): void => {\n if (cur !== null) raw.push(cur)\n cur = null\n }\n for (const seq of nodes) {\n const event = eventAtOf(session, seq)\n if (event === undefined || protectedSeqs.has(seq) || isCheckpointNode(event)) {\n flush()\n continue\n }\n // Surface nodes can be locally out of order after surface replacements in\n // long sessions; a node with a SMALLER seq than the running segment would\n // produce a reversed range (e.g. 110295..106762). Break the segment so\n // ranges always stay start <= end.\n if (cur !== null && seq < cur.start) {\n flush()\n cur = null\n }\n const tokens = defaultCountTokens(extractEventText(event))\n const isTool = isToolEvent(event)\n if (cur === null) {\n cur = { start: seq, end: seq, count: 1, tokens, toolCount: isTool ? 1 : 0 }\n } else {\n cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens, toolCount: cur.toolCount + (isTool ? 1 : 0) }\n }\n }\n flush()\n const out: SeqCompressibleRange[] = []\n for (const range of raw) {\n try {\n const { start, end } = resolveSurfaceRange(session, range.start, range.end)\n const count = range.count\n out.push({\n start,\n end,\n count,\n tokens: range.tokens,\n toolPct: count > 0 ? Math.round((range.toolCount / count) * 100) : 0,\n })\n } catch {\n // Cannot be balanced into a compressible span — skip.\n }\n }\n // Oldest-first: the order is stable across turns (the oldest ranges do not\n // move as new messages land), so the model can consume ranges front-to-back\n // without re-ranking each nudge — matching the kernel's `oldest first` list\n // and the host's own front-to-back compression rhythm.\n return out.sort((a, b) => a.start - b.start)\n}\n\n/**\n * A compact human-readable description of the current surface for the model:\n * node count plus the first/last message seqs. Surface seqs are sparse (the\n * event log interleaves non-message events and expanded delta batches), so a\n * model that never saw the nudge range table — e.g. low-pressure sessions\n * where no nudge fires — cannot guess its own seq space. acp_status and the\n * nudge's range table both surface this so compress edges can be located\n * without blind probing.\n */\nexport function surfaceSummary(session: Session): string {\n const nodes = session.surface.nodes\n if (nodes.length === 0) return 'empty'\n // Surface nodes are NOT guaranteed to be ordered: a compaction replace lands\n // the checkpoint node first, so [15, 6, 7, …]. Report the span as min..max\n // rather than first..last, which would read \"seqs 15..12\" after a compress.\n let first = nodes[0]!\n let last = nodes[0]!\n for (const seq of nodes) {\n if (seq < first) first = seq\n if (seq > last) last = seq\n }\n return `${nodes.length} nodes, seqs ${first}..${last}`\n}\n\n/** One block as seen by the tier machinery: durable id ↔ kernel ref (`bN`). */\nexport interface AcpBlockRegistryEntry {\n /** The durable compaction id. */\n readonly blockId: string\n /** The acp-kernel block ref (`bN`); synthesised by log order for legacy blocks. */\n readonly kernelBlockId: string\n readonly tier: 1 | 2 | 3\n /** The surface seq of this block's checkpoint summary node (null when gone). */\n readonly summarySeq: number | null\n /** True until a LATER block distills this one. Only active blocks are distillable. */\n readonly active: boolean\n readonly parentBlockIds: readonly string[]\n}\n\n/**\n * Rebuild the compactionId ↔ kernel-block-ref registry from the durable log.\n * Legacy blocks (pre-tier, no recorded `kernelBlockId`) are synthesised as\n * `b1`, `b2`, … in log order; recorded ids are kept as-is. A block is active\n * until a later block lists it as a parent.\n */\nexport function blockRegistry(session: Session): AcpBlockRegistryEntry[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const kernelIdOf = new Map<string, string>()\n const raw: AcpBlockRegistryEntry[] = []\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n raw.push({\n blockId: entry.blockId,\n kernelBlockId,\n tier: entry.tier,\n summarySeq: entry.summarySeq ?? null,\n active: true,\n parentBlockIds: [...entry.parentBlockIds],\n })\n }\n const consumed = new Set<string>()\n for (const entry of raw) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n return raw.map((entry) => ({\n ...entry,\n active: !consumed.has(entry.blockId),\n }))\n}\n\n/**\n * The kernel block ref (`bN`) for a surface seq, when that seq is the\n * checkpoint summary node of a block — the edge the model must use to\n * distill (T2/T3). Active blocks distill; a stale (already-distilled) node\n * still maps to its `bN` so the kernel reports \"already compressed\" instead\n * of silently folding the summary as a plain message. Returns null for\n * anything else (plain messages, non-checkpoint nodes).\n */\nexport function blockRefForSummarySeq(session: Session, seq: number): string | null {\n const event = eventAtOf(session, seq)\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId)\n if (entry === undefined) return null\n return entry.kernelBlockId\n}\n\n/** The durable compaction ids distilled by the given kernel block refs (`bN`). */\nexport function compactionIdsOfKernelBlocks(session: Session, kernelBlockIds: readonly string[]): string[] {\n if (kernelBlockIds.length === 0) return []\n const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]))\n return kernelBlockIds\n .map((id) => byKernel.get(id))\n .filter((id): id is string => id !== undefined)\n}\n\n/**\n * Resolve a kernel block ref (`bN`) — as shown by the model tool `acp_status`\n * (kernel `buildStatusReport` renders `block.blockId`) — to the durable\n * compaction id the decompress/search tools accept. Returns null when `bN` is\n * not an exact registry key (unknown ref). Only matches the canonical `bN`\n * form (`/^b\\d+$/`); anything else is not a kernel ref and returns null so the\n * caller falls back to its compaction-id prefix match.\n */\nexport function blockIdOfKernelRef(session: Session, kernelRef: string): string | null {\n if (!/^b\\d+$/.test(kernelRef)) return null\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelRef)\n return entry?.blockId ?? null\n}\n\n/** The checkpoint summary seq of an ACTIVE kernel block (`bN`), or null. */\nexport function summarySeqOfKernelBlock(session: Session, kernelBlockId: string): number | null {\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId)\n return entry?.active ? entry.summarySeq : null\n}\n\n/** The durable block whose checkpoint node sits at `seq` (or null). */\nfunction checkpointBlockIdOf(events: readonly SessionEvent[], seq: number): string | null {\n const event = events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n return source.compactionId\n}\n\n/**\n * The shadowed seqs of a block, recursing into distilled parent blocks: a\n * tier-2 block shadows its parent's checkpoint node, so recovering its\n * originals requires expanding that node into the parent block's own shadowed\n * seqs. Cycle-safe (a block can never be its own ancestor).\n */\nexport function expandShadowedSeqs(session: Session, blockId: string): number[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const byId = new Map(ledger.map((entry) => [entry.blockId, entry]))\n const root = byId.get(blockId)\n if (root === undefined) return []\n const out: number[] = []\n const seen = new Set<string>()\n const visit = (entry: AcpBlockLedgerEntry): void => {\n if (seen.has(entry.blockId)) return\n seen.add(entry.blockId)\n for (const seq of entry.shadowedSeqs) {\n const childId = checkpointBlockIdOf(sessionEventsOf(session), seq)\n const child = childId === null ? undefined : byId.get(childId)\n if (child !== undefined) visit(child)\n else out.push(seq)\n }\n }\n visit(root)\n return out\n}\n","/**\n * Cross-version session event access.\n *\n * DSH `0.1.2-alpha` replaced the public `Session.events` getter with explicit\n * `snapshotEvents()` / `eventAt(seq)` methods; rc.6 / 0.1.1-rc.x still expose\n * `events`. Both shapes are feature-detected here so a single build runs on\n * either seam (the engine's peer range keeps `^0.1.0-rc.6 || ^0.1.1-rc.1`).\n *\n * Semantics match on both sides:\n * - `events` (rc.6) and `snapshotEvents()` (0.1.2-alpha) both return the\n * current full log as a stable, cached snapshot (reused until the next\n * append), with `seq === array index`.\n * - indexed reads map to `events[seq]` / `eventAt(seq)` with the same\n * `undefined`-when-absent contract.\n * @module billion-context-dsh/session-events\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\n\n/** Session surface extended with the 0.1.2-alpha read methods (optional). */\ntype SessionWithSnapshot = Session & {\n snapshotEvents?: () => readonly SessionEvent[]\n eventAt?: (seq: number) => SessionEvent | undefined\n}\n\n/** Session surface narrowed to the rc.6 public events getter. */\ntype SessionWithEvents = Session & {\n events: readonly SessionEvent[]\n}\n\n/** All events of a session in log order (seq == array index). */\nexport function sessionEventsOf(session: Session): readonly SessionEvent[] {\n const snapshot = (session as SessionWithSnapshot).snapshotEvents?.()\n if (snapshot !== undefined) return snapshot\n return (session as SessionWithEvents).events\n}\n\n/** The event at one exact seq, or undefined when the log has no such seq. */\nexport function eventAtOf(session: Session, seq: number): SessionEvent | undefined {\n const eventAt = (session as SessionWithSnapshot).eventAt\n if (typeof eventAt === 'function') return eventAt.call(session, seq)\n return (session as SessionWithEvents).events[seq]\n}","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * The tool-call id of one tool/result surface message, or null.\n *\n * Real DSH tool-result events carry NO `message.toolCallId` (hard-won rule\n * 10): the identity lives in the nested `{ type: 'tool-result', toolCallId }`\n * content block, falling back to `message.source.callId`. Shared with\n * `src/region.ts`'s call/result pairing — one implementation, never a copy.\n */\nexport function toolCallIdOfResultEvent(event: SessionEvent): string | null {\n if (event.type !== 'tool/result') return null\n const message = (event.data as {\n message?: { content?: Array<{ type?: unknown; toolCallId?: unknown }>; source?: { callId?: unknown } }\n }).message\n const block = Array.isArray(message?.content)\n ? message.content.find((candidate) => candidate?.type === 'tool-result')\n : undefined\n const id = block?.toolCallId ?? message?.source?.callId\n return typeof id === 'string' ? id : null\n}\n\n/**\n * Index of assistant tool-call `id` → tool `name`, used to attribute\n * tool/result messages to their tool. Real DSH tool-results carry no\n * `message.toolName` (rule 10), so the projection backfills it from the\n * matching assistant tool-call. Scans ALL events up front (order-independent:\n * a result may precede its call in the array) and covers shadowed calls too.\n */\nexport function buildToolCallIndex(events: readonly SessionEvent[]): ReadonlyMap<string, string> {\n const index = new Map<string, string>()\n for (const event of events) {\n if (event.type !== 'assistant/message') continue\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n if (!Array.isArray(content)) continue\n for (const block of content) {\n const candidate = block as { type?: unknown; id?: unknown; name?: unknown } | null\n if (candidate !== null && typeof candidate === 'object' && candidate.type === 'tool-call' && typeof candidate.id === 'string') {\n index.set(candidate.id, typeof candidate.name === 'string' ? candidate.name : '')\n }\n }\n }\n return index\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (role 'tool'); toolName/toolCallId are\n * backfilled from `toolNames` (assistant tool-call\n * index) — real DSH events do not carry them at the\n * message level. Without an index the result stays\n * untagged (`toolName: ''`), never \"text\".\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent, toolNames?: ReadonlyMap<string, string>): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n const key = toolCallIdOfResultEvent(event)\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: toolNames?.get(key ?? '') ?? '',\n toolCallId: message?.toolCallId ?? key ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[], toolNames?: ReadonlyMap<string, string>): CoreMessage[] {\n const index = toolNames ?? buildToolCallIndex(events)\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event, index))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => eventAtOf(session, seq))\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/**\n * ALL message-type events in log order — the visible surface PLUS everything\n * shadowed by compression. The ACP kernel deactivates any block whose consumed\n * message ids are absent from the array it is given (syncBlocks), and refuses\n * to anchor a block boundary that cannot find its messages, so T2/T3\n * distillation requires the full log, not just the visible surface.\n */\nexport function allLogMessages(session: import('@deepseek-ai/dsh-session').Session): CoreMessage[] {\n return eventsToCoreMessages(sessionEventsOf(session))\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * Host-vocabulary token pricing for the durable shadow-price protocol.\n *\n * The host token-meter prices every appended message with a fixed flat-4\n * heuristic (`estimateContent` / `estimateMessage` in `dsh-token-meter`) and\n * the producer contract requires every `compaction/summary`/`compaction/prune`\n * `shadowedTokenCount` claim to be derived from the SAME estimator. Writing\n * claims with the engine's CJK-aware `defaultCountTokens` overdraws the meter\n * on CJK-heavy sessions and permanently bricks them (live session\n * `session-3aa366c3`, issue #54; AGENTS.md rule 12 — `defaultCountTokens` is\n * display currency, NEVER event currency).\n *\n * This module prices claims in the host's vocabulary: it prefers the live\n * meter's own per-node prices (`ctx.tokenMeter.measure(session).nodes` —\n * exact by construction, follows host estimator changes automatically, the\n * same path the host's own `compaction-basic` uses) and falls back to an\n * exact mirror of the host's estimator when the meter is unreachable.\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { deriveEventMessage } from '@deepseek-ai/dsh-session'\nimport { eventAtOf } from './session-events.ts'\n\n/** Fixed text-density heuristic used by the host meter until exact tokenization. */\nconst CHARS_PER_TOKEN = 4\n/** Per-block structural overhead for JSON framing and type tags. */\nconst BLOCK_OVERHEAD = 4\n/** Role-field framing overhead added to every priced message. */\nconst ROLE_OVERHEAD = 4\n\n/** The host's model-visible content block union (structural, mirror-side only). */\nexport type HostBlock =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool-call'; name: string; arguments: string }\n | { type: 'tool-result'; toolCallId: string; content: HostContent }\n | { type?: string } & Record<string, unknown>\n\n/** A content block list, or a bare string (`tool-result` content may be either). */\nexport type HostContent = readonly HostBlock[] | string\n\nfunction blockType(block: unknown): string | undefined {\n if (typeof block !== 'object' || block === null) return undefined\n const type = (block as { type?: unknown }).type\n return typeof type === 'string' ? type : undefined\n}\n\n/**\n * Exact mirror of the host's `estimateContent`\n * (`@deepseek-ai/dsh-token-meter/lib/types/estimate.js`): text/reasoning\n * `ceil(len/4)+4`, tool-call `ceil(name/4)+ceil(arguments/4)+4`, tool-result\n * recursive over its content, unknown blocks `4+ceil(JSON.stringify/4)` over\n * the ORIGINAL block object. A string content is iterated as an iterable, so\n * every CHARACTER falls to the default branch (`4+ceil(JSON.stringify(char)/4)`\n * — 5 tokens for any single unescaped character).\n */\nexport function estimateHostContent(blocks: HostContent): number {\n if (typeof blocks === 'string') {\n let tokens = 0\n for (const char of blocks) {\n tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(char).length / CHARS_PER_TOKEN)\n }\n return tokens\n }\n let tokens = 0\n for (const block of blocks) {\n switch (blockType(block)) {\n case 'text':\n case 'reasoning': {\n tokens += Math.ceil((block as { text: string }).text.length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD\n break\n }\n case 'tool-call': {\n const call = block as { name: string; arguments: string }\n tokens += Math.ceil(call.name.length / CHARS_PER_TOKEN)\n + Math.ceil(call.arguments.length / CHARS_PER_TOKEN)\n + BLOCK_OVERHEAD\n break\n }\n case 'tool-result': {\n tokens += estimateHostContent((block as { content: HostContent }).content) + BLOCK_OVERHEAD\n break\n }\n default:\n tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)\n }\n }\n return tokens\n}\n\n/** Exact mirror of the host's `estimateMessage` (content + role framing). */\nexport function estimateHostMessage(message: { content: HostContent }): number {\n return estimateHostContent(message.content) + ROLE_OVERHEAD\n}\n\n/**\n * Host price of ONE session event under the mirror: project it through the\n * host's `deriveEventMessage` (null for non-surface events and empty-content\n * assistant messages) and price the derived message; null derives to 0.\n */\nexport function hostPriceEvent(event: SessionEvent): number {\n const message = deriveEventMessage(event)\n return message === null ? 0 : estimateHostMessage(message as { content: HostContent })\n}\n\n/** Mirror price of a set of surface seqs (the fallback claim computation). */\nexport function shadowedHostTokens(session: Session, seqs: readonly number[]): number {\n let total = 0\n for (const seq of seqs) {\n const event = eventAtOf(session, seq)\n if (event !== undefined) total += hostPriceEvent(event)\n }\n return total\n}\n\n/** The slice of the live meter's measurement the engine may price from. */\ninterface TokenMeterLike {\n measure(session: Session): { nodes: ReadonlyArray<{ seq: number; tokens: number }> }\n}\n\n/**\n * Claim price for `seqs` in the host's vocabulary. Prefers the live meter's\n * own per-node prices when `ctx.tokenMeter` is reachable and covers every\n * shadowed seq (exact by construction, follows host estimator changes); ANY\n * failure — meter absent, `measure` throwing (e.g. a step-less log), or a seq\n * missing from the measurement — falls back to the exact mirror. Never returns\n * a `defaultCountTokens` price (rule 12).\n */\nexport function shadowedTokensViaMeter(\n session: Session,\n seqs: readonly number[],\n ctx?: { get?(name: string): unknown } | null,\n): number {\n try {\n const meter = ctx?.get?.('tokenMeter') as TokenMeterLike | undefined\n if (meter?.measure !== undefined) {\n const bySeq = new Map(meter.measure(session).nodes.map((node) => [node.seq, node.tokens]))\n let total = 0\n let missing = false\n for (const seq of seqs) {\n const tokens = bySeq.get(seq)\n if (tokens === undefined) {\n missing = true\n break\n }\n total += tokens\n }\n if (!missing) return total\n }\n } catch {\n // Fall through to the mirror — the mirror IS the host vocabulary.\n }\n return shadowedHostTokens(session, seqs)\n}\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n *\n * Tier-2/3 distillation additionally requires the kernel state to KNOW the\n * blocks: `syncBlocks` deactivates a block whose consumed messages are absent\n * from the message array, and `resolveBoundaries` refuses to anchor a block\n * ref it cannot find — so on first access for a session that already has\n * durable blocks (e.g. after a server restart), the kernel blocks are\n * REHYDRATED from the ledger before use. Live updates continue through `set`.\n * @module billion-context-dsh/state\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionBlock, type CompressionState } from 'acp-kernel'\nimport { rebuildBlockLedger } from './region.ts'\nimport { sessionEventsOf } from './session-events.ts'\n\n/** Rebuild kernel `CompressionBlock`s from the durable ledger (no kernel run needed). */\nfunction rebuildKernelBlocks(events: readonly SessionEvent[]): CompressionBlock[] {\n const ledger = rebuildBlockLedger(events)\n if (ledger.length === 0) return []\n // Durable compactionId → kernel block ref (bN), recorded or synthesised.\n const kernelIdOf = new Map<string, string>()\n const parentKernelIds = new Map<string, string[]>()\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n parentKernelIds.set(\n entry.blockId,\n entry.parentBlockIds\n .map((parent) => kernelIdOf.get(parent))\n .filter((id): id is string => id !== undefined),\n )\n }\n const consumed = new Set<string>()\n for (const entry of ledger) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n const blocks: CompressionBlock[] = []\n for (const entry of ledger) {\n const blockId = kernelIdOf.get(entry.blockId)!\n // The kernel anchors a block by its effectiveMessageIds. Since the tier\n // feature, the transaction records the kernel block's raw coverage\n // (direct/effective message ids) verbatim, so rehydration is faithful —\n // a tier-2 block's coverage is its parents' ORIGINALS, not the checkpoint\n // node it shadows. Legacy blocks fall back to the shadowed seqs (tier 1)\n // or the checkpoint node (tier > 1; multi-tool-call assistant messages in\n // legacy blocks lose bare-seq coverage — a documented legacy limitation).\n const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)]\n const effective = entry.effectiveMessageIds\n ?? (entry.tier > 1\n ? (entry.summarySeq === undefined ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)])\n : [...entry.shadowedSeqs.map(String)])\n blocks.push({\n blockId,\n runId: `r${blocks.length + 1}`,\n tier: entry.tier,\n summary: entry.summary,\n ...(entry.topic === undefined ? {} : { topic: entry.topic }),\n directMessageIds: [...direct],\n effectiveMessageIds: [...effective],\n directBlockIds: parentKernelIds.get(entry.blockId) ?? [],\n compressedTokens: entry.shadowedTokenCount,\n createdAt: entry.createdAt,\n survivedCount: 0,\n generation: 'young',\n active: !consumed.has(entry.blockId),\n })\n }\n return blocks\n}\n\n/** The next kernel block id after the rehydrated blocks (or the initial 1). */\nfunction nextBlockIdAfter(events: readonly SessionEvent[]): number {\n const blocks = rebuildKernelBlocks(events)\n let max = 0\n for (const block of blocks) {\n const num = Number(block.blockId.slice(1))\n if (Number.isInteger(num)) max = Math.max(max, num)\n }\n return max + 1\n}\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n const events = sessionEventsOf(session)\n if (events.some((event) => event.type === 'compaction/summary')) {\n state.blocks = rebuildKernelBlocks(events)\n state.nextBlockId = nextBlockIdAfter(events)\n }\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, ToolArgsError, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { buildStatusReport, defaultCountTokens, searchBlocks, type CompressionCore, type MessageRole, type SearchDoc } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport type { AcpWindow } from './window.ts'\nimport {\n AlreadyCompressedRangeError,\n blockIdOfKernelRef,\n blockRefForSummarySeq,\n blockRegistry,\n compactionIdsOfKernelBlocks,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n stripOrphanedSurfaceToolMessages,\n openToolCallIds,\n surfaceSummary,\n type ResolvedSurfaceRange,\n} from './region.ts'\nimport { allLogMessages, buildToolCallIndex, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { shadowedTokensViaMeter } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\nimport { DEFAULT_RESOLVED, type ResolvedPrompts } from './prompts.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolve the effective context window for an agent (optional: status falls back to modelContextLimit). */\n readonly windowFor?: (agent: Agent) => Promise<AcpWindow>\n /** Resolved prompt templates (optional: falls back to DEFAULT_RESOLVED). */\n readonly prompts?: ResolvedPrompts\n /**\n * Call ids of compress invocations that created a durable block. The engine\n * listens for the matching `tool/result` and hides the call/result pair from\n * the surface, preventing the compaction summary from sitting between them\n * (strict providers reject that sequence with HTTP 400).\n */\n readonly compressCallIdsToHide?: Set<string>\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\n/**\n * Resolve the effective context window for a tool or command run: probe the\n * agent's real window via `windowFor` when provided, otherwise fall back to\n * the environment's `modelContextLimit`. Shared by the compress and\n * acp_status tool handlers and the `/acp` command so the resolution logic\n * lives in exactly one place (issue #63 — the tools used the 128K fallback\n * for pressure decisions even when auto-detection had found a larger window).\n */\nexport async function resolveEffectiveWindow(env: ToolEnvironment, agent: Agent): Promise<AcpWindow> {\n return env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n}\n\nconst compressParameters = {\n // Tolerated wrapped-arguments form: some models emit\n // `{ \"arguments\": \"{\\\"content\\\": [...]}\" }` (double-nested) or\n // `{ \"arguments\": { \"content\": [...] } }` instead of the unwrapped\n // `{ \"content\": [...] }`. The old DSH validator surfaced this as\n // `invalid arguments: \"arguments\" must be an object` and the model retried\n // forever. `arguments` is accepted as an optional JSON node so the wrapped\n // shape passes schema validation; `handleCompress` unwraps it and falls back\n // to a clear runtime error when neither form carries content. `content` is\n // intentionally NOT `required: true` — a required property would reject the\n // wrapped shape before `handleCompress` can see it. The tool description\n // still tells the model content is mandatory.\n //\n // The items fields are the opposite case: startSeq/endSeq/summary MUST be\n // `required: true`. Without that, a model call that omits `summary` (only\n // startSeq/endSeq/topic present) passed schema validation and failed late\n // inside the kernel with \"Summary is empty\" — and live sessions showed the\n // model retrying the identical broken call in a loop. With the fields\n // required, the same call is rejected at the schema gate with\n // `missing required property \"content[0].summary\"`, which tells the model\n // exactly which field to add (same pattern as decompress's required\n // blockId / search_context's required query).\n arguments: { type: 'json', description: 'Tolerated wrapped-arguments form (model-generated); unwrapped in handleCompress. Prefer passing content directly.' },\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary. Required — pass it directly, not wrapped in an arguments key.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n required: true,\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n required: true,\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, required: true, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\n/**\n * Match a drilldown mN ref: \"m00306\" / \"m306\" (kernel `refToIndex` semantics,\n * `m0*(\\d{1,5})`), tolerating a trailing `#callId` fragment (symmetric with\n * `parseSeq`'s `#` handling). Returns the ref index, or null for non-mN input.\n */\nconst MN_RE = /^m0*(\\d{1,5})(?:#.*)?$/i\n\nfunction mnRefIndex(value: string): number | null {\n const match = MN_RE.exec(value.trim())\n if (match === null) return null\n const index = Number(match[1])\n return index >= 1 && index <= 99999 ? index : null\n}\n\n/**\n * Resolve a compress boundary arg to a surface seq. Accepts:\n * - a bare surface seq (number, \"295\", \"295#call_00_x\" — `parseSeq`);\n * - a drilldown mN ref (\"m00306\" / \"m306\") — reverse-mapped via the CURRENT\n * turn's `messageRefs.byRef` (CoreMessage.id = seq or \"seq#callId\" → split\n * on \"#\"). Unknown mN (never assigned on the current surface) fails with\n * guidance; a valid mN whose span was already compressed falls through to\n * the existing recover-stale / already-compressed semantics (rule 7).\n * `byRef` MUST come from `turn.state.messageRefs` (after `processTurn`), not\n * the persisted store state: acp_status's turn is never persisted, so mN refs\n * shown in a drilldown (including refs for messages that arrived since the\n * last nudge/compress) only exist on the current turn's ref map — a lookup\n * against the stored state would report a false \"unknown mN\" and dead-loop\n * the model between acp_status and compress.\n */\nfunction parseBoundary(value: number | string, byRef: Record<string, string>): number {\n const text = String(value)\n const index = mnRefIndex(text)\n if (index === null) return parseSeq(value)\n // Normalize to the kernel's padded key (\"m00306\") — byRef holds exact keys.\n const ref = `m${String(index).padStart(5, '0')}`\n const raw = byRef[ref]\n if (raw === undefined) {\n throw new Error(\n `billion-context-dsh: mN \"${text}\" not found on the current surface — re-run acp_status for fresh refs (the surface may have moved)`,\n )\n }\n const seq = Number(String(raw).split('#')[0]!)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(\n `billion-context-dsh: mN \"${text}\" maps to a non-seq id \"${raw}\" — re-run acp_status`,\n )\n }\n return seq\n}\n\ninterface CompressArgs {\n /** Tolerated wrapped-arguments form (model-generated double-nesting). */\n arguments?: string | { content?: CompressArgs['content'] }\n topic?: string\n content?: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/**\n * Unwrap the tolerated wrapped-arguments forms back to the canonical shape:\n * `{ arguments: \"{\\\"content\\\": [...]}\" }` or `{ arguments: { content: [...] } }`\n * → `{ content: [...] }`. The direct `{ content: [...] }` form passes through\n * untouched. Returns null when no form carries content (caller raises).\n */\nfunction unwrapCompressArgs(args: CompressArgs): CompressArgs | null {\n if (args.content !== undefined) return args\n if (args.arguments === undefined) return null\n let inner: unknown = args.arguments\n if (typeof inner === 'string') {\n try {\n inner = JSON.parse(inner)\n } catch {\n return null\n }\n }\n if (typeof inner !== 'object' || inner === null || Array.isArray(inner)) return null\n const content = (inner as { content?: unknown }).content\n if (content === undefined) return null\n return { ...args, content: content as CompressArgs['content'] }\n}\n\n/**\n * Peel the tolerated wrapped-arguments envelope `{ arguments: {…} }` that some\n * model channels emit for ANY tool — the same double-nesting that birthed\n * `unwrapCompressArgs` (live-verified on acp_status: a drilldown call arrived\n * as `{\"arguments\":{\"scope\":\"compressed\"}}` and was silently dropped, since\n * only compress unwrapped). The envelope may be an object or a JSON string;\n * inner keys win over outer duplicates. Args without an envelope pass through\n * untouched.\n */\nfunction unwrapEnvelope<T extends object>(args: T): T {\n const envelope = (args as { arguments?: unknown }).arguments\n if (envelope === undefined) return args\n let inner: unknown = envelope\n if (typeof inner === 'string') {\n try {\n inner = JSON.parse(inner)\n } catch {\n return args\n }\n }\n if (typeof inner !== 'object' || inner === null || Array.isArray(inner)) return args\n return { ...args, ...(inner as object) } as T\n}\n\n/**\n * Enforce the items-level `required` contract on the EFFECTIVE content, after\n * the wrapped-arguments envelope has been peeled. The DSH schema gate only\n * sees the model's top-level arguments object — when the call arrives wrapped\n * as `{ arguments: { content: [...] } }`, the top-level `content` property is\n * absent there (it lives inside the envelope), so the gate never checks the\n * items and a missing `summary`/`startSeq`/`endSeq` sailed through to the\n * kernel, which fails late with a field-less \"Summary is empty\" and sent live\n * sessions into a retry loop (the same failure mode the schema gate fix for\n * the direct form closed). Running the SAME check on the unwrapped content\n * closes that window for both forms, and produces the identical\n * `invalid arguments: missing required property \"content[0].summary\"` surface\n * by reusing the host's `ToolArgsError` instead of a hand-rolled format.\n * An empty/whitespace-only summary counts as missing (the kernel would\n * reject it anyway — fail early with the field name instead).\n */\nfunction validateContentItems(content: NonNullable<CompressArgs['content']>): void {\n const violations: string[] = []\n content.forEach((item, index) => {\n const path = `content[${index}]`\n if (item.startSeq === undefined) violations.push(`missing required property \"${path}.startSeq\"`)\n if (item.endSeq === undefined) violations.push(`missing required property \"${path}.endSeq\"`)\n if (typeof item.summary !== 'string' || item.summary.trim().length === 0) {\n violations.push(`missing required property \"${path}.summary\"`)\n }\n })\n if (violations.length > 0) throw new ToolArgsError(violations)\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n // Clean orphan tool messages before any range solve: a single orphan result\n // corrupts the pairing balance cache and rejects every large range (issue\n // #18). Every call still in flight — the compress call itself AND any\n // sibling tool called in the same assistant message — must be excluded from\n // orphan pruning: its tool/result lands at the end of the step, and pruning\n // the call now would orphan that result.\n stripOrphanedSurfaceToolMessages(session, openToolCallIds(session))\n const state = env.store.stateFor(session)\n // The kernel gets the FULL log (visible + shadowed): syncBlocks deactivates\n // a block whose consumed messages are absent, and resolveBoundaries refuses\n // to anchor a block ref it cannot find, so tier-2/3 distillation needs the\n // originals present. The token count uses the same priority chain as the\n // nudge (projectedTokens → surfaceTokens → character heuristic).\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const config = kernelConfigFor({ ...env, modelContextLimit: window.limit })\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n // mN drilldown refs resolve against the CURRENT turn's ref map (not the\n // stored state) — acp_status's turn is never persisted, so its mN rows only\n // exist here; the deterministic re-assignment yields the same mN for the\n // same messages (see parseBoundary).\n const byRef = turn.state.messageRefs.byRef\n\n // Tolerate the wrapped-arguments forms some models emit (double-nested\n // `{ arguments: \"...\" }`), which the old DSH validator surfaced as\n // `\"arguments\" must be an object` and sent the model into a retry loop.\n const unwrapped = unwrapCompressArgs(args)\n if (unwrapped === null) {\n return {\n text: 'compress: missing content — pass the content array directly: compress({ content: [{ startSeq, endSeq, summary }] })',\n }\n }\n args = unwrapped\n // Items-level required check AFTER the envelope peel (see\n // validateContentItems for why the schema gate alone cannot do this).\n validateContentItems(args.content!)\n\n const ranges: Array<\n ResolvedSurfaceRange & {\n startSeq: number\n endSeq: number\n startRef: string\n endRef: string\n summary: string\n topic?: string\n }\n > = []\n // Ranges whose whole span was already shadowed by earlier compressions.\n // They land as advisory warnings, never as errors or phantom blocks.\n const alreadyCompressedNotes: string[] = []\n for (const range of args.content!) {\n const startSeq = parseBoundary(range.startSeq, byRef)\n const endSeq = parseBoundary(range.endSeq, byRef)\n let resolved: ResolvedSurfaceRange\n try {\n // Balance edges FIRST: the requested edges may sit on multi-tool-call\n // assistant messages, which project to `${seq}#${callId}` CoreMessage ids\n // and therefore have NO bare-`${seq}` ref. resolveSurfaceRange shifts them\n // to clean tool-pairing-balanced cuts that always carry a bare ref, so the\n // resolved refs exist and the shadowed span matches the returned range.\n // Edges shadowed by an earlier compression (stale nudge table / old\n // compress result) are remapped to the still-live content of the span.\n resolved = resolveSurfaceRange(session, startSeq, endSeq)\n } catch (error) {\n if (error instanceof AlreadyCompressedRangeError) {\n const covering = error.coveringBlockIds\n const blockNote = covering.length === 0\n ? ''\n : ` (block ${covering[0]!.slice(0, 8)}${covering.length > 1 ? ` +${covering.length - 1} more` : ''})`\n alreadyCompressedNotes.push(\n ` seqs ${error.start}..${error.end} already compressed${blockNote} — nothing to reclaim; decompress to recover the originals`,\n )\n continue\n }\n throw error\n }\n // An edge on an ACTIVE block's checkpoint summary node resolves to the\n // kernel block ref (bN) — the boundary that makes applyCompression distill\n // (tier 2/3) instead of folding the summary as a plain message.\n const startBlockRef = blockRefForSummarySeq(session, resolved.start)\n const endBlockRef = blockRefForSummarySeq(session, resolved.end)\n const startRef = startBlockRef ?? byRaw[String(resolved.start)]\n const endRef = endBlockRef ?? byRaw[String(resolved.end)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${resolved.start}..${resolved.end} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n ranges.push({\n ...resolved,\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n })\n }\n\n // Nothing to do: every requested range was already compressed.\n if (ranges.length === 0) {\n const text = ['Compressed 0 block(s), ~0 tokens reclaimed.', ...alreadyCompressedNotes]\n if (alreadyCompressedNotes.length > 0) {\n text.push(' (all requested ranges were already compressed — decompress a block to recover its originals)')\n }\n return { text: text.join('\\n') }\n }\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n // Deliberately NOT overriding protectedMessageIds: with the full log the\n // kernel's recent/last-user protection is computed over the same\n // non-block-covered messages as the visible feed, so default behavior is\n // preserved. Any 'Excluded N protected message(s)' warning is surfaced.\n })\n // A kernel error for ONE range must not poison the whole call: the other\n // ranges still created blocks. This matters for issue #18's \"phantom range\"\n // — messages absorbed into an earlier block's effectiveMessageIds (kernel\n // boundary adjustment) but still live on the surface resolve fine but make\n // the kernel throw \"Range contains no compressible messages\". Fail only\n // when NOTHING landed; otherwise land the successes and surface the\n // failures as advisory lines below.\n if (applied.result.errors.length > 0 && applied.result.blocksCreated === 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n if (applied.result.blocksCreated > 0) {\n // Hide this compress call/result after the tool result lands, so the\n // compaction summary never sits between an assistant tool_calls block and\n // its tool response (strict providers reject that sequence).\n env.compressCallIdsToHide?.add(exec.callId)\n }\n\n // Match freshly created kernel blocks to the requested ranges by their\n // range key (the kernel stamps startRef/endRef onto each new block).\n const previousIds = new Set(turn.state.blocks.map((block) => block.blockId))\n const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId))\n const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]))\n // Warnings carry two shapes: range-prefixed (\"Skipped range (a..b) — …\")\n // attributable to a specific range, and free-form (\"Excluded N protected\n // message(s) …\") attributable to the call as a whole.\n const warningByRangeKey = new Map<string, string[]>()\n const freeWarnings: string[] = []\n for (const warning of applied.result.warnings) {\n const match = /^Skipped range \\((.+?)\\.\\.(.+?)\\)/.exec(warning)\n if (match !== null) {\n const key = `${match[1]}::${match[2]}`\n const list = warningByRangeKey.get(key) ?? []\n list.push(warning)\n warningByRangeKey.set(key, list)\n } else {\n freeWarnings.push(warning)\n }\n }\n\n const lines: string[] = []\n let skippedRanges = 0\n for (const range of ranges) {\n const key = `${range.startRef}::${range.endRef}`\n const block = blockByRangeKey.get(key)\n if (block === undefined) {\n // The kernel skipped this range (already compressed / overlapped): no\n // kernel block was created, so no durable transaction is landed — the\n // ledger must never record a block the kernel does not know.\n skippedRanges += 1\n const warnings = warningByRangeKey.get(key) ?? []\n for (const warning of warnings) lines.push(` ${warning}`)\n continue\n }\n // The edges were already balanced above; shadow exactly that span.\n const { start, end } = range\n const shadowed = shadowedSeqsOf(session, start, end)\n // Price the reclaimed tokens in the HOST's token vocabulary (rule 12):\n // prefer the live meter's per-node prices, fall back to the exact mirror.\n // NEVER defaultCountTokens — that overdraws the meter on CJK (issue #54).\n const shadowedTokens = shadowedTokensViaMeter(session, shadowed, agent.ctx)\n const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1\n const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: range.summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n tier,\n kernelBlockId: block.blockId,\n ...(range.topic === undefined ? {} : { topic: range.topic }),\n ...(parentBlockIds.length === 0 ? {} : { parentBlockIds }),\n // Record the kernel block's raw coverage so a restarted engine\n // rehydrates the SAME effective messages (a tier-2 block's coverage is\n // its parents' originals, not the checkpoint node).\n directMessageIds: block.directMessageIds,\n effectiveMessageIds: block.effectiveMessageIds,\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n // Always report the tier, even tier 1: a silently-downgraded distill\n // (boundary moved off the checkpoint seq → the kernel folds a plain\n // message) must be visible to the model immediately, or the model keeps\n // believing the distillation landed (issue #60, failure mode 2).\n const tierLabel = `, tier ${tier}`\n const note = range.recovered === true\n ? ` (seqs ${range.startSeq}..${range.endSeq} were already shadowed — compressed the live remainder ${start}..${end})`\n : adjusted\n ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)`\n : ''\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}${note}`,\n )\n }\n\n const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`\n const totalSkipped = skippedRanges + alreadyCompressedNotes.length\n const failedLines = applied.result.errors.map((error) => ` ${error}`)\n const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...failedLines, ...alreadyCompressedNotes, ...lines]\n const footer = totalSkipped > 0\n ? ` (${totalSkipped} range(s) skipped or failed — see above)`\n : ''\n return { text: `${summaryLine}\\n${[...warningLines, footer].filter((line) => line !== '').join('\\n')}` }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id: the kernel block ref `bN` shown by acp_status (e.g. b1), or a compaction id / prefix from search_context.' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\n/** Resolve a block arg to its durable compaction id: exact `bN` kernel ref\n * first (acp_status shows `bN`), then the compaction-id prefix match that\n * search_context and /acp have always used. The `bN` branch is exact\n * (`/^b\\d+$/` with `$`), so a UUID that happens to start with `b1` cannot be\n * shadowed — full UUIDs and 8-char prefixes never match the anchored regex. */\nfunction resolveBlockId(session: Session, arg: string): string | null {\n const byKernelRef = blockIdOfKernelRef(session, arg)\n if (byKernelRef !== null) return byKernelRef\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const byPrefix = ledger.find((entry) => entry.blockId.startsWith(arg))\n return byPrefix?.blockId ?? null\n}\n\nfunction handleDecompress(_env: ToolEnvironment, rawArgs: DecompressArgs, exec: ToolRunContext): TextOutput {\n const args = unwrapEnvelope<DecompressArgs>(rawArgs)\n const session = requireAgent(exec).session\n const blockId = resolveBlockId(session, args.blockId)\n if (blockId === null) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const block = ledger.find((entry) => entry.blockId === blockId)\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n const event = eventAtOf(session, seq)\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : ''\n return {\n text: `Block ${block.blockId} — ${block.summary}${tierNote}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\n/** Event type → kernel message role (drives hybrid role weighting). */\nfunction roleOfEvent(event: SessionEvent): MessageRole | null {\n switch (event.type) {\n case 'user/message': return 'user'\n case 'assistant/message': return 'assistant'\n case 'tool/result': return 'tool'\n default: return null\n }\n}\n\n/**\n * Build the unified SearchDoc[] from the log: one block doc per ledger entry\n * (ref = compactionId, so `decompress({ blockId })` closes the loop) plus one\n * message doc per shadowed ORIGINAL (expanded through distilled parents; each\n * seq is claimed by the earliest/innermost block that covered it, mirroring\n * pi's owner map — decompress on that block recovers the original).\n */\nfunction buildSearchDocs(session: Session): SearchDoc[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const docs: SearchDoc[] = []\n const claimed = new Set<number>()\n for (const block of ledger) {\n docs.push({\n kind: 'block',\n ref: block.blockId,\n text: block.summary,\n title: block.summary.slice(0, 60) || block.blockId,\n blockId: block.blockId,\n tier: block.tier,\n tokens: defaultCountTokens(block.summary),\n })\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n if (claimed.has(seq)) continue\n claimed.add(seq)\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n const role = roleOfEvent(event)\n const text = extractEventText(event)\n if (role === null || text.length === 0) continue\n docs.push({\n kind: 'message',\n ref: `seq ${seq}`,\n text,\n title: `${role}: ${text.slice(0, 60)}`,\n role,\n blockId: block.blockId,\n tier: block.tier,\n tokens: defaultCountTokens(text),\n })\n }\n }\n return docs\n}\n\nfunction handleSearch(_env: ToolEnvironment, rawArgs: SearchArgs, exec: ToolRunContext): TextOutput {\n const args = unwrapEnvelope<SearchArgs>(rawArgs)\n const session = requireAgent(exec).session\n if (args.query.trim() === '') return { text: 'search_context: empty query (no matches)' }\n const docs = buildSearchDocs(session)\n // Trust the kernel: hybrid (0.7×BM25 stemmed + 0.3×fuzzy n-gram) is the\n // algorithm contract — no engine-side gate or threshold re-implements\n // search policy. Scores are surfaced so the model can judge a weak hit\n // (fuzzy-only tops out near 0.3).\n const results = searchBlocks(docs, args.query, { limit: args.limit ?? 5, previewLength: 160 })\n if (results.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n const lines = results.map((r) => {\n const kind = r.kind === 'block' ? `block ${r.ref}` : `message ${r.ref} (${r.role ?? '?'}, in block ${r.blockId ?? '?'})`\n return ` - ${kind} (score ${r.score.toFixed(2)}): ${r.preview}`\n })\n return {\n text: `Matches for \"${args.query}\":\\n${lines.join('\\n')}\\n\\nDecompress with: decompress({ blockId })`,\n }\n}\n\n/** acp_status drilldown passthrough (kernel buildStatusReport options). All\n * keys optional — no args = overview. `view`/`tool`/`sort`/`limit` only have\n * meaning under `scope:\"uncompressed\"` (`tool` narrows to `view:\"messages\"`;\n * `sort:\"age\"` applies to `scope:\"compressed\"`); the kernel ignores them in\n * overview mode (upstream status-tool docstring documented the same scope).\n * DSH schema compiler: `string` + `enum` supported, no `required: true`\n * anywhere → all optional (schema.js:192-210). */\nconst statusParameters = {\n scope: {\n type: 'string' as const,\n enum: ['compressed', 'uncompressed'] as const,\n description: 'Drilldown scope: \"compressed\" lists compressed blocks, \"uncompressed\" lists visible messages. Omit for the overview.',\n },\n view: {\n type: 'string' as const,\n enum: ['ranges', 'messages'] as const,\n description: 'Drilldown view under scope:\"uncompressed\": \"ranges\" merges visible messages into ranges (default), \"messages\" lists every message.',\n },\n tool: {\n type: 'string' as const,\n description: 'Filter drilldown rows to one tool name (scope:\"uncompressed\" + view:\"messages\" only).',\n },\n sort: {\n type: 'string' as const,\n enum: ['size', 'time', 'tool', 'age'] as const,\n description: 'Row order: size (default, most tokens first), time, tool; \"age\" applies to compressed blocks.',\n },\n limit: {\n type: 'integer' as const,\n description: 'Cap on rows or blocks shown (default 30).',\n },\n}\n\ninterface StatusArgs {\n scope?: 'compressed' | 'uncompressed'\n view?: 'ranges' | 'messages'\n tool?: string\n sort?: 'size' | 'time' | 'tool' | 'age'\n limit?: number\n}\n\n/** A compaction checkpoint summary node (`source.plugin === 'compact'`). These\n * are NOT in any block's `effectiveMessageIds`, so feeding them to\n * `buildStatusReport` would double-count the summary — once as `block.summary`\n * (summaryTokens) and once as a visible text message (totalText). Excluded\n * before status rendering (design §4.2 P1-3). */\nfunction isCheckpointEvent(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\nasync function handleStatus(env: ToolEnvironment, rawArgs: StatusArgs, exec: ToolRunContext): Promise<TextOutput> {\n // The model channel may wrap ANY tool's args under `{ arguments: {…} }`;\n // peel it or drilldown params never reach buildStatusReport (live-verified\n // `{\"arguments\":{\"scope\":\"compressed\"}}` silently rendered the overview).\n const args = unwrapEnvelope<StatusArgs>(rawArgs)\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n const surface = surfaceEventsOf(session)\n // One tool-call index for both projections below (P2-5): tool/result\n // toolName/toolCallId are backfilled from the assistant tool-calls.\n const toolNames = buildToolCallIndex(surface)\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surface, toolNames)\n const tokenCount = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const config = kernelConfigFor({ ...env, modelContextLimit: window.limit })\n // Run the same pipeline the context transform runs, so what acp_status\n // reports matches what the model actually receives. The returned turn.state\n // carries the freshly assigned refs; it is NOT persisted — acp_status is a\n // read-only view, and env.store.set would advance the nudge baseline a\n // second time in the same turn (design §6.1 P2-2).\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n // Status messages = visible surface EXCLUDING checkpoint summary nodes (P1-3).\n const statusMessages = eventsToCoreMessages(\n surface.filter((event) => !isCheckpointEvent(event)),\n toolNames,\n )\n // Upstream-aligned: the kernel renders the breakdown (percentages of the\n // VISIBLE total — no window semantics; drilldown scope/view/tool/sort/limit\n // pass through verbatim); the engine only appends the nudge decision line,\n // the DSH Surface anchor, and — in drilldown mode — the mN-vs-seq note.\n const report = buildStatusReport(turn.state, statusMessages, defaultCountTokens, args)\n const lines = [report]\n // Mirror upstream pi (`if (args.scope) return base`): a drilldown request\n // answers with the kernel report alone — the nudge decision line is an\n // overview concept. The Surface anchor stays in ALL modes: it is the model's\n // compressible-ref locator (design P2-1).\n if (args.scope === undefined) {\n const nudge = turn.nudge\n if (nudge !== undefined) {\n lines.push('', `Nudge: ${nudge.shouldInject ? 'ACTIVE' : 'idle'} — ${nudge.reason}`)\n }\n // Issue #60 P2: the model's only route to T2/T3 distillation is a LIVE\n // checkpoint seq — but acp_status (kernel buildStatusReport) is blind to\n // summary nodes (they are excluded as messages, rule 9) and shows only bN\n // refs. Append an engine-side mapping bN → checkpoint seq for ACTIVE\n // blocks (only active blocks are distillable). Appending is the\n // kernel-alignment contract: the kernel owns the report text, the engine\n // owns the wiring — this row is wiring, never a rewrite of the report.\n const checkpointRows = blockRegistry(session)\n .filter((entry) => entry.active && entry.summarySeq !== null)\n .map((entry) => `${entry.kernelBlockId} → seq ${entry.summarySeq}`)\n if (checkpointRows.length > 0) {\n lines.push('', `Checkpoint seqs (active blocks — compress a checkpoint seq to distill it): ${checkpointRows.join(', ')}`)\n }\n }\n lines.push('', `Surface: ${surfaceSummary(session)}`)\n // Drilldown rows carry kernel refs (mN, dense log-order ids) — compress\n // accepts them directly (handleCompress reverse-maps mN → live surface seq\n // via the current turn's messageRefs.byRef; issue #31). The Surface anchor\n // remains the model's compressible-seq locator for nudge-style ranges.\n if (args.scope === 'uncompressed') {\n lines.push('', 'Note: drilldown rows are kernel refs (mN) — feed them straight to compress (auto-mapped to the live surface seq); an unknown mN fails with guidance.')\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n const prompts = env.prompts ?? DEFAULT_RESOLVED\n return [\n defineTool({\n name: 'compress',\n description: prompts.tools.compress,\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: prompts.tools.decompress,\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: prompts.tools.searchContext,\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: prompts.tools.acpStatus,\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return handleStatus(env, args as StatusArgs, exec)\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n *\n * NOTE: `AcpCompactionEngine` (src/index.ts) ships its own engine-level\n * defaults 0.70/0.85 for the two nudge thresholds on top of this layer, so an\n * engine with no explicit config lands on 0.70/0.85, not 0.75/0.95.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit guarantee line. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0 || input.coreOverrides?.nudge) {\n // The engine always ships explicit pct defaults (0.70/0.85, see\n // DEFAULT_CONFIG), so nudgePatch is never empty and the plain replace\n // below used to discard coreOverrides.nudge entirely — the documented\n // escape hatch was unreachable whenever the pct knobs were set. User\n // overrides must land LAST so they win over both kernel defaults and\n // the engine pct values.\n overrides.nudge = {\n ...defaultConfig(input.modelContextLimit).nudge,\n ...nudgePatch,\n ...input.coreOverrides?.nudge,\n }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n COMPRESS_PHILOSOPHY,\n TIER2_DISTILL_RULES,\n TIER3_CONDENSE_RULES,\n defaultCountTokens,\n renderNudgeText,\n type CompressionCore,\n type CoreMessage,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { allLogMessages, eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { buildCompressibleSeqRanges, findOpenTurn, summarySeqOfKernelBlock, surfaceSummary } from './region.ts'\nimport { sessionEventsOf } from './session-events.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { DEFAULT_RESOLVED, renderTemplate, type ResolvedPrompts } from './prompts.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolved prompt templates (optional: falls back to DEFAULT_RESOLVED). */\n readonly prompts?: ResolvedPrompts\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/**\n * Resolve the best available token count for ACP pressure decisions.\n *\n * Priority chain:\n * 1. `sessionProjections.contextPressure.projectedTokens` — matches the UI's\n * context-occupancy display (includes fixed overhead: system prompt, tool\n * definitions, AGENTS.md, etc.). Provider-anchored; reacts to compaction.\n * 2. `tokenMeter.measure(session).surfaceTokens` — heuristic surface-only\n * estimate (pure conversation messages, no fixed overhead). Falls back\n * when sessionProjections is unavailable or has no provider anchor yet.\n * 3. `defaultCountTokens` character heuristic — last resort for tests and\n * minimal hosts that lack the token-meter service.\n */\nexport function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n // 1. Prefer sessionProjections.contextPressure.projectedTokens (matches UI).\n const projections = agent.ctx?.get?.('sessionProjections') as\n | { snapshot?: (session: unknown) => { values?: { contextPressure?: { projectedTokens?: number } } } }\n | undefined\n const projected = projections?.snapshot?.(agent.session)?.values?.contextPressure?.projectedTokens\n if (typeof projected === 'number' && projected > 0) return projected\n\n // 2. Fallback to tokenMeter surfaceTokens (heuristic, no fixed overhead).\n const meter = agent.ctx?.get?.('tokenMeter') as\n | { measure?: (session: unknown) => { surfaceTokens?: number } }\n | undefined\n const surface = meter?.measure?.(agent.session)?.surfaceTokens\n if (typeof surface === 'number' && surface > 0) return surface\n\n // 3. Last resort: character heuristic.\n return coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n}\n\n/**\n * Render the compressible-range table as seq refs for the model.\n * Computed directly from the surface (not the kernel's ref map, which can\n * drift and hide large tool results) — see buildCompressibleSeqRanges.\n * UPSTREAM: this self-computation is a labeled workaround for kernel\n * ref-map drift after surface replacements (AGENTS.md rule 11) — drop it and\n * use kernel compressibleRanges once the drift is fixed upstream.\n */\nexport function rangeTable(\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts = DEFAULT_RESOLVED,\n): string {\n const ranges = buildCompressibleSeqRanges(session).slice(0, 6)\n // 零范围:整块省略(保留现状的提前返回与 nudge 尾部 '\\n')。\n if (ranges.length === 0) return ''\n const lines = ranges.map((range) =>\n renderTemplate(prompts.rangeTable.line, {\n start: range.start,\n end: range.end,\n count: range.count,\n tokens: range.tokens,\n toolPct: range.toolPct,\n textPct: 100 - range.toolPct,\n }),\n )\n return [\n // 前导空串元素产生 nudge 中范围表前的唯一空行(§4:parts 层不再加分隔)。\n '',\n renderTemplate(prompts.rangeTable.header, { surface: surfaceSummary(session) }),\n renderTemplate(prompts.rangeTable.title, { count: ranges.length }),\n ...lines,\n prompts.rangeTable.footer,\n ].join('\\n')\n}\n\n/**\n * The token count driving pressure decisions. Prefer `resolveTokenCount` which\n * uses `sessionProjections.contextPressure.projectedTokens` (matches the UI's\n * context-occupancy display, including fixed overhead). Falls back to\n * `tokenMeter.measure(session).surfaceTokens`, then `defaultCountTokens`\n * character heuristic for tests and minimal hosts.\n */\nfunction measuredTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n return resolveTokenCount(agent, coreMessages)\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n // Full log for the kernel (so block anchors survive — see handleCompress);\n // the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = measuredTokenCount(agent, surfaceMessages)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(sessionEventsOf(session)) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, session, env.prompts)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * Render the nudge message text. DEFAULT (no `config.prompts.nudge` override)\n * calls the kernel's own `renderNudgeText` — EFFICIENCY_NOTE/EMERGENCY_HEADER,\n * context breakdown, HOW_TO_COMPRESS_RULES, tier rules, and the batch tip all\n * come from acp-kernel verbatim (the kernel-alignment principle). Only the\n * ref-ID-oriented segments are replaced with our seq-based equivalents,\n * because DSH has no `<acp>` ref tags — see docs/dsh-porting-verification.md:\n * - `rangesStr` (mNNNNN refs) → the surface-seq range table;\n * - the emergency JSON example (startId/endId) → a seq example;\n * - the tier trigger block (block ids bN) → our tier line with surface seqs.\n * When a host overrides any `prompts.nudge` slot, the template path is used so\n * `config.prompts` keeps full control (custom copy wins over kernel defaults).\n */\nexport function buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts = DEFAULT_RESOLVED,\n): string {\n // A host override of any nudge slot → template rendering (config.prompts\n // keeps its v0.1.9 contract: custom copy wins). Only the pristine default\n // reference reaches the kernel path.\n if (prompts.nudge !== DEFAULT_RESOLVED.nudge) {\n return renderNudgeFromTemplates(nudge, emergency, session, prompts)\n }\n const rendered = renderNudgeText(nudge)\n return adaptKernelNudgeToSeq(rendered.text, nudge, session, prompts)\n}\n\n/**\n * Take the kernel-rendered nudge text and replace its ref-ID-oriented segments\n * with our surface-seq equivalents. Everything else (frame, philosophy,\n * breakdown, HOW_TO_COMPRESS_RULES, tier rules, tip) stays kernel verbatim.\n */\nfunction adaptKernelNudgeToSeq(\n text: string,\n nudge: NudgeDecision,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n let out = text\n // Tier nudges: replace the kernel trigger block (block ids bN) with our tier\n // line carrying surface seqs. The kernel's TIER2/3 rules stay in the tail.\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n out = replaceTierTrigger(out, nudge, session, prompts)\n } else if (out.includes('\"startId\"')) {\n // Emergency nudges: replace the ref-ID JSON example with a seq example.\n out = replaceEmergencyExample(out)\n }\n // Replace the ref-ID range table (mNNNNN) with the surface-seq table.\n // A zero-range table leaves the kernel's own \"[No specific ranges detected]\"\n // notice intact — it is a better prompt than an empty table.\n const seqTable = rangeTable(session, prompts)\n if (seqTable !== '') out = replaceRangesStr(out, seqTable)\n return out\n}\n\n/** Replace the kernel rangesStr segment (`Compressible ranges (N, oldest first):…`) with our seq table. */\nfunction replaceRangesStr(text: string, seqTable: string): string {\n const match = text.match(/\\n\\n(?:Compressible ranges \\(|\\[No specific ranges detected)/)\n if (!match) return text\n const start = match.index!\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\n/)\n const end = next !== null ? start + 2 + next.index! : text.length\n const before = text.slice(0, start)\n const after = text.slice(end)\n // seqTable starts with '\\n' (the range table's leading blank line), so\n // `before` + '\\n' + seqTable yields one blank line before the table.\n return before + '\\n' + seqTable + after\n}\n\n/** Replace the kernel tier trigger segment (`[TIER n …TRIGGER]…Example: compress(…)`) with our tier line. */\nfunction replaceTierTrigger(\n text: string,\n nudge: NudgeDecision,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n const start = text.search(/\\n\\n(?:\\[TIER \\d|\\[EMERGENCY — TIER \\d)/)\n if (start === -1) return text\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\nHOW TO COMPRESS/)\n const end = next !== null ? start + 2 + next.index! : text.length\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n .sort((a, b) => a - b)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n const tierValue = nudge.tier === null ? 2 : nudge.tier\n const tierLine = renderTemplate(prompts.nudge.tier, {\n tier: tierValue,\n count: targets.length,\n prevTier: tierValue - 1,\n tokens,\n seqs: summarySeqs.join(', '),\n firstSeq: summarySeqs[0] ?? 'n/a',\n lastSeq: summarySeqs[summarySeqs.length - 1] ?? 'n/a',\n })\n return text.slice(0, start) + '\\n\\n' + tierLine + text.slice(end)\n}\n\n/** Replace the kernel emergency JSON example (startId/endId) with a seq example. */\nfunction replaceEmergencyExample(text: string): string {\n const start = text.search(/\\n\\n\\{ \"topic\":/)\n if (start === -1) return text\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\nCompressible ranges |\\n\\n\\[No specific/)\n const end = next !== null ? start + 2 + next.index! : text.length\n return text.slice(0, start)\n + '\\n\\ncompress({ content: [{ startSeq, endSeq, summary }] }) — use the seqs from the range table above.'\n + text.slice(end)\n}\n\n/**\n * Template rendering path (used only when a host overrides a `prompts.nudge`\n * slot). Kept byte-compatible with the pre-refactor assembly: frame → breakdown\n * → growth → guidance → tier(+rules)/range table → tip.\n */\nfunction renderNudgeFromTemplates(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n // Cap the reported percentage at 100: a broken measurement (e.g. response\n // pressure folded in) must never surface as an absurd \"230%\" to the model.\n const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100)\n const frame = renderTemplate(\n emergency ? prompts.nudge.emergency : prompts.nudge.normal,\n { pct, philosophy: COMPRESS_PHILOSOPHY },\n )\n const parts: string[] = [frame]\n\n // Context breakdown (kernel style, from NudgeDecision.contextBreakdown).\n if (nudge.contextBreakdown) {\n const bd = nudge.contextBreakdown\n const breakdown = renderTemplate(prompts.nudge.breakdown, {\n system: Math.round(bd.system / 1000),\n tool: Math.round(bd.tool / 1000),\n summaries: Math.round(bd.summaries / 1000),\n code: Math.round(bd.code / 1000),\n text: Math.round(bd.text / 1000),\n })\n if (breakdown !== '') parts.push('', breakdown)\n if (bd.growth > 0) {\n const growth = renderTemplate(prompts.nudge.growth, { growth: Math.round(bd.growth / 1000) })\n if (growth !== '') parts.push(growth)\n }\n }\n\n // HOW_TO_COMPRESS_RULES as guidance (kernel puts it in every nudge).\n if (prompts.nudge.guidance !== '') parts.push('', prompts.nudge.guidance)\n\n // Tier line (distillation / condensation suggestion) + tier-specific rules.\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n .sort((a, b) => a - b)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n const tierLine = renderTemplate(prompts.nudge.tier, {\n tier: nudge.tier,\n count: targets.length,\n prevTier: nudge.tier - 1,\n tokens,\n seqs: summarySeqs.join(', '),\n firstSeq: summarySeqs[0] ?? 'n/a',\n lastSeq: summarySeqs[summarySeqs.length - 1] ?? 'n/a',\n })\n if (tierLine !== '') parts.push(tierLine)\n // Tier-specific rules from kernel (TIER2_DISTILL_RULES / TIER3_CONDENSE_RULES).\n const tierRules = nudge.tier === 2 ? TIER2_DISTILL_RULES : TIER3_CONDENSE_RULES\n parts.push('', tierRules)\n } else {\n // Range table for non-tier nudges (DSH-specific: seq-based, not ref-ID-based).\n parts.push(rangeTable(session, prompts))\n }\n\n // Batch-compress tip (from kernel's nudge-text.ts style).\n if (prompts.nudge.tip !== '') parts.push('', prompts.nudge.tip)\n\n return parts.join('\\n')\n}\n","/**\n * M4 — configurable prompt templates: the per-stage model-visible texts\n * (nudge frames, range table, system prompt, tool descriptions) rendered from\n * `config.prompts` templates with named placeholders.\n *\n * Design: docs/configurable-prompts-design.md (v4).\n * - placeholders are `{identifier}` only; literal braces like\n * `compress({ content: [...] })` are left untouched (spaces/commas break the\n * identifier rule);\n * - resolvePrompts merges user overrides over DEFAULT_PROMPTS per key\n * (null/undefined → default, string → override; group-level null → whole\n * group default for YAML hosts) and validates unknown placeholders at\n * construction time (fail-fast, no silent typos);\n * - renderTemplate throws when a known placeholder has no value — callers\n * must provide every value (e.g. tokens via a typeof fallback).\n * @module billion-context-dsh/prompts\n */\n\nimport { COMPRESS_PHILOSOPHY, HOW_TO_COMPRESS_RULES, TIER2_DISTILL_RULES, TIER3_CONDENSE_RULES } from 'acp-kernel'\n\n/** 用户可写值:字符串模板,或 null(= 用默认,等价于不写)。YAML 宿主写 null 是合法输入。 */\nexport type PromptInput = string | null\n\n/** 按组生成\"每键可选、可 null\"的覆盖类型。 */\nexport type PromptOverride<T> = { [K in keyof T]?: PromptInput }\n\nexport interface NudgePrompts {\n /** 普通档首句。占位符:{pct} {philosophy} */\n normal: string\n /** 紧急档首句。占位符:{pct} {philosophy} */\n emergency: string\n /** 指导行(HOW_TO_COMPRESS_RULES)。无占位符 */\n guidance: string\n /** tier 蒸馏行。占位符:{tier} {count} {prevTier} {tokens} {seqs} {firstSeq} {lastSeq} */\n tier: string\n /** 上下文分解。占位符:{system} {tool} {summaries} {code} {text} */\n breakdown: string\n /** 增长行。占位符:{growth} */\n growth: string\n /** 溢出提示。无占位符 */\n tip: string\n}\n\nexport interface RangeTablePrompts {\n /** 表头。占位符:{surface} */\n header: string\n /** 标题。占位符:{count}(表格行数) */\n title: string\n /** 每行。占位符:{start} {end} {count} {tokens} */\n line: string\n /** 表尾调用语法。无占位符 */\n footer: string\n}\n\nexport interface ToolPrompts {\n /** 工具描述(纯文本,无占位符) */\n compress: string\n decompress: string\n searchContext: string\n acpStatus: string\n}\n\nexport interface AcpPrompts {\n readonly nudge?: PromptOverride<NudgePrompts>\n readonly rangeTable?: PromptOverride<RangeTablePrompts>\n readonly tools?: PromptOverride<ToolPrompts>\n /** 整段 system prompt 模板;`{philosophy}` 引用 kernel 的 COMPRESS_PHILOSOPHY */\n readonly systemPrompt?: PromptInput\n}\n\n/** 解析结果 —— 所有字段已填满(纯 string,无 null)、已校验。构造一次,全程复用。 */\nexport interface ResolvedPrompts {\n readonly nudge: NudgePrompts\n readonly rangeTable: RangeTablePrompts\n readonly tools: ToolPrompts\n /** 注意:这是【模板】(含 {philosophy}),不是渲染结果。渲染用 renderSystemPrompt。 */\n readonly systemPromptTemplate: string\n}\n\n/** 每槽允许的占位符名集合(构建期校验用)。 */\nconst NUDGE_ALLOWED: { [K in keyof NudgePrompts]: ReadonlySet<string> } = {\n normal: new Set(['pct', 'philosophy']),\n emergency: new Set(['pct', 'philosophy']),\n guidance: new Set(),\n tier: new Set(['tier', 'count', 'prevTier', 'tokens', 'seqs', 'firstSeq', 'lastSeq']),\n breakdown: new Set(['system', 'tool', 'summaries', 'code', 'text']),\n growth: new Set(['growth']),\n tip: new Set(),\n}\nconst RANGE_TABLE_ALLOWED: { [K in keyof RangeTablePrompts]: ReadonlySet<string> } = {\n header: new Set(['surface']),\n title: new Set(['count']),\n line: new Set(['start', 'end', 'count', 'tokens']),\n footer: new Set(),\n}\nconst TOOLS_ALLOWED: { [K in keyof ToolPrompts]: ReadonlySet<string> } = {\n compress: new Set(),\n decompress: new Set(),\n searchContext: new Set(),\n acpStatus: new Set(),\n}\nconst SYSTEM_ALLOWED = new Set(['philosophy', 'howToCompressRules', 'tier2DistillRules', 'tier3CondenseRules'])\n\n/** 校验单个模板:未知 `{ident}` → throw(带槽位路径)。默认模板开发期已核验,不重扫。 */\nfunction validateTemplate(template: string, allowed: ReadonlySet<string>, path: string): string {\n const re = /\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g\n let match: RegExpExecArray | null\n while ((match = re.exec(template)) !== null) {\n const name = match[1]!\n if (!allowed.has(name)) {\n throw new Error(\n `${path} contains unknown placeholder {${name}} — allowed: ${[...allowed].join(', ') || '(none)'}`,\n )\n }\n }\n return template\n}\n\n/**\n * 纯替换。两个契约:\n * 1. 未知占位符不可能到达这里(构建期已校验);\n * 2. 已知占位符缺值 = 编程错误 → throw(绝不静默渲染空串)。\n */\nexport function renderTemplate(template: string, vars: Record<string, string | number>): string {\n return template.replace(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name: string) => {\n const value = vars[name]\n if (value === undefined) {\n throw new Error(\n `renderTemplate: missing value for placeholder {${name}} in template \"${template.slice(0, 60)}…\"`,\n )\n }\n return String(value)\n })\n}\n\n/**\n * 逐键合并:null / undefined → 默认;字符串 → 覆盖默认(不用 spread,\n * 否则 null 会覆盖默认,与\"null = 用默认\"矛盾)。组级 null/undefined →\n * 整组用默认(YAML 宿主可能写 `{ nudge: null }`,W3)。\n */\nfunction mergeGroup<T extends Record<keyof T, string>>(\n defaults: T,\n override: PromptOverride<T> | null | undefined,\n allowed: { [K in keyof T]: ReadonlySet<string> },\n path: string,\n): T {\n if (override == null) return defaults\n const out = {} as { [K in keyof T]: string }\n for (const key of Object.keys(defaults) as Array<keyof T>) {\n const value = override[key]\n out[key] = value === null || value === undefined\n ? defaults[key]\n : validateTemplate(value, allowed[key], `${path}.${String(key)}`)\n }\n return out as T\n}\n\n/**\n * 深合并 + 校验;引擎构造期调用一次,出错即抛(fail-fast)。\n * 未传入时返回 DEFAULT_RESOLVED,零校验重跑。\n */\nexport function resolvePrompts(input?: AcpPrompts): ResolvedPrompts {\n if (input === undefined) return DEFAULT_RESOLVED\n return {\n nudge: mergeGroup(DEFAULT_PROMPTS.nudge, input.nudge, NUDGE_ALLOWED, 'prompts.nudge'),\n rangeTable: mergeGroup(DEFAULT_PROMPTS.rangeTable, input.rangeTable, RANGE_TABLE_ALLOWED, 'prompts.rangeTable'),\n tools: mergeGroup(DEFAULT_PROMPTS.tools, input.tools, TOOLS_ALLOWED, 'prompts.tools'),\n systemPromptTemplate:\n input.systemPrompt === null || input.systemPrompt === undefined\n ? DEFAULT_PROMPTS.systemPromptTemplate\n : validateTemplate(input.systemPrompt, SYSTEM_ALLOWED, 'prompts.systemPrompt'),\n }\n}\n\n/** 渲染 system prompt 模板(注入 kernel 压缩哲学、压缩规则、蒸馏规则)。 */\nexport function renderSystemPrompt(prompts: ResolvedPrompts): string {\n return renderTemplate(prompts.systemPromptTemplate, {\n philosophy: COMPRESS_PHILOSOPHY,\n howToCompressRules: HOW_TO_COMPRESS_RULES,\n tier2DistillRules: TIER2_DISTILL_RULES,\n tier3CondenseRules: TIER3_CONDENSE_RULES,\n })\n}\n\n/**\n * 默认模板 —— 与 v4 之前的硬编码文案逐字节一致\n * (回归锚点见 tests/prompts.test.ts 的硬编码字面量快照)。\n */\nexport const DEFAULT_PROMPTS: ResolvedPrompts = {\n nudge: {\n // 与 kernel nudge-text.ts EFFICIENCY_NOTE 逐字对齐——不含 \"Context usage is at X%\"\n // 陈述(usage 只通过 breakdown 传达);{pct} 仍可用作自定义占位符。\n normal: 'This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full.\\n\\n{philosophy}',\n emergency: '⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.\\n\\n{philosophy}',\n guidance: HOW_TO_COMPRESS_RULES,\n tier: 'Tier {tier}: {count} tier-{prevTier} block(s) distillable ({tokens} tokens) — distill them by compressing their checkpoint seq(s) [seqs {seqs}] as one range: compress({ content: [{ startSeq: {firstSeq}, endSeq: {lastSeq}, summary }] }).',\n breakdown: 'Context breakdown: {system}K system | {tool}K tool | {summaries}K summaries | {code}K code | {text}K text',\n growth: '+{growth}K since last nudge',\n tip: '💡 Compress all ranges in one call (pass multiple content entries: `content: [{...}, {...}]`).',\n },\n rangeTable: {\n header: 'Surface: {surface}',\n title: 'Compressible ranges ({count}, oldest first; exact surface seqs — usable as-is):',\n line: ' - seq {start}..{end} — {count} messages, ~{tokens} tokens [tool {toolPct}% | text {textPct}%]',\n footer: 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] }) — content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint.\\n'\n + 'Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing.',\n },\n tools: {\n compress: 'Replace older conversation ranges with dense summaries you write. Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated ranges in one call (each content entry becomes its own block); keep ranges disjoint. Never compress content the current step is actively using. Compress boundaries are SURFACE SEQS (acp_status Surface: row, latest nudge table) — NOT the block refs (bN, e.g. b1) that acp_status COMPRESSED BLOCKS shows, which are for decompress only. Drilldown mN refs (e.g. m00306) are ALSO accepted as startSeq/endSeq — they are auto-mapped to the live surface seq; an unknown mN (never assigned on the current surface) fails with guidance. Seq refs must come from the CURRENT surface (acp_status or the latest nudge): a span whose edges were shadowed by an earlier compress is auto-remapped to its still-live content, a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance. Good compression moments: stage or subtask completion whose details you have fully consumed and will not re-check, strategy switches, intermediate milestones, and wrapping up failed exploration — when the details are consumed and no longer critical for the task ahead. Before compressing, ask: will I need to re-verify any detail from this range in this task? If yes, keep it live. When you write a summary, turn dead-end exploration into a conclusion (what was tried, why it failed, the next step) — not a blow-by-blow; and keep the summary the ONLY record: self-contained, so a later reader (or you, after decompress) can continue without the original.',\n decompress: 'Recover the original content of a compressed block by its blockId — the kernel block ref `bN` shown by acp_status (e.g. b1), or a compaction id from search_context (read-only; does not unshadow the range).',\n searchContext: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context. When a summary lacks a detail you need (exact values, error strings, decisions, verbatim code), SEARCH the compressed blocks FIRST — never guess or reconstruct from memory: search_context(query) locates the right block, then decompress only that block to recover the original.',\n acpStatus: 'Context status: overview of the current context — CONTEXT BREAKDOWN (tool/text/summaries token shares of the visible total), COMPRESSED BLOCKS ledger, and the nudge decision. No args = overview. Percentages are shares of the visible content, not the context window. Note: the block refs in COMPRESSED BLOCKS (bN, e.g. b1) are for decompress; compress uses the Surface: seq range, not bN. Drilldown: pass scope:\"compressed\" for a per-block list, or scope:\"uncompressed\" with view:\"messages\" (every visible message) / view:\"ranges\" (merged ranges); tool filters to one tool name, sort reorders (size/time/tool; age for compressed), limit caps rows (default 30). Drilldown row refs are kernel ids (mN) — feed them straight to compress as startSeq/endSeq (auto-mapped to the live surface seq); bN is for decompress, Surface: seqs also work in compress.',\n },\n systemPromptTemplate: `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. The nudge is an efficiency notification: when you see one, consider which ranges you have genuinely consumed and could summarise to keep working context lean.\n\n{philosophy}\n\nWHEN TO COMPRESS:\n- A sub-agent or delegated task has returned a large result that you have already extracted the key facts from.\n- Verbose command output (build/test logs, git diff, directory listings) where you have already used the information you need.\n- Exploration that led nowhere.\n- Repeated reads of the same file or repeated status checks once the decision is recorded.\n- Resolved discussion threads where a decision has been captured in summary or in code.\n- Intermediate steps of a completed multi-step task, once the final result is recorded.\n- A task phase has ended — bug hunt complete, root cause found, exploration done, research sprint wrapped.\n\nWHEN NOT TO COMPRESS:\n- Content the current step is actively reading or reasoning about.\n- Important user messages — preserve their exact intent, constraints, and acceptance criteria.\n- Protected tool outputs — hard-excluded from compression ranges, survive intact in visible context.\n- Content you will still need to cite verbatim — in review/audit/verification tasks, keep source reads un-compressed until the final report is written. If you compressed it and now need the exact detail, decompress costs a full round-trip; prefer delaying the compress.\n\n{howToCompressRules}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- compress: replace one or more seq ranges, each with your own dense summary. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated segments in one call (each entry becomes its own block): compress({ content: [{ startSeq: 1, endSeq: 5, summary: '...' }, { startSeq: 12, endSeq: 18, summary: '...' }] }). Keep ranges disjoint — overlapping entries in one batch are skipped. Edges are auto-balanced to tool-call/result boundaries; a trailing #callId fragment in a seq is ignored. Seq refs must be on the current surface: seqs from older nudges or earlier compresses go stale as the surface moves, so a stale span is auto-remapped to its still-live remainder (the result reports the adjusted span), a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance. The block refs (bN, e.g. b1) in acp_status COMPRESSED BLOCKS are for decompress, NOT compress boundaries.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }) — accept the bN ref shown by acp_status (e.g. b1) or a compaction id.\n- search_context: when a summary lacks the details you need (exact values, error strings, decisions, verbatim code), SEARCH the compressed blocks FIRST — never guess or reconstruct from memory; search_context(query) locates the right block, decompress only that block.\n- acp_status: current context usage and the live compressible-range list. Run it right before compressing — the only seqs that never go stale are the ones you just read. Drilldown (scope/view/tool/sort/limit) lists per-message or per-block sizes; drilldown rows are kernel ids (mN) — compress accepts them directly (auto-mapped to the live surface seq).\n\nTiered compression: each compressed block appears on the surface as one summary node. Compressing that node again DISTILLS the block (tier 2): the parent summary folds into your new summary and the original messages are freed. Distilling a tier-2 block yields tier 3. Distill when a summary itself is consumed — decompress on the tier-2 block recovers the full originals.\n\n{tier2DistillRules}\n\n{tier3CondenseRules}\n\nWhen you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs — the surface moves as messages land and compress; verify with acp_status.`,\n}\n\n/** 模块级默认缓存:默认参/兜底直接引用,避免每次调用重跑校验。 */\nexport const DEFAULT_RESOLVED: ResolvedPrompts = DEFAULT_PROMPTS\n","/**\n * Auto context-window detection — resolve the model's real context window\n * from the host LLM runtime instead of trusting a hardcoded config default.\n *\n * `agent.ctx.llm` (the cordis `LlmRuntime` service) exposes\n * `resolveModelInfo(provider, model)` → `{ context: { contextWindow } }`, the\n * exact-route capacity the adapter learned from the provider API (pi-ai reads\n * `context_window`/`context_length` during discovery). Probing is a standalone\n * capability query — no request is sent.\n * @module billion-context-dsh/window\n */\n\nimport type { Agent } from '@deepseek-ai/dsh-agent'\n\n/** Fallback window when auto-detection is unavailable. Same default as acp-kernel's `defaultConfig`. */\nexport const DEFAULT_CONTEXT_WINDOW = 128000\n\n/** The effective context window plus where it came from. */\nexport interface AcpWindow {\n /** Effective context window in tokens. */\n readonly limit: number\n /** Where the limit came from. */\n readonly source: 'explicit' | 'auto' | 'projection' | 'default'\n /**\n * Route the window was resolved for. 'auto' reports the probed route;\n * 'projection' returns also set it, mirroring agent.options — which can be\n * stale after a mid-session model switch (inert today: windowSourceLabel\n * never reads these fields for the projection source).\n */\n readonly provider?: string\n readonly model?: string\n /**\n * True only when auto-detection was ATTEMPTED and failed (the probe threw or\n * the model API disclosed no window), so the fallback limit is in use. Not\n * set for explicit config, a successful probe, or disabled auto-detection —\n * those must not look like a failure (issue #63: a misconfigured gateway\n * silently fell back to 128K and produced false emergency nudges).\n */\n readonly probeFailed?: boolean\n}\n\n/** Human label for an AcpWindow's source (used by /acp status). */\nexport function windowSourceLabel(window: AcpWindow): string {\n if (window.source === 'explicit') return 'configured'\n if (window.source === 'projection') {\n return `session projection current route (auto-refreshes on model switch)`\n }\n if (window.source === 'auto') {\n return `auto-detected from ${window.provider ?? '?'}/${window.model ?? '?'}`\n }\n if (window.probeFailed === true) return 'default (auto-detection failed — restart to re-probe)'\n return 'default (auto-detection unavailable)'\n}\n\n/** The minimal LlmRuntime surface the probe needs (structural — no as any). */\ninterface LlmProbe {\n resolveModelInfo?: (\n provider: string,\n model: string,\n signal?: AbortSignal,\n ) => Promise<{ context?: { contextWindow?: number } }>\n}\n\n/** The minimal sessionProjections surface the projection source needs. */\ninterface ProjectionProbe {\n snapshot?: (session: unknown) => {\n values?: { contextPressure?: { contextWindow?: number } }\n }\n}\n\n/**\n * Read the live context window from the host session projection\n * (`contextPressure.contextWindow` — the newest recorded route capacity).\n * This tracks the session's CURRENT route: after a mid-session model switch\n * `agent.options.provider/model` stays a stale snapshot, so probing THAT route\n * yields the previous model's window (a 1M-window session read as ~96K →\n * false EMERGENCY nudges at 300%+ usage). The projection is refreshed by the\n * host on every request, so it follows the real model without any config.\n * Returns null when the host exposes no projection or disclosed no window.\n */\nexport function projectedContextWindow(agent: Agent): number | null {\n const projections = agent.ctx?.get?.('sessionProjections') as ProjectionProbe | undefined\n const window = projections?.snapshot?.(agent.session)?.values?.contextPressure?.contextWindow\n if (typeof window === 'number' && Number.isInteger(window) && window > 0) return window\n return null\n}\n\n/**\n * Probe the model's real context window. Returns null when the host provides\n * no llm service, the adapter discloses no window, or the probe throws —\n * callers fall back to DEFAULT_CONTEXT_WINDOW. Never throws.\n */\nexport async function detectContextWindow(\n agent: Agent,\n provider: string,\n model: string,\n): Promise<number | null> {\n const llm = agent.ctx?.get?.('llm') as LlmProbe | undefined\n if (llm?.resolveModelInfo === undefined) return null\n try {\n const info = await llm.resolveModelInfo(provider, model)\n const window = info?.context?.contextWindow\n if (typeof window === 'number' && Number.isInteger(window) && window > 0) return window\n return null\n } catch {\n return null\n }\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { resolveEffectiveWindow, type ToolEnvironment } from './tools.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport { kernelConfigFor } from './config.ts'\nimport {\n blockIdOfKernelRef,\n blockRefForSummarySeq,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { allLogMessages, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { shadowedTokensViaMeter } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\nimport { defaultConfig } from 'acp-kernel'\nimport { windowSourceLabel } from './window.ts'\n\nasync function statusText(env: ToolEnvironment, agent: Agent): Promise<string> {\n const session = agent.session\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n // Full log for the kernel (so block anchors survive — same input as the\n // nudge path); the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const limit = window.limit\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n ` context window: ${limit} (${windowSourceLabel(window)})`,\n ]\n // A failed probe falls back to the 128K default AND is cached for the\n // process lifetime — the /acp panel must say so explicitly, or the operator\n // can't tell why pressure looks wrong (issue #63: a gateway that disclosed\n // no window read as ~55% of 128K instead of ~18% of the real 1M window).\n if (window.probeFailed === true) {\n lines.push(` ⚠ window auto-detection failed — using the ${limit} fallback (restart to re-probe, or set modelContextLimit explicitly)`)\n }\n // Nudge arbitration on the SAME inputs the nudge path uses — a read-only\n // diagnostic, so run on a cloned state and never write it back to the store.\n const state = structuredClone(env.store.stateFor(session))\n const config = kernelConfigFor({ ...env, modelContextLimit: limit })\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount: estimated })\n const nudge = turn.nudge\n if (nudge !== undefined) {\n const label = nudge.shouldInject ? (nudge.tier !== null ? `ACTIVE [T${nudge.tier}]` : 'ACTIVE') : 'idle'\n lines.push(` nudge: ${label} — ${nudge.reason}`)\n if (!nudge.shouldInject) {\n const maxPct = config.nudge.maxContextLimitPct\n const toNudge = Math.max(0, Math.round(maxPct * limit - estimated))\n lines.push(` next nudge: ~${toNudge.toLocaleString()} tokens to go (usage ${Math.round(nudge.contextUsage * 100)}% → ${Math.round(maxPct * 100)}% line)`)\n }\n }\n // Show ALL blocks, not just the oldest 10: /acp status is how the user\n // confirms recent work survived compression, and the block list is folded\n // in the GUI anyway, so length has no cost (issue #47).\n for (const block of ledger) {\n const tier = block.tier > 1 ? ` [T${block.tier}]` : ''\n lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n // A checkpoint summary node can only be distilled through the kernel (the\n // compress tool); /acp compress is a plain T1 range transaction, so refuse\n // rather than silently folding the summary as a message.\n if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {\n return '/acp compress: the range touches a compressed block summary node — distill it with the compress tool (seq-based batch), not /acp compress'\n }\n // The RESOLVED edges define the claim span, never the raw inputs:\n // resolveSurfaceRange may adjust them to a balanced cut, and a raw edge\n // absent from the surface makes shadowedSeqsOf slice a garbage span that\n // assertProvenance rejects when the transaction lands (AGENTS.md rule 12).\n const shadowed = shadowedSeqsOf(session, start, end)\n // Price the reclaimed tokens in the HOST's token vocabulary (rule 12):\n // prefer the live meter's per-node prices, fall back to the exact mirror.\n const shadowedTokens = shadowedTokensViaMeter(session, shadowed, agent.ctx)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n // Accept the kernel block ref (`bN`) the model tool acp_status shows, as\n // well as the compaction-id prefix (same dual-id resolution as the tool).\n const blockId = blockIdOfKernelRef(session, args[0]!)\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const block = blockId === null\n ? ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n : ledger.find((entry) => entry.blockId === blockId)\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n const parts = expandShadowedSeqs(session, block.blockId)\n .map((seq) => extractEventText(eventAtOf(session, seq)!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: await statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress.\n *\n * The text is DEFAULT_PROMPTS.systemPromptTemplate rendered with the kernel's\n * COMPRESS_PHILOSOPHY and HOW_TO_COMPRESS_RULES; hosts can override the whole\n * section via `config.prompts.systemPrompt` (see docs/configurable-prompts-design.md).\n * @module billion-context-dsh/system-prompt\n */\n\nimport { DEFAULT_PROMPTS, renderSystemPrompt } from './prompts.ts'\n\nexport const ACP_SYSTEM_PROMPT = renderSystemPrompt(DEFAULT_PROMPTS)\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["require","block","refNum","formatTokens","numericPart","activeBlocks","refNum","stem","countOccurrences","registry","tokens","createUserMessage","createUserMessage","pct","parseBoundary","tierLabel","registry"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../node_modules/acp-kernel/src/refs.ts","../node_modules/acp-kernel/src/state.ts","../node_modules/acp-kernel/src/prune.ts","../node_modules/acp-kernel/src/sync.ts","../node_modules/acp-kernel/src/tokenize.ts","../node_modules/acp-kernel/src/config.ts","../node_modules/acp-kernel/src/boundaries.ts","../node_modules/acp-kernel/src/truncate-tools.ts","../node_modules/acp-kernel/src/hide-consumed.ts","../node_modules/acp-kernel/src/filter/registry.ts","../node_modules/acp-kernel/src/filter/apply.ts","../node_modules/acp-kernel/src/render-refs.ts","../node_modules/acp-kernel/src/protected.ts","../node_modules/acp-kernel/src/tool-pairs.ts","../node_modules/acp-kernel/src/reasoning-pairs.ts","../node_modules/acp-kernel/src/recommend.ts","../node_modules/acp-kernel/src/pipeline.ts","../node_modules/acp-kernel/src/compress.ts","../node_modules/acp-kernel/src/compression-rules.ts","../node_modules/acp-kernel/src/prompts.ts","../node_modules/acp-kernel/src/nudge-text.ts","../node_modules/acp-kernel/src/decompress.ts","../node_modules/acp-kernel/src/report.ts","../node_modules/acp-kernel/src/rebuild.ts","../node_modules/acp-kernel/src/transform-channel.ts","../node_modules/acp-kernel/src/search/stemmer.ts","../node_modules/acp-kernel/src/search/tokenizer.ts","../node_modules/acp-kernel/src/search/doc-cache.ts","../node_modules/acp-kernel/src/search/algorithms/substring.ts","../node_modules/acp-kernel/src/search/algorithms/bm25.ts","../node_modules/acp-kernel/src/search/algorithms/fuzzy.ts","../node_modules/acp-kernel/src/search/algorithms/hybrid.ts","../node_modules/acp-kernel/src/search/registry.ts","../node_modules/acp-kernel/src/search/types.ts","../node_modules/acp-kernel/src/search/index.ts","../src/region.ts","../src/session-events.ts","../src/tool-pairing.ts","../src/messages.ts","../src/host-tokens.ts","../src/state.ts","../src/tools.ts","../src/config.ts","../src/nudge.ts","../src/prompts.ts","../src/window.ts","../src/commands.ts","../src/system-prompt.ts"],"sourcesContent":["/**\n * billion-context-dsh — Active Context Pruning (ACP) for the DeepSeek Harness,\n * delivered as a `CompactionEngine` backend.\n *\n * The model decides when and what to compress (pure ACP semantics):\n * - the `compress` tool durably shadows a surface range with the model-written\n * summary (no second LLM summarization call — the ACP cost win);\n * - the original events stay in the append-only session log, so `decompress`,\n * `search_context`, and replay always work;\n * - refs are surface seqs carried by the injected nudge's range table (DSH\n * has no in-memory message rewrite hook — see docs/dsh-porting-verification.md);\n * - automatic policy never summarizes by itself: it nudges the model.\n *\n * Mount it wherever a compaction backend is expected:\n *\n * ```yaml\n * - id: compaction-billion-context\n * name: 'billion-context-dsh'\n * config:\n * modelContextLimit: 128000\n * ```\n *\n * The package registers `ctx.compaction` plus the four model tools and the\n * `/acp` command when the hosting composition provides `ctx.tools` /\n * `ctx.commands`.\n * @module billion-context-dsh\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport {\n CompactionEngine,\n ManualCompactionError,\n type CompactionAgentContext,\n type CompactionResult,\n type CompactionTrigger,\n type ManualCompactAgentContext,\n} from '@deepseek-ai/dsh-compaction'\nimport { createCore, type CompressionCore } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { makeTools, type ToolEnvironment } from './tools.ts'\nimport { acpCommand } from './commands.ts'\nimport { buildNudge } from './nudge.ts'\nimport { ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nimport { renderSystemPrompt, resolvePrompts, type AcpPrompts, type ResolvedPrompts } from './prompts.ts'\nimport { DEFAULT_CONTEXT_WINDOW, probeModelWindow, projectedContextWindow, type AcpWindow } from './window.ts'\nimport { deferCompressPairHide, stripOrphanedSurfaceToolMessages } from './region.ts'\n\nexport { AcpStateStore } from './state.ts'\nexport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nexport { ACP_SYSTEM_PROMPT, ACP_SYSTEM_PROMPT_ORDER } from './system-prompt.ts'\nexport {\n DEFAULT_PROMPTS,\n DEFAULT_RESOLVED,\n renderSystemPrompt,\n renderTemplate,\n resolvePrompts,\n type AcpPrompts,\n type NudgePrompts,\n type PromptInput,\n type PromptOverride,\n type RangeTablePrompts,\n type ResolvedPrompts,\n type ToolPrompts,\n} from './prompts.ts'\nexport { makeTools, type ToolEnvironment } from './tools.ts'\nexport { acpCommand } from './commands.ts'\nexport { buildNudge, resolveTokenCount, type NudgeEnvironment, type NudgeOutcome } from './nudge.ts'\nexport {\n DEFAULT_CONTEXT_WINDOW,\n detectContextWindow,\n projectedContextWindow,\n windowSourceLabel,\n type AcpWindow,\n} from './window.ts'\nexport {\n AlreadyCompressedRangeError,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n findOpenTurn,\n assertNoActiveCompaction,\n blockRegistry,\n blockRefForSummarySeq,\n compactionIdsOfKernelBlocks,\n summarySeqOfKernelBlock,\n expandShadowedSeqs,\n hideCompressToolPair,\n stripOrphanedSurfaceToolMessages,\n type AcpBlockLedgerEntry,\n type CompactionTransactionInput,\n type ResolvedSurfaceRange,\n} from './region.ts'\nexport { eventsToCoreMessages, projectEvent, surfaceEventsOf, extractEventText } from './messages.ts'\n\nexport interface AcpConfig {\n /**\n * The context window used for pressure decisions, in tokens. When omitted,\n * `autoModelContextLimit` (default true) resolves it automatically: the live\n * host session projection (`contextPressure.contextWindow`) is preferred,\n * then the model's real window is probed via\n * `agent.ctx.llm.resolveModelInfo(provider, model)`; an explicit value\n * always wins and disables both.\n */\n readonly modelContextLimit?: number\n /** Auto-resolve the real context window: host session projection first, then the LLM runtime probe. Default true. */\n readonly autoModelContextLimit: boolean\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default 0.45 — same as billion-context-pi. */\n readonly nudgeMinContextLimitPct?: number\n /**\n * Nudge window upper bound — over-limit guarantee line: above this the\n * kernel injects a nudge regardless of growth or cadence. Engine default\n * 0.70 (deliberately BELOW the kernel/billion-context-pi default 0.75 and\n * the host compaction-basic auto-compaction line 0.80, so the forced nudge\n * always fires first); an explicit value wins over this default — a\n * same-name key in `coreOverrides.nudge` wins over both (it merges last).\n */\n readonly nudgeMaxContextLimitPct?: number\n /**\n * Emergency nudge threshold (bypasses the per-turn dedup). Engine default\n * 0.85 (down from the kernel/billion-context-pi default 0.95: 95% leaves\n * the model no room to act before the API rejects, and the host's 80%\n * compaction-basic line shadows it in standard/code/cordis modes).\n */\n readonly nudgeEmergencyThresholdPct?: number\n /**\n * Any other acp-kernel Config override (billion-context-pi's `coreOverrides`\n * escape hatch). Merge order per section: kernel defaults → the engine pct\n * knobs above → these keys land LAST, so a same-name key here wins.\n */\n readonly coreOverrides?: Partial<import('acp-kernel').Config>\n /**\n * Custom token-count function for the kernel's internal estimation.\n * Defaults to the kernel's `defaultCountTokens` (CJK: 1 char = 1 token,\n * other: 4 chars = 1 token — aligns with billion-context-pi).\n * Can be overridden for provider-specific tokenization, e.g. DeepSeek's\n * official coefficient: 1 CJK char ≈ 0.6 tokens, 1 other char ≈ 0.3 tokens.\n * Only affects the kernel's internal estimation (compressible range sizing,\n * nudge text, growth branch pending); the `projectedTokens` reading from\n * `sessionProjections` (used for nudge pressure decisions and acp_status)\n * is provider-anchored and unaffected by this function.\n */\n readonly countTokens?: (text: string) => number\n /** Register the four model tools on `ctx.tools`. Default true. */\n readonly autoTools: boolean\n /** Register the `/acp` command on `ctx.commands`. Default true. */\n readonly autoCommand: boolean\n /** Inject the nudge into `agent/pre-step` when the kernel recommends it. Default true. */\n readonly autoNudge: boolean\n /** Per-stage prompt template overrides (nudge / range table / system prompt / tool descriptions). See docs/configurable-prompts-design.md. */\n readonly prompts?: AcpPrompts\n}\n\nconst DEFAULT_CONFIG: AcpConfig = {\n autoModelContextLimit: true,\n autoTools: true,\n autoCommand: true,\n autoNudge: true,\n // Nudge thresholds: engine defaults 0.70/0.85 — deliberately below the\n // kernel/billion-context-pi 0.75/0.95. 0.95 leaves no room to act before\n // the API rejects, and the host's compaction-basic line (thresholdRatio\n // 0.80) shadows it in standard/code/cordis modes; 0.70 keeps the forced\n // over-limit nudge ahead of that 80% line. Explicit values always win\n // against these defaults — `coreOverrides` merges last and beats them on\n // same-name keys.\n nudgeMaxContextLimitPct: 0.7,\n nudgeEmergencyThresholdPct: 0.85,\n}\n\nexport function resolveAcpConfig(config: Partial<AcpConfig> = {}): AcpConfig {\n return { ...DEFAULT_CONFIG, ...config }\n}\n\n/**\n * The ACP compaction backend. Subclasses the seam exactly like\n * `dsh-compaction-basic`; swaps summarization-driven compaction for\n * model-driven block compression without touching the agent loop.\n */\nexport class AcpCompactionEngine extends CompactionEngine {\n /** The framework-agnostic ACP compression core, reused verbatim. */\n readonly kernel: CompressionCore\n /** Per-session kernel state. */\n readonly store: AcpStateStore\n /** Resolved engine configuration. */\n readonly config: AcpConfig\n /** Resolved prompt templates (validated at construction — fail-fast on template typos). */\n readonly prompts: ResolvedPrompts\n /**\n * The environment wired into tools / command / nudge. Exposed so tests (and\n * introspection) can assert the forwarding actually happened: the config\n * chain user config → this.config → env → kernelConfigFor is all OPTIONAL\n * fields, so a dropped forwarding line fails typecheck silently and would\n * revive lost-config bugs with every unit test green.\n */\n readonly env: ToolEnvironment\n\n private readonly lastNudgeTurn = new Map<string, number>()\n /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */\n private readonly compressCallIdsToHide = new Set<string>()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map<string, AcpWindow>()\n /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */\n private readonly outputReservationCache = new Map<string, number | null>()\n\n constructor(ctx: Context, config: Partial<AcpConfig> = {}) {\n super(ctx)\n this.config = resolveAcpConfig(config)\n // Resolve + validate prompt templates BEFORE building env: a template typo\n // must fail engine construction, never silently leak into model context.\n this.prompts = resolvePrompts(config.prompts)\n const ports = this.config.countTokens !== undefined ? { countTokens: this.config.countTokens } : {}\n this.kernel = createCore(ports)\n this.store = new AcpStateStore()\n\n const env: ToolEnvironment = {\n kernel: this.kernel,\n store: this.store,\n // Initial value before any probe; windowFor() replaces it per pre-step.\n modelContextLimit: this.config.modelContextLimit ?? DEFAULT_CONTEXT_WINDOW,\n nudgeMinContextLimitPct: this.config.nudgeMinContextLimitPct,\n nudgeMaxContextLimitPct: this.config.nudgeMaxContextLimitPct,\n nudgeEmergencyThresholdPct: this.config.nudgeEmergencyThresholdPct,\n coreOverrides: this.config.coreOverrides,\n windowFor: (agent) => this.windowFor(agent),\n prompts: this.prompts,\n compressCallIdsToHide: this.compressCallIdsToHide,\n }\n this.env = env\n\n // Tools and commands may not be registered yet on cold start: cordis\n // starts unrelated composition rows concurrently, so the first\n // `ctx.get('tools')` can legitimately be undefined even though the row\n // ships later in the file. HMR-style reloads always see them (already\n // present), but a fresh process races — the tools silently vanished on\n // restart. Register eagerly, then re-attempt when the service appears\n // (`internal/service`) or the app finishes booting (`ready`); guard so a\n // late callback never double-registers.\n const tools = ctx.get('tools')\n if (tools !== undefined) {\n for (const tool of makeTools(env)) tools.register(tool)\n } else {\n let done = false\n const registerTools = (): void => {\n if (done) return\n const registry = ctx.get('tools')\n if (registry === undefined) return\n done = true\n for (const tool of makeTools(env)) registry.register(tool)\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'tools') registerTools()\n })\n }\n const commands = ctx.get('commands')\n if (commands !== undefined) {\n commands.register(acpCommand(env))\n } else {\n let done = false\n const registerCommand = (): void => {\n if (done) return\n const registry = ctx.get('commands')\n if (registry === undefined) return\n done = true\n registry.register(acpCommand(env))\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'commands') registerCommand()\n })\n }\n // After a successful compress tool result is appended, hide its\n // call/result pair. The durable summary node was inserted mid-turn (before\n // the result), so leaving the pair visible would put a user message between\n // an assistant tool_calls block and its tool response — strict providers\n // reject that request with HTTP 400 (issue #18).\n ctx.on('session/event', (session, event) => {\n if (event.type !== 'tool/result') return\n const message = event.data.message\n const block = message.content[0]\n const callId = block?.toolCallId ?? message.source.callId\n if (typeof callId !== 'string' || !this.compressCallIdsToHide.has(callId)) return\n this.compressCallIdsToHide.delete(callId)\n // session.append is NOT reentrant: calling it synchronously inside this\n // session/event dispatch (the outer append still holds the reentry lock)\n // throws \"session append cannot reenter while another append is being\n // published\" on live, store-attached sessions, and the dispatcher\n // silently swallows the error — the hide would be a no-op. Defer it to a\n // microtask: microtasks drain after the append fully publishes and\n // before the agent loop resumes, so the pair is hidden before the next\n // request is built.\n deferCompressPairHide(session, callId, event.seq, (error) => {\n ctx.logger.warn(`billion-context-dsh: hide compress call/result pair failed: ${String(error)}`)\n })\n })\n ctx.on('agent/pre-step', async (payload, next) => {\n // A crash-interrupted tool leaves an orphan call/result on the surface:\n // it corrupts the pairing balance cache AND can 400 the next request\n // (strict providers reject tool messages without their call/response).\n // Clean them before EVERY step — not only when a nudge fires — so a\n // low-pressure session never hits the orphan 400 (issue #18). No call is\n // in flight at pre-step (the previous step's tools all landed), so the\n // default empty in-flight set is safe.\n stripOrphanedSurfaceToolMessages(payload.agent.session)\n if (!this.config.autoNudge) return next()\n const decision = await next()\n if (decision.kind === 'reject') return decision\n const window = await this.windowFor(payload.agent)\n const outcome = buildNudge(payload.agent, { ...env, modelContextLimit: window.limit }, this.lastNudgeTurn)\n if (outcome === null) return decision\n return { kind: 'enter', messages: [...decision.messages, outcome.message] }\n })\n // The load-bearing ACP guidance lives in the system prompt ONCE; nudges\n // stay short and advisory (model-driven: the model decides). The\n // systemPrompt service may not be registered yet on cold start (cordis\n // starts unrelated composition rows concurrently), so apply the same\n // retry pattern as tools and commands: eager registration, then\n // re-attempt when the service appears via `internal/service`; guard so a\n // late callback never double-registers.\n const systemPrompt = ctx.get('systemPrompt')\n if (systemPrompt !== undefined) {\n systemPrompt.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: renderSystemPrompt(this.prompts),\n })\n } else {\n let done = false\n const registerSystemPrompt = (): void => {\n if (done) return\n const registry = ctx.get('systemPrompt')\n if (registry === undefined) return\n done = true\n registry.section({\n name: 'billion-context-dsh',\n order: ACP_SYSTEM_PROMPT_ORDER,\n text: renderSystemPrompt(this.prompts),\n })\n }\n ctx.on('internal/service', (name: unknown) => {\n if (name === 'systemPrompt') registerSystemPrompt()\n })\n }\n }\n\n /**\n * Resolve the effective context window for an agent. An explicitly\n * configured `modelContextLimit` always wins (no probe). Otherwise the live\n * session projection (`contextPressure.contextWindow`) is preferred when it\n * discloses one — it tracks the session's CURRENT route, so a mid-session\n * model switch repairs itself without a restart or config (see\n * projectedContextWindow). Falls back to probing the model's real window\n * via `agent.ctx.llm.resolveModelInfo` (cached per provider/model route,\n * probe failures cached too) and finally to DEFAULT_CONTEXT_WINDOW when\n * auto-detection is disabled or unavailable. On the auto-detected paths the\n * adapter's per-request output cap is then SUBTRACTED from the window\n * (applyReservation): every downstream usage computation must run against\n * the SUSTAINABLE input budget (window minus output reservation), not the\n * raw window — a 96K window with a 16K cap carries at most 80K of input,\n * so the raw denominator understates usage by cap/window (≈17% there, and\n * far worse on short-window models). An explicit limit keeps the operator's\n * exact value (they own the denominator); a failed probe keeps the raw\n * fallback.\n */\n async windowFor(agent: Agent): Promise<AcpWindow> {\n if (this.config.modelContextLimit !== undefined) {\n return { limit: this.config.modelContextLimit, source: 'explicit' }\n }\n const provider = agent.options.provider ?? ''\n const model = agent.options.model ?? ''\n const key = `${provider}\\0${model}`\n // Projection source first: it reflects the live route (agent.options is a\n // stale snapshot after a model switch), and it is not cached here because\n // the projection itself refreshes on every request — caching would freeze\n // the old model's window for the whole process (the false-EMERGENCY trap).\n // Only consulted when auto detection is enabled (same gate as the probe).\n if (this.config.autoModelContextLimit) {\n const projected = projectedContextWindow(agent)\n if (projected !== null) {\n // The window comes from the live projection; the output cap still\n // comes from the (cached) model probe — the projection schema carries\n // no cap. After a mid-session switch agent.options names the\n // PREVIOUS route, so the cap is the best available, not the live one.\n const cap = await this.outputCapFor(agent, provider, model)\n return this.applyReservation({ limit: projected, source: 'projection', provider, model }, cap)\n }\n }\n const cached = this.windowCache.get(key)\n if (cached !== undefined) return cached\n let window: AcpWindow\n let cap: number | null = null\n if (!this.config.autoModelContextLimit) {\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model }\n } else {\n const probe = await probeModelWindow(agent, provider, model)\n cap = probe.outputReservation\n if (probe.contextWindow === null) {\n // Probe failures are cached below too, so the 128K fallback sticks for\n // the whole process lifetime — a gateway operator who fixes the model\n // API must restart (or set modelContextLimit) before the probe retries.\n // Warn loudly instead of failing silently: pressure numbers computed\n // against the fallback are what issue #63's false emergency nudges\n // came from (a gateway that disclosed no window read as ~55% of 128K\n // when the real window was 1M).\n this.ctx.logger.warn(\n `billion-context-dsh: context-window auto-detection failed for ${provider}/${model} — using the ${DEFAULT_CONTEXT_WINDOW} fallback (restart to re-probe, or set modelContextLimit explicitly)`,\n )\n window = { limit: DEFAULT_CONTEXT_WINDOW, source: 'default', provider, model, probeFailed: true }\n cap = null // the probe failed or disclosed nothing — no cap either\n } else {\n window = { limit: probe.contextWindow, source: 'auto', provider, model }\n }\n }\n window = this.applyReservation(window, cap)\n this.windowCache.set(key, window)\n return window\n }\n\n /**\n * The adapter's per-request output cap for a route, from one\n * probeModelWindow call (a local catalog lookup — no request is sent),\n * cached per route like the window itself.\n */\n private async outputCapFor(agent: Agent, provider: string, model: string): Promise<number | null> {\n if (provider === '' || model === '') return null\n const key = `${provider}\\0${model}`\n const known = this.outputReservationCache.get(key)\n if (known !== undefined) return known\n const cap = (await probeModelWindow(agent, provider, model)).outputReservation\n this.outputReservationCache.set(key, cap)\n return cap\n }\n\n /**\n * Subtract the output reservation from a resolved window: `limit` becomes\n * the SUSTAINABLE input budget (`rawLimit - outputReserved`) that every\n * downstream usage computation (nudge tiers, truncate, growth) measures\n * against. No-op when the cap is unknown or not smaller than the window\n * (degenerate config) — the raw-window behavior is preserved.\n */\n private applyReservation(window: AcpWindow, cap: number | null): AcpWindow {\n if (cap === null || cap >= window.limit) return window\n return { ...window, rawLimit: window.limit, outputReserved: cap, limit: window.limit - cap }\n }\n\n /** ACP is model-driven: automatic pressure policy never summarizes by itself. */\n override async compactIfNeeded(\n _agent: CompactionAgentContext,\n _trigger: CompactionTrigger,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /** Explicit idle-session compaction: ACP leaves the decision to the model. */\n override async compactNow(\n _agent: ManualCompactAgentContext,\n signal: AbortSignal,\n ): Promise<CompactionResult | null> {\n signal.throwIfAborted()\n return null\n }\n\n /**\n * The model-driven path lands through the `compress` tool, which runs the\n * full durable transaction directly. This seam method rejects with guidance:\n * automatic summarization is exactly what ACP replaces.\n */\n override async compactRegion(\n _start: number,\n _end: number,\n _agent: CompactionAgentContext,\n signal?: AbortSignal,\n ): Promise<CompactionResult> {\n signal?.throwIfAborted()\n throw new ManualCompactionError(\n 'summary',\n 'billion-context-dsh is model-driven: use the compress tool instead of automatic summarization',\n )\n }\n}\n\nexport default AcpCompactionEngine\n","import type { CoreMessage, MessageRefMap } from \"./types.js\";\n\nconst REF_WIDTH = 5;\nconst MIN_INDEX = 1;\nconst MAX_INDEX = 99999;\nconst REF_PATTERN = /^m0*(\\d{1,5})$/;\n\nexport const BLOCKED_REF = \"BLOCKED\";\n\nexport function emptyRefMap(): MessageRefMap {\n return { byRaw: {}, byRef: {} };\n}\n\nexport function indexToRef(index: number): string {\n if (!Number.isInteger(index) || index < MIN_INDEX || index > MAX_INDEX) {\n throw new RangeError(\n `ref index out of bounds: ${index} (allowed ${MIN_INDEX}-${MAX_INDEX})`,\n );\n }\n return `m${String(index).padStart(REF_WIDTH, \"0\")}`;\n}\n\nexport function refToIndex(ref: string): number | null {\n const match = REF_PATTERN.exec(ref.trim().toLowerCase());\n if (!match) return null;\n const index = Number(match[1]);\n if (index < MIN_INDEX || index > MAX_INDEX) return null;\n return index;\n}\n\nexport function refForRaw(map: MessageRefMap, rawId: string): string | null {\n return map.byRaw[rawId] ?? null;\n}\n\nexport function rawForRef(map: MessageRefMap, ref: string): string | null {\n return map.byRef[ref] ?? null;\n}\n\nexport interface AssignRefsResult {\n map: MessageRefMap;\n nextIndex: number;\n newlyAssigned: number;\n}\n\nexport interface AssignRefsOptions {\n existing: MessageRefMap;\n nextIndex: number;\n isProtected?: (message: CoreMessage) => boolean;\n shouldSkip?: (message: CoreMessage) => boolean;\n}\n\nexport function assignRefs(\n messages: CoreMessage[],\n options: AssignRefsOptions,\n): AssignRefsResult {\n const map: MessageRefMap = {\n byRaw: { ...options.existing.byRaw },\n byRef: { ...options.existing.byRef },\n };\n let cursor =\n Number.isInteger(options.nextIndex) && options.nextIndex >= MIN_INDEX\n ? options.nextIndex\n : MIN_INDEX;\n let newlyAssigned = 0;\n\n for (const message of messages) {\n if (!message.id || options.shouldSkip?.(message)) continue;\n\n if (map.byRaw[message.id]) continue;\n\n if (options.isProtected?.(message)) {\n map.byRaw[message.id] = BLOCKED_REF;\n continue;\n }\n\n const ref = allocateFreeRef(map, cursor);\n cursor = ref.index + 1;\n map.byRaw[message.id] = ref.text;\n map.byRef[ref.text] = message.id;\n newlyAssigned++;\n }\n\n return { map, nextIndex: cursor, newlyAssigned };\n}\n\nfunction allocateFreeRef(\n map: MessageRefMap,\n start: number,\n): { text: string; index: number } {\n let candidate = Math.max(start, MIN_INDEX);\n while (candidate <= MAX_INDEX) {\n const text = indexToRef(candidate);\n if (!map.byRef[text]) {\n return { text, index: candidate };\n }\n candidate++;\n }\n throw new Error(\n `ref capacity exhausted: cannot allocate beyond ${indexToRef(MAX_INDEX)}`,\n );\n}\n\nexport function rebuildRefIndex(map: MessageRefMap): MessageRefMap {\n const byRef: Record<string, string> = {};\n for (const [rawId, ref] of Object.entries(map.byRaw)) {\n if (ref !== BLOCKED_REF) byRef[ref] = rawId;\n }\n return { byRaw: { ...map.byRaw }, byRef };\n}\n\nexport function highestUsedIndex(map: MessageRefMap): number {\n let highest = 0;\n for (const ref of Object.values(map.byRaw)) {\n const index = ref === BLOCKED_REF ? null : refToIndex(ref);\n if (index !== null && index > highest) highest = index;\n }\n return highest;\n}\n","import type { CompressionBlock, CompressionState } from \"./types.js\";\n\nexport function createInitialState(): CompressionState {\n return {\n blocks: [],\n messageRefs: { byRaw: {}, byRef: {} },\n tokenSnapshot: {},\n nudge: {\n lastPerMessageNudgeTokens: 0,\n lastNudgeShownTokens: 0,\n baselineTokens: 0,\n anchors: {},\n lastShownByTier: {},\n },\n stats: { tokensCompressed: 0, compressionCount: 0 },\n nextBlockId: 1,\n nextRunId: 1,\n };\n}\n\nexport function allocateBlockId(state: CompressionState): string {\n const id = state.nextBlockId;\n state.nextBlockId = Math.max(1, id) + 1;\n return `b${id}`;\n}\n\nexport function allocateRunId(state: CompressionState): string {\n const id = state.nextRunId;\n state.nextRunId = Math.max(1, id) + 1;\n return `r${id}`;\n}\n\nexport function blockById(\n state: CompressionState,\n blockId: string,\n): CompressionBlock | undefined {\n return state.blocks.find((block) => block.blockId === blockId);\n}\n\nexport function activeBlocks(state: CompressionState): CompressionBlock[] {\n return state.blocks.filter((block) => block.active);\n}\n\nexport function coveredMessageIds(state: CompressionState): Set<string> {\n const covered = new Set<string>();\n for (const block of state.blocks) {\n if (!block.active) continue;\n for (const id of block.effectiveMessageIds) covered.add(id);\n }\n return covered;\n}\n\nexport function highestActiveTier(state: CompressionState): 0 | 1 | 2 | 3 {\n let highest: 0 | 1 | 2 | 3 = 0;\n for (const block of state.blocks) {\n if (block.active && block.tier > highest) highest = block.tier;\n }\n return highest;\n}\n\nexport function advanceSurvival(\n state: CompressionState,\n promotionThreshold: number,\n): void {\n for (const block of state.blocks) {\n if (!block.active) continue;\n block.survivedCount += 1;\n if (block.survivedCount >= promotionThreshold) {\n block.generation = \"old\";\n }\n }\n}\n","import { activeBlocks, coveredMessageIds } from \"./state.js\";\nimport type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport const SUMMARY_HEADER = \"[Compressed conversation section]\";\n\nexport interface PruneOptions {\n injectSummaries?: boolean;\n}\n\nexport function prune(\n messages: CoreMessage[],\n state: CompressionState,\n options: PruneOptions = {},\n): CoreMessage[] {\n const covered = coveredMessageIds(state);\n if (covered.size === 0) return [...messages];\n\n const inject = options.injectSummaries ?? true;\n const firstUserIndex = messages.findIndex(\n (message) => message.role === \"user\",\n );\n\n const indexById = new Map<string, number>();\n messages.forEach((message, index) => indexById.set(message.id, index));\n\n const anchors = inject ? collectSummaryAnchors(state, indexById) : [];\n\n return stripOrphanedReasoning(\n stripOrphanedToolResults(\n stripOrphanedToolCalls(\n rebuildMessages(messages, covered, firstUserIndex, anchors),\n ),\n ),\n );\n}\n\ninterface SummaryAnchor {\n blockId: string;\n summary: string;\n topic?: string;\n insertAt: number;\n}\n\nfunction collectSummaryAnchors(\n state: CompressionState,\n indexById: Map<string, number>,\n): SummaryAnchor[] {\n const anchors: SummaryAnchor[] = [];\n for (const block of activeBlocks(state)) {\n let earliest: number | null = null;\n for (const id of block.effectiveMessageIds) {\n const index = indexById.get(id);\n if (index !== undefined && (earliest === null || index < earliest)) {\n earliest = index;\n }\n }\n anchors.push({\n blockId: block.blockId,\n summary: block.summary,\n topic: block.topic,\n insertAt: earliest ?? 0,\n });\n }\n anchors.sort((left, right) => left.insertAt - right.insertAt);\n return anchors;\n}\n\nfunction rebuildMessages(\n messages: CoreMessage[],\n covered: Set<string>,\n firstUserIndex: number,\n anchors: SummaryAnchor[],\n): CoreMessage[] {\n const result: CoreMessage[] = [];\n const pending = [...anchors];\n\n for (let index = 0; index < messages.length; index++) {\n while (pending.length > 0 && pending[0]!.insertAt === index) {\n result.push(renderSummary(pending.shift()!));\n }\n if (index === firstUserIndex && firstUserIndex >= 0) {\n result.push(messages[index]!);\n continue;\n }\n if (covered.has(messages[index]!.id)) continue;\n result.push(messages[index]!);\n }\n\n while (pending.length > 0) {\n result.push(renderSummary(pending.shift()!));\n }\n\n return result;\n}\n\nfunction renderSummary(anchor: SummaryAnchor): CoreMessage {\n const body = anchor.summary.trim();\n const topicLine = anchor.topic\n ? `${SUMMARY_HEADER} — ${anchor.topic}`\n : SUMMARY_HEADER;\n const text = body.length === 0 ? topicLine : `${topicLine}\\n${body}`;\n return {\n id: `acp_summary_${anchor.blockId}`,\n role: \"system\",\n contentType: \"text\",\n text,\n };\n}\n\nfunction stripOrphanedToolResults(messages: CoreMessage[]): CoreMessage[] {\n const knownCallIds = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-call\" && m.toolCallId) {\n knownCallIds.add(m.toolCallId);\n }\n }\n return messages.filter(\n (m) =>\n m.contentType !== \"tool-result\" ||\n !m.toolCallId ||\n knownCallIds.has(m.toolCallId),\n );\n}\n\nfunction stripOrphanedToolCalls(messages: CoreMessage[]): CoreMessage[] {\n const knownResultIds = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-result\" && m.toolCallId) {\n knownResultIds.add(m.toolCallId);\n }\n }\n return messages.filter(\n (m) =>\n m.contentType !== \"tool-call\" ||\n !m.toolCallId ||\n m.toolName === \"compress\" ||\n knownResultIds.has(m.toolCallId),\n );\n}\n\n/**\n * Defense-in-depth for reasoning/text pairing (analogue of\n * {@link stripOrphanedToolCalls}). A `reasoning` message is only meaningful\n * when immediately followed — after any same-run reasoning — by its companion\n * assistant text/tool-call; strict thinking models (DeepSeek et al.) reject\n * reasoning_content that has lost its response with HTTP 400. Compress-time\n * boundary expansion normally keeps the pair in one block, so this only fires\n * for degenerate straddles (block-boundary ranges, malformed input, or a\n * reasoning that never had a companion): drop the dangling run rather than\n * ship a 400-triggering half-pair. Runs AFTER tool stripping, since removing\n * an orphaned tool-call can leave its preceding reasoning dangling too.\n */\nfunction stripOrphanedReasoning(messages: CoreMessage[]): CoreMessage[] {\n const drop = new Set<number>();\n for (let i = 0; i < messages.length; i++) {\n if (drop.has(i)) continue;\n if (messages[i]!.contentType !== \"reasoning\") continue;\n let j = i;\n while (\n j + 1 < messages.length &&\n messages[j + 1]!.contentType === \"reasoning\"\n ) {\n j++;\n }\n const companion = messages[j + 1];\n const hasCompanion =\n companion !== undefined &&\n companion.role === \"assistant\" &&\n (companion.contentType === \"text\" ||\n companion.contentType === \"tool-call\");\n if (!hasCompanion) {\n for (let k = i; k <= j; k++) drop.add(k);\n }\n }\n if (drop.size === 0) return messages;\n return messages.filter((_, i) => !drop.has(i));\n}\n","import type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport interface SyncResult {\n state: CompressionState;\n deactivated: string[];\n}\n\nexport function syncBlocks(\n messages: CoreMessage[],\n state: CompressionState,\n): SyncResult {\n const presentIds = new Set(messages.map((message) => message.id));\n const deactivated: string[] = [];\n // Deep-clone (not just `{...state}`) so the caller's input state is never\n // mutated: processTurn stamps `state.nudge.*` and reassigns `messageRefs`,\n // and block sub-arrays must not alias the input. Previously nudge/stats/\n // messageRefs were shared references → input-state mutation leak.\n const result: CompressionState = {\n blocks: state.blocks.map((block) => ({\n ...block,\n directMessageIds: [...block.directMessageIds],\n effectiveMessageIds: [...block.effectiveMessageIds],\n directBlockIds: [...block.directBlockIds],\n })),\n messageRefs: {\n byRaw: { ...state.messageRefs.byRaw },\n byRef: { ...state.messageRefs.byRef },\n },\n // Snapshot is keyed by ref with primitive values — shallow copy suffices.\n tokenSnapshot: { ...(state.tokenSnapshot ?? {}) },\n nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },\n stats: { ...state.stats },\n nextBlockId: state.nextBlockId,\n nextRunId: state.nextRunId,\n };\n\n // Refs are additive (assignRefs never removes them from messageRefs), so\n // prune the snapshot by currently-present message refs — otherwise it grows\n // unboundedly as messages are compressed/deleted across a long session.\n const liveRefs = new Set(\n messages\n .map((m) => result.messageRefs.byRaw[m.id])\n .filter((r): r is string => typeof r === \"string\"),\n );\n if (Object.keys(result.tokenSnapshot).length !== liveRefs.size) {\n const pruned: Record<string, number> = {};\n for (const [ref, n] of Object.entries(result.tokenSnapshot)) {\n if (liveRefs.has(ref)) pruned[ref] = n;\n }\n result.tokenSnapshot = pruned;\n }\n\n const consumedBlockIds = new Set<string>();\n for (const block of result.blocks) {\n for (const consumedId of block.directBlockIds) {\n consumedBlockIds.add(consumedId);\n }\n }\n\n for (const block of result.blocks) {\n if (consumedBlockIds.has(block.blockId)) {\n block.active = false;\n continue;\n }\n block.active = true;\n const stillPresent = block.effectiveMessageIds.some((id) =>\n presentIds.has(id),\n );\n if (!stillPresent) {\n block.active = false;\n deactivated.push(block.blockId);\n }\n }\n\n return { state: result, deactivated };\n}\n","import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\nexport function defaultCountTokens(text: string): number {\n if (!text) return 0;\n // CJK chars tokenize ~1:1 (chars/4 badly underestimates them). Count them\n // directly, then estimate the non-CJK remainder with chars/4 so digits,\n // punctuation, and symbols in code/JSON are not dropped to zero.\n const cjk = text.match(/[\\u4e00-\\u9fff\\u3040-\\u30ff\\uac00-\\ud7af]/g);\n const cjkCount = cjk?.length ?? 0;\n return cjkCount + Math.ceil((text.length - cjkCount) / 4);\n}\n\nexport function estimateMessageTokens(text: string | undefined): number {\n return defaultCountTokens(text ?? \"\");\n}\n\nexport function estimateTokensFast(text: string): number {\n if (!text) return 0;\n return Math.ceil(text.length / 4);\n}\n\nexport type TokenCountFn = (text: string) => number;\n\nconst BPE_SIZE_GUARD = 100_000;\n\nexport function createBpeTokenizer(): TokenCountFn {\n try {\n const mod = require(\"@anthropic-ai/tokenizer\");\n const bpeCount = mod.countTokens ?? mod.default?.countTokens;\n if (typeof bpeCount !== \"function\") return defaultCountTokens;\n return (text: string) => {\n if (text.length > BPE_SIZE_GUARD) return defaultCountTokens(text);\n try {\n return bpeCount(text);\n } catch {\n return defaultCountTokens(text);\n }\n };\n } catch {\n return defaultCountTokens;\n }\n}\n","import type { Config } from \"./types.js\";\n\nexport function defaultConfig(\n modelContextLimit: number,\n overrides: Partial<Config> = {},\n): Config {\n const base: Config = {\n tiers: { enabled: true, tier2Trigger: 5, tier3Trigger: 10 },\n nudge: {\n maxContextLimitPct: 0.75,\n minContextLimitPct: 0.45,\n frequency: 5,\n iterationThreshold: 15,\n force: \"soft\",\n growthRatio: 0.05,\n growthFloor: 50000,\n growthCap: 50000,\n minGrowthFloor: 20000,\n minGrowthRatio: 0.45,\n emergencyThresholdPct: 0.95,\n tier2GrowthMultiplier: 1.5,\n },\n promotionThreshold: 5,\n truncate: { threshold: 0.95 },\n compress: {\n minCompressRange: 5000,\n maxSummaryLength: 20000,\n minSummaryLength: 50,\n },\n protectedTools: [],\n preserveRecentMessages: 5,\n preserveRecentTokens: 5000,\n modelContextLimit,\n };\n return {\n ...base,\n ...overrides,\n tiers: { ...base.tiers, ...overrides.tiers },\n nudge: { ...base.nudge, ...overrides.nudge },\n truncate: { ...base.truncate, ...overrides.truncate },\n compress: { ...base.compress, ...overrides.compress },\n };\n}\n\nexport function validateConfig(config: Config): string[] {\n const errors: string[] = [];\n if (\n !Number.isFinite(config.modelContextLimit) ||\n config.modelContextLimit <= 0\n ) {\n errors.push(\"modelContextLimit must be a positive number\");\n }\n if (config.nudge.minContextLimitPct > config.nudge.maxContextLimitPct) {\n errors.push(\n \"nudge.minContextLimitPct must not exceed nudge.maxContextLimitPct\",\n );\n }\n if (config.nudge.maxContextLimitPct > config.nudge.emergencyThresholdPct) {\n errors.push(\n \"nudge.maxContextLimitPct must not exceed nudge.emergencyThresholdPct\",\n );\n }\n if (config.promotionThreshold < 1) {\n errors.push(\"promotionThreshold must be >= 1\");\n }\n if (config.truncate.threshold <= 0 || config.truncate.threshold > 1) {\n errors.push(\"truncate.threshold must be in (0, 1]\");\n }\n for (const tier of [config.tiers.tier2Trigger, config.tiers.tier3Trigger]) {\n if (tier < 1) errors.push(\"tier triggers must be >= 1\");\n }\n if (config.tiers.tier3Trigger <= config.tiers.tier2Trigger) {\n errors.push(\"tiers.tier3Trigger must be greater than tiers.tier2Trigger\");\n }\n return errors;\n}\n","import { activeBlocks, blockById } from \"./state.js\";\nimport type {\n CompressionState,\n CoreMessage,\n ResolvedBoundary,\n} from \"./types.js\";\n\nexport type BoundaryKind = \"message\" | \"block\";\n\nexport interface ParsedBoundary {\n kind: BoundaryKind;\n numericId: number;\n raw: string;\n}\n\nconst MESSAGE_REF_PATTERN = /^m0*(\\d{1,5})$/;\nconst BLOCK_REF_PATTERN = /^b(\\d{1,9})$/;\n\nexport function parseBoundary(ref: string): ParsedBoundary | null {\n const normalized = ref.trim().toLowerCase();\n const messageMatch = MESSAGE_REF_PATTERN.exec(normalized);\n if (messageMatch) {\n const numericId = Number(messageMatch[1]);\n if (numericId >= 1 && numericId <= 99999) {\n return { kind: \"message\", numericId, raw: normalized };\n }\n }\n const blockMatch = BLOCK_REF_PATTERN.exec(normalized);\n if (blockMatch) {\n const numericId = Number(blockMatch[1]);\n if (numericId >= 1) return { kind: \"block\", numericId, raw: normalized };\n }\n return null;\n}\n\n/**\n * Thrown when a boundary ref parses but cannot be anchored in the visible\n * context. `kind` distinguishes a ref that never existed (\"unknown\", e.g. a\n * typo or a ref from another session) from one that was consumed by an\n * existing block (\"consumed\", messages hidden by prune). `endpoint` names the\n * failing side of the range so callers can attribute the error precisely.\n */\nexport class BoundaryNotFoundError extends Error {\n readonly code = \"BOUNDARY_NOT_FOUND\";\n readonly kind: \"unknown\" | \"consumed\";\n readonly endpoint: \"start\" | \"end\";\n\n constructor(\n kind: \"unknown\" | \"consumed\",\n endpoint: \"start\" | \"end\",\n message: string,\n ) {\n super(message);\n this.name = \"BoundaryNotFoundError\";\n this.code = \"BOUNDARY_NOT_FOUND\";\n this.kind = kind;\n this.endpoint = endpoint;\n }\n}\n\nexport interface ResolveBoundariesInput {\n startRef: string;\n endRef: string;\n messages: CoreMessage[];\n state: CompressionState;\n}\n\nexport interface ResolvedRange {\n startIndex: number;\n endIndex: number;\n messageIds: string[];\n nestedBlockIds: string[];\n boundaryKind: BoundaryKind;\n protectedGaps: number[];\n}\n\nexport function resolveBoundaries(\n input: ResolveBoundariesInput,\n): ResolvedRange {\n const start = parseBoundary(input.startRef);\n const end = parseBoundary(input.endRef);\n if (!start || !end) {\n throw new Error(\n `Invalid boundary ref(s): startId=\"${input.startRef}\", endId=\"${input.endRef}\". Use mNNNNN or bN.`,\n );\n }\n\n const indexByRawId = new Map<string, number>();\n input.messages.forEach((message, index) =>\n indexByRawId.set(message.id, index),\n );\n\n let startIndex = resolveAnchorIndex(start, input.state, indexByRawId, \"start\");\n let endIndex = resolveAnchorIndex(end, input.state, indexByRawId, \"end\");\n\n if (startIndex > endIndex) {\n [startIndex, endIndex] = [endIndex, startIndex];\n }\n\n const messageIds: string[] = [];\n for (let index = startIndex; index <= endIndex; index++) {\n const message = input.messages[index];\n if (message) messageIds.push(message.id);\n }\n\n const boundaryKind: BoundaryKind =\n start.kind === \"block\" || end.kind === \"block\" ? \"block\" : \"message\";\n\n const nestedBlockIds: string[] = [];\n const nestedSeen = new Set<string>();\n for (const block of activeBlocks(input.state)) {\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor !== null && anchor >= startIndex && anchor <= endIndex) {\n if (!nestedSeen.has(block.blockId)) {\n nestedSeen.add(block.blockId);\n nestedBlockIds.push(block.blockId);\n }\n }\n }\n\n const protectedGaps: number[] = [];\n\n return {\n startIndex,\n endIndex,\n messageIds,\n nestedBlockIds,\n boundaryKind,\n protectedGaps,\n };\n}\n\nfunction resolveAnchorIndex(\n boundary: ParsedBoundary,\n state: CompressionState,\n indexByRawId: Map<string, number>,\n endpoint: \"start\" | \"end\",\n): number {\n const label = endpoint === \"start\" ? \"startId\" : \"endId\";\n if (boundary.kind === \"message\") {\n const rawId =\n state.messageRefs.byRef[boundary.raw] ??\n state.messageRefs.byRef[formatPaddedRef(boundary.numericId)];\n if (!rawId) {\n throw new BoundaryNotFoundError(\n \"unknown\",\n endpoint,\n `${label}=\"${boundary.raw}\" does not exist in this session (typo or wrong session) — run acp_status for current refs.`,\n );\n }\n const index = indexByRawId.get(rawId);\n if (index === undefined) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"${boundary.raw}\" not found in visible context (likely consumed by an existing block).`,\n );\n }\n return index;\n }\n\n const block = blockById(state, `b${boundary.numericId}`);\n if (!block) {\n throw new BoundaryNotFoundError(\n \"unknown\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" does not exist in this session (typo or wrong session) — run acp_status for current refs.`,\n );\n }\n if (!block.active) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" not found in visible context (block distilled/consumed by a higher-tier block).`,\n );\n }\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor === null) {\n throw new BoundaryNotFoundError(\n \"consumed\",\n endpoint,\n `${label}=\"b${boundary.numericId}\" not found in visible context (block messages consumed by a higher-tier block).`,\n );\n }\n return anchor;\n}\n\nfunction formatPaddedRef(index: number): string {\n return `m${String(index).padStart(5, \"0\")}`;\n}\n\nexport function earliestIndexOfIds(\n ids: string[],\n indexByRawId: Map<string, number>,\n): number | null {\n let earliest: number | null = null;\n for (const id of ids) {\n const index = indexByRawId.get(id);\n if (index !== undefined && (earliest === null || index < earliest)) {\n earliest = index;\n }\n }\n return earliest;\n}\n\nexport function toResolvedBoundary(range: ResolvedRange): ResolvedBoundary {\n return {\n startIndex: range.startIndex,\n endIndex: range.endIndex,\n protectedGaps: range.protectedGaps,\n };\n}\n","import type { Config, CoreMessage } from \"./types.js\";\n\nexport interface TruncateOptions {\n minOutputTokens?: number;\n keepPrefixChars?: number;\n keepSuffixChars?: number;\n protectRecentMessages?: number;\n}\n\nexport interface TruncateResult {\n messages: CoreMessage[];\n truncatedCount: number;\n savedTokens: number;\n}\n\nconst TRUNCATION_MARKER = \"[truncated for context space]\";\nconst DEFAULTS = {\n minOutputTokens: 1000,\n keepPrefixChars: 2000,\n keepSuffixChars: 2000,\n protectRecentMessages: 3,\n} as const;\n\nexport function truncateLargeToolOutputs(\n messages: CoreMessage[],\n tokenCount: number,\n config: Config,\n countTokens: (text: string) => number,\n options: TruncateOptions = {},\n): TruncateResult {\n const opts = { ...DEFAULTS, ...options };\n if (config.modelContextLimit <= 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const threshold = config.truncate.threshold * config.modelContextLimit;\n if (tokenCount < threshold) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const protectedIndex = messages.length - opts.protectRecentMessages;\n const candidates: Array<{ index: number; tokens: number }> = [];\n\n for (let index = 0; index < messages.length; index++) {\n if (index >= protectedIndex) break;\n const message = messages[index]!;\n if (message.contentType !== \"tool-result\") continue;\n const text = message.text ?? \"\";\n if (text.length === 0 || text.includes(TRUNCATION_MARKER)) continue;\n const tokens = countTokens(text);\n if (tokens < opts.minOutputTokens) continue;\n candidates.push({ index, tokens });\n }\n\n if (candidates.length === 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n candidates.sort((left, right) => right.tokens - left.tokens);\n\n const targetTokens = threshold * 0.9;\n let savedTokens = 0;\n const edits = new Map<number, string>();\n let truncatedCount = 0;\n\n for (const candidate of candidates) {\n if (tokenCount - savedTokens <= targetTokens) break;\n const original = messages[candidate.index]!.text ?? \"\";\n if (original.length <= opts.keepPrefixChars + opts.keepSuffixChars) continue;\n\n const prefix = original.slice(0, opts.keepPrefixChars);\n const suffix = original.slice(-opts.keepSuffixChars);\n const replacement =\n prefix +\n `\\n\\n...${TRUNCATION_MARKER} — original ~${candidate.tokens} tokens]...\\n\\n` +\n suffix;\n edits.set(candidate.index, replacement);\n savedTokens += candidate.tokens - countTokens(replacement);\n truncatedCount++;\n }\n\n if (truncatedCount === 0) return { messages, truncatedCount: 0, savedTokens: 0 };\n\n const updated = messages.map((message, index) =>\n edits.has(index) ? { ...message, text: edits.get(index)! } : message,\n );\n return { messages: updated, truncatedCount, savedTokens };\n}\n","import type { CompressionState, CoreMessage } from \"./types.js\";\n\nconst KEEP_LAST_ORPHANED = 0;\n\nexport interface HideConsumedResult {\n messages: CoreMessage[];\n hidden: number;\n}\n\nfunction rangeKey(startRef: string, endRef: string): string {\n return `${startRef}::${endRef}`;\n}\n\nfunction rewriteCompressText(text: string | undefined, liveKeys: Set<string>): string | null {\n let parsed: unknown;\n try {\n parsed = JSON.parse(text ?? \"\");\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== \"object\") return null;\n const obj = parsed as { content?: unknown };\n const content = obj.content;\n if (!Array.isArray(content) || content.length === 0) return null;\n\n const kept = content.filter((entry): entry is Record<string, unknown> => {\n if (!entry || typeof entry !== \"object\") return false;\n const s = typeof entry.startId === \"string\" ? entry.startId : typeof entry.messageId === \"string\" ? entry.messageId : \"\";\n const e = typeof entry.endId === \"string\" ? entry.endId : typeof entry.messageId === \"string\" ? entry.messageId : \"\";\n return liveKeys.has(rangeKey(s, e));\n });\n\n if (kept.length === content.length || kept.length === 0) return null;\n\n return JSON.stringify({ ...obj, content: kept });\n}\n\nexport function hideConsumedCompressCalls(\n state: CompressionState,\n messages: CoreMessage[],\n): HideConsumedResult {\n const allBlockCallIds = new Set<string>();\n const activeCallIds = new Set<string>();\n const liveRangeKeysByCallId = new Map<string, Set<string>>();\n const legacyLiveByCallId = new Set<string>();\n for (const block of state.blocks) {\n if (!block.compressCallId) continue;\n allBlockCallIds.add(block.compressCallId);\n if (!block.active) continue;\n activeCallIds.add(block.compressCallId);\n if (block.startRef === undefined || block.endRef === undefined) {\n legacyLiveByCallId.add(block.compressCallId);\n continue;\n }\n let keys = liveRangeKeysByCallId.get(block.compressCallId);\n if (!keys) {\n keys = new Set<string>();\n liveRangeKeysByCallId.set(block.compressCallId, keys);\n }\n keys.add(rangeKey(block.startRef, block.endRef));\n }\n\n const lastOrphanedCallIds: string[] = [];\n for (let i = messages.length - 1; i >= 0 && lastOrphanedCallIds.length < KEEP_LAST_ORPHANED; i--) {\n const message = messages[i]!;\n if (message.toolName !== \"compress\" || message.contentType !== \"tool-call\") continue;\n const callId = message.toolCallId;\n if (callId && !allBlockCallIds.has(callId)) {\n lastOrphanedCallIds.push(callId);\n }\n }\n\n const keepCallIds = new Set([...activeCallIds, ...lastOrphanedCallIds]);\n\n const hiddenCallIds = new Set<string>();\n for (const message of messages) {\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n (!message.toolCallId || !keepCallIds.has(message.toolCallId))\n ) {\n if (message.toolCallId) hiddenCallIds.add(message.toolCallId);\n }\n }\n\n let hidden = 0;\n const result: CoreMessage[] = [];\n for (const message of messages) {\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n (!message.toolCallId || !keepCallIds.has(message.toolCallId))\n ) {\n hidden++;\n continue;\n }\n if (\n message.contentType === \"tool-result\" &&\n message.toolCallId &&\n hiddenCallIds.has(message.toolCallId)\n ) {\n hidden++;\n continue;\n }\n if (\n message.toolName === \"compress\" &&\n message.contentType === \"tool-call\" &&\n message.toolCallId &&\n keepCallIds.has(message.toolCallId)\n ) {\n const liveKeys = liveRangeKeysByCallId.get(message.toolCallId);\n if (liveKeys && liveKeys.size > 0 && !legacyLiveByCallId.has(message.toolCallId)) {\n const rewritten = rewriteCompressText(message.text, liveKeys);\n if (rewritten !== null) {\n result.push({ ...message, text: rewritten });\n continue;\n }\n }\n }\n result.push(message);\n }\n\n return { messages: result, hidden };\n}\n","import type { MessageFilter } from \"./types.js\";\n\nconst registry = new Map<string, MessageFilter>();\n\nexport function registerMessageFilter(filter: MessageFilter): void {\n const existing = registry.get(filter.name);\n if (existing && existing.version !== filter.version) {\n throw new Error(\n `Message filter \"${filter.name}\" already registered with version ${existing.version}, cannot register version ${filter.version}.`,\n );\n }\n registry.set(filter.name, filter);\n}\n\nexport function getMessageFilter(name: string): MessageFilter | undefined {\n return registry.get(name);\n}\n\nexport function listMessageFilters(): MessageFilter[] {\n return [...registry.values()];\n}\n\nexport function clearMessageFilters(): void {\n registry.clear();\n}\n","import { listMessageFilters } from \"./registry.js\";\nimport type { CoreMessage } from \"../types.js\";\nimport type { FilterResult, MessageFilterContext, MessageFiltersConfig } from \"./types.js\";\n\nexport interface ApplyResult {\n messages: CoreMessage[];\n partsFiltered: number;\n partsDropped: number;\n partsModified: number;\n}\n\nexport function applyMessageFilters(\n messages: CoreMessage[],\n config: MessageFiltersConfig | undefined,\n): ApplyResult {\n if (!config?.enabled) {\n return { messages, partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n }\n\n const active = listMessageFilters().filter(\n (filter) => config.filters?.[filter.name]?.enabled !== false,\n );\n if (active.length === 0) {\n return { messages, partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n }\n\n let working = messages.map((message) => ({ ...message }));\n const tally = { partsFiltered: 0, partsDropped: 0, partsModified: 0 };\n const total = working.length;\n\n const immediate = active.filter((filter) => !filter.keepLastOnly);\n for (let index = 0; index < working.length; index++) {\n const message = working[index]!;\n const text = message.text ?? \"\";\n if (text.length === 0) continue;\n let current = text;\n const baseCtx: MessageFilterContext = {\n text: current,\n role: message.role,\n messageIndex: index,\n totalMessages: total,\n toolName: message.toolName,\n };\n for (const filter of immediate) {\n let decision: FilterResult;\n try {\n decision = filter.filter(baseCtx);\n } catch {\n continue;\n }\n if (decision.action === \"keep\") continue;\n tally.partsFiltered++;\n if (decision.action === \"drop\") {\n current = \"\";\n tally.partsDropped++;\n } else if (decision.action === \"modify\" && decision.text !== undefined) {\n current = decision.text;\n tally.partsModified++;\n }\n baseCtx.text = current;\n }\n if (current !== text) working[index] = { ...message, text: current };\n }\n\n const keepLast = active.filter((filter) => filter.keepLastOnly);\n for (const filter of keepLast) {\n let foundLast = false;\n for (let index = working.length - 1; index >= 0; index--) {\n const message = working[index]!;\n const text = message.text ?? \"\";\n if (text.length === 0) continue;\n const ctx: MessageFilterContext = {\n text,\n role: message.role,\n messageIndex: index,\n totalMessages: total,\n toolName: message.toolName,\n };\n let decision: FilterResult;\n try {\n decision = filter.filter(ctx);\n } catch {\n continue;\n }\n if (decision.action !== \"drop\" && decision.action !== \"modify\") continue;\n if (foundLast) {\n tally.partsFiltered++;\n tally.partsDropped++;\n working[index] = { ...message, text: \"\" };\n } else {\n foundLast = true;\n if (decision.action === \"modify\" && decision.text !== undefined) {\n tally.partsFiltered++;\n tally.partsModified++;\n working[index] = { ...message, text: decision.text };\n }\n }\n }\n }\n\n return { messages: working, ...tally };\n}\n","import type { CoreMessage, CompressionState, MessageRefMap } from \"./types.js\";\nimport { refForRaw, BLOCKED_REF } from \"./refs.js\";\nimport type { PipelineNode, PipelineContext, NodeIO } from \"./pipeline.js\";\n\n/**\n * Controls which messages get an <acp> ref tag injected into their text.\n * Ref assignment (assignRefsNode) is unconditional — every message always\n * receives a ref in state.messageRefs regardless of this setting. This only\n * governs text rendering:\n * - \"all\": tag every mapped message (in-process hosts like pai-acp)\n * - \"text-only\": tag only user/assistant text; leave tool-call args and\n * tool-result content pristine (proxy hosts — structured content must not\n * be polluted)\n * - \"none\": leave all text untouched (hosts that read the ref map directly)\n */\nexport type RenderStrategy = \"all\" | \"text-only\" | \"none\";\n\n/** Format token count: <1K raw, <10K \"X.YK\", >=10K \"XK\". */\nfunction formatTokens(tokens: number): string {\n if (tokens < 1000) return String(tokens);\n if (tokens < 10000) return (tokens / 1000).toFixed(1) + \"K\";\n return Math.round(tokens / 1000) + \"K\";\n}\n\nfunction classifyType(message: CoreMessage): string {\n if (\n message.contentType === \"tool-call\" ||\n message.contentType === \"tool-result\"\n ) {\n return message.toolName || \"tool\";\n }\n return message.contentType;\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nconst LT = \"\\x3c\";\nconst GT = \"\\x3e\";\nconst TAG_OPEN = LT + \"acp \";\nconst TAG_CLOSE = LT + \"/acp\" + GT;\n\nfunction acpTag(ref: string, tokens: number, type: string): string {\n return TAG_OPEN + 'tokens=\"' + formatTokens(tokens) + '\" type=\"' + type + '\"' + GT + ref + TAG_CLOSE;\n}\n\nfunction renderMessage(\n message: CoreMessage,\n map: MessageRefMap,\n countTokens: (text: string) => number,\n strategy: RenderStrategy,\n snapshot: Record<string, number> | null = null,\n): CoreMessage {\n const ref = refForRaw(map, message.id);\n if (!ref || ref === BLOCKED_REF) return message;\n\n // \"none\": host reads the ref map directly — never pollute text.\n if (strategy === \"none\") return message;\n\n // text-only: never tag structured tool content. Refs are still assigned.\n if (strategy === \"text-only\" && message.contentType !== \"text\") {\n return message;\n }\n\n // Strip own stale tag BEFORE computing tokens (idempotency).\n // Match the message's own ref only — foreign tags survive (content-corruption fix).\n const ownTagRe = new RegExp(\n \"^\" + escapeRegex(TAG_OPEN) + \"[^>]*\" + GT + escapeRegex(ref) + escapeRegex(TAG_CLOSE) + \"\\\\n?\",\n );\n const cleanText = (message.text || \"\").replace(ownTagRe, \"\");\n\n // Snapshot mode: token count is fixed at first render (stable prefix cache).\n // Live mode (snapshot = null): recompute every render — legacy behavior.\n const tokens = snapshot\n ? (snapshot[ref] ?? (snapshot[ref] = countTokens(cleanText)))\n : countTokens(cleanText);\n const type = classifyType(message);\n const prefix = acpTag(ref, tokens, type) + \"\\n\";\n\n if (!cleanText) return { ...message, text: prefix };\n return { ...message, text: prefix + cleanText };\n}\n\nexport function renderVisibleRefs(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (text: string) => number = (text) =>\n Math.ceil(text.length / 4),\n strategy: RenderStrategy = \"all\",\n): CoreMessage[] {\n // Legacy behavior: recompute tokens every render (snapshot = null).\n const map = state.messageRefs;\n return messages.map((message) =>\n renderMessage(message, map, countTokens, strategy),\n );\n}\n\nexport interface RenderWithSnapshotResult {\n messages: CoreMessage[];\n tokenSnapshot: Record<string, number>;\n}\n\n/** Render with a stable token snapshot: token counts are written on first\n * render and reused forever (keyed by ref). The snapshot starts as a shallow\n * copy of the persisted state so old entries survive; new entries are added\n * during this render. */\nexport function renderWithSnapshot(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (text: string) => number = (text) => Math.ceil(text.length / 4),\n strategy: RenderStrategy = \"all\",\n): RenderWithSnapshotResult {\n const map = state.messageRefs;\n const snapshot = { ...(state.tokenSnapshot ?? {}) };\n const rendered = messages.map((message) =>\n renderMessage(message, map, countTokens, strategy, snapshot),\n );\n return { messages: rendered, tokenSnapshot: snapshot };\n}\n\n/** Factory: build a render-refs node bound to a specific render strategy. */\nexport function createRenderRefsNode(strategy: RenderStrategy): PipelineNode {\n return {\n name: \"render-refs\",\n run(io: NodeIO, ctx: PipelineContext): NodeIO {\n const { messages, tokenSnapshot } = renderWithSnapshot(\n io.messages,\n io.state,\n ctx.countTokens,\n strategy,\n );\n // Write the snapshot back only when it grew: steady-state (all hits)\n // must not churn the state object and force an adapter save every turn.\n const prev = io.state.tokenSnapshot;\n const changed =\n !prev || Object.keys(tokenSnapshot).length !== Object.keys(prev).length;\n return changed\n ? { ...io, messages, state: { ...io.state, tokenSnapshot } }\n : { ...io, messages };\n },\n };\n}\n\n/** Backward compat: default render-refs node using strategy \"all\". */\nexport const renderRefsNode: PipelineNode = createRenderRefsNode(\"all\");\n","import type { Config, CoreMessage } from \"./types.js\";\n\n/** Tools that are ALWAYS protected, regardless of user config. These are ACP's\n * own metadata tools whose records must remain in context: compress calls\n * carry the summaries that decompress/search rely on, and the system prompt\n * treats past compress calls as load-bearing metadata. Letting them be\n * compressed away breaks decompress and the \"summary is historical\" contract. */\nexport const ALWAYS_PROTECTED_TOOLS = [\"compress\"] as const;\n\n/** Tool results that must NEVER participate in the soft-protected recent zone\n * (preserveRecentMessages / preserveRecentTokens / last user message).\n *\n * These tools return large content (restored blocks, search hits, file bodies,\n * command output). If such a result lands in the last-N window it becomes\n * un-compressible: the model cannot reclaim that context, and it never appears\n * in the compressible-ranges recommendation list. Excluding these tools from\n * the protected zone lets the model compress them again immediately, while\n * still leaving them visible (the host's preserveRecent is about not\n * compressing the active working set, not about which tool results are in\n * scope).\n *\n * - `decompress`: large restored content as an inline tool result.\n * - `search_context`: large result lists (10 ranked hits with previews).\n * - `read`: file/image contents — the largest common source of context bloat.\n * - `bash`: command output (build/test/logs) — frequently large and spent.\n *\n * Note: this only affects the recent-zone computation. Such messages remain\n * fully visible and compressible like any ordinary message. */\nexport const NEVER_PRESERVE_RECENT_TOOLS = [\n \"decompress\",\n \"search_context\",\n \"read\",\n \"bash\",\n] as const;\n\n/** True for tool-call / tool-result messages whose toolName is in the\n * NEVER_PRESERVE_RECENT_TOOLS list — i.e. tool results (like decompress)\n * that should be excluded from the soft-protected recent zone. */\nexport function isNeverPreserveRecent(msg: CoreMessage): boolean {\n if (msg.contentType !== \"tool-call\" && msg.contentType !== \"tool-result\") {\n return false;\n }\n if (!msg.toolName) return false;\n return (NEVER_PRESERVE_RECENT_TOOLS as readonly string[]).includes(msg.toolName);\n}\n\nexport function matchToolPattern(toolName: string, pattern: string): boolean {\n if (pattern.endsWith(\"*\")) {\n return toolName.startsWith(pattern.slice(0, -1));\n }\n return toolName === pattern;\n}\n\nexport function isMessageProtected(\n msg: CoreMessage,\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n): boolean {\n // tool-result carries the same toolName as its tool-call (the host projects\n // it), so checking toolName covers both sides of a tool exchange.\n if (\n (msg.contentType !== \"tool-call\" && msg.contentType !== \"tool-result\") ||\n !msg.toolName\n ) {\n return false;\n }\n\n // Hard-coded protection: ACP metadata tools are never compressible.\n if ((ALWAYS_PROTECTED_TOOLS as readonly string[]).includes(msg.toolName)) {\n return true;\n }\n\n for (const pattern of config.protectedTools) {\n if (matchToolPattern(msg.toolName, pattern)) return true;\n }\n\n if (config.isToolProtected?.(msg.toolName, msg.text)) return true;\n\n return false;\n}\n\n/** Build the set of toolCallIds whose tool-call is protected. Use this to also\n * protect tool-results that lack a toolName (common when the host projects a\n * tool-result with only toolCallId). Without it, the result half of a\n * protected tool exchange leaks into compressible ranges. */\nexport function collectProtectedToolCallIds(\n messages: CoreMessage[],\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n): Set<string> {\n const ids = new Set<string>();\n for (const m of messages) {\n if (m.contentType === \"tool-call\" && m.toolCallId && isMessageProtected(m, config)) {\n ids.add(m.toolCallId);\n }\n }\n return ids;\n}\n\n/** Like isMessageProtected, but also matches tool-results by toolCallId against\n * the protected call set. Use when you have the full message list available. */\nexport function isMessageProtectedWithPairing(\n msg: CoreMessage,\n config: Pick<Config, \"protectedTools\" | \"isToolProtected\">,\n protectedCallIds: Set<string>,\n): boolean {\n if (isMessageProtected(msg, config)) return true;\n if (\n msg.contentType === \"tool-result\" &&\n msg.toolCallId &&\n protectedCallIds.has(msg.toolCallId)\n ) {\n return true;\n }\n return false;\n}\n","import type { CoreMessage } from \"./types.js\";\n\n/**\n * Adjust compression range boundaries to include tool-call/result pairs.\n *\n * PREVENTIVE approach (adapted from opencode-acp PR #248): before compression\n * is applied, scan for tool-call or tool-result messages whose matching half\n * (the result for a call in range, or the call for a result in range) sits\n * outside the requested range. Pull the orphan half INTO the range so the\n * pair is compressed together — zero information loss.\n *\n * Only MESSAGE-boundary ranges are adjusted. Block-boundary ranges (bN) are\n * left untouched to preserve tier-detection correctness.\n *\n * @returns Adjusted { startIndex, endIndex } — may be wider than input.\n */\nexport function adjustBoundariesForToolPairs(\n startIndex: number,\n endIndex: number,\n messages: CoreMessage[],\n maxScan: number = 20,\n): { startIndex: number; endIndex: number } {\n // Collect all toolCallIds in range (both tool-call and tool-result messages).\n // Skip compress tool — it's force-protected and always survives pruning.\n const callIdsInRange = new Set<string>();\n for (let i = startIndex; i <= endIndex; i++) {\n const msg = messages[i];\n if (!msg || !msg.toolCallId) continue;\n if (msg.toolName === \"compress\") continue;\n callIdsInRange.add(msg.toolCallId);\n }\n\n if (callIdsInRange.size === 0) {\n return { startIndex, endIndex };\n }\n\n // Extend FORWARD: tool-results typically follow their tool-call.\n // Stop at the first gap after finding at least one matching message.\n let newEndIndex = endIndex;\n for (let i = endIndex + 1; i < messages.length && i <= endIndex + maxScan; i++) {\n const msg = messages[i];\n if (!msg) break;\n if (msg.toolCallId && callIdsInRange.has(msg.toolCallId)) {\n newEndIndex = i;\n } else if (newEndIndex > endIndex) {\n break;\n }\n }\n\n // Extend BACKWARD: tool-calls typically precede their tool-result.\n let newStartIndex = startIndex;\n for (let i = startIndex - 1; i >= 0 && i >= startIndex - maxScan; i--) {\n const msg = messages[i];\n if (!msg) break;\n if (msg.toolCallId && callIdsInRange.has(msg.toolCallId)) {\n newStartIndex = i;\n } else if (newStartIndex < startIndex) {\n break;\n }\n }\n\n return { startIndex: newStartIndex, endIndex: newEndIndex };\n}\n","import type { CoreMessage } from \"./types.js\";\n\n/**\n * Adjust compression range boundaries to keep a `reasoning` message together\n * with the assistant text/tool-call it belongs to.\n *\n * Reasoning models (DeepSeek-R1, GLM-4.6 thinking, Qwen-QwQ, Anthropic\n * thinking) emit a `reasoning_content` / thinking block that strict providers\n * require to be echoed back alongside the response on every subsequent\n * request. In acp-kernel that block is a separate `contentType: \"reasoning\"`\n * message immediately preceding the assistant text/tool-call of the same turn.\n * If a compression range covers only one half of the pair, the rebuilt\n * conversation ships reasoning without its response (or vice versa) and the\n * provider returns HTTP 400 (DeepSeek: \"reasoning_content in the thinking mode\n * must be passed back to the API\").\n *\n * This is the reasoning analogue of {@link adjustBoundariesForToolPairs}:\n * before a range is applied, pull the orphan half INTO the range so the pair\n * compresses together — zero information loss. Only MESSAGE-boundary ranges\n * are adjusted (block-boundary ranges are left untouched, like tool pairs).\n *\n * Pairing is adjacency-based — there is no shared id (unlike toolCallId). A\n * `reasoning` message pairs with the assistant text/tool-call immediately\n * following its reasoning run, and an assistant text/tool-call pairs with the\n * reasoning run immediately preceding it. This matches the round-trip contract\n * every adapter relies on when reconstructing reasoning_content.\n *\n * @returns Adjusted { startIndex, endIndex } — may be wider than input.\n */\nexport function adjustBoundariesForReasoningPairs(\n startIndex: number,\n endIndex: number,\n messages: CoreMessage[],\n): { startIndex: number; endIndex: number } {\n if (startIndex > endIndex) {\n return { startIndex, endIndex };\n }\n let newStartIndex = startIndex;\n let newEndIndex = endIndex;\n\n for (let i = startIndex; i <= endIndex && i < messages.length; i++) {\n const msg = messages[i];\n if (!msg) continue;\n\n if (msg.contentType === \"reasoning\") {\n // Forward: pull the companion assistant text/tool-call that follows\n // this reasoning run into the range.\n let j = i;\n while (\n j + 1 < messages.length &&\n messages[j + 1]!.contentType === \"reasoning\"\n ) {\n j++;\n }\n const companion = messages[j + 1];\n if (\n companion !== undefined &&\n companion.role === \"assistant\" &&\n (companion.contentType === \"text\" ||\n companion.contentType === \"tool-call\") &&\n j + 1 > newEndIndex\n ) {\n newEndIndex = j + 1;\n }\n }\n\n if (\n msg.role === \"assistant\" &&\n (msg.contentType === \"text\" || msg.contentType === \"tool-call\")\n ) {\n // Backward: pull the reasoning run immediately preceding this assistant\n // message into the range.\n let k = i - 1;\n while (k >= 0 && messages[k]!.contentType === \"reasoning\") {\n k--;\n }\n const runStart = k + 1;\n if (\n runStart < i &&\n runStart >= 0 &&\n messages[runStart]!.contentType === \"reasoning\" &&\n runStart < newStartIndex\n ) {\n newStartIndex = runStart;\n }\n }\n }\n\n return { startIndex: newStartIndex, endIndex: newEndIndex };\n}\n","/**\n * Recommendation engine — compression protection + recommendation.\n *\n * Clean-room reimplementation of the recommendation algorithm (MIT, ours).\n * These pure functions answer two questions every turn:\n *\n * 1. **Protection** — which messages must NOT be compressed? (protected tools,\n * recent messages, recent tokens)\n * 2. **Recommendation** — which remaining ranges are actually WORTH compressing?\n * (growth-aware threshold; suppress nudges when ranges are too small)\n *\n * Called by the `recommend` pipeline node. No side effects, no state mutation.\n */\n\nimport type {\n CompressibleRange,\n Config,\n ContextRanges,\n CoreMessage,\n ProtectedRange,\n} from \"./types.js\";\nimport type { CompressionState } from \"./types.js\";\nimport {\n collectProtectedToolCallIds,\n isMessageProtectedWithPairing,\n isNeverPreserveRecent,\n} from \"./protected.js\";\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nfunction refNum(ref: string): number {\n const n = parseInt(ref.slice(1), 10);\n return Number.isNaN(n) ? -1 : n;\n}\n\n/** Default token estimate (chars/4) used when the caller doesn't inject a\n * countTokens — preserves the historical behavior for backwards compat. */\nfunction estimateTextTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nfunction isToolMessage(message: CoreMessage): boolean {\n return message.contentType === \"tool-call\" || message.contentType === \"tool-result\";\n}\n\n\nfunction isSyntheticOrPruned(\n message: CoreMessage,\n state: CompressionState,\n): boolean {\n if (message.text?.startsWith(\"[Compressed conversation section]\")) return true;\n for (const block of state.blocks) {\n if (block.active && block.effectiveMessageIds.includes(message.id)) return true;\n }\n return false;\n}\n\n// ─── 1. Protected Refs (soft protection zone) ─────────────────────────────────\n\n/**\n * Compute the set of protected message refs (mNNNNN) that form the\n * \"soft-protected zone\" at the tail of the conversation.\n *\n * Combines two rules:\n * 1. Last N messages (`config.preserveRecentMessages`)\n * 2. Last N tokens expanding backward (`config.preserveRecentTokens`)\n *\n * Only considers visible, non-synthetic, non-pruned messages that have refs.\n */\nexport function computeProtectedRefs(\n messages: CoreMessage[],\n state: CompressionState,\n config: Config,\n countTokens: (text: string) => number = estimateTextTokens,\n): Set<string> {\n const preserveN = config.preserveRecentMessages;\n const preserveTokens = config.preserveRecentTokens;\n\n const result = new Set<string>();\n const visible: { ref: string; tokens: number }[] = [];\n\n for (const msg of messages) {\n if (isSyntheticOrPruned(msg, state)) continue;\n // Exclude decompress-style tool results from the recent-zone window.\n // These are large inline restorations that the model should be free to\n // compress again immediately; counting them toward the last-N window\n // would make them un-compressible and hide them from recommendations.\n // The message stays fully visible — this only affects protection scope.\n if (isNeverPreserveRecent(msg)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (!ref || ref === \"BLOCKED\") continue;\n visible.push({ ref, tokens: countTokens(msg.text ?? \"\") });\n }\n\n // Rule 1: last N messages\n if (preserveN > 0) {\n for (const m of visible.slice(-preserveN)) {\n result.add(m.ref);\n }\n }\n\n // Rule 2: last N tokens (expand backward from tail)\n if (preserveTokens > 0) {\n let tokenAccum = 0;\n for (let i = visible.length - 1; i >= 0 && tokenAccum < preserveTokens; i--) {\n result.add(visible[i]!.ref);\n tokenAccum += visible[i]!.tokens;\n }\n }\n\n // Rule 3: last visible user message. Protected whenever recent-message\n // protection is on (preserveRecentMessages > 0) — this couples it to the\n // same switch as Rule 1, so setting preserveRecentMessages = 0 fully opts\n // out (needed by tests that compress the tail). Production defaultConfig\n // uses 5, so the last user message is always protected in practice.\n // Note: we scan the raw messages array (not `visible`) here so the last\n // user message is still found even when a decompress tool result was\n // skipped above — user intent is always protected regardless of recent\n // tool results.\n if (preserveN > 0) {\n for (let i = messages.length - 1; i >= 0; i--) {\n const msg = messages[i]!;\n if (msg.role !== \"user\" || isSyntheticOrPruned(msg, state)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (ref && ref !== \"BLOCKED\") result.add(ref);\n break;\n }\n }\n\n return result;\n}\n\n// ─── 2. Build Compressible + Protected Ranges ────────────────────────────────\n\n/**\n * Build compressible and protected range groups from the message list.\n *\n * Messages are classified into:\n * - **compressible**: normal messages outside the protected zone\n * - **protected**: messages from protected tools (e.g., skill, task)\n * - **skipped**: covered by blocks, synthetic, or in the protected zone\n *\n * Compressible messages are grouped into contiguous ranges. The protected\n * zone (from `computeProtectedRefs`) splits groups — the unprotected head\n * survives as its own range.\n */\nexport function buildCompressibleRanges(\n messages: CoreMessage[],\n state: CompressionState,\n config: Config,\n protectedZoneRefs?: Set<string>,\n countTokens: (text: string) => number = estimateTextTokens,\n): ContextRanges {\n const compressibleMsgs: {\n ref: string;\n refNum: number;\n tokens: number;\n chars: number;\n isTool: boolean;\n isUser: boolean;\n }[] = [];\n const protectedMsgs: {\n ref: string;\n refNum: number;\n tokens: number;\n tools: string[];\n }[] = [];\n\n // Pairing: a tool-result may carry only toolCallId (no toolName). Collect the\n // callIds of protected tool-calls first, then protect matching results too.\n const protectedCallIds = collectProtectedToolCallIds(messages, config);\n\n for (const msg of messages) {\n if (isSyntheticOrPruned(msg, state)) continue;\n const ref = state.messageRefs.byRaw[msg.id];\n if (!ref || ref === \"BLOCKED\") continue;\n\n const rn = refNum(ref);\n\n if (isMessageProtectedWithPairing(msg, config, protectedCallIds)) {\n protectedMsgs.push({\n ref,\n refNum: rn,\n tokens: countTokens(msg.text ?? \"\"),\n tools: msg.toolName ? [msg.toolName] : [],\n });\n continue;\n }\n\n if (protectedZoneRefs?.has(ref)) {\n continue;\n }\n\n compressibleMsgs.push({\n ref,\n refNum: rn,\n tokens: countTokens(msg.text ?? \"\"),\n chars: (msg.text ?? \"\").length,\n isTool: isToolMessage(msg),\n isUser: msg.role === \"user\",\n });\n }\n\n // Build compressible groups (contiguous, split at ref gaps and at user\n // messages once a group has >= 3 messages). Splitting at user boundaries\n // keeps each compressible range aligned to roughly one user turn, instead\n // of producing one giant range spanning many turns (or, conversely, a\n // fragment per message when ref gaps appear). Mirrors opencode-acp's\n // buildCompressibleRanges condition.\n const compressible: CompressibleRange[] = [];\n let cur: CompressibleRange | null = null;\n let prevRefNum = -2;\n\n for (const info of compressibleMsgs) {\n const hasGap = info.refNum > prevRefNum + 1;\n if (cur && ((info.isUser && cur.count >= 3) || hasGap)) {\n compressible.push(cur);\n cur = null;\n }\n prevRefNum = info.refNum;\n if (!cur) {\n cur = {\n startRef: info.ref,\n endRef: info.ref,\n count: 1,\n tokens: info.tokens,\n chars: info.chars,\n toolPct: info.isTool ? 100 : 0,\n textPct: info.isTool ? 0 : 100,\n };\n } else {\n cur.endRef = info.ref;\n cur.count++;\n cur.tokens += info.tokens;\n cur.chars = (cur.chars ?? 0) + info.chars;\n if (info.isTool) {\n cur.toolPct = Math.round((cur.toolPct * (cur.count - 1) + 100) / cur.count);\n } else {\n cur.toolPct = Math.round((cur.toolPct * (cur.count - 1)) / cur.count);\n }\n cur.textPct = 100 - cur.toolPct;\n }\n }\n if (cur) compressible.push(cur);\n\n // Build protected groups (contiguous)\n const protectedRanges: ProtectedRange[] = [];\n let pcur: ProtectedRange | null = null;\n let pPrevRefNum = -2;\n\n for (const info of protectedMsgs) {\n const hasGap = info.refNum > pPrevRefNum + 1;\n if (pcur && hasGap) {\n protectedRanges.push(pcur);\n pcur = null;\n }\n pPrevRefNum = info.refNum;\n if (!pcur) {\n pcur = {\n startRef: info.ref,\n endRef: info.ref,\n count: 1,\n tokens: info.tokens,\n tools: [...info.tools],\n };\n } else {\n pcur.endRef = info.ref;\n pcur.count++;\n pcur.tokens += info.tokens;\n for (const t of info.tools) {\n if (!pcur!.tools.includes(t)) pcur!.tools.push(t);\n }\n }\n }\n if (pcur) protectedRanges.push(pcur);\n\n return {\n compressible: compressible.filter((g) => g.tokens > 0),\n protected: protectedRanges,\n };\n}\n\nfunction mergeBatch(batch: CompressibleRange[]): CompressibleRange {\n const first = batch[0]!;\n const last = batch[batch.length - 1]!;\n const count = batch.reduce((s, r) => s + r.count, 0);\n const tokens = batch.reduce((s, r) => s + r.tokens, 0);\n const chars = batch.reduce((s, r) => s + rangeChars(r), 0);\n const toolPct = Math.round(\n batch.reduce((s, r) => s + r.toolPct * r.count, 0) / count,\n );\n const merged: CompressibleRange = {\n startRef: first.startRef,\n endRef: last.endRef,\n count,\n tokens,\n chars,\n toolPct,\n textPct: 100 - toolPct,\n };\n if (batch.some((r) => r.dangerous === true)) {\n merged.dangerous = true;\n }\n return merged;\n}\n\n/** Effective size of a range in characters — the unit the apply-side\n * minCompressRange gate uses. Falls back to the historical tokens*4\n * estimate only for hand-built ranges that predate the `chars` field. */\nfunction rangeChars(r: CompressibleRange): number {\n return r.chars ?? r.tokens * 4;\n}\n\n/** Merge adjacent ranges into batches that clear `minChars` of REAL text —\n * the same accounting `applyCompression` uses — so a recommended range is\n * never below the threshold the kernel would atomically reject. Batching by\n * token estimates (tokens*4) instead broke whenever the host injected a\n * tokenizer where tokens != chars/4 (CJK-aware estimators are ~1:1, so\n * tokens*4 overestimated size ~4x and nudge recommended ranges the apply\n * side then refused). A sub-threshold tail batch is still emitted — callers\n * filter by effectiveness separately (see pendingByTier). */\nexport function mergeRangesToThreshold(\n ranges: CompressibleRange[],\n minChars: number,\n): CompressibleRange[] {\n if (minChars <= 0 || ranges.length === 0) return ranges;\n const result: CompressibleRange[] = [];\n let batch: CompressibleRange[] = [];\n let batchChars = 0;\n for (const r of ranges) {\n batch.push(r);\n batchChars += rangeChars(r);\n if (batchChars >= minChars) {\n result.push(mergeBatch(batch));\n batch = [];\n batchChars = 0;\n }\n }\n if (batch.length > 0) {\n result.push(mergeBatch(batch));\n }\n return result;\n}\n","import type { CompressionState, CoreMessage, NudgeDecision } from \"./types.js\";\n\nexport interface PipelineContext {\n readonly config: import(\"./types.js\").Config;\n readonly tokenCount: number;\n readonly countTokens: (text: string) => number;\n}\n\nexport interface NodeEffects {\n nudge?: NudgeDecision;\n recommendation?: import(\"./types.js\").Recommendation;\n truncatedCount?: number;\n readonly [key: string]: unknown;\n}\n\nexport interface NodeIO {\n messages: CoreMessage[];\n state: CompressionState;\n effects: NodeEffects;\n}\n\nexport interface PipelineNode {\n readonly name: string;\n run(io: NodeIO, ctx: PipelineContext): NodeIO;\n enabled?: (io: NodeIO, ctx: PipelineContext) => boolean;\n}\n\nexport function makeIO(\n messages: CoreMessage[],\n state: CompressionState,\n effects: NodeEffects = {},\n): NodeIO {\n return { messages, state, effects };\n}\n\nexport function runPipeline(\n nodes: readonly PipelineNode[],\n initial: NodeIO,\n ctx: PipelineContext,\n): NodeIO {\n let io = initial;\n for (const node of nodes) {\n if (node.enabled && !node.enabled(io, ctx)) continue;\n io = node.run(io, ctx);\n }\n return io;\n}\n","import { assignRefs, highestUsedIndex } from \"./refs.js\";\nimport { prune } from \"./prune.js\";\nimport { syncBlocks } from \"./sync.js\";\nimport { advanceSurvival, activeBlocks, blockById } from \"./state.js\";\nimport {\n allocateBlockId,\n allocateRunId,\n createInitialState,\n} from \"./state.js\";\nimport { defaultCountTokens } from \"./tokenize.js\";\nimport { validateConfig } from \"./config.js\";\nimport {\n BoundaryNotFoundError,\n resolveBoundaries,\n earliestIndexOfIds,\n} from \"./boundaries.js\";\nimport type { ResolvedRange } from \"./boundaries.js\";\nimport { truncateLargeToolOutputs } from \"./truncate-tools.js\";\nimport { hideConsumedCompressCalls } from \"./hide-consumed.js\";\nimport { applyMessageFilters, listMessageFilters } from \"./filter/index.js\";\nimport { createRenderRefsNode } from \"./render-refs.js\";\nimport type { RenderStrategy } from \"./render-refs.js\";\nimport { isMessageProtected } from \"./protected.js\";\nimport { adjustBoundariesForToolPairs } from \"./tool-pairs.js\";\nimport { adjustBoundariesForReasoningPairs } from \"./reasoning-pairs.js\";\nimport {\n computeProtectedRefs,\n buildCompressibleRanges,\n mergeRangesToThreshold,\n} from \"./recommend.js\";\nimport {\n runPipeline,\n type PipelineContext,\n type PipelineNode,\n type NodeIO,\n} from \"./pipeline.js\";\nimport type {\n ApplyCompressionResult,\n CompressionBlock,\n CompressionState,\n CompressionTier,\n Config,\n ContextBreakdown,\n CoreMessage,\n NudgeConfig,\n NudgeDecision,\n ProcessTurnResult,\n Recommendation,\n StatusReport,\n} from \"./types.js\";\n\nexport interface Ports {\n countTokens?: (text: string) => number;\n}\n\nexport interface CompressionCore {\n processTurn(input: ProcessTurnInput): ProcessTurnResult;\n applyCompression(input: ApplyCompressionInput): ApplyCompressionResult;\n defaultNodes(): PipelineNode[];\n decompress(\n blockId: string,\n state: CompressionState,\n ): CompressionBlock | undefined;\n search(query: string, state: CompressionState): CompressionBlock[];\n status(\n state: CompressionState,\n tokenCount: number,\n config: Config,\n ): StatusReport;\n}\n\nexport interface ProcessTurnInput {\n messages: CoreMessage[];\n state: CompressionState;\n config: Config;\n tokenCount: number;\n /**\n * Which messages get an <acp> ref tag injected into their text\n * (the render-refs pipeline node). Refs are ALWAYS assigned regardless\n * (assign-refs node runs unconditionally).\n * - \"all\" (default): tag every mapped message — in-process hosts\n * like pai-acp want tags for the LLM to reference compress ranges.\n * - \"text-only\": tag only user/assistant text; leave tool-call args\n * and tool-result content pristine — proxy hosts where structured\n * content must not be polluted.\n * - \"none\": leave all text untouched — hosts that read the ref map\n * directly from result.state.messageRefs.\n */\n renderTags?: RenderStrategy;\n}\n\nexport interface ApplyCompressionInput {\n ranges: {\n startRef: string;\n endRef: string;\n summary: string;\n topic?: string;\n compressCallId?: string;\n summaryMaxChars?: number;\n }[];\n messages: CoreMessage[];\n state: CompressionState;\n config: Config;\n protectedMessageIds?: Set<string>;\n}\n\n/**\n * Per-range classification from a single resolveBoundaries pass. \"ok\" ranges\n * go on to applySingleRange (which re-resolves internally for tool-pair\n * adjustment); \"consumed\" means the refs existed but their messages were\n * hidden by an existing block; \"unknown\" means a ref never existed in this\n * session; \"invalid\" means a ref failed to parse (e.g. \"foo\").\n */\ntype RangeResolution =\n | { status: \"ok\"; resolved: ResolvedRange }\n | { status: \"consumed\"; error: BoundaryNotFoundError }\n | { status: \"unknown\"; error: BoundaryNotFoundError }\n | { status: \"invalid\"; error: Error };\n\nfunction rangeError(\n spec: { startRef: string; endRef: string },\n message: string,\n): string {\n return `range ${spec.startRef}..${spec.endRef}: ${message}`;\n}\n\nexport function createCore(ports: Ports = {}): CompressionCore {\n const countTokens = ports.countTokens ?? defaultCountTokens;\n\n function applyCompression(\n input: ApplyCompressionInput,\n ): ApplyCompressionResult {\n const state: CompressionState = cloneState(input.state);\n const runId = allocateRunId(state);\n let blocksCreated = 0;\n let tokensCompressed = 0;\n const errors: string[] = [];\n const warnings: string[] = [];\n\n // Default to the soft-protected zone (recent-N + last user message) when the\n // caller doesn't pass an explicit set. This makes applyCompression safe by\n // default; applySingleRange enforces it as a hard backstop.\n const protectedMessageIds =\n input.protectedMessageIds ??\n computeProtectedRefs(input.messages, input.state, input.config, countTokens);\n\n const preExistingCoverage = collectCoverage(state);\n\n // Classify every requested range ONCE. The result feeds overlap\n // skipSpecs, the minCompressRange pre-check, and the per-range loop —\n // previously each re-resolved and silently swallowed failures, so\n // consumed/unknown ranges produced misleading \"too small\" errors.\n const classifications = new Map<typeof input.ranges[number], RangeResolution>();\n const classificationErrors: string[] = [];\n const consumedRanges: typeof input.ranges = [];\n for (const spec of input.ranges) {\n try {\n const resolved = resolveBoundaries({\n startRef: spec.startRef,\n endRef: spec.endRef,\n messages: input.messages,\n state,\n });\n classifications.set(spec, { status: \"ok\", resolved });\n } catch (error) {\n if (error instanceof BoundaryNotFoundError) {\n classifications.set(\n spec,\n error.kind === \"unknown\"\n ? { status: \"unknown\", error }\n : { status: \"consumed\", error },\n );\n if (error.kind === \"consumed\") {\n consumedRanges.push(spec);\n } else {\n classificationErrors.push(rangeError(spec, error.message));\n }\n } else {\n classifications.set(spec, {\n status: \"invalid\",\n error: error instanceof Error ? error : new Error(String(error)),\n });\n classificationErrors.push(\n rangeError(spec, error instanceof Error ? error.message : String(error)),\n );\n }\n }\n }\n\n const rangeIndexSets: { spec: typeof input.ranges[number]; indices: number[] }[] = [];\n for (const [spec, resolution] of classifications) {\n if (resolution.status !== \"ok\") continue;\n const indices = resolution.resolved.messageIds.map((id) =>\n input.messages.findIndex((m) => m.id === id),\n ).filter((i) => i >= 0);\n rangeIndexSets.push({ spec, indices });\n }\n const sortedRanges = [...rangeIndexSets].sort((a, b) => {\n const aMin = a.indices.length > 0 ? Math.min(...a.indices) : Infinity;\n const bMin = b.indices.length > 0 ? Math.min(...b.indices) : Infinity;\n return aMin - bMin;\n });\n // Overlapping ranges warn+skip (earliest wins) rather than aborting the\n // whole batch — see ISSUE-42 / dog/billion-context-pi#21.\n const skipSpecs = new Set<typeof input.ranges[number]>();\n let acceptedMaxIndex = -1;\n for (const entry of sortedRanges) {\n const entryMax = entry.indices.length > 0 ? Math.max(...entry.indices) : -1;\n const entryMin = entry.indices.length > 0 ? Math.min(...entry.indices) : -1;\n if (entryMin >= 0 && entryMin <= acceptedMaxIndex) {\n skipSpecs.add(entry.spec);\n warnings.push(\n `Skipped range (${entry.spec.startRef}..${entry.spec.endRef}) — overlaps an earlier range in the batch; the earlier range takes precedence. Keep ranges disjoint.`,\n );\n continue;\n }\n if (entryMax > acceptedMaxIndex) acceptedMaxIndex = entryMax;\n }\n\n if (input.config.compress.minCompressRange > 0 && input.ranges.length > 0) {\n let totalRangeChars = 0;\n let hasBlockBoundaryRange = false;\n let countedRanges = 0;\n for (const [spec, resolution] of classifications) {\n if (resolution.status !== \"ok\" || skipSpecs.has(spec)) continue;\n if (resolution.resolved.boundaryKind === \"block\") {\n hasBlockBoundaryRange = true;\n continue;\n }\n countedRanges++;\n for (const id of resolution.resolved.messageIds) {\n const msg = input.messages.find((m) => m.id === id);\n totalRangeChars += msg?.text?.length ?? 0;\n }\n }\n if (!hasBlockBoundaryRange && totalRangeChars < input.config.compress.minCompressRange) {\n const gateMessage =\n consumedRanges.length > 0\n ? `Requested range(s) already compressed (e.g. ${consumedRanges[0]!.startRef}..${consumedRanges[0]!.endRef}); remaining compressible content ${totalRangeChars} chars < min ${input.config.compress.minCompressRange}. Nothing to do — run acp_status to see current compressible ranges.`\n : `Total compressible content too small (${totalRangeChars} chars across ${countedRanges} range(s), min ${input.config.compress.minCompressRange}). Combine more messages into your range(s) to meet the threshold.`;\n return {\n state: input.state,\n result: {\n blocksCreated: 0,\n tokensCompressed: 0,\n errors: [gateMessage, ...classificationErrors],\n warnings: [],\n },\n };\n }\n }\n\n for (const spec of input.ranges) {\n if (skipSpecs.has(spec)) continue;\n const resolution = classifications.get(spec);\n if (resolution === undefined) continue;\n if (resolution.status === \"consumed\") {\n warnings.push(\n `Skipped range (${spec.startRef}..${spec.endRef}) — already compressed (messages consumed by existing block(s)); nothing to compress.`,\n );\n continue;\n }\n if (resolution.status === \"unknown\" || resolution.status === \"invalid\") {\n errors.push(rangeError(spec, resolution.error.message));\n continue;\n }\n try {\n const outcome = applySingleRange({\n spec,\n messages: input.messages,\n state,\n runId,\n config: input.config,\n protectedMessageIds,\n countTokens,\n preExistingCoverage,\n });\n blocksCreated++;\n tokensCompressed += outcome.tokens;\n warnings.push(...outcome.warnings);\n } catch (error) {\n errors.push(rangeError(spec, error instanceof Error ? error.message : String(error)));\n }\n }\n\n state.stats.compressionCount += blocksCreated;\n state.stats.tokensCompressed += tokensCompressed;\n\n if (blocksCreated > 0) {\n // Compress succeeded: clear the growth baseline so the next turn\n // re-establishes it at the new (lower) token count. Without this the\n // nudge re-fires in a feedback loop (the §5.7 baseline-reset bug).\n state.nudge.lastPerMessageNudgeTokens = 0;\n state.nudge.lastNudgeShownTokens = 0;\n // Clearing the per-tier cadence too: after a successful compression\n // (which may have consumed blocks of tier N to produce tier N+1), every\n // tier should be eligible to re-evaluate from the new token count.\n state.nudge.lastShownByTier = {};\n }\n\n return { state, result: { blocksCreated, tokensCompressed, errors, warnings } };\n }\n\n function processTurn(input: ProcessTurnInput): ProcessTurnResult {\n const configErrors = validateConfig(input.config);\n if (configErrors.length > 0) {\n console.warn(`[acp-kernel] Config validation warnings: ${configErrors.join(\"; \")}. Thresholds may not fire correctly.`);\n }\n const ctx: PipelineContext = {\n config: input.config,\n tokenCount: input.tokenCount,\n countTokens,\n };\n const initial: NodeIO = {\n messages: input.messages,\n state: input.state,\n effects: {},\n };\n // Conversion (assign-refs) and rendering (render-refs) are separate\n // concerns. Refs are always assigned; renderTags only controls which\n // message texts receive an <acp> tag.\n const strategy: RenderStrategy = input.renderTags ?? \"all\";\n const nodes = buildNodes(strategy);\n const result = runPipeline(nodes, initial, ctx);\n return {\n messages: result.messages,\n state: result.state,\n nudge: result.effects.nudge,\n };\n }\n\n function decompress(blockId: string, state: CompressionState) {\n return blockById(state, blockId);\n }\n\n function search(query: string, state: CompressionState): CompressionBlock[] {\n const terms = query\n .toLowerCase()\n .split(/\\s+/)\n .filter((term) => term.length > 0);\n if (terms.length === 0) return [];\n const scored = activeBlocks(state)\n .map((block) => ({ block, score: scoreRelevance(block, terms) }))\n .filter((entry) => entry.score > 0.1)\n .sort((left, right) => right.score - left.score);\n return scored.map((entry) => entry.block);\n }\n\n function status(\n state: CompressionState,\n tokenCount: number,\n config: Config,\n ): StatusReport {\n const active = activeBlocks(state);\n const usage =\n config.modelContextLimit > 0 ? tokenCount / config.modelContextLimit : 0;\n return {\n contextUsage: usage,\n tokenCount,\n modelContextLimit: config.modelContextLimit,\n activeBlocks: active.length,\n totalBlocks: state.blocks.length,\n tokensCompressed: state.stats.tokensCompressed,\n breakdown: { active: active.length, total: state.blocks.length },\n };\n }\n\n function defaultNodes(): PipelineNode[] {\n return buildNodes(\"all\");\n }\n\n /** Build the pipeline node list for a given render strategy. \"none\" omits\n * the render-refs node entirely; \"all\"/\"text-only\" append a render-refs\n * node bound to that strategy. */\n function buildNodes(strategy: RenderStrategy): PipelineNode[] {\n const base: PipelineNode[] = [\n assignRefsNode,\n syncBlocksNode,\n pruneNode,\n filterNode,\n hideCompressCallsNode,\n recommendNode,\n nudgeNode,\n emergencyTruncateNode,\n ];\n if (strategy === \"none\") return base;\n return [...base, createRenderRefsNode(strategy)];\n }\n\n return { processTurn, applyCompression, defaultNodes, decompress, search, status };\n}\n\n// --- Pipeline nodes -------------------------------------------------------\n// Each node owns ONE concern. The ref map has a SINGLE writer (assignRefsNode);\n// tags are DERIVED at the end (renderRefsNode) — no dual source of truth, so\n// the old stripHallucinations band-aid is gone. Truncation is the LAST\n// token-reducing safety valve; render-refs is the final annotation pass.\n\nconst assignRefsNode: PipelineNode = {\n name: \"assign-refs\",\n run(io, ctx) {\n const hasProtection =\n ctx.config.protectedTools.length > 0 || !!ctx.config.isToolProtected;\n const protectedFn = hasProtection\n ? (m: CoreMessage) => isMessageProtected(m, ctx.config)\n : undefined;\n const refResult = assignRefs(io.messages, {\n existing: io.state.messageRefs,\n nextIndex: highestUsedIndex(io.state.messageRefs) + 1,\n isProtected: protectedFn,\n });\n return { ...io, state: { ...io.state, messageRefs: refResult.map } };\n },\n};\n\nconst syncBlocksNode: PipelineNode = {\n name: \"sync-blocks\",\n run(io, ctx) {\n const synced = syncBlocks(io.messages, io.state);\n advanceSurvival(synced.state, ctx.config.promotionThreshold);\n return { ...io, state: synced.state };\n },\n};\n\nconst pruneNode: PipelineNode = {\n name: \"prune\",\n run(io) {\n return { ...io, messages: prune(io.messages, io.state) };\n },\n};\n\nconst filterNode: PipelineNode = {\n name: \"filter\",\n enabled: (_io, ctx) =>\n !!ctx.config.messageFilters?.enabled && listMessageFilters().length > 0,\n run(io, ctx) {\n const applied = applyMessageFilters(io.messages, ctx.config.messageFilters);\n return { ...io, messages: applied.messages };\n },\n};\n\nconst hideCompressCallsNode: PipelineNode = {\n name: \"hide-compress-calls\",\n run(io) {\n const hidden = hideConsumedCompressCalls(io.state, io.messages);\n return { ...io, messages: hidden.messages };\n },\n};\n\nconst recommendNode: PipelineNode = {\n name: \"recommend\",\n run(io, ctx) {\n const protectedRefs = computeProtectedRefs(\n io.messages,\n io.state,\n ctx.config,\n ctx.countTokens,\n );\n const contextRanges = buildCompressibleRanges(\n io.messages,\n io.state,\n ctx.config,\n protectedRefs,\n ctx.countTokens,\n );\n const nothingToCompress = contextRanges.compressible.length === 0;\n const recommendation: Recommendation = {\n contextRanges,\n recommendedRanges: mergeRangesToThreshold(\n contextRanges.compressible,\n ctx.config.compress.minCompressRange,\n ),\n nothingToCompress,\n };\n return { ...io, effects: { ...io.effects, recommendation } };\n },\n};\n\nconst nudgeNode: PipelineNode = {\n name: \"nudge-inject\",\n run(io, ctx) {\n const nudge = decideNudge({\n tokenCount: ctx.tokenCount,\n config: ctx.config,\n state: io.state,\n messages: io.messages,\n recommendation: io.effects.recommendation,\n countTokens: ctx.countTokens,\n });\n\n const baseline = io.state.nudge.lastPerMessageNudgeTokens;\n const nudgeGrowthTokens = resolveAdaptiveGrowth(\n ctx.config.modelContextLimit,\n ctx.config.nudge,\n );\n\n let stamped = { ...io.state.nudge };\n\n if (\n baseline > 0 &&\n ctx.tokenCount < baseline - nudgeGrowthTokens\n ) {\n stamped.lastPerMessageNudgeTokens = ctx.tokenCount;\n stamped.lastNudgeShownTokens = 0;\n // The context shrank dramatically — host compaction, or a tokenCount\n // scale switch (an adapter moving from session-tree accounting to\n // sent-view estimation). Per-tier cadence stamps recorded at the old\n // scale would otherwise make `tokenCount - lastShownByTier[t] >=\n // growthFloor` unreachable (a stamp above the window never re-arms),\n // suppressing mid-band nudges until the absolute overLimit band fires.\n // Restart tier cadence from the new baseline, mirroring the full stamp\n // reset a successful applyCompression performs.\n stamped.lastShownByTier = {};\n }\n\n if (stamped.lastPerMessageNudgeTokens === 0) {\n stamped.lastPerMessageNudgeTokens = ctx.tokenCount;\n }\n\n if (nudge.shouldInject) {\n stamped.lastNudgeShownTokens = ctx.tokenCount;\n // Record the injected tier's own cadence baseline. Shared baseline\n // (lastNudgeShownTokens) suppresses lower-priority tiers within this\n // turn; the per-tier entry throttles re-firing of the SAME tier.\n if (nudge.tier !== null) {\n stamped.lastShownByTier = { ...stamped.lastShownByTier, [nudge.tier]: ctx.tokenCount };\n }\n }\n\n return {\n ...io,\n state: { ...io.state, nudge: stamped },\n effects: { ...io.effects, nudge },\n };\n },\n};\n\nconst emergencyTruncateNode: PipelineNode = {\n name: \"emergency-truncate\",\n run(io, ctx) {\n const usage =\n ctx.config.modelContextLimit > 0\n ? ctx.tokenCount / ctx.config.modelContextLimit\n : 0;\n if (usage < ctx.config.truncate.threshold) return io;\n const trunc = truncateLargeToolOutputs(\n io.messages,\n ctx.tokenCount,\n ctx.config,\n ctx.countTokens,\n { protectRecentMessages: ctx.config.preserveRecentMessages },\n );\n return {\n ...io,\n messages: trunc.messages,\n effects: { ...io.effects, truncatedCount: trunc.truncatedCount },\n };\n },\n};\n\ninterface SingleRangeInput {\n spec: { startRef: string; endRef: string; summary: string; topic?: string; compressCallId?: string; summaryMaxChars?: number };\n messages: CoreMessage[];\n state: CompressionState;\n runId: string;\n config: Config;\n protectedMessageIds?: Set<string>;\n countTokens: (text: string) => number;\n preExistingCoverage: Set<string>;\n}\n\ninterface SingleRangeOutcome {\n tokens: number;\n warnings: string[];\n}\n\nfunction applySingleRange(input: SingleRangeInput): SingleRangeOutcome {\n const warnings: string[] = [];\n const resolved = resolveBoundaries({\n startRef: input.spec.startRef,\n endRef: input.spec.endRef,\n messages: input.messages,\n state: input.state,\n });\n\n const rangeMessageIds = applyPairBoundaryAdjustments(\n resolved,\n input.messages,\n );\n\n // Re-scan for nested blocks in the ADJUSTED range (tool-pair extension may\n // have pulled in messages that are anchors of existing blocks).\n if (rangeMessageIds.length > resolved.messageIds.length) {\n const indexByRawId = new Map<string, number>();\n input.messages.forEach((m, i) => indexByRawId.set(m.id, i));\n const adjustedStart = indexByRawId.get(rangeMessageIds[0]!) ?? resolved.startIndex;\n const adjustedEnd = indexByRawId.get(rangeMessageIds[rangeMessageIds.length - 1]!) ?? resolved.endIndex;\n const nestedSeen = new Set(resolved.nestedBlockIds);\n for (const block of activeBlocks(input.state)) {\n if (nestedSeen.has(block.blockId)) continue;\n const anchor = earliestIndexOfIds(block.effectiveMessageIds, indexByRawId);\n if (anchor !== null && anchor >= adjustedStart && anchor <= adjustedEnd) {\n nestedSeen.add(block.blockId);\n resolved.nestedBlockIds.push(block.blockId);\n }\n }\n }\n\n const isBlockBoundary = resolved.boundaryKind === \"block\";\n const targetTier = resolveTargetTier(\n input.state,\n resolved.nestedBlockIds,\n isBlockBoundary,\n );\n const outputTier = isBlockBoundary\n ? (Math.min(3, targetTier + 1) as CompressionTier)\n : 1;\n\n const consumedBlockIds = resolved.nestedBlockIds.filter((id) => {\n const block = blockById(input.state, id);\n return block?.active && block.tier === targetTier;\n });\n\n const effectiveMessageIds = new Set<string>(rangeMessageIds);\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) {\n for (const id of consumed.effectiveMessageIds)\n effectiveMessageIds.add(id);\n }\n }\n\n const directMessageIds = [...effectiveMessageIds].filter(\n (id) => !input.preExistingCoverage.has(id),\n );\n\n let filteredIds = filterProtectedToolMessages(\n directMessageIds,\n input.messages,\n input.config,\n );\n\n // filterProtectedToolMessages drops protected tool calls (and their paired\n // results) from the compressible set. They must also leave effectiveMessageIds,\n // otherwise the block would record them as covered and hide them from view.\n // (Bug 39: protected tool messages folded into a block.)\n if (filteredIds.length < directMessageIds.length) {\n const kept = new Set(filteredIds);\n for (const id of directMessageIds) {\n if (!kept.has(id)) effectiveMessageIds.delete(id);\n }\n }\n\n // SOFT PROTECTION: the recent-N / last-user-message zone is advisory-only at\n // compress time. Instead of failing the whole range when it brushes protected\n // messages, exclude those messages and proceed with the rest (so the model\n // isn't blocked when it picks a range that slightly overlaps the recent\n // window). If excluding them empties the range entirely AND there are no\n // consumed blocks to merge, we still fail — there is genuinely nothing to\n // compress. `protectedMessageIds` holds REF ids (mNNNNN) from\n // computeProtectedRefs; filteredIds holds RAW message ids, so convert via\n // state.messageRefs.byRaw before testing membership.\n const protectedRefs = input.protectedMessageIds;\n const hitProtectedRaw = protectedRefs\n ? filteredIds.filter((id) => {\n const ref = input.state.messageRefs.byRaw[id];\n return ref !== undefined && protectedRefs.has(ref);\n })\n : [];\n if (hitProtectedRaw.length > 0) {\n const protectedSet = new Set(hitProtectedRaw);\n filteredIds = filteredIds.filter((id) => !protectedSet.has(id));\n // Remove protected messages from effective coverage too, so they are NOT\n // hidden by the new block (they must stay fully visible).\n for (const id of hitProtectedRaw) effectiveMessageIds.delete(id);\n\n const hitRefs = hitProtectedRaw\n .map((id) => input.state.messageRefs.byRaw[id])\n .filter((v): v is string => typeof v === \"string\");\n\n if (filteredIds.length === 0 && consumedBlockIds.length === 0) {\n const recentN = input.config.preserveRecentMessages;\n throw new Error(\n `Range is entirely within the protected zone (the last ${recentN} messages and/or the most recent user message): ${hitRefs.join(\n \", \",\n )}. Adjust startId/endId to older messages.`,\n );\n }\n warnings.push(\n `Excluded ${hitProtectedRaw.length} protected message(s) ${hitRefs.join(\n \", \",\n )} from compression range (recent/last-user zone).`,\n );\n }\n\n validateCompressionRange(input, filteredIds, consumedBlockIds.length);\n\n let compressedTokens = 0;\n for (const id of filteredIds) {\n const message = input.messages.find((entry) => entry.id === id);\n compressedTokens += input.countTokens(message?.text ?? \"\");\n }\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) {\n compressedTokens += input.countTokens(consumed.summary);\n }\n }\n\n const blockId = allocateBlockId(input.state);\n const block: CompressionBlock = {\n blockId,\n runId: input.runId,\n tier: outputTier,\n topic: input.spec.topic,\n summary: input.spec.summary,\n directMessageIds: filteredIds,\n effectiveMessageIds: [...effectiveMessageIds],\n directBlockIds: [...consumedBlockIds],\n compressedTokens,\n createdAt: Date.now(),\n survivedCount: 0,\n generation: \"young\",\n active: true,\n compressCallId: input.spec.compressCallId,\n startRef: input.spec.startRef,\n endRef: input.spec.endRef,\n };\n input.state.blocks.push(block);\n\n for (const consumedId of consumedBlockIds) {\n const consumed = blockById(input.state, consumedId);\n if (consumed) consumed.active = false;\n }\n\n return { tokens: compressedTokens, warnings };\n}\n\nfunction applyPairBoundaryAdjustments(\n resolved: { startIndex: number; endIndex: number; messageIds: string[]; boundaryKind: string },\n messages: CoreMessage[],\n): string[] {\n if (resolved.boundaryKind === \"block\") {\n return resolved.messageIds;\n }\n // Compose tool-pair and reasoning-pair boundary adjustments to a fixpoint\n // (≤2 passes). Reasoning may pull in a tool-call whose result tool-pairs\n // then extends for; tool-pairs may pull in a tool-call whose preceding\n // reasoning is then drawn in. Both only ever WIDEN the range.\n let startIndex = resolved.startIndex;\n let endIndex = resolved.endIndex;\n for (let pass = 0; pass < 2; pass++) {\n const reasoningAdjusted = adjustBoundariesForReasoningPairs(\n startIndex,\n endIndex,\n messages,\n );\n const toolAdjusted = adjustBoundariesForToolPairs(\n reasoningAdjusted.startIndex,\n reasoningAdjusted.endIndex,\n messages,\n );\n const changed =\n toolAdjusted.startIndex !== startIndex ||\n toolAdjusted.endIndex !== endIndex;\n startIndex = toolAdjusted.startIndex;\n endIndex = toolAdjusted.endIndex;\n if (!changed) break;\n }\n if (\n startIndex === resolved.startIndex &&\n endIndex === resolved.endIndex\n ) {\n return resolved.messageIds;\n }\n const ids: string[] = [];\n for (let i = startIndex; i <= endIndex; i++) {\n const msg = messages[i];\n if (msg) ids.push(msg.id);\n }\n return ids;\n}\n\nfunction validateCompressionRange(\n input: SingleRangeInput,\n directMessageIds: string[],\n consumedBlockCount: number,\n): void {\n const cfg = input.config.compress;\n const summary = input.spec.summary?.trim() ?? \"\";\n\n if (summary.length === 0) {\n throw new Error(\n \"Summary is empty — provide a meaningful summary of the compressed range.\",\n );\n }\n\n if (cfg.minSummaryLength > 0 && summary.length < cfg.minSummaryLength) {\n throw new Error(\n `Summary too short (${summary.length} chars, min ${cfg.minSummaryLength}). The summary must capture the compressed range's key information.`,\n );\n }\n\n const effectiveMax = input.spec.summaryMaxChars ?? cfg.maxSummaryLength;\n if (\n effectiveMax > 0 &&\n summary.length > effectiveMax\n ) {\n throw new Error(\n `Summary too long (${summary.length} chars, max ${effectiveMax}). Strip noise — keep critical paths, decisions, errors, and code references. Or pass summaryMaxChars to increase the limit — don't lose critical info just to fit.`,\n );\n }\n\n if (directMessageIds.length === 0 && consumedBlockCount === 0) {\n throw new Error(\n \"Range contains no compressible messages — all are already covered by active blocks or protected.\",\n );\n }\n}\n\nfunction filterProtectedToolMessages(\n directMessageIds: string[],\n messages: CoreMessage[],\n config: Config,\n): string[] {\n // Protected tool calls (and their results, paired by toolCallId) stay in\n // visible context and are simply dropped from the compressible set. They are\n // NOT folded into the summary — the summary reflects what the author wrote,\n // nothing auto-appended.\n const protectedCallIds = new Set<string>();\n const removedIds = new Set<string>();\n for (const msg of messages) {\n if (isMessageProtected(msg, config) && msg.toolCallId) {\n protectedCallIds.add(msg.toolCallId);\n }\n }\n\n for (const id of directMessageIds) {\n const msg = messages.find((m) => m.id === id);\n if (!msg) continue;\n if (isMessageProtected(msg, config)) {\n removedIds.add(id);\n if (msg.toolCallId) protectedCallIds.add(msg.toolCallId);\n }\n }\n\n for (const id of directMessageIds) {\n if (removedIds.has(id)) continue;\n const msg = messages.find((m) => m.id === id);\n if (!msg) continue;\n if (\n msg.contentType === \"tool-result\" &&\n msg.toolCallId &&\n protectedCallIds.has(msg.toolCallId)\n ) {\n removedIds.add(id);\n }\n }\n\n return directMessageIds.filter((id) => !removedIds.has(id));\n}\n\nfunction resolveTargetTier(\n state: CompressionState,\n nestedBlockIds: string[],\n isBlockBoundary: boolean,\n): CompressionTier {\n if (!isBlockBoundary) return 1;\n if (nestedBlockIds.length === 0) return 1;\n let minTier: CompressionTier = 3;\n for (const id of nestedBlockIds) {\n const block = blockById(state, id);\n if (block && block.tier < minTier) minTier = block.tier;\n }\n return minTier;\n}\n\nfunction collectCoverage(state: CompressionState): Set<string> {\n const coverage = new Set<string>();\n for (const block of activeBlocks(state)) {\n for (const id of block.effectiveMessageIds) coverage.add(id);\n }\n return coverage;\n}\n\ninterface NudgeInput {\n tokenCount: number;\n config: Config;\n state: CompressionState;\n messages: CoreMessage[];\n recommendation?: Recommendation;\n countTokens: (t: string) => number;\n}\n\nfunction resolveAdaptiveGrowth(\n modelContextLimit: number,\n nudge: NudgeConfig,\n): number {\n if (!modelContextLimit || modelContextLimit <= 0) return nudge.growthFloor;\n return Math.min(\n nudge.growthCap,\n Math.max(\n nudge.growthFloor,\n Math.round(modelContextLimit * nudge.growthRatio),\n ),\n );\n}\n\n/** Compressible amount for each tier. T1 = EFFECTIVE merged-range tokens —\n * only ranges whose real char count >= minCompressRange count (avoids\n * inflation from fragmentation; matches the apply-side gate, which counts\n * raw `msg.text.length`, so a nudge never offers a range the kernel would\n * atomically reject — see CompressibleRange.chars); T2 = total summary\n * tokens of all active tier-1 blocks; T3 = total summary tokens of all\n * active tier-2 blocks. */\nfunction pendingByTier(\n state: CompressionState,\n recommendation: Recommendation | undefined,\n countTokens: (t: string) => number,\n minCompressRange: number,\n): Record<number, { pending: number; targetBlocks: CompressionBlock[] }> {\n const out: Record<number, { pending: number; targetBlocks: CompressionBlock[] }> = {};\n const merged = recommendation?.recommendedRanges ?? [];\n const effective =\n minCompressRange > 0\n ? merged.filter((r) => (r.chars ?? r.tokens * 4) >= minCompressRange)\n : merged;\n out[1] = { pending: effective.reduce((s, r) => s + r.tokens, 0), targetBlocks: [] };\n const active = activeBlocks(state);\n const t1 = active.filter((b) => b.tier === 1);\n const t2 = active.filter((b) => b.tier === 2);\n out[2] = { pending: t1.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t1 };\n out[3] = { pending: t2.reduce((s, b) => s + countTokens(b.summary), 0), targetBlocks: t2 };\n return out;\n}\n\nfunction decideNudge(input: NudgeInput): NudgeDecision {\n const { config, state, tokenCount, recommendation, countTokens } = input;\n const limit = config.modelContextLimit;\n const usage = limit > 0 ? tokenCount / limit : 0;\n\n const nudgeGrowthTokens = resolveAdaptiveGrowth(limit, config.nudge);\n\n const overLimit = usage >= config.nudge.maxContextLimitPct;\n const emergencyOverride = usage >= config.nudge.emergencyThresholdPct;\n // High-pressure band: over maxContextLimitPct (subsumes the emergency\n // threshold). Bypasses growth gate + cadence; gated on effective pending.\n const pressure = overLimit || emergencyOverride;\n\n const baseline = state.nudge.lastPerMessageNudgeTokens;\n const hadPendingNudge = state.nudge.lastNudgeShownTokens > 0;\n\n const hasPendingNudge = hadPendingNudge;\n const effectiveThreshold = hasPendingNudge\n ? Math.floor(nudgeGrowthTokens / 2)\n : nudgeGrowthTokens;\n\n const growthReference =\n state.nudge.lastNudgeShownTokens > 0\n ? state.nudge.lastNudgeShownTokens\n : baseline > 0\n ? baseline\n : tokenCount;\n\n const growthFloor = Math.max(\n config.nudge.minGrowthFloor,\n config.nudge.minGrowthRatio * nudgeGrowthTokens,\n );\n\n const growthSinceReference = tokenCount - growthReference;\n\n const rec = recommendation;\n const tiers = pendingByTier(\n state,\n rec,\n countTokens,\n config.compress.minCompressRange,\n );\n\n // Tier arbitration. Emergency (usage >= emergencyThresholdPct) ignores tier\n // priority and picks the tier with the MAX pending. Non-emergency defaults to\n // T1; T2 and T3 override when each crossed the shared 1.5x threshold AND\n // exceeds the effective pending of every lower tier (T2 > T1 effective;\n // T3 > T2 and > T1 effective).\n const tier2Threshold = Math.round(\n nudgeGrowthTokens * (config.nudge.tier2GrowthMultiplier ?? 1.5),\n );\n let injectedTier: CompressionTier | null = null;\n let injectedReason = \"\";\n const growthReady = growthSinceReference >= growthFloor;\n const t1Eff = tiers[1]?.pending ?? 0;\n const t2Pen = tiers[2]?.pending ?? 0;\n const t3Pen = tiers[3]?.pending ?? 0;\n\n if (pressure) {\n // High pressure: pick the tier with the MAX pending so pressure can route\n // to distillation when that reclaims the most tokens. Gated on effective\n // pending (real chars >= minCompressRange for T1) so we never offer ranges\n // the kernel would atomically reject. emergency vs over-limit only\n // changes the reason label/voice; truncate.threshold remains the\n // independent last resort when there is genuinely nothing to compress.\n const candidates: CompressionTier[] = [1];\n if (config.tiers.enabled) {\n candidates.push(2, 3);\n }\n let best: CompressionTier | null = null;\n let bestPending = 0;\n for (const t of candidates) {\n const p = tiers[t]?.pending ?? 0;\n if (p > bestPending) {\n bestPending = p;\n best = t;\n }\n }\n if (best !== null && bestPending > 0) {\n injectedTier = best;\n const label = emergencyOverride ? \"EMERGENCY\" : \"OVER-LIMIT\";\n injectedReason =\n best === 1\n ? `${label} T1: max effective pending ${bestPending}, usage ${Math.round(usage * 100)}%`\n : `${label} T${best} distill: max pending ${bestPending} (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}), usage ${Math.round(usage * 100)}%`;\n }\n } else if (growthReady) {\n if (t1Eff >= nudgeGrowthTokens) {\n injectedTier = 1;\n injectedReason = `T1 effective ${t1Eff} >= ${nudgeGrowthTokens}, growth ${growthSinceReference}, usage ${Math.round(usage * 100)}%`;\n } else if (\n config.tiers.enabled &&\n t2Pen >= tier2Threshold &&\n t2Pen > t1Eff\n ) {\n const lastShown = state.nudge.lastShownByTier[2] ?? 0;\n const cadenceMet =\n lastShown === 0 || tokenCount - lastShown >= growthFloor;\n if (cadenceMet) {\n injectedTier = 2;\n injectedReason = `T2 distill ready: ${tiers[2]!.targetBlocks.length} tier-1 blocks (${t2Pen} tokens) >= ${tier2Threshold} (1.5x) and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;\n }\n } else if (\n config.tiers.enabled &&\n t3Pen >= tier2Threshold &&\n t3Pen > t2Pen &&\n t3Pen > t1Eff\n ) {\n const lastShown = state.nudge.lastShownByTier[3] ?? 0;\n const cadenceMet =\n lastShown === 0 || tokenCount - lastShown >= growthFloor;\n if (cadenceMet) {\n injectedTier = 3;\n injectedReason = `T3 condense ready: ${tiers[3]!.targetBlocks.length} tier-2 blocks (${t3Pen} tokens) >= ${tier2Threshold} (1.5x) and > T2 ${t2Pen} and > T1 effective ${t1Eff}, usage ${Math.round(usage * 100)}%`;\n }\n }\n }\n\n const shouldInject = injectedTier !== null;\n\n let reason: string;\n if (injectedTier !== null) {\n reason = injectedReason;\n } else if (pressure) {\n const label = emergencyOverride ? \"EMERGENCY\" : \"OVER-LIMIT\";\n reason = `${label}: usage ${Math.round(usage * 100)}% but no tier has effective compressible content (T1 effective ${t1Eff}, T2 ${t2Pen}, T3 ${t3Pen}) — nudge suppressed to avoid offering ranges below minCompressRange`;\n } else {\n const tiersList = [1, 2, 3] as const;\n const eligible = tiersList.filter((t) => config.tiers.enabled || t === 1);\n const ready = eligible\n .filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens)\n .map((t) => `T${t} ${tiers[t]!.pending}`);\n const readyHint = ready.length > 0 ? `, ready: ${ready.join(\", \")}` : \"\";\n const blocked = eligible\n .filter((t) => (tiers[t]?.pending ?? 0) >= nudgeGrowthTokens && (state.nudge.lastShownByTier[t] ?? 0) > 0 && tokenCount - (state.nudge.lastShownByTier[t] ?? 0) < growthFloor)\n .map((t) => `T${t} (cadence)`);\n const blockedHint = blocked.length > 0 ? `, blocked: ${blocked.join(\", \")}` : \"\";\n const maxPending = Math.max(0, ...Object.values(tiers).map((t) => t.pending));\n // Report the ACTUAL blocking condition, not a fixed template. A session\n // can have plenty to compress (pending >= threshold) but still not\n // inject because growth/floor/cadence isn't met — the old fixed\n // \"< threshold\" string lied in that case.\n const pendingShort = maxPending < nudgeGrowthTokens;\n const growthShort = growthSinceReference < growthFloor;\n const parts: string[] = [];\n if (pendingShort) parts.push(`max compressible ${maxPending} < threshold ${nudgeGrowthTokens}`);\n if (growthShort) parts.push(`growth ${growthSinceReference} < floor ${growthFloor}`);\n if (parts.length === 0) parts.push(`max compressible ${maxPending}, growth ${growthSinceReference}`);\n reason = `${parts.join(\"; \")}${readyHint}${blockedHint}`;\n }\n\n const ctxBreakdown = computeContextBreakdown(input.messages, tokenCount, growthSinceReference, countTokens);\n\n return {\n shouldInject,\n reason,\n compressibleRanges: rec?.recommendedRanges ?? [],\n protectedRanges: rec?.contextRanges.protected ?? [],\n tierTargetBlocks: injectedTier ? tiers[injectedTier]!.targetBlocks : [],\n contextUsage: usage,\n tier: injectedTier,\n breakdown: {\n usage,\n growth: growthSinceReference,\n growthReference,\n effectiveThreshold,\n nudgeGrowthTokens,\n growthFloor,\n hasPendingNudge: hasPendingNudge ? 1 : 0,\n overLimit: overLimit ? 1 : 0,\n emergencyOverride: emergencyOverride ? 1 : 0,\n pendingT1: tiers[1]!.pending,\n pendingT2: tiers[2]!.pending,\n pendingT3: tiers[3]!.pending,\n },\n contextBreakdown: ctxBreakdown,\n };\n}\n\nfunction computeContextBreakdown(messages: CoreMessage[], total: number, growth: number, countTokens: (t: string) => number): ContextBreakdown {\n const count = countTokens ?? ((t: string) => Math.ceil(t.length / 4));\n let system = 0, tool = 0, summaries = 0, code = 0, text = 0;\n for (const msg of messages) {\n const tokens = count(msg.text ?? \"\");\n if (msg.text?.startsWith(\"[Compressed conversation section]\")) {\n summaries += tokens;\n } else if (msg.contentType === \"tool-call\" || msg.contentType === \"tool-result\") {\n tool += tokens;\n } else if (msg.role === \"system\") {\n system += tokens;\n } else if (msg.text?.includes(\"```\")) {\n code += tokens;\n } else {\n text += tokens;\n }\n }\n return { system, tool, summaries, code, text, total, growth };\n}\n\nfunction cloneState(state: CompressionState): CompressionState {\n return {\n blocks: state.blocks.map((block) => ({\n ...block,\n directMessageIds: [...block.directMessageIds],\n effectiveMessageIds: [...block.effectiveMessageIds],\n directBlockIds: [...block.directBlockIds],\n })),\n messageRefs: {\n byRaw: { ...state.messageRefs.byRaw },\n byRef: { ...state.messageRefs.byRef },\n },\n tokenSnapshot: { ...(state.tokenSnapshot ?? {}) },\n nudge: { ...state.nudge, anchors: { ...state.nudge.anchors } },\n stats: { ...state.stats },\n nextBlockId: state.nextBlockId,\n nextRunId: state.nextRunId,\n };\n}\n\nfunction scoreRelevance(block: CompressionBlock, terms: string[]): number {\n const topic = (block.topic ?? \"\").toLowerCase();\n const summary = block.summary.toLowerCase();\n let score = 0;\n for (const term of terms) {\n const topicHits = countOccurrences(topic, term);\n if (topicHits > 0) score += Math.min(topicHits * 0.15, 0.45);\n const summaryHits = countOccurrences(summary, term);\n if (summaryHits > 0) score += Math.min(summaryHits * 0.04, 0.2);\n }\n return Math.min(score, 1);\n}\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!haystack || !needle) return 0;\n let count = 0;\n let position = 0;\n while ((position = haystack.indexOf(needle, position)) !== -1) {\n count++;\n position += needle.length;\n }\n return count;\n}\n\nexport { createInitialState };\n","/**\n * Compression rule texts — VERBATIM copy from context-compress-algorithms (MIT, ours).\n * These were tuned over months of production use.\n *\n * DO NOT modify the wording — it is the result of extensive tuning.\n */\n\nexport const COMPRESS_PHILOSOPHY = `Compression Philosophy:\n- All compression serves the primary task, but be frugal.\n- Context capacity is precious. Save context by compressing consumed outputs, not by avoiding tools.\n- Compress by need, not by percentage.\n- Work from summaries, not raw tool outputs. All listed ranges (user prompts, tool outputs, code, logs, exploration, intermediate steps) should be compressed to summary format — the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.`;\n\nexport const HOW_TO_COMPRESS_RULES = `HOW TO COMPRESS\n\nWhen you call \\`compress\\`, the summary you write becomes the only record of the replaced conversation. Make it self-contained and complete: every user request, experiment purpose, and work task in the range must be accurately captured. A later reader (or you, after decompressing) should be able to continue the task WITHOUT needing the original.\n\nKEEP VERBATIM — never paraphrase or abbreviate these:\n- Full file paths with line numbers, directory prefix on every mention (\\`lib/hooks.ts:347\\`, \\`src/index.ts:12-18\\`, \\`gatenet_v3/model.py:45\\`). Never abbreviate to a bare filename (\\`hooks.ts\\`, \\`model.py\\`) — they are ambiguous and cannot be grepped or decompressed-to later.\n- Function, class, and type signatures (exact names, params, return types) AND critical code lines that encode logic — the line that IS the finding, not just the function name (e.g. \\`kv_keys += define_gate * a_key[i](emb)\\` is more useful than \"see model_kvnet.py\").\n- Error messages and stack traces (exact text — you need the literal string to grep for it later).\n- Key details from reports and analyses — not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76× PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing; without it the decision looks arbitrary).\n- Constraints discovered (\"must support Node 22\", \"no new dependencies\", \"AGENTS.md forbids \\`as any\\`\").\n- Exact values: versions, config keys, thresholds, magic numbers.\n- User intent — quote short user messages verbatim. When the message is too long to quote, preserve intent with extra care: do not change scope, constraints, priorities, acceptance criteria, or requested outcomes. Mark them clearly as past quotes (e.g., \"User said: ...\"), not as current directives. Losing these changes the task itself.\n- The user's overall goal and any changes to it — the big-picture objective plus how it evolved during the compressed range. Each summary must reflect the goal as it stood at the end of the range, including pivots (e.g., \"initially: fix bug X → pivoted to: refactor module Y after discovering root cause\"). Losing the goal or its evolution makes all subsequent work appear unmotivated.\n- Purpose behind each significant action — preserve not just what was done but why: the hypothesis behind each experiment, the question behind each exploration, the task goal behind each work action. Without purpose, the summary reads as disconnected technical steps with no through-line.\n- Open questions and unresolved TODOs — losing these changes what work appears to remain.\n- Message refs of key anchors (\\`m00420\\`, \\`m00510–m00520\\`) — they let you or a later reader jump back via decompress to the exact original.\n\nDROP — extract the signal, discard the vessel:\n- Verbose logs (build/test/\\`npm\\` output) once you have captured the error line or the result.\n- Duplicate file reads once the needed content is recorded.\n- Consumed exploration — search hits, agent return values, successful tool outputs — once you have extracted the facts you need (same rule as dead-ends, but nothing went wrong; the content is simply spent).\n- Dead-end exploration — but PRESERVE the lesson in one line: \"tried X, failed because Y\".\n- Back-and-forth discussion and self-corrections once the final position is captured (keep the outcome, drop the journey to it).\n- Repeated status checks (\\`git status\\`, \\`ls\\`) once state is known.\n\nFor each significant item you DROP (scripts, reports, large analyses, long tool outputs), add a one-line CONTENT description of what it covers — not where it lives. Bad: \"probe script at /path/probe_kvnet.py\". Good: \"probe_kvnet.py: tests n-gram baseline, generation quality, long-range dependency, position sensitivity, op pipeline, QUERY attention.\" This lets a later decompress target the right block by relevance, not by guessing locations.\n\nPRIORITY — when the summary must be compact, preserve in this order:\n1. User's overall goal, goal evolution, intent, and hard constraints (losing these changes the task).\n2. Decisions and rationale.\n3. Exact technical artifacts: paths, signatures, errors, values.\n4. Conclusions and key findings.\n5. Lessons learned: what failed and why.\n\nWrite dense, scannable bullets — not narrative prose. If the range spans distinct concerns (request → findings → decision), group bullets under short thematic headers so a reader can scan to the part they need. Every line must earn its place. Do not mimic the style of existing summaries in context; follow these rules.`;\n\nexport const TIER2_DISTILL_RULES = `TIER 2 COMPRESSION — DISTILLATION\n\nYou are compressing historical summaries (not raw conversation). These summaries have already captured the details. Your job is to DISTILL them: extract only what matters for future work, discard the process.\n\nKEEP — these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" — the \"because\" is load-bearing).\n- Final outcomes: version numbers shipped, PR numbers merged/closed, bugs fixed or deferred.\n- Key lessons: what failed and why (\"tried X, failed because Y\"). These prevent repeating mistakes.\n- Critical constraints discovered (\"must support Node 22\", \"AGENTS.md forbids as any\").\n- Design decisions with architectural impact (\"chose compress-as-anchor over synthetic messages because prefix cache\").\n- Whether content is OBSOLETE or SUPERSEDED — mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details — just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work — e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures — just enough to LOCATE the code without searching.\n- Exploration findings: if a block was exploratory with no decision, keep the CONCLUSION in one line (\"explored X, not viable because Y\"). Do not keep the exploration process.\n\nDROP — these were useful during the work but are no longer needed:\n- Exact line numbers, diffs, verbose function signatures, full code listings.\n- Build/deploy process details, test execution steps.\n- Review process details (who reviewed, what rounds, test counts).\n- Verbose logs, command output, intermediate debugging steps.\n\nFORMAT:\n- Start each distilled block with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n Example: \\`Source: b5+b7 (56K+44K→268 tok, 375x). [Tool-result recap + publish]\\`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable — no narrative prose.\n- Start with the outcome, not the process: \"v1.13.0 shipped (7 PRs bundled)\" not \"implemented 7 PRs then reviewed then merged\".\n- Cross-block synthesis: if multiple source blocks cover the same topic (same PR, same feature, same bug), MERGE them into a single group of bullets. Do not repeat the same fact from different blocks — keep it once under the most relevant source header.\n\nSIZE TARGET: 50-150 tokens per source block (excluding the header). If you can't fit it in 150 tokens, you're keeping too much process. If a block has nothing worth keeping (pure noise), output just the header followed by \"[no actionable content].\"`;\n\nexport const TIER3_CONDENSE_RULES = `TIER 3 COMPRESSION — ULTRA-CONDENSATION\n\nYou are compressing distilled summaries (Tier 2) into ultra-condensed facts (Tier 3). The distilled summaries already contain only decisions and outcomes. Your job is to reduce them to bare factual references.\n\nPRIORITY — when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) — these are permanent record.\n2. Open work (PRs/issues still pending) — these may need follow-up.\n3. Key decisions with architectural impact (\"chose X over Y because Z\").\n4. Critical constraints (\"must support Node 22\").\nDrop everything else. Tier 3 is a lookup index, not a knowledge base.\n\nFORMAT:\n- Start with a source header line:\n \\`Source: bN+bM+... (XK→YK tok, Zx). [original topic]\\`\n- Output 1-3 facts per source block. Each fact is a single line: subject + outcome.\n- No explanations, no rationale, no process — just the fact.\n- Format: \"[PR/Issue/Version] — [outcome in ≤8 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped — quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged — preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed — compress consumed all user messages\"\n- \"Chose compress-as-anchor — prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any — never suppress types\"\n\nDROP:\n- Multi-sentence context. If a fact needs >1 sentence, it's too detailed for Tier 3.\n- Lessons learned (\"tried X, failed because Y\") — drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details — keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] — drop entirely, note \"[N blocks obsolete]\" in the summary.\n\nSIZE TARGET: 30-60 tokens per source block (including header). For a batch of N source blocks, total output ≈ N × 40 tokens. If a source block has only one trivial fact, output just the header + one line.`;\n","import {\n COMPRESS_PHILOSOPHY,\n HOW_TO_COMPRESS_RULES,\n TIER2_DISTILL_RULES,\n TIER3_CONDENSE_RULES,\n} from \"./compression-rules.js\";\n\n/**\n * Overridable prompt text consumed by the kernel's nudge renderer and, via the\n * adapter, the system prompt. Every field here is LOAD-BEARING: these rules\n * were tuned over months of production use and are quality-critical. Overriding\n * them can degrade summary quality (loss of paths / signatures / decisions →\n * broken retrieval), so {@link resolvePrompts} requires `{ acknowledgeRisk: true }`.\n *\n * Surface-level text (summary section headers, status-report chrome, tool\n * descriptions) is intentionally NOT part of this interface — it is owned by\n * the adapter or a later \"prompt-set format\" layer and is safe to customize\n * freely. See DESIGN.md for the load-bearing vs surface classification.\n */\nexport interface Prompts {\n /** Core compression philosophy. Embedded in the system prompt + every nudge. */\n compressPhilosophy: string;\n /** Rules the model follows when writing a tier-1 summary. */\n howToCompressRules: string;\n /** Rules for tier-2 distillation of existing summaries. */\n tier2DistillRules: string;\n /** Rules for tier-3 ultra-condensation of distilled summaries. */\n tier3CondenseRules: string;\n}\n\n/**\n * The kernel's canonical prompt values (verbatim from compression-rules.ts).\n * Frozen so a buggy caller cannot mutate the shared singleton and corrupt\n * every other consumer of {@link defaultPrompts}.\n */\nexport const defaultPrompts: Prompts = Object.freeze({\n compressPhilosophy: COMPRESS_PHILOSOPHY,\n howToCompressRules: HOW_TO_COMPRESS_RULES,\n tier2DistillRules: TIER2_DISTILL_RULES,\n tier3CondenseRules: TIER3_CONDENSE_RULES,\n}) as Prompts;\n\nexport interface ResolvePromptsOptions {\n /**\n * Must be `true` to override any prompt field. Every {@link Prompts} field is\n * load-bearing; overriding without acknowledging the quality risk is a\n * programming error and throws.\n */\n acknowledgeRisk?: boolean;\n}\n\n/**\n * Merge prompt overrides onto the kernel defaults. All fields are load-bearing,\n * so ANY override requires `{ acknowledgeRisk: true }`.\n *\n * Only `string`-valued overrides take effect: an explicit `undefined`/`null` or\n * a wrong type is silently dropped (never clobbers a good default), so a\n * malformed partial never degrades the canonical rules. Resolve once at host\n * startup, then pass the resulting {@link Prompts} to {@link renderNudgeText}\n * and to the adapter's system-prompt composition so both layers stay consistent.\n */\nexport function resolvePrompts(\n overrides?: Partial<Prompts>,\n options: ResolvePromptsOptions = {},\n): Prompts {\n const clean: Partial<Prompts> = {};\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n if (typeof value === \"string\") {\n (clean as Record<string, unknown>)[key] = value;\n }\n }\n }\n const keys = Object.keys(clean) as (keyof Prompts)[];\n if (keys.length > 0 && !options.acknowledgeRisk) {\n throw new Error(\n `resolvePrompts: overriding compression rules requires { acknowledgeRisk: true }. ` +\n `Overridden keys: ${keys.join(\", \")}. These rules are quality-critical (tuned over months of production use); ` +\n `changing them can degrade summary quality and break retrieval (summaries may lose paths, signatures, decisions).`,\n );\n }\n return { ...defaultPrompts, ...clean };\n}\n","import type { NudgeDecision, CompressibleRange, ProtectedRange, ContextBreakdown, CompressionBlock } from \"./types.js\";\nimport { defaultPrompts } from \"./prompts.js\";\nimport type { Prompts } from \"./prompts.js\";\n\nexport type NudgeVoice = \"gentle\" | \"emergency\";\n\nexport interface RenderedNudge {\n voice: NudgeVoice;\n text: string;\n}\n\nfunction efficiencyNote(prompts: Prompts): string {\n return `This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction emergencyHeader(prompts: Prompts): string {\n return `⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.\\n\\n${prompts.compressPhilosophy}`;\n}\n\nfunction formatK(n: number): string {\n if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;\n return `${n}`;\n}\n\nfunction formatBreakdown(bd?: ContextBreakdown): string {\n if (!bd) return \"\";\n const parts: string[] = [];\n if (bd.system > 0) parts.push(`${formatK(bd.system)} system`);\n if (bd.tool > 0) parts.push(`${formatK(bd.tool)} tool`);\n if (bd.summaries > 0) parts.push(`${formatK(bd.summaries)} summaries`);\n if (bd.code > 0) parts.push(`${formatK(bd.code)} code`);\n if (bd.text > 0) parts.push(`${formatK(bd.text)} text`);\n const growth = bd.growth > 0 ? `\\n+${formatK(bd.growth)} since last nudge` : \"\";\n return `Context breakdown: ${parts.join(\" | \")}${growth}`;\n}\n\n\n\nfunction formatTierTargetBlocks(blocks: CompressionBlock[]): string {\n if (blocks.length === 0) {\n return \"Target blocks: (none — no tier blocks found)\";\n }\n const lines = blocks.map((b) => {\n const summaryTokens = Math.ceil((b.summary ?? \"\").length / 4);\n const topic = b.topic ? ` \"${b.topic}\"` : \"\";\n return ` ${b.blockId} ${b.effectiveMessageIds.length} msgs ${formatK(b.compressedTokens)}→${formatK(summaryTokens)}${topic}`;\n });\n return `Target ${blocks[0]!.tier === 1 ? \"tier-1\" : \"tier-2\"} blocks to distill (${blocks.length}):\\n${lines.join(\"\\n\")}`;\n}\n\nexport function formatRanges(compressible: CompressibleRange[], protectedRanges: ProtectedRange[]): string {\n if (compressible.length === 0 && protectedRanges.length === 0) {\n return \"[No specific ranges detected — compress any consumed content.]\";\n }\n\n // Merge compressible + protected into a single oldest-first list, mirroring\n // opencode-acp's formatCompressibleRanges. Splitting them into two sections\n // lost the time order and hid overlaps; a range can be partly compressible\n // and partly protected, which only the merged view shows correctly.\n interface Merged {\n startRef: string; endRef: string; startNum: number; endNum: number;\n count: number; tokens: number;\n compressibleTokens: number; compressibleCount: number;\n protectedTokens: number; protectedCount: number; protectedTools: string[];\n toolPct: number; textPct: number; dangerous: boolean;\n }\n const refNum = (ref: string): number => {\n const m = ref.match(/\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n };\n const entries: Merged[] = [];\n for (const r of compressible) {\n entries.push({\n startRef: r.startRef, endRef: r.endRef, startNum: refNum(r.startRef), endNum: refNum(r.endRef),\n count: r.count, tokens: r.tokens, toolPct: r.toolPct, textPct: r.textPct,\n compressibleTokens: r.tokens, compressibleCount: r.count,\n protectedTokens: 0, protectedCount: 0, protectedTools: [], dangerous: r.dangerous ?? false,\n });\n }\n for (const r of protectedRanges) {\n entries.push({\n startRef: r.startRef, endRef: r.endRef, startNum: refNum(r.startRef), endNum: refNum(r.endRef),\n count: r.count, tokens: r.tokens, toolPct: 0, textPct: 0,\n compressibleTokens: 0, compressibleCount: 0,\n protectedTokens: r.tokens, protectedCount: r.count, protectedTools: [...r.tools], dangerous: false,\n });\n }\n entries.sort((a, b) => a.startNum - b.startNum);\n // Merge adjacent/overlapping ranges (gap ≤ 1 ref).\n const merged: Merged[] = [];\n for (const e of entries) {\n const last = merged[merged.length - 1];\n if (last && e.startNum <= last.endNum + 1) {\n last.endRef = e.endRef;\n last.endNum = Math.max(last.endNum, e.endNum);\n last.count += e.count;\n last.tokens += e.tokens;\n last.compressibleTokens += e.compressibleTokens;\n last.compressibleCount += e.compressibleCount;\n last.protectedTokens += e.protectedTokens;\n last.protectedCount += e.protectedCount;\n if (e.dangerous) last.dangerous = true;\n for (const t of e.protectedTools) {\n if (!last.protectedTools.includes(t)) last.protectedTools.push(t);\n }\n } else {\n merged.push({ ...e });\n }\n }\n const lines = merged.map((e) => {\n const suffix = e.dangerous && e.compressibleTokens > 0 ? \" ⚠️ NOT recommended unless you are certain.\" : \"\";\n if (e.protectedTokens > 0 && e.compressibleTokens === 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [PROTECTED: ${e.protectedTools.join(\", \")} — not compressible]${suffix}`;\n }\n if (e.protectedTokens > 0 && e.compressibleTokens > 0) {\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [${formatK(e.compressibleTokens)} compressible | ${formatK(e.protectedTokens)} protected: ${e.protectedTools.join(\", \")}]${suffix}`;\n }\n return ` ${e.startRef}–${e.endRef} ${e.count} msgs ${formatK(e.tokens)} [tool ${e.toolPct}% | text ${e.textPct}%]${suffix}`;\n });\n return `Compressible ranges (${merged.length}, oldest first):\\n${lines.join(\"\\n\")}`;\n}\n\nexport function renderNudgeText(decision: NudgeDecision, prompts: Prompts = defaultPrompts): RenderedNudge {\n const breakdownStr = formatBreakdown(decision.contextBreakdown);\n const rangesStr = formatRanges(decision.compressibleRanges, decision.protectedRanges ?? []);\n const isEmergency = !!decision.breakdown?.emergencyOverride || !!decision.breakdown?.overLimit;\n\n if (decision.tier !== null && decision.tier >= 2) {\n const isT2 = decision.tier === 2;\n const targets = decision.tierTargetBlocks ?? [];\n const blockList = formatTierTargetBlocks(targets);\n const startId = targets[0]?.blockId ?? \"b1\";\n const endId = targets[targets.length - 1]?.blockId ?? \"b5\";\n const voice: NudgeVoice = isEmergency ? \"emergency\" : \"gentle\";\n const triggerLine = isEmergency\n ? `[EMERGENCY — TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"}] Context limit reached — distill NOW into a denser summary to reclaim tokens.`\n : `[TIER ${decision.tier} ${isT2 ? \"DISTILLATION\" : \"CONDENSATION\"} TRIGGER]`;\n return {\n voice,\n text: [\n efficiencyNote(prompts),\n \"\",\n breakdownStr,\n \"\",\n triggerLine,\n isT2\n ? `Your tier-1 compression summaries have accumulated. Distill them into a single denser tier-2 summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-2 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 2 distillation rules to the existing summaries, so the whole span is covered and nothing is lost.`\n : `Your tier-2 compression summaries have accumulated. Condense them further into a tier-3 ultra-condensed summary. Use block IDs as boundaries (startId and endId as bN). Any raw (uncompressed) messages sitting between the boundary blocks are absorbed into the tier-3 block as well — apply HOW TO COMPRESS to those raw messages and the TIER 3 condensation rules to the existing summaries, so the whole span is covered and nothing is lost.`,\n blockList,\n `Example: compress({ content: [{ startId: \"${startId}\", endId: \"${endId}\", summary: \"...\" }] })`,\n \"\",\n prompts.howToCompressRules,\n \"\",\n isT2 ? prompts.tier2DistillRules : prompts.tier3CondenseRules,\n ].join(\"\\n\"),\n };\n }\n\n if (isEmergency) {\n return {\n voice: \"emergency\",\n text: [\n emergencyHeader(prompts),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n `{ \"topic\": \"...\", \"content\": [{ \"startId\": \"<ID>\", \"endId\": \"<ID>\", \"summary\": \"...\" }] }`,\n \"Only use IDs from visible messages above. Compress older work first.\",\n \"\",\n rangesStr,\n ].join(\"\\n\"),\n };\n }\n\n return {\n voice: \"gentle\",\n text: [\n efficiencyNote(prompts),\n \"\",\n breakdownStr,\n \"\",\n prompts.howToCompressRules,\n \"\",\n rangesStr,\n \"\",\n `💡 Compress all ranges in one call (pass multiple content entries: \\`content: [{...}, {...}]\\`).`,\n ].join(\"\\n\"),\n };\n}\n","import { SUMMARY_HEADER } from \"./prune.js\";\nimport type { CompressionBlock, CompressionState, CoreMessage } from \"./types.js\";\n\nexport function parseBlockIdArg(arg: string): string | null {\n const normalized = arg.trim().toLowerCase();\n const refMatch = /^b0*(\\d+)$/.exec(normalized);\n if (refMatch && refMatch[1] !== undefined) return `b${refMatch[1]}`;\n const numMatch = /^(\\d+)$/.exec(normalized);\n if (numMatch && numMatch[1] !== undefined) return `b${numMatch[1]}`;\n return null;\n}\n\nexport function findBlocksOverlappingMessages(\n state: CompressionState,\n messageIds: Set<string>,\n): CompressionBlock[] {\n if (messageIds.size === 0) return [];\n const matched: CompressionBlock[] = [];\n for (const block of state.blocks) {\n if (!block.active) continue;\n if (block.effectiveMessageIds.some((id) => messageIds.has(id))) {\n matched.push(block);\n }\n }\n return matched.sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n}\n\nexport function findActiveAncestor(state: CompressionState, blockId: string): string | null {\n const start = state.blocks.find((b) => b.blockId === blockId);\n if (!start) return null;\n const queue: string[] = [...start.directBlockIds];\n const visited = new Set<string>();\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n if (visited.has(currentId)) continue;\n visited.add(currentId);\n const current = state.blocks.find((b) => b.blockId === currentId);\n if (!current) continue;\n if (current.active) return current.blockId;\n for (const ancestorId of current.directBlockIds) {\n if (!visited.has(ancestorId)) queue.push(ancestorId);\n }\n }\n return null;\n}\n\nexport interface DeactivateOptions {\n deep?: boolean;\n}\n\nexport function deactivateBlock(\n state: CompressionState,\n blockIds: string[],\n options: DeactivateOptions = {},\n): CompressionState {\n const targets = new Set(blockIds);\n\n const updated = state.blocks.map((block) => {\n if (!targets.has(block.blockId) || !block.active) return block;\n return {\n ...block,\n active: false,\n durationMs: block.durationMs,\n createdAt: block.createdAt,\n };\n });\n\n let final = updated;\n if (options.deep) {\n const visited = new Set<string>();\n const queue: string[] = [];\n for (const id of blockIds) {\n const block = updated.find((b) => b.blockId === id);\n if (block) queue.push(...block.directBlockIds);\n }\n while (queue.length > 0) {\n const id = queue.shift()!;\n if (visited.has(id)) continue;\n visited.add(id);\n final = final.map((block) => {\n if (block.blockId !== id) return block;\n queue.push(...block.directBlockIds);\n return block.active ? { ...block, active: false } : block;\n });\n }\n }\n\n return { ...state, blocks: final };\n}\n\nexport interface RestoredPreviewResult {\n preview: string;\n restoredCount: number;\n}\n\nexport function buildRestoredContentPreview(\n messages: CoreMessage[],\n beforeActiveMessageIds: Set<string>,\n state: CompressionState,\n): RestoredPreviewResult {\n const restored: CoreMessage[] = [];\n for (const message of messages) {\n if (!beforeActiveMessageIds.has(message.id)) continue;\n const stillCovered = state.blocks.some(\n (b) => b.active && b.effectiveMessageIds.includes(message.id),\n );\n if (!stillCovered) restored.push(message);\n }\n\n if (restored.length === 0) return { preview: \"\", restoredCount: 0 };\n\n const lines: string[] = [];\n let totalLength = 0;\n const MAX_PREVIEW = 2000;\n const MAX_PER_MESSAGE = 200;\n\n for (const message of restored) {\n if (totalLength >= MAX_PREVIEW) break;\n const text = message.text ?? \"\";\n const truncated = text.length > MAX_PER_MESSAGE ? text.slice(0, MAX_PER_MESSAGE) + \"...\" : text;\n const label =\n message.toolName && message.contentType !== \"text\"\n ? `${message.toolName}: ${truncated}`\n : `[${message.role}] ${truncated}`;\n lines.push(label);\n totalLength += label.length + 1;\n }\n\n return { preview: lines.join(\"\\n\"), restoredCount: restored.length };\n}\n\nexport interface CollectedContentResult {\n /** Rendered, human-readable content string (empty when count is 0). */\n text: string;\n /** Number of items rendered: direct messages + nested summaries (full=false) or all messages (full=true). */\n count: number;\n}\n\nexport interface CollectContentOptions {\n /** When true, recurse through all nested tiers to original messages. Default: false (one tier up — nested active children stay folded, their summaries shown). */\n full?: boolean;\n}\n\n/**\n * Collect a block's content as a readable string WITHOUT modifying state.\n *\n * This is the cache-safe decompress primitive: the block stays compressed\n * (folded), its summary stays in place, and the full content is returned as\n * text for the caller to surface (e.g. as a tool result appended to the\n * conversation). Unlike deactivateBlock + prune, this does not mutate the\n * message-array prefix, so prompt cache is preserved.\n *\n * full=false (default): one tier up. Nested ACTIVE children of this block\n * stay folded; their summaries are rendered in place of their messages.\n * The block's own direct messages (not covered by any active child) are\n * rendered in full.\n * full=true: recurse through all nested tiers; every effective message is\n * rendered in full.\n *\n * Returns { text: \"\", count: 0 } when the block covers no messages.\n */\nexport function collectBlockContent(\n state: CompressionState,\n block: CompressionBlock,\n messages: CoreMessage[],\n options: CollectContentOptions = {},\n): CollectedContentResult {\n const full = options.full ?? false;\n const targetIds = new Set(block.effectiveMessageIds);\n\n if (full) {\n const msgs = messages.filter((m) => targetIds.has(m.id));\n if (msgs.length === 0) return { text: \"\", count: 0 };\n return { text: msgs.map(formatMessage).join(\"\\n\\n\"), count: msgs.length };\n }\n\n // One tier up: messages covered by nested ACTIVE children stay folded\n // (their summaries shown); the block's own direct messages shown in full.\n const nestedChildren: CompressionBlock[] = [];\n const nestedCovered = new Set<string>();\n for (const childId of block.directBlockIds) {\n const child = state.blocks.find((b) => b.blockId === childId);\n if (!child?.active) continue;\n nestedChildren.push(child);\n for (const id of child.effectiveMessageIds) nestedCovered.add(id);\n }\n\n const parts: string[] = [];\n for (const child of nestedChildren) {\n const label = child.topic ? `${child.blockId}: ${child.topic}` : child.blockId;\n parts.push(`${SUMMARY_HEADER} — ${label}\\n${child.summary}`);\n }\n\n let directCount = 0;\n for (const m of messages) {\n if (targetIds.has(m.id) && !nestedCovered.has(m.id)) {\n parts.push(formatMessage(m));\n directCount++;\n }\n }\n\n const count = directCount + nestedChildren.length;\n if (count === 0) return { text: \"\", count: 0 };\n return { text: parts.join(\"\\n\\n\"), count };\n}\n\nfunction formatMessage(message: CoreMessage): string {\n const text = message.text ?? \"\";\n if (message.toolName && message.contentType !== \"text\") {\n return `[${message.role} • ${message.toolName}]\\n${text}`;\n }\n return `[${message.role}]\\n${text}`;\n}\n\nfunction numericPart(blockId: string): number {\n const match = /^b(\\d+)$/.exec(blockId);\n return match && match[1] !== undefined ? Number(match[1]) : 0;\n}\n","import { refForRaw } from \"./refs.js\";\nimport type { CompressionBlock, CompressionState, CoreMessage } from \"./types.js\";\n\nfunction formatTokens(n: number): string {\n if (!Number.isFinite(n) || n <= 0) return \"0\";\n return n >= 1000 ? `${(n / 1000).toFixed(1)}K` : String(n);\n}\n\nfunction pct(n: number, total: number): number {\n if (n <= 0 || total <= 0) return 0;\n return Math.max(1, Math.round((n / total) * 100));\n}\n\nfunction numericPart(blockId: string): number {\n const match = /^b(\\d+)$/.exec(blockId);\n return match && match[1] !== undefined ? Number(match[1]) : 0;\n}\n\nfunction summaryTokensOf(block: CompressionBlock, countTokens: (t: string) => number): number {\n return countTokens(block.summary);\n}\n\nfunction effectiveCompressedTokens(\n block: CompressionBlock,\n _state: CompressionState,\n _countTokens: (t: string) => number,\n): number {\n // block.compressedTokens already records the full input token count of the\n // operation that created this block: for a tier-1 block that is the raw\n // messages; for a tier-2 block it is the tier-1 summaries + the new\n // messages it spans. Recursing into directBlockIds and summing children's\n // compressedTokens double-counts the consumed children, so we return the\n // block's own value directly. (The previous recursion inflated tier-2+\n // \"original\" figures and mis-ordered the status report.)\n return block.compressedTokens;\n}\n\nfunction tierLabel(block: CompressionBlock): string {\n return `T${block.tier}`;\n}\n\nfunction tierBreakdown(\n blocks: CompressionBlock[],\n countTokens: (t: string) => number,\n): string | null {\n const tierTokens: Record<number, number> = {};\n for (const block of blocks) {\n tierTokens[block.tier] = (tierTokens[block.tier] ?? 0) + summaryTokensOf(block, countTokens);\n }\n const tiers = Object.keys(tierTokens).map(Number);\n if (tiers.length <= 1) return null;\n const parts: string[] = [];\n for (const tier of [1, 2, 3]) {\n if (tierTokens[tier]) parts.push(`T${tier}: ${formatTokens(tierTokens[tier])}`);\n }\n return parts.join(\" | \");\n}\n\ninterface VisibleMessageInfo {\n ref: string;\n tokens: number;\n tool: string;\n index: number;\n}\n\nfunction collectVisible(\n messages: CoreMessage[],\n state: CompressionState,\n countTokens: (t: string) => number,\n): { visible: VisibleMessageInfo[]; summaryTokens: number } {\n const coveredIds = new Set<string>();\n for (const block of state.blocks) {\n if (!block.active) continue;\n for (const id of block.effectiveMessageIds) coveredIds.add(id);\n }\n let summaryTokens = 0;\n for (const block of state.blocks) {\n if (block.active) summaryTokens += summaryTokensOf(block, countTokens);\n }\n const visible: VisibleMessageInfo[] = [];\n messages.forEach((message, index) => {\n if (coveredIds.has(message.id)) return;\n const ref = refForRaw(state.messageRefs, message.id);\n if (!ref) return;\n const tokens = countTokens(message.text ?? \"\");\n const tool = message.toolName ?? \"text\";\n if (tokens > 0) visible.push({ ref, tokens, tool, index });\n });\n return { visible, summaryTokens };\n}\n\nexport interface StatusReportOptions {\n scope?: \"compressed\" | \"uncompressed\";\n view?: \"ranges\" | \"messages\";\n tool?: string;\n sort?: \"size\" | \"time\" | \"tool\" | \"age\";\n limit?: number;\n}\n\nexport function buildStatusReport(\n state: CompressionState,\n messages: CoreMessage[],\n countTokens: (t: string) => number,\n options: StatusReportOptions = {},\n): string {\n const scope = options.scope;\n const view = options.view ?? \"ranges\";\n const toolFilter = options.tool;\n const sort = options.sort ?? \"size\";\n const limit = options.limit ?? 30;\n\n const activeBlocks = state.blocks\n .filter((b) => b.active)\n .sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n\n if (scope === \"compressed\") {\n return renderCompressedDrilldown(activeBlocks, state, sort, limit, countTokens);\n }\n\n const { visible, summaryTokens } = collectVisible(messages, state, countTokens);\n\n if (scope === \"uncompressed\") {\n if (view === \"messages\") {\n return renderMessageDrilldown(visible, toolFilter, sort, limit);\n }\n return renderUncompressedRanges(visible);\n }\n\n return renderOverview(visible, summaryTokens, activeBlocks, state, countTokens, limit);\n}\n\nfunction renderOverview(\n visible: VisibleMessageInfo[],\n summaryTokens: number,\n blocks: CompressionBlock[],\n state: CompressionState,\n countTokens: (t: string) => number,\n limit: number,\n): string {\n const lines: string[] = [];\n const toolTypeMap = new Map<string, number>();\n for (const message of visible) {\n toolTypeMap.set(message.tool, (toolTypeMap.get(message.tool) ?? 0) + message.tokens);\n }\n const topTool = [...toolTypeMap.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];\n\n const totalTool = visible\n .filter((m) => m.tool !== \"text\")\n .reduce((sum, m) => sum + m.tokens, 0);\n const totalText = visible\n .filter((m) => m.tool === \"text\")\n .reduce((sum, m) => sum + m.tokens, 0);\n const total = summaryTokens + totalTool + totalText;\n\n lines.push(\"CONTEXT BREAKDOWN\");\n lines.push(\n ` ${formatTokens(totalTool)} tool (${pct(totalTool, total)}%) | ${formatTokens(totalText)} text (${pct(totalText, total)}%) | ${formatTokens(summaryTokens)} summaries (${pct(summaryTokens, total)}%)`,\n );\n const topTypes = [...toolTypeMap.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 3);\n if (topTypes.length > 0) {\n lines.push(` Top tools: ${topTypes.map(([t, n]) => `${t} (${pct(n, total)}%)`).join(\", \")}`);\n }\n\n lines.push(\"\");\n if (blocks.length === 0) {\n lines.push(\"COMPRESSED BLOCKS\");\n lines.push(\" No compressed blocks.\");\n } else {\n const totalSummary = blocks.reduce((s, b) => s + summaryTokensOf(b, countTokens), 0);\n const totalEffective = blocks.reduce(\n (s, b) => s + effectiveCompressedTokens(b, state, countTokens),\n 0,\n );\n lines.push(\n `COMPRESSED BLOCKS — ${blocks.length} active (${formatTokens(totalSummary)} summary, ${formatTokens(totalEffective)} original)`,\n );\n const breakdown = tierBreakdown(blocks, countTokens);\n if (breakdown) lines.push(` Tier usage: ${breakdown}`);\n lines.push(\"\");\n const sorted = [...blocks].sort(\n (a, b) =>\n effectiveCompressedTokens(b, state, countTokens) -\n effectiveCompressedTokens(a, state, countTokens) ||\n b.createdAt - a.createdAt,\n );\n for (const block of sorted.slice(0, limit)) {\n const topic = block.topic ?? \"(no topic)\";\n const eff = effectiveCompressedTokens(block, state, countTokens);\n lines.push(\n ` ${block.blockId} (${tierLabel(block)}) ${formatTokens(eff)}→${formatTokens(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs \"${topic}\"`,\n );\n }\n }\n\n lines.push(\"\");\n lines.push(\n `Tip: buildStatusReport({scope:\"uncompressed\", view:\"messages\", tool:\"${topTool ?? \"bash\"}\"}) for per-message listing`,\n );\n return lines.join(\"\\n\");\n}\n\nfunction renderUncompressedRanges(visible: VisibleMessageInfo[]): string {\n const lines: string[] = [];\n const totalTokens = visible.reduce((s, m) => s + m.tokens, 0);\n lines.push(`UNCOMPRESSED — ${formatTokens(totalTokens)} | ${visible.length} visible messages`);\n lines.push(\"\");\n if (visible.length === 0) {\n lines.push(\" (no uncompressed messages)\");\n return lines.join(\"\\n\");\n }\n // Merge consecutive messages into ranges (by numeric ref), aggregating\n // token counts and dominant tool so the view reads as blocks, not a\n // per-message firehose — mirroring the Compressible Ranges output.\n interface Merged { startRef: string; endRef: string; startNum: number; count: number; tokens: number; tool: string; }\n const refNum = (ref: string): number => {\n const m = ref.match(/\\d+/);\n return m ? parseInt(m[0], 10) : 0;\n };\n const merged: Merged[] = [];\n for (const m of visible) {\n const num = refNum(m.ref);\n const last = merged[merged.length - 1];\n if (last && num === last.startNum + last.count) {\n last.endRef = m.ref;\n last.count += 1;\n last.tokens += m.tokens;\n } else {\n merged.push({ startRef: m.ref, endRef: m.ref, startNum: num, count: 1, tokens: m.tokens, tool: m.tool });\n }\n }\n for (const r of merged.slice(0, 30)) {\n const range = r.count === 1 ? r.startRef : `${r.startRef}–${r.endRef}`;\n lines.push(` ${range} (${r.count} msgs, ${formatTokens(r.tokens)}${r.count > 1 ? ` (${Math.round(r.tokens / r.count)}/msg)` : \"\"}) ${r.tool}`);\n }\n if (merged.length > 30) {\n lines.push(` ... and ${merged.length - 30} more ranges`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction renderMessageDrilldown(\n visible: VisibleMessageInfo[],\n toolFilter: string | undefined,\n sort: string,\n limit: number,\n): string {\n let filtered = visible;\n if (toolFilter) filtered = filtered.filter((m) => m.tool === toolFilter);\n\n if (sort === \"time\") filtered.sort((a, b) => a.index - b.index);\n else if (sort === \"tool\") filtered.sort((a, b) => a.tool.localeCompare(b.tool) || b.tokens - a.tokens);\n else filtered.sort((a, b) => b.tokens - a.tokens);\n\n const totalTokens = filtered.reduce((s, m) => s + m.tokens, 0);\n const allTokens = visible.reduce((s, m) => s + m.tokens, 0);\n const header = toolFilter\n ? `UNCOMPRESSED — ${toolFilter}: ${formatTokens(totalTokens)} | ${filtered.length} msgs | ${pct(totalTokens, allTokens)}% of visible`\n : `UNCOMPRESSED — ${formatTokens(totalTokens)} | ${filtered.length} msgs`;\n const lines = [header, `Sorted by ${sort}`, \"\"];\n const shown = filtered.slice(0, limit);\n for (const message of shown) {\n lines.push(` ${message.ref} (${formatTokens(message.tokens)}) ${message.tool}`);\n }\n if (filtered.length > shown.length) {\n lines.push(\"\");\n lines.push(`${shown.length} of ${filtered.length} shown.`);\n }\n return lines.join(\"\\n\");\n}\n\nfunction renderCompressedDrilldown(\n blocks: CompressionBlock[],\n state: CompressionState,\n sort: string,\n limit: number,\n countTokens: (t: string) => number,\n): string {\n let sorted = [...blocks];\n if (sort === \"time\") sorted.sort((a, b) => a.createdAt - b.createdAt);\n else if (sort === \"age\") sorted.sort((a, b) => b.survivedCount - a.survivedCount);\n else\n sorted.sort(\n (a, b) =>\n effectiveCompressedTokens(b, state, countTokens) -\n effectiveCompressedTokens(a, state, countTokens) ||\n b.createdAt - a.createdAt,\n );\n\n const totalSummary = sorted.reduce((s, b) => s + summaryTokensOf(b, countTokens), 0);\n const totalEffective = sorted.reduce(\n (s, b) => s + effectiveCompressedTokens(b, state, countTokens),\n 0,\n );\n const lines = [\n `COMPRESSED — ${sorted.length} blocks | ${formatTokens(totalEffective)} original → ${formatTokens(totalSummary)} summary`,\n ];\n const breakdown = tierBreakdown(sorted, countTokens);\n if (breakdown) lines.push(`Tier usage: ${breakdown}`);\n lines.push(\"\");\n const shown = sorted.slice(0, limit);\n for (const block of shown) {\n const nested = block.directBlockIds.length > 0 ? ` nested=[${block.directBlockIds.join(\",\")}]` : \"\";\n const topic = block.topic ?? \"(no topic)\";\n const eff = effectiveCompressedTokens(block, state, countTokens);\n lines.push(\n ` ${block.blockId} (${tierLabel(block)}) ${formatTokens(eff)}→${formatTokens(summaryTokensOf(block, countTokens))} ${block.effectiveMessageIds.length} msgs age=${block.survivedCount} ${block.generation}${nested}`,\n );\n lines.push(` \"${topic}\"`);\n }\n if (sorted.length > shown.length) {\n lines.push(\"\");\n lines.push(`${shown.length} of ${sorted.length} shown.`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRecap(\n state: CompressionState,\n blockId?: string,\n): string {\n const activeBlocks = state.blocks\n .filter((b) => b.active)\n .sort((a, b) => numericPart(a.blockId) - numericPart(b.blockId));\n\n if (blockId !== undefined) {\n const block = state.blocks.find((b) => b.blockId === blockId);\n if (!block) {\n const activeList = activeBlocks.map((b) => b.blockId).join(\", \");\n return `Block ${blockId} not found. Active blocks: ${activeList}`;\n }\n if (!block.active) {\n return `Block ${blockId} is inactive (deactivated by nested compression).`;\n }\n const range = `${block.effectiveMessageIds.length} messages`;\n return `[Compressed conversation section]\\n${block.summary}\\n\\n[${blockId} | ${range} | topic: \"${block.topic ?? \"(none)\"}\"]`;\n }\n\n if (activeBlocks.length === 0) return \"No active compression blocks.\";\n\n const lines = [`Active compression blocks (${activeBlocks.length}):`];\n for (const block of activeBlocks) {\n const range = `${block.effectiveMessageIds.length} messages`;\n const preview = block.summary.slice(0, 200);\n lines.push(`\\n${block.blockId} | ${range} | \"${block.topic ?? \"(none)\"}\"`);\n lines.push(` ${preview}${block.summary.length > 200 ? \"...\" : \"\"}`);\n }\n lines.push(`\\nCall with blockId to get the full summary.`);\n return lines.join(\"\\n\");\n}\n","import { createCore } from \"./compress.js\";\nimport { assignRefs, highestUsedIndex } from \"./refs.js\";\nimport { defaultCountTokens } from \"./tokenize.js\";\nimport type { CompressionState, CoreMessage } from \"./types.js\";\n\nexport interface CompressInputEntry {\n startId?: string;\n endId?: string;\n messageId?: string;\n summary: string;\n topic?: string;\n}\n\nexport interface RebuildResult {\n state: CompressionState;\n blocksRebuilt: number;\n}\n\nexport interface RebuildPorts {\n countTokens?: (text: string) => number;\n}\n\n/**\n * Fork-recovery: reconstruct compression state by replaying historical\n * `compress` tool-call messages. Message refs (mNNNNN) are assigned by\n * message order, so they are fork-stable — a ref in a historical compress\n * input points to the same logical message after a fork regenerates IDs.\n * The rebuilt state is an approximation: only raw model summaries are\n * replayed (no protected-content enrichments).\n */\nexport function rebuildCompressionState(\n state: CompressionState,\n messages: CoreMessage[],\n config: import(\"./types.js\").Config,\n ports: RebuildPorts = {},\n): RebuildResult {\n const core = createCore({ countTokens: ports.countTokens ?? defaultCountTokens });\n const refResult = assignRefs(messages, {\n existing: state.messageRefs,\n nextIndex: highestUsedIndex(state.messageRefs) + 1,\n });\n let working: CompressionState = { ...state, messageRefs: refResult.map };\n\n const invocations = collectCompressInvocations(messages);\n let blocksRebuilt = 0;\n\n for (const invocation of invocations) {\n const ranges = extractRanges(invocation.input, invocation.callId);\n if (ranges.length === 0) continue;\n const result = core.applyCompression({ ranges, messages, state: working, config });\n working = result.state;\n blocksRebuilt += result.result.blocksCreated;\n }\n\n return { state: working, blocksRebuilt };\n}\n\ninterface CompressInvocation {\n callId: string | undefined;\n input: unknown;\n}\n\nfunction collectCompressInvocations(messages: CoreMessage[]): CompressInvocation[] {\n const invocations: CompressInvocation[] = [];\n for (const message of messages) {\n if (message.toolName !== \"compress\" || message.contentType !== \"tool-call\") continue;\n let input: unknown;\n try {\n input = JSON.parse(message.text ?? \"\");\n } catch {\n continue;\n }\n invocations.push({ callId: message.toolCallId, input });\n }\n return invocations;\n}\n\nfunction extractRanges(\n input: unknown,\n callId: string | undefined,\n): Array<{\n startRef: string;\n endRef: string;\n summary: string;\n topic?: string;\n compressCallId?: string;\n}> {\n const content = (input as { content?: unknown[] })?.content;\n if (!Array.isArray(content)) return [];\n const ranges = [];\n for (const entry of content) {\n if (!entry || typeof entry !== \"object\") continue;\n const e = entry as CompressInputEntry;\n if (typeof e.summary !== \"string\") continue;\n const start = e.startId ?? e.messageId;\n const end = e.endId ?? e.messageId;\n if (typeof start !== \"string\" || typeof end !== \"string\") continue;\n ranges.push({\n startRef: start,\n endRef: end,\n summary: e.summary,\n topic: typeof e.topic === \"string\" ? e.topic : undefined,\n compressCallId: callId,\n });\n }\n return ranges;\n}\n","export type TransformChannel = \"message\" | \"wire\";\n\n/**\n * Pick the transform channel: an explicit preference always wins; otherwise\n * the wire channel is used only when the caller's host actually applies the\n * wire-payload replacement (adapters pass `wireViable` — e.g. the body format\n * is in WIRE_FORMATS and the host honors the hook's return value).\n */\nexport function resolveTransformChannel(\n explicit: TransformChannel | undefined,\n wireViable: boolean,\n): TransformChannel {\n return explicit ?? (wireViable ? \"wire\" : \"message\");\n}\n","/**\n * Lightweight English stemmer (suffix stripping, Porter-inspired).\n * Zero dependencies. Good enough for IR morphology normalization:\n * tokens → token, running → runn, compressed → compress,\n * authentication → authentic, handling → handl, subagents → subagent\n *\n * Not a full Porter stemmer — intentionally simpler and faster. CJK is\n * untouched (handled by bigram tokenization, not stemming).\n */\nexport function stem(word: string): string {\n let w = word;\n if (w.length <= 3) return w;\n if (w.endsWith(\"ies\")) w = w.slice(0, -3) + \"y\";\n else if (w.endsWith(\"ses\") || w.endsWith(\"xes\") || w.endsWith(\"zes\")) w = w.slice(0, -2);\n else if (w.endsWith(\"ches\") || w.endsWith(\"shes\")) w = w.slice(0, -2);\n else if (w.endsWith(\"s\") && !w.endsWith(\"ss\")) w = w.slice(0, -1);\n if (w.endsWith(\"ing\") && w.length > 5) w = w.slice(0, -3);\n if (w.endsWith(\"ed\") && w.length > 4) w = w.slice(0, -2);\n if (w.endsWith(\"ation\") && w.length > 6) w = w.slice(0, -3);\n else if (w.endsWith(\"tion\") && w.length > 5) w = w.slice(0, -4) + \"t\";\n else if (w.endsWith(\"ion\") && w.length > 4) w = w.slice(0, -3);\n if (w.endsWith(\"ment\") && w.length > 6) w = w.slice(0, -4);\n if (w.endsWith(\"ness\") && w.length > 6) w = w.slice(0, -4);\n if (w.endsWith(\"ly\") && w.length > 4) w = w.slice(0, -2);\n return w;\n}\n","/**\n * Search tokenizer.\n *\n * Handles mixed Latin + CJK content — the single biggest quality lever\n * over plain substring search. Latin is split on non-word boundaries;\n * CJK (no spaces) is word-segmented via Intl.Segmenter (CLDR dictionary),\n * falling back to overlapping bigrams on out-of-vocabulary text so a query\n * like \"身份验证\" still scores against doc text \"身份验证流程\".\n *\n * CJK segmentation is a SINGLE segment() pass over the whole text, not one\n * call per CJK run: a segment() call has fixed overhead (~3µs), and\n * run-heavy text (logs: dozens of short runs per line) made per-run calls\n * 10-16× slower than one bulk pass. ICU never merges CJK words across\n * non-CJK boundaries, so bulk segmentation yields the same words per run\n * (differential-verified against the per-run implementation across a\n * mixed-script stress corpus); run boundaries are re-derived below to keep\n * the all-OOV bigram fallback.\n */\n\n/**\n * CJK ideograph/kana/hangul class — the one shared definition of \"non-Latin\n * script that must be handled specially\". Exported so fuzzy.ts relaxes its\n * short-query gate for the SAME range tokenizer.ts segments: two hand-copied\n * regexes would silently drift apart. Latin is deliberately absent — 2-char\n * English tokens (\"to\", \"of\") carry no meaning, while nearly all CJK words\n * are 2-char atomic units (登录/缓存), so the two scripts need opposite rules.\n */\nimport { stem } from \"./stemmer.js\";\n\nexport const CJK = /[\\u3400-\\u9fff\\uf900-\\ufaff\\u3040-\\u30ff\\uac00-\\ud7af]/;\nconst LATIN_WORD = /[a-z][a-z0-9_]*[a-z0-9]|[a-z0-9]/g;\n\nconst cjkSegmenter = new Intl.Segmenter(\"zh\", { granularity: \"word\" });\n\n/**\n * CJK segment groups → tokens, with the all-OOV fallback.\n *\n * `segs` are the word segments the segmenter produced for ONE contiguous\n * CJK run. Multi-char words are kept as whole terms, so \"国际化\" matches\n * \"国际化\" and \"试验证明\" no longer scores against \"验证\" through accidental\n * char runs. When the dictionary finds no multi-char word at all (all-OOV\n * text) we fall back to overlapping bigrams + single chars so recall is\n * preserved — this also covers single-char queries like \"验\".\n */\nfunction cjkRunTokens(segs: string[]): string[] {\n const words = segs.filter((w) => w.length >= 2);\n if (words.length > 0) return words;\n const run = segs.join(\"\");\n const out: string[] = [];\n for (let i = 0; i < run.length - 1; i++) out.push(run.slice(i, i + 2));\n for (const ch of run) out.push(ch);\n return out;\n}\n\nexport interface TokenizeOptions {\n stem?: boolean;\n}\n\nexport function tokenize(text: string, opts: TokenizeOptions = {}): string[] {\n const lower = text.toLowerCase();\n const tokens: string[] = [];\n\n const latin = lower.match(LATIN_WORD) ?? [];\n for (let w of latin) {\n if (w.length >= 2) {\n if (opts.stem) w = stem(w);\n tokens.push(w);\n }\n }\n\n // CJK: one segmenter pass over the whole text instead of one\n // segment() call per CJK run. A segment() call has fixed overhead\n // (~3µs), and run-heavy text (logs: dozens of short runs per line) made\n // per-run calls 10-16× slower than one bulk pass. ICU never merges CJK\n // words across non-CJK boundaries, so bulk segmentation yields the same\n // words per run (differential-verified against the per-run\n // implementation across a mixed-script stress corpus); run boundaries\n // are re-derived below to keep the all-OOV bigram fallback.\n //\n // Guard: skip the segmenter entirely when the text has no CJK at all —\n // the old code never called it for pure-Latin text, and a bulk pass\n // would pay a full-text scan (12ms → 33ms per MB of English) for nothing.\n if (!CJK.test(lower)) return tokens;\n\n // Group the bulk segments back into CJK runs: a non-CJK segment is a run\n // boundary (the segmenter never puts non-CJK inside a CJK word segment).\n const runSegs: string[][] = [];\n let cur: string[] | null = null;\n for (const s of cjkSegmenter.segment(lower)) {\n const t = s.segment;\n if (t.length === 0) continue;\n if (CJK.test(t)) {\n (cur ??= []).push(t);\n } else if (cur) {\n runSegs.push(cur);\n cur = null;\n }\n }\n if (cur) runSegs.push(cur);\n\n for (const segs of runSegs) {\n tokens.push(...cjkRunTokens(segs));\n }\n\n return tokens;\n}\n\n/** Character bigrams over arbitrary text — used by fuzzy matching. */\nexport function charBigrams(text: string): string[] {\n const grams: string[] = [];\n for (let i = 0; i < text.length - 1; i++) {\n const pair = text.slice(i, i + 2);\n if (pair.trim().length === pair.length) grams.push(pair);\n }\n return grams;\n}\n\n/** Term-frequency map. */\nexport function tfMap(text: string, stem: boolean): Map<string, number> {\n const m = new Map<string, number>();\n for (const t of tokenize(text, { stem })) m.set(t, (m.get(t) ?? 0) + 1);\n return m;\n}\n","/**\n * Per-doc derived features, memoized across search calls.\n *\n * A search over the compressed history re-scores the SAME immutable docs on\n * every call — compressed block summaries and folded message text never\n * change. Without this cache, every search_context call re-tokenized the\n * entire corpus (segmenter CJK pass ≈ 0.3s/MB cold) plus re-lowercased it\n * and rebuilt the bigram set for each channel: a 5MB session cost ~3s PER\n * CALL, growing linearly with session length. With the cache the corpus is\n * processed once; later searches are O(docs × query-terms).\n *\n * Keyed by doc text (immutable). Bounded by total cached source chars —\n * oldest docs are evicted when the cap is exceeded, so a long-lived\n * process serving many sessions cannot grow unboundedly. Hosts that want to\n * release the memory eagerly on session shutdown/switch can call\n * clearDocFeatures() (optional: the cap already bounds it).\n */\n\nimport { charBigrams, tfMap } from \"./tokenizer.js\";\n\nexport interface DocFeatures {\n /** Stemmed term frequencies (BM25 channel). */\n tf: Map<string, number>;\n /** Total term count (BM25 length normalization). */\n len: number;\n /** Lower-cased text (substring + fuzzy channels). */\n lower: string;\n /** Unique char bigrams of `lower` (fuzzy channel). */\n grams: Set<string>;\n}\n\nconst DEFAULT_CAP_CHARS = 8 * 1024 * 1024;\nlet capChars = DEFAULT_CAP_CHARS;\nconst cache = new Map<string, DocFeatures>();\nlet cachedChars = 0;\n\nfunction build(text: string): DocFeatures {\n const tf = tfMap(text, true);\n let len = 0;\n for (const v of tf.values()) len += v;\n const lower = text.toLowerCase();\n return { tf, len, lower, grams: new Set(charBigrams(lower)) };\n}\n\nexport function docFeatures(text: string): DocFeatures {\n const hit = cache.get(text);\n if (hit) return hit;\n const f = build(text);\n if (text.length > 0 && text.length <= capChars) {\n while (cachedChars + text.length > capChars && cache.size > 0) {\n const k = cache.keys().next().value as string;\n cachedChars -= k.length;\n cache.delete(k);\n }\n cache.set(text, f);\n cachedChars += text.length;\n }\n return f;\n}\n\n/** Drop all cached features (e.g. on session shutdown/switch). */\nexport function clearDocFeatures(): void {\n cache.clear();\n cachedChars = 0;\n}\n\n/**\n * Set the cache cap in source chars. Docs larger than the cap are never\n * cached. Also used by tests to exercise eviction.\n */\nexport function setDocCacheCap(chars: number): void {\n capChars = Math.max(1, chars);\n while (cachedChars > capChars && cache.size > 0) {\n const k = cache.keys().next().value as string;\n cachedChars -= k.length;\n cache.delete(k);\n }\n}\n\n/** Cache occupancy — for diagnostics. */\nexport function docCacheInfo(): { entries: number; chars: number } {\n return { entries: cache.size, chars: cachedChars };\n}\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * Substring counting — the original baseline algorithm.\n * Exact, lowercased substring occurrence counts. Predictable but blind to\n * morphology, typos, and CJK word boundaries. Kept for backward compat and\n * as a deterministic reference.\n */\nexport const substringAlgorithm: SearchAlgorithm = {\n name: \"substring\",\n description: \"Exact substring counting (original baseline). Predictable, no normalization.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const terms = query.toLowerCase().trim().split(/\\s+/).filter((t) => t.length > 0);\n if (terms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n return docs.map((d) => {\n const haystack = docFeatures(d.text).lower; // memoized across calls\n let score = 0;\n for (const term of terms) score += countOccurrences(haystack, term);\n return { ref: d.ref, score };\n });\n },\n};\n\nfunction countOccurrences(haystack: string, needle: string): number {\n if (!needle) return 0;\n return haystack.split(needle).length - 1;\n}\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { tokenize } from \"../tokenizer.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * BM25 with stemming + CJK bigram tokenization.\n *\n * k1=1.2, b=0.75 (standard IR). IDF down-weights terms common across the\n * corpus; length normalization prevents long summaries from dominating by\n * raw term count. Stemming collapses English morphology\n * (compress/compressed/compression → ~compress).\n *\n * On the 32-block mixed EN/CJK benchmark: MRR 0.833 / R@1 0.833 / R@3 0.833\n * vs 0.797 / 0.792 / 0.792 for substring — better in isolation on every\n * metric, and the precision component of the hybrid default (see hybrid.ts).\n */\nexport const bm25Algorithm: SearchAlgorithm = {\n name: \"bm25\",\n description: \"BM25 with stemming + CJK bigram tokenization. IR-standard relevance ranking.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const N = docs.length;\n const k1 = 1.2;\n const b = 0.75;\n const parsed = docs.map((d) => {\n const f = docFeatures(d.text); // memoized: tf + length, cached across calls\n return { id: d.ref, tf: f.tf, len: f.len };\n });\n const avgdl = parsed.reduce((s, d) => s + d.len, 0) / (N || 1);\n\n const qTerms = tokenize(query, { stem: true });\n if (qTerms.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n const idf = new Map<string, number>();\n for (const t of new Set(qTerms)) {\n let df = 0;\n for (const d of parsed) if (d.tf.has(t)) df++;\n idf.set(t, Math.log(1 + (N - df + 0.5) / (df + 0.5)));\n }\n\n return parsed.map((d) => {\n let score = 0;\n for (const t of qTerms) {\n const f = d.tf.get(t) ?? 0;\n if (f === 0) continue;\n const idfT = idf.get(t) ?? 0;\n score += (idfT * (f * (k1 + 1))) / (f + k1 * (1 - b + (b * d.len) / (avgdl || 1)));\n }\n return { ref: d.id, score };\n });\n },\n};\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { charBigrams, CJK } from \"../tokenizer.js\";\nimport { docFeatures } from \"../doc-cache.js\";\n\n/**\n * Fuzzy character-bigram matching (Jaccard-style).\n *\n * Decomposes the query into character bigrams and measures overlap with\n * each doc. Robust to typos (tokan≈token), partial words, and works\n * uniformly across all scripts (CJK benefits most).\n *\n * Query-token gate — CJK gets its own length rule; Latin is frozen:\n * length >= 4 (any script) typo-tolerant bigram rescue needs a couple of\n * chars before it means anything; 2-3-char Latin tokens (\"to\", \"of\",\n * \"us\") are stop-word noise whose bigrams overlap nearly every doc.\n * length >= 2 && CJK Chinese/Japanese/Korean words are mostly\n * 2-character atomic units (登录/缓存/図表), so the Latin-style >= 4 rule\n * would lock the whole CJK query space out of this recall channel\n * (that gap is what bench \"缓存 → nothing\" exposed). Single CJK chars\n * stay excluded — one char cannot form a bigram, nothing to compare.\n *\n * On benchmark: lowest MRR of any single algorithm (0.795 — a hair under\n * substring's 0.797) — precision is weak, but it is the recall boost in the\n * hybrid default.\n */\nexport const fuzzyAlgorithm: SearchAlgorithm = {\n name: \"fuzzy\",\n description: \"Character bigram overlap. Typo-tolerant, script-agnostic, high recall.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n // Gate (see header): Latin short tokens are noise, 2-char CJK words\n // are real terms — admit the latter so 缓存/登录 reach the scorer.\n const qTokens = query.toLowerCase().split(/[\\s,]+/).filter((t) => t.length >= 4 || (t.length >= 2 && CJK.test(t)));\n if (qTokens.length === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n const qGrams = new Set<string>();\n for (const t of qTokens) for (const g of charBigrams(t)) qGrams.add(g);\n if (qGrams.size === 0) return docs.map((d) => ({ ref: d.ref, score: 0 }));\n\n return docs.map((d) => {\n const docGrams = docFeatures(d.text).grams; // memoized bigram set\n let hits = 0;\n for (const g of qGrams) if (docGrams.has(g)) hits++;\n return { ref: d.ref, score: hits / qGrams.size };\n });\n },\n};\n","import type { SearchAlgorithm, SearchDoc, ScoredBlock } from \"../types.js\";\nimport { bm25Algorithm } from \"./bm25.js\";\nimport { fuzzyAlgorithm } from \"./fuzzy.js\";\n\n/**\n * Hybrid: normalized BM25(stem) + fuzzy n-gram, weighted 0.7 / 0.3.\n *\n * BM25 supplies precision on real terms (with morphology + IDF + length\n * norm); fuzzy supplies recall on typos, partials, and cross-script.\n * Each component is max-normalized to [0,1] before weighting so their\n * scales are comparable regardless of corpus size.\n *\n * Benchmark (32 blocks, 48 mixed EN/CJK queries, final code — segmenter\n * tokenizer + CJK fuzzy gate):\n * substring MRR 0.797 R@1 0.792 R@3 0.792\n * bm25 MRR 0.833 R@1 0.833 R@3 0.833\n * fuzzy MRR 0.795 R@1 0.708 R@3 0.875\n * hybrid MRR 0.898 R@1 0.875 R@3 0.917 ← best on every metric\n * The weight ratio is robust: 0.6–0.8 for BM25 all score within 0.001 MRR.\n */\n\nconst W_BM25 = 0.7;\nconst W_FUZZY = 0.3;\n\nexport const hybridAlgorithm: SearchAlgorithm = {\n name: \"hybrid\",\n description: \"Weighted BM25(stem) + fuzzy n-gram. Default — best precision + recall.\",\n score(docs: SearchDoc[], query: string): ScoredBlock[] {\n const bm = bm25Algorithm.score(docs, query);\n const fz = fuzzyAlgorithm.score(docs, query);\n const maxBm = Math.max(...bm.map((r) => r.score), 1e-9);\n const maxFz = Math.max(...fz.map((r) => r.score), 1e-9);\n const bmMap = new Map(bm.map((r) => [r.ref, r.score / maxBm]));\n const fzMap = new Map(fz.map((r) => [r.ref, r.score / maxFz]));\n return docs.map((d) => ({\n ref: d.ref,\n score: W_BM25 * (bmMap.get(d.ref) ?? 0) + W_FUZZY * (fzMap.get(d.ref) ?? 0),\n }));\n },\n};\n","/**\n * Algorithm registry. Builtins are pre-registered; hosts may register\n * additional algorithms (e.g. an embedding-based semantic provider) via\n * registerSearchAlgorithm and reference them by name in SearchOptions.\n */\nimport type { AnySearchAlgorithm } from \"./types.js\";\nimport { substringAlgorithm } from \"./algorithms/substring.js\";\nimport { bm25Algorithm } from \"./algorithms/bm25.js\";\nimport { fuzzyAlgorithm } from \"./algorithms/fuzzy.js\";\nimport { hybridAlgorithm } from \"./algorithms/hybrid.js\";\n\nconst registry = new Map<string, AnySearchAlgorithm>();\n\nexport function registerSearchAlgorithm(algo: AnySearchAlgorithm): void {\n registry.set(algo.name, algo);\n}\n\nexport function getSearchAlgorithm(name: string): AnySearchAlgorithm | undefined {\n return registry.get(name);\n}\n\nexport function listSearchAlgorithms(): AnySearchAlgorithm[] {\n return [...registry.values()];\n}\n\n// Pre-register builtins. Hybrid is the default (see types.ts DEFAULT_ALGORITHM).\nregisterSearchAlgorithm(substringAlgorithm);\nregisterSearchAlgorithm(bm25Algorithm);\nregisterSearchAlgorithm(fuzzyAlgorithm);\nregisterSearchAlgorithm(hybridAlgorithm);\n","/**\n * Search type definitions.\n *\n * Two data sources are searchable:\n * - Compressed blocks (summary text; ref = \"b{id}\")\n * - Historical messages (original text from the append-only session log;\n * ref = \"m{NNNNN}\"). These let the model locate detail that compression\n * turned into a short summary — search to pinpoint, then decompress the\n * owning block for the full content.\n *\n * A SearchAlgorithm is a stateless scorer over a unified SearchDoc[]. Roles\n * carry a configurable weight (user intent > assistant reasoning > tool noise).\n */\n\n/** Where a searchable document came from. */\nexport type SearchDocKind = \"block\" | \"message\";\n\nexport type MessageRole = \"user\" | \"assistant\" | \"tool\";\n\n/** A unified searchable document — either a block summary or a message. */\nexport interface SearchDoc {\n kind: SearchDocKind;\n /** Stable ref for decompress: \"b3\" for a block, \"m00350\" for a message. */\n ref: string;\n /** Text this doc is scored against (topic+summary for blocks; content for messages). */\n text: string;\n /** For preview/title display. */\n title: string;\n /** Message role (messages only); undefined for blocks. Drives role weighting. */\n role?: MessageRole;\n /** Block owning this doc. For blocks: the block itself. For messages: the block\n * that compressed it (so the model knows which block to decompress for detail). */\n blockId?: string;\n /** Tier of the owning block (display + grouping). */\n tier?: number;\n /** Approx token size (for \"how big is this\" display). */\n tokens?: number;\n}\n\n/** Per-role score multipliers. Defaults favor user intent over tool noise. */\nexport interface RoleWeights {\n user?: number;\n assistant?: number;\n tool?: number;\n block?: number;\n}\n\nexport const DEFAULT_ROLE_WEIGHTS: Required<RoleWeights> = {\n user: 1.5,\n assistant: 1.0,\n tool: 0.6,\n block: 1.0,\n};\n\nexport interface ScoredBlock {\n ref: string;\n score: number;\n}\n\nexport interface SearchAlgorithm {\n name: string;\n description: string;\n score(docs: SearchDoc[], query: string): ScoredBlock[];\n}\n\nexport interface AsyncSearchAlgorithm {\n name: string;\n description: string;\n score(docs: SearchDoc[], query: string): Promise<ScoredBlock[]>;\n}\n\nexport type AnySearchAlgorithm = SearchAlgorithm | AsyncSearchAlgorithm;\n\nexport interface SearchResult {\n /** \"block\" or \"message\". */\n kind: SearchDocKind;\n /** Ref to pass to decompress: \"b3\" or \"m00350\". */\n ref: string;\n /** Owning block id (for messages: the block that compressed it). */\n blockId?: string;\n tier: number;\n score: number;\n title: string;\n preview: string;\n role?: MessageRole;\n tokens?: number;\n}\n\nexport interface SearchOptions {\n algorithm?: string;\n limit?: number;\n previewLength?: number;\n minScore?: number;\n /** Per-role weights (default DEFAULT_ROLE_WEIGHTS). */\n roleWeights?: RoleWeights;\n}\n\n/** Host-supplied historical message, turned into a message SearchDoc. */\nexport interface MessageInput {\n ref: string;\n role: MessageRole;\n text: string;\n tokens?: number;\n /** Block id that compressed this message (undefined if still visible). */\n blockId?: string;\n tier?: number;\n}\n\nexport const DEFAULT_ALGORITHM = \"hybrid\";\n","/**\n * searchBlocks — public search entry point.\n *\n * Scores a unified document set (block summaries + historical messages)\n * and returns ranked results. The model uses search to cheaply locate\n * detail that compression folded into summaries, then decompresses the\n * owning block for the full content.\n *\n * Two entry points:\n * - searchBlocks() — sync. Works for all lexical algorithms.\n * - searchBlocksAsync() — async. Also supports embedding-based semantic\n * algorithms whose score() returns a Promise.\n */\n\nimport type { CompressionState, CompressionBlock } from \"../types.js\";\nimport { getSearchAlgorithm } from \"./registry.js\";\nimport type { SearchDoc, ScoredBlock, MessageInput } from \"./types.js\";\nimport type { SearchResult, SearchOptions, RoleWeights } from \"./types.js\";\nimport { DEFAULT_ALGORITHM, DEFAULT_ROLE_WEIGHTS } from \"./types.js\";\n\n/** Build SearchDoc[] from all blocks (active AND inactive) of the state. */\nexport function blockDocs(state: CompressionState): SearchDoc[] {\n return state.blocks.map((b: CompressionBlock): SearchDoc => ({\n kind: \"block\",\n ref: b.blockId,\n text: `${b.topic ?? \"\"} ${b.summary ?? \"\"}`,\n title: b.topic ?? b.blockId,\n blockId: b.blockId,\n tier: b.tier ?? 1,\n tokens: b.compressedTokens,\n }));\n}\n\n/**\n * Build SearchDoc[] from historical messages supplied by the host. The host\n * (pai-acp) reads these from the append-only session log — they include the\n * original text of messages that compression later folded into block summaries.\n *\n * `ownerOf(ref)` maps a message ref to the block id that compressed it, so a\n * message hit tells the model exactly which block to decompress for detail.\n */\nexport function messageDocs(msgs: MessageInput[]): SearchDoc[] {\n return msgs.map((m): SearchDoc => ({\n kind: \"message\",\n ref: m.ref,\n text: m.text,\n title: `${m.role}: ${m.text.slice(0, 60)}`,\n role: m.role,\n blockId: m.blockId,\n tier: m.tier,\n tokens: m.tokens,\n }));\n}\n\nfunction applyRoleWeight(scored: ScoredBlock[], docs: SearchDoc[], rw: Required<RoleWeights>): ScoredBlock[] {\n if (docs.length === 0) return scored;\n const docByRef = new Map(docs.map((d) => [d.ref, d]));\n return scored.map((s) => {\n const doc = docByRef.get(s.ref);\n if (!doc) return s;\n const w =\n doc.kind === \"message\"\n ? doc.role === \"user\"\n ? rw.user\n : doc.role === \"assistant\"\n ? rw.assistant\n : rw.tool\n : rw.block;\n return { ref: s.ref, score: s.score * w };\n });\n}\n\nfunction runSearch(\n docs: SearchDoc[],\n query: string,\n options: SearchOptions,\n): SearchResult[] | Promise<SearchResult[]> {\n const limit = options.limit ?? 10;\n const previewLength = options.previewLength ?? 200;\n const minScore = options.minScore ?? 0.01;\n const algoName = options.algorithm ?? DEFAULT_ALGORITHM;\n const rw = { ...DEFAULT_ROLE_WEIGHTS, ...options.roleWeights };\n\n const algo = getSearchAlgorithm(algoName);\n if (!algo) return [];\n if (docs.length === 0) return [];\n\n const scoredOrPromise = algo.score(docs, query);\n\n const buildResults = (weighted: ScoredBlock[]): SearchResult[] => {\n const byRef = new Map(docs.map((d) => [d.ref, d]));\n return weighted\n .map((s): SearchResult | null => {\n const doc = byRef.get(s.ref);\n if (!doc) return null;\n return {\n kind: doc.kind,\n ref: doc.ref,\n blockId: doc.blockId,\n tier: doc.tier ?? 1,\n score: s.score,\n title: doc.title,\n preview: makePreview(doc.text, query, previewLength),\n role: doc.role,\n tokens: doc.tokens,\n };\n })\n .filter((r): r is SearchResult => r !== null && r.score >= minScore)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit);\n };\n\n if (scoredOrPromise instanceof Promise) {\n return scoredOrPromise.then((raw) => buildResults(applyRoleWeight(raw, docs, rw)));\n }\n return buildResults(applyRoleWeight(scoredOrPromise, docs, rw));\n}\n\n/** Sync entry — throws for async algorithms. Pass docs from blockDocs() + messageDocs(). */\nexport function searchBlocks(docs: SearchDoc[], query: string, options: SearchOptions = {}): SearchResult[] {\n const result = runSearch(docs, query, options);\n if (result instanceof Promise) {\n throw new Error(\n `searchBlocks: algorithm \"${options.algorithm ?? DEFAULT_ALGORITHM}\" is async (e.g. semantic). Use searchBlocksAsync() instead.`,\n );\n }\n return result;\n}\n\nexport { clearDocFeatures, docCacheInfo, docFeatures, setDocCacheCap } from \"./doc-cache.js\";\nexport type { DocFeatures } from \"./doc-cache.js\";\n\nexport async function searchBlocksAsync(docs: SearchDoc[], query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n return await runSearch(docs, query, options);\n}\n\n/**\n * Preview centered on the first query-term hit (case-insensitive).\n * Falls back to the head when no term hits.\n */\nfunction makePreview(text: string, query: string, len: number): string {\n if (!text) return \"\";\n const terms = query.toLowerCase().trim().split(/\\s+/).filter((t) => t.length > 1);\n if (terms.length === 0) return text.slice(0, len);\n\n const lower = text.toLowerCase();\n let hitIdx = -1;\n for (const term of terms) {\n const idx = lower.indexOf(term);\n if (idx >= 0) {\n hitIdx = idx;\n break;\n }\n }\n\n if (hitIdx < 0) return text.slice(0, len);\n\n const half = Math.max(0, Math.floor(len / 2) - 10);\n const start = Math.max(0, hitIdx - half);\n const end = Math.min(text.length, start + len);\n const prefix = start > 0 ? \"…\" : \"\";\n const suffix = end < text.length ? \"…\" : \"\";\n return prefix + text.slice(start, end).trim() + suffix;\n}\n","/**\n * M5 — durable region transaction and the log-rebuilt block ledger.\n *\n * Modeled on `dsh-compaction-basic/src/region.ts` (which is package-internal\n * and not exported by the seam): validate the surface range and tool-call/result\n * pairing, take the durable `compaction/start` lock, record `compaction/summary`\n * as the shadow price, land the `user/message` surface replacement carrying the\n * summary under `compactCheckpointSource`, and release the lock with\n * `compaction/end`. The original events stay in the append-only log, so\n * decompress/search/status can rebuild everything from the log.\n * @module billion-context-dsh/region\n */\n\nimport { randomUUID } from 'node:crypto'\nimport type { Session, SessionEvent, SessionEventMap } from '@deepseek-ai/dsh-session'\nimport { CompactionId, compactCheckpointSource } from '@deepseek-ai/dsh-compaction'\n// UPSTREAM (issue #124): dsh-compaction@0.1.2-rc.1's toolPairingBalanced*\n// helpers read the removed `session.events` API and crash on every 0.1.2\n// host. Use the local mirror (src/tool-pairing.ts) until the host fix ships,\n// then delete src/tool-pairing.ts and restore the host helpers here.\nimport { toolPairingBalancedAfter, toolPairingBalancedBefore } from './tool-pairing.ts'\nimport { createAssistantMessage, createUserMessage, type ContentBlock } from '@deepseek-ai/dsh-llm'\nimport { defaultCountTokens } from 'acp-kernel'\nimport { extractEventText, extractText, toolCallIdOfResultEvent } from './messages.ts'\nimport { hostPriceEvent } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\n\n/**\n * A surface sequence number as the INSTALLED `dsh-session` sees it. On the\n * alpha line dsh-session brands these as `SessionSeq` (a branded `number`,\n * see dsh-session types.d.ts); on the rc.6 baseline they are plain `number`.\n * Deriving the element type from `Session['surface']` keeps this module\n * type-correct against BOTH without naming the alpha-only brand — which does\n * not exist on rc.6, so naming it would break the rc.6 baseline typecheck.\n * `as SurfaceSeq` below is the single admission point: a plain `number` that a\n * caller (model ref, ledger field) produces is admitted as a surface seq only\n * at the exact write/index site that the installed dsh-session brands.\n */\ntype SurfaceSeq = Session['surface']['nodes'][number]\n\n/** One durable ACP block as rebuilt from the session log. */\nexport interface AcpBlockLedgerEntry {\n /** The compaction transaction id (stable block identity). */\n readonly blockId: string\n readonly summary: string\n /** The block's short label (kernel `CompressionBlock.topic`), when the compress request carried one. */\n readonly topic?: string\n readonly shadowedSeqs: readonly number[]\n readonly shadowedTokenCount: number\n readonly start: number\n readonly end: number\n /** Compression tier: 1 (message range), 2 (distills tier-1 blocks), 3 (distills tier-2 blocks). Legacy blocks default to 1. */\n readonly tier: 1 | 2 | 3\n /** Compaction ids of the blocks this block distilled (parents). Empty for tier-1 blocks. */\n readonly parentBlockIds: readonly string[]\n /** The acp-kernel block id (`bN`) created for this transaction — absent for legacy blocks (synthesised by order). */\n readonly kernelBlockId?: string\n /** The surface seq of this block's checkpoint summary node (derived from the log; null when the node is gone). */\n readonly summarySeq?: number\n /** The kernel block's raw direct/effective message ids at creation (recorded since the tier feature; absent for legacy). */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n /** Unix epoch ms of the compaction/summary event. */\n readonly createdAt: number\n}\n\n/** The open turn number, or null when the log ends between turns. */\nexport function findOpenTurn(events: readonly SessionEvent[]): number | null {\n let open: number | null = null\n for (const event of events) {\n if (event.type === 'turn/start') open = event.data.turn\n else if (event.type === 'turn/end' && event.data.turn === open) open = null\n }\n return open\n}\n\n/**\n * Reject a second concurrent compaction for the same session.\n *\n * Compaction is synchronous and a session is single-writer, so a\n * `compaction/start` with NO matching `compaction/end` in the durable log can\n * only be a stale leftover from a prior run that died mid-write (a hard kill,\n * not a caught throw — every caught throw is paired with a compensating\n * `compaction/end` in runCompactionTransaction). Such a leftover must NOT\n * permanently block every later compress call: this treats it as stale,\n * surfaces it once, and lets a new compaction proceed. The old \"already\n * active\" throw only fired when a genuine concurrent compaction existed,\n * which the synchronous single-writer premise makes impossible.\n */\nexport function assertNoActiveCompaction(events: readonly SessionEvent[]): void {\n let active = false\n for (const event of events) {\n if (event.type === 'compaction/start') active = true\n else if (event.type === 'compaction/end') active = false\n }\n if (active) {\n console.warn('billion-context-dsh: clearing stale compaction flag — found a compaction/start with no matching compaction/end')\n }\n}\n\n/**\n * Whether the surface node at `seq` projects to CoreMessage(s) whose ref key\n * is the bare seq — user messages, tool results, and text-only or SINGLE\n * tool-call assistant messages all do. Multi-tool-call assistant messages\n * project to `${seq}#${callId}` ids (projectEvent) and therefore carry NO\n * bare-`${seq}` ref, so compress's byRaw lookup can never resolve them as\n * range edges. resolveSurfaceRange treats such edges as unbalanced and shifts\n * them to the nearest clean cut.\n */\nfunction hasPlainRef(session: Session, seq: number): boolean {\n const event = eventAtOf(session, seq)\n if (event === undefined) return false\n switch (event.type) {\n case 'user/message':\n case 'tool/result':\n return extractEventText(event).trim().length > 0\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = Array.isArray(content)\n ? content.filter(\n (block) => block !== null && typeof block === 'object' && (block as { type?: string }).type === 'tool-call',\n )\n : []\n if (calls.length > 1) return false\n // One tool-call: projectEvent emits a bare-seq CoreMessage unconditionally.\n // Zero: only when the text is non-empty.\n return calls.length === 1 || extractEventText(event).trim().length > 0\n }\n default:\n return false\n }\n}\n\n/**\n * A requested range whose EVERY live message was already shadowed by one or\n * more blocks. The compress tool catches this and reports the range as already\n * compressed (with the covering block ids) instead of folding block summary\n * nodes as plain messages or erroring out. Distillation stays an explicit act:\n * target a LIVE checkpoint seq directly to distill (tier 2/3).\n */\nexport class AlreadyCompressedRangeError extends Error {\n constructor(\n readonly start: number,\n readonly end: number,\n readonly coveringBlockIds: readonly string[],\n ) {\n super(\n `billion-context-dsh: seq ${start}..${end} already compressed — `\n + 'no live content remains in that span',\n )\n this.name = 'AlreadyCompressedRangeError'\n }\n}\n\ntype StaleRangeRecovery =\n | { kind: 'ok'; start: number; end: number }\n | { kind: 'already-compressed'; coveringBlockIds: string[] }\n | { kind: 'unresolvable'; failedEdge: number }\n\n/**\n * Rebuild a requested range whose edges are no longer on the current surface.\n * The dominant cause is staleness: the seqs came from an older nudge table or\n * a previous compress result, and an earlier compression SHADOWED them (they\n * stay in the append-only log, but are gone from the surface). The recovery:\n *\n * 1. An edge that does not exist in the log at all (invented, or from another\n * session) is unresolvable — there is no way to guess what it meant.\n * 2. The still-LIVE surface nodes inside the requested span, in VALUE order\n * (the surface can be locally non-monotonic after replacements, so value\n * order is the only coherent span). If there are none, the whole span was\n * already compressed → 'already-compressed' with the covering block ids.\n * 3. Otherwise the range snaps to the first..last live PLAIN node in the\n * span. Block checkpoint nodes are deliberately excluded: distilling a\n * block on a STALE reference would silently change block structure the\n * model never intended to touch — distillation requires targeting a live\n * checkpoint seq directly.\n */\nfunction recoverStaleRange(session: Session, start: number, end: number): StaleRangeRecovery {\n if (eventAtOf(session, start) === undefined || eventAtOf(session, end) === undefined) {\n const failedEdge = eventAtOf(session, start) === undefined ? start : end\n return { kind: 'unresolvable', failedEdge }\n }\n const liveInside = session.surface.nodes\n .filter((seq) => seq >= start && seq <= end)\n .sort((a, b) => a - b)\n const plain = liveInside.filter((seq) => !isCheckpointNode(eventAtOf(session, seq)!))\n if (plain.length === 0) {\n const coveringBlockIds = rebuildBlockLedger(sessionEventsOf(session))\n .filter((entry) => entry.shadowedSeqs.some((seq) => seq >= start && seq <= end))\n .map((entry) => entry.blockId)\n return { kind: 'already-compressed', coveringBlockIds }\n }\n return { kind: 'ok', start: plain[0]!, end: plain[plain.length - 1]! }\n}\n\nexport interface ResolvedSurfaceRange {\n readonly start: number\n readonly end: number\n /**\n * True when the requested edges were not on the current surface and were\n * remapped to the still-live content of the requested span (an earlier\n * compression shadowed them). Callers surface this so the model sees what\n * was actually compressed instead of silently shadowing a different span.\n */\n readonly recovered?: boolean\n}\n\n/**\n * Validate one inclusive surface span and adjust its edges to a\n * tool-pairing-balanced range whose boundaries carry a bare-seq ref. Reversed\n * ranges throw. An edge that sits inside a tool-call/result pair — or on a\n * multi-tool-call assistant message that has no bare-seq ref — is first nudged\n * inward to the nearest clean cut; if that collapses the range (e.g. the model\n * asked for a SINGLE tool result, which can never be balanced alone), the\n * range EXPANDS outward to the enclosing clean pair instead — a lone tool\n * message is almost always a \"consumed output\" the model genuinely wants to\n * compress. The returned range is what a caller should actually shadow.\n *\n * Missing edges are NOT an immediate error: the seqs were probably shadowed by\n * an earlier compression (stale nudge table / old compress result). The span\n * is rebuilt from its still-live remainder via recoverStaleRange — a fully\n * shadowed span throws AlreadyCompressedRangeError, a genuinely unknown edge\n * throws the not-in-surface guidance error. The returned range is what a\n * caller should actually shadow.\n */\nexport function resolveSurfaceRange(\n session: Session,\n start: number,\n end: number,\n): ResolvedSurfaceRange {\n const nodes = session.surface.nodes\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n let requestedStartIdx = nodes.indexOf(start as SurfaceSeq)\n let requestedEndIdx = nodes.indexOf(end as SurfaceSeq)\n let recovered = false\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n const stale = recoverStaleRange(session, start, end)\n if (stale.kind === 'unresolvable') {\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + `edge seq ${stale.failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n if (stale.kind === 'already-compressed') {\n throw new AlreadyCompressedRangeError(start, end, stale.coveringBlockIds)\n }\n start = stale.start\n end = stale.end\n recovered = true\n requestedStartIdx = nodes.indexOf(start as SurfaceSeq)\n requestedEndIdx = nodes.indexOf(end as SurfaceSeq)\n if (requestedStartIdx < 0 || requestedEndIdx < 0) {\n // Unreachable in practice (recovery returns live nodes), but never let\n // a negative index reach the balancing passes.\n throw new Error(\n `billion-context-dsh: seq ${start}..${end} not in the current surface — `\n + 'consult acp_status for the current surface range',\n )\n }\n }\n if (requestedStartIdx > requestedEndIdx) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // Belt-and-braces: the surface can be locally out of order after surface\n // replacements, so index order alone does not guarantee value order.\n if (start > end) {\n throw new Error(`billion-context-dsh: reversed range ${start}..${end}`)\n }\n // A boundary must be BOTH tool-pairing-balanced AND carry a bare-seq ref.\n const cleanBefore = (index: number): boolean =>\n toolPairingBalancedBefore(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n const cleanAfter = (index: number): boolean =>\n toolPairingBalancedAfter(session, nodes[index]!) && hasPlainRef(session, nodes[index]!)\n let startIdx = requestedStartIdx\n let endIdx = requestedEndIdx\n // First pass: nudge inward to the nearest clean cuts.\n while (startIdx <= endIdx && !cleanBefore(startIdx)) {\n startIdx += 1\n }\n while (endIdx >= startIdx && !cleanAfter(endIdx)) {\n endIdx -= 1\n }\n if (startIdx <= endIdx && nodes[startIdx]! <= nodes[endIdx]!) {\n return recovered\n ? { start: nodes[startIdx]!, end: nodes[endIdx]!, recovered: true }\n : { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n // A recovered span NEVER expands across block checkpoints: the model's\n // requested edges were stale, so growing the span into block territory could\n // fold content it never intended to touch. If the live remainder cannot be\n // balanced by shrinking alone, give up with guidance instead.\n if (recovered) {\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced live remainder around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n }\n // Second pass: the inward pass collapsed (a lone tool message) — expand\n // outward from the REQUESTED span to the smallest clean enclosing pair.\n startIdx = requestedStartIdx\n endIdx = requestedEndIdx\n while (startIdx > 0 && !cleanBefore(startIdx)) {\n startIdx -= 1\n }\n while (endIdx < nodes.length - 1 && !cleanAfter(endIdx)) {\n endIdx += 1\n }\n // Value order guard: the surface is locally non-monotonic after replacements\n // (a checkpoint seq inserted ahead of older residual nodes), so index order\n // alone is not enough — never return a span whose end seq is numerically\n // BEFORE its start seq. The caller (nudge / compress) skips such a span.\n if (cleanBefore(startIdx) && cleanAfter(endIdx) && nodes[startIdx]! <= nodes[endIdx]!) {\n return { start: nodes[startIdx]!, end: nodes[endIdx]! }\n }\n throw new Error(\n `billion-context-dsh: no tool-pairing-balanced range around seq ${start}..${end} — `\n + 'narrow the range or consult acp_status for the current surface',\n )\n}\n\n/** The surface seqs shadowed by the inclusive positional span. */\nexport function shadowedSeqsOf(session: Session, start: number, end: number): number[] {\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(start as SurfaceSeq)\n const endIdx = nodes.indexOf(end as SurfaceSeq)\n return nodes.slice(startIdx, endIdx + 1)\n}\n\nexport interface CompactionTransactionInput {\n readonly start: number\n readonly end: number\n readonly shadowedSeqs: readonly number[]\n readonly summary: ContentBlock[]\n readonly shadowedTokenCount: number\n readonly provider: string\n readonly model: string\n /** Short block label (kernel `CompressionBlock.topic`) — persisted so a restarted engine rehydrates it. */\n readonly topic?: string\n /** Compression tier of this block (default 1). */\n readonly tier?: 1 | 2 | 3\n /** The acp-kernel block id (`bN`) created by the kernel for this transaction. */\n readonly kernelBlockId?: string\n /** Compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /** The kernel block's direct/effective message ids (raw CoreMessage ids) — recorded for faithful rehydration. */\n readonly directMessageIds?: readonly string[]\n readonly effectiveMessageIds?: readonly string[]\n}\n\n/**\n * ACP tier extension fields carried on `compaction/summary` events. The\n * upstream dsh-compaction event type does not know them, so reads and writes\n * go through this precise intersection (never `any`).\n */\nexport interface AcpCompactionSummaryFields {\n /** Compression tier (1/2/3) — 1 = message range, 2 = distills tier-1, 3 = distills tier-2. */\n readonly tier?: 1 | 2 | 3\n /** Short block label (kernel `CompressionBlock.topic`) — the acp_status block title. */\n readonly topic?: string\n /** The acp-kernel block id (`bN`) created for this transaction. */\n readonly kernelBlockId?: string\n /** Durable compaction ids of the blocks distilled into this one. */\n readonly parentBlockIds?: readonly string[]\n /**\n * The kernel block's direct message ids (raw CoreMessage ids) at creation —\n * recorded so a restarted engine rehydrates the SAME coverage (a tier-2\n * block's coverage is its parents' originals, not the checkpoint node).\n */\n readonly directMessageIds?: readonly string[]\n /** The kernel block's effective message ids (raw CoreMessage ids) at creation. */\n readonly effectiveMessageIds?: readonly string[]\n}\n\ntype CompactionSummaryData = SessionEventMap['compaction/summary']\n\n/** Read a `compaction/summary` event's data including the ACP tier extension fields. */\nexport function readCompactionSummary(event: SessionEvent): CompactionSummaryData & AcpCompactionSummaryFields {\n return event.data as CompactionSummaryData & AcpCompactionSummaryFields\n}\n\n/**\n * Run one durable compression transaction. Throws on invalid state; on success\n * the four events are in the log and the surface has one summary node.\n */\nexport function runCompactionTransaction(\n session: Session,\n input: CompactionTransactionInput,\n): { compactionId: string; seqs: number[] } {\n assertNoActiveCompaction(sessionEventsOf(session))\n const turn = findOpenTurn(sessionEventsOf(session))\n const compactionId = CompactionId(randomUUID())\n const seqs: number[] = []\n\n // Fail fast on an unresolvable range BEFORE writing any durable event. If we\n // let the host's surfaceOp replace throw below, we would first have recorded\n // compaction/start and compaction/summary and then leave a dangling start\n // (poisoning every later compress call) plus an orphan summary in the ledger.\n // Validating the edges up front keeps a bad range a clean, zero-write no-op.\n if (input.start > input.end) {\n throw new Error(`billion-context-dsh: reversed range ${input.start}..${input.end}`)\n }\n if (eventAtOf(session, input.start) === undefined || eventAtOf(session, input.end) === undefined) {\n const failedEdge = eventAtOf(session, input.start) === undefined ? input.start : input.end\n throw new Error(\n `billion-context-dsh: seq ${input.start}..${input.end} not in the current surface — `\n + `edge seq ${failedEdge} is not in this session's log. `\n + 'Surface seqs are sparse message nodes (only user/message, assistant/message, '\n + 'tool/result events); consult acp_status for the current surface range',\n )\n }\n\n try {\n seqs.push(session.append('compaction/start', { compactionId, turn }).seq)\n seqs.push(session.append('compaction/summary', {\n compactionId,\n summary: input.summary,\n shadowedRange: { start: input.start, end: input.end },\n shadowedSeqs: [...input.shadowedSeqs],\n shadowedTokenCount: input.shadowedTokenCount,\n provider: input.provider,\n model: input.model,\n tier: input.tier ?? 1,\n ...(input.kernelBlockId === undefined ? {} : { kernelBlockId: input.kernelBlockId }),\n ...(input.topic === undefined ? {} : { topic: input.topic }),\n ...(input.parentBlockIds === undefined || input.parentBlockIds.length === 0\n ? {}\n : { parentBlockIds: [...input.parentBlockIds] }),\n ...(input.directMessageIds === undefined ? {} : { directMessageIds: [...input.directMessageIds] }),\n ...(input.effectiveMessageIds === undefined ? {} : { effectiveMessageIds: [...input.effectiveMessageIds] }),\n } as CompactionSummaryData & AcpCompactionSummaryFields).seq)\n\n const message = createUserMessage({\n content: input.summary,\n source: compactCheckpointSource(compactionId),\n })\n seqs.push(session.append('user/message', message, {\n surfaceOp: { op: 'replace', start: input.start as SurfaceSeq, end: input.end as SurfaceSeq },\n sourceEventSeqs: [...input.shadowedSeqs] as SurfaceSeq[],\n }).seq)\n\n seqs.push(session.append('compaction/end', { compactionId, turn }).seq)\n } catch (error) {\n // Backstop: if any append AFTER compaction/start throws (the host rejects\n // the surfaceOp replace for a reason we did not pre-validate, the summary\n // serialization fails, …), write a compensating compaction/end so the\n // durable log never holds a dangling start that would block every later\n // compress call. A leftover compaction/summary with no applied replace is\n // surfaced as an orphan ledger block, which is preferable to a hard\n // permanent block.\n try {\n session.append('compaction/end', { compactionId, turn })\n } catch (compensateError) {\n // The durable log may now hold a dangling compaction/start; the next\n // assertNoActiveCompaction call heals it. Never mask the original error.\n console.warn('billion-context-dsh: failed to write a compensating compaction/end', compensateError)\n }\n throw error\n }\n return { compactionId, seqs }\n}\n\n/** The seq of a compaction's checkpoint summary node in the log (visible or shadowed). */\nfunction summarySeqOfCompaction(events: readonly SessionEvent[], compactionId: string): number | null {\n for (const event of events) {\n if (event.type !== 'user/message') continue\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin === 'compact' && source.compactionId === compactionId) return event.seq\n }\n return null\n}\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const ledger: AcpBlockLedgerEntry[] = []\n for (const event of events) {\n if (event.type !== 'compaction/summary') continue\n const data = readCompactionSummary(event)\n // Blocks written before the token-accounting fix carry shadowedTokenCount\n // 0; backfill from the shadowed originals still in the log so acp_status\n // reports real reclaimed tokens.\n let shadowedTokenCount = data.shadowedTokenCount\n if (shadowedTokenCount === 0) {\n shadowedTokenCount = 0\n for (const seq of data.shadowedSeqs) {\n const original = events[seq]\n if (original !== undefined) shadowedTokenCount += defaultCountTokens(extractEventText(original))\n }\n }\n const tier = data.tier === 2 || data.tier === 3 ? data.tier : 1\n const parentBlockIds: string[] = Array.isArray(data.parentBlockIds) ? [...data.parentBlockIds] : []\n const directMessageIds: string[] | undefined = Array.isArray(data.directMessageIds) ? [...data.directMessageIds] : undefined\n const effectiveMessageIds: string[] | undefined = Array.isArray(data.effectiveMessageIds) ? [...data.effectiveMessageIds] : undefined\n const summarySeq = summarySeqOfCompaction(events, data.compactionId)\n ledger.push({\n blockId: data.compactionId,\n summary: extractText(data.summary),\n ...(typeof data.topic === 'string' ? { topic: data.topic } : {}),\n shadowedSeqs: [...data.shadowedSeqs],\n shadowedTokenCount,\n start: data.shadowedRange.start,\n end: data.shadowedRange.end,\n tier,\n parentBlockIds,\n ...(typeof data.kernelBlockId === 'string' ? { kernelBlockId: data.kernelBlockId } : {}),\n ...(summarySeq === null ? {} : { summarySeq }),\n ...(directMessageIds === undefined ? {} : { directMessageIds }),\n ...(effectiveMessageIds === undefined ? {} : { effectiveMessageIds }),\n createdAt: event.time,\n })\n }\n return ledger\n}\n\n/** One self-computed compressible span of the current surface. */\nexport interface SeqCompressibleRange {\n readonly start: number\n readonly end: number\n readonly count: number\n readonly tokens: number\n /** Share of messages that are tool messages (tool-call or tool-result), 0-100 — kernel `toolPct` parity. */\n readonly toolPct: number\n}\n\n/** Whether a surface message event is a tool message (tool-call or tool-result) — kernel `isToolMessage` parity. */\nfunction isToolEvent(event: SessionEvent): boolean {\n if (event.type === 'tool/result') return true\n if (event.type !== 'assistant/message') return false\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n return Array.isArray(content) && content.some((block) => (block as { type?: unknown })?.type === 'tool-call')\n}\n\n/** Whether a surface user message is a compaction checkpoint node (already compressed). */\nfunction isCheckpointNode(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\n/** Tool-call ids carried by one assistant surface message. */\nfunction toolCallIdsOfEvent(event: SessionEvent): string[] {\n if (event.type !== 'assistant/message') return []\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n if (!Array.isArray(content)) return []\n const ids: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; id?: unknown }\n if (b.type === 'tool-call' && typeof b.id === 'string') ids.push(b.id)\n }\n return ids\n}\n\n/**\n * Provider/model to stamp on a synthetic empty assistant pruning node.\n */\nfunction assistantProviderModel(event: SessionEvent): { provider: string; model: string } {\n if (event.type === 'assistant/message') {\n const message = (event.data as { message?: { source?: { provider?: unknown; model?: unknown } } }).message\n return {\n provider: typeof message?.source?.provider === 'string' ? message.source.provider : 'billion-context-dsh',\n model: typeof message?.source?.model === 'string' ? message.source.model : 'surface-prune',\n }\n }\n return { provider: 'billion-context-dsh', model: 'surface-prune' }\n}\n\n/**\n * Durable model-free prune: append `compaction/prune` as the shadow price, then\n * replace the given surface seqs with either a user message carrying `text`\n * (used for compress call/result hiding, so the model still sees the tool\n * outcome) or an EMPTY assistant message (used for orphan cleanup, which DSH\n * derives to nothing). The originals remain in the append-only log.\n */\nfunction hideSurfaceSeqs(\n session: Session,\n seqs: readonly number[],\n provider: string,\n model: string,\n text?: string,\n priceEvent: (event: SessionEvent) => number = hostPriceEvent,\n): void {\n if (seqs.length === 0) return\n const start = seqs[0]!\n const end = seqs[seqs.length - 1]!\n let shadowedTokenCount = 0\n for (const seq of seqs) {\n const event = eventAtOf(session, seq)\n // The prune claim MUST speak the host's token vocabulary (rule 12): the\n // default `hostPriceEvent` is the exact mirror of the host estimator.\n // NEVER defaultCountTokens — that overdraws the meter on CJK (#54).\n if (event !== undefined) shadowedTokenCount += priceEvent(event)\n }\n session.append('compaction/prune', {\n shadowedRange: { start: start as SurfaceSeq, end: end as SurfaceSeq },\n shadowedSeqs: [...seqs] as SurfaceSeq[],\n shadowedTokenCount,\n })\n if (text !== undefined) {\n session.append('user/message', createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'billion-context-dsh' },\n }), {\n surfaceOp: { op: 'replace', start: start as SurfaceSeq, end: end as SurfaceSeq },\n sourceEventSeqs: [...seqs] as SurfaceSeq[],\n })\n return\n }\n session.append('assistant/message', {\n turn: findOpenTurn(sessionEventsOf(session)) ?? 0,\n step: 0,\n message: createAssistantMessage({ content: [], source: { provider, model } }),\n }, {\n surfaceOp: { op: 'replace', start: start as SurfaceSeq, end: end as SurfaceSeq },\n sourceEventSeqs: [...seqs] as SurfaceSeq[],\n })\n}\n\n/**\n * Hide one successful `compress` tool's call/result pair after its tool/result\n * has been logged. The durable compaction summary is inserted BEFORE the\n * current tool result (the compress tool runs mid-turn), so leaving the pair on\n * the surface would produce `assistant(tool_calls) → user(summary) →\n * tool(result)` — rejected by strict providers. Replacing both nodes with a\n * plain user message (the result text) removes the pair from the derived\n * surface without touching the compaction block.\n */\nexport function hideCompressToolPair(session: Session, callId: string, resultSeq?: number): boolean {\n let callSeq: number | null = null\n const events = sessionEventsOf(session)\n for (const event of events) {\n if (event.type !== 'assistant/message') continue\n if (toolCallIdsOfEvent(event).includes(callId)) {\n callSeq = event.seq\n break\n }\n }\n if (callSeq === null) return false\n // Only hide a node that carries EXACTLY the compress call. Hiding a\n // multi-call node replaces the whole assistant message, which would orphan\n // the sibling calls' results (their call ids vanish with the node).\n const callNodeIds = toolCallIdsOfEvent(events[callSeq]!)\n if (callNodeIds.length !== 1 || callNodeIds[0] !== callId) return false\n let resolvedResultSeq = resultSeq ?? null\n if (resolvedResultSeq === null) {\n for (const event of events) {\n if (event.type === 'tool/result' && toolCallIdOfResultEvent(event) === callId) {\n resolvedResultSeq = event.seq\n break\n }\n }\n }\n if (resolvedResultSeq === null) return false\n const nodes = session.surface.nodes\n const startIdx = nodes.indexOf(callSeq as SurfaceSeq)\n const endIdx = nodes.indexOf(resolvedResultSeq as SurfaceSeq)\n // Only hide an actually adjacent pair; never shadow unrelated messages that\n // happen to sit between a stale call and result.\n if (startIdx < 0 || endIdx < 0 || endIdx - startIdx !== 1) return false\n const { provider, model } = assistantProviderModel(events[callSeq]!)\n const resultEvent = events[resolvedResultSeq]\n const resultText = resultEvent === undefined ? '' : extractEventText(resultEvent)\n hideSurfaceSeqs(session, [callSeq, resolvedResultSeq], provider, model, resultText.trim().length > 0 ? resultText : undefined)\n return true\n}\n\n/**\n * Surface-level orphan cleanup: hide tool/result nodes with no matching call,\n * assistant tool-call nodes whose calls all lack results, and \"broken pairs\"\n * whose result is NOT adjacent to the call node on the surface (a\n * non-tool/result node — typically the compaction summary a buggy older\n * version inserted between a compress call and its result — sits between\n * them). A single orphan result corrupts the whole tool-pairing balance cache\n * (every range resolve throws), orphan calls fragment large ranges into tiny\n * uncompressed fragments, and a broken pair cannot serialize for strict\n * providers — the mechanisms behind issue #18's \"only ~28 tokens visible\".\n * Uses the same durable prune protocol as `hideSurfaceSeqs`, so the removed\n * nodes stay recoverable from the append-only log.\n */\nexport function stripOrphanedSurfaceToolMessages(\n session: Session,\n inFlightCallIds: ReadonlySet<string> = new Set(),\n): number {\n const nodes = session.surface.nodes\n const callIdsBySeq = new Map<number, string[]>()\n // callId -> surface position of the assistant node carrying it, for calls\n // whose result has not been decided yet.\n const open = new Map<string, { seq: number; index: number }>()\n const orphanResultSeqs: number[] = []\n // result seq -> call node seq, for pairs whose result landed but is not\n // adjacent to the call node on the surface.\n const brokenResults = new Map<number, number>()\n for (let index = 0; index < nodes.length; index += 1) {\n const seq = nodes[index]!\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n if (event.type === 'assistant/message') {\n const ids = toolCallIdsOfEvent(event)\n if (ids.length === 0) continue\n callIdsBySeq.set(seq, ids)\n for (const id of ids) {\n if (!open.has(id)) open.set(id, { seq, index })\n }\n } else if (event.type === 'tool/result') {\n const id = toolCallIdOfResultEvent(event)\n if (id === null) continue\n const call = open.get(id)\n if (call === undefined) {\n orphanResultSeqs.push(seq)\n continue\n }\n // A pair is healthy only when every node between the call and this\n // result is a tool/result of the SAME call node (multi-call messages).\n // Any other node in between makes the pair unserializable for strict\n // providers: prune both ends.\n const callNodeIds = callIdsBySeq.get(call.seq)\n let adjacent = false\n if (callNodeIds !== undefined) {\n adjacent = true\n for (let mid = call.index + 1; mid < index; mid += 1) {\n const midEvent = eventAtOf(session, nodes[mid]!)\n if (midEvent === undefined || midEvent.type !== 'tool/result') {\n adjacent = false\n break\n }\n const midId = toolCallIdOfResultEvent(midEvent)\n if (midId === null || !callNodeIds.includes(midId)) {\n adjacent = false\n break\n }\n }\n }\n open.delete(id)\n if (!adjacent) brokenResults.set(seq, call.seq)\n }\n }\n // call node seq -> ids of that node whose result is broken (non-adjacent).\n const brokenIdsByCallSeq = new Map<number, string[]>()\n for (const [resultSeq, callSeq] of brokenResults) {\n const id = toolCallIdOfResultEvent(eventAtOf(session, resultSeq)!)\n if (id !== null) {\n const list = brokenIdsByCallSeq.get(callSeq) ?? []\n list.push(id)\n brokenIdsByCallSeq.set(callSeq, list)\n }\n }\n const hiddenSet = new Set<number>(orphanResultSeqs)\n for (const resultSeq of brokenResults.keys()) hiddenSet.add(resultSeq)\n for (const [callSeq, ids] of callIdsBySeq) {\n const brokenIds = brokenIdsByCallSeq.get(callSeq)\n // Only hide an assistant node when NONE of its calls are usable: every id\n // must lack a result (open) or have a broken result. A mixed node (some\n // healthy results) must stay so its valid results are not orphaned by\n // hiding the call — and a node carrying an in-flight call can never be\n // pruned, or the pending result lands orphaned.\n const allUnpaired = !ids.some((candidate) => inFlightCallIds.has(candidate))\n && ids.every((candidate) => open.has(candidate) || brokenIds?.includes(candidate) === true)\n if (allUnpaired) hiddenSet.add(callSeq)\n }\n const hidden = [...hiddenSet].sort((a, b) => a - b)\n let count = 0\n for (const seq of hidden) {\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n const { provider, model } = assistantProviderModel(event)\n hideSurfaceSeqs(session, [seq], provider, model)\n count += 1\n }\n return count\n}\n\n/**\n * All tool-call ids currently visible on the surface with no matching\n * tool/result yet — the in-flight calls of the current step. Sibling tools\n * called in the same assistant message as `compress` are in-flight too, so\n * `handleCompress` must protect the whole set (not just its own call id) or\n * the sibling call would be pruned as an orphan and its result would land\n * orphaned (HTTP 400 until the next cleanup).\n */\nexport function openToolCallIds(session: Session): Set<string> {\n const open = new Set<string>()\n for (const seq of session.surface.nodes) {\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n if (event.type === 'assistant/message') {\n for (const id of toolCallIdsOfEvent(event)) open.add(id)\n } else if (event.type === 'tool/result') {\n const id = toolCallIdOfResultEvent(event)\n if (id !== null) open.delete(id)\n }\n }\n return open\n}\n\n/**\n * Schedule `hideCompressToolPair` on the microtask queue. `session.append`\n * is NOT reentrant: running it synchronously inside a `session/event`\n * listener (while the outer append is still publishing) throws \"session\n * append cannot reenter while another append is being published\" on live,\n * store-attached sessions, and the dispatcher silently swallows the error —\n * so a synchronous hide is a silent no-op in production. A microtask drains\n * after the current append fully publishes and before the agent loop resumes,\n * so the pair is hidden before the next request is built.\n */\nexport function deferCompressPairHide(\n session: Session,\n callId: string,\n resultSeq: number,\n onError?: (error: unknown) => void,\n): void {\n queueMicrotask(() => {\n try {\n hideCompressToolPair(session, callId, resultSeq)\n } catch (error) {\n onError?.(error)\n }\n })\n}\n\n/**\n * Compute compressible spans directly from the surface — independent of the\n * kernel's ref map, which can drift after surface replacements in long\n * sessions and hide large tool results from the nudge range table. Skips the\n * recent protected tail, the last user message, and compaction checkpoints;\n * edges are then balanced through resolveSurfaceRange. Ranges are ordered\n * oldest-first (stable across turns — matches the kernel's `oldest first`).\n * UPSTREAM: this self-computation is a labeled workaround for kernel\n * ref-map drift after surface replacements (AGENTS.md rule 11) — drop it and\n * use kernel compressibleRanges once the drift is fixed upstream.\n */\nexport function buildCompressibleSeqRanges(\n session: Session,\n opts: { preserveRecent?: number } = {},\n): SeqCompressibleRange[] {\n // Orphan tool messages corrupt the pairing balance cache and fragment every\n // large span. Prune them before scanning so the range table reflects the\n // actually compressible surface (issue #18).\n stripOrphanedSurfaceToolMessages(session)\n const nodes = session.surface.nodes\n const preserve = opts.preserveRecent ?? 5\n const protectedSeqs = new Set<number>()\n // `nodes.slice(-preserve)` would protect EVERYTHING when preserve is 0\n // (`slice(-0) === slice(0)`) — guard so 0 means \"no recent protection\".\n if (preserve > 0) {\n for (const seq of nodes.slice(-preserve)) protectedSeqs.add(seq)\n }\n for (let index = nodes.length - 1; index >= 0; index -= 1) {\n const event = eventAtOf(session, nodes[index]!)\n if (event?.type === 'user/message' && !isCheckpointNode(event)) {\n protectedSeqs.add(nodes[index]!)\n break\n }\n }\n const raw: Array<{ start: number; end: number; count: number; tokens: number; toolCount: number }> = []\n let cur: { start: number; end: number; count: number; tokens: number; toolCount: number } | null = null\n const flush = (): void => {\n if (cur !== null) raw.push(cur)\n cur = null\n }\n for (const seq of nodes) {\n const event = eventAtOf(session, seq)\n if (event === undefined || protectedSeqs.has(seq) || isCheckpointNode(event)) {\n flush()\n continue\n }\n // Surface nodes can be locally out of order after surface replacements in\n // long sessions; a node with a SMALLER seq than the running segment would\n // produce a reversed range (e.g. 110295..106762). Break the segment so\n // ranges always stay start <= end.\n if (cur !== null && seq < cur.start) {\n flush()\n cur = null\n }\n const tokens = defaultCountTokens(extractEventText(event))\n const isTool = isToolEvent(event)\n if (cur === null) {\n cur = { start: seq, end: seq, count: 1, tokens, toolCount: isTool ? 1 : 0 }\n } else {\n cur = { start: cur.start, end: seq, count: cur.count + 1, tokens: cur.tokens + tokens, toolCount: cur.toolCount + (isTool ? 1 : 0) }\n }\n }\n flush()\n const out: SeqCompressibleRange[] = []\n for (const range of raw) {\n try {\n const { start, end } = resolveSurfaceRange(session, range.start, range.end)\n const count = range.count\n out.push({\n start,\n end,\n count,\n tokens: range.tokens,\n toolPct: count > 0 ? Math.round((range.toolCount / count) * 100) : 0,\n })\n } catch {\n // Cannot be balanced into a compressible span — skip.\n }\n }\n // Oldest-first: the order is stable across turns (the oldest ranges do not\n // move as new messages land), so the model can consume ranges front-to-back\n // without re-ranking each nudge — matching the kernel's `oldest first` list\n // and the host's own front-to-back compression rhythm.\n return out.sort((a, b) => a.start - b.start)\n}\n\n/**\n * A compact human-readable description of the current surface for the model:\n * node count plus the first/last message seqs. Surface seqs are sparse (the\n * event log interleaves non-message events and expanded delta batches), so a\n * model that never saw the nudge range table — e.g. low-pressure sessions\n * where no nudge fires — cannot guess its own seq space. acp_status and the\n * nudge's range table both surface this so compress edges can be located\n * without blind probing.\n */\nexport function surfaceSummary(session: Session): string {\n const nodes = session.surface.nodes\n if (nodes.length === 0) return 'empty'\n // Surface nodes are NOT guaranteed to be ordered: a compaction replace lands\n // the checkpoint node first, so [15, 6, 7, …]. Report the span as min..max\n // rather than first..last, which would read \"seqs 15..12\" after a compress.\n let first = nodes[0]!\n let last = nodes[0]!\n for (const seq of nodes) {\n if (seq < first) first = seq\n if (seq > last) last = seq\n }\n return `${nodes.length} nodes, seqs ${first}..${last}`\n}\n\n/** One block as seen by the tier machinery: durable id ↔ kernel ref (`bN`). */\nexport interface AcpBlockRegistryEntry {\n /** The durable compaction id. */\n readonly blockId: string\n /** The acp-kernel block ref (`bN`); synthesised by log order for legacy blocks. */\n readonly kernelBlockId: string\n readonly tier: 1 | 2 | 3\n /** The surface seq of this block's checkpoint summary node (null when gone). */\n readonly summarySeq: number | null\n /** True until a LATER block distills this one. Only active blocks are distillable. */\n readonly active: boolean\n readonly parentBlockIds: readonly string[]\n}\n\n/**\n * Rebuild the compactionId ↔ kernel-block-ref registry from the durable log.\n * Legacy blocks (pre-tier, no recorded `kernelBlockId`) are synthesised as\n * `b1`, `b2`, … in log order; recorded ids are kept as-is. A block is active\n * until a later block lists it as a parent.\n */\nexport function blockRegistry(session: Session): AcpBlockRegistryEntry[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const kernelIdOf = new Map<string, string>()\n const raw: AcpBlockRegistryEntry[] = []\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n raw.push({\n blockId: entry.blockId,\n kernelBlockId,\n tier: entry.tier,\n summarySeq: entry.summarySeq ?? null,\n active: true,\n parentBlockIds: [...entry.parentBlockIds],\n })\n }\n const consumed = new Set<string>()\n for (const entry of raw) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n return raw.map((entry) => ({\n ...entry,\n active: !consumed.has(entry.blockId),\n }))\n}\n\n/**\n * The kernel block ref (`bN`) for a surface seq, when that seq is the\n * checkpoint summary node of a block — the edge the model must use to\n * distill (T2/T3). Active blocks distill; a stale (already-distilled) node\n * still maps to its `bN` so the kernel reports \"already compressed\" instead\n * of silently folding the summary as a plain message. Returns null for\n * anything else (plain messages, non-checkpoint nodes).\n */\nexport function blockRefForSummarySeq(session: Session, seq: number): string | null {\n const event = eventAtOf(session, seq)\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n const entry = blockRegistry(session).find((r) => r.blockId === source.compactionId)\n if (entry === undefined) return null\n return entry.kernelBlockId\n}\n\n/** The durable compaction ids distilled by the given kernel block refs (`bN`). */\nexport function compactionIdsOfKernelBlocks(session: Session, kernelBlockIds: readonly string[]): string[] {\n if (kernelBlockIds.length === 0) return []\n const byKernel = new Map(blockRegistry(session).map((r) => [r.kernelBlockId, r.blockId]))\n return kernelBlockIds\n .map((id) => byKernel.get(id))\n .filter((id): id is string => id !== undefined)\n}\n\n/**\n * Resolve a kernel block ref (`bN`) — as shown by the model tool `acp_status`\n * (kernel `buildStatusReport` renders `block.blockId`) — to the durable\n * compaction id the decompress/search tools accept. Returns null when `bN` is\n * not an exact registry key (unknown ref). Only matches the canonical `bN`\n * form (`/^b\\d+$/`); anything else is not a kernel ref and returns null so the\n * caller falls back to its compaction-id prefix match.\n */\nexport function blockIdOfKernelRef(session: Session, kernelRef: string): string | null {\n if (!/^b\\d+$/.test(kernelRef)) return null\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelRef)\n return entry?.blockId ?? null\n}\n\n/** The checkpoint summary seq of an ACTIVE kernel block (`bN`), or null. */\nexport function summarySeqOfKernelBlock(session: Session, kernelBlockId: string): number | null {\n const entry = blockRegistry(session).find((r) => r.kernelBlockId === kernelBlockId)\n return entry?.active ? entry.summarySeq : null\n}\n\n/** The durable block whose checkpoint node sits at `seq` (or null). */\nfunction checkpointBlockIdOf(events: readonly SessionEvent[], seq: number): string | null {\n const event = events[seq]\n if (event?.type !== 'user/message') return null\n const source = (event.data as { source?: { plugin?: string; compactionId?: string } }).source\n if (source?.plugin !== 'compact' || source.compactionId === undefined) return null\n return source.compactionId\n}\n\n/**\n * The shadowed seqs of a block, recursing into distilled parent blocks: a\n * tier-2 block shadows its parent's checkpoint node, so recovering its\n * originals requires expanding that node into the parent block's own shadowed\n * seqs. Cycle-safe (a block can never be its own ancestor).\n */\nexport function expandShadowedSeqs(session: Session, blockId: string): number[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const byId = new Map(ledger.map((entry) => [entry.blockId, entry]))\n const root = byId.get(blockId)\n if (root === undefined) return []\n const out: number[] = []\n const seen = new Set<string>()\n const visit = (entry: AcpBlockLedgerEntry): void => {\n if (seen.has(entry.blockId)) return\n seen.add(entry.blockId)\n for (const seq of entry.shadowedSeqs) {\n const childId = checkpointBlockIdOf(sessionEventsOf(session), seq)\n const child = childId === null ? undefined : byId.get(childId)\n if (child !== undefined) visit(child)\n else out.push(seq)\n }\n }\n visit(root)\n return out\n}\n","/**\n * Cross-version session event access.\n *\n * DSH `0.1.2-alpha` replaced the public `Session.events` getter with explicit\n * `snapshotEvents()` / `eventAt(seq)` methods; rc.6 / 0.1.1-rc.x still expose\n * `events`. Both shapes are feature-detected here so a single build runs on\n * either seam (the engine's peer range keeps `^0.1.0-rc.6 || ^0.1.1-rc.1`).\n *\n * Semantics match on both sides:\n * - `events` (rc.6) and `snapshotEvents()` (0.1.2-alpha) both return the\n * current full log as a stable, cached snapshot (reused until the next\n * append), with `seq === array index`.\n * - indexed reads map to `events[seq]` / `eventAt(seq)` with the same\n * `undefined`-when-absent contract.\n * @module billion-context-dsh/session-events\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\n\n/** Session surface extended with the 0.1.2-alpha read methods (optional). */\ntype SessionWithSnapshot = Session & {\n snapshotEvents?: () => readonly SessionEvent[]\n eventAt?: (seq: number) => SessionEvent | undefined\n}\n\n/** Session surface narrowed to the rc.6 public events getter. */\ntype SessionWithEvents = Session & {\n events: readonly SessionEvent[]\n}\n\n/** All events of a session in log order (seq == array index). */\nexport function sessionEventsOf(session: Session): readonly SessionEvent[] {\n const snapshot = (session as SessionWithSnapshot).snapshotEvents?.()\n if (snapshot !== undefined) return snapshot\n return (session as SessionWithEvents).events\n}\n\n/** The event at one exact seq, or undefined when the log has no such seq. */\nexport function eventAtOf(session: Session, seq: number): SessionEvent | undefined {\n const eventAt = (session as SessionWithSnapshot).eventAt\n if (typeof eventAt === 'function') return eventAt.call(session, seq)\n return (session as SessionWithEvents).events[seq]\n}","/**\n * Local tool-pairing balance checks over the session surface.\n *\n * UPSTREAM: `@deepseek-ai/dsh-compaction@0.1.2-rc.1` reads the REMOVED\n * `session.events` API in its balance cache — `extendCache` does\n * `const events = session.events` and `eventForSeq` does `events[seq]`, so on\n * every dsh 0.1.2 host the official `toolPairingBalancedBefore/After` helpers\n * throw `TypeError: Cannot read properties of undefined (reading '<seq>')`.\n * The host's API docs require compaction backends to use these helpers for\n * edge checks, and the host's own `dsh-compaction-basic` calls them too, so\n * ALL compaction on a 0.1.2-rc.1 host crashes (reproduced offline and pinned\n * in issue #124; tracked in docs/dsh-porting-verification.md).\n *\n * This module mirrors the host's algorithm line for line (per-session cache\n * keyed by `surface.replaceGeneration`, the `cutBalanced` fold, identical\n * error messages) with ONE deliberate difference: events are read through the\n * cross-version accessor `eventAtOf` (src/session-events.ts), which works on\n * both the 0.1.0/0.1.1 lines (`events[seq]`) and 0.1.2+ (`eventAt(seq)`).\n * DELETE this module and switch `src/region.ts` back to\n * `@deepseek-ai/dsh-compaction`'s helpers the moment the host fix ships.\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { eventAtOf } from './session-events.ts'\n\ninterface BalanceCache {\n /** The surface generation this cache was folded against. */\n generation: number | undefined\n /** `cutBalanced[i]` — whether the cut just before surface position `i` is balanced. */\n cutBalanced: boolean[]\n /** surface seq → its position in `cutBalanced`. */\n indexBySeq: Map<number, number>\n /** Tool calls opened but not yet answered while folding. */\n inProgressToolCalls: number\n}\n\nconst balanceCacheBySession = new WeakMap<object, BalanceCache>()\n\n/** How one surface event changes the in-progress tool-call count. */\nfunction eventDelta(event: SessionEvent): number {\n if (event.type === 'tool/result') return -1\n if (event.type === 'assistant/message') {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n if (!Array.isArray(content)) return 0\n let calls = 0\n for (const block of content) {\n if (block !== null && typeof block === 'object' && (block as { type?: unknown }).type === 'tool-call') calls += 1\n }\n return calls\n }\n return 0\n}\n\n/** Read and validate the event named by a surface sequence. */\nfunction eventForSeq(session: Session, seq: number): SessionEvent {\n const event = eventAtOf(session, seq)\n if (event === undefined || event.seq !== seq) {\n throw new Error(`tool-pairing balance: surface seq ${seq} has no matching session event (corrupt surface)`)\n }\n return event\n}\n\n/** Fold surface sequences not yet in the cache into its balance state. */\nfunction extendCache(session: Session, cache: BalanceCache, seqs: readonly number[]): BalanceCache {\n const processed = cache.cutBalanced.length - 1\n const tail = seqs.slice(processed)\n const pendingCuts: boolean[] = []\n let inProgressToolCalls = cache.inProgressToolCalls\n for (const seq of tail) {\n inProgressToolCalls += eventDelta(eventForSeq(session, seq))\n if (inProgressToolCalls < 0) {\n throw new Error(`tool-pairing balance: tool/result at surface seq ${seq} has no matching tool-call (corrupt surface)`)\n }\n pendingCuts.push(inProgressToolCalls === 0)\n }\n tail.forEach((seq, offset) => cache.indexBySeq.set(seq, processed + offset))\n cache.cutBalanced = cache.cutBalanced.concat(pendingCuts)\n cache.inProgressToolCalls = inProgressToolCalls\n return cache\n}\n\n/** Return balance state synchronized with the current session surface. */\nfunction balanceCache(session: Session): BalanceCache {\n const seqs = session.surface.nodes\n const generation = (session.surface as { replaceGeneration?: number | undefined }).replaceGeneration\n const cached = balanceCacheBySession.get(session)\n if (cached === undefined || cached.generation !== generation || cached.cutBalanced.length - 1 > seqs.length) {\n const rebuilt = extendCache(session, {\n generation,\n cutBalanced: [true],\n indexBySeq: new Map(),\n inProgressToolCalls: 0,\n }, seqs)\n balanceCacheBySession.set(session, rebuilt)\n return rebuilt\n }\n if (cached.cutBalanced.length - 1 < seqs.length) return extendCache(session, cached, seqs)\n return cached\n}\n\n/** Balance of the cut at a sequence's position plus offset, rejecting seqs outside current membership. */\nfunction cutBalance(cache: BalanceCache, seq: number, offset: number): boolean {\n const index = cache.indexBySeq.get(seq)\n const balanced = index === undefined ? undefined : cache.cutBalanced[index + offset]\n if (balanced === undefined) throw new Error(`tool-pairing balance: surface seq ${seq} not found`)\n return balanced\n}\n\n/**\n * Whether the cut immediately before a current surface sequence is tool-pairing balanced.\n * @param session - session whose surface is checked.\n * @param seq - event sequence whose leading cut is checked.\n * @returns true when no unanswered tool call crosses the cut.\n */\nexport function toolPairingBalancedBefore(session: Session, seq: number): boolean {\n return cutBalance(balanceCache(session), seq, 0)\n}\n\n/**\n * Whether the cut immediately after a current surface sequence is tool-pairing balanced.\n * @param session - session whose surface is checked.\n * @param seq - event sequence whose trailing cut is checked.\n * @returns true when no unanswered tool call crosses the cut.\n */\nexport function toolPairingBalancedAfter(session: Session, seq: number): boolean {\n return cutBalance(balanceCache(session), seq, 1)\n}\n","/**\n * M1 — session-log projection: DSH surface events → acp-kernel CoreMessage.\n *\n * The ACP kernel is message-array based; DSH is event-log based. This module\n * is the bridge in the direction the engine needs (projectEvent /\n * eventsToCoreMessages). The reverse direction (CoreMessage[] → session\n * appends) is the M5 region transaction's job.\n * Mirrors billion-context-pi's `projectMessage`/`entriesToCoreMessages`\n * against DSH event shapes (see V-verification: SurfaceEventType =\n * 'user/message' | 'assistant/message' | 'tool/result').\n * @module billion-context-pi-dsh/messages\n */\n\nimport type { CoreMessage } from 'acp-kernel'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\n\n/**\n * Extract plain text from a DSH content block array or string.\n *\n * Recursive: a real DSH `tool-result` block is `{ type: 'tool-result',\n * toolCallId, content: ContentBlock[] }` — the inner `content` array holds\n * the actual `text` blocks, so a top-level-only walk would drop every tool\n * result from the projection (and with it the seq's ref assignment, breaking\n * compress boundary resolution). Nested arrays are flattened depth-first.\n */\nexport function extractText(content: unknown): string {\n if (typeof content === 'string') return content\n if (!Array.isArray(content)) return ''\n const parts: string[] = []\n for (const block of content) {\n if (block === null || typeof block !== 'object') continue\n const b = block as { type?: unknown; text?: unknown; content?: unknown }\n if (b.type === 'text' && typeof b.text === 'string') {\n parts.push(b.text)\n } else if (Array.isArray(b.content)) {\n parts.push(extractText(b.content))\n }\n }\n return parts.join('\\n')\n}\n\ninterface ToolCallBlock {\n type: 'tool-call'\n id?: string\n name?: string\n arguments?: unknown\n}\n\nfunction toolCallsOf(content: unknown): ToolCallBlock[] {\n if (!Array.isArray(content)) return []\n return content.filter((b): b is ToolCallBlock => (b as { type?: string }).type === 'tool-call')\n}\n\nfunction stringifyArgs(args: unknown): string {\n if (!args) return ''\n if (typeof args === 'string') return args\n try {\n return JSON.stringify(args)\n } catch {\n return String(args)\n }\n}\n\n/**\n * The tool-call id of one tool/result surface message, or null.\n *\n * Real DSH tool-result events carry NO `message.toolCallId` (hard-won rule\n * 10): the identity lives in the nested `{ type: 'tool-result', toolCallId }`\n * content block, falling back to `message.source.callId`. Shared with\n * `src/region.ts`'s call/result pairing — one implementation, never a copy.\n */\nexport function toolCallIdOfResultEvent(event: SessionEvent): string | null {\n if (event.type !== 'tool/result') return null\n const message = (event.data as {\n message?: { content?: Array<{ type?: unknown; toolCallId?: unknown }>; source?: { callId?: unknown } }\n }).message\n const block = Array.isArray(message?.content)\n ? message.content.find((candidate) => candidate?.type === 'tool-result')\n : undefined\n const id = block?.toolCallId ?? message?.source?.callId\n return typeof id === 'string' ? id : null\n}\n\n/**\n * Index of assistant tool-call `id` → tool `name`, used to attribute\n * tool/result messages to their tool. Real DSH tool-results carry no\n * `message.toolName` (rule 10), so the projection backfills it from the\n * matching assistant tool-call. Scans ALL events up front (order-independent:\n * a result may precede its call in the array) and covers shadowed calls too.\n */\nexport function buildToolCallIndex(events: readonly SessionEvent[]): ReadonlyMap<string, string> {\n const index = new Map<string, string>()\n for (const event of events) {\n if (event.type !== 'assistant/message') continue\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n if (!Array.isArray(content)) continue\n for (const block of content) {\n const candidate = block as { type?: unknown; id?: unknown; name?: unknown } | null\n if (candidate !== null && typeof candidate === 'object' && candidate.type === 'tool-call' && typeof candidate.id === 'string') {\n index.set(candidate.id, typeof candidate.name === 'string' ? candidate.name : '')\n }\n }\n }\n return index\n}\n\n/**\n * Project one surface message event into CoreMessage(s).\n * - user/message → user text (verbatim content)\n * - assistant/message → assistant text, or one CoreMessage per tool-call\n * - tool/result → tool result (role 'tool'); toolName/toolCallId are\n * backfilled from `toolNames` (assistant tool-call\n * index) — real DSH events do not carry them at the\n * message level. Without an index the result stays\n * untagged (`toolName: ''`), never \"text\".\n * Non-surface events project to nothing.\n */\nexport function projectEvent(event: SessionEvent, toolNames?: ReadonlyMap<string, string>): CoreMessage[] {\n switch (event.type) {\n case 'user/message': {\n const text = extractText((event.data as { content?: unknown }).content)\n return text.length > 0 ? [{ id: String(event.seq), role: 'user', contentType: 'text', text }] : []\n }\n case 'assistant/message': {\n const content = (event.data as { message?: { content?: unknown } }).message?.content\n const calls = toolCallsOf(content)\n const text = extractText(content)\n if (calls.length === 0) {\n return text.trim().length > 0\n ? [{ id: String(event.seq), role: 'assistant', contentType: 'text', text }]\n : []\n }\n if (calls.length === 1) {\n const call = calls[0]!\n const argStr = stringifyArgs(call.arguments)\n const body = argStr && text ? `${text}\\n${argStr}` : argStr || text\n return [{\n id: String(event.seq),\n role: 'assistant',\n contentType: 'tool-call',\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: body,\n }]\n }\n return calls.map((call) => ({\n id: `${event.seq}#${call.id ?? ''}`,\n role: 'assistant' as const,\n contentType: 'tool-call' as const,\n toolName: call.name ?? '',\n toolCallId: call.id ?? '',\n text: stringifyArgs(call.arguments) || text,\n }))\n }\n case 'tool/result': {\n const message = (event.data as {\n message?: { content?: unknown; toolName?: string; toolCallId?: string }\n }).message\n const text = extractText(message?.content)\n if (text.length === 0) return []\n const key = toolCallIdOfResultEvent(event)\n return [{\n id: String(event.seq),\n role: 'tool',\n contentType: 'tool-result',\n toolName: toolNames?.get(key ?? '') ?? '',\n toolCallId: message?.toolCallId ?? key ?? '',\n text,\n }]\n }\n default:\n return []\n }\n}\n\n/** Project a session's message events into CoreMessage[] in log order. */\nexport function eventsToCoreMessages(events: readonly SessionEvent[], toolNames?: ReadonlyMap<string, string>): CoreMessage[] {\n const index = toolNames ?? buildToolCallIndex(events)\n const out: CoreMessage[] = []\n for (const event of events) out.push(...projectEvent(event, index))\n return out\n}\n\n/** The surface-visible message events of a session, in model-visible order. */\nexport function surfaceEventsOf(session: Session): SessionEvent[] {\n return session.surface.nodes\n .map((seq) => eventAtOf(session, seq))\n .filter((event): event is SessionEvent => event !== undefined)\n}\n\n/**\n * ALL message-type events in log order — the visible surface PLUS everything\n * shadowed by compression. The ACP kernel deactivates any block whose consumed\n * message ids are absent from the array it is given (syncBlocks), and refuses\n * to anchor a block boundary that cannot find its messages, so T2/T3\n * distillation requires the full log, not just the visible surface.\n */\nexport function allLogMessages(session: import('@deepseek-ai/dsh-session').Session): CoreMessage[] {\n return eventsToCoreMessages(sessionEventsOf(session))\n}\n\n/** Extract the model-facing text of any surface message event. */\nexport function extractEventText(event: SessionEvent): string {\n switch (event.type) {\n case 'user/message':\n return extractText((event.data as { content?: unknown }).content)\n case 'assistant/message':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n case 'tool/result':\n return extractText((event.data as { message?: { content?: unknown } }).message?.content)\n default:\n return ''\n }\n}\n","/**\n * Host-vocabulary token pricing for the durable shadow-price protocol.\n *\n * The host token-meter prices every appended message with a fixed flat-4\n * heuristic (`estimateContent` / `estimateMessage` in `dsh-token-meter`) and\n * the producer contract requires every `compaction/summary`/`compaction/prune`\n * `shadowedTokenCount` claim to be derived from the SAME estimator. Writing\n * claims with the engine's CJK-aware `defaultCountTokens` overdraws the meter\n * on CJK-heavy sessions and permanently bricks them (live session\n * `session-3aa366c3`, issue #54; AGENTS.md rule 12 — `defaultCountTokens` is\n * display currency, NEVER event currency).\n *\n * This module prices claims in the host's vocabulary: it prefers the live\n * meter's own per-node FIXED-HEURISTIC prices (`ctx.tokenMeter.measure(session)`\n * nodes' `heuristicTokens` — the same basis the projection ledger accumulates\n * appends with, so the claim is exact by construction) and falls back to an\n * exact mirror of the host's estimator when the meter is unreachable.\n *\n * Two vocabularies share the meter's node since DSH 0.1.2: `tokens` carries\n * the measured route's request pressure (image occurrences re-priced with the\n * route's declared visual tokens) while `heuristicTokens` keeps the fixed\n * flat-4 heuristic the ledger prices appends with. The claim MUST read\n * `heuristicTokens`: a routed `tokens` claim overstates the replaced range\n * against its own ledger accumulation and folds `messageTokens` negative —\n * the same session-bricking schema rejection as #54, through the image-route\n * channel (issue #103). Older hosts (0.1.0/0.1.1 lines) expose a single\n * `tokens` field that IS the fixed heuristic, so the fallback reads it.\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { deriveEventMessage } from '@deepseek-ai/dsh-session'\nimport { eventAtOf } from './session-events.ts'\n\n/** Fixed text-density heuristic used by the host meter until exact tokenization. */\nconst CHARS_PER_TOKEN = 4\n/** Per-block structural overhead for JSON framing and type tags. */\nconst BLOCK_OVERHEAD = 4\n/** Role-field framing overhead added to every priced message. */\nconst ROLE_OVERHEAD = 4\n\n/** The host's model-visible content block union (structural, mirror-side only). */\nexport type HostBlock =\n | { type: 'text'; text: string }\n | { type: 'reasoning'; text: string }\n | { type: 'tool-call'; name: string; arguments: string }\n | { type: 'tool-result'; toolCallId: string; content: HostContent }\n | { type?: string } & Record<string, unknown>\n\n/** A content block list, or a bare string (`tool-result` content may be either). */\nexport type HostContent = readonly HostBlock[] | string\n\nfunction blockType(block: unknown): string | undefined {\n if (typeof block !== 'object' || block === null) return undefined\n const type = (block as { type?: unknown }).type\n return typeof type === 'string' ? type : undefined\n}\n\n/**\n * Exact mirror of the host's `estimateContent`\n * (`@deepseek-ai/dsh-token-meter/lib/types/estimate.js`): text/reasoning\n * `ceil(len/4)+4`, tool-call `ceil(name/4)+ceil(arguments/4)+4`, tool-result\n * recursive over its content, unknown blocks `4+ceil(JSON.stringify/4)` over\n * the ORIGINAL block object. A string content is iterated as an iterable, so\n * every CHARACTER falls to the default branch (`4+ceil(JSON.stringify(char)/4)`\n * — 5 tokens for any single unescaped character).\n */\nexport function estimateHostContent(blocks: HostContent): number {\n if (typeof blocks === 'string') {\n let tokens = 0\n for (const char of blocks) {\n tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(char).length / CHARS_PER_TOKEN)\n }\n return tokens\n }\n let tokens = 0\n for (const block of blocks) {\n switch (blockType(block)) {\n case 'text':\n case 'reasoning': {\n tokens += Math.ceil((block as { text: string }).text.length / CHARS_PER_TOKEN) + BLOCK_OVERHEAD\n break\n }\n case 'tool-call': {\n const call = block as { name: string; arguments: string }\n tokens += Math.ceil(call.name.length / CHARS_PER_TOKEN)\n + Math.ceil(call.arguments.length / CHARS_PER_TOKEN)\n + BLOCK_OVERHEAD\n break\n }\n case 'tool-result': {\n tokens += estimateHostContent((block as { content: HostContent }).content) + BLOCK_OVERHEAD\n break\n }\n default:\n tokens += BLOCK_OVERHEAD + Math.ceil(JSON.stringify(block).length / CHARS_PER_TOKEN)\n }\n }\n return tokens\n}\n\n/** Exact mirror of the host's `estimateMessage` (content + role framing). */\nexport function estimateHostMessage(message: { content: HostContent }): number {\n return estimateHostContent(message.content) + ROLE_OVERHEAD\n}\n\n/**\n * Host price of ONE session event under the mirror: project it through the\n * host's `deriveEventMessage` (null for non-surface events and empty-content\n * assistant messages) and price the derived message; null derives to 0.\n */\nexport function hostPriceEvent(event: SessionEvent): number {\n const message = deriveEventMessage(event)\n return message === null ? 0 : estimateHostMessage(message as { content: HostContent })\n}\n\n/** Mirror price of a set of surface seqs (the fallback claim computation). */\nexport function shadowedHostTokens(session: Session, seqs: readonly number[]): number {\n let total = 0\n for (const seq of seqs) {\n const event = eventAtOf(session, seq)\n if (event !== undefined) total += hostPriceEvent(event)\n }\n return total\n}\n\n/** The slice of the live meter's measurement the engine may price from. */\ninterface TokenMeterLike {\n measure(session: Session): {\n nodes: ReadonlyArray<{ seq: number; tokens: number; heuristicTokens?: number }>\n }\n}\n\n/**\n * Claim price for `seqs` in the host's vocabulary. Prefers the live meter's\n * own per-node FIXED-HEURISTIC prices when `ctx.tokenMeter` is reachable and\n * covers every shadowed seq (exact by construction — the ledger's\n * `foldSurfaceProjection` accumulates appends with the same fixed heuristic,\n * so the claim and the ledger stay in agreement; follows host estimator\n * changes automatically). `node.heuristicTokens` is that basis since DSH 0.1.2;\n * `node.tokens` there is the measured route's REQUEST pressure (image\n * occurrences carry the route's visual price via `priceSurface`) and MUST NOT\n * be claimed — reading it overstates the claim and folds the host projection\n * negative on image-containing ranges (issue #103, the image-route channel of\n * the #54 brick). Older meters expose a single `tokens` field that IS the\n * fixed heuristic, so `heuristicTokens ?? tokens` covers both shapes. ANY\n * failure — meter absent, `measure` throwing (e.g. a step-less log), or a seq\n * missing from the measurement — falls back to the exact mirror. Never returns\n * a `defaultCountTokens` price (rule 12).\n */\nexport function shadowedTokensViaMeter(\n session: Session,\n seqs: readonly number[],\n ctx?: { get?(name: string): unknown } | null,\n): number {\n try {\n const meter = ctx?.get?.('tokenMeter') as TokenMeterLike | undefined\n if (meter?.measure !== undefined) {\n const bySeq = new Map(meter.measure(session).nodes.map((node) => [node.seq, node.heuristicTokens ?? node.tokens]))\n let total = 0\n let missing = false\n for (const seq of seqs) {\n const tokens = bySeq.get(seq)\n if (tokens === undefined) {\n missing = true\n break\n }\n total += tokens\n }\n if (!missing) return total\n }\n } catch {\n // Fall through to the mirror — the mirror IS the host vocabulary.\n }\n return shadowedHostTokens(session, seqs)\n}\n","/**\n * M2 — per-session ACP kernel state.\n *\n * The in-memory map holds the exact acp-kernel `CompressionState` while a\n * session is live. Durability does not rely on a sidecar file: every durable\n * compression writes a `compaction/summary` event whose shadowed range and\n * summary re-derive the block ledger (`rebuildBlockLedger` in region.ts), so a\n * restarted engine can answer decompress/search/status from the session log\n * alone — DSH's \"log is the source of truth\" model.\n *\n * Tier-2/3 distillation additionally requires the kernel state to KNOW the\n * blocks: `syncBlocks` deactivates a block whose consumed messages are absent\n * from the message array, and `resolveBoundaries` refuses to anchor a block\n * ref it cannot find — so on first access for a session that already has\n * durable blocks (e.g. after a server restart), the kernel blocks are\n * REHYDRATED from the ledger before use. Live updates continue through `set`.\n * @module billion-context-dsh/state\n */\n\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport { createInitialState, type CompressionBlock, type CompressionState } from 'acp-kernel'\nimport { rebuildBlockLedger } from './region.ts'\nimport { sessionEventsOf } from './session-events.ts'\n\n/** Rebuild kernel `CompressionBlock`s from the durable ledger (no kernel run needed). */\nfunction rebuildKernelBlocks(events: readonly SessionEvent[]): CompressionBlock[] {\n const ledger = rebuildBlockLedger(events)\n if (ledger.length === 0) return []\n // Durable compactionId → kernel block ref (bN), recorded or synthesised.\n const kernelIdOf = new Map<string, string>()\n const parentKernelIds = new Map<string, string[]>()\n let next = 1\n for (const entry of ledger) {\n let kernelBlockId: string\n if (entry.kernelBlockId !== undefined && /^b\\d+$/.test(entry.kernelBlockId)) {\n kernelBlockId = entry.kernelBlockId\n const num = Number(kernelBlockId.slice(1))\n if (Number.isInteger(num)) next = Math.max(next, num + 1)\n } else {\n kernelBlockId = `b${next}`\n next += 1\n }\n kernelIdOf.set(entry.blockId, kernelBlockId)\n parentKernelIds.set(\n entry.blockId,\n entry.parentBlockIds\n .map((parent) => kernelIdOf.get(parent))\n .filter((id): id is string => id !== undefined),\n )\n }\n const consumed = new Set<string>()\n for (const entry of ledger) {\n for (const parent of entry.parentBlockIds) consumed.add(parent)\n }\n const blocks: CompressionBlock[] = []\n for (const entry of ledger) {\n const blockId = kernelIdOf.get(entry.blockId)!\n // The kernel anchors a block by its effectiveMessageIds. Since the tier\n // feature, the transaction records the kernel block's raw coverage\n // (direct/effective message ids) verbatim, so rehydration is faithful —\n // a tier-2 block's coverage is its parents' ORIGINALS, not the checkpoint\n // node it shadows. Legacy blocks fall back to the shadowed seqs (tier 1)\n // or the checkpoint node (tier > 1; multi-tool-call assistant messages in\n // legacy blocks lose bare-seq coverage — a documented legacy limitation).\n const direct = entry.directMessageIds ?? [...entry.shadowedSeqs.map(String)]\n const effective = entry.effectiveMessageIds\n ?? (entry.tier > 1\n ? (entry.summarySeq === undefined ? [...entry.shadowedSeqs.map(String)] : [String(entry.summarySeq)])\n : [...entry.shadowedSeqs.map(String)])\n blocks.push({\n blockId,\n runId: `r${blocks.length + 1}`,\n tier: entry.tier,\n summary: entry.summary,\n ...(entry.topic === undefined ? {} : { topic: entry.topic }),\n directMessageIds: [...direct],\n effectiveMessageIds: [...effective],\n directBlockIds: parentKernelIds.get(entry.blockId) ?? [],\n compressedTokens: entry.shadowedTokenCount,\n createdAt: entry.createdAt,\n survivedCount: 0,\n generation: 'young',\n active: !consumed.has(entry.blockId),\n })\n }\n return blocks\n}\n\n/** The next kernel block id after the rehydrated blocks (or the initial 1). */\nfunction nextBlockIdAfter(events: readonly SessionEvent[]): number {\n const blocks = rebuildKernelBlocks(events)\n let max = 0\n for (const block of blocks) {\n const num = Number(block.blockId.slice(1))\n if (Number.isInteger(num)) max = Math.max(max, num)\n }\n return max + 1\n}\n\nexport class AcpStateStore {\n private readonly states = new Map<string, CompressionState>()\n\n /** Kernel state for one session, initialised on first access. */\n stateFor(session: Session): CompressionState {\n const id = session.id\n const existing = this.states.get(id)\n if (existing !== undefined) return existing\n const state = createInitialState()\n const events = sessionEventsOf(session)\n if (events.some((event) => event.type === 'compaction/summary')) {\n state.blocks = rebuildKernelBlocks(events)\n state.nextBlockId = nextBlockIdAfter(events)\n }\n this.states.set(id, state)\n return state\n }\n\n set(session: Session, state: CompressionState): void {\n this.states.set(session.id, state)\n }\n\n delete(session: Session): void {\n this.states.delete(session.id)\n }\n}\n","/**\n * M3 — the four model tools: compress / decompress / search_context /\n * acp_status, registered through `ctx.tools` (defineTool).\n *\n * compress is the heart of ACP: the model writes the summary and the tool\n * lands it as a durable surface replacement (no second LLM summarization\n * call). decompress recovers shadowed content read-only from the log (DSH\n * keeps the originals — V5). search_context scores blocks rebuilt from the\n * log. acp_status reports the block ledger and pressure.\n * @module billion-context-dsh/tools\n */\n\nimport { defineTool, ToolArgsError, type ToolDefinition, type ToolRunContext } from '@deepseek-ai/dsh-tools'\nimport { buildStatusReport, defaultCountTokens, searchBlocks, type CompressionCore, type MessageRole, type SearchDoc } from 'acp-kernel'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport type { Session, SessionEvent } from '@deepseek-ai/dsh-session'\nimport type { AcpStateStore } from './state.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport type { AcpWindow } from './window.ts'\nimport {\n AlreadyCompressedRangeError,\n blockIdOfKernelRef,\n blockRefForSummarySeq,\n blockRegistry,\n compactionIdsOfKernelBlocks,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n stripOrphanedSurfaceToolMessages,\n openToolCallIds,\n surfaceSummary,\n type ResolvedSurfaceRange,\n} from './region.ts'\nimport { allLogMessages, buildToolCallIndex, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { shadowedTokensViaMeter } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\nimport { DEFAULT_RESOLVED, type ResolvedPrompts } from './prompts.ts'\n\nexport interface ToolEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolve the effective context window for an agent (optional: status falls back to modelContextLimit). */\n readonly windowFor?: (agent: Agent) => Promise<AcpWindow>\n /** Resolved prompt templates (optional: falls back to DEFAULT_RESOLVED). */\n readonly prompts?: ResolvedPrompts\n /**\n * Call ids of compress invocations that created a durable block. The engine\n * listens for the matching `tool/result` and hides the call/result pair from\n * the surface, preventing the compaction summary from sitting between them\n * (strict providers reject that sequence with HTTP 400).\n */\n readonly compressCallIdsToHide?: Set<string>\n}\n\ninterface TextOutput {\n text: string\n}\n\nfunction textOutput(): {\n schema: { type: 'object'; properties: { text: { type: 'string' } }; additionalProperties: boolean }\n render: (args: unknown, value: TextOutput) => import('@deepseek-ai/dsh-llm').ContentBlock[]\n} {\n return {\n schema: {\n type: 'object',\n properties: { text: { type: 'string' } },\n additionalProperties: false,\n },\n render: (_args, value) => [{ type: 'text', text: value.text }],\n }\n}\n\nfunction requireAgent(exec: ToolRunContext): Agent {\n if (exec.agent === undefined) {\n throw new Error('billion-context-dsh: tool requires an agent execution context')\n }\n return exec.agent\n}\n\n/**\n * Resolve the effective context window for a tool or command run: probe the\n * agent's real window via `windowFor` when provided, otherwise fall back to\n * the environment's `modelContextLimit`. Shared by the compress and\n * acp_status tool handlers and the `/acp` command so the resolution logic\n * lives in exactly one place (issue #63 — the tools used the 128K fallback\n * for pressure decisions even when auto-detection had found a larger window).\n */\nexport async function resolveEffectiveWindow(env: ToolEnvironment, agent: Agent): Promise<AcpWindow> {\n return env.windowFor === undefined\n ? { limit: env.modelContextLimit, source: 'explicit' as const }\n : await env.windowFor(agent)\n}\n\nconst compressParameters = {\n // Tolerated wrapped-arguments form: some models emit\n // `{ \"arguments\": \"{\\\"content\\\": [...]}\" }` (double-nested) or\n // `{ \"arguments\": { \"content\": [...] } }` instead of the unwrapped\n // `{ \"content\": [...] }`. The old DSH validator surfaced this as\n // `invalid arguments: \"arguments\" must be an object` and the model retried\n // forever. `arguments` is accepted as an optional JSON node so the wrapped\n // shape passes schema validation; `handleCompress` unwraps it and falls back\n // to a clear runtime error when neither form carries content. `content` is\n // intentionally NOT `required: true` — a required property would reject the\n // wrapped shape before `handleCompress` can see it. The tool description\n // still tells the model content is mandatory.\n //\n // The items fields are the opposite case: startSeq/endSeq/summary MUST be\n // `required: true`. Without that, a model call that omits `summary` (only\n // startSeq/endSeq/topic present) passed schema validation and failed late\n // inside the kernel with \"Summary is empty\" — and live sessions showed the\n // model retrying the identical broken call in a loop. With the fields\n // required, the same call is rejected at the schema gate with\n // `missing required property \"content[0].summary\"`, which tells the model\n // exactly which field to add (same pattern as decompress's required\n // blockId / search_context's required query).\n arguments: { type: 'json', description: 'Tolerated wrapped-arguments form (model-generated); unwrapped in handleCompress. Prefer passing content directly.' },\n topic: { type: 'string' as const, description: 'Fallback topic for entries without their own.' },\n content: {\n type: 'array' as const,\n description: 'One or more ranges to compress, each with startSeq/endSeq boundaries (surface seqs) and a dense summary. Required — pass it directly, not wrapped in an arguments key.',\n items: {\n type: 'object' as const,\n properties: {\n startSeq: {\n required: true,\n oneOf: [\n { type: 'integer' as const, description: 'First surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n endSeq: {\n required: true,\n oneOf: [\n { type: 'integer' as const, description: 'Inclusive last surface seq of the range.' },\n { type: 'string' as const, description: 'Seq as text; a trailing #callId fragment is ignored.' },\n ],\n },\n summary: { type: 'string' as const, required: true, description: 'Complete technical summary replacing the range; keep paths, decisions, values verbatim. Minimum 50 characters.' },\n topic: { type: 'string' as const, description: 'Short label (3-5 words) for this range.' },\n },\n additionalProperties: false,\n },\n },\n} as const\n\n/** Normalize a seq arg: number, \"295\", or \"295#call_00_xxx\" → 295. */\nfunction parseSeq(value: number | string): number {\n const text = String(value).split('#')[0]!.trim()\n const seq = Number(text)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(`billion-context-dsh: invalid seq \"${String(value)}\" — use a surface seq like 295`)\n }\n return seq\n}\n\n/**\n * Match a drilldown mN ref: \"m00306\" / \"m306\" (kernel `refToIndex` semantics,\n * `m0*(\\d{1,5})`), tolerating a trailing `#callId` fragment (symmetric with\n * `parseSeq`'s `#` handling). Returns the ref index, or null for non-mN input.\n */\nconst MN_RE = /^m0*(\\d{1,5})(?:#.*)?$/i\n\nfunction mnRefIndex(value: string): number | null {\n const match = MN_RE.exec(value.trim())\n if (match === null) return null\n const index = Number(match[1])\n return index >= 1 && index <= 99999 ? index : null\n}\n\n/**\n * Resolve a compress boundary arg to a surface seq. Accepts:\n * - a bare surface seq (number, \"295\", \"295#call_00_x\" — `parseSeq`);\n * - a drilldown mN ref (\"m00306\" / \"m306\") — reverse-mapped via the CURRENT\n * turn's `messageRefs.byRef` (CoreMessage.id = seq or \"seq#callId\" → split\n * on \"#\"). Unknown mN (never assigned on the current surface) fails with\n * guidance; a valid mN whose span was already compressed falls through to\n * the existing recover-stale / already-compressed semantics (rule 7).\n * `byRef` MUST come from `turn.state.messageRefs` (after `processTurn`), not\n * the persisted store state: acp_status's turn is never persisted, so mN refs\n * shown in a drilldown (including refs for messages that arrived since the\n * last nudge/compress) only exist on the current turn's ref map — a lookup\n * against the stored state would report a false \"unknown mN\" and dead-loop\n * the model between acp_status and compress.\n */\nfunction parseBoundary(value: number | string, byRef: Record<string, string>): number {\n const text = String(value)\n const index = mnRefIndex(text)\n if (index === null) return parseSeq(value)\n // Normalize to the kernel's padded key (\"m00306\") — byRef holds exact keys.\n const ref = `m${String(index).padStart(5, '0')}`\n const raw = byRef[ref]\n if (raw === undefined) {\n throw new Error(\n `billion-context-dsh: mN \"${text}\" not found on the current surface — re-run acp_status for fresh refs (the surface may have moved)`,\n )\n }\n const seq = Number(String(raw).split('#')[0]!)\n if (!Number.isInteger(seq) || seq < 0) {\n throw new Error(\n `billion-context-dsh: mN \"${text}\" maps to a non-seq id \"${raw}\" — re-run acp_status`,\n )\n }\n return seq\n}\n\ninterface CompressArgs {\n /** Tolerated wrapped-arguments form (model-generated double-nesting). */\n arguments?: string | { content?: CompressArgs['content'] }\n topic?: string\n content?: Array<{ startSeq: number | string; endSeq: number | string; summary: string; topic?: string }>\n}\n\n/**\n * Unwrap the tolerated wrapped-arguments forms back to the canonical shape:\n * `{ arguments: \"{\\\"content\\\": [...]}\" }` or `{ arguments: { content: [...] } }`\n * → `{ content: [...] }`. The direct `{ content: [...] }` form passes through\n * untouched. Returns null when no form carries content (caller raises).\n */\nfunction unwrapCompressArgs(args: CompressArgs): CompressArgs | null {\n if (args.content !== undefined) return args\n if (args.arguments === undefined) return null\n let inner: unknown = args.arguments\n if (typeof inner === 'string') {\n try {\n inner = JSON.parse(inner)\n } catch {\n return null\n }\n }\n if (typeof inner !== 'object' || inner === null || Array.isArray(inner)) return null\n const content = (inner as { content?: unknown }).content\n if (content === undefined) return null\n return { ...args, content: content as CompressArgs['content'] }\n}\n\n/**\n * Peel the tolerated wrapped-arguments envelope `{ arguments: {…} }` that some\n * model channels emit for ANY tool — the same double-nesting that birthed\n * `unwrapCompressArgs` (live-verified on acp_status: a drilldown call arrived\n * as `{\"arguments\":{\"scope\":\"compressed\"}}` and was silently dropped, since\n * only compress unwrapped). The envelope may be an object or a JSON string;\n * inner keys win over outer duplicates. Args without an envelope pass through\n * untouched.\n */\nfunction unwrapEnvelope<T extends object>(args: T): T {\n const envelope = (args as { arguments?: unknown }).arguments\n if (envelope === undefined) return args\n let inner: unknown = envelope\n if (typeof inner === 'string') {\n try {\n inner = JSON.parse(inner)\n } catch {\n return args\n }\n }\n if (typeof inner !== 'object' || inner === null || Array.isArray(inner)) return args\n return { ...args, ...(inner as object) } as T\n}\n\n/**\n * Enforce the items-level `required` contract on the EFFECTIVE content, after\n * the wrapped-arguments envelope has been peeled. The DSH schema gate only\n * sees the model's top-level arguments object — when the call arrives wrapped\n * as `{ arguments: { content: [...] } }`, the top-level `content` property is\n * absent there (it lives inside the envelope), so the gate never checks the\n * items and a missing `summary`/`startSeq`/`endSeq` sailed through to the\n * kernel, which fails late with a field-less \"Summary is empty\" and sent live\n * sessions into a retry loop (the same failure mode the schema gate fix for\n * the direct form closed). Running the SAME check on the unwrapped content\n * closes that window for both forms, and produces the identical\n * `invalid arguments: missing required property \"content[0].summary\"` surface\n * by reusing the host's `ToolArgsError` instead of a hand-rolled format.\n * An empty/whitespace-only summary counts as missing (the kernel would\n * reject it anyway — fail early with the field name instead).\n */\nfunction validateContentItems(content: NonNullable<CompressArgs['content']>): void {\n const violations: string[] = []\n content.forEach((item, index) => {\n const path = `content[${index}]`\n if (item.startSeq === undefined) violations.push(`missing required property \"${path}.startSeq\"`)\n if (item.endSeq === undefined) violations.push(`missing required property \"${path}.endSeq\"`)\n if (typeof item.summary !== 'string' || item.summary.trim().length === 0) {\n violations.push(`missing required property \"${path}.summary\"`)\n }\n })\n if (violations.length > 0) throw new ToolArgsError(violations)\n}\n\n/** Resolve seq → kernel ref, then applyCompression and land the transaction. */\nasync function handleCompress(env: ToolEnvironment, args: CompressArgs, exec: ToolRunContext): Promise<TextOutput> {\n const agent = requireAgent(exec)\n const session = agent.session\n // Clean orphan tool messages before any range solve: a single orphan result\n // corrupts the pairing balance cache and rejects every large range (issue\n // #18). Every call still in flight — the compress call itself AND any\n // sibling tool called in the same assistant message — must be excluded from\n // orphan pruning: its tool/result lands at the end of the step, and pruning\n // the call now would orphan that result.\n stripOrphanedSurfaceToolMessages(session, openToolCallIds(session))\n const state = env.store.stateFor(session)\n // The kernel gets the FULL log (visible + shadowed): syncBlocks deactivates\n // a block whose consumed messages are absent, and resolveBoundaries refuses\n // to anchor a block ref it cannot find, so tier-2/3 distillation needs the\n // originals present. The token count uses the same priority chain as the\n // nudge (projectedTokens → surfaceTokens → character heuristic).\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const config = kernelConfigFor({ ...env, modelContextLimit: window.limit })\n\n // Assign refs / advance state exactly like a turn would.\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n const byRaw = turn.state.messageRefs.byRaw\n // mN drilldown refs resolve against the CURRENT turn's ref map (not the\n // stored state) — acp_status's turn is never persisted, so its mN rows only\n // exist here; the deterministic re-assignment yields the same mN for the\n // same messages (see parseBoundary).\n const byRef = turn.state.messageRefs.byRef\n\n // Tolerate the wrapped-arguments forms some models emit (double-nested\n // `{ arguments: \"...\" }`), which the old DSH validator surfaced as\n // `\"arguments\" must be an object` and sent the model into a retry loop.\n const unwrapped = unwrapCompressArgs(args)\n if (unwrapped === null) {\n return {\n text: 'compress: missing content — pass the content array directly: compress({ content: [{ startSeq, endSeq, summary }] })',\n }\n }\n args = unwrapped\n // Items-level required check AFTER the envelope peel (see\n // validateContentItems for why the schema gate alone cannot do this).\n validateContentItems(args.content!)\n\n const ranges: Array<\n ResolvedSurfaceRange & {\n startSeq: number\n endSeq: number\n startRef: string\n endRef: string\n summary: string\n topic?: string\n }\n > = []\n // Ranges whose whole span was already shadowed by earlier compressions.\n // They land as advisory warnings, never as errors or phantom blocks.\n const alreadyCompressedNotes: string[] = []\n for (const range of args.content!) {\n const startSeq = parseBoundary(range.startSeq, byRef)\n const endSeq = parseBoundary(range.endSeq, byRef)\n let resolved: ResolvedSurfaceRange\n try {\n // Balance edges FIRST: the requested edges may sit on multi-tool-call\n // assistant messages, which project to `${seq}#${callId}` CoreMessage ids\n // and therefore have NO bare-`${seq}` ref. resolveSurfaceRange shifts them\n // to clean tool-pairing-balanced cuts that always carry a bare ref, so the\n // resolved refs exist and the shadowed span matches the returned range.\n // Edges shadowed by an earlier compression (stale nudge table / old\n // compress result) are remapped to the still-live content of the span.\n resolved = resolveSurfaceRange(session, startSeq, endSeq)\n } catch (error) {\n if (error instanceof AlreadyCompressedRangeError) {\n const covering = error.coveringBlockIds\n const blockNote = covering.length === 0\n ? ''\n : ` (block ${covering[0]!.slice(0, 8)}${covering.length > 1 ? ` +${covering.length - 1} more` : ''})`\n alreadyCompressedNotes.push(\n ` seqs ${error.start}..${error.end} already compressed${blockNote} — nothing to reclaim; decompress to recover the originals`,\n )\n continue\n }\n throw error\n }\n // An edge on an ACTIVE block's checkpoint summary node resolves to the\n // kernel block ref (bN) — the boundary that makes applyCompression distill\n // (tier 2/3) instead of folding the summary as a plain message.\n const startBlockRef = blockRefForSummarySeq(session, resolved.start)\n const endBlockRef = blockRefForSummarySeq(session, resolved.end)\n const startRef = startBlockRef ?? byRaw[String(resolved.start)]\n const endRef = endBlockRef ?? byRaw[String(resolved.end)]\n if (startRef === undefined || endRef === undefined) {\n throw new Error(\n `billion-context-dsh: seq ${resolved.start}..${resolved.end} has no assigned ref — `\n + 'the range must be on the current surface (run acp_status for the live seq list)',\n )\n }\n ranges.push({\n ...resolved,\n startSeq,\n endSeq,\n startRef,\n endRef,\n summary: range.summary,\n ...(range.topic ?? args.topic) === undefined ? {} : { topic: range.topic ?? args.topic },\n })\n }\n\n // Nothing to do: every requested range was already compressed.\n if (ranges.length === 0) {\n const text = ['Compressed 0 block(s), ~0 tokens reclaimed.', ...alreadyCompressedNotes]\n if (alreadyCompressedNotes.length > 0) {\n text.push(' (all requested ranges were already compressed — decompress a block to recover its originals)')\n }\n return { text: text.join('\\n') }\n }\n\n const applied = env.kernel.applyCompression({\n ranges: ranges.map(({ startRef, endRef, summary, topic }) => ({ startRef, endRef, summary, topic })),\n messages: coreMessages,\n state: turn.state,\n config,\n // Deliberately NOT overriding protectedMessageIds: with the full log the\n // kernel's recent/last-user protection is computed over the same\n // non-block-covered messages as the visible feed, so default behavior is\n // preserved. Any 'Excluded N protected message(s)' warning is surfaced.\n })\n // A kernel error for ONE range must not poison the whole call: the other\n // ranges still created blocks. This matters for issue #18's \"phantom range\"\n // — messages absorbed into an earlier block's effectiveMessageIds (kernel\n // boundary adjustment) but still live on the surface resolve fine but make\n // the kernel throw \"Range contains no compressible messages\". Fail only\n // when NOTHING landed; otherwise land the successes and surface the\n // failures as advisory lines below.\n if (applied.result.errors.length > 0 && applied.result.blocksCreated === 0) {\n return { text: `compress failed: ${applied.result.errors.join('; ')}` }\n }\n env.store.set(session, applied.state)\n if (applied.result.blocksCreated > 0) {\n // Hide this compress call/result after the tool result lands, so the\n // compaction summary never sits between an assistant tool_calls block and\n // its tool response (strict providers reject that sequence).\n env.compressCallIdsToHide?.add(exec.callId)\n }\n\n // Match freshly created kernel blocks to the requested ranges by their\n // range key (the kernel stamps startRef/endRef onto each new block).\n const previousIds = new Set(turn.state.blocks.map((block) => block.blockId))\n const newBlocks = applied.state.blocks.filter((block) => !previousIds.has(block.blockId))\n const blockByRangeKey = new Map(newBlocks.map((block) => [`${block.startRef}::${block.endRef}`, block]))\n // Warnings carry two shapes: range-prefixed (\"Skipped range (a..b) — …\")\n // attributable to a specific range, and free-form (\"Excluded N protected\n // message(s) …\") attributable to the call as a whole.\n const warningByRangeKey = new Map<string, string[]>()\n const freeWarnings: string[] = []\n for (const warning of applied.result.warnings) {\n const match = /^Skipped range \\((.+?)\\.\\.(.+?)\\)/.exec(warning)\n if (match !== null) {\n const key = `${match[1]}::${match[2]}`\n const list = warningByRangeKey.get(key) ?? []\n list.push(warning)\n warningByRangeKey.set(key, list)\n } else {\n freeWarnings.push(warning)\n }\n }\n\n const lines: string[] = []\n let skippedRanges = 0\n for (const range of ranges) {\n const key = `${range.startRef}::${range.endRef}`\n const block = blockByRangeKey.get(key)\n if (block === undefined) {\n // The kernel skipped this range (already compressed / overlapped): no\n // kernel block was created, so no durable transaction is landed — the\n // ledger must never record a block the kernel does not know.\n skippedRanges += 1\n const warnings = warningByRangeKey.get(key) ?? []\n for (const warning of warnings) lines.push(` ${warning}`)\n continue\n }\n // The edges were already balanced above; shadow exactly that span.\n const { start, end } = range\n const shadowed = shadowedSeqsOf(session, start, end)\n // Price the reclaimed tokens in the HOST's token vocabulary (rule 12):\n // prefer the live meter's per-node prices, fall back to the exact mirror.\n // NEVER defaultCountTokens — that overdraws the meter on CJK (issue #54).\n const shadowedTokens = shadowedTokensViaMeter(session, shadowed, agent.ctx)\n const tier = block.tier === 2 || block.tier === 3 ? block.tier : 1\n const parentBlockIds = compactionIdsOfKernelBlocks(session, block.directBlockIds)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: range.summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n tier,\n kernelBlockId: block.blockId,\n ...(range.topic === undefined ? {} : { topic: range.topic }),\n ...(parentBlockIds.length === 0 ? {} : { parentBlockIds }),\n // Record the kernel block's raw coverage so a restarted engine\n // rehydrates the SAME effective messages (a tier-2 block's coverage is\n // its parents' originals, not the checkpoint node).\n directMessageIds: block.directMessageIds,\n effectiveMessageIds: block.effectiveMessageIds,\n })\n const adjusted = start !== range.startSeq || end !== range.endSeq\n // Always report the tier, even tier 1: a silently-downgraded distill\n // (boundary moved off the checkpoint seq → the kernel folds a plain\n // message) must be visible to the model immediately, or the model keeps\n // believing the distillation landed (issue #60, failure mode 2).\n const tierLabel = `, tier ${tier}`\n const note = range.recovered === true\n ? ` (seqs ${range.startSeq}..${range.endSeq} were already shadowed — compressed the live remainder ${start}..${end})`\n : adjusted\n ? ` (adjusted from ${range.startSeq}..${range.endSeq} to balanced edges)`\n : ''\n lines.push(\n ` block ${compactionId.slice(0, 8)}: seqs ${start}..${end}, ${shadowed.length} messages shadowed${tierLabel}${note}`,\n )\n }\n\n const summaryLine = `Compressed ${applied.result.blocksCreated} block(s), ~${applied.result.tokensCompressed} tokens reclaimed.`\n const totalSkipped = skippedRanges + alreadyCompressedNotes.length\n const failedLines = applied.result.errors.map((error) => ` ${error}`)\n const warningLines = [...freeWarnings.map((warning) => ` ${warning}`), ...failedLines, ...alreadyCompressedNotes, ...lines]\n const footer = totalSkipped > 0\n ? ` (${totalSkipped} range(s) skipped or failed — see above)`\n : ''\n return { text: `${summaryLine}\\n${[...warningLines, footer].filter((line) => line !== '').join('\\n')}` }\n}\n\nconst decompressParameters = {\n blockId: { type: 'string' as const, required: true, description: 'Block id: the kernel block ref `bN` shown by acp_status (e.g. b1), or a compaction id / prefix from search_context.' },\n} as const\n\ninterface DecompressArgs {\n blockId: string\n}\n\n/** Resolve a block arg to its durable compaction id: exact `bN` kernel ref\n * first (acp_status shows `bN`), then the compaction-id prefix match that\n * search_context and /acp have always used. The `bN` branch is exact\n * (`/^b\\d+$/` with `$`), so a UUID that happens to start with `b1` cannot be\n * shadowed — full UUIDs and 8-char prefixes never match the anchored regex. */\nfunction resolveBlockId(session: Session, arg: string): string | null {\n const byKernelRef = blockIdOfKernelRef(session, arg)\n if (byKernelRef !== null) return byKernelRef\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const byPrefix = ledger.find((entry) => entry.blockId.startsWith(arg))\n return byPrefix?.blockId ?? null\n}\n\nfunction handleDecompress(_env: ToolEnvironment, rawArgs: DecompressArgs, exec: ToolRunContext): TextOutput {\n const args = unwrapEnvelope<DecompressArgs>(rawArgs)\n const session = requireAgent(exec).session\n const blockId = resolveBlockId(session, args.blockId)\n if (blockId === null) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const block = ledger.find((entry) => entry.blockId === blockId)\n if (block === undefined) {\n return { text: `decompress: block \"${args.blockId}\" not found (see acp_status for the block list)` }\n }\n const parts: string[] = []\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n const event = eventAtOf(session, seq)\n const text = event === undefined ? '' : extractEventText(event)\n if (text.length > 0) parts.push(`[seq ${seq}] ${text}`)\n }\n const tierNote = block.tier > 1 ? ` (tier ${block.tier}, distills ${block.parentBlockIds.length} block(s))` : ''\n return {\n text: `Block ${block.blockId} — ${block.summary}${tierNote}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`,\n }\n}\n\nconst searchParameters = {\n query: { type: 'string' as const, required: true, description: 'Search terms to find inside compressed blocks.' },\n limit: { type: 'integer' as const, description: 'Maximum results (default 5).' },\n} as const\n\ninterface SearchArgs {\n query: string\n limit?: number\n}\n\n/** Event type → kernel message role (drives hybrid role weighting). */\nfunction roleOfEvent(event: SessionEvent): MessageRole | null {\n switch (event.type) {\n case 'user/message': return 'user'\n case 'assistant/message': return 'assistant'\n case 'tool/result': return 'tool'\n default: return null\n }\n}\n\n/**\n * Build the unified SearchDoc[] from the log: one block doc per ledger entry\n * (ref = compactionId, so `decompress({ blockId })` closes the loop) plus one\n * message doc per shadowed ORIGINAL (expanded through distilled parents; each\n * seq is claimed by the earliest/innermost block that covered it, mirroring\n * pi's owner map — decompress on that block recovers the original).\n */\nfunction buildSearchDocs(session: Session): SearchDoc[] {\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const docs: SearchDoc[] = []\n const claimed = new Set<number>()\n for (const block of ledger) {\n docs.push({\n kind: 'block',\n ref: block.blockId,\n text: block.summary,\n title: block.summary.slice(0, 60) || block.blockId,\n blockId: block.blockId,\n tier: block.tier,\n tokens: defaultCountTokens(block.summary),\n })\n for (const seq of expandShadowedSeqs(session, block.blockId)) {\n if (claimed.has(seq)) continue\n claimed.add(seq)\n const event = eventAtOf(session, seq)\n if (event === undefined) continue\n const role = roleOfEvent(event)\n const text = extractEventText(event)\n if (role === null || text.length === 0) continue\n docs.push({\n kind: 'message',\n ref: `seq ${seq}`,\n text,\n title: `${role}: ${text.slice(0, 60)}`,\n role,\n blockId: block.blockId,\n tier: block.tier,\n tokens: defaultCountTokens(text),\n })\n }\n }\n return docs\n}\n\nfunction handleSearch(_env: ToolEnvironment, rawArgs: SearchArgs, exec: ToolRunContext): TextOutput {\n const args = unwrapEnvelope<SearchArgs>(rawArgs)\n const session = requireAgent(exec).session\n if (args.query.trim() === '') return { text: 'search_context: empty query (no matches)' }\n const docs = buildSearchDocs(session)\n // Trust the kernel: hybrid (0.7×BM25 stemmed + 0.3×fuzzy n-gram) is the\n // algorithm contract — no engine-side gate or threshold re-implements\n // search policy. Scores are surfaced so the model can judge a weak hit\n // (fuzzy-only tops out near 0.3).\n const results = searchBlocks(docs, args.query, { limit: args.limit ?? 5, previewLength: 160 })\n if (results.length === 0) return { text: `search_context: no matches for \"${args.query}\"` }\n const lines = results.map((r) => {\n const kind = r.kind === 'block' ? `block ${r.ref}` : `message ${r.ref} (${r.role ?? '?'}, in block ${r.blockId ?? '?'})`\n return ` - ${kind} (score ${r.score.toFixed(2)}): ${r.preview}`\n })\n return {\n text: `Matches for \"${args.query}\":\\n${lines.join('\\n')}\\n\\nDecompress with: decompress({ blockId })`,\n }\n}\n\n/** acp_status drilldown passthrough (kernel buildStatusReport options). All\n * keys optional — no args = overview. `view`/`tool`/`sort`/`limit` only have\n * meaning under `scope:\"uncompressed\"` (`tool` narrows to `view:\"messages\"`;\n * `sort:\"age\"` applies to `scope:\"compressed\"`); the kernel ignores them in\n * overview mode (upstream status-tool docstring documented the same scope).\n * DSH schema compiler: `string` + `enum` supported, no `required: true`\n * anywhere → all optional (schema.js:192-210). */\nconst statusParameters = {\n scope: {\n type: 'string' as const,\n enum: ['compressed', 'uncompressed'] as const,\n description: 'Drilldown scope: \"compressed\" lists compressed blocks, \"uncompressed\" lists visible messages. Omit for the overview.',\n },\n view: {\n type: 'string' as const,\n enum: ['ranges', 'messages'] as const,\n description: 'Drilldown view under scope:\"uncompressed\": \"ranges\" merges visible messages into ranges (default), \"messages\" lists every message.',\n },\n tool: {\n type: 'string' as const,\n description: 'Filter drilldown rows to one tool name (scope:\"uncompressed\" + view:\"messages\" only).',\n },\n sort: {\n type: 'string' as const,\n enum: ['size', 'time', 'tool', 'age'] as const,\n description: 'Row order: size (default, most tokens first), time, tool; \"age\" applies to compressed blocks.',\n },\n limit: {\n type: 'integer' as const,\n description: 'Cap on rows or blocks shown (default 30).',\n },\n}\n\ninterface StatusArgs {\n scope?: 'compressed' | 'uncompressed'\n view?: 'ranges' | 'messages'\n tool?: string\n sort?: 'size' | 'time' | 'tool' | 'age'\n limit?: number\n}\n\n/** A compaction checkpoint summary node (`source.plugin === 'compact'`). These\n * are NOT in any block's `effectiveMessageIds`, so feeding them to\n * `buildStatusReport` would double-count the summary — once as `block.summary`\n * (summaryTokens) and once as a visible text message (totalText). Excluded\n * before status rendering (design §4.2 P1-3). */\nfunction isCheckpointEvent(event: SessionEvent): boolean {\n if (event.type !== 'user/message') return false\n const source = (event.data as { source?: { plugin?: string } }).source\n return source?.plugin === 'compact'\n}\n\nasync function handleStatus(env: ToolEnvironment, rawArgs: StatusArgs, exec: ToolRunContext): Promise<TextOutput> {\n // The model channel may wrap ANY tool's args under `{ arguments: {…} }`;\n // peel it or drilldown params never reach buildStatusReport (live-verified\n // `{\"arguments\":{\"scope\":\"compressed\"}}` silently rendered the overview).\n const args = unwrapEnvelope<StatusArgs>(rawArgs)\n const agent = requireAgent(exec)\n const session = agent.session\n const state = env.store.stateFor(session)\n const surface = surfaceEventsOf(session)\n // One tool-call index for both projections below (P2-5): tool/result\n // toolName/toolCallId are backfilled from the assistant tool-calls.\n const toolNames = buildToolCallIndex(surface)\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surface, toolNames)\n const tokenCount = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const config = kernelConfigFor({ ...env, modelContextLimit: window.limit })\n // Run the same pipeline the context transform runs, so what acp_status\n // reports matches what the model actually receives. The returned turn.state\n // carries the freshly assigned refs; it is NOT persisted — acp_status is a\n // read-only view, and env.store.set would advance the nudge baseline a\n // second time in the same turn (design §6.1 P2-2).\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n // Status messages = visible surface EXCLUDING checkpoint summary nodes (P1-3).\n const statusMessages = eventsToCoreMessages(\n surface.filter((event) => !isCheckpointEvent(event)),\n toolNames,\n )\n // Upstream-aligned: the kernel renders the breakdown (percentages of the\n // VISIBLE total — no window semantics; drilldown scope/view/tool/sort/limit\n // pass through verbatim); the engine only appends the nudge decision line,\n // the DSH Surface anchor, and — in drilldown mode — the mN-vs-seq note.\n const report = buildStatusReport(turn.state, statusMessages, defaultCountTokens, args)\n const lines = [report]\n // Mirror upstream pi (`if (args.scope) return base`): a drilldown request\n // answers with the kernel report alone — the nudge decision line is an\n // overview concept. The Surface anchor stays in ALL modes: it is the model's\n // compressible-ref locator (design P2-1).\n if (args.scope === undefined) {\n const nudge = turn.nudge\n if (nudge !== undefined) {\n lines.push('', `Nudge: ${nudge.shouldInject ? 'ACTIVE' : 'idle'} — ${nudge.reason}`)\n }\n // Issue #60 P2: the model's only route to T2/T3 distillation is a LIVE\n // checkpoint seq — but acp_status (kernel buildStatusReport) is blind to\n // summary nodes (they are excluded as messages, rule 9) and shows only bN\n // refs. Append an engine-side mapping bN → checkpoint seq for ACTIVE\n // blocks (only active blocks are distillable). Appending is the\n // kernel-alignment contract: the kernel owns the report text, the engine\n // owns the wiring — this row is wiring, never a rewrite of the report.\n const checkpointRows = blockRegistry(session)\n .filter((entry) => entry.active && entry.summarySeq !== null)\n .map((entry) => `${entry.kernelBlockId} → seq ${entry.summarySeq}`)\n if (checkpointRows.length > 0) {\n lines.push('', `Checkpoint seqs (active blocks — compress a checkpoint seq to distill it): ${checkpointRows.join(', ')}`)\n }\n }\n lines.push('', `Surface: ${surfaceSummary(session)}`)\n // Drilldown rows carry kernel refs (mN, dense log-order ids) — compress\n // accepts them directly (handleCompress reverse-maps mN → live surface seq\n // via the current turn's messageRefs.byRef; issue #31). The Surface anchor\n // remains the model's compressible-seq locator for nudge-style ranges.\n if (args.scope === 'uncompressed') {\n lines.push('', 'Note: drilldown rows are kernel refs (mN) — feed them straight to compress (auto-mapped to the live surface seq); an unknown mN fails with guidance.')\n }\n return { text: lines.join('\\n') }\n}\n\n/** Build the four ACP model tools bound to one engine. */\nexport function makeTools(env: ToolEnvironment): ToolDefinition[] {\n const prompts = env.prompts ?? DEFAULT_RESOLVED\n return [\n defineTool({\n name: 'compress',\n description: prompts.tools.compress,\n parameters: compressParameters,\n output: textOutput(),\n async execute(args, exec) {\n return handleCompress(env, args as CompressArgs, exec)\n },\n }),\n defineTool({\n name: 'decompress',\n description: prompts.tools.decompress,\n parameters: decompressParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleDecompress(env, args as DecompressArgs, exec))\n },\n }),\n defineTool({\n name: 'search_context',\n description: prompts.tools.searchContext,\n parameters: searchParameters,\n output: textOutput(),\n execute(args, exec) {\n return Promise.resolve(handleSearch(env, args as SearchArgs, exec))\n },\n }),\n defineTool({\n name: 'acp_status',\n description: prompts.tools.acpStatus,\n parameters: statusParameters,\n output: textOutput(),\n execute(args, exec) {\n return handleStatus(env, args as StatusArgs, exec)\n },\n }),\n ]\n}\n","/**\n * Kernel configuration assembly — the DSH counterpart of billion-context-pi's\n * `resolveConfig`: build acp-kernel's `Config` from adapter-level knobs.\n *\n * Defaults are deliberately the acp-kernel `defaultConfig` values (the same\n * defaults billion-context-pi ships: nudge window 45%–75%, emergency 95%,\n * growth ratio 5%, protected last messages 5). Every knob is optional — an\n * omitted value keeps the kernel default, so the behavior matches the Pi\n * adapter exactly unless a deployment opts out.\n *\n * NOTE: `AcpCompactionEngine` (src/index.ts) ships its own engine-level\n * defaults 0.70/0.85 for the two nudge thresholds on top of this layer, so an\n * engine with no explicit config lands on 0.70/0.85, not 0.75/0.95.\n * @module billion-context-dsh/config\n */\n\nimport { defaultConfig, type Config } from 'acp-kernel'\n\n/** The kernel-facing knobs shared by the nudge path and the compress tool. */\nexport interface KernelConfigInput {\n readonly modelContextLimit: number\n /** Nudge window lower bound (usage fraction; validation only — the growth-driven trigger has no percentage floor). Kernel default: 0.45. */\n readonly nudgeMinContextLimitPct?: number\n /** Nudge window upper bound — over-limit guarantee line. Kernel default: 0.75. */\n readonly nudgeMaxContextLimitPct?: number\n /** Emergency nudge threshold (bypasses per-turn dedup). Kernel default: 0.95. */\n readonly nudgeEmergencyThresholdPct?: number\n /** Any other acp-kernel Config override (the billion-context-pi escape hatch). */\n readonly coreOverrides?: Partial<Config>\n}\n\n/**\n * Assemble the kernel config: `defaultConfig(limit)` merged with the optional\n * nudge thresholds (merged into the defaults, never replacing them wholesale)\n * and any additional `coreOverrides`.\n */\nexport function kernelConfigFor(input: KernelConfigInput): Config {\n const nudgePatch: Partial<Config['nudge']> = {}\n if (input.nudgeMinContextLimitPct !== undefined) nudgePatch.minContextLimitPct = input.nudgeMinContextLimitPct\n if (input.nudgeMaxContextLimitPct !== undefined) nudgePatch.maxContextLimitPct = input.nudgeMaxContextLimitPct\n if (input.nudgeEmergencyThresholdPct !== undefined) nudgePatch.emergencyThresholdPct = input.nudgeEmergencyThresholdPct\n\n const overrides: Partial<Config> = { ...input.coreOverrides }\n if (Object.keys(nudgePatch).length > 0 || input.coreOverrides?.nudge) {\n // The engine always ships explicit pct defaults (0.70/0.85, see\n // DEFAULT_CONFIG), so nudgePatch is never empty and the plain replace\n // below used to discard coreOverrides.nudge entirely — the documented\n // escape hatch was unreachable whenever the pct knobs were set. User\n // overrides must land LAST so they win over both kernel defaults and\n // the engine pct values.\n overrides.nudge = {\n ...defaultConfig(input.modelContextLimit).nudge,\n ...nudgePatch,\n ...input.coreOverrides?.nudge,\n }\n }\n return defaultConfig(input.modelContextLimit, overrides)\n}\n","/**\n * M4 — ACP nudge: the kernel's compression recommendation, rendered as an\n * injected user message with a seq-based compressible-range table (D1:\n * \"seq is the ref\" — DSH has no in-memory message rewrite hook, so the model\n * targets ranges by surface seq rather than by <acp> tags).\n * @module billion-context-dsh/nudge\n */\n\nimport {\n COMPRESS_PHILOSOPHY,\n TIER2_DISTILL_RULES,\n TIER3_CONDENSE_RULES,\n defaultCountTokens,\n renderNudgeText,\n type CompressionCore,\n type CoreMessage,\n type NudgeDecision,\n} from 'acp-kernel'\nimport { createUserMessage, type UserMessage } from '@deepseek-ai/dsh-llm'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { AcpStateStore } from './state.ts'\nimport { allLogMessages, eventsToCoreMessages, surfaceEventsOf } from './messages.ts'\nimport { buildCompressibleSeqRanges, findOpenTurn, summarySeqOfKernelBlock, surfaceSummary } from './region.ts'\nimport { sessionEventsOf } from './session-events.ts'\nimport { kernelConfigFor, type KernelConfigInput } from './config.ts'\nimport { DEFAULT_RESOLVED, renderTemplate, type ResolvedPrompts } from './prompts.ts'\n\n/** Kernel inputs the nudge path shares with the compress tool. */\nexport interface NudgeEnvironment extends KernelConfigInput {\n readonly kernel: CompressionCore\n readonly store: AcpStateStore\n /** Resolved prompt templates (optional: falls back to DEFAULT_RESOLVED). */\n readonly prompts?: ResolvedPrompts\n}\n\nexport interface NudgeOutcome {\n readonly message: UserMessage\n readonly emergency: boolean\n}\n\n/**\n * Resolve the best available token count for ACP pressure decisions.\n *\n * Priority chain:\n * 1. `sessionProjections.contextPressure.projectedTokens` — matches the UI's\n * context-occupancy display (includes fixed overhead: system prompt, tool\n * definitions, AGENTS.md, etc.). Provider-anchored; reacts to compaction.\n * 2. `tokenMeter.measure(session).surfaceTokens` — heuristic surface-only\n * estimate (pure conversation messages, no fixed overhead). Falls back\n * when sessionProjections is unavailable or has no provider anchor yet.\n * 3. `defaultCountTokens` character heuristic — last resort for tests and\n * minimal hosts that lack the token-meter service.\n */\nexport function resolveTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n // 1. Prefer sessionProjections.contextPressure.projectedTokens (matches UI).\n const projections = agent.ctx?.get?.('sessionProjections') as\n | { snapshot?: (session: unknown) => { values?: { contextPressure?: { projectedTokens?: number } } } }\n | undefined\n const projected = projections?.snapshot?.(agent.session)?.values?.contextPressure?.projectedTokens\n if (typeof projected === 'number' && projected > 0) return projected\n\n // 2. Fallback to tokenMeter surfaceTokens (heuristic, no fixed overhead).\n const meter = agent.ctx?.get?.('tokenMeter') as\n | { measure?: (session: unknown) => { surfaceTokens?: number } }\n | undefined\n const surface = meter?.measure?.(agent.session)?.surfaceTokens\n if (typeof surface === 'number' && surface > 0) return surface\n\n // 3. Last resort: character heuristic.\n return coreMessages.reduce((sum, message) => sum + defaultCountTokens(message.text ?? ''), 0)\n}\n\n/**\n * Render the compressible-range table as seq refs for the model.\n * Computed directly from the surface (not the kernel's ref map, which can\n * drift and hide large tool results) — see buildCompressibleSeqRanges.\n * UPSTREAM: this self-computation is a labeled workaround for kernel\n * ref-map drift after surface replacements (AGENTS.md rule 11) — drop it and\n * use kernel compressibleRanges once the drift is fixed upstream.\n */\nexport function rangeTable(\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts = DEFAULT_RESOLVED,\n): string {\n const ranges = buildCompressibleSeqRanges(session).slice(0, 6)\n // 零范围:整块省略(保留现状的提前返回与 nudge 尾部 '\\n')。\n if (ranges.length === 0) return ''\n const lines = ranges.map((range) =>\n renderTemplate(prompts.rangeTable.line, {\n start: range.start,\n end: range.end,\n count: range.count,\n tokens: range.tokens,\n toolPct: range.toolPct,\n textPct: 100 - range.toolPct,\n }),\n )\n return [\n // 前导空串元素产生 nudge 中范围表前的唯一空行(§4:parts 层不再加分隔)。\n '',\n renderTemplate(prompts.rangeTable.header, { surface: surfaceSummary(session) }),\n renderTemplate(prompts.rangeTable.title, { count: ranges.length }),\n ...lines,\n prompts.rangeTable.footer,\n ].join('\\n')\n}\n\n/**\n * The token count driving pressure decisions. Prefer `resolveTokenCount` which\n * uses `sessionProjections.contextPressure.projectedTokens` (matches the UI's\n * context-occupancy display, including fixed overhead). Falls back to\n * `tokenMeter.measure(session).surfaceTokens`, then `defaultCountTokens`\n * character heuristic for tests and minimal hosts.\n */\nfunction measuredTokenCount(agent: Agent, coreMessages: CoreMessage[]): number {\n return resolveTokenCount(agent, coreMessages)\n}\n\n/**\n * Decide and build one nudge message for the agent's next pre-step. Returns\n * null when the kernel recommends no nudge or one was already injected for the\n * current turn (emergency nudges always bypass the dedup). Also advances the\n * in-memory kernel state (ref assignment) so the compress tool can resolve\n * seq → mNNNNN refs.\n */\nexport function buildNudge(\n agent: Agent,\n env: NudgeEnvironment,\n lastNudgeTurn: Map<string, number>,\n): NudgeOutcome | null {\n const session = agent.session\n const state = env.store.stateFor(session)\n // Full log for the kernel (so block anchors survive — see handleCompress);\n // the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const tokenCount = measuredTokenCount(agent, surfaceMessages)\n const config = kernelConfigFor(env)\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount })\n env.store.set(session, turn.state)\n\n const nudge = turn.nudge\n if (nudge === undefined || !nudge.shouldInject) return null\n const emergency = nudge.breakdown?.emergencyOverride === 1\n\n const turnNumber = findOpenTurn(sessionEventsOf(session)) ?? 0\n const alreadyShown = !emergency && lastNudgeTurn.get(session.id) === turnNumber\n if (alreadyShown) return null\n lastNudgeTurn.set(session.id, turnNumber)\n\n const text = buildNudgeText(nudge, emergency, session, env.prompts)\n const message = createUserMessage({\n content: [{ type: 'text', text }],\n source: { kind: 'plugin', plugin: 'acp-nudge' },\n })\n return { message, emergency }\n}\n\n/**\n * Render the nudge message text. DEFAULT (no `config.prompts.nudge` override)\n * calls the kernel's own `renderNudgeText` — EFFICIENCY_NOTE/EMERGENCY_HEADER,\n * context breakdown, HOW_TO_COMPRESS_RULES, tier rules, and the batch tip all\n * come from acp-kernel verbatim (the kernel-alignment principle). Only the\n * ref-ID-oriented segments are replaced with our seq-based equivalents,\n * because DSH has no `<acp>` ref tags — see docs/dsh-porting-verification.md:\n * - `rangesStr` (mNNNNN refs) → the surface-seq range table;\n * - the emergency JSON example (startId/endId) → a seq example;\n * - the tier trigger block (block ids bN) → our tier line with surface seqs.\n * When a host overrides any `prompts.nudge` slot, the template path is used so\n * `config.prompts` keeps full control (custom copy wins over kernel defaults).\n */\nexport function buildNudgeText(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts = DEFAULT_RESOLVED,\n): string {\n // A host override of any nudge slot → template rendering (config.prompts\n // keeps its v0.1.9 contract: custom copy wins). Only the pristine default\n // reference reaches the kernel path.\n if (prompts.nudge !== DEFAULT_RESOLVED.nudge) {\n return renderNudgeFromTemplates(nudge, emergency, session, prompts)\n }\n const rendered = renderNudgeText(nudge)\n return adaptKernelNudgeToSeq(rendered.text, nudge, session, prompts)\n}\n\n/**\n * Take the kernel-rendered nudge text and replace its ref-ID-oriented segments\n * with our surface-seq equivalents. Everything else (frame, philosophy,\n * breakdown, HOW_TO_COMPRESS_RULES, tier rules, tip) stays kernel verbatim.\n */\nfunction adaptKernelNudgeToSeq(\n text: string,\n nudge: NudgeDecision,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n let out = text\n // Tier nudges: replace the kernel trigger block (block ids bN) with our tier\n // line carrying surface seqs. The kernel's TIER2/3 rules stay in the tail.\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n out = replaceTierTrigger(out, nudge, session, prompts)\n } else if (out.includes('\"startId\"')) {\n // Emergency nudges: replace the ref-ID JSON example with a seq example.\n out = replaceEmergencyExample(out)\n }\n // Replace the ref-ID range table (mNNNNN) with the surface-seq table.\n // A zero-range table leaves the kernel's own \"[No specific ranges detected]\"\n // notice intact — it is a better prompt than an empty table.\n const seqTable = rangeTable(session, prompts)\n if (seqTable !== '') out = replaceRangesStr(out, seqTable)\n return out\n}\n\n/** Replace the kernel rangesStr segment (`Compressible ranges (N, oldest first):…`) with our seq table. */\nfunction replaceRangesStr(text: string, seqTable: string): string {\n const match = text.match(/\\n\\n(?:Compressible ranges \\(|\\[No specific ranges detected)/)\n if (!match) return text\n const start = match.index!\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\n/)\n const end = next !== null ? start + 2 + next.index! : text.length\n const before = text.slice(0, start)\n const after = text.slice(end)\n // seqTable starts with '\\n' (the range table's leading blank line), so\n // `before` + '\\n' + seqTable yields one blank line before the table.\n return before + '\\n' + seqTable + after\n}\n\n/** Replace the kernel tier trigger segment (`[TIER n …TRIGGER]…Example: compress(…)`) with our tier line. */\nfunction replaceTierTrigger(\n text: string,\n nudge: NudgeDecision,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n const start = text.search(/\\n\\n(?:\\[TIER \\d|\\[EMERGENCY — TIER \\d)/)\n if (start === -1) return text\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\nHOW TO COMPRESS/)\n const end = next !== null ? start + 2 + next.index! : text.length\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n .sort((a, b) => a - b)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n const tierValue = nudge.tier === null ? 2 : nudge.tier\n const tierLine = renderTemplate(prompts.nudge.tier, {\n tier: tierValue,\n count: targets.length,\n prevTier: tierValue - 1,\n tokens,\n seqs: summarySeqs.join(', '),\n firstSeq: summarySeqs[0] ?? 'n/a',\n lastSeq: summarySeqs[summarySeqs.length - 1] ?? 'n/a',\n })\n return text.slice(0, start) + '\\n\\n' + tierLine + text.slice(end)\n}\n\n/** Replace the kernel emergency JSON example (startId/endId) with a seq example. */\nfunction replaceEmergencyExample(text: string): string {\n const start = text.search(/\\n\\n\\{ \"topic\":/)\n if (start === -1) return text\n const rest = text.slice(start + 2)\n const next = rest.match(/\\n\\nCompressible ranges |\\n\\n\\[No specific/)\n const end = next !== null ? start + 2 + next.index! : text.length\n return text.slice(0, start)\n + '\\n\\ncompress({ content: [{ startSeq, endSeq, summary }] }) — use the seqs from the range table above.'\n + text.slice(end)\n}\n\n/**\n * Template rendering path (used only when a host overrides a `prompts.nudge`\n * slot). Kept byte-compatible with the pre-refactor assembly: frame → breakdown\n * → growth → guidance → tier(+rules)/range table → tip.\n */\nfunction renderNudgeFromTemplates(\n nudge: NudgeDecision,\n emergency: boolean,\n session: import('@deepseek-ai/dsh-session').Session,\n prompts: ResolvedPrompts,\n): string {\n // Cap the reported percentage at 100: a broken measurement (e.g. response\n // pressure folded in) must never surface as an absurd \"230%\" to the model.\n const pct = Math.round(Math.min(nudge.contextUsage, 1) * 100)\n const frame = renderTemplate(\n emergency ? prompts.nudge.emergency : prompts.nudge.normal,\n { pct, philosophy: COMPRESS_PHILOSOPHY },\n )\n const parts: string[] = [frame]\n\n // Context breakdown (kernel style, from NudgeDecision.contextBreakdown).\n if (nudge.contextBreakdown) {\n const bd = nudge.contextBreakdown\n const breakdown = renderTemplate(prompts.nudge.breakdown, {\n system: Math.round(bd.system / 1000),\n tool: Math.round(bd.tool / 1000),\n summaries: Math.round(bd.summaries / 1000),\n code: Math.round(bd.code / 1000),\n text: Math.round(bd.text / 1000),\n })\n if (breakdown !== '') parts.push('', breakdown)\n if (bd.growth > 0) {\n const growth = renderTemplate(prompts.nudge.growth, { growth: Math.round(bd.growth / 1000) })\n if (growth !== '') parts.push(growth)\n }\n }\n\n // HOW_TO_COMPRESS_RULES as guidance (kernel puts it in every nudge).\n if (prompts.nudge.guidance !== '') parts.push('', prompts.nudge.guidance)\n\n // Tier line (distillation / condensation suggestion) + tier-specific rules.\n if ((nudge.tier === 2 || nudge.tier === 3) && (nudge.tierTargetBlocks?.length ?? 0) > 0) {\n const targets = nudge.tierTargetBlocks!\n const summarySeqs = targets\n .map((block) => summarySeqOfKernelBlock(session, block.blockId))\n .filter((seq): seq is number => seq !== null)\n .sort((a, b) => a - b)\n const pending = nudge.tier === 2 ? nudge.breakdown?.pendingT2 : nudge.breakdown?.pendingT3\n const tokens = typeof pending === 'number' ? pending : 0\n const tierLine = renderTemplate(prompts.nudge.tier, {\n tier: nudge.tier,\n count: targets.length,\n prevTier: nudge.tier - 1,\n tokens,\n seqs: summarySeqs.join(', '),\n firstSeq: summarySeqs[0] ?? 'n/a',\n lastSeq: summarySeqs[summarySeqs.length - 1] ?? 'n/a',\n })\n if (tierLine !== '') parts.push(tierLine)\n // Tier-specific rules from kernel (TIER2_DISTILL_RULES / TIER3_CONDENSE_RULES).\n const tierRules = nudge.tier === 2 ? TIER2_DISTILL_RULES : TIER3_CONDENSE_RULES\n parts.push('', tierRules)\n } else {\n // Range table for non-tier nudges (DSH-specific: seq-based, not ref-ID-based).\n parts.push(rangeTable(session, prompts))\n }\n\n // Batch-compress tip (from kernel's nudge-text.ts style).\n if (prompts.nudge.tip !== '') parts.push('', prompts.nudge.tip)\n\n return parts.join('\\n')\n}\n","/**\n * M4 — configurable prompt templates: the per-stage model-visible texts\n * (nudge frames, range table, system prompt, tool descriptions) rendered from\n * `config.prompts` templates with named placeholders.\n *\n * Design: docs/configurable-prompts-design.md (v4).\n * - placeholders are `{identifier}` only; literal braces like\n * `compress({ content: [...] })` are left untouched (spaces/commas break the\n * identifier rule);\n * - resolvePrompts merges user overrides over DEFAULT_PROMPTS per key\n * (null/undefined → default, string → override; group-level null → whole\n * group default for YAML hosts) and validates unknown placeholders at\n * construction time (fail-fast, no silent typos);\n * - renderTemplate throws when a known placeholder has no value — callers\n * must provide every value (e.g. tokens via a typeof fallback).\n * @module billion-context-dsh/prompts\n */\n\nimport { COMPRESS_PHILOSOPHY, HOW_TO_COMPRESS_RULES, TIER2_DISTILL_RULES, TIER3_CONDENSE_RULES } from 'acp-kernel'\n\n/** 用户可写值:字符串模板,或 null(= 用默认,等价于不写)。YAML 宿主写 null 是合法输入。 */\nexport type PromptInput = string | null\n\n/** 按组生成\"每键可选、可 null\"的覆盖类型。 */\nexport type PromptOverride<T> = { [K in keyof T]?: PromptInput }\n\nexport interface NudgePrompts {\n /** 普通档首句。占位符:{pct} {philosophy} */\n normal: string\n /** 紧急档首句。占位符:{pct} {philosophy} */\n emergency: string\n /** 指导行(HOW_TO_COMPRESS_RULES)。无占位符 */\n guidance: string\n /** tier 蒸馏行。占位符:{tier} {count} {prevTier} {tokens} {seqs} {firstSeq} {lastSeq} */\n tier: string\n /** 上下文分解。占位符:{system} {tool} {summaries} {code} {text} */\n breakdown: string\n /** 增长行。占位符:{growth} */\n growth: string\n /** 溢出提示。无占位符 */\n tip: string\n}\n\nexport interface RangeTablePrompts {\n /** 表头。占位符:{surface} */\n header: string\n /** 标题。占位符:{count}(表格行数) */\n title: string\n /** 每行。占位符:{start} {end} {count} {tokens} */\n line: string\n /** 表尾调用语法。无占位符 */\n footer: string\n}\n\nexport interface ToolPrompts {\n /** 工具描述(纯文本,无占位符) */\n compress: string\n decompress: string\n searchContext: string\n acpStatus: string\n}\n\nexport interface AcpPrompts {\n readonly nudge?: PromptOverride<NudgePrompts>\n readonly rangeTable?: PromptOverride<RangeTablePrompts>\n readonly tools?: PromptOverride<ToolPrompts>\n /** 整段 system prompt 模板;`{philosophy}` 引用 kernel 的 COMPRESS_PHILOSOPHY */\n readonly systemPrompt?: PromptInput\n}\n\n/** 解析结果 —— 所有字段已填满(纯 string,无 null)、已校验。构造一次,全程复用。 */\nexport interface ResolvedPrompts {\n readonly nudge: NudgePrompts\n readonly rangeTable: RangeTablePrompts\n readonly tools: ToolPrompts\n /** 注意:这是【模板】(含 {philosophy}),不是渲染结果。渲染用 renderSystemPrompt。 */\n readonly systemPromptTemplate: string\n}\n\n/** 每槽允许的占位符名集合(构建期校验用)。 */\nconst NUDGE_ALLOWED: { [K in keyof NudgePrompts]: ReadonlySet<string> } = {\n normal: new Set(['pct', 'philosophy']),\n emergency: new Set(['pct', 'philosophy']),\n guidance: new Set(),\n tier: new Set(['tier', 'count', 'prevTier', 'tokens', 'seqs', 'firstSeq', 'lastSeq']),\n breakdown: new Set(['system', 'tool', 'summaries', 'code', 'text']),\n growth: new Set(['growth']),\n tip: new Set(),\n}\nconst RANGE_TABLE_ALLOWED: { [K in keyof RangeTablePrompts]: ReadonlySet<string> } = {\n header: new Set(['surface']),\n title: new Set(['count']),\n line: new Set(['start', 'end', 'count', 'tokens']),\n footer: new Set(),\n}\nconst TOOLS_ALLOWED: { [K in keyof ToolPrompts]: ReadonlySet<string> } = {\n compress: new Set(),\n decompress: new Set(),\n searchContext: new Set(),\n acpStatus: new Set(),\n}\nconst SYSTEM_ALLOWED = new Set(['philosophy', 'howToCompressRules', 'tier2DistillRules', 'tier3CondenseRules'])\n\n/** 校验单个模板:未知 `{ident}` → throw(带槽位路径)。默认模板开发期已核验,不重扫。 */\nfunction validateTemplate(template: string, allowed: ReadonlySet<string>, path: string): string {\n const re = /\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g\n let match: RegExpExecArray | null\n while ((match = re.exec(template)) !== null) {\n const name = match[1]!\n if (!allowed.has(name)) {\n throw new Error(\n `${path} contains unknown placeholder {${name}} — allowed: ${[...allowed].join(', ') || '(none)'}`,\n )\n }\n }\n return template\n}\n\n/**\n * 纯替换。两个契约:\n * 1. 未知占位符不可能到达这里(构建期已校验);\n * 2. 已知占位符缺值 = 编程错误 → throw(绝不静默渲染空串)。\n */\nexport function renderTemplate(template: string, vars: Record<string, string | number>): string {\n return template.replace(/\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name: string) => {\n const value = vars[name]\n if (value === undefined) {\n throw new Error(\n `renderTemplate: missing value for placeholder {${name}} in template \"${template.slice(0, 60)}…\"`,\n )\n }\n return String(value)\n })\n}\n\n/**\n * 逐键合并:null / undefined → 默认;字符串 → 覆盖默认(不用 spread,\n * 否则 null 会覆盖默认,与\"null = 用默认\"矛盾)。组级 null/undefined →\n * 整组用默认(YAML 宿主可能写 `{ nudge: null }`,W3)。\n */\nfunction mergeGroup<T extends Record<keyof T, string>>(\n defaults: T,\n override: PromptOverride<T> | null | undefined,\n allowed: { [K in keyof T]: ReadonlySet<string> },\n path: string,\n): T {\n if (override == null) return defaults\n const out = {} as { [K in keyof T]: string }\n for (const key of Object.keys(defaults) as Array<keyof T>) {\n const value = override[key]\n out[key] = value === null || value === undefined\n ? defaults[key]\n : validateTemplate(value, allowed[key], `${path}.${String(key)}`)\n }\n return out as T\n}\n\n/**\n * 深合并 + 校验;引擎构造期调用一次,出错即抛(fail-fast)。\n * 未传入时返回 DEFAULT_RESOLVED,零校验重跑。\n */\nexport function resolvePrompts(input?: AcpPrompts): ResolvedPrompts {\n if (input === undefined) return DEFAULT_RESOLVED\n return {\n nudge: mergeGroup(DEFAULT_PROMPTS.nudge, input.nudge, NUDGE_ALLOWED, 'prompts.nudge'),\n rangeTable: mergeGroup(DEFAULT_PROMPTS.rangeTable, input.rangeTable, RANGE_TABLE_ALLOWED, 'prompts.rangeTable'),\n tools: mergeGroup(DEFAULT_PROMPTS.tools, input.tools, TOOLS_ALLOWED, 'prompts.tools'),\n systemPromptTemplate:\n input.systemPrompt === null || input.systemPrompt === undefined\n ? DEFAULT_PROMPTS.systemPromptTemplate\n : validateTemplate(input.systemPrompt, SYSTEM_ALLOWED, 'prompts.systemPrompt'),\n }\n}\n\n/** 渲染 system prompt 模板(注入 kernel 压缩哲学、压缩规则、蒸馏规则)。 */\nexport function renderSystemPrompt(prompts: ResolvedPrompts): string {\n return renderTemplate(prompts.systemPromptTemplate, {\n philosophy: COMPRESS_PHILOSOPHY,\n howToCompressRules: HOW_TO_COMPRESS_RULES,\n tier2DistillRules: TIER2_DISTILL_RULES,\n tier3CondenseRules: TIER3_CONDENSE_RULES,\n })\n}\n\n/**\n * 默认模板 —— 与 v4 之前的硬编码文案逐字节一致\n * (回归锚点见 tests/prompts.test.ts 的硬编码字面量快照)。\n */\nexport const DEFAULT_PROMPTS: ResolvedPrompts = {\n nudge: {\n // 与 kernel nudge-text.ts EFFICIENCY_NOTE 逐字对齐——不含 \"Context usage is at X%\"\n // 陈述(usage 只通过 breakdown 传达);{pct} 仍可用作自定义占位符。\n normal: 'This is an efficiency nudge to compress early and keep context lean — not an overflow warning. A separate, stronger alert will appear if the context is actually full.\\n\\n{philosophy}',\n emergency: '⚠️ Context limit reached — compress now. Prioritize consumed tool outputs.\\n\\n{philosophy}',\n guidance: HOW_TO_COMPRESS_RULES,\n tier: 'Tier {tier}: {count} tier-{prevTier} block(s) distillable ({tokens} tokens) — distill them by compressing their checkpoint seq(s) [seqs {seqs}] as one range: compress({ content: [{ startSeq: {firstSeq}, endSeq: {lastSeq}, summary }] }).',\n breakdown: 'Context breakdown: {system}K system | {tool}K tool | {summaries}K summaries | {code}K code | {text}K text',\n growth: '+{growth}K since last nudge',\n tip: '💡 Compress all ranges in one call (pass multiple content entries: `content: [{...}, {...}]`).',\n },\n rangeTable: {\n header: 'Surface: {surface}',\n title: 'Compressible ranges ({count}, oldest first; exact surface seqs — usable as-is):',\n line: ' - seq {start}..{end} — {count} messages, ~{tokens} tokens [tool {toolPct}% | text {textPct}%]',\n footer: 'Compress with: compress({ content: [{ startSeq, endSeq, summary }] }) — content is an array: batch multiple unrelated segments in one call, each entry its own block. Keep ranges disjoint.\\n'\n + 'Snapshot taken at nudge time: the seqs go stale once the surface moves (a later compress shadows them), so re-run acp_status for fresh refs before compressing.',\n },\n tools: {\n compress: 'Replace older conversation ranges with dense summaries you write. Each message seq is a surface reference. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated ranges in one call (each content entry becomes its own block); keep ranges disjoint. Never compress content the current step is actively using. Compress boundaries are SURFACE SEQS (acp_status Surface: row, latest nudge table) — NOT the block refs (bN, e.g. b1) that acp_status COMPRESSED BLOCKS shows, which are for decompress only. Drilldown mN refs (e.g. m00306) are ALSO accepted as startSeq/endSeq — they are auto-mapped to the live surface seq; an unknown mN (never assigned on the current surface) fails with guidance. Seq refs must come from the CURRENT surface (acp_status or the latest nudge): a span whose edges were shadowed by an earlier compress is auto-remapped to its still-live content, a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance. Good compression moments: stage or subtask completion whose details you have fully consumed and will not re-check, strategy switches, intermediate milestones, and wrapping up failed exploration — when the details are consumed and no longer critical for the task ahead. Before compressing, ask: will I need to re-verify any detail from this range in this task? If yes, keep it live. When you write a summary, turn dead-end exploration into a conclusion (what was tried, why it failed, the next step) — not a blow-by-blow; and keep the summary the ONLY record: self-contained, so a later reader (or you, after decompress) can continue without the original.',\n decompress: 'Recover the original content of a compressed block by its blockId — the kernel block ref `bN` shown by acp_status (e.g. b1), or a compaction id from search_context (read-only; does not unshadow the range).',\n searchContext: 'Search inside compressed blocks (summaries and original content) for information the model no longer sees in context. When a summary lacks a detail you need (exact values, error strings, decisions, verbatim code), SEARCH the compressed blocks FIRST — never guess or reconstruct from memory: search_context(query) locates the right block, then decompress only that block to recover the original.',\n acpStatus: 'Context status: overview of the current context — CONTEXT BREAKDOWN (tool/text/summaries token shares of the visible total), COMPRESSED BLOCKS ledger, and the nudge decision. No args = overview. Percentages are shares of the visible content, not the context window. Note: the block refs in COMPRESSED BLOCKS (bN, e.g. b1) are for decompress; compress uses the Surface: seq range, not bN. Drilldown: pass scope:\"compressed\" for a per-block list, or scope:\"uncompressed\" with view:\"messages\" (every visible message) / view:\"ranges\" (merged ranges); tool filters to one tool name, sort reorders (size/time/tool; age for compressed), limit caps rows (default 30). Drilldown row refs are kernel ids (mN) — feed them straight to compress as startSeq/endSeq (auto-mapped to the live surface seq); bN is for decompress, Surface: seqs also work in compress.',\n },\n systemPromptTemplate: `Active Context Pruning — model-driven context management\n\nYOU decide whether and when to compress context. The nudge is an efficiency notification: when you see one, consider which ranges you have genuinely consumed and could summarise to keep working context lean.\n\n{philosophy}\n\nWHEN TO COMPRESS:\n- A sub-agent or delegated task has returned a large result that you have already extracted the key facts from.\n- Verbose command output (build/test logs, git diff, directory listings) where you have already used the information you need.\n- Exploration that led nowhere.\n- Repeated reads of the same file or repeated status checks once the decision is recorded.\n- Resolved discussion threads where a decision has been captured in summary or in code.\n- Intermediate steps of a completed multi-step task, once the final result is recorded.\n- A task phase has ended — bug hunt complete, root cause found, exploration done, research sprint wrapped.\n\nWHEN NOT TO COMPRESS:\n- Content the current step is actively reading or reasoning about.\n- Important user messages — preserve their exact intent, constraints, and acceptance criteria.\n- Protected tool outputs — hard-excluded from compression ranges, survive intact in visible context.\n- Content you will still need to cite verbatim — in review/audit/verification tasks, keep source reads un-compressed until the final report is written. If you compressed it and now need the exact detail, decompress costs a full round-trip; prefer delaying the compress.\n\n{howToCompressRules}\n\nCompression tools (refs are SURFACE SEQS, not ids):\n- compress: replace one or more seq ranges, each with your own dense summary. Single range: compress({ content: [{ startSeq, endSeq, summary }] }). Batch multiple unrelated segments in one call (each entry becomes its own block): compress({ content: [{ startSeq: 1, endSeq: 5, summary: '...' }, { startSeq: 12, endSeq: 18, summary: '...' }] }). Keep ranges disjoint — overlapping entries in one batch are skipped. Edges are auto-balanced to tool-call/result boundaries; a trailing #callId fragment in a seq is ignored. Seq refs must be on the current surface: seqs from older nudges or earlier compresses go stale as the surface moves, so a stale span is auto-remapped to its still-live remainder (the result reports the adjusted span), a fully compressed span is reported as already compressed, and invented/other-session seqs fail with guidance. The block refs (bN, e.g. b1) in acp_status COMPRESSED BLOCKS are for decompress, NOT compress boundaries.\n- decompress: recover a compressed block's original content, read-only. decompress({ blockId }) — accept the bN ref shown by acp_status (e.g. b1) or a compaction id.\n- search_context: when a summary lacks the details you need (exact values, error strings, decisions, verbatim code), SEARCH the compressed blocks FIRST — never guess or reconstruct from memory; search_context(query) locates the right block, decompress only that block.\n- acp_status: current context usage and the live compressible-range list. Run it right before compressing — the only seqs that never go stale are the ones you just read. Drilldown (scope/view/tool/sort/limit) lists per-message or per-block sizes; drilldown rows are kernel ids (mN) — compress accepts them directly (auto-mapped to the live surface seq).\n\nTiered compression: each compressed block appears on the surface as one summary node. Compressing that node again DISTILLS the block (tier 2): the parent summary folds into your new summary and the original messages are freed. Distilling a tier-2 block yields tier 3. Distill when a summary itself is consumed — decompress on the tier-2 block recovers the full originals.\n\n{tier2DistillRules}\n\n{tier3CondenseRules}\n\nWhen you write a summary, it becomes the ONLY record of that range: keep file paths, signatures, exact values, decisions, and error strings verbatim so a later reader (or you, after decompress) can continue without the original. Never reuse historical seqs — the surface moves as messages land and compress; verify with acp_status.`,\n}\n\n/** 模块级默认缓存:默认参/兜底直接引用,避免每次调用重跑校验。 */\nexport const DEFAULT_RESOLVED: ResolvedPrompts = DEFAULT_PROMPTS\n","/**\n * Auto context-window detection — resolve the model's real context window\n * from the host LLM runtime instead of trusting a hardcoded config default,\n * plus the adapter's per-request output cap (the output reservation subtracted\n * from it so pressure decisions run against the SUSTAINABLE input budget, not\n * the raw window).\n *\n * `agent.ctx.llm` (the cordis `LlmRuntime` service) exposes\n * `resolveModelInfo(provider, model)` →\n * `{ context: { contextWindow }, defaultMaxTokens }` — the exact-route\n * capacity the adapter learned from the provider API (pi-ai reads\n * `context_window`/`context_length` during discovery) plus the output cap it\n * applies when callers omit one. Probing is a standalone capability query —\n * no request is sent.\n * @module billion-context-dsh/window\n */\n\nimport type { Agent } from '@deepseek-ai/dsh-agent'\n\n/** Fallback window when auto-detection is unavailable. Same default as acp-kernel's `defaultConfig`. */\nexport const DEFAULT_CONTEXT_WINDOW = 128000\n\n/** The effective context window plus where it came from. */\nexport interface AcpWindow {\n /** Effective context window in tokens. */\n readonly limit: number\n /** Where the limit came from. */\n readonly source: 'explicit' | 'auto' | 'projection' | 'default'\n /**\n * Route the window was resolved for. 'auto' reports the probed route;\n * 'projection' returns also set it, mirroring agent.options — which can be\n * stale after a mid-session model switch (inert today: windowSourceLabel\n * never reads these fields for the projection source).\n */\n readonly provider?: string\n readonly model?: string\n /**\n * True only when auto-detection was ATTEMPTED and failed (the probe threw or\n * the model API disclosed no window), so the fallback limit is in use. Not\n * set for explicit config, a successful probe, or disabled auto-detection —\n * those must not look like a failure (issue #63: a misconfigured gateway\n * silently fell back to 128K and produced false emergency nudges).\n */\n readonly probeFailed?: boolean\n /**\n * The model's TOTAL context window in tokens, before the output reservation\n * was subtracted. Set only when `outputReserved` is set:\n * `limit = rawLimit - outputReserved`.\n */\n readonly rawLimit?: number\n /**\n * The adapter's per-request output cap (`defaultMaxTokens`) in tokens,\n * subtracted from `rawLimit` to yield `limit` — the output reservation the\n * provider guarantees at the end of the window on every request. Set only\n * when the host discloses it and it is smaller than the raw window.\n */\n readonly outputReserved?: number\n}\n\n/** Human label for an AcpWindow's source (used by /acp status). */\nexport function windowSourceLabel(window: AcpWindow): string {\n if (window.source === 'explicit') return 'configured'\n if (window.source === 'projection') {\n return `session projection current route (auto-refreshes on model switch)`\n }\n if (window.source === 'auto') {\n return `auto-detected from ${window.provider ?? '?'}/${window.model ?? '?'}`\n }\n if (window.probeFailed === true) return 'default (auto-detection failed — restart to re-probe)'\n return 'default (auto-detection unavailable)'\n}\n\n/** The minimal LlmRuntime surface the probe needs (structural — no as any). */\ninterface LlmProbe {\n resolveModelInfo?: (\n provider: string,\n model: string,\n signal?: AbortSignal,\n ) => Promise<{ context?: { contextWindow?: number }; defaultMaxTokens?: number }>\n}\n\n/** The minimal sessionProjections surface the projection source needs. */\ninterface ProjectionProbe {\n snapshot?: (session: unknown) => {\n values?: { contextPressure?: { contextWindow?: number } }\n }\n}\n\n/**\n * Read the live context window from the host session projection\n * (`contextPressure.contextWindow` — the newest recorded route capacity).\n * This tracks the session's CURRENT route: after a mid-session model switch\n * `agent.options.provider/model` stays a stale snapshot, so probing THAT route\n * yields the previous model's window (a 1M-window session read as ~96K →\n * false EMERGENCY nudges at 300%+ usage). The projection is refreshed by the\n * host on every request, so it follows the real model without any config.\n * Returns null when the host exposes no projection or disclosed no window.\n */\nexport function projectedContextWindow(agent: Agent): number | null {\n const projections = agent.ctx?.get?.('sessionProjections') as ProjectionProbe | undefined\n const window = projections?.snapshot?.(agent.session)?.values?.contextPressure?.contextWindow\n if (typeof window === 'number' && Number.isInteger(window) && window > 0) return window\n return null\n}\n\n/** The model window plus the adapter's per-request output cap, in one probe. */\nexport interface ModelWindowProbe {\n /** The model's total context window in tokens, when disclosed. */\n readonly contextWindow: number | null\n /** The adapter's per-request output cap (`defaultMaxTokens`), when disclosed. */\n readonly outputReservation: number | null\n}\n\n/**\n * Probe the model's real context window AND the adapter's per-request output\n * cap in a single `resolveModelInfo` call. The cap is the output reservation\n * the provider guarantees at the end of the window on every request —\n * pressure decisions must run against the SUSTAINABLE input budget (window\n * minus cap), not the raw window: a 96K window with a 16K cap carries at\n * most 80K of input, so the raw denominator understates usage by cap/window\n * (≈17% there — and far worse on short-window models, where the same cap is\n * a quarter or more of the window). Returns nulls — never throws — when the\n * host provides no llm service, discloses nothing, or the probe throws;\n * callers keep the raw-window behavior in those cases.\n */\nexport async function probeModelWindow(\n agent: Agent,\n provider: string,\n model: string,\n): Promise<ModelWindowProbe> {\n const llm = agent.ctx?.get?.('llm') as LlmProbe | undefined\n if (llm?.resolveModelInfo === undefined) return { contextWindow: null, outputReservation: null }\n try {\n const info = await llm.resolveModelInfo(provider, model)\n const window = info?.context?.contextWindow\n const cap = info?.defaultMaxTokens\n return {\n contextWindow: typeof window === 'number' && Number.isInteger(window) && window > 0 ? window : null,\n outputReservation: typeof cap === 'number' && Number.isInteger(cap) && cap > 0 ? cap : null,\n }\n } catch {\n return { contextWindow: null, outputReservation: null }\n }\n}\n\n/**\n * Probe the model's real context window. Returns null when the host provides\n * no llm service, the adapter discloses no window, or the probe throws —\n * callers fall back to DEFAULT_CONTEXT_WINDOW. Never throws.\n */\nexport async function detectContextWindow(\n agent: Agent,\n provider: string,\n model: string,\n): Promise<number | null> {\n return (await probeModelWindow(agent, provider, model)).contextWindow\n}\n","/**\n * M4 — the `/acp` slash command: a human-friendly window into the same\n * machinery the model tools expose (status, one-shot compress, decompress).\n * @module billion-context-dsh/commands\n */\n\nimport type { CommandDefinition } from '@deepseek-ai/dsh-commands'\nimport type { Agent } from '@deepseek-ai/dsh-agent'\nimport { resolveEffectiveWindow, type ToolEnvironment } from './tools.ts'\nimport { resolveTokenCount } from './nudge.ts'\nimport { kernelConfigFor } from './config.ts'\nimport {\n blockIdOfKernelRef,\n blockRefForSummarySeq,\n expandShadowedSeqs,\n rebuildBlockLedger,\n resolveSurfaceRange,\n runCompactionTransaction,\n shadowedSeqsOf,\n} from './region.ts'\nimport { allLogMessages, eventsToCoreMessages, extractEventText, surfaceEventsOf } from './messages.ts'\nimport { shadowedTokensViaMeter } from './host-tokens.ts'\nimport { eventAtOf, sessionEventsOf } from './session-events.ts'\nimport { defaultConfig } from 'acp-kernel'\nimport { windowSourceLabel } from './window.ts'\n\nasync function statusText(env: ToolEnvironment, agent: Agent): Promise<string> {\n const session = agent.session\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const totalTokens = ledger.reduce((sum, block) => sum + block.shadowedTokenCount, 0)\n // Full log for the kernel (so block anchors survive — same input as the\n // nudge path); the measured token count stays a SURFACE measurement.\n const coreMessages = allLogMessages(session)\n const surfaceMessages = eventsToCoreMessages(surfaceEventsOf(session))\n const estimated = resolveTokenCount(agent, surfaceMessages)\n const window = await resolveEffectiveWindow(env, agent)\n const limit = window.limit\n // The window line reveals the output-reservation subtraction: the displayed\n // limit is the SUSTAINABLE input budget the percentage above is measured\n // against, and the raw window stays visible so an operator can see both.\n const windowLine = window.rawLimit !== undefined && window.outputReserved !== undefined\n ? ` context window: ${limit} (raw ${window.rawLimit} − ${window.outputReserved} output reservation; ${windowSourceLabel(window)})`\n : ` context window: ${limit} (${windowSourceLabel(window)})`\n const lines = [\n `ACP status — session ${session.id}`,\n ` blocks: ${ledger.length}`,\n ` tokens compressed: ${totalTokens}`,\n ` estimated context: ${estimated} / ${limit} (${Math.round((estimated / limit) * 100)}%)`,\n windowLine,\n ]\n // A failed probe falls back to the 128K default AND is cached for the\n // process lifetime — the /acp panel must say so explicitly, or the operator\n // can't tell why pressure looks wrong (issue #63: a gateway that disclosed\n // no window read as ~55% of 128K instead of ~18% of the real 1M window).\n if (window.probeFailed === true) {\n lines.push(` ⚠ window auto-detection failed — using the ${limit} fallback (restart to re-probe, or set modelContextLimit explicitly)`)\n }\n // Nudge arbitration on the SAME inputs the nudge path uses — a read-only\n // diagnostic, so run on a cloned state and never write it back to the store.\n const state = structuredClone(env.store.stateFor(session))\n const config = kernelConfigFor({ ...env, modelContextLimit: limit })\n const turn = env.kernel.processTurn({ messages: coreMessages, state, config, tokenCount: estimated })\n const nudge = turn.nudge\n if (nudge !== undefined) {\n const label = nudge.shouldInject ? (nudge.tier !== null ? `ACTIVE [T${nudge.tier}]` : 'ACTIVE') : 'idle'\n lines.push(` nudge: ${label} — ${nudge.reason}`)\n if (!nudge.shouldInject) {\n const maxPct = config.nudge.maxContextLimitPct\n const toNudge = Math.max(0, Math.round(maxPct * limit - estimated))\n lines.push(` next nudge: ~${toNudge.toLocaleString()} tokens to go (usage ${Math.round(nudge.contextUsage * 100)}% → ${Math.round(maxPct * 100)}% line)`)\n }\n }\n // Show ALL blocks, not just the oldest 10: /acp status is how the user\n // confirms recent work survived compression, and the block list is folded\n // in the GUI anyway, so length has no cost (issue #47).\n for (const block of ledger) {\n const tier = block.tier > 1 ? ` [T${block.tier}]` : ''\n lines.push(` - ${block.blockId.slice(0, 8)}${tier}: seqs ${block.start}..${block.end} — ${block.summary.slice(0, 80)}`)\n }\n return lines.join('\\n')\n}\n\nfunction compressText(env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 3) {\n return '/acp compress <startSeq> <endSeq> <summary...>'\n }\n const startSeq = Number(args[0])\n const endSeq = Number(args[1])\n const summary = args.slice(2).join(' ')\n if (!Number.isInteger(startSeq) || !Number.isInteger(endSeq)) {\n return '/acp compress: startSeq and endSeq must be integers'\n }\n const session = agent.session\n const { start, end } = resolveSurfaceRange(session, startSeq, endSeq)\n // A checkpoint summary node can only be distilled through the kernel (the\n // compress tool); /acp compress is a plain T1 range transaction, so refuse\n // rather than silently folding the summary as a message.\n if (blockRefForSummarySeq(session, start) !== null || blockRefForSummarySeq(session, end) !== null) {\n return '/acp compress: the range touches a compressed block summary node — distill it with the compress tool (seq-based batch), not /acp compress'\n }\n // The RESOLVED edges define the claim span, never the raw inputs:\n // resolveSurfaceRange may adjust them to a balanced cut, and a raw edge\n // absent from the surface makes shadowedSeqsOf slice a garbage span that\n // assertProvenance rejects when the transaction lands (AGENTS.md rule 12).\n const shadowed = shadowedSeqsOf(session, start, end)\n // Price the reclaimed tokens in the HOST's token vocabulary (rule 12):\n // prefer the live meter's per-node prices, fall back to the exact mirror.\n const shadowedTokens = shadowedTokensViaMeter(session, shadowed, agent.ctx)\n const { compactionId } = runCompactionTransaction(session, {\n start,\n end,\n shadowedSeqs: shadowed,\n summary: [{ type: 'text', text: summary }],\n shadowedTokenCount: shadowedTokens,\n provider: agent.options.provider ?? '',\n model: agent.options.model ?? '',\n })\n return `Compressed seqs ${start}..${end} (${shadowed.length} messages) as block ${compactionId.slice(0, 8)}`\n}\n\nfunction decompressText(_env: ToolEnvironment, agent: Agent, args: string[]): string {\n if (args.length < 1) return '/acp decompress <blockId>'\n const session = agent.session\n // Accept the kernel block ref (`bN`) the model tool acp_status shows, as\n // well as the compaction-id prefix (same dual-id resolution as the tool).\n const blockId = blockIdOfKernelRef(session, args[0]!)\n const ledger = rebuildBlockLedger(sessionEventsOf(session))\n const block = blockId === null\n ? ledger.find((entry) => entry.blockId.startsWith(args[0]!))\n : ledger.find((entry) => entry.blockId === blockId)\n if (block === undefined) return `block \"${args[0]}\" not found (see /acp status)`\n // Tier-2/3 blocks shadow parent checkpoint nodes: expand to the originals.\n const parts = expandShadowedSeqs(session, block.blockId)\n .map((seq) => extractEventText(eventAtOf(session, seq)!))\n .filter((text) => text.length > 0)\n return `Block ${block.blockId} — ${block.summary}\\n\\n${parts.join('\\n\\n') || '(no recoverable content)'}`\n}\n\n/** Register the /acp command (idempotent per engine). */\nexport function acpCommand(env: ToolEnvironment): CommandDefinition {\n return {\n name: 'acp',\n description:\n 'Active Context Pruning — model-driven context compression. '\n + 'Usage: /acp status | /acp compress <startSeq> <endSeq> <summary> | /acp decompress <blockId>',\n handler: async (invocation) => {\n const raw = invocation.rawInput.trim()\n if (raw === '' || raw === 'status') {\n return { kind: 'success', text: await statusText(env, invocation.agent) }\n }\n if (raw.startsWith('compress')) {\n return { kind: 'success', text: compressText(env, invocation.agent, raw.slice('compress'.length).trim().split(/\\s+/) ) }\n }\n if (raw.startsWith('decompress')) {\n return { kind: 'success', text: decompressText(env, invocation.agent, raw.slice('decompress'.length).trim().split(/\\s+/)) }\n }\n return { kind: 'error', text: `unknown /acp subcommand \"${raw.split(/\\s+/)[0]}\" — use status | compress | decompress` }\n },\n }\n}\n","/**\n * M4 — the ACP system-prompt section (DSH counterpart of billion-context-pi's\n * ACP_SYSTEM_PROMPT): the load-bearing compression guidance lives here, ONCE,\n * instead of being re-sent with every nudge. The nudge itself stays a short,\n * advisory notice — ACP is model-driven, the model decides whether and when\n * to compress.\n *\n * The text is DEFAULT_PROMPTS.systemPromptTemplate rendered with the kernel's\n * COMPRESS_PHILOSOPHY and HOW_TO_COMPRESS_RULES; hosts can override the whole\n * section via `config.prompts.systemPrompt` (see docs/configurable-prompts-design.md).\n * @module billion-context-dsh/system-prompt\n */\n\nimport { DEFAULT_PROMPTS, renderSystemPrompt } from './prompts.ts'\n\nexport const ACP_SYSTEM_PROMPT = renderSystemPrompt(DEFAULT_PROMPTS)\n\n/** System-prompt section order: tool guidance lives in 100–199. */\nexport const ACP_SYSTEM_PROMPT_ORDER = 150\n"],"mappings":"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","names":["require","block","refNum","formatTokens","numericPart","activeBlocks","refNum","stem","countOccurrences","registry","cache","tokens","createUserMessage","createUserMessage","pct","parseBoundary","tierLabel","registry","cap"]}
|