@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.
- package/INSTALL.md +25 -0
- package/PANEL.md +92 -0
- package/README.md +286 -2
- package/RELEASE-NOTES.md +732 -0
- package/bin/bc-acceptance-full.mjs +91 -0
- package/bin/bc-acceptance-tight.mjs +66 -0
- package/bin/bc-acceptance.mjs +22 -0
- package/bin/bc-add-keywords.mjs +21 -0
- package/bin/bc-demo.mjs +47 -0
- package/bin/bc-expand-hint.mjs +179 -0
- package/bin/bc-ingest-index.mjs +35 -0
- package/bin/bc-ingest.mjs +31 -0
- package/bin/bc-label-demo.mjs +18 -0
- package/bin/bc-relabel.mjs +150 -0
- package/bin/bc-sessionize.mjs +59 -0
- package/bin/coref_pipeline.py +47 -0
- package/bin/coref_service.py +57 -0
- package/bin/debug_ckip.py +16 -0
- package/bin/debug_ner_zh.py +18 -0
- package/bin/doctor.mjs +275 -0
- package/bin/en_ner_error_audit.py +33 -0
- package/bin/eval-label-rules.mjs +342 -0
- package/bin/export-turns.py +68 -0
- package/bin/extract-user-paragraph.mjs +14 -0
- package/bin/extraction-composite-smoke.mjs +28 -0
- package/bin/extraction-latency.mjs +58 -0
- package/bin/extraction-user-smoke.mjs +28 -0
- package/bin/extraction_service.py +164 -0
- package/bin/facet-active-experiment.mjs +110 -0
- package/bin/facet-covid.mjs +8 -0
- package/bin/facet-curve.mjs +27 -0
- package/bin/facet-embedding-train.mjs +20 -0
- package/bin/facet-embedding-validate.mjs +83 -0
- package/bin/facet-eval-roleplay.mjs +42 -0
- package/bin/facet-eval.mjs +38 -0
- package/bin/facet-fold.mjs +31 -0
- package/bin/facet-load.mjs +16 -0
- package/bin/facet-mlp-scan.mjs +45 -0
- package/bin/facet-mvp.mjs +34 -0
- package/bin/facet-online-experiments.mjs +78 -0
- package/bin/facet-online-race.mjs +99 -0
- package/bin/facet-online-validate.mjs +51 -0
- package/bin/facet-panel.mjs +22 -0
- package/bin/facet-passage.mjs +41 -0
- package/bin/facet-random-validate.mjs +48 -0
- package/bin/facet-roleplay.mjs +33 -0
- package/bin/facet-scan.mjs +56 -0
- package/bin/facet-segment.mjs +34 -0
- package/bin/facet-train.mjs +19 -0
- package/bin/facet-validate.mjs +103 -0
- package/bin/facet-zeroshot.mjs +14 -0
- package/bin/install-templates.mjs +72 -0
- package/bin/memory-bench.mjs +53 -0
- package/bin/memory-build-summary.mjs +43 -0
- package/bin/memory-build-vector.mjs +31 -0
- package/bin/memory-build.mjs +41 -0
- package/bin/memory-calibration.mjs +70 -0
- package/bin/memory-closed-loop.mjs +119 -0
- package/bin/memory-dry-run.mjs +70 -0
- package/bin/memory-eval.mjs +104 -0
- package/bin/memory-event-alias-test.mjs +89 -0
- package/bin/memory-extraction-bench.mjs +58 -0
- package/bin/memory-load-gate-replay.mjs +137 -0
- package/bin/memory-loop-test.mjs +90 -0
- package/bin/memory-pre-turn-harness.mjs +63 -0
- package/bin/memory-query.mjs +39 -0
- package/bin/memory-replay-hints.mjs +84 -0
- package/bin/memory-scale.mjs +58 -0
- package/bin/memory-scenario-test.mjs +146 -0
- package/bin/memory-user-loop.mjs +95 -0
- package/bin/ner_bench_tiny_zh.py +59 -0
- package/bin/ner_bench_zh.py +60 -0
- package/bin/ner_conll_en.py +139 -0
- package/bin/ner_direct_en.py +123 -0
- package/bin/ner_direct_zh.py +100 -0
- package/bin/ner_pipeline.py +208 -0
- package/bin/noise-selfcheck.mjs +50 -0
- package/bin/pack-integrity.mjs +47 -0
- package/bin/panel-check.mjs +57 -0
- package/bin/plugin-integrity.mjs +74 -0
- package/bin/preview-live.mjs +13 -0
- package/bin/public-install-check.mjs +222 -0
- package/bin/relabel-apply.mjs +272 -0
- package/bin/relabel-dry-run.mjs +381 -0
- package/bin/relik_pipeline.py +39 -0
- package/bin/relik_service.py +52 -0
- package/bin/srl_pipeline.py +55 -0
- package/bin/srl_service.py +68 -0
- package/bin/summary-index.mjs +151 -0
- package/bin/summary-query.mjs +79 -0
- package/bin/vec-check.mjs +27 -0
- package/bin/zh_ner_error_audit.py +36 -0
- package/bin/zh_ner_slot_eval.py +38 -0
- package/client.js +760 -0
- package/cordis.patch.yml +378 -0
- package/docs/API-NOTES.md +1743 -0
- package/docs/ARCHITECTURE.md +355 -0
- package/docs/CACHE.md +253 -0
- package/docs/CONDUCT.md +30 -0
- package/docs/CREATOR-HANDOFF.md +102 -0
- package/docs/EXPERIMENT.md +136 -0
- package/docs/FACET-MVP-REPORT.md +473 -0
- package/docs/GUIDE-0.4.66.md +195 -0
- package/docs/GUIDE.md +127 -0
- package/docs/MECHANISM.md +243 -0
- package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
- package/docs/README.md +37 -0
- package/docs/SESSION-LAYOUT.md +151 -0
- package/docs/history-map.md +249 -0
- package/docs/relabel-migration-wiring.md +366 -0
- package/guides/00-READ-FIRST.md +206 -0
- package/guides/01-INSTALL.md +207 -0
- package/guides/02-TABLES.md +225 -0
- package/guides/03-AUDIT.md +201 -0
- package/guides/04-REBUILD.md +306 -0
- package/guides/05-FIRST-RUN.md +191 -0
- package/guides/06-EMBEDDING.md +146 -0
- package/guides/07-PTC-AND-ASSETS.md +221 -0
- package/icon.svg +7 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/models/README.md +13 -0
- package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
- package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
- package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
- package/package.json +139 -4
- package/src/asset-tool.mjs +237 -0
- package/src/assets.mjs +244 -0
- package/src/backfill.mjs +74 -0
- package/src/backup.mjs +80 -0
- package/src/deadend.mjs +95 -0
- package/src/deleted-store.mjs +35 -0
- package/src/engine.mjs +3831 -0
- package/src/facets/classifier.mjs +146 -0
- package/src/facets/embedding-service.mjs +53 -0
- package/src/facets/embedding-worker.mjs +27 -0
- package/src/facets/embedding.mjs +163 -0
- package/src/facets/extract.mjs +80 -0
- package/src/facets/fold-plan.mjs +84 -0
- package/src/facets/hybrid.mjs +29 -0
- package/src/facets/index.mjs +113 -0
- package/src/facets/linear-probe.mjs +43 -0
- package/src/facets/load.mjs +37 -0
- package/src/facets/mlp-probe.mjs +54 -0
- package/src/facets/mlp-service.mjs +53 -0
- package/src/facets/mlp-worker.mjs +26 -0
- package/src/facets/online-knn.mjs +58 -0
- package/src/facets/online-models.mjs +227 -0
- package/src/facets/panel-state.mjs +23 -0
- package/src/facets/panel.mjs +73 -0
- package/src/facets/random.mjs +26 -0
- package/src/facets/scanner.mjs +35 -0
- package/src/facets/schema.mjs +75 -0
- package/src/facets/seed-examples.mjs +58 -0
- package/src/facets/sensitivity.mjs +98 -0
- package/src/facets/service.mjs +53 -0
- package/src/facets/store.mjs +39 -0
- package/src/facets/worker.mjs +23 -0
- package/src/folds-store.mjs +109 -0
- package/src/groups.mjs +167 -0
- package/src/handoff-tool.mjs +51 -0
- package/src/handoff.mjs +127 -0
- package/src/index.mjs +649 -0
- package/src/inject-policy.mjs +17 -0
- package/src/learning.mjs +88 -0
- package/src/memory/alias-proposal.mjs +40 -0
- package/src/memory/bc-audit.mjs +245 -0
- package/src/memory/bc-index-updater.mjs +404 -0
- package/src/memory/bc-index.mjs +48 -0
- package/src/memory/bc-label.mjs +220 -0
- package/src/memory/bc-panel-state.mjs +107 -0
- package/src/memory/bc-pre-step.mjs +463 -0
- package/src/memory/bc-recall.mjs +470 -0
- package/src/memory/bc-shift.mjs +123 -0
- package/src/memory/bc-store.mjs +41 -0
- package/src/memory/bm25.mjs +112 -0
- package/src/memory/calibration.mjs +54 -0
- package/src/memory/chatter.mjs +29 -0
- package/src/memory/cmap-file.mjs +107 -0
- package/src/memory/concepts-file.mjs +41 -0
- package/src/memory/coref-slots.mjs +40 -0
- package/src/memory/event-alias-ledger.mjs +87 -0
- package/src/memory/event-log.mjs +95 -0
- package/src/memory/event-model.mjs +132 -0
- package/src/memory/event-segmenter.mjs +36 -0
- package/src/memory/event-summary-tree.mjs +122 -0
- package/src/memory/extraction-adapter.mjs +76 -0
- package/src/memory/extraction-composite.mjs +75 -0
- package/src/memory/extraction-core.mjs +76 -0
- package/src/memory/extraction-pre-step.mjs +161 -0
- package/src/memory/extraction-provider.mjs +300 -0
- package/src/memory/extraction-score.mjs +36 -0
- package/src/memory/facet-stabilizer.mjs +59 -0
- package/src/memory/history-map.mjs +139 -0
- package/src/memory/language-router.mjs +30 -0
- package/src/memory/load-gate.mjs +161 -0
- package/src/memory/mechanical-note.mjs +104 -0
- package/src/memory/onboarding.mjs +163 -0
- package/src/memory/persona.mjs +78 -0
- package/src/memory/planner.mjs +249 -0
- package/src/memory/pre-turn.mjs +80 -0
- package/src/memory/query-aliases.mjs +39 -0
- package/src/memory/recall-cue.mjs +43 -0
- package/src/memory/recall.mjs +65 -0
- package/src/memory/referent-candidates.mjs +55 -0
- package/src/memory/referent-ledger.mjs +86 -0
- package/src/memory/relabel-migrate.mjs +376 -0
- package/src/memory/relik-adapter.mjs +25 -0
- package/src/memory/special-keywords.mjs +60 -0
- package/src/memory/structured-adapters.mjs +75 -0
- package/src/memory/summary-index.mjs +335 -0
- package/src/memory/summary-tree.mjs +110 -0
- package/src/memory/term-extractor.mjs +44 -0
- package/src/memory/tokenize.mjs +9 -0
- package/src/memory/vector-index.mjs +54 -0
- package/src/memory/window-summary.mjs +133 -0
- package/src/mood.mjs +133 -0
- package/src/peer-tool.mjs +63 -0
- package/src/peer.mjs +144 -0
- package/src/policy.mjs +170 -0
- package/src/preflight.mjs +113 -0
- package/src/provenance.mjs +94 -0
- package/src/range.mjs +75 -0
- package/src/recall-exact-tool.mjs +267 -0
- package/src/recall-tool.mjs +521 -0
- package/src/recheck.mjs +99 -0
- package/src/save-start.mjs +29 -0
- package/src/session-guard.mjs +140 -0
- package/src/session-log-tool.mjs +322 -0
- package/src/session-log.mjs +606 -0
- package/src/skills.mjs +111 -0
- package/src/split-range.mjs +115 -0
- package/src/state.mjs +198 -0
- package/src/t0.mjs +149 -0
- package/src/tag-confirm.mjs +78 -0
- package/src/tag-detect.mjs +42 -0
- package/src/tag-maintenance.mjs +285 -0
- package/src/tag-reject.mjs +73 -0
- package/src/tags-store.mjs +94 -0
- package/src/tags.mjs +692 -0
- package/src/threads-backend.mjs +26 -0
- package/src/threads-core.mjs +1887 -0
- package/src/threads-store.mjs +207 -0
- package/src/threads-tool.mjs +522 -0
- package/src/threads.mjs +929 -0
- package/src/tool-preview.mjs +427 -0
- package/src/topic.mjs +63 -0
- package/src/training.mjs +75 -0
- package/src/turn-tag.mjs +62 -0
- package/src/user-turn.mjs +39 -0
- package/templates/cmap.md +11 -0
- package/templates/concepts.md +12 -0
- package/templates/habits.md +40 -0
- package/templates/keywords.md +6 -0
- package/templates/rebuild.md +134 -0
- package/templates/rules-safe.md +184 -0
- 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));
|