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
@@ -14,8 +14,48 @@
14
14
  * 所以还必须证明「收尾块之后、下一个 turn/start 之前,同一个 turn 还在继续」。
15
15
  *
16
16
  * 「绝不猜」:任何读不出的东西一律记成事实缺省(`unreadable` / 计数字段),既不当作「没问题」,
17
- * 也不编造具体原因。需要 DSH codec 才能判定的类(缺 message id / 悬空 tool-call / settlement
18
- * 非法)本轮**不产出** —— 没有真凭实据就不下结论(详见本文件末尾的说明)。
17
+ * 也不编造具体原因。
18
+ *
19
+ * 工具生命周期判定(T4)全部**只从行文本证明**,不引入 DSH codec:
20
+ * ① 缺 message id(user/message 的 data.id、assistant/message 与 tool/result 的 data.message.id);
21
+ * ② 空 tool-call id(tool/call.callId 或 assistant/message 内容里 tool-call 块的 id);
22
+ * ③ 已关闭 step 里的悬空 tool-call —— **两种声明载体合并判定**:tool/call 行,以及只由
23
+ * assistant/message 内容块(type:`tool-call`)声明、没有对应 tool/call 行的调用
24
+ * (官方 ToolCallRecovery 的待恢复集合正是以 assistant 内容块为入口);尾部仍开着的 step
25
+ * 是正常崩溃形状,不报;
26
+ * ④ 同一步内重复通告的 tool-call id;
27
+ * ⑤ tool/result 的 toolCallId 与 message.source.callId 不配对。
28
+ *
29
+ * 严重级**不是注释断言,是真 codec 实测**(2026-10-06,真机日志 + 官方读取路径;t13/t16 口径订正后):
30
+ * - v4(headerVersion === 4):tool/result 缺 message.id / toolCallId 不配对 → decodeRow 当场拒读;
31
+ * user/message 缺 data.id、assistant/message 缺 message.id、tool/call.callId 空、内容块 id 空、
32
+ * **同一步重复通告同一 advertised tool call** → 被**已安装 Session 的 seed/restore 闸门**拒读。
33
+ * 以上一律 **unloadable**。
34
+ * 真机原始输出(v4 日志 + `validation:'current'` = 安装版 Session 的校验层,即上面的 ②③④):
35
+ * `finish: seed user/message at index 9 lacks an identified message`
36
+ * `finish: tool call id requires a nonempty string`
37
+ * `finish: assistant/message repeats advertised tool call call_00_rCbEBWsfXGrfOhog9cud3396`
38
+ * - pre-v4(headerVersion < 4):三类消息缺 id / 空 tool-call id / 重复通告同一 callId 走 v0→v1、v3→v4
39
+ * 迁移并被迁移器拒绝 → unloadable。
40
+ * - 版本读不出:按较轻的 nextRequestFails 报,detail 记 codec-uncalibrated(不谎称已验证)。
41
+ *
42
+ * **前台管线的分层必须写准**(官方源码 `@deepseek-ai/dsh-session-persistence-jsonl/lib/index.js`),
43
+ * 否则容易误读成「transformed 容忍 ⇒ 也许还能打开」。官方当前代际(v4)读盘是**四层串行**的:
44
+ * ① `parseHeaderRecord` 的 `sessionFormatCatalog.createRestore(parsed, { recovery:'strict',
45
+ * validation:'transformed' })`:**既解首帧 header,也被 `readZstdPrefix` / `SessionLogScanner`
46
+ * 用来逐行 `decodeRow`** —— 也就是说 strict+transformed 同样是 v4 前台的**行解码器**,
47
+ * 而不是一次性的首帧校验;
48
+ * ② `assertV4RowAdmission` / `assertReleasedV4Relationships`:行准入与关系(引用/工具生命周期)校验;
49
+ * ③ `adoptSessionEvent`(内部如 `assertMessageEventShape`):收编成事件时的形状校验;
50
+ * ④ `Session.fromRestore(generation.meta.id, generation.events, generation.meta,
51
+ * generation.inheritedEventCount, 'detached', currentSessionMessageProjections)` +
52
+ * `assertCurrentAssistantStreams(generation.events)`。
53
+ * **只有 ① 的 `decodeRow` / `finish` 那一层**会对「user/assistant 缺 message.id」「空 tool-call id」
54
+ * 放行;②③④ 任何一层都会拒整份日志 —— 用户真实撞到的是后者(上面的原始输出即来自完整前台管线)。
55
+ * 历史代际(version <= 3)走 `historicalSessionFormatCatalog.createRestore(header,
56
+ * { recovery:'recoverable', validation:'current' })` 加同一套 v0→v1 / v3→v4 迁移器。
57
+ * 结论不变:本模块按**完整前台管线**定严重级(unloadable);① 那一层的容忍不得用于降级,
58
+ * 也不得据此推测「也许能打开」。
19
59
  */
