dsh-config-manager 0.1.71 → 0.1.73

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 (596) hide show
  1. package/README.md +149 -98
  2. package/README.zh-CN.md +245 -88
  3. package/lib/adapters/agent-instructions.js +1 -1
  4. package/lib/adapters/file-collection.js +1 -1
  5. package/lib/adapters/index.d.ts +2 -1
  6. package/lib/adapters/index.js +6 -1
  7. package/lib/adapters/link-report.d.ts +1 -0
  8. package/lib/adapters/link-report.js +27 -3
  9. package/lib/adapters/messages.d.ts +21 -0
  10. package/lib/adapters/messages.js +57 -0
  11. package/lib/adapters/plugin-data.d.ts +114 -0
  12. package/lib/adapters/plugin-data.js +309 -0
  13. package/lib/adapters/plugin-files.js +3 -2
  14. package/lib/adapters/plugins.js +4 -3
  15. package/lib/adapters/prompts.d.ts +1 -0
  16. package/lib/adapters/prompts.js +7 -1
  17. package/lib/adapters/self.d.ts +8 -2
  18. package/lib/adapters/self.js +3 -2
  19. package/lib/adapters/sessions.d.ts +1 -0
  20. package/lib/adapters/sessions.js +4 -2
  21. package/lib/adapters/test-helpers.d.ts +9 -0
  22. package/lib/adapters/test-helpers.js +14 -0
  23. package/lib/adapters/ui.d.ts +2 -2
  24. package/lib/adapters/ui.js +25 -25
  25. package/lib/adapters/workspaces.d.ts +1 -0
  26. package/lib/adapters/workspaces.js +7 -3
  27. package/lib/cli/actions.d.ts +9 -1
  28. package/lib/cli/actions.js +5 -2
  29. package/lib/cli/help.js +23 -10
  30. package/lib/cli/index.d.ts +23 -0
  31. package/lib/cli/index.js +90 -32
  32. package/lib/cli/sessions-inspect.js +3 -1
  33. package/lib/cli/sessions-repair.js +25 -6
  34. package/lib/cli/web/page.d.ts +12 -0
  35. package/lib/cli/web/page.js +21 -12
  36. package/lib/cli/web/routes.js +2 -2
  37. package/lib/cli/web/server.js +4 -4
  38. package/lib/client.d.ts +1442 -494
  39. package/lib/client.js +15326 -8520
  40. package/lib/core/analyzer.d.ts +11 -2
  41. package/lib/core/analyzer.js +31 -8
  42. package/lib/core/backup-plan.d.ts +10 -0
  43. package/lib/core/backup-plan.js +51 -20
  44. package/lib/core/backup-verify.js +24 -23
  45. package/lib/core/backup.d.ts +25 -1
  46. package/lib/core/backup.js +99 -28
  47. package/lib/core/boot-rescue.js +4 -3
  48. package/lib/core/boot-safety.js +2 -1
  49. package/lib/core/command-rollback.d.ts +182 -0
  50. package/lib/core/command-rollback.js +293 -0
  51. package/lib/core/consult-source.js +7 -6
  52. package/lib/core/disk-usage.d.ts +74 -10
  53. package/lib/core/disk-usage.js +67 -4
  54. package/lib/core/engine-locale.d.ts +87 -0
  55. package/lib/core/engine-locale.js +125 -0
  56. package/lib/core/exporter.js +3 -3
  57. package/lib/core/fork-reconcile.d.ts +128 -0
  58. package/lib/core/fork-reconcile.js +150 -0
  59. package/lib/core/journal.js +6 -5
  60. package/lib/core/local-plugin-host.js +3 -2
  61. package/lib/core/local-plugin-pack.js +11 -6
  62. package/lib/core/messages-core.d.ts +294 -0
  63. package/lib/core/messages-core.js +619 -0
  64. package/lib/core/messages.d.ts +61 -2
  65. package/lib/core/messages.js +118 -13
  66. package/lib/core/migration-consult.js +21 -20
  67. package/lib/core/migration-history.js +19 -5
  68. package/lib/core/model-tools.d.ts +0 -22
  69. package/lib/core/model-tools.js +50 -27
  70. package/lib/core/phase3-host.js +30 -5
  71. package/lib/core/plugin-cli.js +18 -19
  72. package/lib/core/plugin-data-registry.d.ts +124 -0
  73. package/lib/core/plugin-data-registry.js +198 -0
  74. package/lib/core/reconcile.d.ts +2 -14
  75. package/lib/core/reconcile.js +87 -17
  76. package/lib/core/recovery-orchestrator.js +19 -18
  77. package/lib/core/reinstall.d.ts +4 -0
  78. package/lib/core/reinstall.js +26 -21
  79. package/lib/core/restore.js +20 -17
  80. package/lib/core/rollback-gate.d.ts +33 -0
  81. package/lib/core/rollback-gate.js +57 -0
  82. package/lib/core/rollback-ignore-rules.d.ts +31 -0
  83. package/lib/core/rollback-ignore-rules.js +140 -0
  84. package/lib/core/rollback-ledger.d.ts +248 -0
  85. package/lib/core/rollback-ledger.js +1282 -0
  86. package/lib/core/rollback-point.d.ts +1391 -0
  87. package/lib/core/rollback-point.js +3503 -0
  88. package/lib/core/rollback.js +6 -4
  89. package/lib/core/run-registry.js +6 -5
  90. package/lib/core/session-health.d.ts +15 -0
  91. package/lib/core/session-meta.d.ts +42 -2
  92. package/lib/core/session-meta.js +140 -1
  93. package/lib/core/session-title.d.ts +28 -0
  94. package/lib/core/session-title.js +185 -0
  95. package/lib/core/startup-barrier.d.ts +24 -0
  96. package/lib/core/startup-barrier.js +22 -0
  97. package/lib/core/transaction-coordinator.js +4 -3
  98. package/lib/core/types.d.ts +37 -1
  99. package/lib/core/validator.js +5 -4
  100. package/lib/core/verify-recovery.js +31 -30
  101. package/lib/foreign/bundle.js +3 -2
  102. package/lib/foreign/continue.js +2 -1
  103. package/lib/foreign/crush.js +2 -1
  104. package/lib/foreign/gemini.js +2 -1
  105. package/lib/foreign/goose.js +2 -1
  106. package/lib/foreign/kilocode.js +2 -1
  107. package/lib/foreign/kimi.js +2 -1
  108. package/lib/foreign/messages.d.ts +15 -0
  109. package/lib/foreign/messages.js +40 -0
  110. package/lib/foreign/mimocode.js +2 -1
  111. package/lib/foreign/openclaw.js +2 -1
  112. package/lib/foreign/opencode.js +2 -1
  113. package/lib/foreign/pi.js +2 -1
  114. package/lib/foreign/qoder.js +2 -1
  115. package/lib/foreign/qwen.js +2 -1
  116. package/lib/foreign/registry.d.ts +1 -1
  117. package/lib/foreign/registry.js +8 -7
  118. package/lib/foreign/teleagent.js +2 -1
  119. package/lib/foreign/workbuddy.js +2 -1
  120. package/lib/foreign/zcode.js +2 -1
  121. package/lib/foreign/zed.js +2 -1
  122. package/lib/index.d.ts +227 -7
  123. package/lib/index.js +799 -54
  124. package/lib/market/git-file-writer.js +20 -19
  125. package/lib/market/github-repos.js +35 -19
  126. package/lib/market/index-parser.js +31 -30
  127. package/lib/market/messages-market.d.ts +138 -0
  128. package/lib/market/messages-market.js +305 -0
  129. package/lib/market/my-repo.d.ts +2 -2
  130. package/lib/market/my-repo.js +32 -29
  131. package/lib/market/prepare.js +18 -15
  132. package/lib/market/security.js +20 -19
  133. package/lib/market/types.js +2 -1
  134. package/lib/market/url.js +2 -1
  135. package/lib/migrations/index.js +6 -5
  136. package/lib/migrations/messages-migrations.d.ts +32 -0
  137. package/lib/migrations/messages-migrations.js +58 -0
  138. package/lib/profiles/dsh-profile-launcher.js +2 -1
  139. package/lib/profiles/dsh-profile-runtime.js +2 -1
  140. package/lib/profiles/messages.d.ts +7 -0
  141. package/lib/profiles/messages.js +27 -0
  142. package/lib/routes/backup.js +18 -2
  143. package/lib/routes/import.js +3 -3
  144. package/lib/routes/index.d.ts +1 -1
  145. package/lib/routes/kit.d.ts +3 -2
  146. package/lib/routes/kit.js +6 -1
  147. package/lib/routes/market.js +1 -1
  148. package/lib/routes/messages.d.ts +43 -0
  149. package/lib/routes/messages.js +102 -0
  150. package/lib/routes/prefs.js +26 -3
  151. package/lib/routes/profiles.js +12 -6
  152. package/lib/routes/recovery.d.ts +21 -0
  153. package/lib/routes/recovery.js +478 -94
  154. package/lib/routes/snapshots.js +51 -4
  155. package/lib/routes/sync.js +79 -17
  156. package/lib/schema/config.d.ts +11 -1
  157. package/lib/schema/config.js +52 -20
  158. package/lib/schema/index.d.ts +8 -6
  159. package/lib/schema/index.js +8 -6
  160. package/lib/schema/manifest.js +19 -18
  161. package/lib/schema/messages.d.ts +47 -0
  162. package/lib/schema/messages.js +110 -0
  163. package/lib/schema/section-registry.d.ts +42 -15
  164. package/lib/schema/section-registry.js +67 -32
  165. package/lib/schema/types.d.ts +1 -1
  166. package/lib/schema/versions.js +6 -5
  167. package/lib/security/encryption.js +16 -15
  168. package/lib/security/integrity.js +8 -7
  169. package/lib/security/messages.d.ts +30 -0
  170. package/lib/security/messages.js +77 -0
  171. package/lib/security/redaction.js +12 -0
  172. package/lib/security/secret-scanner.js +16 -13
  173. package/lib/security/vault.js +2 -1
  174. package/lib/security/zip-security.js +4 -3
  175. package/lib/sync/ancestor.js +2 -1
  176. package/lib/sync/autosync-config.d.ts +11 -0
  177. package/lib/sync/autosync-config.js +12 -0
  178. package/lib/sync/autosync-scheduler.d.ts +11 -1
  179. package/lib/sync/autosync-scheduler.js +115 -76
  180. package/lib/sync/backup-files.js +3 -2
  181. package/lib/sync/backup-scheduler.js +6 -5
  182. package/lib/sync/blob-store.js +2 -1
  183. package/lib/sync/git/git-transport.d.ts +55 -0
  184. package/lib/sync/git/git-transport.js +152 -2
  185. package/lib/sync/github-auth.js +10 -9
  186. package/lib/sync/layout.js +13 -12
  187. package/lib/sync/messages.d.ts +63 -0
  188. package/lib/sync/messages.js +149 -0
  189. package/lib/sync/review-queue.js +6 -5
  190. package/lib/sync/s3/messages.d.ts +7 -0
  191. package/lib/sync/s3/messages.js +16 -0
  192. package/lib/sync/s3/s3-providers.d.ts +4 -1
  193. package/lib/sync/s3/s3-providers.js +5 -5
  194. package/lib/sync/s3/s3-transport.js +1 -1
  195. package/lib/sync/s3/sigv4.js +2 -1
  196. package/lib/sync/snapshot-crypto.js +6 -5
  197. package/lib/sync/snapshot-json.js +3 -2
  198. package/lib/sync/sync-engine.d.ts +58 -22
  199. package/lib/sync/sync-engine.js +119 -48
  200. package/lib/sync/sync-history.js +7 -6
  201. package/lib/sync/sync-selection.d.ts +15 -4
  202. package/lib/sync/sync-selection.js +17 -5
  203. package/lib/sync/ui-prefs.d.ts +22 -0
  204. package/lib/sync/ui-prefs.js +28 -0
  205. package/lib/sync/webdav/webdav-transport.d.ts +1 -0
  206. package/lib/sync/webdav/webdav-transport.js +4 -1
  207. package/lib/ui/artifact-view.js +4 -3
  208. package/lib/ui/commands.d.ts +1 -1
  209. package/lib/ui/commands.js +10 -0
  210. package/lib/ui/diff-view.d.ts +10 -0
  211. package/lib/ui/diff-view.js +17 -0
  212. package/lib/ui/disk-usage-view.d.ts +33 -4
  213. package/lib/ui/disk-usage-view.js +29 -1
  214. package/lib/ui/errors.d.ts +1 -1
  215. package/lib/ui/errors.js +4 -3
  216. package/lib/ui/export-flow.js +26 -20
  217. package/lib/ui/foreign-view.d.ts +5 -1
  218. package/lib/ui/foreign-view.js +26 -2
  219. package/lib/ui/i18n.d.ts +68 -1
  220. package/lib/ui/i18n.js +120 -2
  221. package/lib/ui/import-wizard.d.ts +7 -0
  222. package/lib/ui/import-wizard.js +13 -10
  223. package/lib/ui/progress.d.ts +2 -2
  224. package/lib/ui/progress.js +1 -2
  225. package/lib/ui/report.d.ts +5 -5
  226. package/lib/ui/report.js +6 -6
  227. package/lib/ui/rollback-view.d.ts +815 -0
  228. package/lib/ui/rollback-view.js +738 -0
  229. package/lib/ui/selection-model.d.ts +41 -0
  230. package/lib/ui/selection-model.js +30 -0
  231. package/lib/ui/session-inventory-view.d.ts +97 -3
  232. package/lib/ui/session-inventory-view.js +76 -2
  233. package/lib/ui/session-issue-labels.d.ts +43 -0
  234. package/lib/ui/session-issue-labels.js +48 -0
  235. package/lib/ui/sync-settings-view.d.ts +33 -0
  236. package/lib/ui/sync-settings-view.js +40 -0
  237. package/lib/ui/test-helpers.js +2 -1
  238. package/lib/ui/types.d.ts +8 -3
  239. package/lib/ui/types.js +1 -1
  240. package/lib/utils/atomic-write.js +6 -5
  241. package/lib/utils/bundle-scan.d.ts +11 -1
  242. package/lib/utils/bundle-scan.js +12 -1
  243. package/lib/utils/env-lock.d.ts +10 -0
  244. package/lib/utils/env-lock.js +73 -46
  245. package/lib/utils/json.js +6 -5
  246. package/lib/utils/messages-utils.d.ts +96 -0
  247. package/lib/utils/messages-utils.js +216 -0
  248. package/lib/utils/proxy.js +18 -5
  249. package/lib/utils/recursive-walk.d.ts +22 -0
  250. package/lib/utils/recursive-walk.js +75 -1
  251. package/lib/utils/session-log-repair.js +10 -9
  252. package/lib/utils/zip.js +27 -26
  253. package/package.json +12 -2
  254. package/src/adapters/agent-instructions.ts +1 -1
  255. package/src/adapters/file-collection.test.ts +1 -0
  256. package/src/adapters/file-collection.ts +1 -1
  257. package/src/adapters/files.test.ts +27 -0
  258. package/src/adapters/index.ts +6 -1
  259. package/src/adapters/link-report.test.ts +22 -2
  260. package/src/adapters/link-report.ts +29 -4
  261. package/src/adapters/messages.ts +61 -0
  262. package/src/adapters/plugin-data.test.ts +225 -0
  263. package/src/adapters/plugin-data.ts +351 -0
  264. package/src/adapters/plugin-files.ts +3 -2
  265. package/src/adapters/plugins.ts +4 -3
  266. package/src/adapters/prompts.ts +7 -1
  267. package/src/adapters/self.test.ts +26 -0
  268. package/src/adapters/self.ts +10 -3
  269. package/src/adapters/sessions.ts +4 -2
  270. package/src/adapters/settings.test.ts +38 -1
  271. package/src/adapters/test-helpers.ts +15 -0
  272. package/src/adapters/ui.test.ts +2 -2
  273. package/src/adapters/ui.ts +27 -26
  274. package/src/adapters/workspaces.ts +7 -3
  275. package/src/cli/actions.test.ts +28 -0
  276. package/src/cli/actions.ts +11 -2
  277. package/src/cli/help.ts +23 -10
  278. package/src/cli/index-guards.test.ts +193 -0
  279. package/src/cli/index.ts +106 -31
  280. package/src/cli/sessions-inspect.test.ts +15 -1
  281. package/src/cli/sessions-inspect.ts +3 -1
  282. package/src/cli/sessions-repair.test.ts +84 -0
  283. package/src/cli/sessions-repair.ts +33 -7
  284. package/src/cli/web/console-route-parity.test.ts +115 -0
  285. package/src/cli/web/page.ts +23 -12
  286. package/src/cli/web/routes.ts +2 -1
  287. package/src/cli/web/server.ts +4 -4
  288. package/src/cli/web/web.test.ts +2 -2
  289. package/src/client/ConfigManagerSection.tsx +30 -2
  290. package/src/client/about/AboutPanel.tsx +2 -2
  291. package/src/client/about/PluginUpdateDialog.tsx +1 -1
  292. package/src/client/about/ReleaseNotesDialog.tsx +2 -2
  293. package/src/client/about/about-view.test.ts +13 -3
  294. package/src/client/about/about-view.ts +10 -6
  295. package/src/client/about/release-notes-view.test.ts +14 -4
  296. package/src/client/about/release-notes-view.ts +4 -2
  297. package/src/client/api.ts +18 -6
  298. package/src/client/chat/ChatEntryView.tsx +411 -0
  299. package/src/client/chat/RollbackDiffTab.tsx +108 -0
  300. package/src/client/chat/RollbackFileIcon.tsx +96 -0
  301. package/src/client/chat/RollbackReviewDialog.tsx +607 -0
  302. package/src/client/chat/chat-entry-view.test.ts +1174 -0
  303. package/src/client/chat/chat-entry-view.ts +758 -0
  304. package/src/client/chat/chat-locales.ts +152 -0
  305. package/src/client/chat/chat-node-registration.ts +64 -0
  306. package/src/client/chat/chat-point-window.ts +183 -0
  307. package/src/client/chat/chat-slots.ts +107 -0
  308. package/src/client/chat/rollback-diff-tab.test.ts +105 -0
  309. package/src/client/chat/rollback-diff-tab.ts +174 -0
  310. package/src/client/chat/rollback-entry-view.test.ts +1469 -0
  311. package/src/client/chat/rollback-entry-view.ts +946 -0
  312. package/src/client/chat/sidebar-slots.ts +59 -0
  313. package/src/client/common/ConfirmDialog.tsx +7 -3
  314. package/src/client/common/ContentPicker.tsx +48 -10
  315. package/src/client/common/DiffTable.tsx +133 -0
  316. package/src/client/common/ErrorBanner.tsx +4 -4
  317. package/src/client/common/Icon.tsx +2 -0
  318. package/src/client/common/Modal.tsx +8 -1
  319. package/src/client/common/ProgressBar.tsx +8 -2
  320. package/src/client/common/ReportView.tsx +4 -4
  321. package/src/client/common/RunsCenter.tsx +1 -0
  322. package/src/client/common/client-route-parity.test.ts +84 -0
  323. package/src/client/common/info-hint-guard.test.ts +46 -13
  324. package/src/client/common/picker-lazy-units.test.ts +42 -0
  325. package/src/client/common/plan-text-redaction.test.ts +17 -6
  326. package/src/client/common/progress-view-edge.test.ts +8 -7
  327. package/src/client/common/progress-view.test.ts +9 -9
  328. package/src/client/common/progress-view.ts +2 -2
  329. package/src/client/common/routes.ts +38 -0
  330. package/src/client/common/section-labels.test.ts +1 -0
  331. package/src/client/common/section-labels.ts +2 -0
  332. package/src/client/common/ui.tsx +73 -10
  333. package/src/client/config-manager.module.css +1109 -31
  334. package/src/client/css-scale-guard.test.ts +30 -0
  335. package/src/client/environment/EnvironmentPanel.tsx +11 -4
  336. package/src/client/environment/maintenance/DiskUsageCard.tsx +23 -2
  337. package/src/client/export/ExportView.tsx +13 -8
  338. package/src/client/history/HistoryPanel.tsx +4 -3
  339. package/src/client/history/history-api.ts +2 -2
  340. package/src/client/history/history-locales.ts +5 -1
  341. package/src/client/home/HomePanel.tsx +29 -8
  342. package/src/client/import/ForeignImportView.tsx +4 -5
  343. package/src/client/import/ImportWizardView.tsx +5 -3
  344. package/src/client/import/import-wizard-split.test.ts +3 -1
  345. package/src/client/import/import-wizard-steps.tsx +9 -9
  346. package/src/client/index.ts +232 -3
  347. package/src/client/library/LibraryActions.tsx +32 -7
  348. package/src/client/library/LibraryPanel.tsx +23 -3
  349. package/src/client/library/RestorePlanView.tsx +9 -66
  350. package/src/client/locales.ts +62 -9
  351. package/src/client/lucide-icons.d.ts +1 -0
  352. package/src/client/market/MarketPanel.tsx +13 -2
  353. package/src/client/market/MyConfigsView.tsx +5 -4
  354. package/src/client/market/MyConfigsWizard.tsx +10 -8
  355. package/src/client/market/market-api.test.ts +9 -8
  356. package/src/client/market/market-api.ts +2 -2
  357. package/src/client/market/market-locales.ts +3 -0
  358. package/src/client/market/market-view.test.ts +6 -6
  359. package/src/client/market/market-view.ts +3 -3
  360. package/src/client/market/my-configs-api.ts +2 -2
  361. package/src/client/market/my-configs-view.test.ts +23 -22
  362. package/src/client/market/my-configs-view.ts +8 -8
  363. package/src/client/recovery/IncompleteCopiesSection.tsx +2 -1
  364. package/src/client/recovery/RecoveryPanel.tsx +442 -283
  365. package/src/client/recovery/RollbackCard.tsx +859 -0
  366. package/src/client/recovery/RollbackDiffPane.tsx +69 -0
  367. package/src/client/recovery/RollbackFileChanges.tsx +163 -0
  368. package/src/client/recovery/incident-api.ts +2 -2
  369. package/src/client/recovery/recovery-api.ts +144 -6
  370. package/src/client/recovery/recovery-locales.test.ts +299 -0
  371. package/src/client/recovery/recovery-locales.ts +520 -149
  372. package/src/client/recovery/recovery-panel-layout.test.ts +132 -2
  373. package/src/client/recovery/rollback-enabled.ts +41 -0
  374. package/src/client/recovery/rollback-file-changes.test.ts +114 -0
  375. package/src/client/recovery/session-dialog-mount.test.ts +54 -0
  376. package/src/client/run-store.ts +84 -21
  377. package/src/client/sync/AutosyncCard.tsx +25 -1
  378. package/src/client/sync/ChannelClearConfirmDialog.tsx +6 -3
  379. package/src/client/sync/ChannelConfigDialog.tsx +10 -10
  380. package/src/client/sync/DecryptPasswordCard.tsx +2 -2
  381. package/src/client/sync/SecurityOptionsCard.tsx +27 -17
  382. package/src/client/sync/SyncChannelEntryCard.tsx +14 -2
  383. package/src/client/sync/SyncConfirmView.tsx +46 -8
  384. package/src/client/sync/SyncLogList.tsx +1 -1
  385. package/src/client/sync/SyncPanel.tsx +31 -23
  386. package/src/client/sync/SyncRepositoryPicker.tsx +3 -5
  387. package/src/client/sync/SyncSectionPickerDialog.tsx +11 -2
  388. package/src/client/sync/SyncSettingsView.test.ts +20 -1
  389. package/src/client/sync/UnreadableSnapshotsBanner.tsx +1 -1
  390. package/src/client/sync/WorkDirCard.tsx +112 -0
  391. package/src/client/sync/history-model.test.ts +42 -9
  392. package/src/client/sync/history-model.ts +30 -15
  393. package/src/client/sync/sync-api.test.ts +57 -33
  394. package/src/client/sync/sync-api.ts +80 -6
  395. package/src/client/sync/sync-locales.ts +104 -3
  396. package/src/client/sync/sync-view-v2.test.ts +65 -13
  397. package/src/client/sync/sync-view.test.ts +107 -52
  398. package/src/client/sync/sync-view.ts +124 -41
  399. package/src/core/analyzer.ts +31 -9
  400. package/src/core/backup-plan.test.ts +48 -5
  401. package/src/core/backup-plan.ts +51 -20
  402. package/src/core/backup-verify.ts +24 -23
  403. package/src/core/backup.test.ts +76 -0
  404. package/src/core/backup.ts +105 -29
  405. package/src/core/boot-rescue.ts +4 -3
  406. package/src/core/boot-safety.ts +2 -1
  407. package/src/core/command-rollback.test.ts +536 -0
  408. package/src/core/command-rollback.ts +406 -0
  409. package/src/core/consult-source.ts +7 -6
  410. package/src/core/disk-usage.test.ts +257 -1
  411. package/src/core/disk-usage.ts +134 -14
  412. package/src/core/engine-locale.test.ts +593 -0
  413. package/src/core/engine-locale.ts +175 -0
  414. package/src/core/exporter.ts +3 -3
  415. package/src/core/fork-reconcile.test.ts +279 -0
  416. package/src/core/fork-reconcile.ts +234 -0
  417. package/src/core/journal.ts +6 -5
  418. package/src/core/local-plugin-host.ts +3 -2
  419. package/src/core/local-plugin-pack.ts +11 -6
  420. package/src/core/messages-core.ts +661 -0
  421. package/src/core/messages.ts +125 -17
  422. package/src/core/migration-consult.ts +21 -20
  423. package/src/core/migration-history.ts +19 -5
  424. package/src/core/model-tools.ts +28 -27
  425. package/src/core/phase3-host.ts +21 -5
  426. package/src/core/phase3-p2b-fingerprint.test.ts +22 -0
  427. package/src/core/plugin-cli.ts +18 -19
  428. package/src/core/plugin-data-registry.test.ts +113 -0
  429. package/src/core/plugin-data-registry.ts +261 -0
  430. package/src/core/reconcile.test.ts +117 -0
  431. package/src/core/reconcile.ts +85 -17
  432. package/src/core/recovery-orchestrator.ts +19 -18
  433. package/src/core/reinstall.ts +26 -21
  434. package/src/core/restore.ts +20 -17
  435. package/src/core/rollback-file-diff.test.ts +159 -0
  436. package/src/core/rollback-gate.test.ts +66 -0
  437. package/src/core/rollback-gate.ts +61 -0
  438. package/src/core/rollback-ignore-rules.ts +126 -0
  439. package/src/core/rollback-ledger.test.ts +655 -0
  440. package/src/core/rollback-ledger.ts +1314 -0
  441. package/src/core/rollback-point.test.ts +2853 -0
  442. package/src/core/rollback-point.ts +4340 -0
  443. package/src/core/rollback.ts +5 -3
  444. package/src/core/run-registry.ts +6 -5
  445. package/src/core/session-health.ts +15 -0
  446. package/src/core/session-meta.test.ts +117 -1
  447. package/src/core/session-meta.ts +149 -2
  448. package/src/core/session-title.test.ts +127 -0
  449. package/src/core/session-title.ts +189 -0
  450. package/src/core/startup-barrier.test.ts +69 -0
  451. package/src/core/startup-barrier.ts +27 -0
  452. package/src/core/transaction-coordinator.ts +4 -3
  453. package/src/core/types.ts +37 -1
  454. package/src/core/validator.ts +5 -4
  455. package/src/core/verify-recovery.ts +31 -30
  456. package/src/foreign/bundle.ts +3 -2
  457. package/src/foreign/continue.ts +2 -1
  458. package/src/foreign/crush.ts +2 -1
  459. package/src/foreign/file-budget.test.ts +2 -0
  460. package/src/foreign/gemini.ts +2 -1
  461. package/src/foreign/goose.ts +2 -1
  462. package/src/foreign/kilocode.ts +2 -1
  463. package/src/foreign/kimi.ts +2 -1
  464. package/src/foreign/messages.ts +44 -0
  465. package/src/foreign/mimocode.ts +2 -1
  466. package/src/foreign/openclaw.ts +2 -1
  467. package/src/foreign/opencode.ts +2 -1
  468. package/src/foreign/pi.ts +2 -1
  469. package/src/foreign/qoder.ts +2 -1
  470. package/src/foreign/qwen.ts +2 -1
  471. package/src/foreign/registry.test.ts +1 -1
  472. package/src/foreign/registry.ts +8 -7
  473. package/src/foreign/teleagent.ts +2 -1
  474. package/src/foreign/workbuddy.ts +2 -1
  475. package/src/foreign/zcode.ts +2 -1
  476. package/src/foreign/zed.ts +2 -1
  477. package/src/index.facade.test.ts +340 -1
  478. package/src/index.ts +876 -57
  479. package/src/market/git-file-writer.ts +20 -19
  480. package/src/market/github-repos.test.ts +23 -0
  481. package/src/market/github-repos.ts +37 -19
  482. package/src/market/index-parser.ts +31 -30
  483. package/src/market/messages-market.ts +325 -0
  484. package/src/market/my-repo.ts +32 -29
  485. package/src/market/prepare.ts +18 -15
  486. package/src/market/security.ts +20 -19
  487. package/src/market/types.ts +2 -1
  488. package/src/market/url.ts +2 -1
  489. package/src/migrations/index.ts +6 -5
  490. package/src/migrations/messages-migrations.ts +59 -0
  491. package/src/profiles/dsh-profile-launcher.ts +2 -1
  492. package/src/profiles/dsh-profile-runtime.ts +2 -1
  493. package/src/profiles/messages.ts +31 -0
  494. package/src/routes/backup.ts +18 -2
  495. package/src/routes/import.ts +3 -3
  496. package/src/routes/index.ts +1 -1
  497. package/src/routes/kit.ts +9 -3
  498. package/src/routes/market.ts +1 -1
  499. package/src/routes/messages.ts +106 -0
  500. package/src/routes/prefs.ts +31 -2
  501. package/src/routes/profiles.ts +12 -6
  502. package/src/routes/recovery.test.ts +405 -1
  503. package/src/routes/recovery.ts +477 -94
  504. package/src/routes/snapshots.ts +51 -4
  505. package/src/routes/sync.ts +82 -17
  506. package/src/schema/config.ts +53 -20
  507. package/src/schema/index.ts +7 -5
  508. package/src/schema/manifest-schema.test.ts +4 -4
  509. package/src/schema/manifest.ts +19 -18
  510. package/src/schema/messages.ts +120 -0
  511. package/src/schema/registry.test.ts +28 -7
  512. package/src/schema/section-registry.ts +96 -42
  513. package/src/schema/types.ts +2 -1
  514. package/src/schema/versions.ts +6 -5
  515. package/src/security/encryption.ts +16 -15
  516. package/src/security/integrity.ts +8 -7
  517. package/src/security/messages.ts +87 -0
  518. package/src/security/redaction.test.ts +17 -1
  519. package/src/security/redaction.ts +13 -0
  520. package/src/security/secret-scanner.ts +19 -12
  521. package/src/security/security.test.ts +47 -0
  522. package/src/security/vault.ts +2 -1
  523. package/src/security/zip-security.ts +4 -3
  524. package/src/sync/ancestor.ts +2 -1
  525. package/src/sync/autosync-config.ts +21 -0
  526. package/src/sync/autosync-scheduler.test.ts +63 -7
  527. package/src/sync/autosync-scheduler.ts +118 -78
  528. package/src/sync/backup-files.ts +3 -2
  529. package/src/sync/backup-scheduler.ts +6 -5
  530. package/src/sync/blob-store.ts +2 -1
  531. package/src/sync/git/git-transport.test.ts +170 -8
  532. package/src/sync/git/git-transport.ts +176 -2
  533. package/src/sync/github-auth.ts +10 -9
  534. package/src/sync/layout.ts +13 -12
  535. package/src/sync/messages.ts +153 -0
  536. package/src/sync/review-queue.ts +6 -5
  537. package/src/sync/s3/messages.ts +16 -0
  538. package/src/sync/s3/s3-providers.ts +9 -6
  539. package/src/sync/s3/s3-transport.ts +1 -1
  540. package/src/sync/s3/sigv4.ts +3 -1
  541. package/src/sync/snapshot-crypto.ts +6 -5
  542. package/src/sync/snapshot-json.ts +3 -2
  543. package/src/sync/sync-credentials.test.ts +119 -0
  544. package/src/sync/sync-engine-download-tombstone.test.ts +1 -1
  545. package/src/sync/sync-engine.test.ts +226 -8
  546. package/src/sync/sync-engine.ts +154 -52
  547. package/src/sync/sync-history.ts +7 -6
  548. package/src/sync/sync-selection.test.ts +12 -2
  549. package/src/sync/sync-selection.ts +18 -5
  550. package/src/sync/transport.test.ts +1 -0
  551. package/src/sync/ui-prefs.test.ts +44 -1
  552. package/src/sync/ui-prefs.ts +39 -0
  553. package/src/sync/webdav/webdav-transport.ts +4 -1
  554. package/src/ui/artifact-view.ts +4 -3
  555. package/src/ui/commands.test.ts +1 -0
  556. package/src/ui/commands.ts +11 -1
  557. package/src/ui/diff-view.test.ts +9 -1
  558. package/src/ui/diff-view.ts +22 -0
  559. package/src/ui/disk-usage-view.test.ts +113 -6
  560. package/src/ui/disk-usage-view.ts +63 -7
  561. package/src/ui/errors.ts +4 -3
  562. package/src/ui/export-flow.ts +26 -20
  563. package/src/ui/foreign-view.test.ts +43 -0
  564. package/src/ui/foreign-view.ts +30 -3
  565. package/src/ui/i18n.ts +131 -2
  566. package/src/ui/import-wizard.ts +18 -10
  567. package/src/ui/progress.ts +2 -2
  568. package/src/ui/report.ts +7 -6
  569. package/src/ui/rollback-view.test.ts +836 -0
  570. package/src/ui/rollback-view.ts +1405 -0
  571. package/src/ui/selection-model.test.ts +33 -1
  572. package/src/ui/selection-model.ts +53 -0
  573. package/src/ui/session-inventory-view.test.ts +109 -0
  574. package/src/ui/session-inventory-view.ts +138 -3
  575. package/src/ui/session-issue-labels.test.ts +51 -0
  576. package/src/ui/session-issue-labels.ts +70 -0
  577. package/src/ui/sync-settings-view.test.ts +36 -0
  578. package/src/ui/sync-settings-view.ts +49 -0
  579. package/src/ui/test-helpers.ts +2 -1
  580. package/src/ui/types.ts +10 -4
  581. package/src/utils/atomic-write.ts +6 -5
  582. package/src/utils/bundle-scan.test.ts +2 -2
  583. package/src/utils/bundle-scan.ts +12 -1
  584. package/src/utils/env-lock.ts +83 -49
  585. package/src/utils/json.ts +7 -5
  586. package/src/utils/messages-utils.ts +232 -0
  587. package/src/utils/proxy.test.ts +25 -0
  588. package/src/utils/proxy.ts +19 -5
  589. package/src/utils/recursive-walk.test.ts +43 -0
  590. package/src/utils/recursive-walk.ts +75 -1
  591. package/src/utils/session-log-repair.ts +10 -9
  592. package/src/utils/zip.ts +27 -26
  593. package/lib/core/checkpoint.d.ts +0 -362
  594. package/lib/core/checkpoint.js +0 -1309
  595. package/src/core/checkpoint.test.ts +0 -665
  596. package/src/core/checkpoint.ts +0 -1614
