@adhdev/daemon-core 0.9.82-rc.43 → 0.9.82-rc.431

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 (425) 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 +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 +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 +29 -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 +142 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router.d.ts +276 -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 +100 -1
  60. package/dist/config/mesh-json-config.d.ts +199 -0
  61. package/dist/config/repo-settings.d.ts +77 -0
  62. package/dist/daemon/dev-server.d.ts +0 -2
  63. package/dist/detection/ide-detector.d.ts +13 -0
  64. package/dist/detection/win32-ide-version.d.ts +37 -0
  65. package/dist/git/change-impact-config.d.ts +159 -0
  66. package/dist/git/git-commands.d.ts +11 -1
  67. package/dist/git/git-diff.d.ts +6 -0
  68. package/dist/git/git-executor.d.ts +11 -0
  69. package/dist/git/git-status.d.ts +57 -0
  70. package/dist/git/git-types.d.ts +2 -50
  71. package/dist/git/git-worktree.d.ts +71 -1
  72. package/dist/git/index.d.ts +3 -1
  73. package/dist/index.d.ts +64 -14
  74. package/dist/index.js +49391 -19228
  75. package/dist/index.js.map +1 -1
  76. package/dist/index.mjs +48574 -18570
  77. package/dist/index.mjs.map +1 -1
  78. package/dist/installer.d.ts +1 -4
  79. package/dist/ipc/local-ipc-server.d.ts +91 -0
  80. package/dist/launch.d.ts +1 -1
  81. package/dist/logging/async-batch-writer.d.ts +10 -0
  82. package/dist/logging/log-redactor.d.ts +24 -0
  83. package/dist/logging/log-tail-reader.d.ts +81 -0
  84. package/dist/logging/logger.d.ts +1 -1
  85. package/dist/mesh/contracts.d.ts +164 -0
  86. package/dist/mesh/coordinator-prompt.d.ts +89 -0
  87. package/dist/mesh/coordinator-registry.d.ts +59 -0
  88. package/dist/mesh/mesh-active-work.d.ts +174 -0
  89. package/dist/mesh/mesh-clone-grace.d.ts +8 -0
  90. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  91. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  92. package/dist/mesh/mesh-event-classify.d.ts +5 -0
  93. package/dist/mesh/mesh-event-forwarding.d.ts +41 -0
  94. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  95. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  96. package/dist/mesh/mesh-events-pending.d.ts +69 -0
  97. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  98. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  99. package/dist/mesh/mesh-events.d.ts +6 -49
  100. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  101. package/dist/mesh/mesh-host-ownership.d.ts +9 -0
  102. package/dist/mesh/mesh-init.d.ts +86 -0
  103. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  104. package/dist/mesh/mesh-ledger.d.ts +77 -1
  105. package/dist/mesh/mesh-magi-status.d.ts +63 -0
  106. package/dist/mesh/mesh-missions.d.ts +189 -0
  107. package/dist/mesh/mesh-node-identity.d.ts +289 -0
  108. package/dist/mesh/mesh-queue-assignment.d.ts +113 -0
  109. package/dist/mesh/mesh-reconcile-loop.d.ts +65 -0
  110. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  111. package/dist/mesh/mesh-refine-gates.d.ts +428 -0
  112. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  113. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  114. package/dist/mesh/mesh-routing.d.ts +70 -0
  115. package/dist/mesh/mesh-runtime-store.d.ts +450 -0
  116. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  117. package/dist/mesh/mesh-task-inflight.d.ts +46 -0
  118. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  119. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  120. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  121. package/dist/mesh/mesh-work-queue.d.ts +290 -5
  122. package/dist/mesh/preview-freshness.d.ts +18 -0
  123. package/dist/mesh/refine-config.d.ts +216 -0
  124. package/dist/mesh/worktree-bootstrap-config.d.ts +145 -0
  125. package/dist/providers/acp-provider-instance.d.ts +5 -0
  126. package/dist/providers/approval-utils.d.ts +20 -0
  127. package/dist/providers/chat-message-normalization.d.ts +31 -1
  128. package/dist/providers/cli-provider-instance.d.ts +221 -3
  129. package/dist/providers/contracts.d.ts +139 -6
  130. package/dist/providers/external-sources.d.ts +71 -0
  131. package/dist/providers/manual-attendance.d.ts +63 -0
  132. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +100 -0
  133. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  134. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  135. package/dist/providers/native-history/constants.d.ts +12 -0
  136. package/dist/providers/native-history/dispatcher.d.ts +27 -0
  137. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  138. package/dist/providers/native-history/index.d.ts +13 -0
  139. package/dist/providers/provider-instance-manager.d.ts +29 -0
  140. package/dist/providers/provider-instance.d.ts +23 -1
  141. package/dist/providers/provider-loader.d.ts +26 -4
  142. package/dist/providers/provider-trust.d.ts +31 -0
  143. package/dist/providers/read-chat-contract.d.ts +29 -0
  144. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  145. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  146. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  147. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  148. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  149. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  150. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  151. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  152. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  153. package/dist/providers/sdk/v1/index.d.ts +30 -0
  154. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  155. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  156. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  157. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  158. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  159. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  160. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  161. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  162. package/dist/providers/spec/adapter.d.ts +91 -0
  163. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  164. package/dist/providers/spec/evaluator.d.ts +45 -0
  165. package/dist/providers/spec/fsm-driver.d.ts +422 -0
  166. package/dist/providers/spec/fsm-evaluator.d.ts +76 -0
  167. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  168. package/dist/providers/spec/fsm-types.d.ts +174 -0
  169. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  170. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  171. package/dist/providers/spec/route.d.ts +4 -0
  172. package/dist/providers/spec/types.d.ts +222 -0
  173. package/dist/providers/status-monitor.d.ts +7 -7
  174. package/dist/providers/transcript-v2.d.ts +176 -0
  175. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  176. package/dist/providers/working-dir.d.ts +17 -0
  177. package/dist/repo-mesh-types.d.ts +501 -13
  178. package/dist/runtime-defaults.d.ts +2 -0
  179. package/dist/sessions/registry.d.ts +6 -0
  180. package/dist/shared-types-extra.d.ts +2 -4
  181. package/dist/shared-types.d.ts +59 -2
  182. package/dist/status/normalize.d.ts +1 -1
  183. package/dist/status/normalize.js +1 -0
  184. package/dist/status/normalize.js.map +1 -1
  185. package/dist/status/normalize.mjs +1 -0
  186. package/dist/status/normalize.mjs.map +1 -1
  187. package/dist/status/reporter.d.ts +2 -0
  188. package/dist/status/snapshot.d.ts +26 -0
  189. package/dist/system/hash.d.ts +8 -0
  190. package/dist/system/load-better-sqlite3.d.ts +21 -0
  191. package/dist/types.d.ts +5 -0
  192. package/package.json +7 -3
  193. package/src/agent-stream/poller.ts +2 -3
  194. package/src/agent-stream/provider-adapter.ts +1 -1
  195. package/src/boot/daemon-lifecycle.ts +63 -12
  196. package/src/boot/process-hardening.ts +89 -0
  197. package/src/build-info.ts +73 -0
  198. package/src/chat/source-machine.ts +534 -0
  199. package/src/chat/source-resolver.ts +0 -0
  200. package/src/chat/subscription-updates.ts +20 -1
  201. package/src/cli-adapter-types.d.ts +3 -1
  202. package/src/cli-adapter-types.ts +68 -2
  203. package/src/cli-adapters/cli-script-runner.ts +421 -0
  204. package/src/cli-adapters/cli-state-engine.ts +1246 -0
  205. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  206. package/src/cli-adapters/provider-cli-adapter.ts +914 -1144
  207. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  208. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  209. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  210. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  211. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  212. package/src/cli-adapters/pty-transport.ts +2 -1
  213. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  214. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  215. package/src/cli-adapters/resolve-executable.ts +204 -0
  216. package/src/cli-adapters/session-host-transport.ts +2 -1
  217. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  218. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  219. package/src/cli-adapters/terminal-screen.ts +16 -81
  220. package/src/commands/WINDOWS-UPGRADE-LOCK-FAILURE.md +198 -0
  221. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  222. package/src/commands/chat-commands-read.ts +2327 -0
  223. package/src/commands/chat-commands-scope.ts +54 -0
  224. package/src/commands/chat-commands-shared.ts +114 -0
  225. package/src/commands/chat-commands-write.ts +891 -0
  226. package/src/commands/chat-commands.ts +19 -1841
  227. package/src/commands/cli-manager.ts +544 -26
  228. package/src/commands/handler.ts +841 -2
  229. package/src/commands/high-family/index.ts +28 -0
  230. package/src/commands/high-family/mesh-coordinator-launch.ts +678 -0
  231. package/src/commands/high-family/mesh-events.ts +80 -0
  232. package/src/commands/high-family/mesh-status.ts +710 -0
  233. package/src/commands/high-family/types.ts +76 -0
  234. package/src/commands/low-family/coordinator-prompt.ts +72 -0
  235. package/src/commands/low-family/daemon-lifecycle.ts +107 -0
  236. package/src/commands/low-family/diagnostics.ts +57 -0
  237. package/src/commands/low-family/index.ts +37 -0
  238. package/src/commands/low-family/mesh-ledger.ts +62 -0
  239. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  240. package/src/commands/low-family/notification.ts +116 -0
  241. package/src/commands/low-family/refine-config.ts +106 -0
  242. package/src/commands/low-family/session-host.ts +274 -0
  243. package/src/commands/low-family/spec-providerdev.ts +217 -0
  244. package/src/commands/low-family/status-meta.ts +112 -0
  245. package/src/commands/low-family/types.ts +39 -0
  246. package/src/commands/med-family/cli-agent.ts +238 -0
  247. package/src/commands/med-family/fast-forward.ts +230 -0
  248. package/src/commands/med-family/ide.ts +163 -0
  249. package/src/commands/med-family/index.ts +37 -0
  250. package/src/commands/med-family/mesh-crud.ts +1066 -0
  251. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  252. package/src/commands/med-family/mesh-queue.ts +167 -0
  253. package/src/commands/med-family/mesh-restart.ts +92 -0
  254. package/src/commands/med-family/types.ts +143 -0
  255. package/src/commands/mesh-coordinator.ts +334 -124
  256. package/src/commands/router.ts +2711 -3254
  257. package/src/commands/stream-commands.ts +8 -0
  258. package/src/commands/upgrade-helper.ts +310 -45
  259. package/src/config/chat-history.ts +483 -24
  260. package/src/config/config.ts +12 -0
  261. package/src/config/mesh-config.ts +495 -22
  262. package/src/config/mesh-json-config.ts +376 -0
  263. package/src/config/recent-activity.ts +8 -2
  264. package/src/config/repo-settings.ts +111 -0
  265. package/src/daemon/dev-auto-implement.ts +3 -2
  266. package/src/daemon/dev-cli-debug.ts +10 -1
  267. package/src/daemon/dev-server.ts +0 -541
  268. package/src/detection/cli-detector.ts +28 -9
  269. package/src/detection/ide-detector.ts +55 -16
  270. package/src/detection/win32-ide-version.ts +106 -0
  271. package/src/git/change-impact-config.ts +354 -0
  272. package/src/git/git-commands.ts +59 -15
  273. package/src/git/git-diff.ts +81 -11
  274. package/src/git/git-executor.ts +12 -0
  275. package/src/git/git-status.ts +767 -48
  276. package/src/git/git-types.ts +14 -62
  277. package/src/git/git-worktree.ts +261 -4
  278. package/src/git/index.ts +17 -0
  279. package/src/index.ts +194 -12
  280. package/src/installer.d.ts +1 -1
  281. package/src/installer.ts +8 -6
  282. package/src/ipc/local-ipc-server.ts +278 -0
  283. package/src/launch.d.ts +1 -1
  284. package/src/launch.ts +37 -28
  285. package/src/logging/async-batch-writer.ts +55 -0
  286. package/src/logging/command-log.ts +7 -5
  287. package/src/logging/log-redactor.ts +100 -0
  288. package/src/logging/log-tail-reader.ts +341 -0
  289. package/src/logging/logger.ts +14 -7
  290. package/src/mesh/contracts.ts +338 -0
  291. package/src/mesh/coordinator-prompt.ts +381 -33
  292. package/src/mesh/coordinator-registry.ts +121 -0
  293. package/src/mesh/mesh-active-work.ts +645 -0
  294. package/src/mesh/mesh-clone-grace.ts +68 -0
  295. package/src/mesh/mesh-coordinator-config.ts +97 -0
  296. package/src/mesh/mesh-delivery-policy.ts +315 -0
  297. package/src/mesh/mesh-event-classify.ts +51 -0
  298. package/src/mesh/mesh-event-forwarding.ts +1888 -0
  299. package/src/mesh/mesh-event-trace.ts +67 -0
  300. package/src/mesh/mesh-events-coordinator.ts +32 -0
  301. package/src/mesh/mesh-events-pending.ts +656 -0
  302. package/src/mesh/mesh-events-stale.ts +389 -0
  303. package/src/mesh/mesh-events-utils.ts +443 -0
  304. package/src/mesh/mesh-events.ts +33 -1035
  305. package/src/mesh/mesh-fast-forward.ts +843 -0
  306. package/src/mesh/mesh-host-ownership.ts +73 -0
  307. package/src/mesh/mesh-init.ts +260 -0
  308. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  309. package/src/mesh/mesh-ledger.ts +656 -102
  310. package/src/mesh/mesh-magi-status.ts +223 -0
  311. package/src/mesh/mesh-missions.ts +449 -0
  312. package/src/mesh/mesh-node-identity.ts +1887 -0
  313. package/src/mesh/mesh-queue-assignment.ts +1918 -0
  314. package/src/mesh/mesh-reconcile-loop.ts +1780 -0
  315. package/src/mesh/mesh-refine-batch.ts +205 -0
  316. package/src/mesh/mesh-refine-gates.ts +1673 -0
  317. package/src/mesh/mesh-refine-status.ts +231 -0
  318. package/src/mesh/mesh-review-inbox.ts +307 -0
  319. package/src/mesh/mesh-routing.ts +291 -0
  320. package/src/mesh/mesh-runtime-store.ts +1853 -0
  321. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  322. package/src/mesh/mesh-task-inflight.ts +70 -0
  323. package/src/mesh/mesh-task-stats.ts +161 -0
  324. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  325. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  326. package/src/mesh/mesh-work-queue.ts +1243 -141
  327. package/src/mesh/preview-freshness.ts +118 -0
  328. package/src/mesh/refine-config.ts +423 -0
  329. package/src/mesh/worktree-bootstrap-config.ts +450 -0
  330. package/src/providers/acp-provider-instance.ts +43 -10
  331. package/src/providers/approval-utils.d.ts +5 -0
  332. package/src/providers/approval-utils.ts +57 -5
  333. package/src/providers/chat-message-normalization.ts +92 -4
  334. package/src/providers/cli-provider-instance.ts +1933 -104
  335. package/src/providers/contracts.d.ts +55 -0
  336. package/src/providers/contracts.ts +150 -6
  337. package/src/providers/extension-provider-instance.ts +12 -7
  338. package/src/providers/external-sources.ts +218 -0
  339. package/src/providers/ide-provider-instance.ts +35 -12
  340. package/src/providers/manual-attendance.ts +85 -0
  341. package/src/providers/native-history/antigravity-cli-transcript.ts +643 -0
  342. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  343. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  344. package/src/providers/native-history/constants.ts +19 -0
  345. package/src/providers/native-history/dispatcher.ts +339 -0
  346. package/src/providers/native-history/hermes-cli-transcript.ts +230 -0
  347. package/src/providers/native-history/index.ts +30 -0
  348. package/src/providers/provider-instance-manager.ts +71 -0
  349. package/src/providers/provider-instance.ts +19 -1
  350. package/src/providers/provider-loader.ts +668 -50
  351. package/src/providers/provider-schema.ts +87 -14
  352. package/src/providers/provider-trust.ts +114 -0
  353. package/src/providers/read-chat-contract.ts +76 -16
  354. package/src/providers/sdk/README.md +49 -0
  355. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  356. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  357. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  358. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +250 -0
  359. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  360. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  361. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  362. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  363. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  364. package/src/providers/sdk/v1/index.ts +152 -0
  365. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  366. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  367. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  368. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +504 -0
  369. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  370. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  371. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  372. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  373. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  374. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  375. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  376. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  377. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  378. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  379. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  380. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  381. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  382. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  383. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  384. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  385. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  386. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  387. package/src/providers/sdk/v1/validators/index.ts +19 -0
  388. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  389. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  390. package/src/providers/spec/adapter.ts +235 -0
  391. package/src/providers/spec/cli-adapter.ts +1056 -0
  392. package/src/providers/spec/evaluator.ts +407 -0
  393. package/src/providers/spec/fsm-driver.ts +1410 -0
  394. package/src/providers/spec/fsm-evaluator.ts +272 -0
  395. package/src/providers/spec/fsm-loader.ts +120 -0
  396. package/src/providers/spec/fsm-types.ts +273 -0
  397. package/src/providers/spec/native-history-executor.ts +1128 -0
  398. package/src/providers/spec/pre-launch-trust.ts +104 -0
  399. package/src/providers/spec/route.ts +51 -0
  400. package/src/providers/spec/types.ts +262 -0
  401. package/src/providers/status-monitor.d.ts +7 -7
  402. package/src/providers/status-monitor.ts +37 -22
  403. package/src/providers/transcript-v2.ts +567 -0
  404. package/src/providers/types/interactive-prompt.ts +536 -0
  405. package/src/providers/version-archive.ts +64 -24
  406. package/src/providers/working-dir.ts +23 -0
  407. package/src/repo-mesh-types.ts +740 -14
  408. package/src/runtime-defaults.ts +39 -0
  409. package/src/sessions/registry.ts +6 -0
  410. package/src/shared-types-extra.ts +2 -4
  411. package/src/shared-types.d.ts +8 -0
  412. package/src/shared-types.ts +64 -1
  413. package/src/status/builders.ts +26 -6
  414. package/src/status/normalize.ts +2 -0
  415. package/src/status/reporter.ts +19 -1
  416. package/src/status/snapshot.ts +95 -26
  417. package/src/system/hash.ts +23 -0
  418. package/src/system/host-memory.ts +29 -12
  419. package/src/system/load-better-sqlite3.ts +68 -0
  420. package/src/types.ts +5 -0
  421. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  422. package/dist/mesh/mesh-sync.d.ts +0 -53
  423. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  424. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  425. package/src/mesh/mesh-sync.ts +0 -111
