@dsh-cc/context-crusher 0.6.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.
- package/LICENSE +201 -0
- package/README.i18n.yaml +6 -0
- package/README.md +39 -0
- package/README.zh.md +39 -0
- package/lib/config.d.ts +33 -0
- package/lib/config.d.ts.map +1 -0
- package/lib/config.js +78 -0
- package/lib/config.js.map +1 -0
- package/lib/index.d.ts +68 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +244 -0
- package/lib/index.js.map +1 -0
- package/lib/invariant.d.ts +16 -0
- package/lib/invariant.d.ts.map +1 -0
- package/lib/invariant.js +24 -0
- package/lib/invariant.js.map +1 -0
- package/lib/ledger.d.ts +18 -0
- package/lib/ledger.d.ts.map +1 -0
- package/lib/ledger.js +29 -0
- package/lib/ledger.js.map +1 -0
- package/lib/marker.d.ts +30 -0
- package/lib/marker.d.ts.map +1 -0
- package/lib/marker.js +33 -0
- package/lib/marker.js.map +1 -0
- package/lib/router.d.ts +24 -0
- package/lib/router.d.ts.map +1 -0
- package/lib/router.js +132 -0
- package/lib/router.js.map +1 -0
- package/lib/settings.d.ts +23 -0
- package/lib/settings.d.ts.map +1 -0
- package/lib/settings.js +30 -0
- package/lib/settings.js.map +1 -0
- package/lib/store.d.ts +52 -0
- package/lib/store.d.ts.map +1 -0
- package/lib/store.js +130 -0
- package/lib/store.js.map +1 -0
- package/lib/types.d.ts +57 -0
- package/lib/types.d.ts.map +1 -0
- package/lib/types.js +6 -0
- package/lib/types.js.map +1 -0
- package/package.json +67 -0
package/lib/index.js
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CCR (Compress-Cache-Retrieve) reversible tool-output compression.
|
|
3
|
+
*
|
|
4
|
+
* A `tools/post-execute` listener (registered `{ prepend: true }`, composed
|
|
5
|
+
* post-`next()`) compresses large grep/log-shaped tool results, persists the
|
|
6
|
+
* original in a content-addressed store, and appends a `ccr://<hash>` marker;
|
|
7
|
+
* the `context_retrieve({ hash })` agent tool restores the original. Defaults
|
|
8
|
+
* are `enabled: false, mode: 'dry-run'` — nothing replaces until opted in.
|
|
9
|
+
*
|
|
10
|
+
* Fail-closed everywhere: a missing `dshHomePath` force-disables the crusher,
|
|
11
|
+
* and every store/ledger/sweep I/O error degrades to passthrough — a throw in
|
|
12
|
+
* this waterfall would turn the user's tool result into an error (data loss).
|
|
13
|
+
*
|
|
14
|
+
* @module @dsh-cc/context-crusher
|
|
15
|
+
*/
|
|
16
|
+
import { Service } from '@deepseek-ai/cordis';
|
|
17
|
+
import { defineTool } from '@dsh-cc/tools';
|
|
18
|
+
import { getSessionCwd } from '@dsh-cc/session-cwd';
|
|
19
|
+
import { resolveConfig, overlaySettings, Config } from "./config.js";
|
|
20
|
+
import { registerSettings } from "./settings.js";
|
|
21
|
+
import { route } from "./router.js";
|
|
22
|
+
import { CrusherStore, shortHash } from "./store.js";
|
|
23
|
+
import { SavingsLedger } from "./ledger.js";
|
|
24
|
+
import { buildMarker, CCR_HASH_RE } from "./marker.js";
|
|
25
|
+
export { resolveConfig, overlaySettings, DEFAULTS, DEFAULT_PROTECTED_TOOLS, Config } from "./config.js";
|
|
26
|
+
export { SETTINGS_NAMESPACE } from "./settings.js";
|
|
27
|
+
export { route } from "./router.js";
|
|
28
|
+
export { CrusherStore, shortHash, STORE_MAX_ENTRIES, STORE_TTL_MS } from "./store.js";
|
|
29
|
+
export { SavingsLedger } from "./ledger.js";
|
|
30
|
+
export { buildMarker, parseMarker } from "./marker.js";
|
|
31
|
+
export const RETRIEVE_TOOL_NAME = 'context_retrieve';
|
|
32
|
+
/** The pinned marker contract, mirrored verbatim in the retrieve tool's description. */
|
|
33
|
+
export const RETRIEVE_TOOL_DESCRIPTION = 'Retrieve the ORIGINAL text of a tool result that dsh-cc compressed. When a tool result '
|
|
34
|
+
+ 'ends with a marker like `[dsh-cc compressed 41230→6180 tokens. Original: ccr://a1b2c3d4e5f60708]`, '
|
|
35
|
+
+ 'call this tool with the 16-hex hash from the `ccr://` handle to read the full uncompressed '
|
|
36
|
+
+ 'output. Use it whenever the compressed form is not enough (you need a specific elided line, '
|
|
37
|
+
+ 'a full stack trace, or untruncated rows). The original is retained for 1 hour.';
|
|
38
|
+
/**
|
|
39
|
+
* Read a dsh-home path without throwing when the boot-provided `dshHomePath`
|
|
40
|
+
* resolver is absent — cordis throws on the property access itself (not a
|
|
41
|
+
* plain `undefined`), so the read must be guarded.
|
|
42
|
+
*/
|
|
43
|
+
function dshHomeFn(ctx) {
|
|
44
|
+
try {
|
|
45
|
+
return ctx.dshHomePath;
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Join text blocks; returns undefined when any block is non-text. */
|
|
52
|
+
function allTextBlocks(blocks) {
|
|
53
|
+
const parts = [];
|
|
54
|
+
for (const block of blocks) {
|
|
55
|
+
if (block.type !== 'text')
|
|
56
|
+
return undefined;
|
|
57
|
+
parts.push(block.text);
|
|
58
|
+
}
|
|
59
|
+
return parts.join('\n');
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The context-crusher service: registers the post-execute tripwire listener
|
|
63
|
+
* and the `context_retrieve` agent tool.
|
|
64
|
+
*/
|
|
65
|
+
export class ContextCrusher extends Service {
|
|
66
|
+
static inject = ['tokenMeter'];
|
|
67
|
+
static Config = Config;
|
|
68
|
+
/** Resolved config-layer defaults; the settings overlay re-reads per use. */
|
|
69
|
+
base;
|
|
70
|
+
readSettings;
|
|
71
|
+
store;
|
|
72
|
+
ledger;
|
|
73
|
+
constructor(ctx, config = {}) {
|
|
74
|
+
super(ctx, 'contextCrusher');
|
|
75
|
+
this.base = resolveConfig(config);
|
|
76
|
+
this.readSettings = registerSettings(ctx, this.base);
|
|
77
|
+
const home = dshHomeFn(ctx);
|
|
78
|
+
if (home === undefined) {
|
|
79
|
+
// D6 fail-closed: without a durable home the store/ledger cannot exist.
|
|
80
|
+
ctx.logger.warn('context-crusher: no dshHomePath on the host context; force-disabled');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
this.store = new CrusherStore(home('ccr'));
|
|
84
|
+
this.ledger = new SavingsLedger(home('ccr', 'savings.jsonl'));
|
|
85
|
+
}
|
|
86
|
+
this.registerListener();
|
|
87
|
+
this.registerRetrieveTool();
|
|
88
|
+
}
|
|
89
|
+
/** Effective configuration for one use: config defaults overlaid by live settings. */
|
|
90
|
+
effectiveConfig() {
|
|
91
|
+
return overlaySettings(this.base, this.readSettings());
|
|
92
|
+
}
|
|
93
|
+
registerListener() {
|
|
94
|
+
this.ctx.on('tools/post-execute', async (exec, result, next) => {
|
|
95
|
+
const d = await next();
|
|
96
|
+
if (d.kind !== 'accept')
|
|
97
|
+
return d;
|
|
98
|
+
try {
|
|
99
|
+
return await this.crush(exec, result, d);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
// D6: degrade to passthrough. NEVER throw into the waterfall — a throw
|
|
103
|
+
// turns the user's tool result into an error result (data loss).
|
|
104
|
+
this.ctx.logger.warn(`context-crusher: degraded to passthrough: ${String(error)}`);
|
|
105
|
+
return d;
|
|
106
|
+
}
|
|
107
|
+
}, { prepend: true });
|
|
108
|
+
}
|
|
109
|
+
async crush(exec, result, d) {
|
|
110
|
+
const cfg = this.effectiveConfig();
|
|
111
|
+
const sessionId = exec.agent === undefined ? '' : String(exec.agent.session.id);
|
|
112
|
+
const ledgerRow = async (row) => {
|
|
113
|
+
await this.ledger?.append({
|
|
114
|
+
ts: new Date().toISOString(),
|
|
115
|
+
sessionId,
|
|
116
|
+
tool: exec.name,
|
|
117
|
+
...row,
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
if (!cfg.enabled || this.store === undefined)
|
|
121
|
+
return d;
|
|
122
|
+
// D7: protected tools REPLACE the default list when explicitly set.
|
|
123
|
+
if (cfg.protectedTools.includes(exec.name))
|
|
124
|
+
return d;
|
|
125
|
+
// Never crush the retrieval tool's own output: it would recurse (the
|
|
126
|
+
// retrieved original is large and grep-shaped) and destroy the round-trip.
|
|
127
|
+
if (exec.name === RETRIEVE_TOOL_NAME)
|
|
128
|
+
return d;
|
|
129
|
+
// A value-only accept decision carries no content to crush.
|
|
130
|
+
if (Object.hasOwn(d, 'value'))
|
|
131
|
+
return d;
|
|
132
|
+
const blocks = d.content ?? result.content;
|
|
133
|
+
if (blocks === undefined)
|
|
134
|
+
return d;
|
|
135
|
+
const originalText = allTextBlocks(blocks);
|
|
136
|
+
if (originalText === undefined)
|
|
137
|
+
return d; // D2: any non-text block → skip
|
|
138
|
+
const tokensBefore = this.estimate(originalText);
|
|
139
|
+
if (result.isError && tokensBefore < 2 * cfg.minBytes)
|
|
140
|
+
return d;
|
|
141
|
+
if (tokensBefore < cfg.minBytes)
|
|
142
|
+
return d;
|
|
143
|
+
const candidate = route(originalText);
|
|
144
|
+
if (candidate === null)
|
|
145
|
+
return d;
|
|
146
|
+
const compressedBody = candidate.text;
|
|
147
|
+
const tokensAfter = this.estimate(compressedBody);
|
|
148
|
+
const savings = 1 - tokensAfter / tokensBefore;
|
|
149
|
+
if (savings < cfg.minSavingsRatio)
|
|
150
|
+
return d;
|
|
151
|
+
if (cfg.mode !== 'on') {
|
|
152
|
+
// Dry-run: measure only; the committed result stays original.
|
|
153
|
+
await ledgerRow({ kind: candidate.kind, charsBefore: originalText.length, charsAfter: compressedBody.length, tokensBefore, tokensAfter, applied: false });
|
|
154
|
+
return d;
|
|
155
|
+
}
|
|
156
|
+
const projectKey = this.projectKey(exec);
|
|
157
|
+
if (projectKey === undefined)
|
|
158
|
+
return d;
|
|
159
|
+
const hash = await this.store.put(projectKey, originalText);
|
|
160
|
+
const replacement = {
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: `${compressedBody}\n${buildMarker(tokensBefore, tokensAfter, hash)}`,
|
|
163
|
+
};
|
|
164
|
+
await ledgerRow({ kind: candidate.kind, charsBefore: originalText.length, charsAfter: replacement.text.length, tokensBefore, tokensAfter, applied: true, hash });
|
|
165
|
+
// D11: spread the downstream decision so its additionalContexts survive.
|
|
166
|
+
// Fire-and-forget sweep AFTER the decision is formed (D6).
|
|
167
|
+
void this.store.sweep();
|
|
168
|
+
// D11: spread the downstream decision so its additionalContexts survive.
|
|
169
|
+
// `content` is fresh (D2); the downstream decision cannot be value-carrying
|
|
170
|
+
// here (checked above), so this is the content-carrying accept variant.
|
|
171
|
+
return { kind: 'accept', ...('additionalContexts' in d && d.additionalContexts !== undefined ? { additionalContexts: d.additionalContexts } : {}), content: [replacement] };
|
|
172
|
+
}
|
|
173
|
+
/** Token-based sizing (D3): the same estimator gates, markers, and ledger rows. */
|
|
174
|
+
estimate(text) {
|
|
175
|
+
return this.ctx.tokenMeter.estimateMessage({
|
|
176
|
+
role: 'tool',
|
|
177
|
+
content: [{ type: 'text', text }],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** `sha256(sessionCwd)` first 16 hex — the store's project bucket. */
|
|
181
|
+
projectKey(exec) {
|
|
182
|
+
if (exec.agent === undefined)
|
|
183
|
+
return undefined;
|
|
184
|
+
try {
|
|
185
|
+
// Worktree-path divergence from the TUI project-root convention is
|
|
186
|
+
// accepted here: the store is self-consistent (writer and retriever
|
|
187
|
+
// both key off the session cwd).
|
|
188
|
+
return shortHash(getSessionCwd(exec.agent));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Typed retrieval through the store; fails closed (used by the tool body). */
|
|
195
|
+
async retrieve(exec, hash) {
|
|
196
|
+
if (this.store === undefined)
|
|
197
|
+
return { ok: false, error: 'unavailable' };
|
|
198
|
+
if (!CCR_HASH_RE.test(hash))
|
|
199
|
+
return { ok: false, error: 'invalid_hash' };
|
|
200
|
+
const agent = exec.agent;
|
|
201
|
+
if (agent === undefined)
|
|
202
|
+
return { ok: false, error: 'unavailable' };
|
|
203
|
+
let projectKey;
|
|
204
|
+
try {
|
|
205
|
+
projectKey = shortHash(getSessionCwd(agent));
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return { ok: false, error: 'unavailable' };
|
|
209
|
+
}
|
|
210
|
+
return await this.store.get(projectKey, hash);
|
|
211
|
+
}
|
|
212
|
+
registerRetrieveTool() {
|
|
213
|
+
const tools = this.ctx.get('tools');
|
|
214
|
+
if (tools === undefined)
|
|
215
|
+
return;
|
|
216
|
+
tools.register(defineRetrieveTool(this));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function defineRetrieveTool(crusher) {
|
|
220
|
+
return defineTool({
|
|
221
|
+
name: RETRIEVE_TOOL_NAME,
|
|
222
|
+
description: RETRIEVE_TOOL_DESCRIPTION,
|
|
223
|
+
parameters: {
|
|
224
|
+
hash: {
|
|
225
|
+
type: 'string',
|
|
226
|
+
required: true,
|
|
227
|
+
description: 'The 16-hex content handle from a `ccr://<hash>` marker.',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
output: {
|
|
231
|
+
schema: { type: 'object', additionalProperties: false, properties: { text: { type: 'string', required: true } } },
|
|
232
|
+
render: (_args, value) => [{ type: 'text', text: value.text }],
|
|
233
|
+
},
|
|
234
|
+
async execute(args, exec) {
|
|
235
|
+
const outcome = await crusher.retrieve(exec, args.hash);
|
|
236
|
+
if (!outcome.ok) {
|
|
237
|
+
throw new Error(`context_retrieve failed: ${outcome.error}`);
|
|
238
|
+
}
|
|
239
|
+
return { text: outcome.text };
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
export default ContextCrusher;
|
|
244
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@dsh-cc/context-crusher`.
|
|
3
|
+
* @module @dsh-cc/context-crusher/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "context-crusher-invariant";
|
|
8
|
+
/** Services required before the companion can register. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariant.d.ts","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAKlD,oCAAoC;AACpC,eAAO,MAAM,IAAI,8BAA8B,CAAA;AAC/C,2DAA2D;AAC3D,eAAO,MAAM,MAAM,UAAiB,CAAA;AAUpC;;;;GAIG;AACH,eAAO,MAAM,KAAK,GAAI,KAAK,OAAO,KAAG,OAAO,CAAC,MAAM,IAAI,CACU,CAAA"}
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@dsh-cc/context-crusher`.
|
|
3
|
+
* @module @dsh-cc/context-crusher/invariant
|
|
4
|
+
*/
|
|
5
|
+
const PACKAGE_NAME = '@dsh-cc/context-crusher';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export const name = 'context-crusher-invariant';
|
|
8
|
+
/** Services required before the companion can register. */
|
|
9
|
+
export const inject = ['invariants'];
|
|
10
|
+
/**
|
|
11
|
+
* No runtime invariant: the crusher's safety contract is enforced at the
|
|
12
|
+
* source — post-`next()` composition never converts a non-accept decision,
|
|
13
|
+
* the D2/D11 block shape is pinned by composition tests, and every I/O path
|
|
14
|
+
* fails closed to passthrough rather than relying on a cross-event check.
|
|
15
|
+
*/
|
|
16
|
+
const install = () => { };
|
|
17
|
+
/**
|
|
18
|
+
* Register this package's invariant companion.
|
|
19
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
20
|
+
* @returns the installed registration's disposer after setup succeeds.
|
|
21
|
+
*/
|
|
22
|
+
export const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
23
|
+
/* jscpd:ignore-end */
|
|
24
|
+
//# sourceMappingURL=invariant.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invariant.js","sourceRoot":"","sources":["../src/invariant.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH,MAAM,YAAY,GAAG,yBAAyB,CAAA;AAE9C,oCAAoC;AACpC,MAAM,CAAC,MAAM,IAAI,GAAG,2BAA2B,CAAA;AAC/C,2DAA2D;AAC3D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,YAAY,CAAC,CAAA;AAEpC;;;;;GAKG;AACH,MAAM,OAAO,GAAuB,GAAG,EAAE,GAAE,CAAC,CAAA;AAE5C;;;;GAIG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAY,EAAuB,EAAE,CACzD,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AACjE,sBAAsB"}
|
package/lib/ledger.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only savings ledger (`<dshHome>/ccr/savings.jsonl`).
|
|
3
|
+
*
|
|
4
|
+
* One `appendFile` per row (rows are small, appends are atomic enough for
|
|
5
|
+
* single-process use). sessionId lives ONLY here — never in a store path.
|
|
6
|
+
* All I/O errors are swallowed: the ledger is observability, and a failed row
|
|
7
|
+
* must never degrade a tool result.
|
|
8
|
+
*
|
|
9
|
+
* @module @dsh-cc/context-crusher/ledger
|
|
10
|
+
*/
|
|
11
|
+
import type { LedgerRow } from './types.ts';
|
|
12
|
+
export declare class SavingsLedger {
|
|
13
|
+
private readonly filePath;
|
|
14
|
+
constructor(filePath: string);
|
|
15
|
+
/** Append one row; never throws. */
|
|
16
|
+
append(row: LedgerRow): Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=ledger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.d.ts","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,qBAAa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,MAAM;IAE7C,oCAAoC;IAC9B,MAAM,CAAC,GAAG,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;CAQ5C"}
|
package/lib/ledger.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Append-only savings ledger (`<dshHome>/ccr/savings.jsonl`).
|
|
3
|
+
*
|
|
4
|
+
* One `appendFile` per row (rows are small, appends are atomic enough for
|
|
5
|
+
* single-process use). sessionId lives ONLY here — never in a store path.
|
|
6
|
+
* All I/O errors are swallowed: the ledger is observability, and a failed row
|
|
7
|
+
* must never degrade a tool result.
|
|
8
|
+
*
|
|
9
|
+
* @module @dsh-cc/context-crusher/ledger
|
|
10
|
+
*/
|
|
11
|
+
import { appendFile, mkdir } from 'node:fs/promises';
|
|
12
|
+
import { dirname } from 'node:path';
|
|
13
|
+
export class SavingsLedger {
|
|
14
|
+
filePath;
|
|
15
|
+
constructor(filePath) {
|
|
16
|
+
this.filePath = filePath;
|
|
17
|
+
}
|
|
18
|
+
/** Append one row; never throws. */
|
|
19
|
+
async append(row) {
|
|
20
|
+
try {
|
|
21
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
22
|
+
await appendFile(this.filePath, `${JSON.stringify(row)}\n`, 'utf8');
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Best-effort observability; never surface into the tool waterfall.
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=ledger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ledger.js","sourceRoot":"","sources":["../src/ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAGnC,MAAM,OAAO,aAAa;IACK;IAA7B,YAA6B,QAAgB;QAAhB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;IAEjD,oCAAoC;IACpC,KAAK,CAAC,MAAM,CAAC,GAAc;QACzB,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACxD,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACrE,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;IACH,CAAC;CACF"}
|
package/lib/marker.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker contract for crushed tool results.
|
|
3
|
+
*
|
|
4
|
+
* PINNED CONTRACT: the marker literal is appended to every compressed tool
|
|
5
|
+
* result and the `context_retrieve` tool description references the same
|
|
6
|
+
* `ccr://<hash>` spelling. A test pins the wording ↔ description pairing.
|
|
7
|
+
*
|
|
8
|
+
* @module @dsh-cc/context-crusher/marker
|
|
9
|
+
*/
|
|
10
|
+
/** Extracts a `ccr://<hash16>` handle from the marker. */
|
|
11
|
+
export declare const CCR_HASH_RE: RegExp;
|
|
12
|
+
/**
|
|
13
|
+
* Build the provenance marker appended to compressed text.
|
|
14
|
+
* @param tokensBefore - tokenMeter token estimate of the original.
|
|
15
|
+
* @param tokensAfter - tokenMeter token estimate of the compressed form (marker excluded).
|
|
16
|
+
* @param hash - the 16-hex content handle of the stored original.
|
|
17
|
+
* @returns e.g. `[dsh-cc compressed 41230→6180 tokens. Original: ccr://a1b2c3d4e5f60708]`
|
|
18
|
+
*/
|
|
19
|
+
export declare function buildMarker(tokensBefore: number, tokensAfter: number, hash: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Parse a marker back into its parts.
|
|
22
|
+
* @param line - the marker text.
|
|
23
|
+
* @returns `{tokensBefore, tokensAfter, hash}`, or `null` when not a marker.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parseMarker(line: string): {
|
|
26
|
+
tokensBefore: number;
|
|
27
|
+
tokensAfter: number;
|
|
28
|
+
hash: string;
|
|
29
|
+
} | null;
|
|
30
|
+
//# sourceMappingURL=marker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.d.ts","sourceRoot":"","sources":["../src/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0DAA0D;AAC1D,eAAO,MAAM,WAAW,QAAmB,CAAA;AAE3C;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3F;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAI5G"}
|
package/lib/marker.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Marker contract for crushed tool results.
|
|
3
|
+
*
|
|
4
|
+
* PINNED CONTRACT: the marker literal is appended to every compressed tool
|
|
5
|
+
* result and the `context_retrieve` tool description references the same
|
|
6
|
+
* `ccr://<hash>` spelling. A test pins the wording ↔ description pairing.
|
|
7
|
+
*
|
|
8
|
+
* @module @dsh-cc/context-crusher/marker
|
|
9
|
+
*/
|
|
10
|
+
/** Extracts a `ccr://<hash16>` handle from the marker. */
|
|
11
|
+
export const CCR_HASH_RE = /^[0-9a-f]{16}$/;
|
|
12
|
+
/**
|
|
13
|
+
* Build the provenance marker appended to compressed text.
|
|
14
|
+
* @param tokensBefore - tokenMeter token estimate of the original.
|
|
15
|
+
* @param tokensAfter - tokenMeter token estimate of the compressed form (marker excluded).
|
|
16
|
+
* @param hash - the 16-hex content handle of the stored original.
|
|
17
|
+
* @returns e.g. `[dsh-cc compressed 41230→6180 tokens. Original: ccr://a1b2c3d4e5f60708]`
|
|
18
|
+
*/
|
|
19
|
+
export function buildMarker(tokensBefore, tokensAfter, hash) {
|
|
20
|
+
return `[dsh-cc compressed ${tokensBefore}→${tokensAfter} tokens. Original: ccr://${hash}]`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parse a marker back into its parts.
|
|
24
|
+
* @param line - the marker text.
|
|
25
|
+
* @returns `{tokensBefore, tokensAfter, hash}`, or `null` when not a marker.
|
|
26
|
+
*/
|
|
27
|
+
export function parseMarker(line) {
|
|
28
|
+
const m = /^\[dsh-cc compressed (\d+)→(\d+) tokens\. Original: ccr:\/\/([0-9a-f]{16})\]$/.exec(line);
|
|
29
|
+
if (m === null)
|
|
30
|
+
return null;
|
|
31
|
+
return { tokensBefore: Number(m[1]), tokensAfter: Number(m[2]), hash: m[3] ?? '' };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=marker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"marker.js","sourceRoot":"","sources":["../src/marker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,0DAA0D;AAC1D,MAAM,CAAC,MAAM,WAAW,GAAG,gBAAgB,CAAA;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CAAC,YAAoB,EAAE,WAAmB,EAAE,IAAY;IACjF,OAAO,sBAAsB,YAAY,IAAI,WAAW,4BAA4B,IAAI,GAAG,CAAA;AAC7F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,CAAC,GAAG,+EAA+E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IACpG,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAA;AACpF,CAAC"}
|
package/lib/router.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, deterministic content routing and compression (no I/O).
|
|
3
|
+
*
|
|
4
|
+
* Two Phase-0 compressors, both conservative: if the input does not cleanly
|
|
5
|
+
* match the format's structure the router returns `null` and the listener
|
|
6
|
+
* passes the result through untouched. Every line carrying an error or match
|
|
7
|
+
* signal survives; only repetitive context is dropped.
|
|
8
|
+
*
|
|
9
|
+
* @module @dsh-cc/context-crusher/router
|
|
10
|
+
*/
|
|
11
|
+
export type CompressedKind = 'search' | 'log';
|
|
12
|
+
/** A safe compression, or `null` = no safe saving → passthrough. */
|
|
13
|
+
export interface CompressedCandidate {
|
|
14
|
+
readonly text: string;
|
|
15
|
+
readonly kind: CompressedKind;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Route one tool-result text to a compressor.
|
|
19
|
+
* @param text - the joined tool-result text.
|
|
20
|
+
* @returns a compressed candidate, or `null` when the input does not cleanly
|
|
21
|
+
* match a known format or would not compress.
|
|
22
|
+
*/
|
|
23
|
+
export declare function route(text: string): CompressedCandidate | null;
|
|
24
|
+
//# sourceMappingURL=router.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE7C,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAC9B;AA8GD;;;;;GAKG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,mBAAmB,GAAG,IAAI,CAa9D"}
|
package/lib/router.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure, deterministic content routing and compression (no I/O).
|
|
3
|
+
*
|
|
4
|
+
* Two Phase-0 compressors, both conservative: if the input does not cleanly
|
|
5
|
+
* match the format's structure the router returns `null` and the listener
|
|
6
|
+
* passes the result through untouched. Every line carrying an error or match
|
|
7
|
+
* signal survives; only repetitive context is dropped.
|
|
8
|
+
*
|
|
9
|
+
* @module @dsh-cc/context-crusher/router
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Minimum structural evidence before a compressor claims an input: fewer
|
|
13
|
+
* candidate lines than this is not a confident format match.
|
|
14
|
+
*/
|
|
15
|
+
const MIN_EVIDENCE_LINES = 3;
|
|
16
|
+
/** Share of non-empty lines that must match a format for a confident claim. */
|
|
17
|
+
const MIN_EVIDENCE_RATIO = 0.6;
|
|
18
|
+
/** Log-shaped output needs weaker evidence than grep rows (banners are sparse). */
|
|
19
|
+
const MIN_LOG_RATIO = 0.3;
|
|
20
|
+
/** Ripgrep/grep `file:line:content` row: path without spaces or colons, digits. */
|
|
21
|
+
const SEARCH_ROW = /^(\.{0,2}\/?[\w.@\/-]+?):(\d+):(.*)$/;
|
|
22
|
+
/** Non-search rows that would make a search claim unsafe (paths contain `:`? no). */
|
|
23
|
+
function isSearchRow(line) {
|
|
24
|
+
return SEARCH_ROW.test(line);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Compress grep-style output: cluster consecutive rows per file, emit the
|
|
28
|
+
* path once, then `line:content` per row. Every matching line and every file
|
|
29
|
+
* path survives; only the repeated path prefixes are dropped.
|
|
30
|
+
*/
|
|
31
|
+
function compressSearch(text) {
|
|
32
|
+
const clusters = new Map(); // file → `lineNo:content` rows, first-appearance order
|
|
33
|
+
const loose = [];
|
|
34
|
+
for (const line of text.split('\n')) {
|
|
35
|
+
const match = SEARCH_ROW.exec(line);
|
|
36
|
+
if (match === null) {
|
|
37
|
+
loose.push(line);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const [file, lineNo, content] = [match[1] ?? '', match[2] ?? '', match[3] ?? ''];
|
|
41
|
+
const rows = clusters.get(file) ?? [];
|
|
42
|
+
rows.push(`${lineNo}:${content}`);
|
|
43
|
+
clusters.set(file, rows);
|
|
44
|
+
}
|
|
45
|
+
const out = [...loose];
|
|
46
|
+
for (const [file, rows] of clusters) {
|
|
47
|
+
out.push(`== ${file} ==`, ...rows);
|
|
48
|
+
}
|
|
49
|
+
return out.join('\n');
|
|
50
|
+
}
|
|
51
|
+
/** Lines that carry an error/failure signal and are always preserved. */
|
|
52
|
+
const LOG_SIGNAL = [
|
|
53
|
+
/\b(ERROR|FATAL|ERR!|FAIL(?:ED|URE)?|Traceback|panic|exception)\b/i,
|
|
54
|
+
/^\s+at\s/, // JS stack frame
|
|
55
|
+
/^\s+File "/, // Python stack frame
|
|
56
|
+
/^npm ERR!/,
|
|
57
|
+
/^=+ .* =+$/, // pytest banner
|
|
58
|
+
/^\s*(?:Failed|Error):/,
|
|
59
|
+
];
|
|
60
|
+
function isLogSignal(line) {
|
|
61
|
+
return LOG_SIGNAL.some((re) => re.test(line));
|
|
62
|
+
}
|
|
63
|
+
/** Lines that identify log-shaped output (timestamps, levels, tool banners). */
|
|
64
|
+
const LOG_MARKER = [
|
|
65
|
+
/^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}/,
|
|
66
|
+
/^\d{2}:\d{2}:\d{2}[.,]?\d*/,
|
|
67
|
+
/\b(WARN|INFO|DEBUG|TRACE)\b/,
|
|
68
|
+
/^npm (WARN|notice)/,
|
|
69
|
+
/^(PASS|OK|PASSED|passed)\b/,
|
|
70
|
+
/^(✓|√|✔)/,
|
|
71
|
+
];
|
|
72
|
+
function isLogMarker(line) {
|
|
73
|
+
return LOG_MARKER.some((re) => re.test(line)) || isLogSignal(line);
|
|
74
|
+
}
|
|
75
|
+
/** Non-signal lines kept at the head of each cluster, for orientation. */
|
|
76
|
+
const KEEP_HEAD = 3;
|
|
77
|
+
/**
|
|
78
|
+
* Compress log output: keep every error/signal line and stack frame, keep the
|
|
79
|
+
* first {@link KEEP_HEAD} lines of each contiguous non-signal cluster, elide
|
|
80
|
+
* the rest with an explicit count so the shape stays honest.
|
|
81
|
+
*/
|
|
82
|
+
function compressLog(text) {
|
|
83
|
+
const lines = text.split('\n');
|
|
84
|
+
const out = [];
|
|
85
|
+
let run = [];
|
|
86
|
+
const flush = () => {
|
|
87
|
+
if (run.length === 0)
|
|
88
|
+
return;
|
|
89
|
+
if (run.length <= KEEP_HEAD) {
|
|
90
|
+
out.push(...run);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
out.push(...run.slice(0, KEEP_HEAD));
|
|
94
|
+
out.push(`[… ${run.length - KEEP_HEAD} lines elided by dsh-cc log compression …]`);
|
|
95
|
+
}
|
|
96
|
+
run = [];
|
|
97
|
+
};
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
if (isLogSignal(line) || line === '') {
|
|
100
|
+
flush();
|
|
101
|
+
out.push(line);
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
run.push(line);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
flush();
|
|
108
|
+
return out.join('\n');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Route one tool-result text to a compressor.
|
|
112
|
+
* @param text - the joined tool-result text.
|
|
113
|
+
* @returns a compressed candidate, or `null` when the input does not cleanly
|
|
114
|
+
* match a known format or would not compress.
|
|
115
|
+
*/
|
|
116
|
+
export function route(text) {
|
|
117
|
+
if (text.length === 0)
|
|
118
|
+
return null;
|
|
119
|
+
const lines = text.split('\n').filter((l) => l.trim().length > 0);
|
|
120
|
+
if (lines.length < MIN_EVIDENCE_LINES)
|
|
121
|
+
return null;
|
|
122
|
+
const searchRows = lines.filter((l) => isSearchRow(l)).length;
|
|
123
|
+
if (searchRows >= MIN_EVIDENCE_LINES && searchRows / lines.length >= MIN_EVIDENCE_RATIO) {
|
|
124
|
+
return { text: compressSearch(text), kind: 'search' };
|
|
125
|
+
}
|
|
126
|
+
const logLines = lines.filter((l) => isLogMarker(l)).length;
|
|
127
|
+
if (logLines >= MIN_EVIDENCE_LINES && logLines / lines.length >= MIN_LOG_RATIO) {
|
|
128
|
+
return { text: compressLog(text), kind: 'log' };
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"router.js","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings namespace registration for the `cc-context-compression` overlay.
|
|
3
|
+
* Model-aliases style: the namespace is registered once at mount; the live
|
|
4
|
+
* scope is re-read on EVERY use so a settings write applies immediately.
|
|
5
|
+
* @module @dsh-cc/context-crusher/settings
|
|
6
|
+
*/
|
|
7
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
8
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
9
|
+
import type { CrusherConfig, ResolvedConfig } from './types.ts';
|
|
10
|
+
/** The settings namespace carrying the live crusher overlay. */
|
|
11
|
+
export declare const SETTINGS_NAMESPACE: SettingsNamespace;
|
|
12
|
+
/** A live settings reader bound at mount; returns undefined without a provider. */
|
|
13
|
+
export type SettingsReader = () => CrusherConfig | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Register the settings namespace and return a per-use reader. Graceful
|
|
16
|
+
* without a settings provider: the reader resolves `undefined` and callers
|
|
17
|
+
* fall back to the config defaults.
|
|
18
|
+
* @param ctx - the plug context.
|
|
19
|
+
* @param base - resolved config-layer defaults (validated once here too).
|
|
20
|
+
* @returns the live scope reader.
|
|
21
|
+
*/
|
|
22
|
+
export declare function registerSettings(ctx: Context, base: ResolvedConfig): SettingsReader;
|
|
23
|
+
//# sourceMappingURL=settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"settings.d.ts","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAoB,MAAM,2BAA2B,CAAA;AAEpF,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE/D,gEAAgE;AAChE,eAAO,MAAM,kBAAkB,EAA+B,iBAAiB,CAAA;AAE/E,mFAAmF;AACnF,MAAM,MAAM,cAAc,GAAG,MAAM,aAAa,GAAG,SAAS,CAAA;AAE5D;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,cAAc,GAAG,cAAc,CAWnF"}
|