dsh-config-manager 0.1.69 → 0.1.71

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 (719) hide show
  1. package/README.md +59 -18
  2. package/README.zh-CN.md +29 -9
  3. package/lib/adapters/credentials.js +6 -3
  4. package/lib/adapters/file-collection.d.ts +21 -1
  5. package/lib/adapters/file-collection.js +32 -8
  6. package/lib/adapters/mcp.d.ts +12 -2
  7. package/lib/adapters/mcp.js +30 -13
  8. package/lib/adapters/plugin-files.d.ts +8 -0
  9. package/lib/adapters/plugin-files.js +36 -4
  10. package/lib/adapters/plugins.d.ts +28 -2
  11. package/lib/adapters/plugins.js +204 -55
  12. package/lib/adapters/prompts.d.ts +12 -2
  13. package/lib/adapters/prompts.js +34 -19
  14. package/lib/adapters/providers.js +5 -4
  15. package/lib/adapters/self.d.ts +20 -0
  16. package/lib/adapters/self.js +73 -0
  17. package/lib/adapters/sessions.d.ts +8 -0
  18. package/lib/adapters/sessions.js +47 -1
  19. package/lib/adapters/settings.d.ts +21 -3
  20. package/lib/adapters/settings.js +44 -7
  21. package/lib/adapters/skills.d.ts +36 -4
  22. package/lib/adapters/skills.js +222 -3
  23. package/lib/adapters/test-helpers.d.ts +31 -2
  24. package/lib/adapters/test-helpers.js +42 -5
  25. package/lib/adapters/workspaces.js +97 -5
  26. package/lib/cli/actions.d.ts +50 -3
  27. package/lib/cli/actions.js +103 -11
  28. package/lib/cli/help.d.ts +65 -0
  29. package/lib/cli/help.js +517 -0
  30. package/lib/cli/import-source.d.ts +32 -0
  31. package/lib/cli/import-source.js +178 -0
  32. package/lib/cli/index.d.ts +18 -2
  33. package/lib/cli/index.js +118 -90
  34. package/lib/cli/web/client-script.d.ts +8 -0
  35. package/lib/cli/web/client-script.js +11 -0
  36. package/lib/cli/web/http.d.ts +5 -0
  37. package/lib/cli/web/http.js +2 -1
  38. package/lib/cli/web/page.d.ts +16 -6
  39. package/lib/cli/web/page.js +102 -25
  40. package/lib/cli/web/routes.js +24 -5
  41. package/lib/cli/web/server.js +23 -2
  42. package/lib/client.d.ts +1019 -36
  43. package/lib/client.js +5522 -1564
  44. package/lib/core/analyzer.d.ts +15 -1
  45. package/lib/core/analyzer.js +114 -13
  46. package/lib/core/backup-plan.js +17 -1
  47. package/lib/core/backup.d.ts +8 -0
  48. package/lib/core/backup.js +28 -6
  49. package/lib/core/cache-cleaner.d.ts +10 -0
  50. package/lib/core/cache-cleaner.js +63 -22
  51. package/lib/core/checkpoint.d.ts +362 -0
  52. package/lib/core/checkpoint.js +1309 -0
  53. package/lib/core/journal.d.ts +37 -0
  54. package/lib/core/journal.js +69 -0
  55. package/lib/core/messages.d.ts +13 -0
  56. package/lib/core/messages.js +26 -0
  57. package/lib/core/model-tools.js +3 -0
  58. package/lib/core/patch-layers.d.ts +83 -0
  59. package/lib/core/patch-layers.js +110 -0
  60. package/lib/core/restore.d.ts +21 -0
  61. package/lib/core/restore.js +37 -3
  62. package/lib/core/rollback.js +6 -3
  63. package/lib/core/self-update.d.ts +52 -0
  64. package/lib/core/self-update.js +70 -0
  65. package/lib/core/session-health.d.ts +29 -5
  66. package/lib/core/session-health.js +28 -3
  67. package/lib/core/types.d.ts +126 -0
  68. package/lib/foreign/antigravity.d.ts +28 -0
  69. package/lib/foreign/antigravity.js +157 -0
  70. package/lib/foreign/bundle.d.ts +25 -0
  71. package/lib/foreign/bundle.js +79 -0
  72. package/lib/foreign/chatgpt.d.ts +6 -0
  73. package/lib/foreign/chatgpt.js +25 -0
  74. package/lib/foreign/claude-code.d.ts +3 -0
  75. package/lib/foreign/claude-code.js +85 -0
  76. package/lib/foreign/claude-sessions.d.ts +108 -0
  77. package/lib/foreign/claude-sessions.js +299 -0
  78. package/lib/foreign/cline.d.ts +6 -0
  79. package/lib/foreign/cline.js +27 -0
  80. package/lib/foreign/codex.d.ts +83 -0
  81. package/lib/foreign/codex.js +348 -0
  82. package/lib/foreign/continue.d.ts +9 -0
  83. package/lib/foreign/continue.js +65 -0
  84. package/lib/foreign/copilot.d.ts +39 -0
  85. package/lib/foreign/copilot.js +95 -0
  86. package/lib/foreign/crush.d.ts +11 -0
  87. package/lib/foreign/crush.js +57 -0
  88. package/lib/foreign/cursor.d.ts +87 -0
  89. package/lib/foreign/cursor.js +171 -0
  90. package/lib/foreign/dsh.d.ts +30 -0
  91. package/lib/foreign/dsh.js +101 -0
  92. package/lib/foreign/dsh4.d.ts +3 -0
  93. package/lib/foreign/dsh4.js +13 -0
  94. package/lib/foreign/gemini.d.ts +7 -0
  95. package/lib/foreign/gemini.js +44 -0
  96. package/lib/foreign/goose.d.ts +11 -0
  97. package/lib/foreign/goose.js +53 -0
  98. package/lib/foreign/grokbuild.d.ts +8 -0
  99. package/lib/foreign/grokbuild.js +25 -0
  100. package/lib/foreign/hermes.d.ts +2 -0
  101. package/lib/foreign/hermes.js +78 -0
  102. package/lib/foreign/kernel.d.ts +132 -0
  103. package/lib/foreign/kernel.js +569 -0
  104. package/lib/foreign/kilocode.d.ts +11 -0
  105. package/lib/foreign/kilocode.js +51 -0
  106. package/lib/foreign/kimi.d.ts +7 -0
  107. package/lib/foreign/kimi.js +47 -0
  108. package/lib/foreign/mimocode.d.ts +11 -0
  109. package/lib/foreign/mimocode.js +51 -0
  110. package/lib/foreign/openclaw.d.ts +7 -0
  111. package/lib/foreign/openclaw.js +46 -0
  112. package/lib/foreign/opencode.d.ts +11 -0
  113. package/lib/foreign/opencode.js +52 -0
  114. package/lib/foreign/pi.d.ts +7 -0
  115. package/lib/foreign/pi.js +48 -0
  116. package/lib/foreign/platform-paths.d.ts +66 -0
  117. package/lib/foreign/platform-paths.js +141 -0
  118. package/lib/foreign/qoder.d.ts +10 -0
  119. package/lib/foreign/qoder.js +49 -0
  120. package/lib/foreign/qwen.d.ts +7 -0
  121. package/lib/foreign/qwen.js +45 -0
  122. package/lib/foreign/read-antigravity.d.ts +57 -0
  123. package/lib/foreign/read-antigravity.js +504 -0
  124. package/lib/foreign/read-chatgpt.d.ts +20 -0
  125. package/lib/foreign/read-chatgpt.js +509 -0
  126. package/lib/foreign/read-claude-code.d.ts +24 -0
  127. package/lib/foreign/read-claude-code.js +234 -0
  128. package/lib/foreign/read-cline.d.ts +53 -0
  129. package/lib/foreign/read-cline.js +479 -0
  130. package/lib/foreign/read-codex.d.ts +75 -0
  131. package/lib/foreign/read-codex.js +842 -0
  132. package/lib/foreign/read-continue.d.ts +87 -0
  133. package/lib/foreign/read-continue.js +489 -0
  134. package/lib/foreign/read-copilot.d.ts +42 -0
  135. package/lib/foreign/read-copilot.js +267 -0
  136. package/lib/foreign/read-crush.d.ts +54 -0
  137. package/lib/foreign/read-crush.js +478 -0
  138. package/lib/foreign/read-cursor.d.ts +141 -0
  139. package/lib/foreign/read-cursor.js +705 -0
  140. package/lib/foreign/read-dsh.d.ts +43 -0
  141. package/lib/foreign/read-dsh.js +205 -0
  142. package/lib/foreign/read-dsh4.d.ts +14 -0
  143. package/lib/foreign/read-dsh4.js +27 -0
  144. package/lib/foreign/read-gemini.d.ts +55 -0
  145. package/lib/foreign/read-gemini.js +273 -0
  146. package/lib/foreign/read-goose.d.ts +25 -0
  147. package/lib/foreign/read-goose.js +322 -0
  148. package/lib/foreign/read-grokbuild.d.ts +34 -0
  149. package/lib/foreign/read-grokbuild.js +455 -0
  150. package/lib/foreign/read-hermes.d.ts +74 -0
  151. package/lib/foreign/read-hermes.js +690 -0
  152. package/lib/foreign/read-kilocode.d.ts +17 -0
  153. package/lib/foreign/read-kilocode.js +46 -0
  154. package/lib/foreign/read-kimi.d.ts +63 -0
  155. package/lib/foreign/read-kimi.js +719 -0
  156. package/lib/foreign/read-mimocode.d.ts +12 -0
  157. package/lib/foreign/read-mimocode.js +56 -0
  158. package/lib/foreign/read-openclaw.d.ts +51 -0
  159. package/lib/foreign/read-openclaw.js +445 -0
  160. package/lib/foreign/read-opencode.d.ts +150 -0
  161. package/lib/foreign/read-opencode.js +896 -0
  162. package/lib/foreign/read-pi.d.ts +56 -0
  163. package/lib/foreign/read-pi.js +411 -0
  164. package/lib/foreign/read-qoder.d.ts +54 -0
  165. package/lib/foreign/read-qoder.js +317 -0
  166. package/lib/foreign/read-qwen.d.ts +40 -0
  167. package/lib/foreign/read-qwen.js +326 -0
  168. package/lib/foreign/read-reasonix.d.ts +66 -0
  169. package/lib/foreign/read-reasonix.js +537 -0
  170. package/lib/foreign/read-teleagent.d.ts +15 -0
  171. package/lib/foreign/read-teleagent.js +68 -0
  172. package/lib/foreign/read-trae.d.ts +67 -0
  173. package/lib/foreign/read-trae.js +492 -0
  174. package/lib/foreign/read-vibe.d.ts +28 -0
  175. package/lib/foreign/read-vibe.js +364 -0
  176. package/lib/foreign/read-workbuddy.d.ts +49 -0
  177. package/lib/foreign/read-workbuddy.js +346 -0
  178. package/lib/foreign/read-zcode.d.ts +12 -0
  179. package/lib/foreign/read-zcode.js +38 -0
  180. package/lib/foreign/read-zed.d.ts +30 -0
  181. package/lib/foreign/read-zed.js +473 -0
  182. package/lib/foreign/reasonix.d.ts +6 -0
  183. package/lib/foreign/reasonix.js +20 -0
  184. package/lib/foreign/registry.d.ts +196 -0
  185. package/lib/foreign/registry.js +573 -0
  186. package/lib/foreign/session-ir.d.ts +186 -0
  187. package/lib/foreign/session-ir.js +427 -0
  188. package/lib/foreign/session-read.d.ts +102 -0
  189. package/lib/foreign/session-read.js +254 -0
  190. package/lib/foreign/session-source.d.ts +216 -0
  191. package/lib/foreign/session-source.js +453 -0
  192. package/lib/foreign/source-modules.d.ts +45 -0
  193. package/lib/foreign/source-modules.js +52 -0
  194. package/lib/foreign/sqlite.d.ts +61 -0
  195. package/lib/foreign/sqlite.js +404 -0
  196. package/lib/foreign/teleagent.d.ts +11 -0
  197. package/lib/foreign/teleagent.js +53 -0
  198. package/lib/foreign/trae.d.ts +6 -0
  199. package/lib/foreign/trae.js +20 -0
  200. package/lib/foreign/truth-table.d.ts +62 -0
  201. package/lib/foreign/truth-table.js +333 -0
  202. package/lib/foreign/types.d.ts +272 -0
  203. package/lib/foreign/types.js +2 -0
  204. package/lib/foreign/vibe.d.ts +6 -0
  205. package/lib/foreign/vibe.js +23 -0
  206. package/lib/foreign/workbuddy.d.ts +7 -0
  207. package/lib/foreign/workbuddy.js +45 -0
  208. package/lib/foreign/zcode.d.ts +11 -0
  209. package/lib/foreign/zcode.js +51 -0
  210. package/lib/foreign/zed.d.ts +11 -0
  211. package/lib/foreign/zed.js +53 -0
  212. package/lib/index.d.ts +154 -13
  213. package/lib/index.js +661 -84
  214. package/lib/market/github-repos.d.ts +28 -0
  215. package/lib/market/github-repos.js +43 -1
  216. package/lib/market/types.d.ts +1 -3
  217. package/lib/profiles/dsh-profile-manager.d.ts +54 -2
  218. package/lib/profiles/dsh-profile-manager.js +182 -11
  219. package/lib/profiles/dsh-profile-shared.d.ts +47 -0
  220. package/lib/profiles/dsh-profile-shared.js +15 -1
  221. package/lib/profiles/index.d.ts +1 -1
  222. package/lib/profiles/index.js +1 -1
  223. package/lib/routes/consult.js +51 -5
  224. package/lib/routes/foreign-context.d.ts +43 -0
  225. package/lib/routes/foreign-context.js +65 -0
  226. package/lib/routes/foreign.d.ts +11 -0
  227. package/lib/routes/foreign.js +208 -0
  228. package/lib/routes/import.d.ts +8 -0
  229. package/lib/routes/import.js +22 -1
  230. package/lib/routes/index.d.ts +2 -2
  231. package/lib/routes/index.js +4 -0
  232. package/lib/routes/kit.d.ts +4 -3
  233. package/lib/routes/market.js +0 -1
  234. package/lib/routes/prefs.d.ts +2 -1
  235. package/lib/routes/prefs.js +14 -2
  236. package/lib/routes/recovery.js +363 -3
  237. package/lib/routes/session-export.d.ts +117 -0
  238. package/lib/routes/session-export.js +270 -0
  239. package/lib/routes/snapshots.js +12 -2
  240. package/lib/routes/sync.js +82 -10
  241. package/lib/security/redaction.d.ts +14 -0
  242. package/lib/security/redaction.js +24 -9
  243. package/lib/security/secret-scanner.d.ts +27 -0
  244. package/lib/security/secret-scanner.js +54 -1
  245. package/lib/sync/autosync-scheduler.d.ts +13 -1
  246. package/lib/sync/autosync-scheduler.js +49 -26
  247. package/lib/sync/backup-files.js +6 -0
  248. package/lib/sync/gist/gist-transport.d.ts +91 -0
  249. package/lib/sync/gist/gist-transport.js +406 -0
  250. package/lib/sync/gist/messages.d.ts +22 -0
  251. package/lib/sync/gist/messages.js +46 -0
  252. package/lib/sync/s3/messages.d.ts +53 -0
  253. package/lib/sync/s3/messages.js +83 -0
  254. package/lib/sync/s3/s3-providers.d.ts +78 -0
  255. package/lib/sync/s3/s3-providers.js +156 -0
  256. package/lib/sync/s3/s3-transport.d.ts +126 -0
  257. package/lib/sync/s3/s3-transport.js +551 -0
  258. package/lib/sync/s3/sigv4.d.ts +107 -0
  259. package/lib/sync/s3/sigv4.js +196 -0
  260. package/lib/sync/sync-config.d.ts +250 -4
  261. package/lib/sync/sync-config.js +658 -57
  262. package/lib/sync/sync-engine.d.ts +1 -0
  263. package/lib/sync/sync-engine.js +4 -2
  264. package/lib/sync/sync-history.d.ts +1 -1
  265. package/lib/sync/webdav/webdav-transport.d.ts +31 -3
  266. package/lib/sync/webdav/webdav-transport.js +102 -18
  267. package/lib/ui/commands.d.ts +4 -3
  268. package/lib/ui/commands.js +41 -9
  269. package/lib/ui/disk-usage-view.d.ts +44 -0
  270. package/lib/ui/disk-usage-view.js +55 -1
  271. package/lib/ui/dsh-profiles-view.d.ts +53 -0
  272. package/lib/ui/dsh-profiles-view.js +56 -0
  273. package/lib/ui/foreign-view.d.ts +162 -0
  274. package/lib/ui/foreign-view.js +225 -0
  275. package/lib/ui/i18n.d.ts +77 -0
  276. package/lib/ui/i18n.js +164 -0
  277. package/lib/ui/session-inventory-view.d.ts +106 -0
  278. package/lib/ui/session-inventory-view.js +58 -0
  279. package/lib/ui/session-layout-view.d.ts +265 -0
  280. package/lib/ui/session-layout-view.js +271 -0
  281. package/lib/ui/sync-repository-picker.d.ts +98 -0
  282. package/lib/ui/sync-repository-picker.js +145 -0
  283. package/lib/ui/sync-settings-view.d.ts +143 -4
  284. package/lib/ui/sync-settings-view.js +231 -7
  285. package/lib/ui/types.d.ts +47 -0
  286. package/lib/utils/asar-read.d.ts +113 -0
  287. package/lib/utils/asar-read.js +523 -0
  288. package/lib/utils/env-lock.js +7 -2
  289. package/lib/utils/patch-yaml.d.ts +25 -0
  290. package/lib/utils/patch-yaml.js +62 -0
  291. package/lib/utils/paths.d.ts +10 -1
  292. package/lib/utils/paths.js +24 -3
  293. package/lib/utils/recursive-walk.js +12 -13
  294. package/lib/utils/session-format.d.ts +35 -2
  295. package/lib/utils/session-format.js +131 -5
  296. package/lib/utils/session-health-scan.d.ts +7 -1
  297. package/lib/utils/session-health-scan.js +369 -12
  298. package/lib/utils/session-layout-repair-service.d.ts +98 -0
  299. package/lib/utils/session-layout-repair-service.js +457 -0
  300. package/lib/utils/session-log-repair.d.ts +27 -0
  301. package/lib/utils/session-log-repair.js +233 -62
  302. package/lib/utils/session-repair-service.d.ts +32 -3
  303. package/lib/utils/session-repair-service.js +148 -9
  304. package/lib/utils/session-row-facts.d.ts +73 -0
  305. package/lib/utils/session-row-facts.js +182 -0
  306. package/lib/utils/session-verify.d.ts +118 -0
  307. package/lib/utils/session-verify.js +705 -0
  308. package/lib/utils/shared-constants.d.ts +59 -0
  309. package/lib/utils/shared-constants.js +71 -0
  310. package/package.json +1 -1
  311. package/src/adapters/credentials.test.ts +27 -0
  312. package/src/adapters/credentials.ts +6 -3
  313. package/src/adapters/file-collection.ts +33 -8
  314. package/src/adapters/files.test.ts +222 -0
  315. package/src/adapters/mcp.test.ts +43 -0
  316. package/src/adapters/mcp.ts +37 -14
  317. package/src/adapters/plugin-files.ts +34 -4
  318. package/src/adapters/plugins.test.ts +241 -0
  319. package/src/adapters/plugins.ts +201 -53
  320. package/src/adapters/prompts.test.ts +49 -0
  321. package/src/adapters/prompts.ts +41 -20
  322. package/src/adapters/providers.test.ts +16 -0
  323. package/src/adapters/providers.ts +4 -3
  324. package/src/adapters/self.test.ts +77 -0
  325. package/src/adapters/self.ts +75 -0
  326. package/src/adapters/sessions.test.ts +77 -0
  327. package/src/adapters/sessions.ts +47 -1
  328. package/src/adapters/settings.test.ts +78 -0
  329. package/src/adapters/settings.ts +42 -6
  330. package/src/adapters/skills.ts +221 -3
  331. package/src/adapters/test-helpers.ts +43 -5
  332. package/src/adapters/workspaces-dup-id.test.ts +144 -0
  333. package/src/adapters/workspaces.test.ts +10 -0
  334. package/src/adapters/workspaces.ts +97 -5
  335. package/src/cli/actions.test.ts +117 -2
  336. package/src/cli/actions.ts +132 -12
  337. package/src/cli/help.ts +570 -0
  338. package/src/cli/import-source.test.ts +193 -0
  339. package/src/cli/import-source.ts +229 -0
  340. package/src/cli/index.ts +129 -94
  341. package/src/cli/web/client-script.ts +12 -0
  342. package/src/cli/web/console-fixes.test.ts +36 -0
  343. package/src/cli/web/http.ts +7 -1
  344. package/src/cli/web/page.ts +108 -25
  345. package/src/cli/web/routes.ts +23 -4
  346. package/src/cli/web/script-anchor.test.ts +33 -0
  347. package/src/cli/web/server.ts +28 -2
  348. package/src/cli/web/web.test.ts +447 -4
  349. package/src/client/ConfigManagerSection.tsx +118 -63
  350. package/src/client/about/AboutPanel.tsx +42 -3
  351. package/src/client/about/PluginUpdateDialog.tsx +185 -0
  352. package/src/client/about/ReleaseBody.tsx +144 -0
  353. package/src/client/about/ReleaseNotesDialog.tsx +3 -130
  354. package/src/client/about/about-view.ts +7 -5
  355. package/src/client/about/plugin-update-view.test.ts +25 -0
  356. package/src/client/about/plugin-update-view.ts +26 -0
  357. package/src/client/about/release-notes-view.test.ts +33 -0
  358. package/src/client/about/release-notes-view.ts +34 -0
  359. package/src/client/api.ts +145 -1
  360. package/src/client/common/Icon.tsx +3 -0
  361. package/src/client/common/RunsCenter.tsx +8 -2
  362. package/src/client/common/Select.tsx +9 -2
  363. package/src/client/common/info-hint-guard.test.ts +63 -18
  364. package/src/client/common/plan-text-redaction.test.ts +193 -8
  365. package/src/client/common/routes.ts +15 -0
  366. package/src/client/config-manager.module.css +506 -354
  367. package/src/client/css-scale-guard.test.ts +122 -0
  368. package/src/client/css-token-guard.test.ts +82 -0
  369. package/src/client/environment/EnvironmentPanel.tsx +45 -16
  370. package/src/client/{snapshots → environment/maintenance}/DiskUsageCard.tsx +21 -17
  371. package/src/client/environment/profiles-incomplete-ui.test.ts +103 -0
  372. package/src/client/export/ExportView.tsx +9 -2
  373. package/src/client/{snapshots → home}/BackupScheduleCard.tsx +13 -4
  374. package/src/client/home/HomePanel.tsx +69 -157
  375. package/src/client/icon-layer-guard.test.ts +1 -1
  376. package/src/client/import/ForeignImportView.tsx +318 -0
  377. package/src/client/import/ImportWizardView.tsx +3 -1
  378. package/src/client/import/foreign-import-view-guard.test.ts +195 -0
  379. package/src/client/import/import-file-select.test.ts +6 -6
  380. package/src/client/import/import-wizard-steps.tsx +17 -1
  381. package/src/client/import/use-import-wizard-controller.ts +3 -3
  382. package/src/client/index.ts +1 -1
  383. package/src/client/library/LibraryActions.tsx +31 -34
  384. package/src/client/library/LibraryPanel.tsx +4 -1
  385. package/src/client/{snapshots → library}/RestorePlanView.tsx +1 -1
  386. package/src/client/{snapshots → library}/files-layout.test.ts +2 -2
  387. package/src/client/{snapshots → library}/report-dismiss.test.ts +26 -3
  388. package/src/client/library/task-panel-visibility.test.ts +25 -17
  389. package/src/client/locales.ts +278 -36
  390. package/src/client/lucide-icons.d.ts +1 -0
  391. package/src/client/market/MarketPanel.tsx +11 -2
  392. package/src/client/recovery/IncompleteCopiesSection.tsx +132 -0
  393. package/src/client/recovery/RecoveryPanel.tsx +486 -59
  394. package/src/client/recovery/recovery-api.ts +30 -0
  395. package/src/client/recovery/recovery-incomplete-copies.test.ts +151 -0
  396. package/src/client/recovery/recovery-locales.test.ts +88 -0
  397. package/src/client/recovery/recovery-locales.ts +321 -6
  398. package/src/client/recovery/recovery-panel-layout.test.ts +52 -0
  399. package/src/client/recovery/recovery-view.ts +92 -2
  400. package/src/client/run-store.test.ts +201 -89
  401. package/src/client/run-store.ts +114 -143
  402. package/src/client/session-export/session-export-view.test.ts +103 -0
  403. package/src/client/session-export/session-export-view.ts +118 -0
  404. package/src/client/sync/ChannelConfigDialog.tsx +219 -14
  405. package/src/client/sync/SyncChannelEntryCard.tsx +52 -4
  406. package/src/client/sync/SyncLogList.tsx +1 -1
  407. package/src/client/sync/SyncPanel.tsx +166 -102
  408. package/src/client/sync/SyncRepositoryPicker.tsx +216 -0
  409. package/src/client/sync/SyncSettingsView.test.ts +6 -1
  410. package/src/client/sync/history-model.test.ts +94 -57
  411. package/src/client/sync/history-model.ts +44 -25
  412. package/src/client/sync/sync-api.test.ts +56 -0
  413. package/src/client/sync/sync-api.ts +92 -0
  414. package/src/client/sync/sync-locales.ts +162 -2
  415. package/src/client/sync/sync-view.test.ts +3 -8
  416. package/src/client/sync/sync-view.ts +111 -21
  417. package/src/core/analyzer-cache.test.ts +47 -0
  418. package/src/core/analyzer.ts +115 -14
  419. package/src/core/backup-plan.test.ts +35 -0
  420. package/src/core/backup-plan.ts +19 -1
  421. package/src/core/backup.test.ts +37 -1
  422. package/src/core/backup.ts +27 -6
  423. package/src/core/cache-cleaner.test.ts +120 -1
  424. package/src/core/cache-cleaner.ts +59 -22
  425. package/src/core/checkpoint.test.ts +665 -0
  426. package/src/core/checkpoint.ts +1614 -0
  427. package/src/core/credential-plan-consistency.test.ts +93 -0
  428. package/src/core/incident-wiring.test.ts +6 -2
  429. package/src/core/journal.test.ts +70 -0
  430. package/src/core/journal.ts +68 -0
  431. package/src/core/messages.ts +26 -0
  432. package/src/core/model-tools.ts +3 -0
  433. package/src/core/patch-layers.test.ts +94 -0
  434. package/src/core/patch-layers.ts +150 -0
  435. package/src/core/restore.ts +59 -3
  436. package/src/core/rollback.ts +6 -3
  437. package/src/core/section-content-consistency.test.ts +185 -0
  438. package/src/core/self-update.test.ts +110 -0
  439. package/src/core/self-update.ts +118 -0
  440. package/src/core/session-health.test.ts +91 -1
  441. package/src/core/session-health.ts +56 -9
  442. package/src/core/types.ts +127 -1
  443. package/src/foreign/antigravity.test.ts +271 -0
  444. package/src/foreign/antigravity.ts +188 -0
  445. package/src/foreign/bundle.ts +108 -0
  446. package/src/foreign/chatgpt.test.ts +279 -0
  447. package/src/foreign/chatgpt.ts +30 -0
  448. package/src/foreign/claude-code.test.ts +234 -0
  449. package/src/foreign/claude-code.ts +106 -0
  450. package/src/foreign/claude-sessions-bytes.test.ts +227 -0
  451. package/src/foreign/claude-sessions.test.ts +180 -0
  452. package/src/foreign/claude-sessions.ts +461 -0
  453. package/src/foreign/cline.test.ts +274 -0
  454. package/src/foreign/cline.ts +32 -0
  455. package/src/foreign/codex.test.ts +585 -0
  456. package/src/foreign/codex.ts +409 -0
  457. package/src/foreign/continue.test.ts +365 -0
  458. package/src/foreign/continue.ts +73 -0
  459. package/src/foreign/copilot.test.ts +273 -0
  460. package/src/foreign/copilot.ts +133 -0
  461. package/src/foreign/crush.test.ts +234 -0
  462. package/src/foreign/crush.ts +66 -0
  463. package/src/foreign/cursor.test.ts +509 -0
  464. package/src/foreign/cursor.ts +259 -0
  465. package/src/foreign/dsh.test.ts +198 -0
  466. package/src/foreign/dsh.ts +117 -0
  467. package/src/foreign/dsh4.test.ts +127 -0
  468. package/src/foreign/dsh4.ts +15 -0
  469. package/src/foreign/file-budget.test.ts +256 -0
  470. package/src/foreign/fixtures/antigravity/README.md +18 -0
  471. package/src/foreign/fixtures/antigravity/basic/gemini/antigravity/mcp_config.json +13 -0
  472. package/src/foreign/fixtures/antigravity/basic/gemini/config/mcp_config.json +24 -0
  473. package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_config.json +0 -0
  474. package/src/foreign/fixtures/antigravity/empty/gemini/antigravity/mcp_oauth_tokens.json +0 -0
  475. package/src/foreign/fixtures/antigravity/empty/gemini/config/mcp_config.json +0 -0
  476. package/src/foreign/fixtures/codex/README.md +31 -0
  477. package/src/foreign/fixtures/codex/basic/.agents/skills/hello/SKILL.md +6 -0
  478. package/src/foreign/fixtures/codex/basic/.agents/skills/wrapper/nested/SKILL.md +6 -0
  479. package/src/foreign/fixtures/codex/basic/.codex/AGENTS.md +4 -0
  480. package/src/foreign/fixtures/codex/basic/.codex/config.toml +28 -0
  481. package/src/foreign/fixtures/codex/malformed/.codex/config.toml +3 -0
  482. package/src/foreign/fixtures/codex/override/.codex/AGENTS.md +1 -0
  483. package/src/foreign/fixtures/codex/override/.codex/AGENTS.override.md +3 -0
  484. package/src/foreign/fixtures/codex/sessions/.codex/archived_sessions/rollout-2026-10-05T09-00-00-22222222-2222-4222-8222-222222222222.jsonl +2 -0
  485. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T10-00-00-11111111-1111-4111-8111-111111111111.jsonl +15 -0
  486. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-00-00-33333333-3333-4333-8333-333333333333.jsonl +2 -0
  487. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T11-30-00-55555555-5555-4555-8555-555555555555.jsonl +2 -0
  488. package/src/foreign/fixtures/codex/sessions/.codex/sessions/2026/10/06/rollout-2026-10-06T12-00-00-44444444-4444-4444-8444-444444444444.jsonl +2 -0
  489. package/src/foreign/fixtures/codex/sessions/.codex/sessions/notes.jsonl +1 -0
  490. package/src/foreign/fixtures/copilot/README.md +22 -0
  491. package/src/foreign/fixtures/copilot/basic/copilot-instructions.md +3 -0
  492. package/src/foreign/fixtures/copilot/basic/instructions/empty.instructions.md +1 -0
  493. package/src/foreign/fixtures/copilot/basic/instructions/repo.instructions.md +5 -0
  494. package/src/foreign/fixtures/copilot/basic/mcp-config.json +24 -0
  495. package/src/foreign/fixtures/copilot/basic/skills/hello/SKILL.md +6 -0
  496. package/src/foreign/fixtures/copilot/overridden/mcp-config.json +10 -0
  497. package/src/foreign/fixtures/cursor/README.md +17 -0
  498. package/src/foreign/fixtures/cursor/alt/.cursor/mcp.json +5 -0
  499. package/src/foreign/fixtures/cursor/basic/.cursor/mcp.json +18 -0
  500. package/src/foreign/fixtures/cursor/basic/.cursor/rules/agent.mdc +5 -0
  501. package/src/foreign/fixtures/cursor/basic/.cursor/rules/always.mdc +6 -0
  502. package/src/foreign/fixtures/cursor/basic/.cursor/rules/auto.mdc +7 -0
  503. package/src/foreign/fixtures/cursor/basic/.cursor/rules/manual.mdc +3 -0
  504. package/src/foreign/fixtures/cursor/basic/.cursor/skills/.hidden/SKILL.md +6 -0
  505. package/src/foreign/fixtures/cursor/basic/.cursor/skills/hello/SKILL.md +6 -0
  506. package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/SKILL.md +6 -0
  507. package/src/foreign/fixtures/cursor/basic/.cursor/skills/wrapper/nested/notes.md +1 -0
  508. package/src/foreign/fixtures/cursor/basic/.cursorrules +1 -0
  509. package/src/foreign/fixtures/cursor/basic/project/.cursor/mcp.json +9 -0
  510. package/src/foreign/fixtures/cursor/basic/project/.cursor/rules/project-always.mdc +5 -0
  511. package/src/foreign/fixtures/cursor/basic/project/.cursor/skills/hello/SKILL.md +6 -0
  512. package/src/foreign/fixtures/cursor/basic/project/.cursorrules +1 -0
  513. package/src/foreign/fixtures/cursor/empty/.cursor/mcp.json +0 -0
  514. package/src/foreign/fixtures/cursor/malformed/.cursor/mcp.json +3 -0
  515. package/src/foreign/fixtures/cursor/malformed/.cursor/rules/broken.mdc +6 -0
  516. package/src/foreign/fixtures/cursor/shape/.cursor/mcp.json +3 -0
  517. package/src/foreign/fixtures/hermes/README.md +14 -0
  518. package/src/foreign/fixtures/hermes/basic/SOUL.md +3 -0
  519. package/src/foreign/fixtures/hermes/basic/config.yaml +48 -0
  520. package/src/foreign/fixtures/hermes/basic/dotenv.fixture +1 -0
  521. package/src/foreign/fixtures/hermes/basic/memories/MEMORY.md +1 -0
  522. package/src/foreign/fixtures/hermes/basic/memories/USER.md +1 -0
  523. package/src/foreign/fixtures/hermes/basic/skills/se-team-design/SKILL.md +6 -0
  524. package/src/foreign/fixtures/hermes/basic/skills/se-team-design/examples/usage.md +1 -0
  525. package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/SKILL.md +6 -0
  526. package/src/foreign/fixtures/hermes/basic/skills/software-development/dogfood/notes.md +1 -0
  527. package/src/foreign/fixtures/hermes/basic/state.db +1 -0
  528. package/src/foreign/fixtures/hermes/empty/config.yaml +0 -0
  529. package/src/foreign/gemini.test.ts +291 -0
  530. package/src/foreign/gemini.ts +52 -0
  531. package/src/foreign/goose.test.ts +196 -0
  532. package/src/foreign/goose.ts +62 -0
  533. package/src/foreign/grokbuild.test.ts +246 -0
  534. package/src/foreign/grokbuild.ts +30 -0
  535. package/src/foreign/hermes.test.ts +545 -0
  536. package/src/foreign/hermes.ts +86 -0
  537. package/src/foreign/kernel-credentials.test.ts +88 -0
  538. package/src/foreign/kernel-workspaces.test.ts +88 -0
  539. package/src/foreign/kernel.ts +584 -0
  540. package/src/foreign/kilocode.test.ts +186 -0
  541. package/src/foreign/kilocode.ts +60 -0
  542. package/src/foreign/kimi.test.ts +259 -0
  543. package/src/foreign/kimi.ts +55 -0
  544. package/src/foreign/mcp-value-shape.test.ts +286 -0
  545. package/src/foreign/mimocode.test.ts +178 -0
  546. package/src/foreign/mimocode.ts +60 -0
  547. package/src/foreign/openclaw.test.ts +248 -0
  548. package/src/foreign/openclaw.ts +54 -0
  549. package/src/foreign/opencode.test.ts +376 -0
  550. package/src/foreign/opencode.ts +61 -0
  551. package/src/foreign/pi.test.ts +275 -0
  552. package/src/foreign/pi.ts +56 -0
  553. package/src/foreign/platform-paths.ts +156 -0
  554. package/src/foreign/qoder.test.ts +266 -0
  555. package/src/foreign/qoder.ts +57 -0
  556. package/src/foreign/qwen.test.ts +203 -0
  557. package/src/foreign/qwen.ts +53 -0
  558. package/src/foreign/read-antigravity.ts +569 -0
  559. package/src/foreign/read-chatgpt.ts +506 -0
  560. package/src/foreign/read-claude-code.ts +264 -0
  561. package/src/foreign/read-cline.ts +517 -0
  562. package/src/foreign/read-codex.ts +823 -0
  563. package/src/foreign/read-continue.ts +557 -0
  564. package/src/foreign/read-copilot.ts +304 -0
  565. package/src/foreign/read-crush.ts +503 -0
  566. package/src/foreign/read-cursor.ts +778 -0
  567. package/src/foreign/read-dsh.ts +215 -0
  568. package/src/foreign/read-dsh4.ts +35 -0
  569. package/src/foreign/read-gemini.ts +306 -0
  570. package/src/foreign/read-goose.ts +333 -0
  571. package/src/foreign/read-grokbuild.ts +475 -0
  572. package/src/foreign/read-hermes.ts +724 -0
  573. package/src/foreign/read-kilocode.ts +55 -0
  574. package/src/foreign/read-kimi.ts +768 -0
  575. package/src/foreign/read-mimocode.ts +66 -0
  576. package/src/foreign/read-openclaw.ts +505 -0
  577. package/src/foreign/read-opencode.ts +994 -0
  578. package/src/foreign/read-pi.ts +459 -0
  579. package/src/foreign/read-qoder.ts +351 -0
  580. package/src/foreign/read-qwen.ts +342 -0
  581. package/src/foreign/read-reasonix.ts +590 -0
  582. package/src/foreign/read-teleagent.ts +82 -0
  583. package/src/foreign/read-trae.ts +488 -0
  584. package/src/foreign/read-vibe.ts +353 -0
  585. package/src/foreign/read-workbuddy.ts +374 -0
  586. package/src/foreign/read-zcode.ts +48 -0
  587. package/src/foreign/read-zed.ts +479 -0
  588. package/src/foreign/reasonix.test.ts +336 -0
  589. package/src/foreign/reasonix.ts +25 -0
  590. package/src/foreign/registry.test.ts +377 -0
  591. package/src/foreign/registry.ts +750 -0
  592. package/src/foreign/session-ir.test.ts +189 -0
  593. package/src/foreign/session-ir.ts +614 -0
  594. package/src/foreign/session-read.ts +295 -0
  595. package/src/foreign/session-source.ts +625 -0
  596. package/src/foreign/skill-file-caps.test.ts +171 -0
  597. package/src/foreign/source-modules.ts +86 -0
  598. package/src/foreign/source-registry.test.ts +190 -0
  599. package/src/foreign/sqlite.test.ts +313 -0
  600. package/src/foreign/sqlite.ts +446 -0
  601. package/src/foreign/teleagent.test.ts +147 -0
  602. package/src/foreign/teleagent.ts +62 -0
  603. package/src/foreign/trae.test.ts +316 -0
  604. package/src/foreign/trae.ts +25 -0
  605. package/src/foreign/truncation-report.test.ts +238 -0
  606. package/src/foreign/truth-table.ts +453 -0
  607. package/src/foreign/types.ts +312 -0
  608. package/src/foreign/vibe.test.ts +209 -0
  609. package/src/foreign/vibe.ts +28 -0
  610. package/src/foreign/workbuddy.test.ts +207 -0
  611. package/src/foreign/workbuddy.ts +53 -0
  612. package/src/foreign/zcode.test.ts +224 -0
  613. package/src/foreign/zcode.ts +60 -0
  614. package/src/foreign/zed.test.ts +240 -0
  615. package/src/foreign/zed.ts +62 -0
  616. package/src/index.facade.test.ts +254 -16
  617. package/src/index.ts +704 -82
  618. package/src/market/github-repos.test.ts +98 -1
  619. package/src/market/github-repos.ts +63 -1
  620. package/src/market/types.ts +3 -3
  621. package/src/profiles/dsh-profile-incomplete-copy.test.ts +177 -0
  622. package/src/profiles/dsh-profile-manager.test.ts +37 -0
  623. package/src/profiles/dsh-profile-manager.ts +188 -13
  624. package/src/profiles/dsh-profile-shared.ts +55 -1
  625. package/src/profiles/index.ts +2 -1
  626. package/src/routes/consult.ts +53 -4
  627. package/src/routes/foreign-context.ts +89 -0
  628. package/src/routes/foreign.test.ts +443 -0
  629. package/src/routes/foreign.ts +226 -0
  630. package/src/routes/import.ts +23 -1
  631. package/src/routes/index.ts +6 -2
  632. package/src/routes/kit.ts +4 -3
  633. package/src/routes/market.ts +0 -1
  634. package/src/routes/prefs.ts +15 -1
  635. package/src/routes/recovery.test.ts +167 -0
  636. package/src/routes/recovery.ts +331 -1
  637. package/src/routes/session-export.ts +308 -0
  638. package/src/routes/snapshots.ts +12 -2
  639. package/src/routes/sync.ts +91 -7
  640. package/src/security/redaction.test.ts +149 -0
  641. package/src/security/redaction.ts +25 -9
  642. package/src/security/secret-scanner.test.ts +186 -0
  643. package/src/security/secret-scanner.ts +57 -1
  644. package/src/sync/autosync-risk-tier.test.ts +61 -0
  645. package/src/sync/autosync-scheduler.test.ts +48 -1
  646. package/src/sync/autosync-scheduler.ts +50 -26
  647. package/src/sync/backup-files.ts +5 -0
  648. package/src/sync/gist/gist-msgkeys-catalog.test.ts +33 -0
  649. package/src/sync/gist/gist-msgkeys.test.ts +71 -0
  650. package/src/sync/gist/gist-transport.test.ts +308 -0
  651. package/src/sync/gist/gist-transport.ts +550 -0
  652. package/src/sync/gist/messages.ts +49 -0
  653. package/src/sync/retention-prune-all-off.test.ts +50 -0
  654. package/src/sync/s3/cloud-config.test.ts +283 -0
  655. package/src/sync/s3/messages.ts +102 -0
  656. package/src/sync/s3/s3-delete-index-404.test.ts +170 -0
  657. package/src/sync/s3/s3-delete-index-unreadable.test.ts +141 -0
  658. package/src/sync/s3/s3-msgkeys.test.ts +101 -0
  659. package/src/sync/s3/s3-providers.ts +213 -0
  660. package/src/sync/s3/s3-transport.test.ts +394 -0
  661. package/src/sync/s3/s3-transport.ts +651 -0
  662. package/src/sync/s3/s3-upload-index-404.test.ts +171 -0
  663. package/src/sync/s3/sigv4.test.ts +219 -0
  664. package/src/sync/s3/sigv4.ts +283 -0
  665. package/src/sync/sync-config.test.ts +237 -5
  666. package/src/sync/sync-config.ts +841 -52
  667. package/src/sync/sync-engine-download-tombstone.test.ts +73 -0
  668. package/src/sync/sync-engine.ts +5 -3
  669. package/src/sync/sync-history.ts +1 -1
  670. package/src/sync/webdav/webdav-delete-index-404.test.ts +178 -0
  671. package/src/sync/webdav/webdav-delete-index-unreadable.test.ts +143 -0
  672. package/src/sync/webdav/webdav-msgkeys.test.ts +53 -0
  673. package/src/sync/webdav/webdav-transport.test.ts +9 -5
  674. package/src/sync/webdav/webdav-transport.ts +99 -17
  675. package/src/sync/webdav/webdav-upload-index-404.test.ts +177 -0
  676. package/src/ui/commands.test.ts +4 -1
  677. package/src/ui/commands.ts +46 -12
  678. package/src/ui/disk-usage-view.test.ts +63 -1
  679. package/src/ui/disk-usage-view.ts +93 -1
  680. package/src/ui/dsh-profiles-incomplete-view.test.ts +102 -0
  681. package/src/ui/dsh-profiles-view.ts +94 -0
  682. package/src/ui/foreign-view.test.ts +241 -0
  683. package/src/ui/foreign-view.ts +362 -0
  684. package/src/ui/i18n.ts +164 -0
  685. package/src/ui/mid-ellipsis.test.ts +5 -10
  686. package/src/ui/session-inventory-view.test.ts +142 -2
  687. package/src/ui/session-inventory-view.ts +127 -0
  688. package/src/ui/session-layout-view.test.ts +361 -0
  689. package/src/ui/session-layout-view.ts +444 -0
  690. package/src/ui/sync-repository-picker.test.ts +130 -0
  691. package/src/ui/sync-repository-picker.ts +180 -0
  692. package/src/ui/sync-settings-view.test.ts +166 -0
  693. package/src/ui/sync-settings-view.ts +319 -9
  694. package/src/ui/types.ts +42 -0
  695. package/src/utils/asar-read.test.ts +330 -0
  696. package/src/utils/asar-read.ts +606 -0
  697. package/src/utils/env-lock.test.ts +55 -0
  698. package/src/utils/env-lock.ts +6 -1
  699. package/src/utils/patch-yaml.test.ts +112 -0
  700. package/src/utils/patch-yaml.ts +72 -0
  701. package/src/utils/paths.test.ts +63 -0
  702. package/src/utils/paths.ts +25 -3
  703. package/src/utils/recursive-walk.test.ts +20 -0
  704. package/src/utils/recursive-walk.ts +12 -13
  705. package/src/utils/session-format.test.ts +113 -2
  706. package/src/utils/session-format.ts +127 -5
  707. package/src/utils/session-health-scan.test.ts +364 -1
  708. package/src/utils/session-health-scan.ts +434 -15
  709. package/src/utils/session-layout-repair-service.test.ts +362 -0
  710. package/src/utils/session-layout-repair-service.ts +568 -0
  711. package/src/utils/session-log-repair.test.ts +260 -1
  712. package/src/utils/session-log-repair.ts +294 -55
  713. package/src/utils/session-repair-service.test.ts +148 -1
  714. package/src/utils/session-repair-service.ts +165 -10
  715. package/src/utils/session-row-facts.test.ts +159 -0
  716. package/src/utils/session-row-facts.ts +187 -0
  717. package/src/utils/session-verify.test.ts +762 -0
  718. package/src/utils/session-verify.ts +784 -0
  719. package/src/utils/shared-constants.ts +82 -0
