@jjh32456/dsh-longlive 1.6.8 → 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 (288) hide show
  1. package/CHANGELOG.md +69 -1539
  2. package/LICENSE +1 -1
  3. package/QUICKSTART.md +33 -12
  4. package/README.en.md +17 -7
  5. package/README.md +29 -10
  6. package/SPEC.md +5 -5
  7. package/cordis.patch.yml +17 -12
  8. package/docs/ANSWER-GUARD.md +92 -0
  9. package/docs/ARCHITECTURE.md +3 -3
  10. package/docs/AUTO-RECALL-WIRING.md +1 -1
  11. package/docs/AUTO-RECALL.md +15 -9
  12. package/docs/CONFIG.md +143 -37
  13. package/docs/ERRORS.md +17 -5
  14. package/docs/FAQ.md +7 -7
  15. package/docs/FEATURES.md +66 -29
  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 +3 -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 +42 -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 +129 -18
  41. package/src/activity/model.js +11 -1
  42. package/src/activity/store.js +22 -3
  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 +145 -48
  58. package/src/deps.js +6 -4
  59. package/src/features.js +158 -8
  60. package/src/identity/index.js +13 -5
  61. package/src/identity/inject-position.js +2 -2
  62. package/src/index.js +97 -7
  63. package/src/lifecycle/gap-clocks.js +328 -0
  64. package/src/lifecycle/index.js +269 -0
  65. package/src/memory/activity-capture.js +229 -0
  66. package/src/memory/anchors.js +194 -0
  67. package/src/memory/audit-records.js +222 -0
  68. package/src/memory/auto-cards.js +401 -0
  69. package/src/memory/capture.js +442 -0
  70. package/src/memory/card-anchors.js +203 -0
  71. package/src/memory/conflicts.js +106 -0
  72. package/src/memory/decay.js +311 -0
  73. package/src/memory/distill.js +471 -0
  74. package/src/memory/event-index.js +917 -0
  75. package/src/memory/evidence-chain.js +295 -0
  76. package/src/memory/evidence.js +145 -0
  77. package/src/memory/filter-runner.js +1 -1
  78. package/src/memory/filter.js +5 -1
  79. package/src/memory/forget-v2.js +410 -0
  80. package/src/memory/forget.js +71 -0
  81. package/src/memory/frames-activation.js +247 -0
  82. package/src/memory/fts-query.js +174 -0
  83. package/src/memory/governance.js +604 -0
  84. package/src/memory/index.js +1 -0
  85. package/src/memory/l2-gate.js +169 -0
  86. package/src/memory/lock.js +121 -0
  87. package/src/memory/migrate.js +139 -0
  88. package/src/memory/recall-v2.js +223 -0
  89. package/src/memory/recall.js +65 -2
  90. package/src/memory/records.js +425 -0
  91. package/src/memory/retention.js +128 -0
  92. package/src/memory/review.js +124 -0
  93. package/src/memory/trust.js +88 -0
  94. package/src/memory/version-chain.js +264 -0
  95. package/src/memory/wal.js +137 -0
  96. package/src/memory/working-frame.js +516 -0
  97. package/src/metrics/classify.mjs +5 -3
  98. package/src/metrics/index.mjs +21 -1
  99. package/src/metrics/prestep-latency.mjs +300 -0
  100. package/src/metrics/store.mjs +25 -0
  101. package/src/policy-profile.js +18 -3
  102. package/src/recall/anaphora.mjs +87 -0
  103. package/src/recall/cards.mjs +7 -0
  104. package/src/recall/embedding.mjs +8 -8
  105. package/src/recall/entities.mjs +112 -0
  106. package/src/recall/index.js +235 -19
  107. package/src/recall/inject-order.mjs +10 -8
  108. package/src/recall/privacy.mjs +1 -1
  109. package/src/recall/recall-core.mjs +116 -10
  110. package/src/recall/shared-visibility.js +302 -0
  111. package/src/recall/time-normalizer.mjs +196 -0
  112. package/src/role/activation.js +191 -0
  113. package/src/role/attention.js +318 -0
  114. package/src/role/base.js +36 -0
  115. package/src/role/channels.js +423 -0
  116. package/src/role/index.js +425 -0
  117. package/src/role/interfaces.js +115 -0
  118. package/src/role/personality.js +370 -0
  119. package/src/role/readers.js +151 -0
  120. package/src/role/relationships.js +477 -0
  121. package/src/role/store.js +202 -0
  122. package/src/role/writeprotect.js +373 -0
  123. package/src/router/aliases.json +176 -25
  124. package/src/router/background/agent-runner.mjs +1 -1
  125. package/src/router/background/anchor.mjs +1 -1
  126. package/src/router/background/audit.mjs +2 -2
  127. package/src/router/background/budget-ledger.mjs +2 -2
  128. package/src/router/background/host-sim.mjs +1 -1
  129. package/src/router/background/memory.mjs +4 -4
  130. package/src/router/background/queue.mjs +1 -1
  131. package/src/router/background/retry.mjs +1 -1
  132. package/src/router/background/router.mjs +1 -1
  133. package/src/router/background/stream.mjs +1 -1
  134. package/src/router/background/tool-policy.mjs +1 -1
  135. package/src/router/design-headings.json +5 -5
  136. package/src/router/face/config.mjs +1 -1
  137. package/src/router/face/identity.mjs +3 -3
  138. package/src/router/judge.mjs +8 -7
  139. package/src/router/policies/generic.json +21 -4
  140. package/src/router/route.js +109 -22
  141. package/src/router/router.mjs +10 -6
  142. package/src/router/routing-rules.json +260 -445
  143. package/src/router/run-sim.mjs +8 -8
  144. package/src/router/runtime.js +23 -12
  145. package/src/router/service/config.mjs +5 -5
  146. package/src/router/service/context.mjs +1 -1
  147. package/src/router/service/queue.mjs +1 -1
  148. package/src/router/shadow.mjs +4 -3
  149. package/src/surgery-recovery.js +2 -2
  150. package/src/tool-output.js +2 -2
  151. package/src/tools.js +39 -7
  152. package/src/truncate-visible.js +2 -2
  153. package/src/ui/actions.js +1312 -0
  154. package/src/ui/panel-api.js +367 -0
  155. package/src/ui/panel-auth.js +251 -0
  156. package/src/ui/panel-integrity.js +243 -0
  157. package/src/ui/routes-readonly.js +695 -0
  158. package/src/ui/routes.js +3 -0
  159. package/docs/DEV.md +0 -279
  160. package/docs/PRESENCE-BUS-WIRING.md +0 -491
  161. package/docs/PRESENCE-BUS.md +0 -694
  162. package/docs/SURGERY.md +0 -186
  163. package/scripts/mark-core-cards.mjs +0 -289
  164. package/scripts/pack.mjs +0 -392
  165. package/scripts/router-proposals.mjs +0 -386
  166. package/src/router/fixtures/M1.json +0 -16
  167. package/src/router/fixtures/M2.json +0 -16
  168. package/src/router/fixtures/M3.json +0 -20
  169. package/src/router/fixtures/M4.json +0 -20
  170. package/src/router/fixtures/M5.json +0 -45
  171. package/src/router/fixtures/M6.json +0 -53
  172. package/src/router/fixtures/M7.json +0 -46
  173. package/src/router/fixtures/S1.json +0 -20
  174. package/src/router/fixtures/S2.json +0 -23
  175. package/src/router/fixtures/S3.json +0 -20
  176. package/src/router/fixtures/S4.json +0 -34
  177. package/src/router/fixtures/S5.json +0 -43
  178. package/src/router/fixtures/S6.json +0 -47
  179. package/src/router/fixtures/T1.json +0 -23
  180. package/src/router/fixtures/T2.json +0 -16
  181. package/src/router/fixtures/T3.json +0 -19
  182. package/src/router/fixtures/T4.json +0 -16
  183. package/src/router/fixtures/X1.json +0 -17
  184. package/src/router/fixtures/X2.json +0 -20
  185. package/src/router/gen-design-headings.mjs +0 -107
  186. package/src/router/policies/owner-rulings-2026-11-03.json +0 -62
  187. package/test/activity-realtime.test.mjs +0 -959
  188. package/test/activity-stream.test.mjs +0 -512
  189. package/test/auto-recall.test.mjs +0 -474
  190. package/test/auto-truncate.test.mjs +0 -931
  191. package/test/autobind.test.mjs +0 -399
  192. package/test/bus.test.mjs +0 -1572
  193. package/test/cli-guard.test.mjs +0 -198
  194. package/test/conclusion.test.mjs +0 -408
  195. package/test/config-schema.test.mjs +0 -210
  196. package/test/d6-output.test.mjs +0 -194
  197. package/test/d6.test.mjs +0 -395
  198. package/test/deps.test.mjs +0 -342
  199. package/test/doctor-setup.test.mjs +0 -297
  200. package/test/embedding-recall.test.mjs +0 -347
  201. package/test/feature-flags.test.mjs +0 -585
  202. package/test/filter-shadow.test.mjs +0 -170
  203. package/test/fixtures/activity-append-child.mjs +0 -29
  204. package/test/fixtures/baseline-0.8.0-registration.json +0 -556
  205. package/test/fixtures/d6-header-tools.json +0 -1
  206. package/test/fixtures/lock-child.mjs +0 -67
  207. package/test/fixtures/pre-step-container.mjs +0 -100
  208. package/test/fixtures/privacy-memory/topics/pii-unmarked.md +0 -9
  209. package/test/fixtures/privacy-memory/topics/secret-local-only.md +0 -11
  210. package/test/fixtures/profile-session-longlive-config.json +0 -20
  211. package/test/fixtures/proposal-cas-child.mjs +0 -66
  212. package/test/host-engine-status.test.mjs +0 -307
  213. package/test/hot-truncate.test.mjs +0 -1349
  214. package/test/identity-card-marker.test.mjs +0 -307
  215. package/test/identity-router/run.mjs +0 -721
  216. package/test/integration-pre-step.test.mjs +0 -553
  217. package/test/live.test.mjs +0 -1023
  218. package/test/maintenance-schedule.test.mjs +0 -538
  219. package/test/memory.test.mjs +0 -2828
  220. package/test/metrics.test.mjs +0 -478
  221. package/test/orchestrator.test.mjs +0 -266
  222. package/test/p1_probe.mjs +0 -97
  223. package/test/packaging.test.mjs +0 -184
  224. package/test/portability.test.mjs +0 -343
  225. package/test/rc4-regressions.test.mjs +0 -562
  226. package/test/rc5-regressions.test.mjs +0 -197
  227. package/test/rc6-chinese.test.mjs +0 -394
  228. package/test/router-1100/apply-off.test.mjs +0 -175
  229. package/test/router-1100/budget-reconcile.test.mjs +0 -215
  230. package/test/router-1100/config-context.test.mjs +0 -211
  231. package/test/router-1100/helpers.mjs +0 -129
  232. package/test/router-1100/hint-envelope.test.mjs +0 -150
  233. package/test/router-1100/lock-crossproc.test.mjs +0 -206
  234. package/test/router-1100/proposal-cas-crossproc.test.mjs +0 -184
  235. package/test/router-1100/router-apply.test.mjs +0 -372
  236. package/test/router-1100/router-fixb.test.mjs +0 -602
  237. package/test/router-1100/router-proposals.test.mjs +0 -405
  238. package/test/router-1100/router-queue-lease-wal.test.mjs +0 -355
  239. package/test/router-1100/router-security-cr.test.mjs +0 -333
  240. package/test/router-1100/service-lifecycle.test.mjs +0 -220
  241. package/test/router-1100/tools-contract.test.mjs +0 -164
  242. package/test/router-1100/turn-reflect-tick.test.mjs +0 -89
  243. package/test/router-1100/v6-contract.test.mjs +0 -158
  244. package/test/router-1100/v6b-contract.test.mjs +0 -293
  245. package/test/router-1100/v6c-contract.test.mjs +0 -249
  246. package/test/router-1100/wal-rotation.test.mjs +0 -375
  247. package/test/router-1100/wp1-config.test.mjs +0 -170
  248. package/test/router-1100/wp1-default-off.test.mjs +0 -77
  249. package/test/router-1100/wp1-model-status.test.mjs +0 -90
  250. package/test/router-1100/wp1-status.test.mjs +0 -220
  251. package/test/router-1100/wp1-tools.test.mjs +0 -146
  252. package/test/router-1100/wp5-cli.test.mjs +0 -240
  253. package/test/router-1100/wp5-model-auto.test.mjs +0 -418
  254. package/test/router-1100/wp6-merge.test.mjs +0 -386
  255. package/test/router-1100/wp6-wake.test.mjs +0 -502
  256. package/test/router-background/audit.test.mjs +0 -37
  257. package/test/router-background/budget-ledger.test.mjs +0 -330
  258. package/test/router-background/cross-process.test.mjs +0 -222
  259. package/test/router-background/fixtures/cross-child.mjs +0 -85
  260. package/test/router-background/fixtures/real-shapes.mjs +0 -239
  261. package/test/router-background/router.test.mjs +0 -1076
  262. package/test/router-background/stream-retry.test.mjs +0 -358
  263. package/test/router-background/tool-agent.test.mjs +0 -384
  264. package/test/router-face/bridge.test.mjs +0 -169
  265. package/test/router-face/face.test.mjs +0 -316
  266. package/test/router-failclosed.test.mjs +0 -466
  267. package/test/router-identity.test.mjs +0 -384
  268. package/test/router-l1-l2.contract.test.mjs +0 -1075
  269. package/test/router-memory-tools.test.mjs +0 -262
  270. package/test/router-policy.test.mjs +0 -225
  271. package/test/router-runtime.test.mjs +0 -438
  272. package/test/router-shadow.test.mjs +0 -287
  273. package/test/router.test.mjs +0 -903
  274. package/test/run-rc.mjs +0 -315
  275. package/test/run.mjs +0 -2469
  276. package/test/safety-defaults.test.mjs +0 -181
  277. package/test/seeded-load.test.mjs +0 -436
  278. package/test/session-original.test.mjs +0 -338
  279. package/test/state-truth.test.mjs +0 -937
  280. package/test/surgery-recovery.test.mjs +0 -936
  281. package/test/t7_drive.mjs +0 -151
  282. package/test/tool-output.test.mjs +0 -393
  283. package/test/tool-schema.test.mjs +0 -177
  284. package/test/tools_contract.test.mjs +0 -716
  285. package/test/truncate-visible.test.mjs +0 -1150
  286. package/test/ui-flags.test.mjs +0 -477
  287. package/test/v4-hardening.test.mjs +0 -427
  288. package/test/version-consistency.test.mjs +0 -286
@@ -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
- }