20
60
  import fs from 'node:fs/promises';
21
61
  import { join } from 'node:path';
@@ -35,9 +75,264 @@ export const DEFAULT_MAX_UNITS = 2000;
35
75
  function parseRow(line) {
36
76
  return parseSessionRowFacts(line);
37
77
  }
78
+ /* ---------------- 工具生命周期:只从行文本证明的本地解析(T4) ---------------- */
79
+ /**
80
+ * 只看这些行类型(其余事件不解第二次 JSON)—— `session-row-facts` 是另一任务的写作用域,
81
+ * 需要的新行级事实在本模块本地解析,绝不改动它。
82
+ */
83
+ const LIFECYCLE_ROW_TYPES = new Set([
84
+ 'user/message', 'assistant/message', 'tool/result', 'tool/call',
85
+ 'step/start', 'step/end', 'turn/start', 'turn/end',
86
+ ]);
87
+ function asRecord(value) {
88
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
89
+ ? value
90
+ : undefined;
91
+ }
92
+ function readCount(value) {
93
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : undefined;
94
+ }
95
+ function isNonEmptyString(value) {
96
+ return typeof value === 'string' && value !== '';
97
+ }
98
+ /** 本地解析(只做生命周期判定所需的字段;JSON 解析失败 = undefined,调用方已按不可解析行计数)。 */
99
+ function parseLifecycleRow(line, type) {
100
+ let parsed;
101
+ try {
102
+ parsed = JSON.parse(line);
103
+ }
104
+ catch {
105
+ return undefined;
106
+ }
107
+ const rec = asRecord(parsed);
108
+ if (rec === undefined)
109
+ return undefined;
110
+ const data = asRecord(rec['data']);
111
+ const row = { type };
112
+ const turn = readCount(data?.['turn']);
113
+ const step = readCount(data?.['step']);
114
+ if (turn !== undefined)
115
+ row.turn = turn;
116
+ if (step !== undefined)
117
+ row.step = step;
118
+ if (type === 'user/message') {
119
+ // user/message 的 data **本身即 message**,id 在 data.id
120
+ row.messageIdOk = isNonEmptyString(data?.['id']);
121
+ }
122
+ else if (type === 'assistant/message') {
123
+ const message = asRecord(data?.['message']);
124
+ row.messageIdOk = isNonEmptyString(message?.['id']);
125
+ const content = message?.['content'];
126
+ if (Array.isArray(content)) {
127
+ const ids = [];
128
+ for (const block of content) {
129
+ const blockRec = asRecord(block);
130
+ if (blockRec?.['type'] === 'tool-call')
131
+ ids.push(blockRec['id']);
132
+ }
133
+ if (ids.length > 0)
134
+ row.blockIds = ids;
135
+ }
136
+ }
137
+ else if (type === 'tool/result') {
138
+ const message = asRecord(data?.['message']);
139
+ row.messageIdOk = isNonEmptyString(message?.['id']);
140
+ const sourceCallId = asRecord(message?.['source'])?.['callId'];
141
+ // 代际差异(真机实测):v4 行把 toolCallId 放在 message 上(content[0] 是文本块);
142
+ // v0/v3 行沿用 content[0].toolCallId(block 形状)。两处都读,取实际存在的那一个。
143
+ let toolCallId;
144
+ if (message !== undefined && Object.prototype.hasOwnProperty.call(message, 'toolCallId')) {
145
+ toolCallId = message['toolCallId'];
146
+ }
147
+ else {
148
+ const content = message?.['content'];
149
+ const first = Array.isArray(content) ? asRecord(content[0]) : undefined;
150
+ if (first !== undefined && Object.prototype.hasOwnProperty.call(first, 'toolCallId')) {
151
+ toolCallId = first['toolCallId'];
152
+ }
153
+ }
154
+ row.result = { sourceCallId, toolCallId };
155
+ }
156
+ else if (type === 'tool/call') {
157
+ row.rawCallId = data?.['callId'];
158
+ }
159
+ return row;
160
+ }
161
+ function createLifecycleScan() {
162
+ return {
163
+ facts: {
164
+ missingMessageId: 0,
165
+ missingMessageIdToolResult: 0,
166
+ emptyToolCallId: 0,
167
+ toolResultIdMismatch: 0,
168
+ danglingClosedStep: 0,
169
+ duplicateToolCallId: 0,
170
+ },
171
+ openSteps: new Set(),
172
+ closedSteps: new Set(),
173
+ calls: [],
174
+ queuedCalls: new Map(),
175
+ resultIds: new Set(),
176
+ declarations: new Map(),
177
+ };
178
+ }
179
+ function stepKeyOf(row) {
180
+ return row.turn !== undefined && row.step !== undefined ? row.turn + '/' + row.step : undefined;
181
+ }
182
+ function closeStep(scan, key) {
183
+ scan.openSteps.delete(key);
184
+ scan.closedSteps.add(key);
185
+ }
186
+ function bumpDeclaration(scan, key, callId, kind) {
187
+ let perStep = scan.declarations.get(key);
188
+ if (perStep === undefined) {
189
+ perStep = new Map();
190
+ scan.declarations.set(key, perStep);
191
+ }
192
+ const counts = perStep.get(callId) ?? { blocks: 0, rows: 0 };
193
+ counts[kind] += 1;
194
+ perStep.set(callId, counts);
195
+ }
196
+ /**
197
+ * 把一个调用放进悬空判定队列(去重)。
198
+ *
199
+ * 两种声明载体都走这里:`tool/call` 行与 `assistant/message` 内容块。健康日志里同一个 callId
200
+ * 两个载体都有,不去重就会把**同一次调用**数成两处悬空。
201
+ */
202
+ function queueCall(scan, key, callId) {
203
+ let ids = scan.queuedCalls.get(key);
204
+ if (ids === undefined) {
205
+ ids = new Set();
206
+ scan.queuedCalls.set(key, ids);
207
+ }
208
+ if (ids.has(callId))
209
+ return;
210
+ ids.add(callId);
211
+ scan.calls.push({ key, callId });
212
+ }
213
+ /** 应用一行(顺序即日志顺序;边界按 step/start→step/end、turn/start→turn/end 收窄)。 */
214
+ function applyLifecycleRow(scan, row) {
215
+ const key = stepKeyOf(row);
216
+ switch (row.type) {
217
+ case 'step/start':
218
+ if (key !== undefined) {
219
+ scan.openSteps.add(key);
220
+ scan.closedSteps.delete(key);
221
+ }
222
+ break;
223
+ case 'step/end':
224
+ if (key !== undefined)
225
+ closeStep(scan, key);
226
+ break;
227
+ case 'turn/end': {
228
+ // 回合已结束 → 该回合里仍开着的 step 不可能再有续写(崩溃恢复补写的 closer 也走这条边界)
229
+ if (row.turn === undefined)
230
+ break;
231
+ const prefix = row.turn + '/';
232
+ for (const open of [...scan.openSteps])
233
+ if (open.startsWith(prefix))
234
+ closeStep(scan, open);
235
+ break;
236
+ }
237
+ case 'tool/call': {
238
+ // 空 / 非字符串 callId 只进 ②(能否配对已不可判定),不进悬空判定
239
+ if (!isNonEmptyString(row.rawCallId)) {
240
+ scan.facts.emptyToolCallId += 1;
241
+ break;
242
+ }
243
+ // 定位不到 step(turn/step 读不出)→ 无法证明「step 已关闭」→ 不报悬空
244
+ if (key !== undefined) {
245
+ queueCall(scan, key, row.rawCallId);
246
+ bumpDeclaration(scan, key, row.rawCallId, 'rows');
247
+ }
248
+ break;
249
+ }
250
+ default:
251
+ break;
252
+ }
253
+ if (row.messageIdOk === false) {
254
+ scan.facts.missingMessageId += 1;
255
+ if (row.type === 'tool/result')
256
+ scan.facts.missingMessageIdToolResult += 1;
257
+ }
258
+ if (row.blockIds !== undefined) {
259
+ for (const id of row.blockIds) {
260
+ if (!isNonEmptyString(id)) {
261
+ scan.facts.emptyToolCallId += 1;
262
+ continue;
263
+ }
264
+ // 与 tool/call 行走**同一份** calls → 同一 closedSteps / resultIds 判定(绝不另起一套)
265
+ if (key !== undefined) {
266
+ queueCall(scan, key, id);
267
+ bumpDeclaration(scan, key, id, 'blocks');
268
+ }
269
+ }
270
+ }
271
+ if (row.result !== undefined) {
272
+ if (isNonEmptyString(row.result.sourceCallId))
273
+ scan.resultIds.add(row.result.sourceCallId);
274
+ // toolCallId 缺失 / 非字符串 / 与 source.callId 不一致 → 真 codec 直接拒读(v4 decodeRow;pre-v4 迁移同样拒)
275
+ if (!isNonEmptyString(row.result.toolCallId)
276
+ || !isNonEmptyString(row.result.sourceCallId)
277
+ || row.result.toolCallId !== row.result.sourceCallId) {
278
+ scan.facts.toolResultIdMismatch += 1;
279
+ }
280
+ }
281
+ }
282
+ function finalizeLifecycleScan(scan) {
283
+ for (const call of scan.calls) {
284
+ // 只报「所属 step 已经关闭」的悬空调用(无论它是由 tool/call 行还是 assistant 内容块声明):
285
+ // 日志尾部还开着的 step 是正常崩溃形状(引擎的 interruptedTurnClosers 会补),报它就是误报。
286
+ if (scan.closedSteps.has(call.key) && !scan.resultIds.has(call.callId))
287
+ scan.facts.danglingClosedStep += 1;
288
+ }
289
+ let duplicate = 0;
290
+ for (const perStep of scan.declarations.values()) {
291
+ for (const counts of perStep.values())
292
+ if (counts.blocks >= 2 || counts.rows >= 2)
293
+ duplicate += 1;
294
+ }
295
+ scan.facts.duplicateToolCallId = duplicate;
296
+ return scan.facts;
297
+ }
298
+ /**
299
+ * 缺 message id 的严重级(t13 口径订正)。
300
+ *
301
+ * v4:安装版 Session 的 seed/restore 闸门(`Session.fromRestore`)拒读整份日志 → unloadable
302
+ * (真机原始输出:`seed user/message at index 9 lacks an identified message`);
303
+ * pre-v4:迁移器(v0→v1 / v3→v4)同样拒 → unloadable;
304
+ * 只有**版本读不出**才取较轻的 nextRequestFails 并在 detail 注明未校准。
305
+ * tool/result 载体与代际无关(三种口径下都拒读),单独短路。
306
+ */
307
+ function missingMessageIdSeverity(facts, headerVersion) {
308
+ if (facts.missingMessageIdToolResult > 0)
309
+ return 'unloadable';
310
+ return headerVersion === undefined ? 'nextRequestFails' : 'unloadable';
311
+ }
312
+ /**
313
+ * 空 tool-call id 的严重级(t13 口径订正)。
314
+ *
315
+ * v4:`Session.fromRestore` 闸门拒读(原始输出:`tool call id requires a nonempty string`)→ unloadable;
316
+ * pre-v4:迁移器拒 → unloadable;只有版本读不出才取较轻值。
317
+ */
318
+ function emptyToolCallIdSeverity(headerVersion) {
319
+ return headerVersion === undefined ? 'nextRequestFails' : 'unloadable';
320
+ }
321
+ /**
322
+ * 重复通告同一 advertised tool call 的严重级(t16 口径订正)。
323
+ *
324
+ * v4:`Session.fromRestore` 闸门拒读(真机原始输出:
325
+ * `assistant/message repeats advertised tool call call_00_rCbEBWsfXGrfOhog9cud3396`)→ unloadable;
326
+ * pre-v4:v0→v1 迁移同样拒读(生态矩阵 #5909 记录同一结论)→ unloadable;
327
+ * 只有**版本读不出**才取较轻的 nextRequestFails 并在 detail 注明未校准。
328
+ */
329
+ function duplicateToolCallIdSeverity(headerVersion) {
330
+ return headerVersion === undefined ? 'nextRequestFails' : 'unloadable';
331
+ }
38
332
  /** 行档:解压各帧 → 逐行判定(只做能从字节证明的判定)。 */
