@jjh32456/dsh-longlive 1.6.7 → 1.8.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 (289) hide show
  1. package/CHANGELOG.md +70 -1508
  2. package/LICENSE +1 -1
  3. package/QUICKSTART.md +34 -13
  4. package/README.en.md +18 -7
  5. package/README.md +34 -9
  6. package/SPEC.md +5 -5
  7. package/cordis.patch.yml +23 -9
  8. package/docs/ANSWER-GUARD.md +92 -0
  9. package/docs/ARCHITECTURE.md +4 -3
  10. package/docs/AUTO-RECALL-WIRING.md +1 -1
  11. package/docs/AUTO-RECALL.md +15 -9
  12. package/docs/CONFIG.md +168 -35
  13. package/docs/ERRORS.md +17 -5
  14. package/docs/FAQ.md +7 -7
  15. package/docs/FEATURES.md +96 -28
  16. package/docs/MIGRATION-1.6.0.md +2 -2
  17. package/docs/OPERATIONS.md +6 -6
  18. package/docs/PANEL-INTEGRATION.md +6 -0
  19. package/docs/PRIVACY.md +88 -0
  20. package/docs/QUICKSTART.md +5 -5
  21. package/docs/README.en.md +4 -2
  22. package/docs/RESIDENT-WATCHER.md +1 -1
  23. package/docs/SESSION-WATCH.md +6 -6
  24. package/docs/TROUBLESHOOTING.md +4 -4
  25. package/package.json +47 -117
  26. package/runbook.md +2 -2
  27. package/scripts/backfill-auto-cards.mjs +98 -0
  28. package/scripts/install-surgery-task.ps1 +5 -2
  29. package/scripts/lib/session-watch-fixture.mjs +1 -1
  30. package/scripts/longlive-doctor.mjs +6 -0
  31. package/scripts/migrate-180.mjs +163 -0
  32. package/scripts/recall-index-build.mjs +128 -0
  33. package/scripts/session-watch-drill.mjs +1 -1
  34. package/scripts/surgery-run.cmd +1 -1
  35. package/scripts/unattended-surgery.ps1 +5 -2
  36. package/scripts/verify-card-anchors.mjs +138 -0
  37. package/src/actions/audit.js +125 -0
  38. package/src/actions/evidence.js +122 -0
  39. package/src/actions/fs.js +162 -0
  40. package/src/activity/index.js +760 -0
  41. package/src/activity/model.js +351 -0
  42. package/src/activity/store.js +644 -0
  43. package/src/activity/thinking.js +176 -0
  44. package/src/adapters/constants.js +11 -0
  45. package/src/adapters/engram/data/aliases.json +51 -0
  46. package/src/adapters/engram/data/policy.json +8 -0
  47. package/src/adapters/engram/data/routing-rules.json +546 -0
  48. package/src/adapters/engram/index.js +235 -0
  49. package/src/adapters/registry.js +315 -0
  50. package/src/answer/audit.js +193 -0
  51. package/src/answer/evidence.js +30 -0
  52. package/src/answer/guard.js +1370 -0
  53. package/src/answer/index.js +1067 -0
  54. package/src/answer/query.js +111 -0
  55. package/src/answer/second-hop.mjs +101 -0
  56. package/src/auto-truncate.js +1 -1
  57. package/src/config.js +171 -47
  58. package/src/deps.js +6 -4
  59. package/src/features.js +182 -6
  60. package/src/heartbeat.js +156 -23
  61. package/src/identity/handover.js +52 -1
  62. package/src/identity/index.js +13 -5
  63. package/src/identity/inject-position.js +2 -2
  64. package/src/index.js +97 -7
  65. package/src/lifecycle/gap-clocks.js +328 -0
  66. package/src/lifecycle/index.js +269 -0
  67. package/src/live.js +22 -2
  68. package/src/memory/activity-capture.js +229 -0
  69. package/src/memory/anchors.js +194 -0
  70. package/src/memory/audit-records.js +222 -0
  71. package/src/memory/auto-cards.js +401 -0
  72. package/src/memory/capture.js +442 -0
  73. package/src/memory/card-anchors.js +203 -0
  74. package/src/memory/conflicts.js +106 -0
  75. package/src/memory/decay.js +311 -0
  76. package/src/memory/distill.js +471 -0
  77. package/src/memory/event-index.js +917 -0
  78. package/src/memory/evidence-chain.js +295 -0
  79. package/src/memory/evidence.js +145 -0
  80. package/src/memory/filter-runner.js +1 -1
  81. package/src/memory/filter.js +5 -1
  82. package/src/memory/forget-v2.js +410 -0
  83. package/src/memory/forget.js +71 -0
  84. package/src/memory/frames-activation.js +247 -0
  85. package/src/memory/fts-query.js +174 -0
  86. package/src/memory/governance.js +604 -0
  87. package/src/memory/index.js +1 -0
  88. package/src/memory/l2-gate.js +169 -0
  89. package/src/memory/lock.js +121 -0
  90. package/src/memory/migrate.js +139 -0
  91. package/src/memory/recall-v2.js +223 -0
  92. package/src/memory/recall.js +65 -2
  93. package/src/memory/records.js +425 -0
  94. package/src/memory/retention.js +128 -0
  95. package/src/memory/review.js +124 -0
  96. package/src/memory/trust.js +88 -0
  97. package/src/memory/version-chain.js +264 -0
  98. package/src/memory/wal.js +137 -0
  99. package/src/memory/working-frame.js +516 -0
  100. package/src/metrics/classify.mjs +5 -3
  101. package/src/metrics/index.mjs +21 -1
  102. package/src/metrics/prestep-latency.mjs +300 -0
  103. package/src/metrics/store.mjs +25 -0
  104. package/src/policy-profile.js +18 -3
  105. package/src/recall/anaphora.mjs +87 -0
  106. package/src/recall/cards.mjs +7 -0
  107. package/src/recall/embedding.mjs +8 -8
  108. package/src/recall/entities.mjs +112 -0
  109. package/src/recall/index.js +235 -19
  110. package/src/recall/inject-order.mjs +10 -8
  111. package/src/recall/privacy.mjs +105 -16
  112. package/src/recall/recall-core.mjs +116 -10
  113. package/src/recall/shared-visibility.js +302 -0
  114. package/src/recall/time-normalizer.mjs +196 -0
  115. package/src/role/activation.js +191 -0
  116. package/src/role/attention.js +318 -0
  117. package/src/role/base.js +36 -0
  118. package/src/role/channels.js +423 -0
  119. package/src/role/index.js +425 -0
  120. package/src/role/interfaces.js +115 -0
  121. package/src/role/personality.js +370 -0
  122. package/src/role/readers.js +151 -0
  123. package/src/role/relationships.js +477 -0
  124. package/src/role/store.js +202 -0
  125. package/src/role/writeprotect.js +373 -0
  126. package/src/router/aliases.json +176 -25
  127. package/src/router/background/agent-runner.mjs +1 -1
  128. package/src/router/background/anchor.mjs +1 -1
  129. package/src/router/background/audit.mjs +2 -2
  130. package/src/router/background/budget-ledger.mjs +2 -2
  131. package/src/router/background/host-sim.mjs +1 -1
  132. package/src/router/background/memory.mjs +4 -4
  133. package/src/router/background/queue.mjs +1 -1
  134. package/src/router/background/retry.mjs +1 -1
  135. package/src/router/background/router.mjs +1 -1
  136. package/src/router/background/stream.mjs +1 -1
  137. package/src/router/background/tool-policy.mjs +1 -1
  138. package/src/router/design-headings.json +5 -5
  139. package/src/router/face/config.mjs +1 -1
  140. package/src/router/face/identity.mjs +3 -3
  141. package/src/router/judge.mjs +8 -7
  142. package/src/router/policies/generic.json +21 -4
  143. package/src/router/route.js +109 -22
  144. package/src/router/router.mjs +10 -6
  145. package/src/router/routing-rules.json +260 -445
  146. package/src/router/run-sim.mjs +8 -8
  147. package/src/router/runtime.js +23 -12
  148. package/src/router/service/board.mjs +95 -15
  149. package/src/router/service/config.mjs +5 -5
  150. package/src/router/service/context.mjs +1 -1
  151. package/src/router/service/hint.mjs +14 -0
  152. package/src/router/service/queue.mjs +1 -1
  153. package/src/router/service/service.mjs +27 -11
  154. package/src/router/shadow.mjs +4 -3
  155. package/src/surgery-recovery.js +2 -2
  156. package/src/tool-output.js +2 -2
  157. package/src/tools.js +39 -7
  158. package/src/truncate-visible.js +2 -2
  159. package/src/ui/actions.js +1312 -0
  160. package/src/ui/panel-api.js +367 -0
  161. package/src/ui/panel-auth.js +251 -0
  162. package/src/ui/panel-integrity.js +243 -0
  163. package/src/ui/routes-readonly.js +695 -0
  164. package/src/ui/routes.js +3 -0
  165. package/docs/DEV.md +0 -279
  166. package/docs/PRESENCE-BUS-WIRING.md +0 -491
  167. package/docs/PRESENCE-BUS.md +0 -694
  168. package/docs/SURGERY.md +0 -186
  169. package/scripts/mark-core-cards.mjs +0 -289
  170. package/scripts/pack.mjs +0 -392
  171. package/scripts/router-proposals.mjs +0 -386
  172. package/src/router/fixtures/M1.json +0 -16
  173. package/src/router/fixtures/M2.json +0 -16
  174. package/src/router/fixtures/M3.json +0 -20
  175. package/src/router/fixtures/M4.json +0 -20
  176. package/src/router/fixtures/M5.json +0 -45
  177. package/src/router/fixtures/M6.json +0 -53
  178. package/src/router/fixtures/M7.json +0 -46
  179. package/src/router/fixtures/S1.json +0 -20
  180. package/src/router/fixtures/S2.json +0 -23
  181. package/src/router/fixtures/S3.json +0 -20
  182. package/src/router/fixtures/S4.json +0 -34
  183. package/src/router/fixtures/S5.json +0 -43
  184. package/src/router/fixtures/S6.json +0 -47
  185. package/src/router/fixtures/T1.json +0 -23
  186. package/src/router/fixtures/T2.json +0 -16
  187. package/src/router/fixtures/T3.json +0 -19
  188. package/src/router/fixtures/T4.json +0 -16
  189. package/src/router/fixtures/X1.json +0 -17
  190. package/src/router/fixtures/X2.json +0 -20
  191. package/src/router/gen-design-headings.mjs +0 -107
  192. package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
  193. package/test/auto-recall.test.mjs +0 -474
  194. package/test/auto-truncate.test.mjs +0 -931
  195. package/test/autobind.test.mjs +0 -385
  196. package/test/bus.test.mjs +0 -1572
  197. package/test/cli-guard.test.mjs +0 -198
  198. package/test/conclusion.test.mjs +0 -408
  199. package/test/config-schema.test.mjs +0 -204
  200. package/test/d6-output.test.mjs +0 -194
  201. package/test/d6.test.mjs +0 -395
  202. package/test/deps.test.mjs +0 -342
  203. package/test/doctor-setup.test.mjs +0 -297
  204. package/test/embedding-recall.test.mjs +0 -347
  205. package/test/feature-flags.test.mjs +0 -568
  206. package/test/filter-shadow.test.mjs +0 -170
  207. package/test/fixtures/baseline-0.8.0-registration.json +0 -556
  208. package/test/fixtures/d6-header-tools.json +0 -1
  209. package/test/fixtures/lock-child.mjs +0 -67
  210. package/test/fixtures/pre-step-container.mjs +0 -100
  211. package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
  212. package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
  213. package/test/fixtures/profile-session-longlive-config.json +0 -20
  214. package/test/fixtures/proposal-cas-child.mjs +0 -66
  215. package/test/host-engine-status.test.mjs +0 -307
  216. package/test/hot-truncate.test.mjs +0 -1349
  217. package/test/identity-card-marker.test.mjs +0 -307
  218. package/test/identity-router/run.mjs +0 -721
  219. package/test/integration-pre-step.test.mjs +0 -553
  220. package/test/live.test.mjs +0 -1020
  221. package/test/maintenance-schedule.test.mjs +0 -538
  222. package/test/memory.test.mjs +0 -2828
  223. package/test/metrics.test.mjs +0 -478
  224. package/test/orchestrator.test.mjs +0 -266
  225. package/test/p1_probe.mjs +0 -97
  226. package/test/packaging.test.mjs +0 -182
  227. package/test/portability.test.mjs +0 -343
  228. package/test/rc4-regressions.test.mjs +0 -562
  229. package/test/rc5-regressions.test.mjs +0 -197
  230. package/test/rc6-chinese.test.mjs +0 -394
  231. package/test/router-1100/apply-off.test.mjs +0 -175
  232. package/test/router-1100/budget-reconcile.test.mjs +0 -215
  233. package/test/router-1100/config-context.test.mjs +0 -211
  234. package/test/router-1100/helpers.mjs +0 -129
  235. package/test/router-1100/hint-envelope.test.mjs +0 -150
  236. package/test/router-1100/lock-crossproc.test.mjs +0 -206
  237. package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
  238. package/test/router-1100/router-apply.test.mjs +0 -372
  239. package/test/router-1100/router-fixb.test.mjs +0 -602
  240. package/test/router-1100/router-proposals.test.mjs +0 -405
  241. package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
  242. package/test/router-1100/router-security-cr.test.mjs +0 -333
  243. package/test/router-1100/service-lifecycle.test.mjs +0 -220
  244. package/test/router-1100/tools-contract.test.mjs +0 -164
  245. package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
  246. package/test/router-1100/v6-contract.test.mjs +0 -158
  247. package/test/router-1100/v6b-contract.test.mjs +0 -293
  248. package/test/router-1100/v6c-contract.test.mjs +0 -249
  249. package/test/router-1100/wal-rotation.test.mjs +0 -375
  250. package/test/router-1100/wp1-config.test.mjs +0 -170
  251. package/test/router-1100/wp1-default-off.test.mjs +0 -77
  252. package/test/router-1100/wp1-model-status.test.mjs +0 -90
  253. package/test/router-1100/wp1-status.test.mjs +0 -220
  254. package/test/router-1100/wp1-tools.test.mjs +0 -146
  255. package/test/router-1100/wp5-cli.test.mjs +0 -240
  256. package/test/router-1100/wp5-model-auto.test.mjs +0 -418
  257. package/test/router-1100/wp6-merge.test.mjs +0 -386
  258. package/test/router-1100/wp6-wake.test.mjs +0 -502
  259. package/test/router-background/audit.test.mjs +0 -37
  260. package/test/router-background/budget-ledger.test.mjs +0 -330
  261. package/test/router-background/cross-process.test.mjs +0 -222
  262. package/test/router-background/fixtures/cross-child.mjs +0 -85
  263. package/test/router-background/fixtures/real-shapes.mjs +0 -239
  264. package/test/router-background/router.test.mjs +0 -1044
  265. package/test/router-background/stream-retry.test.mjs +0 -358
  266. package/test/router-background/tool-agent.test.mjs +0 -384
  267. package/test/router-face/bridge.test.mjs +0 -169
  268. package/test/router-face/face.test.mjs +0 -316
  269. package/test/router-failclosed.test.mjs +0 -466
  270. package/test/router-identity.test.mjs +0 -384
  271. package/test/router-l1-l2.contract.test.mjs +0 -1075
  272. package/test/router-memory-tools.test.mjs +0 -262
  273. package/test/router-policy.test.mjs +0 -225
  274. package/test/router-runtime.test.mjs +0 -438
  275. package/test/router-shadow.test.mjs +0 -287
  276. package/test/router.test.mjs +0 -903
  277. package/test/run-rc.mjs +0 -299
  278. package/test/run.mjs +0 -2469
  279. package/test/safety-defaults.test.mjs +0 -179
  280. package/test/seeded-load.test.mjs +0 -436
  281. package/test/session-original.test.mjs +0 -338
  282. package/test/surgery-recovery.test.mjs +0 -936
  283. package/test/t7_drive.mjs +0 -151
  284. package/test/tool-output.test.mjs +0 -393
  285. package/test/tool-schema.test.mjs +0 -177
  286. package/test/tools_contract.test.mjs +0 -716
  287. package/test/truncate-visible.test.mjs +0 -1150
  288. package/test/ui-flags.test.mjs +0 -477
  289. package/test/v4-hardening.test.mjs +0 -427