@@ -0,0 +1,175 @@
1
+ /**
2
+ * 引擎语言运行时(宿主 / 引擎半)——「英文界面零中文」的引擎侧根因修复。
3
+ *
4
+ * 语义(唯一要记住的一句话):**语言必须在「取文案的那一刻」解析,而不是在「装配的那一刻」**。
5
+ * 改造前的写法是 this.msg = makeMsg(resolveAppLanguage(ctx)):apply() 只在插件加载时执行一次,
6
+ * 于是宿主产出的报告 / 计划 / 错误文本被冻结成加载时那门语言(用户切到 English 后仍是中文)。
7
+ *
8
+ * 本模块提供三件事:
9
+ * 1. 语言运行时:setEngineLocale / setEngineLanguageSource / getEngineLocale /
10
+ * invalidateEngineLocale(函数源按 LANGUAGE_TTL_MS 兜底缓存;取文案时才解析);
11
+ * 2. 消息目录注册:registerMessageCatalog(领域模块追加自己的目录,**仅在既有目录未命中时生效**);
12
+ * 基础目录(src/core/messages.ts 的 zh/en)由该模块经 registerBaseMessageCatalog 反向注册;
13
+ * 3. engineMsg:宿主 / 引擎半的活翻译器(MsgFunc 形状),未知键回退 zh → 键名,绝不抛错。
14
+ *
15
+ * 纪律:
16
+ * - 未设定语言时行为等于 zh(与改造前的缺省逐字一致);
17
+ * - 本模块**不 import src/core/messages.ts**(否则形成循环依赖):基础目录由 messages.ts 反向注册;
18
+ * - 目录查找每次调用现查(目录是常量,成本可忽略);语言只按 LANGUAGE_TTL_MS 缓存。
19
+ */
20
+ import type { MsgParams } from './msg-types.ts';
21
+
22
+ /** 应用语言(与 DSH settings 的 locale preference 同口径)。 */
23
+ export type LanguageId = 'zh' | 'en';
24
+
25
+ /**
26
+ * 语言源允许的缓存上界(毫秒)。
27
+ *
28
+ * 只给**非请求路径**(调度器、后台任务)兜底:请求入口应调用 invalidateEngineLocale()
29
+ * 让每个请求都拿到最新语言(见 routes/kit.ts 的注册点)。
30
+ */
31
+ export const LANGUAGE_TTL_MS = 1000;
32
+
33
+ /** 目录(zh 源 / en 镜像;两本都可选,缺的那本不参与查找)。 */
34
+ export interface MessageCatalog {
35
+ /** 目录标识(诊断用)。 */
36
+ readonly id?: string;
37
+ readonly zh?: Readonly<Record<string, string>>;
38
+ readonly en?: Readonly<Record<string, string>>;
39
+ }
40
+
41
+ /** 语言源:固定值,或「调用期解析」的活源。 */
42
+ export type LanguageSource = LanguageId | (() => LanguageId);
43
+
44
+ const DEFAULT_LANGUAGE: LanguageId = 'zh';
45
+
46
+ let source: LanguageSource = DEFAULT_LANGUAGE;
47
+ let cachedLanguage: LanguageId = DEFAULT_LANGUAGE;
48
+ let cachedAt = 0;
49
+ let cachedValid = false;
50
+
51
+ function normalize(value: unknown): LanguageId {
52
+ return value === 'en' ? 'en' : 'zh';
53
+ }
54
+
55
+ /** 设定引擎语言(固定值):构造宿主时按 resolveAppLanguage(ctx) 初始化。 */
56
+ export function setEngineLocale(locale: LanguageSource): void {
57
+ source = locale;
58
+ invalidateEngineLocale();
59
+ }
60
+
61
+ /**
62
+ * 注册「调用期解析」的语言活源(宿主用 () => resolveAppLanguage(ctx))。
63
+ *
64
+ * 与 setEngineLocale 同源:后者给构造期的初值,本函数让**之后**每次取文案都能跟随用户切语言。
65
+ */
66
+ export function setEngineLanguageSource(resolve: () => LanguageId): void {
67
+ setEngineLocale(resolve);
68
+ }
69
+
70
+ /** 强制失效:下一次 getEngineLocale() 必须重新解析(路由入口 / 测试用)。 */
71
+ export function invalidateEngineLocale(): void {
72
+ cachedValid = false;
73
+ cachedAt = 0;
74
+ }
75
+
76
+ /**
77
+ * 当前引擎语言(调用期解析)。
78
+ *
79
+ * 函数源的结果按 LANGUAGE_TTL_MS 缓存;固定值源缓存无副作用。解析失败一律回落 zh(绝不抛错)。
80
+ * now 可注入(单测用假时钟验证 TTL 上界)。
81
+ */
82
+ export function getEngineLocale(now: number = Date.now()): LanguageId {
83
+ if (cachedValid && now - cachedAt < LANGUAGE_TTL_MS) return cachedLanguage;
84
+ let next = DEFAULT_LANGUAGE;
85
+ try {
86
+ next = normalize(typeof source === 'function' ? source() : source);
87
+ } catch {
88
+ next = cachedValid ? cachedLanguage : DEFAULT_LANGUAGE;
89
+ }
90
+ cachedLanguage = next;
91
+ cachedAt = now;
92
+ cachedValid = true;
93
+ return next;
94
+ }
95
+
96
+ /* ------------------------------------------------------------- 目录注册 */
97
+
98
+ const registry: MessageCatalog[] = [];
99
+
100
+ let baseZh: Readonly<Record<string, string>> | undefined;
101
+ let baseEn: Readonly<Record<string, string>> | undefined;
102
+
103
+ /**
104
+ * 注册基础目录(zh 源 / en 镜像)。
105
+ *
106
+ * 由 src/core/messages.ts 在模块初始化时调用一次 —— 这样本模块不必 import 它(避免循环依赖)。
107
+ * 直接 import engine-locale 而不 import messages.ts 的模块,engineMsg 只能拿到注册目录 + 键名。
108
+ */
109
+ export function registerBaseMessageCatalog(catalogs: {
110
+ readonly zh: Readonly<Record<string, string>>;
111
+ readonly en: Readonly<Record<string, string>>;
112
+ }): void {
113
+ baseZh = catalogs.zh;
114
+ baseEn = catalogs.en;
115
+ }
116
+
117
+ /**
118
+ * 注册一本追加目录(领域模块的键并入既有目录)。
119
+ *
120
+ * **只在既有目录未命中时生效**(makeMsg / engineMsg 的查找顺序:既有目录 → 注册目录 → 键名);
121
+ * 同一键先注册者优先。en 语言下注册目录未命中时再回退注册目录的 zh(与既有 en → zh 同口径)。
122
+ */
123
+ export function registerMessageCatalog(catalog: MessageCatalog): void {
124
+ registry.push(catalog);
125
+ }
126
+
127
+ /** 已注册的追加目录(只读快照,诊断用)。 */
128
+ export function registeredMessageCatalogs(): readonly MessageCatalog[] {
129
+ return registry;
130
+ }
131
+
132
+ /** 清空追加目录(测试 / 装配隔离用)。 */
133
+ export function resetMessageCatalogs(): void {
134
+ registry.length = 0;
135
+ }
136
+
137
+ /** 注册目录里查一条(未命中 → undefined)。 */
138
+ export function registeredMessage(lang: LanguageId, key: string): string | undefined {
139
+ for (const catalog of registry) {
140
+ const hit = catalog[lang]?.[key];
141
+ if (hit !== undefined) return hit;
142
+ }
143
+ if (lang === 'en') {
144
+ for (const catalog of registry) {
145
+ const hit = catalog.zh?.[key];
146
+ if (hit !== undefined) return hit;
147
+ }
148
+ }
149
+ return undefined;
150
+ }
151
+
152
+ /* --------------------------------------------------------------- 翻译器 */
153
+
154
+ /** 插值:{param} → 形参值;缺参原样保留(便于排查)。与改造前的 messages.ts 逐字同口径。 */
155
+ export function interpolate(template: string, params?: MsgParams): string {
156
+ if (params === undefined) return template;
157
+ return template.replace(/\{(\w+)\}/g, (match, key: string) => {
158
+ const value = params[key];
159
+ return value === undefined ? match : String(value);
160
+ });
161
+ }
162
+
163
+ /**
164
+ * 活翻译器(宿主 / 引擎半的 MsgFunc):每次调用按 getEngineLocale() 选目录。
165
+ *
166
+ * 查找顺序:既有目录(当前语言)→ en 语言下回退既有 zh → 注册目录(同语言 → zh)→ 键名。
167
+ * 未设定语言时 = zh 目录,行为与改造前逐字一致;未知键回退键名,绝不抛错。
168
+ */
169
+ export function engineMsg(key: string, params?: MsgParams): string {
170
+ const lang = getEngineLocale();
171
+ const dict = lang === 'en' ? baseEn : baseZh;
172
+ const fallback = lang === 'en' ? baseZh : undefined;
173
+ const template = dict?.[key] ?? fallback?.[key] ?? registeredMessage(lang, key) ?? key;
174
+ return interpolate(template, params);
175
+ }
@@ -18,6 +18,7 @@ import { SECTION_JSON_PATHS, SECTION_FILE_PREFIXES, isFileSection } from '../sch
18
18
  import { DEFAULT_SENSITIVE_RELS, refreshVault } from '../security/vault.ts';