@@ -25,6 +25,8 @@ import type { SessionRegistry, SessionRuntimeTarget } from '../sessions/registry
25
25
  import { reconcileIdeRuntimeSessions } from '../sessions/reconcile.js';
26
26
  import { LOG } from '../logging/logger.js';
27
27
  import { resolveLegacyProviderScript, type LegacyStringScript } from './provider-script-resolver.js';
28
+ import { sha256Hex } from '../system/hash.js';
29
+ import { MANUAL_ATTENDANCE_COMMANDS } from '../providers/manual-attendance.js';
28
30
 
29
31
  // Sub-module imports
30
32
  import * as Chat from './chat-commands.js';
@@ -375,6 +377,36 @@ export class DaemonCommandHandler implements CommandHelpers {
375
377
  this._agentStream = manager;
376
378
  }
377
379
 
380
+ /**
381
+ * When a command in the manual-attendance set arrives for a session this
382
+ * daemon hosts, stamp the live instance so auto-approve holds while the user
383
+ * drives the session by hand. Provider-common: the signal is the command
384
+ * (foreground select_session / open_panel, controlbar invoke_provider_script
385
+ * / set_mode / change_model / set_thought_level, manual resolve_action,
386
+ * pty_input), never any CLI-specific modal text — so it works identically for
387
+ * every CLI/ACP provider. send_chat is deliberately excluded because a
388
+ * coordinator delegating a task to a worker also uses send_chat; counting it
389
+ * would wrongly suppress the worker's delegated auto-approve. For a remote
390
+ * mesh worker session the controlbar commands are forwarded to the owning
391
+ * worker daemon, which runs this same hook there, so attendance is recorded
392
+ * on the daemon that actually hosts the instance.
393
+ */
394
+ private noteManualAttendanceIfApplicable(cmd: string, args: any): void {
395
+ if (!MANUAL_ATTENDANCE_COMMANDS.has(cmd)) return;
396
+ const sessionId = this._currentRoute.session?.sessionId
397
+ || (typeof args?.targetSessionId === 'string' ? args.targetSessionId.trim() : '');
398
+ if (!sessionId) return;
399
+ const session = this._ctx.sessionRegistry?.get(sessionId);
400
+ const instanceKey = session?.adapterKey || session?.instanceKey || sessionId;
401
+ const instance = this._ctx.instanceManager?.getInstance(instanceKey) as
402
+ { noteManualInteraction?: (now?: number) => void } | undefined;
403
+ try {
404
+ instance?.noteManualInteraction?.();
405
+ } catch {
406
+ // attendance is best-effort — never block command dispatch
407
+ }
408
+ }
409
+
378
410
  // ─── Command Dispatcher ──────────────────────────
