dsh-remote-control 1.0.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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/cordis.patch.yml +21 -0
  3. package/dist/bundle/index.js +33956 -0
  4. package/dist/src/core/clock.d.ts +45 -0
  5. package/dist/src/core/clock.js +83 -0
  6. package/dist/src/core/clock.js.map +1 -0
  7. package/dist/src/core/keys.d.ts +98 -0
  8. package/dist/src/core/keys.js +176 -0
  9. package/dist/src/core/keys.js.map +1 -0
  10. package/dist/src/core/pairing-window.d.ts +95 -0
  11. package/dist/src/core/pairing-window.js +92 -0
  12. package/dist/src/core/pairing-window.js.map +1 -0
  13. package/dist/src/core/runtime.d.ts +104 -0
  14. package/dist/src/core/runtime.js +491 -0
  15. package/dist/src/core/runtime.js.map +1 -0
  16. package/dist/src/core/sleep-policy.d.ts +81 -0
  17. package/dist/src/core/sleep-policy.js +134 -0
  18. package/dist/src/core/sleep-policy.js.map +1 -0
  19. package/dist/src/core/window.d.ts +88 -0
  20. package/dist/src/core/window.js +185 -0
  21. package/dist/src/core/window.js.map +1 -0
  22. package/dist/src/index.d.ts +70 -0
  23. package/dist/src/index.js +882 -0
  24. package/dist/src/index.js.map +1 -0
  25. package/dist/src/platform/carrier-services.d.ts +96 -0
  26. package/dist/src/platform/carrier-services.js +982 -0
  27. package/dist/src/platform/carrier-services.js.map +1 -0
  28. package/dist/src/platform/guard.d.ts +60 -0
  29. package/dist/src/platform/guard.js +124 -0
  30. package/dist/src/platform/guard.js.map +1 -0
  31. package/dist/src/platform/mock-kernel.d.ts +95 -0
  32. package/dist/src/platform/mock-kernel.js +180 -0
  33. package/dist/src/platform/mock-kernel.js.map +1 -0
  34. package/dist/src/platform/qr.d.ts +28 -0
  35. package/dist/src/platform/qr.js +80 -0
  36. package/dist/src/platform/qr.js.map +1 -0
  37. package/dist/src/platform/sleep-posix.d.ts +17 -0
  38. package/dist/src/platform/sleep-posix.js +91 -0
  39. package/dist/src/platform/sleep-posix.js.map +1 -0
  40. package/dist/src/ports/index.d.ts +246 -0
  41. package/dist/src/ports/index.js +2 -0
  42. package/dist/src/ports/index.js.map +1 -0
  43. package/dist/src/shell/config.d.ts +56 -0
  44. package/dist/src/shell/config.js +212 -0
  45. package/dist/src/shell/config.js.map +1 -0
  46. package/dist/src/shell/pairing-text.d.ts +38 -0
  47. package/dist/src/shell/pairing-text.js +22 -0
  48. package/dist/src/shell/pairing-text.js.map +1 -0
  49. package/dist/src/shell/status.d.ts +39 -0
  50. package/dist/src/shell/status.js +119 -0
  51. package/dist/src/shell/status.js.map +1 -0
  52. package/dist/src/transport/relay.d.ts +107 -0
  53. package/dist/src/transport/relay.js +359 -0
  54. package/dist/src/transport/relay.js.map +1 -0
  55. package/dist/tests/carrier-events.test.d.ts +1 -0
  56. package/dist/tests/carrier-events.test.js +88 -0
  57. package/dist/tests/carrier-events.test.js.map +1 -0
  58. package/dist/tests/carrier-services.test.d.ts +1 -0
  59. package/dist/tests/carrier-services.test.js +562 -0
  60. package/dist/tests/carrier-services.test.js.map +1 -0
  61. package/dist/tests/carrier-vocabulary.test.d.ts +1 -0
  62. package/dist/tests/carrier-vocabulary.test.js +149 -0
  63. package/dist/tests/carrier-vocabulary.test.js.map +1 -0
  64. package/dist/tests/clock-probe-timers.test.d.ts +1 -0
  65. package/dist/tests/clock-probe-timers.test.js +73 -0
  66. package/dist/tests/clock-probe-timers.test.js.map +1 -0
  67. package/dist/tests/command-result.test.d.ts +1 -0
  68. package/dist/tests/command-result.test.js +185 -0
  69. package/dist/tests/command-result.test.js.map +1 -0
  70. package/dist/tests/config.test.d.ts +1 -0
  71. package/dist/tests/config.test.js +223 -0
  72. package/dist/tests/config.test.js.map +1 -0
  73. package/dist/tests/guard.test.d.ts +1 -0
  74. package/dist/tests/guard.test.js +175 -0
  75. package/dist/tests/guard.test.js.map +1 -0
  76. package/dist/tests/ids-and-keys.test.d.ts +1 -0
  77. package/dist/tests/ids-and-keys.test.js +211 -0
  78. package/dist/tests/ids-and-keys.test.js.map +1 -0
  79. package/dist/tests/pairing-text.test.d.ts +1 -0
  80. package/dist/tests/pairing-text.test.js +62 -0
  81. package/dist/tests/pairing-text.test.js.map +1 -0
  82. package/dist/tests/pairing-window.test.d.ts +1 -0
  83. package/dist/tests/pairing-window.test.js +165 -0
  84. package/dist/tests/pairing-window.test.js.map +1 -0
  85. package/dist/tests/relay-client.test.d.ts +1 -0
  86. package/dist/tests/relay-client.test.js +557 -0
  87. package/dist/tests/relay-client.test.js.map +1 -0
  88. package/dist/tests/runtime.test.d.ts +1 -0
  89. package/dist/tests/runtime.test.js +963 -0
  90. package/dist/tests/runtime.test.js.map +1 -0
  91. package/dist/tests/sleep.test.d.ts +1 -0
  92. package/dist/tests/sleep.test.js +264 -0
  93. package/dist/tests/sleep.test.js.map +1 -0
  94. package/dist/tests/status.test.d.ts +1 -0
  95. package/dist/tests/status.test.js +238 -0
  96. package/dist/tests/status.test.js.map +1 -0
  97. package/dist/tests/window.test.d.ts +1 -0
  98. package/dist/tests/window.test.js +210 -0
  99. package/dist/tests/window.test.js.map +1 -0
  100. package/package.json +70 -0
