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

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 +65 -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 +53532 -19073
  82. package/dist/index.js.map +1 -1
  83. package/dist/index.mjs +53283 -18989
  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 +1268 -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
@@ -20,11 +20,14 @@ import type { ProviderModule, ProviderScripts } from '../providers/contracts.js'
20
20
  import type { DaemonAgentStreamManager } from '../agent-stream/index.js';
21
21
  import type { CliAdapter } from '../cli-adapter-types.js';
22
22
  import { loadConfig } from '../config/config.js';
23
+ import { resolveRegistryBaseUrl } from '../config/registry-resolver.js';
23
24
  import { ChatHistoryWriter } from '../config/chat-history.js';
24
25
  import type { SessionRegistry, SessionRuntimeTarget } from '../sessions/registry.js';
25
26
  import { reconcileIdeRuntimeSessions } from '../sessions/reconcile.js';
26
27
  import { LOG } from '../logging/logger.js';
27
28
  import { resolveLegacyProviderScript, type LegacyStringScript } from './provider-script-resolver.js';
29
+ import { sha256Hex } from '../system/hash.js';
30
+ import { MANUAL_ATTENDANCE_COMMANDS, MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS } from '../providers/manual-attendance.js';
28
31
 
29
32
  // Sub-module imports
30
33
  import * as Chat from './chat-commands.js';
@@ -375,6 +378,39 @@ export class DaemonCommandHandler implements CommandHelpers {
375
378
  this._agentStream = manager;
376
379
  }
377
380
 
381
+ /**
382
+ * When a command in the manual-attendance set arrives for a session this
383
+ * daemon hosts, stamp the live instance so auto-approve holds while the user
384
+ * drives the session by hand. Provider-common: the signal is the command
385
+ * (foreground select_session / open_panel, controlbar invoke_provider_script
386
+ * / set_mode / change_model / set_thought_level, manual resolve_action,
387
+ * pty_input), never any CLI-specific modal text — so it works identically for
388
+ * every CLI/ACP provider. send_chat is deliberately excluded because a
389
+ * coordinator delegating a task to a worker also uses send_chat; counting it
390
+ * would wrongly suppress the worker's delegated auto-approve. For a remote
391
+ * mesh worker session the controlbar commands are forwarded to the owning
392
+ * worker daemon, which runs this same hook there, so attendance is recorded
393
+ * on the daemon that actually hosts the instance.
394
+ */
395
+ private noteManualAttendanceIfApplicable(cmd: string, args: any): void {
396
+ if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
397
+ // Passive view-only actions (select_session / open_panel) attend a
398
+ // foreground session but NOT a delegated worker — the instance decides.
399
+ const passive = MANUAL_ATTENDANCE_PASSIVE_VIEW_COMMANDS.has(cmd);
400
+ const sessionId = this._currentRoute.session?.sessionId
401
+ || (typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '');
402
+ if (!sessionId) return;
403
+ const session = this._ctx.sessionRegistry?.get(sessionId);
404
+ const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
405
+ const instance = this._ctx.instanceManager?.getInstance(instanceKey) as
406
+ { noteManualInteraction?: (now?: number, opts?: { passive?: boolean }) => void } | undefined;
407
+ try {
408
+ instance?.noteManualInteraction?.(undefined, { passive });
409
+ } catch {
410
+ // attendance is best-effort — never block command dispatch
411
+ }
412
+ }
413
+
378
414
  // ─── Command Dispatcher ──────────────────────────
379
415
 
