@adhdev/daemon-core 0.9.82-rc.49 → 0.9.82-rc.491

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 (473) hide show
  1. package/dist/boot/daemon-lifecycle.d.ts +9 -0
  2. package/dist/boot/process-hardening.d.ts +50 -0
  3. package/dist/build-info.d.ts +37 -0
  4. package/dist/chat/source-machine.d.ts +166 -0
  5. package/dist/chat/source-resolver.d.ts +104 -0
  6. package/dist/chat/subscription-updates.d.ts +1 -0
  7. package/dist/cli-adapter-types.d.ts +68 -2
  8. package/dist/cli-adapters/cli-script-runner.d.ts +117 -0
  9. package/dist/cli-adapters/cli-state-engine.d.ts +220 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +137 -64
  11. package/dist/cli-adapters/provider-cli-parse.d.ts +5 -0
  12. package/dist/cli-adapters/provider-cli-runtime.d.ts +34 -0
  13. package/dist/cli-adapters/provider-cli-shared.d.ts +74 -0
  14. package/dist/cli-adapters/pty-write-chunking.d.ts +34 -0
  15. package/dist/cli-adapters/raw-terminal-io.d.ts +37 -0
  16. package/dist/cli-adapters/resolve-executable.d.ts +69 -0
  17. package/dist/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +6 -3
  18. package/dist/cli-adapters/terminal-backends/types.d.ts +10 -2
  19. package/dist/cli-adapters/terminal-screen.d.ts +4 -11
  20. package/dist/commands/chat-commands-debug-bundle.d.ts +14 -0
  21. package/dist/commands/chat-commands-read.d.ts +23 -0
  22. package/dist/commands/chat-commands-scope.d.ts +39 -0
  23. package/dist/commands/chat-commands-shared.d.ts +33 -0
  24. package/dist/commands/chat-commands-write.d.ts +14 -0
  25. package/dist/commands/chat-commands.d.ts +10 -23
  26. package/dist/commands/cli-manager.d.ts +71 -1
  27. package/dist/commands/handler.d.ts +125 -0
  28. package/dist/commands/high-family/index.d.ts +3 -0
  29. package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
  30. package/dist/commands/high-family/mesh-events.d.ts +2 -0
  31. package/dist/commands/high-family/mesh-status.d.ts +2 -0
  32. package/dist/commands/high-family/types.d.ts +64 -0
  33. package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
  34. package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
  35. package/dist/commands/low-family/diagnostics.d.ts +2 -0
  36. package/dist/commands/low-family/index.d.ts +3 -0
  37. package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
  38. package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
  39. package/dist/commands/low-family/notification.d.ts +2 -0
  40. package/dist/commands/low-family/refine-config.d.ts +2 -0
  41. package/dist/commands/low-family/session-host.d.ts +2 -0
  42. package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
  43. package/dist/commands/low-family/status-meta.d.ts +2 -0
  44. package/dist/commands/low-family/types.d.ts +33 -0
  45. package/dist/commands/med-family/cli-agent.d.ts +2 -0
  46. package/dist/commands/med-family/fast-forward.d.ts +2 -0
  47. package/dist/commands/med-family/ide.d.ts +10 -0
  48. package/dist/commands/med-family/index.d.ts +3 -0
  49. package/dist/commands/med-family/mesh-crud.d.ts +48 -0
  50. package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
  51. package/dist/commands/med-family/mesh-queue.d.ts +2 -0
  52. package/dist/commands/med-family/mesh-restart.d.ts +2 -0
  53. package/dist/commands/med-family/types.d.ts +152 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router-aggregate-status.d.ts +24 -0
  56. package/dist/commands/router-mesh-session-owner.d.ts +59 -0
  57. package/dist/commands/router-refine.d.ts +151 -0
  58. package/dist/commands/router-worktree-cleanup.d.ts +133 -0
  59. package/dist/commands/router.d.ts +203 -15
  60. package/dist/commands/upgrade-helper.d.ts +41 -1
  61. package/dist/config/chat-history.d.ts +11 -0
  62. package/dist/config/config.d.ts +19 -0
  63. package/dist/config/mesh-config.d.ts +66 -0
  64. package/dist/config/mesh-json-config.d.ts +199 -0
  65. package/dist/config/registry-resolver.d.ts +54 -0
  66. package/dist/config/repo-settings.d.ts +77 -0
  67. package/dist/config/state-store.d.ts +30 -0
  68. package/dist/daemon/dev-server.d.ts +0 -2
  69. package/dist/detection/cli-detector.d.ts +17 -0
  70. package/dist/detection/ide-detector.d.ts +13 -0
  71. package/dist/detection/win32-ide-version.d.ts +37 -0
  72. package/dist/git/change-impact-config.d.ts +159 -0
  73. package/dist/git/git-commands.d.ts +26 -2
  74. package/dist/git/git-diff.d.ts +6 -0
  75. package/dist/git/git-executor.d.ts +11 -0
  76. package/dist/git/git-status.d.ts +80 -0
  77. package/dist/git/git-types.d.ts +2 -50
  78. package/dist/git/git-worktree.d.ts +71 -1
  79. package/dist/git/index.d.ts +3 -1
  80. package/dist/index.d.ts +70 -17
  81. package/dist/index.js +53556 -19074
  82. package/dist/index.js.map +1 -1
  83. package/dist/index.mjs +53380 -19063
  84. package/dist/index.mjs.map +1 -1
  85. package/dist/installer.d.ts +1 -4
  86. package/dist/ipc/local-ipc-server.d.ts +91 -0
  87. package/dist/launch.d.ts +1 -1
  88. package/dist/logging/async-batch-writer.d.ts +10 -0
  89. package/dist/logging/debug-config.d.ts +16 -0
  90. package/dist/logging/log-redactor.d.ts +24 -0
  91. package/dist/logging/log-tail-reader.d.ts +81 -0
  92. package/dist/logging/logger.d.ts +1 -1
  93. package/dist/mesh/contracts.d.ts +256 -0
  94. package/dist/mesh/coordinator-prompt.d.ts +76 -0
  95. package/dist/mesh/coordinator-registry.d.ts +59 -0
  96. package/dist/mesh/mesh-active-work.d.ts +203 -0
  97. package/dist/mesh/mesh-clone-grace.d.ts +8 -0
  98. package/dist/mesh/mesh-completion-synthesis.d.ts +4 -0
  99. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  100. package/dist/mesh/mesh-delivery-policy.d.ts +104 -0
  101. package/dist/mesh/mesh-event-classify.d.ts +7 -0
  102. package/dist/mesh/mesh-event-forwarding.d.ts +58 -0
  103. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  104. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  105. package/dist/mesh/mesh-events-pending.d.ts +258 -0
  106. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  107. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  108. package/dist/mesh/mesh-events.d.ts +6 -49
  109. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  110. package/dist/mesh/mesh-host-ownership.d.ts +21 -1
  111. package/dist/mesh/mesh-init.d.ts +128 -0
  112. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  113. package/dist/mesh/mesh-ledger.d.ts +139 -1
  114. package/dist/mesh/mesh-magi-status.d.ts +63 -0
  115. package/dist/mesh/mesh-missions.d.ts +281 -0
  116. package/dist/mesh/mesh-node-identity.d.ts +310 -0
  117. package/dist/mesh/mesh-queue-assignment.d.ts +162 -0
  118. package/dist/mesh/mesh-reconcile-acked-hold.d.ts +17 -0
  119. package/dist/mesh/mesh-reconcile-config.d.ts +6 -0
  120. package/dist/mesh/mesh-reconcile-identity.d.ts +6 -0
  121. package/dist/mesh/mesh-reconcile-loop.d.ts +84 -0
  122. package/dist/mesh/mesh-reconcile-v2-backstop.d.ts +16 -0
  123. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  124. package/dist/mesh/mesh-refine-gates.d.ts +457 -0
  125. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  126. package/dist/mesh/mesh-remote-event-pull.d.ts +15 -0
  127. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  128. package/dist/mesh/mesh-routing.d.ts +70 -0
  129. package/dist/mesh/mesh-runtime-store.d.ts +567 -0
  130. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  131. package/dist/mesh/mesh-task-inflight.d.ts +46 -0
  132. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  133. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +34 -0
  134. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  135. package/dist/mesh/mesh-work-queue.d.ts +392 -1
  136. package/dist/mesh/preview-freshness.d.ts +28 -0
  137. package/dist/mesh/refine-config.d.ts +97 -0
  138. package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
  139. package/dist/providers/acp-provider-instance.d.ts +5 -0
  140. package/dist/providers/approval-utils.d.ts +46 -0
  141. package/dist/providers/chat-message-normalization.d.ts +58 -1
  142. package/dist/providers/cli-provider-effect-format.d.ts +30 -0
  143. package/dist/providers/cli-provider-history-dedup.d.ts +17 -0
  144. package/dist/providers/cli-provider-input-prompt.d.ts +12 -0
  145. package/dist/providers/cli-provider-instance-types.d.ts +45 -0
  146. package/dist/providers/cli-provider-instance.d.ts +426 -30
  147. package/dist/providers/cli-provider-status-helpers.d.ts +46 -0
  148. package/dist/providers/cli-provider-transcript-merge.d.ts +7 -0
  149. package/dist/providers/contracts.d.ts +196 -6
  150. package/dist/providers/external-sources.d.ts +71 -0
  151. package/dist/providers/manual-attendance.d.ts +79 -0
  152. package/dist/providers/native-history/antigravity-claim-registry.d.ts +39 -0
  153. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
  154. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  155. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  156. package/dist/providers/native-history/constants.d.ts +12 -0
  157. package/dist/providers/native-history/dispatcher.d.ts +44 -0
  158. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  159. package/dist/providers/native-history/index.d.ts +13 -0
  160. package/dist/providers/provider-instance-manager.d.ts +30 -0
  161. package/dist/providers/provider-instance.d.ts +31 -1
  162. package/dist/providers/provider-loader.d.ts +42 -5
  163. package/dist/providers/provider-trust.d.ts +31 -0
  164. package/dist/providers/read-chat-contract.d.ts +29 -0
  165. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  166. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  167. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  168. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  169. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  170. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  171. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  172. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  173. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  174. package/dist/providers/sdk/v1/index.d.ts +30 -0
  175. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  176. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  177. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  178. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  179. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  180. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  181. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  182. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  183. package/dist/providers/spec/adapter.d.ts +98 -0
  184. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  185. package/dist/providers/spec/evaluator.d.ts +45 -0
  186. package/dist/providers/spec/fsm-driver.d.ts +449 -0
  187. package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
  188. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  189. package/dist/providers/spec/fsm-types.d.ts +202 -0
  190. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  191. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  192. package/dist/providers/spec/route.d.ts +4 -0
  193. package/dist/providers/spec/types.d.ts +244 -0
  194. package/dist/providers/status-monitor.d.ts +7 -7
  195. package/dist/providers/transcript-v2.d.ts +176 -0
  196. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  197. package/dist/providers/working-dir.d.ts +17 -0
  198. package/dist/repo-mesh-types.d.ts +616 -12
  199. package/dist/runtime-defaults.d.ts +2 -0
  200. package/dist/session-host/managed-host.d.ts +64 -0
  201. package/dist/session-host/runtime-surface.d.ts +10 -16
  202. package/dist/sessions/registry.d.ts +26 -0
  203. package/dist/shared-types-extra.d.ts +2 -4
  204. package/dist/shared-types.d.ts +110 -55
  205. package/dist/status/chat-tail-hot-sessions.d.ts +40 -0
  206. package/dist/status/normalize.d.ts +1 -1
  207. package/dist/status/normalize.js +1 -0
  208. package/dist/status/normalize.js.map +1 -1
  209. package/dist/status/normalize.mjs +1 -0
  210. package/dist/status/normalize.mjs.map +1 -1
  211. package/dist/status/reporter.d.ts +2 -0
  212. package/dist/status/snapshot.d.ts +27 -0
  213. package/dist/system/hash.d.ts +8 -0
  214. package/dist/system/load-better-sqlite3.d.ts +21 -0
  215. package/dist/types.d.ts +20 -6
  216. package/package.json +11 -4
  217. package/src/agent-stream/poller.ts +2 -3
  218. package/src/agent-stream/provider-adapter.ts +1 -1
  219. package/src/boot/daemon-lifecycle.ts +80 -14
  220. package/src/boot/process-hardening.ts +89 -0
  221. package/src/build-info.ts +73 -0
  222. package/src/chat/source-machine.ts +534 -0
  223. package/src/chat/source-resolver.ts +0 -0
  224. package/src/chat/subscription-updates.ts +20 -1
  225. package/src/cli-adapter-types.d.ts +3 -1
  226. package/src/cli-adapter-types.ts +68 -2
  227. package/src/cli-adapters/cli-script-runner.ts +421 -0
  228. package/src/cli-adapters/cli-state-engine.ts +1282 -0
  229. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  230. package/src/cli-adapters/provider-cli-adapter.ts +1048 -1143
  231. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  232. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  233. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  234. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  235. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  236. package/src/cli-adapters/pty-transport.ts +2 -1
  237. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  238. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  239. package/src/cli-adapters/resolve-executable.ts +204 -0
  240. package/src/cli-adapters/session-host-transport.ts +2 -1
  241. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  242. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  243. package/src/cli-adapters/terminal-screen.ts +16 -81
  244. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  245. package/src/commands/chat-commands-read.ts +2788 -0
  246. package/src/commands/chat-commands-scope.ts +54 -0
  247. package/src/commands/chat-commands-shared.ts +114 -0
  248. package/src/commands/chat-commands-write.ts +891 -0
  249. package/src/commands/chat-commands.ts +19 -1841
  250. package/src/commands/cli-manager.d.ts +2 -0
  251. package/src/commands/cli-manager.ts +724 -27
  252. package/src/commands/handler.ts +865 -2
  253. package/src/commands/high-family/index.ts +28 -0
  254. package/src/commands/high-family/mesh-coordinator-launch.ts +696 -0
  255. package/src/commands/high-family/mesh-events.ts +102 -0
  256. package/src/commands/high-family/mesh-status.ts +857 -0
  257. package/src/commands/high-family/types.ts +80 -0
  258. package/src/commands/low-family/coordinator-prompt.ts +125 -0
  259. package/src/commands/low-family/daemon-lifecycle.ts +120 -0
  260. package/src/commands/low-family/diagnostics.ts +57 -0
  261. package/src/commands/low-family/index.ts +37 -0
  262. package/src/commands/low-family/mesh-ledger.ts +62 -0
  263. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  264. package/src/commands/low-family/notification.ts +116 -0
  265. package/src/commands/low-family/refine-config.ts +106 -0
  266. package/src/commands/low-family/session-host.ts +282 -0
  267. package/src/commands/low-family/spec-providerdev.ts +217 -0
  268. package/src/commands/low-family/status-meta.ts +112 -0
  269. package/src/commands/low-family/types.ts +39 -0
  270. package/src/commands/med-family/cli-agent.ts +270 -0
  271. package/src/commands/med-family/fast-forward.ts +230 -0
  272. package/src/commands/med-family/ide.ts +163 -0
  273. package/src/commands/med-family/index.ts +37 -0
  274. package/src/commands/med-family/mesh-crud.ts +1309 -0
  275. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  276. package/src/commands/med-family/mesh-queue.ts +167 -0
  277. package/src/commands/med-family/mesh-restart.ts +92 -0
  278. package/src/commands/med-family/types.ts +153 -0
  279. package/src/commands/mesh-coordinator.ts +334 -124
  280. package/src/commands/router-aggregate-status.ts +209 -0
  281. package/src/commands/router-mesh-session-owner.ts +114 -0
  282. package/src/commands/router-refine.ts +1794 -0
  283. package/src/commands/router-worktree-cleanup.ts +870 -0
  284. package/src/commands/router.ts +567 -4149
  285. package/src/commands/stream-commands.ts +8 -0
  286. package/src/commands/upgrade-helper.ts +310 -45
  287. package/src/config/chat-history.ts +517 -24
  288. package/src/config/config.ts +30 -0
  289. package/src/config/mesh-config.ts +300 -24
  290. package/src/config/mesh-json-config.ts +376 -0
  291. package/src/config/recent-activity.ts +8 -2
  292. package/src/config/registry-resolver.ts +100 -0
  293. package/src/config/repo-settings.ts +111 -0
  294. package/src/config/state-store.ts +55 -0
  295. package/src/daemon/dev-auto-implement.ts +3 -2
  296. package/src/daemon/dev-cli-debug.ts +10 -1
  297. package/src/daemon/dev-server.ts +0 -541
  298. package/src/detection/cli-detector.ts +94 -9
  299. package/src/detection/ide-detector.ts +55 -16
  300. package/src/detection/win32-ide-version.ts +106 -0
  301. package/src/git/change-impact-config.ts +354 -0
  302. package/src/git/git-commands.ts +112 -16
  303. package/src/git/git-diff.ts +81 -11
  304. package/src/git/git-executor.ts +12 -0
  305. package/src/git/git-status.ts +822 -48
  306. package/src/git/git-types.ts +14 -62
  307. package/src/git/git-worktree.ts +261 -4
  308. package/src/git/index.ts +17 -0
  309. package/src/index.ts +190 -13
  310. package/src/installer.d.ts +1 -1
  311. package/src/installer.ts +8 -6
  312. package/src/ipc/local-ipc-server.ts +278 -0
  313. package/src/launch.d.ts +1 -1
  314. package/src/launch.ts +37 -28
  315. package/src/logging/async-batch-writer.ts +55 -0
  316. package/src/logging/command-log.ts +7 -5
  317. package/src/logging/debug-config.ts +25 -0
  318. package/src/logging/debug-trace.ts +7 -2
  319. package/src/logging/log-redactor.ts +100 -0
  320. package/src/logging/log-tail-reader.ts +341 -0
  321. package/src/logging/logger.ts +14 -7
  322. package/src/mesh/contracts.ts +510 -0
  323. package/src/mesh/coordinator-prompt.ts +627 -34
  324. package/src/mesh/coordinator-registry.ts +121 -0
  325. package/src/mesh/mesh-active-work.ts +706 -0
  326. package/src/mesh/mesh-clone-grace.ts +68 -0
  327. package/src/mesh/mesh-completion-synthesis.ts +416 -0
  328. package/src/mesh/mesh-coordinator-config.ts +97 -0
  329. package/src/mesh/mesh-delivery-policy.ts +284 -0
  330. package/src/mesh/mesh-event-classify.ts +78 -0
  331. package/src/mesh/mesh-event-forwarding.ts +2030 -0
  332. package/src/mesh/mesh-event-trace.ts +67 -0
  333. package/src/mesh/mesh-events-coordinator.ts +32 -0
  334. package/src/mesh/mesh-events-pending.ts +1548 -0
  335. package/src/mesh/mesh-events-stale.ts +452 -0
  336. package/src/mesh/mesh-events-utils.ts +443 -0
  337. package/src/mesh/mesh-events.ts +43 -1035
  338. package/src/mesh/mesh-fast-forward.ts +856 -0
  339. package/src/mesh/mesh-host-ownership.ts +41 -3
  340. package/src/mesh/mesh-init.ts +350 -0
  341. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  342. package/src/mesh/mesh-ledger.ts +897 -102
  343. package/src/mesh/mesh-magi-status.ts +223 -0
  344. package/src/mesh/mesh-missions.ts +695 -0
  345. package/src/mesh/mesh-node-identity.ts +2065 -0
  346. package/src/mesh/mesh-queue-assignment.ts +2498 -0
  347. package/src/mesh/mesh-reconcile-acked-hold.ts +230 -0
  348. package/src/mesh/mesh-reconcile-config.ts +66 -0
  349. package/src/mesh/mesh-reconcile-identity.ts +103 -0
  350. package/src/mesh/mesh-reconcile-loop.ts +1696 -0
  351. package/src/mesh/mesh-reconcile-v2-backstop.ts +62 -0
  352. package/src/mesh/mesh-refine-batch.ts +205 -0
  353. package/src/mesh/mesh-refine-gates.ts +1792 -0
  354. package/src/mesh/mesh-refine-status.ts +231 -0
  355. package/src/mesh/mesh-remote-event-pull.ts +279 -0
  356. package/src/mesh/mesh-review-inbox.ts +308 -0
  357. package/src/mesh/mesh-routing.ts +291 -0
  358. package/src/mesh/mesh-runtime-store.ts +2292 -0
  359. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  360. package/src/mesh/mesh-task-inflight.ts +70 -0
  361. package/src/mesh/mesh-task-stats.ts +161 -0
  362. package/src/mesh/mesh-unresolved-forward-outbox.ts +215 -0
  363. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  364. package/src/mesh/mesh-work-queue.ts +1489 -145
  365. package/src/mesh/preview-freshness.ts +163 -0
  366. package/src/mesh/refine-config.ts +129 -12
  367. package/src/mesh/worktree-bootstrap-config.ts +599 -0
  368. package/src/providers/acp-provider-instance.ts +43 -10
  369. package/src/providers/approval-utils.d.ts +5 -0
  370. package/src/providers/approval-utils.ts +100 -6
  371. package/src/providers/chat-message-normalization.ts +126 -4
  372. package/src/providers/cli-provider-effect-format.ts +53 -0
  373. package/src/providers/cli-provider-history-dedup.ts +75 -0
  374. package/src/providers/cli-provider-input-prompt.ts +133 -0
  375. package/src/providers/cli-provider-instance-types.ts +131 -0
  376. package/src/providers/cli-provider-instance.ts +2771 -499
  377. package/src/providers/cli-provider-status-helpers.ts +123 -0
  378. package/src/providers/cli-provider-transcript-merge.ts +114 -0
  379. package/src/providers/contracts.d.ts +55 -0
  380. package/src/providers/contracts.ts +207 -6
  381. package/src/providers/extension-provider-instance.ts +12 -7
  382. package/src/providers/external-sources.ts +218 -0
  383. package/src/providers/ide-provider-instance.ts +35 -12
  384. package/src/providers/manual-attendance.ts +105 -0
  385. package/src/providers/native-history/antigravity-claim-registry.ts +138 -0
  386. package/src/providers/native-history/antigravity-cli-transcript.ts +1275 -0
  387. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  388. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  389. package/src/providers/native-history/constants.ts +19 -0
  390. package/src/providers/native-history/dispatcher.ts +617 -0
  391. package/src/providers/native-history/hermes-cli-transcript.ts +312 -0
  392. package/src/providers/native-history/index.ts +30 -0
  393. package/src/providers/provider-instance-manager.ts +71 -0
  394. package/src/providers/provider-instance.ts +24 -1
  395. package/src/providers/provider-loader.ts +700 -55
  396. package/src/providers/provider-schema.ts +93 -14
  397. package/src/providers/provider-trust.ts +114 -0
  398. package/src/providers/read-chat-contract.ts +76 -16
  399. package/src/providers/sdk/README.md +49 -0
  400. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  401. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  402. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  403. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
  404. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  405. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  406. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  407. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  408. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  409. package/src/providers/sdk/v1/index.ts +152 -0
  410. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  411. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  412. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  413. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +538 -0
  414. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  415. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  416. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  417. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  418. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  419. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  420. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  421. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  422. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  423. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  424. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  425. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  426. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  427. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  428. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  429. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  430. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  431. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  432. package/src/providers/sdk/v1/validators/index.ts +19 -0
  433. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  434. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  435. package/src/providers/spec/adapter.ts +246 -0
  436. package/src/providers/spec/cli-adapter.ts +1064 -0
  437. package/src/providers/spec/evaluator.ts +407 -0
  438. package/src/providers/spec/fsm-driver.ts +1528 -0
  439. package/src/providers/spec/fsm-evaluator.ts +290 -0
  440. package/src/providers/spec/fsm-loader.ts +128 -0
  441. package/src/providers/spec/fsm-types.ts +301 -0
  442. package/src/providers/spec/native-history-executor.ts +1241 -0
  443. package/src/providers/spec/pre-launch-trust.ts +104 -0
  444. package/src/providers/spec/route.ts +51 -0
  445. package/src/providers/spec/types.ts +284 -0
  446. package/src/providers/status-monitor.d.ts +7 -7
  447. package/src/providers/status-monitor.ts +37 -22
  448. package/src/providers/transcript-v2.ts +567 -0
  449. package/src/providers/types/interactive-prompt.ts +536 -0
  450. package/src/providers/version-archive.ts +64 -24
  451. package/src/providers/working-dir.ts +23 -0
  452. package/src/repo-mesh-types.ts +861 -14
  453. package/src/runtime-defaults.ts +39 -0
  454. package/src/session-host/managed-host.ts +218 -0
  455. package/src/session-host/runtime-surface.ts +20 -80
  456. package/src/sessions/registry.ts +44 -0
  457. package/src/shared-types-extra.ts +2 -4
  458. package/src/shared-types.d.ts +8 -0
  459. package/src/shared-types.ts +148 -55
  460. package/src/status/builders.ts +69 -6
  461. package/src/status/chat-tail-hot-sessions.ts +117 -2
  462. package/src/status/normalize.ts +2 -0
  463. package/src/status/reporter.ts +19 -1
  464. package/src/status/snapshot.ts +100 -27
  465. package/src/system/hash.ts +23 -0
  466. package/src/system/host-memory.ts +29 -12
  467. package/src/system/load-better-sqlite3.ts +68 -0
  468. package/src/types.ts +26 -6
  469. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  470. package/dist/mesh/mesh-sync.d.ts +0 -53
  471. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  472. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  473. package/src/mesh/mesh-sync.ts +0 -111