@@ -0,0 +1,223 @@
1
+ /**
2
+ * config.test — `shell/config.ts`:三级合成、校验、夹取、脱敏。
3
+ *
4
+ * 这一层守的是"装上了但没配好"这类故障(docs/legacy-spec/host-plugin-cordis.md §5.1/§5.2):
5
+ * GUI 宿主里没人看 stdout,配置写错时插件必须**要么带着次优值跑起来、要么留下能读的原因**,
6
+ * 而不是不加载——"为什么什么都没发生"要有答案(docs/DESIGN.md §7 阶段 C 第 ⑥ 条:
7
+ * 配置校验只拦"真不可用")。
8
+ *
9
+ * 一个都必须钉死的点:`idleReleaseSec` 非法值绝不能把 NaN 传给 KeepAwake——
10
+ * `NaN * 1000` 会让"空闲自动释放"的判据 `now - lastActiveAt > NaN` 恒为 false,
11
+ * 表现是那台机器一次都睡不着,而且没有任何日志(旧实现 §4.4 那条 300 秒阈值就是这么失效的)。
12
+ */
13
+ import { test } from 'node:test';
14
+ import assert from 'node:assert/strict';
15
+ import { DEFAULT_CONFIG, readConfig, redact, validateConfig } from '../src/shell/config.js';
16
+ /** 一眼假的 token:只用来验证脱敏形状,仓库里不许出现任何真凭据。 */
17
+ const FAKE_TOKEN = 'fake-token-not-a-real-secret-0123456789abcdef';
18
+ const LONG_ENOUGH = 'fake-fake-fake-fake-fake-0123456789';
19
+ function base(overrides = {}) {
20
+ return { ...DEFAULT_CONFIG, hostToken: LONG_ENOUGH, ...overrides };
21
+ }
22
+ test('优先级是环境变量 > patch 注入 > 默认值:环境变量是运维唯一的兜底入口', () => {
23
+ const config = readConfig({ serverUrl: 'wss://from-patch.example', hostLabel: 'patch-label', hostId: 'patch-host' }, { DRC_SERVER_URL: 'wss://from-env.example', DRC_HOST_LABEL: 'env-label', DRC_HOST_ID: 'env-host' });
24
+ assert.equal(config.serverUrl, 'wss://from-env.example', '环境变量没赢 → 中继地址没法临时切换(旧实现的运维习惯)');
25
+ assert.equal(config.hostLabel, 'env-label', 'hostLabel 环境变量没赢');
26
+ assert.equal(config.hostId, 'env-host', 'hostId 环境变量没赢:手机看到的"是哪台主机"就错了');
27
+ const patchOnly = readConfig({ serverUrl: 'wss://from-patch.example' }, {});
28
+ assert.equal(patchOnly.serverUrl, 'wss://from-patch.example', 'patch 注入必须压过默认值');
29
+ const neither = readConfig(undefined, {});
30
+ assert.equal(neither.serverUrl, DEFAULT_CONFIG.serverUrl, '两者都没有时退回默认值(本机中继是最常见形态)');
31
+ assert.equal(neither.enabled, true, '默认是启用:装了就 expect 能用(§5.2 的降级原则)');
32
+ });
33
+ test('host token 只从环境变量取;hostTokenEnv 改名后按新名字取', () => {
34
+ const config = readConfig({ hostTokenEnv: 'MY_DRC_TOKEN' }, { MY_DRC_TOKEN: LONG_ENOUGH });
35
+ assert.equal(config.hostToken, LONG_ENOUGH, '按 hostTokenEnv 指向的变量名取值:取不到就得报 error,不能悄悄用空 token 连不上');
36
+ const fromPatch = readConfig({ hostToken: LONG_ENOUGH }, {});
37
+ assert.equal(fromPatch.hostToken, LONG_ENOUGH, 'patch 里直接写 hostToken 也接受(env 缺失时的兜底)');
38
+ const envWins = readConfig({ hostToken: 'fake-patched-patched-patched-0000' }, { DRC_HOST_TOKEN: LONG_ENOUGH });
39
+ assert.equal(envWins.hostToken, LONG_ENOUGH, '环境变量必须压过 patch 里的 hostToken');
40
+ });
41
+ test('缺 token 是 error 级:整条链路连不上中继,手机看到的是"永远连不上"', () => {
42
+ const problems = validateConfig({ ...DEFAULT_CONFIG, hostToken: '' });
43
+ const errors = problems.filter((problem) => problem.level === 'error');
44
+ assert.ok(errors.length >= 1, '空 token 必须报 error:只有 warn 的话上层启动门不拦,插件会连到中继后被 bad_token 踢');
45
+ assert.equal(errors[0]?.field, 'hostToken', '字段名要报对,否则"为什么什么都没发生"没有答案');
46
+ assert.match(String(errors[0]?.message), /DRC_HOST_TOKEN/, 'message 里要带上该往哪个环境变量里填');
47
+ });
48
+ test('token 短于 24 字符只是 warn:能跑,但强度不够', () => {
49
+ const problems = validateConfig(base({ hostToken: 'fake-short' }));
50
+ const warns = problems.filter((problem) => problem.level === 'warn');
51
+ assert.equal(problems.some((problem) => problem.level === 'error'), false, '短 token 不该拦启动:那是用户的部署决定');
52
+ assert.ok(warns.some((problem) => problem.field === 'hostToken'), '要有这条 warn,否则弱 token 会一路带到线上');
53
+ });
54
+ test('非回环地址走明文 ws:// 必须 warn;回环与 wss:// 不 warn', () => {
55
+ const publicPlain = validateConfig(base({ serverUrl: 'ws://drc.example.com' }));
56
+ assert.ok(publicPlain.some((problem) => problem.level === 'warn' && problem.field === 'serverUrl'), '明文 ws:// 出公网时配对码与密文在链路上可见:这条 warn 是唯一提醒');
57
+ for (const url of ['ws://127.0.0.1:8787', 'ws://localhost:8787', 'ws://[::1]:8787', 'wss://drc.example.com']) {
58
+ const problems = validateConfig(base({ serverUrl: url }));
59
+ assert.equal(problems.some((problem) => problem.field === 'serverUrl'), false, `${url} 被误报了:本机开发(127.0.0.1/localhost/::1)与 wss:// 都该安静通过`);
60
+ }
61
+ });
62
+ test('serverUrl 根本不是 ws/wss 时是 error:连不上任何东西的插件不该假装在跑', () => {
63
+ for (const url of ['http://127.0.0.1:8787', 'drc.example.com', '']) {
64
+ const problems = validateConfig(base({ serverUrl: url }));
65
+ const errors = problems.filter((problem) => problem.level === 'error' && problem.field === 'serverUrl');
66
+ assert.ok(errors.length >= 1, `${JSON.stringify(url)} 没被判为 error:小程序侧 T8 只接受 ws/wss,配错了要立刻说`);
67
+ }
68
+ });
69
+ test('idleReleaseSec 非法值一律夹回 300:NaN 传给 KeepAwake 会让那台机器永不睡觉且毫无日志', () => {
70
+ for (const bad of [Number.NaN, Number.POSITIVE_INFINITY, -1, -300]) {
71
+ const config = base({ keepAwake: { enabled: true, idleReleaseSec: bad, keepDisplay: false } });
72
+ const problems = validateConfig(config);
73
+ assert.ok(problems.some((problem) => problem.level === 'warn' && problem.field === 'keepAwake.idleReleaseSec'), `idleReleaseSec=${String(bad)} 没有 warn:用户以为自己配了值`);
74
+ assert.equal(config.keepAwake.idleReleaseSec, 300, `idleReleaseSec=${String(bad)} 没被夹回 300`);
75
+ assert.equal(Number.isFinite(config.keepAwake.idleReleaseSec * 1000), true, '换算成毫秒后必须是有限数:KeepAwake 的判据是 now-lastActiveAt > idleReleaseMs');
76
+ }
77
+ // 0 是合法值("保持到被显式关闭"),不许被夹走。
78
+ const zero = base({ keepAwake: { enabled: true, idleReleaseSec: 0, keepDisplay: false } });
79
+ assert.equal(validateConfig(zero).some((problem) => problem.field === 'keepAwake.idleReleaseSec'), false, '0 被当成非法值:手机上的"永不自动释放"选项就没了');
80
+ assert.equal(zero.keepAwake.idleReleaseSec, 0, '0 必须原样保留');
81
+ });
82
+ test('pairTtlMs 非正数夹回 120000:0 会让刚发布的配对码立刻过期,手机永远扫不上', () => {
83
+ const config = base({ pairTtlMs: 0 });
84
+ const problems = validateConfig(config);
85
+ assert.ok(problems.some((problem) => problem.field === 'pairTtlMs' && problem.level === 'warn'), 'pairTtlMs=0 必须 warn');
86
+ assert.equal(config.pairTtlMs, 120_000, '必须夹回默认值:本地过期时间是 peer-joined 取 PSK 的前提');
87
+ const negative = base({ pairTtlMs: -1 });
88
+ validateConfig(negative);
89
+ assert.equal(negative.pairTtlMs, 120_000, '负值同样要夹');
90
+ });
91
+ test('pairOnStartSec>0 必须 warn:它会把仍然有效的配对码与 PSK 写进 status.json', () => {
92
+ const problems = validateConfig(base({ pairOnStartSec: 60 }));
93
+ const warn = problems.find((problem) => problem.field === 'pairOnStartSec');
94
+ assert.ok(warn !== undefined, '这条验证辅助开关默认关(0),开起来必须有提醒:0600 文件里躺着可用的 PSK');
95
+ assert.equal(warn?.level, 'warn');
96
+ assert.match(String(warn?.message), /PSK/, '提醒内容要说清泄露的是什么');
97
+ const off = validateConfig(base({ pairOnStartSec: 0 }));
98
+ assert.equal(off.some((problem) => problem.field === 'pairOnStartSec'), false, '默认关的时候不该刷屏');
99
+ });
100
+ test('statusFile 为空只 warn 并关闭状态快照:GUI 宿主里那是唯一的排错入口', () => {
101
+ const problems = validateConfig(base({ statusFile: '' }));
102
+ assert.ok(problems.some((problem) => problem.field === 'statusFile' && problem.level === 'warn'), '为空要提醒:否则"为什么什么都没发生"没有答案');
103
+ assert.equal(problems.some((problem) => problem.level === 'error'), false, '为空是合法选择(关掉功能),不该拦启动');
104
+ });
105
+ test('takeOverQuestions 必须 warn:它会剥夺桌面 UI 的提问能力', () => {
106
+ const problems = validateConfig(base({ takeOverQuestions: true }));
107
+ assert.ok(problems.some((problem) => problem.field === 'takeOverQuestions' && problem.level === 'warn'), '接管提问是不可逆的能力转移,必须提醒');
108
+ assert.equal(validateConfig(base({ takeOverQuestions: false })).some((problem) => problem.field === 'takeOverQuestions'), false, '默认关时不该出现这条');
109
+ });
110
+ test('redact() 不许泄露 token:短 token 全打星,长 token 只留前 4 后 2 与长度', () => {
111
+ const short = redact('tiny');
112
+ assert.equal(short, '****', '长度 <=8 的凭据必须整体打星:留任何片段都等于把整条给出去');
113
+ assert.equal(short.includes('tiny'), false, '短 token 里出现了明文片段');
114
+ const long = redact(FAKE_TOKEN);
115
+ assert.equal(long, `fake…ef(${FAKE_TOKEN.length})`, `脱敏形状不对:收到 ${long}(约定是 前4+…+后2+长度)`);
116
+ assert.equal(long.includes(FAKE_TOKEN), false, '整条 token 原样出现 = status.json 里躺着凭据');
117
+ assert.equal(long.includes('not-a-real-secret'), false, '中段必须被抹掉');
118
+ assert.ok(long.endsWith(`(${FAKE_TOKEN.length})`), '长度要带出来:排错时靠它判断是不是拿错了变量');
119
+ // 长度 8 与 9 的分界:9 个字符只允许留前 4 与后 2。
120
+ assert.equal(redact('fakefake9'), 'fake…e9(9)', '边界值 9 个字符只允许留前 4 与后 2');
121
+ assert.equal(redact('fakefake9').includes('efak'), false, '中段一个字符都不许多留');
122
+ assert.equal(redact('fakefake'), '****', '边界值 8 个字符必须全打星');
123
+ });
124
+ test('qrStyle 非法值不猜、保留上层值:把非法样式悄悄换成别的样式,等于运维写了没生效还不知道', () => {
125
+ assert.equal(readConfig(undefined, { DRC_QR_STYLE: 'block' }).qrStyle, 'block', '合法值要透过去');
126
+ assert.equal(readConfig(undefined, { DRC_QR_STYLE: 'half' }).qrStyle, 'half', 'half 是默认的紧凑样式');
127
+ assert.equal(readConfig(undefined, {}).qrStyle, 'half', '什么都没设时是 half(文本二维码是主路径)');
128
+ for (const garbage of ['svg', 'ASCII', 'unicode', 'svg,png']) {
129
+ const config = readConfig({ qrStyle: 'block' }, { DRC_QR_STYLE: garbage });
130
+ assert.equal(config.qrStyle, 'block', `DRC_QR_STYLE=${JSON.stringify(garbage)} 非法时必须保留上层值,而不是换成一个没人写过的样式`);
131
+ }
132
+ // 非法值要有人说话:静默保留上层值 = 运维以为改了但其实没改。
133
+ const injected = base({ qrStyle: 'svg' });
134
+ assert.ok(validateConfig(injected).some((problem) => problem.field === 'qrStyle'), '非法 qrStyle 必须 warn');
135
+ assert.equal(injected.qrStyle, 'half', 'warn 之后夹回默认样式,渲染层才不会拿到不认识的样式');
136
+ });
137
+ test('布尔型环境变量只认显式的 1/0:拼错的值不许把功能打开或关掉', () => {
138
+ assert.equal(readConfig(undefined, { DRC_MOCK_BRIDGE: '1' }).mockBridge, true, 'DRC_MOCK_BRIDGE=1 必须打开内存替身');
139
+ assert.equal(readConfig({ mockBridge: true }, { DRC_MOCK_BRIDGE: '0' }).mockBridge, true, '文档约定的开关是 =1,0 只表示"没打开",不许把 patch 里的设置反掉');
140
+ assert.equal(readConfig({ qrImage: true }, { DRC_QR_IMAGE: '0' }).qrImage, false, 'DRC_QR_IMAGE=0 必须关掉 PNG 落盘');
141
+ assert.equal(readConfig({ qrOpen: true }, { DRC_QR_OPEN: '0' }).qrOpen, false, 'DRC_QR_OPEN=0 必须关掉"自动打开"');
142
+ assert.equal(readConfig({ takeOverQuestions: true }, { DRC_TAKE_OVER_QUESTIONS: '1' }).takeOverQuestions, true, '接管开关按显式 1 走');
143
+ });
144
+ test('DRC_PAIR_ON_START_SEC 的非有限值也必须折成 0:`Number(v) || 0` 会放过 Infinity', () => {
145
+ // Infinity 是**真值**,所以"用 || 兜底"的写法根本拦不住它;而 patch 里写
146
+ // `pairOnStartSec: .inf` 走的是注入路径,同样必须被折掉。
147
+ // 语义后果:这张带着 PSK 的配对码"永久有效",正好是这条配置想避免的事。
148
+ for (const bad of ['Infinity', '+Infinity', '1e999', '-5', '-0.5']) {
149
+ assert.equal(readConfig({ pairOnStartSec: 60 }, { DRC_PAIR_ON_START_SEC: bad }).pairOnStartSec, 0, `DRC_PAIR_ON_START_SEC=${bad} 漏进了配置`);
150
+ }
151
+ for (const bad of [Number.POSITIVE_INFINITY, Number.NaN, -1]) {
152
+ const config = base({ pairOnStartSec: bad });
153
+ const problems = validateConfig(config);
154
+ assert.equal(config.pairOnStartSec, 0, `注入值 ${String(bad)} 没被 validateConfig 折回 0`);
155
+ assert.ok(problems.some((problem) => problem.field === 'pairOnStartSec'), `折回 0 却不留痕(${JSON.stringify(problems.map((problem) => problem.field))})`);
156
+ }
157
+ });
158
+ test('DRC_PAIR_ON_START_SEC 的非数字值折成 0(关):绝不能折成 NaN 让发布循环卡住', () => {
159
+ assert.equal(readConfig(undefined, { DRC_PAIR_ON_START_SEC: '120' }).pairOnStartSec, 120, '数字值要生效');
160
+ assert.equal(readConfig(undefined, { DRC_PAIR_ON_START_SEC: '1e69' }).pairOnStartSec, 1e69, '合法但荒谬的数字照样通过:只拦 NaN,不拦量级(这条是有意的宽松)');
161
+ for (const bad of ['abc', '0', 'nan', 'true', '12sec']) {
162
+ assert.equal(readConfig({ pairOnStartSec: 60 }, { DRC_PAIR_ON_START_SEC: bad }).pairOnStartSec, 0, `DRC_PAIR_ON_START_SEC=${JSON.stringify(bad)} 必须落到 0=关,而不是保留上层的值`);
163
+ }
164
+ assert.equal(Number.isNaN(readConfig(undefined, { DRC_PAIR_ON_START_SEC: 'abc' }).pairOnStartSec), false, 'NaN 漏进配置:`pairOnStartSec > 0` 判据会变成 undefined 分支');
165
+ // 空串等于"没有这个环境变量":不许把 patch 里的值反掉,也不许折成 0。
166
+ assert.equal(readConfig({ pairOnStartSec: 60 }, { DRC_PAIR_ON_START_SEC: '' }).pairOnStartSec, 60, '空串被当成 0 会静默关掉自动发码');
167
+ assert.equal(readConfig({ qrStyle: 'block' }, { DRC_QR_STYLE: '' }).qrStyle, 'block', '空串同样不该覆盖 patch 里的样式');
168
+ });
169
+ test('keepAwake 是深合并:patch 只给一个子字段时其余子字段保留默认值', () => {
170
+ // patch YAML 是无类型的:用户真的会只写一个子键,这里按现实形状注入。
171
+ const config = readConfig({ keepAwake: { keepDisplay: true } }, {});
172
+ assert.equal(config.keepAwake.keepDisplay, true, '子字段没合进去:用户只想开屏幕常锁,结果整个开关被默认值覆盖');
173
+ assert.equal(config.keepAwake.enabled, true, 'keepAwake.enabled 必须保留默认 true:浅合并会把它变成 undefined');
174
+ assert.equal(config.keepAwake.idleReleaseSec, DEFAULT_CONFIG.keepAwake.idleReleaseSec, 'idleReleaseSec 同样要保留默认 300');
175
+ });
176
+ test('校验不许改动合法配置:夹取只发生在非法值上', () => {
177
+ const config = base({ keepAwake: { enabled: true, idleReleaseSec: 45, keepDisplay: true }, pairTtlMs: 30_000 });
178
+ const copy = JSON.parse(JSON.stringify(config));
179
+ validateConfig(config);
180
+ assert.deepEqual(config.keepAwake, copy.keepAwake, '合法值被改写了:用户配的 45 秒没了');
181
+ assert.equal(config.pairTtlMs, 30_000, '合法的 pairTtlMs 不许被动');
182
+ assert.equal(config.hostToken, copy.hostToken, '校验永远不许改 token(也永远不许把它写进任何返回值)');
183
+ });
184
+ test('一份典型配置在合法输入下不该有任何 error:否则插件会静默不加载', () => {
185
+ const config = readConfig({
186
+ serverUrl: 'wss://drc.example.com',
187
+ hostLabel: '我的主机',
188
+ keepAwake: { enabled: true, idleReleaseSec: 600, keepDisplay: false },
189
+ }, { DRC_HOST_TOKEN: FAKE_TOKEN });
190
+ const problems = validateConfig(config);
191
+ assert.equal(problems.filter((problem) => problem.level === 'error').length, 0, `合法配置报了 error:${JSON.stringify(problems.filter((p) => p.level === 'error'))}`);
192
+ assert.equal(config.hostToken, FAKE_TOKEN, 'token 必须真的从环境变量读到');
193
+ });
194
+ /**
195
+ * 配对二维码的呈现默认值是一条拍板,2026-10-02 翻转:
196
+ * 真机取证发现 DSH 命令卡按 `line-height:1.6` 渲染等宽输出,行间留白把半块二维码
197
+ * 横切成条(zxing ≥1.15 行距即失败),文本码在唯一宿主上扫不出来——于是默认产物改成图片。
198
+ * **仍然不许把用户赶到 DSH 外面去"看预览"**:`qrOpen` 保持关(写图 ≠ 替用户开查看器)。
199
+ * 这条断言存在的意义是:哪天有人把 `qrOpen` 默认打开、或把关掉的文本默认改回来,测试会拦住他。
200
+ */
201
+ test('二维码默认出图片(qrImage 开)、但不自动打开(qrOpen 关);qrAnsi 关、样式 half', () => {
202
+ assert.equal(DEFAULT_CONFIG.qrImage, true, '文本码在 DSH 命令卡里扫不出来(行高把它切条),默认必须出图片');
203
+ assert.equal(DEFAULT_CONFIG.qrOpen, false, '默认调用系统打开图片:配对动作被搬到了宿主外面');
204
+ assert.equal(DEFAULT_CONFIG.qrAnsi, false, '默认套 ANSI:宿主按纯文本渲染时会把转义序列打成乱码');
205
+ assert.equal(DEFAULT_CONFIG.qrStyle, 'half', '默认文本样式仍是半块(供关了图片时当退路)');
206
+ });
207
+ test('qr* 三个开关的环境变量只认 1/0/true/false,其余值退回上层值(不猜)', () => {
208
+ assert.equal(readConfig(undefined, { DRC_QR_IMAGE: '1' }).qrImage, true, 'DRC_QR_IMAGE=1 要能把图片打开(默认已是开,这里要幂等)');
209
+ assert.equal(readConfig({ qrImage: true }, { DRC_QR_IMAGE: '0' }).qrImage, false, '=0 要能关掉 patch 里开的值');
210
+ assert.equal(readConfig({ qrAnsi: true }, { DRC_QR_ANSI: 'yes' }).qrAnsi, true, '无法识别的值必须退回上层值,而不是静默变成关');
211
+ assert.equal(readConfig({ qrStyle: 'block' }, { DRC_QR_STYLE: 'fancy' }).qrStyle, 'block', '非法样式在 readConfig 阶段不猜');
212
+ const injected = base({ qrStyle: 'fancy' });
213
+ const problems = validateConfig(injected);
214
+ assert.equal(injected.qrStyle, 'half', '非法 qrStyle 要在 validateConfig 里夹回并留痕');
215
+ assert.ok(problems.some((problem) => problem.field === 'qrStyle'), '夹回必须 warn');
216
+ });
217
+ test('qrOpen 而 qrImage 关着是无效组合:要 warn(关了图片就没有文件可开)', () => {
218
+ const problems = validateConfig(base({ qrImage: false, qrOpen: true }));
219
+ const warn = problems.find((problem) => problem.field === 'qrOpen');
220
+ assert.ok(warn && warn.level === 'warn', `这个组合什么都没打开,却没人告诉运维:${JSON.stringify(problems.map((problem) => problem.field))}`);
221
+ assert.equal(validateConfig(base({ qrImage: true, qrOpen: true })).some((problem) => problem.field === 'qrOpen'), false, '两个都开是合法组合,不该刷屏');
222
+ });
223
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../../tests/config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,175 @@
1
+ /**
2
+ * guard.test — `platform/guard.ts`:订阅的唯一入口。
3
+ *
4
+ * 守的是"插件绝不许崩宿主"这条总则下最贵的一起事故(docs/DESIGN.md §7 阶段 C 第 ② 条):
5
+ * 旧插件为了可观测性订阅了 `agent/request`(一条 **waterfall**),监听器返回 undefined
6
+ * 就把整条链的结果冲掉了,于是宿主内核进程里**每一个 turn 都崩**,
7
+ * 报 `Cannot read properties of undefined (reading 'provider')`,连桌面 UI 人工发消息也一起崩
8
+ * (取证 docs/legacy-spec/host-plugin-cordis.md §2.1、HANDOFF.md §4.4,
9
+ * 崩溃点 `@deepseek-ai/dsh-agent-loop/lib/index.js:685-691`)。
10
+ *
11
+ * 因此本文件有两条"自校验":白名单与 waterfall 清单的交集必须为空,
12
+ * 以及旧黑名单里那两个真出过事的名字必须仍在拒绝列表里。
13
+ * 白名单的意义在于"宿主代际升级后不会静默失效"(旧实现是 13 项黑名单,缺一个就漏一个)。
14
+ */
15
+ import { test } from 'node:test';
16
+ import assert from 'node:assert/strict';
17
+ import { SUBSCRIBABLE_EVENTS, WATERFALL_EVENTS, WATERFALL_PARTICIPANTS, canParticipate, canSubscribe, guardedSubscribe, } from '../src/platform/guard.js';
18
+ /** 一个最小的事件注册口替身:只记录"有没有把订阅透下去"。 */
19
+ function makeBus(options = {}) {
20
+ const registered = [];
21
+ const warns = [];
22
+ const offed = [];
23
+ const bus = {
24
+ registered,
25
+ warns,
26
+ on(name, listener) {
27
+ if (options.throwOn === name)
28
+ throw new Error(`宿主不认识事件 ${name}`);
29
+ registered.push(name);
30
+ if (options.disposer === false)
31
+ return undefined;
32
+ return () => {
33
+ offed.push(name);
34
+ };
35
+ },
36
+ off(name, _listener) {
37
+ offed.push(name);
38
+ },
39
+ };
40
+ bus.offed = offed;
41
+ return bus;
42
+ }
43
+ const warnOf = (bus) => (message, fields) => {
44
+ bus.warns.push({ message, ...(fields === undefined ? {} : { fields }) });
45
+ };
46
+ test('白名单非空且含 session/event:少一个名字就少一类推送能力', () => {
47
+ assert.ok(SUBSCRIBABLE_EVENTS.length >= 1, '白名单是空的:那就等于"什么都订阅不到",手机上永远没有输出');
48
+ for (const name of ['session/event', 'session/created', 'agent/status', 'agent/error']) {
49
+ assert.equal(canSubscribe(name), true, `${name} 必须在白名单里(emit-mode,有取证位置):缺了它手机少一类事件`);
50
+ assert.equal(SUBSCRIBABLE_EVENTS.includes(name), true, `${name} 要能在名单本体里查到`);
51
+ }
52
+ });
53
+ test('白名单外的名字一律拒订,包括三条 waterfall 通道(agent/request、approval/request、llm/stream)', () => {
54
+ for (const name of ['agent/request', 'approval/request', 'llm/stream', 'agent/pre-step', 'system-prompt/assemble']) {
55
+ assert.equal(canSubscribe(name), false, `${name} 是 waterfall:注册即参与,处理器返回 undefined 会把这条链的结果冲掉`);
56
+ const bus = makeBus();
57
+ const unsubscribe = guardedSubscribe((n, l) => bus.on(n, l), name, (() => { }), warnOf(bus));
58
+ assert.equal(bus.registered.length, 0, `${name} 被订阅了 → 宿主进程里每一个 turn 都会崩(旧事故形状)`);
59
+ assert.equal(typeof unsubscribe, 'function', '拒订也必须返回一个可调用的退订函数:调用方不该为此判空');
60
+ assert.doesNotThrow(() => unsubscribe(), '空退订函数不许抛');
61
+ assert.equal(bus.warns.length, 1, '拒订必须留下恰好一条告警,否则"为什么收不到"没有答案');
62
+ }
63
+ });
64
+ test('拼错的、空的、带命名空间前缀的名字一律拒订:白名单不是前缀匹配', () => {
65
+ for (const name of ['', ' ', 'session/events', 'Session/event', 'session/event2', 'session/*', 'any-name']) {
66
+ assert.equal(canSubscribe(name), false, `${JSON.stringify(name)} 不该被放行:黑名单时代的"看起来不像危险名字"就是漏口的来源`);
67
+ const bus = makeBus();
68
+ guardedSubscribe((n, l) => bus.on(n, l), name, (() => { }), warnOf(bus));
69
+ assert.equal(bus.registered.length, 0, `${JSON.stringify(name)} 竟然被订阅了`);
70
+ }
71
+ });
72
+ test('自校验一:白名单与 WATERFALL_EVENTS 的交集必须为空(防止有人顺手往白名单里加一个 waterfall 名字)', () => {
73
+ const overlap = SUBSCRIBABLE_EVENTS.filter((name) => WATERFALL_EVENTS.includes(name));
74
+ assert.deepEqual(overlap, [], `白名单里出现了 waterfall 名字:${JSON.stringify(overlap)} → 这条测试就是为了一次都不让它发生`);
75
+ });
76
+ test('自校验二:waterfall 清单必须含旧黑名单里真出过事的两个名字,且比旧清单更宽', () => {
77
+ for (const name of ['agent/request', 'agent/pre-step']) {
78
+ assert.equal(WATERFALL_EVENTS.includes(name), true, `${name} 必须留在 waterfall 清单里:它就是那次"每个 turn 都崩"的元凶`);
79
+ }
80
+ assert.ok(WATERFALL_EVENTS.length >= 13, `waterfall 清单只有 ${WATERFALL_EVENTS.length} 项,比旧实现的 13 项黑名单还窄`);
81
+ for (const name of [
82
+ 'approval/request',
83
+ 'user-questions/request',
84
+ 'llm/stream',
85
+ 'system-prompt/assemble',
86
+ 'internal/config',
87
+ ]) {
88
+ assert.equal(WATERFALL_EVENTS.includes(name), true, `${name} 是宿主权威表里的 @mode waterfall,必须列出(旧黑名单一项都没有,这就是它静默失效的原因)`);
89
+ }
90
+ assert.equal(WATERFALL_EVENTS.filter((name, index) => WATERFALL_EVENTS.indexOf(name) !== index).length, 0, '清单里有重复名字:说明这份表没有对着宿主权威表核过');
91
+ });
92
+ test('guardedSubscribe 放行白名单内的订阅,并把宿主给的退订函数原样接上', () => {
93
+ const bus = makeBus();
94
+ const seen = [];
95
+ const listener = ((payload) => {
96
+ seen.push(payload);
97
+ });
98
+ const unsubscribe = guardedSubscribe((n, l) => bus.on(n, l), 'session/event', listener, warnOf(bus));
99
+ assert.deepEqual(bus.registered, ['session/event'], '白名单内的订阅没有透下去 → 手机收不到任何会话事件');
100
+ assert.equal(bus.warns.length, 0, '正常放行不该告警');
101
+ unsubscribe();
102
+ assert.equal(seen.length, 0, '夹具自检:监听器本身没被调用过');
103
+ });
104
+ test('宿主 on() 抛异常时必须咽下去:少订阅一个事件只是少一类推送,不该让插件整个不工作', () => {
105
+ const bus = makeBus({ throwOn: 'session/event' });
106
+ let unsubscribe;
107
+ assert.doesNotThrow(() => {
108
+ unsubscribe = guardedSubscribe((n, l) => bus.on(n, l), 'session/event', (() => { }), warnOf(bus));
109
+ }, '订阅失败把异常抛了出去 → apply() 的红线破了,用户的 DSH 进程起不来');
110
+ assert.equal(typeof unsubscribe, 'function', '抛异常后也必须返回一个可用的退订函数');
111
+ assert.doesNotThrow(() => unsubscribe?.(), '退订函数不许二次抛');
112
+ assert.equal(bus.warns.length, 1, '订阅失败要留一条告警:否则"为什么没有事件"没有答案');
113
+ assert.equal(String(bus.warns[0]?.fields?.name), 'session/event', '告警要带上事件名,便于对着宿主权威表排查');
114
+ });
115
+ test('没有退订函数的宿主代际要靠 off() 退订:两条路都要尽力而为且不许抛', () => {
116
+ const bus = makeBus({ disposer: false });
117
+ const listener = (() => { });
118
+ // 这一代宿主不给退订函数,只能靠 off():把 off 挂到可调用对象上才是 guardedSubscribe 认的形状。
119
+ const onWithOff = Object.assign((n, l) => bus.on(n, l), {
120
+ off: (n, l) => bus.off?.(n, l),
121
+ });
122
+ const unsubscribe = guardedSubscribe(onWithOff, 'agent/status', listener, warnOf(bus));
123
+ assert.deepEqual(bus.registered, ['agent/status'], '白名单内必须放行');
124
+ assert.doesNotThrow(() => unsubscribe(), '靠 off() 退订这条路不许抛');
125
+ const offed = bus.offed;
126
+ assert.deepEqual(offed, ['agent/status'], `没有调用 off() 退订(offed=${JSON.stringify(offed)}):订阅泄漏会让插件重启后收到双份事件`);
127
+ // 反面对照:调用方只递一个箭头包装(不带 off)时,退订必须静默无害——
128
+ // 但这条路径等于"订阅撤不掉",是 carrier 侧的接线要求(见回报 BUG-4)。
129
+ const bare = makeBus({ disposer: false });
130
+ const bareOff = guardedSubscribe((n, l) => bare.on(n, l), 'agent/error', listener, warnOf(bare));
131
+ assert.deepEqual(bare.registered, ['agent/error'], '夹具自检:订阅本身要放行');
132
+ assert.doesNotThrow(() => bareOff(), '不带 off 的宿主形状退订时不许抛');
133
+ assert.deepEqual(bare.offed, [], '夹具自检:这条路径确实什么都做不了(不是偷偷调了 off)');
134
+ // off 自己抛错也必须咽下去。
135
+ const hostile = makeBus({ disposer: false });
136
+ const hostileOn = Object.assign((n, l) => hostile.on(n, l), {
137
+ off: () => {
138
+ throw new Error('这一代 cordis 的 off 会抛');
139
+ },
140
+ });
141
+ const off2 = guardedSubscribe(hostileOn, 'agent/error', listener, warnOf(hostile));
142
+ assert.doesNotThrow(() => off2(), '退订失败抛了出去:进程要走了,退订失败无所谓');
143
+ });
144
+ test('参与名单是极窄的显式清单:approval/request 允许以参与者身份注册,其它 waterfall 一律不许', () => {
145
+ assert.deepEqual([...WATERFALL_PARTICIPANTS], ['approval/request'], '参与名单只能有这一条:审批要想到达手机只能参与这条 waterfall');
146
+ assert.equal(canParticipate('approval/request'), true, 'approval/request 必须允许参与:它是 emit-mode 替代通道不存在的情况下唯一的路');
147
+ for (const name of [
148
+ 'agent/request',
149
+ 'llm/stream',
150
+ 'user-questions/request',
151
+ 'session/event',
152
+ 'system-prompt/assemble',
153
+ ]) {
154
+ assert.equal(canParticipate(name), false, `${name} 不许被"参与":那是把观察代码写成链上的一环`);
155
+ }
156
+ assert.equal(canParticipate(''), false, '空名字不许放行');
157
+ });
158
+ test('canSubscribe 与 canParticipate 是两件不同的事:白名单不含审批,但审批必须能参与', () => {
159
+ assert.equal(canSubscribe('approval/request'), false, '观测订阅仍然禁止(否则 undefined 会冲掉 outcome)');
160
+ assert.equal(canParticipate('approval/request'), true, '但参与者通道必须打开,否则手机永远收不到审批卡:审批只会在桌面 UI 里被决定');
161
+ assert.equal(canSubscribe('session/event'), true, 'session/event 是 emit-mode 观测通道,必须在白名单里');
162
+ assert.equal(canParticipate('session/event'), false, '观测事件不许被当成参与者:approval 的 outcome 语义不适用于它');
163
+ });
164
+ test('拒订的告警要如实标出"这是 waterfall":让排错的人一眼看到为什么不给订', () => {
165
+ const bus = makeBus();
166
+ guardedSubscribe((n, l) => bus.on(n, l), 'agent/request', (() => { }), warnOf(bus));
167
+ assert.equal(bus.warns.length, 1, '没有留下告警');
168
+ assert.equal(bus.warns[0]?.message, 'refusing to subscribe', `告警文案是 ${String(bus.warns[0]?.message)}`);
169
+ assert.equal(bus.warns[0]?.fields?.waterfall, true, 'fields.waterfall 必须为 true:这是"为什么拒订"的直接答案');
170
+ assert.equal(bus.warns[0]?.fields?.name, 'agent/request', '要带上被拒的事件名');
171
+ const other = makeBus();
172
+ guardedSubscribe((n, l) => other.on(n, l), 'totally/unknown', (() => { }), warnOf(other));
173
+ assert.equal(other.warns[0]?.fields?.waterfall, false, '不在 waterfall 清单里的名字要如实报 false:否则会误以为已知风险');
174
+ });
175
+ //# sourceMappingURL=guard.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guard.test.js","sourceRoot":"","sources":["../../tests/guard.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};