@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/INSTALL.md +25 -0
  2. package/PANEL.md +92 -0
  3. package/README.md +286 -2
  4. package/RELEASE-NOTES.md +732 -0
  5. package/bin/bc-acceptance-full.mjs +91 -0
  6. package/bin/bc-acceptance-tight.mjs +66 -0
  7. package/bin/bc-acceptance.mjs +22 -0
  8. package/bin/bc-add-keywords.mjs +21 -0
  9. package/bin/bc-demo.mjs +47 -0
  10. package/bin/bc-expand-hint.mjs +179 -0
  11. package/bin/bc-ingest-index.mjs +35 -0
  12. package/bin/bc-ingest.mjs +31 -0
  13. package/bin/bc-label-demo.mjs +18 -0
  14. package/bin/bc-relabel.mjs +150 -0
  15. package/bin/bc-sessionize.mjs +59 -0
  16. package/bin/coref_pipeline.py +47 -0
  17. package/bin/coref_service.py +57 -0
  18. package/bin/debug_ckip.py +16 -0
  19. package/bin/debug_ner_zh.py +18 -0
  20. package/bin/doctor.mjs +275 -0
  21. package/bin/en_ner_error_audit.py +33 -0
  22. package/bin/eval-label-rules.mjs +342 -0
  23. package/bin/export-turns.py +68 -0
  24. package/bin/extract-user-paragraph.mjs +14 -0
  25. package/bin/extraction-composite-smoke.mjs +28 -0
  26. package/bin/extraction-latency.mjs +58 -0
  27. package/bin/extraction-user-smoke.mjs +28 -0
  28. package/bin/extraction_service.py +164 -0
  29. package/bin/facet-active-experiment.mjs +110 -0
  30. package/bin/facet-covid.mjs +8 -0
  31. package/bin/facet-curve.mjs +27 -0
  32. package/bin/facet-embedding-train.mjs +20 -0
  33. package/bin/facet-embedding-validate.mjs +83 -0
  34. package/bin/facet-eval-roleplay.mjs +42 -0
  35. package/bin/facet-eval.mjs +38 -0
  36. package/bin/facet-fold.mjs +31 -0
  37. package/bin/facet-load.mjs +16 -0
  38. package/bin/facet-mlp-scan.mjs +45 -0
  39. package/bin/facet-mvp.mjs +34 -0
  40. package/bin/facet-online-experiments.mjs +78 -0
  41. package/bin/facet-online-race.mjs +99 -0
  42. package/bin/facet-online-validate.mjs +51 -0
  43. package/bin/facet-panel.mjs +22 -0
  44. package/bin/facet-passage.mjs +41 -0
  45. package/bin/facet-random-validate.mjs +48 -0
  46. package/bin/facet-roleplay.mjs +33 -0
  47. package/bin/facet-scan.mjs +56 -0
  48. package/bin/facet-segment.mjs +34 -0
  49. package/bin/facet-train.mjs +19 -0
  50. package/bin/facet-validate.mjs +103 -0
  51. package/bin/facet-zeroshot.mjs +14 -0
  52. package/bin/install-templates.mjs +72 -0
  53. package/bin/memory-bench.mjs +53 -0
  54. package/bin/memory-build-summary.mjs +43 -0
  55. package/bin/memory-build-vector.mjs +31 -0
  56. package/bin/memory-build.mjs +41 -0
  57. package/bin/memory-calibration.mjs +70 -0
  58. package/bin/memory-closed-loop.mjs +119 -0
  59. package/bin/memory-dry-run.mjs +70 -0
  60. package/bin/memory-eval.mjs +104 -0
  61. package/bin/memory-event-alias-test.mjs +89 -0
  62. package/bin/memory-extraction-bench.mjs +58 -0
  63. package/bin/memory-load-gate-replay.mjs +137 -0
  64. package/bin/memory-loop-test.mjs +90 -0
  65. package/bin/memory-pre-turn-harness.mjs +63 -0
  66. package/bin/memory-query.mjs +39 -0
  67. package/bin/memory-replay-hints.mjs +84 -0
  68. package/bin/memory-scale.mjs +58 -0
  69. package/bin/memory-scenario-test.mjs +146 -0
  70. package/bin/memory-user-loop.mjs +95 -0
  71. package/bin/ner_bench_tiny_zh.py +59 -0
  72. package/bin/ner_bench_zh.py +60 -0
  73. package/bin/ner_conll_en.py +139 -0
  74. package/bin/ner_direct_en.py +123 -0
  75. package/bin/ner_direct_zh.py +100 -0
  76. package/bin/ner_pipeline.py +208 -0
  77. package/bin/noise-selfcheck.mjs +50 -0
  78. package/bin/pack-integrity.mjs +47 -0
  79. package/bin/panel-check.mjs +57 -0
  80. package/bin/plugin-integrity.mjs +74 -0
  81. package/bin/preview-live.mjs +13 -0
  82. package/bin/public-install-check.mjs +222 -0
  83. package/bin/relabel-apply.mjs +272 -0
  84. package/bin/relabel-dry-run.mjs +381 -0
  85. package/bin/relik_pipeline.py +39 -0
  86. package/bin/relik_service.py +52 -0
  87. package/bin/srl_pipeline.py +55 -0
  88. package/bin/srl_service.py +68 -0
  89. package/bin/summary-index.mjs +151 -0
  90. package/bin/summary-query.mjs +79 -0
  91. package/bin/vec-check.mjs +27 -0
  92. package/bin/zh_ner_error_audit.py +36 -0
  93. package/bin/zh_ner_slot_eval.py +38 -0
  94. package/client.js +760 -0
  95. package/cordis.patch.yml +378 -0
  96. package/docs/API-NOTES.md +1743 -0
  97. package/docs/ARCHITECTURE.md +355 -0
  98. package/docs/CACHE.md +253 -0
  99. package/docs/CONDUCT.md +30 -0
  100. package/docs/CREATOR-HANDOFF.md +102 -0
  101. package/docs/EXPERIMENT.md +136 -0
  102. package/docs/FACET-MVP-REPORT.md +473 -0
  103. package/docs/GUIDE-0.4.66.md +195 -0
  104. package/docs/GUIDE.md +127 -0
  105. package/docs/MECHANISM.md +243 -0
  106. package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
  107. package/docs/README.md +37 -0
  108. package/docs/SESSION-LAYOUT.md +151 -0
  109. package/docs/history-map.md +249 -0
  110. package/docs/relabel-migration-wiring.md +366 -0
  111. package/guides/00-READ-FIRST.md +206 -0
  112. package/guides/01-INSTALL.md +207 -0
  113. package/guides/02-TABLES.md +225 -0
  114. package/guides/03-AUDIT.md +201 -0
  115. package/guides/04-REBUILD.md +306 -0
  116. package/guides/05-FIRST-RUN.md +191 -0
  117. package/guides/06-EMBEDDING.md +146 -0
  118. package/guides/07-PTC-AND-ASSETS.md +221 -0
  119. package/icon.svg +7 -0
  120. package/locale/en.json +6 -0
  121. package/locale/zh.json +6 -0
  122. package/models/README.md +13 -0
  123. package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
  124. package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  125. package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  126. package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  127. package/package.json +139 -4
  128. package/src/asset-tool.mjs +237 -0
  129. package/src/assets.mjs +244 -0
  130. package/src/backfill.mjs +74 -0
  131. package/src/backup.mjs +80 -0
  132. package/src/deadend.mjs +95 -0
  133. package/src/deleted-store.mjs +35 -0
  134. package/src/engine.mjs +3831 -0
  135. package/src/facets/classifier.mjs +146 -0
  136. package/src/facets/embedding-service.mjs +53 -0
  137. package/src/facets/embedding-worker.mjs +27 -0
  138. package/src/facets/embedding.mjs +163 -0
  139. package/src/facets/extract.mjs +80 -0
  140. package/src/facets/fold-plan.mjs +84 -0
  141. package/src/facets/hybrid.mjs +29 -0
  142. package/src/facets/index.mjs +113 -0
  143. package/src/facets/linear-probe.mjs +43 -0
  144. package/src/facets/load.mjs +37 -0
  145. package/src/facets/mlp-probe.mjs +54 -0
  146. package/src/facets/mlp-service.mjs +53 -0
  147. package/src/facets/mlp-worker.mjs +26 -0
  148. package/src/facets/online-knn.mjs +58 -0
  149. package/src/facets/online-models.mjs +227 -0
  150. package/src/facets/panel-state.mjs +23 -0
  151. package/src/facets/panel.mjs +73 -0
  152. package/src/facets/random.mjs +26 -0
  153. package/src/facets/scanner.mjs +35 -0
  154. package/src/facets/schema.mjs +75 -0
  155. package/src/facets/seed-examples.mjs +58 -0
  156. package/src/facets/sensitivity.mjs +98 -0
  157. package/src/facets/service.mjs +53 -0
  158. package/src/facets/store.mjs +39 -0
  159. package/src/facets/worker.mjs +23 -0
  160. package/src/folds-store.mjs +109 -0
  161. package/src/groups.mjs +167 -0
  162. package/src/handoff-tool.mjs +51 -0
  163. package/src/handoff.mjs +127 -0
  164. package/src/index.mjs +649 -0
  165. package/src/inject-policy.mjs +17 -0
  166. package/src/learning.mjs +88 -0
  167. package/src/memory/alias-proposal.mjs +40 -0
  168. package/src/memory/bc-audit.mjs +245 -0
  169. package/src/memory/bc-index-updater.mjs +404 -0
  170. package/src/memory/bc-index.mjs +48 -0
  171. package/src/memory/bc-label.mjs +220 -0
  172. package/src/memory/bc-panel-state.mjs +107 -0
  173. package/src/memory/bc-pre-step.mjs +463 -0
  174. package/src/memory/bc-recall.mjs +470 -0
  175. package/src/memory/bc-shift.mjs +123 -0
  176. package/src/memory/bc-store.mjs +41 -0
  177. package/src/memory/bm25.mjs +112 -0
  178. package/src/memory/calibration.mjs +54 -0
  179. package/src/memory/chatter.mjs +29 -0
  180. package/src/memory/cmap-file.mjs +107 -0
  181. package/src/memory/concepts-file.mjs +41 -0
  182. package/src/memory/coref-slots.mjs +40 -0
  183. package/src/memory/event-alias-ledger.mjs +87 -0
  184. package/src/memory/event-log.mjs +95 -0
  185. package/src/memory/event-model.mjs +132 -0
  186. package/src/memory/event-segmenter.mjs +36 -0
  187. package/src/memory/event-summary-tree.mjs +122 -0
  188. package/src/memory/extraction-adapter.mjs +76 -0
  189. package/src/memory/extraction-composite.mjs +75 -0
  190. package/src/memory/extraction-core.mjs +76 -0
  191. package/src/memory/extraction-pre-step.mjs +161 -0
  192. package/src/memory/extraction-provider.mjs +300 -0
  193. package/src/memory/extraction-score.mjs +36 -0
  194. package/src/memory/facet-stabilizer.mjs +59 -0
  195. package/src/memory/history-map.mjs +139 -0
  196. package/src/memory/language-router.mjs +30 -0
  197. package/src/memory/load-gate.mjs +161 -0
  198. package/src/memory/mechanical-note.mjs +104 -0
  199. package/src/memory/onboarding.mjs +163 -0
  200. package/src/memory/persona.mjs +78 -0
  201. package/src/memory/planner.mjs +249 -0
  202. package/src/memory/pre-turn.mjs +80 -0
  203. package/src/memory/query-aliases.mjs +39 -0
  204. package/src/memory/recall-cue.mjs +43 -0
  205. package/src/memory/recall.mjs +65 -0
  206. package/src/memory/referent-candidates.mjs +55 -0
  207. package/src/memory/referent-ledger.mjs +86 -0
  208. package/src/memory/relabel-migrate.mjs +376 -0
  209. package/src/memory/relik-adapter.mjs +25 -0
  210. package/src/memory/special-keywords.mjs +60 -0
  211. package/src/memory/structured-adapters.mjs +75 -0
  212. package/src/memory/summary-index.mjs +335 -0
  213. package/src/memory/summary-tree.mjs +110 -0
  214. package/src/memory/term-extractor.mjs +44 -0
  215. package/src/memory/tokenize.mjs +9 -0
  216. package/src/memory/vector-index.mjs +54 -0
  217. package/src/memory/window-summary.mjs +133 -0
  218. package/src/mood.mjs +133 -0
  219. package/src/peer-tool.mjs +63 -0
  220. package/src/peer.mjs +144 -0
  221. package/src/policy.mjs +170 -0
  222. package/src/preflight.mjs +113 -0
  223. package/src/provenance.mjs +94 -0
  224. package/src/range.mjs +75 -0
  225. package/src/recall-exact-tool.mjs +267 -0
  226. package/src/recall-tool.mjs +521 -0
  227. package/src/recheck.mjs +99 -0
  228. package/src/save-start.mjs +29 -0
  229. package/src/session-guard.mjs +140 -0
  230. package/src/session-log-tool.mjs +322 -0
  231. package/src/session-log.mjs +606 -0
  232. package/src/skills.mjs +111 -0
  233. package/src/split-range.mjs +115 -0
  234. package/src/state.mjs +198 -0
  235. package/src/t0.mjs +149 -0
  236. package/src/tag-confirm.mjs +78 -0
  237. package/src/tag-detect.mjs +42 -0
  238. package/src/tag-maintenance.mjs +285 -0
  239. package/src/tag-reject.mjs +73 -0
  240. package/src/tags-store.mjs +94 -0
  241. package/src/tags.mjs +692 -0
  242. package/src/threads-backend.mjs +26 -0
  243. package/src/threads-core.mjs +1887 -0
  244. package/src/threads-store.mjs +207 -0
  245. package/src/threads-tool.mjs +522 -0
  246. package/src/threads.mjs +929 -0
  247. package/src/tool-preview.mjs +427 -0
  248. package/src/topic.mjs +63 -0
  249. package/src/training.mjs +75 -0
  250. package/src/turn-tag.mjs +62 -0
  251. package/src/user-turn.mjs +39 -0
  252. package/templates/cmap.md +11 -0
  253. package/templates/concepts.md +12 -0
  254. package/templates/habits.md +40 -0
  255. package/templates/keywords.md +6 -0
  256. package/templates/rebuild.md +134 -0
  257. package/templates/rules-safe.md +184 -0
  258. package/templates/rules.md +177 -0
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Event-bound alias scenarios: same-event repeat, cross-event repeat,
4
+ * assistant-only, role change, accommodation.
5
+ */
6
+ import { EventSegmenter } from '../src/memory/event-segmenter.mjs';
7
+ import { EventAliasLedger } from '../src/memory/event-alias-ledger.mjs';
8
+
9
+ const run = { activity: '跑团', topic: '剧情' };
10
+ const dev = { activity: '开发', topic: '技术' };
11
+
12
+ const scenarios = [
13
+ {
14
+ id: 'same-event-vs-cross-event',
15
+ steps: [
16
+ { text: '我们开个新团,叫星港', facets: run, term: '星港', role: 'campaign', target: '跑团', expected: 'none' },
17
+ { text: '星港的规则是掷骰', facets: run, term: '星港', role: 'campaign', target: '跑团', expected: 'none' },
18
+ { text: '先写文档', facets: dev, expected: 'none' },
19
+ { text: '回到星港', facets: run, term: '星港', role: 'campaign', target: '跑团', expected: 'propose' },
20
+ ],
21
+ },
22
+ {
23
+ id: 'assistant-only',
24
+ steps: [
25
+ { text: '星港开场', facets: run, term: '星港', role: 'campaign', target: '跑团', source: 'assistant', expected: 'none' },
26
+ { text: '星港的钟楼', facets: run, term: '星港', role: 'campaign', target: '跑团', source: 'assistant', expected: 'none' },
27
+ { text: '先写文档', facets: dev, expected: 'none' },
28
+ { text: '星港还在', facets: run, term: '星港', role: 'campaign', target: '跑团', source: 'assistant', expected: 'none' },
29
+ ],
30
+ },
31
+ {
32
+ id: 'role-change',
33
+ steps: [
34
+ { text: '新团叫星港', facets: run, term: '星港', role: 'campaign', target: '跑团', expected: 'none' },
35
+ { text: '先写文档', facets: dev, expected: 'none' },
36
+ { text: '星港的灯亮了', facets: run, term: '星港', role: 'location', target: '跑团', expected: 'none' },
37
+ ],
38
+ },
39
+ {
40
+ id: 'accommodation',
41
+ steps: [
42
+ { text: '新规则叫潮汐语法', facets: dev, term: '潮汐语法', role: 'rule', target: null, expected: 'none' },
43
+ { text: '开始跑团', facets: run, expected: 'none' },
44
+ { text: '潮汐语法还在', facets: dev, term: '潮汐语法', role: 'rule', target: null, expected: 'accommodate' },
45
+ ],
46
+ },
47
+ ];
48
+
49
+ const output = [];
50
+ for (const scenario of scenarios) {
51
+ const segmenter = new EventSegmenter();
52
+ const ledger = new EventAliasLedger();
53
+ const events = [];
54
+ scenario.steps.forEach((step, index) => {
55
+ const nowTurn = index + 1;
56
+ const segment = segmenter.observe({ facets: step.facets, nowTurn });
57
+ const alias = step.term === undefined
58
+ ? { action: 'none', reason: 'no-term' }
59
+ : ledger.observe({
60
+ term: step.term,
61
+ source: step.source ?? 'user',
62
+ eventId: segment.eventId,
63
+ role: step.role,
64
+ target: step.target,
65
+ nowTurn,
66
+ });
67
+ events.push({
68
+ text: step.text,
69
+ eventId: segment.eventId,
70
+ boundary: segment.boundary,
71
+ alias: alias.action,
72
+ aliasReason: alias.reason,
73
+ expected: step.expected,
74
+ pass: alias.action === step.expected,
75
+ textHint: alias.text ?? null,
76
+ });
77
+ });
78
+ output.push({
79
+ id: scenario.id,
80
+ pass: events.every((event) => event.pass),
81
+ events,
82
+ });
83
+ }
84
+ const summary = {
85
+ scenarios: output.length,
86
+ pass: output.filter((item) => item.pass).length,
87
+ failures: output.filter((item) => !item.pass).map((item) => item.id),
88
+ };
89
+ console.log(JSON.stringify({ summary, output }, null, 2));
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Extract-bench harness. Fixtures: JSONL with {text, language?, entities:[...]}.
4
+ * Provider: a JS module exporting extract(text, route) or {zh,en,mixed} functions.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { pathToFileURL } from 'node:url';
9
+ import { createExtractionCore } from '../src/memory/extraction-core.mjs';
10
+ import { scoreEntities, aggregateScores } from '../src/memory/extraction-score.mjs';
11
+
12
+ const args = process.argv.slice(2);
13
+ const argOf = (name) => {
14
+ const i = args.indexOf(name);
15
+ return i >= 0 ? args[i + 1] : undefined;
16
+ };
17
+ const fixturesPath = argOf('--fixtures');
18
+ const providerPath = argOf('--provider');
19
+ const outPath = argOf('--out') ?? null;
20
+ if (!fixturesPath || !providerPath) {
21
+ console.error('usage: node bin/memory-extraction-bench.mjs --fixtures dev.jsonl --provider provider.mjs [--out result.json]');
22
+ process.exit(2);
23
+ }
24
+ const mod = await import(pathToFileURL(path.resolve(providerPath)).href);
25
+ const provider = mod.default ?? mod.provider ?? mod;
26
+ const providers = typeof provider === 'function'
27
+ ? { zh: provider, en: provider, mixed: provider }
28
+ : provider;
29
+ const core = createExtractionCore({ providers });
30
+ const fixtures = fs.readFileSync(fixturesPath, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line.replace(/^\uFEFF/, '')));
31
+ const rows = [];
32
+ for (const fixture of fixtures) {
33
+ const result = core.extract(fixture.text);
34
+ const predicted = result.ok ? result.entities : [];
35
+ const score = scoreEntities(predicted, fixture.entities ?? []);
36
+ rows.push({
37
+ text: fixture.text,
38
+ language: result.language,
39
+ ok: result.ok,
40
+ reason: result.reason ?? null,
41
+ predicted: predicted.length,
42
+ expected: fixture.entities?.length ?? 0,
43
+ score,
44
+ });
45
+ }
46
+ const byLanguage = {};
47
+ for (const language of [...new Set(rows.map((row) => row.language))]) {
48
+ byLanguage[language] = aggregateScores(rows.filter((row) => row.language === language).map((row) => row.score));
49
+ }
50
+ const output = {
51
+ fixtures: rows.length,
52
+ failed: rows.filter((row) => !row.ok).length,
53
+ aggregate: aggregateScores(rows.map((row) => row.score)),
54
+ byLanguage,
55
+ rows,
56
+ };
57
+ if (outPath) fs.writeFileSync(outPath, JSON.stringify(output, null, 2));
58
+ console.log(JSON.stringify({ fixtures: output.fixtures, failed: output.failed, aggregate: output.aggregate, byLanguage }, null, 2));
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Load-gate replay + fuzz at 50 / 200 / 800 / 1500.
4
+ * Checks decisions, scene purity and duplicate loads.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readEventLog } from '../src/memory/event-log.mjs';
9
+ import {
10
+ MemoryLoadState,
11
+ latestCandidate,
12
+ candidateSegment,
13
+ isSceneRecord,
14
+ } from '../src/memory/load-gate.mjs';
15
+
16
+ const args = process.argv.slice(2);
17
+ const argOf = (name) => {
18
+ const i = args.indexOf(name);
19
+ return i >= 0 ? args[i + 1] : undefined;
20
+ };
21
+ const dir = argOf('--dir');
22
+ if (!dir) {
23
+ console.error('usage: node bin/memory-load-gate-replay.mjs --dir .durable-memory');
24
+ process.exit(2);
25
+ }
26
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
27
+ const checkpoints = [50, 200, 800, 1500];
28
+ const dev = { activity: '开发', topic: '技术' };
29
+ const run = { activity: '跑团', topic: '剧情' };
30
+
31
+ const scenarios = [
32
+ { id: 'start-run', text: '我们现在开始跑团吧', facets: run, expected: 'load' },
33
+ { id: 'plain-start', text: '开始跑团', facets: run, expected: 'load' },
34
+ { id: 'open-table', text: '开团', facets: run, expected: 'load' },
35
+ { id: 'coo-start', text: 'Coo:来一局', facets: run, expected: 'load', role: 'coo' },
36
+ { id: 'meta-rules', text: '规则怎么写', facets: dev, expected: 'none' },
37
+ { id: 'ordinary-mention', text: '跑团这个设计挺有意思', facets: dev, expected: 'none' },
38
+ { id: 'generic-start', text: '开始写文档', facets: dev, expected: 'none' },
39
+ { id: 'recall-run', text: '上次跑团怎么样了', facets: run, expected: 'load' },
40
+ { id: 'recall-vague', text: '之前那个呢', facets: run, expected: 'load' },
41
+ { id: 'new-scene', text: '灯室见', facets: run, expected: 'hint' },
42
+ { id: 'coo-new-scene', text: 'Coo:灯室见', facets: run, expected: 'load', role: 'coo' },
43
+ { id: 'same-scene', text: '我推门进去', facets: run, expected: 'none', initial: run },
44
+ { id: 'assistant', text: '开始跑团', facets: run, expected: 'none', role: 'assistant' },
45
+ { id: 'low-confidence', text: '开始跑团', facets: run, expected: 'none', confidence: 0.3 },
46
+ ];
47
+
48
+ const output = [];
49
+ for (const checkpoint of checkpoints) {
50
+ const visible = records.filter((record) => record.turn <= checkpoint);
51
+ const actual = [];
52
+ const state = new MemoryLoadState();
53
+ for (const record of visible) {
54
+ const prior = visible.filter((item) => item.turn < record.turn);
55
+ const candidate = latestCandidate(prior, 'activity', record.facets.activity);
56
+ const result = state.decide({
57
+ text: record.text,
58
+ currentFacets: record.facets,
59
+ confidence: 0.9,
60
+ nowTurn: record.turn,
61
+ hasCandidate: candidate !== null,
62
+ candidate,
63
+ });
64
+ if (result.action === 'load') {
65
+ const segment = candidateSegment(visible, candidate);
66
+ actual.push({
67
+ turn: record.turn,
68
+ reason: result.reason,
69
+ candidateTurn: candidate?.turn ?? null,
70
+ pure: candidate === null ? false : isSceneRecord(candidate),
71
+ segmentTurns: segment?.turns ?? 0,
72
+ });
73
+ state.recordLoad({ facets: record.facets, seq: segment?.seqStart ?? null, nowTurn: record.turn, reason: result.reason });
74
+ } else if (result.action === 'hint') {
75
+ actual.push({ turn: record.turn, reason: result.reason, hint: true });
76
+ }
77
+ }
78
+
79
+ const fuzz = [];
80
+ for (const testCase of scenarios) {
81
+ const candidate = latestCandidate(visible, 'activity', testCase.facets.activity);
82
+ const segment = candidateSegment(visible, candidate);
83
+ const testState = new MemoryLoadState({ loadedFacets: testCase.initial ?? dev, loadedAtTurn: checkpoint - 1 });
84
+ const result = testState.decide({
85
+ text: testCase.text,
86
+ currentFacets: testCase.facets,
87
+ confidence: testCase.confidence ?? 0.9,
88
+ nowTurn: checkpoint,
89
+ hasCandidate: candidate !== null,
90
+ candidate,
91
+ role: testCase.role ?? 'user',
92
+ });
93
+ const pass = (testCase.expected === 'load' && result.action === 'load')
94
+ || (testCase.expected === 'none' && result.action === 'none')
95
+ || (testCase.expected === 'hint' && result.action === 'hint');
96
+ fuzz.push({
97
+ id: testCase.id,
98
+ expected: testCase.expected,
99
+ actual: result.action,
100
+ reason: result.reason,
101
+ pass,
102
+ candidateTurn: candidate?.turn ?? null,
103
+ candidatePure: candidate === null ? false : isSceneRecord(candidate),
104
+ segmentTurns: segment?.turns ?? 0,
105
+ });
106
+ }
107
+
108
+ const loads = actual.filter((item) => item.hint !== true);
109
+ output.push({
110
+ checkpoint,
111
+ visible: visible.length,
112
+ naturalLoads: loads.length,
113
+ naturalHints: actual.filter((item) => item.hint === true).length,
114
+ impureLoads: loads.filter((item) => !item.pure).length,
115
+ impureSample: loads.filter((item) => !item.pure).slice(0, 6),
116
+ duplicateActivityLoads: loads.filter((item, index) => index > 0
117
+ && item.reason === 'activity-change'
118
+ && loads[index - 1].reason === 'activity-change').length,
119
+ fuzzPass: fuzz.filter((item) => item.pass).length,
120
+ fuzzTotal: fuzz.length,
121
+ fuzzFailures: fuzz.filter((item) => !item.pass),
122
+ impureCandidates: fuzz.filter((item) => !item.candidatePure).map((item) => item.id),
123
+ naturalSample: loads.slice(0, 4),
124
+ fuzz,
125
+ });
126
+ }
127
+ const result = { dir, output };
128
+ fs.writeFileSync(path.join(dir, 'LOAD-GATE-REPLAY.json'), JSON.stringify(result, null, 2));
129
+ console.log(JSON.stringify(output.map((entry) => ({
130
+ checkpoint: entry.checkpoint,
131
+ naturalLoads: entry.naturalLoads,
132
+ naturalHints: entry.naturalHints,
133
+ impureLoads: entry.impureLoads,
134
+ duplicateActivityLoads: entry.duplicateActivityLoads,
135
+ fuzzPass: `${entry.fuzzPass}/${entry.fuzzTotal}`,
136
+ fuzzFailures: entry.fuzzFailures.map((item) => item.id),
137
+ })), null, 2));
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Real-data checkpoint loop: 50 / 200 / 800 / 1500 turns.
4
+ * Simulates "上次的 X 怎么样了" and records route, load size, future leaks,
5
+ * latest-hit correctness, noise and misses.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { readEventLog } from '../src/memory/event-log.mjs';
10
+ import { BM25Index } from '../src/memory/bm25.mjs';
11
+ import { VectorIndex } from '../src/memory/vector-index.mjs';
12
+ import { buildSummaryTree } from '../src/memory/summary-tree.mjs';
13
+ import { buildPlanner, summaryIndex } from '../src/memory/planner.mjs';
14
+ import { embedTexts } from '../src/facets/embedding.mjs';
15
+
16
+ const args = process.argv.slice(2);
17
+ const argOf = (name) => {
18
+ const i = args.indexOf(name);
19
+ return i >= 0 ? args[i + 1] : undefined;
20
+ };
21
+ const dir = argOf('--dir') ?? 'C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a';
22
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
23
+ const embeddings = JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400/embeddings-1500.json', 'utf8'));
24
+ const vectorByTurn = new Map(embeddings.map((row) => [row.turn, row.vector]));
25
+ const cases = [
26
+ ['上次的跑团怎么样了', (r) => r.facets.activity === '跑团'],
27
+ ['上次的TAG闸是什么', (r) => /tag/i.test(r.text) && /闸/.test(r.text)],
28
+ ['上次的头像呢', (r) => r.facets.medium === '图像'],
29
+ ['上次的上下文折叠', (r) => /上下文/.test(r.text) && /折叠/.test(r.text)],
30
+ ['上次手抖是哪轮', (r) => /手抖/.test(r.text)],
31
+ ['上次的灯室', (r) => /灯室/.test(r.text)],
32
+ ['上次 沈砚 七把钥匙', (r) => /沈砚/.test(r.text) && /七把钥匙/.test(r.text)],
33
+ ['上次 dsh-continuity-0.1.0.tgz', (r) => /dsh-continuity-0\.1\.0\.tgz/.test(r.text)],
34
+ ['上次 C v5', (r) => /C v5/.test(r.text)],
35
+ ['上次量子计算', () => false],
36
+ ];
37
+ const queryVectors = new Map();
38
+ for (const [query] of cases) {
39
+ const [vector] = await embedTexts([query]);
40
+ queryVectors.set(query, vector);
41
+ }
42
+ const checkpoints = [50, 200, 800, 1500];
43
+ const output = [];
44
+ for (const checkpoint of checkpoints) {
45
+ const visible = records.filter((record) => record.turn <= checkpoint);
46
+ const bm25 = new BM25Index().build(visible);
47
+ const vectors = new VectorIndex().build(visible, vectorByTurn);
48
+ const tree = buildSummaryTree(visible);
49
+ const planner = buildPlanner({ records: visible, bm25, vectorIndex: vectors, summaryBm25: summaryIndex(tree) });
50
+ const rows = [];
51
+ for (const [query, predicate] of cases) {
52
+ const expected = visible.filter(predicate).map((record) => record.turn);
53
+ const expectedSet = new Set(expected);
54
+ const plan = planner.query(query, { queryVector: queryVectors.get(query), topK: 10, asOf: checkpoint });
55
+ const loaded = plan.fused.map((hit) => visible.find((record) => record.turn === hit.turn)).filter(Boolean);
56
+ const turns = loaded.map((record) => record.turn);
57
+ const expectedHits = turns.filter((turn) => expectedSet.has(turn));
58
+ const futureLeak = turns.filter((turn) => turn > checkpoint);
59
+ const latestExpected = expected.length === 0 ? null : Math.max(...expected);
60
+ rows.push({
61
+ query,
62
+ route: plan.route,
63
+ expected: expected.length,
64
+ latestExpected,
65
+ returned: turns.length,
66
+ expectedHits: expectedHits.length,
67
+ latestHit: latestExpected === null ? null : turns.includes(latestExpected),
68
+ futureLeak: futureLeak.length,
69
+ noise: turns.length - expectedHits.length,
70
+ chars: loaded.reduce((sum, record) => sum + record.text.length, 0),
71
+ top: turns.slice(0, 5),
72
+ });
73
+ }
74
+ output.push({ checkpoint, visible: visible.length, rows });
75
+ }
76
+ const summary = {
77
+ checkpoints: output.map((entry) => ({
78
+ checkpoint: entry.checkpoint,
79
+ visible: entry.visible,
80
+ futureLeaks: entry.rows.reduce((sum, row) => sum + row.futureLeak, 0),
81
+ latestMiss: entry.rows.filter((row) => row.latestHit === false).map((row) => row.query),
82
+ emptyFalse: entry.rows.filter((row) => row.expected === 0 && row.returned > 0).map((row) => row.query),
83
+ misses: entry.rows.filter((row) => row.expected > 0 && row.expectedHits === 0).map((row) => row.query),
84
+ noise: entry.rows.reduce((sum, row) => sum + row.noise, 0),
85
+ chars: entry.rows.reduce((sum, row) => sum + row.chars, 0),
86
+ })),
87
+ };
88
+ const result = { summary, output };
89
+ fs.writeFileSync(path.join(dir, 'LOOP-CHECKPOINTS.json'), JSON.stringify(result, null, 2));
90
+ console.log(JSON.stringify(summary, null, 2));
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Offline pre-turn harness: scripted user/Coo messages through the integration
4
+ * layer. No live session and no writes.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { readEventLog } from '../src/memory/event-log.mjs';
9
+ import { PreTurnController } from '../src/memory/pre-turn.mjs';
10
+ import { MemoryLoadState } from '../src/memory/load-gate.mjs';
11
+
12
+ const args = process.argv.slice(2);
13
+ const argOf = (name) => {
14
+ const i = args.indexOf(name);
15
+ return i >= 0 ? args[i + 1] : undefined;
16
+ };
17
+ const dir = argOf('--dir');
18
+ if (!dir) {
19
+ console.error('usage: node bin/memory-pre-turn-harness.mjs --dir .durable-memory');
20
+ process.exit(2);
21
+ }
22
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
23
+ const dev = { activity: '开发', topic: '技术' };
24
+ const run = { activity: '跑团', topic: '剧情' };
25
+ const controller = new PreTurnController({
26
+ records,
27
+ gate: new MemoryLoadState({ loadedFacets: dev, loadedAtTurn: 1490 }),
28
+ });
29
+ const messages = [
30
+ { text: '我们先写文档', role: 'user', facets: dev, eventSlots: { activity: '开发', topic: '技术', location: null }, confidence: 0.9 },
31
+ { text: '我们现在开始跑团吧', role: 'user', facets: run, eventSlots: { activity: '跑团', topic: '剧情', location: '灯室' }, confidence: 0.9 },
32
+ { text: '我推门进灯室', role: 'user', facets: run, eventSlots: { activity: '跑团', topic: '剧情', location: '灯室' }, confidence: 0.9 },
33
+ { text: 'Coo:灯室见', role: 'coo', facets: run, eventSlots: { activity: '跑团', topic: '剧情', location: '钟楼' }, confidence: 0.9 },
34
+ { text: '规则怎么写', role: 'user', facets: dev, eventSlots: { activity: '开发', topic: '规则', location: null }, confidence: 0.9 },
35
+ { text: '上次跑团怎么样了', role: 'user', facets: run, eventSlots: { activity: '跑团', topic: '剧情', location: '灯室' }, confidence: 0.9 },
36
+ { text: '开始跑团', role: 'assistant', facets: run, eventSlots: { activity: '跑团', topic: '剧情', location: '灯室' }, confidence: 0.9 },
37
+ { text: '继续', role: 'user', facets: run, eventSlots: { location: 42 }, confidence: 0.9 },
38
+ ];
39
+ const rows = messages.map((message, index) => {
40
+ const result = controller.step({ ...message, nowTurn: 1500 + index });
41
+ return {
42
+ text: message.text,
43
+ role: message.role,
44
+ action: result.action,
45
+ reason: result.reason,
46
+ boundaryScore: result.boundaryScore,
47
+ eventId: result.eventId,
48
+ eventError: result.eventError ?? null,
49
+ candidateTurn: result.candidateTurn,
50
+ segmentTurns: result.segment?.turns ?? 0,
51
+ telemetry: result.telemetry,
52
+ };
53
+ });
54
+ const summary = {
55
+ messages: rows.length,
56
+ loads: rows.filter((row) => row.action === 'load').length,
57
+ hints: rows.filter((row) => row.action === 'hint').length,
58
+ none: rows.filter((row) => row.action === 'none').length,
59
+ eventErrors: rows.filter((row) => row.eventError !== null).length,
60
+ };
61
+ const output = { dir, summary, rows };
62
+ fs.writeFileSync(path.join(dir, 'PRE-TURN-HARNESS.json'), JSON.stringify(output, null, 2));
63
+ console.log(JSON.stringify(output, null, 2));
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { readEventLog } from '../src/memory/event-log.mjs';
5
+ import { BM25Index } from '../src/memory/bm25.mjs';
6
+ import { VectorIndex } from '../src/memory/vector-index.mjs';
7
+ import { buildPlanner, summaryIndex } from '../src/memory/planner.mjs';
8
+ import { embedTexts } from '../src/facets/embedding.mjs';
9
+
10
+ const args = process.argv.slice(2);
11
+ const argOf = (name) => {
12
+ const i = args.indexOf(name);
13
+ return i >= 0 ? args[i + 1] : undefined;
14
+ };
15
+ const dir = argOf('--dir');
16
+ const query = argOf('--query');
17
+ const model = argOf('--model');
18
+ if (!dir || !query) {
19
+ console.error('usage: node bin/memory-query.mjs --dir .durable-memory --query "..." [--model ...]');
20
+ process.exit(2);
21
+ }
22
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
23
+ const bm25 = BM25Index.fromJSON(JSON.parse(fs.readFileSync(path.join(dir, 'bm25.json'), 'utf8')));
24
+ const vectors = VectorIndex.fromJSON(JSON.parse(fs.readFileSync(path.join(dir, 'vectors.json'), 'utf8')));
25
+ const tree = JSON.parse(fs.readFileSync(path.join(dir, 'summary-tree.json'), 'utf8'));
26
+ const [queryVector] = await embedTexts([query], model);
27
+ const planner = buildPlanner({
28
+ records,
29
+ bm25,
30
+ vectorIndex: vectors,
31
+ summaryBm25: summaryIndex(tree),
32
+ });
33
+ const result = planner.query(query, { queryVector, topK: 10 });
34
+ console.log(JSON.stringify({
35
+ query: result.query,
36
+ layers: result.layers,
37
+ facet: result.facet,
38
+ results: result.fused,
39
+ }, null, 2));
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Prequential hint replay: turn 1 -> end.
4
+ * At each turn, only prior turns are visible; if the current text mentions an
5
+ * alias, ask buildLoadHint and record every popup.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { readEventLog } from '../src/memory/event-log.mjs';
10
+ import { buildLoadHint } from '../src/facets/panel.mjs';
11
+ import { DEFAULT_ALIASES } from '../src/facets/schema.mjs';
12
+
13
+ const args = process.argv.slice(2);
14
+ const argOf = (name) => {
15
+ const i = args.indexOf(name);
16
+ return i >= 0 ? args[i + 1] : undefined;
17
+ };
18
+ const dir = argOf('--dir');
19
+ if (!dir) {
20
+ console.error('usage: node bin/memory-replay-hints.mjs --dir .durable-memory');
21
+ process.exit(2);
22
+ }
23
+ const records = readEventLog(path.join(dir, 'events.jsonl'));
24
+ const aliases = Object.keys(DEFAULT_ALIASES).sort((a, b) => b.length - a.length);
25
+ const hints = [];
26
+ for (let index = 0; index < records.length; index += 1) {
27
+ const current = records[index];
28
+ const visible = records.slice(0, index);
29
+ const entries = visible.map((record) => ({
30
+ turn: record.turn,
31
+ seqStart: record.seqStart,
32
+ seqEnd: record.seqEnd,
33
+ facets: record.facets,
34
+ unknown: false,
35
+ }));
36
+ const mentioned = aliases.filter((alias) => current.text.includes(alias));
37
+ for (const alias of mentioned) {
38
+ const hint = buildLoadHint(entries, {
39
+ query: alias,
40
+ currentTurn: current.turn,
41
+ recentTurns: 20,
42
+ minScore: 0.2,
43
+ });
44
+ if (hint === null) continue;
45
+ const turns = new Set();
46
+ let chars = 0;
47
+ for (const range of hint.ranges) {
48
+ for (const record of visible) {
49
+ if (record.seqStart >= range.seqStart && record.seqEnd <= range.seqEnd) {
50
+ turns.add(record.turn);
51
+ chars += record.text.length;
52
+ }
53
+ }
54
+ }
55
+ hints.push({
56
+ turn: current.turn,
57
+ alias,
58
+ text: hint.text,
59
+ topScore: hint.top.score,
60
+ rangeCount: hint.ranges.length,
61
+ rangeTurns: turns.size,
62
+ chars,
63
+ ranges: hint.ranges.map((range) => ({
64
+ seqStart: range.seqStart,
65
+ seqEnd: range.seqEnd,
66
+ turns: range.turns,
67
+ lastTurn: range.lastTurn,
68
+ })),
69
+ snippet: current.text.slice(0, 100).replace(/\s+/g, ' '),
70
+ });
71
+ }
72
+ }
73
+ const summary = {
74
+ records: records.length,
75
+ hints: hints.length,
76
+ byAlias: Object.fromEntries([...new Set(hints.map((hint) => hint.alias))].map((alias) => [alias, hints.filter((hint) => hint.alias === alias).length])),
77
+ futureLeaks: hints.filter((hint) => hint.ranges.some((range) => range.lastTurn >= hint.turn)).length,
78
+ large: hints.filter((hint) => hint.rangeTurns > 50 || hint.chars > 10000).length,
79
+ maxTurns: Math.max(0, ...hints.map((hint) => hint.rangeTurns)),
80
+ maxChars: Math.max(0, ...hints.map((hint) => hint.chars)),
81
+ };
82
+ const output = { dir, summary, hints };
83
+ fs.writeFileSync(path.join(dir, 'REPLAY-HINTS.json'), JSON.stringify(output, null, 2));
84
+ console.log(JSON.stringify(summary, null, 2));
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Scale benchmark for the exact durable-memory layers.
4
+ * Replicates the real 1,462-turn copy to 10k/50k for latency/memory testing.
5
+ */
6
+ import fs from 'node:fs';
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { buildEventLog } from '../src/memory/event-log.mjs';
10
+ import { BM25Index } from '../src/memory/bm25.mjs';
11
+ import { VectorIndex } from '../src/memory/vector-index.mjs';
12
+
13
+ const args = process.argv.slice(2);
14
+ const argOf = (name) => {
15
+ const i = args.indexOf(name);
16
+ return i >= 0 ? args[i + 1] : undefined;
17
+ };
18
+ const dir = argOf('--dir') ?? 'C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a';
19
+ const source = fs.readFileSync(path.join(dir, 'events.jsonl'), 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
20
+ const embeddings = JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400/embeddings-1500.json', 'utf8'));
21
+ const vectorBySourceTurn = new Map(embeddings.map((row) => [row.turn, row.vector]));
22
+ const sizes = [10000, 50000];
23
+ const out = [];
24
+ for (const size of sizes) {
25
+ const rows = [];
26
+ const vectorMap = new Map();
27
+ for (let index = 0; index < size; index += 1) {
28
+ const base = source[index % source.length];
29
+ const turn = 100000 + index;
30
+ rows.push({ ...base, turn, seqStart: 1000000 + index, seqEnd: 1000000 + index });
31
+ vectorMap.set(turn, vectorBySourceTurn.get(base.turn));
32
+ }
33
+ const buildStart = performance.now();
34
+ const records = buildEventLog(rows);
35
+ const bm25 = new BM25Index().build(records);
36
+ const vectors = new VectorIndex().build(records, vectorMap);
37
+ const buildMs = performance.now() - buildStart;
38
+ const queries = Array.from({ length: 100 }, (_, index) => source[index % source.length]);
39
+ const vectorStart = performance.now();
40
+ for (const row of queries) vectors.search(vectorBySourceTurn.get(row.turn), { topK: 10 });
41
+ const vectorMs = performance.now() - vectorStart;
42
+ const bm25Start = performance.now();
43
+ for (const row of queries) bm25.search(row.text, { topK: 10 });
44
+ const bm25Ms = performance.now() - bm25Start;
45
+ const mem = process.memoryUsage();
46
+ out.push({
47
+ size,
48
+ buildMs: Math.round(buildMs),
49
+ vector100Ms: Math.round(vectorMs),
50
+ vectorMsPerQuery: +(vectorMs / queries.length).toFixed(2),
51
+ bm25100Ms: Math.round(bm25Ms),
52
+ bm25MsPerQuery: +(bm25Ms / queries.length).toFixed(2),
53
+ heapMB: +(mem.heapUsed / 1048576).toFixed(1),
54
+ rssMB: +(mem.rss / 1048576).toFixed(1),
55
+ });
56
+ }
57
+ fs.writeFileSync(path.join(dir, 'SCALE-DURABLE.json'), JSON.stringify(out, null, 2));
58
+ console.log(JSON.stringify(out, null, 2));