@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,307 +0,0 @@
1
- // C1 (1.4.0): engines.dsh startup self-check — data for the panel.
2
- //
3
- // Pins the three promises that make this advisory instead of a new failure mode:
4
- // 1. a mismatch only warns and writes `<memoryRoot>/.longlive/status.json`; it
5
- // never throws and never stops tools from registering;
6
- // 2. a matching host writes nothing, and clears a stale mismatch so a fixed
7
- // installation stops showing the panel red dot;
8
- // 3. an undetectable/unparseable version is "unknown": plugin loads, no new
9
- // record, no crash, and an existing record is left untouched.
10
- //
11
- // The host-manifest fallback is also exercised with temp files and an injected
12
- // `readFile`, so no test depends on the machine's real DSH installation.
13
-
14
- import fs from 'node:fs';
15
- import os from 'node:os';
16
- import path from 'node:path';
17
- import { fileURLToPath } from 'node:url';
18
- import test from 'node:test';
19
- import assert from 'node:assert/strict';
20
-
21
- import { apply } from '../src/index.js';
22
- import {
23
- HOST_PACKAGE_ENV,
24
- detectHostPackageVersion,
25
- detectHostVersion,
26
- hostPackageCandidates,
27
- } from '../src/host-version.js';
28
- import {
29
- ENGINE_STATUS_DIR,
30
- ENGINE_STATUS_FILE,
31
- engineStatusPath,
32
- recordEngineStatus,
33
- } from '../src/engine-status.js';
34
-
35
- const PACKAGE_ROOT = path.dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
36
- const DECLARED_RANGE = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8')).engines.dsh;
37
- const FIXED_NOW = '2026-11-05T13:02:01.000Z';
38
-
39
- function tempDir(t, prefix = 'longlive-engine-') {
40
- const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
41
- t.after(() => fs.rmSync(dir, { recursive: true, force: true }));
42
- return dir;
43
- }
44
-
45
- function statusFile(memoryRoot) {
46
- return path.join(memoryRoot, ENGINE_STATUS_DIR, ENGINE_STATUS_FILE);
47
- }
48
-
49
- function readStatus(memoryRoot) {
50
- return JSON.parse(fs.readFileSync(statusFile(memoryRoot), 'utf8'));
51
- }
52
-
53
- function writeStatus(memoryRoot, value) {
54
- const file = statusFile(memoryRoot);
55
- fs.mkdirSync(path.dirname(file), { recursive: true });
56
- fs.writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
57
- }
58
-
59
- function fakeContext() {
60
- const tools = [];
61
- const warnings = [];
62
- const hooks = [];
63
- const ctx = {
64
- tools: {
65
- register(definition) {
66
- tools.push(definition);
67
- return () => {};
68
- },
69
- },
70
- on(name, listener) {
71
- hooks.push({ name, listener });
72
- return () => {};
73
- },
74
- logger() {
75
- return { warn: (message) => warnings.push(String(message)), info() {}, debug() {} };
76
- },
77
- effect(fn) {
78
- const dispose = fn();
79
- return typeof dispose === 'function' ? dispose : () => {};
80
- },
81
- };
82
- return { ctx, tools, warnings, hooks };
83
- }
84
-
85
- function runApply(t, config = {}, prepare = null) {
86
- const temp = tempDir(t);
87
- const home = path.join(temp, 'home');
88
- const memoryRoot = path.join(temp, 'memory');
89
- fs.mkdirSync(home, { recursive: true });
90
- fs.mkdirSync(memoryRoot, { recursive: true });
91
- if (prepare) prepare({ temp, home, memoryRoot });
92
-
93
- const { ctx, tools, warnings } = fakeContext();
94
- const returned = apply(ctx, {
95
- home,
96
- memoryRoot,
97
- env: {},
98
- heartbeatMs: 0,
99
- recoverOnStart: false,
100
- filterEnabled: false,
101
- routerRuntime: 'off',
102
- defineTool: (definition) => definition,
103
- now: () => Date.parse(FIXED_NOW),
104
- ...config,
105
- });
106
- return { temp, home, memoryRoot, ctx, tools, warnings, returned };
107
- }
108
-
109
- // ---------------------------------------------------------------------------
110
- // Host manifest fallback
111
- // ---------------------------------------------------------------------------
112
-
113
- test('detectHostVersion reads a host package.json through config.hostPackagePath', (t) => {
114
- const dir = tempDir(t);
115
- const manifest = path.join(dir, 'package.json');
116
- fs.writeFileSync(manifest, JSON.stringify({
117
- name: '@deepseek-ai/dsh-desktop-runtime',
118
- version: '0.2.2',
119
- }), 'utf8');
120
-
121
- const detection = detectHostVersion({}, { hostPackagePath: manifest }, {});
122
- assert.equal(detection.version, '0.2.2');
123
- assert.equal(detection.source, 'hostPackage');
124
- assert.equal(detection.path, manifest);
125
- assert.equal(detection.origin, 'config.hostPackagePath');
126
- });
127
-
128
- test('detectHostVersion understands the desktop-runtime.json release.version shape', (t) => {
129
- const dir = tempDir(t);
130
- const runtime = path.join(dir, 'desktop-runtime.json');
131
- fs.writeFileSync(runtime, JSON.stringify({ release: { version: '0.2.1-rc.5' } }), 'utf8');
132
-
133
- const detection = detectHostVersion({}, { hostPackagePath: runtime }, {});
134
- assert.equal(detection.version, '0.2.1-rc.5');
135
- assert.equal(detection.path, runtime);
136
- });
137
-
138
- test('a corrupt or missing host manifest is skipped, and the next candidate wins', (t) => {
139
- const dir = tempDir(t);
140
- const broken = path.join(dir, 'package.json');
141
- fs.writeFileSync(broken, '{ not json', 'utf8');
142
- const valid = path.join(dir, 'fallback-package.json');
143
- fs.writeFileSync(valid, JSON.stringify({ version: '0.3.0' }), 'utf8');
144
-
145
- const viaEnv = detectHostVersion({}, { hostPackagePath: broken }, { [HOST_PACKAGE_ENV]: valid });
146
- assert.equal(viaEnv.version, '0.3.0');
147
- assert.equal(viaEnv.path, valid);
148
- assert.equal(viaEnv.origin, `env.${HOST_PACKAGE_ENV}`);
149
-
150
- const missing = detectHostVersion({}, { hostPackagePath: path.join(dir, 'nope.json') }, {});
151
- assert.equal(missing.version, null);
152
- assert.equal(missing.source, null);
153
- });
154
-
155
- test('hostPackageCandidates is dependency-free, ordered, and de-duplicated', () => {
156
- const configPath = path.resolve('C:/fake/config/package.json');
157
- const envPath = path.resolve('C:/fake/env/package.json');
158
- const candidates = hostPackageCandidates({
159
- configPath,
160
- env: { [HOST_PACKAGE_ENV]: envPath },
161
- resourcesPath: path.resolve('C:/fake/resources'),
162
- execPath: path.resolve('C:/fake/install/Harness.exe'),
163
- });
164
- const paths = candidates.map((entry) => entry.path);
165
- assert.equal(paths[0], configPath);
166
- assert.equal(paths[1], envPath);
167
- assert.ok(paths.includes(path.resolve('C:/fake/resources/app.asar/dsh/package.json')));
168
- assert.ok(paths.includes(path.resolve('C:/fake/resources/app.asar/dsh/desktop-runtime.json')));
169
- assert.ok(paths.includes(path.resolve('C:/fake/install/resources/app.asar/dsh/package.json')));
170
- assert.equal(new Set(paths).size, paths.length, 'no duplicate candidate paths');
171
- });
172
-
173
- test('an unreadable host manifest is unknown, not an exception', () => {
174
- const detection = detectHostPackageVersion({
175
- configPath: path.resolve('C:/fake/missing/package.json'),
176
- env: {},
177
- resourcesPath: undefined,
178
- execPath: path.resolve('C:/fake/node.exe'),
179
- readFile: () => { throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' }); },
180
- });
181
- assert.equal(detection, null);
182
- });
183
-
184
- // ---------------------------------------------------------------------------
185
- // apply(): mismatch / match / unknown
186
- // ---------------------------------------------------------------------------
187
-
188
- test('mismatch: plugin still registers every tool and writes the panel record', (t) => {
189
- const run = runApply(t, { hostVersion: '0.1.0' });
190
-
191
- assert.ok(run.returned, 'apply returned normally');
192
- assert.ok(run.tools.length >= 20, `expected the full tool table, got ${run.tools.length}`);
193
- assert.ok(run.tools.some((tool) => tool.name === 'session_truncate'), 'session_truncate is registered');
194
- assert.ok(run.warnings.some((line) => line.includes('0.1.0') && line.includes(DECLARED_RANGE)),
195
- `a mismatch warning naming both ends is emitted: ${run.warnings.join(' | ')}`);
196
- assert.ok(run.warnings.some((line) => line.includes('降级')), 'warning says the plugin degrades');
197
-
198
- const status = readStatus(run.memoryRoot);
199
- assert.deepEqual(status.engineMismatch, {
200
- hostVersion: '0.1.0',
201
- required: DECLARED_RANGE,
202
- at: FIXED_NOW,
203
- source: 'config.hostVersion',
204
- });
205
-
206
- const leftovers = fs.readdirSync(path.join(run.memoryRoot, ENGINE_STATUS_DIR))
207
- .filter((name) => name.includes('.tmp-'));
208
- assert.deepEqual(leftovers, [], 'no temporary write file is left behind');
209
- });
210
-
211
- test('mismatch through the environment uses the env source label', (t) => {
212
- const run = runApply(t, { env: { DSH_HOST_VERSION: '2.0.0' } });
213
-
214
- assert.ok(run.tools.length >= 20);
215
- const status = readStatus(run.memoryRoot);
216
- assert.equal(status.engineMismatch.hostVersion, '2.0.0');
217
- assert.equal(status.engineMismatch.source, 'env.DSH_HOST_VERSION');
218
- });
219
-
220
- test('match: no record is written', (t) => {
221
- const run = runApply(t, { hostVersion: '0.9.0' });
222
-
223
- assert.ok(run.tools.length >= 20);
224
- assert.equal(fs.existsSync(statusFile(run.memoryRoot)), false, 'a matching host writes nothing');
225
- assert.equal(run.warnings.some((line) => line.includes('0.9.0')), false, 'no warning on a match');
226
- });
227
-
228
- test('match clears a stale engineMismatch but keeps unrelated keys', (t) => {
229
- const run = runApply(t, { hostVersion: '0.7.0' }, ({ memoryRoot }) => {
230
- writeStatus(memoryRoot, {
231
- at: '2026-11-01T00:00:00.000Z',
232
- ok: true,
233
- engineMismatch: { hostVersion: '0.1.0', required: DECLARED_RANGE, at: '2026-11-01T00:00:00.000Z' },
234
- });
235
- });
236
-
237
- const status = readStatus(run.memoryRoot);
238
- assert.equal(status.engineMismatch, undefined);
239
- assert.equal(status.ok, true);
240
- assert.equal(status.at, '2026-11-01T00:00:00.000Z');
241
- });
242
-
243
- test('match deletes status.json when engineMismatch was its only key', (t) => {
244
- const run = runApply(t, { hostVersion: '0.7.1' }, ({ memoryRoot }) => {
245
- writeStatus(memoryRoot, {
246
- engineMismatch: { hostVersion: '0.1.0', required: DECLARED_RANGE, at: '2026-11-01T00:00:00.000Z' },
247
- });
248
- });
249
-
250
- assert.equal(fs.existsSync(statusFile(run.memoryRoot)), false, 'stale file is removed');
251
- });
252
-
253
- test('unknown: an unparseable host version loads, warns about nothing, writes nothing', (t) => {
254
- const run = runApply(t, { hostVersion: 'not-a-version' });
255
-
256
- assert.ok(run.tools.length >= 20);
257
- assert.equal(fs.existsSync(statusFile(run.memoryRoot)), false);
258
- assert.equal(run.warnings.some((line) => line.includes('not-a-version')), false);
259
- });
260
-
261
- test('unknown: a direct verdict leaves an existing record untouched', (t) => {
262
- const root = tempDir(t);
263
- writeStatus(root, { engineMismatch: { hostVersion: '0.1.0', required: DECLARED_RANGE, at: FIXED_NOW } });
264
-
265
- const result = recordEngineStatus(root, {
266
- compat: { status: 'unknown', version: 'nope', range: DECLARED_RANGE, warning: null },
267
- now: () => new Date(FIXED_NOW),
268
- });
269
- assert.equal(result.ok, true);
270
- assert.equal(result.action, 'unknown');
271
- assert.equal(readStatus(root).engineMismatch.hostVersion, '0.1.0');
272
- });
273
-
274
- test('a memoryRoot path that is a file warns but never blocks activation', (t) => {
275
- const temp = tempDir(t);
276
- const home = path.join(temp, 'home');
277
- fs.mkdirSync(home, { recursive: true });
278
- const bogusRoot = path.join(temp, 'not-a-directory');
279
- fs.writeFileSync(bogusRoot, 'x', 'utf8');
280
-
281
- const { ctx, tools, warnings } = fakeContext();
282
- assert.doesNotThrow(() => {
283
- apply(ctx, {
284
- home,
285
- memoryRoot: bogusRoot,
286
- hostVersion: '0.1.1',
287
- env: {},
288
- heartbeatMs: 0,
289
- recoverOnStart: false,
290
- filterEnabled: false,
291
- routerRuntime: 'off',
292
- defineTool: (definition) => definition,
293
- });
294
- });
295
- assert.ok(tools.length >= 20, 'tools still load when the status file cannot be written');
296
- assert.ok(warnings.some((line) => line.includes('记忆根目录')), 'the bad root is reported');
297
- });
298
-
299
- test('recordEngineStatus with no memoryRoot is a silent no-op', () => {
300
- assert.equal(engineStatusPath(''), null);
301
- assert.equal(engineStatusPath(' '), null);
302
- const result = recordEngineStatus(null, {
303
- compat: { status: 'mismatch', version: '0.1.0', range: DECLARED_RANGE, warning: 'x' },
304
- });
305
- assert.equal(result.ok, false);
306
- assert.equal(result.action, 'no-memory-root');
307
- });