acp-kernel 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/DESIGN.md +220 -0
  2. package/LICENSE +21 -0
  3. package/PROVENANCE.md +92 -0
  4. package/README.md +96 -2
  5. package/dist/boundaries.d.ts +25 -0
  6. package/dist/boundaries.d.ts.map +1 -0
  7. package/dist/compress.d.ts +37 -0
  8. package/dist/compress.d.ts.map +1 -0
  9. package/dist/compression-rules.d.ts +11 -0
  10. package/dist/compression-rules.d.ts.map +1 -0
  11. package/dist/config.d.ts +4 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/decompress.d.ts +14 -0
  14. package/dist/decompress.d.ts.map +1 -0
  15. package/dist/filter/apply.d.ts +10 -0
  16. package/dist/filter/apply.d.ts.map +1 -0
  17. package/dist/filter/index.d.ts +5 -0
  18. package/dist/filter/index.d.ts.map +1 -0
  19. package/dist/filter/registry.d.ts +6 -0
  20. package/dist/filter/registry.d.ts.map +1 -0
  21. package/dist/filter/types.d.ts +28 -0
  22. package/dist/filter/types.d.ts.map +1 -0
  23. package/dist/hide-consumed.d.ts +7 -0
  24. package/dist/hide-consumed.d.ts.map +1 -0
  25. package/dist/index.d.ts +33 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +2303 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/keep-markers.d.ts +9 -0
  30. package/dist/keep-markers.d.ts.map +1 -0
  31. package/dist/merge.d.ts +9 -0
  32. package/dist/merge.d.ts.map +1 -0
  33. package/dist/nudge-text.d.ts +8 -0
  34. package/dist/nudge-text.d.ts.map +1 -0
  35. package/dist/pipeline.d.ts +26 -0
  36. package/dist/pipeline.d.ts.map +1 -0
  37. package/dist/protected.d.ts +4 -0
  38. package/dist/protected.d.ts.map +1 -0
  39. package/dist/prune.d.ts +7 -0
  40. package/dist/prune.d.ts.map +1 -0
  41. package/dist/rebuild.d.ts +25 -0
  42. package/dist/rebuild.d.ts.map +1 -0
  43. package/dist/recommend.d.ts +40 -0
  44. package/dist/recommend.d.ts.map +1 -0
  45. package/dist/refs.d.ts +22 -0
  46. package/dist/refs.d.ts.map +1 -0
  47. package/dist/render-refs.d.ts +5 -0
  48. package/dist/render-refs.d.ts.map +1 -0
  49. package/dist/report.d.ts +11 -0
  50. package/dist/report.d.ts.map +1 -0
  51. package/dist/state.d.ts +10 -0
  52. package/dist/state.d.ts.map +1 -0
  53. package/dist/sync.d.ts +7 -0
  54. package/dist/sync.d.ts.map +1 -0
  55. package/dist/tokenize.d.ts +6 -0
  56. package/dist/tokenize.d.ts.map +1 -0
  57. package/dist/truncate-tools.d.ts +14 -0
  58. package/dist/truncate-tools.d.ts.map +1 -0
  59. package/dist/types.d.ts +190 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/package.json +58 -5