379
411
 
380
412
  async handle(cmd: string, args: any): Promise<CommandResult> {
@@ -382,6 +414,7 @@ export class DaemonCommandHandler implements CommandHelpers {
382
414
  this._currentRoute = this.resolveRoute(args);
383
415
  const startedAt = Date.now();
384
416
  this.logCommandStart(cmd, args);
417
+ this.noteManualAttendanceIfApplicable(cmd, args);
385
418
  let result: CommandResult;
386
419
 
387
420
  if (isGitCommandName(cmd)) {
@@ -408,12 +441,19 @@ export class DaemonCommandHandler implements CommandHelpers {
408
441
  'invoke_provider_script',
409
442
  ]);
410
443
 
444
+ // read_chat and get_chat_debug_bundle can serve historical transcript data even
445
+ // when the live session record is gone (stopped/destroyed). Allow the fallback
446
+ // when the provider type is known and any session identity hint is present:
447
+ // an explicit providerSessionId/historySessionId, or the targetSessionId itself
448
+ // (which getHistorySessionId already uses as a fallback history key).
449
+ const isReadOrDebugCmd = cmd === 'read_chat' || cmd === 'get_chat_debug_bundle';
411
450
  const allowsInactiveReadChatFallback =
412
- cmd === 'read_chat'
451
+ isReadOrDebugCmd
413
452
  && !!this._currentRoute.providerType
414
453
  && (
415
454
  (typeof args?.providerSessionId === 'string' && args.providerSessionId.trim().length > 0)
416
455
  || (typeof args?.historySessionId === 'string' && args.historySessionId.trim().length > 0)
456
+ || (typeof args?.targetSessionId === 'string' && args.targetSessionId.trim().length > 0)
417
457
  );
418
458
 
419
459
  if (this._currentRoute.sessionLookupFailed && sessionScopedCommands.has(cmd) && !allowsInactiveReadChatFallback) {
@@ -503,6 +543,15 @@ export class DaemonCommandHandler implements CommandHelpers {
503
543
 
504
544
  // ─── Script manage ───────────────────
505
545
  case 'refresh_scripts': return this.handleRefreshScripts(args);
546
+ case 'list_provider_availability': return this.handleListProviderAvailability(args);
547
+ case 'install_provider_manifest': return this.handleInstallProviderManifest(args);
548
+ case 'uninstall_provider_manifest': return this.handleUninstallProviderManifest(args);
549
+ case 'check_provider_updates': return this.handleCheckProviderUpdates(args);
550
+ case 'list_installed_providers': return this.handleListInstalledProviders(args);
551
+ case 'add_provider_source': return this.handleAddProviderSource(args);
552
+ case 'remove_provider_source': return this.handleRemoveProviderSource(args);
553
+ case 'list_provider_sources': return this.handleListProviderSources(args);
554
+ case 'set_active_provider_source': return this.handleSetActiveProviderSource(args);
506
555
 
507
556
  // ─── Stream commands (stream-commands.ts) ───────────
508
557
  case 'select_session': return Stream.handleSelectSession(this, args);
@@ -538,9 +587,15 @@ export class DaemonCommandHandler implements CommandHelpers {
538
587
 
539
588
  // ─── Misc (kept in handler — too small to extract) ───────
540
589
 
590
+ /**
591
+ * Reload providers from disk. Does NOT pull from the registry — the user
592
+ * controls installs explicitly via install_provider_manifest. To upgrade
593
+ * an installed provider, call install_provider_manifest again with the
594
+ * desired version (or with no version to pick up the latest from
595
+ * registry), or use check_provider_updates to see what is out of date.
596
+ */
541
597
  private async handleRefreshScripts(_args: any): Promise<CommandResult> {
542
598
  if (this._ctx.providerLoader) {
543
- await this._ctx.providerLoader.fetchLatest().catch(() => {});
544
599
  this._ctx.providerLoader.reload();
545
600
  this._ctx.providerLoader.registerToDetector();
546
601
  const refreshedInstances = this._ctx.instanceManager
@@ -556,6 +611,790 @@ export class DaemonCommandHandler implements CommandHelpers {
556
611
  return { success: false, error: 'ProviderLoader not initialized' };
557
612
  }
558
613
 
614
+ /**
615
+ * Return per-provider availability so the dashboard's provider catalog
616
+ * can show "Installed" badges. Reuses the existing detection state from
617
+ * ProviderLoader.getMachineProviderStatus() — no probing is triggered.
618
+ */
619
+ private handleListProviderAvailability(_args: any): CommandResult {
620
+ if (!this._ctx.providerLoader) {
621
+ return { success: false, error: 'ProviderLoader not initialized' };
622
+ }
623
+ const { describeTrust, requiresConfirmation } =
624
+ require('../providers/provider-trust.js') as typeof import('../providers/provider-trust.js');
625
+ const loader = this._ctx.providerLoader;
626
+ const items = loader.getAll().map((provider) => {
627
+ const machineConfig = loader.getMachineProviderConfig(provider.type);
628
+ const lastDetection = machineConfig.lastDetection;
629
+ const trust = (provider as any)._sourceTrust ?? 'trusted';
630
+ const layer = (provider as any)._sourceLayer ?? 'upstream';
631
+ const sourceName = (provider as any)._sourceName ?? null;
632
+ return {
633
+ type: provider.type,
634
+ category: provider.category,
635
+ status: loader.getMachineProviderStatus(provider.type),
636
+ installed: lastDetection?.ok === true,
637
+ detectedPath: lastDetection?.path ?? null,
638
+ checkedAt: lastDetection?.checkedAt ?? null,
639
+ trust,
640
+ trustDescription: describeTrust(trust),
641
+ requiresConfirmation: requiresConfirmation(trust),
642
+ sourceLayer: layer,
643
+ sourceName,
644
+ };
645
+ });
646
+ return { success: true, providers: items };
647
+ }
648
+
649
+ /**
650
+ * Compute the *upstream cache root*. install_provider_manifest writes
651
+ * official-registry manifests here so the daemon's standard upstream
652
+ * layer picks them up — no special handling needed at load time, and
653
+ * the manifests inherit the official-trust badge instead of the
654
+ * untrusted-external one.
655
+ *
656
+ * Path matches ProviderLoader.upstreamDir but we recompute it from
657
+ * homedir() so this method stays usable in dev where userDir can
658
+ * point at a sibling git checkout.
659
+ */
660
+ private getUpstreamInstallRoot(): string {
661
+ const os = require('os') as typeof import('os');
662
+ const path = require('path') as typeof import('path');
663
+ return path.join(os.homedir(), '.adhdev', 'providers', '.upstream');
664
+ }
665
+
666
+ /**
667
+ * Download a single provider manifest from the registry and write it to
668
+ * ~/.adhdev/providers/.upstream/{category}/{type}/provider.json.
669
+ *
670
+ * Used by standalone onboarding to seed the upstream cache with the
671
+ * default provider set on first launch. Verifies SHA-256 checksum
672
+ * against the registry meta before persisting. Refuses to write
673
+ * outside the upstream root.
674
+ *
675
+ * Args: { type: string, category?: string, version?: string }
676
+ * If category/version are omitted, looks up the latest from the registry.
677
+ */
678
+ private async handleInstallProviderManifest(args: any): Promise<CommandResult> {
679
+ if (!this._ctx.providerLoader) {
680
+ return { success: false, error: 'ProviderLoader not initialized' };
681
+ }
682
+ const type = typeof args?.type === 'string' ? args.type : '';
683
+ if (!type) return { success: false, error: 'type is required' };
684
+ // Defense in depth: reject any obvious path-traversal in the type.
685
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
686
+ return { success: false, error: 'invalid type' };
687
+ }
688
+
689
+ const https = require('https') as typeof import('https');
690
+ const fs = require('fs') as typeof import('fs');
691
+ const path = require('path') as typeof import('path');
692
+ const REGISTRY = 'https://api.adhf.dev/api/v1/registry';
693
+
694
+ function fetchText(url: string, timeoutMs: number): Promise<string> {
695
+ return new Promise((resolve, reject) => {
696
+ const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: timeoutMs }, (res) => {
697
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
698
+ const chunks: Buffer[] = [];
699
+ res.on('data', (c: Buffer) => chunks.push(c));
700
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
701
+ });
702
+ req.on('error', reject);
703
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
704
+ });
705
+ }
706
+
707
+ try {
708
+ // 1. Look up provider metadata so we know the expected category, version, checksum.
709
+ const metaBody = await fetchText(`${REGISTRY}/providers/${encodeURIComponent(type)}`, 10000);
710
+ const meta = JSON.parse(metaBody) as { type: string; category: string; version: string; checksum: string };
711
+ const category = typeof args?.category === 'string' ? args.category : meta.category;
712
+ const version = typeof args?.version === 'string' ? args.version : meta.version;
713
+
714
+ // Defense in depth on category as well — only known categories.
715
+ if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
716
+ return { success: false, error: `unknown category: ${category}` };
717
+ }
718
+
719
+ // 2. Download the manifest body.
720
+ const manifestBody = await fetchText(
721
+ `${REGISTRY}/providers/${encodeURIComponent(type)}/${encodeURIComponent(version)}/download`,
722
+ 30000
723
+ );
724
+
725
+ // 3. Verify checksum.
726
+ const actualChecksum = sha256Hex(manifestBody);
727
+ if (actualChecksum !== meta.checksum) {
728
+ return { success: false, error: `checksum mismatch: expected ${meta.checksum}, got ${actualChecksum}` };
729
+ }
730
+
731
+ // 4. Write to the upstream cache root, NOT to ProviderLoader.getUserDir():
732
+ // in dev, userDir points at the sibling adhdev-providers git checkout.
733
+ const installRoot = this.getUpstreamInstallRoot();
734
+ const installRootResolved = path.resolve(installRoot);
735
+ const targetDir = path.resolve(path.join(installRoot, category, type));
736
+ if (!targetDir.startsWith(installRootResolved + path.sep)) {
737
+ return { success: false, error: 'install path escaped upstream root' };
738
+ }
739
+ fs.mkdirSync(targetDir, { recursive: true });
740
+ // v1 vs v0 manifest selection — v1 manifests carry an SDK
741
+ // $schema URL or v1-only keys (tui, overrides-as-object,
742
+ // source, canonicalHistory). The loader prefers
743
+ // provider.v1.json when both exist, so writing v1 manifests
744
+ // under the v0 name would shadow them. Detect and write to
745
+ // the right file.
746
+ let manifestProbe: Record<string, any> = {};
747
+ try { manifestProbe = JSON.parse(manifestBody) as Record<string, any>; } catch { /* validation below */ }
748
+ const isV1 = typeof manifestProbe?.$schema === 'string' && manifestProbe.$schema.includes('/v1/')
749
+ || (manifestProbe?.overrides && typeof manifestProbe.overrides === 'object' && !Array.isArray(manifestProbe.overrides))
750
+ || !!manifestProbe?.tui;
751
+
752
+ // Reject v1 manifests that don't match the schema at install
753
+ // time so the daemon never persists a known-bad manifest.
754
+ // The provider-loader keeps a permissive warn-only behavior
755
+ // for manifests already on disk, but the install path is the
756
+ // right place to fail fast.
757
+ if (isV1 && manifestProbe?.category === 'cli') {
758
+ try {
759
+ const { validateCliProviderManifest, formatManifestValidationIssues } =
760
+ require('../providers/sdk/v1/validators/manifest.js') as typeof import('../providers/sdk/v1/validators/manifest.js');
761
+ const validation = validateCliProviderManifest(manifestProbe);
762
+ if (!validation.ok) {
763
+ return {
764
+ success: false,
765
+ error: `manifest failed v1 schema validation:\n${formatManifestValidationIssues(validation.issues)}`,
766
+ validationIssues: validation.issues,
767
+ };
768
+ }
769
+ } catch (e: any) {
770
+ // Validator load failure shouldn't block install — log
771
+ // and continue. The loader's warn-only path will
772
+ // surface the same issue at boot if it's real.
773
+ LOG.warn('Command', `[install_provider_manifest] schema validator unavailable: ${e?.message || e}`);
774
+ }
775
+ }
776
+
777
+ const targetFile = isV1 ? 'provider.v1.json' : 'provider.json';
778
+ const targetPath = path.join(targetDir, targetFile);
779
+ fs.writeFileSync(targetPath, manifestBody, 'utf-8');
780
+
781
+ // 5. If the manifest declares a `source` GitHub repo, fetch the
782
+ // script directories listed in the manifest (defaultScriptDir +
783
+ // each compatibility[].scriptDir). Extended-tier providers
784
+ // bundle their override JS this way — the registry intentionally
785
+ // only stores the manifest JSON, not the script bytes, so a
786
+ // third-party can publish a manifest pointing at their own fork
787
+ // without pushing files into our R2 bucket.
788
+ const manifestJson = JSON.parse(manifestBody) as Record<string, any>;
789
+ const scriptFetch = await this.fetchProviderSources(
790
+ manifestJson,
791
+ category,
792
+ type,
793
+ targetDir,
794
+ );
795
+
796
+ // Hot-reload so the daemon picks up the new manifest.
797
+ this._ctx.providerLoader.reload();
798
+ this._ctx.providerLoader.registerToDetector();
799
+
800
+ return {
801
+ success: true,
802
+ installed: {
803
+ type, category, version, checksum: actualChecksum, path: targetPath,
804
+ scriptsFetched: scriptFetch.fetchedCount,
805
+ scriptSource: scriptFetch.source,
806
+ scriptErrors: scriptFetch.errors,
807
+ },
808
+ };
809
+ } catch (e: any) {
810
+ return { success: false, error: `install failed: ${e?.message || e}` };
811
+ }
812
+ }
813
+
814
+ /**
815
+ * If `manifest.source = { type:'github', repo, ref, subdir? }` is set,
816
+ * walk each script directory the manifest references and download every
817
+ * file from the public GitHub raw endpoint. Returns a small summary so
818
+ * the caller can report what was fetched.
819
+ *
820
+ * Best-effort: failures don't reject the install — the manifest itself is
821
+ * usable for declarative-only providers, and the user still gets a clear
822
+ * error string back if a needed script is missing.
823
+ */
824
+ private async fetchProviderSources(
825
+ manifest: Record<string, any>,
826
+ category: string,
827
+ type: string,
828
+ targetDir: string,
829
+ ): Promise<{ fetchedCount: number; source: string | null; errors: string[] }> {
830
+ const errors: string[] = [];
831
+ const source = manifest?.source;
832
+ if (!source || source.type !== 'github' || typeof source.repo !== 'string' || typeof source.ref !== 'string') {
833
+ return { fetchedCount: 0, source: null, errors };
834
+ }
835
+
836
+ // Collect every script directory the manifest references. v1 manifests
837
+ // use `defaultScriptDir` and `compatibility[].scriptDir`. We also pull
838
+ // any override path's directory (e.g. overrides.detectStatus.path =
839
+ // "scripts/v1/detect_status.js" → fetch the scripts/v1/ directory too).
840
+ const scriptDirs = new Set<string>();
841
+ if (typeof manifest.defaultScriptDir === 'string') scriptDirs.add(manifest.defaultScriptDir);
842
+ if (Array.isArray(manifest.compatibility)) {
843
+ for (const c of manifest.compatibility) {
844
+ if (typeof c?.scriptDir === 'string') scriptDirs.add(c.scriptDir);
845
+ // Spec-driven providers (claude/codex/agy/…) point at a
846
+ // single specs/<version>.json instead of a scriptDir.
847
+ // The whole specs/ directory needs to come down so the
848
+ // spec adapter can resolve the file at runtime — without
849
+ // this, install_provider_manifest leaves the marketplace
850
+ // copy spec-less and provider-loader falls back to the
851
+ // legacy tui-based ProviderCliAdapter.
852
+ if (typeof c?.spec === 'string' && c.spec.includes('/')) {
853
+ const dir = c.spec.substring(0, c.spec.lastIndexOf('/'));
854
+ if (dir) scriptDirs.add(dir);
855
+ }
856
+ }
857
+ }
858
+ if (manifest.overrides && typeof manifest.overrides === 'object' && !Array.isArray(manifest.overrides)) {
859
+ for (const override of Object.values(manifest.overrides) as Array<Record<string, unknown>>) {
860
+ const overridePath = override?.path;
861
+ if (typeof overridePath === 'string' && overridePath.includes('/')) {
862
+ const dir = overridePath.substring(0, overridePath.lastIndexOf('/'));
863
+ if (dir) scriptDirs.add(dir);
864
+ }
865
+ }
866
+ }
867
+ if (scriptDirs.size === 0) {
868
+ return { fetchedCount: 0, source: `${source.repo}@${source.ref}`, errors };
869
+ }
870
+
871
+ const subdir: string = typeof source.subdir === 'string' && source.subdir.length > 0
872
+ ? source.subdir
873
+ : `${category}/${type}`;
874
+ const repo: string = source.repo;
875
+ const ref: string = source.ref;
876
+
877
+ const https = require('https') as typeof import('https');
878
+ const fs = require('fs') as typeof import('fs');
879
+ const path = require('path') as typeof import('path');
880
+
881
+ function fetchJson(url: string, timeoutMs: number): Promise<any> {
882
+ return new Promise((resolve, reject) => {
883
+ const req = https.get(url, {
884
+ headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/vnd.github+json' },
885
+ timeout: timeoutMs,
886
+ }, (res) => {
887
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
888
+ const chunks: Buffer[] = [];
889
+ res.on('data', (c: Buffer) => chunks.push(c));
890
+ res.on('end', () => {
891
+ try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
892
+ catch (e) { reject(e); }
893
+ });
894
+ });
895
+ req.on('error', reject);
896
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
897
+ });
898
+ }
899
+
900
+ function fetchBinary(url: string, timeoutMs: number): Promise<Buffer> {
901
+ return new Promise((resolve, reject) => {
902
+ const req = https.get(url, {
903
+ headers: { 'User-Agent': 'adhdev-daemon' },
904
+ timeout: timeoutMs,
905
+ }, (res) => {
906
+ // Raw endpoint redirects through codeload — follow the redirect.
907
+ if (res.statusCode === 301 || res.statusCode === 302) {
908
+ if (res.headers.location) {
909
+ return fetchBinary(res.headers.location, timeoutMs).then(resolve, reject);
910
+ }
911
+ }
912
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
913
+ const chunks: Buffer[] = [];
914
+ res.on('data', (c: Buffer) => chunks.push(c));
915
+ res.on('end', () => resolve(Buffer.concat(chunks)));
916
+ });
917
+ req.on('error', reject);
918
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
919
+ });
920
+ }
921
+
922
+ let fetchedCount = 0;
923
+
924
+ // The shared helpers directory (cli/_shared/) is referenced by most
925
+ // CLI providers via `require('../../../_shared/...')`. Fetch it once
926
+ // alongside the provider's own scripts so node's require resolution
927
+ // succeeds at runtime. Best-effort — silent if the source repo has no
928
+ // _shared dir (e.g. ACP-only repos).
929
+ const sharedDirRel = `${category}/_shared`;
930
+ const sharedTargetDir = path.resolve(path.join(targetDir, '../_shared'));
931
+ const installRootResolved = path.resolve(path.join(targetDir, '../..'));
932
+ if (sharedTargetDir.startsWith(installRootResolved + path.sep)) {
933
+ const sharedStack: string[] = [sharedDirRel];
934
+ while (sharedStack.length) {
935
+ const relDir = sharedStack.pop()!;
936
+ const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
937
+ let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
938
+ try {
939
+ entries = await fetchJson(apiUrl, 15000);
940
+ } catch (e: any) {
941
+ // Silent: _shared may not exist on third-party repos
942
+ if (relDir === sharedDirRel) break;
943
+ errors.push(`list shared ${relDir}: ${e?.message ?? e}`);
944
+ continue;
945
+ }
946
+ if (!Array.isArray(entries)) continue;
947
+ for (const entry of entries) {
948
+ if (entry.type === 'dir') { sharedStack.push(entry.path); continue; }
949
+ if (entry.type !== 'file' || !entry.download_url) continue;
950
+ try {
951
+ const body = await fetchBinary(entry.download_url, 30000);
952
+ // entry.path is like 'cli/_shared/foo.js' — strip 'cli/_shared/' prefix
953
+ const relInside = entry.path.startsWith(sharedDirRel + '/')
954
+ ? entry.path.slice(sharedDirRel.length + 1)
955
+ : entry.path;
956
+ const outPath = path.resolve(path.join(sharedTargetDir, relInside));
957
+ if (!outPath.startsWith(path.resolve(sharedTargetDir) + path.sep)) continue;
958
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
959
+ fs.writeFileSync(outPath, body);
960
+ fetchedCount++;
961
+ } catch (e: any) {
962
+ errors.push(`fetch shared ${entry.path}: ${e?.message ?? e}`);
963
+ }
964
+ }
965
+ }
966
+ }
967
+
968
+ for (const scriptDir of scriptDirs) {
969
+ // GitHub Contents API returns the file list under the dir. We
970
+ // recurse into subdirectories so e.g. scripts/v1/helpers/foo.js is
971
+ // captured too.
972
+ const stack: string[] = [`${subdir}/${scriptDir}`];
973
+ while (stack.length) {
974
+ const relDir = stack.pop()!;
975
+ const apiUrl = `https://api.github.com/repos/${repo}/contents/${encodeURI(relDir)}?ref=${encodeURIComponent(ref)}`;
976
+ let entries: Array<{ type: string; path: string; name: string; download_url: string | null }>;
977
+ try {
978
+ entries = await fetchJson(apiUrl, 15000);
979
+ } catch (e: any) {
980
+ errors.push(`list ${relDir}: ${e?.message ?? e}`);
981
+ continue;
982
+ }
983
+ if (!Array.isArray(entries)) {
984
+ errors.push(`list ${relDir}: unexpected response shape`);
985
+ continue;
986
+ }
987
+ for (const entry of entries) {
988
+ if (entry.type === 'dir') {
989
+ stack.push(entry.path);
990
+ continue;
991
+ }
992
+ if (entry.type !== 'file' || !entry.download_url) continue;
993
+ try {
994
+ const body = await fetchBinary(entry.download_url, 30000);
995
+ // entry.path is relative to repo root → strip the repo subdir prefix
996
+ // so the path inside targetDir matches the layout the loader expects.
997
+ const relInsideProvider = entry.path.startsWith(subdir + '/')
998
+ ? entry.path.slice(subdir.length + 1)
999
+ : entry.path;
1000
+ const outPath = path.resolve(path.join(targetDir, relInsideProvider));
1001
+ // Path-traversal guard.
1002
+ if (!outPath.startsWith(path.resolve(targetDir) + path.sep)) {
1003
+ errors.push(`refusing to write outside targetDir: ${entry.path}`);
1004
+ continue;
1005
+ }
1006
+ fs.mkdirSync(path.dirname(outPath), { recursive: true });
1007
+ fs.writeFileSync(outPath, body);
1008
+ fetchedCount++;
1009
+ } catch (e: any) {
1010
+ errors.push(`fetch ${entry.path}: ${e?.message ?? e}`);
1011
+ }
1012
+ }
1013
+ }
1014
+ }
1015
+
1016
+ return { fetchedCount, source: `${repo}@${ref}`, errors };
1017
+ }
1018
+
1019
+ /**
1020
+ * Remove a provider manifest from the upstream cache root
1021
+ * (~/.adhdev/providers/.upstream/{category}/{type}/). Refuses to touch
1022
+ * anything outside that root. Used by onboarding to opt out of a
1023
+ * provider the user doesn't want; the dashboard no longer exposes a
1024
+ * per-provider uninstall button (external sources are removed as a
1025
+ * whole via remove_provider_source).
1026
+ */
1027
+ private async handleUninstallProviderManifest(args: any): Promise<CommandResult> {
1028
+ const type = typeof args?.type === 'string' ? args.type : '';
1029
+ const category = typeof args?.category === 'string' ? args.category : '';
1030
+ if (!type || !category) return { success: false, error: 'type and category are required' };
1031
+ if (!/^[a-z0-9][a-z0-9_-]{0,63}$/i.test(type)) {
1032
+ return { success: false, error: 'invalid type' };
1033
+ }
1034
+ if (!['cli', 'ide', 'extension', 'acp'].includes(category)) {
1035
+ return { success: false, error: `unknown category: ${category}` };
1036
+ }
1037
+
1038
+ const fs = require('fs') as typeof import('fs');
1039
+ const path = require('path') as typeof import('path');
1040
+
1041
+ try {
1042
+ const installRoot = this.getUpstreamInstallRoot();
1043
+ const installRootResolved = path.resolve(installRoot);
1044
+ const targetDir = path.resolve(path.join(installRoot, category, type));
1045
+
1046
+ if (!targetDir.startsWith(installRootResolved + path.sep)) {
1047
+ return { success: false, error: 'refusing to delete outside upstream root' };
1048
+ }
1049
+ if (!fs.existsSync(targetDir)) {
1050
+ return { success: false, error: 'not installed' };
1051
+ }
1052
+
1053
+ fs.rmSync(targetDir, { recursive: true, force: true });
1054
+
1055
+ if (this._ctx.providerLoader) {
1056
+ this._ctx.providerLoader.reload();
1057
+ this._ctx.providerLoader.registerToDetector();
1058
+ }
1059
+
1060
+ return { success: true, removed: { type, category, path: targetDir } };
1061
+ } catch (e: any) {
1062
+ return { success: false, error: `uninstall failed: ${e?.message || e}` };
1063
+ }
1064
+ }
1065
+
1066
+ /**
1067
+ * Return everything currently installed in the upstream cache with its
1068
+ * version. This is the "what does this daemon have" answer used both by
1069
+ * the UI and by the update checker.
1070
+ */
1071
+ private handleListInstalledProviders(_args: any): CommandResult {
1072
+ const fs = require('fs') as typeof import('fs');
1073
+ const path = require('path') as typeof import('path');
1074
+
1075
+ const installRoot = this.getUpstreamInstallRoot();
1076
+ if (!fs.existsSync(installRoot)) return { success: true, providers: [] };
1077
+
1078
+ const CATEGORIES = ['cli', 'ide', 'extension', 'acp'] as const;
1079
+ const items: Array<{ type: string; category: string; version: string; path: string }> = [];
1080
+
1081
+ for (const category of CATEGORIES) {
1082
+ const categoryDir = path.join(installRoot, category);
1083
+ if (!fs.existsSync(categoryDir)) continue;
1084
+ let entries: string[];
1085
+ try { entries = fs.readdirSync(categoryDir); } catch { continue; }
1086
+ for (const type of entries) {
1087
+ // v1 manifest takes precedence over v0 when both are present.
1088
+ const v1Path = path.join(categoryDir, type, 'provider.v1.json');
1089
+ const v0Path = path.join(categoryDir, type, 'provider.json');
1090
+ const manifestPath = fs.existsSync(v1Path) ? v1Path : (fs.existsSync(v0Path) ? v0Path : null);
1091
+ if (!manifestPath) continue;
1092
+ try {
1093
+ const m = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
1094
+ items.push({
1095
+ type,
1096
+ category,
1097
+ version: typeof m.providerVersion === 'string' ? m.providerVersion : '0.0.0',
1098
+ path: manifestPath,
1099
+ });
1100
+ } catch {
1101
+ // Corrupt manifest — skip but don't fail the whole listing.
1102
+ }
1103
+ }
1104
+ }
1105
+ return { success: true, providers: items };
1106
+ }
1107
+
1108
+ /**
1109
+ * For each installed provider, ask the registry for its current latest
1110
+ * version and report whether an update is available. The user can then
1111
+ * call install_provider_manifest to upgrade (it overwrites the file).
1112
+ *
1113
+ * Returns { providers: [{ type, category, installedVersion, latestVersion,
1114
+ * updateAvailable, error? }] }
1115
+ */
1116
+ private async handleCheckProviderUpdates(_args: any): Promise<CommandResult> {
1117
+ const installed = this.handleListInstalledProviders({});
1118
+ if (!installed.success) return installed;
1119
+
1120
+ const https = require('https') as typeof import('https');
1121
+ const REGISTRY = 'https://api.adhf.dev/api/v1/registry';
1122
+
1123
+ function fetchJson(url: string): Promise<any> {
1124
+ return new Promise((resolve, reject) => {
1125
+ const req = https.get(url, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
1126
+ if (res.statusCode !== 200) { reject(new Error(`HTTP ${res.statusCode}`)); return; }
1127
+ const chunks: Buffer[] = [];
1128
+ res.on('data', (c: Buffer) => chunks.push(c));
1129
+ res.on('end', () => {
1130
+ try { resolve(JSON.parse(Buffer.concat(chunks).toString('utf-8'))); }
1131
+ catch (e) { reject(e); }
1132
+ });
1133
+ });
1134
+ req.on('error', reject);
1135
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
1136
+ });
1137
+ }
1138
+
1139
+ const installedList = (installed as unknown as { providers: Array<{ type: string; category: string; version: string }> }).providers;
1140
+ const checks = await Promise.all(
1141
+ installedList.map(async (p) => {
1142
+ try {
1143
+ const remote = await fetchJson(`${REGISTRY}/providers/${encodeURIComponent(p.type)}`);
1144
+ const latestVersion = String(remote?.version ?? '');
1145
+ return {
1146
+ type: p.type,
1147
+ category: p.category,
1148
+ installedVersion: p.version,
1149
+ latestVersion,
1150
+ updateAvailable: latestVersion !== '' && latestVersion !== p.version,
1151
+ };
1152
+ } catch (e: any) {
1153
+ return {
1154
+ type: p.type,
1155
+ category: p.category,
1156
+ installedVersion: p.version,
1157
+ latestVersion: null,
1158
+ updateAvailable: false,
1159
+ error: e?.message ?? String(e),
1160
+ };
1161
+ }
1162
+ })
1163
+ );
1164
+
1165
+ return { success: true, providers: checks };
1166
+ }
1167
+
1168
+ // ─── External provider sources (3rd-party git URLs) ──────────────
1169
+
1170
+ /**
1171
+ * Register a new external provider source. The daemon clones the repo
1172
+ * to ~/.adhdev/external/<name>/, walks it once to detect provided
1173
+ * types, and surfaces any conflicts with already-installed types so
1174
+ * the dashboard can ask the user how to resolve them.
1175
+ *
1176
+ * Args: { url: string, ref?: string, name?: string }
1177
+ * - url: https://, git@, or any git-cloneable URL
1178
+ * - ref: branch/tag/commit (default "main")
1179
+ * - name: short identifier (default derived from URL)
1180
+ *
1181
+ * Returns: { source, providers, conflicts }
1182
+ * - conflicts: list of types this new source provides that another
1183
+ * source already exposes. UI uses this to prompt for active-source
1184
+ * selection before the load takes effect.
1185
+ */
1186
+ private async handleAddProviderSource(args: any): Promise<CommandResult> {
1187
+ const url = typeof args?.url === 'string' ? args.url.trim() : '';
1188
+ if (!url) return { success: false, error: 'url is required' };
1189
+ const ref = typeof args?.ref === 'string' && args.ref.trim() ? args.ref.trim() : 'main';
1190
+
1191
+ // Defense in depth against argv flag-smuggling: reject anything that
1192
+ // looks like a git option in either positional. The `--` end-of-options
1193
+ // sentinel below catches accidental cases, but rejecting early gives
1194
+ // a clear error message and stops obviously malicious inputs from
1195
+ // even touching git.
1196
+ if (url.startsWith('-')) return { success: false, error: 'url must not start with "-"' };
1197
+ if (ref.startsWith('-')) return { success: false, error: 'ref must not start with "-"' };
1198
+ // Whitelist the protocols we'll forward to git. Anything else
1199
+ // (file://, ext-protocol-handlers, …) is refused outright.
1200
+ if (!/^(https?:\/\/|git@[a-z0-9._-]+:)[a-z0-9._@:/~\-]+$/i.test(url)) {
1201
+ return { success: false, error: 'url must be https://… or git@host:… and contain only URL-safe characters' };
1202
+ }
1203
+ // Refs are git refnames — letters, digits, slashes, dots, underscores,
1204
+ // dashes. Rejects e.g. spaces, semicolons, backticks, shell metas.
1205
+ if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
1206
+ return { success: false, error: 'ref must contain only [A-Za-z0-9._/-]' };
1207
+ }
1208
+
1209
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1210
+ const requestedName = typeof args?.name === 'string' && args.name.trim() ? args.name.trim() : ext.deriveSourceName(url);
1211
+ if (!/^@[a-z0-9_-]+$/i.test(requestedName)) {
1212
+ return { success: false, error: 'name must match @[a-z0-9_-]+' };
1213
+ }
1214
+
1215
+ const fs = require('node:fs') as typeof import('node:fs');
1216
+ const path = require('node:path') as typeof import('node:path');
1217
+ const { spawnSync } = require('node:child_process') as typeof import('node:child_process');
1218
+
1219
+ const file = ext.loadExternalSources();
1220
+ if (file.sources.some(s => s.name === requestedName)) {
1221
+ return { success: false, error: `source name "${requestedName}" is already registered` };
1222
+ }
1223
+ if (file.sources.some(s => s.url === url && s.ref === ref)) {
1224
+ return { success: false, error: `source url+ref already registered (use a different name to track another ref)` };
1225
+ }
1226
+
1227
+ const sourceDir = path.join(ext.externalRoot(), requestedName);
1228
+ if (!fs.existsSync(ext.externalRoot())) fs.mkdirSync(ext.externalRoot(), { recursive: true });
1229
+ if (fs.existsSync(sourceDir)) {
1230
+ return { success: false, error: `directory already exists: ${sourceDir} (rename or remove first)` };
1231
+ }
1232
+
1233
+ // `--` sentinel after the option list so any future regex-bypassing
1234
+ // url that *did* start with `-` would still be treated as a path
1235
+ // by git rather than an option.
1236
+ const clone = spawnSync('git', ['clone', '--depth=1', '--branch', ref, '--', url, sourceDir], {
1237
+ encoding: 'utf-8',
1238
+ stdio: ['ignore', 'pipe', 'pipe'],
1239
+ timeout: 60_000,
1240
+ });
1241
+ if (clone.status !== 0) {
1242
+ try { fs.rmSync(sourceDir, { recursive: true, force: true }); } catch { /* best-effort */ }
1243
+ return { success: false, error: `git clone failed: ${(clone.stderr || clone.stdout || '').trim() || 'unknown error'}` };
1244
+ }
1245
+
1246
+ const source: import('../providers/external-sources.js').ExternalSource = {
1247
+ name: requestedName,
1248
+ url,
1249
+ ref,
1250
+ addedAt: new Date().toISOString(),
1251
+ };
1252
+ ext.saveExternalSources({ schema: 1, sources: [...file.sources, source] });
1253
+
1254
+ // Detect type-level conflicts with what's already on disk after this clone.
1255
+ const inventory = ext.inventoryExternalSources();
1256
+ const conflicts: { category: string; type: string; sources: string[] }[] = [];
1257
+ const newEntry = inventory.find(e => e.sourceName === requestedName);
1258
+ if (newEntry) {
1259
+ for (const [category, types] of Object.entries(newEntry.providers)) {
1260
+ for (const type of types) {
1261
+ const sources = ext.sourcesProviding(category, type);
1262
+ if (sources.length > 1) conflicts.push({ category, type, sources });
1263
+ }
1264
+ }
1265
+ }
1266
+
1267
+ // Hot-reload so the daemon picks up the new providers immediately.
1268
+ if (this._ctx.providerLoader) {
1269
+ this._ctx.providerLoader.reload();
1270
+ this._ctx.providerLoader.registerToDetector();
1271
+ }
1272
+
1273
+ return {
1274
+ success: true,
1275
+ source,
1276
+ providers: newEntry?.providers ?? {},
1277
+ conflicts,
1278
+ };
1279
+ }
1280
+
1281
+ /**
1282
+ * Remove a registered external source. Deletes the clone directory and
1283
+ * any active-source entry pointing to it.
1284
+ *
1285
+ * Args: { name: string }
1286
+ */
1287
+ private async handleRemoveProviderSource(args: any): Promise<CommandResult> {
1288
+ const name = typeof args?.name === 'string' ? args.name.trim() : '';
1289
+ if (!name) return { success: false, error: 'name is required' };
1290
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1291
+
1292
+ const fs = require('node:fs') as typeof import('node:fs');
1293
+ const path = require('node:path') as typeof import('node:path');
1294
+ const file = ext.loadExternalSources();
1295
+ const match = file.sources.find(s => s.name === name);
1296
+ if (!match) return { success: false, error: `source "${name}" not registered` };
1297
+
1298
+ const sourceDir = path.join(ext.externalRoot(), name);
1299
+ if (fs.existsSync(sourceDir)) {
1300
+ try { fs.rmSync(sourceDir, { recursive: true, force: true }); }
1301
+ catch (e: any) { return { success: false, error: `failed to delete ${sourceDir}: ${e?.message || e}` }; }
1302
+ }
1303
+
1304
+ ext.saveExternalSources({
1305
+ schema: 1,
1306
+ sources: file.sources.filter(s => s.name !== name),
1307
+ });
1308
+
1309
+ // Drop any active-source entries that pointed at this source.
1310
+ const active = ext.loadProvidersActive();
1311
+ const filteredActive: Record<string, string> = {};
1312
+ for (const [type, src] of Object.entries(active.active)) {
1313
+ if (src !== name) filteredActive[type] = src;
1314
+ }
1315
+ ext.saveProvidersActive({ schema: 1, active: filteredActive });
1316
+
1317
+ if (this._ctx.providerLoader) {
1318
+ this._ctx.providerLoader.reload();
1319
+ this._ctx.providerLoader.registerToDetector();
1320
+ }
1321
+
1322
+ return { success: true, removed: { name } };
1323
+ }
1324
+
1325
+ /**
1326
+ * List registered external sources + each source's currently installed
1327
+ * providers + the active selection for any conflicting types. Used by
1328
+ * the dashboard's "Sources" tab.
1329
+ */
1330
+ private handleListProviderSources(_args: any): CommandResult {
1331
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1332
+ const file = ext.loadExternalSources();
1333
+ const inventory = ext.inventoryExternalSources();
1334
+ const active = ext.loadProvidersActive();
1335
+
1336
+ // Build a per-source view + flag types that have ambiguity.
1337
+ const sources = file.sources.map(s => {
1338
+ const inv = inventory.find(e => e.sourceName === s.name);
1339
+ return {
1340
+ ...s,
1341
+ providers: inv?.providers ?? {},
1342
+ };
1343
+ });
1344
+
1345
+ // Compute conflicts globally — any type provided by ≥ 2 sources.
1346
+ const conflictMap = new Map<string, { category: string; sources: string[] }>();
1347
+ for (const inv of inventory) {
1348
+ for (const [category, types] of Object.entries(inv.providers)) {
1349
+ for (const type of types) {
1350
+ const candidates = ext.sourcesProviding(category, type);
1351
+ if (candidates.length > 1 && !conflictMap.has(type)) {
1352
+ conflictMap.set(type, { category, sources: candidates });
1353
+ }
1354
+ }
1355
+ }
1356
+ }
1357
+ const conflicts = [...conflictMap.entries()].map(([type, info]) => ({
1358
+ type,
1359
+ category: info.category,
1360
+ candidates: info.sources,
1361
+ active: active.active[type] ?? null,
1362
+ }));
1363
+
1364
+ return { success: true, sources, conflicts };
1365
+ }
1366
+
1367
+ /**
1368
+ * Pick which source's copy of a conflicting provider type is active.
1369
+ * Other sources' copies stay on disk but the loader ignores them.
1370
+ *
1371
+ * Args: { type: string, sourceName: string }
1372
+ */
1373
+ private handleSetActiveProviderSource(args: any): CommandResult {
1374
+ const type = typeof args?.type === 'string' ? args.type.trim() : '';
1375
+ const sourceName = typeof args?.sourceName === 'string' ? args.sourceName.trim() : '';
1376
+ if (!type || !sourceName) return { success: false, error: 'type and sourceName are required' };
1377
+ const ext = require('../providers/external-sources.js') as typeof import('../providers/external-sources.js');
1378
+
1379
+ // Validate: the source must actually provide that type.
1380
+ const inventory = ext.inventoryExternalSources();
1381
+ const entry = inventory.find(e => e.sourceName === sourceName);
1382
+ if (!entry) return { success: false, error: `source "${sourceName}" not found` };
1383
+ const provided = Object.values(entry.providers).some(types => types.includes(type));
1384
+ if (!provided) return { success: false, error: `source "${sourceName}" does not provide type "${type}"` };
1385
+
1386
+ const active = ext.loadProvidersActive();
1387
+ active.active[type] = sourceName;
1388
+ ext.saveProvidersActive(active);
1389
+
1390
+ if (this._ctx.providerLoader) {
1391
+ this._ctx.providerLoader.reload();
1392
+ this._ctx.providerLoader.registerToDetector();
1393
+ }
1394
+
1395
+ return { success: true, type, sourceName };
1396
+ }
1397
+
559
1398
  // ─── DevServer HTTP proxy helpers ─────────────────
560
1399
  // These bridge WS commands to the DevServer REST API (localhost:19280)
561
1400