39
333
  function scanRows(frames, bytes) {
40
334
  const rows = [];
335
+ const lifecycle = createLifecycleScan();
41
336
  let unparsable = 0;
42
337
  for (const frame of frames) {
43
338
  let text;
@@ -64,6 +359,12 @@ function scanRows(frames, bytes) {
64
359
  if (facts.turn !== undefined)
65
360
  entry.turn = facts.turn;
66
361
  rows.push(entry);
362
+ const type = facts.type;
363
+ if (type !== undefined && LIFECYCLE_ROW_TYPES.has(type)) {
364
+ const lifecycleRow = parseLifecycleRow(line, type);
365
+ if (lifecycleRow !== undefined)
366
+ applyLifecycleRow(lifecycle, lifecycleRow);
367
+ }
67
368
  }
68
369
  }
69
370
  // ① 字节相同且 seq 相同 → 重放族(同一 seq 被重写,重复的那份可零损失丢弃)
@@ -88,7 +389,7 @@ function scanRows(frames, bytes) {
88
389
  }
89
390
  // ③ 合成 closer 块:整块都是收尾类型且含 turn/end,且**后面还有行**(真实续写)
90
391
  const syntheticCloser = detectSyntheticCloser(rows);
91
- return { unparsable, duplicateRows, seqGaps, syntheticCloser };
392
+ return { unparsable, duplicateRows, seqGaps, syntheticCloser, lifecycle: finalizeLifecycleScan(lifecycle) };
92
393
  }
