@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,343 +0,0 @@
1
- // Portability acceptance tests (0.7.3).
2
- //
3
- // These cases pin the behaviour that made the package machine-specific before:
4
- // PORT1 a missing memory root resolves to <home>/longlive-memory and is created
5
- // PORT2 explicit config / alias / environment precedence is one shared chain
6
- // PORT3 strict mode still refuses to guess, and a configured-but-missing root
7
- // is never created silently
8
- // PORT4 the host version check parses semver, reads engines.dsh, and degrades
9
- // with a readable warning instead of throwing
10
- // PORT5 every published file is free of developer-machine paths
11
- // PORT6 the identity-consent rule names no assistant: consent belongs to the
12
- // assistant of the current session, whoever that assistant is
13
- //
14
- // Nothing here writes inside the package tree (P6 checks that afterwards).
15
-
16
- import fs from 'node:fs';
17
- import os from 'node:os';
18
- import path from 'node:path';
19
- import { fileURLToPath } from 'node:url';
20
-
21
- import {
22
- MEMORY_ROOT_ENV,
23
- LEGACY_MEMORY_ROOT_ENV,
24
- DEFAULT_MEMORY_DIRNAME,
25
- memoryRootFromConfig,
26
- memoryRootSetting,
27
- defaultMemoryRoot,
28
- } from '../src/memory/paths.js';
29
- import { memRoot, TOOLS } from '../src/tools.js';
30
- import {
31
- HOST_VERSION_ENV,
32
- DEFAULT_HOST_RANGE,
33
- parseVersion,
34
- satisfies,
35
- declaredHostRange,
36
- checkHostCompatibility,
37
- } from '../src/host-version.js';
38
- import { VERSION } from '../src/version.js';
39
-
40
- const PACKAGE_ROOT = path.dirname(fileURLToPath(new URL('../package.json', import.meta.url)));
41
-
42
- function readIf(file) {
43
- try {
44
- return fs.readFileSync(file, 'utf8');
45
- } catch {
46
- return null;
47
- }
48
- }
49
-
50
- function walk(files, file) {
51
- const text = readIf(file);
52
- if (text !== null) files.push({ file, text });
53
- }
54
-
55
- // package.json `files` ships src/router/*.{js,mjs} plus every nested JSON, and
56
- // only top-level .js elsewhere under src. Walk src recursively and mirror that
57
- // shape, so a new subdirectory (src/router was the first) cannot fall out of
58
- // the scan again.
59
- const SRC_SHIPPED = [
60
- /^src\/[^/]+\.js$/,
61
- /^src\/memory\/[^/]+\.js$/,
62
- /^src\/router\/[^/]+\.(?:js|mjs)$/,
63
- /^src\/router\/(?:[^/]+\/)*[^/]+\.json$/,
64
- ];
65
-
66
- function collectSrc(files, dir) {
67
- for (const name of fs.readdirSync(dir).sort()) {
68
- const abs = path.join(dir, name);
69
- if (fs.statSync(abs).isDirectory()) {
70
- collectSrc(files, abs);
71
- continue;
72
- }
73
- const rel = path.relative(PACKAGE_ROOT, abs).split(path.sep).join('/');
74
- if (SRC_SHIPPED.some((re) => re.test(rel))) walk(files, abs);
75
- }
76
- }
77
-
78
- function publishedFiles() {
79
- const files = [];
80
- for (const name of [
81
- 'package.json', 'cordis.patch.yml', 'README.md', 'README.en.md', 'SPEC.md',
82
- 'runbook.md', 'QUICKSTART.md', 'CHANGELOG.md', 'docs/ERRORS.md',
83
- ]) walk(files, path.join(PACKAGE_ROOT, name));
84
- for (const dir of ['scripts', 'test', 'locale']) {
85
- const abs = path.join(PACKAGE_ROOT, dir);
86
- if (!fs.existsSync(abs)) continue;
87
- for (const name of fs.readdirSync(abs)) {
88
- const file = path.join(abs, name);
89
- if (!fs.statSync(file).isFile()) continue;
90
- // test ships .mjs too; locale ships JSON; scripts ship both.
91
- const shipped = /\.m?js$/.test(name)
92
- || name.endsWith('.ps1')
93
- || /\.json$/.test(name);
94
- if (shipped) walk(files, file);
95
- }
96
- }
97
- const src = path.join(PACKAGE_ROOT, 'src');
98
- if (fs.existsSync(src)) collectSrc(files, src);
99
- return files;
100
- }
101
-
102
- // Patterns are assembled from pieces so this scanner does not match its own
103
- // source text (the file itself ships and is scanned like any other).
104
- const DRIVE = '[A-Za-z]:';
105
- const SEP = '[\\\\/]+';
106
- const MACHINE_PATTERNS = [
107
- { name: 'developer profile path', re: new RegExp(`${DRIVE}${SEP}Users${SEP}Admin` + 'istrator', 'i') },
108
- { name: 'private toolbox path', re: new RegExp(`${DRIVE}${SEP}` + 'dshops', 'i') },
109
- { name: 'personal memory drive', re: new RegExp('(?<![A-Za-z0-9_])H:' + '\\\\', 'i') },
110
- { name: 'developer runtime drive', re: new RegExp('(?<![A-Za-z0-9_])G:' + '\\\\', 'i') },
111
- { name: 'ACE Studio install path', re: new RegExp('Program Files' + SEP + 'ACE ' + 'Studio', 'i') },
112
- { name: 'private research path', re: new RegExp('听澜' + SEP + 'research') },
113
- ];
114
-
115
- // PORT6 scans the authored policy text (routing rules + judge/router sources +
116
- // the error catalogue) for a hardcoded assistant name. The consent holder is
117
- // "the assistant of the current session" (consent.holder=assistant); a user's
118
- // unilateral request is never consent, no matter what the assistant is called.
119
- const PERSONA = '听澜';
120
- const OPERATOR_NAME = '金嘉恒';
121
- const PERSONA_RE = new RegExp(`${PERSONA}|${OPERATOR_NAME}`);
122
- // Development-only absolute paths are not policy text: strip the path stem
123
- // (`<persona>/research/...`) before looking for persona wording.
124
- const DEV_PATH_RE = new RegExp(`${PERSONA}[\\\\/]+research`, 'g');
125
- // An override audit records WHO overrode a rule; that is an example signature,
126
- // not the subject of a policy.
127
- const EXAMPLE_SIGNATURE_RE = /\bby:\s*'[^']*'/g;
128
- // Skipped wholesale, each because its persona mentions are example data. Keep
129
- // this list short: every other top-level src/router source is policy surface.
130
- const EXAMPLE_DATA_FILES = new Map([
131
- ['src/router/design-headings.json',
132
- 'generated verbatim snapshot of the external design doc; quoting the ruling as written is the point'],
133
- ['src/router/run-sim.mjs',
134
- 'sandbox simulation driver; its literal names are example journal content and override signatures'],
135
- ]);
136
-
137
- function policyTextFiles() {
138
- const files = [];
139
- const routerDir = path.join(PACKAGE_ROOT, 'src/router');
140
- const collect = (dir, prefix) => {
141
- for (const name of fs.readdirSync(dir).sort()) {
142
- if (!/\.(?:json|m?js)$/.test(name)) continue;
143
- const abs = path.join(dir, name);
144
- if (!fs.statSync(abs).isFile()) continue;
145
- const rel = `${prefix}/${name}`;
146
- if (EXAMPLE_DATA_FILES.has(rel)) continue;
147
- files.push({ rel, text: fs.readFileSync(abs, 'utf8') });
148
- }
149
- };
150
- collect(routerDir, 'src/router');
151
- // 策略 profile 是可配置的家规面:generic/owner 都必须保持可移植(无个人名/本机路径)。
152
- const policiesDir = path.join(routerDir, 'policies');
153
- if (fs.existsSync(policiesDir)) collect(policiesDir, 'src/router/policies');
154
- files.push({
155
- rel: 'docs/ERRORS.md',
156
- text: fs.readFileSync(path.join(PACKAGE_ROOT, 'docs/ERRORS.md'), 'utf8'),
157
- });
158
- return files;
159
- }
160
-
161
- export function runPortabilitySuite({ runCase, assert, TMP }) {
162
- runCase('PORT1', '缺省记忆根:派生 <home>/longlive-memory、按需创建、memRoot 同源', () => {
163
- const home = path.join(TMP, 'port-home-1');
164
- fs.rmSync(home, { recursive: true, force: true });
165
- const expected = path.join(home, DEFAULT_MEMORY_DIRNAME);
166
-
167
- const setting = memoryRootSetting({ home }, {});
168
- assert(setting.explicit === '', 'no explicit root expected');
169
- assert(setting.source === 'default', `unexpected source: ${setting.source}`);
170
- assert(setting.strict === false, 'strict must default off');
171
- assert(defaultMemoryRoot({ home }, {}) === expected, 'derived root path mismatch');
172
-
173
- const root = memoryRootFromConfig({ home }, {});
174
- assert(root === expected, `memoryRootFromConfig returned ${root}`);
175
- assert(fs.statSync(root).isDirectory(), 'derived root was not created');
176
-
177
- assert(memRoot({ home }, {}) === root, 'session_wake and memory_* disagree on the root');
178
- assert(fs.statSync(root).isDirectory(), 'memRoot did not keep the derived root');
179
- });
180
-
181
- runCase('PORT2', '记忆根优先级:memoryRoot > memRoot > 新环境变量 > 旧环境变量', () => {
182
- const a = path.join(TMP, 'port-root-a');
183
- const b = path.join(TMP, 'port-root-b');
184
- const c = path.join(TMP, 'port-root-c');
185
- const d = path.join(TMP, 'port-root-d');
186
- for (const dir of [a, b, c, d]) fs.mkdirSync(dir, { recursive: true });
187
-
188
- assert(memoryRootFromConfig({ memoryRoot: a, memRoot: b }, {}) === a, 'memoryRoot must win');
189
- assert(memoryRootFromConfig({ memRoot: b }, { [MEMORY_ROOT_ENV]: c, [LEGACY_MEMORY_ROOT_ENV]: d }) === b,
190
- 'memRoot alias must win over the environment');
191
- assert(memoryRootFromConfig({}, { [MEMORY_ROOT_ENV]: c, [LEGACY_MEMORY_ROOT_ENV]: d }) === c,
192
- 'new environment variable must win over the legacy one');
193
- assert(memoryRootFromConfig({}, { [LEGACY_MEMORY_ROOT_ENV]: d }) === d,
194
- 'legacy DSH_MEM must still be honoured');
195
- assert(memRoot({ memRoot: b }, {}) === b, 'memRoot must honour the memoryRoot alias chain');
196
-
197
- const setting = memoryRootSetting({}, { [MEMORY_ROOT_ENV]: c });
198
- assert(setting.source === MEMORY_ROOT_ENV, `unexpected source ${setting.source}`);
199
- assert(setting.explicit === c, 'setting.explicit mismatch');
200
- });
201
-
202
- runCase('PORT3', 'strict 模式与显式缺失:报错可操作,且绝不创建显式配置的路径', () => {
203
- const home = path.join(TMP, 'port-home-3');
204
- fs.rmSync(home, { recursive: true, force: true });
205
-
206
- let strictError = null;
207
- try {
208
- memoryRootFromConfig({ home, strictMemoryRoot: true }, {});
209
- } catch (error) {
210
- strictError = error;
211
- }
212
- assert(strictError, 'strict mode must refuse to derive a root');
213
- assert(strictError.code === 'E_MEMORY_ROOT_UNSET', `unexpected code ${strictError.code}`);
214
- assert(/memoryRoot/.test(strictError.message), 'strict message must name memoryRoot');
215
- assert(strictError.message.includes(MEMORY_ROOT_ENV), 'strict message must name the environment variable');
216
- assert(strictError.message.includes('longlive-memory'), 'strict message must name the derived default');
217
-
218
- const missing = path.join(TMP, 'port-explicit-missing');
219
- let missingError = null;
220
- try {
221
- memoryRootFromConfig({ memoryRoot: missing }, {});
222
- } catch (error) {
223
- missingError = error;
224
- }
225
- assert(missingError, 'a configured root that does not exist must be reported');
226
- assert(missingError.code === 'E_MEMORY_ROOT_MISSING', `unexpected code ${missingError.code}`);
227
- assert(!fs.existsSync(missing), 'an explicitly configured path must never be created silently');
228
- assert(/mkdir/.test(missingError.message), 'missing-root message must tell the user how to fix it');
229
-
230
- const envMissing = path.join(TMP, 'port-env-missing');
231
- let envError = null;
232
- try {
233
- memoryRootFromConfig({}, { [LEGACY_MEMORY_ROOT_ENV]: envMissing });
234
- } catch (error) {
235
- envError = error;
236
- }
237
- assert(envError && envError.code === 'E_MEMORY_ROOT_MISSING', 'legacy env root must be validated');
238
- assert(!fs.existsSync(envMissing), 'environment root must never be created silently');
239
- });
240
-
241
- runCase('PORT4', '宿主版本自检:semver、engines.dsh 上界、未知不阻断、失配只降级', () => {
242
- const parsed = parseVersion('v0.2.1-rc.5');
243
- assert(parsed && parsed.major === 0 && parsed.minor === 2 && parsed.patch === 1, 'prerelease core must parse');
244
- assert(satisfies('0.2.1-rc.5', DEFAULT_HOST_RANGE), 'the running host must satisfy the declared range');
245
- assert(satisfies('0.2.0', DEFAULT_HOST_RANGE), 'lower bound is inclusive');
246
- assert(!satisfies('0.1.9', DEFAULT_HOST_RANGE), 'older host must be refused');
247
- assert(!satisfies('1.0.0', DEFAULT_HOST_RANGE), 'newer major must be refused');
248
- assert(satisfies('0.7.2', '^0.7.0') && !satisfies('0.8.0', '^0.7.0'), 'caret on 0.x');
249
- assert(satisfies('1.4.0', '>=1.0.0 || =0.9.9'), 'or-ranges');
250
-
251
- const range = declaredHostRange();
252
- assert(range === DEFAULT_HOST_RANGE, `package.json engines.dsh is ${range}`);
253
- const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
254
- assert(pkg.engines.dsh === range, 'declared range must come from package.json');
255
-
256
- const good = checkHostCompatibility({ version: '0.2.1-rc.5', range, pluginVersion: VERSION });
257
- assert(good.ok === true && good.status === 'ok' && good.warning === null, 'matching host must pass');
258
-
259
- const unknown = checkHostCompatibility({ range, pluginVersion: VERSION });
260
- assert(unknown.ok === true && unknown.status === 'unknown', 'an unknown host must not be blocked');
261
- assert(unknown.warning === null, 'an unknown host must not warn until it is known');
262
-
263
- const bad = checkHostCompatibility({ version: '0.1.0', range, pluginVersion: VERSION });
264
- assert(bad.ok === false && bad.status === 'mismatch', 'old host must be a mismatch');
265
- assert(bad.warning.includes('0.1.0') && bad.warning.includes('engines.dsh'), 'warning must name both sides');
266
- assert(/降级|degraded/i.test(bad.warning), 'warning must promise degraded loading, not a crash');
267
-
268
- assert(typeof HOST_VERSION_ENV === 'string' && HOST_VERSION_ENV.startsWith('DSH_'), 'env name');
269
- const oldDrive = 'H:' + '\\dsh-memory';
270
- const wake = TOOLS.find((tool) => tool.name === 'session_wake');
271
- assert(wake && !wake.description.includes(oldDrive), 'session_wake still advertises the old drive');
272
- const lease = TOOLS.find((tool) => tool.name === 'lease_acquire');
273
- assert(lease && !lease.description.includes(oldDrive), 'lease_acquire still advertises the old drive');
274
- });
275
-
276
- runCase('PORT5', '发布文件不含本机盘符、用户目录与私有目录名', () => {
277
- for (const required of ['QUICKSTART.md', 'docs/ERRORS.md']) {
278
- assert(fs.existsSync(path.join(PACKAGE_ROOT, required)), `${required} must ship`);
279
- }
280
- const files = publishedFiles();
281
- const scanned = files.map(({ file }) => path.relative(PACKAGE_ROOT, file).split(path.sep).join('/'));
282
- // A scan that silently skips a shipped subtree proves nothing: pin the
283
- // files whose machine paths this case exists to catch.
284
- for (const required of [
285
- 'src/router/router.mjs',
286
- 'src/router/design-headings.json',
287
- 'src/router/shadow.mjs',
288
- 'scripts/pack.mjs',
289
- ]) {
290
- assert(scanned.includes(required), `PORT5 漏扫随包文件 ${required}`);
291
- }
292
- const hits = [];
293
- for (const { file, text } of files) {
294
- const lines = text.split(/\r?\n/);
295
- for (let i = 0; i < lines.length; i += 1) {
296
- for (const { name, re } of MACHINE_PATTERNS) {
297
- if (re.test(lines[i])) hits.push(`${path.relative(PACKAGE_ROOT, file)}:${i + 1} (${name}) ${lines[i].trim().slice(0, 100)}`);
298
- }
299
- }
300
- }
301
- assert(hits.length === 0, `machine-specific paths in published files:\n${hits.join('\n')}`);
302
- });
303
-
304
- runCase('PORT6', '身份同意规则不写死某个 agent 的名字:同意权归当前会话的助手本人', () => {
305
- for (const rel of EXAMPLE_DATA_FILES.keys()) {
306
- assert(fs.existsSync(path.join(PACKAGE_ROOT, rel)), `${rel} 被列为示例数据但文件不存在`);
307
- }
308
-
309
- const files = policyTextFiles();
310
- const scanned = files.map((file) => file.rel);
311
- for (const required of [
312
- 'src/router/routing-rules.json',
313
- 'src/router/judge.mjs',
314
- 'src/router/route.js',
315
- 'docs/ERRORS.md',
316
- ]) {
317
- assert(scanned.includes(required), `可移植性扫描漏了 ${required}`);
318
- }
319
-
320
- const hits = [];
321
- for (const { rel, text } of files) {
322
- const lines = text.split(/\r?\n/);
323
- for (let i = 0; i < lines.length; i += 1) {
324
- const subject = lines[i]
325
- .replace(DEV_PATH_RE, '<dev-path>')
326
- .replace(EXAMPLE_SIGNATURE_RE, 'by: <example-signature>');
327
- if (PERSONA_RE.test(subject)) hits.push(`${rel}:${i + 1} ${lines[i].trim().slice(0, 120)}`);
328
- }
329
- }
330
- assert(hits.length === 0,
331
- `身份同意规则仍写死具体 agent 名(应写「当前会话的助手本人」):\n${hits.join('\n')}`);
332
-
333
- // Removing the wording must not pass the scan: the rule still has to say
334
- // whose consent counts, and that the user's own request never does.
335
- const rules = files.find((file) => file.rel === 'src/router/routing-rules.json').text;
336
- assert(rules.includes('当前会话的助手本人(consent.holder=assistant)'),
337
- 'whoMayConsent 未声明同意权归「当前会话的助手本人」');
338
- assert(rules.includes('用户单方面要求永远不构成同意'), '「用户单方面要求不算同意」的语义丢了');
339
- const catalogue = files.find((file) => file.rel === 'docs/ERRORS.md').text;
340
- assert(catalogue.includes('本规则与被判断的 agent 具体叫什么无关'),
341
- 'ERRORS.md 缺少「本规则与 agent 具体叫什么无关」的说明');
342
- });
343
- }