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

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
@@ -18,10 +18,12 @@ import * as path from 'path';
18
18
  import * as os from 'os';
19
19
  import * as chokidar from 'chokidar';
20
20
  import { registerIDEDefinition } from '../detection/ide-detector.js';
21
+ import { sha256Hex } from '../system/hash.js';
21
22
  import { LOG } from '../logging/logger.js';
22
23
  import { VersionArchive } from './version-archive.js';
23
24
  import type {
24
25
  ProviderCompatibilityEntry,
26
+ ProviderControlDef,
25
27
  ProviderModule,
26
28
  ProviderCategory,
27
29
  ProviderScripts,
@@ -30,8 +32,34 @@ import type {
30
32
  ResolvedProvider,
31
33
  } from './contracts.js';
32
34
  import { validateProviderDefinition } from './provider-schema.js';
35
+ import {
36
+ loadProvidersActive,
37
+ resolveActiveSource,
38
+ } from './external-sources.js';
33
39
  import type { ProviderSourceMode } from '../config/config.js';
34
40
  import type { ProviderSourceConfigSnapshot, ProviderUserDirSource } from '../config/provider-source-config.js';
41
+ import { executeNativeHistory } from './spec/native-history-executor.js';
42
+ import { createNativeHistoryDispatcher, type ReaderId } from './native-history/dispatcher.js';
43
+
44
+ /**
45
+ * Adds a provider-script root to the require whitelist. Wrapped in a
46
+ * try/catch + null check so a loader hot-path can't crash on a path
47
+ * that doesn't exist yet or one the whitelist hook rejects.
48
+ *
49
+ * The require-whitelist module is loaded lazily on first call. Eagerly
50
+ * top-level importing it pulls `node:fs.realpathSync.native` into
51
+ * module evaluation, which breaks unit tests that partially mock `fs`
52
+ * (e.g. test/commands/get-logs-incremental.test.ts mocks only
53
+ * existsSync + readFileSync). Lazy load keeps that mock surface valid.
54
+ */
55
+ function registerProviderScriptRootSafely(root: string | null | undefined): void {
56
+ if (!root || typeof root !== 'string') return;
57
+ try {
58
+ const { registerProviderScriptRoot } =
59
+ require('./sdk/v1/sandbox/require-whitelist.js') as typeof import('./sdk/v1/sandbox/require-whitelist.js');
60
+ registerProviderScriptRoot(root);
61
+ } catch { /* boot-time only — swallow */ }
62
+ }
35
63
 
36
64
  interface ProviderAvailabilityState {
37
65
  installed: boolean;
@@ -61,6 +89,78 @@ export interface MachineProviderConfig {
61
89
  lastVerification?: MachineProviderCheckResult;
62
90
  }
63
91
 
92
+ /**
93
+ * Translate a spec `control_bar` array into the web-facing
94
+ * `ProviderControlDef[]` shape the dashboard renders.
95
+ *
96
+ * The two shapes are distinct: `control_bar` entries are daemon-side
97
+ * `{ id, label, visible_when_state, action }` records driving
98
+ * SpecCliAdapter.invokeScript, while the dashboard's chat bar reads
99
+ * `ProviderControlDef` (`{ id, type, label, placement, ... }`). Spec
100
+ * providers (claude-cli / codex-cli) historically declared *only*
101
+ * `control_bar`, so the dashboard saw no controls at all — the Model / Mode
102
+ * pickers never rendered. This bridges that gap without changing how the
103
+ * controls actually dispatch.
104
+ *
105
+ * Script-name contract: the dashboard sends the control's
106
+ * `listScript` / `setScript` / `invokeScript` name through
107
+ * `invoke_provider_script`, which gates on `provider.scripts[<name>]` and then
108
+ * routes to `SpecCliAdapter.invokeScript(<name>)` — which matches the name
109
+ * against `control_bar[].id`. So every synthesized script name MUST equal the
110
+ * control id (the loader stubs `provider.scripts[id]` from the same source).
111
+ *
112
+ * Mapping:
113
+ * open_picker → select (dynamic): list + set both keyed on the control id;
114
+ * the adapter distinguishes LIST vs SELECT by the presence of
115
+ * a choice arg, so one id serves both roles.
116
+ * send_keys → action: one-shot keystroke (stop, cycle_mode).
117
+ * attach_image → skipped: it needs an image blob from a file picker, not a
118
+ * bare bar button; surfacing it as an `action` would only
119
+ * produce a button that errors with "requires args.blob".
120
+ */
121
+ function synthesizeControlsFromControlBar(specControls: any[]): ProviderControlDef[] {
122
+ const out: ProviderControlDef[] = [];
123
+ specControls.forEach((ctl, index) => {
124
+ const id = typeof ctl?.id === 'string' ? ctl.id.trim() : '';
125
+ const actionType = ctl?.action?.type;
126
+ if (!id || !actionType) return;
127
+ const label = typeof ctl?.label === 'string' && ctl.label.trim() ? ctl.label : id;
128
+ // Preserve the spec's state gating so the web bar can mirror the daemon's
129
+ // FsmDriver.handleClickControl enforcement (otherwise the button renders in
130
+ // states where the daemon would silently drop the click).
131
+ const visibleWhenState = Array.isArray(ctl?.visible_when_state)
132
+ ? ctl.visible_when_state.filter((s: unknown): s is string => typeof s === 'string')
133
+ : undefined;
134
+ if (actionType === 'open_picker') {
135
+ out.push({
136
+ id,
137
+ type: 'select',
138
+ label,
139
+ placement: 'bar',
140
+ dynamic: true,
141
+ listScript: id,
142
+ setScript: id,
143
+ readFrom: id,
144
+ order: index,
145
+ ...(visibleWhenState && visibleWhenState.length > 0 ? { visibleWhenState } : {}),
146
+ });
147
+ } else if (actionType === 'send_keys') {
148
+ out.push({
149
+ id,
150
+ type: 'action',
151
+ label,
152
+ placement: 'bar',
153
+ invokeScript: id,
154
+ resultDisplay: 'none',
155
+ order: index,
156
+ ...(visibleWhenState && visibleWhenState.length > 0 ? { visibleWhenState } : {}),
157
+ });
158
+ }
159
+ // attach_image intentionally skipped — see fn doc.
160
+ });
161
+ return out;
162
+ }
163
+
64
164
  type CliDetectionEntry = {
65
165
  id: string;
66
166
  displayName: string;
@@ -92,7 +192,9 @@ export class ProviderLoader {
92
192
  }
93
193
 
94
194
  private static readonly GITHUB_TARBALL_URL = 'https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz';
195
+ private static readonly REGISTRY_BASE_URL = 'https://api.adhf.dev/api/v1/registry';
95
196
  private static readonly META_FILE = '.meta.json';
197
+ private static readonly REGISTRY_META_FILE = '.registry-meta.json';
96
198
  private static readonly REPO_PROVIDER_DIRNAME = 'adhdev-providers';
97
199
  private static readonly SIBLING_MARKER_FILE = '.adhdev-provider-root';
98
200
  private static readonly SIBLING_ENV_VAR = 'ADHDEV_USE_SIBLING_PROVIDERS';
@@ -193,6 +295,33 @@ export class ProviderLoader {
193
295
  sourceMode: options?.sourceMode,
194
296
  disableUpstream: options?.disableUpstream,
195
297
  });
298
+
299
+ // One-time migration: ~/.adhdev/marketplace → ~/.adhdev/external.
300
+ // The directory was renamed when the "marketplace" install model was
301
+ // dropped in favour of explicit external git sources. Best-effort:
302
+ // if the rename fails we leave both dirs in place and log so the user
303
+ // can investigate.
304
+ this.migrateMarketplaceDirToExternal();
305
+ }
306
+
307
+ private migrateMarketplaceDirToExternal(): void {
308
+ try {
309
+ const home = os.homedir();
310
+ const oldDir = path.join(home, '.adhdev', 'marketplace');
311
+ const newDir = path.join(home, '.adhdev', 'external');
312
+ if (!fs.existsSync(oldDir)) return;
313
+ if (fs.existsSync(newDir)) {
314
+ // Both exist — don't merge. Leave old in place; surface in logs so
315
+ // the user can decide what to keep. Loader still loads from
316
+ // external/ only, so old marketplace/ becomes inert.
317
+ this.log(`Migration skipped: both ~/.adhdev/marketplace and ~/.adhdev/external exist (marketplace dir is now inert and can be removed manually).`);
318
+ return;
319
+ }
320
+ fs.renameSync(oldDir, newDir);
321
+ this.log(`Migrated ~/.adhdev/marketplace → ~/.adhdev/external (one-time rename after provider source-layer cleanup).`);
322
+ } catch (e: any) {
323
+ this.log(`Marketplace→external migration failed: ${e?.message || e}`);
324
+ }
196
325
  }
197
326
 
198
327
  private log(msg: string): void {
@@ -224,7 +353,12 @@ export class ProviderLoader {
224
353
  * Highest-priority editable overrides come first.
225
354
  */
226
355
  getProviderRoots(): string[] {
227
- return [this.userDir, this.upstreamDir];
356
+ // Order matters: user customs > external (3rd-party sources) > upstream
357
+ // (official auto-sync). findProviderDirInternal walks this list in order
358
+ // to locate the provider dir containing the scripts/, so external must
359
+ // be included here even though loadAll() also reads it directly.
360
+ const externalDir = path.join(os.homedir(), '.adhdev', 'external');
361
+ return [this.userDir, externalDir, this.upstreamDir];
228
362
  }
229
363
 
230
364
  getSourceConfig(): ProviderSourceConfigSnapshot {
@@ -316,16 +450,19 @@ export class ProviderLoader {
316
450
 
317
451
  /**
318
452
  * Load all providers (3-tier priority)
319
- * 1. .upstream/ (GitHub auto-download — primary source)
320
- * 2. User custom (~/.adhdev/providers/ excluding .upstream)
321
- * User custom always wins (highest priority).
453
+ * 1. ~/.adhdev/providers/.upstream/ official git, auto-synced
454
+ * 2. ~/.adhdev/external/ 3rd-party git sources, user-added,
455
+ * bundled providers may include arbitrary JS (untrusted by default)
456
+ * 3. ~/.adhdev/providers/ (excluding .upstream) — user-authored customs,
457
+ * always wins
458
+ * Highest priority listed last (overwrites earlier loads).
322
459
  * If .upstream/ is empty, call fetchLatest() before loadAll().
323
460
  */
324
461
  loadAll(): void {
325
462
  this.providers.clear();
326
463
  this.providerAvailability.clear();
327
464
 
328
- // 1. Load upstream (GitHub auto-download — primary source)
465
+ // 1. Load upstream (GitHub auto-download — primary official source)
329
466
  let upstreamCount = 0;
330
467
  if (!this.disableUpstream && fs.existsSync(this.upstreamDir)) {
331
468
  upstreamCount = this.loadDir(this.upstreamDir);
@@ -336,7 +473,90 @@ export class ProviderLoader {
336
473
  this.log('Upstream loading disabled (sourceMode=no-upstream)');
337
474
  }
338
475
 
339
- // 2. Load user custom (excluding .upstream — highest priority, never auto-updated)
476
+ // 2. Load external providers from ~/.adhdev/external/<source-name>/
477
+ // (3rd-party git sources). Overrides upstream but is itself overridden
478
+ // by user customs in step 3.
479
+ //
480
+ // Each registered source is a separate subdirectory so two sources can
481
+ // both expose the same provider type without overwriting each other.
482
+ // When more than one source provides the same type, providers-active.json
483
+ // chooses the active one; without an explicit choice we deterministically
484
+ // pick the first in disk-walk order and log the ambiguity so the user
485
+ // can resolve it from the dashboard.
486
+ //
487
+ // Any non-spec manifest (tui block / overrides / scriptDir) coming from
488
+ // an external source runs JavaScript the daemon hasn't audited, so
489
+ // dashboards must surface an "untrusted source" badge before letting
490
+ // the user enable them.
491
+ const externalDir = path.join(os.homedir(), '.adhdev', 'external');
492
+ if (fs.existsSync(externalDir)) {
493
+ // Legacy layout (pre-source-namespace): manifests sit directly at
494
+ // external/<category>/<type>/. Detect by presence of category dirs at
495
+ // the root and migrate inline by treating the whole tree as a single
496
+ // implicit source. Loader behavior unchanged for legacy callers.
497
+ const rootEntries = (() => {
498
+ try { return fs.readdirSync(externalDir, { withFileTypes: true }); }
499
+ catch { return [] as fs.Dirent[]; }
500
+ })();
501
+ const KNOWN_CATEGORIES = new Set(['cli', 'ide', 'extension', 'acp']);
502
+ const looksLegacy = rootEntries.some(e => e.isDirectory() && KNOWN_CATEGORIES.has(e.name));
503
+ if (looksLegacy) {
504
+ // Tree shape predates per-source dirs — treat the whole thing as a
505
+ // single anonymous source so existing installs keep working until
506
+ // they're migrated to a real source registration.
507
+ const externalCount = this.loadDir(externalDir);
508
+ if (externalCount > 0) {
509
+ this.log(`Loaded ${externalCount} external providers (legacy unnamed source)`);
510
+ }
511
+ } else {
512
+ // New layout: external/<source-name>/<category>/<type>/…
513
+ const activeFile = loadProvidersActive();
514
+ let totalLoaded = 0;
515
+ const ambiguousTypes: { type: string; chosen: string; candidates: string[] }[] = [];
516
+ // Per-source load, then filter by active-selection: for each type
517
+ // present in more than one source, only the active source's copy
518
+ // is left in this.providers.
519
+ for (const sourceEntry of rootEntries) {
520
+ if (!sourceEntry.isDirectory()) continue;
521
+ const sourceDir = path.join(externalDir, sourceEntry.name);
522
+ const sourceLoaded = this.loadDir(sourceDir);
523
+ if (sourceLoaded > 0) {
524
+ totalLoaded += sourceLoaded;
525
+ this.log(`Loaded ${sourceLoaded} providers from external source "${sourceEntry.name}"`);
526
+ }
527
+ }
528
+ // Resolve ambiguities — when the same type came from multiple
529
+ // sources, the last load wins by default. Replay with the active
530
+ // selection so the user-chosen source ends up winning.
531
+ for (const [type] of this.providers) {
532
+ const prov = this.providers.get(type);
533
+ if (!prov) continue;
534
+ const resolved = resolveActiveSource(prov.category, type, activeFile);
535
+ if (resolved.candidates.length <= 1) continue;
536
+ if (resolved.ambiguous) {
537
+ ambiguousTypes.push({ type, chosen: resolved.source ?? '?', candidates: resolved.candidates });
538
+ }
539
+ if (resolved.source && resolved.source !== '?') {
540
+ const sourceDir = path.join(externalDir, resolved.source);
541
+ // Reload only this source's copy of the conflicting type so it
542
+ // overwrites whatever else won the initial pass.
543
+ const reloadCount = this.loadDir(sourceDir);
544
+ // reloadCount is a sanity check — we expect ≥1
545
+ if (reloadCount === 0) {
546
+ this.log(`Active source "${resolved.source}" no longer provides ${type}`);
547
+ }
548
+ }
549
+ }
550
+ if (totalLoaded > 0) {
551
+ this.log(`Loaded ${totalLoaded} external providers (3rd-party sources)`);
552
+ }
553
+ for (const a of ambiguousTypes) {
554
+ this.log(`Ambiguous provider "${a.type}" — provided by [${a.candidates.join(', ')}], defaulted to "${a.chosen}". Set the active source from the dashboard to silence this warning.`);
555
+ }
556
+ }
557
+ }
558
+
559
+ // 3. Load user custom (excluding .upstream — highest priority, never auto-updated)
340
560
  if (fs.existsSync(this.userDir)) {
341
561
  const userCount = this.loadDir(this.userDir, ['.upstream']);
342
562
  if (userCount > 0) {
@@ -909,18 +1129,28 @@ export class ProviderLoader {
909
1129
 
910
1130
  for (const entry of compat) {
911
1131
  if (this.matchesVersion(currentVersion, entry.ideVersion)) {
912
- const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
913
- if (loaded) {
914
- resolved.scripts = loaded;
915
- this.debugLog(` [compatibility] ${type} v${currentVersion} ${entry.scriptDir}`);
916
- resolved._resolvedScriptDir = entry.scriptDir;
917
- resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
918
- if (providerDir) {
919
- const fullDir = path.join(providerDir, entry.scriptDir);
920
- resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
921
- ? path.join(fullDir, 'scripts.js')
922
- : fullDir;
1132
+ // entry.scriptDir is optional now — spec-driven providers (agy,
1133
+ // codex on >=0.137, claude on >=2.1) only ship `spec` here, so
1134
+ // there's nothing to load from the filesystem. SpecCliAdapter
1135
+ // takes over via the `spec` path later in this method.
1136
+ if (entry.scriptDir) {
1137
+ const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
1138
+ if (loaded) {
1139
+ resolved.scripts = loaded;
1140
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
1141
+ resolved._resolvedScriptDir = entry.scriptDir;
1142
+ resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
1143
+ if (providerDir) {
1144
+ const fullDir = path.join(providerDir, entry.scriptDir);
1145
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
1146
+ ? path.join(fullDir, 'scripts.js')
1147
+ : fullDir;
1148
+ }
1149
+ matched = true;
923
1150
  }
1151
+ } else {
1152
+ // Spec-only entry — still counts as a match so the
1153
+ // defaultScriptDir fallback below doesn't kick in.
924
1154
  matched = true;
925
1155
  }
926
1156
  break; // first match wins
@@ -988,7 +1218,12 @@ export class ProviderLoader {
988
1218
  }
989
1219
 
990
1220
  // 3. Composite override (OS + version)
991
- if (base.overrides) {
1221
+ // Legacy shape: base.overrides is an Array<{ when: {os,version}, scripts }>.
1222
+ // v1 manifests (Phase 3-4) repurposed `overrides` as an object map of
1223
+ // capability overrides (e.g. { detectStatus: { path, schema } }), which is
1224
+ // consumed by the SDK builders, not by this resolver. Only iterate when
1225
+ // the field is in the legacy array shape.
1226
+ if (Array.isArray(base.overrides)) {
992
1227
  for (const override of base.overrides) {
993
1228
  const osMatch = !override.when.os || override.when.os === currentOs;
994
1229
  const verMatch = !override.when.version || (currentVersion && this.matchesVersion(currentVersion, override.when.version));
@@ -996,6 +1231,40 @@ export class ProviderLoader {
996
1231
  resolved.scripts = { ...resolved.scripts, ...override.scripts };
997
1232
  }
998
1233
  }
1234
+ } else if (base.overrides && typeof base.overrides === 'object') {
1235
+ // v1 manifest shape: { detectStatus: { path }, parseSession: { path }, ... }
1236
+ // Each script name maps to a path inside the provider directory. We load
1237
+ // the file and merge its export(s) into resolved.scripts. Lets a
1238
+ // provider override a single primitive (e.g. just detectStatus) while
1239
+ // letting the SDK synthesize the rest from the tui block.
1240
+ const providerDir = this.findProviderDirInternal(base.type);
1241
+ if (providerDir) {
1242
+ for (const [scriptName, override] of Object.entries(base.overrides as Record<string, any>)) {
1243
+ if (!override || typeof override.path !== 'string') continue;
1244
+ const fullPath = path.join(providerDir, override.path);
1245
+ if (!fs.existsSync(fullPath)) {
1246
+ this.log(` [overrides] ${base.type}: ${scriptName} path not found: ${fullPath}`);
1247
+ continue;
1248
+ }
1249
+ try {
1250
+ // Override scripts go through the same whitelist gate as the
1251
+ // main scripts dir. Use the provider parent root so a v1
1252
+ // override can still require ../_shared helpers.
1253
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1254
+ delete require.cache[require.resolve(fullPath)];
1255
+ const fn = require(fullPath);
1256
+ const target = typeof fn === 'function' ? fn : (fn && fn[scriptName]);
1257
+ if (typeof target === 'function') {
1258
+ resolved.scripts = { ...resolved.scripts, [scriptName]: target } as any;
1259
+ this.log(` [overrides] ${base.type}: ${scriptName} loaded from ${override.path}`);
1260
+ } else {
1261
+ this.log(` [overrides] ${base.type}: ${scriptName} export missing in ${override.path}`);
1262
+ }
1263
+ } catch (e: any) {
1264
+ this.log(` [overrides] ${base.type}: ${scriptName} require failed: ${e?.message || e}`);
1265
+ }
1266
+ }
1267
+ }
999
1268
  }
1000
1269
 
1001
1270
  if ((resolved.category === 'cli' || resolved.category === 'acp') && resolved.spawn?.command) {
@@ -1006,6 +1275,135 @@ export class ProviderLoader {
1006
1275
  };
1007
1276
  }
1008
1277
 
1278
+ // (spec migration) Late-binding spec.json native-history hook. Runs
1279
+ // *after* every script-loading path (compatibility / defaultScriptDir /
1280
+ // overrides) so it deterministically wins over a legacy v1 scripts.js
1281
+ // export. Three modes, picked by spec.json's native_history block:
1282
+ // 1. source — declarative jsonl/sqlite executor (new-provider path,
1283
+ // no daemon change needed for new on-disk formats)
1284
+ // 2. override_path — provider-supplied reader file (escape hatch for
1285
+ // exotic formats); module default-exports a reader fn
1286
+ // 3. reader — built-in reader id (claude-cli / codex-cli /
1287
+ // antigravity-cli / hermes-cli), kept for backwards
1288
+ // compatibility with the four shipped providers
1289
+ if (providerDir) {
1290
+ try {
1291
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1292
+ const fs = require('node:fs');
1293
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1294
+ const path = require('node:path');
1295
+ // Pick the right spec file for the detected CLI version. Resolution
1296
+ // order:
1297
+ // 1. compatibility[i].spec where ideVersion matches currentVersion
1298
+ // (lets a provider ship specs/2.0.json, specs/2.1.json, etc.
1299
+ // alongside the matching scriptDir)
1300
+ // 2. specs/default.json — explicit fallback
1301
+ // 3. spec.json — legacy single-spec layout
1302
+ // Missing files fall through silently to the next candidate.
1303
+ const candidates: string[] = [];
1304
+ if (Array.isArray((base as any).compatibility)) {
1305
+ for (const entry of (base as any).compatibility) {
1306
+ if (typeof entry?.spec !== 'string') continue;
1307
+ // If currentVersion is unknown (cli-manager hasn't probed yet)
1308
+ // we still let compatibility entries that don't pin a version
1309
+ // through, plus any entry whose pin matches.
1310
+ const matches = !entry.ideVersion
1311
+ || (currentVersion && this.matchesVersion(currentVersion, entry.ideVersion))
1312
+ || !currentVersion;
1313
+ if (matches) candidates.push(path.join(providerDir, entry.spec));
1314
+ }
1315
+ }
1316
+ candidates.push(path.join(providerDir, 'specs', 'default.json'));
1317
+ candidates.push(path.join(providerDir, 'spec.json'));
1318
+ const specPath = candidates.find((p: string) => fs.existsSync(p));
1319
+ if (specPath) {
1320
+ // Hand the resolved spec path off to route.ts via a hidden field
1321
+ // so the routing layer doesn't have to repeat the candidate walk.
1322
+ (resolved as any)._resolvedSpecPath = specPath;
1323
+ // Extract control_bar + native_history directly from the JSON header.
1324
+ let specControls: any[] | undefined;
1325
+ let nh: any | undefined;
1326
+ try {
1327
+ const rawSpec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
1328
+ specControls = rawSpec.control_bar;
1329
+ nh = rawSpec.native_history;
1330
+ } catch { /* unreadable spec — leave controls/native unavailable */ }
1331
+ // Stub each control_bar entry as a provider.scripts.<id>. The
1332
+ // upstream invoke_provider_script gate checks that the script
1333
+ // name exists on provider.scripts before calling adapter.invokeScript;
1334
+ // for spec providers the *actual* dispatch happens inside
1335
+ // SpecCliAdapter.invokeScript which maps the name to control_bar.
1336
+ // The stub is just a presence marker so the gate doesn't reject.
1337
+ if (specControls && specControls.length > 0) {
1338
+ resolved.scripts = { ...(resolved.scripts || {}) };
1339
+ for (const ctl of specControls) {
1340
+ if (!(resolved.scripts as any)[ctl.id]) {
1341
+ (resolved.scripts as any)[ctl.id] = (..._args: unknown[]) => ({
1342
+ __spec_control: true,
1343
+ controlId: ctl.id,
1344
+ actionType: ctl.action.type,
1345
+ });
1346
+ }
1347
+ }
1348
+ // Bridge the spec control_bar into the web-facing controls schema so
1349
+ // the dashboard chat bar actually renders Model/Mode pickers. Only
1350
+ // synthesize when the provider hasn't already declared its own
1351
+ // `controls` in provider.v1.json (e.g. hermes-cli) — an explicit
1352
+ // declaration wins and must not be clobbered.
1353
+ const hasDeclaredControls = Array.isArray((resolved as any).controls)
1354
+ && (resolved as any).controls.length > 0;
1355
+ if (!hasDeclaredControls) {
1356
+ const synthesized = synthesizeControlsFromControlBar(specControls);
1357
+ if (synthesized.length > 0) {
1358
+ resolved.controls = synthesized;
1359
+ }
1360
+ }
1361
+ }
1362
+ if (nh) {
1363
+ let reader: ((input: any) => any) | null = null;
1364
+ let format = 'spec';
1365
+
1366
+ if (nh.source) {
1367
+ format = `spec-${nh.source.kind}`;
1368
+ reader = (input: any) => executeNativeHistory(nh, input);
1369
+ } else if (nh.override_path) {
1370
+ const overrideFile = path.resolve(providerDir, nh.override_path);
1371
+ if (fs.existsSync(overrideFile)) {
1372
+ try {
1373
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1374
+ delete require.cache[require.resolve(overrideFile)];
1375
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1376
+ const mod = require(overrideFile);
1377
+ const fn = typeof mod === 'function' ? mod : (mod && typeof mod.default === 'function' ? mod.default : null);
1378
+ if (fn) {
1379
+ format = 'spec-override';
1380
+ reader = (input: any) => fn(input);
1381
+ }
1382
+ } catch { /* fall through — leave native unavailable */ }
1383
+ }
1384
+ } else if (nh.reader) {
1385
+ const dispatch = createNativeHistoryDispatcher(nh.reader as ReaderId);
1386
+ format = nh.reader;
1387
+ reader = (input: any) => dispatch(input);
1388
+ }
1389
+
1390
+ if (reader) {
1391
+ resolved.scripts = { ...(resolved.scripts || {}) };
1392
+ (resolved.scripts as any).readNativeHistory = reader;
1393
+ (resolved as any).nativeHistory = {
1394
+ format,
1395
+ watchPath: undefined,
1396
+ scripts: { readSession: 'readNativeHistory' },
1397
+ mode: 'native-source',
1398
+ };
1399
+ }
1400
+ }
1401
+ }
1402
+ } catch {
1403
+ // Best-effort — spec wiring failure must not break legacy providers.
1404
+ }
1405
+ }
1406
+
1009
1407
  return resolved;
1010
1408
  }
1011
1409
 
@@ -1016,16 +1414,27 @@ export class ProviderLoader {
1016
1414
  private loadScriptsFromDir(type: string, scriptDir: string): Partial<ProviderScripts> | null {
1017
1415
  const providerDir = this.findProviderDirInternal(type);
1018
1416
  if (!providerDir) {
1019
- this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
1417
+ // No provider dir for this type a spec-only provider with no
1418
+ // legacy scripts/v1 layout is a normal configuration, not a
1419
+ // problem to surface at INFO. resolve() calls this on every
1420
+ // request; INFO spam every 200ms drowns out the real signal.
1421
+ this.debugLog(`[loadScriptsFromDir] ${type}: providerDir not found`);
1020
1422
  return null;
1021
1423
  }
1022
1424
 
1023
1425
  const dir = path.join(providerDir, scriptDir);
1024
1426
  if (!fs.existsSync(dir)) {
1025
- this.log(` [loadScriptsFromDir] ${type}: dir not found: ${dir}`);
1427
+ this.debugLog(`[loadScriptsFromDir] ${type}: dir not found: ${dir}`);
1026
1428
  return null;
1027
1429
  }
1028
1430
 
1431
+ // Register the provider's *parent root* (e.g. .../adhdev-providers/) so
1432
+ // the require whitelist gates every script + every _shared helper this
1433
+ // provider may reach. Picking the grandparent (one above the category
1434
+ // dir `cli/`) lets sibling helpers in `_shared` resolve while still
1435
+ // blocking `../../etc/...` escapes. Idempotent.
1436
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1437
+
1029
1438
  // Return cached scripts if available (cleared on reload/watch)
1030
1439
  const cached = this.scriptsCache.get(dir);
1031
1440
  if (cached) return cached;
@@ -1036,7 +1445,7 @@ export class ProviderLoader {
1036
1445
  try {
1037
1446
  delete require.cache[require.resolve(scriptsJs)];
1038
1447
  const loaded = require(scriptsJs);
1039
- this.log(` [loadScriptsFromDir] ${type}: loaded scripts.js from ${dir} (${Object.keys(loaded).length} exports)`);
1448
+ this.debugLog(`[loadScriptsFromDir] ${type}: loaded scripts.js from ${dir} (${Object.keys(loaded).length} exports)`);
1040
1449
  this.scriptsCache.set(dir, loaded);
1041
1450
  return loaded;
1042
1451
  } catch (e) {
@@ -1067,13 +1476,17 @@ export class ProviderLoader {
1067
1476
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
1068
1477
  });
1069
1478
 
1479
+ let reloadTimer: ReturnType<typeof setTimeout> | null = null;
1070
1480
  const handleChange = (filePath: string) => {
1071
1481
  if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
1072
1482
  return;
1073
1483
  }
1074
1484
  if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
1075
- this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1076
- this.reload();
1485
+ if (reloadTimer) clearTimeout(reloadTimer);
1486
+ reloadTimer = setTimeout(() => {
1487
+ this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1488
+ this.reload();
1489
+ }, 300);
1077
1490
  }
1078
1491
  };
1079
1492
 
@@ -1124,13 +1537,115 @@ export class ProviderLoader {
1124
1537
  *
1125
1538
  * @returns Whether an update occurred
1126
1539
  */
1540
+ /**
1541
+ * Sync providers from the ADHDev registry (registry.adhf.dev).
1542
+ *
1543
+ * Downloads only providers whose server checksum differs from the locally
1544
+ * cached checksum. Falls back gracefully to the GitHub tarball path if the
1545
+ * registry is unreachable or returns an unexpected response.
1546
+ *
1547
+ * Returns `{ updated: true }` when at least one provider file changed on disk,
1548
+ * `{ updated: false }` when everything is already current, or
1549
+ * `{ updated: false, error }` when the registry couldn't be reached and we
1550
+ * should proceed to the GitHub tarball fallback.
1551
+ */
1552
+ async fetchFromRegistry(): Promise<{ updated: boolean; error?: string }> {
1553
+ if (this.disableUpstream) {
1554
+ this.log('Registry sync skipped (sourceMode=no-upstream)');
1555
+ return { updated: false };
1556
+ }
1557
+ this.log(`Registry sync starting (${ProviderLoader.REGISTRY_BASE_URL})...`);
1558
+
1559
+ const https = require('https') as typeof import('https');
1560
+ const regMetaPath = path.join(this.upstreamDir, ProviderLoader.REGISTRY_META_FILE);
1561
+
1562
+ // Load cached checksums
1563
+ let cachedChecksums: Record<string, string> = {};
1564
+ try {
1565
+ if (fs.existsSync(regMetaPath)) {
1566
+ cachedChecksums = JSON.parse(fs.readFileSync(regMetaPath, 'utf-8')).checksums ?? {};
1567
+ }
1568
+ } catch { }
1569
+
1570
+ try {
1571
+ // 1. Fetch provider list
1572
+ const listUrl = `${ProviderLoader.REGISTRY_BASE_URL}/providers`;
1573
+ const listBody = await new Promise<string>((resolve, reject) => {
1574
+ const req = https.get(listUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
1575
+ if (res.statusCode !== 200) { reject(new Error(`registry list HTTP ${res.statusCode}`)); return; }
1576
+ const chunks: Buffer[] = [];
1577
+ res.on('data', (c: Buffer) => chunks.push(c));
1578
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
1579
+ });
1580
+ req.on('error', reject);
1581
+ req.on('timeout', () => { req.destroy(); reject(new Error('registry list timeout')); });
1582
+ });
1583
+
1584
+ const list = JSON.parse(listBody) as { providers: Array<{ type: string; category: string; checksum: string; version: string }> };
1585
+ if (!Array.isArray(list.providers)) throw new Error('unexpected registry response shape');
1586
+
1587
+ let updatedCount = 0;
1588
+
1589
+ for (const entry of list.providers) {
1590
+ const { type, category, checksum, version } = entry;
1591
+ const cacheKey = `${category}/${type}`;
1592
+ if (cachedChecksums[cacheKey] === checksum) continue; // already current
1593
+
1594
+ // Download this provider's manifest
1595
+ const dlUrl = `${ProviderLoader.REGISTRY_BASE_URL}/providers/${type}/${version}/download`;
1596
+ const manifestBody = await new Promise<string>((resolve, reject) => {
1597
+ const req = https.get(dlUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 30000 }, (res) => {
1598
+ if (res.statusCode !== 200) { reject(new Error(`registry download HTTP ${res.statusCode} for ${type}@${version}`)); return; }
1599
+ const chunks: Buffer[] = [];
1600
+ res.on('data', (c: Buffer) => chunks.push(c));
1601
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
1602
+ });
1603
+ req.on('error', reject);
1604
+ req.on('timeout', () => { req.destroy(); reject(new Error(`download timeout for ${type}`)); });
1605
+ });
1606
+
1607
+ // Verify checksum
1608
+ const actualChecksum = sha256Hex(manifestBody);
1609
+ if (actualChecksum !== checksum) {
1610
+ this.log(`⚠ Registry checksum mismatch for ${type}@${version} — skipping`);
1611
+ continue;
1612
+ }
1613
+
1614
+ // Write to upstream dir
1615
+ const providerDir = path.join(this.upstreamDir, category, type);
1616
+ fs.mkdirSync(providerDir, { recursive: true });
1617
+ fs.writeFileSync(path.join(providerDir, 'provider.json'), manifestBody, 'utf-8');
1618
+
1619
+ cachedChecksums[cacheKey] = checksum;
1620
+ updatedCount++;
1621
+ this.log(`✓ Registry updated: ${category}/${type}@${version}`);
1622
+ }
1623
+
1624
+ // Persist updated checksums
1625
+ fs.mkdirSync(this.upstreamDir, { recursive: true });
1626
+ fs.writeFileSync(regMetaPath, JSON.stringify({
1627
+ checksums: cachedChecksums,
1628
+ syncedAt: new Date().toISOString(),
1629
+ providerCount: list.providers.length,
1630
+ }, null, 2));
1631
+
1632
+ this.log(`Registry sync complete: ${list.providers.length} providers, ${updatedCount} updated`);
1633
+ return { updated: updatedCount > 0 };
1634
+ } catch (e: any) {
1635
+ this.log(`⚠ Registry sync failed (falling back to GitHub tarball): ${e?.message}`);
1636
+ return { updated: false, error: e?.message };
1637
+ }
1638
+ }
1639
+
1127
1640
  async fetchLatest(): Promise<{ updated: boolean; error?: string }> {
1128
1641
  if (this.disableUpstream) {
1129
1642
  this.log('Upstream fetch skipped (sourceMode=no-upstream)');
1130
1643
  return { updated: false };
1131
1644
  }
1132
1645
  const https = require('https') as typeof import('https');
1133
- const { execSync } = require('child_process') as typeof import('child_process');
1646
+ const { exec } = require('child_process') as typeof import('child_process');
1647
+ const { promisify } = require('util');
1648
+ const execAsync = promisify(exec);
1134
1649
 
1135
1650
  const metaPath = path.join(this.upstreamDir, ProviderLoader.META_FILE);
1136
1651
  let prevEtag = '';
@@ -1207,7 +1722,7 @@ export class ProviderLoader {
1207
1722
 
1208
1723
  // Extract
1209
1724
  fs.mkdirSync(tmpExtract, { recursive: true });
1210
- execSync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1725
+ await execAsync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1211
1726
 
1212
1727
  // Tarball internal structure: adhdev-providers-main/ide/... → strip 1 level
1213
1728
  const extracted = fs.readdirSync(tmpExtract);
@@ -1315,15 +1830,17 @@ export class ProviderLoader {
1315
1830
  } catch { }
1316
1831
  }
1317
1832
 
1318
- /** Count provider files (provider.js or provider.json) */
1833
+ /** Count provider files (provider.v1.json or provider.json — at most one per dir). */
1319
1834
  private countProviders(dir: string): number {
1320
1835
  if (!fs.existsSync(dir)) return 0;
1321
1836
  let count = 0;
1322
1837
  const scan = (d: string) => {
1323
1838
  try {
1324
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
1839
+ const entries = fs.readdirSync(d, { withFileTypes: true });
1840
+ const hasManifest = entries.some(e => e.name === 'provider.v1.json' || e.name === 'provider.json');
1841
+ if (hasManifest) count++;
1842
+ for (const entry of entries) {
1325
1843
  if (entry.isDirectory()) scan(path.join(d, entry.name));
1326
- else if (entry.name === 'provider.json') count++;
1327
1844
  }
1328
1845
  } catch { }
1329
1846
  };
@@ -1372,11 +1889,15 @@ export class ProviderLoader {
1372
1889
  return args ? args.map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(' ') : '';
1373
1890
  }
1374
1891
  const schemaDef = this.getSettingsSchema(providerType)[key];
1375
- const defaultVal = schemaDef
1376
- ? (key === 'autoApprove' && schemaDef.type === 'boolean'
1377
- ? true
1378
- : schemaDef.default)
1379
- : undefined;
1892
+ // (fix) Previously this hard-coded `autoApprove` boolean default to `true`,
1893
+ // overriding whatever schemaDef.default the provider.json declared. That
1894
+ // surfaced as soon as a provider added an `autoApprove` schema entry with
1895
+ // default=false: the user had never opted in but the daemon treated the
1896
+ // session as auto-approve, which then triggered recordAutoApproval every
1897
+ // time the CLI showed an approval modal — producing a flood of system
1898
+ // "Auto-approved: ..." messages and keeping the session pinned to
1899
+ // generating while modals cycled in and out. Trust the schemaDef.default.
1900
+ const defaultVal = schemaDef ? schemaDef.default : undefined;
1380
1901
 
1381
1902
  const config = this.readConfig();
1382
1903
  const userVal = config?.providerSettings?.[providerType]?.[key];
@@ -1490,10 +2011,17 @@ export class ProviderLoader {
1490
2011
  ...this.getSyntheticSettings(type, provider),
1491
2012
  ...(provider.settings || {}),
1492
2013
  };
2014
+ // (fix) Previously this clause forced `autoApprove.default = true` for any
2015
+ // boolean autoApprove schema, even when the provider.json explicitly set
2016
+ // `default: false`. Combined with the synthetic-settings fallback at
2017
+ // getSyntheticSettings (which also defaults autoApprove to true when the
2018
+ // provider doesn't supply one), that meant CLI providers silently turned on
2019
+ // auto-approval, producing a flood of "Auto-approved: ..." system messages
2020
+ // every time an approval modal appeared and pinning the session to
2021
+ // generating while modals cycled. Trust the provider's declared default.
1493
2022
  if (result.autoApprove?.type === 'boolean') {
1494
2023
  result.autoApprove = {
1495
2024
  ...result.autoApprove,
1496
- default: true,
1497
2025
  public: true,
1498
2026
  label: result.autoApprove.label || 'Auto Approve',
1499
2027
  description: result.autoApprove.description || 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1518,7 +2046,10 @@ export class ProviderLoader {
1518
2046
  if (!provider.settings?.autoApprove) {
1519
2047
  result.autoApprove = {
1520
2048
  type: 'boolean',
1521
- default: true,
2049
+ // (fix) Safe default is *off*. Auto-approving every modal without the
2050
+ // user opting in produced silent-bash-execution surprises and the
2051
+ // "Auto-approved: ..." system-message flood seen on AGY/Codex.
2052
+ default: false,
1522
2053
  public: true,
1523
2054
  label: 'Auto Approve',
1524
2055
  description: 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1581,23 +2112,32 @@ export class ProviderLoader {
1581
2112
  const cat = provider.category;
1582
2113
 
1583
2114
  const searchRoots = this.getProviderRoots();
2115
+ const hasManifest = (dir: string) =>
2116
+ fs.existsSync(path.join(dir, 'provider.v1.json')) || fs.existsSync(path.join(dir, 'provider.json'));
2117
+ const readManifestType = (dir: string): string | null => {
2118
+ for (const file of ['provider.v1.json', 'provider.json']) {
2119
+ const p = path.join(dir, file);
2120
+ if (!fs.existsSync(p)) continue;
2121
+ try {
2122
+ const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
2123
+ if (typeof data?.type === 'string') return data.type;
2124
+ } catch { /* skip */ }
2125
+ }
2126
+ return null;
2127
+ };
1584
2128
  for (const root of searchRoots) {
1585
2129
  if (!fs.existsSync(root)) continue;
1586
2130
  const candidate = this.getProviderDir(root, cat, type);
1587
- if (fs.existsSync(path.join(candidate, 'provider.json'))) return candidate;
2131
+ if (hasManifest(candidate)) return candidate;
1588
2132
  // Scan category dir for type match
1589
2133
  const catDir = path.join(root, cat);
1590
2134
  if (fs.existsSync(catDir)) {
1591
2135
  try {
1592
2136
  for (const entry of fs.readdirSync(catDir, { withFileTypes: true })) {
1593
2137
  if (!entry.isDirectory()) continue;
1594
- const jsonPath = path.join(catDir, entry.name, 'provider.json');
1595
- if (fs.existsSync(jsonPath)) {
1596
- try {
1597
- const data = JSON.parse(fs.readFileSync(jsonPath, 'utf-8'));
1598
- if (data.type === type) return path.join(catDir, entry.name);
1599
- } catch { /* skip */ }
1600
- }
2138
+ const entryDir = path.join(catDir, entry.name);
2139
+ const manifestType = readManifestType(entryDir);
2140
+ if (manifestType === type) return entryDir;
1601
2141
  }
1602
2142
  } catch { /* skip */ }
1603
2143
  }
@@ -1690,17 +2230,44 @@ export class ProviderLoader {
1690
2230
  return;
1691
2231
  }
1692
2232
 
1693
- // Check if this directory has provider.json
2233
+ // v1-first manifest selection. provider.v1.json (the SDK-shape
2234
+ // manifest with `overrides`, `tui`, `source`, `canonicalHistory`)
2235
+ // wins over provider.json (legacy). Without this branch the v1
2236
+ // file is silently ignored — that's how the codex-cli `overrides`
2237
+ // path and the tui-block builders went un-honored for the first
2238
+ // pass of SDK rollout.
2239
+ const hasV1 = entries.some(e => e.name === 'provider.v1.json');
1694
2240
  const hasJson = entries.some(e => e.name === 'provider.json');
1695
2241
 
1696
- if (hasJson) {
1697
- const jsonPath = path.join(d, 'provider.json');
2242
+ if (hasV1 || hasJson) {
2243
+ const manifestFile = hasV1 ? 'provider.v1.json' : 'provider.json';
2244
+ const jsonPath = path.join(d, manifestFile);
1698
2245
  try {
1699
2246
  const raw = fs.readFileSync(jsonPath, 'utf-8');
1700
2247
  const mod = JSON.parse(raw) as Omit<ProviderModule, 'extensionIdPattern'> & {
1701
2248
  extensionIdPattern?: RegExp | string;
1702
2249
  };
1703
2250
 
2251
+ // Validate v1 manifests against the SDK schema. Failures are
2252
+ // surfaced as a single warning line with all issues attached
2253
+ // so manifest authors don't need to guess which field is wrong.
2254
+ // Loading still proceeds — bricking the daemon on a single
2255
+ // bad field would be worse than running with a known warning.
2256
+ if (hasV1 && mod?.category === 'cli') {
2257
+ try {
2258
+ const { validateCliProviderManifest, formatManifestValidationIssues } =
2259
+ require('./sdk/v1/validators/manifest.js') as typeof import('./sdk/v1/validators/manifest.js');
2260
+ const validation = validateCliProviderManifest(mod);
2261
+ if (!validation.ok) {
2262
+ this.log(`⚠ ${jsonPath}: schema validation failed:\n${formatManifestValidationIssues(validation.issues)}`);
2263
+ }
2264
+ } catch (e: any) {
2265
+ // Validator load failed — log once and continue so a
2266
+ // broken validator can't take down provider loading.
2267
+ this.log(`⚠ ${jsonPath}: validator unavailable: ${e?.message || e}`);
2268
+ }
2269
+ }
2270
+
1704
2271
  // Restore RegExp fields from JSON (extensionIdPattern)
1705
2272
  if (typeof mod.extensionIdPattern === 'string') {
1706
2273
  const flags = mod.extensionIdPattern_flags || '';
@@ -1712,6 +2279,22 @@ export class ProviderLoader {
1712
2279
  ...(extensionIdPattern instanceof RegExp ? { extensionIdPattern } : {}),
1713
2280
  };
1714
2281
 
2282
+ // v1 manifests use `nativeHistory` as the canonical field name.
2283
+ // Legacy v0 manifests use `canonicalHistory`. The daemon's
2284
+ // runtime + downstream code reads `provider.nativeHistory`, so
2285
+ // for legacy manifests we copy `canonicalHistory` into
2286
+ // `nativeHistory` here. We also keep `canonicalHistory`
2287
+ // populated in both directions (deprecated alias) so any
2288
+ // external consumers still reading the old name keep working
2289
+ // during the one-release deprecation window.
2290
+ const nh = (normalizedProvider as any).nativeHistory;
2291
+ const ch = (normalizedProvider as any).canonicalHistory;
2292
+ if (nh && !ch) {
2293
+ (normalizedProvider as any).canonicalHistory = nh;
2294
+ } else if (ch && !nh) {
2295
+ (normalizedProvider as any).nativeHistory = ch;
2296
+ }
2297
+
1715
2298
  const validation = validateProviderDefinition(normalizedProvider);
1716
2299
  for (const warning of validation.warnings) {
1717
2300
  this.log(`⚠ ${jsonPath}: ${warning}`);
@@ -1725,6 +2308,10 @@ export class ProviderLoader {
1725
2308
  const scriptsPath = path.join(d, 'scripts.js');
1726
2309
  if (!hasCompatibility && fs.existsSync(scriptsPath)) {
1727
2310
  try {
2311
+ // Gate the IDE/extension scripts.js (legacy single-file
2312
+ // format) under the same whitelist. `d` here is the
2313
+ // provider dir; its grandparent contains _shared.
2314
+ registerProviderScriptRootSafely(path.dirname(path.dirname(d)));
1728
2315
  delete require.cache[require.resolve(scriptsPath)];
1729
2316
  const scripts = require(scriptsPath) as Partial<ProviderScripts>;
1730
2317
  normalizedProvider.scripts = scripts;
@@ -1733,14 +2320,40 @@ export class ProviderLoader {
1733
2320
  }
1734
2321
  }
1735
2322
 
2323
+ // Classify trust based on which on-disk layer this manifest
2324
+ // came from + whether it ships JavaScript hooks. The dashboard
2325
+ // uses this to render trust badges; non-spec external manifests
2326
+ // need an explicit user confirm before activation.
2327
+ const externalDirAbs = path.join(os.homedir(), '.adhdev', 'external');
2328
+ const layer: 'user' | 'upstream' | 'external' = d.startsWith(externalDirAbs)
2329
+ ? 'external'
2330
+ : (d.startsWith(this.userDir) && !d.includes('.upstream') ? 'user' : 'upstream');
2331
+ try {
2332
+ const { inspectManifestShape, classifyTrust } =
2333
+ require('./provider-trust.js') as typeof import('./provider-trust.js');
2334
+ const shape = inspectManifestShape(mod as Record<string, unknown>);
2335
+ const trust = classifyTrust(layer, shape);
2336
+ (normalizedProvider as any)._sourceLayer = layer;
2337
+ (normalizedProvider as any)._sourceTrust = trust;
2338
+ (normalizedProvider as any)._manifestShape = shape;
2339
+ // For external-namespaced layouts (external/<source>/…) record
2340
+ // which source the manifest came from so dashboards can name
2341
+ // it in the trust badge.
2342
+ if (layer === 'external') {
2343
+ const rel = path.relative(externalDirAbs, d);
2344
+ const firstSeg = rel.split(path.sep)[0];
2345
+ if (firstSeg && firstSeg !== '..') (normalizedProvider as any)._sourceName = firstSeg;
2346
+ }
2347
+ } catch { /* best-effort — trust is enrichment, not gating */ }
2348
+
1736
2349
  const existed = this.providers.has(normalizedProvider.type);
1737
2350
  this.providers.set(normalizedProvider.type, normalizedProvider);
1738
2351
  count++;
1739
- // Identify source tier for debugging
1740
- const source = d.startsWith(this.userDir) && !d.includes('.upstream')
1741
- ? 'user' : 'upstream';
2352
+ const source = (normalizedProvider as any)._sourceLayer ?? 'upstream';
1742
2353
  const overrideWarning = existed && source === 'user' ? ' ⚠ OVERRIDES upstream' : '';
1743
- this.log(` ${existed ? '🔄' : '✅'} ${normalizedProvider.type} (${normalizedProvider.category}) ${normalizedProvider.name} [${source}]${overrideWarning}`);
2354
+ const sourceName = (normalizedProvider as any)._sourceName;
2355
+ const sourceLabel = sourceName ? `${source}/${sourceName}` : source;
2356
+ this.log(` ${existed ? '🔄' : '✅'} ${normalizedProvider.type} (${normalizedProvider.category}) — ${normalizedProvider.name} [${sourceLabel}]${overrideWarning}`);
1744
2357
  }
1745
2358
  } catch (e) {
1746
2359
  this.log(`⚠ Failed to load ${jsonPath}: ${(e as Error).message}`);
@@ -1752,6 +2365,11 @@ export class ProviderLoader {
1752
2365
  for (const entry of entries) {
1753
2366
  if (!entry.isDirectory()) continue;
1754
2367
  if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
2368
+ // `examples/` is a documentation / scaffold tree (e.g. stub-cli),
2369
+ // not a real provider source. SDK authors copy from here when
2370
+ // writing a new provider; daemon-core tests reference the
2371
+ // manifest by path. Keep it off the dashboard's provider list.
2372
+ if (d === dir && entry.name === 'examples') continue;
1755
2373
  if (excludeDirs && d === dir && excludeDirs.includes(entry.name)) continue;
1756
2374
  scan(path.join(d, entry.name));
1757
2375
  }