93
394
  /** 合成 closer 块探测 —— 委托 `session-row-facts.findSyntheticCloserRun` 的唯一实现(判据见其文件头)。 */
94
395
  function detectSyntheticCloser(rows) {
@@ -172,16 +473,41 @@ async function scanUnit(sessionsDir, projectKey, sessionId, withRows) {
172
473
  const structural = picked?.structural ?? { ok: false, headerUnreadable: true };
173
474
  const rows = picked?.rows;
174
475
  // 行档结论 → 深度校验事实(**只标 verified=true 当真的逐行解析过**)
175
- const deepIssueCodes = [];
476
+ const deepIssues = [];
176
477
  if (rows !== undefined) {
177
478
  if (rows.unparsable > 0)
178
- deepIssueCodes.push({ code: 'unparsable-event', detail: String(rows.unparsable) + ' line(s)' });
479
+ deepIssues.push({ code: 'unparsable-event', detail: String(rows.unparsable) + ' line(s)' });
179
480
  if (rows.duplicateRows > 0)
180
- deepIssueCodes.push({ code: 'replay-duplicate-rows', detail: String(rows.duplicateRows) });
481
+ deepIssues.push({ code: 'replay-duplicate-rows', detail: String(rows.duplicateRows) });
181
482
  if (rows.seqGaps > 0)
182
- deepIssueCodes.push({ code: 'seq-gap', detail: String(rows.seqGaps) });
483
+ deepIssues.push({ code: 'seq-gap', detail: String(rows.seqGaps) });
183
484
  if (rows.syntheticCloser)
184
- deepIssueCodes.push({ code: 'synthetic-closer' });
485
+ deepIssues.push({ code: 'synthetic-closer' });
486
+ // ---- T4:工具生命周期(只报不修;严重级 = 真 codec 实测口径,见文件头)----
487
+ const lifecycle = rows.lifecycle;
488
+ const version = header?.version;
489
+ if (lifecycle.missingMessageId > 0) {
490
+ const severity = missingMessageIdSeverity(lifecycle, version);
491
+ // 只有「版本读不出 → 取较轻值」需要注明未校准;v4/pre-v4 都已实测拒读。
492
+ const note = version === undefined ? '; codec-uncalibrated' : '';
493
+ deepIssues.push({ code: 'missing-message-id', severity, detail: String(lifecycle.missingMessageId) + ' row(s)' + note });
494
+ }
495
+ if (lifecycle.toolResultIdMismatch > 0) {
496
+ deepIssues.push({ code: 'tool-result-id-mismatch', severity: 'unloadable', detail: String(lifecycle.toolResultIdMismatch) });
497
+ }
498
+ if (lifecycle.emptyToolCallId > 0) {
499
+ const severity = emptyToolCallIdSeverity(version);
500
+ const note = version === undefined ? '; codec-uncalibrated' : '';
501
+ deepIssues.push({ code: 'empty-tool-call-id', severity, detail: String(lifecycle.emptyToolCallId) + note });
502
+ }
503
+ if (lifecycle.danglingClosedStep > 0) {
504
+ deepIssues.push({ code: 'dangling-tool-call', severity: 'unloadable', detail: String(lifecycle.danglingClosedStep) + ' unresolved' });
505
+ }
506
+ if (lifecycle.duplicateToolCallId > 0) {
507
+ const severity = duplicateToolCallIdSeverity(version);
508
+ const note = version === undefined ? '; codec-uncalibrated' : '';
509
+ deepIssues.push({ code: 'duplicate-tool-call-id', severity, detail: String(lifecycle.duplicateToolCallId) + note });
510
+ }
185
511
  }
186
512
  const input = {
187
513
  unitId: projectKey + '/' + sessionId,
@@ -195,10 +521,15 @@ async function scanUnit(sessionsDir, projectKey, sessionId, withRows) {
195
521
  ...(header?.version !== undefined ? { headerVersion: header.version } : {}),
196
522
  ...(names.length > 0 ? { sizeBytes: totalBytes } : {}),
197
523
  ...(rows !== undefined
198
- ? { deep: { verified: true, ...(deepIssueCodes.length > 0 ? { issues: deepIssueCodes } : {}) } }
524
+ ? { deep: { verified: true, ...(deepIssues.length > 0 ? { issues: deepIssues } : {}) } }
199
525
  : { deep: { verified: false, unverifiedReason: 'row-scan-not-run' } }),
200
526
  };
201
- return { input, unreadable };
527
+ // 首帧 header 的 id 是这一条会话的**真身份**(目录名可能是 `session-<uuid>`,也可能被手工改过);
528
+ // 采集器自证时优先用它,取不到才回落到目录名。
529
+ const out = { input, unreadable };
530
+ if (header?.id !== undefined)
531
+ out.headerSessionId = header.id;
532
+ return out;
202
533
  }
203
534
  /**
204
535
  * 扫描本机会话库(只读;**绝不写任何字节**,绝不抛错)。
@@ -236,6 +567,22 @@ export async function scanSessionHealth(options) {
236
567
  const projectKeys = projectDirs.filter((name) => PROJECT_KEY_RE.test(name));
237
568
  const inputs = [];
238
569
  const duplicates = new Set();
570
+ /**
571
+ * **采集器自证**用的已知会话 id 集合(调用方不提供 `knownSessionIds` 时用它)。
572
+ *
573
+ * 为什么需要:调用方(CLI / 救急台)不提供 `knownSessionIds` 时,旧实现用 `?? new Set()`
574
+ * 把「未知」伪造成「确知为空集」,于是「父对话存在性」判据对**每一条**子代理会话都成立 ——
575
+ * 真机实测 1199 条里 800 条被误报 `subagent-without-parent`(bySeverity.invisible=800)。
576
+ *
577
+ * **真实覆盖(两个来源,边界不同,别读成「一律含全部单元」)**:
578
+ * · **目录名**(第一趟,`sessionIdKey(sessionId)`):覆盖本机**全部**单元目录,不受 `maxUnits` 约束 ——
579
+ * 这是集合的主干,DSH 的目录名就是会话 id(`session-<uuid>` 与裸 `<uuid>` 由 `sessionIdKey` 归一);
580
+ * · **首帧 header.id**:只在**被扫到**的单元上补(`scanned < maxUnits` 的循环里)—— 它只多给出
581
+ * 「目录名被手工改过、header id 才是真身份」这种窄情况,**不覆盖**超出限额而未扫的单元。
582
+ * 两个来源都只**加**不进「未知」:集合越全 ⇒ 越少报「缺父」,所以这个窄边界只会让**超限那批单元**
583
+ * 的父对话在极端情形下被多报一次(保守方向),绝不会把「确知为空集」重新伪造成未知。
584
+ */
585
+ const knownIdsFromTraversal = new Set();
239
586
  // 第一趟:只数「同一会话 id 出现在几个 projectKey 目录」(重复 id 是启动级故障)
240
587
  const idLocations = new Map();
241
588
  for (const projectKey of projectKeys) {
@@ -250,6 +597,7 @@ export async function scanSessionHealth(options) {
250
597
  }
251
598
  for (const sessionId of dirs) {
252
599
  const key = sessionIdKey(sessionId);
600
+ knownIdsFromTraversal.add(key);
253
601
  const set = idLocations.get(key) ?? new Set();
254
602
  set.add(projectKey);
255
603
  idLocations.set(key, set);
@@ -302,14 +650,23 @@ export async function scanSessionHealth(options) {
302
650
  const withRows = deepLimit > 0 && deepUsed < deepLimit;
303
651
  if (withRows)
304
652
  deepUsed += 1;
305
- const { input, unreadable } = await scanUnit(sessionsDir, unit.projectKey, unit.sessionId, withRows);
653
+ const { input, unreadable, headerSessionId } = await scanUnit(sessionsDir, unit.projectKey, unit.sessionId, withRows);
306
654
  result.unreadableEntries += unreadable;
655
+ // 补一个 header.id 的归一键(只覆盖**被扫到**的单元,见 knownIdsFromTraversal 的覆盖说明):
656
+ // 目录名被手工改过时,header id 才是这条会话的真身份;取不到 header 就不补,绝不用目录名冒充。
657
+ if (headerSessionId !== undefined)
658
+ knownIdsFromTraversal.add(sessionIdKey(headerSessionId));
307
659
  inputs.push(input);
308
660
  }
309
661
  const analyzed = analyzeSessionHealth(inputs, {
310
662
  ...(options.targetFormatVersion !== undefined ? { targetFormatVersion: options.targetFormatVersion } : {}),
663
+ // workspaceKeys 维持「size > 0 才判未登记」:调用方读不到注册表与「注册表为空」不可区分,
664
+ // 把空集当确知会让每一条会话都报未登记(另一种误报)。这一点与 knownSessionIds 不同 —— 那里
665
+ // 「未知」由调用方用 undefined 显式表达。
311
666
  workspaceKeys: options.workspaceKeys ?? new Set(),
312
- knownSessionIds: options.knownSessionIds ?? new Set(),
667
+ // 「未知」绝不伪造成「确知为空集」:调用方提供了就用它的(UI 的 parentRelations 更权威);
668
+ // 没提供 → 用本次遍历到的全部单元 id 自证。
669
+ knownSessionIds: options.knownSessionIds ?? knownIdsFromTraversal,
313
670
  duplicateSessionIds: options.duplicateSessionIds ?? duplicates,
314
671
  });
315
672
  result.rows = analyzed.rows;
@@ -0,0 +1,98 @@
1
+ import type { SessionRepairActionKind, SessionRepairReason, SessionRepairSummary } from '../core/session-repair.ts';
2
+ import type { PathMappingRule } from '../core/path-mapping.ts';
3
+ /** 计划里的一个动作(**不含绝对路径**:身份用 `<fromProjectKey>/<sessionId>`)。 */
4
+ export interface SessionLayoutPlanItem {
5
+ unitId: string;
6
+ sessionId: string;
7
+ kind: SessionRepairActionKind;
8
+ fromProjectKey: string;
9
+ toProjectKey?: string;
10
+ /** 需要改写首帧 cwd(面板入口不支持映射 ⇒ 恒缺席;字段与 core 同形,留给以后) */
11
+ rewrite?: {
12
+ from: string;
13
+ to: string;
14
+ };
15
+ reason: SessionRepairReason;
16
+ /** 是否真的落盘(false = 只报告:缺 cwd / 加锁 / 未点名 keep 的重复项) */
17
+ applies: boolean;
18
+ }
19
+ export interface SessionLayoutPlan {
20
+ ok: boolean;
21
+ /** 只读计划:`POST /sessions/layout` 在 apply!==true 时只调这里,**零写入** */
22
+ readOnly: true;
23
+ /** 会话根读不出来(机器码;不含绝对路径) */
24
+ reason?: 'sessions-root-unreadable';
25
+ summary: SessionRepairSummary;
26
+ actions: SessionLayoutPlanItem[];
27
+ /** 与 core `sessionRepairNeedsAttention` 同源;另把「keep 被拒」也算作需要介入 */
28
+ needsAttention: boolean;
29
+ /** 有重复 id 但调用方未点名(或 keep 指向非候选副本)的 sessionId —— 面板据此要求用户选保留哪一份 */
30
+ needsKeep: string[];
31
+ }
32
+ /** 逐条失败/拒绝的机器可读原因。 */
33
+ export type SessionLayoutApplyReason = 'locked' | 'busy' | 'no-cwd' | 'inconsistent-generations' | 'missing-keep' | 'keep-not-a-candidate' | 'target-exists' | 'quarantine-exists' | 'rewrite-failed' | 'missing-target-key' | 'move-failed' | 'reindex-failed' | 'not-found' | 'io-error';
34
+ export interface SessionLayoutApplyItem {
35
+ unitId: string;
36
+ sessionId: string;
37
+ action: SessionRepairActionKind;
38
+ ok: boolean;
39
+ reason?: SessionLayoutApplyReason;
40
+ /** 隔离去向(**相对 `<home>/sessions`**;不含绝对路径) */
41
+ quarantineDir?: string;
42
+ /** 归位后的新身份 `<toProjectKey>/<sessionId>` */
43
+ movedUnitId?: string;
44
+ /** 失败后是否已成功回滚(回到原状态) */
45
+ rolledBack?: boolean;
46
+ /** 该条需要用户介入(被门拒绝 / 无法判定 / 失败) */
47
+ needsAttention?: boolean;
48
+ }
49
+ export interface SessionLayoutApplyResult {
50
+ ok: boolean;
51
+ applied: number;
52
+ failed: number;
53
+ skipped: number;
54
+ results: SessionLayoutApplyItem[];
55
+ /** 整体未执行的机器码(没有刷新端口 / 会话根读不出来) */
56
+ reason?: 'reindex-unavailable' | 'sessions-root-unreadable';
57
+ }
58
+ export interface SessionLayoutPlanOptions {
59
+ homeDir: string;
60
+ /** sessionId → 要保留的副本 unitId(`<projectKey>/<sessionId>`);缺 = 该 id 的重复项一律拒绝执行 */
61
+ keep?: Record<string, string>;
62
+ /**
63
+ * 可选路径映射(CLI `--map` 语义,逐条交给 core 规划器)。**面板入口暂不暴露** ⇒ 缺省无映射、
64
+ * `rewrite` 恒缺席(响应因此不含任何绝对路径);留给以后需要「跨机重定基后归位」的调用方。
65
+ */
66
+ mappings?: readonly PathMappingRule[];
67
+ }
68
+ export interface SessionLayoutApplyOptions extends SessionLayoutPlanOptions {
69
+ /** 只处理这些 unitId(缺省 = 计划里全部可执行项) */
70
+ unitIds?: readonly string[];
71
+ /**
72
+ * 宿主注入的索引刷新(`SessionStoreFacade.reindexSessionHeader`)。
73
+ * **缺省 = 一条也不执行**(如实拒绝:没有刷新能力就不动盘,否则运行中的 DSH 会看到错位状态)。
74
+ */
75
+ reindex?: (sessionId: string) => Promise<boolean>;
76
+ /** 注入当前时间(静止期判定;测试固定用) */
77
+ now?: () => Date;
78
+ /** 测试注入隔离目录时间戳;缺省 = ISO 时间戳(与 CLI 同形:`:`/`.` 换成 `-`) */
79
+ stamp?: string;
80
+ /**
81
+ * 改写原语(缺省 = `utils/session-log.ts` 的 `rewriteSessionLogDir`)。
82
+ * 只为**故障注入测试**存在(「改写失败 → 不搬目录 / 回滚」这条路径否则无法覆盖)。
83
+ */
84
+ rewriteDir?: (dir: string, newCwd: string) => Promise<{
85
+ ok: boolean;
86
+ reason?: string;
87
+ }>;
88
+ }
89
+ /** 计划视图(剥离绝对路径):`POST /sessions/layout` 的 `apply!==true` 分支只调它。 */
90
+ export declare function planSessionLayoutRepair(options: SessionLayoutPlanOptions): Promise<SessionLayoutPlan>;
91
+ /**
92
+ * 应用(逐条执行;失败逐条回滚)。
93
+ *
94
+ * 计数口径(与 CLI 的退出码同源):`applied` = 真的写成/搬动的条数;`failed` = 被门拒绝 / 被护栏拒绝 /
95
+ * 写失败(含回滚)的条数;`skipped` = 规划器判定「只报告」的条数(缺 cwd / 加锁 / 多 generation 不一致)。
96
+ * `ok = failed === 0`。
97
+ */
98
+ export declare function applySessionLayoutRepair(options: SessionLayoutApplyOptions): Promise<SessionLayoutApplyResult>;