@@ -0,0 +1,870 @@
1
+ /**
2
+ * Worktree / mesh-session cleanup — extracted from router.ts (behavior-preserving code move).
3
+ *
4
+ * These functions were `DaemonCommandRouter` methods; they now take the router
5
+ * instance as `self`. The class keeps thin delegating wrappers (cleanupMeshSessions /
6
+ * cleanupLocalWorktreeNode / precheckLocalWorktreeRemovable are bound into
7
+ * MedFamilyContext; bestEffortRemoveWorktreeDir is overridden on the instance by a
8
+ * unit test — intra-cluster calls therefore go through `self.` so instance dispatch
9
+ * (and that test override) is preserved). No log string, error message, refusal
10
+ * condition, or result shape changed — only physical location + `this.` → `self.`.
11
+ */
12
+ import * as fs from 'fs';
13
+ import { execFileSync } from 'node:child_process';
14
+ import { resolve as pathResolve } from 'path';
15
+ import type { DaemonCommandRouter } from './router.js';
16
+ import { LOG } from '../logging/logger.js';
17
+ import { meshNodeIdMatches, daemonIdsEquivalent } from '@adhdev/mesh-shared';
18
+ import { readStringValue, readObjectRecord, readBooleanValue } from '../mesh/mesh-node-identity.js';
19
+ import { checkWorktreeChangesPatchEquivalentInRef, MeshWorktreePatchContainmentSummary } from '../mesh/mesh-refine-gates.js';
20
+ import { getSessionHostSurfaceKind } from '../session-host/runtime-surface.js';
21
+ import type { RepoMeshSessionCleanupMode } from '../repo-mesh-types.js';
22
+ import { DEFAULT_MESH_POLICY, magiAutoLaunchedSessionCleanupDecision } from '../repo-mesh-types.js';
23
+
24
+ export function sessionMatchesMeshNode(self: DaemonCommandRouter, record: any, node: any, nodeId: string, sessionIds?: Set<string>): boolean {
25
+ const sessionId = typeof record?.sessionId === 'string' ? record.sessionId : '';
26
+ if (!sessionId) return false;
27
+ if (sessionIds?.size) return sessionIds.has(sessionId);
28
+ const workspace = typeof node?.workspace === 'string' ? node.workspace : '';
29
+ if (workspace && record?.workspace === workspace) return true;
30
+ if (record?.meta?.meshNodeId === nodeId) return true;
31
+ return false;
32
+ }
33
+
34
+ /**
35
+ * Best-effort recursive removal of a managed worktree directory.
36
+ *
37
+ * The git-registry de-registration is the safety-critical step of worktree
38
+ * teardown; a leftover directory must never gate dropping the node from the
39
+ * mesh. On Windows, `fs.rmSync` can throw EINVAL/EPERM/EBUSY on submodule
40
+ * gitlink (`.git`) files, long paths, junctions, or while a just-stopped
41
+ * delegate session is still releasing a handle/cwd on the directory. This
42
+ * helper absorbs those errors (never throws), with bounded retries + backoff
43
+ * to give handles time to release, and reports whether residue remains.
44
+ */
45
+ export async function bestEffortRemoveWorktreeDir(self: DaemonCommandRouter, dir: string): Promise<{ removed: boolean; residue: boolean; error?: string }> {
46
+ if (!dir || !fs.existsSync(dir)) return { removed: true, residue: false };
47
+ const sleep = (ms: number) => new Promise<void>(resolve => setTimeout(resolve, ms));
48
+ // EINVAL is the Windows symptom for submodule gitlink residue; the rest are
49
+ // transient lock/permission classes. None should escape as a throw here.
50
+ const ABSORB = new Set(['EINVAL', 'EPERM', 'EBUSY', 'ENOTEMPTY', 'EACCES', 'EMFILE', 'ENFILE']);
51
+ let lastErr: any;
52
+ for (let attempt = 0; attempt < 4; attempt++) {
53
+ try {
54
+ // maxRetries/retryDelay give fs.rmSync its own internal backoff for
55
+ // EBUSY/EPERM/ENOTEMPTY; the outer loop extends tolerance to EINVAL.
56
+ fs.rmSync(dir, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
57
+ if (!fs.existsSync(dir)) return { removed: true, residue: false };
58
+ lastErr = new Error('directory still present after rmSync');
59
+ } catch (e: any) {
60
+ lastErr = e;
61
+ const code = typeof e?.code === 'string' ? e.code : '';
62
+ if (code && !ABSORB.has(code)) {
63
+ // Unexpected error class — stay best-effort (no throw) but stop retrying.
64
+ break;
65
+ }
66
+ }
67
+ await sleep(150 * (attempt + 1));
68
+ }
69
+ return fs.existsSync(dir)
70
+ ? { removed: false, residue: true, error: String(lastErr?.message || lastErr || 'unknown rm error') }
71
+ : { removed: true, residue: false };
72
+ }
73
+
74
+ /**
75
+ * Non-destructive precheck mirroring every REFUSAL condition in
76
+ * {@link cleanupLocalWorktreeNode} — missing workspace / source-repo / branch
77
+ * metadata, unexpected (non-managed) path, branch mismatch — PLUS the
78
+ * dirty-worktree guard that `removeWorktree(requireClean)` enforces
79
+ * (`git status --porcelain`). It performs ZERO destructive actions: no
80
+ * `git worktree remove`, no `git worktree prune`, no directory deletion.
81
+ *
82
+ * remove_mesh_node calls this BEFORE any session cleanup so that a refusal
83
+ * (the common one being a dirty worktree) does not first stop/delete the
84
+ * delegated session and orphan it — the original ordering bug. Success/skip
85
+ * cases that the real cleanup handles idempotently (worktree path already
86
+ * gone, git-de-registered residue) are NOT refusals and return `{ ok: true }`.
87
+ *
88
+ * `force:true` skips the dirty guard, preserving `removeWorktree`'s
89
+ * `requireClean: !force` semantics. This is a read-only superset check; the
90
+ * authoritative `requireClean` guard inside `removeWorktree` is intentionally
91
+ * kept as a second line of defense against a precheck→execute race.
92
+ */
93
+ export async function precheckLocalWorktreeRemovable(self: DaemonCommandRouter, args: {
94
+ mesh: any;
95
+ node: any;
96
+ nodeId: string;
97
+ force?: boolean;
98
+ }): Promise<{ ok: true } | { ok: false; code: string; error: string; recoveryHint: string }> {
99
+ const sessionPreservedNote = ' The delegated session was left running (not stopped) — resolve the issue and retry mesh_remove_node.';
100
+ const workspace = typeof args.node?.workspace === 'string' ? args.node.workspace.trim() : '';
101
+ if (!workspace) {
102
+ return {
103
+ ok: false,
104
+ code: 'mesh_worktree_cleanup_missing_workspace',
105
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
106
+ recoveryHint: 'Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains.' + sessionPreservedNote,
107
+ };
108
+ }
109
+
110
+ // Worktree path already gone → not a refusal; the real cleanup returns a
111
+ // skipped:true success and the node is dropped from the registry.
112
+ if (!fs.existsSync(workspace)) return { ok: true };
113
+
114
+ const sourceNode = args.node?.clonedFromNodeId
115
+ ? args.mesh?.nodes?.find((n: any) => meshNodeIdMatches(n, args.node.clonedFromNodeId))
116
+ : args.mesh?.nodes?.find((n: any) => !n.isLocalWorktree);
117
+ const repoRoot = typeof sourceNode?.repoRoot === 'string' && sourceNode.repoRoot.trim()
118
+ ? sourceNode.repoRoot.trim()
119
+ : typeof sourceNode?.workspace === 'string' && sourceNode.workspace.trim()
120
+ ? sourceNode.workspace.trim()
121
+ : '';
122
+ if (!repoRoot || !fs.existsSync(repoRoot)) {
123
+ return {
124
+ ok: false,
125
+ code: 'mesh_worktree_cleanup_missing_source_repo',
126
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
127
+ recoveryHint: 'Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying.' + sessionPreservedNote,
128
+ };
129
+ }
130
+ if (typeof args.node?.worktreeBranch !== 'string' || !args.node.worktreeBranch.trim()) {
131
+ return {
132
+ ok: false,
133
+ code: 'mesh_worktree_cleanup_missing_branch',
134
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
135
+ recoveryHint: 'Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata.' + sessionPreservedNote,
136
+ };
137
+ }
138
+
139
+ const { resolveWorktreePath, listWorktrees } = await import('../git/git-worktree.js');
140
+ const normalizePath = (value: string) => {
141
+ const resolved = pathResolve(value);
142
+ try { return fs.realpathSync(resolved); } catch { return resolved; }
143
+ };
144
+ const expectedPath = normalizePath(resolveWorktreePath(repoRoot, String(args.mesh?.name || args.mesh?.id || 'mesh'), args.node.worktreeBranch));
145
+ const actualPath = normalizePath(workspace);
146
+ if (actualPath !== expectedPath) {
147
+ return {
148
+ ok: false,
149
+ code: 'mesh_worktree_cleanup_unexpected_path',
150
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
151
+ recoveryHint: 'Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree.' + sessionPreservedNote,
152
+ };
153
+ }
154
+
155
+ const entries = await listWorktrees(repoRoot);
156
+ const managedEntry = entries.find(entry => normalizePath(entry.path) === actualPath);
157
+ // De-registered residue (git no longer lists it as a worktree) is an
158
+ // idempotent recovery path in the real cleanup, NOT a refusal — neither the
159
+ // branch-mismatch nor the dirty check applies, so let the removal proceed.
160
+ if (!managedEntry) return { ok: true };
161
+
162
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
163
+ return {
164
+ ok: false,
165
+ code: 'mesh_worktree_cleanup_branch_mismatch',
166
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
167
+ recoveryHint: 'Inspect the worktree branch and mesh metadata before retrying cleanup.' + sessionPreservedNote,
168
+ };
169
+ }
170
+
171
+ // Dirty-worktree guard — a read-only mirror of removeWorktree(requireClean)
172
+ // (`git status --porcelain` run inside the worktree). `force:true` skips it,
173
+ // preserving the requireClean:!force semantics.
174
+ if (args.force !== true) {
175
+ const { execFile } = await import('node:child_process');
176
+ const { promisify } = await import('node:util');
177
+ const execFileAsync = promisify(execFile);
178
+ try {
179
+ const { stdout } = await execFileAsync('git', ['status', '--porcelain'], {
180
+ cwd: workspace, encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true,
181
+ });
182
+ if (stdout.trim()) {
183
+ return {
184
+ ok: false,
185
+ code: 'mesh_worktree_cleanup_dirty',
186
+ error: `Refusing to remove dirty worktree: ${workspace}`,
187
+ recoveryHint: 'Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe.' + sessionPreservedNote,
188
+ };
189
+ }
190
+ } catch {
191
+ // A status probe failure is not itself proof of dirtiness; defer to
192
+ // the authoritative removeWorktree(requireClean) guard rather than
193
+ // refusing here (which would block an otherwise-clean removal).
194
+ return { ok: true };
195
+ }
196
+ }
197
+
198
+ return { ok: true };
199
+ }
200
+
201
+ export async function cleanupLocalWorktreeNode(self: DaemonCommandRouter, args: {
202
+ mesh: any;
203
+ node: any;
204
+ nodeId: string;
205
+ force?: boolean;
206
+ }): Promise<{ success: true; skipped?: boolean; removedPath?: string; repoRoot?: string; reason?: string; fallback?: string; forced?: boolean; convergence?: Record<string, unknown>; recovered?: boolean; residue?: boolean; residueWarning?: string; residueError?: string; branchRefDeleted?: boolean; branchRefReason?: string; branchRefForced?: boolean; branchRefWarning?: string } | { success: false; code: string; error: string; recoveryHint: string; convergence?: Record<string, unknown> }> {
207
+ const workspace = typeof args.node?.workspace === 'string' ? args.node.workspace.trim() : '';
208
+ if (!workspace) {
209
+ return {
210
+ success: false,
211
+ code: 'mesh_worktree_cleanup_missing_workspace',
212
+ error: `Worktree node '${args.nodeId}' is missing workspace metadata`,
213
+ recoveryHint: 'Inspect the mesh node record before removing it, or remove stale metadata manually only after confirming no managed worktree remains.',
214
+ };
215
+ }
216
+
217
+ const worktreeExists = fs.existsSync(workspace);
218
+ const sourceNode = args.node?.clonedFromNodeId
219
+ ? args.mesh?.nodes?.find((n: any) => meshNodeIdMatches(n, args.node.clonedFromNodeId))
220
+ : args.mesh?.nodes?.find((n: any) => !n.isLocalWorktree);
221
+ const repoRoot = typeof sourceNode?.repoRoot === 'string' && sourceNode.repoRoot.trim()
222
+ ? sourceNode.repoRoot.trim()
223
+ : typeof sourceNode?.workspace === 'string' && sourceNode.workspace.trim()
224
+ ? sourceNode.workspace.trim()
225
+ : '';
226
+
227
+ if (!worktreeExists) {
228
+ return { success: true, skipped: true, removedPath: workspace, repoRoot: repoRoot || undefined, reason: 'worktree_path_missing' };
229
+ }
230
+ if (!repoRoot || !fs.existsSync(repoRoot)) {
231
+ return {
232
+ success: false,
233
+ code: 'mesh_worktree_cleanup_missing_source_repo',
234
+ error: `Refusing to remove worktree '${workspace}' because the source repo root is unavailable`,
235
+ recoveryHint: 'Run mesh_remove_node from the machine that owns the source repo, or verify the source node metadata before retrying.',
236
+ };
237
+ }
238
+ if (typeof args.node?.worktreeBranch !== 'string' || !args.node.worktreeBranch.trim()) {
239
+ return {
240
+ success: false,
241
+ code: 'mesh_worktree_cleanup_missing_branch',
242
+ error: `Refusing to remove worktree '${workspace}' because worktreeBranch metadata is missing`,
243
+ recoveryHint: 'Confirm this is an ADHDev-managed worktree before removing it manually; managed worktree nodes include worktreeBranch metadata.',
244
+ };
245
+ }
246
+
247
+ const { resolveWorktreePath, listWorktrees, removeWorktree } = await import('../git/git-worktree.js');
248
+ const normalizePath = (value: string) => {
249
+ const resolved = pathResolve(value);
250
+ try { return fs.realpathSync(resolved); } catch { return resolved; }
251
+ };
252
+ const expectedPath = normalizePath(resolveWorktreePath(repoRoot, String(args.mesh?.name || args.mesh?.id || 'mesh'), args.node.worktreeBranch));
253
+ const actualPath = normalizePath(workspace);
254
+ if (actualPath !== expectedPath) {
255
+ return {
256
+ success: false,
257
+ code: 'mesh_worktree_cleanup_unexpected_path',
258
+ error: `Refusing to remove worktree '${workspace}' because it is not at the expected managed path '${expectedPath}'`,
259
+ recoveryHint: 'Use git worktree list/status to inspect the path. Retry only after confirming the mesh node metadata points to an ADHDev-managed worktree.',
260
+ };
261
+ }
262
+
263
+ const entries = await listWorktrees(repoRoot);
264
+ const managedEntry = entries.find(entry => normalizePath(entry.path) === actualPath);
265
+ if (!managedEntry) {
266
+ // Idempotent residue recovery (NOT a refusal). By this point the path is
267
+ // already proven ADHDev-managed: worktreeBranch metadata is present and
268
+ // actualPath === expectedPath. Git nonetheless no longer lists it as a
269
+ // worktree. This is the post-force-fallback re-entry state — an earlier
270
+ // removal de-registered the worktree from git but left the directory
271
+ // behind (commonly Windows EINVAL on submodule gitlink files). Refusing
272
+ // here would strand the node in mesh membership forever, so prune any
273
+ // stale registration, best-effort remove the leftover directory, and
274
+ // report success so the caller drops the node from the mesh registry.
275
+ try {
276
+ const { execFile } = await import('node:child_process');
277
+ const { promisify } = await import('node:util');
278
+ const execFileAsync = promisify(execFile);
279
+ await execFileAsync('git', ['worktree', 'prune'], {
280
+ cwd: repoRoot, encoding: 'utf8', timeout: 30_000, maxBuffer: 4 * 1024 * 1024, windowsHide: true,
281
+ });
282
+ } catch { /* prune is best-effort */ }
283
+ const rm = await self.bestEffortRemoveWorktreeDir(workspace);
284
+ return {
285
+ success: true,
286
+ removedPath: workspace,
287
+ repoRoot,
288
+ reason: 'worktree_unregistered_residue_recovered',
289
+ recovered: true,
290
+ ...(rm.residue ? {
291
+ residue: true,
292
+ residueWarning: `Worktree was already de-registered from git but the directory could not be fully removed (leftover residue at '${workspace}'): ${rm.error || 'unknown error'}. The node will be dropped from the mesh; remove the directory manually if needed.`,
293
+ residueError: rm.error,
294
+ } : {}),
295
+ };
296
+ }
297
+ if (managedEntry.branch && managedEntry.branch !== args.node.worktreeBranch) {
298
+ return {
299
+ success: false,
300
+ code: 'mesh_worktree_cleanup_branch_mismatch',
301
+ error: `Refusing to remove '${workspace}' because git reports branch '${managedEntry.branch}', expected '${args.node.worktreeBranch}'`,
302
+ recoveryHint: 'Inspect the worktree branch and mesh metadata before retrying cleanup.',
303
+ };
304
+ }
305
+
306
+ // Always evaluate real merge convergence so we can decide whether the
307
+ // branch ref is safe to delete after removal — even on the force path,
308
+ // where `force_override` only authorizes the *worktree* removal and must
309
+ // NOT be taken as proof the branch is merged (that would risk work loss).
310
+ const mergeConvergence = await self.getWorktreeForceCleanupConvergence({ repoRoot, workspace, node: args.node });
311
+ const forceFallbackConvergence = args.force
312
+ ? { allow: true, status: 'force_override', source: 'caller_force_flag' }
313
+ : mergeConvergence;
314
+
315
+ // After the worktree is removed, delete the branch ref iff the branch is
316
+ // fully merged into the default ref (no work loss). Otherwise preserve it
317
+ // and surface a warning. `mergeConvergence` (NOT the force override) is the
318
+ // authority on merged-ness.
319
+ const deleteBranchIfMerged = async () => {
320
+ const branch = String(args.node.worktreeBranch).trim();
321
+ const status = mergeConvergence.allow ? (mergeConvergence.status || '') : '';
322
+ const MERGED_STATUSES = new Set([
323
+ 'merged_to_main', 'merged_pushed', 'merged_to_default_ref', 'cleanup_candidate',
324
+ ]);
325
+ const PATCH_EQUIV_STATUS = 'patch_equivalent_to_default_ref';
326
+ if (!branch) {
327
+ return { branchRefDeleted: false, branchRefReason: 'empty_branch_name' };
328
+ }
329
+ if (!mergeConvergence.allow || (!MERGED_STATUSES.has(status) && status !== PATCH_EQUIV_STATUS)) {
330
+ return {
331
+ branchRefDeleted: false,
332
+ branchRefReason: `branch_not_merged_preserved: ${mergeConvergence.error || mergeConvergence.status || 'convergence_unverified'}`,
333
+ branchRefWarning: `Branch ref '${branch}' was preserved (not deleted) because it is not confirmed merged into the default ref — no work was lost. Merge it (or pass a verified branchConvergence final state) and re-run cleanup, or delete it manually after confirming.`,
334
+ };
335
+ }
336
+ const { deleteBranchRef } = await import('../git/git-worktree.js');
337
+ // `-d` can detect a true fast-forward/merge; patch-equivalent landings
338
+ // (squash/cherry-pick) are invisible to `-d`, so allow the verified `-D`
339
+ // fallback only for the patch-equivalence status.
340
+ const res = await deleteBranchRef(repoRoot, branch, { safeDeleteOnly: status !== PATCH_EQUIV_STATUS });
341
+ return {
342
+ branchRefDeleted: res.deleted,
343
+ branchRefReason: res.reason,
344
+ ...(res.forced ? { branchRefForced: true } : {}),
345
+ ...(res.deleted ? {} : {
346
+ branchRefWarning: `Branch ref '${branch}' could not be deleted (${res.reason}); it was preserved so no work is lost.`,
347
+ }),
348
+ };
349
+ };
350
+
351
+ try {
352
+ const result = await removeWorktree(repoRoot, workspace, {
353
+ requireClean: !args.force,
354
+ allowSubmoduleForceFallback: forceFallbackConvergence.allow,
355
+ });
356
+ const branchOutcome = await deleteBranchIfMerged();
357
+ return {
358
+ success: true,
359
+ removedPath: result.removedPath,
360
+ repoRoot,
361
+ ...branchOutcome,
362
+ ...(result.fallback ? {
363
+ fallback: result.fallback,
364
+ forced: result.forced,
365
+ reason: result.reason,
366
+ convergence: forceFallbackConvergence,
367
+ } : {}),
368
+ };
369
+ } catch (e: any) {
370
+ const message = String(e?.message || e || 'worktree cleanup failed');
371
+ const dirty = message.includes('dirty worktree') || message.includes('local changes');
372
+ const isSubmoduleGuard = /working trees containing submodules cannot be moved or removed/i.test(message);
373
+ const submoduleForceBlocked = isSubmoduleGuard && !forceFallbackConvergence.allow;
374
+
375
+ // Fallback 1: submodule guard on --force path — deinit submodules first, then retry remove
376
+ if (isSubmoduleGuard && forceFallbackConvergence.allow) {
377
+ const { execFile } = await import('node:child_process');
378
+ const { promisify } = await import('node:util');
379
+ const execFileAsync = promisify(execFile);
380
+ const GIT_TIMEOUT_CLEANUP = 30_000;
381
+ const GIT_MAX_BUFFER_CLEANUP = 4 * 1024 * 1024;
382
+ try {
383
+ await execFileAsync('git', ['-C', workspace, 'submodule', 'deinit', '--all', '-f'], {
384
+ encoding: 'utf8', timeout: GIT_TIMEOUT_CLEANUP, maxBuffer: GIT_MAX_BUFFER_CLEANUP, windowsHide: true,
385
+ });
386
+ await execFileAsync('git', ['worktree', 'remove', '--force', workspace], {
387
+ cwd: repoRoot, encoding: 'utf8', timeout: GIT_TIMEOUT_CLEANUP, maxBuffer: GIT_MAX_BUFFER_CLEANUP, windowsHide: true,
388
+ });
389
+ const branchOutcome = await deleteBranchIfMerged();
390
+ return {
391
+ success: true,
392
+ removedPath: workspace,
393
+ repoRoot,
394
+ ...branchOutcome,
395
+ fallback: 'git_worktree_remove_submodule_deinit' as const,
396
+ forced: true,
397
+ reason: 'working_trees_containing_submodules' as const,
398
+ convergence: forceFallbackConvergence,
399
+ };
400
+ } catch (deinitError: any) {
401
+ // Fallback 2: deinit+remove still failed — best-effort directory
402
+ // removal + prune. The path is already proven managed/converged
403
+ // here, and a leftover directory must NOT gate dropping the node
404
+ // from the mesh, so absorb Windows EINVAL/EPERM and report success
405
+ // with a residue warning instead of failing the whole removal.
406
+ const rm = await self.bestEffortRemoveWorktreeDir(workspace);
407
+ try {
408
+ await execFileAsync('git', ['worktree', 'prune'], {
409
+ cwd: repoRoot, encoding: 'utf8', timeout: GIT_TIMEOUT_CLEANUP, maxBuffer: GIT_MAX_BUFFER_CLEANUP, windowsHide: true,
410
+ });
411
+ } catch { /* prune is best-effort */ }
412
+ const branchOutcome = await deleteBranchIfMerged();
413
+ return {
414
+ success: true,
415
+ removedPath: workspace,
416
+ repoRoot,
417
+ ...branchOutcome,
418
+ fallback: 'fs_rm_worktree_prune' as const,
419
+ forced: true,
420
+ reason: 'working_trees_containing_submodules' as const,
421
+ convergence: forceFallbackConvergence,
422
+ ...(rm.residue ? {
423
+ residue: true,
424
+ residueWarning: `Worktree was de-registered from git but the directory could not be fully removed (leftover residue at '${workspace}'): ${rm.error || 'unknown error'}; deinit+remove first failed with: ${deinitError?.message || deinitError}. The node will be dropped from the mesh; remove the directory manually if needed.`,
425
+ residueError: rm.error,
426
+ } : {}),
427
+ };
428
+ }
429
+ }
430
+
431
+ return {
432
+ success: false,
433
+ code: dirty
434
+ ? 'mesh_worktree_cleanup_dirty'
435
+ : submoduleForceBlocked
436
+ ? 'mesh_worktree_cleanup_force_fallback_blocked'
437
+ : 'mesh_worktree_cleanup_failed',
438
+ error: submoduleForceBlocked
439
+ ? `${message}; refusing --force fallback because convergence could not be verified: ${forceFallbackConvergence.error || 'unknown convergence state'}`
440
+ : message,
441
+ recoveryHint: dirty
442
+ ? 'Commit, stash, or intentionally discard the worktree changes before retrying mesh_remove_node. The mesh registry entry is preserved until cleanup is safe.'
443
+ : submoduleForceBlocked
444
+ ? 'Verify the worktree branch is merged/contained in the source default branch (for example origin/main) or mark the node with a safe branchConvergence final state, or pass force:true if content is confirmed already in main.'
445
+ : 'Inspect git worktree status/list from the source repo and retry after resolving the reported cleanup failure.',
446
+ ...(submoduleForceBlocked ? { convergence: forceFallbackConvergence } : {}),
447
+ };
448
+ }
449
+ }
450
+
451
+ export async function getWorktreeForceCleanupConvergence(self: DaemonCommandRouter, args: {
452
+ repoRoot: string;
453
+ workspace: string;
454
+ node: any;
455
+ }): Promise<{ allow: boolean; status?: string; source?: string; ref?: string; error?: string }> {
456
+ const metadataStatus = typeof args.node?.branchConvergence?.status === 'string'
457
+ ? args.node.branchConvergence.status
458
+ : '';
459
+ if (metadataStatus === 'merged_to_main' || metadataStatus === 'cleanup_candidate' || metadataStatus === 'merged_pushed') {
460
+ return { allow: true, status: metadataStatus, source: 'node_branch_convergence' };
461
+ }
462
+
463
+ // Also allow when the node's last recorded refine job reached final convergence merged_pushed
464
+ const refinedConvergence = typeof args.node?.refineState?.finalBranchConvergenceState?.status === 'string'
465
+ ? args.node.refineState.finalBranchConvergenceState.status
466
+ : typeof args.node?.lastRefineResult?.finalBranchConvergenceState?.status === 'string'
467
+ ? args.node.lastRefineResult.finalBranchConvergenceState.status
468
+ : '';
469
+ if (refinedConvergence === 'merged_pushed' || refinedConvergence === 'merged_to_main') {
470
+ return { allow: true, status: refinedConvergence, source: 'node_refine_state' };
471
+ }
472
+
473
+ const { execFile } = await import('node:child_process');
474
+ const { promisify } = await import('node:util');
475
+ const execFileAsync = promisify(execFile);
476
+ const runGit = async (gitArgs: string[], cwd: string): Promise<string> => {
477
+ const { stdout } = await execFileAsync('git', gitArgs, {
478
+ cwd,
479
+ encoding: 'utf8',
480
+ timeout: 30_000,
481
+ maxBuffer: 4 * 1024 * 1024,
482
+ windowsHide: true,
483
+ });
484
+ return String(stdout || '').trim();
485
+ };
486
+
487
+ let head = '';
488
+ try {
489
+ head = await runGit(['rev-parse', 'HEAD'], args.workspace);
490
+ } catch (e: any) {
491
+ return { allow: false, error: `could not resolve worktree HEAD: ${e?.message || e}` };
492
+ }
493
+ if (!head) return { allow: false, error: 'worktree HEAD is empty' };
494
+
495
+ const candidateRefs: string[] = [];
496
+ try {
497
+ const defaultBranch = await runGit(['branch', '--show-current'], args.repoRoot);
498
+ if (defaultBranch) {
499
+ candidateRefs.push(defaultBranch, `origin/${defaultBranch}`);
500
+ }
501
+ } catch { /* fall through to common refs */ }
502
+ candidateRefs.push('origin/main', 'origin/master', 'main', 'master');
503
+
504
+ const seen = new Set<string>();
505
+ const checkedRefs: string[] = [];
506
+ const resolvedRefCommits: Array<{ ref: string; commit: string }> = [];
507
+ for (const ref of candidateRefs) {
508
+ if (!ref || seen.has(ref)) continue;
509
+ seen.add(ref);
510
+ let commit = '';
511
+ try {
512
+ commit = await runGit(['rev-parse', '--verify', `${ref}^{commit}`], args.repoRoot);
513
+ } catch {
514
+ continue;
515
+ }
516
+ checkedRefs.push(ref);
517
+ resolvedRefCommits.push({ ref, commit });
518
+ try {
519
+ await runGit(['merge-base', '--is-ancestor', head, commit], args.repoRoot);
520
+ return { allow: true, status: 'merged_to_default_ref', source: 'git_merge_base', ref };
521
+ } catch {
522
+ // Not contained in this candidate ref; keep checking other safe refs.
523
+ }
524
+ }
525
+
526
+ // SHA-reachability fallback: the worktree HEAD is not an ancestor of any
527
+ // candidate ref, but its CONTENT may already be present via cherry-pick /
528
+ // squash / rebase (a different commit SHA carrying the same patch). The
529
+ // Refinery accepts such patch-equivalent landings; mirror that here so the
530
+ // cleanup guard does not falsely block a converged worktree. This is the
531
+ // heavier merge-tree/patch-id path, so it only runs after every ancestor
532
+ // check has already failed. Any failure stays conservative (NOT contained).
533
+ for (const { ref, commit } of resolvedRefCommits) {
534
+ let containment: MeshWorktreePatchContainmentSummary;
535
+ try {
536
+ containment = await checkWorktreeChangesPatchEquivalentInRef(args.repoRoot, commit, head);
537
+ } catch {
538
+ // Defensive: the helper is already exception-safe, but never let a
539
+ // thrown error escape into an allow.
540
+ continue;
541
+ }
542
+ if (containment.contained) {
543
+ return { allow: true, status: 'patch_equivalent_to_default_ref', source: 'git_patch_equivalence', ref };
544
+ }
545
+ }
546
+
547
+ return {
548
+ allow: false,
549
+ status: metadataStatus || undefined,
550
+ error: checkedRefs.length
551
+ ? `worktree HEAD is not contained in checked refs: ${checkedRefs.join(', ')}`
552
+ : 'no default/main refs were available for convergence verification',
553
+ };
554
+ }
555
+
556
+ export function isCompletedHostedSession(self: DaemonCommandRouter, record: any): boolean {
557
+ return record?.lifecycle === 'stopped' || record?.lifecycle === 'failed' || record?.lifecycle === 'interrupted';
558
+ }
559
+
560
+ export async function recordIntentionalMeshSessionStop(self: DaemonCommandRouter, args: {
561
+ meshId: string;
562
+ nodeId: string;
563
+ node: any;
564
+ sessionId: string;
565
+ mode: RepoMeshSessionCleanupMode;
566
+ source: 'mesh_cleanup_sessions' | 'mesh_remove_node' | 'magi_session_cleanup';
567
+ action: 'stop_session' | 'delete_session_force';
568
+ }): Promise<void> {
569
+ try {
570
+ const { appendLedgerEntry } = await import('../mesh/mesh-ledger.js');
571
+ appendLedgerEntry(args.meshId, {
572
+ kind: 'session_stopped',
573
+ nodeId: args.nodeId,
574
+ sessionId: args.sessionId,
575
+ payload: {
576
+ intentional: true,
577
+ reason: 'operator_cleanup',
578
+ intentionalStopReason: 'operator_cleanup',
579
+ source: args.source,
580
+ cleanupMode: args.mode,
581
+ action: args.action,
582
+ workspace: typeof args.node?.workspace === 'string' ? args.node.workspace : undefined,
583
+ },
584
+ });
585
+ } catch (e: any) {
586
+ LOG.warn('MeshCleanup', `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
587
+ }
588
+ }
589
+
590
+ export async function cleanupMeshSessions(self: DaemonCommandRouter, args: {
591
+ meshId: string;
592
+ nodeId: string;
593
+ node: any;
594
+ mode: RepoMeshSessionCleanupMode;
595
+ sessionIds?: string[];
596
+ dryRun?: boolean;
597
+ source?: 'mesh_cleanup_sessions' | 'mesh_remove_node' | 'magi_session_cleanup';
598
+ /**
599
+ * MAGI auto-cleanup safety gate: a map of sessionId → the queue task id that
600
+ * session must have been AUTO-LAUNCHED for (record meta autoLaunchedForQueueTaskId).
601
+ * When set, a matched explicit session is only acted on if its record carries that
602
+ * exact marker. A reused idle session (no marker), the coordinator session, a
603
+ * re-assigned session, or any session whose marker points at a DIFFERENT task is
604
+ * skipped (reason 'auto_launch_marker_mismatch') — so MAGI never kills a session it
605
+ * didn't itself spawn for this fan-out. Only consulted alongside explicit sessionIds.
606
+ */
607
+ requireAutoLaunchedForTaskIds?: Record<string, string>;
608
+ /**
609
+ * Opt-in orphan reclaim (default false). See SESSION-ACCUMULATION-LEAK.
610
+ * When true, a workspace-only live_runtime session (no node binding) OR a
611
+ * live_runtime session bound to a node absent from `liveMeshNodeIds` is
612
+ * stopped instead of skipped by the conservative shared-daemon guard. A
613
+ * session whose meshNodeId is STILL in `liveMeshNodeIds` (active sibling)
614
+ * is never reclaimed — that is the shared-daemon safety this preserves.
615
+ */
616
+ reclaimOrphans?: boolean;
617
+ liveMeshNodeIds?: string[];
618
+ }): Promise<{ success: boolean; [key: string]: unknown }> {
619
+ if (args.mode === 'preserve') {
620
+ return { success: true, mode: 'preserve', matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
621
+ }
622
+ if (!self.deps.sessionHostControl) return { success: false, error: 'Session host control unavailable' };
623
+
624
+ const requestedSessionIds = Array.isArray(args.sessionIds)
625
+ ? new Set(args.sessionIds.map(id => typeof id === 'string' ? id.trim() : '').filter(Boolean))
626
+ : undefined;
627
+ // Orphan-reclaim support (opt-in). The live-node id list lets us tell an
628
+ // orphan (owning node gone from the mesh) apart from a still-active sibling
629
+ // sharing this daemon. daemonIdsEquivalent (not raw ===) guards against the
630
+ // canon-identity defect class: daemonId forms (daemon_mach_ vs mach_) and
631
+ // normalized vs raw meshNodeId must compare equivalent, not literally equal.
632
+ const reclaimOrphans = args.reclaimOrphans === true;
633
+ const liveMeshNodeIds = Array.isArray(args.liveMeshNodeIds)
634
+ ? args.liveMeshNodeIds.map(id => typeof id === 'string' ? id.trim() : '').filter(Boolean)
635
+ : [];
636
+ const isNodeStillLive = (candidateNodeId: string): boolean => {
637
+ if (!candidateNodeId) return false;
638
+ return liveMeshNodeIds.some(liveId =>
639
+ liveId === candidateNodeId
640
+ || meshNodeIdMatches({ id: liveId }, candidateNodeId)
641
+ || daemonIdsEquivalent(liveId, candidateNodeId));
642
+ };
643
+ const reclaimedOrphanSessionIds: string[] = [];
644
+ const sessions = await self.deps.sessionHostControl.listSessions();
645
+ const matched = sessions.filter(record => self.sessionMatchesMeshNode(record, args.node, args.nodeId, requestedSessionIds));
646
+ const hasExplicitSessionIds = !!requestedSessionIds?.size;
647
+ const stoppedSessionIds: string[] = [];
648
+ const deletedSessionIds: string[] = [];
649
+ const skippedSessionIds: string[] = [];
650
+ const skippedLiveSessionIds: string[] = [];
651
+ const skippedCoordinatorSessionIds: string[] = [];
652
+ const skippedLiveSessionReasons: Array<{ sessionId: string; reason: string }> = [];
653
+ const skippedMarkerMismatchSessionIds: string[] = [];
654
+ const actedLiveDelegateSessionIds: string[] = [];
655
+ const deleteUnsupportedSessionIds: string[] = [];
656
+ const recordsRemainSessionIds: string[] = [];
657
+ const errors: Array<{ sessionId: string; error: string }> = [];
658
+ const cleanupSource = args.source || 'mesh_cleanup_sessions';
659
+ const markedIntentionalStopSessionIds = new Set<string>();
660
+ const markIntentionalStop = async (sessionId: string, action: 'stop_session' | 'delete_session_force') => {
661
+ if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
662
+ markedIntentionalStopSessionIds.add(sessionId);
663
+ await self.recordIntentionalMeshSessionStop({
664
+ meshId: args.meshId,
665
+ nodeId: args.nodeId,
666
+ node: args.node,
667
+ sessionId,
668
+ mode: args.mode,
669
+ source: cleanupSource,
670
+ action,
671
+ });
672
+ };
673
+ const matchedBySurfaceKind = {
674
+ live_runtime: 0,
675
+ recovery_snapshot: 0,
676
+ inactive_record: 0,
677
+ };
678
+
679
+ for (const record of matched) {
680
+ const surfaceKind = getSessionHostSurfaceKind(record);
681
+ matchedBySurfaceKind[surfaceKind] += 1;
682
+ }
683
+
684
+ for (const record of matched) {
685
+ const sessionId = String(record.sessionId);
686
+ const completed = self.isCompletedHostedSession(record);
687
+ const surfaceKind = getSessionHostSurfaceKind(record);
688
+ const liveRuntime = surfaceKind === 'live_runtime';
689
+ const coordinatorSession = readStringValue(record?.meta?.meshCoordinatorFor) === args.meshId;
690
+ // A delegate session was launched by the coordinator specifically FOR this node
691
+ // (meta.meshNodeId === this node). It is 1:1 bound to the node, even when the node
692
+ // shares its daemon runtime with the main/other nodes. Removing the node should be
693
+ // able to stop its own delegate session — the shared-daemon concern only applies to
694
+ // sessions we matched by workspace alone (which could belong to the coordinator or to
695
+ // a sibling node that is still active).
696
+ const recordNodeId = readStringValue(record?.meta?.meshNodeId);
697
+ const recordMeshNodeFor = readStringValue(record?.meta?.meshNodeFor);
698
+ const delegateBoundToThisNode = !!recordNodeId
699
+ && recordNodeId === args.nodeId
700
+ && (!recordMeshNodeFor || recordMeshNodeFor === args.meshId);
701
+ if (!hasExplicitSessionIds && coordinatorSession) {
702
+ skippedSessionIds.push(sessionId);
703
+ skippedCoordinatorSessionIds.push(sessionId);
704
+ continue;
705
+ }
706
+ // MAGI auto-cleanup marker gate. When the caller supplied a per-session
707
+ // expected autoLaunchedForQueueTaskId, the session must (a) carry the marker
708
+ // on its record meta AND (b) have it equal the expected replica task id.
709
+ // This is the safety core for MAGI: explicit session_ids bypass the
710
+ // self-coordinator / shared-daemon guards (hasExplicitSessionIds=true), so the
711
+ // ONLY thing protecting a reused-idle / coordinator / re-assigned session here
712
+ // is this marker check. Always-skip the coordinator session even when its id is
713
+ // passed explicitly (it never carries an autoLaunchedForQueueTaskId marker, so
714
+ // the mismatch branch already covers it, but be explicit for clarity).
715
+ if (args.requireAutoLaunchedForTaskIds) {
716
+ const decision = magiAutoLaunchedSessionCleanupDecision({
717
+ recordMarker: readStringValue(record?.meta?.autoLaunchedForQueueTaskId),
718
+ expectedTaskId: args.requireAutoLaunchedForTaskIds[sessionId],
719
+ isCoordinatorSession: coordinatorSession,
720
+ });
721
+ if (!decision.allow) {
722
+ skippedSessionIds.push(sessionId);
723
+ skippedMarkerMismatchSessionIds.push(sessionId);
724
+ skippedLiveSessionReasons.push({ sessionId, reason: decision.reason });
725
+ continue;
726
+ }
727
+ }
728
+ // Only the conservative shared-daemon guard for live sessions that are NOT a delegate
729
+ // explicitly bound to this node. Delegate-bound live sessions fall through and are
730
+ // stopped/deleted by the mode handlers below (which already record an intentional stop).
731
+ //
732
+ // Worktree-removal exception: when a WORKTREE node is being removed
733
+ // (source === 'mesh_remove_node' AND node.isLocalWorktree === true), its
734
+ // node-binding is already gone, so a still-live session in that workspace
735
+ // matches by workspace ALONE (recordNodeId is empty). The shared-daemon
736
+ // concern does not apply — a worktree has a private workspace path that is
737
+ // not shared with the base/other nodes — so leaving it skipped orphans the
738
+ // chat after the node + worktree dir + branch are gone. Clean it instead.
739
+ // This narrowly covers ONLY the pure workspace-only-no-binding case; a
740
+ // session bound to ANOTHER node (recordNodeId set and != this node) is still
741
+ // skipped (live_delegate_bound_to_other_node), and the coordinator session is
742
+ // already protected unconditionally above.
743
+ const matchedByWorkspaceOnly = !recordNodeId;
744
+ const isWorktreeNodeRemoval = cleanupSource === 'mesh_remove_node' && args.node?.isLocalWorktree === true;
745
+ const cleanWorkspaceOnlyForWorktree = isWorktreeNodeRemoval && matchedByWorkspaceOnly;
746
+ // Opt-in orphan reclaim (SESSION-ACCUMULATION-LEAK): a live_runtime that
747
+ // is either workspace-only (no node binding — the leaked spec-CLI case)
748
+ // or bound to a node no longer present in the mesh is an orphan and safe
749
+ // to stop. A session bound to a node STILL live in the mesh is an active
750
+ // sibling on this shared daemon and is left to the normal guard. Node
751
+ // equivalence uses isNodeStillLive (daemonIdsEquivalent under the hood),
752
+ // never a raw === on the id form.
753
+ const reclaimableOrphan = reclaimOrphans
754
+ && liveRuntime
755
+ && !delegateBoundToThisNode
756
+ && (matchedByWorkspaceOnly || !isNodeStillLive(recordNodeId));
757
+ if (!hasExplicitSessionIds && liveRuntime && !delegateBoundToThisNode && !cleanWorkspaceOnlyForWorktree && !reclaimableOrphan) {
758
+ skippedSessionIds.push(sessionId);
759
+ skippedLiveSessionIds.push(sessionId);
760
+ const reason = recordNodeId && recordNodeId !== args.nodeId
761
+ ? `live_delegate_bound_to_other_node:${recordNodeId}`
762
+ : matchedByWorkspaceOnly
763
+ ? 'live_session_matched_by_workspace_only_no_node_binding'
764
+ : 'live_session_not_bound_to_this_node';
765
+ skippedLiveSessionReasons.push({ sessionId, reason });
766
+ continue;
767
+ }
768
+ if (cleanWorkspaceOnlyForWorktree && !delegateBoundToThisNode) {
769
+ // A workspace-only live session on a worktree being removed is treated
770
+ // like a bound delegate for accounting (so callers can see it was acted on).
771
+ actedLiveDelegateSessionIds.push(sessionId);
772
+ }
773
+ if (reclaimableOrphan && !cleanWorkspaceOnlyForWorktree && !delegateBoundToThisNode) {
774
+ // Reclaimed orphan — surface it distinctly so callers can audit what
775
+ // the opt-in reclaim path stopped that the normal guard would have kept.
776
+ reclaimedOrphanSessionIds.push(sessionId);
777
+ actedLiveDelegateSessionIds.push(sessionId);
778
+ }
779
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode && args.mode === 'delete_stopped') {
780
+ // delete_stopped never stops live runtimes by contract — even bound delegates.
781
+ // Surface a clear reason instead of an unexplained skip so callers know to use
782
+ // stop / stop_and_delete to release a still-running bound delegate.
783
+ skippedSessionIds.push(sessionId);
784
+ skippedLiveSessionIds.push(sessionId);
785
+ skippedLiveSessionReasons.push({ sessionId, reason: 'live_delegate_preserved_by_delete_stopped_mode_use_stop_or_stop_and_delete' });
786
+ continue;
787
+ }
788
+ if (!hasExplicitSessionIds && liveRuntime && delegateBoundToThisNode) {
789
+ actedLiveDelegateSessionIds.push(sessionId);
790
+ }
791
+ try {
792
+ if (args.mode === 'stop') {
793
+ if (!completed) {
794
+ if (!args.dryRun) {
795
+ await markIntentionalStop(sessionId, 'stop_session');
796
+ await self.deps.sessionHostControl.stopSession(sessionId);
797
+ }
798
+ stoppedSessionIds.push(sessionId);
799
+ } else {
800
+ skippedSessionIds.push(sessionId);
801
+ }
802
+ continue;
803
+ }
804
+
805
+ if (args.mode === 'delete_stopped') {
806
+ if (completed) {
807
+ if (!args.dryRun) await self.deps.sessionHostControl.deleteSession(sessionId, { force: false });
808
+ deletedSessionIds.push(sessionId);
809
+ } else {
810
+ skippedSessionIds.push(sessionId);
811
+ }
812
+ continue;
813
+ }
814
+
815
+ if (args.mode === 'stop_and_delete') {
816
+ if (!completed) await markIntentionalStop(sessionId, 'delete_session_force');
817
+ if (!args.dryRun) await self.deps.sessionHostControl.deleteSession(sessionId, { force: true });
818
+ deletedSessionIds.push(sessionId);
819
+ continue;
820
+ }
821
+ } catch (e: any) {
822
+ const message = e?.message || String(e);
823
+ if (message.includes('Unsupported session host request: delete_session')
824
+ && (args.mode === 'delete_stopped' || args.mode === 'stop_and_delete')) {
825
+ deleteUnsupportedSessionIds.push(sessionId);
826
+ recordsRemainSessionIds.push(sessionId);
827
+ if (args.mode === 'stop_and_delete' && !completed) {
828
+ try {
829
+ await markIntentionalStop(sessionId, 'stop_session');
830
+ await self.deps.sessionHostControl.stopSession(sessionId);
831
+ stoppedSessionIds.push(sessionId);
832
+ } catch (stopError: any) {
833
+ errors.push({ sessionId, error: stopError?.message || String(stopError) });
834
+ continue;
835
+ }
836
+ }
837
+ skippedSessionIds.push(sessionId);
838
+ continue;
839
+ }
840
+ errors.push({ sessionId, error: message });
841
+ }
842
+ }
843
+
844
+ const deleteUnsupported = deleteUnsupportedSessionIds.length > 0;
845
+ return {
846
+ success: errors.length === 0,
847
+ mode: args.mode,
848
+ dryRun: args.dryRun === true,
849
+ matchedCount: matched.length,
850
+ matchedBySurfaceKind,
851
+ stoppedSessionIds,
852
+ deletedSessionIds,
853
+ skippedSessionIds,
854
+ skippedLiveSessionIds,
855
+ skippedCoordinatorSessionIds,
856
+ ...(skippedMarkerMismatchSessionIds.length ? { skippedMarkerMismatchSessionIds } : {}),
857
+ ...(actedLiveDelegateSessionIds.length ? { actedLiveDelegateSessionIds } : {}),
858
+ ...(reclaimedOrphanSessionIds.length ? { reclaimedOrphanSessionIds } : {}),
859
+ ...(skippedLiveSessionReasons.length ? { skippedLiveSessionReasons } : {}),
860
+ ...(deleteUnsupported ? {
861
+ deleteUnsupported: true,
862
+ effectiveCleanup: args.mode === 'stop_and_delete'
863
+ ? 'stopped_only_records_remain'
864
+ : 'delete_unsupported_records_remain',
865
+ deleteUnsupportedSessionIds,
866
+ recordsRemainSessionIds,
867
+ } : {}),
868
+ ...(errors.length ? { errors } : {}),
869
+ };
870
+ }