package/DESIGN.md ADDED
@@ -0,0 +1,220 @@
1
+ # acp-kernel Design
2
+
3
+ Framework-agnostic, model-driven context-compression engine. This document is the authoritative contract for the pure core.
4
+
5
+ ---
6
+
7
+ ## 1. Mental Model
8
+
9
+ ACP-style compression is **not** like zip in one essential way: **the model writes the summaries; this library orchestrates everything around them.**
10
+
11
+ - **zip**: computes the compressed output itself (`bytes → bytes`).
12
+ - **acp-kernel**: the *summary text* is produced externally (by an LLM) and passed in as an argument. The core decides *when* to compress, *what range* to compress, tracks *state*, applies a compress *decision*, prunes ranges, and supports decompress/search. **It never calls a model.**
13
+
14
+ This is precisely why the core can be a pure library: the one external dependency (the summarizer) is reduced to a string input.
15
+
16
+ ---
17
+
18
+ ## 2. Module Boundary
19
+
20
+ ```
21
+ ┌───────────────────────────────────────────────────────┐
22
+ │ CORE (pure TS, zero host dependency, MIT) │
23
+ │ stateless w.r.t. storage: state passed in and out │
24
+ │ │
25
+ │ processTurn / applyCompression / resolveBoundaries │
26
+ │ decompress / search / status / decideNudge │
27
+ │ (node pipeline: assign-refs → sync → merge → prune → │
28
+ │ filter → hide → nudge → emergency-truncate → render) │
29
+ └──────────────────────┬────────────────────────────────┘
30
+ │ pure function calls
31
+ ┌──────────────────────┴────────────────────────────────┐
32
+ │ ADAPTER (thin, host-specific) — one per host │
33
+ │ OpenCode adapter | Pi adapter | custom │
34
+ │ - pass messages into the core each turn │
35
+ │ - own + persist the state object │
36
+ │ - render NudgeDecision into host message format │
37
+ │ - register tools / commands / lifecycle hooks │
38
+ └───────────────────────────────────────────────────────┘
39
+ ```
40
+
41
+ **The core holds no state and performs no I/O.** State is an explicit input and output of every call. The host decides how to persist it (filesystem, memory, whatever). This is the zip model: `zip(data) → data`.
42
+
43
+ ---
44
+
45
+ ## 3. Domain Types
46
+
47
+ Portable, host-agnostic. Adapters translate host-native messages into `CoreMessage[]` before calling the core, and apply the core's output back.
48
+
49
+ ```ts
50
+ type CoreMessage = {
51
+ id: string;
52
+ role: "user" | "assistant" | "system" | "tool";
53
+ contentType: "text" | "tool-call" | "tool-result" | "reasoning";
54
+ text?: string;
55
+ toolName?: string; // for protected-tool filtering
56
+ toolCallId?: string; // links tool-call ↔ tool-result
57
+ };
58
+
59
+ type CompressionBlock = {
60
+ blockId: string; // "b0", "b1", ...
61
+ runId: string;
62
+ tier: 1 | 2 | 3;
63
+ topic?: string;
64
+ summary: string; // produced by the model
65
+ directMessageIds: string[];
66
+ effectiveMessageIds: string[];
67
+ directBlockIds: string[]; // nested blocks consumed
68
+ createdAt: number;
69
+ survivedCount: number;
70
+ generation: "young" | "old";
71
+ active: boolean;
72
+ };
73
+
74
+ type CompressionState = {
75
+ blocks: CompressionBlock[];
76
+ messageRefs: { byRaw: Record<string, string>; byRef: Record<string, string> }; // raw ↔ mNNNNN
77
+ nudge: {
78
+ lastPerMessageNudgeTokens: number;
79
+ lastNudgeShownTokens: number;
80
+ baselineTokens: number;
81
+ anchors: Record<string, unknown>;
82
+ };
83
+ stats: { tokensCompressed: number; compressionCount: number };
84
+ nextBlockId: number;
85
+ nextRunId: number;
86
+ };
87
+
88
+ type Config = {
89
+ tiers: { enabled: boolean; tier2Trigger: number; tier3Trigger: number };
90
+ nudge: {
91
+ maxContextLimitPct: number; // e.g. 0.55 (currently advisory; threshold gate uses minContextLimitPct)
92
+ minContextLimitPct: number; // e.g. 0.45 — nudge threshold gate
93
+ frequency: number; // advisory (reserved for future turn-frequency gating)
94
+ iterationThreshold: number; // advisory (reserved for future iteration gating)
95
+ force: "soft" | "strong";
96
+ };
97
+ // young→old promotion after N survivals (drives the merge-blocks node).
98
+ // NOTE: there is no GC — no age-based deactivation, no summary truncation.
99
+ promotionThreshold: number;
100
+ truncate: { threshold: number }; // emergency tool-output truncation node (LAST safety valve); 1.0 = 100%
101
+ merge: { maxSummaryLength: number; minOldGenBlocks: number }; // batch-merge old-gen blocks into one summary
102
+ protectedTools: string[];
103
+ preserveRecentMessages: number;
104
+ modelContextLimit: number;
105
+ };
106
+ ```
107
+
108
+ ---
109
+
110
+ ## 4. Core Operations
111
+
112
+ ```ts
113
+ interface CompressionCore {
114
+ // Per-turn node pipeline (replaces the message-transform hook's algorithm part).
115
+ // Runs every turn (canonical order): assign-refs → sync-blocks → merge-blocks →
116
+ // prune → filter → hide-compress-calls → nudge-inject → emergency-truncate →
117
+ // render-refs. Survives/promotes blocks via advanceSurvival (no age-based
118
+ // deactivation). Returns transformed messages + updated state + nudge decision.
119
+ processTurn(input: {
120
+ messages: CoreMessage[];
121
+ state: CompressionState;
122
+ config: Config;
123
+ tokenCount: number;
124
+ }): {
125
+ messages: CoreMessage[];
126
+ state: CompressionState;
127
+ nudge?: NudgeDecision;
128
+ };
129
+
130
+ // When the model calls compress. `ranges[].summary` is model-produced text.
131
+ // Allocates block(s), deactivates consumed blocks, updates indices, resets the
132
+ // nudge growth baseline on success (§5.7 feedback-loop fix).
133
+ applyCompression(input: {
134
+ ranges: { startRef: string; endRef: string; summary: string; topic?: string }[];
135
+ messages: CoreMessage[];
136
+ state: CompressionState;
137
+ config: Config;
138
+ }): {
139
+ state: CompressionState;
140
+ result: { blocksCreated: number; tokensCompressed: number; errors: string[] };
141
+ };
142
+
143
+ resolveBoundaries(input: {
144
+ startRef: string;
145
+ endRef: string;
146
+ messages: CoreMessage[];
147
+ state: CompressionState;
148
+ }): { startIndex: number; endIndex: number; protectedGaps: number[] };
149
+ // protectedGaps is reserved (currently always []); protected-tool hard-exclusion
150
+ // is intentionally NOT implemented in the core — see README "Known limitation".
151
+
152
+ decompress(blockId: string, state: CompressionState): CompressionBlock | undefined;
153
+
154
+ search(query: string, state: CompressionState): CompressionBlock[];
155
+
156
+ status(state: CompressionState, tokenCount: number, config: Config): StatusReport;
157
+
158
+ // No gc(): age-based deactivation was removed (it caused memory-loss upstream).
159
+ // Block promotion (young→old) still happens via advanceSurvival in sync-blocks,
160
+ // and old-gen blocks are batch-merged by the merge-blocks node — not dropped.
161
+ }
162
+
163
+ type CompressCall = {
164
+ mode: "range" | "message";
165
+ ranges: { startRef: string; endRef: string; summary: string; topic?: string }[];
166
+ };
167
+
168
+ type NudgeDecision = {
169
+ shouldInject: boolean;
170
+ reason: string;
171
+ compressibleRanges: { startRef: string; endRef: string; tokens: number }[];
172
+ contextUsage: number; // 0..1
173
+ tier: 1 | 2 | 3 | null; // multi-tier trigger, if any
174
+ breakdown: Record<string, number>;
175
+ };
176
+ ```
177
+
178
+ ---
179
+
180
+ ## 5. Port Surface (host implements)
181
+
182
+ The core needs two capabilities from the host, both injected (the core never imports a host SDK):
183
+
184
+ ```ts
185
+ interface Ports {
186
+ countTokens(text: string): number; // default impl ships in core
187
+ // messages are PASSED IN to each call (never fetched) → no MessageStore port
188
+ // state persistence is the host's job (state is plain data) → no storage port
189
+ }
190
+ ```
191
+
192
+ A default `countTokens` ships with the core (word-level + unicode CJK tokenizer, the same MIT `cc-alg` tokenizer). Hosts may override with a model-specific tokenizer.
193
+
194
+ ---
195
+
196
+ ## 6. Concept Mapping (nothing is lost)
197
+
198
+ | ACP concept | Destination in acp-kernel |
199
+ |---|---|
200
+ | message-id ↔ ref mapping | **core** `processTurn` (pure) |
201
+ | prune (range → summary block) | **core** `processTurn` (pure) |
202
+ | boundary resolution / search | **core** `resolveBoundaries` (pure) |
203
+ | block allocation / state mutation / tiers | **core** `applyCompression` (pure) |
204
+ | young→old promotion / batch merge | **core** `sync-blocks` node (`advanceSurvival`) + `merge-blocks` node (pure) |
205
+ | emergency truncation (context near full) | **core** `emergency-truncate` node — the LAST safety valve; no age-based GC |
206
+ | protected-tools filtering logic | **core** (pure: message + config → bool) |
207
+ | `inject` **decision** (shouldNudge / growth / threshold) | **core** `decideNudge` (pure) |
208
+ | `inject` **text rendering** (nudge → message string) | **adapter** (host message format) |
209
+ | prompts (system / nudge text templates) | rules as **structured data** in core; text rendering in **adapter** |
210
+ | compress/decompress/search/status **tool registration** | **adapter** (calls core pure fns) |
211
+ | `/acp` commands | **adapter** |
212
+ | opencode hooks | **OpenCode adapter** |
213
+ | config three-layer merge | **adapter** (core only consumes its own `Config`) |
214
+ | logger / auth / persistence / update | **adapter** (core does zero I/O) |
215
+
216
+ ---
217
+
218
+ ## 7. Why the algorithm, but not the DCP-derived code, comes here
219
+
220
+ Copyright protects *expression*, not ideas, methods, or algorithms (17 USC §102(b)). The compression *methods* (3-tier, growth cadence, protected filtering) are the author's. This core reimplements them in **fresh expression** — it is not a copy or refactor of DCP-derived files. See [PROVENANCE.md](./PROVENANCE.md) for the per-module origin classification (original-bring / DCP-derived-reimplement / adapter-only).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ranxianglei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PROVENANCE.md ADDED
@@ -0,0 +1,92 @@
1
+ # acp-kernel Provenance Audit
2
+
3
+ This document classifies **every** source file in `opencode-acp/lib/` (the donor) into one of three buckets, to determine what can be carried into the MIT `acp-kernel` and what must be reimplemented in fresh expression.
4
+
5
+ **Method**: exact path comparison of `opencode-acp` (the ACP fork, 82 `lib/*.ts` files) against the upstream DCP repository (66 `lib/*.ts` files). A file that has **no DCP equivalent** is original work (bucket A). A file with a **DCP equivalent at the same path** is a DCP derivative — its *expression* is AGPL-bound regardless of how much it was later changed (derivation is judged by whether a file was created by transforming the original, not by % changed; per copyright law expression is protected, not ideas/methods/algorithms).
6
+
7
+ ---
8
+
9
+ ## Legend
10
+
11
+ | Bucket | Meaning | Action in acp-kernel |
12
+ |---|---|---|
13
+ | **A** | No DCP equivalent → original work of ranxianglei | **Bring verbatim** (the author's own code, MIT-safe). License header rewritten to MIT. |
14
+ | **B** | DCP-derived file (same path exists upstream) → derivative expression is AGPL | **Reimplement in fresh expression** using the author's algorithm; do NOT copy/refactor the file's code. |
15
+ | **N/A** | Adapter-only (framework-specific: opencode hooks, I/O, auth, commands, UI) | **Excluded** from the pure core. Stays in host adapters. Origin irrelevant. |
16
+
17
+ ---
18
+
19
+ ## Bucket A — Original work (bring verbatim) — 30 files
20
+
21
+ Quality gate intentionally omitted from acp-kernel (may be added later if needed).
22
+
23
+ ### `compress/`
24
+ | File | Notes |
25
+ |---|---|
26
+ | `compress/decompress.ts` | ACP-original (v1.11+); DCP has no decompress |
27
+ | `compress/decompress-logic.ts` | ACP-original |
28
+ | `compress/hide-consumed.ts` | ACP-original (v1.14+) |
29
+ | `compress/hide-failed.ts` | ACP-original |
30
+ | `compress/keep-markers.ts` | ACP-original (v1.12+) |
31
+ | `compress/parts.ts` | ACP-original |
32
+ | `compress/recap.ts` | ACP-original (v1.12.1) |
33
+ | `compress/status.ts` | ACP-original (v1.11+) |
34
+
35
+ ### other
36
+ | File | Notes |
37
+ |---|---|
38
+ | `config-validation.ts` | ACP-extracted for testability; no DCP equivalent |
39
+ | `gc/merge.ts` | ACP-original (DCP has no `gc/` dir) |
40
+ | `messages/truncate-tools.ts` | ACP-original (v1.14.5, replaced DCP's `gc/truncate.ts`) |
41
+ | `messages/filter/*` (9 files) | ACP-original filtering subsystem |
42
+ | `messages/inject/policy/*` (3 files) | ACP-original (v1.13.1) — inject **policy** logic, not rendering |
43
+ | `state/rebuild.ts` | ACP-original (v1.11+) |
44
+
45
+ ---
46
+
47
+ ## Bucket B — DCP-derived (reimplement in fresh expression) — 40 files
48
+
49
+ Only the **algorithmic** subset is reimplemented into the pure core; the rest are adapter-only (persistence, config-merge, prompts-rendering) and excluded from the core. Listed by what they become.
50
+
51
+ ### Reimplemented into acp-kernel (fresh expression, ~9 substantial files)
52
+ | DCP-derived file | Becomes | What to reimplement |
53
+ |---|---|---|
54
+ | `compress/range.ts` | `core/applyCompression` (range mode) | block allocation, nested-block handling, boundary resolution |
55
+ | `compress/search.ts` | `core/resolveBoundaries` | ref→index mapping, reversed-boundary swap, protected-gap detection |
56
+ | `compress/state.ts` | `core/state` (mutation) | block id/run allocation, deactivation, byMessageId index |
57
+ | `compress/pipeline.ts` | `core/processTurn` prep/finalize | permission (host-side), fetch (host-side), state wrap |
58
+ | `messages/prune.ts` | `core/prune` | replace compressed ranges with summary blocks |
59
+ | `messages/sync.ts` | `core/sync` | deactivate orphaned blocks when messages deleted |
60
+ | `message-ids.ts` | `core/refs` | raw↔mNNNNN bidirectional map |
61
+ | `messages/inject/inject.ts` + `inject/utils.ts` | `core/decideNudge` | **decision only** — shouldNudge, growth baseline, threshold, compressible ranges |
62
+ | `config.ts` (core subset) | `core/Config` defaults | defaults + validation (use A-class `config-validation.ts`) |
63
+
64
+ ### Supporting types/barrels (trivial, write fresh)
65
+ `compress/{index,types,timing,range-utils}.ts`, `messages/{index,priority,query,reasoning-strip,shape,utils}.ts`, `state/{index,types,utils}.ts`, `token-utils.ts` (wrap `cc-alg` tokenizer), `protected-patterns.ts`, `compress/protected-content.ts`.
66
+
67
+ ### Excluded from core (adapter-only despite B lineage)
68
+ `state/persistence.ts` (filesystem I/O), `prompts/*` (text rendering → adapter), `compress-permission.ts` (permission = host concern).
69
+
70
+ ---
71
+
72
+ ## Bucket N/A — Adapter-only (excluded from core) — 12 files
73
+
74
+ All framework-specific; irrelevant to the pure core. Stay in the OpenCode adapter (and a future Pi adapter).
75
+
76
+ `auth.ts`, `hooks.ts`, `host-permissions.ts`, `logger.ts`, `update.ts`, `ui/notification.ts`, `ui/utils.ts`, `commands/{compression-targets,context,index,stats}.ts`, `compress-permission.ts`.
77
+
78
+ ---
79
+
80
+ ## Summary
81
+
82
+ | Bucket | Files | Disposition |
83
+ |---|---|---|
84
+ | **B** DCP-derived | 40 | ~9 substantial algorithms reimplemented fresh + ~12 types/barrels rewritten fresh; rest excluded (adapter) |
85
+ | **N/A** adapter | 12 | excluded from core |
86
+
87
+
88
+ ---
89
+
90
+ ## Compliance note
91
+
92
+ This audit exists to ensure acp-kernel is **genuinely MIT**, not "MIT-labeled but AGPL-tainted." The rule applied throughout: an idea/algorithm is free regardless of source; a *file's code expression* is bound to the license of the file it descends from. A-class files descend from no DCP file. B-class files' code is NOT carried — the algorithms are reimplemented in new expression. Should any contributor question a classification, the comparison data above (and the upstream DCP tree) allow independent verification.
package/README.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # acp-kernel
2
2
 
3
- Active Context Pruningplatform-agnostic compression kernel for AI coding agents.
3
+ Framework-agnostic, model-driven context-compression engine. Pure TypeScript core with **zero host dependency** like a zip library, it does not assume any agent, server, or UI exists.
4
4
 
5
- Placeholder package. Full release coming soon.
5
+ ## What this is
6
+
7
+ `acp-kernel` is a **host-agnostic, model-driven context-compression engine**: 3-tier LSM-tree context compression, growth-based nudge policy, protected-content filtering. Its compression algorithms and pipeline architecture (`PipelineNode` / `processTurn` / `CompressionCore`) are **original work by the ACP authors** — an independent reimplementation, not a port of any existing codebase.
8
+
9
+ The key design principle: **the model writes the summaries; this library orchestrates everything around them.** The core decides *when* to compress, *what range* to compress, tracks *state* (blocks, message-id mapping, tiers), applies a compress *decision*, prunes compressed ranges, and supports decompress/search. It never calls a model.
10
+
11
+ ## Why a separate library
12
+
13
+ - **Decoupling**: the original plugin is tightly coupled to OpenCode's hook system, making the algorithm hard to test and reuse.
14
+ - **Multi-host**: one core, multiple thin adapters (OpenCode, Pi, or any agent).
15
+ - **License clarity**: an independent reimplementation that shares **no source code** with its inspiration, opencode-dynamic-context-pruning (DCP, AGPL-3.0). Released under the permissive MIT license. See the [License](#license) section for the full provenance statement.
16
+
17
+ ## Mental model
18
+
19
+ ```
20
+ processTurn({ messages, state }) → { messages, state, nudge? } // like zip(data)→data, but stateful state passed in/out
21
+ applyCompression({ call, state }) → { state, result } // call.summary is produced externally by the model
22
+ ```
23
+
24
+ The core is **stateless with respect to storage**: state is an explicit input and output of every call. The host persists state between turns however it likes.
25
+
26
+ See [DESIGN.md](./DESIGN.md) for the full contract and [PROVENANCE.md](./PROVENANCE.md) for the per-module origin audit (which modules are original work vs. reimplemented from scratch).
27
+
28
+ ## API
29
+
30
+ ### Core engine (`createCore`)
31
+
32
+ ```ts
33
+ import { createCore, createInitialState, defaultConfig } from "acp-kernel";
34
+
35
+ const core = createCore(); // optional: { countTokens }
36
+ const state = createInitialState();
37
+ const config = defaultConfig(200000); // modelContextLimit (positional); optional overrides as 2nd arg
38
+
39
+ // processTurn runs the canonical node pipeline every turn:
40
+ // assign-refs → sync-blocks → merge-blocks → prune → filter →
41
+ // hide-compress-calls → nudge-inject → emergency-truncate → render-refs
42
+ const { messages, state: nextState, nudge } = core.processTurn({
43
+ messages, state, config, tokenCount,
44
+ });
45
+
46
+ // When the model emits a compress decision (summary written by the model):
47
+ const { state: compressed, result } = core.applyCompression({
48
+ ranges: [{ startRef: "m00005", endRef: "m00020", summary: "..." }],
49
+ messages, state: nextState, config,
50
+ });
51
+
52
+ core.decompress("b3", compressed); // look up a block
53
+ core.search("auth token", compressed); // relevance-ranked block search
54
+ core.status(compressed, tokenCount, config); // context-usage report
55
+ ```
56
+
57
+ ### Standalone modules
58
+
59
+ | Module | Purpose |
60
+ |--------|---------|
61
+ | `truncateLargeToolOutputs` | Emergency context-threshold-gated truncation of large visible tool outputs (last-resort safety valve; summaries are never touched) |
62
+ | `hideConsumedCompressCalls` | Hide historical compress tool-calls whose block is inactive |
63
+ | `resolveKeepMarkers` | Expand `[[KEEP:mNNNNN]]` / rewrite `[[REF:mNNNNN\|desc]]` |
64
+ | `buildStatusReport` / `buildRecap` | Context-usage report + block recap |
65
+ | `mergeMarkedBlocks` / `collectOldGenBlocks` | Batch merge old-gen blocks into one summary |
66
+ | `rebuildCompressionState` | Fork-recovery: replay historical compress calls |
67
+ | `applyMessageFilters` | Pluggable message-filter framework |
68
+
69
+ ### Nudge system
70
+
71
+ The nudge system tells the model *when* to compress. It implements:
72
+
73
+ - **Threshold gate**: fires when context usage ≥ `nudge.minContextLimitPct`.
74
+ - **Growth-gating**: a repeat nudge requires positive growth since the baseline (prevents re-firing every turn). `"strong"` force relaxes this.
75
+ - **Tier-distillation triggers**: when active tier-1 blocks pile up past `tiers.tier2Trigger`, emit a tier-2 distillation nudge; tier-3 analogously.
76
+ - **Compressible-range computation**: reports the actual compressible ranges (excluding covered + preserved-recent messages) so the model knows what to target.
77
+ - **Baseline reset on compress**: `applyCompression` clears the growth baseline on success, preventing the feedback-loop bug where the nudge re-fires post-compress.
78
+
79
+ ## Status
80
+
81
+ ✅ **Engine complete** — 23 source modules, 167 tests, typecheck + build clean. 3-tier compression, growth-gated nudges, emergency truncation,, fork-recovery, batch merge, composable node pipeline. Ready for adapter authoring.
82
+
83
+ > **Known limitation:** protected tool messages are excluded from *ref assignment* (marked `BLOCKED`) but, unlike opencode-acp's Bug 39, are **not** hard-excluded from an explicitly-referenced compress range. A model that names a range covering a `BLOCKED` message will compress it. This is an intentional simplification for the pure core; adapters that need hard-exclusion should pre-split ranges before calling `applyCompression`.
84
+
85
+ ## License
86
+
87
+ MIT © ranxianglei
88
+
89
+ ### Provenance
90
+
91
+ `acp-kernel` is an **independent reimplementation** of the ACP compression engine. Its compression algorithms and pipeline architecture (`PipelineNode` / `runPipeline` / `processTurn` / `CompressionCore`, the `CompressionBlock` data model, the `messageRefs` mapping, `assign-refs`, `NudgeDecision`, etc.) are **original work by the ACP authors**.
92
+
93
+ It is **inspired by, but not derived from**, [opencode-dynamic-context-pruning](https://github.com/Tarquinen/opencode-dynamic-context-pruning) (DCP, AGPL-3.0, by Tarquinen). The two projects:
94
+
95
+ - share **no source code** (different tokenizers — chars/4 vs. tiktoken; different strategies; different data models);
96
+ - were written from scratch independently;
97
+ - use unrelated host integration models.
98
+
99
+ Because acp-kernel is an independent work rather than a derivative of DCP, the ACP authors — as sole copyright holders of this codebase — release it under the permissive MIT license. This is independent of DCP's AGPL-3.0 terms, which govern only DCP and its derivatives (such as [opencode-acp](https://github.com/ranxianglei/opencode-acp)).
@@ -0,0 +1,25 @@
1
+ import type { CompressionState, CoreMessage, ResolvedBoundary } from "./types.js";
2
+ export type BoundaryKind = "message" | "block";
3
+ export interface ParsedBoundary {
4
+ kind: BoundaryKind;
5
+ numericId: number;
6
+ raw: string;
7
+ }
8
+ export declare function parseBoundary(ref: string): ParsedBoundary | null;
9
+ export interface ResolveBoundariesInput {
10
+ startRef: string;
11
+ endRef: string;
12
+ messages: CoreMessage[];
13
+ state: CompressionState;
14
+ }
15
+ export interface ResolvedRange {
16
+ startIndex: number;
17
+ endIndex: number;
18
+ messageIds: string[];
19
+ nestedBlockIds: string[];
20
+ boundaryKind: BoundaryKind;
21
+ protectedGaps: number[];
22
+ }
23
+ export declare function resolveBoundaries(input: ResolveBoundariesInput): ResolvedRange;
24
+ export declare function toResolvedBoundary(range: ResolvedRange): ResolvedBoundary;
25
+ //# sourceMappingURL=boundaries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"boundaries.d.ts","sourceRoot":"","sources":["../src/boundaries.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAChB,WAAW,EACX,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAEpB,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,OAAO,CAAC;AAE/C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;CACb;AAKD,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAehE;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,YAAY,EAAE,YAAY,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,sBAAsB,GAC5B,aAAa,CA2Df;AAuCD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,aAAa,GAAG,gBAAgB,CAMzE"}
@@ -0,0 +1,37 @@
1
+ import { createInitialState } from "./state.js";
2
+ import { type PipelineNode } from "./pipeline.js";
3
+ import type { ApplyCompressionResult, CompressionBlock, CompressionState, Config, CoreMessage, ProcessTurnResult, StatusReport } from "./types.js";
4
+ export interface Ports {
5
+ countTokens?: (text: string) => number;
6
+ }
7
+ export interface CompressionCore {
8
+ processTurn(input: ProcessTurnInput): ProcessTurnResult;
9
+ applyCompression(input: ApplyCompressionInput): ApplyCompressionResult;
10
+ defaultNodes(): PipelineNode[];
11
+ decompress(blockId: string, state: CompressionState): CompressionBlock | undefined;
12
+ search(query: string, state: CompressionState): CompressionBlock[];
13
+ status(state: CompressionState, tokenCount: number, config: Config): StatusReport;
14
+ }
15
+ export interface ProcessTurnInput {
16
+ messages: CoreMessage[];
17
+ state: CompressionState;
18
+ config: Config;
19
+ tokenCount: number;
20
+ }
21
+ export interface ApplyCompressionInput {
22
+ ranges: {
23
+ startRef: string;
24
+ endRef: string;
25
+ summary: string;
26
+ topic?: string;
27
+ compressCallId?: string;
28
+ summaryMaxChars?: number;
29
+ }[];
30
+ messages: CoreMessage[];
31
+ state: CompressionState;
32
+ config: Config;
33
+ protectedMessageIds?: Set<string>;
34
+ }
35
+ export declare function createCore(ports?: Ports): CompressionCore;
36
+ export { createInitialState };
37
+ //# sourceMappingURL=compress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compress.d.ts","sourceRoot":"","sources":["../src/compress.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,kBAAkB,EACnB,MAAM,YAAY,CAAC;AAcpB,OAAO,EAGL,KAAK,YAAY,EAElB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EACV,sBAAsB,EACtB,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,EAEN,WAAW,EAGX,iBAAiB,EAEjB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,KAAK;IACpB,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,iBAAiB,CAAC;IACxD,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,sBAAsB,CAAC;IACvE,YAAY,IAAI,YAAY,EAAE,CAAC;IAC/B,UAAU,CACR,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,gBAAgB,GACtB,gBAAgB,GAAG,SAAS,CAAC;IAChC,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,gBAAgB,GAAG,gBAAgB,EAAE,CAAC;IACnE,MAAM,CACJ,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GACb,YAAY,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE;QACN,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,EAAE,CAAC;IACJ,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,KAAK,EAAE,gBAAgB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACnC;AAED,wBAAgB,UAAU,CAAC,KAAK,GAAE,KAAU,GAAG,eAAe,CA0M7D;AAijBD,OAAO,EAAE,kBAAkB,EAAE,CAAC"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Compression rule texts — VERBATIM copy from context-compress-algorithms (MIT, ours).
3
+ * These were tuned over months of production use.
4
+ *
5
+ * DO NOT modify the wording — it is the result of extensive tuning.
6
+ */
7
+ export declare 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 \u2014 the ONLY exceptions are protected content, content the current step is actively using, or critical content you cannot reconstruct.\n- Curate summaries like a well-structured document. User prompts, compressed tool outputs, code, logs, or skill-call intermediate results that are critically important should be preserved \u2014 not by exempting them from compression, but by embedding them in the summary via [[KEEP:mNNNNN]] (auto-expanded verbatim) and [[REF:mNNNNN|description]] (compact link).";
8
+ export declare 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 \u2014 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`) \u2014 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 \u2014 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 \u2014 you need the literal string to grep for it later).\n- Key details from reports and analyses \u2014 not just the conclusion. Keep the comparison numbers and the mechanism, not \"X is worse\" alone (write \"1.76\u00D7 PPL gap because KV store is static\", not \"KVNet underperforms\").\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 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 \u2014 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 \u2014 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 \u2192 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 \u2014 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 \u2014 losing these changes what work appears to remain.\n- Message refs of key anchors (`m00420`, `m00510\u2013m00520`) \u2014 they let you or a later reader jump back via decompress to the exact original.\n\nDROP \u2014 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 \u2014 search hits, agent return values, successful tool outputs \u2014 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 \u2014 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 \u2014 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\nKEEP MARKERS: `[[KEEP:mNNNNN]]` expands original message content into the summary (truncated to a max length). Do NOT use KEEP for verbose command output, diagnostic scripts, log dumps, or any content whose value is in the conclusion rather than the raw output \u2014 summarize these or use `[[REF:mNNNNN|desc]` instead.\n\nPRIORITY \u2014 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 \u2014 not narrative prose. If the range spans distinct concerns (request \u2192 findings \u2192 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.";
9
+ export declare const TIER2_DISTILL_RULES = "TIER 2 COMPRESSION \u2014 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 \u2014 these are the only things that survive distillation:\n- Decisions and their rationale (\"chose X over Y because Z\" \u2014 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 \u2014 mark with one line: \"[SUPERSEDED by PR #NNN]\" or \"[OBSOLETE: deleted in vX.Y.Z]\". Do NOT keep the obsolete content's details \u2014 just the marker and reason.\n- Function/class/type names and module paths that are the SUBJECT of the work \u2014 e.g., \"fixed filterCompressedRanges in prune.ts\", \"added SessionStateRegistry in state.ts\". Not exact line numbers or full signatures \u2014 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 \u2014 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\u2192YK tok, Zx). [original topic]`\n Example: `Source: b5+b7 (56K+44K\u2192268 tok, 375x). [Tool-result recap + publish]`\n- 3-5 bullet points per source block, each a self-contained fact.\n- Dense, scannable \u2014 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 \u2014 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].\"";
10
+ export declare const TIER3_CONDENSE_RULES = "TIER 3 COMPRESSION \u2014 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 \u2014 when a source block has more facts than the size target allows, keep in this order:\n1. Shipped outcomes (versions released, PRs merged) \u2014 these are permanent record.\n2. Open work (PRs/issues still pending) \u2014 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\u2192YK 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 \u2014 just the fact.\n- Format: \"[PR/Issue/Version] \u2014 [outcome in \u22648 words]\"\n- Merge related facts from different source blocks if they concern the same topic.\n\nEXAMPLES:\n- \"v1.13.0 shipped \u2014 quality gate + GC fix (7 PRs)\"\n- \"PR #196 merged \u2014 preserve-first-user (supersedes #169)\"\n- \"Bug 1214 fixed \u2014 compress consumed all user messages\"\n- \"Chose compress-as-anchor \u2014 prefix cache benefit over synthetic injection\"\n- \"Constraint: AGENTS.md forbids as any \u2014 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\") \u2014 drop UNLESS the failure is likely to recur and the block is <30 days old.\n- Design rationale details \u2014 keep the decision, drop the \"because\" unless it's a critical constraint.\n- Anything marked [OBSOLETE] or [SUPERSEDED] \u2014 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 \u2248 N \u00D7 40 tokens. If a source block has only one trivial fact, output just the header + one line.";
11
+ //# sourceMappingURL=compression-rules.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compression-rules.d.ts","sourceRoot":"","sources":["../src/compression-rules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,eAAO,MAAM,mBAAmB,64BAKuU,CAAC;AAExW,eAAO,MAAM,qBAAqB,qgKAqC8R,CAAC;AAEjU,eAAO,MAAM,mBAAmB,mxFA6ByN,CAAC;AAE1P,eAAO,MAAM,oBAAoB,0nEAgC4K,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { Config } from "./types.js";
2
+ export declare function defaultConfig(modelContextLimit: number, overrides?: Partial<Config>): Config;
3
+ export declare function validateConfig(config: Config): string[];
4
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEzC,wBAAgB,aAAa,CAC3B,iBAAiB,EAAE,MAAM,EACzB,SAAS,GAAE,OAAO,CAAC,MAAM,CAAM,GAC9B,MAAM,CAsCR;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAgCvD"}
@@ -0,0 +1,14 @@
1
+ import type { CompressionBlock, CompressionState, CoreMessage } from "./types.js";
2
+ export declare function parseBlockIdArg(arg: string): string | null;
3
+ export declare function findBlocksOverlappingMessages(state: CompressionState, messageIds: Set<string>): CompressionBlock[];
4
+ export declare function findActiveAncestor(state: CompressionState, blockId: string): string | null;
5
+ export interface DeactivateOptions {
6
+ deep?: boolean;
7
+ }
8
+ export declare function deactivateBlock(state: CompressionState, blockIds: string[], options?: DeactivateOptions): CompressionState;
9
+ export interface RestoredPreviewResult {
10
+ preview: string;
11
+ restoredCount: number;
12
+ }
13
+ export declare function buildRestoredContentPreview(messages: CoreMessage[], beforeActiveMessageIds: Set<string>, state: CompressionState): RestoredPreviewResult;
14
+ //# sourceMappingURL=decompress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decompress.d.ts","sourceRoot":"","sources":["../src/decompress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAElF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAO1D;AAED,wBAAgB,6BAA6B,CACzC,KAAK,EAAE,gBAAgB,EACvB,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,GACxB,gBAAgB,EAAE,CAUpB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAiB1F;AAED,MAAM,WAAW,iBAAiB;IAC9B,IAAI,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,eAAe,CAC3B,KAAK,EAAE,gBAAgB,EACvB,QAAQ,EAAE,MAAM,EAAE,EAClB,OAAO,GAAE,iBAAsB,GAChC,gBAAgB,CAkClB;AAED,MAAM,WAAW,qBAAqB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,2BAA2B,CACvC,QAAQ,EAAE,WAAW,EAAE,EACvB,sBAAsB,EAAE,GAAG,CAAC,MAAM,CAAC,EACnC,KAAK,EAAE,gBAAgB,GACxB,qBAAqB,CA8BvB"}
@@ -0,0 +1,10 @@
1
+ import type { CoreMessage } from "../types.js";
2
+ import type { MessageFiltersConfig } from "./types.js";
3
+ export interface ApplyResult {
4
+ messages: CoreMessage[];
5
+ partsFiltered: number;
6
+ partsDropped: number;
7
+ partsModified: number;
8
+ }
9
+ export declare function applyMessageFilters(messages: CoreMessage[], config: MessageFiltersConfig | undefined): ApplyResult;
10
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/filter/apply.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAsC,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAE3F,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,mBAAmB,CAC/B,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,oBAAoB,GAAG,SAAS,GACzC,WAAW,CAuFb"}
@@ -0,0 +1,5 @@
1
+ export * from "./types.js";
2
+ export { registerMessageFilter, getMessageFilter, listMessageFilters, clearMessageFilters } from "./registry.js";
3
+ export { applyMessageFilters } from "./apply.js";
4
+ export type { ApplyResult } from "./apply.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/filter/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjH,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { MessageFilter } from "./types.js";
2
+ export declare function registerMessageFilter(filter: MessageFilter): void;
3
+ export declare function getMessageFilter(name: string): MessageFilter | undefined;
4
+ export declare function listMessageFilters(): MessageFilter[];
5
+ export declare function clearMessageFilters(): void;
6
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/filter/registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAIhD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAQjE;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS,CAExE;AAED,wBAAgB,kBAAkB,IAAI,aAAa,EAAE,CAEpD;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}