@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,204 +0,0 @@
1
- // suite: host-visible Config schema (1.5.0, UI-config batch)
2
- //
3
- // Contract under test:
4
- // - the exact config block from the live desktop profile's
5
- // `- id: session-longlive` row parses without losing a value;
6
- // - every declared default equals the value the 1.5.0 runtime already
7
- // falls back to (compared against the normalisers, not re-typed copies);
8
- // - absent fields fall back, unknown keys are preserved (schemastery objects
9
- // merge unknown members in non-strict mode), and non-boolean switch values
10
- // are rejected by `~standard.validate` exactly as Cordis calls it.
11
- //
12
- // The fixture is a verbatim copy of the live desktop profile's
13
- // `- id: session-longlive` config block (cordis.patch.yml lines 47-64), read
14
- // on 2026-11-06. It has 10 top-level keys / 10 leaf settings (the task note
15
- // said 11; the file is the authority).
16
-
17
- import test from 'node:test';
18
- import assert from 'node:assert/strict';
19
- import fs from 'node:fs';
20
- import path from 'node:path';
21
- import { fileURLToPath } from 'node:url';
22
-
23
- import { Config, CONFIG_SCHEMA_AVAILABLE } from '../src/config.js';
24
- import { D2_DEFAULTS, normalizeD2Config } from '../src/tool-output.js';
25
- import { FEATURE_DEFAULTS, normalizeFeatureConfig } from '../src/features.js';
26
- import { FACE_CONFIG_KEYS, normalizeFaceConfig } from '../src/router/face/config.mjs';
27
- import { AUTO_TRUNCATE_DEFAULTS, normalizeAutoTruncateConfig } from '../src/auto-truncate.js';
28
-
29
- const HERE = path.dirname(fileURLToPath(import.meta.url));
30
- const ROOT = path.resolve(HERE, '..');
31
- const FIXTURE = path.join(ROOT, 'test', 'fixtures', 'profile-session-longlive-config.json');
32
-
33
- const skip = CONFIG_SCHEMA_AVAILABLE
34
- ? false
35
- : 'schemastery 不可解析:安装 peer 或设置 NODE_PATH 指向含 @deepseek-ai/schemastery 的 node_modules';
36
-
37
- /** The config block the shipped cordis.patch.yml inserts (read from the work copy). */
38
- const SHIPPED_PATCH_CONFIG = {
39
- keepEvents: 20000,
40
- recentWriteWindowMs: 60000,
41
- routerPolicyProfile: 'generic',
42
- surgeryRecovery: true,
43
- routerRuntime: 'off',
44
- workers: { enabled: false },
45
- autoTruncate: { enabled: false, mode: 'dryrun' },
46
- filterEnabled: true,
47
- shadowEnabled: true,
48
- autoRecall: { enabled: false, topK: 3, budgetTokens: 2000, everyNTurns: 6 },
49
- identityCard: { enabled: false, path: '<memoryRoot>/STATE.md' },
50
- presenceBus: { enabled: false, pollMs: 3000 },
51
- };
52
-
53
- test('1.6.0 真实 profile 的 session-longlive config 原样通过校验', { skip }, () => {
54
- const fixture = JSON.parse(fs.readFileSync(FIXTURE, 'utf8'));
55
- assert.equal(Object.keys(fixture).length, 10, 'fixture 是 profile 的 10 个顶层键的逐字副本');
56
- const parsed = Config(fixture);
57
- for (const [key, value] of Object.entries(fixture)) {
58
- if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
59
- for (const [child, childValue] of Object.entries(value)) {
60
- assert.equal(parsed[key][child], childValue, `${key}.${child} 必须原样保留`);
61
- }
62
- } else {
63
- assert.equal(parsed[key], value, `${key} 必须原样保留`);
64
- }
65
- }
66
- // The schema fills the documented sub-defaults even when the profile only
67
- // wrote the switch.
68
- assert.deepEqual(parsed.d2, {
69
- enabled: true,
70
- maxInlineChars: D2_DEFAULTS.maxInlineChars,
71
- headRatio: D2_DEFAULTS.headRatio,
72
- skipTools: [...D2_DEFAULTS.skipTools],
73
- trimErrors: D2_DEFAULTS.trimErrors,
74
- });
75
- assert.equal(parsed.autoRecall.topK, 3);
76
- assert.equal(parsed.autoRecall.budgetTokens, 2000);
77
- assert.equal(parsed.autoRecall.everyNTurns, 6);
78
- assert.equal(parsed.presenceBus.pollMs, 3000);
79
- assert.equal(parsed.routerRuntime, 'shadow');
80
- assert.equal(parsed.routerProvider, 'deepseek-account');
81
- assert.equal(parsed.routerModel, 'auto');
82
- assert.equal(parsed.routerSharedBoard, true);
83
- });
84
-
85
- test('缺省字段回落到 1.6.0 运行时的默认值(对照归一化器,不重复抄常量)', { skip }, () => {
86
- const parsed = Config({});
87
-
88
- assert.deepEqual(parsed.d2, normalizeD2Config(undefined), 'd2 默认值必须等于 normalizeD2Config(undefined)');
89
- assert.deepEqual(parsed.d6, {
90
- enabled: false,
91
- dryRun: false,
92
- log: true,
93
- keepTools: [],
94
- });
95
-
96
- const features = normalizeFeatureConfig({});
97
- for (const key of ['enabled', 'topK', 'budgetTokens', 'everyNTurns']) {
98
- assert.equal(parsed.autoRecall[key], features.autoRecall[key], `autoRecall.${key}`);
99
- }
100
- for (const key of ['enabled', 'path', 'maxChars']) {
101
- assert.equal(parsed.identityCard[key], features.identityCard[key], `identityCard.${key}`);
102
- }
103
- for (const key of ['enabled', 'pollMs']) {
104
- assert.equal(parsed.presenceBus[key], features.presenceBus[key], `presenceBus.${key}`);
105
- }
106
- for (const key of ['enabled', 'consolidate', 'forget', 'runOnStart', 'startDelayMs']) {
107
- assert.equal(parsed.autoMaintenance[key], features.autoMaintenance[key], `autoMaintenance.${key}`);
108
- }
109
-
110
- const face = normalizeFaceConfig(undefined);
111
- for (const key of FACE_CONFIG_KEYS) {
112
- assert.equal(parsed.routerFace[key], face[key], `routerFace.${key}`);
113
- }
114
-
115
- assert.deepEqual(parsed.autoTruncate, normalizeAutoTruncateConfig(undefined));
116
-
117
- // The fields that intentionally have no schema default because the runtime
118
- // distinguishes "absent" from a value.
119
- assert.equal(parsed.routerRuntime, undefined);
120
- assert.equal(parsed.routerProvider, undefined);
121
- assert.equal(parsed.memoryRoot, undefined);
122
- assert.equal(parsed.routerProposalEnabled, undefined);
123
- assert.equal(parsed.routerL4MergeEnabled, undefined);
124
- assert.equal(parsed.routerWriterLeaseMs, undefined);
125
-
126
- assert.equal(parsed.routerEnabled, false);
127
- assert.equal(parsed.routerSharedBoard, false);
128
- assert.equal(parsed.routerModel, 'auto');
129
- assert.equal(parsed.routerPolicyProfile, 'generic');
130
- assert.equal(parsed.filterEnabled, true);
131
- assert.equal(parsed.shadowEnabled, true);
132
- assert.equal(parsed.surgeryRecovery, true);
133
- assert.equal(parsed.recoverOnStart, true);
134
- assert.equal(parsed.autoConclusion, false);
135
- assert.equal(parsed.keepEvents, 20000);
136
- assert.equal(parsed.recentWriteWindowMs, 60000);
137
- assert.equal(parsed.heartbeatMs, 15000);
138
- });
139
-
140
- test('发布包 cordis.patch.yml 的 config 也通过校验', { skip }, () => {
141
- const parsed = Config(SHIPPED_PATCH_CONFIG);
142
- assert.equal(parsed.routerRuntime, 'off');
143
- assert.equal(parsed.workers.enabled, false);
144
- assert.equal(parsed.autoTruncate.enabled, false);
145
- assert.equal(parsed.autoTruncate.mode, 'dryrun');
146
- assert.equal(parsed.autoTruncate.keepTurns, AUTO_TRUNCATE_DEFAULTS.keepTurns);
147
- assert.equal(parsed.autoTruncate.keepTokens, AUTO_TRUNCATE_DEFAULTS.keepTokens);
148
- assert.deepEqual(parsed.autoRecall, SHIPPED_PATCH_CONFIG.autoRecall);
149
- assert.deepEqual(parsed.identityCard, { ...SHIPPED_PATCH_CONFIG.identityCard, maxChars: 800 });
150
- assert.equal(parsed.presenceBus.enabled, false);
151
- assert.equal(parsed.presenceBus.pollMs, 3000);
152
- assert.equal('root' in parsed.presenceBus, false, '未配置的可选字段不应被凭空写出');
153
- });
154
-
155
- test('未知键不被拒绝:顶层、嵌套块与测试注入口都保留', { skip }, () => {
156
- const now = () => new Date(0);
157
- const parsed = Config({
158
- now,
159
- futureTopLevel: { flag: 1 },
160
- d2: { enabled: false, futureSubKey: 'kept' },
161
- });
162
- assert.equal(parsed.now, now, '测试注入的函数必须原样穿过 Config');
163
- assert.deepEqual(parsed.futureTopLevel, { flag: 1 });
164
- assert.equal(parsed.d2.futureSubKey, 'kept');
165
- });
166
-
167
- test('开关字段类型校验:非布尔被拒绝(~standard 与直接调用一致)', { skip }, () => {
168
- for (const config of [
169
- { d2: { enabled: 'true' } },
170
- { d6: { enabled: 1 } },
171
- { autoRecall: { enabled: 'yes' } },
172
- { presenceBus: { enabled: false, pollMs: 'soon' } },
173
- { autoMaintenance: { enabled: 'yes' } },
174
- { routerFace: { enabled: 'on' } },
175
- { routerEnabled: 'true' },
176
- ]) {
177
- assert.throws(() => Config(config), /expected (boolean|number)/, JSON.stringify(config));
178
- const result = Config['~standard'].validate(config);
179
- assert.ok(Array.isArray(result.issues) && result.issues.length > 0,
180
- `${JSON.stringify(config)} 必须被 ~standard.validate 拒绝`);
181
- }
182
- const accepted = Config['~standard'].validate({ d2: { enabled: true } });
183
- assert.equal(accepted.issues, undefined);
184
- assert.equal(accepted.value.d2.enabled, true);
185
- });
186
-
187
- test('schema 面:可 toJSON、描述同时有 zh/en、分组可折叠', { skip }, () => {
188
- assert.equal(typeof Config, 'function');
189
- assert.equal(Config['~standard'].version, 1);
190
- assert.equal(typeof Config.toJSON, 'function');
191
- const json = Config.toJSON();
192
- assert.ok(json && json.refs, 'toJSON 必须返回 schemastery 的 refs 信封');
193
-
194
- const d2Enabled = Config.dict.d2.dict.enabled;
195
- assert.equal(d2Enabled.meta.description.zh, '开启 D2 工具输出裁剪(默认关闭)。');
196
- assert.equal(d2Enabled.meta.description.en, 'Enable D2 tool-output trimming (off by default).');
197
- assert.equal(d2Enabled.meta.description[''], '开启 D2 工具输出裁剪(默认关闭)。');
198
- assert.equal(Config.dict.d2.meta.collapse, true, '嵌套块应可折叠');
199
- assert.equal(Config.dict.d6.meta.collapse, true);
200
- assert.equal(Config.dict.routerFace.meta.collapse, true);
201
-
202
- assert.equal(Config.dict.memoryRoot.meta.disabled, true, 'memoryRoot 是只读展示字段');
203
- assert.equal(Config.dict.memRoot.meta.disabled, true);
204
- });
@@ -1,194 +0,0 @@
1
- // D6 tool_search result-content gate.
2
- //
3
- // Host contract (@deepseek-ai/dsh-tools 0.2.2-rc.1, pipeline.createSuccessResult):
4
- // the return value of `output.render(args, value)` is used VERBATIM as the
5
- // tool-result message `content`. The model layer then walks that content as
6
- // typed blocks: @deepseek-ai/dsh-llm contentHasImage() does
7
- //
8
- // content.some((block) => block.type === 'image')
9
- //
10
- // so a string makes the host throw `content.some is not a function` — exactly
11
- // the 1.5.0-rc.2 VM recheck failure (REPORT.md item 3).
12
- //
13
- // rc.2's TOOL_SEARCH_SPEC.output.render returned JSON.stringify(value); the
14
- // other 22 tools in src/tools.js render through OUTPUT_TEXT ->
15
- // [{ type: 'text', text }]. This suite pins tool_search to that host shape and
16
- // proves the legacy string shape fails the same walk.
17
-
18
- import assert from 'node:assert/strict';
19
- import test from 'node:test';
20
-
21
- import { TOOL_SEARCH_SPEC, registerD6 } from '../src/d6/runtime.js';
22
- import { registerTools } from '../src/tools.js';
23
-
24
- function tool(name, description) {
25
- return { name, description, parameters: { value: { type: 'string' } } };
26
- }
27
-
28
- const BASE = [
29
- tool('read', 'Read a file from the workspace.'),
30
- tool('write', 'Write a file in the workspace.'),
31
- tool('edit', 'Edit a text file by replacing literal text.'),
32
- tool('pwsh', 'Run a PowerShell command.'),
33
- tool('glob', 'Find files by glob pattern.'),
34
- tool('grep', 'Search file contents with a regex.'),
35
- tool('todo_write', 'Record a task list.'),
36
- tool('session_health', 'Inspect a DSH session log.'),
37
- tool('session_search', 'Search prior sessions.'),
38
- tool('memory_recall', 'Search memory cards.'),
39
- tool('read_image', 'Read a PNG/JPEG/WebP/GIF image.'),
40
- tool('send_meme', 'Send a meme image.'),
41
- tool('learn_meme', 'Add an image to the meme library.'),
42
- tool('meme_list', 'List meme images.'),
43
- tool('present', 'Declare existing files as final deliverables.'),
44
- tool('job_list', 'List background jobs.'),
45
- tool('job_output', 'Read a background job.'),
46
- tool('workflow', 'Run a JavaScript workflow of subagents.'),
47
- tool('subagent', 'Delegate a task to a subagent.'),
48
- ];
49
-
50
- const USER = (text) => ({ role: 'user', source: { kind: 'user' }, content: [{ type: 'text', text }] });
51
-
52
- function makeHost({ config = { d6: { enabled: true } } } = {}) {
53
- const defs = [...BASE, TOOL_SEARCH_SPEC];
54
- const layers = [];
55
- const listeners = new Map();
56
- const ctx = {
57
- tools: {
58
- definitions() {
59
- const denied = new Set(layers.flatMap((layer) => [...layer]));
60
- return defs.filter((entry) => !denied.has(entry.name));
61
- },
62
- view() {
63
- const denied = new Set(layers.flatMap((layer) => [...layer]));
64
- const visible = new Map();
65
- for (const entry of defs) {
66
- if (!denied.has(entry.name)) visible.set(entry.name, { definition: entry });
67
- }
68
- return { visible, restrictableNames: new Set(defs.map((entry) => entry.name)) };
69
- },
70
- restrict({ deny }) {
71
- const layer = new Set(deny);
72
- layers.push(layer);
73
- let active = true;
74
- return () => {
75
- if (!active) return;
76
- active = false;
77
- const index = layers.indexOf(layer);
78
- if (index >= 0) layers.splice(index, 1);
79
- };
80
- },
81
- },
82
- on(name, listener) {
83
- const list = listeners.get(name) ?? [];
84
- list.push(listener);
85
- listeners.set(name, list);
86
- return () => {};
87
- },
88
- logger() {
89
- return { info: () => {}, warn: () => {}, debug: () => {} };
90
- },
91
- effect() {},
92
- };
93
- const agent = { ctx, session: { snapshotEvents: () => [] } };
94
- const registration = registerD6(ctx, config);
95
- const emitClaimed = (message, turn = 1) => {
96
- for (const listener of listeners.get('agent/inbox/claimed') ?? []) {
97
- listener({ agent, message, turn });
98
- }
99
- };
100
- return { ctx, agent, registration, emitClaimed };
101
- }
102
-
103
- // Literal copy of the host walk that threw in the VM
104
- // (@deepseek-ai/dsh-llm 0.2.2-rc.1 contentHasImage). Keep `content.some(...)`
105
- // untouched: a string must fail here with the exact TypeError text.
106
- function contentHasImage(content) {
107
- return content.some((block) => block.type === 'image');
108
- }
109
-
110
- // Host result-content contract: the walk above must not throw, the content must
111
- // be a non-empty array, and every block must be a typed content block with a
112
- // string `text` when it is a text block (the shape OUTPUT_TEXT emits).
113
- function assertHostToolResultContent(content) {
114
- contentHasImage(content);
115
- assert.ok(Array.isArray(content), 'tool result content must be an array of content blocks');
116
- assert.ok(content.length > 0, 'tool result content must contain at least one block');
117
- for (const block of content) {
118
- assert.ok(block !== null && typeof block === 'object' && !Array.isArray(block), 'every content block must be an object');
119
- assert.equal(typeof block.type, 'string', 'every content block needs a string type');
120
- if (block.type === 'text') assert.equal(typeof block.text, 'string', 'text blocks need a string text');
121
- }
122
- return content;
123
- }
124
-
125
- test('tool_search renders a legal host content-block array on list and add paths', () => {
126
- const host = makeHost();
127
- host.emitClaimed(USER('修复这个 TypeScript 编译错误,补单元测试并跑通'));
128
- const toolSearch = host.registration.tools[0];
129
- assert.equal(toolSearch.name, 'tool_search');
130
-
131
- const listed = toolSearch.invoke({}, { exec: { agent: host.agent }, host: () => host.ctx });
132
- const listedContent = toolSearch.output.render({}, listed);
133
- assertHostToolResultContent(listedContent);
134
- assert.deepEqual(JSON.parse(listedContent[0].text), listed);
135
- assert.match(listedContent[0].text, /"totalTrimmed"/);
136
-
137
- const added = toolSearch.invoke(
138
- { names: ['read_image'] },
139
- { exec: { agent: host.agent }, host: () => host.ctx },
140
- );
141
- assert.equal(added.ok, true);
142
- const addedContent = toolSearch.output.render({ names: ['read_image'] }, added);
143
- assertHostToolResultContent(addedContent);
144
- assert.deepEqual(JSON.parse(addedContent[0].text), added);
145
- assert.match(addedContent[0].text, /read_image/);
146
- });
147
-
148
- test('tool_search error payloads render through the same content-block shape', () => {
149
- const host = makeHost();
150
- const toolSearch = host.registration.tools[0];
151
- const result = toolSearch.invoke({}, { exec: { agent: host.agent } });
152
- assert.equal(result.error, 'D6_NOT_FROZEN');
153
- const content = toolSearch.output.render({}, result);
154
- assertHostToolResultContent(content);
155
- assert.deepEqual(JSON.parse(content[0].text), result);
156
- assert.match(content[0].text, /D6_NOT_FROZEN/);
157
- });
158
-
159
- test('registerTools forwards tool_search output.render untouched to the host seam', async () => {
160
- const host = makeHost();
161
- const captured = [];
162
- const count = await registerTools(
163
- {
164
- tools: {
165
- register(definition) {
166
- captured.push(definition);
167
- return () => {};
168
- },
169
- },
170
- },
171
- { defineTool: (definition) => definition, d6Tools: host.registration.tools },
172
- );
173
- assert.equal(count, 23);
174
- const search = captured.find((definition) => definition.name === 'tool_search');
175
- assert.ok(search, 'tool_search was not registered through registerTools');
176
- assert.equal(search.output, TOOL_SEARCH_SPEC.output, 'registerTools must forward the output contract by identity');
177
-
178
- const value = await search.execute({}, { agent: host.agent });
179
- assert.equal(value.error, 'D6_NOT_FROZEN');
180
- const content = search.output.render({}, value);
181
- assertHostToolResultContent(content);
182
- assert.deepEqual(JSON.parse(content[0].text), value);
183
- });
184
-
185
- test('the rc.2 string shape fails the same host walk (mutation guard)', () => {
186
- const legacyRender = (_args, value) => JSON.stringify(value);
187
- const legacyContent = legacyRender({}, { ok: true, added: ['read_image'] });
188
- assert.equal(typeof legacyContent, 'string');
189
- assert.equal(Array.isArray(legacyContent), false);
190
-
191
- // The pre-fix shape must reproduce the reported host failure verbatim.
192
- assert.throws(() => contentHasImage(legacyContent), /content\.some is not a function/);
193
- assert.throws(() => assertHostToolResultContent(legacyContent), /content\.some is not a function/);
194
- });