From 22885ed654e6e1e5c8040e18a8c8110be08acb87 Mon Sep 17 00:00:00 2001 From: ework-agent Date: Sun, 6 Sep 2026 22:27:13 +0800 Subject: [PATCH] perf(region): memoize rebuildBlockLedger per snapshot to cut O(B^2*N) rebuilds (#109) --- dist/index.js | 6 +++++- dist/index.js.map | 2 +- src/region.ts | 7 +++++++ tests/region.test.ts | 21 +++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/dist/index.js b/dist/index.js index 20f5d0e..595c76e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4,7 +4,7 @@ import { ManualCompactionError } from "@deepseek-ai/dsh-compaction"; -// node_modules/acp-kernel/dist/index.js +// ../dsh/node_modules/acp-kernel/dist/index.js import { createRequire } from "module"; var REF_WIDTH = 5; var MIN_INDEX = 1; @@ -3065,7 +3065,10 @@ function summarySeqOfCompaction(events, compactionId) { } return null; } +var blockLedgerCache = /* @__PURE__ */ new WeakMap(); function rebuildBlockLedger(events) { + const cached = blockLedgerCache.get(events); + if (cached !== void 0 && cached.len === events.length) return cached.ledger; const ledger = []; for (const event of events) { if (event.type !== "compaction/summary") continue; @@ -3100,6 +3103,7 @@ function rebuildBlockLedger(events) { createdAt: event.time }); } + blockLedgerCache.set(events, { len: events.length, ledger }); return ledger; } function isToolEvent(event) { diff --git a/dist/index.js.map b/dist/index.js.map index c4be97e..237cc96 100644 --- a/dist/index.js.map +++ b/dist/index.js.map @@ -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, 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\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 {\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()\n /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */\n private readonly compressCallIdsToHide = new Set()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map()\n /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */\n private readonly outputReservationCache = new Map()\n\n constructor(ctx: Context, config: Partial = {}) {\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 {\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 {\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 {\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 {\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 {\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 = {};\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 {\n const covered = new Set();\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();\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,\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,\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();\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();\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();\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 = {};\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();\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 = {},\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();\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();\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,\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,\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();\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 | 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 => {\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();\n const activeCallIds = new Set();\n const liveRangeKeysByCallId = new Map>();\n const legacyLiveByCallId = new Set();\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();\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();\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();\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 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 | 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;\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,\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,\n): Set {\n const ids = new Set();\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,\n protectedCallIds: Set,\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();\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 {\n const preserveN = config.preserveRecentMessages;\n const preserveTokens = config.preserveRecentTokens;\n\n const result = new Set();\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,\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 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;\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();\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();\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 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;\n countTokens: (text: string) => number;\n preExistingCoverage: Set;\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();\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(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();\n const removedIds = new Set();\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 {\n const coverage = new Set();\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 {\n const out: Record = {};\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,\n options: ResolvePromptsOptions = {},\n): Prompts {\n const clean: Partial = {};\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n if (typeof value === \"string\") {\n (clean as Record)[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\": \"\", \"endId\": \"\", \"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,\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();\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();\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,\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();\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 = {};\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();\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();\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 {\n const m = new Map();\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;\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;\n}\n\nconst DEFAULT_CAP_CHARS = 8 * 1024 * 1024;\nlet capChars = DEFAULT_CAP_CHARS;\nconst cache = new Map();\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();\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();\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();\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 = {\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;\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): 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 {\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 {\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 = new Set(),\n): number {\n const nodes = session.surface.nodes\n const callIdsBySeq = new Map()\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()\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()\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()\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(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 {\n const open = new Set()\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()\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()\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()\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()\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 {\n const index = new Map()\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): 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): 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\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()\n const parentKernelIds = new Map()\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()\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()\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\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\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 {\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): 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(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): 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 {\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()\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(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()\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(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 {\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(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\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 = {}\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 = { ...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 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,\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 `` 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 = { [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\n readonly rangeTable?: PromptOverride\n readonly tools?: PromptOverride\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 } = {\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 } = {\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 } = {\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, 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 {\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>(\n defaults: T,\n override: PromptOverride | null | undefined,\n allowed: { [K in keyof T]: ReadonlySet },\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) {\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 {\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 {\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 {\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 '\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 '\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 | /acp decompress ',\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","cap"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../../dsh/node_modules/acp-kernel/src/refs.ts","../../dsh/node_modules/acp-kernel/src/state.ts","../../dsh/node_modules/acp-kernel/src/prune.ts","../../dsh/node_modules/acp-kernel/src/sync.ts","../../dsh/node_modules/acp-kernel/src/tokenize.ts","../../dsh/node_modules/acp-kernel/src/config.ts","../../dsh/node_modules/acp-kernel/src/boundaries.ts","../../dsh/node_modules/acp-kernel/src/truncate-tools.ts","../../dsh/node_modules/acp-kernel/src/hide-consumed.ts","../../dsh/node_modules/acp-kernel/src/filter/registry.ts","../../dsh/node_modules/acp-kernel/src/filter/apply.ts","../../dsh/node_modules/acp-kernel/src/render-refs.ts","../../dsh/node_modules/acp-kernel/src/protected.ts","../../dsh/node_modules/acp-kernel/src/tool-pairs.ts","../../dsh/node_modules/acp-kernel/src/reasoning-pairs.ts","../../dsh/node_modules/acp-kernel/src/recommend.ts","../../dsh/node_modules/acp-kernel/src/pipeline.ts","../../dsh/node_modules/acp-kernel/src/compress.ts","../../dsh/node_modules/acp-kernel/src/compression-rules.ts","../../dsh/node_modules/acp-kernel/src/prompts.ts","../../dsh/node_modules/acp-kernel/src/nudge-text.ts","../../dsh/node_modules/acp-kernel/src/decompress.ts","../../dsh/node_modules/acp-kernel/src/report.ts","../../dsh/node_modules/acp-kernel/src/rebuild.ts","../../dsh/node_modules/acp-kernel/src/transform-channel.ts","../../dsh/node_modules/acp-kernel/src/search/stemmer.ts","../../dsh/node_modules/acp-kernel/src/search/tokenizer.ts","../../dsh/node_modules/acp-kernel/src/search/doc-cache.ts","../../dsh/node_modules/acp-kernel/src/search/algorithms/substring.ts","../../dsh/node_modules/acp-kernel/src/search/algorithms/bm25.ts","../../dsh/node_modules/acp-kernel/src/search/algorithms/fuzzy.ts","../../dsh/node_modules/acp-kernel/src/search/algorithms/hybrid.ts","../../dsh/node_modules/acp-kernel/src/search/registry.ts","../../dsh/node_modules/acp-kernel/src/search/types.ts","../../dsh/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, 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\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 {\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()\n /** Successful compress call ids awaiting their tool/result so the pair can be hidden. */\n private readonly compressCallIdsToHide = new Set()\n /** Per provider/model route the resolved window (probe failures cached too). */\n private readonly windowCache = new Map()\n /** Per route the adapter's per-request output cap (the output reservation); null = undisclosed. */\n private readonly outputReservationCache = new Map()\n\n constructor(ctx: Context, config: Partial = {}) {\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 {\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 {\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 {\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 {\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 {\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 = {};\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 {\n const covered = new Set();\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();\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,\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,\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();\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();\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();\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 = {};\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();\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 = {},\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();\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();\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,\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,\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();\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 | 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 => {\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();\n const activeCallIds = new Set();\n const liveRangeKeysByCallId = new Map>();\n const legacyLiveByCallId = new Set();\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();\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();\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();\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 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 | 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;\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,\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,\n): Set {\n const ids = new Set();\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,\n protectedCallIds: Set,\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();\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 {\n const preserveN = config.preserveRecentMessages;\n const preserveTokens = config.preserveRecentTokens;\n\n const result = new Set();\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,\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 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;\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();\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();\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 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;\n countTokens: (text: string) => number;\n preExistingCoverage: Set;\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();\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(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();\n const removedIds = new Set();\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 {\n const coverage = new Set();\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 {\n const out: Record = {};\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,\n options: ResolvePromptsOptions = {},\n): Prompts {\n const clean: Partial = {};\n if (overrides) {\n for (const [key, value] of Object.entries(overrides)) {\n if (typeof value === \"string\") {\n (clean as Record)[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\": \"\", \"endId\": \"\", \"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,\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();\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();\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,\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();\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 = {};\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();\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();\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 {\n const m = new Map();\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;\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;\n}\n\nconst DEFAULT_CAP_CHARS = 8 * 1024 * 1024;\nlet capChars = DEFAULT_CAP_CHARS;\nconst cache = new Map();\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();\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();\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();\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 = {\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;\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): 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 {\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 {\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// Memoized on the append-only snapshot array (stable within one tool call, see\n// sessionEventsOf): identity+length never goes stale; avoids O(B^2*N) rebuilds (#109).\nconst blockLedgerCache = new WeakMap()\n\n/** Rebuild the block ledger from the durable log (no kernel state needed). */\nexport function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] {\n const cached = blockLedgerCache.get(events)\n if (cached !== undefined && cached.len === events.length) return cached.ledger\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 blockLedgerCache.set(events, { len: events.length, ledger })\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 = new Set(),\n): number {\n const nodes = session.surface.nodes\n const callIdsBySeq = new Map()\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()\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()\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()\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(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 {\n const open = new Set()\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()\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()\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()\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()\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 {\n const index = new Map()\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): 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): 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\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()\n const parentKernelIds = new Map()\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()\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()\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\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\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 {\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): 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(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): 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 {\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()\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(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()\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(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 {\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(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\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 = {}\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 = { ...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 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,\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 `` 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 = { [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\n readonly rangeTable?: PromptOverride\n readonly tools?: PromptOverride\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 } = {\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 } = {\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 } = {\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, 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 {\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>(\n defaults: T,\n override: PromptOverride | null | undefined,\n allowed: { [K in keyof T]: ReadonlySet },\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) {\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 {\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 {\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 {\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 '\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 '\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 | /acp decompress ',\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":";AA6BA;AAAA,EACE;AAAA,EACA;AAAA,OAKK;;;AKpCP,SAAS,qBAAqB;AJE9B,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,YAAY;AAClB,IAAM,cAAc;AAEb,IAAM,cAAc;AAMpB,SAAS,WAAW,OAAuB;AAChD,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,QAAQ,aAAa,QAAQ,WAAW;AACtE,UAAM,IAAI;MACR,4BAA4B,KAAK,aAAa,SAAS,IAAI,SAAS;IACtE;EACF;AACA,SAAO,IAAI,OAAO,KAAK,EAAE,SAAS,WAAW,GAAG,CAAC;AACnD;AAEO,SAAS,WAAW,KAA4B;AACrD,QAAM,QAAQ,YAAY,KAAK,IAAI,KAAK,EAAE,YAAY,CAAC;AACvD,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC7B,MAAI,QAAQ,aAAa,QAAQ,UAAW,QAAO;AACnD,SAAO;AACT;AAEO,SAAS,UAAU,KAAoB,OAA8B;AAC1E,SAAO,IAAI,MAAM,KAAK,KAAK;AAC7B;AAmBO,SAAS,WACd,UACA,SACkB;AAClB,QAAM,MAAqB;IACzB,OAAO,EAAE,GAAG,QAAQ,SAAS,MAAM;IACnC,OAAO,EAAE,GAAG,QAAQ,SAAS,MAAM;EACrC;AACA,MAAI,SACF,OAAO,UAAU,QAAQ,SAAS,KAAK,QAAQ,aAAa,YACxD,QAAQ,YACR;AACN,MAAI,gBAAgB;AAEpB,aAAW,WAAW,UAAU;AAC9B,QAAI,CAAC,QAAQ,MAAM,QAAQ,aAAa,OAAO,EAAG;AAElD,QAAI,IAAI,MAAM,QAAQ,EAAE,EAAG;AAE3B,QAAI,QAAQ,cAAc,OAAO,GAAG;AAClC,UAAI,MAAM,QAAQ,EAAE,IAAI;AACxB;IACF;AAEA,UAAM,MAAM,gBAAgB,KAAK,MAAM;AACvC,aAAS,IAAI,QAAQ;AACrB,QAAI,MAAM,QAAQ,EAAE,IAAI,IAAI;AAC5B,QAAI,MAAM,IAAI,IAAI,IAAI,QAAQ;AAC9B;EACF;AAEA,SAAO,EAAE,KAAK,WAAW,QAAQ,cAAc;AACjD;AAEA,SAAS,gBACP,KACA,OACiC;AACjC,MAAI,YAAY,KAAK,IAAI,OAAO,SAAS;AACzC,SAAO,aAAa,WAAW;AAC7B,UAAM,OAAO,WAAW,SAAS;AACjC,QAAI,CAAC,IAAI,MAAM,IAAI,GAAG;AACpB,aAAO,EAAE,MAAM,OAAO,UAAU;IAClC;AACA;EACF;AACA,QAAM,IAAI;IACR,kDAAkD,WAAW,SAAS,CAAC;EACzE;AACF;AAUO,SAAS,iBAAiB,KAA4B;AAC3D,MAAI,UAAU;AACd,aAAW,OAAO,OAAO,OAAO,IAAI,KAAK,GAAG;AAC1C,UAAM,QAAQ,QAAQ,cAAc,OAAO,WAAW,GAAG;AACzD,QAAI,UAAU,QAAQ,QAAQ,QAAS,WAAU;EACnD;AACA,SAAO;AACT;ACnHO,SAAS,qBAAuC;AACrD,SAAO;IACL,QAAQ,CAAC;IACT,aAAa,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,EAAE;IACpC,eAAe,CAAC;IAChB,OAAO;MACL,2BAA2B;MAC3B,sBAAsB;MACtB,gBAAgB;MAChB,SAAS,CAAC;MACV,iBAAiB,CAAC;IACpB;IACA,OAAO,EAAE,kBAAkB,GAAG,kBAAkB,EAAE;IAClD,aAAa;IACb,WAAW;EACb;AACF;AAEO,SAAS,gBAAgB,OAAiC;AAC/D,QAAM,KAAK,MAAM;AACjB,QAAM,cAAc,KAAK,IAAI,GAAG,EAAE,IAAI;AACtC,SAAO,IAAI,EAAE;AACf;AAEO,SAAS,cAAc,OAAiC;AAC7D,QAAM,KAAK,MAAM;AACjB,QAAM,YAAY,KAAK,IAAI,GAAG,EAAE,IAAI;AACpC,SAAO,IAAI,EAAE;AACf;AAEO,SAAS,UACd,OACA,SAC8B;AAC9B,SAAO,MAAM,OAAO,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AAC/D;AAEO,SAAS,aAAa,OAA6C;AACxE,SAAO,MAAM,OAAO,OAAO,CAAC,UAAU,MAAM,MAAM;AACpD;AAEO,SAAS,kBAAkB,OAAsC;AACtE,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,MAAM,QAAQ;AAChC,QAAI,CAAC,MAAM,OAAQ;AACnB,eAAW,MAAM,MAAM,oBAAqB,SAAQ,IAAI,EAAE;EAC5D;AACA,SAAO;AACT;AAUO,SAAS,gBACd,OACA,oBACM;AACN,aAAW,SAAS,MAAM,QAAQ;AAChC,QAAI,CAAC,MAAM,OAAQ;AACnB,UAAM,iBAAiB;AACvB,QAAI,MAAM,iBAAiB,oBAAoB;AAC7C,YAAM,aAAa;IACrB;EACF;AACF;ACpEO,IAAM,iBAAiB;AAMvB,SAAS,MACd,UACA,OACA,UAAwB,CAAC,GACV;AACf,QAAM,UAAU,kBAAkB,KAAK;AACvC,MAAI,QAAQ,SAAS,EAAG,QAAO,CAAC,GAAG,QAAQ;AAE3C,QAAM,SAAS,QAAQ,mBAAmB;AAC1C,QAAM,iBAAiB,SAAS;IAC9B,CAAC,YAAY,QAAQ,SAAS;EAChC;AAEA,QAAM,YAAY,oBAAI,IAAoB;AAC1C,WAAS,QAAQ,CAAC,SAAS,UAAU,UAAU,IAAI,QAAQ,IAAI,KAAK,CAAC;AAErE,QAAM,UAAU,SAAS,sBAAsB,OAAO,SAAS,IAAI,CAAC;AAEpE,SAAO;IACL;MACE;QACE,gBAAgB,UAAU,SAAS,gBAAgB,OAAO;MAC5D;IACF;EACF;AACF;AASA,SAAS,sBACP,OACA,WACiB;AACjB,QAAM,UAA2B,CAAC;AAClC,aAAW,SAAS,aAAa,KAAK,GAAG;AACvC,QAAI,WAA0B;AAC9B,eAAW,MAAM,MAAM,qBAAqB;AAC1C,YAAM,QAAQ,UAAU,IAAI,EAAE;AAC9B,UAAI,UAAU,WAAc,aAAa,QAAQ,QAAQ,WAAW;AAClE,mBAAW;MACb;IACF;AACA,YAAQ,KAAK;MACX,SAAS,MAAM;MACf,SAAS,MAAM;MACf,OAAO,MAAM;MACb,UAAU,YAAY;IACxB,CAAC;EACH;AACA,UAAQ,KAAK,CAAC,MAAM,UAAU,KAAK,WAAW,MAAM,QAAQ;AAC5D,SAAO;AACT;AAEA,SAAS,gBACP,UACA,SACA,gBACA,SACe;AACf,QAAM,SAAwB,CAAC;AAC/B,QAAM,UAAU,CAAC,GAAG,OAAO;AAE3B,WAAS,QAAQ,GAAG,QAAQ,SAAS,QAAQ,SAAS;AACpD,WAAO,QAAQ,SAAS,KAAK,QAAQ,CAAC,EAAG,aAAa,OAAO;AAC3D,aAAO,KAAK,cAAc,QAAQ,MAAM,CAAE,CAAC;IAC7C;AACA,QAAI,UAAU,kBAAkB,kBAAkB,GAAG;AACnD,aAAO,KAAK,SAAS,KAAK,CAAE;AAC5B;IACF;AACA,QAAI,QAAQ,IAAI,SAAS,KAAK,EAAG,EAAE,EAAG;AACtC,WAAO,KAAK,SAAS,KAAK,CAAE;EAC9B;AAEA,SAAO,QAAQ,SAAS,GAAG;AACzB,WAAO,KAAK,cAAc,QAAQ,MAAM,CAAE,CAAC;EAC7C;AAEA,SAAO;AACT;AAEA,SAAS,cAAc,QAAoC;AACzD,QAAM,OAAO,OAAO,QAAQ,KAAK;AACjC,QAAM,YAAY,OAAO,QACrB,GAAG,cAAc,WAAM,OAAO,KAAK,KACnC;AACJ,QAAM,OAAO,KAAK,WAAW,IAAI,YAAY,GAAG,SAAS;EAAK,IAAI;AAClE,SAAO;IACL,IAAI,eAAe,OAAO,OAAO;IACjC,MAAM;IACN,aAAa;IACb;EACF;AACF;AAEA,SAAS,yBAAyB,UAAwC;AACxE,QAAM,eAAe,oBAAI,IAAY;AACrC,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,gBAAgB,eAAe,EAAE,YAAY;AACjD,mBAAa,IAAI,EAAE,UAAU;IAC/B;EACF;AACA,SAAO,SAAS;IACd,CAAC,MACC,EAAE,gBAAgB,iBAClB,CAAC,EAAE,cACH,aAAa,IAAI,EAAE,UAAU;EACjC;AACF;AAEA,SAAS,uBAAuB,UAAwC;AACtE,QAAM,iBAAiB,oBAAI,IAAY;AACvC,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,gBAAgB,iBAAiB,EAAE,YAAY;AACnD,qBAAe,IAAI,EAAE,UAAU;IACjC;EACF;AACA,SAAO,SAAS;IACd,CAAC,MACC,EAAE,gBAAgB,eAClB,CAAC,EAAE,cACH,EAAE,aAAa,cACf,eAAe,IAAI,EAAE,UAAU;EACnC;AACF;AAcA,SAAS,uBAAuB,UAAwC;AACtE,QAAM,OAAO,oBAAI,IAAY;AAC7B,WAAS,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;AACxC,QAAI,KAAK,IAAI,CAAC,EAAG;AACjB,QAAI,SAAS,CAAC,EAAG,gBAAgB,YAAa;AAC9C,QAAI,IAAI;AACR,WACE,IAAI,IAAI,SAAS,UACjB,SAAS,IAAI,CAAC,EAAG,gBAAgB,aACjC;AACA;IACF;AACA,UAAM,YAAY,SAAS,IAAI,CAAC;AAChC,UAAM,eACJ,cAAc,UACd,UAAU,SAAS,gBAClB,UAAU,gBAAgB,UACzB,UAAU,gBAAgB;AAC9B,QAAI,CAAC,cAAc;AACjB,eAAS,IAAI,GAAG,KAAK,GAAG,IAAK,MAAK,IAAI,CAAC;IACzC;EACF;AACA,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,SAAO,SAAS,OAAO,CAAC,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;AAC/C;ACzKO,SAAS,WACd,UACA,OACY;AACZ,QAAM,aAAa,IAAI,IAAI,SAAS,IAAI,CAAC,YAAY,QAAQ,EAAE,CAAC;AAChE,QAAM,cAAwB,CAAC;AAK/B,QAAM,SAA2B;IAC/B,QAAQ,MAAM,OAAO,IAAI,CAAC,WAAW;MACnC,GAAG;MACH,kBAAkB,CAAC,GAAG,MAAM,gBAAgB;MAC5C,qBAAqB,CAAC,GAAG,MAAM,mBAAmB;MAClD,gBAAgB,CAAC,GAAG,MAAM,cAAc;IAC1C,EAAE;IACF,aAAa;MACX,OAAO,EAAE,GAAG,MAAM,YAAY,MAAM;MACpC,OAAO,EAAE,GAAG,MAAM,YAAY,MAAM;IACtC;;IAEA,eAAe,EAAE,GAAI,MAAM,iBAAiB,CAAC,EAAG;IAChD,OAAO,EAAE,GAAG,MAAM,OAAO,SAAS,EAAE,GAAG,MAAM,MAAM,QAAQ,EAAE;IAC7D,OAAO,EAAE,GAAG,MAAM,MAAM;IACxB,aAAa,MAAM;IACnB,WAAW,MAAM;EACnB;AAKA,QAAM,WAAW,IAAI;IACnB,SACG,IAAI,CAAC,MAAM,OAAO,YAAY,MAAM,EAAE,EAAE,CAAC,EACzC,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;EACrD;AACA,MAAI,OAAO,KAAK,OAAO,aAAa,EAAE,WAAW,SAAS,MAAM;AAC9D,UAAM,SAAiC,CAAC;AACxC,eAAW,CAAC,KAAK,CAAC,KAAK,OAAO,QAAQ,OAAO,aAAa,GAAG;AAC3D,UAAI,SAAS,IAAI,GAAG,EAAG,QAAO,GAAG,IAAI;IACvC;AACA,WAAO,gBAAgB;EACzB;AAEA,QAAM,mBAAmB,oBAAI,IAAY;AACzC,aAAW,SAAS,OAAO,QAAQ;AACjC,eAAW,cAAc,MAAM,gBAAgB;AAC7C,uBAAiB,IAAI,UAAU;IACjC;EACF;AAEA,aAAW,SAAS,OAAO,QAAQ;AACjC,QAAI,iBAAiB,IAAI,MAAM,OAAO,GAAG;AACvC,YAAM,SAAS;AACf;IACF;AACA,UAAM,SAAS;AACf,UAAM,eAAe,MAAM,oBAAoB;MAAK,CAAC,OACnD,WAAW,IAAI,EAAE;IACnB;AACA,QAAI,CAAC,cAAc;AACjB,YAAM,SAAS;AACf,kBAAY,KAAK,MAAM,OAAO;IAChC;EACF;AAEA,SAAO,EAAE,OAAO,QAAQ,YAAY;AACtC;ACzEA,IAAMA,WAAU,cAAc,YAAY,GAAG;AAEtC,SAAS,mBAAmB,MAAsB;AACvD,MAAI,CAAC,KAAM,QAAO;AAIlB,QAAM,MAAM,KAAK,MAAM,4CAA4C;AACnE,QAAM,WAAW,KAAK,UAAU;AAChC,SAAO,WAAW,KAAK,MAAM,KAAK,SAAS,YAAY,CAAC;AAC1D;ACVO,SAAS,cACd,mBACA,YAA6B,CAAC,GACtB;AACR,QAAM,OAAe;IACnB,OAAO,EAAE,SAAS,MAAM,cAAc,GAAG,cAAc,GAAG;IAC1D,OAAO;MACL,oBAAoB;MACpB,oBAAoB;MACpB,WAAW;MACX,oBAAoB;MACpB,OAAO;MACP,aAAa;MACb,aAAa;MACb,WAAW;MACX,gBAAgB;MAChB,gBAAgB;MAChB,uBAAuB;MACvB,uBAAuB;IACzB;IACA,oBAAoB;IACpB,UAAU,EAAE,WAAW,KAAK;IAC5B,UAAU;MACR,kBAAkB;MAClB,kBAAkB;MAClB,kBAAkB;IACpB;IACA,gBAAgB,CAAC;IACjB,wBAAwB;IACxB,sBAAsB;IACtB;EACF;AACA,SAAO;IACL,GAAG;IACH,GAAG;IACH,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,UAAU,MAAM;IAC3C,OAAO,EAAE,GAAG,KAAK,OAAO,GAAG,UAAU,MAAM;IAC3C,UAAU,EAAE,GAAG,KAAK,UAAU,GAAG,UAAU,SAAS;IACpD,UAAU,EAAE,GAAG,KAAK,UAAU,GAAG,UAAU,SAAS;EACtD;AACF;AAEO,SAAS,eAAe,QAA0B;AACvD,QAAM,SAAmB,CAAC;AAC1B,MACE,CAAC,OAAO,SAAS,OAAO,iBAAiB,KACzC,OAAO,qBAAqB,GAC5B;AACA,WAAO,KAAK,6CAA6C;EAC3D;AACA,MAAI,OAAO,MAAM,qBAAqB,OAAO,MAAM,oBAAoB;AACrE,WAAO;MACL;IACF;EACF;AACA,MAAI,OAAO,MAAM,qBAAqB,OAAO,MAAM,uBAAuB;AACxE,WAAO;MACL;IACF;EACF;AACA,MAAI,OAAO,qBAAqB,GAAG;AACjC,WAAO,KAAK,iCAAiC;EAC/C;AACA,MAAI,OAAO,SAAS,aAAa,KAAK,OAAO,SAAS,YAAY,GAAG;AACnE,WAAO,KAAK,sCAAsC;EACpD;AACA,aAAW,QAAQ,CAAC,OAAO,MAAM,cAAc,OAAO,MAAM,YAAY,GAAG;AACzE,QAAI,OAAO,EAAG,QAAO,KAAK,4BAA4B;EACxD;AACA,MAAI,OAAO,MAAM,gBAAgB,OAAO,MAAM,cAAc;AAC1D,WAAO,KAAK,4DAA4D;EAC1E;AACA,SAAO;AACT;AC5DA,IAAM,sBAAsB;AAC5B,IAAM,oBAAoB;AAEnB,SAAS,cAAc,KAAoC;AAChE,QAAM,aAAa,IAAI,KAAK,EAAE,YAAY;AAC1C,QAAM,eAAe,oBAAoB,KAAK,UAAU;AACxD,MAAI,cAAc;AAChB,UAAM,YAAY,OAAO,aAAa,CAAC,CAAC;AACxC,QAAI,aAAa,KAAK,aAAa,OAAO;AACxC,aAAO,EAAE,MAAM,WAAW,WAAW,KAAK,WAAW;IACvD;EACF;AACA,QAAM,aAAa,kBAAkB,KAAK,UAAU;AACpD,MAAI,YAAY;AACd,UAAM,YAAY,OAAO,WAAW,CAAC,CAAC;AACtC,QAAI,aAAa,EAAG,QAAO,EAAE,MAAM,SAAS,WAAW,KAAK,WAAW;EACzE;AACA,SAAO;AACT;AASO,IAAM,wBAAN,cAAoC,MAAM;EACtC,OAAO;EACP;EACA;EAET,YACE,MACA,UACA,SACA;AACA,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,WAAW;EAClB;AACF;AAkBO,SAAS,kBACd,OACe;AACf,QAAM,QAAQ,cAAc,MAAM,QAAQ;AAC1C,QAAM,MAAM,cAAc,MAAM,MAAM;AACtC,MAAI,CAAC,SAAS,CAAC,KAAK;AAClB,UAAM,IAAI;MACR,qCAAqC,MAAM,QAAQ,aAAa,MAAM,MAAM;IAC9E;EACF;AAEA,QAAM,eAAe,oBAAI,IAAoB;AAC7C,QAAM,SAAS;IAAQ,CAAC,SAAS,UAC/B,aAAa,IAAI,QAAQ,IAAI,KAAK;EACpC;AAEA,MAAI,aAAa,mBAAmB,OAAO,MAAM,OAAO,cAAc,OAAO;AAC7E,MAAI,WAAW,mBAAmB,KAAK,MAAM,OAAO,cAAc,KAAK;AAEvE,MAAI,aAAa,UAAU;AACzB,KAAC,YAAY,QAAQ,IAAI,CAAC,UAAU,UAAU;EAChD;AAEA,QAAM,aAAuB,CAAC;AAC9B,WAAS,QAAQ,YAAY,SAAS,UAAU,SAAS;AACvD,UAAM,UAAU,MAAM,SAAS,KAAK;AACpC,QAAI,QAAS,YAAW,KAAK,QAAQ,EAAE;EACzC;AAEA,QAAM,eACJ,MAAM,SAAS,WAAW,IAAI,SAAS,UAAU,UAAU;AAE7D,QAAM,iBAA2B,CAAC;AAClC,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,SAAS,aAAa,MAAM,KAAK,GAAG;AAC7C,UAAM,SAAS,mBAAmB,MAAM,qBAAqB,YAAY;AACzE,QAAI,WAAW,QAAQ,UAAU,cAAc,UAAU,UAAU;AACjE,UAAI,CAAC,WAAW,IAAI,MAAM,OAAO,GAAG;AAClC,mBAAW,IAAI,MAAM,OAAO;AAC5B,uBAAe,KAAK,MAAM,OAAO;MACnC;IACF;EACF;AAEA,QAAM,gBAA0B,CAAC;AAEjC,SAAO;IACL;IACA;IACA;IACA;IACA;IACA;EACF;AACF;AAEA,SAAS,mBACP,UACA,OACA,cACA,UACQ;AACR,QAAM,QAAQ,aAAa,UAAU,YAAY;AACjD,MAAI,SAAS,SAAS,WAAW;AAC/B,UAAM,QACJ,MAAM,YAAY,MAAM,SAAS,GAAG,KACpC,MAAM,YAAY,MAAM,gBAAgB,SAAS,SAAS,CAAC;AAC7D,QAAI,CAAC,OAAO;AACV,YAAM,IAAI;QACR;QACA;QACA,GAAG,KAAK,KAAK,SAAS,GAAG;MAC3B;IACF;AACA,UAAM,QAAQ,aAAa,IAAI,KAAK;AACpC,QAAI,UAAU,QAAW;AACvB,YAAM,IAAI;QACR;QACA;QACA,GAAG,KAAK,KAAK,SAAS,GAAG;MAC3B;IACF;AACA,WAAO;EACT;AAEA,QAAM,QAAQ,UAAU,OAAO,IAAI,SAAS,SAAS,EAAE;AACvD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI;MACR;MACA;MACA,GAAG,KAAK,MAAM,SAAS,SAAS;IAClC;EACF;AACA,MAAI,CAAC,MAAM,QAAQ;AACjB,UAAM,IAAI;MACR;MACA;MACA,GAAG,KAAK,MAAM,SAAS,SAAS;IAClC;EACF;AACA,QAAM,SAAS,mBAAmB,MAAM,qBAAqB,YAAY;AACzE,MAAI,WAAW,MAAM;AACnB,UAAM,IAAI;MACR;MACA;MACA,GAAG,KAAK,MAAM,SAAS,SAAS;IAClC;EACF;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAuB;AAC9C,SAAO,IAAI,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC;AAC3C;AAEO,SAAS,mBACd,KACA,cACe;AACf,MAAI,WAA0B;AAC9B,aAAW,MAAM,KAAK;AACpB,UAAM,QAAQ,aAAa,IAAI,EAAE;AACjC,QAAI,UAAU,WAAc,aAAa,QAAQ,QAAQ,WAAW;AAClE,iBAAW;IACb;EACF;AACA,SAAO;AACT;AC5LA,IAAM,oBAAoB;AAC1B,IAAM,WAAW;EACb,iBAAiB;EACjB,iBAAiB;EACjB,iBAAiB;EACjB,uBAAuB;AAC3B;AAEO,SAAS,yBACZ,UACA,YACA,QACA,aACA,UAA2B,CAAC,GACd;AACd,QAAM,OAAO,EAAE,GAAG,UAAU,GAAG,QAAQ;AACvC,MAAI,OAAO,qBAAqB,EAAG,QAAO,EAAE,UAAU,gBAAgB,GAAG,aAAa,EAAE;AAExF,QAAM,YAAY,OAAO,SAAS,YAAY,OAAO;AACrD,MAAI,aAAa,UAAW,QAAO,EAAE,UAAU,gBAAgB,GAAG,aAAa,EAAE;AAEjF,QAAM,iBAAiB,SAAS,SAAS,KAAK;AAC9C,QAAM,aAAuD,CAAC;AAE9D,WAAS,QAAQ,GAAG,QAAQ,SAAS,QAAQ,SAAS;AAClD,QAAI,SAAS,eAAgB;AAC7B,UAAM,UAAU,SAAS,KAAK;AAC9B,QAAI,QAAQ,gBAAgB,cAAe;AAC3C,UAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAI,KAAK,WAAW,KAAK,KAAK,SAAS,iBAAiB,EAAG;AAC3D,UAAM,SAAS,YAAY,IAAI;AAC/B,QAAI,SAAS,KAAK,gBAAiB;AACnC,eAAW,KAAK,EAAE,OAAO,OAAO,CAAC;EACrC;AAEA,MAAI,WAAW,WAAW,EAAG,QAAO,EAAE,UAAU,gBAAgB,GAAG,aAAa,EAAE;AAClF,aAAW,KAAK,CAAC,MAAM,UAAU,MAAM,SAAS,KAAK,MAAM;AAE3D,QAAM,eAAe,YAAY;AACjC,MAAI,cAAc;AAClB,QAAM,QAAQ,oBAAI,IAAoB;AACtC,MAAI,iBAAiB;AAErB,aAAW,aAAa,YAAY;AAChC,QAAI,aAAa,eAAe,aAAc;AAC9C,UAAM,WAAW,SAAS,UAAU,KAAK,EAAG,QAAQ;AACpD,QAAI,SAAS,UAAU,KAAK,kBAAkB,KAAK,gBAAiB;AAEpE,UAAM,SAAS,SAAS,MAAM,GAAG,KAAK,eAAe;AACrD,UAAM,SAAS,SAAS,MAAM,CAAC,KAAK,eAAe;AACnD,UAAM,cACF,SACA;;KAAU,iBAAiB,qBAAgB,UAAU,MAAM;;IAC3D;AACJ,UAAM,IAAI,UAAU,OAAO,WAAW;AACtC,mBAAe,UAAU,SAAS,YAAY,WAAW;AACzD;EACJ;AAEA,MAAI,mBAAmB,EAAG,QAAO,EAAE,UAAU,gBAAgB,GAAG,aAAa,EAAE;AAE/E,QAAM,UAAU,SAAS;IAAI,CAAC,SAAS,UACnC,MAAM,IAAI,KAAK,IAAI,EAAE,GAAG,SAAS,MAAM,MAAM,IAAI,KAAK,EAAG,IAAI;EACjE;AACA,SAAO,EAAE,UAAU,SAAS,gBAAgB,YAAY;AAC5D;AC9EA,IAAM,qBAAqB;AAO3B,SAAS,SAAS,UAAkB,QAAwB;AACxD,SAAO,GAAG,QAAQ,KAAK,MAAM;AACjC;AAEA,SAAS,oBAAoB,MAA0B,UAAsC;AACzF,MAAI;AACJ,MAAI;AACA,aAAS,KAAK,MAAM,QAAQ,EAAE;EAClC,QAAQ;AACJ,WAAO;EACX;AACA,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,QAAM,MAAM;AACZ,QAAM,UAAU,IAAI;AACpB,MAAI,CAAC,MAAM,QAAQ,OAAO,KAAK,QAAQ,WAAW,EAAG,QAAO;AAE5D,QAAM,OAAO,QAAQ,OAAO,CAAC,UAA4C;AACrE,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,UAAM,IAAI,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AACtH,UAAM,IAAI,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,OAAO,MAAM,cAAc,WAAW,MAAM,YAAY;AAClH,WAAO,SAAS,IAAI,SAAS,GAAG,CAAC,CAAC;EACtC,CAAC;AAED,MAAI,KAAK,WAAW,QAAQ,UAAU,KAAK,WAAW,EAAG,QAAO;AAEhE,SAAO,KAAK,UAAU,EAAE,GAAG,KAAK,SAAS,KAAK,CAAC;AACnD;AAEO,SAAS,0BACZ,OACA,UACkB;AAClB,QAAM,kBAAkB,oBAAI,IAAY;AACxC,QAAM,gBAAgB,oBAAI,IAAY;AACtC,QAAM,wBAAwB,oBAAI,IAAyB;AAC3D,QAAM,qBAAqB,oBAAI,IAAY;AAC3C,aAAW,SAAS,MAAM,QAAQ;AAC9B,QAAI,CAAC,MAAM,eAAgB;AAC3B,oBAAgB,IAAI,MAAM,cAAc;AACxC,QAAI,CAAC,MAAM,OAAQ;AACnB,kBAAc,IAAI,MAAM,cAAc;AACtC,QAAI,MAAM,aAAa,UAAa,MAAM,WAAW,QAAW;AAC5D,yBAAmB,IAAI,MAAM,cAAc;AAC3C;IACJ;AACA,QAAI,OAAO,sBAAsB,IAAI,MAAM,cAAc;AACzD,QAAI,CAAC,MAAM;AACP,aAAO,oBAAI,IAAY;AACvB,4BAAsB,IAAI,MAAM,gBAAgB,IAAI;IACxD;AACA,SAAK,IAAI,SAAS,MAAM,UAAU,MAAM,MAAM,CAAC;EACnD;AAEA,QAAM,sBAAgC,CAAC;AACvC,WAAS,IAAI,SAAS,SAAS,GAAG,KAAK,KAAK,oBAAoB,SAAS,oBAAoB,KAAK;AAC9F,UAAM,UAAU,SAAS,CAAC;AAC1B,QAAI,QAAQ,aAAa,cAAc,QAAQ,gBAAgB,YAAa;AAC5E,UAAM,SAAS,QAAQ;AACvB,QAAI,UAAU,CAAC,gBAAgB,IAAI,MAAM,GAAG;AACxC,0BAAoB,KAAK,MAAM;IACnC;EACJ;AAEA,QAAM,cAAc,oBAAI,IAAI,CAAC,GAAG,eAAe,GAAG,mBAAmB,CAAC;AAEtE,QAAM,gBAAgB,oBAAI,IAAY;AACtC,aAAW,WAAW,UAAU;AAC5B,QACI,QAAQ,aAAa,cACrB,QAAQ,gBAAgB,gBACvB,CAAC,QAAQ,cAAc,CAAC,YAAY,IAAI,QAAQ,UAAU,IAC7D;AACE,UAAI,QAAQ,WAAY,eAAc,IAAI,QAAQ,UAAU;IAChE;EACJ;AAEA,MAAI,SAAS;AACb,QAAM,SAAwB,CAAC;AAC/B,aAAW,WAAW,UAAU;AAC5B,QACI,QAAQ,aAAa,cACrB,QAAQ,gBAAgB,gBACvB,CAAC,QAAQ,cAAc,CAAC,YAAY,IAAI,QAAQ,UAAU,IAC7D;AACE;AACA;IACJ;AACA,QACI,QAAQ,gBAAgB,iBACxB,QAAQ,cACR,cAAc,IAAI,QAAQ,UAAU,GACtC;AACE;AACA;IACJ;AACA,QACI,QAAQ,aAAa,cACrB,QAAQ,gBAAgB,eACxB,QAAQ,cACR,YAAY,IAAI,QAAQ,UAAU,GACpC;AACE,YAAM,WAAW,sBAAsB,IAAI,QAAQ,UAAU;AAC7D,UAAI,YAAY,SAAS,OAAO,KAAK,CAAC,mBAAmB,IAAI,QAAQ,UAAU,GAAG;AAC9E,cAAM,YAAY,oBAAoB,QAAQ,MAAM,QAAQ;AAC5D,YAAI,cAAc,MAAM;AACpB,iBAAO,KAAK,EAAE,GAAG,SAAS,MAAM,UAAU,CAAC;AAC3C;QACJ;MACJ;IACJ;AACA,WAAO,KAAK,OAAO;EACvB;AAEA,SAAO,EAAE,UAAU,QAAQ,OAAO;AACtC;ACzHA,IAAM,WAAW,oBAAI,IAA2B;AAgBzC,SAAS,qBAAsC;AAClD,SAAO,CAAC,GAAG,SAAS,OAAO,CAAC;AAChC;ACTO,SAAS,oBACZ,UACA,QACW;AACX,MAAI,CAAC,QAAQ,SAAS;AAClB,WAAO,EAAE,UAAU,eAAe,GAAG,cAAc,GAAG,eAAe,EAAE;EAC3E;AAEA,QAAM,SAAS,mBAAmB,EAAE;IAChC,CAAC,WAAW,OAAO,UAAU,OAAO,IAAI,GAAG,YAAY;EAC3D;AACA,MAAI,OAAO,WAAW,GAAG;AACrB,WAAO,EAAE,UAAU,eAAe,GAAG,cAAc,GAAG,eAAe,EAAE;EAC3E;AAEA,MAAI,UAAU,SAAS,IAAI,CAAC,aAAa,EAAE,GAAG,QAAQ,EAAE;AACxD,QAAM,QAAQ,EAAE,eAAe,GAAG,cAAc,GAAG,eAAe,EAAE;AACpE,QAAM,QAAQ,QAAQ;AAEtB,QAAM,YAAY,OAAO,OAAO,CAAC,WAAW,CAAC,OAAO,YAAY;AAChE,WAAS,QAAQ,GAAG,QAAQ,QAAQ,QAAQ,SAAS;AACjD,UAAM,UAAU,QAAQ,KAAK;AAC7B,UAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAI,KAAK,WAAW,EAAG;AACvB,QAAI,UAAU;AACd,UAAM,UAAgC;MAClC,MAAM;MACN,MAAM,QAAQ;MACd,cAAc;MACd,eAAe;MACf,UAAU,QAAQ;IACtB;AACA,eAAW,UAAU,WAAW;AAC5B,UAAI;AACJ,UAAI;AACA,mBAAW,OAAO,OAAO,OAAO;MACpC,QAAQ;AACJ;MACJ;AACA,UAAI,SAAS,WAAW,OAAQ;AAChC,YAAM;AACN,UAAI,SAAS,WAAW,QAAQ;AAC5B,kBAAU;AACV,cAAM;MACV,WAAW,SAAS,WAAW,YAAY,SAAS,SAAS,QAAW;AACpE,kBAAU,SAAS;AACnB,cAAM;MACV;AACA,cAAQ,OAAO;IACnB;AACA,QAAI,YAAY,KAAM,SAAQ,KAAK,IAAI,EAAE,GAAG,SAAS,MAAM,QAAQ;EACvE;AAEA,QAAM,WAAW,OAAO,OAAO,CAAC,WAAW,OAAO,YAAY;AAC9D,aAAW,UAAU,UAAU;AAC3B,QAAI,YAAY;AAChB,aAAS,QAAQ,QAAQ,SAAS,GAAG,SAAS,GAAG,SAAS;AACtD,YAAM,UAAU,QAAQ,KAAK;AAC7B,YAAM,OAAO,QAAQ,QAAQ;AAC7B,UAAI,KAAK,WAAW,EAAG;AACvB,YAAM,MAA4B;QAC9B;QACA,MAAM,QAAQ;QACd,cAAc;QACd,eAAe;QACf,UAAU,QAAQ;MACtB;AACA,UAAI;AACJ,UAAI;AACA,mBAAW,OAAO,OAAO,GAAG;MAChC,QAAQ;AACJ;MACJ;AACA,UAAI,SAAS,WAAW,UAAU,SAAS,WAAW,SAAU;AAChE,UAAI,WAAW;AACX,cAAM;AACN,cAAM;AACN,gBAAQ,KAAK,IAAI,EAAE,GAAG,SAAS,MAAM,GAAG;MAC5C,OAAO;AACH,oBAAY;AACZ,YAAI,SAAS,WAAW,YAAY,SAAS,SAAS,QAAW;AAC7D,gBAAM;AACN,gBAAM;AACN,kBAAQ,KAAK,IAAI,EAAE,GAAG,SAAS,MAAM,SAAS,KAAK;QACvD;MACJ;IACJ;EACJ;AAEA,SAAO,EAAE,UAAU,SAAS,GAAG,MAAM;AACzC;ACnFA,SAAS,aAAa,QAAwB;AAC5C,MAAI,SAAS,IAAM,QAAO,OAAO,MAAM;AACvC,MAAI,SAAS,IAAO,SAAQ,SAAS,KAAM,QAAQ,CAAC,IAAI;AACxD,SAAO,KAAK,MAAM,SAAS,GAAI,IAAI;AACrC;AAEA,SAAS,aAAa,SAA8B;AAClD,MACE,QAAQ,gBAAgB,eACxB,QAAQ,gBAAgB,eACxB;AACA,WAAO,QAAQ,YAAY;EAC7B;AACA,SAAO,QAAQ;AACjB;AAEA,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;AAEA,IAAM,KAAK;AACX,IAAM,KAAK;AACX,IAAM,WAAW,KAAK;AACtB,IAAM,YAAY,KAAK,SAAS;AAEhC,SAAS,OAAO,KAAa,QAAgB,MAAsB;AACjE,SAAO,WAAW,aAAa,aAAa,MAAM,IAAI,aAAa,OAAO,MAAM,KAAK,MAAM;AAC7F;AAEA,SAAS,cACP,SACA,KACA,aACA,UACA,WAA0C,MAC7B;AACb,QAAM,MAAM,UAAU,KAAK,QAAQ,EAAE;AACrC,MAAI,CAAC,OAAO,QAAQ,YAAa,QAAO;AAGxC,MAAI,aAAa,OAAQ,QAAO;AAGhC,MAAI,aAAa,eAAe,QAAQ,gBAAgB,QAAQ;AAC9D,WAAO;EACT;AAIA,QAAM,WAAW,IAAI;IACnB,MAAM,YAAY,QAAQ,IAAI,UAAU,KAAK,YAAY,GAAG,IAAI,YAAY,SAAS,IAAI;EAC3F;AACA,QAAM,aAAa,QAAQ,QAAQ,IAAI,QAAQ,UAAU,EAAE;AAI3D,QAAM,SAAS,WACV,SAAS,GAAG,MAAM,SAAS,GAAG,IAAI,YAAY,SAAS,KACxD,YAAY,SAAS;AACzB,QAAM,OAAO,aAAa,OAAO;AACjC,QAAM,SAAS,OAAO,KAAK,QAAQ,IAAI,IAAI;AAE3C,MAAI,CAAC,UAAW,QAAO,EAAE,GAAG,SAAS,MAAM,OAAO;AAClD,SAAO,EAAE,GAAG,SAAS,MAAM,SAAS,UAAU;AAChD;AAyBO,SAAS,mBACd,UACA,OACA,cAAwC,CAAC,SAAS,KAAK,KAAK,KAAK,SAAS,CAAC,GAC3E,WAA2B,OACD;AAC1B,QAAM,MAAM,MAAM;AAClB,QAAM,WAAW,EAAE,GAAI,MAAM,iBAAiB,CAAC,EAAG;AAClD,QAAM,WAAW,SAAS;IAAI,CAAC,YAC7B,cAAc,SAAS,KAAK,aAAa,UAAU,QAAQ;EAC7D;AACA,SAAO,EAAE,UAAU,UAAU,eAAe,SAAS;AACvD;AAGO,SAAS,qBAAqB,UAAwC;AAC3E,SAAO;IACL,MAAM;IACN,IAAI,IAAY,KAA8B;AAC5C,YAAM,EAAE,UAAU,cAAc,IAAI;QAClC,GAAG;QACH,GAAG;QACH,IAAI;QACJ;MACF;AAGA,YAAM,OAAO,GAAG,MAAM;AACtB,YAAM,UACJ,CAAC,QAAQ,OAAO,KAAK,aAAa,EAAE,WAAW,OAAO,KAAK,IAAI,EAAE;AACnE,aAAO,UACH,EAAE,GAAG,IAAI,UAAU,OAAO,EAAE,GAAG,GAAG,OAAO,cAAc,EAAE,IACzD,EAAE,GAAG,IAAI,SAAS;IACxB;EACF;AACF;AAGO,IAAM,iBAA+B,qBAAqB,KAAK;AC1I/D,IAAM,yBAAyB,CAAC,UAAU;AAqB1C,IAAM,8BAA8B;EACzC;EACA;EACA;EACA;AACF;AAKO,SAAS,sBAAsB,KAA2B;AAC/D,MAAI,IAAI,gBAAgB,eAAe,IAAI,gBAAgB,eAAe;AACxE,WAAO;EACT;AACA,MAAI,CAAC,IAAI,SAAU,QAAO;AAC1B,SAAQ,4BAAkD,SAAS,IAAI,QAAQ;AACjF;AAEO,SAAS,iBAAiB,UAAkB,SAA0B;AAC3E,MAAI,QAAQ,SAAS,GAAG,GAAG;AACzB,WAAO,SAAS,WAAW,QAAQ,MAAM,GAAG,EAAE,CAAC;EACjD;AACA,SAAO,aAAa;AACtB;AAEO,SAAS,mBACd,KACA,QACS;AAGT,MACG,IAAI,gBAAgB,eAAe,IAAI,gBAAgB,iBACxD,CAAC,IAAI,UACL;AACA,WAAO;EACT;AAGA,MAAK,uBAA6C,SAAS,IAAI,QAAQ,GAAG;AACxE,WAAO;EACT;AAEA,aAAW,WAAW,OAAO,gBAAgB;AAC3C,QAAI,iBAAiB,IAAI,UAAU,OAAO,EAAG,QAAO;EACtD;AAEA,MAAI,OAAO,kBAAkB,IAAI,UAAU,IAAI,IAAI,EAAG,QAAO;AAE7D,SAAO;AACT;AAMO,SAAS,4BACd,UACA,QACa;AACb,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,UAAU;AACxB,QAAI,EAAE,gBAAgB,eAAe,EAAE,cAAc,mBAAmB,GAAG,MAAM,GAAG;AAClF,UAAI,IAAI,EAAE,UAAU;IACtB;EACF;AACA,SAAO;AACT;AAIO,SAAS,8BACd,KACA,QACA,kBACS;AACT,MAAI,mBAAmB,KAAK,MAAM,EAAG,QAAO;AAC5C,MACE,IAAI,gBAAgB,iBACpB,IAAI,cACJ,iBAAiB,IAAI,IAAI,UAAU,GACnC;AACA,WAAO;EACT;AACA,SAAO;AACT;ACjGO,SAAS,6BACZ,YACA,UACA,UACA,UAAkB,IACsB;AAGxC,QAAM,iBAAiB,oBAAI,IAAY;AACvC,WAAS,IAAI,YAAY,KAAK,UAAU,KAAK;AACzC,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,CAAC,OAAO,CAAC,IAAI,WAAY;AAC7B,QAAI,IAAI,aAAa,WAAY;AACjC,mBAAe,IAAI,IAAI,UAAU;EACrC;AAEA,MAAI,eAAe,SAAS,GAAG;AAC3B,WAAO,EAAE,YAAY,SAAS;EAClC;AAIA,MAAI,cAAc;AAClB,WAAS,IAAI,WAAW,GAAG,IAAI,SAAS,UAAU,KAAK,WAAW,SAAS,KAAK;AAC5E,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,CAAC,IAAK;AACV,QAAI,IAAI,cAAc,eAAe,IAAI,IAAI,UAAU,GAAG;AACtD,oBAAc;IAClB,WAAW,cAAc,UAAU;AAC/B;IACJ;EACJ;AAGA,MAAI,gBAAgB;AACpB,WAAS,IAAI,aAAa,GAAG,KAAK,KAAK,KAAK,aAAa,SAAS,KAAK;AACnE,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,CAAC,IAAK;AACV,QAAI,IAAI,cAAc,eAAe,IAAI,IAAI,UAAU,GAAG;AACtD,sBAAgB;IACpB,WAAW,gBAAgB,YAAY;AACnC;IACJ;EACJ;AAEA,SAAO,EAAE,YAAY,eAAe,UAAU,YAAY;AAC9D;ACjCO,SAAS,kCACd,YACA,UACA,UAC0C;AAC1C,MAAI,aAAa,UAAU;AACzB,WAAO,EAAE,YAAY,SAAS;EAChC;AACA,MAAI,gBAAgB;AACpB,MAAI,cAAc;AAElB,WAAS,IAAI,YAAY,KAAK,YAAY,IAAI,SAAS,QAAQ,KAAK;AAClE,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,CAAC,IAAK;AAEV,QAAI,IAAI,gBAAgB,aAAa;AAGnC,UAAI,IAAI;AACR,aACE,IAAI,IAAI,SAAS,UACjB,SAAS,IAAI,CAAC,EAAG,gBAAgB,aACjC;AACA;MACF;AACA,YAAM,YAAY,SAAS,IAAI,CAAC;AAChC,UACE,cAAc,UACd,UAAU,SAAS,gBAClB,UAAU,gBAAgB,UACzB,UAAU,gBAAgB,gBAC5B,IAAI,IAAI,aACR;AACA,sBAAc,IAAI;MACpB;IACF;AAEA,QACE,IAAI,SAAS,gBACZ,IAAI,gBAAgB,UAAU,IAAI,gBAAgB,cACnD;AAGA,UAAI,IAAI,IAAI;AACZ,aAAO,KAAK,KAAK,SAAS,CAAC,EAAG,gBAAgB,aAAa;AACzD;MACF;AACA,YAAM,WAAW,IAAI;AACrB,UACE,WAAW,KACX,YAAY,KACZ,SAAS,QAAQ,EAAG,gBAAgB,eACpC,WAAW,eACX;AACA,wBAAgB;MAClB;IACF;EACF;AAEA,SAAO,EAAE,YAAY,eAAe,UAAU,YAAY;AAC5D;AC3DA,SAAS,OAAO,KAAqB;AACnC,QAAM,IAAI,SAAS,IAAI,MAAM,CAAC,GAAG,EAAE;AACnC,SAAO,OAAO,MAAM,CAAC,IAAI,KAAK;AAChC;AAIA,SAAS,mBAAmB,MAAsB;AAChD,SAAO,KAAK,KAAK,KAAK,SAAS,CAAC;AAClC;AAEA,SAAS,cAAc,SAA+B;AACpD,SAAO,QAAQ,gBAAgB,eAAe,QAAQ,gBAAgB;AACxE;AAGA,SAAS,oBACP,SACA,OACS;AACT,MAAI,QAAQ,MAAM,WAAW,mCAAmC,EAAG,QAAO;AAC1E,aAAW,SAAS,MAAM,QAAQ;AAChC,QAAI,MAAM,UAAU,MAAM,oBAAoB,SAAS,QAAQ,EAAE,EAAG,QAAO;EAC7E;AACA,SAAO;AACT;AAcO,SAAS,qBACd,UACA,OACA,QACA,cAAwC,oBAC3B;AACb,QAAM,YAAY,OAAO;AACzB,QAAM,iBAAiB,OAAO;AAE9B,QAAM,SAAS,oBAAI,IAAY;AAC/B,QAAM,UAA6C,CAAC;AAEpD,aAAW,OAAO,UAAU;AAC1B,QAAI,oBAAoB,KAAK,KAAK,EAAG;AAMrC,QAAI,sBAAsB,GAAG,EAAG;AAChC,UAAM,MAAM,MAAM,YAAY,MAAM,IAAI,EAAE;AAC1C,QAAI,CAAC,OAAO,QAAQ,UAAW;AAC/B,YAAQ,KAAK,EAAE,KAAK,QAAQ,YAAY,IAAI,QAAQ,EAAE,EAAE,CAAC;EAC3D;AAGA,MAAI,YAAY,GAAG;AACjB,eAAW,KAAK,QAAQ,MAAM,CAAC,SAAS,GAAG;AACzC,aAAO,IAAI,EAAE,GAAG;IAClB;EACF;AAGA,MAAI,iBAAiB,GAAG;AACtB,QAAI,aAAa;AACjB,aAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,KAAK,aAAa,gBAAgB,KAAK;AAC3E,aAAO,IAAI,QAAQ,CAAC,EAAG,GAAG;AAC1B,oBAAc,QAAQ,CAAC,EAAG;IAC5B;EACF;AAWA,MAAI,YAAY,GAAG;AACjB,aAAS,IAAI,SAAS,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,YAAM,MAAM,SAAS,CAAC;AACtB,UAAI,IAAI,SAAS,UAAU,oBAAoB,KAAK,KAAK,EAAG;AAC5D,YAAM,MAAM,MAAM,YAAY,MAAM,IAAI,EAAE;AAC1C,UAAI,OAAO,QAAQ,UAAW,QAAO,IAAI,GAAG;AAC5C;IACF;EACF;AAEA,SAAO;AACT;AAgBO,SAAS,wBACd,UACA,OACA,QACA,mBACA,cAAwC,oBACzB;AACf,QAAM,mBAOA,CAAC;AACP,QAAM,gBAKA,CAAC;AAIP,QAAM,mBAAmB,4BAA4B,UAAU,MAAM;AAErE,aAAW,OAAO,UAAU;AAC1B,QAAI,oBAAoB,KAAK,KAAK,EAAG;AACrC,UAAM,MAAM,MAAM,YAAY,MAAM,IAAI,EAAE;AAC1C,QAAI,CAAC,OAAO,QAAQ,UAAW;AAE/B,UAAM,KAAK,OAAO,GAAG;AAErB,QAAI,8BAA8B,KAAK,QAAQ,gBAAgB,GAAG;AAChE,oBAAc,KAAK;QACjB;QACA,QAAQ;QACR,QAAQ,YAAY,IAAI,QAAQ,EAAE;QAClC,OAAO,IAAI,WAAW,CAAC,IAAI,QAAQ,IAAI,CAAC;MAC1C,CAAC;AACD;IACF;AAEA,QAAI,mBAAmB,IAAI,GAAG,GAAG;AAC/B;IACF;AAEA,qBAAiB,KAAK;MACpB;MACA,QAAQ;MACR,QAAQ,YAAY,IAAI,QAAQ,EAAE;MAClC,QAAQ,IAAI,QAAQ,IAAI;MACxB,QAAQ,cAAc,GAAG;MACzB,QAAQ,IAAI,SAAS;IACvB,CAAC;EACH;AAQA,QAAM,eAAoC,CAAC;AAC3C,MAAI,MAAgC;AACpC,MAAI,aAAa;AAEjB,aAAW,QAAQ,kBAAkB;AACnC,UAAM,SAAS,KAAK,SAAS,aAAa;AAC1C,QAAI,QAAS,KAAK,UAAU,IAAI,SAAS,KAAM,SAAS;AACtD,mBAAa,KAAK,GAAG;AACrB,YAAM;IACR;AACA,iBAAa,KAAK;AAClB,QAAI,CAAC,KAAK;AACR,YAAM;QACJ,UAAU,KAAK;QACf,QAAQ,KAAK;QACb,OAAO;QACP,QAAQ,KAAK;QACb,OAAO,KAAK;QACZ,SAAS,KAAK,SAAS,MAAM;QAC7B,SAAS,KAAK,SAAS,IAAI;MAC7B;IACF,OAAO;AACL,UAAI,SAAS,KAAK;AAClB,UAAI;AACJ,UAAI,UAAU,KAAK;AACnB,UAAI,SAAS,IAAI,SAAS,KAAK,KAAK;AACpC,UAAI,KAAK,QAAQ;AACf,YAAI,UAAU,KAAK,OAAO,IAAI,WAAW,IAAI,QAAQ,KAAK,OAAO,IAAI,KAAK;MAC5E,OAAO;AACL,YAAI,UAAU,KAAK,MAAO,IAAI,WAAW,IAAI,QAAQ,KAAM,IAAI,KAAK;MACtE;AACA,UAAI,UAAU,MAAM,IAAI;IAC1B;EACF;AACA,MAAI,IAAK,cAAa,KAAK,GAAG;AAG9B,QAAM,kBAAoC,CAAC;AAC3C,MAAI,OAA8B;AAClC,MAAI,cAAc;AAElB,aAAW,QAAQ,eAAe;AAChC,UAAM,SAAS,KAAK,SAAS,cAAc;AAC3C,QAAI,QAAQ,QAAQ;AAClB,sBAAgB,KAAK,IAAI;AACzB,aAAO;IACT;AACA,kBAAc,KAAK;AACnB,QAAI,CAAC,MAAM;AACT,aAAO;QACL,UAAU,KAAK;QACf,QAAQ,KAAK;QACb,OAAO;QACP,QAAQ,KAAK;QACb,OAAO,CAAC,GAAG,KAAK,KAAK;MACvB;IACF,OAAO;AACL,WAAK,SAAS,KAAK;AACnB,WAAK;AACL,WAAK,UAAU,KAAK;AACpB,iBAAW,KAAK,KAAK,OAAO;AAC1B,YAAI,CAAC,KAAM,MAAM,SAAS,CAAC,EAAG,MAAM,MAAM,KAAK,CAAC;MAClD;IACF;EACF;AACA,MAAI,KAAM,iBAAgB,KAAK,IAAI;AAEnC,SAAO;IACL,cAAc,aAAa,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;IACrD,WAAW;EACb;AACF;AAEA,SAAS,WAAW,OAA+C;AACjE,QAAM,QAAQ,MAAM,CAAC;AACrB,QAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,QAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,OAAO,CAAC;AACnD,QAAM,SAAS,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AACrD,QAAM,QAAQ,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,WAAW,CAAC,GAAG,CAAC;AACzD,QAAM,UAAU,KAAK;IACnB,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI;EACvD;AACA,QAAM,SAA4B;IAChC,UAAU,MAAM;IAChB,QAAQ,KAAK;IACb;IACA;IACA;IACA;IACA,SAAS,MAAM;EACjB;AACA,MAAI,MAAM,KAAK,CAAC,MAAM,EAAE,cAAc,IAAI,GAAG;AAC3C,WAAO,YAAY;EACrB;AACA,SAAO;AACT;AAKA,SAAS,WAAW,GAA8B;AAChD,SAAO,EAAE,SAAS,EAAE,SAAS;AAC/B;AAUO,SAAS,uBACd,QACA,UACqB;AACrB,MAAI,YAAY,KAAK,OAAO,WAAW,EAAG,QAAO;AACjD,QAAM,SAA8B,CAAC;AACrC,MAAI,QAA6B,CAAC;AAClC,MAAI,aAAa;AACjB,aAAW,KAAK,QAAQ;AACtB,UAAM,KAAK,CAAC;AACZ,kBAAc,WAAW,CAAC;AAC1B,QAAI,cAAc,UAAU;AAC1B,aAAO,KAAK,WAAW,KAAK,CAAC;AAC7B,cAAQ,CAAC;AACT,mBAAa;IACf;EACF;AACA,MAAI,MAAM,SAAS,GAAG;AACpB,WAAO,KAAK,WAAW,KAAK,CAAC;EAC/B;AACA,SAAO;AACT;ACnTO,SAAS,YACd,OACA,SACA,KACQ;AACR,MAAI,KAAK;AACT,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,WAAW,CAAC,KAAK,QAAQ,IAAI,GAAG,EAAG;AAC5C,SAAK,KAAK,IAAI,IAAI,GAAG;EACvB;AACA,SAAO;AACT;ACyEA,SAAS,WACP,MACA,SACQ;AACR,SAAO,SAAS,KAAK,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO;AAC3D;AAEO,SAAS,WAAW,QAAe,CAAC,GAAoB;AAC7D,QAAM,cAAc,MAAM,eAAe;AAEzC,WAAS,iBACP,OACwB;AACxB,UAAM,QAA0B,WAAW,MAAM,KAAK;AACtD,UAAM,QAAQ,cAAc,KAAK;AACjC,QAAI,gBAAgB;AACpB,QAAI,mBAAmB;AACvB,UAAM,SAAmB,CAAC;AAC1B,UAAM,WAAqB,CAAC;AAK5B,UAAM,sBACJ,MAAM,uBACN,qBAAqB,MAAM,UAAU,MAAM,OAAO,MAAM,QAAQ,WAAW;AAE7E,UAAM,sBAAsB,gBAAgB,KAAK;AAMjD,UAAM,kBAAkB,oBAAI,IAAkD;AAC9E,UAAM,uBAAiC,CAAC;AACxC,UAAM,iBAAsC,CAAC;AAC7C,eAAW,QAAQ,MAAM,QAAQ;AAC/B,UAAI;AACF,cAAM,WAAW,kBAAkB;UACjC,UAAU,KAAK;UACf,QAAQ,KAAK;UACb,UAAU,MAAM;UAChB;QACF,CAAC;AACD,wBAAgB,IAAI,MAAM,EAAE,QAAQ,MAAM,SAAS,CAAC;MACtD,SAAS,OAAO;AACd,YAAI,iBAAiB,uBAAuB;AAC1C,0BAAgB;YACd;YACA,MAAM,SAAS,YACX,EAAE,QAAQ,WAAW,MAAM,IAC3B,EAAE,QAAQ,YAAY,MAAM;UAClC;AACA,cAAI,MAAM,SAAS,YAAY;AAC7B,2BAAe,KAAK,IAAI;UAC1B,OAAO;AACL,iCAAqB,KAAK,WAAW,MAAM,MAAM,OAAO,CAAC;UAC3D;QACF,OAAO;AACL,0BAAgB,IAAI,MAAM;YACxB,QAAQ;YACR,OAAO,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;UACjE,CAAC;AACD,+BAAqB;YACnB,WAAW,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;UACzE;QACF;MACF;IACF;AAEA,UAAM,iBAA6E,CAAC;AACpF,eAAW,CAAC,MAAM,UAAU,KAAK,iBAAiB;AAChD,UAAI,WAAW,WAAW,KAAM;AAChC,YAAM,UAAU,WAAW,SAAS,WAAW;QAAI,CAAC,OAClD,MAAM,SAAS,UAAU,CAAC,MAAM,EAAE,OAAO,EAAE;MAC7C,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;AACtB,qBAAe,KAAK,EAAE,MAAM,QAAQ,CAAC;IACvC;AACA,UAAM,eAAe,CAAC,GAAG,cAAc,EAAE,KAAK,CAAC,GAAG,MAAM;AACtD,YAAM,OAAO,EAAE,QAAQ,SAAS,IAAI,KAAK,IAAI,GAAG,EAAE,OAAO,IAAI;AAC7D,YAAM,OAAO,EAAE,QAAQ,SAAS,IAAI,KAAK,IAAI,GAAG,EAAE,OAAO,IAAI;AAC7D,aAAO,OAAO;IAChB,CAAC;AAGD,UAAM,YAAY,oBAAI,IAAiC;AACvD,QAAI,mBAAmB;AACvB,eAAW,SAAS,cAAc;AAChC,YAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,OAAO,IAAI;AACzE,YAAM,WAAW,MAAM,QAAQ,SAAS,IAAI,KAAK,IAAI,GAAG,MAAM,OAAO,IAAI;AACzE,UAAI,YAAY,KAAK,YAAY,kBAAkB;AACjD,kBAAU,IAAI,MAAM,IAAI;AACxB,iBAAS;UACP,kBAAkB,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK,MAAM;QAC7D;AACA;MACF;AACA,UAAI,WAAW,iBAAkB,oBAAmB;IACtD;AAEA,QAAI,MAAM,OAAO,SAAS,mBAAmB,KAAK,MAAM,OAAO,SAAS,GAAG;AACzE,UAAI,kBAAkB;AACtB,UAAI,wBAAwB;AAC5B,UAAI,gBAAgB;AACpB,iBAAW,CAAC,MAAM,UAAU,KAAK,iBAAiB;AAChD,YAAI,WAAW,WAAW,QAAQ,UAAU,IAAI,IAAI,EAAG;AACvD,YAAI,WAAW,SAAS,iBAAiB,SAAS;AAChD,kCAAwB;AACxB;QACF;AACA;AACA,mBAAW,MAAM,WAAW,SAAS,YAAY;AAC/C,gBAAM,MAAM,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAClD,6BAAmB,KAAK,MAAM,UAAU;QAC1C;MACF;AACA,UAAI,CAAC,yBAAyB,kBAAkB,MAAM,OAAO,SAAS,kBAAkB;AACtF,cAAM,cACJ,eAAe,SAAS,IACpB,+CAA+C,eAAe,CAAC,EAAG,QAAQ,KAAK,eAAe,CAAC,EAAG,MAAM,qCAAqC,eAAe,gBAAgB,MAAM,OAAO,SAAS,gBAAgB,8EAClN,yCAAyC,eAAe,iBAAiB,aAAa,kBAAkB,MAAM,OAAO,SAAS,gBAAgB;AACpJ,eAAO;UACL,OAAO,MAAM;UACb,QAAQ;YACN,eAAe;YACf,kBAAkB;YAClB,QAAQ,CAAC,aAAa,GAAG,oBAAoB;YAC7C,UAAU,CAAC;UACb;QACF;MACF;IACF;AAEA,eAAW,QAAQ,MAAM,QAAQ;AAC/B,UAAI,UAAU,IAAI,IAAI,EAAG;AACzB,YAAM,aAAa,gBAAgB,IAAI,IAAI;AAC3C,UAAI,eAAe,OAAW;AAC9B,UAAI,WAAW,WAAW,YAAY;AACpC,iBAAS;UACP,kBAAkB,KAAK,QAAQ,KAAK,KAAK,MAAM;QACjD;AACA;MACF;AACA,UAAI,WAAW,WAAW,aAAa,WAAW,WAAW,WAAW;AACtE,eAAO,KAAK,WAAW,MAAM,WAAW,MAAM,OAAO,CAAC;AACtD;MACF;AACA,UAAI;AACF,cAAM,UAAU,iBAAiB;UAC/B;UACA,UAAU,MAAM;UAChB;UACA;UACA,QAAQ,MAAM;UACd;UACA;UACA;QACF,CAAC;AACD;AACA,4BAAoB,QAAQ;AAC5B,iBAAS,KAAK,GAAG,QAAQ,QAAQ;MACnC,SAAS,OAAO;AACd,eAAO,KAAK,WAAW,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC,CAAC;MACtF;IACF;AAEA,UAAM,MAAM,oBAAoB;AAChC,UAAM,MAAM,oBAAoB;AAEhC,QAAI,gBAAgB,GAAG;AAIrB,YAAM,MAAM,4BAA4B;AACxC,YAAM,MAAM,uBAAuB;AAInC,YAAM,MAAM,kBAAkB,CAAC;IACjC;AAEA,WAAO,EAAE,OAAO,QAAQ,EAAE,eAAe,kBAAkB,QAAQ,SAAS,EAAE;EAChF;AAEA,WAAS,YAAY,OAA4C;AAC/D,UAAM,eAAe,eAAe,MAAM,MAAM;AAChD,QAAI,aAAa,SAAS,GAAG;AAC3B,cAAQ,KAAK,4CAA4C,aAAa,KAAK,IAAI,CAAC,sCAAsC;IACxH;AACA,UAAM,MAAuB;MAC3B,QAAQ,MAAM;MACd,YAAY,MAAM;MAClB;IACF;AACA,UAAM,UAAkB;MACtB,UAAU,MAAM;MAChB,OAAO,MAAM;MACb,SAAS,CAAC;IACZ;AAIA,UAAM,WAA2B,MAAM,cAAc;AACrD,UAAM,QAAQ,WAAW,QAAQ;AACjC,UAAM,SAAS,YAAY,OAAO,SAAS,GAAG;AAC9C,WAAO;MACL,UAAU,OAAO;MACjB,OAAO,OAAO;MACd,OAAO,OAAO,QAAQ;IACxB;EACF;AAEA,WAAS,WAAW,SAAiB,OAAyB;AAC5D,WAAO,UAAU,OAAO,OAAO;EACjC;AAEA,WAAS,OAAO,OAAe,OAA6C;AAC1E,UAAM,QAAQ,MACX,YAAY,EACZ,MAAM,KAAK,EACX,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACnC,QAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,UAAM,SAAS,aAAa,KAAK,EAC9B,IAAI,CAAC,WAAW,EAAE,OAAO,OAAO,eAAe,OAAO,KAAK,EAAE,EAAE,EAC/D,OAAO,CAAC,UAAU,MAAM,QAAQ,GAAG,EACnC,KAAK,CAAC,MAAM,UAAU,MAAM,QAAQ,KAAK,KAAK;AACjD,WAAO,OAAO,IAAI,CAAC,UAAU,MAAM,KAAK;EAC1C;AAEA,WAAS,OACP,OACA,YACA,QACc;AACd,UAAM,SAAS,aAAa,KAAK;AACjC,UAAM,QACJ,OAAO,oBAAoB,IAAI,aAAa,OAAO,oBAAoB;AACzE,WAAO;MACL,cAAc;MACd;MACA,mBAAmB,OAAO;MAC1B,cAAc,OAAO;MACrB,aAAa,MAAM,OAAO;MAC1B,kBAAkB,MAAM,MAAM;MAC9B,WAAW,EAAE,QAAQ,OAAO,QAAQ,OAAO,MAAM,OAAO,OAAO;IACjE;EACF;AAEA,WAAS,eAA+B;AACtC,WAAO,WAAW,KAAK;EACzB;AAKA,WAAS,WAAW,UAA0C;AAC5D,UAAM,OAAuB;MAC3B;MACA;MACA;MACA;MACA;MACA;MACA;MACA;IACF;AACA,QAAI,aAAa,OAAQ,QAAO;AAChC,WAAO,CAAC,GAAG,MAAM,qBAAqB,QAAQ,CAAC;EACjD;AAEA,SAAO,EAAE,aAAa,kBAAkB,cAAc,YAAY,QAAQ,OAAO;AACnF;AAQA,IAAM,iBAA+B;EACnC,MAAM;EACN,IAAI,IAAI,KAAK;AACX,UAAM,gBACJ,IAAI,OAAO,eAAe,SAAS,KAAK,CAAC,CAAC,IAAI,OAAO;AACvD,UAAM,cAAc,gBAChB,CAAC,MAAmB,mBAAmB,GAAG,IAAI,MAAM,IACpD;AACJ,UAAM,YAAY,WAAW,GAAG,UAAU;MACxC,UAAU,GAAG,MAAM;MACnB,WAAW,iBAAiB,GAAG,MAAM,WAAW,IAAI;MACpD,aAAa;IACf,CAAC;AACD,WAAO,EAAE,GAAG,IAAI,OAAO,EAAE,GAAG,GAAG,OAAO,aAAa,UAAU,IAAI,EAAE;EACrE;AACF;AAEA,IAAM,iBAA+B;EACnC,MAAM;EACN,IAAI,IAAI,KAAK;AACX,UAAM,SAAS,WAAW,GAAG,UAAU,GAAG,KAAK;AAC/C,oBAAgB,OAAO,OAAO,IAAI,OAAO,kBAAkB;AAC3D,WAAO,EAAE,GAAG,IAAI,OAAO,OAAO,MAAM;EACtC;AACF;AAEA,IAAM,YAA0B;EAC9B,MAAM;EACN,IAAI,IAAI;AACN,WAAO,EAAE,GAAG,IAAI,UAAU,MAAM,GAAG,UAAU,GAAG,KAAK,EAAE;EACzD;AACF;AAEA,IAAM,aAA2B;EAC/B,MAAM;EACN,SAAS,CAAC,KAAK,QACb,CAAC,CAAC,IAAI,OAAO,gBAAgB,WAAW,mBAAmB,EAAE,SAAS;EACxE,IAAI,IAAI,KAAK;AACX,UAAM,UAAU,oBAAoB,GAAG,UAAU,IAAI,OAAO,cAAc;AAC1E,WAAO,EAAE,GAAG,IAAI,UAAU,QAAQ,SAAS;EAC7C;AACF;AAEA,IAAM,wBAAsC;EAC1C,MAAM;EACN,IAAI,IAAI;AACN,UAAM,SAAS,0BAA0B,GAAG,OAAO,GAAG,QAAQ;AAC9D,WAAO,EAAE,GAAG,IAAI,UAAU,OAAO,SAAS;EAC5C;AACF;AAEA,IAAM,gBAA8B;EAClC,MAAM;EACN,IAAI,IAAI,KAAK;AACX,UAAM,gBAAgB;MACpB,GAAG;MACH,GAAG;MACH,IAAI;MACJ,IAAI;IACN;AACA,UAAM,gBAAgB;MACpB,GAAG;MACH,GAAG;MACH,IAAI;MACJ;MACA,IAAI;IACN;AACA,UAAM,oBAAoB,cAAc,aAAa,WAAW;AAChE,UAAM,iBAAiC;MACrC;MACA,mBAAmB;QACjB,cAAc;QACd,IAAI,OAAO,SAAS;MACtB;MACA;IACF;AACA,WAAO,EAAE,GAAG,IAAI,SAAS,EAAE,GAAG,GAAG,SAAS,eAAe,EAAE;EAC7D;AACF;AAEA,IAAM,YAA0B;EAC9B,MAAM;EACN,IAAI,IAAI,KAAK;AACX,UAAM,QAAQ,YAAY;MACxB,YAAY,IAAI;MAChB,QAAQ,IAAI;MACZ,OAAO,GAAG;MACV,UAAU,GAAG;MACb,gBAAgB,GAAG,QAAQ;MAC3B,aAAa,IAAI;IACnB,CAAC;AAED,UAAM,WAAW,GAAG,MAAM,MAAM;AAChC,UAAM,oBAAoB;MACxB,IAAI,OAAO;MACX,IAAI,OAAO;IACb;AAEA,QAAI,UAAU,EAAE,GAAG,GAAG,MAAM,MAAM;AAElC,QACE,WAAW,KACX,IAAI,aAAa,WAAW,mBAC5B;AACA,cAAQ,4BAA4B,IAAI;AACxC,cAAQ,uBAAuB;AAS/B,cAAQ,kBAAkB,CAAC;IAC7B;AAEA,QAAI,QAAQ,8BAA8B,GAAG;AAC3C,cAAQ,4BAA4B,IAAI;IAC1C;AAEA,QAAI,MAAM,cAAc;AACtB,cAAQ,uBAAuB,IAAI;AAInC,UAAI,MAAM,SAAS,MAAM;AACvB,gBAAQ,kBAAkB,EAAE,GAAG,QAAQ,iBAAiB,CAAC,MAAM,IAAI,GAAG,IAAI,WAAW;MACvF;IACF;AAEA,WAAO;MACL,GAAG;MACH,OAAO,EAAE,GAAG,GAAG,OAAO,OAAO,QAAQ;MACrC,SAAS,EAAE,GAAG,GAAG,SAAS,MAAM;IAClC;EACF;AACF;AAEA,IAAM,wBAAsC;EAC1C,MAAM;EACN,IAAI,IAAI,KAAK;AACX,UAAM,QACJ,IAAI,OAAO,oBAAoB,IAC3B,IAAI,aAAa,IAAI,OAAO,oBAC5B;AACN,QAAI,QAAQ,IAAI,OAAO,SAAS,UAAW,QAAO;AAClD,UAAM,QAAQ;MACZ,GAAG;MACH,IAAI;MACJ,IAAI;MACJ,IAAI;MACJ,EAAE,uBAAuB,IAAI,OAAO,uBAAuB;IAC7D;AACA,WAAO;MACL,GAAG;MACH,UAAU,MAAM;MAChB,SAAS,EAAE,GAAG,GAAG,SAAS,gBAAgB,MAAM,eAAe;IACjE;EACF;AACF;AAkBA,SAAS,iBAAiB,OAA6C;AACrE,QAAM,WAAqB,CAAC;AAC5B,QAAM,WAAW,kBAAkB;IACjC,UAAU,MAAM,KAAK;IACrB,QAAQ,MAAM,KAAK;IACnB,UAAU,MAAM;IAChB,OAAO,MAAM;EACf,CAAC;AAED,QAAM,kBAAkB;IACtB;IACA,MAAM;EACR;AAIA,MAAI,gBAAgB,SAAS,SAAS,WAAW,QAAQ;AACvD,UAAM,eAAe,oBAAI,IAAoB;AAC7C,UAAM,SAAS,QAAQ,CAAC,GAAG,MAAM,aAAa,IAAI,EAAE,IAAI,CAAC,CAAC;AAC1D,UAAM,gBAAgB,aAAa,IAAI,gBAAgB,CAAC,CAAE,KAAK,SAAS;AACxE,UAAM,cAAc,aAAa,IAAI,gBAAgB,gBAAgB,SAAS,CAAC,CAAE,KAAK,SAAS;AAC/F,UAAM,aAAa,IAAI,IAAI,SAAS,cAAc;AAClD,eAAWC,UAAS,aAAa,MAAM,KAAK,GAAG;AAC7C,UAAI,WAAW,IAAIA,OAAM,OAAO,EAAG;AACnC,YAAM,SAAS,mBAAmBA,OAAM,qBAAqB,YAAY;AACzE,UAAI,WAAW,QAAQ,UAAU,iBAAiB,UAAU,aAAa;AACvE,mBAAW,IAAIA,OAAM,OAAO;AAC5B,iBAAS,eAAe,KAAKA,OAAM,OAAO;MAC5C;IACF;EACF;AAEA,QAAM,kBAAkB,SAAS,iBAAiB;AAClD,QAAM,aAAa;IACjB,MAAM;IACN,SAAS;IACT;EACF;AACA,QAAM,aAAa,kBACd,KAAK,IAAI,GAAG,aAAa,CAAC,IAC3B;AAEJ,QAAM,mBAAmB,SAAS,eAAe,OAAO,CAAC,OAAO;AAC9D,UAAMA,SAAQ,UAAU,MAAM,OAAO,EAAE;AACvC,WAAOA,QAAO,UAAUA,OAAM,SAAS;EACzC,CAAC;AAED,QAAM,sBAAsB,IAAI,IAAY,eAAe;AAC3D,aAAW,cAAc,kBAAkB;AACzC,UAAM,WAAW,UAAU,MAAM,OAAO,UAAU;AAClD,QAAI,UAAU;AACZ,iBAAW,MAAM,SAAS;AACxB,4BAAoB,IAAI,EAAE;IAC9B;EACF;AAEA,QAAM,mBAAmB,CAAC,GAAG,mBAAmB,EAAE;IAChD,CAAC,OAAO,CAAC,MAAM,oBAAoB,IAAI,EAAE;EAC3C;AAEA,MAAI,cAAc;IAChB;IACA,MAAM;IACN,MAAM;EACR;AAMA,MAAI,YAAY,SAAS,iBAAiB,QAAQ;AAChD,UAAM,OAAO,IAAI,IAAI,WAAW;AAChC,eAAW,MAAM,kBAAkB;AACjC,UAAI,CAAC,KAAK,IAAI,EAAE,EAAG,qBAAoB,OAAO,EAAE;IAClD;EACF;AAWA,QAAM,gBAAgB,MAAM;AAC5B,QAAM,kBAAkB,gBACpB,YAAY,OAAO,CAAC,OAAO;AACzB,UAAM,MAAM,MAAM,MAAM,YAAY,MAAM,EAAE;AAC5C,WAAO,QAAQ,UAAa,cAAc,IAAI,GAAG;EACnD,CAAC,IACD,CAAC;AACL,MAAI,gBAAgB,SAAS,GAAG;AAC9B,UAAM,eAAe,IAAI,IAAI,eAAe;AAC5C,kBAAc,YAAY,OAAO,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;AAG9D,eAAW,MAAM,gBAAiB,qBAAoB,OAAO,EAAE;AAE/D,UAAM,UAAU,gBACb,IAAI,CAAC,OAAO,MAAM,MAAM,YAAY,MAAM,EAAE,CAAC,EAC7C,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ;AAEnD,QAAI,YAAY,WAAW,KAAK,iBAAiB,WAAW,GAAG;AAC7D,YAAM,UAAU,MAAM,OAAO;AAC7B,YAAM,IAAI;QACR,yDAAyD,OAAO,mDAAmD,QAAQ;UACzH;QACF,CAAC;MACH;IACF;AACA,aAAS;MACP,YAAY,gBAAgB,MAAM,yBAAyB,QAAQ;QACjE;MACF,CAAC;IACH;EACF;AAEA,2BAAyB,OAAO,aAAa,iBAAiB,MAAM;AAEpE,MAAI,mBAAmB;AACvB,aAAW,MAAM,aAAa;AAC5B,UAAM,UAAU,MAAM,SAAS,KAAK,CAAC,UAAU,MAAM,OAAO,EAAE;AAC9D,wBAAoB,MAAM,YAAY,SAAS,QAAQ,EAAE;EAC3D;AACA,aAAW,cAAc,kBAAkB;AACzC,UAAM,WAAW,UAAU,MAAM,OAAO,UAAU;AAClD,QAAI,UAAU;AACZ,0BAAoB,MAAM,YAAY,SAAS,OAAO;IACxD;EACF;AAEA,QAAM,UAAU,gBAAgB,MAAM,KAAK;AAC3C,QAAM,QAA0B;IAC9B;IACA,OAAO,MAAM;IACb,MAAM;IACN,OAAO,MAAM,KAAK;IAClB,SAAS,MAAM,KAAK;IACpB,kBAAkB;IAClB,qBAAqB,CAAC,GAAG,mBAAmB;IAC5C,gBAAgB,CAAC,GAAG,gBAAgB;IACpC;IACA,WAAW,KAAK,IAAI;IACpB,eAAe;IACf,YAAY;IACZ,QAAQ;IACR,gBAAgB,MAAM,KAAK;IAC3B,UAAU,MAAM,KAAK;IACrB,QAAQ,MAAM,KAAK;EACrB;AACA,QAAM,MAAM,OAAO,KAAK,KAAK;AAE7B,aAAW,cAAc,kBAAkB;AACzC,UAAM,WAAW,UAAU,MAAM,OAAO,UAAU;AAClD,QAAI,SAAU,UAAS,SAAS;EAClC;AAEA,SAAO,EAAE,QAAQ,kBAAkB,SAAS;AAC9C;AAEA,SAAS,6BACP,UACA,UACU;AACV,MAAI,SAAS,iBAAiB,SAAS;AACrC,WAAO,SAAS;EAClB;AAKA,MAAI,aAAa,SAAS;AAC1B,MAAI,WAAW,SAAS;AACxB,WAAS,OAAO,GAAG,OAAO,GAAG,QAAQ;AACnC,UAAM,oBAAoB;MACxB;MACA;MACA;IACF;AACA,UAAM,eAAe;MACnB,kBAAkB;MAClB,kBAAkB;MAClB;IACF;AACA,UAAM,UACJ,aAAa,eAAe,cAC5B,aAAa,aAAa;AAC5B,iBAAa,aAAa;AAC1B,eAAW,aAAa;AACxB,QAAI,CAAC,QAAS;EAChB;AACA,MACE,eAAe,SAAS,cACxB,aAAa,SAAS,UACtB;AACA,WAAO,SAAS;EAClB;AACA,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,YAAY,KAAK,UAAU,KAAK;AAC3C,UAAM,MAAM,SAAS,CAAC;AACtB,QAAI,IAAK,KAAI,KAAK,IAAI,EAAE;EAC1B;AACA,SAAO;AACT;AAEA,SAAS,yBACP,OACA,kBACA,oBACM;AACN,QAAM,MAAM,MAAM,OAAO;AACzB,QAAM,UAAU,MAAM,KAAK,SAAS,KAAK,KAAK;AAE9C,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI;MACR;IACF;EACF;AAEA,MAAI,IAAI,mBAAmB,KAAK,QAAQ,SAAS,IAAI,kBAAkB;AACrE,UAAM,IAAI;MACR,sBAAsB,QAAQ,MAAM,eAAe,IAAI,gBAAgB;IACzE;EACF;AAEA,QAAM,eAAe,MAAM,KAAK,mBAAmB,IAAI;AACvD,MACE,eAAe,KACf,QAAQ,SAAS,cACjB;AACA,UAAM,IAAI;MACR,qBAAqB,QAAQ,MAAM,eAAe,YAAY;IAChE;EACF;AAEA,MAAI,iBAAiB,WAAW,KAAK,uBAAuB,GAAG;AAC7D,UAAM,IAAI;MACR;IACF;EACF;AACF;AAEA,SAAS,4BACP,kBACA,UACA,QACU;AAKV,QAAM,mBAAmB,oBAAI,IAAY;AACzC,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,OAAO,UAAU;AAC1B,QAAI,mBAAmB,KAAK,MAAM,KAAK,IAAI,YAAY;AACrD,uBAAiB,IAAI,IAAI,UAAU;IACrC;EACF;AAEA,aAAW,MAAM,kBAAkB;AACjC,UAAM,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC5C,QAAI,CAAC,IAAK;AACV,QAAI,mBAAmB,KAAK,MAAM,GAAG;AACnC,iBAAW,IAAI,EAAE;AACjB,UAAI,IAAI,WAAY,kBAAiB,IAAI,IAAI,UAAU;IACzD;EACF;AAEA,aAAW,MAAM,kBAAkB;AACjC,QAAI,WAAW,IAAI,EAAE,EAAG;AACxB,UAAM,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC5C,QAAI,CAAC,IAAK;AACV,QACE,IAAI,gBAAgB,iBACpB,IAAI,cACJ,iBAAiB,IAAI,IAAI,UAAU,GACnC;AACA,iBAAW,IAAI,EAAE;IACnB;EACF;AAEA,SAAO,iBAAiB,OAAO,CAAC,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;AAC5D;AAEA,SAAS,kBACP,OACA,gBACA,iBACiB;AACjB,MAAI,CAAC,gBAAiB,QAAO;AAC7B,MAAI,eAAe,WAAW,EAAG,QAAO;AACxC,MAAI,UAA2B;AAC/B,aAAW,MAAM,gBAAgB;AAC/B,UAAM,QAAQ,UAAU,OAAO,EAAE;AACjC,QAAI,SAAS,MAAM,OAAO,QAAS,WAAU,MAAM;EACrD;AACA,SAAO;AACT;AAEA,SAAS,gBAAgB,OAAsC;AAC7D,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,SAAS,aAAa,KAAK,GAAG;AACvC,eAAW,MAAM,MAAM,oBAAqB,UAAS,IAAI,EAAE;EAC7D;AACA,SAAO;AACT;AAWA,SAAS,sBACP,mBACA,OACQ;AACR,MAAI,CAAC,qBAAqB,qBAAqB,EAAG,QAAO,MAAM;AAC/D,SAAO,KAAK;IACV,MAAM;IACN,KAAK;MACH,MAAM;MACN,KAAK,MAAM,oBAAoB,MAAM,WAAW;IAClD;EACF;AACF;AASA,SAAS,cACP,OACA,gBACA,aACA,kBACuE;AACvE,QAAM,MAA6E,CAAC;AACpF,QAAM,SAAS,gBAAgB,qBAAqB,CAAC;AACrD,QAAM,YACJ,mBAAmB,IACf,OAAO,OAAO,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,MAAM,gBAAgB,IAClE;AACN,MAAI,CAAC,IAAI,EAAE,SAAS,UAAU,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC,GAAG,cAAc,CAAC,EAAE;AAClF,QAAM,SAAS,aAAa,KAAK;AACjC,QAAM,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC5C,QAAM,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC5C,MAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,YAAY,EAAE,OAAO,GAAG,CAAC,GAAG,cAAc,GAAG;AACzF,MAAI,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,GAAG,MAAM,IAAI,YAAY,EAAE,OAAO,GAAG,CAAC,GAAG,cAAc,GAAG;AACzF,SAAO;AACT;AAEA,SAAS,YAAY,OAAkC;AACrD,QAAM,EAAE,QAAQ,OAAO,YAAY,gBAAgB,YAAY,IAAI;AACnE,QAAM,QAAQ,OAAO;AACrB,QAAM,QAAQ,QAAQ,IAAI,aAAa,QAAQ;AAE/C,QAAM,oBAAoB,sBAAsB,OAAO,OAAO,KAAK;AAEnE,QAAM,YAAY,SAAS,OAAO,MAAM;AACxC,QAAM,oBAAoB,SAAS,OAAO,MAAM;AAGhD,QAAM,WAAW,aAAa;AAE9B,QAAM,WAAW,MAAM,MAAM;AAC7B,QAAM,kBAAkB,MAAM,MAAM,uBAAuB;AAE3D,QAAM,kBAAkB;AACxB,QAAM,qBAAqB,kBACvB,KAAK,MAAM,oBAAoB,CAAC,IAChC;AAEJ,QAAM,kBACJ,MAAM,MAAM,uBAAuB,IAC/B,MAAM,MAAM,uBACZ,WAAW,IACT,WACA;AAER,QAAM,cAAc,KAAK;IACvB,OAAO,MAAM;IACb,OAAO,MAAM,iBAAiB;EAChC;AAEA,QAAM,uBAAuB,aAAa;AAE1C,QAAM,MAAM;AACZ,QAAM,QAAQ;IACZ;IACA;IACA;IACA,OAAO,SAAS;EAClB;AAOA,QAAM,iBAAiB,KAAK;IAC1B,qBAAqB,OAAO,MAAM,yBAAyB;EAC7D;AACA,MAAI,eAAuC;AAC3C,MAAI,iBAAiB;AACrB,QAAM,cAAc,wBAAwB;AAC5C,QAAM,QAAQ,MAAM,CAAC,GAAG,WAAW;AACnC,QAAM,QAAQ,MAAM,CAAC,GAAG,WAAW;AACnC,QAAM,QAAQ,MAAM,CAAC,GAAG,WAAW;AAEnC,MAAI,UAAU;AAOZ,UAAM,aAAgC,CAAC,CAAC;AACxC,QAAI,OAAO,MAAM,SAAS;AACxB,iBAAW,KAAK,GAAG,CAAC;IACtB;AACA,QAAI,OAA+B;AACnC,QAAI,cAAc;AAClB,eAAW,KAAK,YAAY;AAC1B,YAAM,IAAI,MAAM,CAAC,GAAG,WAAW;AAC/B,UAAI,IAAI,aAAa;AACnB,sBAAc;AACd,eAAO;MACT;IACF;AACA,QAAI,SAAS,QAAQ,cAAc,GAAG;AACpC,qBAAe;AACf,YAAM,QAAQ,oBAAoB,cAAc;AAChD,uBACE,SAAS,IACL,GAAG,KAAK,8BAA8B,WAAW,WAAW,KAAK,MAAM,QAAQ,GAAG,CAAC,MACnF,GAAG,KAAK,KAAK,IAAI,yBAAyB,WAAW,kBAAkB,KAAK,QAAQ,KAAK,QAAQ,KAAK,YAAY,KAAK,MAAM,QAAQ,GAAG,CAAC;IACjJ;EACF,WAAW,aAAa;AACtB,QAAI,SAAS,mBAAmB;AAC9B,qBAAe;AACf,uBAAiB,gBAAgB,KAAK,OAAO,iBAAiB,YAAY,oBAAoB,WAAW,KAAK,MAAM,QAAQ,GAAG,CAAC;IAClI,WACE,OAAO,MAAM,WACb,SAAS,kBACT,QAAQ,OACR;AACA,YAAM,YAAY,MAAM,MAAM,gBAAgB,CAAC,KAAK;AACpD,YAAM,aACJ,cAAc,KAAK,aAAa,aAAa;AAC/C,UAAI,YAAY;AACd,uBAAe;AACf,yBAAiB,qBAAqB,MAAM,CAAC,EAAG,aAAa,MAAM,mBAAmB,KAAK,eAAe,cAAc,8BAA8B,KAAK,WAAW,KAAK,MAAM,QAAQ,GAAG,CAAC;MAC/L;IACF,WACE,OAAO,MAAM,WACb,SAAS,kBACT,QAAQ,SACR,QAAQ,OACR;AACA,YAAM,YAAY,MAAM,MAAM,gBAAgB,CAAC,KAAK;AACpD,YAAM,aACJ,cAAc,KAAK,aAAa,aAAa;AAC/C,UAAI,YAAY;AACd,uBAAe;AACf,yBAAiB,sBAAsB,MAAM,CAAC,EAAG,aAAa,MAAM,mBAAmB,KAAK,eAAe,cAAc,oBAAoB,KAAK,uBAAuB,KAAK,WAAW,KAAK,MAAM,QAAQ,GAAG,CAAC;MAClN;IACF;EACF;AAEA,QAAM,eAAe,iBAAiB;AAEtC,MAAI;AACJ,MAAI,iBAAiB,MAAM;AACzB,aAAS;EACX,WAAW,UAAU;AACnB,UAAM,QAAQ,oBAAoB,cAAc;AAChD,aAAS,GAAG,KAAK,WAAW,KAAK,MAAM,QAAQ,GAAG,CAAC,kEAAkE,KAAK,QAAQ,KAAK,QAAQ,KAAK;EACtJ,OAAO;AACL,UAAM,YAAY,CAAC,GAAG,GAAG,CAAC;AAC1B,UAAM,WAAW,UAAU,OAAO,CAAC,MAAM,OAAO,MAAM,WAAW,MAAM,CAAC;AACxE,UAAM,QAAQ,SACX,OAAO,CAAC,OAAO,MAAM,CAAC,GAAG,WAAW,MAAM,iBAAiB,EAC3D,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,MAAM,CAAC,EAAG,OAAO,EAAE;AAC1C,UAAM,YAAY,MAAM,SAAS,IAAI,YAAY,MAAM,KAAK,IAAI,CAAC,KAAK;AACtE,UAAM,UAAU,SACb,OAAO,CAAC,OAAO,MAAM,CAAC,GAAG,WAAW,MAAM,sBAAsB,MAAM,MAAM,gBAAgB,CAAC,KAAK,KAAK,KAAK,cAAc,MAAM,MAAM,gBAAgB,CAAC,KAAK,KAAK,WAAW,EAC5K,IAAI,CAAC,MAAM,IAAI,CAAC,YAAY;AAC/B,UAAM,cAAc,QAAQ,SAAS,IAAI,cAAc,QAAQ,KAAK,IAAI,CAAC,KAAK;AAC9E,UAAM,aAAa,KAAK,IAAI,GAAG,GAAG,OAAO,OAAO,KAAK,EAAE,IAAI,CAAC,MAAM,EAAE,OAAO,CAAC;AAK5E,UAAM,eAAe,aAAa;AAClC,UAAM,cAAc,uBAAuB;AAC3C,UAAM,QAAkB,CAAC;AACzB,QAAI,aAAc,OAAM,KAAK,oBAAoB,UAAU,gBAAgB,iBAAiB,EAAE;AAC9F,QAAI,YAAa,OAAM,KAAK,UAAU,oBAAoB,YAAY,WAAW,EAAE;AACnF,QAAI,MAAM,WAAW,EAAG,OAAM,KAAK,oBAAoB,UAAU,YAAY,oBAAoB,EAAE;AACnG,aAAS,GAAG,MAAM,KAAK,IAAI,CAAC,GAAG,SAAS,GAAG,WAAW;EACxD;AAEA,QAAM,eAAe,wBAAwB,MAAM,UAAU,YAAY,sBAAsB,WAAW;AAE1G,SAAO;IACL;IACA;IACA,oBAAoB,KAAK,qBAAqB,CAAC;IAC/C,iBAAiB,KAAK,cAAc,aAAa,CAAC;IAClD,kBAAkB,eAAe,MAAM,YAAY,EAAG,eAAe,CAAC;IACtE,cAAc;IACd,MAAM;IACN,WAAW;MACT;MACA,QAAQ;MACR;MACA;MACA;MACA;MACA,iBAAiB,kBAAkB,IAAI;MACvC,WAAW,YAAY,IAAI;MAC3B,mBAAmB,oBAAoB,IAAI;MAC3C,WAAW,MAAM,CAAC,EAAG;MACrB,WAAW,MAAM,CAAC,EAAG;MACrB,WAAW,MAAM,CAAC,EAAG;IACvB;IACA,kBAAkB;EACpB;AACF;AAEA,SAAS,wBAAwB,UAAyB,OAAe,QAAgB,aAAsD;AAC7I,QAAM,QAAQ,gBAAgB,CAAC,MAAc,KAAK,KAAK,EAAE,SAAS,CAAC;AACnE,MAAI,SAAS,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,GAAG,OAAO;AAC1D,aAAW,OAAO,UAAU;AAC1B,UAAM,SAAS,MAAM,IAAI,QAAQ,EAAE;AACnC,QAAI,IAAI,MAAM,WAAW,mCAAmC,GAAG;AAC7D,mBAAa;IACf,WAAW,IAAI,gBAAgB,eAAe,IAAI,gBAAgB,eAAe;AAC/E,cAAQ;IACV,WAAW,IAAI,SAAS,UAAU;AAChC,gBAAU;IACZ,WAAW,IAAI,MAAM,SAAS,KAAK,GAAG;AACpC,cAAQ;IACV,OAAO;AACL,cAAQ;IACV;EACF;AACA,SAAO,EAAE,QAAQ,MAAM,WAAW,MAAM,MAAM,OAAO,OAAO;AAC9D;AAEA,SAAS,WAAW,OAA2C;AAC7D,SAAO;IACL,QAAQ,MAAM,OAAO,IAAI,CAAC,WAAW;MACnC,GAAG;MACH,kBAAkB,CAAC,GAAG,MAAM,gBAAgB;MAC5C,qBAAqB,CAAC,GAAG,MAAM,mBAAmB;MAClD,gBAAgB,CAAC,GAAG,MAAM,cAAc;IAC1C,EAAE;IACF,aAAa;MACX,OAAO,EAAE,GAAG,MAAM,YAAY,MAAM;MACpC,OAAO,EAAE,GAAG,MAAM,YAAY,MAAM;IACtC;IACA,eAAe,EAAE,GAAI,MAAM,iBAAiB,CAAC,EAAG;IAChD,OAAO,EAAE,GAAG,MAAM,OAAO,SAAS,EAAE,GAAG,MAAM,MAAM,QAAQ,EAAE;IAC7D,OAAO,EAAE,GAAG,MAAM,MAAM;IACxB,aAAa,MAAM;IACnB,WAAW,MAAM;EACnB;AACF;AAEA,SAAS,eAAe,OAAyB,OAAyB;AACxE,QAAM,SAAS,MAAM,SAAS,IAAI,YAAY;AAC9C,QAAM,UAAU,MAAM,QAAQ,YAAY;AAC1C,MAAI,QAAQ;AACZ,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,iBAAiB,OAAO,IAAI;AAC9C,QAAI,YAAY,EAAG,UAAS,KAAK,IAAI,YAAY,MAAM,IAAI;AAC3D,UAAM,cAAc,iBAAiB,SAAS,IAAI;AAClD,QAAI,cAAc,EAAG,UAAS,KAAK,IAAI,cAAc,MAAM,GAAG;EAChE;AACA,SAAO,KAAK,IAAI,OAAO,CAAC;AAC1B;AAEA,SAAS,iBAAiB,UAAkB,QAAwB;AAClE,MAAI,CAAC,YAAY,CAAC,OAAQ,QAAO;AACjC,MAAI,QAAQ;AACZ,MAAI,WAAW;AACf,UAAQ,WAAW,SAAS,QAAQ,QAAQ,QAAQ,OAAO,IAAI;AAC7D;AACA,gBAAY,OAAO;EACrB;AACA,SAAO;AACT;AClpCO,IAAM,sBAAsB;;;;;AAM5B,IAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqC9B,IAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+B5B,IAAM,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC9C7B,IAAM,iBAA0B,OAAO,OAAO;EACnD,oBAAoB;EACpB,oBAAoB;EACpB,mBAAmB;EACnB,oBAAoB;AACtB,CAAC;AC7BD,SAAS,eAAe,SAA0B;AAChD,SAAO;;EAA6K,QAAQ,kBAAkB;AAChN;AAEA,SAAS,gBAAgB,SAA0B;AACjD,SAAO;;EAAiF,QAAQ,kBAAkB;AACpH;AAEA,SAAS,QAAQ,GAAmB;AAClC,MAAI,KAAK,IAAM,QAAO,IAAI,IAAI,KAAM,QAAQ,CAAC,CAAC;AAC9C,SAAO,GAAG,CAAC;AACb;AAEA,SAAS,gBAAgB,IAA+B;AACtD,MAAI,CAAC,GAAI,QAAO;AAChB,QAAM,QAAkB,CAAC;AACzB,MAAI,GAAG,SAAS,EAAG,OAAM,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC,SAAS;AAC5D,MAAI,GAAG,OAAO,EAAG,OAAM,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC,OAAO;AACtD,MAAI,GAAG,YAAY,EAAG,OAAM,KAAK,GAAG,QAAQ,GAAG,SAAS,CAAC,YAAY;AACrE,MAAI,GAAG,OAAO,EAAG,OAAM,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC,OAAO;AACtD,MAAI,GAAG,OAAO,EAAG,OAAM,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC,OAAO;AACtD,QAAM,SAAS,GAAG,SAAS,IAAI;GAAM,QAAQ,GAAG,MAAM,CAAC,sBAAsB;AAC7E,SAAO,sBAAsB,MAAM,KAAK,KAAK,CAAC,GAAG,MAAM;AACzD;AAIA,SAAS,uBAAuB,QAAoC;AAClE,MAAI,OAAO,WAAW,GAAG;AACvB,WAAO;EACT;AACA,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM;AAC9B,UAAM,gBAAgB,KAAK,MAAM,EAAE,WAAW,IAAI,SAAS,CAAC;AAC5D,UAAM,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,MAAM;AAC3C,WAAO,KAAK,EAAE,OAAO,KAAK,EAAE,oBAAoB,MAAM,UAAU,QAAQ,EAAE,gBAAgB,CAAC,SAAI,QAAQ,aAAa,CAAC,GAAG,KAAK;EAC/H,CAAC;AACD,SAAO,UAAU,OAAO,CAAC,EAAG,SAAS,IAAI,WAAW,QAAQ,uBAAuB,OAAO,MAAM;EAAO,MAAM,KAAK,IAAI,CAAC;AACzH;AAEO,SAAS,aAAa,cAAmC,iBAA2C;AACzG,MAAI,aAAa,WAAW,KAAK,gBAAgB,WAAW,GAAG;AAC7D,WAAO;EACT;AAaA,QAAMC,UAAS,CAAC,QAAwB;AACtC,UAAM,IAAI,IAAI,MAAM,KAAK;AACzB,WAAO,IAAI,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI;EAClC;AACA,QAAM,UAAoB,CAAC;AAC3B,aAAW,KAAK,cAAc;AAC5B,YAAQ,KAAK;MACX,UAAU,EAAE;MAAU,QAAQ,EAAE;MAAQ,UAAUA,QAAO,EAAE,QAAQ;MAAG,QAAQA,QAAO,EAAE,MAAM;MAC7F,OAAO,EAAE;MAAO,QAAQ,EAAE;MAAQ,SAAS,EAAE;MAAS,SAAS,EAAE;MACjE,oBAAoB,EAAE;MAAQ,mBAAmB,EAAE;MACnD,iBAAiB;MAAG,gBAAgB;MAAG,gBAAgB,CAAC;MAAG,WAAW,EAAE,aAAa;IACvF,CAAC;EACH;AACA,aAAW,KAAK,iBAAiB;AAC/B,YAAQ,KAAK;MACX,UAAU,EAAE;MAAU,QAAQ,EAAE;MAAQ,UAAUA,QAAO,EAAE,QAAQ;MAAG,QAAQA,QAAO,EAAE,MAAM;MAC7F,OAAO,EAAE;MAAO,QAAQ,EAAE;MAAQ,SAAS;MAAG,SAAS;MACvD,oBAAoB;MAAG,mBAAmB;MAC1C,iBAAiB,EAAE;MAAQ,gBAAgB,EAAE;MAAO,gBAAgB,CAAC,GAAG,EAAE,KAAK;MAAG,WAAW;IAC/F,CAAC;EACH;AACA,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;AAE9C,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,QAAQ,EAAE,YAAY,KAAK,SAAS,GAAG;AACzC,WAAK,SAAS,EAAE;AAChB,WAAK,SAAS,KAAK,IAAI,KAAK,QAAQ,EAAE,MAAM;AAC5C,WAAK,SAAS,EAAE;AAChB,WAAK,UAAU,EAAE;AACjB,WAAK,sBAAsB,EAAE;AAC7B,WAAK,qBAAqB,EAAE;AAC5B,WAAK,mBAAmB,EAAE;AAC1B,WAAK,kBAAkB,EAAE;AACzB,UAAI,EAAE,UAAW,MAAK,YAAY;AAClC,iBAAW,KAAK,EAAE,gBAAgB;AAChC,YAAI,CAAC,KAAK,eAAe,SAAS,CAAC,EAAG,MAAK,eAAe,KAAK,CAAC;MAClE;IACF,OAAO;AACL,aAAO,KAAK,EAAE,GAAG,EAAE,CAAC;IACtB;EACF;AACA,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM;AAC9B,UAAM,SAAS,EAAE,aAAa,EAAE,qBAAqB,IAAI,2DAAiD;AAC1G,QAAI,EAAE,kBAAkB,KAAK,EAAE,uBAAuB,GAAG;AACvD,aAAO,KAAK,EAAE,QAAQ,SAAI,EAAE,MAAM,KAAK,EAAE,KAAK,UAAU,QAAQ,EAAE,MAAM,CAAC,gBAAgB,EAAE,eAAe,KAAK,IAAI,CAAC,4BAAuB,MAAM;IACnJ;AACA,QAAI,EAAE,kBAAkB,KAAK,EAAE,qBAAqB,GAAG;AACrD,aAAO,KAAK,EAAE,QAAQ,SAAI,EAAE,MAAM,KAAK,EAAE,KAAK,UAAU,QAAQ,EAAE,MAAM,CAAC,KAAK,QAAQ,EAAE,kBAAkB,CAAC,mBAAmB,QAAQ,EAAE,eAAe,CAAC,eAAe,EAAE,eAAe,KAAK,IAAI,CAAC,IAAI,MAAM;IAC9M;AACA,WAAO,KAAK,EAAE,QAAQ,SAAI,EAAE,MAAM,KAAK,EAAE,KAAK,UAAU,QAAQ,EAAE,MAAM,CAAC,UAAU,EAAE,OAAO,YAAY,EAAE,OAAO,KAAK,MAAM;EAC9H,CAAC;AACD,SAAO,wBAAwB,OAAO,MAAM;EAAqB,MAAM,KAAK,IAAI,CAAC;AACnF;AAEO,SAAS,gBAAgB,UAAyB,UAAmB,gBAA+B;AACzG,QAAM,eAAe,gBAAgB,SAAS,gBAAgB;AAC9D,QAAM,YAAY,aAAa,SAAS,oBAAoB,SAAS,mBAAmB,CAAC,CAAC;AAC1F,QAAM,cAAc,CAAC,CAAC,SAAS,WAAW,qBAAqB,CAAC,CAAC,SAAS,WAAW;AAErF,MAAI,SAAS,SAAS,QAAQ,SAAS,QAAQ,GAAG;AAChD,UAAM,OAAO,SAAS,SAAS;AAC/B,UAAM,UAAU,SAAS,oBAAoB,CAAC;AAC9C,UAAM,YAAY,uBAAuB,OAAO;AAChD,UAAM,UAAU,QAAQ,CAAC,GAAG,WAAW;AACvC,UAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC,GAAG,WAAW;AACtD,UAAM,QAAoB,cAAc,cAAc;AACtD,UAAM,cAAc,cAChB,0BAAqB,SAAS,IAAI,IAAI,OAAO,iBAAiB,cAAc,wFAC5E,SAAS,SAAS,IAAI,IAAI,OAAO,iBAAiB,cAAc;AACpE,WAAO;MACL;MACA,MAAM;QACJ,eAAe,OAAO;QACtB;QACA;QACA;QACA;QACA,OACI,kbACA;QACJ;QACA,6CAA6C,OAAO,cAAc,KAAK;QACvE;QACA,QAAQ;QACR;QACA,OAAO,QAAQ,oBAAoB,QAAQ;MAC7C,EAAE,KAAK,IAAI;IACb;EACF;AAEA,MAAI,aAAa;AACf,WAAO;MACL,OAAO;MACP,MAAM;QACJ,gBAAgB,OAAO;QACvB;QACA;QACA;QACA,QAAQ;QACR;QACA;QACA;QACA;QACA;MACF,EAAE,KAAK,IAAI;IACb;EACF;AAEA,SAAO;IACL,OAAO;IACP,MAAM;MACJ,eAAe,OAAO;MACtB;MACA;MACA;MACA,QAAQ;MACR;MACA;MACA;MACA;IACF,EAAE,KAAK,IAAI;EACb;AACF;AE3LA,SAASC,cAAa,GAAmB;AACrC,MAAI,CAAC,OAAO,SAAS,CAAC,KAAK,KAAK,EAAG,QAAO;AAC1C,SAAO,KAAK,MAAO,IAAI,IAAI,KAAM,QAAQ,CAAC,CAAC,MAAM,OAAO,CAAC;AAC7D;AAEA,SAAS,IAAI,GAAW,OAAuB;AAC3C,MAAI,KAAK,KAAK,SAAS,EAAG,QAAO;AACjC,SAAO,KAAK,IAAI,GAAG,KAAK,MAAO,IAAI,QAAS,GAAG,CAAC;AACpD;AAEA,SAASC,aAAY,SAAyB;AAC1C,QAAM,QAAQ,WAAW,KAAK,OAAO;AACrC,SAAO,SAAS,MAAM,CAAC,MAAM,SAAY,OAAO,MAAM,CAAC,CAAC,IAAI;AAChE;AAEA,SAAS,gBAAgB,OAAyB,aAA4C;AAC1F,SAAO,YAAY,MAAM,OAAO;AACpC;AAEA,SAAS,0BACL,OACA,QACA,cACM;AAQN,SAAO,MAAM;AACjB;AAEA,SAAS,UAAU,OAAiC;AAChD,SAAO,IAAI,MAAM,IAAI;AACzB;AAEA,SAAS,cACL,QACA,aACa;AACb,QAAM,aAAqC,CAAC;AAC5C,aAAW,SAAS,QAAQ;AACxB,eAAW,MAAM,IAAI,KAAK,WAAW,MAAM,IAAI,KAAK,KAAK,gBAAgB,OAAO,WAAW;EAC/F;AACA,QAAM,QAAQ,OAAO,KAAK,UAAU,EAAE,IAAI,MAAM;AAChD,MAAI,MAAM,UAAU,EAAG,QAAO;AAC9B,QAAM,QAAkB,CAAC;AACzB,aAAW,QAAQ,CAAC,GAAG,GAAG,CAAC,GAAG;AAC1B,QAAI,WAAW,IAAI,EAAG,OAAM,KAAK,IAAI,IAAI,KAAKD,cAAa,WAAW,IAAI,CAAC,CAAC,EAAE;EAClF;AACA,SAAO,MAAM,KAAK,KAAK;AAC3B;AASA,SAAS,eACL,UACA,OACA,aACwD;AACxD,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,SAAS,MAAM,QAAQ;AAC9B,QAAI,CAAC,MAAM,OAAQ;AACnB,eAAW,MAAM,MAAM,oBAAqB,YAAW,IAAI,EAAE;EACjE;AACA,MAAI,gBAAgB;AACpB,aAAW,SAAS,MAAM,QAAQ;AAC9B,QAAI,MAAM,OAAQ,kBAAiB,gBAAgB,OAAO,WAAW;EACzE;AACA,QAAM,UAAgC,CAAC;AACvC,WAAS,QAAQ,CAAC,SAAS,UAAU;AACjC,QAAI,WAAW,IAAI,QAAQ,EAAE,EAAG;AAChC,UAAM,MAAM,UAAU,MAAM,aAAa,QAAQ,EAAE;AACnD,QAAI,CAAC,IAAK;AACV,UAAM,SAAS,YAAY,QAAQ,QAAQ,EAAE;AAC7C,UAAM,OAAO,QAAQ,YAAY;AACjC,QAAI,SAAS,EAAG,SAAQ,KAAK,EAAE,KAAK,QAAQ,MAAM,MAAM,CAAC;EAC7D,CAAC;AACD,SAAO,EAAE,SAAS,cAAc;AACpC;AAUO,SAAS,kBACZ,OACA,UACA,aACA,UAA+B,CAAC,GAC1B;AACN,QAAM,QAAQ,QAAQ;AACtB,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,aAAa,QAAQ;AAC3B,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,QAAQ,QAAQ,SAAS;AAE/B,QAAME,gBAAe,MAAM,OACtB,OAAO,CAAC,MAAM,EAAE,MAAM,EACtB,KAAK,CAAC,GAAG,MAAMD,aAAY,EAAE,OAAO,IAAIA,aAAY,EAAE,OAAO,CAAC;AAEnE,MAAI,UAAU,cAAc;AACxB,WAAO,0BAA0BC,eAAc,OAAO,MAAM,OAAO,WAAW;EAClF;AAEA,QAAM,EAAE,SAAS,cAAc,IAAI,eAAe,UAAU,OAAO,WAAW;AAE9E,MAAI,UAAU,gBAAgB;AAC1B,QAAI,SAAS,YAAY;AACrB,aAAO,uBAAuB,SAAS,YAAY,MAAM,KAAK;IAClE;AACA,WAAO,yBAAyB,OAAO;EAC3C;AAEA,SAAO,eAAe,SAAS,eAAeA,eAAc,OAAO,aAAa,KAAK;AACzF;AAEA,SAAS,eACL,SACA,eACA,QACA,OACA,aACA,OACM;AACN,QAAM,QAAkB,CAAC;AACzB,QAAM,cAAc,oBAAI,IAAoB;AAC5C,aAAW,WAAW,SAAS;AAC3B,gBAAY,IAAI,QAAQ,OAAO,YAAY,IAAI,QAAQ,IAAI,KAAK,KAAK,QAAQ,MAAM;EACvF;AACA,QAAM,UAAU,CAAC,GAAG,YAAY,QAAQ,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC;AAE7E,QAAM,YAAY,QACb,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACzC,QAAM,YAAY,QACb,OAAO,CAAC,MAAM,EAAE,SAAS,MAAM,EAC/B,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;AACzC,QAAM,QAAQ,gBAAgB,YAAY;AAE1C,QAAM,KAAK,mBAAmB;AAC9B,QAAM;IACF,KAAKF,cAAa,SAAS,CAAC,UAAU,IAAI,WAAW,KAAK,CAAC,QAAQA,cAAa,SAAS,CAAC,UAAU,IAAI,WAAW,KAAK,CAAC,QAAQA,cAAa,aAAa,CAAC,eAAe,IAAI,eAAe,KAAK,CAAC;EACxM;AACA,QAAM,WAAW,CAAC,GAAG,YAAY,QAAQ,CAAC,EACrC,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,EAC1B,MAAM,GAAG,CAAC;AACf,MAAI,SAAS,SAAS,GAAG;AACrB,UAAM,KAAK,gBAAgB,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,IAAI,EAAE,KAAK,IAAI,CAAC,EAAE;EAChG;AAEA,QAAM,KAAK,EAAE;AACb,MAAI,OAAO,WAAW,GAAG;AACrB,UAAM,KAAK,mBAAmB;AAC9B,UAAM,KAAK,yBAAyB;EACxC,OAAO;AACH,UAAM,eAAe,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,gBAAgB,GAAG,WAAW,GAAG,CAAC;AACnF,UAAM,iBAAiB,OAAO;MAC1B,CAAC,GAAG,MAAM,IAAI,0BAA0B,GAAG,OAAO,WAAW;MAC7D;IACJ;AACA,UAAM;MACF,4BAAuB,OAAO,MAAM,YAAYA,cAAa,YAAY,CAAC,aAAaA,cAAa,cAAc,CAAC;IACvH;AACA,UAAM,YAAY,cAAc,QAAQ,WAAW;AACnD,QAAI,UAAW,OAAM,KAAK,iBAAiB,SAAS,EAAE;AACtD,UAAM,KAAK,EAAE;AACb,UAAM,SAAS,CAAC,GAAG,MAAM,EAAE;MACvB,CAAC,GAAG,MACA,0BAA0B,GAAG,OAAO,WAAW,IAC3C,0BAA0B,GAAG,OAAO,WAAW,KACnD,EAAE,YAAY,EAAE;IACxB;AACA,eAAW,SAAS,OAAO,MAAM,GAAG,KAAK,GAAG;AACxC,YAAM,QAAQ,MAAM,SAAS;AAC7B,YAAM,MAAM,0BAA0B,OAAO,OAAO,WAAW;AAC/D,YAAM;QACF,KAAK,MAAM,OAAO,KAAK,UAAU,KAAK,CAAC,MAAMA,cAAa,GAAG,CAAC,SAAIA,cAAa,gBAAgB,OAAO,WAAW,CAAC,CAAC,KAAK,MAAM,oBAAoB,MAAM,WAAW,KAAK;MAC5K;IACJ;EACJ;AAEA,QAAM,KAAK,EAAE;AACb,QAAM;IACF,wEAAwE,WAAW,MAAM;EAC7F;AACA,SAAO,MAAM,KAAK,IAAI;AAC1B;AAEA,SAAS,yBAAyB,SAAuC;AACrE,QAAM,QAAkB,CAAC;AACzB,QAAM,cAAc,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AAC5D,QAAM,KAAK,uBAAkBA,cAAa,WAAW,CAAC,MAAM,QAAQ,MAAM,mBAAmB;AAC7F,QAAM,KAAK,EAAE;AACb,MAAI,QAAQ,WAAW,GAAG;AACtB,UAAM,KAAK,8BAA8B;AACzC,WAAO,MAAM,KAAK,IAAI;EAC1B;AAKA,QAAMG,UAAS,CAAC,QAAwB;AACpC,UAAM,IAAI,IAAI,MAAM,KAAK;AACzB,WAAO,IAAI,SAAS,EAAE,CAAC,GAAG,EAAE,IAAI;EACpC;AACA,QAAM,SAAmB,CAAC;AAC1B,aAAW,KAAK,SAAS;AACrB,UAAM,MAAMA,QAAO,EAAE,GAAG;AACxB,UAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,QAAI,QAAQ,QAAQ,KAAK,WAAW,KAAK,OAAO;AAC5C,WAAK,SAAS,EAAE;AAChB,WAAK,SAAS;AACd,WAAK,UAAU,EAAE;IACrB,OAAO;AACH,aAAO,KAAK,EAAE,UAAU,EAAE,KAAK,QAAQ,EAAE,KAAK,UAAU,KAAK,OAAO,GAAG,QAAQ,EAAE,QAAQ,MAAM,EAAE,KAAK,CAAC;IAC3G;EACJ;AACA,aAAW,KAAK,OAAO,MAAM,GAAG,EAAE,GAAG;AACjC,UAAM,QAAQ,EAAE,UAAU,IAAI,EAAE,WAAW,GAAG,EAAE,QAAQ,SAAI,EAAE,MAAM;AACpE,UAAM,KAAK,KAAK,KAAK,MAAM,EAAE,KAAK,UAAUH,cAAa,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,IAAI,KAAK,KAAK,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE;EACnJ;AACA,MAAI,OAAO,SAAS,IAAI;AACpB,UAAM,KAAK,aAAa,OAAO,SAAS,EAAE,cAAc;EAC5D;AACA,SAAO,MAAM,KAAK,IAAI;AAC1B;AAEA,SAAS,uBACL,SACA,YACA,MACA,OACM;AACN,MAAI,WAAW;AACf,MAAI,WAAY,YAAW,SAAS,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU;AAEvE,MAAI,SAAS,OAAQ,UAAS,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;WACrD,SAAS,OAAQ,UAAS,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,KAAK,EAAE,SAAS,EAAE,MAAM;MAChG,UAAS,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM;AAEhD,QAAM,cAAc,SAAS,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AAC7D,QAAM,YAAY,QAAQ,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC;AAC1D,QAAM,SAAS,aACT,uBAAkB,UAAU,KAAKA,cAAa,WAAW,CAAC,MAAM,SAAS,MAAM,WAAW,IAAI,aAAa,SAAS,CAAC,iBACrH,uBAAkBA,cAAa,WAAW,CAAC,MAAM,SAAS,MAAM;AACtE,QAAM,QAAQ,CAAC,QAAQ,aAAa,IAAI,IAAI,EAAE;AAC9C,QAAM,QAAQ,SAAS,MAAM,GAAG,KAAK;AACrC,aAAW,WAAW,OAAO;AACzB,UAAM,KAAK,KAAK,QAAQ,GAAG,KAAKA,cAAa,QAAQ,MAAM,CAAC,KAAK,QAAQ,IAAI,EAAE;EACnF;AACA,MAAI,SAAS,SAAS,MAAM,QAAQ;AAChC,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,GAAG,MAAM,MAAM,OAAO,SAAS,MAAM,SAAS;EAC7D;AACA,SAAO,MAAM,KAAK,IAAI;AAC1B;AAEA,SAAS,0BACL,QACA,OACA,MACA,OACA,aACM;AACN,MAAI,SAAS,CAAC,GAAG,MAAM;AACvB,MAAI,SAAS,OAAQ,QAAO,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,EAAE,SAAS;WAC3D,SAAS,MAAO,QAAO,KAAK,CAAC,GAAG,MAAM,EAAE,gBAAgB,EAAE,aAAa;;AAE5E,WAAO;MACH,CAAC,GAAG,MACA,0BAA0B,GAAG,OAAO,WAAW,IAC3C,0BAA0B,GAAG,OAAO,WAAW,KACnD,EAAE,YAAY,EAAE;IACxB;AAEJ,QAAM,eAAe,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,gBAAgB,GAAG,WAAW,GAAG,CAAC;AACnF,QAAM,iBAAiB,OAAO;IAC1B,CAAC,GAAG,MAAM,IAAI,0BAA0B,GAAG,OAAO,WAAW;IAC7D;EACJ;AACA,QAAM,QAAQ;IACV,qBAAgB,OAAO,MAAM,aAAaA,cAAa,cAAc,CAAC,oBAAeA,cAAa,YAAY,CAAC;EACnH;AACA,QAAM,YAAY,cAAc,QAAQ,WAAW;AACnD,MAAI,UAAW,OAAM,KAAK,eAAe,SAAS,EAAE;AACpD,QAAM,KAAK,EAAE;AACb,QAAM,QAAQ,OAAO,MAAM,GAAG,KAAK;AACnC,aAAW,SAAS,OAAO;AACvB,UAAM,SAAS,MAAM,eAAe,SAAS,IAAI,YAAY,MAAM,eAAe,KAAK,GAAG,CAAC,MAAM;AACjG,UAAM,QAAQ,MAAM,SAAS;AAC7B,UAAM,MAAM,0BAA0B,OAAO,OAAO,WAAW;AAC/D,UAAM;MACF,KAAK,MAAM,OAAO,KAAK,UAAU,KAAK,CAAC,MAAMA,cAAa,GAAG,CAAC,SAAIA,cAAa,gBAAgB,OAAO,WAAW,CAAC,CAAC,KAAK,MAAM,oBAAoB,MAAM,cAAc,MAAM,aAAa,IAAI,MAAM,UAAU,GAAG,MAAM;IAC1N;AACA,UAAM,KAAK,QAAQ,KAAK,GAAG;EAC/B;AACA,MAAI,OAAO,SAAS,MAAM,QAAQ;AAC9B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,GAAG,MAAM,MAAM,OAAO,OAAO,MAAM,SAAS;EAC3D;AACA,SAAO,MAAM,KAAK,IAAI;AAC1B;AGnTO,SAAS,KAAK,MAAsB;AACvC,MAAI,IAAI;AACR,MAAI,EAAE,UAAU,EAAG,QAAO;AAC1B,MAAI,EAAE,SAAS,KAAK,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE,IAAI;WACnC,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,KAAK,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;WAC9E,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,MAAM,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;WAC3D,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,IAAI,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AAChE,MAAI,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACxD,MAAI,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACvD,MAAI,EAAE,SAAS,OAAO,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;WACjD,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE,IAAI;WACzD,EAAE,SAAS,KAAK,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AAC7D,MAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACzD,MAAI,EAAE,SAAS,MAAM,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACzD,MAAI,EAAE,SAAS,IAAI,KAAK,EAAE,SAAS,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AACvD,SAAO;AACX;ACIO,IAAM,MAAM;AACnB,IAAM,aAAa;AAEnB,IAAM,eAAe,IAAI,KAAK,UAAU,MAAM,EAAE,aAAa,OAAO,CAAC;AAYrE,SAAS,aAAa,MAA0B;AAC5C,QAAM,QAAQ,KAAK,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC;AAC9C,MAAI,MAAM,SAAS,EAAG,QAAO;AAC7B,QAAM,MAAM,KAAK,KAAK,EAAE;AACxB,QAAM,MAAgB,CAAC;AACvB,WAAS,IAAI,GAAG,IAAI,IAAI,SAAS,GAAG,IAAK,KAAI,KAAK,IAAI,MAAM,GAAG,IAAI,CAAC,CAAC;AACrE,aAAW,MAAM,IAAK,KAAI,KAAK,EAAE;AACjC,SAAO;AACX;AAMO,SAAS,SAAS,MAAc,OAAwB,CAAC,GAAa;AACzE,QAAM,QAAQ,KAAK,YAAY;AAC/B,QAAM,SAAmB,CAAC;AAE1B,QAAM,QAAQ,MAAM,MAAM,UAAU,KAAK,CAAC;AAC1C,WAAS,KAAK,OAAO;AACjB,QAAI,EAAE,UAAU,GAAG;AACf,UAAI,KAAK,KAAM,KAAI,KAAK,CAAC;AACzB,aAAO,KAAK,CAAC;IACjB;EACJ;AAcA,MAAI,CAAC,IAAI,KAAK,KAAK,EAAG,QAAO;AAI7B,QAAM,UAAsB,CAAC;AAC7B,MAAI,MAAuB;AAC3B,aAAW,KAAK,aAAa,QAAQ,KAAK,GAAG;AACzC,UAAM,IAAI,EAAE;AACZ,QAAI,EAAE,WAAW,EAAG;AACpB,QAAI,IAAI,KAAK,CAAC,GAAG;AACb,OAAC,QAAQ,CAAC,GAAG,KAAK,CAAC;IACvB,WAAW,KAAK;AACZ,cAAQ,KAAK,GAAG;AAChB,YAAM;IACV;EACJ;AACA,MAAI,IAAK,SAAQ,KAAK,GAAG;AAEzB,aAAW,QAAQ,SAAS;AACxB,WAAO,KAAK,GAAG,aAAa,IAAI,CAAC;EACrC;AAEA,SAAO;AACX;AAGO,SAAS,YAAY,MAAwB;AAChD,QAAM,QAAkB,CAAC;AACzB,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,GAAG,KAAK;AACtC,UAAM,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;AAChC,QAAI,KAAK,KAAK,EAAE,WAAW,KAAK,OAAQ,OAAM,KAAK,IAAI;EAC3D;AACA,SAAO;AACX;AAGO,SAAS,MAAM,MAAcI,OAAoC;AACpE,QAAM,IAAI,oBAAI,IAAoB;AAClC,aAAW,KAAK,SAAS,MAAM,EAAE,MAAAA,MAAK,CAAC,EAAG,GAAE,IAAI,IAAI,EAAE,IAAI,CAAC,KAAK,KAAK,CAAC;AACtE,SAAO;AACX;AC3FA,IAAM,oBAAoB,IAAI,OAAO;AACrC,IAAI,WAAW;AACf,IAAM,QAAQ,oBAAI,IAAyB;AAC3C,IAAI,cAAc;AAElB,SAAS,MAAM,MAA2B;AACtC,QAAM,KAAK,MAAM,MAAM,IAAI;AAC3B,MAAI,MAAM;AACV,aAAW,KAAK,GAAG,OAAO,EAAG,QAAO;AACpC,QAAM,QAAQ,KAAK,YAAY;AAC/B,SAAO,EAAE,IAAI,KAAK,OAAO,OAAO,IAAI,IAAI,YAAY,KAAK,CAAC,EAAE;AAChE;AAEO,SAAS,YAAY,MAA2B;AACnD,QAAM,MAAM,MAAM,IAAI,IAAI;AAC1B,MAAI,IAAK,QAAO;AAChB,QAAM,IAAI,MAAM,IAAI;AACpB,MAAI,KAAK,SAAS,KAAK,KAAK,UAAU,UAAU;AAC5C,WAAO,cAAc,KAAK,SAAS,YAAY,MAAM,OAAO,GAAG;AAC3D,YAAM,IAAI,MAAM,KAAK,EAAE,KAAK,EAAE;AAC9B,qBAAe,EAAE;AACjB,YAAM,OAAO,CAAC;IAClB;AACA,UAAM,IAAI,MAAM,CAAC;AACjB,mBAAe,KAAK;EACxB;AACA,SAAO;AACX;ACjDO,IAAM,qBAAsC;EAC/C,MAAM;EACN,aAAa;EACb,MAAM,MAAmB,OAA8B;AACnD,UAAM,QAAQ,MAAM,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAChF,QAAI,MAAM,WAAW,EAAG,QAAO,KAAK,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,EAAE;AACzE,WAAO,KAAK,IAAI,CAAC,MAAM;AACnB,YAAM,WAAW,YAAY,EAAE,IAAI,EAAE;AACrC,UAAI,QAAQ;AACZ,iBAAW,QAAQ,MAAO,UAASC,kBAAiB,UAAU,IAAI;AAClE,aAAO,EAAE,KAAK,EAAE,KAAK,MAAM;IAC/B,CAAC;EACL;AACJ;AAEA,SAASA,kBAAiB,UAAkB,QAAwB;AAChE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,SAAS,MAAM,MAAM,EAAE,SAAS;AAC3C;ACXO,IAAM,gBAAiC;EAC1C,MAAM;EACN,aAAa;EACb,MAAM,MAAmB,OAA8B;AACnD,UAAM,IAAI,KAAK;AACf,UAAM,KAAK;AACX,UAAM,IAAI;AACV,UAAM,SAAS,KAAK,IAAI,CAAC,MAAM;AAC3B,YAAM,IAAI,YAAY,EAAE,IAAI;AAC5B,aAAO,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE,IAAI,KAAK,EAAE,IAAI;IAC7C,CAAC;AACD,UAAM,QAAQ,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,KAAK,CAAC,KAAK,KAAK;AAE5D,UAAM,SAAS,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;AAC7C,QAAI,OAAO,WAAW,EAAG,QAAO,KAAK,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,EAAE;AAE1E,UAAM,MAAM,oBAAI,IAAoB;AACpC,eAAW,KAAK,IAAI,IAAI,MAAM,GAAG;AAC7B,UAAI,KAAK;AACT,iBAAW,KAAK,OAAQ,KAAI,EAAE,GAAG,IAAI,CAAC,EAAG;AACzC,UAAI,IAAI,GAAG,KAAK,IAAI,KAAK,IAAI,KAAK,QAAQ,KAAK,IAAI,CAAC;IACxD;AAEA,WAAO,OAAO,IAAI,CAAC,MAAM;AACrB,UAAI,QAAQ;AACZ,iBAAW,KAAK,QAAQ;AACpB,cAAM,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK;AACzB,YAAI,MAAM,EAAG;AACb,cAAM,OAAO,IAAI,IAAI,CAAC,KAAK;AAC3B,iBAAU,QAAQ,KAAK,KAAK,OAAQ,IAAI,MAAM,IAAI,IAAK,IAAI,EAAE,OAAQ,SAAS;MAClF;AACA,aAAO,EAAE,KAAK,EAAE,IAAI,MAAM;IAC9B,CAAC;EACL;AACJ;ACzBO,IAAM,iBAAkC;EAC3C,MAAM;EACN,aAAa;EACb,MAAM,MAAmB,OAA8B;AAGnD,UAAM,UAAU,MAAM,YAAY,EAAE,MAAM,QAAQ,EAAE,OAAO,CAAC,MAAM,EAAE,UAAU,KAAM,EAAE,UAAU,KAAK,IAAI,KAAK,CAAC,CAAE;AACjH,QAAI,QAAQ,WAAW,EAAG,QAAO,KAAK,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,EAAE;AAE3E,UAAM,SAAS,oBAAI,IAAY;AAC/B,eAAW,KAAK,QAAS,YAAW,KAAK,YAAY,CAAC,EAAG,QAAO,IAAI,CAAC;AACrE,QAAI,OAAO,SAAS,EAAG,QAAO,KAAK,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,EAAE;AAExE,WAAO,KAAK,IAAI,CAAC,MAAM;AACnB,YAAM,WAAW,YAAY,EAAE,IAAI,EAAE;AACrC,UAAI,OAAO;AACX,iBAAW,KAAK,OAAQ,KAAI,SAAS,IAAI,CAAC,EAAG;AAC7C,aAAO,EAAE,KAAK,EAAE,KAAK,OAAO,OAAO,OAAO,KAAK;IACnD,CAAC;EACL;AACJ;ACxBA,IAAM,SAAS;AACf,IAAM,UAAU;AAET,IAAM,kBAAmC;EAC5C,MAAM;EACN,aAAa;EACb,MAAM,MAAmB,OAA8B;AACnD,UAAM,KAAK,cAAc,MAAM,MAAM,KAAK;AAC1C,UAAM,KAAK,eAAe,MAAM,MAAM,KAAK;AAC3C,UAAM,QAAQ,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;AACtD,UAAM,QAAQ,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI;AACtD,UAAM,QAAQ,IAAI,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,KAAK,CAAC,CAAC;AAC7D,UAAM,QAAQ,IAAI,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,QAAQ,KAAK,CAAC,CAAC;AAC7D,WAAO,KAAK,IAAI,CAAC,OAAO;MACpB,KAAK,EAAE;MACP,OAAO,UAAU,MAAM,IAAI,EAAE,GAAG,KAAK,KAAK,WAAW,MAAM,IAAI,EAAE,GAAG,KAAK;IAC7E,EAAE;EACN;AACJ;AC5BA,IAAMC,YAAW,oBAAI,IAAgC;AAE9C,SAAS,wBAAwB,MAAgC;AACpEA,YAAS,IAAI,KAAK,MAAM,IAAI;AAChC;AAEO,SAAS,mBAAmB,MAA8C;AAC7E,SAAOA,UAAS,IAAI,IAAI;AAC5B;AAOA,wBAAwB,kBAAkB;AAC1C,wBAAwB,aAAa;AACrC,wBAAwB,cAAc;AACtC,wBAAwB,eAAe;ACkBhC,IAAM,uBAA8C;EACvD,MAAM;EACN,WAAW;EACX,MAAM;EACN,OAAO;AACX;AAwDO,IAAM,oBAAoB;ACtDjC,SAAS,gBAAgB,QAAuB,MAAmB,IAA0C;AACzG,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,WAAW,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACpD,SAAO,OAAO,IAAI,CAAC,MAAM;AACrB,UAAM,MAAM,SAAS,IAAI,EAAE,GAAG;AAC9B,QAAI,CAAC,IAAK,QAAO;AACjB,UAAM,IACF,IAAI,SAAS,YACP,IAAI,SAAS,SACT,GAAG,OACH,IAAI,SAAS,cACX,GAAG,YACH,GAAG,OACT,GAAG;AACb,WAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,QAAQ,EAAE;EAC5C,CAAC;AACL;AAEA,SAAS,UACL,MACA,OACA,SACwC;AACxC,QAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,gBAAgB,QAAQ,iBAAiB;AAC/C,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,WAAW,QAAQ,aAAa;AACtC,QAAM,KAAK,EAAE,GAAG,sBAAsB,GAAG,QAAQ,YAAY;AAE7D,QAAM,OAAO,mBAAmB,QAAQ;AACxC,MAAI,CAAC,KAAM,QAAO,CAAC;AACnB,MAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAE/B,QAAM,kBAAkB,KAAK,MAAM,MAAM,KAAK;AAE9C,QAAM,eAAe,CAAC,aAA4C;AAC9D,UAAM,QAAQ,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;AACjD,WAAO,SACF,IAAI,CAAC,MAA2B;AAC7B,YAAM,MAAM,MAAM,IAAI,EAAE,GAAG;AAC3B,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO;QACH,MAAM,IAAI;QACV,KAAK,IAAI;QACT,SAAS,IAAI;QACb,MAAM,IAAI,QAAQ;QAClB,OAAO,EAAE;QACT,OAAO,IAAI;QACX,SAAS,YAAY,IAAI,MAAM,OAAO,aAAa;QACnD,MAAM,IAAI;QACV,QAAQ,IAAI;MAChB;IACJ,CAAC,EACA,OAAO,CAAC,MAAyB,MAAM,QAAQ,EAAE,SAAS,QAAQ,EAClE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EAChC,MAAM,GAAG,KAAK;EACvB;AAEA,MAAI,2BAA2B,SAAS;AACpC,WAAO,gBAAgB,KAAK,CAAC,QAAQ,aAAa,gBAAgB,KAAK,MAAM,EAAE,CAAC,CAAC;EACrF;AACA,SAAO,aAAa,gBAAgB,iBAAiB,MAAM,EAAE,CAAC;AAClE;AAGO,SAAS,aAAa,MAAmB,OAAe,UAAyB,CAAC,GAAmB;AACxG,QAAM,SAAS,UAAU,MAAM,OAAO,OAAO;AAC7C,MAAI,kBAAkB,SAAS;AAC3B,UAAM,IAAI;MACN,4BAA4B,QAAQ,aAAa,iBAAiB;IACtE;EACJ;AACA,SAAO;AACX;AAaA,SAAS,YAAY,MAAc,OAAe,KAAqB;AACnE,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,MAAM,YAAY,EAAE,KAAK,EAAE,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAChF,MAAI,MAAM,WAAW,EAAG,QAAO,KAAK,MAAM,GAAG,GAAG;AAEhD,QAAM,QAAQ,KAAK,YAAY;AAC/B,MAAI,SAAS;AACb,aAAW,QAAQ,OAAO;AACtB,UAAM,MAAM,MAAM,QAAQ,IAAI;AAC9B,QAAI,OAAO,GAAG;AACV,eAAS;AACT;IACJ;EACJ;AAEA,MAAI,SAAS,EAAG,QAAO,KAAK,MAAM,GAAG,GAAG;AAExC,QAAM,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,CAAC,IAAI,EAAE;AACjD,QAAM,QAAQ,KAAK,IAAI,GAAG,SAAS,IAAI;AACvC,QAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,QAAQ,GAAG;AAC7C,QAAM,SAAS,QAAQ,IAAI,WAAM;AACjC,QAAM,SAAS,MAAM,KAAK,SAAS,WAAM;AACzC,SAAO,SAAS,KAAK,MAAM,OAAO,GAAG,EAAE,KAAK,IAAI;AACpD;;;ACtJA,SAAS,kBAAkB;AAE3B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB,yBAA4C;;;ACUtE,SAAS,gBAAgB,SAA2C;AACzE,QAAM,WAAY,QAAgC,iBAAiB;AACnE,MAAI,aAAa,OAAW,QAAO;AACnC,SAAQ,QAA8B;AACxC;AAGO,SAAS,UAAU,SAAkB,KAAuC;AACjF,QAAM,UAAW,QAAgC;AACjD,MAAI,OAAO,YAAY,WAAY,QAAO,QAAQ,KAAK,SAAS,GAAG;AACnE,SAAQ,QAA8B,OAAO,GAAG;AAClD;;;AChBO,SAAS,YAAY,SAA0B;AACpD,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO;AACpC,QAAM,QAAkB,CAAC;AACzB,aAAW,SAAS,SAAS;AAC3B,QAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AACjD,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,UAAU;AACnD,YAAM,KAAK,EAAE,IAAI;AAAA,IACnB,WAAW,MAAM,QAAQ,EAAE,OAAO,GAAG;AACnC,YAAM,KAAK,YAAY,EAAE,OAAO,CAAC;AAAA,IACnC;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AASA,SAAS,YAAY,SAAmC;AACtD,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO,CAAC;AACrC,SAAO,QAAQ,OAAO,CAAC,MAA2B,EAAwB,SAAS,WAAW;AAChG;AAEA,SAAS,cAAc,MAAuB;AAC5C,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,OAAO,SAAS,SAAU,QAAO;AACrC,MAAI;AACF,WAAO,KAAK,UAAU,IAAI;AAAA,EAC5B,QAAQ;AACN,WAAO,OAAO,IAAI;AAAA,EACpB;AACF;AAUO,SAAS,wBAAwB,OAAoC;AAC1E,MAAI,MAAM,SAAS,cAAe,QAAO;AACzC,QAAM,UAAW,MAAM,KAEpB;AACH,QAAM,QAAQ,MAAM,QAAQ,SAAS,OAAO,IACxC,QAAQ,QAAQ,KAAK,CAAC,cAAc,WAAW,SAAS,aAAa,IACrE;AACJ,QAAM,KAAK,OAAO,cAAc,SAAS,QAAQ;AACjD,SAAO,OAAO,OAAO,WAAW,KAAK;AACvC;AASO,SAAS,mBAAmB,QAA8D;AAC/F,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,oBAAqB;AACxC,UAAM,UAAW,MAAM,KAA6C,SAAS;AAC7E,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,eAAW,SAAS,SAAS;AAC3B,YAAM,YAAY;AAClB,UAAI,cAAc,QAAQ,OAAO,cAAc,YAAY,UAAU,SAAS,eAAe,OAAO,UAAU,OAAO,UAAU;AAC7H,cAAM,IAAI,UAAU,IAAI,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO,EAAE;AAAA,MAClF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAaO,SAAS,aAAa,OAAqB,WAAwD;AACxG,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK,gBAAgB;AACnB,YAAM,OAAO,YAAa,MAAM,KAA+B,OAAO;AACtE,aAAO,KAAK,SAAS,IAAI,CAAC,EAAE,IAAI,OAAO,MAAM,GAAG,GAAG,MAAM,QAAQ,aAAa,QAAQ,KAAK,CAAC,IAAI,CAAC;AAAA,IACnG;AAAA,IACA,KAAK,qBAAqB;AACxB,YAAM,UAAW,MAAM,KAA6C,SAAS;AAC7E,YAAM,QAAQ,YAAY,OAAO;AACjC,YAAM,OAAO,YAAY,OAAO;AAChC,UAAI,MAAM,WAAW,GAAG;AACtB,eAAO,KAAK,KAAK,EAAE,SAAS,IACxB,CAAC,EAAE,IAAI,OAAO,MAAM,GAAG,GAAG,MAAM,aAAa,aAAa,QAAQ,KAAK,CAAC,IACxE,CAAC;AAAA,MACP;AACA,UAAI,MAAM,WAAW,GAAG;AACtB,cAAM,OAAO,MAAM,CAAC;AACpB,cAAM,SAAS,cAAc,KAAK,SAAS;AAC3C,cAAM,OAAO,UAAU,OAAO,GAAG,IAAI;AAAA,EAAK,MAAM,KAAK,UAAU;AAC/D,eAAO,CAAC;AAAA,UACN,IAAI,OAAO,MAAM,GAAG;AAAA,UACpB,MAAM;AAAA,UACN,aAAa;AAAA,UACb,UAAU,KAAK,QAAQ;AAAA,UACvB,YAAY,KAAK,MAAM;AAAA,UACvB,MAAM;AAAA,QACR,CAAC;AAAA,MACH;AACA,aAAO,MAAM,IAAI,CAAC,UAAU;AAAA,QAC1B,IAAI,GAAG,MAAM,GAAG,IAAI,KAAK,MAAM,EAAE;AAAA,QACjC,MAAM;AAAA,QACN,aAAa;AAAA,QACb,UAAU,KAAK,QAAQ;AAAA,QACvB,YAAY,KAAK,MAAM;AAAA,QACvB,MAAM,cAAc,KAAK,SAAS,KAAK;AAAA,MACzC,EAAE;AAAA,IACJ;AAAA,IACA,KAAK,eAAe;AAClB,YAAM,UAAW,MAAM,KAEpB;AACH,YAAM,OAAO,YAAY,SAAS,OAAO;AACzC,UAAI,KAAK,WAAW,EAAG,QAAO,CAAC;AAC/B,YAAM,MAAM,wBAAwB,KAAK;AACzC,aAAO,CAAC;AAAA,QACN,IAAI,OAAO,MAAM,GAAG;AAAA,QACpB,MAAM;AAAA,QACN,aAAa;AAAA,QACb,UAAU,WAAW,IAAI,OAAO,EAAE,KAAK;AAAA,QACvC,YAAY,SAAS,cAAc,OAAO;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA;AACE,aAAO,CAAC;AAAA,EACZ;AACF;AAGO,SAAS,qBAAqB,QAAiC,WAAwD;AAC5H,QAAM,QAAQ,aAAa,mBAAmB,MAAM;AACpD,QAAM,MAAqB,CAAC;AAC5B,aAAW,SAAS,OAAQ,KAAI,KAAK,GAAG,aAAa,OAAO,KAAK,CAAC;AAClE,SAAO;AACT;AAGO,SAAS,gBAAgB,SAAkC;AAChE,SAAO,QAAQ,QAAQ,MACpB,IAAI,CAAC,QAAQ,UAAU,SAAS,GAAG,CAAC,EACpC,OAAO,CAAC,UAAiC,UAAU,MAAS;AACjE;AASO,SAAS,eAAe,SAAoE;AACjG,SAAO,qBAAqB,gBAAgB,OAAO,CAAC;AACtD;AAGO,SAAS,iBAAiB,OAA6B;AAC5D,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,YAAa,MAAM,KAA+B,OAAO;AAAA,IAClE,KAAK;AACH,aAAO,YAAa,MAAM,KAA6C,SAAS,OAAO;AAAA,IACzF,KAAK;AACH,aAAO,YAAa,MAAM,KAA6C,SAAS,OAAO;AAAA,IACzF;AACE,aAAO;AAAA,EACX;AACF;;;AClMA,SAAS,0BAA0B;AAInC,IAAM,kBAAkB;AAExB,IAAM,iBAAiB;AAEvB,IAAM,gBAAgB;AAatB,SAAS,UAAU,OAAoC;AACrD,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,OAAQ,MAA6B;AAC3C,SAAO,OAAO,SAAS,WAAW,OAAO;AAC3C;AAWO,SAAS,oBAAoB,QAA6B;AAC/D,MAAI,OAAO,WAAW,UAAU;AAC9B,QAAIC,UAAS;AACb,eAAW,QAAQ,QAAQ;AACzB,MAAAA,WAAU,iBAAiB,KAAK,KAAK,KAAK,UAAU,IAAI,EAAE,SAAS,eAAe;AAAA,IACpF;AACA,WAAOA;AAAA,EACT;AACA,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC1B,YAAQ,UAAU,KAAK,GAAG;AAAA,MACxB,KAAK;AAAA,MACL,KAAK,aAAa;AAChB,kBAAU,KAAK,KAAM,MAA2B,KAAK,SAAS,eAAe,IAAI;AACjF;AAAA,MACF;AAAA,MACA,KAAK,aAAa;AAChB,cAAM,OAAO;AACb,kBAAU,KAAK,KAAK,KAAK,KAAK,SAAS,eAAe,IAClD,KAAK,KAAK,KAAK,UAAU,SAAS,eAAe,IACjD;AACJ;AAAA,MACF;AAAA,MACA,KAAK,eAAe;AAClB,kBAAU,oBAAqB,MAAmC,OAAO,IAAI;AAC7E;AAAA,MACF;AAAA,MACA;AACE,kBAAU,iBAAiB,KAAK,KAAK,KAAK,UAAU,KAAK,EAAE,SAAS,eAAe;AAAA,IACvF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,oBAAoB,SAA2C;AAC7E,SAAO,oBAAoB,QAAQ,OAAO,IAAI;AAChD;AAOO,SAAS,eAAe,OAA6B;AAC1D,QAAM,UAAU,mBAAmB,KAAK;AACxC,SAAO,YAAY,OAAO,IAAI,oBAAoB,OAAmC;AACvF;AAGO,SAAS,mBAAmB,SAAkB,MAAiC;AACpF,MAAI,QAAQ;AACZ,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,QAAI,UAAU,OAAW,UAAS,eAAe,KAAK;AAAA,EACxD;AACA,SAAO;AACT;AAeO,SAAS,uBACd,SACA,MACA,KACQ;AACR,MAAI;AACF,UAAM,QAAQ,KAAK,MAAM,YAAY;AACrC,QAAI,OAAO,YAAY,QAAW;AAChC,YAAM,QAAQ,IAAI,IAAI,MAAM,QAAQ,OAAO,EAAE,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,CAAC;AACzF,UAAI,QAAQ;AACZ,UAAI,UAAU;AACd,iBAAW,OAAO,MAAM;AACtB,cAAM,SAAS,MAAM,IAAI,GAAG;AAC5B,YAAI,WAAW,QAAW;AACxB,oBAAU;AACV;AAAA,QACF;AACA,iBAAS;AAAA,MACX;AACA,UAAI,CAAC,QAAS,QAAO;AAAA,IACvB;AAAA,EACF,QAAQ;AAAA,EAER;AACA,SAAO,mBAAmB,SAAS,IAAI;AACzC;;;AHtFO,SAAS,aAAa,QAAgD;AAC3E,MAAI,OAAsB;AAC1B,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,aAAc,QAAO,MAAM,KAAK;AAAA,aAC1C,MAAM,SAAS,cAAc,MAAM,KAAK,SAAS,KAAM,QAAO;AAAA,EACzE;AACA,SAAO;AACT;AAeO,SAAS,yBAAyB,QAAuC;AAC9E,MAAI,SAAS;AACb,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,mBAAoB,UAAS;AAAA,aACvC,MAAM,SAAS,iBAAkB,UAAS;AAAA,EACrD;AACA,MAAI,QAAQ;AACV,YAAQ,KAAK,qHAAgH;AAAA,EAC/H;AACF;AAWA,SAAS,YAAY,SAAkB,KAAsB;AAC3D,QAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,MAAI,UAAU,OAAW,QAAO;AAChC,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAAA,IACL,KAAK;AACH,aAAO,iBAAiB,KAAK,EAAE,KAAK,EAAE,SAAS;AAAA,IACjD,KAAK,qBAAqB;AACxB,YAAM,UAAW,MAAM,KAA6C,SAAS;AAC7E,YAAM,QAAQ,MAAM,QAAQ,OAAO,IAC/B,QAAQ;AAAA,QACN,CAAC,UAAU,UAAU,QAAQ,OAAO,UAAU,YAAa,MAA4B,SAAS;AAAA,MAClG,IACA,CAAC;AACL,UAAI,MAAM,SAAS,EAAG,QAAO;AAG7B,aAAO,MAAM,WAAW,KAAK,iBAAiB,KAAK,EAAE,KAAK,EAAE,SAAS;AAAA,IACvE;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AASO,IAAM,8BAAN,cAA0C,MAAM;AAAA,EACrD,YACW,OACA,KACA,kBACT;AACA;AAAA,MACE,4BAA4B,KAAK,KAAK,GAAG;AAAA,IAE3C;AAPS;AACA;AACA;AAMT,SAAK,OAAO;AAAA,EACd;AAAA,EATW;AAAA,EACA;AAAA,EACA;AAQb;AAyBA,SAAS,kBAAkB,SAAkB,OAAe,KAAiC;AAC3F,MAAI,UAAU,SAAS,KAAK,MAAM,UAAa,UAAU,SAAS,GAAG,MAAM,QAAW;AACpF,UAAM,aAAa,UAAU,SAAS,KAAK,MAAM,SAAY,QAAQ;AACrE,WAAO,EAAE,MAAM,gBAAgB,WAAW;AAAA,EAC5C;AACA,QAAM,aAAa,QAAQ,QAAQ,MAChC,OAAO,CAAC,QAAQ,OAAO,SAAS,OAAO,GAAG,EAC1C,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvB,QAAM,QAAQ,WAAW,OAAO,CAAC,QAAQ,CAAC,iBAAiB,UAAU,SAAS,GAAG,CAAE,CAAC;AACpF,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,mBAAmB,mBAAmB,gBAAgB,OAAO,CAAC,EACjE,OAAO,CAAC,UAAU,MAAM,aAAa,KAAK,CAAC,QAAQ,OAAO,SAAS,OAAO,GAAG,CAAC,EAC9E,IAAI,CAAC,UAAU,MAAM,OAAO;AAC/B,WAAO,EAAE,MAAM,sBAAsB,iBAAiB;AAAA,EACxD;AACA,SAAO,EAAE,MAAM,MAAM,OAAO,MAAM,CAAC,GAAI,KAAK,MAAM,MAAM,SAAS,CAAC,EAAG;AACvE;AAgCO,SAAS,oBACd,SACA,OACA,KACsB;AACtB,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,MAAI,QAAQ,KAAK;AACf,UAAM,IAAI,MAAM,uCAAuC,KAAK,KAAK,GAAG,EAAE;AAAA,EACxE;AACA,MAAI,oBAAoB,MAAM,QAAQ,KAAmB;AACzD,MAAI,kBAAkB,MAAM,QAAQ,GAAiB;AACrD,MAAI,YAAY;AAChB,MAAI,oBAAoB,KAAK,kBAAkB,GAAG;AAChD,UAAM,QAAQ,kBAAkB,SAAS,OAAO,GAAG;AACnD,QAAI,MAAM,SAAS,gBAAgB;AACjC,YAAM,IAAI;AAAA,QACR,4BAA4B,KAAK,KAAK,GAAG,+CAC3B,MAAM,UAAU;AAAA,MAGhC;AAAA,IACF;AACA,QAAI,MAAM,SAAS,sBAAsB;AACvC,YAAM,IAAI,4BAA4B,OAAO,KAAK,MAAM,gBAAgB;AAAA,IAC1E;AACA,YAAQ,MAAM;AACd,UAAM,MAAM;AACZ,gBAAY;AACZ,wBAAoB,MAAM,QAAQ,KAAmB;AACrD,sBAAkB,MAAM,QAAQ,GAAiB;AACjD,QAAI,oBAAoB,KAAK,kBAAkB,GAAG;AAGhD,YAAM,IAAI;AAAA,QACR,4BAA4B,KAAK,KAAK,GAAG;AAAA,MAE3C;AAAA,IACF;AAAA,EACF;AACA,MAAI,oBAAoB,iBAAiB;AACvC,UAAM,IAAI,MAAM,uCAAuC,KAAK,KAAK,GAAG,EAAE;AAAA,EACxE;AAGA,MAAI,QAAQ,KAAK;AACf,UAAM,IAAI,MAAM,uCAAuC,KAAK,KAAK,GAAG,EAAE;AAAA,EACxE;AAEA,QAAM,cAAc,CAAC,UACnB,0BAA0B,SAAS,MAAM,KAAK,CAAE,KAAK,YAAY,SAAS,MAAM,KAAK,CAAE;AACzF,QAAM,aAAa,CAAC,UAClB,yBAAyB,SAAS,MAAM,KAAK,CAAE,KAAK,YAAY,SAAS,MAAM,KAAK,CAAE;AACxF,MAAI,WAAW;AACf,MAAI,SAAS;AAEb,SAAO,YAAY,UAAU,CAAC,YAAY,QAAQ,GAAG;AACnD,gBAAY;AAAA,EACd;AACA,SAAO,UAAU,YAAY,CAAC,WAAW,MAAM,GAAG;AAChD,cAAU;AAAA,EACZ;AACA,MAAI,YAAY,UAAU,MAAM,QAAQ,KAAM,MAAM,MAAM,GAAI;AAC5D,WAAO,YACH,EAAE,OAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,MAAM,GAAI,WAAW,KAAK,IAChE,EAAE,OAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,MAAM,EAAG;AAAA,EACrD;AAKA,MAAI,WAAW;AACb,UAAM,IAAI;AAAA,MACR,2EAA2E,KAAK,KAAK,GAAG;AAAA,IAE1F;AAAA,EACF;AAGA,aAAW;AACX,WAAS;AACT,SAAO,WAAW,KAAK,CAAC,YAAY,QAAQ,GAAG;AAC7C,gBAAY;AAAA,EACd;AACA,SAAO,SAAS,MAAM,SAAS,KAAK,CAAC,WAAW,MAAM,GAAG;AACvD,cAAU;AAAA,EACZ;AAKA,MAAI,YAAY,QAAQ,KAAK,WAAW,MAAM,KAAK,MAAM,QAAQ,KAAM,MAAM,MAAM,GAAI;AACrF,WAAO,EAAE,OAAO,MAAM,QAAQ,GAAI,KAAK,MAAM,MAAM,EAAG;AAAA,EACxD;AACA,QAAM,IAAI;AAAA,IACR,kEAAkE,KAAK,KAAK,GAAG;AAAA,EAEjF;AACF;AAGO,SAAS,eAAe,SAAkB,OAAe,KAAuB;AACrF,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,WAAW,MAAM,QAAQ,KAAmB;AAClD,QAAM,SAAS,MAAM,QAAQ,GAAiB;AAC9C,SAAO,MAAM,MAAM,UAAU,SAAS,CAAC;AACzC;AAkDO,SAAS,sBAAsB,OAAyE;AAC7G,SAAO,MAAM;AACf;AAMO,SAAS,yBACd,SACA,OAC0C;AAC1C,2BAAyB,gBAAgB,OAAO,CAAC;AACjD,QAAM,OAAO,aAAa,gBAAgB,OAAO,CAAC;AAClD,QAAM,eAAe,aAAa,WAAW,CAAC;AAC9C,QAAM,OAAiB,CAAC;AAOxB,MAAI,MAAM,QAAQ,MAAM,KAAK;AAC3B,UAAM,IAAI,MAAM,uCAAuC,MAAM,KAAK,KAAK,MAAM,GAAG,EAAE;AAAA,EACpF;AACA,MAAI,UAAU,SAAS,MAAM,KAAK,MAAM,UAAa,UAAU,SAAS,MAAM,GAAG,MAAM,QAAW;AAChG,UAAM,aAAa,UAAU,SAAS,MAAM,KAAK,MAAM,SAAY,MAAM,QAAQ,MAAM;AACvF,UAAM,IAAI;AAAA,MACR,4BAA4B,MAAM,KAAK,KAAK,MAAM,GAAG,+CACvC,UAAU;AAAA,IAG1B;AAAA,EACF;AAEA,MAAI;AACF,SAAK,KAAK,QAAQ,OAAO,oBAAoB,EAAE,cAAc,KAAK,CAAC,EAAE,GAAG;AACxE,SAAK,KAAK,QAAQ,OAAO,sBAAsB;AAAA,MAC7C;AAAA,MACA,SAAS,MAAM;AAAA,MACf,eAAe,EAAE,OAAO,MAAM,OAAO,KAAK,MAAM,IAAI;AAAA,MACpD,cAAc,CAAC,GAAG,MAAM,YAAY;AAAA,MACpC,oBAAoB,MAAM;AAAA,MAC1B,UAAU,MAAM;AAAA,MAChB,OAAO,MAAM;AAAA,MACb,MAAM,MAAM,QAAQ;AAAA,MACpB,GAAI,MAAM,kBAAkB,SAAY,CAAC,IAAI,EAAE,eAAe,MAAM,cAAc;AAAA,MAClF,GAAI,MAAM,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,MAC1D,GAAI,MAAM,mBAAmB,UAAa,MAAM,eAAe,WAAW,IACtE,CAAC,IACD,EAAE,gBAAgB,CAAC,GAAG,MAAM,cAAc,EAAE;AAAA,MAChD,GAAI,MAAM,qBAAqB,SAAY,CAAC,IAAI,EAAE,kBAAkB,CAAC,GAAG,MAAM,gBAAgB,EAAE;AAAA,MAChG,GAAI,MAAM,wBAAwB,SAAY,CAAC,IAAI,EAAE,qBAAqB,CAAC,GAAG,MAAM,mBAAmB,EAAE;AAAA,IAC3G,CAAuD,EAAE,GAAG;AAE5D,UAAM,UAAU,kBAAkB;AAAA,MAChC,SAAS,MAAM;AAAA,MACf,QAAQ,wBAAwB,YAAY;AAAA,IAC9C,CAAC;AACD,SAAK,KAAK,QAAQ,OAAO,gBAAgB,SAAS;AAAA,MAChD,WAAW,EAAE,IAAI,WAAW,OAAO,MAAM,OAAqB,KAAK,MAAM,IAAkB;AAAA,MAC3F,iBAAiB,CAAC,GAAG,MAAM,YAAY;AAAA,IACzC,CAAC,EAAE,GAAG;AAEN,SAAK,KAAK,QAAQ,OAAO,kBAAkB,EAAE,cAAc,KAAK,CAAC,EAAE,GAAG;AAAA,EACxE,SAAS,OAAO;AAQd,QAAI;AACF,cAAQ,OAAO,kBAAkB,EAAE,cAAc,KAAK,CAAC;AAAA,IACzD,SAAS,iBAAiB;AAGxB,cAAQ,KAAK,sEAAsE,eAAe;AAAA,IACpG;AACA,UAAM;AAAA,EACR;AACA,SAAO,EAAE,cAAc,KAAK;AAC9B;AAGA,SAAS,uBAAuB,QAAiC,cAAqC;AACpG,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,eAAgB;AACnC,UAAM,SAAU,MAAM,KAAiE;AACvF,QAAI,QAAQ,WAAW,aAAa,OAAO,iBAAiB,aAAc,QAAO,MAAM;AAAA,EACzF;AACA,SAAO;AACT;AAIA,IAAM,mBAAmB,oBAAI,QAAiF;AAGvG,SAAS,mBAAmB,QAAwD;AACzF,QAAM,SAAS,iBAAiB,IAAI,MAAM;AAC1C,MAAI,WAAW,UAAa,OAAO,QAAQ,OAAO,OAAQ,QAAO,OAAO;AACxE,QAAM,SAAgC,CAAC;AACvC,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,qBAAsB;AACzC,UAAM,OAAO,sBAAsB,KAAK;AAIxC,QAAI,qBAAqB,KAAK;AAC9B,QAAI,uBAAuB,GAAG;AAC5B,2BAAqB;AACrB,iBAAW,OAAO,KAAK,cAAc;AACnC,cAAM,WAAW,OAAO,GAAG;AAC3B,YAAI,aAAa,OAAW,uBAAsB,mBAAmB,iBAAiB,QAAQ,CAAC;AAAA,MACjG;AAAA,IACF;AACA,UAAM,OAAO,KAAK,SAAS,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;AAC9D,UAAM,iBAA2B,MAAM,QAAQ,KAAK,cAAc,IAAI,CAAC,GAAG,KAAK,cAAc,IAAI,CAAC;AAClG,UAAM,mBAAyC,MAAM,QAAQ,KAAK,gBAAgB,IAAI,CAAC,GAAG,KAAK,gBAAgB,IAAI;AACnH,UAAM,sBAA4C,MAAM,QAAQ,KAAK,mBAAmB,IAAI,CAAC,GAAG,KAAK,mBAAmB,IAAI;AAC5H,UAAM,aAAa,uBAAuB,QAAQ,KAAK,YAAY;AACnE,WAAO,KAAK;AAAA,MACV,SAAS,KAAK;AAAA,MACd,SAAS,YAAY,KAAK,OAAO;AAAA,MACjC,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,MAC9D,cAAc,CAAC,GAAG,KAAK,YAAY;AAAA,MACnC;AAAA,MACA,OAAO,KAAK,cAAc;AAAA,MAC1B,KAAK,KAAK,cAAc;AAAA,MACxB;AAAA,MACA;AAAA,MACA,GAAI,OAAO,KAAK,kBAAkB,WAAW,EAAE,eAAe,KAAK,cAAc,IAAI,CAAC;AAAA,MACtF,GAAI,eAAe,OAAO,CAAC,IAAI,EAAE,WAAW;AAAA,MAC5C,GAAI,qBAAqB,SAAY,CAAC,IAAI,EAAE,iBAAiB;AAAA,MAC7D,GAAI,wBAAwB,SAAY,CAAC,IAAI,EAAE,oBAAoB;AAAA,MACnE,WAAW,MAAM;AAAA,IACnB,CAAC;AAAA,EACH;AACA,mBAAiB,IAAI,QAAQ,EAAE,KAAK,OAAO,QAAQ,OAAO,CAAC;AAC3D,SAAO;AACT;AAaA,SAAS,YAAY,OAA8B;AACjD,MAAI,MAAM,SAAS,cAAe,QAAO;AACzC,MAAI,MAAM,SAAS,oBAAqB,QAAO;AAC/C,QAAM,UAAW,MAAM,KAA6C,SAAS;AAC7E,SAAO,MAAM,QAAQ,OAAO,KAAK,QAAQ,KAAK,CAAC,UAAW,OAA8B,SAAS,WAAW;AAC9G;AAGA,SAAS,iBAAiB,OAA8B;AACtD,MAAI,MAAM,SAAS,eAAgB,QAAO;AAC1C,QAAM,SAAU,MAAM,KAA0C;AAChE,SAAO,QAAQ,WAAW;AAC5B;AAGA,SAAS,mBAAmB,OAA+B;AACzD,MAAI,MAAM,SAAS,oBAAqB,QAAO,CAAC;AAChD,QAAM,UAAW,MAAM,KAA6C,SAAS;AAC7E,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO,CAAC;AACrC,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,SAAS;AAC3B,QAAI,UAAU,QAAQ,OAAO,UAAU,SAAU;AACjD,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,eAAe,OAAO,EAAE,OAAO,SAAU,KAAI,KAAK,EAAE,EAAE;AAAA,EACvE;AACA,SAAO;AACT;AAKA,SAAS,uBAAuB,OAA0D;AACxF,MAAI,MAAM,SAAS,qBAAqB;AACtC,UAAM,UAAW,MAAM,KAA4E;AACnG,WAAO;AAAA,MACL,UAAU,OAAO,SAAS,QAAQ,aAAa,WAAW,QAAQ,OAAO,WAAW;AAAA,MACpF,OAAO,OAAO,SAAS,QAAQ,UAAU,WAAW,QAAQ,OAAO,QAAQ;AAAA,IAC7E;AAAA,EACF;AACA,SAAO,EAAE,UAAU,uBAAuB,OAAO,gBAAgB;AACnE;AASA,SAAS,gBACP,SACA,MACA,UACA,OACA,MACA,aAA8C,gBACxC;AACN,MAAI,KAAK,WAAW,EAAG;AACvB,QAAM,QAAQ,KAAK,CAAC;AACpB,QAAM,MAAM,KAAK,KAAK,SAAS,CAAC;AAChC,MAAI,qBAAqB;AACzB,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,UAAU,SAAS,GAAG;AAIpC,QAAI,UAAU,OAAW,uBAAsB,WAAW,KAAK;AAAA,EACjE;AACA,UAAQ,OAAO,oBAAoB;AAAA,IACjC,eAAe,EAAE,OAA4B,IAAuB;AAAA,IACpE,cAAc,CAAC,GAAG,IAAI;AAAA,IACtB;AAAA,EACF,CAAC;AACD,MAAI,SAAS,QAAW;AACtB,YAAQ,OAAO,gBAAgB,kBAAkB;AAAA,MAC/C,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC;AAAA,MAChC,QAAQ,EAAE,MAAM,UAAU,QAAQ,sBAAsB;AAAA,IAC1D,CAAC,GAAG;AAAA,MACF,WAAW,EAAE,IAAI,WAAW,OAA4B,IAAuB;AAAA,MAC/E,iBAAiB,CAAC,GAAG,IAAI;AAAA,IAC3B,CAAC;AACD;AAAA,EACF;AACA,UAAQ,OAAO,qBAAqB;AAAA,IAClC,MAAM,aAAa,gBAAgB,OAAO,CAAC,KAAK;AAAA,IAChD,MAAM;AAAA,IACN,SAAS,uBAAuB,EAAE,SAAS,CAAC,GAAG,QAAQ,EAAE,UAAU,MAAM,EAAE,CAAC;AAAA,EAC9E,GAAG;AAAA,IACD,WAAW,EAAE,IAAI,WAAW,OAA4B,IAAuB;AAAA,IAC/E,iBAAiB,CAAC,GAAG,IAAI;AAAA,EAC3B,CAAC;AACH;AAWO,SAAS,qBAAqB,SAAkB,QAAgB,WAA6B;AAClG,MAAI,UAAyB;AAC7B,QAAM,SAAS,gBAAgB,OAAO;AACtC,aAAW,SAAS,QAAQ;AAC1B,QAAI,MAAM,SAAS,oBAAqB;AACxC,QAAI,mBAAmB,KAAK,EAAE,SAAS,MAAM,GAAG;AAC9C,gBAAU,MAAM;AAChB;AAAA,IACF;AAAA,EACF;AACA,MAAI,YAAY,KAAM,QAAO;AAI7B,QAAM,cAAc,mBAAmB,OAAO,OAAO,CAAE;AACvD,MAAI,YAAY,WAAW,KAAK,YAAY,CAAC,MAAM,OAAQ,QAAO;AAClE,MAAI,oBAAoB,aAAa;AACrC,MAAI,sBAAsB,MAAM;AAC9B,eAAW,SAAS,QAAQ;AAC1B,UAAI,MAAM,SAAS,iBAAiB,wBAAwB,KAAK,MAAM,QAAQ;AAC7E,4BAAoB,MAAM;AAC1B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,MAAI,sBAAsB,KAAM,QAAO;AACvC,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,WAAW,MAAM,QAAQ,OAAqB;AACpD,QAAM,SAAS,MAAM,QAAQ,iBAA+B;AAG5D,MAAI,WAAW,KAAK,SAAS,KAAK,SAAS,aAAa,EAAG,QAAO;AAClE,QAAM,EAAE,UAAU,MAAM,IAAI,uBAAuB,OAAO,OAAO,CAAE;AACnE,QAAM,cAAc,OAAO,iBAAiB;AAC5C,QAAM,aAAa,gBAAgB,SAAY,KAAK,iBAAiB,WAAW;AAChF,kBAAgB,SAAS,CAAC,SAAS,iBAAiB,GAAG,UAAU,OAAO,WAAW,KAAK,EAAE,SAAS,IAAI,aAAa,MAAS;AAC7H,SAAO;AACT;AAeO,SAAS,iCACd,SACA,kBAAuC,oBAAI,IAAI,GACvC;AACR,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,eAAe,oBAAI,IAAsB;AAG/C,QAAM,OAAO,oBAAI,IAA4C;AAC7D,QAAM,mBAA6B,CAAC;AAGpC,QAAM,gBAAgB,oBAAI,IAAoB;AAC9C,WAAS,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;AACpD,UAAM,MAAM,MAAM,KAAK;AACvB,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,QAAI,UAAU,OAAW;AACzB,QAAI,MAAM,SAAS,qBAAqB;AACtC,YAAM,MAAM,mBAAmB,KAAK;AACpC,UAAI,IAAI,WAAW,EAAG;AACtB,mBAAa,IAAI,KAAK,GAAG;AACzB,iBAAW,MAAM,KAAK;AACpB,YAAI,CAAC,KAAK,IAAI,EAAE,EAAG,MAAK,IAAI,IAAI,EAAE,KAAK,MAAM,CAAC;AAAA,MAChD;AAAA,IACF,WAAW,MAAM,SAAS,eAAe;AACvC,YAAM,KAAK,wBAAwB,KAAK;AACxC,UAAI,OAAO,KAAM;AACjB,YAAM,OAAO,KAAK,IAAI,EAAE;AACxB,UAAI,SAAS,QAAW;AACtB,yBAAiB,KAAK,GAAG;AACzB;AAAA,MACF;AAKA,YAAM,cAAc,aAAa,IAAI,KAAK,GAAG;AAC7C,UAAI,WAAW;AACf,UAAI,gBAAgB,QAAW;AAC7B,mBAAW;AACX,iBAAS,MAAM,KAAK,QAAQ,GAAG,MAAM,OAAO,OAAO,GAAG;AACpD,gBAAM,WAAW,UAAU,SAAS,MAAM,GAAG,CAAE;AAC/C,cAAI,aAAa,UAAa,SAAS,SAAS,eAAe;AAC7D,uBAAW;AACX;AAAA,UACF;AACA,gBAAM,QAAQ,wBAAwB,QAAQ;AAC9C,cAAI,UAAU,QAAQ,CAAC,YAAY,SAAS,KAAK,GAAG;AAClD,uBAAW;AACX;AAAA,UACF;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO,EAAE;AACd,UAAI,CAAC,SAAU,eAAc,IAAI,KAAK,KAAK,GAAG;AAAA,IAChD;AAAA,EACF;AAEA,QAAM,qBAAqB,oBAAI,IAAsB;AACrD,aAAW,CAAC,WAAW,OAAO,KAAK,eAAe;AAChD,UAAM,KAAK,wBAAwB,UAAU,SAAS,SAAS,CAAE;AACjE,QAAI,OAAO,MAAM;AACf,YAAM,OAAO,mBAAmB,IAAI,OAAO,KAAK,CAAC;AACjD,WAAK,KAAK,EAAE;AACZ,yBAAmB,IAAI,SAAS,IAAI;AAAA,IACtC;AAAA,EACF;AACA,QAAM,YAAY,IAAI,IAAY,gBAAgB;AAClD,aAAW,aAAa,cAAc,KAAK,EAAG,WAAU,IAAI,SAAS;AACrE,aAAW,CAAC,SAAS,GAAG,KAAK,cAAc;AACzC,UAAM,YAAY,mBAAmB,IAAI,OAAO;AAMhD,UAAM,cAAc,CAAC,IAAI,KAAK,CAAC,cAAc,gBAAgB,IAAI,SAAS,CAAC,KACtE,IAAI,MAAM,CAAC,cAAc,KAAK,IAAI,SAAS,KAAK,WAAW,SAAS,SAAS,MAAM,IAAI;AAC5F,QAAI,YAAa,WAAU,IAAI,OAAO;AAAA,EACxC;AACA,QAAM,SAAS,CAAC,GAAG,SAAS,EAAE,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AAClD,MAAI,QAAQ;AACZ,aAAW,OAAO,QAAQ;AACxB,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,QAAI,UAAU,OAAW;AACzB,UAAM,EAAE,UAAU,MAAM,IAAI,uBAAuB,KAAK;AACxD,oBAAgB,SAAS,CAAC,GAAG,GAAG,UAAU,KAAK;AAC/C,aAAS;AAAA,EACX;AACA,SAAO;AACT;AAUO,SAAS,gBAAgB,SAA+B;AAC7D,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,OAAO,QAAQ,QAAQ,OAAO;AACvC,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,QAAI,UAAU,OAAW;AACzB,QAAI,MAAM,SAAS,qBAAqB;AACtC,iBAAW,MAAM,mBAAmB,KAAK,EAAG,MAAK,IAAI,EAAE;AAAA,IACzD,WAAW,MAAM,SAAS,eAAe;AACvC,YAAM,KAAK,wBAAwB,KAAK;AACxC,UAAI,OAAO,KAAM,MAAK,OAAO,EAAE;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAYO,SAAS,sBACd,SACA,QACA,WACA,SACM;AACN,iBAAe,MAAM;AACnB,QAAI;AACF,2BAAqB,SAAS,QAAQ,SAAS;AAAA,IACjD,SAAS,OAAO;AACd,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,CAAC;AACH;AAaO,SAAS,2BACd,SACA,OAAoC,CAAC,GACb;AAIxB,mCAAiC,OAAO;AACxC,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,QAAM,WAAW,KAAK,kBAAkB;AACxC,QAAM,gBAAgB,oBAAI,IAAY;AAGtC,MAAI,WAAW,GAAG;AAChB,eAAW,OAAO,MAAM,MAAM,CAAC,QAAQ,EAAG,eAAc,IAAI,GAAG;AAAA,EACjE;AACA,WAAS,QAAQ,MAAM,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG;AACzD,UAAM,QAAQ,UAAU,SAAS,MAAM,KAAK,CAAE;AAC9C,QAAI,OAAO,SAAS,kBAAkB,CAAC,iBAAiB,KAAK,GAAG;AAC9D,oBAAc,IAAI,MAAM,KAAK,CAAE;AAC/B;AAAA,IACF;AAAA,EACF;AACA,QAAM,MAA+F,CAAC;AACtG,MAAI,MAA+F;AACnG,QAAM,QAAQ,MAAY;AACxB,QAAI,QAAQ,KAAM,KAAI,KAAK,GAAG;AAC9B,UAAM;AAAA,EACR;AACA,aAAW,OAAO,OAAO;AACvB,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,QAAI,UAAU,UAAa,cAAc,IAAI,GAAG,KAAK,iBAAiB,KAAK,GAAG;AAC5E,YAAM;AACN;AAAA,IACF;AAKA,QAAI,QAAQ,QAAQ,MAAM,IAAI,OAAO;AACnC,YAAM;AACN,YAAM;AAAA,IACR;AACA,UAAM,SAAS,mBAAmB,iBAAiB,KAAK,CAAC;AACzD,UAAM,SAAS,YAAY,KAAK;AAChC,QAAI,QAAQ,MAAM;AAChB,YAAM,EAAE,OAAO,KAAK,KAAK,KAAK,OAAO,GAAG,QAAQ,WAAW,SAAS,IAAI,EAAE;AAAA,IAC5E,OAAO;AACL,YAAM,EAAE,OAAO,IAAI,OAAO,KAAK,KAAK,OAAO,IAAI,QAAQ,GAAG,QAAQ,IAAI,SAAS,QAAQ,WAAW,IAAI,aAAa,SAAS,IAAI,GAAG;AAAA,IACrI;AAAA,EACF;AACA,QAAM;AACN,QAAM,MAA8B,CAAC;AACrC,aAAW,SAAS,KAAK;AACvB,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,IAAI,oBAAoB,SAAS,MAAM,OAAO,MAAM,GAAG;AAC1E,YAAM,QAAQ,MAAM;AACpB,UAAI,KAAK;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA,QACA,QAAQ,MAAM;AAAA,QACd,SAAS,QAAQ,IAAI,KAAK,MAAO,MAAM,YAAY,QAAS,GAAG,IAAI;AAAA,MACrE,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAKA,SAAO,IAAI,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAC7C;AAWO,SAAS,eAAe,SAA0B;AACvD,QAAM,QAAQ,QAAQ,QAAQ;AAC9B,MAAI,MAAM,WAAW,EAAG,QAAO;AAI/B,MAAI,QAAQ,MAAM,CAAC;AACnB,MAAI,OAAO,MAAM,CAAC;AAClB,aAAW,OAAO,OAAO;AACvB,QAAI,MAAM,MAAO,SAAQ;AACzB,QAAI,MAAM,KAAM,QAAO;AAAA,EACzB;AACA,SAAO,GAAG,MAAM,MAAM,gBAAgB,KAAK,KAAK,IAAI;AACtD;AAsBO,SAAS,cAAc,SAA2C;AACvE,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,MAA+B,CAAC;AACtC,MAAI,OAAO;AACX,aAAW,SAAS,QAAQ;AAC1B,QAAI;AACJ,QAAI,MAAM,kBAAkB,UAAa,SAAS,KAAK,MAAM,aAAa,GAAG;AAC3E,sBAAgB,MAAM;AACtB,YAAM,MAAM,OAAO,cAAc,MAAM,CAAC,CAAC;AACzC,UAAI,OAAO,UAAU,GAAG,EAAG,QAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAAA,IAC1D,OAAO;AACL,sBAAgB,IAAI,IAAI;AACxB,cAAQ;AAAA,IACV;AACA,eAAW,IAAI,MAAM,SAAS,aAAa;AAC3C,QAAI,KAAK;AAAA,MACP,SAAS,MAAM;AAAA,MACf;AAAA,MACA,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM,cAAc;AAAA,MAChC,QAAQ;AAAA,MACR,gBAAgB,CAAC,GAAG,MAAM,cAAc;AAAA,IAC1C,CAAC;AAAA,EACH;AACA,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,SAAS,KAAK;AACvB,eAAW,UAAU,MAAM,eAAgB,UAAS,IAAI,MAAM;AAAA,EAChE;AACA,SAAO,IAAI,IAAI,CAAC,WAAW;AAAA,IACzB,GAAG;AAAA,IACH,QAAQ,CAAC,SAAS,IAAI,MAAM,OAAO;AAAA,EACrC,EAAE;AACJ;AAUO,SAAS,sBAAsB,SAAkB,KAA4B;AAClF,QAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,MAAI,OAAO,SAAS,eAAgB,QAAO;AAC3C,QAAM,SAAU,MAAM,KAAiE;AACvF,MAAI,QAAQ,WAAW,aAAa,OAAO,iBAAiB,OAAW,QAAO;AAC9E,QAAM,QAAQ,cAAc,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,YAAY,OAAO,YAAY;AAClF,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,MAAM;AACf;AAGO,SAAS,4BAA4B,SAAkB,gBAA6C;AACzG,MAAI,eAAe,WAAW,EAAG,QAAO,CAAC;AACzC,QAAM,WAAW,IAAI,IAAI,cAAc,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC;AACxF,SAAO,eACJ,IAAI,CAAC,OAAO,SAAS,IAAI,EAAE,CAAC,EAC5B,OAAO,CAAC,OAAqB,OAAO,MAAS;AAClD;AAUO,SAAS,mBAAmB,SAAkB,WAAkC;AACrF,MAAI,CAAC,SAAS,KAAK,SAAS,EAAG,QAAO;AACtC,QAAM,QAAQ,cAAc,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,kBAAkB,SAAS;AAC9E,SAAO,OAAO,WAAW;AAC3B;AAGO,SAAS,wBAAwB,SAAkB,eAAsC;AAC9F,QAAM,QAAQ,cAAc,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,kBAAkB,aAAa;AAClF,SAAO,OAAO,SAAS,MAAM,aAAa;AAC5C;AAGA,SAAS,oBAAoB,QAAiC,KAA4B;AACxF,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,OAAO,SAAS,eAAgB,QAAO;AAC3C,QAAM,SAAU,MAAM,KAAiE;AACvF,MAAI,QAAQ,WAAW,aAAa,OAAO,iBAAiB,OAAW,QAAO;AAC9E,SAAO,OAAO;AAChB;AAQO,SAAS,mBAAmB,SAAkB,SAA2B;AAC9E,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,OAAO,IAAI,IAAI,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,SAAS,KAAK,CAAC,CAAC;AAClE,QAAM,OAAO,KAAK,IAAI,OAAO;AAC7B,MAAI,SAAS,OAAW,QAAO,CAAC;AAChC,QAAM,MAAgB,CAAC;AACvB,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,UAAqC;AAClD,QAAI,KAAK,IAAI,MAAM,OAAO,EAAG;AAC7B,SAAK,IAAI,MAAM,OAAO;AACtB,eAAW,OAAO,MAAM,cAAc;AACpC,YAAM,UAAU,oBAAoB,gBAAgB,OAAO,GAAG,GAAG;AACjE,YAAM,QAAQ,YAAY,OAAO,SAAY,KAAK,IAAI,OAAO;AAC7D,UAAI,UAAU,OAAW,OAAM,KAAK;AAAA,UAC/B,KAAI,KAAK,GAAG;AAAA,IACnB;AAAA,EACF;AACA,QAAM,IAAI;AACV,SAAO;AACT;;;AI1hCA,SAAS,oBAAoB,QAAqD;AAChF,QAAM,SAAS,mBAAmB,MAAM;AACxC,MAAI,OAAO,WAAW,EAAG,QAAO,CAAC;AAEjC,QAAM,aAAa,oBAAI,IAAoB;AAC3C,QAAM,kBAAkB,oBAAI,IAAsB;AAClD,MAAI,OAAO;AACX,aAAW,SAAS,QAAQ;AAC1B,QAAI;AACJ,QAAI,MAAM,kBAAkB,UAAa,SAAS,KAAK,MAAM,aAAa,GAAG;AAC3E,sBAAgB,MAAM;AACtB,YAAM,MAAM,OAAO,cAAc,MAAM,CAAC,CAAC;AACzC,UAAI,OAAO,UAAU,GAAG,EAAG,QAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAAA,IAC1D,OAAO;AACL,sBAAgB,IAAI,IAAI;AACxB,cAAQ;AAAA,IACV;AACA,eAAW,IAAI,MAAM,SAAS,aAAa;AAC3C,oBAAgB;AAAA,MACd,MAAM;AAAA,MACN,MAAM,eACH,IAAI,CAAC,WAAW,WAAW,IAAI,MAAM,CAAC,EACtC,OAAO,CAAC,OAAqB,OAAO,MAAS;AAAA,IAClD;AAAA,EACF;AACA,QAAM,WAAW,oBAAI,IAAY;AACjC,aAAW,SAAS,QAAQ;AAC1B,eAAW,UAAU,MAAM,eAAgB,UAAS,IAAI,MAAM;AAAA,EAChE;AACA,QAAM,SAA6B,CAAC;AACpC,aAAW,SAAS,QAAQ;AAC1B,UAAM,UAAU,WAAW,IAAI,MAAM,OAAO;AAQ5C,UAAM,SAAS,MAAM,oBAAoB,CAAC,GAAG,MAAM,aAAa,IAAI,MAAM,CAAC;AAC3E,UAAM,YAAY,MAAM,wBAClB,MAAM,OAAO,IACZ,MAAM,eAAe,SAAY,CAAC,GAAG,MAAM,aAAa,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,MAAM,UAAU,CAAC,IACjG,CAAC,GAAG,MAAM,aAAa,IAAI,MAAM,CAAC;AACxC,WAAO,KAAK;AAAA,MACV;AAAA,MACA,OAAO,IAAI,OAAO,SAAS,CAAC;AAAA,MAC5B,MAAM,MAAM;AAAA,MACZ,SAAS,MAAM;AAAA,MACf,GAAI,MAAM,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,MAC1D,kBAAkB,CAAC,GAAG,MAAM;AAAA,MAC5B,qBAAqB,CAAC,GAAG,SAAS;AAAA,MAClC,gBAAgB,gBAAgB,IAAI,MAAM,OAAO,KAAK,CAAC;AAAA,MACvD,kBAAkB,MAAM;AAAA,MACxB,WAAW,MAAM;AAAA,MACjB,eAAe;AAAA,MACf,YAAY;AAAA,MACZ,QAAQ,CAAC,SAAS,IAAI,MAAM,OAAO;AAAA,IACrC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,QAAyC;AACjE,QAAM,SAAS,oBAAoB,MAAM;AACzC,MAAI,MAAM;AACV,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,OAAO,MAAM,QAAQ,MAAM,CAAC,CAAC;AACzC,QAAI,OAAO,UAAU,GAAG,EAAG,OAAM,KAAK,IAAI,KAAK,GAAG;AAAA,EACpD;AACA,SAAO,MAAM;AACf;AAEO,IAAM,gBAAN,MAAoB;AAAA,EACR,SAAS,oBAAI,IAA8B;AAAA;AAAA,EAG5D,SAAS,SAAoC;AAC3C,UAAM,KAAK,QAAQ;AACnB,UAAM,WAAW,KAAK,OAAO,IAAI,EAAE;AACnC,QAAI,aAAa,OAAW,QAAO;AACnC,UAAM,QAAQ,mBAAmB;AACjC,UAAM,SAAS,gBAAgB,OAAO;AACtC,QAAI,OAAO,KAAK,CAAC,UAAU,MAAM,SAAS,oBAAoB,GAAG;AAC/D,YAAM,SAAS,oBAAoB,MAAM;AACzC,YAAM,cAAc,iBAAiB,MAAM;AAAA,IAC7C;AACA,SAAK,OAAO,IAAI,IAAI,KAAK;AACzB,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAkB,OAA+B;AACnD,SAAK,OAAO,IAAI,QAAQ,IAAI,KAAK;AAAA,EACnC;AAAA,EAEA,OAAO,SAAwB;AAC7B,SAAK,OAAO,OAAO,QAAQ,EAAE;AAAA,EAC/B;AACF;;;AChHA,SAAS,YAAY,qBAA+D;;;ACwB7E,SAAS,gBAAgB,OAAkC;AAChE,QAAM,aAAuC,CAAC;AAC9C,MAAI,MAAM,4BAA4B,OAAW,YAAW,qBAAqB,MAAM;AACvF,MAAI,MAAM,4BAA4B,OAAW,YAAW,qBAAqB,MAAM;AACvF,MAAI,MAAM,+BAA+B,OAAW,YAAW,wBAAwB,MAAM;AAE7F,QAAM,YAA6B,EAAE,GAAG,MAAM,cAAc;AAC5D,MAAI,OAAO,KAAK,UAAU,EAAE,SAAS,KAAK,MAAM,eAAe,OAAO;AAOpE,cAAU,QAAQ;AAAA,MAChB,GAAG,cAAc,MAAM,iBAAiB,EAAE;AAAA,MAC1C,GAAG;AAAA,MACH,GAAG,MAAM,eAAe;AAAA,IAC1B;AAAA,EACF;AACA,SAAO,cAAc,MAAM,mBAAmB,SAAS;AACzD;;;ACvCA,SAAS,qBAAAC,0BAA2C;;;AC8DpD,IAAM,gBAAoE;AAAA,EACxE,QAAQ,oBAAI,IAAI,CAAC,OAAO,YAAY,CAAC;AAAA,EACrC,WAAW,oBAAI,IAAI,CAAC,OAAO,YAAY,CAAC;AAAA,EACxC,UAAU,oBAAI,IAAI;AAAA,EAClB,MAAM,oBAAI,IAAI,CAAC,QAAQ,SAAS,YAAY,UAAU,QAAQ,YAAY,SAAS,CAAC;AAAA,EACpF,WAAW,oBAAI,IAAI,CAAC,UAAU,QAAQ,aAAa,QAAQ,MAAM,CAAC;AAAA,EAClE,QAAQ,oBAAI,IAAI,CAAC,QAAQ,CAAC;AAAA,EAC1B,KAAK,oBAAI,IAAI;AACf;AACA,IAAM,sBAA+E;AAAA,EACnF,QAAQ,oBAAI,IAAI,CAAC,SAAS,CAAC;AAAA,EAC3B,OAAO,oBAAI,IAAI,CAAC,OAAO,CAAC;AAAA,EACxB,MAAM,oBAAI,IAAI,CAAC,SAAS,OAAO,SAAS,QAAQ,CAAC;AAAA,EACjD,QAAQ,oBAAI,IAAI;AAClB;AACA,IAAM,gBAAmE;AAAA,EACvE,UAAU,oBAAI,IAAI;AAAA,EAClB,YAAY,oBAAI,IAAI;AAAA,EACpB,eAAe,oBAAI,IAAI;AAAA,EACvB,WAAW,oBAAI,IAAI;AACrB;AACA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,cAAc,sBAAsB,qBAAqB,oBAAoB,CAAC;AAG9G,SAAS,iBAAiB,UAAkB,SAA8B,MAAsB;AAC9F,QAAM,KAAK;AACX,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,QAAQ,OAAO,MAAM;AAC3C,UAAM,OAAO,MAAM,CAAC;AACpB,QAAI,CAAC,QAAQ,IAAI,IAAI,GAAG;AACtB,YAAM,IAAI;AAAA,QACR,GAAG,IAAI,kCAAkC,IAAI,qBAAgB,CAAC,GAAG,OAAO,EAAE,KAAK,IAAI,KAAK,QAAQ;AAAA,MAClG;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAOO,SAAS,eAAe,UAAkB,MAA+C;AAC9F,SAAO,SAAS,QAAQ,iCAAiC,CAAC,QAAQ,SAAiB;AACjF,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,UAAU,QAAW;AACvB,YAAM,IAAI;AAAA,QACR,kDAAkD,IAAI,kBAAkB,SAAS,MAAM,GAAG,EAAE,CAAC;AAAA,MAC/F;AAAA,IACF;AACA,WAAO,OAAO,KAAK;AAAA,EACrB,CAAC;AACH;AAOA,SAAS,WACP,UACA,UACA,SACA,MACG;AACH,MAAI,YAAY,KAAM,QAAO;AAC7B,QAAM,MAAM,CAAC;AACb,aAAW,OAAO,OAAO,KAAK,QAAQ,GAAqB;AACzD,UAAM,QAAQ,SAAS,GAAG;AAC1B,QAAI,GAAG,IAAI,UAAU,QAAQ,UAAU,SACnC,SAAS,GAAG,IACZ,iBAAiB,OAAO,QAAQ,GAAG,GAAG,GAAG,IAAI,IAAI,OAAO,GAAG,CAAC,EAAE;AAAA,EACpE;AACA,SAAO;AACT;AAMO,SAAS,eAAe,OAAqC;AAClE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO;AAAA,IACL,OAAO,WAAW,gBAAgB,OAAO,MAAM,OAAO,eAAe,eAAe;AAAA,IACpF,YAAY,WAAW,gBAAgB,YAAY,MAAM,YAAY,qBAAqB,oBAAoB;AAAA,IAC9G,OAAO,WAAW,gBAAgB,OAAO,MAAM,OAAO,eAAe,eAAe;AAAA,IACpF,sBACE,MAAM,iBAAiB,QAAQ,MAAM,iBAAiB,SAClD,gBAAgB,uBAChB,iBAAiB,MAAM,cAAc,gBAAgB,sBAAsB;AAAA,EACnF;AACF;AAGO,SAAS,mBAAmB,SAAkC;AACnE,SAAO,eAAe,QAAQ,sBAAsB;AAAA,IAClD,YAAY;AAAA,IACZ,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,IACnB,oBAAoB;AAAA,EACtB,CAAC;AACH;AAMO,IAAM,kBAAmC;AAAA,EAC9C,OAAO;AAAA;AAAA;AAAA,IAGL,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,UAAU;AAAA,IACV,MAAM;AAAA,IACN,WAAW;AAAA,IACX,QAAQ;AAAA,IACR,KAAK;AAAA,EACP;AAAA,EACA,YAAY;AAAA,IACV,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,MAAM;AAAA,IACN,QAAQ;AAAA,EAEV;AAAA,EACA,OAAO;AAAA,IACL,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,eAAe;AAAA,IACf,WAAW;AAAA,EACb;AAAA,EACA,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAoCxB;AAGO,IAAM,mBAAoC;;;ADvM1C,SAAS,kBAAkB,OAAc,cAAqC;AAEnF,QAAM,cAAc,MAAM,KAAK,MAAM,oBAAoB;AAGzD,QAAM,YAAY,aAAa,WAAW,MAAM,OAAO,GAAG,QAAQ,iBAAiB;AACnF,MAAI,OAAO,cAAc,YAAY,YAAY,EAAG,QAAO;AAG3D,QAAM,QAAQ,MAAM,KAAK,MAAM,YAAY;AAG3C,QAAM,UAAU,OAAO,UAAU,MAAM,OAAO,GAAG;AACjD,MAAI,OAAO,YAAY,YAAY,UAAU,EAAG,QAAO;AAGvD,SAAO,aAAa,OAAO,CAAC,KAAK,YAAY,MAAM,mBAAmB,QAAQ,QAAQ,EAAE,GAAG,CAAC;AAC9F;AAUO,SAAS,WACd,SACA,UAA2B,kBACnB;AACR,QAAM,SAAS,2BAA2B,OAAO,EAAE,MAAM,GAAG,CAAC;AAE7D,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,QAAM,QAAQ,OAAO;AAAA,IAAI,CAAC,UACxB,eAAe,QAAQ,WAAW,MAAM;AAAA,MACtC,OAAO,MAAM;AAAA,MACb,KAAK,MAAM;AAAA,MACX,OAAO,MAAM;AAAA,MACb,QAAQ,MAAM;AAAA,MACd,SAAS,MAAM;AAAA,MACf,SAAS,MAAM,MAAM;AAAA,IACvB,CAAC;AAAA,EACH;AACA,SAAO;AAAA;AAAA,IAEL;AAAA,IACA,eAAe,QAAQ,WAAW,QAAQ,EAAE,SAAS,eAAe,OAAO,EAAE,CAAC;AAAA,IAC9E,eAAe,QAAQ,WAAW,OAAO,EAAE,OAAO,OAAO,OAAO,CAAC;AAAA,IACjE,GAAG;AAAA,IACH,QAAQ,WAAW;AAAA,EACrB,EAAE,KAAK,IAAI;AACb;AASA,SAAS,mBAAmB,OAAc,cAAqC;AAC7E,SAAO,kBAAkB,OAAO,YAAY;AAC9C;AASO,SAAS,WACd,OACA,KACA,eACqB;AACrB,QAAM,UAAU,MAAM;AACtB,QAAM,QAAQ,IAAI,MAAM,SAAS,OAAO;AAGxC,QAAM,eAAe,eAAe,OAAO;AAC3C,QAAM,kBAAkB,qBAAqB,gBAAgB,OAAO,CAAC;AACrE,QAAM,aAAa,mBAAmB,OAAO,eAAe;AAC5D,QAAM,SAAS,gBAAgB,GAAG;AAClC,QAAM,OAAO,IAAI,OAAO,YAAY,EAAE,UAAU,cAAc,OAAO,QAAQ,WAAW,CAAC;AACzF,MAAI,MAAM,IAAI,SAAS,KAAK,KAAK;AAEjC,QAAM,QAAQ,KAAK;AACnB,MAAI,UAAU,UAAa,CAAC,MAAM,aAAc,QAAO;AACvD,QAAM,YAAY,MAAM,WAAW,sBAAsB;AAEzD,QAAM,aAAa,aAAa,gBAAgB,OAAO,CAAC,KAAK;AAC7D,QAAM,eAAe,CAAC,aAAa,cAAc,IAAI,QAAQ,EAAE,MAAM;AACrE,MAAI,aAAc,QAAO;AACzB,gBAAc,IAAI,QAAQ,IAAI,UAAU;AAExC,QAAM,OAAO,eAAe,OAAO,WAAW,SAAS,IAAI,OAAO;AAClE,QAAM,UAAUC,mBAAkB;AAAA,IAChC,SAAS,CAAC,EAAE,MAAM,QAAQ,KAAK,CAAC;AAAA,IAChC,QAAQ,EAAE,MAAM,UAAU,QAAQ,YAAY;AAAA,EAChD,CAAC;AACD,SAAO,EAAE,SAAS,UAAU;AAC9B;AAeO,SAAS,eACd,OACA,WACA,SACA,UAA2B,kBACnB;AAIR,MAAI,QAAQ,UAAU,iBAAiB,OAAO;AAC5C,WAAO,yBAAyB,OAAO,WAAW,SAAS,OAAO;AAAA,EACpE;AACA,QAAM,WAAW,gBAAgB,KAAK;AACtC,SAAO,sBAAsB,SAAS,MAAM,OAAO,SAAS,OAAO;AACrE;AAOA,SAAS,sBACP,MACA,OACA,SACA,SACQ;AACR,MAAI,MAAM;AAGV,OAAK,MAAM,SAAS,KAAK,MAAM,SAAS,OAAO,MAAM,kBAAkB,UAAU,KAAK,GAAG;AACvF,UAAM,mBAAmB,KAAK,OAAO,SAAS,OAAO;AAAA,EACvD,WAAW,IAAI,SAAS,WAAW,GAAG;AAEpC,UAAM,wBAAwB,GAAG;AAAA,EACnC;AAIA,QAAM,WAAW,WAAW,SAAS,OAAO;AAC5C,MAAI,aAAa,GAAI,OAAM,iBAAiB,KAAK,QAAQ;AACzD,SAAO;AACT;AAGA,SAAS,iBAAiB,MAAc,UAA0B;AAChE,QAAM,QAAQ,KAAK,MAAM,8DAA8D;AACvF,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,QAAQ,MAAM;AACpB,QAAM,OAAO,KAAK,MAAM,QAAQ,CAAC;AACjC,QAAM,OAAO,KAAK,MAAM,MAAM;AAC9B,QAAM,MAAM,SAAS,OAAO,QAAQ,IAAI,KAAK,QAAS,KAAK;AAC3D,QAAM,SAAS,KAAK,MAAM,GAAG,KAAK;AAClC,QAAM,QAAQ,KAAK,MAAM,GAAG;AAG5B,SAAO,SAAS,OAAO,WAAW;AACpC;AAGA,SAAS,mBACP,MACA,OACA,SACA,SACQ;AACR,QAAM,QAAQ,KAAK,OAAO,yCAAyC;AACnE,MAAI,UAAU,GAAI,QAAO;AACzB,QAAM,OAAO,KAAK,MAAM,QAAQ,CAAC;AACjC,QAAM,OAAO,KAAK,MAAM,qBAAqB;AAC7C,QAAM,MAAM,SAAS,OAAO,QAAQ,IAAI,KAAK,QAAS,KAAK;AAC3D,QAAM,UAAU,MAAM;AACtB,QAAM,cAAc,QACjB,IAAI,CAAC,UAAU,wBAAwB,SAAS,MAAM,OAAO,CAAC,EAC9D,OAAO,CAAC,QAAuB,QAAQ,IAAI,EAC3C,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvB,QAAM,UAAU,MAAM,SAAS,IAAI,MAAM,WAAW,YAAY,MAAM,WAAW;AACjF,QAAM,SAAS,OAAO,YAAY,WAAW,UAAU;AACvD,QAAM,YAAY,MAAM,SAAS,OAAO,IAAI,MAAM;AAClD,QAAM,WAAW,eAAe,QAAQ,MAAM,MAAM;AAAA,IAClD,MAAM;AAAA,IACN,OAAO,QAAQ;AAAA,IACf,UAAU,YAAY;AAAA,IACtB;AAAA,IACA,MAAM,YAAY,KAAK,IAAI;AAAA,IAC3B,UAAU,YAAY,CAAC,KAAK;AAAA,IAC5B,SAAS,YAAY,YAAY,SAAS,CAAC,KAAK;AAAA,EAClD,CAAC;AACD,SAAO,KAAK,MAAM,GAAG,KAAK,IAAI,SAAS,WAAW,KAAK,MAAM,GAAG;AAClE;AAGA,SAAS,wBAAwB,MAAsB;AACrD,QAAM,QAAQ,KAAK,OAAO,iBAAiB;AAC3C,MAAI,UAAU,GAAI,QAAO;AACzB,QAAM,OAAO,KAAK,MAAM,QAAQ,CAAC;AACjC,QAAM,OAAO,KAAK,MAAM,4CAA4C;AACpE,QAAM,MAAM,SAAS,OAAO,QAAQ,IAAI,KAAK,QAAS,KAAK;AAC3D,SAAO,KAAK,MAAM,GAAG,KAAK,IACtB,+GACA,KAAK,MAAM,GAAG;AACpB;AAOA,SAAS,yBACP,OACA,WACA,SACA,SACQ;AAGR,QAAMC,OAAM,KAAK,MAAM,KAAK,IAAI,MAAM,cAAc,CAAC,IAAI,GAAG;AAC5D,QAAM,QAAQ;AAAA,IACZ,YAAY,QAAQ,MAAM,YAAY,QAAQ,MAAM;AAAA,IACpD,EAAE,KAAAA,MAAK,YAAY,oBAAoB;AAAA,EACzC;AACA,QAAM,QAAkB,CAAC,KAAK;AAG9B,MAAI,MAAM,kBAAkB;AAC1B,UAAM,KAAK,MAAM;AACjB,UAAM,YAAY,eAAe,QAAQ,MAAM,WAAW;AAAA,MACxD,QAAQ,KAAK,MAAM,GAAG,SAAS,GAAI;AAAA,MACnC,MAAM,KAAK,MAAM,GAAG,OAAO,GAAI;AAAA,MAC/B,WAAW,KAAK,MAAM,GAAG,YAAY,GAAI;AAAA,MACzC,MAAM,KAAK,MAAM,GAAG,OAAO,GAAI;AAAA,MAC/B,MAAM,KAAK,MAAM,GAAG,OAAO,GAAI;AAAA,IACjC,CAAC;AACD,QAAI,cAAc,GAAI,OAAM,KAAK,IAAI,SAAS;AAC9C,QAAI,GAAG,SAAS,GAAG;AACjB,YAAM,SAAS,eAAe,QAAQ,MAAM,QAAQ,EAAE,QAAQ,KAAK,MAAM,GAAG,SAAS,GAAI,EAAE,CAAC;AAC5F,UAAI,WAAW,GAAI,OAAM,KAAK,MAAM;AAAA,IACtC;AAAA,EACF;AAGA,MAAI,QAAQ,MAAM,aAAa,GAAI,OAAM,KAAK,IAAI,QAAQ,MAAM,QAAQ;AAGxE,OAAK,MAAM,SAAS,KAAK,MAAM,SAAS,OAAO,MAAM,kBAAkB,UAAU,KAAK,GAAG;AACvF,UAAM,UAAU,MAAM;AACtB,UAAM,cAAc,QACjB,IAAI,CAAC,UAAU,wBAAwB,SAAS,MAAM,OAAO,CAAC,EAC9D,OAAO,CAAC,QAAuB,QAAQ,IAAI,EAC3C,KAAK,CAAC,GAAG,MAAM,IAAI,CAAC;AACvB,UAAM,UAAU,MAAM,SAAS,IAAI,MAAM,WAAW,YAAY,MAAM,WAAW;AACjF,UAAM,SAAS,OAAO,YAAY,WAAW,UAAU;AACvD,UAAM,WAAW,eAAe,QAAQ,MAAM,MAAM;AAAA,MAClD,MAAM,MAAM;AAAA,MACZ,OAAO,QAAQ;AAAA,MACf,UAAU,MAAM,OAAO;AAAA,MACvB;AAAA,MACA,MAAM,YAAY,KAAK,IAAI;AAAA,MAC3B,UAAU,YAAY,CAAC,KAAK;AAAA,MAC5B,SAAS,YAAY,YAAY,SAAS,CAAC,KAAK;AAAA,IAClD,CAAC;AACD,QAAI,aAAa,GAAI,OAAM,KAAK,QAAQ;AAExC,UAAM,YAAY,MAAM,SAAS,IAAI,sBAAsB;AAC3D,UAAM,KAAK,IAAI,SAAS;AAAA,EAC1B,OAAO;AAEL,UAAM,KAAK,WAAW,SAAS,OAAO,CAAC;AAAA,EACzC;AAGA,MAAI,QAAQ,MAAM,QAAQ,GAAI,OAAM,KAAK,IAAI,QAAQ,MAAM,GAAG;AAE9D,SAAO,MAAM,KAAK,IAAI;AACxB;;;AF5RA,SAAS,aAGP;AACA,SAAO;AAAA,IACL,QAAQ;AAAA,MACN,MAAM;AAAA,MACN,YAAY,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE;AAAA,MACvC,sBAAsB;AAAA,IACxB;AAAA,IACA,QAAQ,CAAC,OAAO,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,KAAK,CAAC;AAAA,EAC/D;AACF;AAEA,SAAS,aAAa,MAA6B;AACjD,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AACA,SAAO,KAAK;AACd;AAUA,eAAsB,uBAAuB,KAAsB,OAAkC;AACnG,SAAO,IAAI,cAAc,SACrB,EAAE,OAAO,IAAI,mBAAmB,QAAQ,WAAoB,IAC5D,MAAM,IAAI,UAAU,KAAK;AAC/B;AAEA,IAAM,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsBzB,WAAW,EAAE,MAAM,QAAQ,aAAa,oHAAoH;AAAA,EAC5J,OAAO,EAAE,MAAM,UAAmB,aAAa,gDAAgD;AAAA,EAC/F,SAAS;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,MACL,MAAM;AAAA,MACN,YAAY;AAAA,QACV,UAAU;AAAA,UACR,UAAU;AAAA,UACV,OAAO;AAAA,YACL,EAAE,MAAM,WAAoB,aAAa,kCAAkC;AAAA,YAC3E,EAAE,MAAM,UAAmB,aAAa,uDAAuD;AAAA,UACjG;AAAA,QACF;AAAA,QACA,QAAQ;AAAA,UACN,UAAU;AAAA,UACV,OAAO;AAAA,YACL,EAAE,MAAM,WAAoB,aAAa,2CAA2C;AAAA,YACpF,EAAE,MAAM,UAAmB,aAAa,uDAAuD;AAAA,UACjG;AAAA,QACF;AAAA,QACA,SAAS,EAAE,MAAM,UAAmB,UAAU,MAAM,aAAa,iHAAiH;AAAA,QAClL,OAAO,EAAE,MAAM,UAAmB,aAAa,0CAA0C;AAAA,MAC3F;AAAA,MACA,sBAAsB;AAAA,IACxB;AAAA,EACF;AACF;AAGA,SAAS,SAAS,OAAgC;AAChD,QAAM,OAAO,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE,CAAC,EAAG,KAAK;AAC/C,QAAM,MAAM,OAAO,IAAI;AACvB,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI,MAAM,qCAAqC,OAAO,KAAK,CAAC,qCAAgC;AAAA,EACpG;AACA,SAAO;AACT;AAOA,IAAM,QAAQ;AAEd,SAAS,WAAW,OAA8B;AAChD,QAAM,QAAQ,MAAM,KAAK,MAAM,KAAK,CAAC;AACrC,MAAI,UAAU,KAAM,QAAO;AAC3B,QAAM,QAAQ,OAAO,MAAM,CAAC,CAAC;AAC7B,SAAO,SAAS,KAAK,SAAS,QAAQ,QAAQ;AAChD;AAiBA,SAASC,eAAc,OAAwB,OAAuC;AACpF,QAAM,OAAO,OAAO,KAAK;AACzB,QAAM,QAAQ,WAAW,IAAI;AAC7B,MAAI,UAAU,KAAM,QAAO,SAAS,KAAK;AAEzC,QAAM,MAAM,IAAI,OAAO,KAAK,EAAE,SAAS,GAAG,GAAG,CAAC;AAC9C,QAAM,MAAM,MAAM,GAAG;AACrB,MAAI,QAAQ,QAAW;AACrB,UAAM,IAAI;AAAA,MACR,4BAA4B,IAAI;AAAA,IAClC;AAAA,EACF;AACA,QAAM,MAAM,OAAO,OAAO,GAAG,EAAE,MAAM,GAAG,EAAE,CAAC,CAAE;AAC7C,MAAI,CAAC,OAAO,UAAU,GAAG,KAAK,MAAM,GAAG;AACrC,UAAM,IAAI;AAAA,MACR,4BAA4B,IAAI,2BAA2B,GAAG;AAAA,IAChE;AAAA,EACF;AACA,SAAO;AACT;AAeA,SAAS,mBAAmB,MAAyC;AACnE,MAAI,KAAK,YAAY,OAAW,QAAO;AACvC,MAAI,KAAK,cAAc,OAAW,QAAO;AACzC,MAAI,QAAiB,KAAK;AAC1B,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,QAAM,UAAW,MAAgC;AACjD,MAAI,YAAY,OAAW,QAAO;AAClC,SAAO,EAAE,GAAG,MAAM,QAA4C;AAChE;AAWA,SAAS,eAAiC,MAAY;AACpD,QAAM,WAAY,KAAiC;AACnD,MAAI,aAAa,OAAW,QAAO;AACnC,MAAI,QAAiB;AACrB,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI;AACF,cAAQ,KAAK,MAAM,KAAK;AAAA,IAC1B,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AACA,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,EAAG,QAAO;AAChF,SAAO,EAAE,GAAG,MAAM,GAAI,MAAiB;AACzC;AAkBA,SAAS,qBAAqB,SAAqD;AACjF,QAAM,aAAuB,CAAC;AAC9B,UAAQ,QAAQ,CAAC,MAAM,UAAU;AAC/B,UAAM,OAAO,WAAW,KAAK;AAC7B,QAAI,KAAK,aAAa,OAAW,YAAW,KAAK,8BAA8B,IAAI,YAAY;AAC/F,QAAI,KAAK,WAAW,OAAW,YAAW,KAAK,8BAA8B,IAAI,UAAU;AAC3F,QAAI,OAAO,KAAK,YAAY,YAAY,KAAK,QAAQ,KAAK,EAAE,WAAW,GAAG;AACxE,iBAAW,KAAK,8BAA8B,IAAI,WAAW;AAAA,IAC/D;AAAA,EACF,CAAC;AACD,MAAI,WAAW,SAAS,EAAG,OAAM,IAAI,cAAc,UAAU;AAC/D;AAGA,eAAe,eAAe,KAAsB,MAAoB,MAA2C;AACjH,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,UAAU,MAAM;AAOtB,mCAAiC,SAAS,gBAAgB,OAAO,CAAC;AAClE,QAAM,QAAQ,IAAI,MAAM,SAAS,OAAO;AAMxC,QAAM,eAAe,eAAe,OAAO;AAC3C,QAAM,kBAAkB,qBAAqB,gBAAgB,OAAO,CAAC;AACrE,QAAM,aAAa,kBAAkB,OAAO,eAAe;AAC3D,QAAM,SAAS,MAAM,uBAAuB,KAAK,KAAK;AACtD,QAAM,SAAS,gBAAgB,EAAE,GAAG,KAAK,mBAAmB,OAAO,MAAM,CAAC;AAG1E,QAAM,OAAO,IAAI,OAAO,YAAY,EAAE,UAAU,cAAc,OAAO,QAAQ,WAAW,CAAC;AACzF,MAAI,MAAM,IAAI,SAAS,KAAK,KAAK;AACjC,QAAM,QAAQ,KAAK,MAAM,YAAY;AAKrC,QAAM,QAAQ,KAAK,MAAM,YAAY;AAKrC,QAAM,YAAY,mBAAmB,IAAI;AACzC,MAAI,cAAc,MAAM;AACtB,WAAO;AAAA,MACL,MAAM;AAAA,IACR;AAAA,EACF;AACA,SAAO;AAGP,uBAAqB,KAAK,OAAQ;AAElC,QAAM,SASF,CAAC;AAGL,QAAM,yBAAmC,CAAC;AAC1C,aAAW,SAAS,KAAK,SAAU;AACjC,UAAM,WAAWA,eAAc,MAAM,UAAU,KAAK;AACpD,UAAM,SAASA,eAAc,MAAM,QAAQ,KAAK;AAChD,QAAI;AACJ,QAAI;AAQF,iBAAW,oBAAoB,SAAS,UAAU,MAAM;AAAA,IAC1D,SAAS,OAAO;AACd,UAAI,iBAAiB,6BAA6B;AAChD,cAAM,WAAW,MAAM;AACvB,cAAM,YAAY,SAAS,WAAW,IAClC,KACA,WAAW,SAAS,CAAC,EAAG,MAAM,GAAG,CAAC,CAAC,GAAG,SAAS,SAAS,IAAI,KAAK,SAAS,SAAS,CAAC,UAAU,EAAE;AACpG,+BAAuB;AAAA,UACrB,UAAU,MAAM,KAAK,KAAK,MAAM,GAAG,sBAAsB,SAAS;AAAA,QACpE;AACA;AAAA,MACF;AACA,YAAM;AAAA,IACR;AAIA,UAAM,gBAAgB,sBAAsB,SAAS,SAAS,KAAK;AACnE,UAAM,cAAc,sBAAsB,SAAS,SAAS,GAAG;AAC/D,UAAM,WAAW,iBAAiB,MAAM,OAAO,SAAS,KAAK,CAAC;AAC9D,UAAM,SAAS,eAAe,MAAM,OAAO,SAAS,GAAG,CAAC;AACxD,QAAI,aAAa,UAAa,WAAW,QAAW;AAClD,YAAM,IAAI;AAAA,QACR,4BAA4B,SAAS,KAAK,KAAK,SAAS,GAAG;AAAA,MAE7D;AAAA,IACF;AACA,WAAO,KAAK;AAAA,MACV,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,IAAI,MAAM,SAAS,KAAK,WAAW,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,SAAS,KAAK,MAAM;AAAA,IACzF,CAAC;AAAA,EACH;AAGA,MAAI,OAAO,WAAW,GAAG;AACvB,UAAM,OAAO,CAAC,+CAA+C,GAAG,sBAAsB;AACtF,QAAI,uBAAuB,SAAS,GAAG;AACrC,WAAK,KAAK,qGAAgG;AAAA,IAC5G;AACA,WAAO,EAAE,MAAM,KAAK,KAAK,IAAI,EAAE;AAAA,EACjC;AAEA,QAAM,UAAU,IAAI,OAAO,iBAAiB;AAAA,IAC1C,QAAQ,OAAO,IAAI,CAAC,EAAE,UAAU,QAAQ,SAAS,MAAM,OAAO,EAAE,UAAU,QAAQ,SAAS,MAAM,EAAE;AAAA,IACnG,UAAU;AAAA,IACV,OAAO,KAAK;AAAA,IACZ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF,CAAC;AAQD,MAAI,QAAQ,OAAO,OAAO,SAAS,KAAK,QAAQ,OAAO,kBAAkB,GAAG;AAC1E,WAAO,EAAE,MAAM,oBAAoB,QAAQ,OAAO,OAAO,KAAK,IAAI,CAAC,GAAG;AAAA,EACxE;AACA,MAAI,MAAM,IAAI,SAAS,QAAQ,KAAK;AACpC,MAAI,QAAQ,OAAO,gBAAgB,GAAG;AAIpC,QAAI,uBAAuB,IAAI,KAAK,MAAM;AAAA,EAC5C;AAIA,QAAM,cAAc,IAAI,IAAI,KAAK,MAAM,OAAO,IAAI,CAAC,UAAU,MAAM,OAAO,CAAC;AAC3E,QAAM,YAAY,QAAQ,MAAM,OAAO,OAAO,CAAC,UAAU,CAAC,YAAY,IAAI,MAAM,OAAO,CAAC;AACxF,QAAM,kBAAkB,IAAI,IAAI,UAAU,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,QAAQ,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,CAAC;AAIvG,QAAM,oBAAoB,oBAAI,IAAsB;AACpD,QAAM,eAAyB,CAAC;AAChC,aAAW,WAAW,QAAQ,OAAO,UAAU;AAC7C,UAAM,QAAQ,oCAAoC,KAAK,OAAO;AAC9D,QAAI,UAAU,MAAM;AAClB,YAAM,MAAM,GAAG,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC;AACpC,YAAM,OAAO,kBAAkB,IAAI,GAAG,KAAK,CAAC;AAC5C,WAAK,KAAK,OAAO;AACjB,wBAAkB,IAAI,KAAK,IAAI;AAAA,IACjC,OAAO;AACL,mBAAa,KAAK,OAAO;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,gBAAgB;AACpB,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,GAAG,MAAM,QAAQ,KAAK,MAAM,MAAM;AAC9C,UAAM,QAAQ,gBAAgB,IAAI,GAAG;AACrC,QAAI,UAAU,QAAW;AAIvB,uBAAiB;AACjB,YAAM,WAAW,kBAAkB,IAAI,GAAG,KAAK,CAAC;AAChD,iBAAW,WAAW,SAAU,OAAM,KAAK,KAAK,OAAO,EAAE;AACzD;AAAA,IACF;AAEA,UAAM,EAAE,OAAO,IAAI,IAAI;AACvB,UAAM,WAAW,eAAe,SAAS,OAAO,GAAG;AAInD,UAAM,iBAAiB,uBAAuB,SAAS,UAAU,MAAM,GAAG;AAC1E,UAAM,OAAO,MAAM,SAAS,KAAK,MAAM,SAAS,IAAI,MAAM,OAAO;AACjE,UAAM,iBAAiB,4BAA4B,SAAS,MAAM,cAAc;AAChF,UAAM,EAAE,aAAa,IAAI,yBAAyB,SAAS;AAAA,MACzD;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,MAAM,QAAQ,CAAC;AAAA,MAC/C,oBAAoB;AAAA,MACpB,UAAU,MAAM,QAAQ,YAAY;AAAA,MACpC,OAAO,MAAM,QAAQ,SAAS;AAAA,MAC9B;AAAA,MACA,eAAe,MAAM;AAAA,MACrB,GAAI,MAAM,UAAU,SAAY,CAAC,IAAI,EAAE,OAAO,MAAM,MAAM;AAAA,MAC1D,GAAI,eAAe,WAAW,IAAI,CAAC,IAAI,EAAE,eAAe;AAAA;AAAA;AAAA;AAAA,MAIxD,kBAAkB,MAAM;AAAA,MACxB,qBAAqB,MAAM;AAAA,IAC7B,CAAC;AACD,UAAM,WAAW,UAAU,MAAM,YAAY,QAAQ,MAAM;AAK3D,UAAMC,aAAY,UAAU,IAAI;AAChC,UAAM,OAAO,MAAM,cAAc,OAC7B,UAAU,MAAM,QAAQ,KAAK,MAAM,MAAM,+DAA0D,KAAK,KAAK,GAAG,MAChH,WACE,mBAAmB,MAAM,QAAQ,KAAK,MAAM,MAAM,wBAClD;AACN,UAAM;AAAA,MACJ,WAAW,aAAa,MAAM,GAAG,CAAC,CAAC,UAAU,KAAK,KAAK,GAAG,KAAK,SAAS,MAAM,qBAAqBA,UAAS,GAAG,IAAI;AAAA,IACrH;AAAA,EACF;AAEA,QAAM,cAAc,cAAc,QAAQ,OAAO,aAAa,eAAe,QAAQ,OAAO,gBAAgB;AAC5G,QAAM,eAAe,gBAAgB,uBAAuB;AAC5D,QAAM,cAAc,QAAQ,OAAO,OAAO,IAAI,CAAC,UAAU,KAAK,KAAK,EAAE;AACrE,QAAM,eAAe,CAAC,GAAG,aAAa,IAAI,CAAC,YAAY,KAAK,OAAO,EAAE,GAAG,GAAG,aAAa,GAAG,wBAAwB,GAAG,KAAK;AAC3H,QAAM,SAAS,eAAe,IAC1B,MAAM,YAAY,kDAClB;AACJ,SAAO,EAAE,MAAM,GAAG,WAAW;AAAA,EAAK,CAAC,GAAG,cAAc,MAAM,EAAE,OAAO,CAAC,SAAS,SAAS,EAAE,EAAE,KAAK,IAAI,CAAC,GAAG;AACzG;AAEA,IAAM,uBAAuB;AAAA,EAC3B,SAAS,EAAE,MAAM,UAAmB,UAAU,MAAM,aAAa,sHAAsH;AACzL;AAWA,SAAS,eAAe,SAAkB,KAA4B;AACpE,QAAM,cAAc,mBAAmB,SAAS,GAAG;AACnD,MAAI,gBAAgB,KAAM,QAAO;AACjC,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,WAAW,OAAO,KAAK,CAAC,UAAU,MAAM,QAAQ,WAAW,GAAG,CAAC;AACrE,SAAO,UAAU,WAAW;AAC9B;AAEA,SAAS,iBAAiB,MAAuB,SAAyB,MAAkC;AAC1G,QAAM,OAAO,eAA+B,OAAO;AACnD,QAAM,UAAU,aAAa,IAAI,EAAE;AACnC,QAAM,UAAU,eAAe,SAAS,KAAK,OAAO;AACpD,MAAI,YAAY,MAAM;AACpB,WAAO,EAAE,MAAM,sBAAsB,KAAK,OAAO,kDAAkD;AAAA,EACrG;AACA,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,QAAQ,OAAO,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AAC9D,MAAI,UAAU,QAAW;AACvB,WAAO,EAAE,MAAM,sBAAsB,KAAK,OAAO,kDAAkD;AAAA,EACrG;AACA,QAAM,QAAkB,CAAC;AAEzB,aAAW,OAAO,mBAAmB,SAAS,MAAM,OAAO,GAAG;AAC5D,UAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,UAAM,OAAO,UAAU,SAAY,KAAK,iBAAiB,KAAK;AAC9D,QAAI,KAAK,SAAS,EAAG,OAAM,KAAK,QAAQ,GAAG,KAAK,IAAI,EAAE;AAAA,EACxD;AACA,QAAM,WAAW,MAAM,OAAO,IAAI,UAAU,MAAM,IAAI,cAAc,MAAM,eAAe,MAAM,eAAe;AAC9G,SAAO;AAAA,IACL,MAAM,SAAS,MAAM,OAAO,WAAM,MAAM,OAAO,GAAG,QAAQ;AAAA;AAAA,EAAO,MAAM,KAAK,MAAM,KAAK,0BAA0B;AAAA,EACnH;AACF;AAEA,IAAM,mBAAmB;AAAA,EACvB,OAAO,EAAE,MAAM,UAAmB,UAAU,MAAM,aAAa,iDAAiD;AAAA,EAChH,OAAO,EAAE,MAAM,WAAoB,aAAa,+BAA+B;AACjF;AAQA,SAAS,YAAY,OAAyC;AAC5D,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AAAgB,aAAO;AAAA,IAC5B,KAAK;AAAqB,aAAO;AAAA,IACjC,KAAK;AAAe,aAAO;AAAA,IAC3B;AAAS,aAAO;AAAA,EAClB;AACF;AASA,SAAS,gBAAgB,SAA+B;AACtD,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,OAAoB,CAAC;AAC3B,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,SAAS,QAAQ;AAC1B,SAAK,KAAK;AAAA,MACR,MAAM;AAAA,MACN,KAAK,MAAM;AAAA,MACX,MAAM,MAAM;AAAA,MACZ,OAAO,MAAM,QAAQ,MAAM,GAAG,EAAE,KAAK,MAAM;AAAA,MAC3C,SAAS,MAAM;AAAA,MACf,MAAM,MAAM;AAAA,MACZ,QAAQ,mBAAmB,MAAM,OAAO;AAAA,IAC1C,CAAC;AACD,eAAW,OAAO,mBAAmB,SAAS,MAAM,OAAO,GAAG;AAC5D,UAAI,QAAQ,IAAI,GAAG,EAAG;AACtB,cAAQ,IAAI,GAAG;AACf,YAAM,QAAQ,UAAU,SAAS,GAAG;AACpC,UAAI,UAAU,OAAW;AACzB,YAAM,OAAO,YAAY,KAAK;AAC9B,YAAM,OAAO,iBAAiB,KAAK;AACnC,UAAI,SAAS,QAAQ,KAAK,WAAW,EAAG;AACxC,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,KAAK,OAAO,GAAG;AAAA,QACf;AAAA,QACA,OAAO,GAAG,IAAI,KAAK,KAAK,MAAM,GAAG,EAAE,CAAC;AAAA,QACpC;AAAA,QACA,SAAS,MAAM;AAAA,QACf,MAAM,MAAM;AAAA,QACZ,QAAQ,mBAAmB,IAAI;AAAA,MACjC,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,MAAuB,SAAqB,MAAkC;AAClG,QAAM,OAAO,eAA2B,OAAO;AAC/C,QAAM,UAAU,aAAa,IAAI,EAAE;AACnC,MAAI,KAAK,MAAM,KAAK,MAAM,GAAI,QAAO,EAAE,MAAM,2CAA2C;AACxF,QAAM,OAAO,gBAAgB,OAAO;AAKpC,QAAM,UAAU,aAAa,MAAM,KAAK,OAAO,EAAE,OAAO,KAAK,SAAS,GAAG,eAAe,IAAI,CAAC;AAC7F,MAAI,QAAQ,WAAW,EAAG,QAAO,EAAE,MAAM,mCAAmC,KAAK,KAAK,IAAI;AAC1F,QAAM,QAAQ,QAAQ,IAAI,CAAC,MAAM;AAC/B,UAAM,OAAO,EAAE,SAAS,UAAU,SAAS,EAAE,GAAG,KAAK,WAAW,EAAE,GAAG,KAAK,EAAE,QAAQ,GAAG,cAAc,EAAE,WAAW,GAAG;AACrH,WAAO,OAAO,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAC,CAAC,MAAM,EAAE,OAAO;AAAA,EAChE,CAAC;AACD,SAAO;AAAA,IACL,MAAM,gBAAgB,KAAK,KAAK;AAAA,EAAO,MAAM,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA,EACzD;AACF;AASA,IAAM,mBAAmB;AAAA,EACvB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,MAAM,CAAC,cAAc,cAAc;AAAA,IACnC,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,CAAC,UAAU,UAAU;AAAA,IAC3B,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AAAA,EACA,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,MAAM,CAAC,QAAQ,QAAQ,QAAQ,KAAK;AAAA,IACpC,aAAa;AAAA,EACf;AAAA,EACA,OAAO;AAAA,IACL,MAAM;AAAA,IACN,aAAa;AAAA,EACf;AACF;AAeA,SAAS,kBAAkB,OAA8B;AACvD,MAAI,MAAM,SAAS,eAAgB,QAAO;AAC1C,QAAM,SAAU,MAAM,KAA0C;AAChE,SAAO,QAAQ,WAAW;AAC5B;AAEA,eAAe,aAAa,KAAsB,SAAqB,MAA2C;AAIhH,QAAM,OAAO,eAA2B,OAAO;AAC/C,QAAM,QAAQ,aAAa,IAAI;AAC/B,QAAM,UAAU,MAAM;AACtB,QAAM,QAAQ,IAAI,MAAM,SAAS,OAAO;AACxC,QAAM,UAAU,gBAAgB,OAAO;AAGvC,QAAM,YAAY,mBAAmB,OAAO;AAC5C,QAAM,eAAe,eAAe,OAAO;AAC3C,QAAM,kBAAkB,qBAAqB,SAAS,SAAS;AAC/D,QAAM,aAAa,kBAAkB,OAAO,eAAe;AAC3D,QAAM,SAAS,MAAM,uBAAuB,KAAK,KAAK;AACtD,QAAM,SAAS,gBAAgB,EAAE,GAAG,KAAK,mBAAmB,OAAO,MAAM,CAAC;AAM1E,QAAM,OAAO,IAAI,OAAO,YAAY,EAAE,UAAU,cAAc,OAAO,QAAQ,WAAW,CAAC;AAEzF,QAAM,iBAAiB;AAAA,IACrB,QAAQ,OAAO,CAAC,UAAU,CAAC,kBAAkB,KAAK,CAAC;AAAA,IACnD;AAAA,EACF;AAKA,QAAM,SAAS,kBAAkB,KAAK,OAAO,gBAAgB,oBAAoB,IAAI;AACrF,QAAM,QAAQ,CAAC,MAAM;AAKrB,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,QAAQ,KAAK;AACnB,QAAI,UAAU,QAAW;AACvB,YAAM,KAAK,IAAI,UAAU,MAAM,eAAe,WAAW,MAAM,WAAM,MAAM,MAAM,EAAE;AAAA,IACrF;AAQA,UAAM,iBAAiB,cAAc,OAAO,EACzC,OAAO,CAAC,UAAU,MAAM,UAAU,MAAM,eAAe,IAAI,EAC3D,IAAI,CAAC,UAAU,GAAG,MAAM,aAAa,eAAU,MAAM,UAAU,EAAE;AACpE,QAAI,eAAe,SAAS,GAAG;AAC7B,YAAM,KAAK,IAAI,mFAA8E,eAAe,KAAK,IAAI,CAAC,EAAE;AAAA,IAC1H;AAAA,EACF;AACA,QAAM,KAAK,IAAI,YAAY,eAAe,OAAO,CAAC,EAAE;AAKpD,MAAI,KAAK,UAAU,gBAAgB;AACjC,UAAM,KAAK,IAAI,2JAAsJ;AAAA,EACvK;AACA,SAAO,EAAE,MAAM,MAAM,KAAK,IAAI,EAAE;AAClC;AAGO,SAAS,UAAU,KAAwC;AAChE,QAAM,UAAU,IAAI,WAAW;AAC/B,SAAO;AAAA,IACL,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa,QAAQ,MAAM;AAAA,MAC3B,YAAY;AAAA,MACZ,QAAQ,WAAW;AAAA,MACnB,MAAM,QAAQ,MAAM,MAAM;AACxB,eAAO,eAAe,KAAK,MAAsB,IAAI;AAAA,MACvD;AAAA,IACF,CAAC;AAAA,IACD,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa,QAAQ,MAAM;AAAA,MAC3B,YAAY;AAAA,MACZ,QAAQ,WAAW;AAAA,MACnB,QAAQ,MAAM,MAAM;AAClB,eAAO,QAAQ,QAAQ,iBAAiB,KAAK,MAAwB,IAAI,CAAC;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,IACD,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa,QAAQ,MAAM;AAAA,MAC3B,YAAY;AAAA,MACZ,QAAQ,WAAW;AAAA,MACnB,QAAQ,MAAM,MAAM;AAClB,eAAO,QAAQ,QAAQ,aAAa,KAAK,MAAoB,IAAI,CAAC;AAAA,MACpE;AAAA,IACF,CAAC;AAAA,IACD,WAAW;AAAA,MACT,MAAM;AAAA,MACN,aAAa,QAAQ,MAAM;AAAA,MAC3B,YAAY;AAAA,MACZ,QAAQ,WAAW;AAAA,MACnB,QAAQ,MAAM,MAAM;AAClB,eAAO,aAAa,KAAK,MAAoB,IAAI;AAAA,MACnD;AAAA,IACF,CAAC;AAAA,EACH;AACF;;;AI9xBO,IAAM,yBAAyB;AAwC/B,SAAS,kBAAkB,QAA2B;AAC3D,MAAI,OAAO,WAAW,WAAY,QAAO;AACzC,MAAI,OAAO,WAAW,cAAc;AAClC,WAAO;AAAA,EACT;AACA,MAAI,OAAO,WAAW,QAAQ;AAC5B,WAAO,sBAAsB,OAAO,YAAY,GAAG,IAAI,OAAO,SAAS,GAAG;AAAA,EAC5E;AACA,MAAI,OAAO,gBAAgB,KAAM,QAAO;AACxC,SAAO;AACT;AA4BO,SAAS,uBAAuB,OAA6B;AAClE,QAAM,cAAc,MAAM,KAAK,MAAM,oBAAoB;AACzD,QAAM,SAAS,aAAa,WAAW,MAAM,OAAO,GAAG,QAAQ,iBAAiB;AAChF,MAAI,OAAO,WAAW,YAAY,OAAO,UAAU,MAAM,KAAK,SAAS,EAAG,QAAO;AACjF,SAAO;AACT;AAsBA,eAAsB,iBACpB,OACA,UACA,OAC2B;AAC3B,QAAM,MAAM,MAAM,KAAK,MAAM,KAAK;AAClC,MAAI,KAAK,qBAAqB,OAAW,QAAO,EAAE,eAAe,MAAM,mBAAmB,KAAK;AAC/F,MAAI;AACF,UAAM,OAAO,MAAM,IAAI,iBAAiB,UAAU,KAAK;AACvD,UAAM,SAAS,MAAM,SAAS;AAC9B,UAAM,MAAM,MAAM;AAClB,WAAO;AAAA,MACL,eAAe,OAAO,WAAW,YAAY,OAAO,UAAU,MAAM,KAAK,SAAS,IAAI,SAAS;AAAA,MAC/F,mBAAmB,OAAO,QAAQ,YAAY,OAAO,UAAU,GAAG,KAAK,MAAM,IAAI,MAAM;AAAA,IACzF;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,eAAe,MAAM,mBAAmB,KAAK;AAAA,EACxD;AACF;AAOA,eAAsB,oBACpB,OACA,UACA,OACwB;AACxB,UAAQ,MAAM,iBAAiB,OAAO,UAAU,KAAK,GAAG;AAC1D;;;AClIA,eAAe,WAAW,KAAsB,OAA+B;AAC7E,QAAM,UAAU,MAAM;AACtB,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,cAAc,OAAO,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,oBAAoB,CAAC;AAGnF,QAAM,eAAe,eAAe,OAAO;AAC3C,QAAM,kBAAkB,qBAAqB,gBAAgB,OAAO,CAAC;AACrE,QAAM,YAAY,kBAAkB,OAAO,eAAe;AAC1D,QAAM,SAAS,MAAM,uBAAuB,KAAK,KAAK;AACtD,QAAM,QAAQ,OAAO;AAIrB,QAAM,aAAa,OAAO,aAAa,UAAa,OAAO,mBAAmB,SAC1E,qBAAqB,KAAK,SAAS,OAAO,QAAQ,WAAM,OAAO,cAAc,wBAAwB,kBAAkB,MAAM,CAAC,MAC9H,qBAAqB,KAAK,KAAK,kBAAkB,MAAM,CAAC;AAC5D,QAAM,QAAQ;AAAA,IACZ,6BAAwB,QAAQ,EAAE;AAAA,IAClC,aAAa,OAAO,MAAM;AAAA,IAC1B,wBAAwB,WAAW;AAAA,IACnC,wBAAwB,SAAS,MAAM,KAAK,KAAK,KAAK,MAAO,YAAY,QAAS,GAAG,CAAC;AAAA,IACtF;AAAA,EACF;AAKA,MAAI,OAAO,gBAAgB,MAAM;AAC/B,UAAM,KAAK,0DAAgD,KAAK,sEAAsE;AAAA,EACxI;AAGA,QAAM,QAAQ,gBAAgB,IAAI,MAAM,SAAS,OAAO,CAAC;AACzD,QAAM,SAAS,gBAAgB,EAAE,GAAG,KAAK,mBAAmB,MAAM,CAAC;AACnE,QAAM,OAAO,IAAI,OAAO,YAAY,EAAE,UAAU,cAAc,OAAO,QAAQ,YAAY,UAAU,CAAC;AACpG,QAAM,QAAQ,KAAK;AACnB,MAAI,UAAU,QAAW;AACvB,UAAM,QAAQ,MAAM,eAAgB,MAAM,SAAS,OAAO,YAAY,MAAM,IAAI,MAAM,WAAY;AAClG,UAAM,KAAK,YAAY,KAAK,WAAM,MAAM,MAAM,EAAE;AAChD,QAAI,CAAC,MAAM,cAAc;AACvB,YAAM,SAAS,OAAO,MAAM;AAC5B,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,QAAQ,SAAS,CAAC;AAClE,YAAM,KAAK,kBAAkB,QAAQ,eAAe,CAAC,wBAAwB,KAAK,MAAM,MAAM,eAAe,GAAG,CAAC,YAAO,KAAK,MAAM,SAAS,GAAG,CAAC,SAAS;AAAA,IAC3J;AAAA,EACF;AAIA,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,MAAM,OAAO,IAAI,MAAM,MAAM,IAAI,MAAM;AACpD,UAAM,KAAK,OAAO,MAAM,QAAQ,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,UAAU,MAAM,KAAK,KAAK,MAAM,GAAG,WAAM,MAAM,QAAQ,MAAM,GAAG,EAAE,CAAC,EAAE;AAAA,EACzH;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,aAAa,KAAsB,OAAc,MAAwB;AAChF,MAAI,KAAK,SAAS,GAAG;AACnB,WAAO;AAAA,EACT;AACA,QAAM,WAAW,OAAO,KAAK,CAAC,CAAC;AAC/B,QAAM,SAAS,OAAO,KAAK,CAAC,CAAC;AAC7B,QAAM,UAAU,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG;AACtC,MAAI,CAAC,OAAO,UAAU,QAAQ,KAAK,CAAC,OAAO,UAAU,MAAM,GAAG;AAC5D,WAAO;AAAA,EACT;AACA,QAAM,UAAU,MAAM;AACtB,QAAM,EAAE,OAAO,IAAI,IAAI,oBAAoB,SAAS,UAAU,MAAM;AAIpE,MAAI,sBAAsB,SAAS,KAAK,MAAM,QAAQ,sBAAsB,SAAS,GAAG,MAAM,MAAM;AAClG,WAAO;AAAA,EACT;AAKA,QAAM,WAAW,eAAe,SAAS,OAAO,GAAG;AAGnD,QAAM,iBAAiB,uBAAuB,SAAS,UAAU,MAAM,GAAG;AAC1E,QAAM,EAAE,aAAa,IAAI,yBAAyB,SAAS;AAAA,IACzD;AAAA,IACA;AAAA,IACA,cAAc;AAAA,IACd,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,IACzC,oBAAoB;AAAA,IACpB,UAAU,MAAM,QAAQ,YAAY;AAAA,IACpC,OAAO,MAAM,QAAQ,SAAS;AAAA,EAChC,CAAC;AACD,SAAO,mBAAmB,KAAK,KAAK,GAAG,KAAK,SAAS,MAAM,uBAAuB,aAAa,MAAM,GAAG,CAAC,CAAC;AAC5G;AAEA,SAAS,eAAe,MAAuB,OAAc,MAAwB;AACnF,MAAI,KAAK,SAAS,EAAG,QAAO;AAC5B,QAAM,UAAU,MAAM;AAGtB,QAAM,UAAU,mBAAmB,SAAS,KAAK,CAAC,CAAE;AACpD,QAAM,SAAS,mBAAmB,gBAAgB,OAAO,CAAC;AAC1D,QAAM,QAAQ,YAAY,OACtB,OAAO,KAAK,CAAC,UAAU,MAAM,QAAQ,WAAW,KAAK,CAAC,CAAE,CAAC,IACzD,OAAO,KAAK,CAAC,UAAU,MAAM,YAAY,OAAO;AACpD,MAAI,UAAU,OAAW,QAAO,UAAU,KAAK,CAAC,CAAC;AAEjD,QAAM,QAAQ,mBAAmB,SAAS,MAAM,OAAO,EACpD,IAAI,CAAC,QAAQ,iBAAiB,UAAU,SAAS,GAAG,CAAE,CAAC,EACvD,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC;AACnC,SAAO,SAAS,MAAM,OAAO,WAAM,MAAM,OAAO;AAAA;AAAA,EAAO,MAAM,KAAK,MAAM,KAAK,0BAA0B;AACzG;AAGO,SAAS,WAAW,KAAyC;AAClE,SAAO;AAAA,IACL,MAAM;AAAA,IACN,aACE;AAAA,IAEF,SAAS,OAAO,eAAe;AAC7B,YAAM,MAAM,WAAW,SAAS,KAAK;AACrC,UAAI,QAAQ,MAAM,QAAQ,UAAU;AAClC,eAAO,EAAE,MAAM,WAAW,MAAM,MAAM,WAAW,KAAK,WAAW,KAAK,EAAE;AAAA,MAC1E;AACA,UAAI,IAAI,WAAW,UAAU,GAAG;AAC9B,eAAO,EAAE,MAAM,WAAW,MAAM,aAAa,KAAK,WAAW,OAAO,IAAI,MAAM,WAAW,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,CAAE,EAAE;AAAA,MACzH;AACA,UAAI,IAAI,WAAW,YAAY,GAAG;AAChC,eAAO,EAAE,MAAM,WAAW,MAAM,eAAe,KAAK,WAAW,OAAO,IAAI,MAAM,aAAa,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,CAAC,EAAE;AAAA,MAC5H;AACA,aAAO,EAAE,MAAM,SAAS,MAAM,4BAA4B,IAAI,MAAM,KAAK,EAAE,CAAC,CAAC,8CAAyC;AAAA,IACxH;AAAA,EACF;AACF;;;AChJO,IAAM,oBAAoB,mBAAmB,eAAe;AAG5D,IAAM,0BAA0B;;;A/CwIvC,IAAM,iBAA4B;AAAA,EAChC,uBAAuB;AAAA,EACvB,WAAW;AAAA,EACX,aAAa;AAAA,EACb,WAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQX,yBAAyB;AAAA,EACzB,4BAA4B;AAC9B;AAEO,SAAS,iBAAiB,SAA6B,CAAC,GAAc;AAC3E,SAAO,EAAE,GAAG,gBAAgB,GAAG,OAAO;AACxC;AAOO,IAAM,sBAAN,cAAkC,iBAAiB;AAAA;AAAA,EAE/C;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA;AAAA,EAEQ,gBAAgB,oBAAI,IAAoB;AAAA;AAAA,EAExC,wBAAwB,oBAAI,IAAY;AAAA;AAAA,EAExC,cAAc,oBAAI,IAAuB;AAAA;AAAA,EAEzC,yBAAyB,oBAAI,IAA2B;AAAA,EAEzE,YAAY,KAAc,SAA6B,CAAC,GAAG;AACzD,UAAM,GAAG;AACT,SAAK,SAAS,iBAAiB,MAAM;AAGrC,SAAK,UAAU,eAAe,OAAO,OAAO;AAC5C,UAAM,QAAQ,KAAK,OAAO,gBAAgB,SAAY,EAAE,aAAa,KAAK,OAAO,YAAY,IAAI,CAAC;AAClG,SAAK,SAAS,WAAW,KAAK;AAC9B,SAAK,QAAQ,IAAI,cAAc;AAE/B,UAAM,MAAuB;AAAA,MAC3B,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA;AAAA,MAEZ,mBAAmB,KAAK,OAAO,qBAAqB;AAAA,MACpD,yBAAyB,KAAK,OAAO;AAAA,MACrC,yBAAyB,KAAK,OAAO;AAAA,MACrC,4BAA4B,KAAK,OAAO;AAAA,MACxC,eAAe,KAAK,OAAO;AAAA,MAC3B,WAAW,CAAC,UAAU,KAAK,UAAU,KAAK;AAAA,MAC1C,SAAS,KAAK;AAAA,MACd,uBAAuB,KAAK;AAAA,IAC9B;AACA,SAAK,MAAM;AAUX,UAAM,QAAQ,IAAI,IAAI,OAAO;AAC7B,QAAI,UAAU,QAAW;AACvB,iBAAW,QAAQ,UAAU,GAAG,EAAG,OAAM,SAAS,IAAI;AAAA,IACxD,OAAO;AACL,UAAI,OAAO;AACX,YAAM,gBAAgB,MAAY;AAChC,YAAI,KAAM;AACV,cAAMC,YAAW,IAAI,IAAI,OAAO;AAChC,YAAIA,cAAa,OAAW;AAC5B,eAAO;AACP,mBAAW,QAAQ,UAAU,GAAG,EAAG,CAAAA,UAAS,SAAS,IAAI;AAAA,MAC3D;AACA,UAAI,GAAG,oBAAoB,CAAC,SAAkB;AAC5C,YAAI,SAAS,QAAS,eAAc;AAAA,MACtC,CAAC;AAAA,IACH;AACA,UAAM,WAAW,IAAI,IAAI,UAAU;AACnC,QAAI,aAAa,QAAW;AAC1B,eAAS,SAAS,WAAW,GAAG,CAAC;AAAA,IACnC,OAAO;AACL,UAAI,OAAO;AACX,YAAM,kBAAkB,MAAY;AAClC,YAAI,KAAM;AACV,cAAMA,YAAW,IAAI,IAAI,UAAU;AACnC,YAAIA,cAAa,OAAW;AAC5B,eAAO;AACP,QAAAA,UAAS,SAAS,WAAW,GAAG,CAAC;AAAA,MACnC;AACA,UAAI,GAAG,oBAAoB,CAAC,SAAkB;AAC5C,YAAI,SAAS,WAAY,iBAAgB;AAAA,MAC3C,CAAC;AAAA,IACH;AAMA,QAAI,GAAG,iBAAiB,CAAC,SAAS,UAAU;AAC1C,UAAI,MAAM,SAAS,cAAe;AAClC,YAAM,UAAU,MAAM,KAAK;AAC3B,YAAM,QAAQ,QAAQ,QAAQ,CAAC;AAC/B,YAAM,SAAS,OAAO,cAAc,QAAQ,OAAO;AACnD,UAAI,OAAO,WAAW,YAAY,CAAC,KAAK,sBAAsB,IAAI,MAAM,EAAG;AAC3E,WAAK,sBAAsB,OAAO,MAAM;AASxC,4BAAsB,SAAS,QAAQ,MAAM,KAAK,CAAC,UAAU;AAC3D,YAAI,OAAO,KAAK,+DAA+D,OAAO,KAAK,CAAC,EAAE;AAAA,MAChG,CAAC;AAAA,IACH,CAAC;AACD,QAAI,GAAG,kBAAkB,OAAO,SAAS,SAAS;AAQhD,uCAAiC,QAAQ,MAAM,OAAO;AACtD,UAAI,CAAC,KAAK,OAAO,UAAW,QAAO,KAAK;AACxC,YAAM,WAAW,MAAM,KAAK;AAC5B,UAAI,SAAS,SAAS,SAAU,QAAO;AACvC,YAAM,SAAS,MAAM,KAAK,UAAU,QAAQ,KAAK;AACjD,YAAM,UAAU,WAAW,QAAQ,OAAO,EAAE,GAAG,KAAK,mBAAmB,OAAO,MAAM,GAAG,KAAK,aAAa;AACzG,UAAI,YAAY,KAAM,QAAO;AAC7B,aAAO,EAAE,MAAM,SAAS,UAAU,CAAC,GAAG,SAAS,UAAU,QAAQ,OAAO,EAAE;AAAA,IAC5E,CAAC;AAQD,UAAM,eAAe,IAAI,IAAI,cAAc;AAC3C,QAAI,iBAAiB,QAAW;AAC9B,mBAAa,QAAQ;AAAA,QACnB,MAAM;AAAA,QACN,OAAO;AAAA,QACP,MAAM,mBAAmB,KAAK,OAAO;AAAA,MACvC,CAAC;AAAA,IACH,OAAO;AACL,UAAI,OAAO;AACX,YAAM,uBAAuB,MAAY;AACvC,YAAI,KAAM;AACV,cAAMA,YAAW,IAAI,IAAI,cAAc;AACvC,YAAIA,cAAa,OAAW;AAC5B,eAAO;AACP,QAAAA,UAAS,QAAQ;AAAA,UACf,MAAM;AAAA,UACN,OAAO;AAAA,UACP,MAAM,mBAAmB,KAAK,OAAO;AAAA,QACvC,CAAC;AAAA,MACH;AACA,UAAI,GAAG,oBAAoB,CAAC,SAAkB;AAC5C,YAAI,SAAS,eAAgB,sBAAqB;AAAA,MACpD,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,UAAU,OAAkC;AAChD,QAAI,KAAK,OAAO,sBAAsB,QAAW;AAC/C,aAAO,EAAE,OAAO,KAAK,OAAO,mBAAmB,QAAQ,WAAW;AAAA,IACpE;AACA,UAAM,WAAW,MAAM,QAAQ,YAAY;AAC3C,UAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,UAAM,MAAM,GAAG,QAAQ,KAAK,KAAK;AAMjC,QAAI,KAAK,OAAO,uBAAuB;AACrC,YAAM,YAAY,uBAAuB,KAAK;AAC9C,UAAI,cAAc,MAAM;AAKtB,cAAMC,OAAM,MAAM,KAAK,aAAa,OAAO,UAAU,KAAK;AAC1D,eAAO,KAAK,iBAAiB,EAAE,OAAO,WAAW,QAAQ,cAAc,UAAU,MAAM,GAAGA,IAAG;AAAA,MAC/F;AAAA,IACF;AACA,UAAM,SAAS,KAAK,YAAY,IAAI,GAAG;AACvC,QAAI,WAAW,OAAW,QAAO;AACjC,QAAI;AACJ,QAAI,MAAqB;AACzB,QAAI,CAAC,KAAK,OAAO,uBAAuB;AACtC,eAAS,EAAE,OAAO,wBAAwB,QAAQ,WAAW,UAAU,MAAM;AAAA,IAC/E,OAAO;AACL,YAAM,QAAQ,MAAM,iBAAiB,OAAO,UAAU,KAAK;AAC3D,YAAM,MAAM;AACZ,UAAI,MAAM,kBAAkB,MAAM;AAQhC,aAAK,IAAI,OAAO;AAAA,UACd,iEAAiE,QAAQ,IAAI,KAAK,qBAAgB,sBAAsB;AAAA,QAC1H;AACA,iBAAS,EAAE,OAAO,wBAAwB,QAAQ,WAAW,UAAU,OAAO,aAAa,KAAK;AAChG,cAAM;AAAA,MACR,OAAO;AACL,iBAAS,EAAE,OAAO,MAAM,eAAe,QAAQ,QAAQ,UAAU,MAAM;AAAA,MACzE;AAAA,IACF;AACA,aAAS,KAAK,iBAAiB,QAAQ,GAAG;AAC1C,SAAK,YAAY,IAAI,KAAK,MAAM;AAChC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,aAAa,OAAc,UAAkB,OAAuC;AAChG,QAAI,aAAa,MAAM,UAAU,GAAI,QAAO;AAC5C,UAAM,MAAM,GAAG,QAAQ,KAAK,KAAK;AACjC,UAAM,QAAQ,KAAK,uBAAuB,IAAI,GAAG;AACjD,QAAI,UAAU,OAAW,QAAO;AAChC,UAAM,OAAO,MAAM,iBAAiB,OAAO,UAAU,KAAK,GAAG;AAC7D,SAAK,uBAAuB,IAAI,KAAK,GAAG;AACxC,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,QAAmB,KAA+B;AACzE,QAAI,QAAQ,QAAQ,OAAO,OAAO,MAAO,QAAO;AAChD,WAAO,EAAE,GAAG,QAAQ,UAAU,OAAO,OAAO,gBAAgB,KAAK,OAAO,OAAO,QAAQ,IAAI;AAAA,EAC7F;AAAA;AAAA,EAGA,MAAe,gBACb,QACA,UACA,QACkC;AAClC,WAAO,eAAe;AACtB,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAe,WACb,QACA,QACkC;AAClC,WAAO,eAAe;AACtB,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAe,cACb,QACA,MACA,QACA,QAC2B;AAC3B,YAAQ,eAAe;AACvB,UAAM,IAAI;AAAA,MACR;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":["require","block","refNum","formatTokens","numericPart","activeBlocks","refNum","stem","countOccurrences","registry","tokens","createUserMessage","createUserMessage","pct","parseBoundary","tierLabel","registry","cap"]} \ No newline at end of file diff --git a/src/region.ts b/src/region.ts index c600a76..aefc6a6 100644 --- a/src/region.ts +++ b/src/region.ts @@ -473,8 +473,14 @@ function summarySeqOfCompaction(events: readonly SessionEvent[], compactionId: s return null } +// Memoized on the append-only snapshot array (stable within one tool call, see +// sessionEventsOf): identity+length never goes stale; avoids O(B^2*N) rebuilds (#109). +const blockLedgerCache = new WeakMap() + /** Rebuild the block ledger from the durable log (no kernel state needed). */ export function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLedgerEntry[] { + const cached = blockLedgerCache.get(events) + if (cached !== undefined && cached.len === events.length) return cached.ledger const ledger: AcpBlockLedgerEntry[] = [] for (const event of events) { if (event.type !== 'compaction/summary') continue @@ -512,6 +518,7 @@ export function rebuildBlockLedger(events: readonly SessionEvent[]): AcpBlockLed createdAt: event.time, }) } + blockLedgerCache.set(events, { len: events.length, ledger }) return ledger } diff --git a/tests/region.test.ts b/tests/region.test.ts index 0db17b0..3479214 100644 --- a/tests/region.test.ts +++ b/tests/region.test.ts @@ -42,6 +42,27 @@ test('M5: findOpenTurn / assertNoActiveCompaction track the durable lock', () => assertNoActiveCompaction(session.events) }) +test('M5: rebuildBlockLedger is idempotent across repeated calls on one snapshot (issue #109)', () => { + const session = buildTextSession(8) + const c1 = runCompactionTransaction(session, { + start: 1, end: 3, shadowedSeqs: [1, 2, 3], + summary: [{ type: 'text', text: 'first summary detail' }], + shadowedTokenCount: 100, provider: 'p', model: 'm', + }) + const c2 = runCompactionTransaction(session, { + start: 4, end: 6, shadowedSeqs: [4, 5, 6], + summary: [{ type: 'text', text: 'second summary detail' }], + shadowedTokenCount: 200, provider: 'p', model: 'm', + }) + const events = session.events + const first = rebuildBlockLedger(events) + const second = rebuildBlockLedger(events) + assert.equal(first.length, 2) + assert.deepEqual(second, first, 'repeated calls on the same snapshot return identical ledger') + assert.equal(first[0]!.blockId, c1.compactionId) + assert.equal(first[1]!.blockId, c2.compactionId) +}) + test('M5: runCompactionTransaction lands the four events and shadows the range', () => { const session = buildTextSession(6) const { compactionId, seqs } = runCompactionTransaction(session, {