@@ -4,7 +4,8 @@
4
4
  * 钉住的语义(这些是安全边界,不是实现细节):
5
5
  * - **一次性 token**:不带 token 的请求一律 403;token 只能换一次会话 cookie;
6
6
  * - **cookie 不可猜**:换到 cookie 后可以正常访问页面,伪造 cookie 仍然 403;
7
- * - **只读**:只暴露 GET,任何写方法一律 405(kit 的方法白名单);
7
+ * - **页面只读、写动作显式**:页面只用 GET 渲染;写动作是独立的 POST 表单(一次性 action token
8
+ * + 安全门),任何不在声明内的方法一律 405 —— 且必须渲染成 HTML 错误页而不是裸 JSON(cli-F4);
8
9
  * - **只在本机**:非回环来源一律 403(kit 的围栏);
9
10
  * - **不写盘**:打开首页/磁盘/会话页不产生任何文件(零写入)。
10
11
  */
@@ -15,9 +16,29 @@ import { readFileSync } from 'node:fs'
15
16
  import http from 'node:http'
16
17
  import os from 'node:os'
17
18
  import path from 'node:path'
19
+ import { fileURLToPath } from 'node:url'
18
20
 
19
21
  import { startConsoleServer, CONSOLE_SESSION_COOKIE, type ConsoleServerHandle } from './server.ts'