@@ -1,347 +0,0 @@
1
- /**
2
- * A3/A4 自测:向量索引(tokenizer / 增量缓存 / 融合打分)与 autoRecall 的
3
- * recall-log 记账。只依赖临时目录与测试 embedder,不加载真模型、不碰 profile。
4
- */
5
-
6
- import test from 'node:test';
7
- import assert from 'node:assert/strict';
8
- import fs from 'node:fs';
9
- import os from 'node:os';
10
- import path from 'node:path';
11
-
12
- import {
13
- EMBEDDING_VERSION,
14
- buildVectorIndex,
15
- cardChunkTexts,
16
- createEmbeddingRuntime,
17
- loadTokenizer,
18
- normalizeEmbeddingConfig,
19
- } from '../src/recall/embedding.mjs';
20
- import { chooseRecall } from '../src/recall/recall-core.mjs';
21
- import { apply as applyAutoRecall } from '../src/recall/index.js';
22
- import { normalizeFeatureConfig } from '../src/features.js';
23
- import { PreStepBus, makeUserMessageFactory, textMessage } from './fixtures/pre-step-container.mjs';
24
-
25
- function tmpRoot(t) {
26
- const root = fs.mkdtempSync(path.join(os.tmpdir(), 'longlive-embed-'));
27
- t.after(() => fs.rmSync(root, { recursive: true, force: true }));
28
- return root;
29
- }
30
-
31
- function writeFile(root, rel, text) {
32
- const file = path.join(root, rel);
33
- fs.mkdirSync(path.dirname(file), { recursive: true });
34
- fs.writeFileSync(file, text, 'utf8');
35
- return file;
36
- }
37
-
38
- function makeEmbedder({ fingerprint = 'test-fp', vector = [1, 0], dim = 2 } = {}) {
39
- const docTexts = [];
40
- const embedDocument = async (text) => {
41
- docTexts.push(text);
42
- return Float32Array.from(vector);
43
- };
44
- return {
45
- dim,
46
- modelFingerprint: fingerprint,
47
- docTexts,
48
- embedQuery: async () => Float32Array.from(vector),
49
- embedDocument,
50
- embedMany: async (texts) => Promise.all(texts.map((text) => embedDocument(text))),
51
- };
52
- }
53
-
54
- function makeCtx(bus) {
55
- const warns = [];
56
- return {
57
- warns,
58
- ctx: {
59
- on: bus.on.bind(bus),
60
- logger: {
61
- warn: (...args) => warns.push(args.join(' ')),
62
- info: () => {},
63
- },
64
- },
65
- };
66
- }
67
-
68
- test('embedding 默认关闭,权重/阈值/缓存目录可配置', () => {
69
- const config = normalizeEmbeddingConfig({}, { memoryRoot: 'X:/mem' });
70
- assert.equal(config.enabled, false);
71
- assert.equal(config.keywordWeight, 0.6);
72
- assert.equal(config.vectorWeight, 0.4);
73
- assert.equal(config.semanticAdmit, 0.55);
74
- assert.equal(config.minFusedScore, 0.2);
75
- assert.equal(config.maxSeqLen, 256);
76
- assert.equal(config.cacheDir, path.join('X:/mem', '.longlive', 'embeddings'));
77
-
78
- const custom = normalizeEmbeddingConfig(
79
- { enabled: true, keywordWeight: 0.7, vectorWeight: 0.3, semanticAdmit: 0.6 },
80
- { memoryRoot: 'X:/mem' },
81
- );
82
- assert.equal(custom.enabled, true);
83
- assert.equal(custom.keywordWeight, 0.7);
84
- assert.equal(custom.vectorWeight, 0.3);
85
- assert.equal(custom.semanticAdmit, 0.6);
86
- });
87
-
88
- test('autoRecall 注入预算硬上限 2000 token(A4)', () => {
89
- const normalized = normalizeFeatureConfig({ autoRecall: { enabled: true, budgetTokens: 50000 } });
90
- assert.equal(normalized.autoRecall.budgetTokens, 2000);
91
- const small = normalizeFeatureConfig({ autoRecall: { enabled: true, budgetTokens: 400 } });
92
- assert.equal(small.autoRecall.budgetTokens, 400);
93
- });
94
-
95
- test('tokenizer.json 自解析:中文单字 + 英文 WordPiece + maxLen 截断', (t) => {
96
- const root = tmpRoot(t);
97
- const vocab = {
98
- '[PAD]': 0,
99
- '[UNK]': 1,
100
- '[CLS]': 2,
101
- '[SEP]': 3,
102
- '[MASK]': 4,
103
- 你: 5,
104
- 好: 6,
105
- play: 7,
106
- '##ing': 8,
107
- 世: 9,
108
- 界: 10,
109
- };
110
- const file = writeFile(root, 'tokenizer.json', JSON.stringify({
111
- normalizer: { type: 'BertNormalizer', clean_text: true, handle_chinese_chars: true, lowercase: false },
112
- pre_tokenizer: { type: 'BertPreTokenizer' },
113
- model: { type: 'WordPiece', unk_token: '[UNK]', continuing_subword_prefix: '##', max_input_chars_per_word: 100, vocab },
114
- added_tokens: [
115
- { id: 0, content: '[PAD]' },
116
- { id: 1, content: '[UNK]' },
117
- { id: 2, content: '[CLS]' },
118
- { id: 3, content: '[SEP]' },
119
- { id: 4, content: '[MASK]' },
120
- ],
121
- }));
122
- const tokenizer = loadTokenizer(file);
123
- assert.equal(tokenizer.vocabSize, 11);
124
- assert.deepEqual(tokenizer.encode('你好 playing'), [2, 5, 6, 7, 8, 3]);
125
- assert.deepEqual(tokenizer.encode('你好 playing', { maxLen: 4 }), [2, 5, 6, 3]);
126
- assert.deepEqual(tokenizer.tokensOf('playing'), ['play', '##ing']);
127
- assert.equal(tokenizer.decode([2, 5, 6, 3]), '[CLS] 你 好 [SEP]');
128
- });
129
-
130
- test('向量缓存按卡增量:未改复用、改一张只重算一张、指纹变全量', async (t) => {
131
- const root = tmpRoot(t);
132
- const cachePath = path.join(root, 'index.json');
133
- const config = normalizeEmbeddingConfig({}, { memoryRoot: root });
134
- const cards = [
135
- { path: 'topics/a.md', title: 'A', scopeIn: ['甲'], aliases: [], body: '甲卡的正文。' },
136
- { path: 'topics/b.md', title: 'B', scopeIn: ['乙'], aliases: [], body: '乙卡的正文。' },
137
- ];
138
- const embedder = makeEmbedder();
139
-
140
- const first = await buildVectorIndex({ cards, config, embedder, cachePath });
141
- assert.equal(first.stats.embeddedCards, 2);
142
- assert.equal(first.stats.reusedCards, 0);
143
- assert.equal(first.stats.written, true);
144
- assert.equal(first.cardVectors.get('topics/a.md')[0].length, 2);
145
- assert.equal(fs.existsSync(cachePath), true);
146
- const cache = JSON.parse(fs.readFileSync(cachePath, 'utf8'));
147
- assert.equal(cache.version, EMBEDDING_VERSION);
148
- assert.equal(cache.modelFingerprint, 'test-fp');
149
- assert.deepEqual(Object.keys(cache.cards).sort(), ['topics/a.md', 'topics/b.md']);
150
-
151
- const second = await buildVectorIndex({ cards, config, embedder, cachePath });
152
- assert.equal(second.stats.embeddedCards, 0);
153
- assert.equal(second.stats.reusedCards, 2);
154
-
155
- cards[1] = { ...cards[1], body: '乙卡的正文改了。' };
156
- const third = await buildVectorIndex({ cards, config, embedder, cachePath });
157
- assert.equal(third.stats.embeddedCards, 1);
158
- assert.equal(third.stats.reusedCards, 1);
159
- assert.equal(embedder.docTexts.filter((text) => text.includes('乙卡')).length, 2);
160
-
161
- const other = makeEmbedder({ fingerprint: 'test-fp-2' });
162
- const fourth = await buildVectorIndex({ cards, config, embedder: other, cachePath });
163
- assert.equal(fourth.stats.embeddedCards, 2);
164
- assert.equal(fourth.stats.reusedCards, 0);
165
- });
166
-
167
- test('融合打分:关键词+向量、纯语义准入、静默不误召、权重可调', () => {
168
- const cards = [
169
- { path: 'topics/a.md', title: '沙箱卡', scopeIn: ['沙箱', 'pwsh'], aliases: [], body: '沙箱里 pwsh 起不来。' },
170
- { path: 'topics/b.md', title: '语义卡', scopeIn: ['量子纠缠'], aliases: [], body: '只靠语义进来的卡。' },
171
- ];
172
- const queryText = '沙箱 pwsh 起不来怎么办';
173
- const queryVector = Float32Array.from([1, 0]);
174
- const vectors = new Map([
175
- ['topics/a.md', [Float32Array.from([1, 0])]],
176
- ['topics/b.md', [Float32Array.from([0.9, 0.1])]],
177
- ]);
178
-
179
- const fused = chooseRecall({
180
- queryText, cards, queryVector, vectors, budget: 1200,
181
- options: { maxItems: 3, trace: true },
182
- });
183
- assert.equal(fused.items.length, 2);
184
- assert.equal(fused.items[0].path, 'topics/a.md');
185
- assert.equal(fused.items[0].reason, 'keyword+vector');
186
- assert.equal(fused.items[1].path, 'topics/b.md');
187
- assert.equal(fused.items[1].reason, 'vector');
188
- assert.ok(fused.items[1].score > 0.39 && fused.items[1].score < 0.4);
189
- assert.equal(fused.hits.semanticAdmitted, 2);
190
-
191
- // 静默样本:没有关键词命中,余弦也低于 semanticAdmit → 一张都不注入。
192
- const silent = chooseRecall({
193
- queryText: '今天天气怎么样',
194
- cards,
195
- queryVector,
196
- vectors: new Map([
197
- ['topics/a.md', [Float32Array.from([0.3, 0.9539])]],
198
- ['topics/b.md', [Float32Array.from([0.3, 0.9539])]],
199
- ]),
200
- budget: 1200,
201
- });
202
- assert.deepEqual(silent.items, []);
203
- assert.equal(silent.hits.semanticAdmitted, 0);
204
-
205
- // 权重可调:把向量权重压到 0,纯语义卡过不了 minFusedScore。
206
- const keywordOnly = chooseRecall({
207
- queryText, cards, queryVector, vectors, budget: 1200,
208
- options: { maxItems: 3, fusion: { keywordWeight: 1, vectorWeight: 0 } },
209
- });
210
- assert.deepEqual(keywordOnly.items.map((item) => item.path), ['topics/a.md']);
211
- });
212
-
213
- test('autoRecall:融合注入 + recall-log 行格式(含 blocked)', async (t) => {
214
- const root = tmpRoot(t);
215
- writeFile(root, 'topics/a.md', '---\ntitle: 沙箱卡\nscope_in: ["沙箱", "pwsh"]\n---\n# 沙箱卡\n- 沙箱里的 pwsh 起不来。\n');
216
- writeFile(root, 'topics/b.md', '---\ntitle: 语义卡甲\nscope_in: ["量子纠缠"]\n---\n# 语义卡甲\n- 只靠语义进来的甲。\n');
217
- writeFile(root, 'topics/c.md', '---\ntitle: 语义卡乙\nscope_in: ["黎曼猜想"]\n---\n# 语义卡乙\n- 只靠语义进来的乙。\n');
218
-
219
- const bus = new PreStepBus();
220
- const { ctx, warns } = makeCtx(bus);
221
- applyAutoRecall(ctx, {
222
- memoryRoot: root,
223
- dirs: ['topics'],
224
- budget: 1200,
225
- maxItems: 2,
226
- everyNTurns: 6,
227
- log: true,
228
- createUserMessage: makeUserMessageFactory(),
229
- embedding: { enabled: true, embedder: makeEmbedder() },
230
- });
231
-
232
- const { decision } = await bus.compose({
233
- messages: [textMessage('沙箱 pwsh 起不来怎么办')],
234
- agent: { session: { id: 'sess-embed', header: { origin: 'main' } } },
235
- turn: 1,
236
- step: 1,
237
- });
238
- assert.equal(decision.messages.length, 2);
239
- const injected = decision.messages[1];
240
- assert.equal(injected.source?.kind, 'session-reference');
241
- const text = injected.content.map((part) => part.text).join('\n');
242
- assert.match(text, /【自动想起】/);
243
- assert.match(text, /沙箱卡/);
244
- assert.match(text, /语义卡甲/);
245
- assert.doesNotMatch(text, /语义卡乙/);
246
-
247
- const logPath = path.join(root, '.longlive', 'recall-log.jsonl');
248
- const lines = fs.readFileSync(logPath, 'utf8').trim().split(/\r?\n/);
249
- const row = JSON.parse(lines.at(-1));
250
- assert.deepEqual(Object.keys(row).sort(), ['at', 'blocked', 'injected', 'sessionId', 'turn']);
251
- assert.equal(row.sessionId, 'sess-embed');
252
- assert.equal(row.turn, 1);
253
- assert.equal(Number.isNaN(Date.parse(row.at)), false);
254
- assert.deepEqual(row.injected.map((item) => item.id), ['topics/a.md', 'topics/b.md']);
255
- for (const item of row.injected) {
256
- assert.equal(typeof item.title, 'string');
257
- assert.equal(typeof item.score, 'number');
258
- assert.match(item.reason, /^(keyword\+vector|vector|keyword)$/);
259
- }
260
- assert.equal(row.injected[0].reason, 'keyword+vector');
261
- assert.equal(row.injected[1].reason, 'vector');
262
- assert.equal(row.blocked.length, 1);
263
- assert.equal(row.blocked[0].id, 'topics/c.md');
264
- assert.equal(row.blocked[0].reason, 'max-items');
265
- assert.deepEqual(warns.filter((line) => /EMBEDDING_FALLBACK/.test(line)), []);
266
- });
267
-
268
- test('autoRecall:模型缺失时静默回退关键词,recall-log 照样写 reason=keyword', async (t) => {
269
- const root = tmpRoot(t);
270
- writeFile(root, 'topics/a.md', '---\ntitle: 沙箱卡\nscope_in: ["沙箱", "pwsh"]\n---\n# 沙箱卡\n- 沙箱里的 pwsh 起不来。\n');
271
-
272
- const bus = new PreStepBus();
273
- const { ctx, warns } = makeCtx(bus);
274
- applyAutoRecall(ctx, {
275
- memoryRoot: root,
276
- dirs: ['topics'],
277
- budget: 1200,
278
- maxItems: 2,
279
- log: true,
280
- createUserMessage: makeUserMessageFactory(),
281
- embedding: { enabled: true, modelPath: path.join(root, 'no-such-model') },
282
- });
283
-
284
- const { decision } = await bus.compose({
285
- messages: [textMessage('沙箱 pwsh 起不来怎么办')],
286
- agent: { session: { id: 'sess-fallback', header: { origin: 'main' } } },
287
- turn: 1,
288
- step: 1,
289
- });
290
- assert.equal(decision.messages.length, 2);
291
- assert.match(decision.messages[1].content[0].text, /沙箱卡/);
292
- assert.equal(warns.some((line) => /EMBEDDING_FALLBACK/.test(line)), true);
293
-
294
- const row = JSON.parse(fs.readFileSync(path.join(root, '.longlive', 'recall-log.jsonl'), 'utf8').trim());
295
- assert.equal(row.injected.length, 1);
296
- assert.equal(row.injected[0].id, 'topics/a.md');
297
- assert.equal(row.injected[0].reason, 'keyword');
298
- });
299
-
300
- test('autoRecall:embedding 关闭时不建 embeddings 目录,也不读模型', async (t) => {
301
- const root = tmpRoot(t);
302
- writeFile(root, 'topics/a.md', '---\ntitle: 沙箱卡\nscope_in: ["沙箱"]\n---\n# 沙箱卡\n- 沙箱相关。\n');
303
- const bus = new PreStepBus();
304
- const { ctx } = makeCtx(bus);
305
- applyAutoRecall(ctx, {
306
- memoryRoot: root,
307
- dirs: ['topics'],
308
- budget: 1200,
309
- createUserMessage: makeUserMessageFactory(),
310
- });
311
- const { decision } = await bus.compose({
312
- messages: [textMessage('沙箱里怎么运行')],
313
- agent: { session: { id: 'sess-off', header: { origin: 'main' } } },
314
- turn: 1,
315
- step: 1,
316
- });
317
- assert.equal(decision.messages.length, 2);
318
- assert.equal(fs.existsSync(path.join(root, '.longlive', 'embeddings')), false);
319
- const row = JSON.parse(fs.readFileSync(path.join(root, '.longlive', 'recall-log.jsonl'), 'utf8').trim());
320
- assert.equal(row.injected[0].reason, 'keyword');
321
- });
322
-
323
- test('createEmbeddingRuntime:ensure 失败返回结构化错误而不是抛进宿主', async (t) => {
324
- const root = tmpRoot(t);
325
- const runtime = createEmbeddingRuntime(
326
- { enabled: true, modelPath: path.join(root, 'missing') },
327
- { memoryRoot: root },
328
- );
329
- const result = await runtime.ensure([]);
330
- assert.equal(result.ok, false);
331
- assert.equal(result.code, 'E_EMBEDDING_MODEL_MISSING');
332
- assert.equal(runtime._state().ok, false);
333
- });
334
-
335
- test('cardChunkTexts:B 文本包含标题/关键词/正文三段', () => {
336
- const texts = cardChunkTexts({
337
- path: 'topics/a.md',
338
- title: '标题甲',
339
- scopeIn: ['词一'],
340
- aliases: ['别称一'],
341
- body: '正文第一段。',
342
- }, { maxChunkChars: 400, maxChunksPerCard: 6 });
343
- assert.equal(texts.length, 1);
344
- assert.match(texts[0], /【标题】标题甲/);
345
- assert.match(texts[0], /【关键词】词一 别称一/);
346
- assert.match(texts[0], /【正文】/);
347
- });