@adhdev/daemon-core 0.9.82-rc.41 → 0.9.82-rc.410

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 (417) 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 +200 -0
  10. package/dist/cli-adapters/provider-cli-adapter.d.ts +126 -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 +14 -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 +7 -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 +15 -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 +60 -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 +2 -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 +116 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router.d.ts +256 -2
  56. package/dist/commands/upgrade-helper.d.ts +41 -1
  57. package/dist/config/chat-history.d.ts +9 -0
  58. package/dist/config/config.d.ts +5 -0
  59. package/dist/config/mesh-config.d.ts +77 -1
  60. package/dist/config/mesh-json-config.d.ts +299 -0
  61. package/dist/daemon/dev-server.d.ts +0 -2
  62. package/dist/detection/ide-detector.d.ts +13 -0
  63. package/dist/detection/win32-ide-version.d.ts +37 -0
  64. package/dist/git/change-impact-config.d.ts +159 -0
  65. package/dist/git/git-commands.d.ts +11 -1
  66. package/dist/git/git-diff.d.ts +6 -0
  67. package/dist/git/git-executor.d.ts +11 -0
  68. package/dist/git/git-status.d.ts +57 -0
  69. package/dist/git/git-types.d.ts +2 -50
  70. package/dist/git/git-worktree.d.ts +71 -1
  71. package/dist/git/index.d.ts +3 -1
  72. package/dist/index.d.ts +57 -13
  73. package/dist/index.js +46445 -17229
  74. package/dist/index.js.map +1 -1
  75. package/dist/index.mjs +46052 -16978
  76. package/dist/index.mjs.map +1 -1
  77. package/dist/installer.d.ts +1 -4
  78. package/dist/ipc/local-ipc-server.d.ts +91 -0
  79. package/dist/launch.d.ts +1 -1
  80. package/dist/logging/async-batch-writer.d.ts +10 -0
  81. package/dist/logging/log-redactor.d.ts +24 -0
  82. package/dist/logging/log-tail-reader.d.ts +81 -0
  83. package/dist/logging/logger.d.ts +1 -1
  84. package/dist/mesh/contracts.d.ts +164 -0
  85. package/dist/mesh/coordinator-prompt.d.ts +89 -0
  86. package/dist/mesh/coordinator-registry.d.ts +59 -0
  87. package/dist/mesh/mesh-active-work.d.ts +174 -0
  88. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  89. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  90. package/dist/mesh/mesh-event-classify.d.ts +5 -0
  91. package/dist/mesh/mesh-event-forwarding.d.ts +19 -0
  92. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  93. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  94. package/dist/mesh/mesh-events-pending.d.ts +55 -0
  95. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  96. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  97. package/dist/mesh/mesh-events.d.ts +6 -49
  98. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  99. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  100. package/dist/mesh/mesh-init.d.ts +86 -0
  101. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  102. package/dist/mesh/mesh-ledger.d.ts +77 -1
  103. package/dist/mesh/mesh-missions.d.ts +164 -0
  104. package/dist/mesh/mesh-node-identity.d.ts +289 -0
  105. package/dist/mesh/mesh-queue-assignment.d.ts +95 -0
  106. package/dist/mesh/mesh-reconcile-loop.d.ts +65 -0
  107. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  108. package/dist/mesh/mesh-refine-gates.d.ts +428 -0
  109. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  110. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  111. package/dist/mesh/mesh-routing.d.ts +70 -0
  112. package/dist/mesh/mesh-runtime-store.d.ts +447 -0
  113. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  114. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  115. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  116. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  117. package/dist/mesh/mesh-work-queue.d.ts +281 -5
  118. package/dist/mesh/preview-freshness.d.ts +18 -0
  119. package/dist/mesh/refine-config.d.ts +216 -0
  120. package/dist/mesh/worktree-bootstrap-config.d.ts +145 -0
  121. package/dist/providers/acp-provider-instance.d.ts +5 -0
  122. package/dist/providers/approval-utils.d.ts +20 -0
  123. package/dist/providers/chat-message-normalization.d.ts +31 -1
  124. package/dist/providers/cli-provider-instance.d.ts +197 -3
  125. package/dist/providers/contracts.d.ts +139 -6
  126. package/dist/providers/external-sources.d.ts +71 -0
  127. package/dist/providers/manual-attendance.d.ts +63 -0
  128. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  129. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  130. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  131. package/dist/providers/native-history/constants.d.ts +12 -0
  132. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  133. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  134. package/dist/providers/native-history/index.d.ts +13 -0
  135. package/dist/providers/provider-instance-manager.d.ts +14 -0
  136. package/dist/providers/provider-instance.d.ts +23 -1
  137. package/dist/providers/provider-loader.d.ts +26 -4
  138. package/dist/providers/provider-trust.d.ts +31 -0
  139. package/dist/providers/read-chat-contract.d.ts +29 -0
  140. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  141. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  142. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  143. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  144. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  145. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  146. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  147. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  148. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  149. package/dist/providers/sdk/v1/index.d.ts +30 -0
  150. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  151. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  152. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  153. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  154. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  155. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  156. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  157. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  158. package/dist/providers/spec/adapter.d.ts +91 -0
  159. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  160. package/dist/providers/spec/evaluator.d.ts +45 -0
  161. package/dist/providers/spec/fsm-driver.d.ts +407 -0
  162. package/dist/providers/spec/fsm-evaluator.d.ts +76 -0
  163. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  164. package/dist/providers/spec/fsm-types.d.ts +174 -0
  165. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  166. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  167. package/dist/providers/spec/route.d.ts +4 -0
  168. package/dist/providers/spec/types.d.ts +222 -0
  169. package/dist/providers/status-monitor.d.ts +7 -7
  170. package/dist/providers/transcript-v2.d.ts +176 -0
  171. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  172. package/dist/providers/working-dir.d.ts +17 -0
  173. package/dist/repo-mesh-types.d.ts +420 -13
  174. package/dist/runtime-defaults.d.ts +2 -0
  175. package/dist/sessions/registry.d.ts +6 -0
  176. package/dist/shared-types-extra.d.ts +2 -4
  177. package/dist/shared-types.d.ts +59 -2
  178. package/dist/status/normalize.d.ts +1 -1
  179. package/dist/status/normalize.js +1 -0
  180. package/dist/status/normalize.js.map +1 -1
  181. package/dist/status/normalize.mjs +1 -0
  182. package/dist/status/normalize.mjs.map +1 -1
  183. package/dist/status/reporter.d.ts +2 -0
  184. package/dist/status/snapshot.d.ts +26 -0
  185. package/dist/system/hash.d.ts +8 -0
  186. package/dist/system/load-better-sqlite3.d.ts +21 -0
  187. package/dist/types.d.ts +5 -0
  188. package/package.json +7 -3
  189. package/src/agent-stream/poller.ts +2 -3
  190. package/src/agent-stream/provider-adapter.ts +1 -1
  191. package/src/boot/daemon-lifecycle.ts +63 -12
  192. package/src/boot/process-hardening.ts +89 -0
  193. package/src/build-info.ts +73 -0
  194. package/src/chat/source-machine.ts +534 -0
  195. package/src/chat/source-resolver.ts +0 -0
  196. package/src/chat/subscription-updates.ts +20 -1
  197. package/src/cli-adapter-types.d.ts +3 -1
  198. package/src/cli-adapter-types.ts +68 -2
  199. package/src/cli-adapters/cli-script-runner.ts +421 -0
  200. package/src/cli-adapters/cli-state-engine.ts +1246 -0
  201. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  202. package/src/cli-adapters/provider-cli-adapter.ts +914 -1144
  203. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  204. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  205. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  206. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  207. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  208. package/src/cli-adapters/pty-transport.ts +2 -1
  209. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  210. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  211. package/src/cli-adapters/resolve-executable.ts +90 -0
  212. package/src/cli-adapters/session-host-transport.ts +2 -1
  213. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  214. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  215. package/src/cli-adapters/terminal-screen.ts +16 -81
  216. package/src/commands/WINDOWS-UPGRADE-LOCK-FAILURE.md +198 -0
  217. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  218. package/src/commands/chat-commands-read.ts +2327 -0
  219. package/src/commands/chat-commands-scope.ts +54 -0
  220. package/src/commands/chat-commands-shared.ts +114 -0
  221. package/src/commands/chat-commands-write.ts +891 -0
  222. package/src/commands/chat-commands.ts +19 -1841
  223. package/src/commands/cli-manager.ts +527 -26
  224. package/src/commands/handler.ts +841 -2
  225. package/src/commands/high-family/index.ts +28 -0
  226. package/src/commands/high-family/mesh-coordinator-launch.ts +673 -0
  227. package/src/commands/high-family/mesh-events.ts +80 -0
  228. package/src/commands/high-family/mesh-status.ts +678 -0
  229. package/src/commands/high-family/types.ts +76 -0
  230. package/src/commands/low-family/coordinator-prompt.ts +72 -0
  231. package/src/commands/low-family/daemon-lifecycle.ts +107 -0
  232. package/src/commands/low-family/diagnostics.ts +57 -0
  233. package/src/commands/low-family/index.ts +37 -0
  234. package/src/commands/low-family/mesh-ledger.ts +62 -0
  235. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  236. package/src/commands/low-family/notification.ts +116 -0
  237. package/src/commands/low-family/refine-config.ts +106 -0
  238. package/src/commands/low-family/session-host.ts +274 -0
  239. package/src/commands/low-family/spec-providerdev.ts +217 -0
  240. package/src/commands/low-family/status-meta.ts +112 -0
  241. package/src/commands/low-family/types.ts +39 -0
  242. package/src/commands/med-family/cli-agent.ts +238 -0
  243. package/src/commands/med-family/fast-forward.ts +230 -0
  244. package/src/commands/med-family/ide.ts +163 -0
  245. package/src/commands/med-family/index.ts +37 -0
  246. package/src/commands/med-family/mesh-crud.ts +874 -0
  247. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  248. package/src/commands/med-family/mesh-queue.ts +131 -0
  249. package/src/commands/med-family/mesh-restart.ts +92 -0
  250. package/src/commands/med-family/types.ts +120 -0
  251. package/src/commands/mesh-coordinator.ts +334 -124
  252. package/src/commands/router.ts +2516 -3221
  253. package/src/commands/stream-commands.ts +8 -0
  254. package/src/commands/upgrade-helper.ts +310 -45
  255. package/src/config/chat-history.ts +483 -24
  256. package/src/config/config.ts +12 -0
  257. package/src/config/mesh-config.ts +353 -22
  258. package/src/config/mesh-json-config.ts +617 -0
  259. package/src/config/recent-activity.ts +8 -2
  260. package/src/daemon/dev-auto-implement.ts +3 -2
  261. package/src/daemon/dev-cli-debug.ts +10 -1
  262. package/src/daemon/dev-server.ts +0 -541
  263. package/src/detection/cli-detector.ts +28 -9
  264. package/src/detection/ide-detector.ts +55 -16
  265. package/src/detection/win32-ide-version.ts +106 -0
  266. package/src/git/change-impact-config.ts +354 -0
  267. package/src/git/git-commands.ts +59 -15
  268. package/src/git/git-diff.ts +81 -11
  269. package/src/git/git-executor.ts +12 -0
  270. package/src/git/git-status.ts +767 -48
  271. package/src/git/git-types.ts +14 -62
  272. package/src/git/git-worktree.ts +261 -4
  273. package/src/git/index.ts +17 -0
  274. package/src/index.ts +171 -12
  275. package/src/installer.d.ts +1 -1
  276. package/src/installer.ts +8 -6
  277. package/src/ipc/local-ipc-server.ts +278 -0
  278. package/src/launch.d.ts +1 -1
  279. package/src/launch.ts +37 -28
  280. package/src/logging/async-batch-writer.ts +55 -0
  281. package/src/logging/command-log.ts +7 -5
  282. package/src/logging/log-redactor.ts +100 -0
  283. package/src/logging/log-tail-reader.ts +341 -0
  284. package/src/logging/logger.ts +14 -7
  285. package/src/mesh/contracts.ts +338 -0
  286. package/src/mesh/coordinator-prompt.ts +381 -33
  287. package/src/mesh/coordinator-registry.ts +121 -0
  288. package/src/mesh/mesh-active-work.ts +645 -0
  289. package/src/mesh/mesh-coordinator-config.ts +97 -0
  290. package/src/mesh/mesh-delivery-policy.ts +315 -0
  291. package/src/mesh/mesh-event-classify.ts +51 -0
  292. package/src/mesh/mesh-event-forwarding.ts +1773 -0
  293. package/src/mesh/mesh-event-trace.ts +67 -0
  294. package/src/mesh/mesh-events-coordinator.ts +31 -0
  295. package/src/mesh/mesh-events-pending.ts +590 -0
  296. package/src/mesh/mesh-events-stale.ts +389 -0
  297. package/src/mesh/mesh-events-utils.ts +443 -0
  298. package/src/mesh/mesh-events.ts +32 -1035
  299. package/src/mesh/mesh-fast-forward.ts +843 -0
  300. package/src/mesh/mesh-host-ownership.ts +73 -0
  301. package/src/mesh/mesh-init.ts +260 -0
  302. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  303. package/src/mesh/mesh-ledger.ts +596 -102
  304. package/src/mesh/mesh-missions.ts +322 -0
  305. package/src/mesh/mesh-node-identity.ts +1887 -0
  306. package/src/mesh/mesh-queue-assignment.ts +1738 -0
  307. package/src/mesh/mesh-reconcile-loop.ts +1726 -0
  308. package/src/mesh/mesh-refine-batch.ts +205 -0
  309. package/src/mesh/mesh-refine-gates.ts +1666 -0
  310. package/src/mesh/mesh-refine-status.ts +231 -0
  311. package/src/mesh/mesh-review-inbox.ts +307 -0
  312. package/src/mesh/mesh-routing.ts +291 -0
  313. package/src/mesh/mesh-runtime-store.ts +1836 -0
  314. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  315. package/src/mesh/mesh-task-stats.ts +161 -0
  316. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  317. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  318. package/src/mesh/mesh-work-queue.ts +1205 -141
  319. package/src/mesh/preview-freshness.ts +118 -0
  320. package/src/mesh/refine-config.ts +423 -0
  321. package/src/mesh/worktree-bootstrap-config.ts +369 -0
  322. package/src/providers/acp-provider-instance.ts +43 -10
  323. package/src/providers/approval-utils.d.ts +5 -0
  324. package/src/providers/approval-utils.ts +57 -5
  325. package/src/providers/chat-message-normalization.ts +92 -4
  326. package/src/providers/cli-provider-instance.ts +1865 -104
  327. package/src/providers/contracts.d.ts +55 -0
  328. package/src/providers/contracts.ts +150 -6
  329. package/src/providers/extension-provider-instance.ts +12 -7
  330. package/src/providers/external-sources.ts +218 -0
  331. package/src/providers/ide-provider-instance.ts +35 -12
  332. package/src/providers/manual-attendance.ts +85 -0
  333. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  334. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  335. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  336. package/src/providers/native-history/constants.ts +19 -0
  337. package/src/providers/native-history/dispatcher.ts +339 -0
  338. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  339. package/src/providers/native-history/index.ts +30 -0
  340. package/src/providers/provider-instance-manager.ts +30 -0
  341. package/src/providers/provider-instance.ts +19 -1
  342. package/src/providers/provider-loader.ts +668 -50
  343. package/src/providers/provider-schema.ts +87 -14
  344. package/src/providers/provider-trust.ts +114 -0
  345. package/src/providers/read-chat-contract.ts +76 -16
  346. package/src/providers/sdk/README.md +49 -0
  347. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  348. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  349. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  350. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  351. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  352. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  353. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  354. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  355. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  356. package/src/providers/sdk/v1/index.ts +152 -0
  357. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  358. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  359. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  360. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  361. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  362. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  363. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  364. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  365. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  366. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  367. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  368. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  369. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  370. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  371. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  372. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  373. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  374. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  375. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  376. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  377. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  378. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  379. package/src/providers/sdk/v1/validators/index.ts +19 -0
  380. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  381. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  382. package/src/providers/spec/adapter.ts +235 -0
  383. package/src/providers/spec/cli-adapter.ts +1056 -0
  384. package/src/providers/spec/evaluator.ts +407 -0
  385. package/src/providers/spec/fsm-driver.ts +1304 -0
  386. package/src/providers/spec/fsm-evaluator.ts +272 -0
  387. package/src/providers/spec/fsm-loader.ts +120 -0
  388. package/src/providers/spec/fsm-types.ts +273 -0
  389. package/src/providers/spec/native-history-executor.ts +1128 -0
  390. package/src/providers/spec/pre-launch-trust.ts +104 -0
  391. package/src/providers/spec/route.ts +51 -0
  392. package/src/providers/spec/types.ts +262 -0
  393. package/src/providers/status-monitor.d.ts +7 -7
  394. package/src/providers/status-monitor.ts +37 -22
  395. package/src/providers/transcript-v2.ts +567 -0
  396. package/src/providers/types/interactive-prompt.ts +536 -0
  397. package/src/providers/version-archive.ts +64 -24
  398. package/src/providers/working-dir.ts +23 -0
  399. package/src/repo-mesh-types.ts +629 -14
  400. package/src/runtime-defaults.ts +39 -0
  401. package/src/sessions/registry.ts +6 -0
  402. package/src/shared-types-extra.ts +2 -4
  403. package/src/shared-types.d.ts +8 -0
  404. package/src/shared-types.ts +64 -1
  405. package/src/status/builders.ts +26 -6
  406. package/src/status/normalize.ts +2 -0
  407. package/src/status/reporter.ts +19 -1
  408. package/src/status/snapshot.ts +95 -26
  409. package/src/system/hash.ts +23 -0
  410. package/src/system/host-memory.ts +29 -12
  411. package/src/system/load-better-sqlite3.ts +68 -0
  412. package/src/types.ts +5 -0
  413. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  414. package/dist/mesh/mesh-sync.d.ts +0 -53
  415. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  416. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  417. package/src/mesh/mesh-sync.ts +0 -111