20
- import type { RescuePaths } from '../actions.ts'
22
+ import {
23
+ renderDiskPage,
24
+ renderExportPage,
25
+ renderHomePage,
26
+ renderLockPage,
27
+ renderMessagePage,
28
+ renderProfilesPage,
29
+ renderReinstallPage,
30
+ renderRestorePage,
31
+ renderResultPage,
32
+ renderSessionsPage,
33
+ renderUnlockPage,
34
+ renderVerifyPage,
35
+ type InlineRepairView,
36
+ } from './page.ts'
37
+ import type { LockReport, RescuePaths, RescueStatus, RestorePlanView } from '../actions.ts'
38
+ import type { SessionHealthScanResult } from '../../utils/session-health-scan.ts'
39
+ import { DISK_USAGE_AREAS, type DiskUsageArea, type DiskUsageAreaReport, type DiskUsageReport } from '../../core/disk-usage.ts'
40
+ import type { SnapshotMeta } from '../../core/restore.ts'
41
+ import type { BackupFileMeta } from '../../sync/backup-files.ts'
21
42
 
22
43
  const silentIo = { log: () => undefined, error: () => undefined }
23
44
 
@@ -196,6 +217,22 @@ async function getPage(handle: ConsoleServerHandle, cookie: string, path: string
196
217
  return await res.text()
197
218
  }