19
19
  import { writeZip } from '../utils/zip.ts';
20
20
  import { msgOf } from './messages.ts';
21
+ import { engineMsg } from './messages-core.ts';
21
22
  import { declareBundledSessionsInWorkspaces, normalizeSessionLimit, sessionIdKey } from './session-select.ts';
22
23
  // issue #45 连带匹配的第二判据:工作区 path 的 cwd 目录键(DSH 的 sessionIds 覆盖率很低,见 coupleSessionWorkspaces)
23
24
  import { projectKeyOf } from './session-meta.ts';
@@ -370,11 +371,10 @@ export class Exporter {
370
371
  warnings.push(this.msg('export.fileSectionSecrets', { section: adapter.id, path: hitPath }));
371
372
  }
372
373
  // 被截断的文件数必须**显式**汇总告警,否则截断本身又变成静默丢失。
373
- // 说明:此处未新增消息键(如 export.fileSectionSecretsTruncated)是为避免与并行任务冲突
374
- // (src/core/messages.ts 属他人改动范围);故复用 export.fileSectionSecrets,仅 path 传汇总文案。
374
+ // 文案在 t4 域目录(src/core/messages-core.ts):不再借用 export.fileSectionSecrets 传中文 path 值。
375
375
  const truncatedCount = hitPaths.length - warnedPaths.length;