@@ -0,0 +1,874 @@
1
+ /**
2
+ * RF-ROUTER MED family — mesh CRUD + node CRUD commands.
3
+ *
4
+ * Mesh records (list/get/create/update/delete_mesh) and node lifecycle
5
+ * (add/update/remove/clone_mesh_node, cleanup_mesh_sessions,
6
+ * retry_mesh_node_bootstrap). get_mesh hydrates direct git truth; the mutating
7
+ * node commands gate on the Mesh Host owner check and bust the aggregate-status
8
+ * cache; clone/remove/retry forward to the owning daemon for remote worktrees.
9
+ * Extracted verbatim from executeDaemonCommand; the inline-cache, session/worktree
10
+ * cleanup and aggregate-status collaborators come from ctx.
11
+ */
12
+ import { daemonIdsEquivalent, meshNodeIdMatches } from '@adhdev/mesh-shared';
13
+ import { resolveMeshHostStatus, normalizeMeshDaemonRole } from '../../mesh/mesh-host-ownership.js';
14
+ import {
15
+ loadMeshWorktreeBootstrapConfig,
16
+ runMeshWorktreeBootstrap,
17
+ type WorktreeBootstrapState,
18
+ } from '../../mesh/worktree-bootstrap-config.js';
19
+ import { handleMeshForwardEvent, queuePendingMeshCoordinatorEvent } from '../../mesh/mesh-events.js';
20
+ import { loadConfig } from '../../config/config.js';
21
+ import {
22
+ hydrateInlineMeshDirectTruth,
23
+ normalizeProviderRoles,
24
+ readMeshNodeMachineId,
25
+ } from '../router.js';
26
+ import type { CommandRouterResult } from '../router.js';
27
+ import type { MedFamilyContext, MedFamilyHandler } from './types.js';
28
+
29
+ export const meshCrudHandlers: Record<string, MedFamilyHandler> = {
30
+ list_meshes: async (_ctx: MedFamilyContext, _args: any) => {
31
+ try {
32
+ const { listMeshes } = await import('../../config/mesh-config.js');
33
+ return { success: true, meshes: listMeshes() };
34
+ } catch (e: any) {
35
+ return { success: false, error: e.message };
36
+ }
37
+ },
38
+
39
+ get_mesh: async (ctx: MedFamilyContext, args: any) => {
40
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
41
+ if (!meshId) return { success: false, error: 'meshId required' };
42
+ const meshRecord = await ctx.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
43
+ if (!meshRecord?.mesh) return { success: false, error: 'Mesh not found' };
44
+
45
+ const requireDirectPeerTruth = args?.requireDirectPeerTruth === true;
46
+ // Only an explicit refresh fans out a blocking peer probe.
47
+ // Default loads are satisfied from held standing-state git truth.
48
+ const probeRemotePeers = args?.refresh === true || args?.forceRefresh === true;
49
+ const directTruth = await hydrateInlineMeshDirectTruth({
50
+ mesh: meshRecord.mesh,
51
+ meshSource: meshRecord.source,
52
+ dispatchMeshCommand: ctx.deps.dispatchMeshCommand,
53
+ getMeshPeerConnectionStatus: ctx.deps.getMeshPeerConnectionStatus,
54
+ statusInstanceId: ctx.deps.statusInstanceId,
55
+ localMachineId: loadConfig().machineId || '',
56
+ probeRemotePeers,
57
+ probeCache: ctx.meshGitProbeCache,
58
+ });
59
+ const directTruthSatisfied = meshRecord.source !== 'inline_bootstrap' || directTruth.directEvidenceCount > 0;
60
+ const sourceOfTruth = {
61
+ membership: meshRecord.source === 'inline_cache'
62
+ ? 'coordinator_inline_mesh_cache'
63
+ : meshRecord.source === 'local_config'
64
+ ? 'local_mesh_config'
65
+ : 'inline_bootstrap_snapshot',
66
+ coordinatorOwnsLiveTruth: directTruthSatisfied,
67
+ directPeerTruth: {
68
+ required: requireDirectPeerTruth,
69
+ satisfied: directTruthSatisfied,
70
+ directEvidenceCount: directTruth.directEvidenceCount,
71
+ localConfirmedCount: directTruth.localConfirmedCount,
72
+ peerAttemptedCount: directTruth.peerAttemptedCount,
73
+ peerConfirmedCount: directTruth.peerConfirmedCount,
74
+ unavailableNodeIds: directTruth.unavailableNodeIds,
75
+ },
76
+ };
77
+ if (requireDirectPeerTruth && !directTruthSatisfied) {
78
+ return {
79
+ success: false,
80
+ code: 'mesh_direct_peer_truth_unavailable',
81
+ error: 'Selected coordinator could not confirm direct mesh truth yet. Bootstrap inventory stays unavailable until direct get_mesh probes succeed.',
82
+ sourceOfTruth,
83
+ };
84
+ }
85
+ return { success: true, mesh: meshRecord.mesh, sourceOfTruth };
86
+ },
87
+
88
+ create_mesh: async (_ctx: MedFamilyContext, args: any) => {
89
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
90
+ const repoIdentity = typeof args?.repoIdentity === 'string' ? args.repoIdentity.trim() : '';
91
+ const repoRemoteUrl = typeof args?.repoRemoteUrl === 'string' ? args.repoRemoteUrl.trim() : undefined;
92
+ const defaultBranch = typeof args?.defaultBranch === 'string' ? args.defaultBranch.trim() : undefined;
93
+ if (!name) return { success: false, error: 'name required' };
94
+ try {
95
+ const { createMesh } = await import('../../config/mesh-config.js');
96
+ const meshHost = args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)
97
+ ? args.meshHost
98
+ : undefined;
99
+ const mesh = createMesh({ name, repoIdentity, repoRemoteUrl, defaultBranch, policy: args?.policy, meshHost });
100
+ return { success: true, mesh };
101
+ } catch (e: any) {
102
+ return { success: false, error: e.message };
103
+ }
104
+ },
105
+
106
+ update_mesh: async (ctx: MedFamilyContext, args: any) => {
107
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
108
+ if (!meshId) return { success: false, error: 'meshId required' };
109
+ try {
110
+ const { updateMesh } = await import('../../config/mesh-config.js');
111
+ const patch: Record<string, unknown> = {};
112
+ if (typeof args?.name === 'string') patch.name = args.name;
113
+ if (typeof args?.defaultBranch === 'string') patch.defaultBranch = args.defaultBranch;
114
+ if (args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)) patch.policy = args.policy;
115
+ if (args?.coordinator && typeof args.coordinator === 'object' && !Array.isArray(args.coordinator)) patch.coordinator = args.coordinator;
116
+ if (args?.meshHost && typeof args.meshHost === 'object' && !Array.isArray(args.meshHost)) patch.meshHost = args.meshHost;
117
+ if (!Object.keys(patch).length) return { success: false, error: 'No updates provided' };
118
+ const mesh = updateMesh(meshId, patch as any);
119
+ if (!mesh) return { success: false, error: 'Mesh not found' };
120
+ ctx.inlineMeshCache.set(meshId, mesh);
121
+ ctx.invalidateAggregateMeshStatus(meshId);
122
+ return { success: true, mesh };
123
+ } catch (e: any) {
124
+ return { success: false, error: e.message };
125
+ }
126
+ },
127
+
128
+ // OPSRULES — emit a `.adhdev/mesh.json` DRAFT from the machine-local mesh
129
+ // entry. This is an export scaffold for the operator to review and commit to
130
+ // the repo, NOT an automatic data migration: nothing is written to disk and
131
+ // meshes.json is untouched. The returned `scaffold` (object) + `scaffoldJson`
132
+ // (2-space text) capture the local policy + coordinator prompt override/append.
133
+ export_mesh_json_config: async (_ctx: MedFamilyContext, args: any) => {
134
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
135
+ if (!meshId) return { success: false, error: 'meshId required' };
136
+ try {
137
+ const { getMesh } = await import('../../config/mesh-config.js');
138
+ const mesh = getMesh(meshId);
139
+ if (!mesh) return { success: false, error: 'Mesh not found' };
140
+ const { buildMeshJsonConfigScaffold, serializeMeshJsonConfigScaffold, MESH_JSON_CONFIG_LOCATIONS } =
141
+ await import('../../config/mesh-json-config.js');
142
+ const scaffold = buildMeshJsonConfigScaffold(mesh);
143
+ const scaffoldJson = serializeMeshJsonConfigScaffold(scaffold);
144
+ return {
145
+ success: true,
146
+ meshId,
147
+ suggestedPath: MESH_JSON_CONFIG_LOCATIONS[0],
148
+ scaffold,
149
+ scaffoldJson,
150
+ note: 'Draft only — review and commit to the repo at the suggested path. Nothing was written; meshes.json is unchanged (local-wins).',
151
+ };
152
+ } catch (e: any) {
153
+ return { success: false, error: e.message };
154
+ }
155
+ },
156
+
157
+ delete_mesh: async (_ctx: MedFamilyContext, args: any) => {
158
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
159
+ if (!meshId) return { success: false, error: 'meshId required' };
160
+ try {
161
+ const { deleteMesh } = await import('../../config/mesh-config.js');
162
+ const deleted = deleteMesh(meshId);
163
+ return { success: true, deleted };
164
+ } catch (e: any) {
165
+ return { success: false, error: e.message };
166
+ }
167
+ },
168
+
169
+ add_mesh_node: async (ctx: MedFamilyContext, args: any) => {
170
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
171
+ const workspace = typeof args?.workspace === 'string' ? args.workspace.trim() : '';
172
+ if (!meshId) return { success: false, error: 'meshId required' };
173
+ if (!workspace) return { success: false, error: 'workspace required' };
174
+ const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node addition');
175
+ if (ownerFailure) return ownerFailure;
176
+ try {
177
+ const { addNode } = await import('../../config/mesh-config.js');
178
+ const providerPriority = Array.isArray(args?.providerPriority)
179
+ ? args.providerPriority.map((type: any) => typeof type === 'string' ? type.trim() : '').filter(Boolean)
180
+ : [];
181
+ const readOnly = args?.readOnly === true;
182
+ const providerRoles = normalizeProviderRoles(args?.providerRoles);
183
+ const policy = {
184
+ ...(readOnly ? { readOnly: true } : {}),
185
+ ...(providerPriority.length ? { providerPriority } : {}),
186
+ ...(providerRoles.length ? { providerRoles } : {}),
187
+ };
188
+ const role = normalizeMeshDaemonRole(args?.role);
189
+ const daemonId = typeof args?.daemonId === 'string' && args.daemonId.trim() ? args.daemonId.trim() : undefined;
190
+ const machineId = typeof args?.machineId === 'string' && args.machineId.trim() ? args.machineId.trim() : undefined;
191
+ const repoRoot = typeof args?.repoRoot === 'string' && args.repoRoot.trim() ? args.repoRoot.trim() : undefined;
192
+ const node = addNode(meshId, {
193
+ workspace,
194
+ ...(repoRoot ? { repoRoot } : {}),
195
+ ...(daemonId ? { daemonId } : {}),
196
+ ...(machineId ? { machineId } : {}),
197
+ ...(policy ? { policy } : {}),
198
+ ...(role ? { role } : {}),
199
+ });
200
+ if (!node) return { success: false, error: 'Mesh not found' };
201
+ // mesh_status hands back a coordinator-memory aggregate
202
+ // snapshot keyed on (meshId, queueRevision). Adding a
203
+ // node touches neither, so without an explicit cache
204
+ // bust the dashboard graph keeps rendering the pre-add
205
+ // node list (empty for a fresh mesh) even after the
206
+ // user clicks Refresh.
207
+ ctx.invalidateAggregateMeshStatus(meshId);
208
+ return { success: true, node };
209
+ } catch (e: any) {
210
+ return { success: false, error: e.message };
211
+ }
212
+ },
213
+
214
+ update_mesh_node: async (ctx: MedFamilyContext, args: any) => {
215
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
216
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
217
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
218
+ const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node update');
219
+ if (ownerFailure) return ownerFailure;
220
+ try {
221
+ const { updateNode } = await import('../../config/mesh-config.js');
222
+ const policy = args?.policy && typeof args.policy === 'object' && !Array.isArray(args.policy)
223
+ ? { ...(args.policy as Record<string, unknown>) }
224
+ : {};
225
+ if (Array.isArray(args?.providerPriority)) {
226
+ const providerPriority = args.providerPriority
227
+ .map((type: any) => typeof type === 'string' ? type.trim() : '')
228
+ .filter(Boolean);
229
+ delete (policy as any).provider_priority;
230
+ if (providerPriority.length) {
231
+ (policy as any).providerPriority = providerPriority;
232
+ } else {
233
+ delete (policy as any).providerPriority;
234
+ }
235
+ }
236
+ // providerRoles: per-(node, provider) role label + maxParallel cap.
237
+ // Passing an explicit (possibly empty) array clears/replaces the
238
+ // declarations; omitting the arg leaves any value already on policy
239
+ // untouched (a full policy object passed by the caller still carries it).
240
+ if (Array.isArray(args?.providerRoles)) {
241
+ const providerRoles = normalizeProviderRoles(args.providerRoles);
242
+ if (providerRoles.length) {
243
+ (policy as any).providerRoles = providerRoles;
244
+ } else {
245
+ delete (policy as any).providerRoles;
246
+ }
247
+ }
248
+ const patch: Record<string, unknown> = { policy: policy as any };
249
+ if (typeof args?.systemPrompt === 'string') {
250
+ const trimmed = (args.systemPrompt as string).trim();
251
+ patch.systemPrompt = trimmed || undefined;
252
+ } else if (args?.systemPrompt === null) {
253
+ patch.systemPrompt = undefined;
254
+ }
255
+ const node = updateNode(meshId, nodeId, patch as any);
256
+ if (!node) return { success: false, error: 'Mesh node not found' };
257
+ // Provider priority / systemPrompt changes don't touch
258
+ // the queue revision, so without a manual bust the
259
+ // cached aggregate keeps surfacing pre-update values
260
+ // (priority chip, coordinator prompt preview, etc.).
261
+ ctx.invalidateAggregateMeshStatus(meshId);
262
+ return { success: true, node };
263
+ } catch (e: any) {
264
+ return { success: false, error: e.message };
265
+ }
266
+ },
267
+
268
+ cleanup_mesh_sessions: async (ctx: MedFamilyContext, args: any) => {
269
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
270
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
271
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
272
+ const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'node removal');
273
+ if (ownerFailure) return ownerFailure;
274
+ try {
275
+ // preferInline so inline-cache-only clone nodes resolve (matches owner check above).
276
+ const meshRecord = await ctx.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
277
+ const mesh = meshRecord?.mesh;
278
+ if (!mesh) return { success: false, error: 'Mesh not found' };
279
+ const node = mesh?.nodes?.find((n: any) => meshNodeIdMatches(n, nodeId));
280
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
281
+ const mode = ctx.normalizeMeshSessionCleanupMode(args?.mode ?? mesh?.policy?.sessionCleanupOnNodeRemove);
282
+ const sessionIds = Array.isArray(args?.sessionIds)
283
+ ? args.sessionIds.map((id: any) => typeof id === 'string' ? id.trim() : '').filter(Boolean)
284
+ : undefined;
285
+ const result = await ctx.cleanupMeshSessions({
286
+ meshId,
287
+ nodeId,
288
+ node,
289
+ mode,
290
+ sessionIds,
291
+ dryRun: args?.dryRun === true,
292
+ source: 'mesh_cleanup_sessions',
293
+ });
294
+ return result;
295
+ } catch (e: any) {
296
+ return { success: false, error: e.message };
297
+ }
298
+ },
299
+
300
+ remove_mesh_node: async (ctx: MedFamilyContext, args: any) => {
301
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
302
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
303
+ if (!meshId || !nodeId) return { success: false, error: 'meshId and nodeId required' };
304
+ try {
305
+ // preferInline so removal can resolve inline-cache-only clone worktree nodes.
306
+ const meshRecord = await ctx.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
307
+ const mesh = meshRecord?.mesh;
308
+ const node = mesh?.nodes?.find((n: any) => meshNodeIdMatches(n, nodeId));
309
+
310
+ // Guard: refuse to remove the coordinator's OWN local base node
311
+ // (same machine, NOT a worktree). Removing it breaks live mesh
312
+ // membership — the coordinator can no longer be reached and has
313
+ // to be restarted. Worktree clones are always safe to remove;
314
+ // only the non-worktree node bound to this daemon is protected.
315
+ // An explicit force:true overrides for intentional mesh teardown.
316
+ if (node && !args?._meshDirectDispatch && node.isLocalWorktree !== true && args?.force !== true) {
317
+ const nodeDaemonId = typeof node.daemonId === 'string' ? node.daemonId.trim() : '';
318
+ const nodeMachineId = readMeshNodeMachineId(node as Record<string, unknown>) || '';
319
+ const selfDaemonId = ctx.deps.statusInstanceId || '';
320
+ const selfMachineId = (() => { try { return loadConfig().machineId || ''; } catch { return ''; } })();
321
+ // Identity match is form-safe: a daemon answers to the same machine
322
+ // under interchangeable id forms (bare `mach_X`, cloud `daemon_mach_X`,
323
+ // standalone `standalone_mach_X`). statusInstanceId/loadConfig().machineId
324
+ // and the node's stored daemonId/machineId frequently hold DIFFERENT forms
325
+ // of the same machine, so a raw `===` would miss the self-match and let the
326
+ // coordinator delete its own live base node (the very accident this guard
327
+ // exists to prevent). daemonIdsEquivalent collapses every form to its
328
+ // machine core before comparing, so a same-machine match is caught
329
+ // regardless of which form each side carries. This only widens matches
330
+ // (every raw-`===` hit still matches) — fail-open → fail-closed.
331
+ const isCoordinatorBaseNode =
332
+ (!!selfDaemonId && (daemonIdsEquivalent(nodeDaemonId, selfDaemonId) || daemonIdsEquivalent(nodeMachineId, selfDaemonId)))
333
+ || (!!selfMachineId && (daemonIdsEquivalent(nodeDaemonId, selfMachineId) || daemonIdsEquivalent(nodeMachineId, selfMachineId)));
334
+ if (isCoordinatorBaseNode) {
335
+ return {
336
+ success: false,
337
+ removed: false,
338
+ code: 'mesh_remove_coordinator_base_node_protected',
339
+ error: `Refusing to remove the coordinator's own base node '${typeof node.workspace === 'string' ? node.workspace : nodeId}'. `
340
+ + `It is the local non-worktree node bound to this coordinator daemon; removing it breaks live mesh membership and forces a restart.`,
341
+ recoveryHint: 'Remove worktree clone nodes instead, or pass force:true only if you are intentionally tearing down this mesh and accept that the coordinator must be re-registered/restarted.',
342
+ };
343
+ }
344
+ }
345
+
346
+ // Default worktree session cleanup ON: when the caller OMITS a mode and the
347
+ // node is a local worktree, default to 'stop_and_delete' instead of the mesh
348
+ // policy ('preserve' by default). A worktree's chat session has no reason to
349
+ // outlive the worktree's node + directory + branch, and leaving it on
350
+ // 'preserve' is what orphaned chats after a worktree remove. An explicit mode
351
+ // (including an explicit 'preserve') is always honored — only the OMITTED case
352
+ // changes, and only for worktrees. Base nodes keep arg ?? policy ?? preserve.
353
+ const explicitCleanupMode = args?.sessionCleanupMode ?? args?.session_cleanup_mode;
354
+ const sessionCleanupMode = ctx.normalizeMeshSessionCleanupMode(
355
+ explicitCleanupMode
356
+ ?? (node?.isLocalWorktree === true ? 'stop_and_delete' : undefined)
357
+ ?? mesh?.policy?.sessionCleanupOnNodeRemove,
358
+ );
359
+ // Explicit sessionIds (e.g. supplied by refine auto-cleanup) bypass the
360
+ // workspace-only-match guard so a delegate session that lacks a
361
+ // meta.meshNodeId binding can still be stopped/deleted.
362
+ const explicitSessionIds = Array.isArray(args?.sessionIds)
363
+ ? (args.sessionIds as unknown[]).filter((v): v is string => typeof v === 'string' && v.trim().length > 0).map(v => v.trim())
364
+ : undefined;
365
+ let sessionCleanup: Record<string, unknown> | undefined;
366
+ if (node && sessionCleanupMode !== 'preserve') {
367
+ sessionCleanup = await ctx.cleanupMeshSessions({
368
+ meshId,
369
+ nodeId,
370
+ node,
371
+ mode: sessionCleanupMode,
372
+ ...(explicitSessionIds && explicitSessionIds.length > 0 ? { sessionIds: explicitSessionIds } : {}),
373
+ source: 'mesh_remove_node',
374
+ });
375
+ if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
376
+ }
377
+
378
+ let worktreeCleanup: Record<string, unknown> | undefined;
379
+ if (node?.isLocalWorktree) {
380
+ const nodeDaemonId = typeof node.daemonId === 'string' ? node.daemonId.trim() : undefined;
381
+ // daemonIdsEquivalent: an equivalent-form daemonId is this machine —
382
+ // clean up locally, do not forward. Equivalent → local.
383
+ const isRemoteWorktree = nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand
384
+ && !args?._meshDirectDispatch;
385
+ if (isRemoteWorktree) {
386
+ // Worktree lives on a different machine — ask that daemon to clean it up.
387
+ // _meshDirectDispatch prevents re-forwarding when stored daemonId uses legacy format.
388
+ const forwarded = await ctx.deps.dispatchMeshCommand!(nodeDaemonId!, 'remove_mesh_node', {
389
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
390
+ _meshDirectDispatch: true,
391
+ });
392
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
393
+ }
394
+ const cleanupResult = await ctx.cleanupLocalWorktreeNode({ mesh, node, nodeId, force: args?.force === true });
395
+ // De-gating: membership removal is NOT gated on the worktree
396
+ // directory actually being deleted. cleanupLocalWorktreeNode now
397
+ // returns success:true (with a residue flag) whenever the path is
398
+ // proven managed and the only remaining problem is leftover
399
+ // directory bytes (e.g. Windows EINVAL). A success:false here means
400
+ // a genuinely-unsafe condition — missing metadata, a non-managed /
401
+ // unexpected path, a branch mismatch, a dirty worktree, or an
402
+ // unverified force fallback — and those still block removal.
403
+ if (cleanupResult.success === false) {
404
+ return {
405
+ success: false,
406
+ removed: false,
407
+ code: cleanupResult.code,
408
+ error: cleanupResult.error,
409
+ recoveryHint: cleanupResult.recoveryHint,
410
+ ...(sessionCleanup ? { sessionCleanup } : {}),
411
+ worktreeCleanup: cleanupResult,
412
+ };
413
+ }
414
+ worktreeCleanup = cleanupResult;
415
+ }
416
+
417
+ let removed = false;
418
+ if (meshRecord?.inline) {
419
+ removed = ctx.removeInlineMeshNode(meshId, mesh, nodeId);
420
+ // Inline meshes share the same aggregate snapshot cache as
421
+ // local-config meshes; without this bust the removed node
422
+ // keeps showing up in the dashboard graph until the cache
423
+ // ages out on its own.
424
+ if (removed) ctx.invalidateAggregateMeshStatus(meshId);
425
+ // Node was already absent from the inline mesh (e.g. removed by a
426
+ // prior refine cleanup). Treat as removed so caller gets removed:true.
427
+ if (!removed && !node) removed = true;
428
+ } else {
429
+ const { removeNode } = await import('../../config/mesh-config.js');
430
+ removed = removeNode(meshId, nodeId);
431
+ // Node already absent from config (e.g. removed by a prior refine
432
+ // cleanup after a successful Refinery merge). Treat as removed so
433
+ // the response is accurate.
434
+ if (!removed && !node) removed = true;
435
+ if (removed) ctx.invalidateAggregateMeshStatus(meshId);
436
+ }
437
+
438
+ // Record in task ledger
439
+ if (removed) {
440
+ try {
441
+ const { appendLedgerEntry } = await import('../../mesh/mesh-ledger.js');
442
+ appendLedgerEntry(meshId, {
443
+ kind: 'node_removed',
444
+ nodeId,
445
+ payload: {
446
+ worktree: !!node?.isLocalWorktree,
447
+ sessionCleanupMode,
448
+ workspace: typeof node?.workspace === 'string' ? node.workspace : undefined,
449
+ daemonId: typeof node?.daemonId === 'string' ? node.daemonId : undefined,
450
+ worktreeBranch: typeof node?.worktreeBranch === 'string' ? node.worktreeBranch : undefined,
451
+ worktreeCleanupFallback: typeof worktreeCleanup?.fallback === 'string' ? worktreeCleanup.fallback : undefined,
452
+ forced: worktreeCleanup?.forced === true ? true : undefined,
453
+ forceFallbackReason: typeof worktreeCleanup?.reason === 'string' ? worktreeCleanup.reason : undefined,
454
+ branchRefDeleted: typeof worktreeCleanup?.branchRefDeleted === 'boolean' ? worktreeCleanup.branchRefDeleted : undefined,
455
+ branchRefReason: typeof worktreeCleanup?.branchRefReason === 'string' ? worktreeCleanup.branchRefReason : undefined,
456
+ },
457
+ });
458
+ } catch { /* ledger append is best-effort */ }
459
+ }
460
+
461
+ // Surface leftover-directory residue at the top level so callers
462
+ // see the node was dropped from the mesh even though the worktree
463
+ // directory could not be fully removed (best-effort, non-gating).
464
+ const residueWarning = worktreeCleanup?.residue === true && typeof worktreeCleanup?.residueWarning === 'string'
465
+ ? worktreeCleanup.residueWarning
466
+ : undefined;
467
+
468
+ // Surface a preserved-branch warning at the top level so callers see
469
+ // that the branch ref was intentionally NOT deleted (unmerged work is
470
+ // never silently dropped). When the branch ref WAS deleted, the nested
471
+ // worktreeCleanup.branchRefDeleted flag already records it.
472
+ const branchRefWarning = typeof worktreeCleanup?.branchRefWarning === 'string'
473
+ ? worktreeCleanup.branchRefWarning
474
+ : undefined;
475
+
476
+ // Orphan guard: if the session cleanup still left any LIVE session skipped
477
+ // (e.g. a future skip reason, or a workspace-only session on a base node),
478
+ // surface it at the top level so the caller knows a manual mesh_cleanup_sessions
479
+ // is still required for those sessionIds. Without this, a skipped-live session
480
+ // silently outlives a removed node (the very NODE-REMOVE-SESSION-ORPHAN bug).
481
+ const skippedLiveSessionIds = Array.isArray(sessionCleanup?.skippedLiveSessionIds)
482
+ ? (sessionCleanup!.skippedLiveSessionIds as unknown[]).filter((v): v is string => typeof v === 'string')
483
+ : [];
484
+ const orphanedSessionsRemaining = skippedLiveSessionIds.length > 0;
485
+ const orphanNextAction = orphanedSessionsRemaining
486
+ ? `Live session(s) [${skippedLiveSessionIds.join(', ')}] were skipped and still survive this node removal. `
487
+ + `Run mesh_cleanup_sessions with mode:'stop_and_delete' and sessionIds:[${skippedLiveSessionIds.map(id => `'${id}'`).join(', ')}] to release them.`
488
+ : undefined;
489
+ return {
490
+ success: true,
491
+ removed,
492
+ ...(residueWarning ? { residueWarning } : {}),
493
+ ...(branchRefWarning ? { branchRefWarning } : {}),
494
+ ...(sessionCleanup ? { sessionCleanup } : {}),
495
+ ...(worktreeCleanup ? { worktreeCleanup } : {}),
496
+ ...(orphanedSessionsRemaining
497
+ ? { orphanedSessionsRemaining: true, nextAction: orphanNextAction }
498
+ : {}),
499
+ };
500
+ } catch (e: any) {
501
+ return { success: false, error: e.message };
502
+ }
503
+ },
504
+
505
+ clone_mesh_node: async (ctx: MedFamilyContext, args: any) => {
506
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
507
+ const sourceNodeId = typeof args?.sourceNodeId === 'string' ? args.sourceNodeId.trim() : '';
508
+ const branch = typeof args?.branch === 'string' ? args.branch.trim() : '';
509
+ const baseBranch = typeof args?.baseBranch === 'string' ? args.baseBranch.trim() : undefined;
510
+ if (!meshId) return { success: false, error: 'meshId required' };
511
+ if (!sourceNodeId) return { success: false, error: 'sourceNodeId required' };
512
+ if (!branch) return { success: false, error: 'branch required' };
513
+ const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'worktree clone');
514
+ if (ownerFailure) return ownerFailure;
515
+
516
+ try {
517
+ // Resolve with preferInline so the clone writes the new node into the
518
+ // same representation that get_mesh reads back. The MCP coordinator
519
+ // passes inlineMesh on every mesh command, so when it owns an inline
520
+ // mesh the membership read path (get_mesh, preferInline: true) returns
521
+ // the inline cache. Without preferInline here, clone could resolve to a
522
+ // local-config mesh and write the node only to config — leaving the
523
+ // inline cache (and therefore get_mesh / refreshMeshFromDaemon) without
524
+ // the node, so the new worktree node is never visible in live mesh
525
+ // membership even though worktree_bootstrap_complete fires.
526
+ const meshRecord = await ctx.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
527
+ const mesh = meshRecord?.mesh;
528
+ if (!mesh) return { success: false, error: 'Mesh not found' };
529
+
530
+ const sourceNode = mesh.nodes?.find((n: any) => meshNodeIdMatches(n, sourceNodeId));
531
+ if (!sourceNode) return { success: false, error: `Source node '${sourceNodeId}' not found in mesh` };
532
+
533
+ // Forward to the source node's daemon if it's on a different machine.
534
+ // _meshDirectDispatch prevents infinite re-forwarding when the stored daemonId
535
+ // uses a legacy format that doesn't match the receiving daemon's statusInstanceId.
536
+ const sourceDaemonId = typeof sourceNode.daemonId === 'string' ? sourceNode.daemonId.trim() : undefined;
537
+ // daemonIdsEquivalent: an equivalent-form source daemonId is this machine —
538
+ // clone locally, do not forward. Equivalent → local.
539
+ if (sourceDaemonId && !daemonIdsEquivalent(sourceDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand
540
+ && !args?._meshDirectDispatch) {
541
+ const forwarded = await ctx.deps.dispatchMeshCommand(sourceDaemonId, 'clone_mesh_node', {
542
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
543
+ _meshDirectDispatch: true,
544
+ });
545
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
546
+ }
547
+
548
+ const repoRoot = sourceNode.repoRoot || sourceNode.workspace;
549
+ const { createWorktree } = await import('../../git/git-worktree.js');
550
+ const result = await createWorktree({
551
+ repoRoot,
552
+ branch,
553
+ baseBranch,
554
+ meshName: mesh.name,
555
+ });
556
+ if (result.baseSync?.warning) {
557
+ console.warn(`[mesh] clone_mesh_node base sync (${result.baseSync.action}): ${result.baseSync.warning}`);
558
+ } else if (result.baseSync && result.baseSync.action !== 'up_to_date') {
559
+ console.log(`[mesh] clone_mesh_node base sync: ${result.baseSync.action} (startRef=${result.baseSync.startRef})`);
560
+ }
561
+
562
+ let node: any;
563
+ if (meshRecord.inline) {
564
+ const { randomUUID } = await import('crypto');
565
+ node = {
566
+ id: `node_${randomUUID().replace(/-/g, '')}`,
567
+ workspace: result.worktreePath,
568
+ repoRoot: result.worktreePath,
569
+ daemonId: sourceNode.daemonId,
570
+ machineId: sourceNode.machineId ?? (sourceNode as any).machine_id,
571
+ userOverrides: { ...(sourceNode.userOverrides || {}) },
572
+ policy: { ...(sourceNode.policy || {}) },
573
+ isLocalWorktree: true,
574
+ worktreeBranch: result.branch,
575
+ clonedFromNodeId: sourceNodeId,
576
+ };
577
+ ctx.updateInlineMeshNode(meshId, mesh, node);
578
+ } else {
579
+ const { addNode } = await import('../../config/mesh-config.js');
580
+ node = addNode(meshId, {
581
+ workspace: result.worktreePath,
582
+ repoRoot: result.worktreePath,
583
+ daemonId: sourceNode.daemonId,
584
+ machineId: sourceNode.machineId ?? (sourceNode as any).machine_id,
585
+ userOverrides: { ...(sourceNode.userOverrides || {}) },
586
+ isLocalWorktree: true,
587
+ worktreeBranch: result.branch,
588
+ clonedFromNodeId: sourceNodeId,
589
+ policy: { ...(sourceNode.policy || {}) },
590
+ });
591
+ if (!node) return { success: false, error: 'Failed to register worktree node' };
592
+ // Also reconcile the freshly-registered node into any warmed inline
593
+ // cache for this mesh. get_mesh (preferInline: true) reads the inline
594
+ // cache first when one exists; if we only wrote to local config the
595
+ // node would be invisible to membership reads. updateInlineMeshNode is
596
+ // a no-op when no inline cache is present.
597
+ const inlineForReconcile = ctx.getCachedInlineMesh(meshId);
598
+ if (inlineForReconcile) ctx.updateInlineMeshNode(meshId, inlineForReconcile, node);
599
+ ctx.invalidateAggregateMeshStatus(meshId);
600
+ }
601
+
602
+ const persistWorktreeSetupState = async (bootstrapState: WorktreeBootstrapState): Promise<void> => {
603
+ node.worktreeBootstrap = bootstrapState;
604
+ if (meshRecord.inline) {
605
+ ctx.updateInlineMeshNode(meshId, mesh, node);
606
+ return;
607
+ }
608
+ try {
609
+ const { updateNode } = await import('../../config/mesh-config.js');
610
+ updateNode(meshId, node.id, { worktreeBootstrap: bootstrapState });
611
+ ctx.invalidateAggregateMeshStatus(meshId);
612
+ } catch { /* bootstrap status persistence is best-effort */ }
613
+ };
614
+
615
+ const appendCloneLedger = async (initSubmodules: boolean, bootstrapState: WorktreeBootstrapState): Promise<void> => {
616
+ try {
617
+ const { appendLedgerEntry } = await import('../../mesh/mesh-ledger.js');
618
+ appendLedgerEntry(meshId, {
619
+ kind: 'node_cloned',
620
+ nodeId: node.id,
621
+ payload: {
622
+ sourceNodeId,
623
+ branch: result.branch,
624
+ worktreePath: result.worktreePath,
625
+ submodulesInitialized: initSubmodules,
626
+ worktreeBootstrap: {
627
+ status: bootstrapState.status,
628
+ required: bootstrapState.required,
629
+ configSource: bootstrapState.configSource,
630
+ configSourceType: bootstrapState.configSourceType,
631
+ lastCommand: bootstrapState.lastCommand,
632
+ exitCode: bootstrapState.exitCode,
633
+ },
634
+ },
635
+ });
636
+ } catch { /* ledger append is best-effort */ }
637
+ };
638
+
639
+ const initSubmodules = (sourceNode.policy as any)?.initSubmodulesOnClone !== false;
640
+ const loadedBootstrap = loadMeshWorktreeBootstrapConfig(mesh, result.worktreePath);
641
+ const runningBootstrapState: WorktreeBootstrapState = {
642
+ status: 'running',
643
+ required: loadedBootstrap.config?.required !== false,
644
+ configSource: loadedBootstrap.path || loadedBootstrap.source,
645
+ configSourceType: loadedBootstrap.sourceType,
646
+ startedAt: new Date().toISOString(),
647
+ };
648
+ await persistWorktreeSetupState(runningBootstrapState);
649
+
650
+ const finishWorktreeSetup = async (): Promise<{ submodulesInitialized: boolean; bootstrapState: WorktreeBootstrapState }> => {
651
+ let submodulesInitialized = false;
652
+ if (initSubmodules) {
653
+ try {
654
+ const { runGit } = await import('../../git/git-executor.js');
655
+ await runGit(
656
+ { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true },
657
+ ['submodule', 'update', '--init', '--recursive'],
658
+ { timeoutMs: 120000 },
659
+ );
660
+ submodulesInitialized = true;
661
+
662
+ // Sync oss submodule to source node HEAD (best-effort)
663
+ const sourceWorkspace = sourceNode.repoRoot || sourceNode.workspace;
664
+ if (sourceWorkspace) {
665
+ try {
666
+ const { runGit: rg } = await import('../../git/git-executor.js');
667
+ const sourceCtx = { workspace: sourceWorkspace, repoRoot: sourceWorkspace, isGitRepo: true };
668
+ const worktreeCtx = { workspace: result.worktreePath, repoRoot: result.worktreePath, isGitRepo: true };
669
+
670
+ // Read source node's oss submodule SHA
671
+ const sourceStatusOut = await rg(sourceCtx, ['submodule', 'status', 'oss'], { timeoutMs: 10000 });
672
+ const sourceStatusLine = (typeof sourceStatusOut === 'string' ? sourceStatusOut : (sourceStatusOut as any)?.stdout ?? '').trim();
673
+ const sourceShaMatch = sourceStatusLine.match(/^[+\- ]?([0-9a-f]{40})/);
674
+ const sourceSha = sourceShaMatch?.[1];
675
+
676
+ if (sourceSha) {
677
+ // Read worktree's current oss HEAD
678
+ const ossCtx = { workspace: `${result.worktreePath}/oss`, repoRoot: `${result.worktreePath}/oss`, isGitRepo: true };
679
+ const worktreeOssHeadOut = await rg(ossCtx, ['rev-parse', 'HEAD'], { timeoutMs: 10000 });
680
+ const worktreeOssSha = (typeof worktreeOssHeadOut === 'string' ? worktreeOssHeadOut : (worktreeOssHeadOut as any)?.stdout ?? '').trim();
681
+
682
+ if (worktreeOssSha !== sourceSha) {
683
+ // Fetch target SHA from source node's oss directory
684
+ await rg(ossCtx, ['fetch', `${sourceWorkspace}/oss`, 'HEAD'], { timeoutMs: 60000 });
685
+ await rg(ossCtx, ['checkout', sourceSha], { timeoutMs: 10000 });
686
+ await rg(worktreeCtx, ['add', 'oss'], { timeoutMs: 10000 });
687
+ await rg(worktreeCtx, ['commit', '-m', 'chore: sync oss to source node HEAD on clone'], { timeoutMs: 10000 });
688
+ console.log(`[mesh] Synced oss submodule to source HEAD ${sourceSha.slice(0, 8)} in worktree`);
689
+ }
690
+ }
691
+ } catch (ossErr: any) {
692
+ console.warn('[mesh] oss submodule sync to source HEAD failed (best-effort):', ossErr.message);
693
+ }
694
+ }
695
+ } catch (subErr: any) {
696
+ // Submodule init is best-effort; don't fail the clone
697
+ console.warn('[mesh] Submodule init failed for worktree:', subErr.message);
698
+ }
699
+ }
700
+ const bootstrapState: WorktreeBootstrapState = await runMeshWorktreeBootstrap(mesh, result.worktreePath);
701
+ await persistWorktreeSetupState(bootstrapState);
702
+ await appendCloneLedger(submodulesInitialized, bootstrapState);
703
+ return { submodulesInitialized, bootstrapState };
704
+ };
705
+
706
+ const requestedSetupWaitMs = Number(args?.setupWaitMs ?? args?.bootstrapWaitMs ?? 8000);
707
+ const setupWaitMs = Number.isFinite(requestedSetupWaitMs)
708
+ ? Math.min(Math.max(requestedSetupWaitMs, 0), 14000)
709
+ : 8000;
710
+ const setupPromise = finishWorktreeSetup();
711
+ const setupResult = await Promise.race([
712
+ setupPromise.then((value) => ({ completed: true as const, value })),
713
+ new Promise<{ completed: false }>((resolve) => setTimeout(() => resolve({ completed: false }), setupWaitMs)),
714
+ ]);
715
+
716
+ const emitBootstrapEvent = (eventStatus: 'bootstrap_complete' | 'bootstrap_failed', bootstrapState: WorktreeBootstrapState, startedAtMs: number, extraPayload?: Record<string, unknown>): void => {
717
+ try {
718
+ const durationMs = Date.now() - startedAtMs;
719
+ const event = `worktree_${eventStatus}` as const;
720
+ const metadataEvent = {
721
+ source: 'clone_mesh_node_bootstrap',
722
+ nodeId: node.id,
723
+ status: eventStatus,
724
+ worktreePath: result.worktreePath,
725
+ durationMs,
726
+ bootstrapStatus: bootstrapState.status,
727
+ ...(bootstrapState.error ? { error: bootstrapState.error } : {}),
728
+ ...(bootstrapState.exitCode !== undefined ? { exitCode: bootstrapState.exitCode } : {}),
729
+ ...(extraPayload || {}),
730
+ };
731
+ if (typeof ctx.deps.instanceManager?.getByCategory === 'function') {
732
+ const forwarded = handleMeshForwardEvent(
733
+ { instanceManager: ctx.deps.instanceManager } as any,
734
+ { event, meshId, nodeId: node.id, workspace: result.worktreePath, metadataEvent },
735
+ );
736
+ if (forwarded?.success === true) return;
737
+ }
738
+ queuePendingMeshCoordinatorEvent({
739
+ event,
740
+ meshId,
741
+ nodeLabel: node.id,
742
+ nodeId: node.id,
743
+ workspace: result.worktreePath,
744
+ metadataEvent,
745
+ queuedAt: Date.now(),
746
+ });
747
+ } catch { /* event emission is best-effort */ }
748
+ };
749
+
750
+ const bootstrapStartedMs = Date.now();
751
+
752
+ if (!setupResult.completed) {
753
+ setupPromise
754
+ .then(({ bootstrapState }) => {
755
+ emitBootstrapEvent('bootstrap_complete', bootstrapState, bootstrapStartedMs);
756
+ })
757
+ .catch((error: any) => {
758
+ const failedState: WorktreeBootstrapState = {
759
+ ...runningBootstrapState,
760
+ status: 'failed',
761
+ completedAt: new Date().toISOString(),
762
+ error: error?.message || String(error),
763
+ };
764
+ void persistWorktreeSetupState(failedState);
765
+ void appendCloneLedger(false, failedState);
766
+ emitBootstrapEvent('bootstrap_failed', failedState, bootstrapStartedMs, { error: error?.message || String(error) });
767
+ });
768
+ return {
769
+ success: true,
770
+ async: true,
771
+ status: 'accepted',
772
+ node,
773
+ worktreePath: result.worktreePath,
774
+ branch: result.branch,
775
+ ...(result.baseSync ? { baseSync: result.baseSync } : {}),
776
+ ...(result.baseSync?.warning ? { baseStaleWarning: result.baseSync.warning } : {}),
777
+ worktreeBootstrap: runningBootstrapState,
778
+ worktreeSetup: {
779
+ status: 'running',
780
+ setupWaitMs,
781
+ message: 'Worktree node is registered; submodule/bootstrap setup is continuing in the background.',
782
+ },
783
+ };
784
+ }
785
+
786
+ const { submodulesInitialized, bootstrapState } = setupResult.value;
787
+ emitBootstrapEvent('bootstrap_complete', bootstrapState, bootstrapStartedMs);
788
+ return {
789
+ success: true,
790
+ node,
791
+ worktreePath: result.worktreePath,
792
+ branch: result.branch,
793
+ ...(result.baseSync ? { baseSync: result.baseSync } : {}),
794
+ ...(result.baseSync?.warning ? { baseStaleWarning: result.baseSync.warning } : {}),
795
+ submodulesInitialized,
796
+ worktreeBootstrap: bootstrapState,
797
+ };
798
+ } catch (e: any) {
799
+ return { success: false, error: e.message };
800
+ }
801
+ },
802
+
803
+ retry_mesh_node_bootstrap: async (ctx: MedFamilyContext, args: any) => {
804
+ const meshId = typeof args?.meshId === 'string' ? args.meshId.trim() : '';
805
+ const nodeId = typeof args?.nodeId === 'string' ? args.nodeId.trim() : '';
806
+ if (!meshId) return { success: false, error: 'meshId required' };
807
+ if (!nodeId) return { success: false, error: 'nodeId required' };
808
+ const ownerFailure = await ctx.requireMeshHostMutationOwner(meshId, args?.inlineMesh, 'bootstrap retry');
809
+ if (ownerFailure) return ownerFailure;
810
+
811
+ try {
812
+ // preferInline so bootstrap-retry can resolve inline-cache-only clone worktree nodes.
813
+ const meshRecord = await ctx.getMeshForCommand(meshId, args?.inlineMesh, { preferInline: true });
814
+ const mesh = meshRecord?.mesh;
815
+ if (!mesh) return { success: false, error: 'Mesh not found' };
816
+
817
+ const node = mesh.nodes?.find((n: any) => meshNodeIdMatches(n, nodeId));
818
+ if (!node) return { success: false, error: `Node '${nodeId}' not found in mesh` };
819
+ if (!node.isLocalWorktree) return { success: false, error: 'Node is not a local worktree node' };
820
+
821
+ // Bootstrap runs scripts in the worktree path — forward to the node's daemon if remote.
822
+ // _meshDirectDispatch prevents re-forwarding when stored daemonId uses legacy format.
823
+ const nodeDaemonId = typeof node.daemonId === 'string' ? node.daemonId.trim() : undefined;
824
+ // daemonIdsEquivalent: an equivalent-form daemonId is this machine —
825
+ // bootstrap locally, do not forward. Equivalent → local.
826
+ if (nodeDaemonId && !daemonIdsEquivalent(nodeDaemonId, ctx.deps.statusInstanceId) && ctx.deps.dispatchMeshCommand
827
+ && !args?._meshDirectDispatch) {
828
+ const forwarded = await ctx.deps.dispatchMeshCommand(nodeDaemonId, 'retry_mesh_node_bootstrap', {
829
+ ...(typeof args === 'object' && args !== null ? args as Record<string, unknown> : {}),
830
+ _meshDirectDispatch: true,
831
+ });
832
+ return (forwarded ?? { success: false, error: 'no response from remote node' }) as CommandRouterResult;
833
+ }
834
+
835
+ const currentBootstrap = node.worktreeBootstrap as WorktreeBootstrapState | undefined;
836
+ if (currentBootstrap?.status === 'running') {
837
+ return { success: false, error: 'Bootstrap is already running for this node' };
838
+ }
839
+
840
+ const worktreePath: string = node.workspace || node.repoRoot;
841
+ if (!worktreePath) return { success: false, error: 'Node has no workspace path' };
842
+
843
+ const loadedBootstrap = loadMeshWorktreeBootstrapConfig(mesh, worktreePath);
844
+ const runningState: WorktreeBootstrapState = {
845
+ status: 'running',
846
+ required: loadedBootstrap.config?.required !== false,
847
+ configSource: loadedBootstrap.path || loadedBootstrap.source,
848
+ configSourceType: loadedBootstrap.sourceType,
849
+ startedAt: new Date().toISOString(),
850
+ };
851
+
852
+ const persistState = async (bootstrapState: WorktreeBootstrapState): Promise<void> => {
853
+ node.worktreeBootstrap = bootstrapState;
854
+ if (meshRecord.inline) {
855
+ ctx.updateInlineMeshNode(meshId, mesh, node);
856
+ return;
857
+ }
858
+ try {
859
+ const { updateNode } = await import('../../config/mesh-config.js');
860
+ updateNode(meshId, node.id, { worktreeBootstrap: bootstrapState });
861
+ ctx.invalidateAggregateMeshStatus(meshId);
862
+ } catch { /* best-effort */ }
863
+ };
864
+
865
+ await persistState(runningState);
866
+ const bootstrapState = await runMeshWorktreeBootstrap(mesh, worktreePath);
867
+ await persistState(bootstrapState);
868
+
869
+ return { success: true, bootstrapState };
870
+ } catch (e: any) {
871
+ return { success: false, error: e.message };
872
+ }
873
+ },
874
+ };