198
219
 
220
+ /**
221
+ * 抠出某一节卡片的 HTML(`<h2>标题…</h2><section class="card">…</section>`)。
222
+ *
223
+ * 为什么必须按卡片切:页脚(renderFooter)**每一页**都印着
224
+ * `dsh-config-manager sessions repair --apply — 会话布局归位(本页按同一实现执行)`,
225
+ * 所以整页级断言分不清「修复二段把 sessions repair 说成等价通道」与「页脚那条成立的布局归位命令」
226
+ * —— 旧断言正是在这一点上是**假阴性**(C1b 实测)。
227
+ */
228
+ function cardOf(html: string, titlePrefix: string): string {
229
+ const start = html.indexOf('<h2>' + titlePrefix)
230
+ assert.notEqual(start, -1, '页面上找不到卡片:' + titlePrefix)
231
+ const end = html.indexOf('</section>', start)
232
+ assert.notEqual(end, -1, '卡片未闭合:' + titlePrefix)
233
+ return html.slice(start, end)
234
+ }
235
+
199
236
  async function postForm(
200
237
  handle: ConsoleServerHandle,
201
238
  cookie: string,
@@ -777,10 +814,316 @@ test('R1-02 就地修复入口:能力边界如实呈现(只做重放重复
777
814
  // 分母必须给出来(「没发现」不能是拍胸脯)
778
815
  assert.match(html, /探测了 <strong>0<\/strong> 条/)
779
816
  assert.match(html, /本次没有发现可零损失修复的会话/)
780
- assert.match(html, /dsh-config-manager sessions repair --apply/)
781
817
  })