376
376
  if (truncatedCount > 0) {
377
- warnings.push(this.msg('export.fileSectionSecrets', { section: adapter.id, path: `(另有 ${truncatedCount} 个文件命中,详见报告)` }));
377
+ warnings.push(engineMsg('core.exporter.fileSectionSecretsTruncated', { section: adapter.id, count: truncatedCount }));
378
378
  }
379
379
  }
380
380
  }
@@ -0,0 +1,279 @@
1
+ /**
2
+ * t17 历史 fork 归位:只读盘点 + 逐个归位 + 逐个复核,逐条回执、失败带具体理由。
3
+ *
4
+ * 测试策略:seam 用**带真实状态的小注册表**(成员集合 + 写计数)而不是「返回固定值的桩」——
5
+ * 这样「已归位 ⇒ 幂等不重复写」与「复核不通过 ⇒ 不报成功」才是被真的判据证明的,
6
+ * 而不是把桩的返回值抄一遍。
7
+ */
8
+ import test from 'node:test'
9
+ import assert from 'node:assert/strict'
10
+ import { readFileSync } from 'node:fs'
11
+ import { fileURLToPath } from 'node:url'
12
+
13
+ import {
14
+ FORK_RECONCILE_RECEIPT_LIMIT,
15
+ reconcileForkSessions,
16
+ type ForkReconcileChild,
17
+ type ForkReconcileOutcome,
18
+ } from './fork-reconcile.ts'
19
+
20
+ /**
21
+ * 影子工作区注册表:`path` 是真实目录(realpath 判定用不到真实盘 —— 本文件只喂 seam),
22
+ * `members` 是生效成员,`writes` 记录真的写了几次(幂等判据看它)。
23
+ */
24
+ function fakeWorkspace(input: { id: string; path: string; resolvable: boolean }) {
25
+ return {
26
+ id: input.id,
27
+ path: input.path,
28
+ resolvable: input.resolvable,
29
+ members: new Set<string>(),
30
+ writes: 0,
31
+ attach(childId: string): void {
32
+ this.writes += 1
33
+ this.members.add(childId)
34
+ },
35
+ }
36
+ }
37
+
38
+ /**
39
+ * seam 工厂:复刻 t10 的判据(成员/血缘/cwd 同目录三档 + 写后复核),但用影子注册表 ——
40
+ * **写后复核就是重新读一遍 members**(真机就是重新读 `list()`)。
41
+ */
42
+ function makeSeam(input: {
43
+ workspaces: ReturnType<typeof fakeWorkspace>[]
44
+ /** 每条子会话的 cwd(缺省 = 解析不到任何工作区) */
45
+ cwdOf?: Record<string, string>
46
+ /** 强制某条子会话 attach 抛错(模拟「写调用被拒」) */
47
+ rejectOf?: Record<string, string>
48
+ /** 强制某条子会话「写了但复核不含它」(模拟 DSH 没真的落盘) */
49
+ noopOf?: Set<string>
50
+ calls?: ForkReconcileChild[]
51
+ }): (child: ForkReconcileChild) => Promise<ForkReconcileOutcome> {
52
+ return async (child) => {
53
+ input.calls?.push(child)
54
+ // ① 已在账内(幂等:**不重复写**)
55
+ const member = input.workspaces.find((ws) => ws.members.has(child.sessionId))
56
+ if (member !== undefined) return { ok: true, status: 'already', workspaceId: member.id }
57
+ // ② cwd → 已注册工作区(解析不到 ⇒ 具体理由)
58
+ const cwd = input.cwdOf?.[child.sessionId]
59
+ if (cwd === undefined) return { ok: false, reason: 'cwd-unavailable' }
60
+ const target = input.workspaces.find((ws) => ws.path === cwd && ws.resolvable)
61
+ if (target === undefined) return { ok: false, reason: 'no-workspace-for-cwd', detail: cwd }
62
+ // ③ 写(可能被拒 / 可能没落盘)
63
+ const reject = input.rejectOf?.[child.sessionId]
64
+ if (reject !== undefined) return { ok: false, reason: 'attach-rejected', detail: reject }
65
+ if (input.noopOf?.has(child.sessionId) === true) return { ok: false, reason: 'verify-failed', detail: 'child-not-in-any-workspace-sessionIds' }
66
+ target.attach(child.sessionId)
67
+ // ④ 复核:重新读 members(attach 返回成功 ≠ 归位成功)
68
+ const verified = input.workspaces.find((ws) => ws.members.has(child.sessionId))
69
+ return verified === undefined
70
+ ? { ok: false, reason: 'verify-failed', detail: 'child-not-in-any-workspace-sessionIds' }
71
+ : { ok: true, status: 'attached', workspaceId: verified.id }
72
+ }
73
+ }
74
+
75
+ const HERE = fileURLToPath(new URL('.', import.meta.url))
76
+
77
+ /** 候选清单简写:`kids(['child-1', 'parent-1'])`(subagent 只用于「有意跳过」那条用例)。 */
78
+ const kids = (...pairs: [string, string][]): ForkReconcileChild[] =>
79
+ pairs.map(([sessionId, parentSessionId]) => ({ sessionId, parentSessionId }))
80
+
81
+ test('t17 能解析 ⇒ 归位且复核通过(attached)', async () => {
82
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
83
+ const result = await reconcileForkSessions({
84
+ childSessions: async () => kids(['child-1', 'parent-1']),
85
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' } }),
86
+ })
87
+ assert.equal(result.ok, true)
88
+ assert.equal(result.candidates, 1)
89
+ assert.equal(result.attached, 1)
90
+ assert.equal(result.already, 0)
91
+ assert.equal(result.refused, 0)
92
+ assert.deepEqual(result.receipts, [{
93
+ sessionId: 'child-1',
94
+ parentSessionId: 'parent-1',
95
+ status: 'attached',
96
+ reason: 'attached',
97
+ workspaceId: 'ws-1',
98
+ }])
99
+ assert.equal(ws.writes, 1, '真的写了一次')
100
+ assert.deepEqual([...ws.members], ['child-1'], '复核读的就是它(写后重读)')
101
+ })
102
+
103
+ test('t17 解析不到 ⇒ refused 且带具体理由(绝不猜工作区)', async () => {
104
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
105
+ const result = await reconcileForkSessions({
106
+ childSessions: async () => kids(['child-elsewhere', 'parent-1'], ['child-nocwd', 'parent-1']),
107
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-elsewhere': 'D:/other/place' } }),
108
+ })
109
+ assert.equal(result.ok, true)
110
+ assert.equal(result.candidates, 2)
111
+ assert.equal(result.attached, 0)
112
+ assert.equal(result.refused, 2)
113
+ assert.deepEqual(result.reasonCounts, { 'no-workspace-for-cwd': 1, 'cwd-unavailable': 1 })
114
+ assert.equal(ws.writes, 0, '一条都不许猜着写')
115
+ assert.deepEqual(result.receipts.map((r) => [r.sessionId, r.status, r.reason]), [
116
+ ['child-elsewhere', 'refused', 'no-workspace-for-cwd'],
117
+ ['child-nocwd', 'refused', 'cwd-unavailable'],
118
+ ])
119
+ assert.equal(result.receipts[0]?.detail, 'D:/other/place', '理由要能定位(带路径)')
120
+ })
121
+
122
+ test('t17 已归位 ⇒ already,且**幂等不重复写**(连跑两次第二次零写)', async () => {
123
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
124
+ const calls: ForkReconcileChild[] = []
125
+ const deps = {
126
+ childSessions: async () => kids(['child-1', 'parent-1']),
127
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, calls }),
128
+ }
129
+ const first = await reconcileForkSessions(deps)
130
+ assert.equal(first.attached, 1)
131
+ assert.equal(ws.writes, 1)
132
+ const second = await reconcileForkSessions(deps)
133
+ assert.equal(second.already, 1, '第二次必须报 already(不是又写一次)')
134
+ assert.equal(second.attached, 0)
135
+ assert.equal(ws.writes, 1, '幂等:attach 只写了一次')
136
+ assert.deepEqual(second.receipts[0]?.reason, 'already-attached')
137
+ assert.deepEqual(calls.map((c) => c.sessionId), ['child-1', 'child-1'], '同一条会话每次盘点各问一次,绝不重复写')
138
+ })
139
+
140
+ test('t17 复核不通过 ⇒ 报失败(refused/verify-failed),绝不报成功', async () => {
141
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
142
+ const result = await reconcileForkSessions({
143
+ childSessions: async () => kids(['child-ghost', 'parent-1']),
144
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-ghost': 'D:/proj/a' }, noopOf: new Set(['child-ghost']) }),
145
+ })
146
+ assert.equal(result.attached, 0, '写调用没抛错但复核不含它 ⇒ 不许报成功')
147
+ assert.equal(result.refused, 1)
148
+ assert.deepEqual(result.reasonCounts, { 'verify-failed': 1 })
149
+ assert.equal(result.receipts[0]?.status, 'refused')
150
+ assert.equal(ws.writes, 0, 'noop 档连写都没发生(判据在 seam 内,本用例只证明 core 不把它算成功)')
151
+ })
152
+
153
+ test('t17 写被拒 ⇒ refused/attach-rejected 带 detail(失败不静默)', async () => {
154
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
155
+ const logs: string[] = []
156
+ const result = await reconcileForkSessions({
157
+ childSessions: async () => kids(['child-1', 'parent-1']),
158
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, rejectOf: { 'child-1': 'workspace write failed' } }),
159
+ log: (line) => logs.push(line),
160
+ })
161
+ assert.equal(result.refused, 1)
162
+ assert.deepEqual(result.reasonCounts, { 'attach-rejected': 1 })
163
+ assert.equal(result.receipts[0]?.detail, 'workspace write failed')
164
+ assert.equal(logs.some((line) => line.includes('refused: attach-rejected')), true, '逐条失败必须留痕')
165
+ })
166
+
167
+ test('t17 候选只认「有父会话的子会话」:无父/空 id/自环的噪声项只计入 scanned', async () => {
168
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
169
+ const calls: ForkReconcileChild[] = []
170
+ const result = await reconcileForkSessions({
171
+ childSessions: async () => kids(
172
+ ['child-1', 'parent-1'],
173
+ ['child-1', 'parent-1'], // 重复项只处理一次
174
+ ['root-1', ''], // 无父
175
+ ['', 'parent-1'], // 空 id
176
+ ['self-1', 'self-1'], // 自环
177
+ ),
178
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'child-1': 'D:/proj/a' }, calls }),
179
+ })
180
+ assert.equal(result.scanned, 5, 'scanned = 原始清单条数(含噪声项)')
181
+ assert.equal(result.candidates, 1, '只有 child-1 是候选')
182
+ assert.deepEqual(calls.map((c) => c.sessionId), ['child-1'])
183
+ assert.equal(result.attached, 1)
184
+ })
185
+
186
+ test('t17 一条抛错不影响其余(收成 refused/unexpected-error 且逐条互不影响)', async () => {
187
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
188
+ const base = makeSeam({ workspaces: [ws], cwdOf: { 'child-2': 'D:/proj/a' } })
189
+ const result = await reconcileForkSessions({
190
+ childSessions: async () => kids(['child-boom', 'parent-1'], ['child-2', 'parent-1']),
191
+ reconcileOne: async (child) => {
192
+ if (child.sessionId === 'child-boom') throw new Error('registry exploded')
193
+ return await base(child)
194
+ },
195
+ })
196
+ assert.equal(result.ok, true)
197
+ assert.equal(result.attached, 1, '后面那条照常归位')
198
+ assert.equal(result.refused, 1)
199
+ assert.deepEqual(result.reasonCounts, { 'unexpected-error': 1 })
200
+ assert.equal(result.receipts[0]?.detail, 'registry exploded')
201
+ })
202
+
203
+ test('t17 盘点读不到父子关系 ⇒ ok:false + error(**不是**「本机没有子会话」)', async () => {
204
+ const result = await reconcileForkSessions({
205
+ childSessions: async () => { throw new Error('session store not ready') },
206
+ reconcileOne: async () => ({ ok: true, status: 'attached' }),
207
+ })
208
+ assert.equal(result.ok, false)
209
+ assert.equal(result.candidates, 0)
210
+ assert.match(String(result.error), /child-sessions-unavailable: session store not ready/)
211
+ assert.deepEqual(result.receipts, [])
212
+ })
213
+
214
+ test('t17 默认口径:**全部**有父会话的子会话都是候选(origin=subagent 也照做,真机 50/58 条 fork 带这个标记)', async () => {
215
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
216
+ const calls: ForkReconcileChild[] = []
217
+ const result = await reconcileForkSessions({
218
+ childSessions: async () => [
219
+ { sessionId: 'fork-1', parentSessionId: 'parent-1' },
220
+ { sessionId: 'fork-2', parentSessionId: 'parent-1', subagent: true },
221
+ ],
222
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'fork-1': 'D:/proj/a', 'fork-2': 'D:/proj/a' }, calls }),
223
+ })
224
+ assert.equal(result.candidates, 2, 'subagent 标记不得让回退 fork 掉队(真机 50/58 条就带它)')
225
+ assert.deepEqual(result.skipped, { subagent: 0 })
226
+ assert.deepEqual(calls.map((c) => c.sessionId), ['fork-1', 'fork-2'])
227
+ assert.equal(result.attached, 2)
228
+ })
229
+
230
+ test('t17 显式窄化(skipSubagentSessions: true):跳过的条数如实计数、不静默', async () => {
231
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
232
+ const calls: ForkReconcileChild[] = []
233
+ const logs: string[] = []
234
+ const result = await reconcileForkSessions({
235
+ childSessions: async () => [
236
+ { sessionId: 'fork-1', parentSessionId: 'parent-1' },
237
+ { sessionId: 'sub-1', parentSessionId: 'parent-1', subagent: true },
238
+ { sessionId: 'sub-2', parentSessionId: 'parent-1', subagent: true },
239
+ ],
240
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf: { 'fork-1': 'D:/proj/a' }, calls }),
241
+ log: (line) => logs.push(line),
242
+ }, { skipSubagentSessions: true })
243
+ assert.equal(result.scanned, 3)
244
+ assert.equal(result.candidates, 1)
245
+ assert.deepEqual(result.skipped, { subagent: 2 }, '跳过必须如实计数(不是静默丢弃)')
246
+ assert.deepEqual(calls.map((c) => c.sessionId), ['fork-1'], '一条子代理会话都没被碰')
247
+ assert.equal(result.attached, 1)
248
+ assert.equal(result.refused, 0)
249
+ assert.equal(logs.some((line) => line.includes('skipped 2 subagent sessions')), true, '跳过要有留痕')
250
+ })
251
+
252
+ test('t17 回执有界但计数精确(截断如实标记)', async () => {
253
+ const ws = fakeWorkspace({ id: 'ws-1', path: 'D:/proj/a', resolvable: true })
254
+ const relations: ForkReconcileChild[] = []
255
+ const cwdOf: Record<string, string> = {}
256
+ const total = FORK_RECONCILE_RECEIPT_LIMIT + 25
257
+ for (let i = 0; i < total; i += 1) {
258
+ const id = 'child-' + String(i)
259
+ relations.push({ sessionId: id, parentSessionId: 'parent-1' })
260
+ if (i % 5 !== 0) cwdOf[id] = 'D:/proj/a' // 1/5 解析不到
261
+ }
262
+ const result = await reconcileForkSessions({
263
+ childSessions: async () => relations,
264
+ reconcileOne: makeSeam({ workspaces: [ws], cwdOf }),
265
+ })
266
+ assert.equal(result.candidates, total)
267
+ assert.equal(result.attached + result.already + result.refused, total, '计数精确')
268
+ assert.equal(result.receipts.length, FORK_RECONCILE_RECEIPT_LIMIT, '明细有界')
269
+ assert.equal(result.receiptsTruncated, true)
270
+ })
271
+
272
+ test('t17 只读纪律:core 不碰文件系统(源码级),候选里也没有「删除/清理」这种动作', () => {
273
+ const source = readFileSync(new URL('./fork-reconcile.ts', import.meta.url), 'utf8')
274
+ assert.ok(!/from 'node:/.test(source), 'core 只编排,IO 全在 seam 里(绝不自己读盘/写盘)')
275
+ assert.ok(!/rmSync|rmdirSync|unlinkSync|fs\.rm\(|fs\.unlink\(|writeFileSync|appendFileSync/.test(source), '绝不删除、绝不改写任何东西')
276
+ assert.ok(!/session-log|readLogHeader|zstd/.test(source), '绝不碰会话日志字节(连读都不读)')
277
+ // 本文件(测试)也不许碰真实盘:只读源码文本 + 内存夹具
278
+ assert.equal(HERE.length > 0, true)
279
+ })