dsh-engram 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +283 -27
- package/README.zh.md +184 -16
- package/cordis.patch.yml +6 -0
- package/docs/CONTEXT-GC-GUIDE.zh.md +192 -0
- package/docs/PROPOSAL-agentmemory.zh.md +108 -0
- package/docs/PROPOSAL-beads.zh.md +81 -0
- package/docs/PROPOSAL-contextgc.zh.md +188 -0
- package/docs/PROPOSAL-esrtrigger.zh.md +199 -0
- package/docs/PROPOSAL-hindsight.zh.md +156 -0
- package/lib/api.js +76 -2
- package/lib/capture.js +132 -4
- package/lib/client.js +45 -2
- package/lib/compaction.js +2 -18
- package/lib/context-gc.js +234 -2
- package/lib/goal-capture.js +89 -0
- package/lib/index-block.js +96 -1
- package/lib/index.js +233 -5
- package/lib/redact.js +48 -0
- package/lib/session-cjk.js +159 -0
- package/lib/settings.js +13 -0
- package/lib/status.js +57 -0
- package/lib/store.js +411 -6
- package/lib/todo-sink.js +146 -0
- package/lib/tools.js +98 -40
- package/lib/trigger.js +10 -8
- package/lib/usage.js +147 -5
- package/lib/web-provision.js +430 -0
- package/package.json +13 -2
- package/scripts/dsh-engram.mjs +268 -0
- package/scripts/engram-web-compaction.mjs +7 -0
- package/scripts/prepublish-check.mjs +106 -0
- package/scripts/setup-links.mjs +241 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-engram web-plane auto-wiring for Context GC.
|
|
3
|
+
*
|
|
4
|
+
* Why this file exists: web sessions resolve their `compaction` engine inside
|
|
5
|
+
* the agent preset's isolated realm — the shipped `standard` preset mounts its
|
|
6
|
+
* own `compaction-basic` there — and the profile patch cannot reach into
|
|
7
|
+
* preset files (verified against the harness: `mountPreset` builds
|
|
8
|
+
* `Include.Config` with no patches, and the loader's patch semantics apply to
|
|
9
|
+
* the main composition only). The one sanctioned lever left is editing the
|
|
10
|
+
* preset file itself, so this module performs that edit automatically:
|
|
11
|
+
*
|
|
12
|
+
* - it covers the resolved DEFAULT preset plus EVERY preset on the roster
|
|
13
|
+
* (`list()` — shipped roots and the user root alike), so a session composed
|
|
14
|
+
* from any preset gets Context GC, not just the default one;
|
|
15
|
+
* - per preset it touches ONLY compositions that are still the untouched
|
|
16
|
+
* stock layout (`compaction-basic` present inside the `compaction` group);
|
|
17
|
+
* a preset with a custom compaction group — or none at all (e.g. the
|
|
18
|
+
* shipped `minimal` preset) — is left alone and only logged;
|
|
19
|
+
* - a `.engram.bak` copy of the original is taken before any write;
|
|
20
|
+
* - every step is validated; an already-wired preset is never rewritten, and
|
|
21
|
+
* a failed edit restores the original.
|
|
22
|
+
*
|
|
23
|
+
* The swap is textual and deliberately tiny (one stock row → the engram row),
|
|
24
|
+
* so it survives harness upgrades that only touch surrounding content; a
|
|
25
|
+
* harness upgrade that changes the `compaction` group's stock shape makes the
|
|
26
|
+
* "stock" status fail (status "custom"/"layout-mismatch") and the provisioner
|
|
27
|
+
* leaves the file alone instead of guessing.
|
|
28
|
+
*
|
|
29
|
+
* Flip `autoWebCompaction:false` to opt out; `revertWebCompaction` restores
|
|
30
|
+
* the stock row on uninstall.
|
|
31
|
+
* @module dsh-engram/web-provision
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
35
|
+
|
|
36
|
+
/** The engine package subpath a preset row mounts dsh-engram's engine under. */
|
|
37
|
+
export const ENGRAM_ROW_NAME = "dsh-engram/compaction";
|
|
38
|
+
|
|
39
|
+
/** The stock row the shipped `standard` preset mounts inside `compaction`. */
|
|
40
|
+
export const STOCK_COMPACTION_ROW =
|
|
41
|
+
" - id: compaction-basic\n name: '@deepseek-ai/dsh-compaction-basic'";
|
|
42
|
+
|
|
43
|
+
/** Engine config the plugin writes into each wired preset row (settings-driven). */
|
|
44
|
+
export const DEFAULT_ROW_CONFIG = Object.freeze({
|
|
45
|
+
gcReplacesCompaction: true,
|
|
46
|
+
gcNarrative: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The engram row text for a given engine config (indentation must match the
|
|
51
|
+
* stock preset: `- id:` at 4 spaces, keys at 6, sub-keys at 8).
|
|
52
|
+
* @param {{ gcReplacesCompaction?: boolean; gcNarrative?: boolean }} [config]
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
55
|
+
export function engramCompactionRowText(config = {}) {
|
|
56
|
+
const { gcReplacesCompaction = true, gcNarrative = true } = config;
|
|
57
|
+
return [
|
|
58
|
+
" - id: engram-compaction",
|
|
59
|
+
" name: dsh-engram/compaction",
|
|
60
|
+
" config:",
|
|
61
|
+
` gcReplacesCompaction: ${gcReplacesCompaction}`,
|
|
62
|
+
` gcNarrative: ${gcNarrative}`,
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** The replacement row at default settings (back-compat export). */
|
|
67
|
+
export const ENGRAM_COMPACTION_ROW = engramCompactionRowText(DEFAULT_ROW_CONFIG);
|
|
68
|
+
|
|
69
|
+
/** Backup filename suffix written next to the preset composition. */
|
|
70
|
+
export const BACKUP_SUFFIX = ".engram.bak";
|
|
71
|
+
|
|
72
|
+
/** @param {unknown} error */
|
|
73
|
+
function describe(error) {
|
|
74
|
+
return error instanceof Error ? error.message : String(error);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** @param {unknown} error */
|
|
78
|
+
function codeOf(error) {
|
|
79
|
+
return typeof error === "object" && error !== null && "code" in error
|
|
80
|
+
? String(error.code)
|
|
81
|
+
: undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Classify a preset composition's compaction wiring.
|
|
86
|
+
* - `wired` — already carries `dsh-engram/compaction`;
|
|
87
|
+
* - `stock` — untouched shipped layout; safe to auto-wire;
|
|
88
|
+
* - `custom` — anything else; never touch.
|
|
89
|
+
* @param {string} text - the preset composition text.
|
|
90
|
+
* @returns {"wired" | "stock" | "custom"}
|
|
91
|
+
*/
|
|
92
|
+
export function compactionWireStatus(text) {
|
|
93
|
+
if (text.includes(ENGRAM_ROW_NAME)) return "wired";
|
|
94
|
+
if (text.includes(STOCK_COMPACTION_ROW)) return "stock";
|
|
95
|
+
return "custom";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Post-swap sanity invariants the result must satisfy. */
|
|
99
|
+
function invariantsOk(text) {
|
|
100
|
+
return (
|
|
101
|
+
text.includes(ENGRAM_ROW_NAME) &&
|
|
102
|
+
!text.includes("@deepseek-ai/dsh-compaction-basic") &&
|
|
103
|
+
text.includes("command-compact") &&
|
|
104
|
+
text.includes("tool-result-pruner") &&
|
|
105
|
+
text.includes("name: cordis:group") &&
|
|
106
|
+
text.includes("isolate:")
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Locate the engram row block (the two fixed lines plus any deeper-indented
|
|
112
|
+
* `config:` lines) inside a wired composition.
|
|
113
|
+
* @param {string} text
|
|
114
|
+
* @returns {{ start: number; end: number } | null} line indices `[start, end)`
|
|
115
|
+
*/
|
|
116
|
+
function engramRowBlockBounds(text) {
|
|
117
|
+
const lines = text.split("\n");
|
|
118
|
+
const start = lines.findIndex((line) => line === " - id: engram-compaction");
|
|
119
|
+
if (start === -1) return null;
|
|
120
|
+
let end = start + 1;
|
|
121
|
+
while (end < lines.length) {
|
|
122
|
+
const line = lines[end];
|
|
123
|
+
if (line.trim() === "") break;
|
|
124
|
+
if (line.startsWith(" - id:")) break;
|
|
125
|
+
if (line.startsWith(" ")) { end += 1; continue; }
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
return { start, end };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** Does the file already carry exactly the expected row text? */
|
|
132
|
+
function rowMatches(text, rowText) {
|
|
133
|
+
const bounds = engramRowBlockBounds(text);
|
|
134
|
+
if (bounds === null) return false;
|
|
135
|
+
const current = text.split("\n").slice(bounds.start, bounds.end).join("\n");
|
|
136
|
+
return current === rowText;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Produce the wired composition for `rowConfig`. Input may be `stock` (first
|
|
141
|
+
* wiring: the stock row is replaced) or `wired` (refresh: the existing engram
|
|
142
|
+
* row block is updated to the requested config). Returns the new text, or
|
|
143
|
+
* `null` when the input is not something we may rewrite. The caller can detect
|
|
144
|
+
* a no-op refresh via `next === text`.
|
|
145
|
+
* @param {string} text - the preset composition text.
|
|
146
|
+
* @param {{ gcReplacesCompaction?: boolean; gcNarrative?: boolean }} [rowConfig]
|
|
147
|
+
* @returns {string | null}
|
|
148
|
+
*/
|
|
149
|
+
export function swapCompactionRow(text, rowConfig = {}) {
|
|
150
|
+
const rowText = engramCompactionRowText(rowConfig);
|
|
151
|
+
const status = compactionWireStatus(text);
|
|
152
|
+
if (status === "stock") {
|
|
153
|
+
if (text.split(STOCK_COMPACTION_ROW).length - 1 !== 1) return null;
|
|
154
|
+
const next = text.replace(STOCK_COMPACTION_ROW, rowText);
|
|
155
|
+
return invariantsOk(next) ? next : null;
|
|
156
|
+
}
|
|
157
|
+
if (status === "wired") {
|
|
158
|
+
if (rowMatches(text, rowText)) return text; // already exactly this config
|
|
159
|
+
const bounds = engramRowBlockBounds(text);
|
|
160
|
+
if (bounds === null) return null;
|
|
161
|
+
const lines = text.split("\n");
|
|
162
|
+
const next = [
|
|
163
|
+
...lines.slice(0, bounds.start),
|
|
164
|
+
rowText,
|
|
165
|
+
...lines.slice(bounds.end),
|
|
166
|
+
].join("\n");
|
|
167
|
+
return invariantsOk(next) ? next : null;
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Reverse {@link swapCompactionRow}: restore the stock `compaction-basic` row
|
|
174
|
+
* from a wired composition (any engram row block — engine config included).
|
|
175
|
+
* `null` when the input is not a composition we wired.
|
|
176
|
+
* @param {string} text - the preset composition text.
|
|
177
|
+
* @returns {string | null}
|
|
178
|
+
*/
|
|
179
|
+
export function restoreCompactionRow(text) {
|
|
180
|
+
if (compactionWireStatus(text) !== "wired") return null;
|
|
181
|
+
const bounds = engramRowBlockBounds(text);
|
|
182
|
+
if (bounds === null) return null;
|
|
183
|
+
const lines = text.split("\n");
|
|
184
|
+
const next = [
|
|
185
|
+
...lines.slice(0, bounds.start),
|
|
186
|
+
STOCK_COMPACTION_ROW,
|
|
187
|
+
...lines.slice(bounds.end),
|
|
188
|
+
].join("\n");
|
|
189
|
+
return compactionWireStatus(next) === "stock" ? next : null;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Collect the presets to consider — the resolved DEFAULT plus every preset on
|
|
194
|
+
* the roster (`service.list()` when present). Deduped by path; broken presets
|
|
195
|
+
* are skipped with a log line. Never throws.
|
|
196
|
+
* @returns {Promise<Array<{ id: string; path: string }>>}
|
|
197
|
+
*/
|
|
198
|
+
async function collectPresetTargets(service, log) {
|
|
199
|
+
const targets = [];
|
|
200
|
+
const seen = new Set();
|
|
201
|
+
const push = (preset) => {
|
|
202
|
+
if (preset === undefined || preset === null) return;
|
|
203
|
+
if (preset.broken !== undefined && preset.broken !== null) {
|
|
204
|
+
log(
|
|
205
|
+
`engram web-provision: preset "${preset.id}" is broken (${preset.broken}) — leaving it alone`,
|
|
206
|
+
);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (seen.has(preset.path)) return;
|
|
210
|
+
seen.add(preset.path);
|
|
211
|
+
targets.push(preset);
|
|
212
|
+
};
|
|
213
|
+
try {
|
|
214
|
+
push(await service.resolve());
|
|
215
|
+
} catch (error) {
|
|
216
|
+
log(
|
|
217
|
+
`engram web-provision: default agent preset unavailable (${describe(error)}) — continuing with the roster`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (typeof service.list === "function") {
|
|
221
|
+
try {
|
|
222
|
+
for (const preset of await service.list()) push(preset);
|
|
223
|
+
} catch (error) {
|
|
224
|
+
log(
|
|
225
|
+
`engram web-provision: preset roster unavailable (${describe(error)}) — using the default only`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return targets;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Automatically wire Context GC into the web plane's agent presets: the
|
|
234
|
+
* default preset plus every roster preset (shipped and user roots alike).
|
|
235
|
+
* Only stock-layout presets are touched; a custom layout is skipped. A preset
|
|
236
|
+
* already wired with a DIFFERENT engine config is refreshed to `rowConfig`,
|
|
237
|
+
* so the settings knobs (`gcReplacesCompaction` / `gcNarrative`) keep
|
|
238
|
+
* governing the web plane across boots.
|
|
239
|
+
*
|
|
240
|
+
* Adapters (all optional, injected for tests):
|
|
241
|
+
* - `readText(preset)` / `writeText(preset, text)` — default: `node:fs`
|
|
242
|
+
* reads/writes `preset.path`;
|
|
243
|
+
* - `writeBackup(path, text)` — default: create-only (`wx`) write;
|
|
244
|
+
* - `log(line)` — default: no-op.
|
|
245
|
+
*
|
|
246
|
+
* Returns a report with one entry per considered preset; never throws.
|
|
247
|
+
* @param {{ resolve(): Promise<any>; list?: () => Promise<any[]> }} service - the `agentPresets` service.
|
|
248
|
+
* @param {object} [opts]
|
|
249
|
+
* @returns {Promise<{ action: string; presets: Array<Record<string, any>>; presetId?: string; path?: string }>}
|
|
250
|
+
*/
|
|
251
|
+
export async function provisionWebCompaction(service, opts = {}) {
|
|
252
|
+
const log = opts.log ?? (() => {});
|
|
253
|
+
const rowConfig = opts.rowConfig ?? {};
|
|
254
|
+
const readText = opts.readText ?? ((preset) => readFile(preset.path, "utf8"));
|
|
255
|
+
const writeText = opts.writeText ?? ((preset, text) => writeFile(preset.path, text, "utf8"));
|
|
256
|
+
const writeBackup =
|
|
257
|
+
opts.writeBackup ?? ((path, text) => writeFile(path, text, { flag: "wx" }));
|
|
258
|
+
const backupSuffix = opts.backupSuffix ?? BACKUP_SUFFIX;
|
|
259
|
+
|
|
260
|
+
const presets = await collectPresetTargets(service, log);
|
|
261
|
+
if (presets.length === 0) {
|
|
262
|
+
log("engram web-provision: no usable agent preset found — Context GC auto-wiring skipped");
|
|
263
|
+
return { action: "no-default", presets: [] };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const results = [];
|
|
267
|
+
let anySwapped = false;
|
|
268
|
+
let anyFailed = false;
|
|
269
|
+
let anyAlready = false;
|
|
270
|
+
|
|
271
|
+
for (const preset of presets) {
|
|
272
|
+
let text;
|
|
273
|
+
try {
|
|
274
|
+
text = await readText(preset);
|
|
275
|
+
} catch (error) {
|
|
276
|
+
log(`engram web-provision: cannot read ${preset.path} (${describe(error)}) — skipping`);
|
|
277
|
+
results.push({ presetId: preset.id, path: preset.path, action: "skipped", reason: "read-failed" });
|
|
278
|
+
anyFailed = true;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const status = compactionWireStatus(text);
|
|
283
|
+
if (status === "custom") {
|
|
284
|
+
log(
|
|
285
|
+
`engram web-provision: preset "${preset.id}" has a custom compaction layout — leaving it untouched; `
|
|
286
|
+
+ `wire ${ENGRAM_ROW_NAME} manually if you want Context GC here`,
|
|
287
|
+
);
|
|
288
|
+
results.push({ presetId: preset.id, path: preset.path, action: "skipped", reason: "custom" });
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Handles both first wiring (stock → engram row) and config refresh
|
|
293
|
+
// (wired with a stale row → rewritten to the requested engine config).
|
|
294
|
+
const next = swapCompactionRow(text, rowConfig);
|
|
295
|
+
if (next === null) {
|
|
296
|
+
log(
|
|
297
|
+
`engram web-provision: preset "${preset.id}" did not match the expected stock layout — leaving it untouched`,
|
|
298
|
+
);
|
|
299
|
+
results.push({ presetId: preset.id, path: preset.path, action: "skipped", reason: "layout-mismatch" });
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (next === text) {
|
|
303
|
+
log(`engram web-provision: preset "${preset.id}" already uses ${ENGRAM_ROW_NAME} with the requested config — nothing to do`);
|
|
304
|
+
results.push({ presetId: preset.id, path: preset.path, action: "already" });
|
|
305
|
+
anyAlready = true;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const backupPath = `${preset.path}${backupSuffix}`;
|
|
310
|
+
try {
|
|
311
|
+
// Create-only: the FIRST original stays the backup even across later runs.
|
|
312
|
+
await writeBackup(backupPath, text).catch((error) => {
|
|
313
|
+
if (codeOf(error) !== "EEXIST") throw error;
|
|
314
|
+
});
|
|
315
|
+
await writeText(preset, next);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
log(
|
|
318
|
+
`engram web-provision: write failed for preset "${preset.id}" (${describe(error)}) — `
|
|
319
|
+
+ "Context GC not wired; the session keeps DSH's default summarizer",
|
|
320
|
+
);
|
|
321
|
+
results.push({ presetId: preset.id, path: preset.path, action: "failed", reason: "write-failed" });
|
|
322
|
+
anyFailed = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Post-write verification; restore the original on any doubt.
|
|
327
|
+
let verified = false;
|
|
328
|
+
try {
|
|
329
|
+
verified = compactionWireStatus(await readText(preset)) === "wired";
|
|
330
|
+
} catch {
|
|
331
|
+
verified = false;
|
|
332
|
+
}
|
|
333
|
+
if (!verified) {
|
|
334
|
+
log(`engram web-provision: post-write verification failed for preset "${preset.id}" — restoring`);
|
|
335
|
+
try {
|
|
336
|
+
await writeText(preset, text);
|
|
337
|
+
} catch {}
|
|
338
|
+
results.push({ presetId: preset.id, path: preset.path, action: "failed", reason: "verify-failed" });
|
|
339
|
+
anyFailed = true;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
log(
|
|
344
|
+
`engram web-provision: wired Context GC into preset "${preset.id}" (${preset.path}); `
|
|
345
|
+
+ "restart dsh web so sessions pick it up",
|
|
346
|
+
);
|
|
347
|
+
results.push({ presetId: preset.id, path: preset.path, action: "swapped", backupPath });
|
|
348
|
+
anySwapped = true;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const action = anySwapped ? "swapped" : anyFailed ? "failed" : anyAlready ? "already" : "skipped";
|
|
352
|
+
const first = results[0];
|
|
353
|
+
return {
|
|
354
|
+
action,
|
|
355
|
+
presets: results,
|
|
356
|
+
...(first !== undefined ? { presetId: first.presetId, path: first.path } : {}),
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Reverse {@link provisionWebCompaction}: restore the stock `compaction-basic`
|
|
362
|
+
* row in every wired preset (run before uninstalling dsh-engram so a preset
|
|
363
|
+
* never dangles a reference to the removed engine). No-ops on non-wired
|
|
364
|
+
* presets. Never throws.
|
|
365
|
+
* @param {{ resolve(): Promise<any>; list?: () => Promise<any[]> }} service - the `agentPresets` service.
|
|
366
|
+
* @param {object} [opts]
|
|
367
|
+
* @returns {Promise<{ action: string; presets: Array<Record<string, any>>; presetId?: string; path?: string }>}
|
|
368
|
+
*/
|
|
369
|
+
export async function revertWebCompaction(service, opts = {}) {
|
|
370
|
+
const log = opts.log ?? (() => {});
|
|
371
|
+
const readText = opts.readText ?? ((preset) => readFile(preset.path, "utf8"));
|
|
372
|
+
const writeText = opts.writeText ?? ((preset, text) => writeFile(preset.path, text, "utf8"));
|
|
373
|
+
|
|
374
|
+
const presets = await collectPresetTargets(service, log);
|
|
375
|
+
if (presets.length === 0) return { action: "no-default", presets: [] };
|
|
376
|
+
|
|
377
|
+
const results = [];
|
|
378
|
+
let anyReverted = false;
|
|
379
|
+
let anyFailed = false;
|
|
380
|
+
|
|
381
|
+
for (const preset of presets) {
|
|
382
|
+
let text;
|
|
383
|
+
try {
|
|
384
|
+
text = await readText(preset);
|
|
385
|
+
} catch (error) {
|
|
386
|
+
log(`engram web-provision: cannot read ${preset.path} (${describe(error)}) — nothing reverted`);
|
|
387
|
+
results.push({ presetId: preset.id, path: preset.path, action: "skipped", reason: "read-failed" });
|
|
388
|
+
anyFailed = true;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (compactionWireStatus(text) !== "wired") {
|
|
393
|
+
log(`engram web-provision: preset "${preset.id}" is not wired — nothing to revert`);
|
|
394
|
+
results.push({ presetId: preset.id, path: preset.path, action: "noop" });
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const restored = restoreCompactionRow(text);
|
|
399
|
+
if (restored === null) {
|
|
400
|
+
log(
|
|
401
|
+
`engram web-provision: preset "${preset.id}" changed since it was wired — not guessing; `
|
|
402
|
+
+ "restore the `.engram.bak` next to it manually if present",
|
|
403
|
+
);
|
|
404
|
+
results.push({ presetId: preset.id, path: preset.path, action: "failed", reason: "layout-mismatch" });
|
|
405
|
+
anyFailed = true;
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
try {
|
|
410
|
+
await writeText(preset, restored);
|
|
411
|
+
} catch (error) {
|
|
412
|
+
log(`engram web-provision: revert write failed for "${preset.id}" (${describe(error)}) — restore manually`);
|
|
413
|
+
results.push({ presetId: preset.id, path: preset.path, action: "failed", reason: "write-failed" });
|
|
414
|
+
anyFailed = true;
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
log(`engram web-provision: reverted preset "${preset.id}" to the stock compaction-basic row`);
|
|
419
|
+
results.push({ presetId: preset.id, path: preset.path, action: "reverted" });
|
|
420
|
+
anyReverted = true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const action = anyReverted ? "reverted" : anyFailed ? "failed" : "noop";
|
|
424
|
+
const first = results[0];
|
|
425
|
+
return {
|
|
426
|
+
action,
|
|
427
|
+
presets: results,
|
|
428
|
+
...(first !== undefined ? { presetId: first.presetId, path: first.path } : {}),
|
|
429
|
+
};
|
|
430
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-engram",
|
|
3
3
|
"description": "Minimalist long-term memory for DeepSeek Harness, distilling symbolic-index + pi-esr ideas: zero-LLM auto-capture, a symbolic [ENGRAM] index with progressive disclosure, and an ESR-lite evidence-closure protocol (esr_task / esr_node / esr_close / esr_link). Real agent-behaviour telemetry (usage rollup + /stats) and a deterministic offline recall benchmark (npm run eval). Hot path is model-free; storage on ctx.storageDomain; web memory viewer, observability panel and config card through DSH's native settings slots.",
|
|
4
|
-
"version": "0.3.
|
|
4
|
+
"version": "0.3.6",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
},
|
|
14
14
|
"type": "module",
|
|
15
15
|
"main": "lib/index.js",
|
|
16
|
+
"bin": {
|
|
17
|
+
"dsh-engram": "./scripts/dsh-engram.mjs"
|
|
18
|
+
},
|
|
16
19
|
"exports": {
|
|
17
20
|
".": "./lib/index.js",
|
|
18
21
|
"./compaction": "./lib/compaction.js",
|
|
@@ -22,6 +25,8 @@
|
|
|
22
25
|
},
|
|
23
26
|
"files": [
|
|
24
27
|
"lib",
|
|
28
|
+
"scripts",
|
|
29
|
+
"docs",
|
|
25
30
|
"cordis.patch.yml",
|
|
26
31
|
"README.md",
|
|
27
32
|
"README.zh.md",
|
|
@@ -36,7 +41,13 @@
|
|
|
36
41
|
"build:client": "node client/build.mjs && node client/build-model.mjs",
|
|
37
42
|
"test": "node --test \"test/**/*.test.mjs\"",
|
|
38
43
|
"eval": "node eval/recall-bench.mjs",
|
|
39
|
-
"typecheck": "node client/typecheck.mjs"
|
|
44
|
+
"typecheck": "node client/typecheck.mjs",
|
|
45
|
+
"web-compaction": "node scripts/dsh-engram.mjs",
|
|
46
|
+
"web-compaction:status": "node scripts/dsh-engram.mjs status",
|
|
47
|
+
"web-compaction:enable": "node scripts/dsh-engram.mjs enable",
|
|
48
|
+
"web-compaction:revert": "node scripts/dsh-engram.mjs revert",
|
|
49
|
+
"dsh-engram": "node scripts/dsh-engram.mjs",
|
|
50
|
+
"dsh-engram:doctor": "node scripts/dsh-engram.mjs doctor"
|
|
40
51
|
},
|
|
41
52
|
"publishConfig": {
|
|
42
53
|
"access": "public"
|