380
416
  async handle(cmd: string, args: any): Promise<CommandResult> {
@@ -382,6 +418,7 @@ export class DaemonCommandHandler implements CommandHelpers {
382
418
  this._currentRoute = this.resolveRoute(args);
383
419
  const startedAt = Date.now();
384
420
  this.logCommandStart(cmd, args);
421
+ this.noteManualAttendanceIfApplicable(cmd, args);
385
422
  let result: CommandResult;
386
423
 
387
424
  if (isGitCommandName(cmd)) {
@@ -408,12 +445,19 @@ export class DaemonCommandHandler implements CommandHelpers {
408
445
  'invoke_provider_script',
409
446
  ]);
410
447
 
448
+ // read_chat and get_chat_debug_bundle can serve historical transcript data even
449
+ // when the live session record is gone (stopped/destroyed). Allow the fallback
450
+ // when the provider type is known and any session identity hint is present:
451
+ // an explicit providerSessionId/historySessionId, or the targetSessionId itself
452
+ // (which getHistorySessionId already uses as a fallback history key).
453
+ const isReadOrDebugCmd = cmd === 'read_chat' || cmd === 'get_chat_debug_bundle';
411
454
  const allowsInactiveReadChatFallback =
412
- cmd === 'read_chat'
455
+ isReadOrDebugCmd
413
456
  && !!this._currentRoute.providerType
414
457
  && (
415
458
  (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
416
459
  || (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
460
+ || (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim().length > 0)
417
461
  );
418
462
 
419
463
  if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
@@ -503,6 +547,15 @@ export class DaemonCommandHandler implements CommandHelpers {
503
547
 
504
548
  // ─── Script manage ───────────────────
505
549
  case 'refresh_scripts': return this.handleRefreshScripts(args);
550
+ case 'list_provider_availability': return this.handleListProviderAvailability(args);
551
+ case 'install_provider_manifest': return this.handleInstallProviderManifest(args);
552
+ case 'uninstall_provider_manifest': return this.handleUninstallProviderManifest(args);
553
+ case 'check_provider_updates': return this.handleCheckProviderUpdates(args);
554
+ case 'list_installed_providers': return this.handleListInstalledProviders(args);
555
+ case 'add_provider_source': return this.handleAddProviderSource(args);
556
+ case 'remove_provider_source': return this.handleRemoveProviderSource(args);
557
+ case 'list_provider_sources': return this.handleListProviderSources(args);
558
+ case 'set_active_provider_source': return this.handleSetActiveProviderSource(args);
506
559
 
507
560
  // ─── Stream commands (stream-commands.ts) ───────────
508
561
  case 'select_session': return Stream.handleSelectSession(this, args);
@@ -538,9 +591,15 @@ export class DaemonCommandHandler implements CommandHelpers {
538
591
 
539
592
  // ─── Misc (kept in handler — too small to extract) ───────
540
593
 
594
+ /**
595
+ * Reload providers from disk. Does NOT pull from the registry — the user
596
+ * controls installs explicitly via install_provider_manifest. To upgrade
597
+ * an installed provider, call install_provider_manifest again with the
598
+ * desired version (or with no version to pick up the latest from
599
+ * registry), or use check_provider_updates to see what is out of date.
600
+ */
541
601
  private async handleRefreshScripts(_args: any): Promise<CommandResult> {
542
602
  if (this._ctx.providerLoader) {
543
- await this._ctx.providerLoader.fetchLatest().catch(() => {});
544
603
  this._ctx.providerLoader.reload();
545
604
  this._ctx.providerLoader.registerToDetector();
546
605
  const refreshedInstances = this._ctx.instanceManager
@@ -556,6 +615,810 @@ export class DaemonCommandHandler implements CommandHelpers {
556
615
  return { success: false, error: 'ProviderLoader not initialized' };
557
616
  }
558
617
 
618
+ /**
619
+ * Return per-provider availability so the dashboard's provider catalog
620
+ * can show "Installed" badges. Reuses the existing detection state from
621
+ * ProviderLoader.getMachineProviderStatus() — no probing is triggered.
622
+ */
623
+ private handleListProviderAvailability(_args: any): CommandResult {
624
+ if (!this._ctx.providerLoader) {
625
+ return { success: false, error: 'ProviderLoader not initialized' };
626
+ }
627
+ const { describeTrust, requiresConfirmation } =
628
+ require('../providers/provider-trust.js') as typeof import('../providers/provider-trust.js');
629
+ const loader = this._ctx.providerLoader;
630
+ const items = loader.getAll().map((provider) => {
631
+ const machineConfig = loader.getMachineProviderConfig(provider.type);
632
+ const lastDetection = machineConfig.lastDetection;
633
+ const trust = (provider as any)._sourceTrust ?? 'trusted';
634
+ const layer = (provider as any)._sourceLayer ?? 'upstream';
635
+ const sourceName = (provider as any)._sourceName ?? null;
636
+ return {
637
+ type: provider.type,
638
+ category: provider.category,
639
+ status: loader.getMachineProviderStatus(provider.type),
640
+ installed: lastDetection?.ok === true,
641
+ detectedPath: lastDetection?.path ?? null,
642
+ checkedAt: lastDetection?.checkedAt ?? null,
643
+ trust,
644
+ trustDescription: describeTrust(trust),
645
+ requiresConfirmation: requiresConfirmation(trust),
646
+ sourceLayer: layer,
647
+ sourceName,
648
+ };
649
+ });
650
+ return { success: true, providers: items };
651
+ }
652
+
653
+ /**
654
+ * Compute the *upstream cache root*. install_provider_manifest writes
655
+ * official-registry manifests here so the daemon's standard upstream
656
+ * layer picks them up — no special handling needed at load time, and
657
+ * the manifests inherit the official-trust badge instead of the
658
+ * untrusted-external one.
659
+ *
660
+ * Path matches ProviderLoader.upstreamDir but we recompute it from
661
+ * homedir() so this method stays usable in dev where userDir can
662
+ * point at a sibling git checkout.
663
+ */
664
+ private getUpstreamInstallRoot(): string {
665
+ const os = require('os') as typeof import('os');
666
+ const path = require('path') as typeof import('path');
667
+ return path.join(os.homedir(), '.adhdev', 'providers', '.upstream');
668
+ }
669
+
670
+ /**
671
+ * Download a single provider manifest from the registry and write it to
672
+ * ~/.adhdev/providers/.upstream/{category}/{type}/provider.json.
673
+ *
674
+ * Used by standalone onboarding to seed the upstream cache with the
675
+ * default provider set on first launch. Verifies SHA-256 checksum
676
+ * against the registry meta before persisting. Refuses to write
677
+ * outside the upstream root.
678
+ *
679
+ * Args: { type: string, category?: string, version?: string }
680
+ * If category/version are omitted, looks up the latest from the registry.
681
+ */
682
+ private async handleInstallProviderManifest(args: any): Promise<CommandResult> {
683
+ if (!this._ctx.providerLoader) {
684
+ return { success: false, error: 'ProviderLoader not initialized' };
685
+ }
686
+ const type = typeof args?.type === 'string' ? args.type : '';
687
+ if (!type) return { success: false, error: 'type is required' };
688
+ // Defense in depth: reject any obvious path-traversal in the type.
689
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
690
+ return { success: false, error: 'invalid type' };
691
+ }
692
+
693
+ const https = require('https') as typeof import('https');
694
+ const fs = require('fs') as typeof import('fs');
695
+ const path = require('path') as typeof import('path');
696
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
697
+
698
+ function fetchText(url: string, timeoutMs: number): Promise<string> {
699
+ return new Promise((resolve, reject) => {
700
+ const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: timeoutMs }, (res) => {
701
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
702
+ const chunks: Buffer[] = [];
703
+ res.on('data', (c: Buffer) => chunks.push(c));
704
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
705
+ });
706
+ req.on('error', reject);
707
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
708
+ });
709
+ }
710
+
711
+ try {
712
+ // 1. Look up provider metadata so we know the expected category, version, checksum.
713
+ const metaBody = await fetchText(`${REGISTRY}/providers/${encodeURIComponent(type)}`, 10000);
714
+ const meta = JSON.parse(metaBody) as { type: string; category: string; version: string; checksum: string };
715
+ const category = typeof args?.category === 'string' ? args.category : meta.category;
716
+ const version = typeof args?.version === 'string' ? args.version : meta.version;
717
+
718
+ // Defense in depth on category as well — only known categories.
719
+ if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
720
+ return { success: false, error: `unknown category: ${category}` };
721
+ }
722
+
723
+ // 2. Download the manifest body.
724
+ const manifestBody = await fetchText(
725
+ `${REGISTRY}/providers/${encodeURIComponent(type)}/${encodeURIComponent(version)}/download`,
726
+ 30000
727
+ );
728
+
729
+ // 3. Verify checksum.
730
+ const actualChecksum = sha256Hex(manifestBody);
731
+ if (actualChecksum !== meta.checksum) {
732
+ return { success: false, error: `checksum mismatch: expected ${meta.checksum}, got ${actualChecksum}` };
733
+ }
734
+
735
+ // 4. Write to the upstream cache root, NOT to ProviderLoader.getUserDir():
736
+ // in dev, userDir points at the sibling adhdev-providers git checkout.
737
+ const installRoot = this.getUpstreamInstallRoot();
738
+ const installRootResolved = path.resolve(installRoot);
739
+ const targetDir = path.resolve(path.join(installRoot, category, type));
740
+ if (!targetDir.startsWith(installRootResolved + path.sep)) {
741
+ return { success: false, error: 'install path escaped upstream root' };
742
+ }
743
+ fs.mkdirSync(targetDir, { recursive: true });
744
+ // v1 vs v0 manifest selection — v1 manifests carry an SDK
745
+ // $schema URL or v1-only keys (tui, overrides-as-object,
746
+ // source, canonicalHistory). The loader prefers
747
+ // provider.v1.json when both exist, so writing v1 manifests
748
+ // under the v0 name would shadow them. Detect and write to
749
+ // the right file.
750
+ let manifestProbe: Record<string, any> = {};
751
+ try { manifestProbe = JSON.parse(manifestBody) as Record<string, any>; } catch { /* validation below */ }
752
+ const isV1 = typeof manifestProbe?.$schema === 'string' && manifestProbe.$schema.includes('/v1/')
753
+ || (manifestProbe?.overrides && typeof manifestProbe.overrides === 'object' && !Array.isArray(manifestProbe.overrides))
754
+ || !!manifestProbe?.tui;
755
+
756
+ // Reject v1 manifests that don't match the schema at install
757
+ // time so the daemon never persists a known-bad manifest.
758
+ // The provider-loader keeps a permissive warn-only behavior
759
+ // for manifests already on disk, but the install path is the
760
+ // right place to fail fast.
761
+ if (isV1 && manifestProbe?.category === 'cli') {
762
+ try {
763
+ const { validateCliProviderManifest, formatManifestValidationIssues } =
764
+ require('../providers/sdk/v1/validators/manifest.js') as typeof import('../providers/sdk/v1/validators/manifest.js');
765
+ const validation = validateCliProviderManifest(manifestProbe);
766
+ if (!validation.ok) {
767
+ return {
768
+ success: false,
769
+ error: `manifest failed v1 schema validation:\n${formatManifestValidationIssues(validation.issues)}`,
770
+ validationIssues: validation.issues,
771
+ };
772
+ }
773
+ } catch (e: any) {
774
+ // Validator load failure shouldn't block install — log
775
+ // and continue. The loader's warn-only path will
776
+ // surface the same issue at boot if it's real.
777
+ LOG.warn('Command', `[install_provider_manifest] schema validator unavailable: ${e?.message || e}`);
778
+ }
779
+ }
780
+
781
+ const targetFile = isV1 ? 'provider.v1.json' : 'provider.json';
782
+ const targetPath = path.join(targetDir, targetFile);
783
+ fs.writeFileSync(targetPath, manifestBody, 'utf-8');
784
+
785
+ // 5. If the manifest declares a `source` GitHub repo, fetch the
786
+ // script directories listed in the manifest (defaultScriptDir +
787
+ // each compatibility[].scriptDir). Extended-tier providers
788
+ // bundle their override JS this way — the registry intentionally
789
+ // only stores the manifest JSON, not the script bytes, so a
790
+ // third-party can publish a manifest pointing at their own fork
791
+ // without pushing files into our R2 bucket.
792
+ const manifestJson = JSON.parse(manifestBody) as Record<string, any>;
793
+ const scriptFetch = await this.fetchProviderSources(
794
+ manifestJson,
795
+ category,
796
+ type,
797
+ targetDir,
798
+ );
799
+
800
+ // Hot-reload so the daemon picks up the new manifest.
801
+ this._ctx.providerLoader.reload();
802
+ this._ctx.providerLoader.registerToDetector();
803
+
804
+ return {
805
+ success: true,
806
+ installed: {
807
+ type, category, version, checksum: actualChecksum, path: targetPath,
808
+ scriptsFetched: scriptFetch.fetchedCount,
809
+ scriptSource: scriptFetch.source,
810
+ scriptErrors: scriptFetch.errors,
811
+ },
812
+ };
813
+ } catch (e: any) {
814
+ return { success: false, error: `install failed: ${e?.message || e}` };
815
+ }
816
+ }
817
+
818
+ /**
819
+ * If `manifest.source = { type:'github', repo, ref, subdir? }` is set,
820
+ * walk each script directory the manifest references and download every
821
+ * file from the public GitHub raw endpoint. Returns a small summary so
822
+ * the caller can report what was fetched.
823
+ *
824
+ * Best-effort: failures don't reject the install — the manifest itself is
825
+ * usable for declarative-only providers, and the user still gets a clear
826
+ * error string back if a needed script is missing.
827
+ */
828
+ private async fetchProviderSources(
829
+ manifest: Record<string, any>,
830
+ category: string,
831
+ type: string,
832
+ targetDir: string,
833
+ ): Promise<{ fetchedCount: number; source: string | null; errors: string[] }> {
834
+ const errors: string[] = [];
835
+ const source = manifest?.source;
836
+ if (!source || source.type !== 'github' || typeof source.repo !== 'string' || typeof source.ref !== 'string') {
837
+ return { fetchedCount: 0, source: null, errors };
838
+ }
839
+
840
+ // Collect every script directory the manifest references. v1 manifests
841
+ // use `defaultScriptDir` and `compatibility[].scriptDir`. We also pull
842
+ // any override path's directory (e.g. overrides.detectStatus.path =
843
+ // "scripts/v1/detect_status.js" → fetch the scripts/v1/ directory too).
844
+ const scriptDirs = new Set<string>();
845
+ if (typeof manifest.defaultScriptDir === 'string') scriptDirs.add(manifest.defaultScriptDir);
846
+ if (Array.isArray(manifest.compatibility)) {
847
+ for (const c of manifest.compatibility) {
848
+ if (typeof c?.scriptDir === 'string') scriptDirs.add(c.scriptDir);
849
+ // Spec-driven providers (claude/codex/agy/…) point at a
850
+ // single specs/<version>.json instead of a scriptDir.
851
+ // The whole specs/ directory needs to come down so the
852
+ // spec adapter can resolve the file at runtime — without
853
+ // this, install_provider_manifest leaves the marketplace
854
+ // copy spec-less and provider-loader falls back to the
855
+ // legacy tui-based ProviderCliAdapter.
856
+ if (typeof c?.spec === 'string' && c.spec.includes('/')) {
857
+ const dir = c.spec.substring(0, c.spec.lastIndexOf('/'));
858
+ if (dir) scriptDirs.add(dir);
859
+ }
860
+ }
861
+ }
862
+ if (manifest.overrides && typeof manifest.overrides === 'object' && !Array.isArray(manifest.overrides)) {
863
+ for (const override of Object.values(manifest.overrides) as Array<Record<string, unknown>>) {
864
+ const overridePath = override?.path;
865
+ if (typeof overridePath === 'string' && overridePath.includes('/')) {
866
+ const dir = overridePath.substring(0, overridePath.lastIndexOf('/'));
867
+ if (dir) scriptDirs.add(dir);
868
+ }
869
+ }
870
+ }
871
+ if (scriptDirs.size === 0) {
872
+ return { fetchedCount: 0, source: `${source.repo}@${source.ref}`, errors };
873
+ }
874
+
875
+ const subdir: string = typeof source.subdir === 'string' && source.subdir.length > 0
876
+ ? source.subdir
877
+ : `${category}/${type}`;
878
+ const repo: string = source.repo;
879
+ const ref: string = source.ref;
880
+
881
+ const https = require('https') as typeof import('https');
882
+ const fs = require('fs') as typeof import('fs');
883
+ const path = require('path') as typeof import('path');
884
+
885
+ function fetchJson(url: string, timeoutMs: number): Promise<any> {
886
+ return new Promise((resolve, reject) => {
887
+ const req = https.get(url, {
888
+ headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/vnd.github+json' },
889
+ timeout: timeoutMs,
890
+ }, (res) => {
891
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
892
+ const chunks: Buffer[] = [];
893
+ res.on('data', (c: Buffer) => chunks.push(c));
894
+ res.on('end', () => {
895
+ try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
896
+ catch (e) { reject(e); }
897
+ });
898
+ });
899
+ req.on('error', reject);
900
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
901
+ });
902
+ }
903
+
904
+ function fetchBinary(url: string, timeoutMs: number): Promise<Buffer> {
905
+ return new Promise((resolve, reject) => {
906
+ const req = https.get(url, {
907
+ headers: { 'User-Agent': 'adhdev-daemon' },
908
+ timeout: timeoutMs,
909
+ }, (res) => {
910
+ // Raw endpoint redirects through codeload — follow the redirect.
911
+ if (res.statusCode === 301 || res.statusCode === 302) {
912
+ if (res.headers.location) {
913
+ return fetchBinary(res.headers.location, timeoutMs).then(resolve, reject);
914
+ }
915
+ }
916
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
917
+ const chunks: Buffer[] = [];
918
+ res.on('data', (c: Buffer) => chunks.push(c));
919
+ res.on('end', () => resolve(Buffer.concat(chunks)));
920
+ });
921
+ req.on('error', reject);
922
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
923
+ });
924
+ }
925
+
926
+ let fetchedCount = 0;
927
+
928
+ // The shared helpers directory (cli/_shared/) is referenced by most
929
+ // CLI providers via `require('../../../_shared/...')`. Fetch it once
930
+ // alongside the provider's own scripts so node's require resolution
931
+ // succeeds at runtime. Best-effort — silent if the source repo has no
932
+ // _shared dir (e.g. ACP-only repos).
933
+ const sharedDirRel = `${category}/_shared`;
934
+ const sharedTargetDir = path.resolve(path.join(targetDir, '../_shared'));
935
+ const installRootResolved = path.resolve(path.join(targetDir, '../..'));
936
+ if (sharedTargetDir.startsWith(installRootResolved + path.sep)) {
937
+ const sharedStack: string[] = [sharedDirRel];
938
+ while (sharedStack.length) {
939
+ const relDir = sharedStack.pop()!;
940
+ const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
941
+ let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
942
+ try {
943
+ entries = await fetchJson(apiUrl, 15000);
944
+ } catch (e: any) {
945
+ // Silent: _shared may not exist on third-party repos
946
+ if (relDir === sharedDirRel) break;
947
+ errors.push(`list shared ${relDir}: ${e?.message ?? e}`);
948
+ continue;
949
+ }
950
+ if (!Array.isArray(entries)) continue;
951
+ for (const entry of entries) {
952
+ if (entry.type === 'dir') { sharedStack.push(entry.path); continue; }
953
+ if (entry.type !== 'file' || !entry.download_url) continue;
954
+ try {
955
+ const body = await fetchBinary(entry.download_url, 30000);
956
+ // entry.path is like 'cli/_shared/foo.js' — strip 'cli/_shared/' prefix
957
+ const relInside = entry.path.startsWith(sharedDirRel + '/')
958
+ ? entry.path.slice(sharedDirRel.length + 1)
959
+ : entry.path;
960
+ const outPath = path.resolve(path.join(sharedTargetDir, relInside));
961
+ if (!outPath.startsWith(path.resolve(sharedTargetDir) + path.sep)) continue;
962
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
963
+ fs.writeFileSync(outPath, body);
964
+ fetchedCount++;
965
+ } catch (e: any) {
966
+ errors.push(`fetch shared ${entry.path}: ${e?.message ?? e}`);
967
+ }
968
+ }
969
+ }
970
+ }
971
+
972
+ for (const scriptDir of scriptDirs) {
973
+ // GitHub Contents API returns the file list under the dir. We
974
+ // recurse into subdirectories so e.g. scripts/v1/helpers/foo.js is
975
+ // captured too.
976
+ const stack: string[] = [`${subdir}/${scriptDir}`];
977
+ while (stack.length) {
978
+ const relDir = stack.pop()!;
979
+ const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
980
+ let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
981
+ try {
982
+ entries = await fetchJson(apiUrl, 15000);
983
+ } catch (e: any) {
984
+ errors.push(`list ${relDir}: ${e?.message ?? e}`);
985
+ continue;
986
+ }
987
+ if (!Array.isArray(entries)) {
988
+ errors.push(`list ${relDir}: unexpected response shape`);
989
+ continue;
990
+ }
991
+ for (const entry of entries) {
992
+ if (entry.type === 'dir') {
993
+ stack.push(entry.path);
994
+ continue;
995
+ }
996
+ if (entry.type !== 'file' || !entry.download_url) continue;
997
+ try {
998
+ const body = await fetchBinary(entry.download_url, 30000);
999
+ // entry.path is relative to repo root → strip the repo subdir prefix
1000
+ // so the path inside targetDir matches the layout the loader expects.
1001
+ const relInsideProvider = entry.path.startsWith(subdir + '/')
1002
+ ? entry.path.slice(subdir.length + 1)
1003
+ : entry.path;
1004
+ const outPath = path.resolve(path.join(targetDir, relInsideProvider));
1005
+ // Path-traversal guard.
1006
+ if (!outPath.startsWith(path.resolve(targetDir) + path.sep)) {
1007
+ errors.push(`refusing to write outside targetDir: ${entry.path}`);
1008
+ continue;
1009
+ }
1010
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
1011
+ fs.writeFileSync(outPath, body);
1012
+ fetchedCount++;
1013
+ } catch (e: any) {
1014
+ errors.push(`fetch ${entry.path}: ${e?.message ?? e}`);
1015
+ }
1016
+ }
1017
+ }
1018
+ }
1019
+
1020
+ return { fetchedCount, source: `${repo}@${ref}`, errors };
1021
+ }
1022
+
1023
+ /**
1024
+ * Remove a provider manifest from the upstream cache root
1025
+ * (~/.adhdev/providers/.upstream/{category}/{type}/). Refuses to touch
1026
+ * anything outside that root. Used by onboarding to opt out of a
1027
+ * provider the user doesn't want; the dashboard no longer exposes a
1028
+ * per-provider uninstall button (external sources are removed as a
1029
+ * whole via remove_provider_source).
1030
+ */
1031
+ private async handleUninstallProviderManifest(args: any): Promise<CommandResult> {
1032
+ const type = typeof args?.type === 'string' ? args.type : '';
1033
+ const category = typeof args?.category === 'string' ? args.category : '';
1034
+ if (!type || !category) return { success: false, error: 'type and category are required' };
1035
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
1036
+ return { success: false, error: 'invalid type' };
1037
+ }
1038
+ if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
1039
+ return { success: false, error: `unknown category: ${category}` };
1040
+ }
1041
+
1042
+ const fs = require('fs') as typeof import('fs');
1043
+ const path = require('path') as typeof import('path');
1044
+
1045
+ try {
1046
+ const installRoot = this.getUpstreamInstallRoot();
1047
+ const installRootResolved = path.resolve(installRoot);
1048
+ const targetDir = path.resolve(path.join(installRoot, category, type));
1049
+
1050
+ if (!targetDir.startsWith(installRootResolved + path.sep)) {
1051
+ return { success: false, error: 'refusing to delete outside upstream root' };
1052
+ }
1053
+ if (!fs.existsSync(targetDir)) {
1054
+ return { success: false, error: 'not installed' };
1055
+ }
1056
+
1057
+ fs.rmSync(targetDir, { recursive: true, force: true });
1058
+
1059
+ if (this._ctx.providerLoader) {
1060
+ this._ctx.providerLoader.reload();
1061
+ this._ctx.providerLoader.registerToDetector();
1062
+ }
1063
+
1064
+ return { success: true, removed: { type, category, path: targetDir } };
1065
+ } catch (e: any) {
1066
+ return { success: false, error: `uninstall failed: ${e?.message || e}` };
1067
+ }
1068
+ }
1069
+
1070
+ /**
1071
+ * Return everything currently installed in the upstream cache with its
1072
+ * version. This is the "what does this daemon have" answer used both by
1073
+ * the UI and by the update checker.
1074
+ */
1075
+ private handleListInstalledProviders(_args: any): CommandResult {
1076
+ const fs = require('fs') as typeof import('fs');
1077
+ const path = require('path') as typeof import('path');
1078
+
1079
+ const installRoot = this.getUpstreamInstallRoot();
1080
+ if (!fs.existsSync(installRoot)) return { success: true, providers: [] };
1081
+
1082
+ const CATEGORIES = ['cli', 'ide', 'extension', 'acp'] as const;
1083
+ const items: Array<{
1084
+ type: string;
1085
+ category: string;
1086
+ version: string;
1087
+ path: string;
1088
+ modelOptions?: string[];
1089
+ thinkingLevelOptions?: string[];
1090
+ }> = [];
1091
+
1092
+ for (const category of CATEGORIES) {
1093
+ const categoryDir = path.join(installRoot, category);
1094
+ if (!fs.existsSync(categoryDir)) continue;
1095
+ let entries: string[];
1096
+ try { entries = fs.readdirSync(categoryDir); } catch { continue; }
1097
+ for (const type of entries) {
1098
+ // v1 manifest takes precedence over v0 when both are present.
1099
+ const v1Path = path.join(categoryDir, type, 'provider.v1.json');
1100
+ const v0Path = path.join(categoryDir, type, 'provider.json');
1101
+ const manifestPath = fs.existsSync(v1Path) ? v1Path : (fs.existsSync(v0Path) ? v0Path : null);
1102
+ if (!manifestPath) continue;
1103
+ try {
1104
+ const m = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
1105
+ // Surface the manifest's advisory model / thinking-level lists so
1106
+ // consumers of this endpoint (standalone New-session dialog, mesh
1107
+ // node slot editor) get the same provider-specific dropdowns the
1108
+ // status-snapshot path already carries — otherwise every provider
1109
+ // (codex included) falls back to a free-text Model field.
1110
+ const modelOptions = Array.isArray(m.modelOptions)
1111
+ ? m.modelOptions.filter((x: unknown): x is string => typeof x === 'string' && !!x.trim())
1112
+ : [];
1113
+ const thinkingLevelOptions = Array.isArray(m.thinkingLevelOptions)
1114
+ ? m.thinkingLevelOptions.filter((x: unknown): x is string => typeof x === 'string' && !!x.trim())
1115
+ : [];
1116
+ items.push({
1117
+ type,
1118
+ category,
1119
+ version: typeof m.providerVersion === 'string' ? m.providerVersion : '0.0.0',
1120
+ path: manifestPath,
1121
+ ...(modelOptions.length ? { modelOptions } : {}),
1122
+ ...(thinkingLevelOptions.length ? { thinkingLevelOptions } : {}),
1123
+ });
1124
+ } catch {
1125
+ // Corrupt manifest — skip but don't fail the whole listing.
1126
+ }
1127
+ }
1128
+ }
1129
+ return { success: true, providers: items };
1130
+ }
1131
+
1132
+ /**
1133
+ * For each installed provider, ask the registry for its current latest
1134
+ * version and report whether an update is available. The user can then
1135
+ * call install_provider_manifest to upgrade (it overwrites the file).
1136
+ *
1137
+ * Returns { providers: [{ type, category, installedVersion, latestVersion,
1138
+ * updateAvailable, error? }] }
1139
+ */
1140
+ private async handleCheckProviderUpdates(_args: any): Promise<CommandResult> {
1141
+ const installed = this.handleListInstalledProviders({});
1142
+ if (!installed.success) return installed;
1143
+
1144
+ const https = require('https') as typeof import('https');
1145
+ const REGISTRY = resolveRegistryBaseUrl(loadConfig().registryUrl);
1146
+
1147
+ function fetchJson(url: string): Promise<any> {
1148
+ return new Promise((resolve, reject) => {
1149
+ const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
1150
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
1151
+ const chunks: Buffer[] = [];
1152
+ res.on('data', (c: Buffer) => chunks.push(c));
1153
+ res.on('end', () => {
1154
+ try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
1155
+ catch (e) { reject(e); }
1156
+ });
1157
+ });
1158
+ req.on('error', reject);
1159
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
1160
+ });
1161
+ }
1162
+
1163
+ const installedList = (installed as unknown as { providers: Array<{ type: string; category: string; version: string }> }).providers;
1164
+ const checks = await Promise.all(
1165
+ installedList.map(async (p) => {
1166
+ try {
1167
+ const remote = await fetchJson(`${REGISTRY}/providers/${encodeURIComponent(p.type)}`);
1168
+ const latestVersion = String(remote?.version ?? '');
1169
+ return {
1170
+ type: p.type,
1171
+ category: p.category,
1172
+ installedVersion: p.version,
1173
+ latestVersion,
1174
+ updateAvailable: latestVersion !== '' && latestVersion !== p.version,
1175
+ };
1176
+ } catch (e: any) {
1177
+ return {
1178
+ type: p.type,
1179
+ category: p.category,
1180
+ installedVersion: p.version,
1181
+ latestVersion: null,
1182
+ updateAvailable: false,
1183
+ error: e?.message ?? String(e),
1184
+ };
1185
+ }
1186
+ })
1187
+ );
1188
+
1189
+ return { success: true, providers: checks };
1190
+ }
1191
+
1192
+ // ─── External provider sources (3rd-party git URLs) ──────────────
1193
+
1194
+ /**
1195
+ * Register a new external provider source. The daemon clones the repo
1196
+ * to ~/.adhdev/external/<name>/, walks it once to detect provided
1197
+ * types, and surfaces any conflicts with already-installed types so
1198
+ * the dashboard can ask the user how to resolve them.
1199
+ *
1200
+ * Args: { url: string, ref?: string, name?: string }
1201
+ * - url: https://, git@, or any git-cloneable URL
1202
+ * - ref: branch/tag/commit (default "main")
1203
+ * - name: short identifier (default derived from URL)
1204
+ *
1205
+ * Returns: { source, providers, conflicts }
1206
+ * - conflicts: list of types this new source provides that another
1207
+ * source already exposes. UI uses this to prompt for active-source
1208
+ * selection before the load takes effect.
1209
+ */
1210
+ private async handleAddProviderSource(args: any): Promise<CommandResult> {
1211
+ const url = typeof args?.url === 'string' ? args.url.trim() : '';
1212
+ if (!url) return { success: false, error: 'url is required' };
1213
+ const ref = typeof args?.ref === 'string' && args.ref.trim() ? args.ref.trim() : 'main';
1214
+
1215
+ // Defense in depth against argv flag-smuggling: reject anything that
1216
+ // looks like a git option in either positional. The `--` end-of-options
1217
+ // sentinel below catches accidental cases, but rejecting early gives
1218
+ // a clear error message and stops obviously malicious inputs from
1219
+ // even touching git.
1220
+ if (url.startsWith('-')) return { success: false, error: 'url must not start with "-"' };
1221
+ if (ref.startsWith('-')) return { success: false, error: 'ref must not start with "-"' };
1222
+ // Whitelist the protocols we'll forward to git. Anything else
1223
+ // (file://, ext-protocol-handlers, …) is refused outright.
1224
+ if (!/^(https?:\/\/|git@[a-z0-9._-]+:)[a-z0-9._@:/~\-]+$/i.test(url)) {
1225
+ return { success: false, error: 'url must be https://… or git@host:… and contain only URL-safe characters' };
1226
+ }
1227
+ // Refs are git refnames — letters, digits, slashes, dots, underscores,
1228
+ // dashes. Rejects e.g. spaces, semicolons, backticks, shell metas.
1229
+ if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
1230
+ return { success: false, error: 'ref must contain only [A-Za-z0-9._/-]' };
1231
+ }
1232
+
1233
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1234
+ const requestedName = typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : ext.deriveSourceName(url);
1235
+ if (!/^@[a-z0-9_-]+$/i.test(requestedName)) {
1236
+ return { success: false, error: 'name must match @[a-z0-9_-]+' };
1237
+ }
1238
+
1239
+ const fs = require('node:fs') as typeof import('node:fs');
1240
+ const path = require('node:path') as typeof import('node:path');
1241
+ const { spawnSync } = require('node:child_process') as typeof import('node:child_process');
1242
+
1243
+ const file = ext.loadExternalSources();
1244
+ if (file.sources.some(s => s.name === requestedName)) {
1245
+ return { success: false, error: `source name "${requestedName}" is already registered` };
1246
+ }
1247
+ if (file.sources.some(s => s.url === url && s.ref === ref)) {
1248
+ return { success: false, error: `source url+ref already registered (use a different name to track another ref)` };
1249
+ }
1250
+
1251
+ const sourceDir = path.join(ext.externalRoot(), requestedName);
1252
+ if (!fs.existsSync(ext.externalRoot())) fs.mkdirSync(ext.externalRoot(), { recursive: true });
1253
+ if (fs.existsSync(sourceDir)) {
1254
+ return { success: false, error: `directory already exists: ${sourceDir} (rename or remove first)` };
1255
+ }
1256
+
1257
+ // `--` sentinel after the option list so any future regex-bypassing
1258
+ // url that *did* start with `-` would still be treated as a path
1259
+ // by git rather than an option.
1260
+ const clone = spawnSync('git', ['clone', '--depth=1', '--branch', ref, '--', url, sourceDir], {
1261
+ encoding: 'utf-8',
1262
+ stdio: ['ignore', 'pipe', 'pipe'],
1263
+ timeout: 60_000,
1264
+ });
1265
+ if (clone.status !== 0) {
1266
+ try { fs.rmSync(sourceDir, { recursive: true, force: true }); } catch { /* best-effort */ }
1267
+ return { success: false, error: `git clone failed: ${(clone.stderr || clone.stdout || '').trim() || 'unknown error'}` };
1268
+ }
1269
+
1270
+ const source: import('../providers/external-sources.js').ExternalSource = {
1271
+ name: requestedName,
1272
+ url,
1273
+ ref,
1274
+ addedAt: new Date().toISOString(),
1275
+ };
1276
+ ext.saveExternalSources({ schema: 1, sources: [...file.sources, source] });
1277
+
1278
+ // Detect type-level conflicts with what's already on disk after this clone.
1279
+ const inventory = ext.inventoryExternalSources();
1280
+ const conflicts: { category: string; type: string; sources: string[] }[] = [];
1281
+ const newEntry = inventory.find(e => e.sourceName === requestedName);
1282
+ if (newEntry) {
1283
+ for (const [category, types] of Object.entries(newEntry.providers)) {
1284
+ for (const type of types) {
1285
+ const sources = ext.sourcesProviding(category, type);
1286
+ if (sources.length > 1) conflicts.push({ category, type, sources });
1287
+ }
1288
+ }
1289
+ }
1290
+
1291
+ // Hot-reload so the daemon picks up the new providers immediately.
1292
+ if (this._ctx.providerLoader) {
1293
+ this._ctx.providerLoader.reload();
1294
+ this._ctx.providerLoader.registerToDetector();
1295
+ }
1296
+
1297
+ return {
1298
+ success: true,
1299
+ source,
1300
+ providers: newEntry?.providers ?? {},
1301
+ conflicts,
1302
+ };
1303
+ }
1304
+
1305
+ /**
1306
+ * Remove a registered external source. Deletes the clone directory and
1307
+ * any active-source entry pointing to it.
1308
+ *
1309
+ * Args: { name: string }
1310
+ */
1311
+ private async handleRemoveProviderSource(args: any): Promise<CommandResult> {
1312
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
1313
+ if (!name) return { success: false, error: 'name is required' };
1314
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1315
+
1316
+ const fs = require('node:fs') as typeof import('node:fs');
1317
+ const path = require('node:path') as typeof import('node:path');
1318
+ const file = ext.loadExternalSources();
1319
+ const match = file.sources.find(s => s.name === name);
1320
+ if (!match) return { success: false, error: `source "${name}" not registered` };
1321
+
1322
+ const sourceDir = path.join(ext.externalRoot(), name);
1323
+ if (fs.existsSync(sourceDir)) {
1324
+ try { fs.rmSync(sourceDir, { recursive: true, force: true }); }
1325
+ catch (e: any) { return { success: false, error: `failed to delete ${sourceDir}: ${e?.message || e}` }; }
1326
+ }
1327
+
1328
+ ext.saveExternalSources({
1329
+ schema: 1,
1330
+ sources: file.sources.filter(s => s.name !== name),
1331
+ });
1332
+
1333
+ // Drop any active-source entries that pointed at this source.
1334
+ const active = ext.loadProvidersActive();
1335
+ const filteredActive: Record<string, string> = {};
1336
+ for (const [type, src] of Object.entries(active.active)) {
1337
+ if (src !== name) filteredActive[type] = src;
1338
+ }
1339
+ ext.saveProvidersActive({ schema: 1, active: filteredActive });
1340
+
1341
+ if (this._ctx.providerLoader) {
1342
+ this._ctx.providerLoader.reload();
1343
+ this._ctx.providerLoader.registerToDetector();
1344
+ }
1345
+
1346
+ return { success: true, removed: { name } };
1347
+ }
1348
+
1349
+ /**
1350
+ * List registered external sources + each source's currently installed
1351
+ * providers + the active selection for any conflicting types. Used by
1352
+ * the dashboard's "Sources" tab.
1353
+ */
1354
+ private handleListProviderSources(_args: any): CommandResult {
1355
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1356
+ const file = ext.loadExternalSources();
1357
+ const inventory = ext.inventoryExternalSources();
1358
+ const active = ext.loadProvidersActive();
1359
+
1360
+ // Build a per-source view + flag types that have ambiguity.
1361
+ const sources = file.sources.map(s => {
1362
+ const inv = inventory.find(e => e.sourceName === s.name);
1363
+ return {
1364
+ ...s,
1365
+ providers: inv?.providers ?? {},
1366
+ };
1367
+ });
1368
+
1369
+ // Compute conflicts globally — any type provided by ≥ 2 sources.
1370
+ const conflictMap = new Map<string, { category: string; sources: string[] }>();
1371
+ for (const inv of inventory) {
1372
+ for (const [category, types] of Object.entries(inv.providers)) {
1373
+ for (const type of types) {
1374
+ const candidates = ext.sourcesProviding(category, type);
1375
+ if (candidates.length > 1 && !conflictMap.has(type)) {
1376
+ conflictMap.set(type, { category, sources: candidates });
1377
+ }
1378
+ }
1379
+ }
1380
+ }
1381
+ const conflicts = [...conflictMap.entries()].map(([type, info]) => ({
1382
+ type,
1383
+ category: info.category,
1384
+ candidates: info.sources,
1385
+ active: active.active[type] ?? null,
1386
+ }));
1387
+
1388
+ return { success: true, sources, conflicts };
1389
+ }
1390
+
1391
+ /**
1392
+ * Pick which source's copy of a conflicting provider type is active.
1393
+ * Other sources' copies stay on disk but the loader ignores them.
1394
+ *
1395
+ * Args: { type: string, sourceName: string }
1396
+ */
1397
+ private handleSetActiveProviderSource(args: any): CommandResult {
1398
+ const type = typeof args?.type === 'string' ? args.type.trim() : '';
1399
+ const sourceName = typeof args?.sourceName === 'string' ? args.sourceName.trim() : '';
1400
+ if (!type || !sourceName) return { success: false, error: 'type and sourceName are required' };
1401
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1402
+
1403
+ // Validate: the source must actually provide that type.
1404
+ const inventory = ext.inventoryExternalSources();
1405
+ const entry = inventory.find(e => e.sourceName === sourceName);
1406
+ if (!entry) return { success: false, error: `source "${sourceName}" not found` };
1407
+ const provided = Object.values(entry.providers).some(types => types.includes(type));
1408
+ if (!provided) return { success: false, error: `source "${sourceName}" does not provide type "${type}"` };
1409
+
1410
+ const active = ext.loadProvidersActive();
1411
+ active.active[type] = sourceName;
1412
+ ext.saveProvidersActive(active);
1413
+
1414
+ if (this._ctx.providerLoader) {
1415
+ this._ctx.providerLoader.reload();
1416
+ this._ctx.providerLoader.registerToDetector();
1417
+ }
1418
+
1419
+ return { success: true, type, sourceName };
1420
+ }
1421
+
559
1422
  // ─── DevServer HTTP proxy helpers ─────────────────
560
1423
  // These bridge WS commands to the DevServer REST API (localhost:19280)
561
1424