dsh-daoing-memory 0.1.0

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 (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +91 -0
  3. package/README.zh-CN.md +91 -0
  4. package/cordis.patch.yml +22 -0
  5. package/lib/client.js +10339 -0
  6. package/lib/client.js.map +1 -0
  7. package/lib/index.js +3138 -0
  8. package/lib/invariant.js +33 -0
  9. package/lib/tools.js +1472 -0
  10. package/lib/typert.host.d.ts +3 -0
  11. package/lib/typert.host.js +4058 -0
  12. package/lib/typert.remote-client.d.ts +149 -0
  13. package/lib/typert.remote-client.d.ts.map +1 -0
  14. package/lib/typert.remote-client.js +3167 -0
  15. package/lib/types/client/ExperiencePage.d.ts +15 -0
  16. package/lib/types/client/ExperiencePage.d.ts.map +1 -0
  17. package/lib/types/client/ExperiencePage.js +117 -0
  18. package/lib/types/client/ExperiencePage.js.map +1 -0
  19. package/lib/types/client/FactDiaryPage.d.ts +17 -0
  20. package/lib/types/client/FactDiaryPage.d.ts.map +1 -0
  21. package/lib/types/client/FactDiaryPage.js +155 -0
  22. package/lib/types/client/FactDiaryPage.js.map +1 -0
  23. package/lib/types/client/HumanOpsPage.d.ts +16 -0
  24. package/lib/types/client/HumanOpsPage.d.ts.map +1 -0
  25. package/lib/types/client/HumanOpsPage.js +208 -0
  26. package/lib/types/client/HumanOpsPage.js.map +1 -0
  27. package/lib/types/client/LedgerPage.d.ts +13 -0
  28. package/lib/types/client/LedgerPage.d.ts.map +1 -0
  29. package/lib/types/client/LedgerPage.js +113 -0
  30. package/lib/types/client/LedgerPage.js.map +1 -0
  31. package/lib/types/client/MemoryNavSection.d.ts +20 -0
  32. package/lib/types/client/MemoryNavSection.d.ts.map +1 -0
  33. package/lib/types/client/MemoryNavSection.js +23 -0
  34. package/lib/types/client/MemoryNavSection.js.map +1 -0
  35. package/lib/types/client/Workbench.d.ts +23 -0
  36. package/lib/types/client/Workbench.d.ts.map +1 -0
  37. package/lib/types/client/Workbench.js +140 -0
  38. package/lib/types/client/Workbench.js.map +1 -0
  39. package/lib/types/client/actions.d.ts +165 -0
  40. package/lib/types/client/actions.d.ts.map +1 -0
  41. package/lib/types/client/actions.js +51 -0
  42. package/lib/types/client/actions.js.map +1 -0
  43. package/lib/types/client/index.d.ts +24 -0
  44. package/lib/types/client/index.d.ts.map +1 -0
  45. package/lib/types/client/index.js +103 -0
  46. package/lib/types/client/index.js.map +1 -0
  47. package/lib/types/client/navStore.d.ts +45 -0
  48. package/lib/types/client/navStore.d.ts.map +1 -0
  49. package/lib/types/client/navStore.js +36 -0
  50. package/lib/types/client/navStore.js.map +1 -0
  51. package/lib/types/core.d.ts +195 -0
  52. package/lib/types/core.d.ts.map +1 -0
  53. package/lib/types/core.js +1304 -0
  54. package/lib/types/core.js.map +1 -0
  55. package/lib/types/index.d.ts +71 -0
  56. package/lib/types/index.d.ts.map +1 -0
  57. package/lib/types/index.js +97 -0
  58. package/lib/types/index.js.map +1 -0
  59. package/lib/types/invariant.d.ts +21 -0
  60. package/lib/types/invariant.d.ts.map +1 -0
  61. package/lib/types/invariant.js +33 -0
  62. package/lib/types/invariant.js.map +1 -0
  63. package/lib/types/service.d.ts +130 -0
  64. package/lib/types/service.d.ts.map +1 -0
  65. package/lib/types/service.js +417 -0
  66. package/lib/types/service.js.map +1 -0
  67. package/lib/types/store.d.ts +244 -0
  68. package/lib/types/store.d.ts.map +1 -0
  69. package/lib/types/store.js +864 -0
  70. package/lib/types/store.js.map +1 -0
  71. package/lib/types/tools.d.ts +25 -0
  72. package/lib/types/tools.d.ts.map +1 -0
  73. package/lib/types/tools.js +1144 -0
  74. package/lib/types/tools.js.map +1 -0
  75. package/lib/types/types.d.ts +836 -0
  76. package/lib/types/types.d.ts.map +1 -0
  77. package/lib/types/types.js +8 -0
  78. package/lib/types/types.js.map +1 -0
  79. package/package.json +106 -0
  80. package/skill/memory-extraction.md +62 -0
package/lib/index.js ADDED
@@ -0,0 +1,3138 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import { join, resolve } from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ import { createHash, randomUUID } from "node:crypto";
5
+ import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
6
+ //#region lib/types/store.js
7
+ /**
8
+ * Memory store: durable SQLite persistence for the experience lifecycle,
9
+ * use reports, diary, facts, extractions, recall telemetry, and the
10
+ * append-only hash-chained ledger. Uses node:sqlite (DatabaseSync) so the
11
+ * library runs with zero external services.
12
+ * @module dsh-daoing-memory/store
13
+ */
14
+ /** Monotone store schema version. v1→v2 is an additive ALTER migration (see open()). */
15
+ const MEMORY_SCHEMA_VERSION = 5;
16
+ /** Half-life (ms) of the recency weighting applied to verification samples. */
17
+ const TRUST_HALF_LIFE_MS = 4320 * 60 * 60 * 1e3;
18
+ /** Hash one ledger block's content, chained to the previous hash. */
19
+ function blockHash(ts, op, objectType, objectId, actor, payload, prevHash) {
20
+ const body = JSON.stringify({
21
+ ts,
22
+ op,
23
+ objectType,
24
+ objectId,
25
+ actor,
26
+ payload,
27
+ prevHash
28
+ });
29
+ return createHash("sha256").update(body).digest("hex").slice(0, 24);
30
+ }
31
+ /** Tokenize into latin words + CJK bigrams so Chinese situations match. */
32
+ function tokenize(text) {
33
+ const lower = text.toLowerCase();
34
+ const latin = lower.match(/[a-z0-9_]+/g) ?? [];
35
+ const cjk = lower.match(/[\u4e00-\u9fff]+/g) ?? [];
36
+ const bigrams = [];
37
+ for (const seg of cjk) {
38
+ if (seg.length === 1) bigrams.push(seg);
39
+ for (let i = 0; i < seg.length - 1; i++) bigrams.push(seg.slice(i, i + 2));
40
+ }
41
+ return [...latin, ...bigrams];
42
+ }
43
+ /** Rough token estimate for injection budgeting (CJK-heavy text). */
44
+ function estimateTokens(text) {
45
+ const cjk = (text.match(/[\u4e00-\u9fff]/g) ?? []).length;
46
+ const rest = text.length - cjk;
47
+ return Math.ceil(cjk * .6 + rest / 4);
48
+ }
49
+ /** The durable store behind the memory core. */
50
+ var MemoryStore = class {
51
+ db;
52
+ /** @param db - an open node:sqlite DatabaseSync handle. */
53
+ constructor(db) {
54
+ this.db = db;
55
+ db.exec(`
56
+ PRAGMA journal_mode = WAL;
57
+ CREATE TABLE IF NOT EXISTS memory_meta (
58
+ key TEXT PRIMARY KEY,
59
+ value TEXT NOT NULL
60
+ );
61
+ CREATE TABLE IF NOT EXISTS experiences (
62
+ family_id TEXT NOT NULL,
63
+ revision INTEGER NOT NULL,
64
+ kind TEXT NOT NULL,
65
+ source TEXT NOT NULL,
66
+ family TEXT NOT NULL,
67
+ gist TEXT NOT NULL,
68
+ situation TEXT NOT NULL,
69
+ path TEXT NOT NULL,
70
+ reasoning TEXT NOT NULL,
71
+ limits TEXT NOT NULL,
72
+ status TEXT NOT NULL,
73
+ alpha REAL NOT NULL,
74
+ beta REAL NOT NULL,
75
+ last_verified_at INTEGER,
76
+ pinned INTEGER NOT NULL DEFAULT 0,
77
+ tokens_saved REAL NOT NULL DEFAULT 0,
78
+ tokens_spent REAL NOT NULL DEFAULT 0,
79
+ parent_revision INTEGER,
80
+ failure_reason TEXT,
81
+ evidence TEXT,
82
+ challenge_reason TEXT,
83
+ deleted INTEGER NOT NULL DEFAULT 0,
84
+ context TEXT NOT NULL DEFAULT '',
85
+ verified_count INTEGER NOT NULL DEFAULT 0,
86
+ reject_count INTEGER NOT NULL DEFAULT 0,
87
+ global_flag INTEGER NOT NULL DEFAULT 0,
88
+ created_at INTEGER NOT NULL,
89
+ updated_at INTEGER NOT NULL,
90
+ PRIMARY KEY (family_id, revision)
91
+ );
92
+ CREATE INDEX IF NOT EXISTS idx_exp_status ON experiences (status, deleted);
93
+ CREATE INDEX IF NOT EXISTS idx_exp_family ON experiences (family);
94
+ CREATE TABLE IF NOT EXISTS use_reports (
95
+ id TEXT PRIMARY KEY,
96
+ experience_id TEXT NOT NULL,
97
+ revision INTEGER NOT NULL,
98
+ outcome TEXT NOT NULL,
99
+ attribution TEXT NOT NULL,
100
+ counted TEXT NOT NULL,
101
+ evidence TEXT,
102
+ dedupe_key TEXT,
103
+ ts INTEGER NOT NULL
104
+ );
105
+ CREATE INDEX IF NOT EXISTS idx_reports_exp ON use_reports (experience_id, revision, ts);
106
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_reports_dedupe
107
+ ON use_reports (experience_id, revision, dedupe_key)
108
+ WHERE dedupe_key IS NOT NULL;
109
+ CREATE TABLE IF NOT EXISTS ledger (
110
+ seq INTEGER PRIMARY KEY AUTOINCREMENT,
111
+ ts INTEGER NOT NULL,
112
+ op TEXT NOT NULL,
113
+ object_type TEXT NOT NULL,
114
+ object_id TEXT NOT NULL,
115
+ actor TEXT NOT NULL,
116
+ reason TEXT,
117
+ payload TEXT NOT NULL,
118
+ prev_hash TEXT NOT NULL,
119
+ hash TEXT NOT NULL
120
+ );
121
+ CREATE INDEX IF NOT EXISTS idx_ledger_object ON ledger (object_type, object_id, seq DESC);
122
+ CREATE TABLE IF NOT EXISTS diary (
123
+ id TEXT PRIMARY KEY,
124
+ ts INTEGER NOT NULL,
125
+ kind TEXT NOT NULL,
126
+ content TEXT NOT NULL,
127
+ session_ref TEXT,
128
+ tags TEXT NOT NULL,
129
+ extracted INTEGER NOT NULL DEFAULT 0
130
+ );
131
+ CREATE TABLE IF NOT EXISTS facts (
132
+ id TEXT PRIMARY KEY,
133
+ category TEXT NOT NULL,
134
+ fact_key TEXT NOT NULL,
135
+ value TEXT NOT NULL,
136
+ origin TEXT NOT NULL,
137
+ source_diary_ids TEXT NOT NULL,
138
+ corroboration INTEGER NOT NULL DEFAULT 1,
139
+ valid_from INTEGER NOT NULL,
140
+ valid_to INTEGER,
141
+ recorded_at INTEGER NOT NULL,
142
+ superseded_by TEXT,
143
+ locked INTEGER NOT NULL DEFAULT 0,
144
+ deleted INTEGER NOT NULL DEFAULT 0,
145
+ conflict_pending INTEGER NOT NULL DEFAULT 0
146
+ );
147
+ CREATE INDEX IF NOT EXISTS idx_facts_slot ON facts (category, fact_key, valid_to);
148
+ CREATE TABLE IF NOT EXISTS extractions (
149
+ id TEXT PRIMARY KEY,
150
+ ts INTEGER NOT NULL,
151
+ trigger TEXT NOT NULL,
152
+ summary TEXT NOT NULL,
153
+ produced_fact_ids TEXT NOT NULL,
154
+ diary_count INTEGER NOT NULL
155
+ );
156
+ CREATE TABLE IF NOT EXISTS recall_events (
157
+ id TEXT PRIMARY KEY,
158
+ ts INTEGER NOT NULL,
159
+ situation TEXT NOT NULL,
160
+ injected_ids TEXT NOT NULL,
161
+ none INTEGER NOT NULL DEFAULT 0,
162
+ context TEXT NOT NULL DEFAULT ''
163
+ );
164
+ CREATE TABLE IF NOT EXISTS concerns (
165
+ id TEXT PRIMARY KEY,
166
+ parent_id TEXT,
167
+ title TEXT NOT NULL,
168
+ background TEXT NOT NULL DEFAULT '',
169
+ kind TEXT,
170
+ status TEXT,
171
+ ts INTEGER NOT NULL,
172
+ source_diary_ids TEXT NOT NULL,
173
+ context TEXT NOT NULL DEFAULT '',
174
+ deleted INTEGER NOT NULL DEFAULT 0
175
+ );
176
+ CREATE TABLE IF NOT EXISTS consolidations (
177
+ id TEXT PRIMARY KEY,
178
+ ts INTEGER NOT NULL,
179
+ merged_ids TEXT NOT NULL,
180
+ produced_id TEXT NOT NULL,
181
+ note TEXT NOT NULL DEFAULT ''
182
+ );
183
+ `);
184
+ const row = db.prepare("SELECT value FROM memory_meta WHERE key = ?").get("schema_version");
185
+ if (row === void 0) db.prepare("INSERT INTO memory_meta (key, value) VALUES (?, ?)").run("schema_version", String(5));
186
+ else {
187
+ let v = Number(row.value);
188
+ if (v === 1) {
189
+ db.exec(`
190
+ ALTER TABLE experiences ADD COLUMN context TEXT NOT NULL DEFAULT '';
191
+ ALTER TABLE experiences ADD COLUMN verified_count INTEGER NOT NULL DEFAULT 0;
192
+ ALTER TABLE experiences ADD COLUMN reject_count INTEGER NOT NULL DEFAULT 0;
193
+ ALTER TABLE experiences ADD COLUMN global_flag INTEGER NOT NULL DEFAULT 0;
194
+ ALTER TABLE recall_events ADD COLUMN context TEXT NOT NULL DEFAULT '';
195
+ `);
196
+ v = 2;
197
+ }
198
+ if (v === 2 && true) v = 3;
199
+ if (v === 3 && true) v = 4;
200
+ if (v === 4 && true) {
201
+ db.exec(`ALTER TABLE concerns ADD COLUMN background TEXT NOT NULL DEFAULT '';`);
202
+ v = 5;
203
+ }
204
+ if (v !== 5) throw new Error(`memory store schema version ${row.value} does not match 5; refusing to open`);
205
+ db.prepare("UPDATE memory_meta SET value = ? WHERE key = ?").run(String(5), "schema_version");
206
+ }
207
+ db.exec("CREATE INDEX IF NOT EXISTS idx_exp_context ON experiences (context);");
208
+ db.exec("CREATE INDEX IF NOT EXISTS idx_concern_parent ON concerns (parent_id);");
209
+ }
210
+ /** Insert or update one experience revision row. */
211
+ upsertExperience(s) {
212
+ this.db.prepare(`
213
+ INSERT INTO experiences (
214
+ family_id, revision, kind, source, family, gist, situation, path, reasoning, limits,
215
+ status, alpha, beta, last_verified_at, pinned, tokens_saved, tokens_spent,
216
+ parent_revision, failure_reason, evidence, challenge_reason, deleted,
217
+ context, verified_count, reject_count, global_flag, created_at, updated_at
218
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0,?,?,?,?,?,?)
219
+ ON CONFLICT (family_id, revision) DO UPDATE SET
220
+ kind = excluded.kind,
221
+ source = excluded.source,
222
+ family = excluded.family,
223
+ gist = excluded.gist,
224
+ situation = excluded.situation,
225
+ path = excluded.path,
226
+ reasoning = excluded.reasoning,
227
+ limits = excluded.limits,
228
+ status = excluded.status,
229
+ alpha = excluded.alpha,
230
+ beta = excluded.beta,
231
+ last_verified_at = excluded.last_verified_at,
232
+ pinned = excluded.pinned,
233
+ tokens_saved = excluded.tokens_saved,
234
+ tokens_spent = excluded.tokens_spent,
235
+ parent_revision = excluded.parent_revision,
236
+ failure_reason = excluded.failure_reason,
237
+ evidence = excluded.evidence,
238
+ challenge_reason = excluded.challenge_reason,
239
+ context = excluded.context,
240
+ verified_count = excluded.verified_count,
241
+ reject_count = excluded.reject_count,
242
+ global_flag = excluded.global_flag,
243
+ updated_at = excluded.updated_at
244
+ `).run(s.id, s.revision, s.kind, s.source, s.family, s.gist, JSON.stringify(s.situation), JSON.stringify(s.path), s.reasoning, JSON.stringify(s.limits), s.status, s.alpha, s.beta, s.lastVerifiedAt ?? null, s.pinned ? 1 : 0, s.tokensSaved, s.tokensSpent, s.parentRevision ?? null, s.failureReason ?? null, s.evidence === void 0 ? null : JSON.stringify(s.evidence), s.challengeReason ?? null, s.context ?? "", s.verifiedCount ?? 0, s.rejectCount ?? 0, s.globalFlag === true ? 1 : 0, s.createdAt, s.updatedAt);
245
+ }
246
+ /** Read one experience revision. */
247
+ getExperience(familyId, revision) {
248
+ const row = this.db.prepare("SELECT * FROM experiences WHERE family_id = ? AND revision = ? AND deleted = 0").get(familyId, revision);
249
+ return row === void 0 ? void 0 : this.rowToExperience(row);
250
+ }
251
+ /** The active (non-superseded, non-deleted) revision of a family, if any. */
252
+ getActiveRevision(familyId) {
253
+ const row = this.db.prepare(`
254
+ SELECT * FROM experiences
255
+ WHERE family_id = ? AND deleted = 0 AND status IN ('candidate', 'live', 'challenged')
256
+ ORDER BY revision DESC LIMIT 1
257
+ `).get(familyId);
258
+ return row === void 0 ? void 0 : this.rowToExperience(row);
259
+ }
260
+ /** All revisions of one family, oldest first. */
261
+ getFamily(familyId) {
262
+ return this.db.prepare("SELECT * FROM experiences WHERE family_id = ? AND deleted = 0 ORDER BY revision ASC").all(familyId).map((row) => this.rowToExperience(row));
263
+ }
264
+ /** List experience revisions by filter; live-ish first, best trust first. */
265
+ listExperiences(filter) {
266
+ const clauses = ["deleted = 0"];
267
+ const params = [];
268
+ if (filter.status !== void 0) {
269
+ clauses.push("status = ?");
270
+ params.push(filter.status);
271
+ }
272
+ if (filter.kind !== void 0) {
273
+ clauses.push("kind = ?");
274
+ params.push(filter.kind);
275
+ }
276
+ if (filter.family !== void 0) {
277
+ clauses.push("family = ?");
278
+ params.push(filter.family);
279
+ }
280
+ if (filter.context !== void 0) {
281
+ clauses.push("context = ?");
282
+ params.push(filter.context);
283
+ }
284
+ return this.db.prepare(`
285
+ SELECT * FROM experiences WHERE ${clauses.join(" AND ")}
286
+ ORDER BY CASE status WHEN 'live' THEN 0 WHEN 'candidate' THEN 1 WHEN 'challenged' THEN 2
287
+ WHEN 'cold' THEN 3 WHEN 'archived' THEN 4 ELSE 5 END,
288
+ (alpha + 1.0) / (alpha + beta + 2.0) DESC,
289
+ updated_at DESC
290
+ `).all(...params).map((row) => this.rowToExperience(row));
291
+ }
292
+ /**
293
+ * Recall candidates: token overlap against situation+gist+limits of the given
294
+ * statuses, best overlap first. An optional context scopes the pool: only
295
+ * same-context or globally-shared (global_flag) revisions qualify (006 §2).
296
+ */
297
+ recallCandidates(queryTokens, topK, opts = {}) {
298
+ const statuses = opts.statuses ?? ["live"];
299
+ let sql = `
300
+ SELECT * FROM experiences
301
+ WHERE deleted = 0 AND status IN (${statuses.map(() => "?").join(", ")})
302
+ `;
303
+ const params = [...statuses];
304
+ if (opts.context !== void 0 && opts.context !== "") {
305
+ sql += " AND (context = ? OR global_flag = 1)";
306
+ params.push(opts.context);
307
+ }
308
+ const rows = this.db.prepare(sql).all(...params);
309
+ const scored = [];
310
+ for (const row of rows) {
311
+ const snapshot = this.rowToExperience(row);
312
+ const hay = new Set(tokenize([
313
+ snapshot.gist,
314
+ ...snapshot.situation,
315
+ ...snapshot.limits
316
+ ].join(" ")));
317
+ if (hay.size === 0) continue;
318
+ let hits = 0;
319
+ for (const token of queryTokens) if (hay.has(token)) hits += 1;
320
+ const score = hits / Math.sqrt(queryTokens.size * hay.size);
321
+ if (score > 0) scored.push({
322
+ snapshot,
323
+ score
324
+ });
325
+ }
326
+ scored.sort((a, b) => b.score - a.score);
327
+ return scored.slice(0, topK);
328
+ }
329
+ /**
330
+ * Near-duplicate detection for the information-gain gate (007 flow-log fix).
331
+ * Unlike recallCandidates it scores on gist+situation only (the lesson's
332
+ * identity), so identical lessons with divergent limits/paths still match.
333
+ * Returns the single best match across the requested statuses, or undefined.
334
+ */
335
+ findNearDuplicate(queryTokens, statuses) {
336
+ if (queryTokens.size === 0) return void 0;
337
+ const sql = `
338
+ SELECT * FROM experiences
339
+ WHERE deleted = 0 AND status IN (${statuses.map(() => "?").join(", ")})
340
+ `;
341
+ const rows = this.db.prepare(sql).all(...statuses);
342
+ let best;
343
+ for (const row of rows) {
344
+ const snapshot = this.rowToExperience(row);
345
+ const hay = new Set(tokenize([snapshot.gist, ...snapshot.situation].join(" ")));
346
+ if (hay.size === 0) continue;
347
+ let hits = 0;
348
+ for (const token of queryTokens) if (hay.has(token)) hits += 1;
349
+ const score = hits / Math.sqrt(queryTokens.size * hay.size);
350
+ if (best === void 0 || score > best.score) best = {
351
+ snapshot,
352
+ score
353
+ };
354
+ }
355
+ return best;
356
+ }
357
+ /** Count live revisions in one family (capacity budget). */
358
+ countFamilyActive(familyTag) {
359
+ return this.db.prepare("SELECT COUNT(*) AS n FROM experiences WHERE family = ? AND status = 'live' AND deleted = 0").get(familyTag).n;
360
+ }
361
+ /** Mark one revision deleted (human delete tombstone). */
362
+ deleteFamily(familyId) {
363
+ this.db.prepare("UPDATE experiences SET deleted = 1, updated_at = ? WHERE family_id = ?").run(Date.now(), familyId);
364
+ }
365
+ /**
366
+ * Insert one counted report; a repeated (family, revision, dedupeKey)
367
+ * returns false instead of double-counting (idempotent use).
368
+ */
369
+ insertReport(report) {
370
+ try {
371
+ this.db.prepare(`
372
+ INSERT INTO use_reports (id, experience_id, revision, outcome, attribution, counted, evidence, dedupe_key, ts)
373
+ VALUES (?,?,?,?,?,?,?,?,?)
374
+ `).run(report.id, report.experienceId, report.revision, report.outcome, report.attribution, report.counted, report.evidence === void 0 ? null : JSON.stringify(report.evidence), report.dedupeKey ?? null, report.ts);
375
+ return true;
376
+ } catch (error) {
377
+ if (error instanceof Error && error.message.includes("UNIQUE")) return false;
378
+ throw error;
379
+ }
380
+ }
381
+ /** Recent counted reports of one revision, newest first. */
382
+ reportsFor(familyId, revision, limit) {
383
+ return this.db.prepare(`
384
+ SELECT * FROM use_reports WHERE experience_id = ? AND revision = ? AND counted != 'none'
385
+ ORDER BY ts DESC LIMIT ?
386
+ `).all(familyId, revision, limit).map((row) => this.rowToReport(row));
387
+ }
388
+ /** Every report row (export). */
389
+ allReports() {
390
+ return this.db.prepare("SELECT * FROM use_reports ORDER BY ts ASC").all().map((row) => this.rowToReport(row));
391
+ }
392
+ /** Recency-weighted alpha/beta over counted reports. */
393
+ weightedTrust(familyId, revision, now) {
394
+ const rows = this.db.prepare(`
395
+ SELECT ts, counted FROM use_reports
396
+ WHERE experience_id = ? AND revision = ? AND counted IN ('alpha', 'beta')
397
+ `).all(familyId, revision);
398
+ let alpha = 0;
399
+ let beta = 0;
400
+ for (const row of rows) {
401
+ const weight = Math.pow(.5, (now - row.ts) / TRUST_HALF_LIFE_MS);
402
+ if (row.counted === "alpha") alpha += weight;
403
+ else beta += weight;
404
+ }
405
+ return (alpha + 1) / (alpha + beta + 2);
406
+ }
407
+ /** Append one block, chaining the hash; returns the stored block. */
408
+ appendLedger(block) {
409
+ const hash = blockHash(block.ts, block.op, block.objectType, block.objectId, block.actor, block.payload, block.prevHash);
410
+ const info = this.db.prepare(`
411
+ INSERT INTO ledger (ts, op, object_type, object_id, actor, reason, payload, prev_hash, hash)
412
+ VALUES (?,?,?,?,?,?,?,?,?)
413
+ `).run(block.ts, block.op, block.objectType, block.objectId, block.actor, block.reason ?? null, block.payload, block.prevHash, hash);
414
+ return {
415
+ ...block,
416
+ seq: Number(info.lastInsertRowid),
417
+ hash
418
+ };
419
+ }
420
+ /** The newest block's hash ('' when the ledger is empty). */
421
+ ledgerHead() {
422
+ return this.db.prepare("SELECT hash FROM ledger ORDER BY seq DESC LIMIT 1").get()?.hash ?? "";
423
+ }
424
+ /** Ledger blocks, newest first, optionally filtered. */
425
+ ledgerQuery(filter) {
426
+ const { clauses, params } = this.ledgerFilterClauses(filter);
427
+ params.push(filter.limit);
428
+ let sql = `SELECT * FROM ledger WHERE ${clauses.join(" AND ")} ORDER BY seq DESC LIMIT ?`;
429
+ if (filter.offset !== void 0 && filter.offset > 0) {
430
+ sql += " OFFSET ?";
431
+ params.push(filter.offset);
432
+ }
433
+ return this.db.prepare(sql).all(...params).map((row) => this.rowToLedger(row));
434
+ }
435
+ /** Shared WHERE-builder for the ledger query and its filtered count. */
436
+ ledgerFilterClauses(filter) {
437
+ const clauses = ["1 = 1"];
438
+ const params = [];
439
+ if (filter.objectType !== void 0) {
440
+ clauses.push("object_type = ?");
441
+ params.push(filter.objectType);
442
+ }
443
+ if (filter.objectId !== void 0) {
444
+ clauses.push("object_id = ?");
445
+ params.push(filter.objectId);
446
+ }
447
+ if (filter.op !== void 0) {
448
+ clauses.push("op = ?");
449
+ params.push(filter.op);
450
+ }
451
+ if (filter.seqFrom !== void 0) {
452
+ clauses.push("seq >= ?");
453
+ params.push(filter.seqFrom);
454
+ }
455
+ if (filter.seqTo !== void 0) {
456
+ clauses.push("seq <= ?");
457
+ params.push(filter.seqTo);
458
+ }
459
+ return {
460
+ clauses,
461
+ params
462
+ };
463
+ }
464
+ /** Count ledger blocks matching a filter (for pagination, 007 §2). */
465
+ ledgerQueryCount(filter) {
466
+ const { clauses, params } = this.ledgerFilterClauses(filter);
467
+ return this.db.prepare(`SELECT COUNT(*) AS n FROM ledger WHERE ${clauses.join(" AND ")}`).get(...params).n;
468
+ }
469
+ /** The complete ledger, oldest first (integrity checks + export). */
470
+ ledgerAll() {
471
+ return this.db.prepare("SELECT * FROM ledger ORDER BY seq ASC").all().map((row) => this.rowToLedger(row));
472
+ }
473
+ /** Total ledger block count. */
474
+ ledgerCount() {
475
+ return this.db.prepare("SELECT COUNT(*) AS n FROM ledger").get().n;
476
+ }
477
+ /** Append one diary entry (append-only layer). */
478
+ insertDiary(entry) {
479
+ this.db.prepare(`
480
+ INSERT INTO diary (id, ts, kind, content, session_ref, tags, extracted)
481
+ VALUES (?,?,?,?,?,?,?)
482
+ `).run(entry.id, entry.ts, entry.kind, entry.content, entry.sessionRef ?? null, JSON.stringify(entry.tags), entry.extracted ? 1 : 0);
483
+ }
484
+ /** Diary entries, newest first. */
485
+ listDiary(limit, offset, onlyUnextracted) {
486
+ const where = onlyUnextracted ? "WHERE extracted = 0" : "";
487
+ return this.db.prepare(`SELECT * FROM diary ${where} ORDER BY ts DESC, rowid DESC LIMIT ? OFFSET ?`).all(limit, offset).map((row) => this.rowToDiary(row));
488
+ }
489
+ /** Unextracted entries, oldest first (the extraction window). */
490
+ unextractedDiary() {
491
+ return this.db.prepare("SELECT * FROM diary WHERE extracted = 0 ORDER BY ts ASC").all().map((row) => this.rowToDiary(row));
492
+ }
493
+ /** Mark diary entries extracted. */
494
+ markDiaryExtracted(ids) {
495
+ const stmt = this.db.prepare("UPDATE diary SET extracted = 1 WHERE id = ?");
496
+ for (const id of ids) stmt.run(id);
497
+ }
498
+ /** One diary entry by id. */
499
+ getDiary(id) {
500
+ const row = this.db.prepare("SELECT * FROM diary WHERE id = ?").get(id);
501
+ return row === void 0 ? void 0 : this.rowToDiary(row);
502
+ }
503
+ /** Several diary entries by id, preserving the requested order (008 Path A: fact→diary provenance). */
504
+ getDiaryByIds(ids) {
505
+ const out = [];
506
+ const stmt = this.db.prepare("SELECT * FROM diary WHERE id = ?");
507
+ for (const id of ids) {
508
+ const row = stmt.get(id);
509
+ if (row !== void 0) out.push(this.rowToDiary(row));
510
+ }
511
+ return out;
512
+ }
513
+ /** Diary counters. */
514
+ diaryCounts() {
515
+ const total = this.db.prepare("SELECT COUNT(*) AS n FROM diary").get();
516
+ const unextracted = this.db.prepare("SELECT COUNT(*) AS n FROM diary WHERE extracted = 0").get();
517
+ return {
518
+ total: total.n,
519
+ unextracted: unextracted.n
520
+ };
521
+ }
522
+ /** Insert one fact version. */
523
+ insertFact(fact) {
524
+ this.db.prepare(`
525
+ INSERT INTO facts (
526
+ id, category, fact_key, value, origin, source_diary_ids, corroboration,
527
+ valid_from, valid_to, recorded_at, superseded_by, locked, deleted, conflict_pending
528
+ ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
529
+ `).run(fact.id, fact.category, fact.factKey, fact.value, fact.origin, JSON.stringify(fact.sourceDiaryIds), fact.corroboration, fact.validFrom, fact.validTo ?? null, fact.recordedAt, fact.supersededBy ?? null, fact.locked ? 1 : 0, fact.deleted ? 1 : 0, fact.conflictPending ? 1 : 0);
530
+ }
531
+ /** Update one fact version in place (locking, tombstones, supersede links). */
532
+ updateFact(fact) {
533
+ this.db.prepare(`
534
+ UPDATE facts SET value = ?, valid_to = ?, superseded_by = ?, locked = ?, deleted = ?, conflict_pending = ?
535
+ WHERE id = ?
536
+ `).run(fact.value, fact.validTo ?? null, fact.supersededBy ?? null, fact.locked ? 1 : 0, fact.deleted ? 1 : 0, fact.conflictPending ? 1 : 0, fact.id);
537
+ }
538
+ /** The current (open valid-time window, not deleted) version of one slot. */
539
+ currentFact(category, factKey) {
540
+ const row = this.db.prepare("SELECT * FROM facts WHERE category = ? AND fact_key = ? AND valid_to IS NULL AND deleted = 0 ORDER BY recorded_at DESC LIMIT 1").get(category, factKey);
541
+ return row === void 0 ? void 0 : this.rowToFact(row);
542
+ }
543
+ /** One fact version by id. */
544
+ getFact(id) {
545
+ const row = this.db.prepare("SELECT * FROM facts WHERE id = ?").get(id);
546
+ return row === void 0 ? void 0 : this.rowToFact(row);
547
+ }
548
+ /** Shared WHERE for fact filters (008 §3: reused by list + count). */
549
+ factFilterClauses(filter) {
550
+ const clauses = ["deleted = 0"];
551
+ const params = [];
552
+ if (!filter.includeHistory) clauses.push("valid_to IS NULL");
553
+ if (filter.category !== void 0) {
554
+ clauses.push("category = ?");
555
+ params.push(filter.category);
556
+ }
557
+ return {
558
+ where: clauses.join(" AND "),
559
+ params
560
+ };
561
+ }
562
+ /** Fact versions by filter (008 §3: server-side pagination). */
563
+ listFacts(filter, limit, offset) {
564
+ const { where, params } = this.factFilterClauses(filter);
565
+ return this.db.prepare(`SELECT * FROM facts WHERE ${where} ORDER BY category, fact_key, recorded_at DESC LIMIT ? OFFSET ?`).all(...params, limit, offset).map((row) => this.rowToFact(row));
566
+ }
567
+ /** Count of facts matching the filter (008 §3: pagination total). */
568
+ factFilteredCount(filter) {
569
+ const { where, params } = this.factFilterClauses(filter);
570
+ return this.db.prepare(`SELECT COUNT(*) AS n FROM facts WHERE ${where}`).get(...params).n;
571
+ }
572
+ /** All fact versions including tombstones (export). */
573
+ allFacts() {
574
+ return this.db.prepare("SELECT * FROM facts ORDER BY recorded_at ASC").all().map((row) => this.rowToFact(row));
575
+ }
576
+ /** Fact counters. */
577
+ factCounts() {
578
+ const q = (where) => this.db.prepare(`SELECT COUNT(*) AS n FROM facts WHERE ${where}`).get().n;
579
+ return {
580
+ total: q("deleted = 0"),
581
+ current: q("valid_to IS NULL AND deleted = 0"),
582
+ locked: q("valid_to IS NULL AND deleted = 0 AND locked = 1"),
583
+ conflictPending: q("valid_to IS NULL AND deleted = 0 AND conflict_pending = 1")
584
+ };
585
+ }
586
+ rowToConcern(row) {
587
+ return {
588
+ id: row.id,
589
+ ...row.parent_id === null ? {} : { parentId: row.parent_id },
590
+ title: row.title,
591
+ ...row.background === "" ? {} : { background: row.background },
592
+ ...row.kind === null ? {} : { kind: row.kind },
593
+ ...row.status === null ? {} : { status: row.status },
594
+ ts: row.ts,
595
+ sourceDiaryIds: JSON.parse(row.source_diary_ids),
596
+ ...row.context === "" ? {} : { context: row.context },
597
+ deleted: row.deleted === 1
598
+ };
599
+ }
600
+ /** Insert one concerns row (top-level or a discussion mention). */
601
+ insertConcern(c) {
602
+ this.db.prepare(`
603
+ INSERT INTO concerns (id, parent_id, title, background, kind, status, ts, source_diary_ids, context, deleted)
604
+ VALUES (?,?,?,?,?,?,?,?,?,?)
605
+ `).run(c.id, c.parentId ?? null, c.title, c.background ?? "", c.kind ?? null, c.status ?? null, c.ts, JSON.stringify(c.sourceDiaryIds), c.context ?? "", c.deleted ? 1 : 0);
606
+ }
607
+ /**
608
+ * Top-level concerns (newest first) each with its discussion loop (oldest
609
+ * first). Optional kind/status filter + limit/offset pagination over the
610
+ * top-level rows; a mention set is attached for every returned top-level.
611
+ */
612
+ listConcernTrees(filter, limit, offset) {
613
+ const where = ["parent_id IS NULL", "deleted = 0"];
614
+ const params = [];
615
+ if (filter.kind !== void 0 && filter.kind !== "") {
616
+ where.push("kind = ?");
617
+ params.push(filter.kind);
618
+ }
619
+ if (filter.status !== void 0 && filter.status !== "") {
620
+ where.push("status = ?");
621
+ params.push(filter.status);
622
+ }
623
+ let sql = `SELECT * FROM concerns WHERE ${where.join(" AND ")} ORDER BY ts DESC`;
624
+ if (limit !== void 0) {
625
+ sql += " LIMIT ? OFFSET ?";
626
+ params.push(limit, offset ?? 0);
627
+ }
628
+ const tops = this.db.prepare(sql).all(...params);
629
+ const mentions = this.db.prepare("SELECT * FROM concerns WHERE parent_id IS NOT NULL AND deleted = 0 ORDER BY ts ASC").all();
630
+ const byParent = /* @__PURE__ */ new Map();
631
+ for (const m of mentions) {
632
+ const list = byParent.get(m.parent_id) ?? [];
633
+ list.push(this.rowToConcern(m));
634
+ byParent.set(m.parent_id, list);
635
+ }
636
+ return tops.map((t) => ({
637
+ concern: this.rowToConcern(t),
638
+ mentions: byParent.get(t.id) ?? []
639
+ }));
640
+ }
641
+ /** Count top-level (non-deleted) concerns matching the optional kind/status filter. */
642
+ listConcernsCount(filter) {
643
+ const where = ["parent_id IS NULL", "deleted = 0"];
644
+ const params = [];
645
+ if (filter.kind !== void 0 && filter.kind !== "") {
646
+ where.push("kind = ?");
647
+ params.push(filter.kind);
648
+ }
649
+ if (filter.status !== void 0 && filter.status !== "") {
650
+ where.push("status = ?");
651
+ params.push(filter.status);
652
+ }
653
+ return this.db.prepare(`SELECT COUNT(*) AS n FROM concerns WHERE ${where.join(" AND ")}`).get(...params).n;
654
+ }
655
+ /** Update a top-level concern's lifecycle status. */
656
+ setConcernStatus(id, status) {
657
+ this.db.prepare("UPDATE concerns SET status = ? WHERE id = ? AND parent_id IS NULL").run(status, id);
658
+ }
659
+ /** Tombstone a concern and its whole discussion loop (human-only cleanup). */
660
+ deleteConcernSubtree(id) {
661
+ this.db.prepare("UPDATE concerns SET deleted = 1 WHERE id = ? OR parent_id = ?").run(id, id);
662
+ }
663
+ /** Record one extraction run. */
664
+ insertExtraction(record) {
665
+ this.db.prepare(`
666
+ INSERT INTO extractions (id, ts, trigger, summary, produced_fact_ids, diary_count)
667
+ VALUES (?,?,?,?,?,?)
668
+ `).run(record.id, record.ts, record.trigger, record.summary, JSON.stringify(record.producedFactIds), record.diaryCount);
669
+ }
670
+ /** Extraction runs, newest first (008 §3: server-side pagination). */
671
+ listExtractions(limit, offset) {
672
+ return this.db.prepare("SELECT * FROM extractions ORDER BY ts DESC, rowid DESC LIMIT ? OFFSET ?").all(limit, offset).map((row) => ({
673
+ id: row.id,
674
+ ts: row.ts,
675
+ trigger: row.trigger,
676
+ summary: row.summary,
677
+ producedFactIds: JSON.parse(row.produced_fact_ids),
678
+ diaryCount: row.diary_count
679
+ }));
680
+ }
681
+ /** Total extraction runs (008 §3: pagination count). */
682
+ extractionCount() {
683
+ return this.db.prepare("SELECT COUNT(*) AS n FROM extractions").get().n;
684
+ }
685
+ /** Last extraction ts (cadence gate). */
686
+ lastExtractionTs() {
687
+ return this.db.prepare("SELECT MAX(ts) AS ts FROM extractions").get().ts ?? 0;
688
+ }
689
+ /** Record one consolidation run (which experiences merged into which). */
690
+ recordConsolidation(record) {
691
+ this.db.prepare("INSERT INTO consolidations (id, ts, merged_ids, produced_id, note) VALUES (?,?,?,?,?)").run(record.id, record.ts, JSON.stringify(record.mergedIds), record.producedId, record.note);
692
+ }
693
+ /** Last consolidation ts (interval cadence gate; 0 = never consolidated). */
694
+ lastConsolidationTs() {
695
+ return this.db.prepare("SELECT MAX(ts) AS ts FROM consolidations").get().ts ?? 0;
696
+ }
697
+ /** Non-deleted experiences created strictly after `ts` (new material since last consolidation). */
698
+ countExperiencesSince(ts) {
699
+ return this.db.prepare("SELECT COUNT(*) AS n FROM experiences WHERE deleted = 0 AND created_at > ?").get(ts).n;
700
+ }
701
+ /** Archive a set of experiences by family_id (leave recall; recoverable — never hard-deleted autonomously). */
702
+ archiveExperienceIds(ids, ts) {
703
+ const stmt = this.db.prepare(`UPDATE experiences SET status = ?, updated_at = ? WHERE family_id = ? AND deleted = 0 AND status IN ('candidate','live','challenged')`);
704
+ for (const id of ids) stmt.run("archived", ts, id);
705
+ }
706
+ /** Record one recall event. */
707
+ insertRecallEvent(event) {
708
+ this.db.prepare("INSERT INTO recall_events (id, ts, situation, injected_ids, none, context) VALUES (?,?,?,?,?,?)").run(event.id, event.ts, event.situation, JSON.stringify(event.injectedIds), event.none ? 1 : 0, event.context ?? "");
709
+ }
710
+ /** Recall telemetry counters. */
711
+ recallCounts() {
712
+ const events = this.db.prepare("SELECT COUNT(*) AS n FROM recall_events").get();
713
+ const negative = this.db.prepare("SELECT COUNT(*) AS n FROM recall_events WHERE none = 1").get();
714
+ return {
715
+ events: events.n,
716
+ negative: negative.n
717
+ };
718
+ }
719
+ /** All recall events (export). */
720
+ allRecallEvents() {
721
+ return this.db.prepare("SELECT * FROM recall_events ORDER BY ts ASC").all().map((row) => ({
722
+ id: row.id,
723
+ ts: row.ts,
724
+ situation: row.situation,
725
+ injectedIds: JSON.parse(row.injected_ids),
726
+ none: row.none === 1,
727
+ context: row.context ?? ""
728
+ }));
729
+ }
730
+ rowToExperience(row) {
731
+ const alpha = row.alpha;
732
+ const beta = row.beta;
733
+ const snapshot = {
734
+ id: row.family_id,
735
+ revision: row.revision,
736
+ kind: row.kind,
737
+ source: row.source,
738
+ family: row.family,
739
+ gist: row.gist,
740
+ situation: JSON.parse(row.situation),
741
+ path: JSON.parse(row.path),
742
+ reasoning: row.reasoning,
743
+ limits: JSON.parse(row.limits),
744
+ status: row.status,
745
+ alpha,
746
+ beta,
747
+ samples: alpha + beta,
748
+ trust: (alpha + 1) / (alpha + beta + 2),
749
+ weightedTrust: this.weightedTrust(row.family_id, row.revision, Date.now()),
750
+ pinned: row.pinned === 1,
751
+ tokensSaved: row.tokens_saved,
752
+ tokensSpent: row.tokens_spent,
753
+ context: row.context,
754
+ verifiedCount: row.verified_count,
755
+ rejectCount: row.reject_count,
756
+ globalFlag: row.global_flag === 1,
757
+ createdAt: row.created_at,
758
+ updatedAt: row.updated_at
759
+ };
760
+ if (row.last_verified_at !== null) snapshot.lastVerifiedAt = row.last_verified_at;
761
+ if (row.parent_revision !== null) snapshot.parentRevision = row.parent_revision;
762
+ if (row.failure_reason !== null) snapshot.failureReason = row.failure_reason;
763
+ if (row.evidence !== null) snapshot.evidence = JSON.parse(row.evidence);
764
+ if (row.challenge_reason !== null) snapshot.challengeReason = row.challenge_reason;
765
+ return snapshot;
766
+ }
767
+ rowToReport(row) {
768
+ return {
769
+ id: row.id,
770
+ experienceId: row.experience_id,
771
+ revision: row.revision,
772
+ outcome: row.outcome,
773
+ attribution: row.attribution,
774
+ counted: row.counted,
775
+ evidence: row.evidence === null ? void 0 : JSON.parse(row.evidence),
776
+ dedupeKey: row.dedupe_key === null ? void 0 : row.dedupe_key,
777
+ ts: row.ts
778
+ };
779
+ }
780
+ rowToLedger(row) {
781
+ const block = {
782
+ seq: row.seq,
783
+ ts: row.ts,
784
+ op: row.op,
785
+ objectType: row.object_type,
786
+ objectId: row.object_id,
787
+ actor: row.actor,
788
+ payload: row.payload,
789
+ prevHash: row.prev_hash,
790
+ hash: row.hash
791
+ };
792
+ if (row.reason !== null) block.reason = row.reason;
793
+ return block;
794
+ }
795
+ rowToDiary(row) {
796
+ const entry = {
797
+ id: row.id,
798
+ ts: row.ts,
799
+ kind: row.kind,
800
+ content: row.content,
801
+ tags: JSON.parse(row.tags),
802
+ extracted: row.extracted === 1
803
+ };
804
+ if (row.session_ref !== null) entry.sessionRef = row.session_ref;
805
+ return entry;
806
+ }
807
+ rowToFact(row) {
808
+ const fact = {
809
+ id: row.id,
810
+ category: row.category,
811
+ factKey: row.fact_key,
812
+ value: row.value,
813
+ origin: row.origin,
814
+ sourceDiaryIds: JSON.parse(row.source_diary_ids),
815
+ corroboration: row.corroboration,
816
+ validFrom: row.valid_from,
817
+ recordedAt: row.recorded_at,
818
+ locked: row.locked === 1,
819
+ deleted: row.deleted === 1,
820
+ conflictPending: row.conflict_pending === 1
821
+ };
822
+ if (row.valid_to !== null) fact.validTo = row.valid_to;
823
+ if (row.superseded_by !== null) fact.supersededBy = row.superseded_by;
824
+ return fact;
825
+ }
826
+ };
827
+ //#endregion
828
+ //#region lib/types/core.js
829
+ /**
830
+ * Memory core: the 生·用·修·记 mechanisms over the durable store. Pure and
831
+ * ctx-free. Implements 005's P0/P1 semantics:
832
+ *
833
+ * - 生 refine: complexity gate + information-gain gate + mandatory evidence
834
+ * pointers; positive and negative candidates symmetric.
835
+ * - 用 recall: recall → adjudication (limits vs situation conflicts,
836
+ * direct/reference/clue/not-applicable) → injection budget → explicit
837
+ * negative channel.
838
+ * - 用·验 report: Beta posterior (alpha+1)/(alpha+beta+2) with recency
839
+ * weighting; four-way attribution with objective-evidence priority and
840
+ * no-count on insufficient signal; idempotent reports.
841
+ * - 修 revise: challenged quarantine (immediately out of recall), draft
842
+ * proposal, adoption only after one successful use or shadow replay,
843
+ * superseded read-only index with rollback.
844
+ * - 记 diary/facts: append-only diary, cadence-driven extraction with source
845
+ * pointers, bi-temporal facts with conflict arbitration and human gates.
846
+ * - Ledger: append-only hash-chained event sourcing for every mutation,
847
+ * including every human operation.
848
+ *
849
+ * @module dsh-daoing-memory/core
850
+ */
851
+ /** The mechanism defaults (005 mapping documented in the README). */
852
+ const DEFAULT_CORE_CONFIG = {
853
+ diaryExtractEvery: 8,
854
+ diaryExtractIntervalHours: 12,
855
+ recallTopK: 6,
856
+ injectionBudgetTokens: 1200,
857
+ challengeConsecutiveFails: 2,
858
+ challengeWindow: 6,
859
+ challengeWindowFailRate: .6,
860
+ familyLiveCap: 12,
861
+ complexityTokenGate: 4e3,
862
+ complexityStepGate: 6,
863
+ duplicateOverlapGate: .85,
864
+ recallFloorScore: .1,
865
+ shadowPassRate: .8,
866
+ humanFloorAlpha: 5,
867
+ humanFloorBeta: 2,
868
+ pinnedTrustFloor: .67,
869
+ candidateTrialTopK: 3,
870
+ candidateTrialFloorScore: .18,
871
+ consolidateEveryNew: 6,
872
+ consolidateIntervalHours: 24
873
+ };
874
+ /** Objective-evidence markers overriding a claimed experience-attributed failure. */
875
+ const ENVIRONMENT_EVIDENCE_PATTERN = /network|timeout|timed out|EPERM|EACCES|ECONNREFUSED|ENOTFOUND|429|503|quota|rate.?limit|unauthorized|dns|proxy|网络|超时|断网|权限|配额|限流|服务不可用|服务繁忙/i;
876
+ /**
877
+ * Source-authority priors for the ingest channel (006 §1.3): a vetted skill or
878
+ * document starts a candidate with more trust than an unreviewed note. The prior
879
+ * only affects trust once the candidate is verified — candidates never recall.
880
+ */
881
+ const INGEST_SOURCE_PRIOR = {
882
+ skill: {
883
+ alpha: 5,
884
+ beta: 2
885
+ },
886
+ document: {
887
+ alpha: 4,
888
+ beta: 2
889
+ },
890
+ book: {
891
+ alpha: 4,
892
+ beta: 2
893
+ },
894
+ conversation: {
895
+ alpha: 2,
896
+ beta: 2
897
+ },
898
+ note: {
899
+ alpha: 1,
900
+ beta: 2
901
+ },
902
+ other: {
903
+ alpha: 1,
904
+ beta: 2
905
+ }
906
+ };
907
+ /** Cosine-ish overlap of two token sets (for intra-batch dedup, 007). */
908
+ function tokenOverlap(a, b) {
909
+ if (a.size === 0 || b.size === 0) return 0;
910
+ let hits = 0;
911
+ for (const t of a) if (b.has(t)) hits += 1;
912
+ return hits / Math.sqrt(a.size * b.size);
913
+ }
914
+ /** The memory core: stateless over the injected store. */
915
+ var MemoryCore = class {
916
+ store;
917
+ config;
918
+ /** @param store - the durable SQLite store. */
919
+ /** @param config - resolved mechanism parameters. */
920
+ constructor(store, config) {
921
+ this.store = store;
922
+ this.config = config;
923
+ }
924
+ ledger(op, objectType, objectId, actor, payload, reason) {
925
+ this.store.appendLedger({
926
+ ts: Date.now(),
927
+ op,
928
+ objectType,
929
+ objectId,
930
+ actor,
931
+ payload: JSON.stringify(payload),
932
+ prevHash: this.store.ledgerHead(),
933
+ ...reason === void 0 ? {} : { reason }
934
+ });
935
+ }
936
+ /** 生: refine one completed trajectory into a candidate (dual gate). */
937
+ refine(request, actor) {
938
+ if (!((request.evidence.traceRef ?? "").trim() !== "" || (request.evidence.sessionRef ?? "").trim() !== "" || (request.evidence.note ?? "").trim() !== "")) return {
939
+ accepted: false,
940
+ reason: "rejected-evidence: every assertion needs an episodic evidence pointer (traceRef/sessionRef/note)"
941
+ };
942
+ if (request.kind === "negative" && (request.failureReason ?? "").trim() === "") return {
943
+ accepted: false,
944
+ reason: "rejected-schema: negative experiences must carry the confirmed failureReason"
945
+ };
946
+ const c = request.complexity;
947
+ if (!(request.humanMarked === true || c.hadFailure === true || (c.tokens ?? 0) >= this.config.complexityTokenGate || (c.steps ?? 0) >= this.config.complexityStepGate)) return {
948
+ accepted: false,
949
+ reason: `rejected-complexity: trajectory below both gates (tokens < ${String(this.config.complexityTokenGate)}, steps < ${String(this.config.complexityStepGate)}, no failure, not human-marked)`
950
+ };
951
+ const dedupTokens = new Set(tokenize([request.gist, ...request.situation].join(" ")));
952
+ const near = this.store.findNearDuplicate(dedupTokens, [
953
+ "candidate",
954
+ "live",
955
+ "challenged",
956
+ "archived",
957
+ "cold"
958
+ ]);
959
+ if (near !== void 0 && near.score >= this.config.duplicateOverlapGate) {
960
+ this.ledger("corroborate", "experience", near.snapshot.id, actor, {
961
+ family: request.family,
962
+ gist: request.gist,
963
+ score: near.score
964
+ });
965
+ return {
966
+ accepted: false,
967
+ reason: "rejected-information-gain: near-duplicate of an existing experience; corroborated it instead",
968
+ corroboratedId: near.snapshot.id
969
+ };
970
+ }
971
+ const now = Date.now();
972
+ const snapshot = {
973
+ id: randomUUID(),
974
+ revision: 1,
975
+ kind: request.kind,
976
+ source: "agent",
977
+ family: request.family,
978
+ gist: request.gist,
979
+ situation: [...request.situation],
980
+ path: [...request.path],
981
+ reasoning: request.reasoning,
982
+ limits: [...request.limits],
983
+ status: "candidate",
984
+ alpha: 0,
985
+ beta: 0,
986
+ samples: 0,
987
+ trust: .5,
988
+ weightedTrust: .5,
989
+ pinned: false,
990
+ tokensSaved: 0,
991
+ tokensSpent: (request.complexity.tokens ?? 0) / 10,
992
+ context: request.context ?? "",
993
+ verifiedCount: 0,
994
+ rejectCount: 0,
995
+ globalFlag: false,
996
+ evidence: request.evidence,
997
+ createdAt: now,
998
+ updatedAt: now
999
+ };
1000
+ if (request.failureReason !== void 0) snapshot.failureReason = request.failureReason;
1001
+ this.store.upsertExperience(snapshot);
1002
+ this.ledger("refine", "experience", snapshot.id, actor, {
1003
+ kind: snapshot.kind,
1004
+ family: snapshot.family,
1005
+ revision: 1
1006
+ });
1007
+ return {
1008
+ accepted: true,
1009
+ experience: snapshot
1010
+ };
1011
+ }
1012
+ /**
1013
+ * 摄取归一: source-agnostic intake. Every extracted draft becomes an earned
1014
+ * candidate carrying provenance (sourceType + sourceRef), the source-authority
1015
+ * prior, and the declared context scope. Candidates never recall until verified.
1016
+ */
1017
+ ingest(request, actor) {
1018
+ if ((request.sourceRef ?? "").trim() === "") throw new Error("memory: ingest requires a non-empty sourceRef provenance");
1019
+ const prior = INGEST_SOURCE_PRIOR[request.sourceType] ?? INGEST_SOURCE_PRIOR.other;
1020
+ const context = request.context ?? "";
1021
+ const accepted = [];
1022
+ const rejected = [];
1023
+ const acceptedDedup = [];
1024
+ for (const item of request.experiences) {
1025
+ if (item.kind === "negative" && (item.failureReason ?? "").trim() === "") {
1026
+ rejected.push({
1027
+ gist: item.gist,
1028
+ reason: "rejected-schema: negative experiences must carry the confirmed failureReason"
1029
+ });
1030
+ continue;
1031
+ }
1032
+ const dedupTokens = new Set(tokenize([item.gist, ...item.situation].join(" ")));
1033
+ const cross = this.store.findNearDuplicate(dedupTokens, [
1034
+ "candidate",
1035
+ "live",
1036
+ "challenged",
1037
+ "archived",
1038
+ "cold"
1039
+ ]);
1040
+ if (cross !== void 0 && cross.score >= this.config.duplicateOverlapGate) {
1041
+ this.ledger("corroborate", "experience", cross.snapshot.id, actor, {
1042
+ family: item.family,
1043
+ gist: item.gist,
1044
+ score: cross.score,
1045
+ via: "ingest"
1046
+ });
1047
+ rejected.push({
1048
+ gist: item.gist,
1049
+ reason: "rejected-information-gain: near-duplicate of an existing experience; corroborated it instead"
1050
+ });
1051
+ continue;
1052
+ }
1053
+ if (acceptedDedup.some((prev) => tokenOverlap(dedupTokens, prev) >= this.config.duplicateOverlapGate)) {
1054
+ rejected.push({
1055
+ gist: item.gist,
1056
+ reason: "rejected-information-gain: near-duplicate of another item in this same ingest batch"
1057
+ });
1058
+ continue;
1059
+ }
1060
+ const now = Date.now();
1061
+ const snapshot = {
1062
+ id: randomUUID(),
1063
+ revision: 1,
1064
+ kind: item.kind,
1065
+ source: "agent",
1066
+ family: item.family,
1067
+ gist: item.gist,
1068
+ situation: [...item.situation],
1069
+ path: [...item.path],
1070
+ reasoning: item.reasoning,
1071
+ limits: [...item.limits],
1072
+ status: "candidate",
1073
+ alpha: prior.alpha,
1074
+ beta: prior.beta,
1075
+ samples: prior.alpha + prior.beta,
1076
+ trust: (prior.alpha + 1) / (prior.alpha + prior.beta + 2),
1077
+ weightedTrust: (prior.alpha + 1) / (prior.alpha + prior.beta + 2),
1078
+ pinned: false,
1079
+ tokensSaved: 0,
1080
+ tokensSpent: 0,
1081
+ context,
1082
+ verifiedCount: 0,
1083
+ rejectCount: 0,
1084
+ globalFlag: false,
1085
+ evidence: { note: `${request.sourceType}:${request.sourceRef}` },
1086
+ createdAt: now,
1087
+ updatedAt: now
1088
+ };
1089
+ if (item.failureReason !== void 0) snapshot.failureReason = item.failureReason;
1090
+ this.store.upsertExperience(snapshot);
1091
+ this.ledger("ingest", "experience", snapshot.id, actor, {
1092
+ kind: snapshot.kind,
1093
+ family: snapshot.family,
1094
+ sourceType: request.sourceType,
1095
+ sourceRef: request.sourceRef,
1096
+ ...context === "" ? {} : { context },
1097
+ ...request.note === void 0 ? {} : { note: request.note }
1098
+ });
1099
+ accepted.push(snapshot);
1100
+ acceptedDedup.push(dedupTokens);
1101
+ }
1102
+ return {
1103
+ accepted,
1104
+ rejected,
1105
+ sourcePrior: prior
1106
+ };
1107
+ }
1108
+ /** 用: recall → scope → adjudicate → budget → inject, plus the candidate probe channel. */
1109
+ recall(request, actor) {
1110
+ const topK = request.topK ?? this.config.recallTopK;
1111
+ const budget = request.budgetTokens ?? this.config.injectionBudgetTokens;
1112
+ const context = request.context ?? "";
1113
+ const queryTokens = new Set(tokenize(request.situation));
1114
+ let candidateTrials = [];
1115
+ if (request.includeTrials !== false && queryTokens.size > 0) candidateTrials = this.store.recallCandidates(queryTokens, this.config.candidateTrialTopK * 2, {
1116
+ statuses: ["candidate"],
1117
+ context
1118
+ }).filter((m) => m.score >= this.config.candidateTrialFloorScore).slice(0, this.config.candidateTrialTopK).map((m) => ({
1119
+ experience: m.snapshot,
1120
+ score: m.score
1121
+ }));
1122
+ const candidates = queryTokens.size === 0 ? [] : this.store.recallCandidates(queryTokens, topK * 3, {
1123
+ statuses: request.deep === true ? ["live", "archived"] : ["live"],
1124
+ context
1125
+ });
1126
+ const adjudicated = [];
1127
+ for (const { snapshot, score } of candidates) {
1128
+ if (score < this.config.recallFloorScore) continue;
1129
+ const conflicts = [];
1130
+ for (const limit of snapshot.limits) {
1131
+ const limitTokens = tokenize(limit);
1132
+ if (limitTokens.length === 0) continue;
1133
+ if (limitTokens.filter((token) => queryTokens.has(token)).length / limitTokens.length >= .5) conflicts.push(limit);
1134
+ }
1135
+ const verdict = conflicts.length > 0 ? "reference" : score >= .45 ? "direct" : score >= .22 ? "reference" : "clue";
1136
+ adjudicated.push({
1137
+ experience: snapshot,
1138
+ score,
1139
+ verdict,
1140
+ conflicts
1141
+ });
1142
+ }
1143
+ if (adjudicated.length === 0) {
1144
+ const none = {
1145
+ items: [],
1146
+ none: true,
1147
+ reason: "no relevant experience in the library for this situation",
1148
+ omitted: 0,
1149
+ estimatedTokens: 0,
1150
+ candidateTrials,
1151
+ consolidationDue: this.consolidationDue().due
1152
+ };
1153
+ this.store.insertRecallEvent({
1154
+ id: randomUUID(),
1155
+ ts: Date.now(),
1156
+ situation: request.situation,
1157
+ injectedIds: [],
1158
+ none: true,
1159
+ context
1160
+ });
1161
+ return none;
1162
+ }
1163
+ const ranked = [...adjudicated].sort((a, b) => {
1164
+ const value = (item) => {
1165
+ const trust = item.experience.pinned ? Math.max(item.experience.weightedTrust, this.config.pinnedTrustFloor) : item.experience.weightedTrust;
1166
+ return item.score * trust;
1167
+ };
1168
+ return value(b) - value(a);
1169
+ });
1170
+ const items = [];
1171
+ let usedTokens = 0;
1172
+ let omitted = 0;
1173
+ for (const item of ranked) {
1174
+ const cost = estimateTokens(JSON.stringify(item.experience));
1175
+ if (usedTokens + cost > budget && items.length > 0) {
1176
+ omitted += 1;
1177
+ continue;
1178
+ }
1179
+ items.push(item);
1180
+ usedTokens += cost;
1181
+ }
1182
+ this.store.insertRecallEvent({
1183
+ id: randomUUID(),
1184
+ ts: Date.now(),
1185
+ situation: request.situation,
1186
+ injectedIds: items.map((item) => `${item.experience.id}@${String(item.experience.revision)}`),
1187
+ none: false,
1188
+ context
1189
+ });
1190
+ return {
1191
+ items,
1192
+ none: false,
1193
+ omitted,
1194
+ estimatedTokens: usedTokens,
1195
+ candidateTrials,
1196
+ consolidationDue: this.consolidationDue().due
1197
+ };
1198
+ }
1199
+ /** 用·验: one use outcome → Beta update, attribution, quarantine, gates. */
1200
+ report(request, actor) {
1201
+ const target = request.revision === void 0 ? this.store.getActiveRevision(request.id) : this.store.getExperience(request.id, request.revision);
1202
+ if (target === void 0) throw new Error(`memory: experience not found: ${request.id}`);
1203
+ const base = {
1204
+ snapshot: target,
1205
+ counted: "none",
1206
+ attributionApplied: "unknown",
1207
+ challenged: false,
1208
+ promoted: false,
1209
+ adopted: false
1210
+ };
1211
+ let attribution;
1212
+ let overrideNote;
1213
+ if (request.outcome === "success") attribution = "experience";
1214
+ else {
1215
+ const claimed = request.attribution ?? "unknown";
1216
+ const evidenceNote = request.evidence?.note ?? "";
1217
+ if (evidenceNote !== "" && ENVIRONMENT_EVIDENCE_PATTERN.test(evidenceNote)) {
1218
+ attribution = "environment";
1219
+ if (claimed !== "environment") overrideNote = "objective evidence indicates an environment failure; claim overridden";
1220
+ } else attribution = claimed;
1221
+ if (attribution === "experience" && evidenceNote.trim() === "") {
1222
+ if (target.status !== "candidate") {
1223
+ attribution = "unknown";
1224
+ overrideNote = "experience-attributed failure without evidence note: insufficient signal, not counted";
1225
+ }
1226
+ }
1227
+ }
1228
+ const counted = request.outcome === "success" ? "alpha" : attribution === "experience" ? "beta" : "none";
1229
+ if (!this.store.insertReport({
1230
+ id: randomUUID(),
1231
+ experienceId: target.id,
1232
+ revision: target.revision,
1233
+ outcome: request.outcome,
1234
+ attribution,
1235
+ counted,
1236
+ evidence: request.evidence,
1237
+ dedupeKey: request.dedupeKey,
1238
+ ts: Date.now()
1239
+ })) {
1240
+ if (overrideNote !== void 0) base.overrideNote = overrideNote;
1241
+ return base;
1242
+ }
1243
+ let next = {
1244
+ ...target,
1245
+ updatedAt: Date.now()
1246
+ };
1247
+ if (counted === "alpha") next = {
1248
+ ...next,
1249
+ alpha: next.alpha + 1,
1250
+ verifiedCount: next.verifiedCount + 1,
1251
+ lastVerifiedAt: Date.now()
1252
+ };
1253
+ else if (counted === "beta") {
1254
+ const isCandidateTrial = next.status === "candidate";
1255
+ next = {
1256
+ ...next,
1257
+ beta: next.beta + 1,
1258
+ rejectCount: isCandidateTrial ? next.rejectCount + 1 : next.rejectCount,
1259
+ lastVerifiedAt: Date.now()
1260
+ };
1261
+ }
1262
+ next = {
1263
+ ...next,
1264
+ tokensSaved: next.tokensSaved + (request.tokensSaved ?? 0),
1265
+ tokensSpent: next.tokensSpent + (request.tokensUsed ?? 0),
1266
+ samples: next.alpha + next.beta,
1267
+ trust: (next.alpha + 1) / (next.alpha + next.beta + 2)
1268
+ };
1269
+ next = {
1270
+ ...next,
1271
+ weightedTrust: this.store.weightedTrust(next.id, next.revision, Date.now())
1272
+ };
1273
+ if (request.outcome === "success" && attribution === "experience") {
1274
+ if (next.status === "candidate" && next.parentRevision !== void 0) {
1275
+ const adopted = {
1276
+ ...next,
1277
+ status: "live"
1278
+ };
1279
+ delete adopted.challengeReason;
1280
+ next = adopted;
1281
+ this.store.upsertExperience(next);
1282
+ this.supersedeParent(next, actor);
1283
+ this.ledger("adopt", "experience", next.id, actor, {
1284
+ revision: next.revision,
1285
+ via: "use"
1286
+ });
1287
+ this.enforceFamilyCap(next.family, actor);
1288
+ base.adopted = true;
1289
+ base.promoted = true;
1290
+ } else if (next.status === "candidate") {
1291
+ next = {
1292
+ ...next,
1293
+ status: "live"
1294
+ };
1295
+ this.ledger("promote", "experience", next.id, actor, {
1296
+ revision: next.revision,
1297
+ via: "use"
1298
+ });
1299
+ base.promoted = true;
1300
+ } else if (next.status === "archived") {
1301
+ next = {
1302
+ ...next,
1303
+ status: "live"
1304
+ };
1305
+ this.ledger("restore", "experience", next.id, actor, { revision: next.revision });
1306
+ base.promoted = true;
1307
+ }
1308
+ }
1309
+ if (next.status === "candidate" && counted === "beta") {
1310
+ const reason = "candidate trial failed; human re-release required";
1311
+ next = {
1312
+ ...next,
1313
+ status: "cold",
1314
+ challengeReason: reason
1315
+ };
1316
+ this.ledger("trial-fail", "experience", next.id, actor, { revision: next.revision }, reason);
1317
+ base.cooled = true;
1318
+ }
1319
+ if (next.status === "live" && counted === "beta" && this.shouldChallenge(next)) {
1320
+ const reason = "posterior pressure: repeated experience-attributed failures";
1321
+ next = {
1322
+ ...next,
1323
+ status: "challenged",
1324
+ challengeReason: reason
1325
+ };
1326
+ this.ledger("challenge", "experience", next.id, actor, { revision: next.revision }, reason);
1327
+ base.challenged = true;
1328
+ }
1329
+ this.store.upsertExperience(next);
1330
+ this.ledger("use", "experience", next.id, actor, {
1331
+ revision: next.revision,
1332
+ outcome: request.outcome,
1333
+ attribution,
1334
+ counted,
1335
+ ...request.dedupeKey === void 0 ? {} : { dedupeKey: request.dedupeKey }
1336
+ });
1337
+ base.snapshot = next;
1338
+ base.counted = counted;
1339
+ base.attributionApplied = attribution;
1340
+ if (overrideNote !== void 0) base.overrideNote = overrideNote;
1341
+ return base;
1342
+ }
1343
+ /** Windowed challenge rule: consecutive fails or window failure rate. */
1344
+ shouldChallenge(exp) {
1345
+ const reports = this.store.reportsFor(exp.id, exp.revision, this.config.challengeWindow);
1346
+ let consecutive = 0;
1347
+ for (const report of reports) if (report.outcome === "fail" && report.attribution === "experience") consecutive += 1;
1348
+ else break;
1349
+ if (consecutive >= this.config.challengeConsecutiveFails) return true;
1350
+ if (reports.length >= 3) {
1351
+ if (reports.filter((r) => r.outcome === "fail" && r.attribution === "experience").length / reports.length >= this.config.challengeWindowFailRate) return true;
1352
+ }
1353
+ return false;
1354
+ }
1355
+ /** Close the parent revision as superseded after a draft adoption. */
1356
+ supersedeParent(draft, actor) {
1357
+ if (draft.parentRevision === void 0) return;
1358
+ const parent = this.store.getExperience(draft.id, draft.parentRevision);
1359
+ if (parent === void 0) return;
1360
+ this.store.upsertExperience({
1361
+ ...parent,
1362
+ status: "superseded",
1363
+ updatedAt: Date.now()
1364
+ });
1365
+ this.ledger("supersede", "experience", parent.id, actor, {
1366
+ revision: parent.revision,
1367
+ by: draft.revision
1368
+ });
1369
+ this.enforceFamilyCap(draft.family, actor);
1370
+ }
1371
+ /** Capacity budget: archive the lowest economic value when a family overflows. */
1372
+ enforceFamilyCap(familyTag, actor) {
1373
+ const overflow = this.store.countFamilyActive(familyTag) - this.config.familyLiveCap;
1374
+ if (overflow <= 0) return;
1375
+ const live = this.store.listExperiences({
1376
+ status: "live",
1377
+ family: familyTag
1378
+ }).filter((exp) => !exp.pinned).sort((a, b) => a.tokensSaved - a.tokensSpent - (b.tokensSaved - b.tokensSpent));
1379
+ for (const exp of live.slice(0, overflow)) {
1380
+ this.store.upsertExperience({
1381
+ ...exp,
1382
+ status: "archived",
1383
+ updatedAt: Date.now()
1384
+ });
1385
+ this.ledger("archive", "experience", exp.id, actor, {
1386
+ revision: exp.revision,
1387
+ reason: "family capacity budget"
1388
+ });
1389
+ }
1390
+ }
1391
+ /** 修: propose a revised draft for a challenged experience. */
1392
+ revise(request, actor) {
1393
+ const current = this.store.getActiveRevision(request.id);
1394
+ if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
1395
+ if (current.status !== "challenged") throw new Error(`memory: only challenged experiences accept revisions (status=${current.status})`);
1396
+ const now = Date.now();
1397
+ const draft = {
1398
+ ...current,
1399
+ revision: current.revision + 1,
1400
+ status: "candidate",
1401
+ gist: request.gist ?? current.gist,
1402
+ situation: request.situation ?? current.situation,
1403
+ path: request.path ?? current.path,
1404
+ reasoning: request.reasoning ?? current.reasoning,
1405
+ limits: request.limits ?? current.limits,
1406
+ alpha: 0,
1407
+ beta: 0,
1408
+ samples: 0,
1409
+ trust: .5,
1410
+ weightedTrust: .5,
1411
+ verifiedCount: 0,
1412
+ rejectCount: 0,
1413
+ parentRevision: current.revision,
1414
+ createdAt: now,
1415
+ updatedAt: now
1416
+ };
1417
+ delete draft.lastVerifiedAt;
1418
+ delete draft.challengeReason;
1419
+ this.store.upsertExperience(draft);
1420
+ this.ledger("propose", "experience", draft.id, actor, { revision: draft.revision }, request.reason);
1421
+ return draft;
1422
+ }
1423
+ /** V1 controlled re-enactment: replay historical samples against a draft. */
1424
+ verifyShadow(request, actor) {
1425
+ const draft = this.store.getExperience(request.id, request.revision);
1426
+ if (draft === void 0) throw new Error(`memory: draft not found: ${request.id} v${String(request.revision)}`);
1427
+ if (draft.status !== "candidate") throw new Error(`memory: shadow replay verifies candidate drafts (status=${draft.status})`);
1428
+ if (request.samples.length === 0) return {
1429
+ passed: false,
1430
+ agreement: 0,
1431
+ reason: "no samples supplied"
1432
+ };
1433
+ const draftTokens = new Set(tokenize([
1434
+ draft.gist,
1435
+ ...draft.situation,
1436
+ ...draft.limits
1437
+ ].join(" ")));
1438
+ let matched = 0;
1439
+ for (const sample of request.samples) {
1440
+ const sampleTokens = new Set(tokenize(sample.situation));
1441
+ let hits = 0;
1442
+ for (const token of sampleTokens) if (draftTokens.has(token)) hits += 1;
1443
+ if ((sampleTokens.size === 0 ? 0 : hits / sampleTokens.size) >= this.config.recallFloorScore * 2 === (sample.expected === "success")) matched += 1;
1444
+ }
1445
+ const agreement = matched / request.samples.length;
1446
+ if (agreement < this.config.shadowPassRate) {
1447
+ this.ledger("shadow-fail", "experience", draft.id, actor, {
1448
+ revision: draft.revision,
1449
+ agreement
1450
+ });
1451
+ return {
1452
+ passed: false,
1453
+ agreement,
1454
+ reason: `agreement ${agreement.toFixed(2)} below pass rate ${String(this.config.shadowPassRate)}`
1455
+ };
1456
+ }
1457
+ const adopted = {
1458
+ ...draft,
1459
+ status: "live",
1460
+ lastVerifiedAt: Date.now(),
1461
+ updatedAt: Date.now()
1462
+ };
1463
+ this.store.upsertExperience(adopted);
1464
+ this.supersedeParent(adopted, actor);
1465
+ this.ledger("shadow-pass", "experience", adopted.id, actor, {
1466
+ revision: adopted.revision,
1467
+ agreement
1468
+ });
1469
+ return {
1470
+ passed: true,
1471
+ agreement,
1472
+ snapshot: adopted
1473
+ };
1474
+ }
1475
+ /** Roll the family back to a superseded revision. */
1476
+ rollback(request, actor) {
1477
+ const target = this.store.getExperience(request.id, request.toRevision);
1478
+ if (target === void 0) throw new Error(`memory: revision not found: ${request.id} v${String(request.toRevision)}`);
1479
+ if (target.status !== "superseded") throw new Error(`memory: only superseded revisions can be restored (status=${target.status})`);
1480
+ const current = this.store.getActiveRevision(request.id);
1481
+ if (current !== void 0) {
1482
+ this.store.upsertExperience({
1483
+ ...current,
1484
+ status: "superseded",
1485
+ updatedAt: Date.now()
1486
+ });
1487
+ this.ledger("supersede", "experience", current.id, actor, {
1488
+ revision: current.revision,
1489
+ by: request.toRevision
1490
+ });
1491
+ }
1492
+ const restored = {
1493
+ ...target,
1494
+ status: "live",
1495
+ updatedAt: Date.now()
1496
+ };
1497
+ this.store.upsertExperience(restored);
1498
+ this.ledger("rollback", "experience", restored.id, actor, { revision: restored.revision }, request.reason);
1499
+ return restored;
1500
+ }
1501
+ /** One experience revision. */
1502
+ get(id, revision) {
1503
+ return revision === void 0 ? this.store.getActiveRevision(id) : this.store.getExperience(id, revision);
1504
+ }
1505
+ /** Experience revisions by filter. */
1506
+ list(filter) {
1507
+ return this.store.listExperiences(filter);
1508
+ }
1509
+ /** Every revision of one family (the rollback picker's data). */
1510
+ family(id) {
1511
+ return this.store.getFamily(id);
1512
+ }
1513
+ /** 记: append one diary entry; signals when the extraction cadence is due. */
1514
+ appendDiary(request, actor) {
1515
+ const entry = {
1516
+ id: randomUUID(),
1517
+ ts: Date.now(),
1518
+ kind: request.kind,
1519
+ content: request.content,
1520
+ tags: request.tags ?? [],
1521
+ extracted: false
1522
+ };
1523
+ if (request.sessionRef !== void 0) entry.sessionRef = request.sessionRef;
1524
+ this.store.insertDiary(entry);
1525
+ this.ledger("diary", "diary", entry.id, actor, { kind: entry.kind });
1526
+ const pending = this.store.unextractedDiary();
1527
+ const intervalOk = Date.now() - this.store.lastExtractionTs() >= this.config.diaryExtractIntervalHours * 60 * 60 * 1e3;
1528
+ if (!(pending.length >= this.config.diaryExtractEvery && intervalOk)) return {
1529
+ entry,
1530
+ extractionDue: false
1531
+ };
1532
+ return {
1533
+ entry,
1534
+ extractionDue: true,
1535
+ pendingDiary: pending
1536
+ };
1537
+ }
1538
+ /** 上升通道: apply extracted facts over the pending diary window. */
1539
+ extract(request, actor, trigger = "manual") {
1540
+ const result = {
1541
+ applied: [],
1542
+ conflicts: [],
1543
+ rejected: [],
1544
+ appliedConcerns: 0
1545
+ };
1546
+ const now = Date.now();
1547
+ const consumedDiary = /* @__PURE__ */ new Set();
1548
+ for (const proposal of request.proposals) {
1549
+ const rejection = this.validateProposal(proposal);
1550
+ if (rejection !== void 0) {
1551
+ result.rejected.push({
1552
+ proposal,
1553
+ reason: rejection
1554
+ });
1555
+ continue;
1556
+ }
1557
+ for (const id of proposal.sourceDiaryIds) consumedDiary.add(id);
1558
+ const current = this.store.currentFact(proposal.category, proposal.factKey);
1559
+ if (current !== void 0 && current.value === proposal.value) {
1560
+ const merged = [...new Set([...current.sourceDiaryIds, ...proposal.sourceDiaryIds])];
1561
+ const corroborated = {
1562
+ ...current,
1563
+ corroboration: current.corroboration + 1,
1564
+ sourceDiaryIds: merged
1565
+ };
1566
+ this.store.updateFact(corroborated);
1567
+ this.ledger("fact-corroborate", "fact", corroborated.id, actor, {
1568
+ category: proposal.category,
1569
+ factKey: proposal.factKey
1570
+ });
1571
+ result.applied.push(corroborated);
1572
+ continue;
1573
+ }
1574
+ if (current !== void 0 && current.locked) {
1575
+ const conflict = {
1576
+ id: randomUUID(),
1577
+ category: proposal.category,
1578
+ factKey: proposal.factKey,
1579
+ value: proposal.value,
1580
+ origin: "extraction",
1581
+ sourceDiaryIds: [...proposal.sourceDiaryIds],
1582
+ corroboration: 1,
1583
+ validFrom: now,
1584
+ validTo: now,
1585
+ recordedAt: now,
1586
+ locked: false,
1587
+ deleted: false,
1588
+ conflictPending: true
1589
+ };
1590
+ this.store.insertFact(conflict);
1591
+ this.ledger("fact-conflict", "fact", conflict.id, actor, {
1592
+ category: proposal.category,
1593
+ factKey: proposal.factKey,
1594
+ against: current.id
1595
+ });
1596
+ result.conflicts.push(conflict);
1597
+ continue;
1598
+ }
1599
+ const version = {
1600
+ id: randomUUID(),
1601
+ category: proposal.category,
1602
+ factKey: proposal.factKey,
1603
+ value: proposal.value,
1604
+ origin: current === void 0 ? "extraction" : "supersede",
1605
+ sourceDiaryIds: [...proposal.sourceDiaryIds],
1606
+ corroboration: 1,
1607
+ validFrom: now,
1608
+ recordedAt: now,
1609
+ locked: false,
1610
+ deleted: false,
1611
+ conflictPending: false
1612
+ };
1613
+ if (current !== void 0) {
1614
+ this.store.updateFact({
1615
+ ...current,
1616
+ validTo: now,
1617
+ supersededBy: version.id
1618
+ });
1619
+ this.ledger("fact-supersede", "fact", current.id, actor, {
1620
+ category: proposal.category,
1621
+ factKey: proposal.factKey,
1622
+ by: version.id
1623
+ });
1624
+ }
1625
+ this.store.insertFact(version);
1626
+ this.ledger("fact-extract", "fact", version.id, actor, {
1627
+ category: proposal.category,
1628
+ factKey: proposal.factKey
1629
+ });
1630
+ result.applied.push(version);
1631
+ }
1632
+ for (const cp of request.concerns ?? []) {
1633
+ for (const id of cp.sourceDiaryIds) consumedDiary.add(id);
1634
+ if (cp.action === "new") {
1635
+ if ((cp.title ?? "").trim() === "") continue;
1636
+ const top = {
1637
+ id: randomUUID(),
1638
+ title: (cp.title ?? "").trim(),
1639
+ ...cp.background === void 0 || cp.background.trim() === "" ? {} : { background: cp.background.trim() },
1640
+ kind: cp.kind ?? "other",
1641
+ status: "ongoing",
1642
+ ts: now,
1643
+ sourceDiaryIds: [...cp.sourceDiaryIds],
1644
+ ...cp.context === void 0 ? {} : { context: cp.context },
1645
+ deleted: false
1646
+ };
1647
+ this.store.insertConcern(top);
1648
+ this.ledger("concern-new", "concern", top.id, actor, { kind: top.kind }, top.title);
1649
+ result.appliedConcerns += 1;
1650
+ } else if (cp.action === "mention") {
1651
+ if (cp.concernId === void 0 || (cp.title ?? "").trim() === "") continue;
1652
+ const mention = {
1653
+ id: randomUUID(),
1654
+ parentId: cp.concernId,
1655
+ title: (cp.title ?? "").trim(),
1656
+ ts: now,
1657
+ sourceDiaryIds: [...cp.sourceDiaryIds],
1658
+ deleted: false
1659
+ };
1660
+ this.store.insertConcern(mention);
1661
+ this.ledger("concern-mention", "concern", cp.concernId, actor, {}, mention.title);
1662
+ result.appliedConcerns += 1;
1663
+ } else if (cp.action === "status") {
1664
+ if (cp.concernId === void 0 || cp.status === void 0) continue;
1665
+ this.store.setConcernStatus(cp.concernId, cp.status);
1666
+ this.ledger("concern-status", "concern", cp.concernId, actor, { status: cp.status });
1667
+ result.appliedConcerns += 1;
1668
+ }
1669
+ }
1670
+ if (consumedDiary.size > 0) this.store.markDiaryExtracted([...consumedDiary]);
1671
+ const producedIds = [...result.applied, ...result.conflicts].map((fact) => fact.id);
1672
+ this.store.insertExtraction({
1673
+ id: randomUUID(),
1674
+ ts: now,
1675
+ trigger,
1676
+ summary: request.summary,
1677
+ producedFactIds: producedIds,
1678
+ diaryCount: consumedDiary.size
1679
+ });
1680
+ this.ledger("extract", "library", "diary-window", actor, {
1681
+ proposals: request.proposals.length,
1682
+ applied: result.applied.length,
1683
+ conflicts: result.conflicts.length,
1684
+ rejected: result.rejected.length
1685
+ }, request.summary);
1686
+ return result;
1687
+ }
1688
+ /** Validate one proposal's shape and source pointers. */
1689
+ validateProposal(proposal) {
1690
+ if (proposal.category.trim() === "" || proposal.factKey.trim() === "" || proposal.value.trim() === "") return "category, factKey and value are required";
1691
+ if (proposal.sourceDiaryIds.length === 0) return "sourceDiaryIds must point to at least one diary entry";
1692
+ for (const id of proposal.sourceDiaryIds) {
1693
+ const entry = this.store.getDiary(id);
1694
+ if (entry === void 0) return `unknown diary entry: ${id}`;
1695
+ if (entry.extracted) return `diary entry already extracted: ${id}`;
1696
+ }
1697
+ }
1698
+ /** Diary entries for the workbench timeline. */
1699
+ listDiary(limit, offset, onlyUnextracted) {
1700
+ return this.store.listDiary(limit, offset, onlyUnextracted);
1701
+ }
1702
+ /** Several diary entries by id (008 Path A: fact→diary provenance). */
1703
+ getDiaryByIds(ids) {
1704
+ return this.store.getDiaryByIds(ids);
1705
+ }
1706
+ /** Fact versions for the workbench (008 §3: server-side pagination). */
1707
+ listFacts(category, includeHistory, limit, offset) {
1708
+ return this.store.listFacts(category === void 0 ? { includeHistory } : {
1709
+ category,
1710
+ includeHistory
1711
+ }, limit, offset);
1712
+ }
1713
+ /** Count of facts matching the workbench filter (008 §3: pagination total). */
1714
+ listFactsCount(category, includeHistory) {
1715
+ return this.store.factFilteredCount(category === void 0 ? { includeHistory } : {
1716
+ category,
1717
+ includeHistory
1718
+ });
1719
+ }
1720
+ /** 关心事项 (007 §2 / 010 §D): top-level concerns + loop, kind/status filter + pagination. */
1721
+ listConcerns(kind, status, limit, offset) {
1722
+ const filter = {};
1723
+ if (kind !== void 0) filter.kind = kind;
1724
+ if (status !== void 0) filter.status = status;
1725
+ return this.store.listConcernTrees(filter, limit, offset);
1726
+ }
1727
+ /** Count of top-level concerns matching the workbench filter (010 §D: pagination total). */
1728
+ listConcernsCount(kind, status) {
1729
+ const filter = {};
1730
+ if (kind !== void 0) filter.kind = kind;
1731
+ if (status !== void 0) filter.status = status;
1732
+ return this.store.listConcernsCount(filter);
1733
+ }
1734
+ /**
1735
+ * 010 §F: a compact runtime snapshot for the AI's context — the profile (the
1736
+ * AI's perception of the user) plus still-open concern memos it may remind
1737
+ * the user about. Empty string when there is nothing yet.
1738
+ */
1739
+ profileSnapshot() {
1740
+ const facts = this.store.listFacts({ includeHistory: false }, 60, 0);
1741
+ const openConcerns = this.store.listConcernTrees({ status: "ongoing" }, 12, 0);
1742
+ if (facts.length === 0 && openConcerns.length === 0) return "";
1743
+ const lines = [];
1744
+ if (facts.length > 0) {
1745
+ lines.push("User profile (your perception of this user — collaborate accordingly):");
1746
+ for (const f of facts) lines.push(`- [${f.category}] ${f.factKey}: ${f.value}`);
1747
+ }
1748
+ if (openConcerns.length > 0) {
1749
+ lines.push("Open concern memos (the user's unclosed loops — remind when relevant, never fabricate closure):");
1750
+ for (const t of openConcerns) lines.push(`- (${t.concern.kind ?? "other"}) ${t.concern.title}`);
1751
+ }
1752
+ return lines.join("\n");
1753
+ }
1754
+ /** Extraction runs, newest first (008 §3: server-side pagination). */
1755
+ extractionLog(limit, offset) {
1756
+ return this.store.listExtractions(limit, offset);
1757
+ }
1758
+ /** Total extraction runs (008 §3: pagination total). */
1759
+ extractionLogCount() {
1760
+ return this.store.extractionCount();
1761
+ }
1762
+ /**
1763
+ * Whether a consolidation run is due. The period is measured as a DURATION
1764
+ * since the last consolidation (interval), not a fixed clock time: a run is
1765
+ * due once (a) enough NEW experiences have accumulated since the last run
1766
+ * AND (b) enough hours have elapsed since it.
1767
+ */
1768
+ consolidationDue() {
1769
+ const lastTs = this.store.lastConsolidationTs();
1770
+ const newSince = this.store.countExperiencesSince(lastTs);
1771
+ const hoursSince = lastTs === 0 ? Number.POSITIVE_INFINITY : (Date.now() - lastTs) / 36e5;
1772
+ const enoughNew = newSince >= this.config.consolidateEveryNew;
1773
+ const intervalPassed = hoursSince >= this.config.consolidateIntervalHours;
1774
+ return {
1775
+ due: enoughNew && intervalPassed,
1776
+ lastTs,
1777
+ newSince,
1778
+ hoursSince
1779
+ };
1780
+ }
1781
+ /**
1782
+ * Apply a consolidation run: for each merge, create one consolidated
1783
+ * experience and archive the sources (they leave recall but stay recoverable
1784
+ * — consolidation never hard-deletes). Every step is ledgered.
1785
+ */
1786
+ consolidate(request, actor) {
1787
+ const now = Date.now();
1788
+ const result = {
1789
+ consolidated: 0,
1790
+ archived: 0,
1791
+ skipped: []
1792
+ };
1793
+ for (const merge of request.merges) {
1794
+ const sources = merge.sourceIds.map((id) => this.store.getActiveRevision(id)).filter((s) => s !== void 0);
1795
+ if (sources.length < 2) {
1796
+ result.skipped.push({
1797
+ sourceIds: merge.sourceIds,
1798
+ reason: "至少需要 2 条仍有效的来源经验"
1799
+ });
1800
+ continue;
1801
+ }
1802
+ if (merge.gist.trim() === "" || merge.reasoning.trim() === "") {
1803
+ result.skipped.push({
1804
+ sourceIds: merge.sourceIds,
1805
+ reason: "合并后的 gist/reasoning 不能为空"
1806
+ });
1807
+ continue;
1808
+ }
1809
+ const allLive = sources.every((s) => s.status === "live");
1810
+ const alpha = sources.reduce((sum, s) => sum + s.alpha, 0);
1811
+ const beta = sources.reduce((sum, s) => sum + s.beta, 0);
1812
+ const verifiedCount = sources.reduce((sum, s) => sum + (s.verifiedCount ?? 0), 0);
1813
+ const rejectCount = sources.reduce((sum, s) => sum + (s.rejectCount ?? 0), 0);
1814
+ const lastVerifiedAt = sources.reduce((max, s) => s.lastVerifiedAt !== void 0 && (max === void 0 || s.lastVerifiedAt > max) ? s.lastVerifiedAt : max, void 0);
1815
+ const consolidated = {
1816
+ id: randomUUID(),
1817
+ revision: 1,
1818
+ kind: merge.kind,
1819
+ source: "agent",
1820
+ family: merge.family,
1821
+ gist: merge.gist.trim(),
1822
+ situation: merge.situation,
1823
+ path: merge.path.map((action, index) => ({
1824
+ action,
1825
+ order: index + 1
1826
+ })),
1827
+ reasoning: merge.reasoning.trim(),
1828
+ limits: merge.limits,
1829
+ status: allLive ? "live" : "candidate",
1830
+ alpha,
1831
+ beta,
1832
+ samples: alpha + beta,
1833
+ trust: (alpha + 1) / (alpha + beta + 2),
1834
+ weightedTrust: (alpha + 1) / (alpha + beta + 2),
1835
+ ...lastVerifiedAt === void 0 ? {} : { lastVerifiedAt },
1836
+ pinned: false,
1837
+ tokensSaved: 0,
1838
+ tokensSpent: 0,
1839
+ evidence: { note: `consolidated from ${String(sources.length)} experiences: ${sources.map((s) => s.id.slice(0, 8)).join(",")}` },
1840
+ context: "",
1841
+ verifiedCount,
1842
+ rejectCount,
1843
+ globalFlag: false,
1844
+ createdAt: now,
1845
+ updatedAt: now
1846
+ };
1847
+ this.store.upsertExperience(consolidated);
1848
+ this.store.archiveExperienceIds(merge.sourceIds, now);
1849
+ this.store.recordConsolidation({
1850
+ id: randomUUID(),
1851
+ ts: now,
1852
+ mergedIds: merge.sourceIds,
1853
+ producedId: consolidated.id,
1854
+ note: merge.note ?? request.note ?? ""
1855
+ });
1856
+ this.ledger("consolidate", "experience", consolidated.id, actor, {
1857
+ mergedFrom: merge.sourceIds,
1858
+ gist: merge.gist.trim(),
1859
+ status: consolidated.status
1860
+ });
1861
+ result.consolidated += 1;
1862
+ result.archived += sources.length;
1863
+ }
1864
+ return result;
1865
+ }
1866
+ /** Human pin/unpin: pinned cards keep the trust floor and escape budgets. */
1867
+ humanPin(request, actor) {
1868
+ const current = this.store.getActiveRevision(request.id);
1869
+ if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
1870
+ const next = {
1871
+ ...current,
1872
+ pinned: request.pinned,
1873
+ updatedAt: Date.now()
1874
+ };
1875
+ this.store.upsertExperience(next);
1876
+ this.ledger(request.pinned ? "pin" : "unpin", "experience", next.id, actor, { revision: next.revision }, request.reason);
1877
+ return next;
1878
+ }
1879
+ /** Human delete: tombstone the family; the ledger keeps the fingerprint. */
1880
+ humanDeleteExperience(request, actor) {
1881
+ const familyRows = this.store.getFamily(request.id);
1882
+ if (familyRows.length === 0) throw new Error(`memory: experience not found: ${request.id}`);
1883
+ const head = familyRows[familyRows.length - 1];
1884
+ this.store.deleteFamily(request.id);
1885
+ this.ledger("delete", "experience", request.id, actor, {
1886
+ gist: head.gist,
1887
+ family: head.family,
1888
+ revisions: familyRows.length
1889
+ }, request.reason);
1890
+ }
1891
+ /** Human edit: rewrite fields of the active revision in place. */
1892
+ humanEditExperience(request, actor) {
1893
+ const current = this.store.getActiveRevision(request.id);
1894
+ if (current === void 0) throw new Error(`memory: experience not found: ${request.id}`);
1895
+ const next = {
1896
+ ...current,
1897
+ gist: request.gist ?? current.gist,
1898
+ situation: request.situation ?? current.situation,
1899
+ path: request.path ?? current.path,
1900
+ reasoning: request.reasoning ?? current.reasoning,
1901
+ limits: request.limits ?? current.limits,
1902
+ family: request.family ?? current.family,
1903
+ context: request.context ?? current.context,
1904
+ globalFlag: request.globalFlag ?? current.globalFlag,
1905
+ updatedAt: Date.now()
1906
+ };
1907
+ this.store.upsertExperience(next);
1908
+ this.ledger("edit", "experience", next.id, actor, { revision: next.revision }, request.reason);
1909
+ return next;
1910
+ }
1911
+ /** Human injection: fixed format, source=human, trust floor, directly live. */
1912
+ humanAddExperience(request, actor) {
1913
+ const now = Date.now();
1914
+ const snapshot = {
1915
+ id: randomUUID(),
1916
+ revision: 1,
1917
+ kind: request.kind,
1918
+ source: "human",
1919
+ family: request.family,
1920
+ gist: request.gist,
1921
+ situation: [...request.situation],
1922
+ path: [...request.path],
1923
+ reasoning: request.reasoning,
1924
+ limits: [...request.limits],
1925
+ status: "live",
1926
+ alpha: this.config.humanFloorAlpha,
1927
+ beta: this.config.humanFloorBeta,
1928
+ samples: this.config.humanFloorAlpha + this.config.humanFloorBeta,
1929
+ trust: (this.config.humanFloorAlpha + 1) / (this.config.humanFloorAlpha + this.config.humanFloorBeta + 2),
1930
+ weightedTrust: (this.config.humanFloorAlpha + 1) / (this.config.humanFloorAlpha + this.config.humanFloorBeta + 2),
1931
+ lastVerifiedAt: now,
1932
+ pinned: false,
1933
+ tokensSaved: 0,
1934
+ tokensSpent: 0,
1935
+ context: request.context ?? "",
1936
+ verifiedCount: 0,
1937
+ rejectCount: 0,
1938
+ globalFlag: false,
1939
+ createdAt: now,
1940
+ updatedAt: now
1941
+ };
1942
+ if (request.failureReason !== void 0) snapshot.failureReason = request.failureReason;
1943
+ this.store.upsertExperience(snapshot);
1944
+ this.ledger("add", "experience", snapshot.id, actor, {
1945
+ kind: snapshot.kind,
1946
+ family: snapshot.family,
1947
+ trustFloor: snapshot.trust
1948
+ }, request.reason);
1949
+ return snapshot;
1950
+ }
1951
+ /** Human authority (V2): promote a candidate straight to live. */
1952
+ humanPromote(id, reason, actor) {
1953
+ const current = this.store.getActiveRevision(id);
1954
+ if (current === void 0) throw new Error(`memory: experience not found: ${id}`);
1955
+ if (current.status !== "candidate") throw new Error(`memory: only candidates accept human promotion (status=${current.status})`);
1956
+ const next = {
1957
+ ...current,
1958
+ status: "live",
1959
+ lastVerifiedAt: Date.now(),
1960
+ updatedAt: Date.now()
1961
+ };
1962
+ this.store.upsertExperience(next);
1963
+ if (next.parentRevision !== void 0) this.supersedeParent(next, actor);
1964
+ this.ledger("human-promote", "experience", next.id, actor, { revision: next.revision }, reason);
1965
+ return next;
1966
+ }
1967
+ /** Human re-release (006 §3.2): move a cold-palace revision back to candidate. */
1968
+ humanReleaseCold(request, actor) {
1969
+ const cold = [...this.store.getFamily(request.id)].reverse().find((rev) => rev.status === "cold");
1970
+ if (cold === void 0) throw new Error(`memory: no cold revision to re-release for ${request.id}`);
1971
+ const next = {
1972
+ ...cold,
1973
+ status: "candidate",
1974
+ updatedAt: Date.now()
1975
+ };
1976
+ delete next.challengeReason;
1977
+ this.store.upsertExperience(next);
1978
+ this.ledger("release-cold", "experience", next.id, actor, { revision: next.revision }, request.reason);
1979
+ return next;
1980
+ }
1981
+ /** Human acknowledgement of a pending diary entry: reviewed, no fact extracted. */
1982
+ humanAckDiary(request, actor) {
1983
+ const entry = this.store.getDiary(request.diaryId);
1984
+ if (entry === void 0) throw new Error(`memory: unknown diary entry: ${request.diaryId}`);
1985
+ if (entry.extracted) throw new Error(`memory: diary entry already extracted: ${request.diaryId}`);
1986
+ this.store.markDiaryExtracted([request.diaryId]);
1987
+ this.ledger("diary-ack", "diary", request.diaryId, actor, {}, request.reason);
1988
+ return {
1989
+ ...entry,
1990
+ extracted: true
1991
+ };
1992
+ }
1993
+ /** Human lifecycle change of a top-level concern (007 §2.4, audited). */
1994
+ humanSetConcernStatus(request, actor) {
1995
+ this.store.setConcernStatus(request.id, request.status);
1996
+ this.ledger("concern-status", "concern", request.id, actor, {
1997
+ status: request.status,
1998
+ via: "human"
1999
+ }, request.reason);
2000
+ }
2001
+ /** Human delete of a concern subtree (007 §2.4, tombstone, audited). */
2002
+ humanDeleteConcern(request, actor) {
2003
+ this.store.deleteConcernSubtree(request.id);
2004
+ this.ledger("concern-delete", "concern", request.id, actor, { via: "human" }, request.reason);
2005
+ }
2006
+ /** Human fact add: origin=human, locked by default. */
2007
+ humanAddFact(request, actor) {
2008
+ const now = Date.now();
2009
+ const current = this.store.currentFact(request.category, request.factKey);
2010
+ const version = {
2011
+ id: randomUUID(),
2012
+ category: request.category,
2013
+ factKey: request.factKey,
2014
+ value: request.value,
2015
+ origin: "human",
2016
+ sourceDiaryIds: [],
2017
+ corroboration: 1,
2018
+ validFrom: now,
2019
+ recordedAt: now,
2020
+ locked: request.locked ?? true,
2021
+ deleted: false,
2022
+ conflictPending: false
2023
+ };
2024
+ if (current !== void 0) {
2025
+ this.store.updateFact({
2026
+ ...current,
2027
+ validTo: now,
2028
+ supersededBy: version.id
2029
+ });
2030
+ this.ledger("fact-supersede", "fact", current.id, actor, { by: version.id }, request.reason);
2031
+ }
2032
+ this.store.insertFact(version);
2033
+ this.ledger("fact-add", "fact", version.id, actor, {
2034
+ category: request.category,
2035
+ factKey: request.factKey
2036
+ }, request.reason);
2037
+ return version;
2038
+ }
2039
+ /** Human fact edit: supersedes the current version with a locked one. */
2040
+ humanEditFact(request, actor) {
2041
+ const current = this.store.getFact(request.factId);
2042
+ if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
2043
+ if (current.validTo !== void 0 || current.deleted) throw new Error("memory: only the current fact version can be edited");
2044
+ const now = Date.now();
2045
+ const version = {
2046
+ ...current,
2047
+ id: randomUUID(),
2048
+ value: request.value,
2049
+ origin: "human",
2050
+ validFrom: now,
2051
+ recordedAt: now,
2052
+ locked: true,
2053
+ conflictPending: false,
2054
+ corroboration: current.corroboration
2055
+ };
2056
+ delete version.validTo;
2057
+ delete version.supersededBy;
2058
+ this.store.updateFact({
2059
+ ...current,
2060
+ validTo: now,
2061
+ supersededBy: version.id
2062
+ });
2063
+ this.store.insertFact(version);
2064
+ this.ledger("fact-edit", "fact", version.id, actor, {
2065
+ category: version.category,
2066
+ factKey: version.factKey,
2067
+ previous: current.id
2068
+ }, request.reason);
2069
+ return version;
2070
+ }
2071
+ /** Human fact delete: tombstone the current version. */
2072
+ humanDeleteFact(request, actor) {
2073
+ const current = this.store.getFact(request.factId);
2074
+ if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
2075
+ if (current.validTo !== void 0 || current.deleted) throw new Error("memory: only the current fact version can be deleted");
2076
+ this.store.updateFact({
2077
+ ...current,
2078
+ validTo: Date.now(),
2079
+ deleted: true
2080
+ });
2081
+ this.ledger("fact-delete", "fact", current.id, actor, {
2082
+ category: current.category,
2083
+ factKey: current.factKey,
2084
+ value: current.value
2085
+ }, request.reason);
2086
+ }
2087
+ /** Human fact confirmation: lock or unlock the current version. */
2088
+ humanConfirmFact(request, actor) {
2089
+ const current = this.store.getFact(request.factId);
2090
+ if (current === void 0) throw new Error(`memory: fact not found: ${request.factId}`);
2091
+ const next = {
2092
+ ...current,
2093
+ locked: request.locked,
2094
+ conflictPending: false
2095
+ };
2096
+ this.store.updateFact(next);
2097
+ this.ledger(request.locked ? "fact-lock" : "fact-unlock", "fact", next.id, actor, {
2098
+ category: next.category,
2099
+ factKey: next.factKey
2100
+ }, request.reason);
2101
+ return next;
2102
+ }
2103
+ /** Human rollback (same gate, human actor). */
2104
+ humanRollback(request, actor) {
2105
+ return this.rollback(request, actor);
2106
+ }
2107
+ /** Aggregated library statistics. */
2108
+ stats() {
2109
+ const all = this.store.listExperiences({});
2110
+ const byStatus = {
2111
+ candidate: 0,
2112
+ live: 0,
2113
+ challenged: 0,
2114
+ superseded: 0,
2115
+ archived: 0,
2116
+ cold: 0
2117
+ };
2118
+ const byKind = {
2119
+ positive: 0,
2120
+ negative: 0
2121
+ };
2122
+ let trustSum = 0;
2123
+ let pinned = 0;
2124
+ for (const exp of all) {
2125
+ byStatus[exp.status] += 1;
2126
+ byKind[exp.kind] += 1;
2127
+ trustSum += exp.trust;
2128
+ if (exp.pinned) pinned += 1;
2129
+ }
2130
+ const diary = this.store.diaryCounts();
2131
+ const recall = this.store.recallCounts();
2132
+ const reports = this.store.allReports().filter((r) => r.counted !== "none").length;
2133
+ return {
2134
+ experiences: {
2135
+ total: all.length,
2136
+ byStatus,
2137
+ byKind,
2138
+ pinned,
2139
+ avgTrust: all.length === 0 ? 0 : trustSum / all.length
2140
+ },
2141
+ facts: this.store.factCounts(),
2142
+ diary: {
2143
+ total: diary.total,
2144
+ unextracted: diary.unextracted,
2145
+ extractions: this.store.extractionCount()
2146
+ },
2147
+ ledgerBlocks: this.store.ledgerCount(),
2148
+ recall: {
2149
+ events: recall.events,
2150
+ negative: recall.negative,
2151
+ reportsAfterRecall: reports
2152
+ },
2153
+ consolidation: {
2154
+ lastTs: this.store.lastConsolidationTs(),
2155
+ due: this.consolidationDue().due,
2156
+ newSince: this.store.countExperiencesSince(this.store.lastConsolidationTs())
2157
+ }
2158
+ };
2159
+ }
2160
+ /** Verify the ledger hash chain end to end. */
2161
+ verifyLedger() {
2162
+ const blocks = this.store.ledgerAll();
2163
+ let prev = "";
2164
+ for (const block of blocks) {
2165
+ const expected = blockHash(block.ts, block.op, block.objectType, block.objectId, block.actor, block.payload, prev);
2166
+ if (block.prevHash !== prev || block.hash !== expected) return {
2167
+ ok: false,
2168
+ checked: blocks.length,
2169
+ brokenAt: block.seq
2170
+ };
2171
+ prev = block.hash;
2172
+ }
2173
+ return {
2174
+ ok: true,
2175
+ checked: blocks.length
2176
+ };
2177
+ }
2178
+ /** Ledger query for the workbench and the model tool. */
2179
+ ledgerQuery(request) {
2180
+ return this.store.ledgerQuery({
2181
+ ...request.objectType === void 0 ? {} : { objectType: request.objectType },
2182
+ ...request.objectId === void 0 ? {} : { objectId: request.objectId },
2183
+ ...request.op === void 0 ? {} : { op: request.op },
2184
+ ...request.offset === void 0 ? {} : { offset: request.offset },
2185
+ ...request.seqFrom === void 0 ? {} : { seqFrom: request.seqFrom },
2186
+ ...request.seqTo === void 0 ? {} : { seqTo: request.seqTo },
2187
+ limit: request.limit ?? 50
2188
+ });
2189
+ }
2190
+ /** Count ledger blocks matching a filter (007 §2 pagination). */
2191
+ ledgerQueryCount(request) {
2192
+ return this.store.ledgerQueryCount({
2193
+ ...request.objectType === void 0 ? {} : { objectType: request.objectType },
2194
+ ...request.objectId === void 0 ? {} : { objectId: request.objectId },
2195
+ ...request.op === void 0 ? {} : { op: request.op },
2196
+ ...request.seqFrom === void 0 ? {} : { seqFrom: request.seqFrom },
2197
+ ...request.seqTo === void 0 ? {} : { seqTo: request.seqTo }
2198
+ });
2199
+ }
2200
+ /** Full library export (experiments + migration). */
2201
+ exportLibrary() {
2202
+ const diaryEntries = this.store.listDiary(1e6, 0, false);
2203
+ return {
2204
+ exportedAt: Date.now(),
2205
+ schemaVersion: 1,
2206
+ experiences: this.store.listExperiences({}),
2207
+ reports: this.store.allReports().map((report) => ({
2208
+ id: report.id,
2209
+ experienceId: report.experienceId,
2210
+ revision: report.revision,
2211
+ outcome: report.outcome,
2212
+ attribution: report.attribution,
2213
+ counted: report.counted,
2214
+ ...report.evidence === void 0 ? {} : { evidence: report.evidence },
2215
+ ...report.dedupeKey === void 0 ? {} : { dedupeKey: report.dedupeKey },
2216
+ ts: report.ts
2217
+ })),
2218
+ diary: diaryEntries,
2219
+ facts: this.store.allFacts(),
2220
+ extractions: this.store.listExtractions(1e6, 0),
2221
+ recalls: this.store.allRecallEvents().map((event) => ({
2222
+ id: event.id,
2223
+ ts: event.ts,
2224
+ situation: event.situation,
2225
+ injectedIds: event.injectedIds,
2226
+ none: event.none
2227
+ })),
2228
+ ledger: this.store.ledgerAll()
2229
+ };
2230
+ }
2231
+ };
2232
+ //#endregion
2233
+ //#region lib/types/service.js
2234
+ /**
2235
+ * Memory library Typert Remote service: the wire face over MemoryCore.
2236
+ * Every method takes the calling Agent first (Typert wire identity); the
2237
+ * library itself is process-global — one memory shared by every session.
2238
+ * Human operations carry an audited reason and land in the ledger.
2239
+ * @module dsh-daoing-memory/service
2240
+ */
2241
+ var __runInitializers = function(thisArg, initializers, value) {
2242
+ var useValue = arguments.length > 2;
2243
+ for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
2244
+ return useValue ? value : void 0;
2245
+ };
2246
+ var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
2247
+ function accept(f) {
2248
+ if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
2249
+ return f;
2250
+ }
2251
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
2252
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
2253
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
2254
+ var _, done = false;
2255
+ for (var i = decorators.length - 1; i >= 0; i--) {
2256
+ var context = {};
2257
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
2258
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
2259
+ context.addInitializer = function(f) {
2260
+ if (done) throw new TypeError("Cannot add initializers after decoration has completed");
2261
+ extraInitializers.push(accept(f || null));
2262
+ };
2263
+ var result = (0, decorators[i])(kind === "accessor" ? {
2264
+ get: descriptor.get,
2265
+ set: descriptor.set
2266
+ } : descriptor[key], context);
2267
+ if (kind === "accessor") {
2268
+ if (result === void 0) continue;
2269
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
2270
+ if (_ = accept(result.get)) descriptor.get = _;
2271
+ if (_ = accept(result.set)) descriptor.set = _;
2272
+ if (_ = accept(result.init)) initializers.unshift(_);
2273
+ } else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
2274
+ else descriptor[key] = _;
2275
+ }
2276
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
2277
+ done = true;
2278
+ };
2279
+ /** Derive the ledger actor label from the wire identity. */
2280
+ function actorOf(agent) {
2281
+ const sessionId = agent.session?.id;
2282
+ return sessionId === void 0 ? "agent" : `agent:${sessionId}`;
2283
+ }
2284
+ /**
2285
+ * Remote face of the memory library. All methods delegate to the core; the
2286
+ * core carries the 生·用·修·记 mechanism semantics.
2287
+ */
2288
+ let MemoryService = (() => {
2289
+ let _classSuper = TypertRemoteService;
2290
+ let _instanceExtraInitializers = [];
2291
+ let _refine_decorators;
2292
+ let _recall_decorators;
2293
+ let _report_decorators;
2294
+ let _ingest_decorators;
2295
+ let _revise_decorators;
2296
+ let _verifyShadow_decorators;
2297
+ let _rollback_decorators;
2298
+ let _get_decorators;
2299
+ let _list_decorators;
2300
+ let _family_decorators;
2301
+ let _appendDiary_decorators;
2302
+ let _extract_decorators;
2303
+ let _listDiary_decorators;
2304
+ let _getDiaryByIds_decorators;
2305
+ let _listFacts_decorators;
2306
+ let _listFactsCount_decorators;
2307
+ let _listConcerns_decorators;
2308
+ let _listConcernsCount_decorators;
2309
+ let _extractionLog_decorators;
2310
+ let _extractionLogCount_decorators;
2311
+ let _consolidate_decorators;
2312
+ let _consolidationDue_decorators;
2313
+ let _ledgerQuery_decorators;
2314
+ let _ledgerQueryCount_decorators;
2315
+ let _verifyLedger_decorators;
2316
+ let _stats_decorators;
2317
+ let _workbenchInfo_decorators;
2318
+ let _exportLibrary_decorators;
2319
+ let _humanPin_decorators;
2320
+ let _humanDeleteExperience_decorators;
2321
+ let _humanEditExperience_decorators;
2322
+ let _humanAddExperience_decorators;
2323
+ let _humanPromote_decorators;
2324
+ let _humanReleaseCold_decorators;
2325
+ let _humanRollback_decorators;
2326
+ let _humanAddFact_decorators;
2327
+ let _humanEditFact_decorators;
2328
+ let _humanDeleteFact_decorators;
2329
+ let _humanConfirmFact_decorators;
2330
+ let _humanAckDiary_decorators;
2331
+ let _humanSetConcernStatus_decorators;
2332
+ let _humanDeleteConcern_decorators;
2333
+ return class MemoryService extends _classSuper {
2334
+ static {
2335
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
2336
+ _refine_decorators = [Remote("refine")];
2337
+ _recall_decorators = [Remote("recall")];
2338
+ _report_decorators = [Remote("report")];
2339
+ _ingest_decorators = [Remote("ingest")];
2340
+ _revise_decorators = [Remote("revise")];
2341
+ _verifyShadow_decorators = [Remote("verifyShadow")];
2342
+ _rollback_decorators = [Remote("rollback")];
2343
+ _get_decorators = [Remote("get")];
2344
+ _list_decorators = [Remote("list")];
2345
+ _family_decorators = [Remote("family")];
2346
+ _appendDiary_decorators = [Remote("appendDiary")];
2347
+ _extract_decorators = [Remote("extract")];
2348
+ _listDiary_decorators = [Remote("listDiary")];
2349
+ _getDiaryByIds_decorators = [Remote("getDiaryByIds")];
2350
+ _listFacts_decorators = [Remote("listFacts")];
2351
+ _listFactsCount_decorators = [Remote("listFactsCount")];
2352
+ _listConcerns_decorators = [Remote("listConcerns")];
2353
+ _listConcernsCount_decorators = [Remote("listConcernsCount")];
2354
+ _extractionLog_decorators = [Remote("extractionLog")];
2355
+ _extractionLogCount_decorators = [Remote("extractionLogCount")];
2356
+ _consolidate_decorators = [Remote("consolidate")];
2357
+ _consolidationDue_decorators = [Remote("consolidationDue")];
2358
+ _ledgerQuery_decorators = [Remote("ledgerQuery")];
2359
+ _ledgerQueryCount_decorators = [Remote("ledgerQueryCount")];
2360
+ _verifyLedger_decorators = [Remote("verifyLedger")];
2361
+ _stats_decorators = [Remote("stats")];
2362
+ _workbenchInfo_decorators = [Remote("workbenchInfo")];
2363
+ _exportLibrary_decorators = [Remote("exportLibrary")];
2364
+ _humanPin_decorators = [Remote("humanPin")];
2365
+ _humanDeleteExperience_decorators = [Remote("humanDeleteExperience")];
2366
+ _humanEditExperience_decorators = [Remote("humanEditExperience")];
2367
+ _humanAddExperience_decorators = [Remote("humanAddExperience")];
2368
+ _humanPromote_decorators = [Remote("humanPromote")];
2369
+ _humanReleaseCold_decorators = [Remote("humanReleaseCold")];
2370
+ _humanRollback_decorators = [Remote("humanRollback")];
2371
+ _humanAddFact_decorators = [Remote("humanAddFact")];
2372
+ _humanEditFact_decorators = [Remote("humanEditFact")];
2373
+ _humanDeleteFact_decorators = [Remote("humanDeleteFact")];
2374
+ _humanConfirmFact_decorators = [Remote("humanConfirmFact")];
2375
+ _humanAckDiary_decorators = [Remote("humanAckDiary")];
2376
+ _humanSetConcernStatus_decorators = [Remote("humanSetConcernStatus")];
2377
+ _humanDeleteConcern_decorators = [Remote("humanDeleteConcern")];
2378
+ __esDecorate(this, null, _refine_decorators, {
2379
+ kind: "method",
2380
+ name: "refine",
2381
+ static: false,
2382
+ private: false,
2383
+ access: {
2384
+ has: (obj) => "refine" in obj,
2385
+ get: (obj) => obj.refine
2386
+ },
2387
+ metadata: _metadata
2388
+ }, null, _instanceExtraInitializers);
2389
+ __esDecorate(this, null, _recall_decorators, {
2390
+ kind: "method",
2391
+ name: "recall",
2392
+ static: false,
2393
+ private: false,
2394
+ access: {
2395
+ has: (obj) => "recall" in obj,
2396
+ get: (obj) => obj.recall
2397
+ },
2398
+ metadata: _metadata
2399
+ }, null, _instanceExtraInitializers);
2400
+ __esDecorate(this, null, _report_decorators, {
2401
+ kind: "method",
2402
+ name: "report",
2403
+ static: false,
2404
+ private: false,
2405
+ access: {
2406
+ has: (obj) => "report" in obj,
2407
+ get: (obj) => obj.report
2408
+ },
2409
+ metadata: _metadata
2410
+ }, null, _instanceExtraInitializers);
2411
+ __esDecorate(this, null, _ingest_decorators, {
2412
+ kind: "method",
2413
+ name: "ingest",
2414
+ static: false,
2415
+ private: false,
2416
+ access: {
2417
+ has: (obj) => "ingest" in obj,
2418
+ get: (obj) => obj.ingest
2419
+ },
2420
+ metadata: _metadata
2421
+ }, null, _instanceExtraInitializers);
2422
+ __esDecorate(this, null, _revise_decorators, {
2423
+ kind: "method",
2424
+ name: "revise",
2425
+ static: false,
2426
+ private: false,
2427
+ access: {
2428
+ has: (obj) => "revise" in obj,
2429
+ get: (obj) => obj.revise
2430
+ },
2431
+ metadata: _metadata
2432
+ }, null, _instanceExtraInitializers);
2433
+ __esDecorate(this, null, _verifyShadow_decorators, {
2434
+ kind: "method",
2435
+ name: "verifyShadow",
2436
+ static: false,
2437
+ private: false,
2438
+ access: {
2439
+ has: (obj) => "verifyShadow" in obj,
2440
+ get: (obj) => obj.verifyShadow
2441
+ },
2442
+ metadata: _metadata
2443
+ }, null, _instanceExtraInitializers);
2444
+ __esDecorate(this, null, _rollback_decorators, {
2445
+ kind: "method",
2446
+ name: "rollback",
2447
+ static: false,
2448
+ private: false,
2449
+ access: {
2450
+ has: (obj) => "rollback" in obj,
2451
+ get: (obj) => obj.rollback
2452
+ },
2453
+ metadata: _metadata
2454
+ }, null, _instanceExtraInitializers);
2455
+ __esDecorate(this, null, _get_decorators, {
2456
+ kind: "method",
2457
+ name: "get",
2458
+ static: false,
2459
+ private: false,
2460
+ access: {
2461
+ has: (obj) => "get" in obj,
2462
+ get: (obj) => obj.get
2463
+ },
2464
+ metadata: _metadata
2465
+ }, null, _instanceExtraInitializers);
2466
+ __esDecorate(this, null, _list_decorators, {
2467
+ kind: "method",
2468
+ name: "list",
2469
+ static: false,
2470
+ private: false,
2471
+ access: {
2472
+ has: (obj) => "list" in obj,
2473
+ get: (obj) => obj.list
2474
+ },
2475
+ metadata: _metadata
2476
+ }, null, _instanceExtraInitializers);
2477
+ __esDecorate(this, null, _family_decorators, {
2478
+ kind: "method",
2479
+ name: "family",
2480
+ static: false,
2481
+ private: false,
2482
+ access: {
2483
+ has: (obj) => "family" in obj,
2484
+ get: (obj) => obj.family
2485
+ },
2486
+ metadata: _metadata
2487
+ }, null, _instanceExtraInitializers);
2488
+ __esDecorate(this, null, _appendDiary_decorators, {
2489
+ kind: "method",
2490
+ name: "appendDiary",
2491
+ static: false,
2492
+ private: false,
2493
+ access: {
2494
+ has: (obj) => "appendDiary" in obj,
2495
+ get: (obj) => obj.appendDiary
2496
+ },
2497
+ metadata: _metadata
2498
+ }, null, _instanceExtraInitializers);
2499
+ __esDecorate(this, null, _extract_decorators, {
2500
+ kind: "method",
2501
+ name: "extract",
2502
+ static: false,
2503
+ private: false,
2504
+ access: {
2505
+ has: (obj) => "extract" in obj,
2506
+ get: (obj) => obj.extract
2507
+ },
2508
+ metadata: _metadata
2509
+ }, null, _instanceExtraInitializers);
2510
+ __esDecorate(this, null, _listDiary_decorators, {
2511
+ kind: "method",
2512
+ name: "listDiary",
2513
+ static: false,
2514
+ private: false,
2515
+ access: {
2516
+ has: (obj) => "listDiary" in obj,
2517
+ get: (obj) => obj.listDiary
2518
+ },
2519
+ metadata: _metadata
2520
+ }, null, _instanceExtraInitializers);
2521
+ __esDecorate(this, null, _getDiaryByIds_decorators, {
2522
+ kind: "method",
2523
+ name: "getDiaryByIds",
2524
+ static: false,
2525
+ private: false,
2526
+ access: {
2527
+ has: (obj) => "getDiaryByIds" in obj,
2528
+ get: (obj) => obj.getDiaryByIds
2529
+ },
2530
+ metadata: _metadata
2531
+ }, null, _instanceExtraInitializers);
2532
+ __esDecorate(this, null, _listFacts_decorators, {
2533
+ kind: "method",
2534
+ name: "listFacts",
2535
+ static: false,
2536
+ private: false,
2537
+ access: {
2538
+ has: (obj) => "listFacts" in obj,
2539
+ get: (obj) => obj.listFacts
2540
+ },
2541
+ metadata: _metadata
2542
+ }, null, _instanceExtraInitializers);
2543
+ __esDecorate(this, null, _listFactsCount_decorators, {
2544
+ kind: "method",
2545
+ name: "listFactsCount",
2546
+ static: false,
2547
+ private: false,
2548
+ access: {
2549
+ has: (obj) => "listFactsCount" in obj,
2550
+ get: (obj) => obj.listFactsCount
2551
+ },
2552
+ metadata: _metadata
2553
+ }, null, _instanceExtraInitializers);
2554
+ __esDecorate(this, null, _listConcerns_decorators, {
2555
+ kind: "method",
2556
+ name: "listConcerns",
2557
+ static: false,
2558
+ private: false,
2559
+ access: {
2560
+ has: (obj) => "listConcerns" in obj,
2561
+ get: (obj) => obj.listConcerns
2562
+ },
2563
+ metadata: _metadata
2564
+ }, null, _instanceExtraInitializers);
2565
+ __esDecorate(this, null, _listConcernsCount_decorators, {
2566
+ kind: "method",
2567
+ name: "listConcernsCount",
2568
+ static: false,
2569
+ private: false,
2570
+ access: {
2571
+ has: (obj) => "listConcernsCount" in obj,
2572
+ get: (obj) => obj.listConcernsCount
2573
+ },
2574
+ metadata: _metadata
2575
+ }, null, _instanceExtraInitializers);
2576
+ __esDecorate(this, null, _extractionLog_decorators, {
2577
+ kind: "method",
2578
+ name: "extractionLog",
2579
+ static: false,
2580
+ private: false,
2581
+ access: {
2582
+ has: (obj) => "extractionLog" in obj,
2583
+ get: (obj) => obj.extractionLog
2584
+ },
2585
+ metadata: _metadata
2586
+ }, null, _instanceExtraInitializers);
2587
+ __esDecorate(this, null, _extractionLogCount_decorators, {
2588
+ kind: "method",
2589
+ name: "extractionLogCount",
2590
+ static: false,
2591
+ private: false,
2592
+ access: {
2593
+ has: (obj) => "extractionLogCount" in obj,
2594
+ get: (obj) => obj.extractionLogCount
2595
+ },
2596
+ metadata: _metadata
2597
+ }, null, _instanceExtraInitializers);
2598
+ __esDecorate(this, null, _consolidate_decorators, {
2599
+ kind: "method",
2600
+ name: "consolidate",
2601
+ static: false,
2602
+ private: false,
2603
+ access: {
2604
+ has: (obj) => "consolidate" in obj,
2605
+ get: (obj) => obj.consolidate
2606
+ },
2607
+ metadata: _metadata
2608
+ }, null, _instanceExtraInitializers);
2609
+ __esDecorate(this, null, _consolidationDue_decorators, {
2610
+ kind: "method",
2611
+ name: "consolidationDue",
2612
+ static: false,
2613
+ private: false,
2614
+ access: {
2615
+ has: (obj) => "consolidationDue" in obj,
2616
+ get: (obj) => obj.consolidationDue
2617
+ },
2618
+ metadata: _metadata
2619
+ }, null, _instanceExtraInitializers);
2620
+ __esDecorate(this, null, _ledgerQuery_decorators, {
2621
+ kind: "method",
2622
+ name: "ledgerQuery",
2623
+ static: false,
2624
+ private: false,
2625
+ access: {
2626
+ has: (obj) => "ledgerQuery" in obj,
2627
+ get: (obj) => obj.ledgerQuery
2628
+ },
2629
+ metadata: _metadata
2630
+ }, null, _instanceExtraInitializers);
2631
+ __esDecorate(this, null, _ledgerQueryCount_decorators, {
2632
+ kind: "method",
2633
+ name: "ledgerQueryCount",
2634
+ static: false,
2635
+ private: false,
2636
+ access: {
2637
+ has: (obj) => "ledgerQueryCount" in obj,
2638
+ get: (obj) => obj.ledgerQueryCount
2639
+ },
2640
+ metadata: _metadata
2641
+ }, null, _instanceExtraInitializers);
2642
+ __esDecorate(this, null, _verifyLedger_decorators, {
2643
+ kind: "method",
2644
+ name: "verifyLedger",
2645
+ static: false,
2646
+ private: false,
2647
+ access: {
2648
+ has: (obj) => "verifyLedger" in obj,
2649
+ get: (obj) => obj.verifyLedger
2650
+ },
2651
+ metadata: _metadata
2652
+ }, null, _instanceExtraInitializers);
2653
+ __esDecorate(this, null, _stats_decorators, {
2654
+ kind: "method",
2655
+ name: "stats",
2656
+ static: false,
2657
+ private: false,
2658
+ access: {
2659
+ has: (obj) => "stats" in obj,
2660
+ get: (obj) => obj.stats
2661
+ },
2662
+ metadata: _metadata
2663
+ }, null, _instanceExtraInitializers);
2664
+ __esDecorate(this, null, _workbenchInfo_decorators, {
2665
+ kind: "method",
2666
+ name: "workbenchInfo",
2667
+ static: false,
2668
+ private: false,
2669
+ access: {
2670
+ has: (obj) => "workbenchInfo" in obj,
2671
+ get: (obj) => obj.workbenchInfo
2672
+ },
2673
+ metadata: _metadata
2674
+ }, null, _instanceExtraInitializers);
2675
+ __esDecorate(this, null, _exportLibrary_decorators, {
2676
+ kind: "method",
2677
+ name: "exportLibrary",
2678
+ static: false,
2679
+ private: false,
2680
+ access: {
2681
+ has: (obj) => "exportLibrary" in obj,
2682
+ get: (obj) => obj.exportLibrary
2683
+ },
2684
+ metadata: _metadata
2685
+ }, null, _instanceExtraInitializers);
2686
+ __esDecorate(this, null, _humanPin_decorators, {
2687
+ kind: "method",
2688
+ name: "humanPin",
2689
+ static: false,
2690
+ private: false,
2691
+ access: {
2692
+ has: (obj) => "humanPin" in obj,
2693
+ get: (obj) => obj.humanPin
2694
+ },
2695
+ metadata: _metadata
2696
+ }, null, _instanceExtraInitializers);
2697
+ __esDecorate(this, null, _humanDeleteExperience_decorators, {
2698
+ kind: "method",
2699
+ name: "humanDeleteExperience",
2700
+ static: false,
2701
+ private: false,
2702
+ access: {
2703
+ has: (obj) => "humanDeleteExperience" in obj,
2704
+ get: (obj) => obj.humanDeleteExperience
2705
+ },
2706
+ metadata: _metadata
2707
+ }, null, _instanceExtraInitializers);
2708
+ __esDecorate(this, null, _humanEditExperience_decorators, {
2709
+ kind: "method",
2710
+ name: "humanEditExperience",
2711
+ static: false,
2712
+ private: false,
2713
+ access: {
2714
+ has: (obj) => "humanEditExperience" in obj,
2715
+ get: (obj) => obj.humanEditExperience
2716
+ },
2717
+ metadata: _metadata
2718
+ }, null, _instanceExtraInitializers);
2719
+ __esDecorate(this, null, _humanAddExperience_decorators, {
2720
+ kind: "method",
2721
+ name: "humanAddExperience",
2722
+ static: false,
2723
+ private: false,
2724
+ access: {
2725
+ has: (obj) => "humanAddExperience" in obj,
2726
+ get: (obj) => obj.humanAddExperience
2727
+ },
2728
+ metadata: _metadata
2729
+ }, null, _instanceExtraInitializers);
2730
+ __esDecorate(this, null, _humanPromote_decorators, {
2731
+ kind: "method",
2732
+ name: "humanPromote",
2733
+ static: false,
2734
+ private: false,
2735
+ access: {
2736
+ has: (obj) => "humanPromote" in obj,
2737
+ get: (obj) => obj.humanPromote
2738
+ },
2739
+ metadata: _metadata
2740
+ }, null, _instanceExtraInitializers);
2741
+ __esDecorate(this, null, _humanReleaseCold_decorators, {
2742
+ kind: "method",
2743
+ name: "humanReleaseCold",
2744
+ static: false,
2745
+ private: false,
2746
+ access: {
2747
+ has: (obj) => "humanReleaseCold" in obj,
2748
+ get: (obj) => obj.humanReleaseCold
2749
+ },
2750
+ metadata: _metadata
2751
+ }, null, _instanceExtraInitializers);
2752
+ __esDecorate(this, null, _humanRollback_decorators, {
2753
+ kind: "method",
2754
+ name: "humanRollback",
2755
+ static: false,
2756
+ private: false,
2757
+ access: {
2758
+ has: (obj) => "humanRollback" in obj,
2759
+ get: (obj) => obj.humanRollback
2760
+ },
2761
+ metadata: _metadata
2762
+ }, null, _instanceExtraInitializers);
2763
+ __esDecorate(this, null, _humanAddFact_decorators, {
2764
+ kind: "method",
2765
+ name: "humanAddFact",
2766
+ static: false,
2767
+ private: false,
2768
+ access: {
2769
+ has: (obj) => "humanAddFact" in obj,
2770
+ get: (obj) => obj.humanAddFact
2771
+ },
2772
+ metadata: _metadata
2773
+ }, null, _instanceExtraInitializers);
2774
+ __esDecorate(this, null, _humanEditFact_decorators, {
2775
+ kind: "method",
2776
+ name: "humanEditFact",
2777
+ static: false,
2778
+ private: false,
2779
+ access: {
2780
+ has: (obj) => "humanEditFact" in obj,
2781
+ get: (obj) => obj.humanEditFact
2782
+ },
2783
+ metadata: _metadata
2784
+ }, null, _instanceExtraInitializers);
2785
+ __esDecorate(this, null, _humanDeleteFact_decorators, {
2786
+ kind: "method",
2787
+ name: "humanDeleteFact",
2788
+ static: false,
2789
+ private: false,
2790
+ access: {
2791
+ has: (obj) => "humanDeleteFact" in obj,
2792
+ get: (obj) => obj.humanDeleteFact
2793
+ },
2794
+ metadata: _metadata
2795
+ }, null, _instanceExtraInitializers);
2796
+ __esDecorate(this, null, _humanConfirmFact_decorators, {
2797
+ kind: "method",
2798
+ name: "humanConfirmFact",
2799
+ static: false,
2800
+ private: false,
2801
+ access: {
2802
+ has: (obj) => "humanConfirmFact" in obj,
2803
+ get: (obj) => obj.humanConfirmFact
2804
+ },
2805
+ metadata: _metadata
2806
+ }, null, _instanceExtraInitializers);
2807
+ __esDecorate(this, null, _humanAckDiary_decorators, {
2808
+ kind: "method",
2809
+ name: "humanAckDiary",
2810
+ static: false,
2811
+ private: false,
2812
+ access: {
2813
+ has: (obj) => "humanAckDiary" in obj,
2814
+ get: (obj) => obj.humanAckDiary
2815
+ },
2816
+ metadata: _metadata
2817
+ }, null, _instanceExtraInitializers);
2818
+ __esDecorate(this, null, _humanSetConcernStatus_decorators, {
2819
+ kind: "method",
2820
+ name: "humanSetConcernStatus",
2821
+ static: false,
2822
+ private: false,
2823
+ access: {
2824
+ has: (obj) => "humanSetConcernStatus" in obj,
2825
+ get: (obj) => obj.humanSetConcernStatus
2826
+ },
2827
+ metadata: _metadata
2828
+ }, null, _instanceExtraInitializers);
2829
+ __esDecorate(this, null, _humanDeleteConcern_decorators, {
2830
+ kind: "method",
2831
+ name: "humanDeleteConcern",
2832
+ static: false,
2833
+ private: false,
2834
+ access: {
2835
+ has: (obj) => "humanDeleteConcern" in obj,
2836
+ get: (obj) => obj.humanDeleteConcern
2837
+ },
2838
+ metadata: _metadata
2839
+ }, null, _instanceExtraInitializers);
2840
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, {
2841
+ enumerable: true,
2842
+ configurable: true,
2843
+ writable: true,
2844
+ value: _metadata
2845
+ });
2846
+ }
2847
+ core = __runInitializers(this, _instanceExtraInitializers);
2848
+ workbench;
2849
+ /** @param ctx - host context. */
2850
+ /** @param core - the ctx-free memory core. */
2851
+ /** @param workbench - descriptor the browser half matches workspaces against. */
2852
+ constructor(ctx, core, workbench) {
2853
+ super(ctx, "memory");
2854
+ this.core = core;
2855
+ this.workbench = workbench;
2856
+ }
2857
+ /** 生: refine a completed trajectory into an experience candidate. */
2858
+ refine(agent, request) {
2859
+ return this.core.refine(request, actorOf(agent));
2860
+ }
2861
+ /** 用: recall + adjudication + injection budget + negative channel. */
2862
+ recall(agent, request) {
2863
+ return this.core.recall(request, actorOf(agent));
2864
+ }
2865
+ /** 用·验: report one use outcome with attribution (V0 verification). */
2866
+ report(agent, request) {
2867
+ return this.core.report(request, actorOf(agent));
2868
+ }
2869
+ /** 摄取归一: source-agnostic intake; drafts become earned candidates (006 §1). */
2870
+ ingest(agent, request) {
2871
+ return this.core.ingest(request, actorOf(agent));
2872
+ }
2873
+ /** 修: propose a revised draft for a challenged experience. */
2874
+ revise(agent, request) {
2875
+ return this.core.revise(request, actorOf(agent));
2876
+ }
2877
+ /** V1 controlled re-enactment: shadow replay verification for a draft. */
2878
+ verifyShadow(agent, request) {
2879
+ return this.core.verifyShadow(request, actorOf(agent));
2880
+ }
2881
+ /** Restore a superseded revision to live (rollback). */
2882
+ rollback(agent, request) {
2883
+ return this.core.rollback(request, actorOf(agent));
2884
+ }
2885
+ /** Read one experience revision (active when revision omitted). */
2886
+ get(agent, id, revision) {
2887
+ return this.core.get(id, revision);
2888
+ }
2889
+ /** List experience revisions by filter. */
2890
+ list(agent, filter) {
2891
+ return this.core.list(filter);
2892
+ }
2893
+ /** Every revision of one family (superseded index for rollback). */
2894
+ family(agent, id) {
2895
+ return this.core.family(id);
2896
+ }
2897
+ /** 记: append one diary entry; signals the extraction duty when due. */
2898
+ appendDiary(agent, request) {
2899
+ return this.core.appendDiary(request, actorOf(agent));
2900
+ }
2901
+ /** 上升通道: apply extracted facts over the pending diary window. */
2902
+ extract(agent, request) {
2903
+ return this.core.extract(request, actorOf(agent), "manual");
2904
+ }
2905
+ /** Diary timeline for the workbench (007 §2: server-side pagination, newest first). */
2906
+ listDiary(agent, limit, offset, onlyUnextracted) {
2907
+ return this.core.listDiary(limit, offset, onlyUnextracted);
2908
+ }
2909
+ /** Several diary entries by id (008 Path A: fact→diary provenance). */
2910
+ getDiaryByIds(agent, ids) {
2911
+ return this.core.getDiaryByIds(ids);
2912
+ }
2913
+ /** Fact versions for the workbench (008 §3: server-side pagination). */
2914
+ listFacts(agent, category, includeHistory, limit, offset) {
2915
+ return this.core.listFacts(category === "" ? void 0 : category, includeHistory, limit, offset);
2916
+ }
2917
+ /** Count facts matching the workbench filter (008 §3: pagination total). */
2918
+ listFactsCount(agent, category, includeHistory) {
2919
+ return this.core.listFactsCount(category === "" ? void 0 : category, includeHistory);
2920
+ }
2921
+ /** 关心事项 trees (top-level + discussion loop) for the workbench (010 §D: filter + pagination). */
2922
+ listConcerns(agent, kind, status, limit, offset) {
2923
+ return this.core.listConcerns(kind === "" ? void 0 : kind, status === "" ? void 0 : status, limit, offset);
2924
+ }
2925
+ /** Count of top-level concerns matching the workbench filter (010 §D: pagination total). */
2926
+ listConcernsCount(agent, kind, status) {
2927
+ return this.core.listConcernsCount(kind === "" ? void 0 : kind, status === "" ? void 0 : status);
2928
+ }
2929
+ /** 010 §F: compact profile + open-concern snapshot for the AI's context (host-side). */
2930
+ profileSnapshot() {
2931
+ return this.core.profileSnapshot();
2932
+ }
2933
+ /** Extraction runs for the workbench (008 §3: server-side pagination). */
2934
+ extractionLog(agent, limit, offset) {
2935
+ return this.core.extractionLog(limit, offset);
2936
+ }
2937
+ /** Total extraction runs (008 §3: pagination total). */
2938
+ extractionLogCount(agent) {
2939
+ return this.core.extractionLogCount();
2940
+ }
2941
+ /** Apply a consolidation run: merge related experiences (008 §1). */
2942
+ consolidate(agent, request) {
2943
+ return this.core.consolidate(request, "agent");
2944
+ }
2945
+ /** Whether a consolidation run is due (interval cadence; 008 §1). */
2946
+ consolidationDue(agent) {
2947
+ return this.core.consolidationDue();
2948
+ }
2949
+ /** Ledger query (newest first, filtered). */
2950
+ ledgerQuery(agent, request) {
2951
+ return this.core.ledgerQuery(request);
2952
+ }
2953
+ /** Count ledger blocks matching a filter (007 §2 pagination). */
2954
+ ledgerQueryCount(agent, request) {
2955
+ return this.core.ledgerQueryCount(request);
2956
+ }
2957
+ /** Verify the ledger hash chain end to end. */
2958
+ verifyLedger(agent) {
2959
+ return this.core.verifyLedger();
2960
+ }
2961
+ /** Aggregated library statistics. */
2962
+ stats(agent) {
2963
+ return this.core.stats();
2964
+ }
2965
+ /** Workbench descriptor (workspace matching + config view). */
2966
+ workbenchInfo(agent) {
2967
+ return this.workbench();
2968
+ }
2969
+ /** Full library export for experiments and migration. */
2970
+ exportLibrary(agent) {
2971
+ return this.core.exportLibrary();
2972
+ }
2973
+ /**
2974
+ * Host-only ledger integrity check (no wire identity needed): the package
2975
+ * invariant companion asserts the hash chain through this accessor.
2976
+ * @returns the chain verification outcome.
2977
+ */
2978
+ verifyLedgerIntegrity() {
2979
+ return this.core.verifyLedger();
2980
+ }
2981
+ /** Human pin/unpin. */
2982
+ humanPin(agent, request) {
2983
+ return this.core.humanPin(request, "human");
2984
+ }
2985
+ /** Human delete (tombstone + ledger fingerprint). */
2986
+ humanDeleteExperience(agent, request) {
2987
+ this.core.humanDeleteExperience(request, "human");
2988
+ return { deleted: true };
2989
+ }
2990
+ /** Human edit of the active revision. */
2991
+ humanEditExperience(agent, request) {
2992
+ return this.core.humanEditExperience(request, "human");
2993
+ }
2994
+ /** Human injection in the fixed experience format. */
2995
+ humanAddExperience(agent, request) {
2996
+ return this.core.humanAddExperience(request, "human");
2997
+ }
2998
+ /** Human authority (V2): promote a candidate straight to live. */
2999
+ humanPromote(agent, id, reason) {
3000
+ return this.core.humanPromote(id, reason, "human");
3001
+ }
3002
+ /** Human re-release of a cold-palace revision back to candidate (006 §3.2). */
3003
+ humanReleaseCold(agent, request) {
3004
+ return this.core.humanReleaseCold(request, "human");
3005
+ }
3006
+ /** Human rollback. */
3007
+ humanRollback(agent, request) {
3008
+ return this.core.humanRollback(request, "human");
3009
+ }
3010
+ /** Human fact add. */
3011
+ humanAddFact(agent, request) {
3012
+ return this.core.humanAddFact(request, "human");
3013
+ }
3014
+ /** Human fact edit. */
3015
+ humanEditFact(agent, request) {
3016
+ return this.core.humanEditFact(request, "human");
3017
+ }
3018
+ /** Human fact delete (tombstone). */
3019
+ humanDeleteFact(agent, request) {
3020
+ this.core.humanDeleteFact(request, "human");
3021
+ return { deleted: true };
3022
+ }
3023
+ /** Human fact confirmation (lock/unlock). */
3024
+ humanConfirmFact(agent, request) {
3025
+ return this.core.humanConfirmFact(request, "human");
3026
+ }
3027
+ /** Human acknowledgement of a pending diary entry (reviewed, no fact extracted). */
3028
+ humanAckDiary(agent, request) {
3029
+ return this.core.humanAckDiary(request, "human");
3030
+ }
3031
+ /** Human lifecycle change of a top-level concern (007 §2.4). */
3032
+ humanSetConcernStatus(agent, request) {
3033
+ this.core.humanSetConcernStatus(request, "human");
3034
+ return { ok: true };
3035
+ }
3036
+ /** Human delete of a concern subtree (007 §2.4). */
3037
+ humanDeleteConcern(agent, request) {
3038
+ this.core.humanDeleteConcern(request, "human");
3039
+ return { deleted: true };
3040
+ }
3041
+ };
3042
+ })();
3043
+ //#endregion
3044
+ //#region lib/types/index.js
3045
+ /**
3046
+ * Memory library host plugin: the self-evolving memory layer (生·用·修·记 +
3047
+ * diary/fact semantic memory). One process-global library shared by every
3048
+ * session, backed by a local SQLite file. Registers `ctx.memory`
3049
+ * (MemoryService). The monitoring UI is an independent left-sidebar nav group
3050
+ * (browser half), not a workspace; this host half performs no workspace
3051
+ * adoption.
3052
+ * @module dsh-daoing-memory
3053
+ */
3054
+ /** Resolve the plugin config with explicit defaults; unknown keys fail loud. */
3055
+ function resolveConfig(config) {
3056
+ const home = process.env.DSH_HOME ?? join(process.cwd(), ".dsh");
3057
+ const known = [
3058
+ "databasePath",
3059
+ "workspacePath",
3060
+ "workspaceTitle",
3061
+ "diaryExtractEvery",
3062
+ "diaryExtractIntervalHours",
3063
+ "recallTopK",
3064
+ "injectionBudgetTokens",
3065
+ "challengeConsecutiveFails",
3066
+ "challengeWindow",
3067
+ "challengeWindowFailRate",
3068
+ "familyLiveCap",
3069
+ "complexityTokenGate",
3070
+ "complexityStepGate",
3071
+ "duplicateOverlapGate",
3072
+ "recallFloorScore",
3073
+ "shadowPassRate"
3074
+ ];
3075
+ const unknown = Object.keys(config).filter((key) => !known.includes(key));
3076
+ if (unknown.length > 0) throw new Error(`memory: unknown config key(s) ${unknown.join(", ")}`);
3077
+ return {
3078
+ databasePath: resolve(config.databasePath ?? join(home, "storages", "memory.db")),
3079
+ workspacePath: resolve(config.workspacePath ?? join(home, "memory-workbench")),
3080
+ workspaceTitle: config.workspaceTitle ?? "记忆监控",
3081
+ diaryExtractEvery: config.diaryExtractEvery ?? DEFAULT_CORE_CONFIG.diaryExtractEvery,
3082
+ diaryExtractIntervalHours: config.diaryExtractIntervalHours ?? DEFAULT_CORE_CONFIG.diaryExtractIntervalHours,
3083
+ recallTopK: config.recallTopK ?? DEFAULT_CORE_CONFIG.recallTopK,
3084
+ injectionBudgetTokens: config.injectionBudgetTokens ?? DEFAULT_CORE_CONFIG.injectionBudgetTokens,
3085
+ challengeConsecutiveFails: config.challengeConsecutiveFails ?? DEFAULT_CORE_CONFIG.challengeConsecutiveFails,
3086
+ challengeWindow: config.challengeWindow ?? DEFAULT_CORE_CONFIG.challengeWindow,
3087
+ challengeWindowFailRate: config.challengeWindowFailRate ?? DEFAULT_CORE_CONFIG.challengeWindowFailRate,
3088
+ familyLiveCap: config.familyLiveCap ?? DEFAULT_CORE_CONFIG.familyLiveCap,
3089
+ complexityTokenGate: config.complexityTokenGate ?? DEFAULT_CORE_CONFIG.complexityTokenGate,
3090
+ complexityStepGate: config.complexityStepGate ?? DEFAULT_CORE_CONFIG.complexityStepGate,
3091
+ duplicateOverlapGate: config.duplicateOverlapGate ?? DEFAULT_CORE_CONFIG.duplicateOverlapGate,
3092
+ recallFloorScore: config.recallFloorScore ?? DEFAULT_CORE_CONFIG.recallFloorScore,
3093
+ shadowPassRate: config.shadowPassRate ?? DEFAULT_CORE_CONFIG.shadowPassRate
3094
+ };
3095
+ }
3096
+ /**
3097
+ * Host plugin body: open the SQLite store, provide `ctx.memory`, and adopt
3098
+ * the monitoring workspace directory once a workspace registry is available.
3099
+ * @param ctx - host context.
3100
+ * @param config - plugin config (see {@link Config}).
3101
+ */
3102
+ function apply(ctx, config = {}) {
3103
+ const resolved = resolveConfig(config);
3104
+ const coreConfig = {
3105
+ ...DEFAULT_CORE_CONFIG,
3106
+ diaryExtractEvery: resolved.diaryExtractEvery,
3107
+ diaryExtractIntervalHours: resolved.diaryExtractIntervalHours,
3108
+ recallTopK: resolved.recallTopK,
3109
+ injectionBudgetTokens: resolved.injectionBudgetTokens,
3110
+ challengeConsecutiveFails: resolved.challengeConsecutiveFails,
3111
+ challengeWindow: resolved.challengeWindow,
3112
+ challengeWindowFailRate: resolved.challengeWindowFailRate,
3113
+ familyLiveCap: resolved.familyLiveCap,
3114
+ complexityTokenGate: resolved.complexityTokenGate,
3115
+ complexityStepGate: resolved.complexityStepGate,
3116
+ duplicateOverlapGate: resolved.duplicateOverlapGate,
3117
+ recallFloorScore: resolved.recallFloorScore,
3118
+ shadowPassRate: resolved.shadowPassRate
3119
+ };
3120
+ mkdirSync(join(resolved.databasePath, ".."), { recursive: true });
3121
+ const db = new DatabaseSync(resolved.databasePath);
3122
+ const core = new MemoryCore(new MemoryStore(db), coreConfig);
3123
+ const workbenchInfo = () => ({
3124
+ workspacePath: resolved.workspacePath,
3125
+ workspaceTitle: resolved.workspaceTitle,
3126
+ databasePath: resolved.databasePath,
3127
+ diaryExtractEvery: resolved.diaryExtractEvery,
3128
+ injectionBudgetTokens: resolved.injectionBudgetTokens
3129
+ });
3130
+ new MemoryService(ctx, core, workbenchInfo);
3131
+ ctx.effect(() => () => {
3132
+ try {
3133
+ db.close();
3134
+ } catch {}
3135
+ }, "memory: SQLite store lifetime");
3136
+ }
3137
+ //#endregion
3138
+ export { DEFAULT_CORE_CONFIG, MEMORY_SCHEMA_VERSION, MemoryCore, MemoryService, MemoryStore, apply, resolveConfig };