782
818
  })
783
819
 
820
+ /**
821
+ * C1b:救急台「修复二」段不得再把 `sessions repair --apply` 说成等价通道(它不碰字节),
822
+ * 同时「修复一:会话布局归位」卡里**成立**的等价命令**必须还在** —— 防以后一刀切删掉那一处。
823
+ */
824
+ test('R1-02b 「修复二」段不把 sessions repair 说成等价通道;「修复一」布局归位卡的等价命令仍在', async () => {
825
+ await withConsole(async (handle, paths) => {
826
+ const { cookie } = await bootstrap(handle)
827
+
828
+ // ① 空盘(没有可零损失修项):这一段最容易把「布局归位」说成「重放去重」的等价通道
829
+ const empty = await getPage(handle, cookie!, '/sessions')
830
+ const emptyInline = cardOf(empty, '修复二:重放重复行')
831
+ assert.doesNotMatch(emptyInline, /等价命令/, '修复二段不得再出现「等价命令」表述')
832
+ assert.doesNotMatch(emptyInline, /sessions repair --apply/, '修复二段不得把 sessions repair --apply 当成出口')
833
+ // 新表述:指向本救急台自己的就地操作 + 写明 sessions repair 的职责边界
834
+ assert.match(emptyInline, /职责边界/)
835
+ assert.match(emptyInline, /sessions repair \[--fix\]/)
836
+ assert.match(emptyInline, /不改写会话字节/)
837
+ assert.match(emptyInline, /dcm web/)
838
+ assert.match(emptyInline, /修复选中的会话(零损失)/)
839
+
840
+ // ② 造一条错位会话 → 出现可执行的布局归位表单:那里的等价命令是**成立**的,必须保留
841
+ const { encodeZstdFrame } = await import('../../utils/zstd-frame.ts')
842
+ const dir = path.join(paths.homeDir, 'sessions', '--D-Ghost-proj--', 'session-layout')
843
+ await fs.mkdir(dir, { recursive: true })
844
+ await fs.writeFile(path.join(dir, 'session.v3.jsonl.zstd'), Buffer.concat([
845
+ encodeZstdFrame(Buffer.from(JSON.stringify({ version: 3, id: 'session-layout', cwd: 'D:/Real/proj' }) + '\n', 'utf8')),
846
+ encodeZstdFrame(Buffer.from('{"seq":1}\n', 'utf8')),
847
+ ]))
848
+
849
+ const page = await getPage(handle, cookie!, '/sessions')
850
+ const layout = cardOf(page, '修复一:会话布局归位')
851
+ assert.match(layout, /等价命令:dsh-config-manager sessions repair --apply/, '布局归位卡的等价命令是成立的,不得被一刀切删掉')
852
+ const inline = cardOf(page, '修复二:重放重复行')
853
+ assert.doesNotMatch(inline, /等价命令/, '修复二段(有可修项时)同样不得出现等价命令表述')
854
+ assert.match(inline, /职责边界/)
855
+ })
856
+ })
857
+
858
+ /**
859
+ * C1c:会话页的「写入口」事实必须唯一且真实,且卡片段里不得再把 `**` 当强调符号输出成字面星号。
860
+ *
861
+ * 这里**直接渲染** renderSessionsPage(页面是服务端直出 HTML,渲染结果就是用户看到的字节):
862
+ * 「修复二」段有两条分支(无零损失可修项 → 只报告分布段;有可修项 → 确认表单),
863
+ * 两条分支过去各有一处 `**`,走 HTTP + 造会话夹具只能覆盖其中一条。
864
+ */
865
+ test('C1c 会话页写入口表述真实(不说「本页不提供」)+ 修复二段无字面 **', () => {
866
+ const paths: RescuePaths = {
867
+ homeDir: 'D:/home',
868
+ dataDir: 'D:/home/dsh-config-manager',
869
+ snapshotsDir: 'D:/home/dsh-config-manager/snapshots',
870
+ exportsDir: 'D:/home/dsh-config-manager/exports',
871
+ locksDir: 'D:/home/dsh-config-manager/locks',
872
+ controlRoots: ['D:/home/dsh-config-manager'],
873
+ profile: 'web',
874
+ }
875
+ const result: SessionHealthScanResult = {
876
+ rows: [{
877
+ unitId: '--D--/a',
878
+ sessionId: 'a',
879
+ projectKey: '--D--',
880
+ severity: 'nextRequestFails',
881
+ issues: [{ code: 'seq-gap', severity: 'nextRequestFails' }],
882
+ }],
883
+ summary: {
884
+ total: 1,
885
+ bySeverity: { blocksStartup: 0, unloadable: 0, nextRequestFails: 1, invisible: 0, ok: 0 },
886
+ structurallyChecked: 1,
887
+ deepVerified: 1,
888
+ deepUnverified: 0,
889
+ },
890
+ untested: 0,
891
+ unreadableEntries: 0,
892
+ sessionsDir: 'D:/home/sessions',
893
+ sessionsDirExists: true,
894
+ }
895
+ const blockedInline: InlineRepairView = { fixable: [], probed: 3, blocked: [{ reason: 'seq-gap', count: 2 }] }
896
+ const blocked = renderSessionsPage(result, undefined, '0.0.0-test', paths, null, 'tok', '', null, blockedInline)
897
+
898
+ // ① fixHint(有「需要处理」的行才渲染):不得再声称「本页不提供」——同页「修复一」卡就是本页提供的写动作
899
+ const problems = cardOf(blocked, '需要处理的会话(')
900
+ assert.doesNotMatch(problems, /本页不提供/, 'fixHint 不得再声称本页不提供该写动作(自相矛盾)')
901
+ assert.match(problems, /本页<strong>提供<\/strong>两个写入口/, '必须正向写清本页提供哪些写入口')
902
+ assert.match(problems, /sessions repair --apply/, '「修复一」的等价命令是成立的,仍要如实给出')
903
+
904
+ // ② 无零损失可修项 + 有「只报告」原因分布 → 该分支确实渲染,且强调是真 <strong> 而不是字面 **
905
+ assert.match(blockedInlineCard(blocked), /只报告/, '该分支必须真的渲染「只报告」文案(否则下面的断言是假阴性)')
906
+ assert.match(blockedInlineCard(blocked), /<strong>只报告<\/strong>/, '强调必须是真 <strong>')
907
+ assert.doesNotMatch(blockedInlineCard(blocked), /\*\*/, '卡片段里不得再出现字面 **')
908
+
909
+ // ③ 有零损失可修项 → 确认表单分支(consequence 里也曾有字面 **)
910
+ const fixable = renderSessionsPage(result, undefined, '0.0.0-test', paths, null, 'tok', '', null,
911
+ { fixable: [{ unitId: '--D--/a', droppedRows: 3 }], probed: 1, blocked: [] })
912
+ const fixableCard = cardOf(fixable, '修复二:重放重复行')
913
+ assert.match(fixableCard, /修复选中的会话(零损失)/, '该分支必须真的渲染确认表单')
914
+ assert.doesNotMatch(fixableCard, /\*\*/, '确认表单段同样不得出现字面 **')
915
+
916
+ // ④ 不误伤:页脚速查表照旧;「修复一」卡里那处**成立**的等价命令仍在源码里(渲染路径见 R1-02b 的真实磁盘用例)
917
+ assert.match(fixable, /会话布局归位(本页按同一实现执行)/, '页脚速查表不得被误改')
918
+
919
+ // ⑤ 源码级:fixHint 那一小段里不得再有「本页不提供」(页面另外两处是别的写动作,别误伤)
920
+ const source = readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), 'page.ts'), 'utf8')
921
+ const at = source.indexOf('const fixHint =')
922
+ assert.notEqual(at, -1, '源码里找不到 fixHint')
923
+ const snippet = source.slice(at, source.indexOf('parts.push(section(', at))
924
+ assert.doesNotMatch(snippet, /本页不提供/, 'fixHint 源码段不得再出现「本页不提供」')
925
+ assert.match(snippet, /PAGE_SESSION_WRITE_ENTRIES/, 'fixHint 只能渲染单一事实源常量')
926
+ // 「修复一」布局归位卡的等价命令是**成立**的(本页与 CLI 同实现),不得被这轮收尾一刀切删掉
927
+ assert.ok(source.includes('等价命令:dsh-config-manager sessions repair --apply'), '布局归位卡的等价命令必须仍在')
928
+ })
929
+
930
+ /** 取「修复二」段卡片(C1c 用例内部用,避免页脚命中造成假阴性)。 */
931
+ function blockedInlineCard(html: string): string {
932
+ return cardOf(html, '修复二:重放重复行')
933
+ }
934
+
935
+ /**
936
+ * 取页面**正文**(`<body>` … `<footer>`)。
937
+ *
938
+ * 为什么要切片:页脚(renderFooter)**每一页**都印同一份速查表,整页 match 会把它命中成假阴性
939
+ * (C1b 实测过),`<head>` 里的样式/脚本也不是用户看到的正文。
940
+ */
941
+ function bodyOf(html: string): string {
942
+ const start = html.indexOf('<body>')
943
+ assert.notEqual(start, -1, '页面没有 <body>')
944
+ const end = html.indexOf('<footer>', start)
945
+ assert.notEqual(end, -1, '页面没有 <footer>(速查表页脚是每页都渲染的)')
946
+ return html.slice(start, end)
947
+ }
948
+
949
+ /**
950
+ * C1d:救急台是**多页**的,所以每一页正文里都不许出现
951
+ * ① 字面 `**`(强调符号被当正文输出,用户看到星号);
952
+ * ② 空泛的「本页不提供」(captain 裁决:要么指向**本页内真实存在**的入口,要么**点名到页**)。
953
+ *
954
+ * **覆盖口径**:枚举来源 = `page.ts` 导出的全部 `render*Page(`(= 整页渲染入口),并用集合相等断言钉住 ——
955
+ * 新增一个页面而不登记进 `pages` 会直接红(此前只渲染 7 页却宣称「各页正文」,t11 抓到)。
956
+ * 断言落在**正文切片**上(页脚不计入,避免 C1b 遇到过的假阴性);每页先跑一条**非空证明**,
957
+ * 证明该分支确实渲染了,否则「不存在」类断言会退化成空态假阴性。
958
+ */
959
+ test('C1d 各页正文无字面 ** 且无空泛「本页不提供」(枚举 = page.ts 全部 render*Page)', () => {
960
+ const paths: RescuePaths = {
961
+ homeDir: 'D:/home',
962
+ dataDir: 'D:/home/dsh-config-manager',
963
+ snapshotsDir: 'D:/home/dsh-config-manager/snapshots',
964
+ exportsDir: 'D:/home/dsh-config-manager/exports',
965
+ locksDir: 'D:/home/dsh-config-manager/locks',
966
+ controlRoots: ['D:/home/dsh-config-manager'],
967
+ profile: 'web',
968
+ }
969
+ const version = '0.0.0-test'
970
+ const snapshot: SnapshotMeta = {
971
+ id: 'snap-1',
972
+ createdAt: '2026-10-07T00:00:00.000Z',
973
+ sourceZip: 'dsh-config.zip',
974
+ entryCount: 3,
975
+ hostFileBackupCount: 0,
976
+ beforePluginCount: 0,
977
+ }
978
+ // 选中某快照后的恢复计划:只有带危险动作,页面才会渲染 POST /restore/run 的确认表单
979
+ const restorePlan: RestorePlanView = {
980
+ ok: true,
981
+ code: 'ok',
982
+ message: '',
983
+ snapshotId: 'snap-1',
984
+ pluginBaselineConfirmed: false,
985
+ actions: [{ kind: 'hostFileRestore', description: '还原 settings', target: 'D:/home/settings.yaml', dangerous: true }],
986
+ summary: { hostFileRestores: 1 },
987
+ }
988
+ const lock: LockReport = { locksDir: paths.locksDir, present: true, state: 'LOCKED' }
989
+ const homeLock: LockReport = { locksDir: paths.locksDir, present: false, state: 'NONE' }
990
+ const status: RescueStatus = {
991
+ generatedAt: '2026-10-07T00:00:00.000Z',
992
+ paths,
993
+ safeMode: [],
994
+ lock: homeLock,
995
+ instances: [],
996
+ snapshots: [snapshot],
997
+ backups: [],
998
+ errors: [],
999
+ }
1000
+ const areas = Object.fromEntries(DISK_USAGE_AREAS.map((area) => [area, {
1001
+ sizeBytes: 0,
1002
+ fileCount: 0,
1003
+ policy: 'regenerable' as const,
1004
+ unreadable: false,
1005
+ }])) as Record<DiskUsageArea, DiskUsageAreaReport>
1006
+ const diskReport: DiskUsageReport = {
1007
+ dataDir: paths.dataDir,
1008
+ totalBytes: 0,
1009
+ totalFiles: 0,
1010
+ reclaimableBytes: 0,
1011
+ expiredBytes: 0,
1012
+ areas,
1013
+ backupRetention: { keepLast: 10, latestBackupBytes: 0, latestBackupAt: null },
1014
+ }
1015
+ const encrypted: BackupFileMeta[] = [{
1016
+ name: 'dsh-config-enc.zip',
1017
+ path: 'D:/home/dsh-config-manager/exports/dsh-config-enc.zip',
1018
+ sizeBytes: 2048,
1019
+ mtimeMs: 1_700_000_000_000,
1020
+ source: 'manual',
1021
+ }]
1022
+
1023
+ const home = renderHomePage({ status, version })
1024
+ const sessions = renderSessionsPage(
1025
+ {
1026
+ rows: [{
1027
+ unitId: '--D--/a', sessionId: 'a', projectKey: '--D--', severity: 'nextRequestFails',
1028
+ issues: [{ code: 'seq-gap', severity: 'nextRequestFails' }],
1029
+ }],
1030
+ summary: {
1031
+ total: 1, bySeverity: { blocksStartup: 0, unloadable: 0, nextRequestFails: 1, invisible: 0, ok: 0 },
1032
+ structurallyChecked: 1, deepVerified: 1, deepUnverified: 0,
1033
+ },
1034
+ untested: 0, unreadableEntries: 0, sessionsDir: 'D:/home/sessions', sessionsDirExists: true,
1035
+ },
1036
+ undefined, version, paths, null, 'tok', '', null,
1037
+ { fixable: [], probed: 1, blocked: [{ reason: 'seq-gap', count: 1 }] },
1038
+ )
1039
+ const restore = renderRestorePage([snapshot], restorePlan, version, paths, 'tok')
1040
+ const disk = renderDiskPage(diskReport, version, paths, 'tok', '')
1041
+ const lockPage = renderLockPage(lock, version, paths, 'tok', '')
1042
+ const unlock = renderUnlockPage(encrypted, version, paths, 'tok', '')
1043
+ const reinstall = renderReinstallPage(
1044
+ [{ id: 'plugins', label: '插件', desc: '清空 ~/.dsh 的插件目录', destructive: true, defaultOn: false }],
1045
+ version, paths, 'tok',
1046
+ )
1047
+
1048
+ // 导出页(本轮纳入 sweep 的那一页;banner 正文曾出现字面 **)
1049
+ const exportPage = renderExportPage(['settings', 'providers'], ['mcp'], version, paths, 'tok')
1050
+ const verify = renderVerifyPage({
1051
+ file: 'D:/home/dsh-config-manager/exports/dsh-config.zip', verdict: 'OK',
1052
+ sizeBytes: 2048, entryCount: 3, sections: ['settings'], errors: [], warnings: [],
1053
+ }, version, paths)
1054
+ const result = renderResultPage('就地修复完成(零损失)', ['✔ --D--/a:已丢弃 2 行重放重复事件'], version, paths, 'ok', '/sessions')
1055
+ const profiles = renderProfilesPage({
1056
+ ok: true, profilesDir: 'D:/home/profiles', errors: [],
1057
+ rows: [{
1058
+ name: 'web', shape: 'web', bundles: 3, dependencies: 12, hasNodeModules: true,
1059
+ patchEntryCount: 2, updatedAtMs: 1_700_000_000_000, dshVersion: '0.2.0', sessionFormatVersion: 4,
1060
+ launchable: true, owned: false, running: false, port: null, url: null, pid: null,
1061
+ }],
1062
+ }, version, paths, 'tok', '', '')
1063
+ const message = renderMessagePage('403 需要访问链接', '请回到终端,用打印出来的带 token 链接打开本页。', '链接用过即废。')
1064
+
1065
+ // 枚举来源:page.ts 里**全部** `export function render*Page(`(每个 = 一个整页渲染入口;
1066
+ // `renderLayout` 是外壳、`renderConfirmForm` 是片段,不在此列)。
1067
+ // 下面这条集合相等断言保证「新增页面不登记进 sweep」会红 —— 本轮之前 C1d 只渲染 7 页却宣称「各页正文」,
1068
+ // 正是缺这条(t11 抓到)。
1069
+ const declared = [...readFileSync(new URL('./page.ts', import.meta.url), 'utf8')
1070
+ .matchAll(/export function (render\w*Page)\s*\(/g)].map((m) => m[1] ?? '').sort()
1071
+
1072
+ const pages: Array<{ fn: string; name: string; html: string; slice?: (html: string) => string }> = [
1073
+ { fn: 'renderHomePage', name: '首页', html: home },
1074
+ { fn: 'renderSessionsPage', name: '会话', html: sessions },
1075
+ { fn: 'renderRestorePage', name: '恢复', html: restore },
1076
+ { fn: 'renderDiskPage', name: '磁盘', html: disk },
1077
+ { fn: 'renderLockPage', name: '残留锁', html: lockPage },
1078
+ { fn: 'renderUnlockPage', name: '解锁', html: unlock },
1079
+ { fn: 'renderReinstallPage', name: '重装', html: reinstall },
1080
+ { fn: 'renderVerifyPage', name: '备份自检', html: verify },
1081
+ { fn: 'renderExportPage', name: '导出', html: exportPage },
1082
+ { fn: 'renderProfilesPage', name: '档案', html: profiles },
1083
+ { fn: 'renderResultPage', name: '写动作结果', html: result },
1084
+ // 403/404 极简页**没有 <footer>**(刻意不依赖任何目录事实),所以不走 bodyOf 的切片,取 <body> 起的整段。
1085
+ { fn: 'renderMessagePage', name: '错误极简页', html: message, slice: (h) => h.slice(h.indexOf('<body>')) },
1086
+ ]
1087
+ assert.deepEqual(
1088
+ pages.map((p) => p.fn).sort(),
1089
+ declared,
1090
+ 'sweep 必须穷尽 page.ts 的全部 render*Page(枚举来源 = 该模块的 export function render*Page)',
1091
+ )
1092
+
1093
+ // 非空证明表(每页一条):先证明「该段确实渲染了」,否则上面的「不得出现 **」会退化成空态假阴性
1094
+ const proofs: Record<string, RegExp> = {
1095
+ renderHomePage: /需要终端里打印的 6 位确认码/,
1096
+ renderSessionsPage: /需要处理的会话/,
1097
+ renderRestorePage: /恢复计划(/,
1098
+ renderDiskPage: /导出产物只在勾选时、且只按保留期回收/,
1099
+ renderLockPage: /不会自动回收/,
1100
+ renderUnlockPage: /会在内存中解出明文 ZIP/,
1101
+ renderReinstallPage: /只在终端里打印的 6 位确认码/,
1102
+ renderExportPage: /settings \/ providers \/ mcp 等结构化分区的值必须经 DSH 服务读取/,
1103
+ renderVerifyPage: /自检通过/,
1104
+ renderProfilesPage: /档案 = DSH 的 profile/,
1105
+ renderResultPage: /逐条结果/,
1106
+ renderMessagePage: /403/,
1107
+ }
1108
+ for (const page of pages) {
1109
+ const body = page.slice !== undefined ? page.slice(page.html) : bodyOf(page.html)
1110
+ const proof = proofs[page.fn]
1111
+ assert.ok(proof !== undefined, page.name + ':sweep 里每页都必须有非空证明(否则可能空态假阴性)')
1112
+ assert.match(body, proof, page.name + ':该段确实渲染(非空证明,避免空态假阴性)')
1113
+ assert.doesNotMatch(body, /\*\*/, page.name + ':正文不得出现字面 **(强调符号被当正文输出)')
1114
+ assert.doesNotMatch(body, /本页不提供/, page.name + ':正文不得出现空泛的「本页不提供」(要么指向本页入口,要么点名到页)')
1115
+ }
1116
+
1117
+ // 交叉证明(原有用例,保留):被点名的页 / 入口确实存在
1118
+ const homeBody = bodyOf(home)
1119
+ assert.match(homeBody, /请在「恢复」页执行/, '首页快照卡必须点名到「恢复」页')
1120
+ assert.match(homeBody, /本页(首页)不提供/, '「本页」必须限定为首页,不留空泛说法')
1121
+ const diskBody = bodyOf(disk)
1122
+ assert.match(diskBody, /清理是写动作,就在本页下方「清理磁盘(写动作)」卡里执行/, '磁盘保留期说明必须指向本页的清理入口')
1123
+ assert.match(diskBody, /action="\/disk\/cleanup"/, '被指向的入口在本页真实存在(裁决 (a) 成立)')
1124
+ assert.match(bodyOf(restore), /action="\/restore\/run"/, '点名到的「恢复」页确实提供该写动作(裁决 (b) 成立)')
1125
+ })
1126
+
784
1127
  test('R1-03 就地修复写路由:token 一次性;没 token 一律 400(不写任何字节)', async () => {
785
1128
  await withConsole(async (handle) => {
786
1129
  const { cookie } = await bootstrap(handle)
@@ -953,7 +1296,9 @@ function runConsoleScript(cookie: string): {
953
1296
  querySelectorAll: (sel: string) => (sel === '[data-dcm-theme]' ? buttons : []),
954
1297
  addEventListener: (type: string, fn: (event: { target: unknown }) => void) => { if (type === 'click') clicks.push(fn) },
955
1298
  }
956
- const script = readFileSync(new URL('./client-script.ts', import.meta.url), 'utf8')
1299
+ // issue #70:源码字面量守卫必须与检出 EOL 无关(Windows core.autocrlf=true ⇒ CRLF 工作区)。
1300
+ // 仓库既有 11 处同款先例(readFileSync 后折 LF),这三处此前漏了。
1301
+ const script = readFileSync(new URL('./client-script.ts', import.meta.url), 'utf8').replace(/\r\n/g, '\n')
957
1302
  const body = /export const CONSOLE_SCRIPT = `([\s\S]*?)`\n/.exec(script)?.[1]
958
1303
  assert.ok(body, 'client-script.ts 必须导出 CONSOLE_SCRIPT 模板字符串')
959
1304
  // 只喂脚本真正会碰的浏览器 API(显式列出,不用 Proxy 兜底 —— 缺哪个就报错,别让假 DOM 悄悄骗过测试)
@@ -1001,3 +1346,101 @@ test('R3-04 主题脚本的真行为:cookie 解析 / 非法值回落自动 /
1001
1346
  assert.equal(clicky.attrs['data-theme'], 'auto')
1002
1347
  assert.match(clicky.cookieNow(), /dcm-theme=auto/)
1003
1348
  })
1349
+ /* ------------------------------------------------ t26 新增回归(cli-F1/F3/F4/F6/F7) */
1350
+
1351
+ /** 造一条「本插件启动的实例」台账(pid = 当前进程,isProcessAlive 必然为真)。 */
1352
+ async function seedLaunchedInstance(paths: RescuePaths, name: string, url: string): Promise<void> {
1353
+ const profileDir = path.join(paths.homeDir, 'profiles', name)
1354
+ await fs.mkdir(profileDir, { recursive: true })
1355
+ await fs.writeFile(path.join(profileDir, 'package.json'), JSON.stringify({
1356
+ name, version: '0.0.0', dependencies: {},
1357
+ dsh: { profile: { bundles: ['@deepseek-ai/dsh-web-app'] } },
1358
+ }))
1359
+ await fs.mkdir(paths.dataDir, { recursive: true })
1360
+ await fs.writeFile(path.join(paths.dataDir, 'launches.json'), JSON.stringify({
1361
+ version: 1,
1362
+ launches: [{
1363
+ name, port: 3099, pid: process.pid, url,
1364
+ logFile: path.join(paths.dataDir, 'launch.log'), startedAt: new Date().toISOString(),
1365
+ }],
1366
+ }))
1367
+ }
1368
+
1369
+ test('cli-F1 救急台回传的授权 URL 不得被脱敏抹掉(入口链接逐字节等于台账 URL)', async () => {
1370
+ await withConsole(async (handle, paths) => {
1371
+ const authUrl = 'http://127.0.0.1:3099/?token=probe-token-abc123'
1372
+ await seedLaunchedInstance(paths, 'probe-demo', authUrl)
1373
+ const { cookie } = await bootstrap(handle)
1374
+ const res = await fetch(handle.url.replace(/\/$/, '') + '/profiles', { headers: { cookie: cookie! } })
1375
+ assert.equal(res.status, 200)
1376
+ const html = await res.text()
1377
+ // 修复前:redact() 的 URL_QUERY_RE 把 token 值换成 ***REDACTED***,点开必然 401
1378
+ assert.ok(html.includes('<a href="' + authUrl + '">打开实例</a>'), '入口链接必须带真实 token')
1379
+ assert.doesNotMatch(html, /token=\*\*\*REDACTED\*\*\*/)
1380
+ })
1381
+ })
1382
+
1383
+ test('cli-F3 页面必须禁止被 iframe 嵌入(frame-ancestors + X-Frame-Options)', async () => {
1384
+ await withConsole(async (handle) => {
1385
+ const { cookie } = await bootstrap(handle)
1386
+ const res = await fetch(handle.url, { headers: { cookie: cookie! } })
1387
+ assert.equal(res.status, 200)
1388
+ const csp = res.headers.get('content-security-policy') ?? ''
1389
+ assert.match(csp, /frame-ancestors 'none'/, 'default-src 不覆盖 frame-ancestors,必须显式声明')
1390
+ assert.equal(res.headers.get('x-frame-options'), 'DENY')
1391
+ // 403 极简页走同一个写出点,也必须带上
1392
+ const anon = await fetch(handle.url)
1393
+ assert.equal(anon.status, 403)
1394
+ assert.match(anon.headers.get('content-security-policy') ?? '', /frame-ancestors 'none'/)
1395
+ assert.equal(anon.headers.get('x-frame-options'), 'DENY')
1396
+ })
1397
+ })
1398
+
1399
+ test('cli-F4 方法不被允许时必须是 HTML 错误页,不得吐插件 API 形状的裸 JSON', async () => {
1400
+ await withConsole(async (handle) => {
1401
+ const { cookie } = await bootstrap(handle)
1402
+ const res = await fetch(handle.url, { method: 'POST', headers: { cookie: cookie! } })
1403
+ assert.equal(res.status, 405)
1404
+ const body = await res.text()
1405
+ assert.match(body, /<html lang="zh-CN">/, '405 必须渲染成页面')
1406
+ assert.ok(!body.startsWith('{"error"'), '不得是 kit 的 JSON 出口')
1407
+ // 方法判定不得改变「路由未命中」语义
1408
+ const nope = await fetch(handle.url.replace(/\/$/, '') + '/nope', { method: 'POST', headers: { cookie: cookie! } })
1409
+ assert.equal(nope.status, 404)
1410
+ })
1411
+ })
1412
+
1413
+ test('cli-F6 /healthz 的写路由自述必须与真实声明同源且完整', async () => {
1414
+ await withConsole(async (handle) => {
1415
+ const { cookie } = await bootstrap(handle)
1416
+ const res = await fetch(handle.url.replace(/\/$/, '') + '/healthz', { headers: { cookie: cookie! } })
1417
+ const payload = await res.json() as { writes: string[]; writeRoutes?: string[] }
1418
+ assert.deepEqual(payload.writes, ['sessions-repair', 'disk-cleanup', 'recover-stale-lock'], '既有 writes 契约不变')
1419
+ assert.ok(Array.isArray(payload.writeRoutes), '/healthz 必须自述全部写路由')
1420
+ const declared = payload.writeRoutes as string[]
1421
+ for (const line of [
1422
+ 'POST /sessions/repair', 'POST /sessions/inline-repair', 'POST /disk/cleanup', 'POST /lock/recover',
1423
+ 'POST /profiles/launch', 'POST /profiles/stop', 'POST /unlock/run', 'POST /restore/run',
1424
+ 'POST /export/run', 'POST /reinstall/plan', 'POST /reinstall/run',
1425
+ ]) {
1426
+ assert.ok(declared.includes(line), '缺写路由自述: ' + line + ' → ' + declared.join(','))
1427
+ }
1428
+ assert.ok(!declared.some((line) => line.startsWith('GET ')), 'GET 路由不得混进写自述')
1429
+ })
1430
+ })
1431
+
1432
+ test('cli-F7 //x 形态的请求目标不得被解析成首页', async () => {
1433
+ await withConsole(async (handle) => {
1434
+ const { cookie } = await bootstrap(handle)
1435
+ // 协议相对目标:修复前 new URL('//evil') 得到 authority=evil / pathname='/' → 冒充首页
1436
+ const evil = await fetch(handle.url.replace(/\/$/, '') + '//evil', { headers: { cookie: cookie! } })
1437
+ assert.equal(evil.status, 404, '//evil 不是合法页面,不得返回首页')
1438
+ // //disk 折叠成 /disk:必须是**磁盘页**(有该页专属的 section 标题),不是首页
1439
+ const disk = await fetch(handle.url.replace(/\/$/, '') + '//disk', { headers: { cookie: cookie! } })
1440
+ assert.equal(disk.status, 200)
1441
+ assert.match(await disk.text(), /磁盘占用(只读体检)/, '//disk 必须落到磁盘页而不是首页')
1442
+ const home = await fetch(handle.url, { headers: { cookie: cookie! } })
1443
+ assert.equal(home.status, 200)
1444
+ })
1445
+ })
1446
+