@adhdev/daemon-core 0.9.82-rc.46 → 0.9.82-rc.461

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 (440) 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 +25 -1
  27. package/dist/commands/handler.d.ts +125 -0
  28. package/dist/commands/high-family/index.d.ts +3 -0
  29. package/dist/commands/high-family/mesh-coordinator-launch.d.ts +2 -0
  30. package/dist/commands/high-family/mesh-events.d.ts +2 -0
  31. package/dist/commands/high-family/mesh-status.d.ts +2 -0
  32. package/dist/commands/high-family/types.d.ts +64 -0
  33. package/dist/commands/low-family/coordinator-prompt.d.ts +9 -0
  34. package/dist/commands/low-family/daemon-lifecycle.d.ts +2 -0
  35. package/dist/commands/low-family/diagnostics.d.ts +2 -0
  36. package/dist/commands/low-family/index.d.ts +3 -0
  37. package/dist/commands/low-family/mesh-ledger.d.ts +10 -0
  38. package/dist/commands/low-family/mesh-node-logs.d.ts +2 -0
  39. package/dist/commands/low-family/notification.d.ts +2 -0
  40. package/dist/commands/low-family/refine-config.d.ts +2 -0
  41. package/dist/commands/low-family/session-host.d.ts +2 -0
  42. package/dist/commands/low-family/spec-providerdev.d.ts +11 -0
  43. package/dist/commands/low-family/status-meta.d.ts +2 -0
  44. package/dist/commands/low-family/types.d.ts +33 -0
  45. package/dist/commands/med-family/cli-agent.d.ts +2 -0
  46. package/dist/commands/med-family/fast-forward.d.ts +2 -0
  47. package/dist/commands/med-family/ide.d.ts +10 -0
  48. package/dist/commands/med-family/index.d.ts +3 -0
  49. package/dist/commands/med-family/mesh-crud.d.ts +48 -0
  50. package/dist/commands/med-family/mesh-host-pairing.d.ts +2 -0
  51. package/dist/commands/med-family/mesh-queue.d.ts +2 -0
  52. package/dist/commands/med-family/mesh-restart.d.ts +2 -0
  53. package/dist/commands/med-family/types.d.ts +152 -0
  54. package/dist/commands/mesh-coordinator.d.ts +85 -1
  55. package/dist/commands/router-refine.d.ts +137 -0
  56. package/dist/commands/router-worktree-cleanup.d.ts +133 -0
  57. package/dist/commands/router.d.ts +241 -12
  58. package/dist/commands/upgrade-helper.d.ts +41 -1
  59. package/dist/config/chat-history.d.ts +9 -0
  60. package/dist/config/config.d.ts +19 -0
  61. package/dist/config/mesh-config.d.ts +136 -1
  62. package/dist/config/mesh-json-config.d.ts +199 -0
  63. package/dist/config/registry-resolver.d.ts +54 -0
  64. package/dist/config/repo-settings.d.ts +77 -0
  65. package/dist/daemon/dev-server.d.ts +0 -2
  66. package/dist/detection/cli-detector.d.ts +17 -0
  67. package/dist/detection/ide-detector.d.ts +13 -0
  68. package/dist/detection/win32-ide-version.d.ts +37 -0
  69. package/dist/git/change-impact-config.d.ts +159 -0
  70. package/dist/git/git-commands.d.ts +26 -2
  71. package/dist/git/git-diff.d.ts +6 -0
  72. package/dist/git/git-executor.d.ts +11 -0
  73. package/dist/git/git-status.d.ts +57 -0
  74. package/dist/git/git-types.d.ts +2 -50
  75. package/dist/git/git-worktree.d.ts +71 -1
  76. package/dist/git/index.d.ts +3 -1
  77. package/dist/index.d.ts +67 -15
  78. package/dist/index.js +58583 -25541
  79. package/dist/index.js.map +1 -1
  80. package/dist/index.mjs +58207 -25332
  81. package/dist/index.mjs.map +1 -1
  82. package/dist/installer.d.ts +1 -4
  83. package/dist/ipc/local-ipc-server.d.ts +91 -0
  84. package/dist/launch.d.ts +1 -1
  85. package/dist/logging/async-batch-writer.d.ts +10 -0
  86. package/dist/logging/debug-config.d.ts +16 -0
  87. package/dist/logging/log-redactor.d.ts +24 -0
  88. package/dist/logging/log-tail-reader.d.ts +81 -0
  89. package/dist/logging/logger.d.ts +1 -1
  90. package/dist/mesh/contracts.d.ts +230 -0
  91. package/dist/mesh/coordinator-prompt.d.ts +76 -0
  92. package/dist/mesh/coordinator-registry.d.ts +59 -0
  93. package/dist/mesh/mesh-active-work.d.ts +174 -0
  94. package/dist/mesh/mesh-clone-grace.d.ts +8 -0
  95. package/dist/mesh/mesh-coordinator-config.d.ts +21 -0
  96. package/dist/mesh/mesh-delivery-policy.d.ts +131 -0
  97. package/dist/mesh/mesh-event-classify.d.ts +7 -0
  98. package/dist/mesh/mesh-event-forwarding.d.ts +41 -0
  99. package/dist/mesh/mesh-event-trace.d.ts +21 -0
  100. package/dist/mesh/mesh-events-coordinator.d.ts +4 -0
  101. package/dist/mesh/mesh-events-pending.d.ts +155 -0
  102. package/dist/mesh/mesh-events-stale.d.ts +61 -0
  103. package/dist/mesh/mesh-events-utils.d.ts +75 -0
  104. package/dist/mesh/mesh-events.d.ts +6 -49
  105. package/dist/mesh/mesh-fast-forward.d.ts +81 -0
  106. package/dist/mesh/mesh-host-ownership.d.ts +29 -0
  107. package/dist/mesh/mesh-init.d.ts +128 -0
  108. package/dist/mesh/mesh-ledger-reconciliation.d.ts +23 -4
  109. package/dist/mesh/mesh-ledger.d.ts +133 -1
  110. package/dist/mesh/mesh-magi-status.d.ts +63 -0
  111. package/dist/mesh/mesh-missions.d.ts +189 -0
  112. package/dist/mesh/mesh-node-identity.d.ts +310 -0
  113. package/dist/mesh/mesh-queue-assignment.d.ts +158 -0
  114. package/dist/mesh/mesh-reconcile-loop.d.ts +80 -0
  115. package/dist/mesh/mesh-refine-batch.d.ts +68 -0
  116. package/dist/mesh/mesh-refine-gates.d.ts +428 -0
  117. package/dist/mesh/mesh-refine-status.d.ts +62 -0
  118. package/dist/mesh/mesh-review-inbox.d.ts +90 -0
  119. package/dist/mesh/mesh-routing.d.ts +70 -0
  120. package/dist/mesh/mesh-runtime-store.d.ts +493 -0
  121. package/dist/mesh/mesh-scheduling-runtime.d.ts +78 -0
  122. package/dist/mesh/mesh-task-inflight.d.ts +46 -0
  123. package/dist/mesh/mesh-task-stats.d.ts +49 -0
  124. package/dist/mesh/mesh-unresolved-forward-outbox.d.ts +30 -0
  125. package/dist/mesh/mesh-warmup-deadline.d.ts +68 -0
  126. package/dist/mesh/mesh-work-queue.d.ts +318 -5
  127. package/dist/mesh/preview-freshness.d.ts +28 -0
  128. package/dist/mesh/refine-config.d.ts +216 -0
  129. package/dist/mesh/worktree-bootstrap-config.d.ts +219 -0
  130. package/dist/providers/acp-provider-instance.d.ts +5 -0
  131. package/dist/providers/approval-utils.d.ts +45 -0
  132. package/dist/providers/chat-message-normalization.d.ts +58 -1
  133. package/dist/providers/cli-provider-instance.d.ts +290 -3
  134. package/dist/providers/contracts.d.ts +149 -6
  135. package/dist/providers/external-sources.d.ts +71 -0
  136. package/dist/providers/manual-attendance.d.ts +79 -0
  137. package/dist/providers/native-history/antigravity-claim-registry.d.ts +28 -0
  138. package/dist/providers/native-history/antigravity-cli-transcript.d.ts +137 -0
  139. package/dist/providers/native-history/claude-cli-transcript.d.ts +70 -0
  140. package/dist/providers/native-history/codex-cli-transcript.d.ts +73 -0
  141. package/dist/providers/native-history/constants.d.ts +12 -0
  142. package/dist/providers/native-history/dispatcher.d.ts +31 -0
  143. package/dist/providers/native-history/hermes-cli-transcript.d.ts +30 -0
  144. package/dist/providers/native-history/index.d.ts +13 -0
  145. package/dist/providers/provider-instance-manager.d.ts +29 -0
  146. package/dist/providers/provider-instance.d.ts +30 -1
  147. package/dist/providers/provider-loader.d.ts +42 -5
  148. package/dist/providers/provider-trust.d.ts +31 -0
  149. package/dist/providers/read-chat-contract.d.ts +29 -0
  150. package/dist/providers/sdk/v1/builders/acp/detect-status.d.ts +68 -0
  151. package/dist/providers/sdk/v1/builders/cli/detect-status.d.ts +86 -0
  152. package/dist/providers/sdk/v1/builders/cli/parse-approval-squash.d.ts +59 -0
  153. package/dist/providers/sdk/v1/builders/cli/parse-approval.d.ts +65 -0
  154. package/dist/providers/sdk/v1/builders/cli/parse-session.d.ts +91 -0
  155. package/dist/providers/sdk/v1/builders/cli/visible-region.d.ts +42 -0
  156. package/dist/providers/sdk/v1/fixture-tooling/format.d.ts +126 -0
  157. package/dist/providers/sdk/v1/fixture-tooling/index.d.ts +8 -0
  158. package/dist/providers/sdk/v1/fixture-tooling/replay.d.ts +38 -0
  159. package/dist/providers/sdk/v1/index.d.ts +30 -0
  160. package/dist/providers/sdk/v1/sandbox/README-design.d.ts +193 -0
  161. package/dist/providers/sdk/v1/sandbox/require-whitelist.d.ts +74 -0
  162. package/dist/providers/sdk/v1/sandbox/script-runner.d.ts +98 -0
  163. package/dist/providers/sdk/v1/types/cli/index.d.ts +277 -0
  164. package/dist/providers/sdk/v1/types/common/index.d.ts +203 -0
  165. package/dist/providers/sdk/v1/validators/index.d.ts +5 -0
  166. package/dist/providers/sdk/v1/validators/manifest.d.ts +40 -0
  167. package/dist/providers/sdk/v1/validators/taint.d.ts +52 -0
  168. package/dist/providers/spec/adapter.d.ts +98 -0
  169. package/dist/providers/spec/cli-adapter.d.ts +223 -0
  170. package/dist/providers/spec/evaluator.d.ts +45 -0
  171. package/dist/providers/spec/fsm-driver.d.ts +449 -0
  172. package/dist/providers/spec/fsm-evaluator.d.ts +93 -0
  173. package/dist/providers/spec/fsm-loader.d.ts +14 -0
  174. package/dist/providers/spec/fsm-types.d.ts +202 -0
  175. package/dist/providers/spec/native-history-executor.d.ts +41 -0
  176. package/dist/providers/spec/pre-launch-trust.d.ts +16 -0
  177. package/dist/providers/spec/route.d.ts +4 -0
  178. package/dist/providers/spec/types.d.ts +222 -0
  179. package/dist/providers/status-monitor.d.ts +7 -7
  180. package/dist/providers/transcript-v2.d.ts +176 -0
  181. package/dist/providers/types/interactive-prompt.d.ts +84 -0
  182. package/dist/providers/working-dir.d.ts +17 -0
  183. package/dist/repo-mesh-types.d.ts +598 -12
  184. package/dist/runtime-defaults.d.ts +2 -0
  185. package/dist/session-host/managed-host.d.ts +64 -0
  186. package/dist/session-host/runtime-surface.d.ts +10 -16
  187. package/dist/sessions/registry.d.ts +6 -0
  188. package/dist/shared-types-extra.d.ts +2 -4
  189. package/dist/shared-types.d.ts +86 -55
  190. package/dist/status/normalize.d.ts +1 -1
  191. package/dist/status/normalize.js +1 -0
  192. package/dist/status/normalize.js.map +1 -1
  193. package/dist/status/normalize.mjs +1 -0
  194. package/dist/status/normalize.mjs.map +1 -1
  195. package/dist/status/reporter.d.ts +2 -0
  196. package/dist/status/snapshot.d.ts +26 -0
  197. package/dist/system/hash.d.ts +8 -0
  198. package/dist/system/load-better-sqlite3.d.ts +21 -0
  199. package/dist/types.d.ts +5 -0
  200. package/package.json +11 -4
  201. package/src/agent-stream/poller.ts +2 -3
  202. package/src/agent-stream/provider-adapter.ts +1 -1
  203. package/src/boot/daemon-lifecycle.ts +80 -14
  204. package/src/boot/process-hardening.ts +89 -0
  205. package/src/build-info.ts +73 -0
  206. package/src/chat/source-machine.ts +534 -0
  207. package/src/chat/source-resolver.ts +0 -0
  208. package/src/chat/subscription-updates.ts +20 -1
  209. package/src/cli-adapter-types.d.ts +3 -1
  210. package/src/cli-adapter-types.ts +68 -2
  211. package/src/cli-adapters/cli-script-runner.ts +421 -0
  212. package/src/cli-adapters/cli-state-engine.ts +1246 -0
  213. package/src/cli-adapters/provider-cli-adapter.d.ts +1 -1
  214. package/src/cli-adapters/provider-cli-adapter.ts +914 -1144
  215. package/src/cli-adapters/provider-cli-parse.d.ts +2 -0
  216. package/src/cli-adapters/provider-cli-parse.ts +19 -0
  217. package/src/cli-adapters/provider-cli-runtime.ts +56 -7
  218. package/src/cli-adapters/provider-cli-shared.d.ts +4 -0
  219. package/src/cli-adapters/provider-cli-shared.ts +121 -11
  220. package/src/cli-adapters/pty-transport.ts +2 -1
  221. package/src/cli-adapters/pty-write-chunking.ts +106 -0
  222. package/src/cli-adapters/raw-terminal-io.ts +252 -0
  223. package/src/cli-adapters/resolve-executable.ts +204 -0
  224. package/src/cli-adapters/session-host-transport.ts +2 -1
  225. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.ts +63 -29
  226. package/src/cli-adapters/terminal-backends/types.ts +10 -3
  227. package/src/cli-adapters/terminal-screen.ts +16 -81
  228. package/src/commands/chat-commands-debug-bundle.ts +398 -0
  229. package/src/commands/chat-commands-read.ts +2488 -0
  230. package/src/commands/chat-commands-scope.ts +54 -0
  231. package/src/commands/chat-commands-shared.ts +114 -0
  232. package/src/commands/chat-commands-write.ts +891 -0
  233. package/src/commands/chat-commands.ts +19 -1841
  234. package/src/commands/cli-manager.ts +596 -27
  235. package/src/commands/handler.ts +845 -2
  236. package/src/commands/high-family/index.ts +28 -0
  237. package/src/commands/high-family/mesh-coordinator-launch.ts +680 -0
  238. package/src/commands/high-family/mesh-events.ts +89 -0
  239. package/src/commands/high-family/mesh-status.ts +832 -0
  240. package/src/commands/high-family/types.ts +80 -0
  241. package/src/commands/low-family/coordinator-prompt.ts +72 -0
  242. package/src/commands/low-family/daemon-lifecycle.ts +120 -0
  243. package/src/commands/low-family/diagnostics.ts +57 -0
  244. package/src/commands/low-family/index.ts +37 -0
  245. package/src/commands/low-family/mesh-ledger.ts +62 -0
  246. package/src/commands/low-family/mesh-node-logs.ts +87 -0
  247. package/src/commands/low-family/notification.ts +116 -0
  248. package/src/commands/low-family/refine-config.ts +106 -0
  249. package/src/commands/low-family/session-host.ts +274 -0
  250. package/src/commands/low-family/spec-providerdev.ts +217 -0
  251. package/src/commands/low-family/status-meta.ts +112 -0
  252. package/src/commands/low-family/types.ts +39 -0
  253. package/src/commands/med-family/cli-agent.ts +241 -0
  254. package/src/commands/med-family/fast-forward.ts +230 -0
  255. package/src/commands/med-family/ide.ts +163 -0
  256. package/src/commands/med-family/index.ts +37 -0
  257. package/src/commands/med-family/mesh-crud.ts +1287 -0
  258. package/src/commands/med-family/mesh-host-pairing.ts +234 -0
  259. package/src/commands/med-family/mesh-queue.ts +167 -0
  260. package/src/commands/med-family/mesh-restart.ts +92 -0
  261. package/src/commands/med-family/types.ts +153 -0
  262. package/src/commands/mesh-coordinator.ts +334 -124
  263. package/src/commands/router-refine.ts +1711 -0
  264. package/src/commands/router-worktree-cleanup.ts +870 -0
  265. package/src/commands/router.ts +1025 -4070
  266. package/src/commands/stream-commands.ts +8 -0
  267. package/src/commands/upgrade-helper.ts +310 -45
  268. package/src/config/chat-history.ts +493 -24
  269. package/src/config/config.ts +30 -0
  270. package/src/config/mesh-config.ts +632 -23
  271. package/src/config/mesh-json-config.ts +376 -0
  272. package/src/config/recent-activity.ts +8 -2
  273. package/src/config/registry-resolver.ts +100 -0
  274. package/src/config/repo-settings.ts +111 -0
  275. package/src/daemon/dev-auto-implement.ts +3 -2
  276. package/src/daemon/dev-cli-debug.ts +10 -1
  277. package/src/daemon/dev-server.ts +0 -541
  278. package/src/detection/cli-detector.ts +94 -9
  279. package/src/detection/ide-detector.ts +55 -16
  280. package/src/detection/win32-ide-version.ts +106 -0
  281. package/src/git/change-impact-config.ts +354 -0
  282. package/src/git/git-commands.ts +112 -16
  283. package/src/git/git-diff.ts +81 -11
  284. package/src/git/git-executor.ts +12 -0
  285. package/src/git/git-status.ts +767 -48
  286. package/src/git/git-types.ts +14 -62
  287. package/src/git/git-worktree.ts +261 -4
  288. package/src/git/index.ts +17 -0
  289. package/src/index.ts +200 -12
  290. package/src/installer.d.ts +1 -1
  291. package/src/installer.ts +8 -6
  292. package/src/ipc/local-ipc-server.ts +278 -0
  293. package/src/launch.d.ts +1 -1
  294. package/src/launch.ts +37 -28
  295. package/src/logging/async-batch-writer.ts +55 -0
  296. package/src/logging/command-log.ts +7 -5
  297. package/src/logging/debug-config.ts +25 -0
  298. package/src/logging/debug-trace.ts +7 -2
  299. package/src/logging/log-redactor.ts +100 -0
  300. package/src/logging/log-tail-reader.ts +341 -0
  301. package/src/logging/logger.ts +14 -7
  302. package/src/mesh/contracts.ts +469 -0
  303. package/src/mesh/coordinator-prompt.ts +554 -34
  304. package/src/mesh/coordinator-registry.ts +121 -0
  305. package/src/mesh/mesh-active-work.ts +656 -0
  306. package/src/mesh/mesh-clone-grace.ts +68 -0
  307. package/src/mesh/mesh-coordinator-config.ts +97 -0
  308. package/src/mesh/mesh-delivery-policy.ts +315 -0
  309. package/src/mesh/mesh-event-classify.ts +73 -0
  310. package/src/mesh/mesh-event-forwarding.ts +1950 -0
  311. package/src/mesh/mesh-event-trace.ts +67 -0
  312. package/src/mesh/mesh-events-coordinator.ts +32 -0
  313. package/src/mesh/mesh-events-pending.ts +1132 -0
  314. package/src/mesh/mesh-events-stale.ts +440 -0
  315. package/src/mesh/mesh-events-utils.ts +443 -0
  316. package/src/mesh/mesh-events.ts +35 -1035
  317. package/src/mesh/mesh-fast-forward.ts +856 -0
  318. package/src/mesh/mesh-host-ownership.ts +111 -0
  319. package/src/mesh/mesh-init.ts +350 -0
  320. package/src/mesh/mesh-ledger-reconciliation.ts +12 -7
  321. package/src/mesh/mesh-ledger.ts +873 -102
  322. package/src/mesh/mesh-magi-status.ts +223 -0
  323. package/src/mesh/mesh-missions.ts +449 -0
  324. package/src/mesh/mesh-node-identity.ts +2065 -0
  325. package/src/mesh/mesh-queue-assignment.ts +2233 -0
  326. package/src/mesh/mesh-reconcile-loop.ts +2397 -0
  327. package/src/mesh/mesh-refine-batch.ts +205 -0
  328. package/src/mesh/mesh-refine-gates.ts +1686 -0
  329. package/src/mesh/mesh-refine-status.ts +231 -0
  330. package/src/mesh/mesh-review-inbox.ts +308 -0
  331. package/src/mesh/mesh-routing.ts +291 -0
  332. package/src/mesh/mesh-runtime-store.ts +2056 -0
  333. package/src/mesh/mesh-scheduling-runtime.ts +198 -0
  334. package/src/mesh/mesh-task-inflight.ts +70 -0
  335. package/src/mesh/mesh-task-stats.ts +161 -0
  336. package/src/mesh/mesh-unresolved-forward-outbox.ts +185 -0
  337. package/src/mesh/mesh-warmup-deadline.ts +152 -0
  338. package/src/mesh/mesh-work-queue.ts +1281 -141
  339. package/src/mesh/preview-freshness.ts +163 -0
  340. package/src/mesh/refine-config.ts +423 -0
  341. package/src/mesh/worktree-bootstrap-config.ts +599 -0
  342. package/src/providers/acp-provider-instance.ts +43 -10
  343. package/src/providers/approval-utils.d.ts +5 -0
  344. package/src/providers/approval-utils.ts +99 -5
  345. package/src/providers/chat-message-normalization.ts +126 -4
  346. package/src/providers/cli-provider-instance.ts +2456 -107
  347. package/src/providers/contracts.d.ts +55 -0
  348. package/src/providers/contracts.ts +160 -6
  349. package/src/providers/extension-provider-instance.ts +12 -7
  350. package/src/providers/external-sources.ts +218 -0
  351. package/src/providers/ide-provider-instance.ts +35 -12
  352. package/src/providers/manual-attendance.ts +105 -0
  353. package/src/providers/native-history/antigravity-claim-registry.ts +131 -0
  354. package/src/providers/native-history/antigravity-cli-transcript.ts +1193 -0
  355. package/src/providers/native-history/claude-cli-transcript.ts +396 -0
  356. package/src/providers/native-history/codex-cli-transcript.ts +479 -0
  357. package/src/providers/native-history/constants.ts +19 -0
  358. package/src/providers/native-history/dispatcher.ts +503 -0
  359. package/src/providers/native-history/hermes-cli-transcript.ts +260 -0
  360. package/src/providers/native-history/index.ts +30 -0
  361. package/src/providers/provider-instance-manager.ts +71 -0
  362. package/src/providers/provider-instance.ts +24 -1
  363. package/src/providers/provider-loader.ts +700 -55
  364. package/src/providers/provider-schema.ts +87 -14
  365. package/src/providers/provider-trust.ts +114 -0
  366. package/src/providers/read-chat-contract.ts +76 -16
  367. package/src/providers/sdk/README.md +49 -0
  368. package/src/providers/sdk/v1/builders/acp/detect-status.ts +144 -0
  369. package/src/providers/sdk/v1/builders/cli/detect-status.ts +313 -0
  370. package/src/providers/sdk/v1/builders/cli/parse-approval-squash.ts +158 -0
  371. package/src/providers/sdk/v1/builders/cli/parse-approval.ts +261 -0
  372. package/src/providers/sdk/v1/builders/cli/parse-session.ts +276 -0
  373. package/src/providers/sdk/v1/builders/cli/visible-region.ts +143 -0
  374. package/src/providers/sdk/v1/fixture-tooling/format.ts +130 -0
  375. package/src/providers/sdk/v1/fixture-tooling/index.ts +22 -0
  376. package/src/providers/sdk/v1/fixture-tooling/replay.ts +352 -0
  377. package/src/providers/sdk/v1/index.ts +152 -0
  378. package/src/providers/sdk/v1/sandbox/README-design.ts +195 -0
  379. package/src/providers/sdk/v1/sandbox/require-whitelist.ts +472 -0
  380. package/src/providers/sdk/v1/sandbox/script-runner.ts +150 -0
  381. package/src/providers/sdk/v1/schemas/cli/provider.schema.json +509 -0
  382. package/src/providers/sdk/v1/schemas/primitives/acp-session-protocol-v1.json +131 -0
  383. package/src/providers/sdk/v1/schemas/primitives/native-history-codex-rollout-v1.json +66 -0
  384. package/src/providers/sdk/v1/schemas/primitives/tui-approval-squash-v1.json +91 -0
  385. package/src/providers/sdk/v1/schemas/primitives/tui-assistant-block-v1.json +91 -0
  386. package/src/providers/sdk/v1/schemas/primitives/tui-cue-ordering-v1.json +47 -0
  387. package/src/providers/sdk/v1/schemas/primitives/tui-dispatch-order-v1.json +32 -0
  388. package/src/providers/sdk/v1/schemas/primitives/tui-footer-chrome-v1.json +42 -0
  389. package/src/providers/sdk/v1/schemas/primitives/tui-index-finder-v1.json +27 -0
  390. package/src/providers/sdk/v1/schemas/primitives/tui-modal-v1.json +125 -0
  391. package/src/providers/sdk/v1/schemas/primitives/tui-prompt-marker-v1.json +45 -0
  392. package/src/providers/sdk/v1/schemas/primitives/tui-session-id-extraction-v1.json +46 -0
  393. package/src/providers/sdk/v1/schemas/primitives/tui-settled-prompt-v1.json +71 -0
  394. package/src/providers/sdk/v1/schemas/primitives/tui-spinner-v1.json +83 -0
  395. package/src/providers/sdk/v1/schemas/primitives/tui-transcript-pty-v1.json +83 -0
  396. package/src/providers/sdk/v1/schemas/primitives/tui-visible-region-v1.json +57 -0
  397. package/src/providers/sdk/v1/schemas/primitives/tui-welcome-screen-v1.json +35 -0
  398. package/src/providers/sdk/v1/types/cli/index.ts +365 -0
  399. package/src/providers/sdk/v1/types/common/index.ts +229 -0
  400. package/src/providers/sdk/v1/validators/index.ts +19 -0
  401. package/src/providers/sdk/v1/validators/manifest.ts +110 -0
  402. package/src/providers/sdk/v1/validators/taint.ts +309 -0
  403. package/src/providers/spec/adapter.ts +246 -0
  404. package/src/providers/spec/cli-adapter.ts +1064 -0
  405. package/src/providers/spec/evaluator.ts +407 -0
  406. package/src/providers/spec/fsm-driver.ts +1528 -0
  407. package/src/providers/spec/fsm-evaluator.ts +290 -0
  408. package/src/providers/spec/fsm-loader.ts +128 -0
  409. package/src/providers/spec/fsm-types.ts +301 -0
  410. package/src/providers/spec/native-history-executor.ts +1174 -0
  411. package/src/providers/spec/pre-launch-trust.ts +104 -0
  412. package/src/providers/spec/route.ts +51 -0
  413. package/src/providers/spec/types.ts +262 -0
  414. package/src/providers/status-monitor.d.ts +7 -7
  415. package/src/providers/status-monitor.ts +37 -22
  416. package/src/providers/transcript-v2.ts +567 -0
  417. package/src/providers/types/interactive-prompt.ts +536 -0
  418. package/src/providers/version-archive.ts +64 -24
  419. package/src/providers/working-dir.ts +23 -0
  420. package/src/repo-mesh-types.ts +836 -13
  421. package/src/runtime-defaults.ts +39 -0
  422. package/src/session-host/managed-host.ts +218 -0
  423. package/src/session-host/runtime-surface.ts +20 -80
  424. package/src/sessions/registry.ts +6 -0
  425. package/src/shared-types-extra.ts +2 -4
  426. package/src/shared-types.d.ts +8 -0
  427. package/src/shared-types.ts +124 -55
  428. package/src/status/builders.ts +26 -6
  429. package/src/status/normalize.ts +2 -0
  430. package/src/status/reporter.ts +19 -1
  431. package/src/status/snapshot.ts +95 -26
  432. package/src/system/hash.ts +23 -0
  433. package/src/system/host-memory.ts +29 -12
  434. package/src/system/load-better-sqlite3.ts +68 -0
  435. package/src/types.ts +5 -0
  436. package/dist/cli-adapters/terminal-backends/xterm-backend.d.ts +0 -17
  437. package/dist/mesh/mesh-sync.d.ts +0 -53
  438. package/src/cli-adapters/terminal-backends/ghostty-vt-backend.d.ts +0 -16
  439. package/src/cli-adapters/terminal-backends/xterm-backend.ts +0 -97
  440. 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,39 @@ 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';
40
+ import {
41
+ resolveRegistryBaseUrl,
42
+ resolveProviderTarballUrl,
43
+ resolveProviderTarballTarget,
44
+ } from '../config/registry-resolver.js';
34
45
  import type { ProviderSourceConfigSnapshot, ProviderUserDirSource } from '../config/provider-source-config.js';
46
+ import { executeNativeHistory } from './spec/native-history-executor.js';
47
+ import { createNativeHistoryDispatcher, type ReaderId } from './native-history/dispatcher.js';
48
+
49
+ /**
50
+ * Adds a provider-script root to the require whitelist. Wrapped in a
51
+ * try/catch + null check so a loader hot-path can't crash on a path
52
+ * that doesn't exist yet or one the whitelist hook rejects.
53
+ *
54
+ * The require-whitelist module is loaded lazily on first call. Eagerly
55
+ * top-level importing it pulls `node:fs.realpathSync.native` into
56
+ * module evaluation, which breaks unit tests that partially mock `fs`
57
+ * (e.g. test/commands/get-logs-incremental.test.ts mocks only
58
+ * existsSync + readFileSync). Lazy load keeps that mock surface valid.
59
+ */
60
+ function registerProviderScriptRootSafely(root: string | null | undefined): void {
61
+ if (!root || typeof root !== 'string') return;
62
+ try {
63
+ const { registerProviderScriptRoot } =
64
+ require('./sdk/v1/sandbox/require-whitelist.js') as typeof import('./sdk/v1/sandbox/require-whitelist.js');
65
+ registerProviderScriptRoot(root);
66
+ } catch { /* boot-time only — swallow */ }
67
+ }
35
68
 
36
69
  interface ProviderAvailabilityState {
37
70
  installed: boolean;
@@ -61,6 +94,78 @@ export interface MachineProviderConfig {
61
94
  lastVerification?: MachineProviderCheckResult;
62
95
  }
63
96
 
97
+ /**
98
+ * Translate a spec `control_bar` array into the web-facing
99
+ * `ProviderControlDef[]` shape the dashboard renders.
100
+ *
101
+ * The two shapes are distinct: `control_bar` entries are daemon-side
102
+ * `{ id, label, visible_when_state, action }` records driving
103
+ * SpecCliAdapter.invokeScript, while the dashboard's chat bar reads
104
+ * `ProviderControlDef` (`{ id, type, label, placement, ... }`). Spec
105
+ * providers (claude-cli / codex-cli) historically declared *only*
106
+ * `control_bar`, so the dashboard saw no controls at all — the Model / Mode
107
+ * pickers never rendered. This bridges that gap without changing how the
108
+ * controls actually dispatch.
109
+ *
110
+ * Script-name contract: the dashboard sends the control's
111
+ * `listScript` / `setScript` / `invokeScript` name through
112
+ * `invoke_provider_script`, which gates on `provider.scripts[<name>]` and then
113
+ * routes to `SpecCliAdapter.invokeScript(<name>)` — which matches the name
114
+ * against `control_bar[].id`. So every synthesized script name MUST equal the
115
+ * control id (the loader stubs `provider.scripts[id]` from the same source).
116
+ *
117
+ * Mapping:
118
+ * open_picker → select (dynamic): list + set both keyed on the control id;
119
+ * the adapter distinguishes LIST vs SELECT by the presence of
120
+ * a choice arg, so one id serves both roles.
121
+ * send_keys → action: one-shot keystroke (stop, cycle_mode).
122
+ * attach_image → skipped: it needs an image blob from a file picker, not a
123
+ * bare bar button; surfacing it as an `action` would only
124
+ * produce a button that errors with "requires args.blob".
125
+ */
126
+ function synthesizeControlsFromControlBar(specControls: any[]): ProviderControlDef[] {
127
+ const out: ProviderControlDef[] = [];
128
+ specControls.forEach((ctl, index) => {
129
+ const id = typeof ctl?.id === 'string' ? ctl.id.trim() : '';
130
+ const actionType = ctl?.action?.type;
131
+ if (!id || !actionType) return;
132
+ const label = typeof ctl?.label === 'string' && ctl.label.trim() ? ctl.label : id;
133
+ // Preserve the spec's state gating so the web bar can mirror the daemon's
134
+ // FsmDriver.handleClickControl enforcement (otherwise the button renders in
135
+ // states where the daemon would silently drop the click).
136
+ const visibleWhenState = Array.isArray(ctl?.visible_when_state)
137
+ ? ctl.visible_when_state.filter((s: unknown): s is string => typeof s === 'string')
138
+ : undefined;
139
+ if (actionType === 'open_picker') {
140
+ out.push({
141
+ id,
142
+ type: 'select',
143
+ label,
144
+ placement: 'bar',
145
+ dynamic: true,
146
+ listScript: id,
147
+ setScript: id,
148
+ readFrom: id,
149
+ order: index,
150
+ ...(visibleWhenState && visibleWhenState.length > 0 ? { visibleWhenState } : {}),
151
+ });
152
+ } else if (actionType === 'send_keys') {
153
+ out.push({
154
+ id,
155
+ type: 'action',
156
+ label,
157
+ placement: 'bar',
158
+ invokeScript: id,
159
+ resultDisplay: 'none',
160
+ order: index,
161
+ ...(visibleWhenState && visibleWhenState.length > 0 ? { visibleWhenState } : {}),
162
+ });
163
+ }
164
+ // attach_image intentionally skipped — see fn doc.
165
+ });
166
+ return out;
167
+ }
168
+
64
169
  type CliDetectionEntry = {
65
170
  id: string;
66
171
  displayName: string;
@@ -86,13 +191,21 @@ export class ProviderLoader {
86
191
  private versionArchive: VersionArchive | null = null;
87
192
  private scriptsCache = new Map<string, Partial<ProviderScripts>>();
88
193
 
194
+ /**
195
+ * Resolved registry base URL and provider tarball URL. Resolution order:
196
+ * explicit config field (constructor option) → env var → vendor default.
197
+ * See `config/registry-resolver.ts`.
198
+ */
199
+ private readonly registryBaseUrl: string;
200
+ private readonly providerTarballUrl: string;
201
+
89
202
  /** Inject VersionArchive so resolve() can auto-detect installed versions */
90
203
  setVersionArchive(archive: VersionArchive): void {
91
204
  this.versionArchive = archive;
92
205
  }
93
206
 
94
- private static readonly GITHUB_TARBALL_URL = 'https://github.com/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz';
95
207
  private static readonly META_FILE = '.meta.json';
208
+ private static readonly REGISTRY_META_FILE = '.registry-meta.json';
96
209
  private static readonly REPO_PROVIDER_DIRNAME = 'adhdev-providers';
97
210
  private static readonly SIBLING_MARKER_FILE = '.adhdev-provider-root';
98
211
  private static readonly SIBLING_ENV_VAR = 'ADHDEV_USE_SIBLING_PROVIDERS';
@@ -176,9 +289,21 @@ export class ProviderLoader {
176
289
  * probing; production code should leave this unset.
177
290
  */
178
291
  probeStarts?: string[];
292
+ /**
293
+ * Explicit provider registry base URL override (config.registryUrl).
294
+ * Highest-priority resolver source, ahead of ADHDEV_REGISTRY_URL + default.
295
+ */
296
+ registryUrl?: string;
297
+ /**
298
+ * Explicit provider tarball URL override (config.providerTarballUrl).
299
+ * Highest-priority resolver source, ahead of ADHDEV_PROVIDER_TARBALL_URL + default.
300
+ */
301
+ providerTarballUrl?: string;
179
302
  }) {
180
303
  this.logFn = options?.logFn || LOG.forComponent('Provider').asLogFn();
181
304
  this.probeStarts = options?.probeStarts ?? [process.cwd(), __dirname];
305
+ this.registryBaseUrl = resolveRegistryBaseUrl(options?.registryUrl);
306
+ this.providerTarballUrl = resolveProviderTarballUrl(options?.providerTarballUrl);
182
307
 
183
308
  // Default directory for auto-downloads
184
309
  this.defaultProvidersDir = path.join(os.homedir(), '.adhdev', 'providers');
@@ -193,6 +318,33 @@ export class ProviderLoader {
193
318
  sourceMode: options?.sourceMode,
194
319
  disableUpstream: options?.disableUpstream,
195
320
  });
321
+
322
+ // One-time migration: ~/.adhdev/marketplace → ~/.adhdev/external.
323
+ // The directory was renamed when the "marketplace" install model was
324
+ // dropped in favour of explicit external git sources. Best-effort:
325
+ // if the rename fails we leave both dirs in place and log so the user
326
+ // can investigate.
327
+ this.migrateMarketplaceDirToExternal();
328
+ }
329
+
330
+ private migrateMarketplaceDirToExternal(): void {
331
+ try {
332
+ const home = os.homedir();
333
+ const oldDir = path.join(home, '.adhdev', 'marketplace');
334
+ const newDir = path.join(home, '.adhdev', 'external');
335
+ if (!fs.existsSync(oldDir)) return;
336
+ if (fs.existsSync(newDir)) {
337
+ // Both exist — don't merge. Leave old in place; surface in logs so
338
+ // the user can decide what to keep. Loader still loads from
339
+ // external/ only, so old marketplace/ becomes inert.
340
+ this.log(`Migration skipped: both ~/.adhdev/marketplace and ~/.adhdev/external exist (marketplace dir is now inert and can be removed manually).`);
341
+ return;
342
+ }
343
+ fs.renameSync(oldDir, newDir);
344
+ this.log(`Migrated ~/.adhdev/marketplace → ~/.adhdev/external (one-time rename after provider source-layer cleanup).`);
345
+ } catch (e: any) {
346
+ this.log(`Marketplace→external migration failed: ${e?.message || e}`);
347
+ }
196
348
  }
197
349
 
198
350
  private log(msg: string): void {
@@ -224,7 +376,12 @@ export class ProviderLoader {
224
376
  * Highest-priority editable overrides come first.
225
377
  */
226
378
  getProviderRoots(): string[] {
227
- return [this.userDir, this.upstreamDir];
379
+ // Order matters: user customs > external (3rd-party sources) > upstream
380
+ // (official auto-sync). findProviderDirInternal walks this list in order
381
+ // to locate the provider dir containing the scripts/, so external must
382
+ // be included here even though loadAll() also reads it directly.
383
+ const externalDir = path.join(os.homedir(), '.adhdev', 'external');
384
+ return [this.userDir, externalDir, this.upstreamDir];
228
385
  }
229
386
 
230
387
  getSourceConfig(): ProviderSourceConfigSnapshot {
@@ -316,16 +473,19 @@ export class ProviderLoader {
316
473
 
317
474
  /**
318
475
  * 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).
476
+ * 1. ~/.adhdev/providers/.upstream/ official git, auto-synced
477
+ * 2. ~/.adhdev/external/ 3rd-party git sources, user-added,
478
+ * bundled providers may include arbitrary JS (untrusted by default)
479
+ * 3. ~/.adhdev/providers/ (excluding .upstream) — user-authored customs,
480
+ * always wins
481
+ * Highest priority listed last (overwrites earlier loads).
322
482
  * If .upstream/ is empty, call fetchLatest() before loadAll().
323
483
  */
324
484
  loadAll(): void {
325
485
  this.providers.clear();
326
486
  this.providerAvailability.clear();
327
487
 
328
- // 1. Load upstream (GitHub auto-download — primary source)
488
+ // 1. Load upstream (GitHub auto-download — primary official source)
329
489
  let upstreamCount = 0;
330
490
  if (!this.disableUpstream && fs.existsSync(this.upstreamDir)) {
331
491
  upstreamCount = this.loadDir(this.upstreamDir);
@@ -336,7 +496,90 @@ export class ProviderLoader {
336
496
  this.log('Upstream loading disabled (sourceMode=no-upstream)');
337
497
  }
338
498
 
339
- // 2. Load user custom (excluding .upstream — highest priority, never auto-updated)
499
+ // 2. Load external providers from ~/.adhdev/external/<source-name>/
500
+ // (3rd-party git sources). Overrides upstream but is itself overridden
501
+ // by user customs in step 3.
502
+ //
503
+ // Each registered source is a separate subdirectory so two sources can
504
+ // both expose the same provider type without overwriting each other.
505
+ // When more than one source provides the same type, providers-active.json
506
+ // chooses the active one; without an explicit choice we deterministically
507
+ // pick the first in disk-walk order and log the ambiguity so the user
508
+ // can resolve it from the dashboard.
509
+ //
510
+ // Any non-spec manifest (tui block / overrides / scriptDir) coming from
511
+ // an external source runs JavaScript the daemon hasn't audited, so
512
+ // dashboards must surface an "untrusted source" badge before letting
513
+ // the user enable them.
514
+ const externalDir = path.join(os.homedir(), '.adhdev', 'external');
515
+ if (fs.existsSync(externalDir)) {
516
+ // Legacy layout (pre-source-namespace): manifests sit directly at
517
+ // external/<category>/<type>/. Detect by presence of category dirs at
518
+ // the root and migrate inline by treating the whole tree as a single
519
+ // implicit source. Loader behavior unchanged for legacy callers.
520
+ const rootEntries = (() => {
521
+ try { return fs.readdirSync(externalDir, { withFileTypes: true }); }
522
+ catch { return [] as fs.Dirent[]; }
523
+ })();
524
+ const KNOWN_CATEGORIES = new Set(['cli', 'ide', 'extension', 'acp']);
525
+ const looksLegacy = rootEntries.some(e => e.isDirectory() && KNOWN_CATEGORIES.has(e.name));
526
+ if (looksLegacy) {
527
+ // Tree shape predates per-source dirs — treat the whole thing as a
528
+ // single anonymous source so existing installs keep working until
529
+ // they're migrated to a real source registration.
530
+ const externalCount = this.loadDir(externalDir);
531
+ if (externalCount > 0) {
532
+ this.log(`Loaded ${externalCount} external providers (legacy unnamed source)`);
533
+ }
534
+ } else {
535
+ // New layout: external/<source-name>/<category>/<type>/…
536
+ const activeFile = loadProvidersActive();
537
+ let totalLoaded = 0;
538
+ const ambiguousTypes: { type: string; chosen: string; candidates: string[] }[] = [];
539
+ // Per-source load, then filter by active-selection: for each type
540
+ // present in more than one source, only the active source's copy
541
+ // is left in this.providers.
542
+ for (const sourceEntry of rootEntries) {
543
+ if (!sourceEntry.isDirectory()) continue;
544
+ const sourceDir = path.join(externalDir, sourceEntry.name);
545
+ const sourceLoaded = this.loadDir(sourceDir);
546
+ if (sourceLoaded > 0) {
547
+ totalLoaded += sourceLoaded;
548
+ this.log(`Loaded ${sourceLoaded} providers from external source "${sourceEntry.name}"`);
549
+ }
550
+ }
551
+ // Resolve ambiguities — when the same type came from multiple
552
+ // sources, the last load wins by default. Replay with the active
553
+ // selection so the user-chosen source ends up winning.
554
+ for (const [type] of this.providers) {
555
+ const prov = this.providers.get(type);
556
+ if (!prov) continue;
557
+ const resolved = resolveActiveSource(prov.category, type, activeFile);
558
+ if (resolved.candidates.length <= 1) continue;
559
+ if (resolved.ambiguous) {
560
+ ambiguousTypes.push({ type, chosen: resolved.source ?? '?', candidates: resolved.candidates });
561
+ }
562
+ if (resolved.source && resolved.source !== '?') {
563
+ const sourceDir = path.join(externalDir, resolved.source);
564
+ // Reload only this source's copy of the conflicting type so it
565
+ // overwrites whatever else won the initial pass.
566
+ const reloadCount = this.loadDir(sourceDir);
567
+ // reloadCount is a sanity check — we expect ≥1
568
+ if (reloadCount === 0) {
569
+ this.log(`Active source "${resolved.source}" no longer provides ${type}`);
570
+ }
571
+ }
572
+ }
573
+ if (totalLoaded > 0) {
574
+ this.log(`Loaded ${totalLoaded} external providers (3rd-party sources)`);
575
+ }
576
+ for (const a of ambiguousTypes) {
577
+ 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.`);
578
+ }
579
+ }
580
+ }
581
+
582
+ // 3. Load user custom (excluding .upstream — highest priority, never auto-updated)
340
583
  if (fs.existsSync(this.userDir)) {
341
584
  const userCount = this.loadDir(this.userDir, ['.upstream']);
342
585
  if (userCount > 0) {
@@ -909,18 +1152,28 @@ export class ProviderLoader {
909
1152
 
910
1153
  for (const entry of compat) {
911
1154
  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;
1155
+ // entry.scriptDir is optional now — spec-driven providers (agy,
1156
+ // codex on >=0.137, claude on >=2.1) only ship `spec` here, so
1157
+ // there's nothing to load from the filesystem. SpecCliAdapter
1158
+ // takes over via the `spec` path later in this method.
1159
+ if (entry.scriptDir) {
1160
+ const loaded = this.loadScriptsFromDir(type, entry.scriptDir);
1161
+ if (loaded) {
1162
+ resolved.scripts = loaded;
1163
+ this.debugLog(` [compatibility] ${type} v${currentVersion} → ${entry.scriptDir}`);
1164
+ resolved._resolvedScriptDir = entry.scriptDir;
1165
+ resolved._resolvedScriptsSource = `compatibility:${entry.ideVersion}`;
1166
+ if (providerDir) {
1167
+ const fullDir = path.join(providerDir, entry.scriptDir);
1168
+ resolved._resolvedScriptsPath = fs.existsSync(path.join(fullDir, 'scripts.js'))
1169
+ ? path.join(fullDir, 'scripts.js')
1170
+ : fullDir;
1171
+ }
1172
+ matched = true;
923
1173
  }
1174
+ } else {
1175
+ // Spec-only entry — still counts as a match so the
1176
+ // defaultScriptDir fallback below doesn't kick in.
924
1177
  matched = true;
925
1178
  }
926
1179
  break; // first match wins
@@ -988,7 +1241,12 @@ export class ProviderLoader {
988
1241
  }
989
1242
 
990
1243
  // 3. Composite override (OS + version)
991
- if (base.overrides) {
1244
+ // Legacy shape: base.overrides is an Array<{ when: {os,version}, scripts }>.
1245
+ // v1 manifests (Phase 3-4) repurposed `overrides` as an object map of
1246
+ // capability overrides (e.g. { detectStatus: { path, schema } }), which is
1247
+ // consumed by the SDK builders, not by this resolver. Only iterate when
1248
+ // the field is in the legacy array shape.
1249
+ if (Array.isArray(base.overrides)) {
992
1250
  for (const override of base.overrides) {
993
1251
  const osMatch = !override.when.os || override.when.os === currentOs;
994
1252
  const verMatch = !override.when.version || (currentVersion && this.matchesVersion(currentVersion, override.when.version));
@@ -996,6 +1254,40 @@ export class ProviderLoader {
996
1254
  resolved.scripts = { ...resolved.scripts, ...override.scripts };
997
1255
  }
998
1256
  }
1257
+ } else if (base.overrides && typeof base.overrides === 'object') {
1258
+ // v1 manifest shape: { detectStatus: { path }, parseSession: { path }, ... }
1259
+ // Each script name maps to a path inside the provider directory. We load
1260
+ // the file and merge its export(s) into resolved.scripts. Lets a
1261
+ // provider override a single primitive (e.g. just detectStatus) while
1262
+ // letting the SDK synthesize the rest from the tui block.
1263
+ const providerDir = this.findProviderDirInternal(base.type);
1264
+ if (providerDir) {
1265
+ for (const [scriptName, override] of Object.entries(base.overrides as Record<string, any>)) {
1266
+ if (!override || typeof override.path !== 'string') continue;
1267
+ const fullPath = path.join(providerDir, override.path);
1268
+ if (!fs.existsSync(fullPath)) {
1269
+ this.log(` [overrides] ${base.type}: ${scriptName} path not found: ${fullPath}`);
1270
+ continue;
1271
+ }
1272
+ try {
1273
+ // Override scripts go through the same whitelist gate as the
1274
+ // main scripts dir. Use the provider parent root so a v1
1275
+ // override can still require ../_shared helpers.
1276
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1277
+ delete require.cache[require.resolve(fullPath)];
1278
+ const fn = require(fullPath);
1279
+ const target = typeof fn === 'function' ? fn : (fn && fn[scriptName]);
1280
+ if (typeof target === 'function') {
1281
+ resolved.scripts = { ...resolved.scripts, [scriptName]: target } as any;
1282
+ this.log(` [overrides] ${base.type}: ${scriptName} loaded from ${override.path}`);
1283
+ } else {
1284
+ this.log(` [overrides] ${base.type}: ${scriptName} export missing in ${override.path}`);
1285
+ }
1286
+ } catch (e: any) {
1287
+ this.log(` [overrides] ${base.type}: ${scriptName} require failed: ${e?.message || e}`);
1288
+ }
1289
+ }
1290
+ }
999
1291
  }
1000
1292
 
1001
1293
  if ((resolved.category === 'cli' || resolved.category === 'acp') && resolved.spawn?.command) {
@@ -1006,6 +1298,135 @@ export class ProviderLoader {
1006
1298
  };
1007
1299
  }
1008
1300
 
1301
+ // (spec migration) Late-binding spec.json native-history hook. Runs
1302
+ // *after* every script-loading path (compatibility / defaultScriptDir /
1303
+ // overrides) so it deterministically wins over a legacy v1 scripts.js
1304
+ // export. Three modes, picked by spec.json's native_history block:
1305
+ // 1. source — declarative jsonl/sqlite executor (new-provider path,
1306
+ // no daemon change needed for new on-disk formats)
1307
+ // 2. override_path — provider-supplied reader file (escape hatch for
1308
+ // exotic formats); module default-exports a reader fn
1309
+ // 3. reader — built-in reader id (claude-cli / codex-cli /
1310
+ // antigravity-cli / hermes-cli), kept for backwards
1311
+ // compatibility with the four shipped providers
1312
+ if (providerDir) {
1313
+ try {
1314
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1315
+ const fs = require('node:fs');
1316
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1317
+ const path = require('node:path');
1318
+ // Pick the right spec file for the detected CLI version. Resolution
1319
+ // order:
1320
+ // 1. compatibility[i].spec where ideVersion matches currentVersion
1321
+ // (lets a provider ship specs/2.0.json, specs/2.1.json, etc.
1322
+ // alongside the matching scriptDir)
1323
+ // 2. specs/default.json — explicit fallback
1324
+ // 3. spec.json — legacy single-spec layout
1325
+ // Missing files fall through silently to the next candidate.
1326
+ const candidates: string[] = [];
1327
+ if (Array.isArray((base as any).compatibility)) {
1328
+ for (const entry of (base as any).compatibility) {
1329
+ if (typeof entry?.spec !== 'string') continue;
1330
+ // If currentVersion is unknown (cli-manager hasn't probed yet)
1331
+ // we still let compatibility entries that don't pin a version
1332
+ // through, plus any entry whose pin matches.
1333
+ const matches = !entry.ideVersion
1334
+ || (currentVersion && this.matchesVersion(currentVersion, entry.ideVersion))
1335
+ || !currentVersion;
1336
+ if (matches) candidates.push(path.join(providerDir, entry.spec));
1337
+ }
1338
+ }
1339
+ candidates.push(path.join(providerDir, 'specs', 'default.json'));
1340
+ candidates.push(path.join(providerDir, 'spec.json'));
1341
+ const specPath = candidates.find((p: string) => fs.existsSync(p));
1342
+ if (specPath) {
1343
+ // Hand the resolved spec path off to route.ts via a hidden field
1344
+ // so the routing layer doesn't have to repeat the candidate walk.
1345
+ (resolved as any)._resolvedSpecPath = specPath;
1346
+ // Extract control_bar + native_history directly from the JSON header.
1347
+ let specControls: any[] | undefined;
1348
+ let nh: any | undefined;
1349
+ try {
1350
+ const rawSpec = JSON.parse(fs.readFileSync(specPath, 'utf8'));
1351
+ specControls = rawSpec.control_bar;
1352
+ nh = rawSpec.native_history;
1353
+ } catch { /* unreadable spec — leave controls/native unavailable */ }
1354
+ // Stub each control_bar entry as a provider.scripts.<id>. The
1355
+ // upstream invoke_provider_script gate checks that the script
1356
+ // name exists on provider.scripts before calling adapter.invokeScript;
1357
+ // for spec providers the *actual* dispatch happens inside
1358
+ // SpecCliAdapter.invokeScript which maps the name to control_bar.
1359
+ // The stub is just a presence marker so the gate doesn't reject.
1360
+ if (specControls && specControls.length > 0) {
1361
+ resolved.scripts = { ...(resolved.scripts || {}) };
1362
+ for (const ctl of specControls) {
1363
+ if (!(resolved.scripts as any)[ctl.id]) {
1364
+ (resolved.scripts as any)[ctl.id] = (..._args: unknown[]) => ({
1365
+ __spec_control: true,
1366
+ controlId: ctl.id,
1367
+ actionType: ctl.action.type,
1368
+ });
1369
+ }
1370
+ }
1371
+ // Bridge the spec control_bar into the web-facing controls schema so
1372
+ // the dashboard chat bar actually renders Model/Mode pickers. Only
1373
+ // synthesize when the provider hasn't already declared its own
1374
+ // `controls` in provider.v1.json (e.g. hermes-cli) — an explicit
1375
+ // declaration wins and must not be clobbered.
1376
+ const hasDeclaredControls = Array.isArray((resolved as any).controls)
1377
+ && (resolved as any).controls.length > 0;
1378
+ if (!hasDeclaredControls) {
1379
+ const synthesized = synthesizeControlsFromControlBar(specControls);
1380
+ if (synthesized.length > 0) {
1381
+ resolved.controls = synthesized;
1382
+ }
1383
+ }
1384
+ }
1385
+ if (nh) {
1386
+ let reader: ((input: any) => any) | null = null;
1387
+ let format = 'spec';
1388
+
1389
+ if (nh.source) {
1390
+ format = `spec-${nh.source.kind}`;
1391
+ reader = (input: any) => executeNativeHistory(nh, input);
1392
+ } else if (nh.override_path) {
1393
+ const overrideFile = path.resolve(providerDir, nh.override_path);
1394
+ if (fs.existsSync(overrideFile)) {
1395
+ try {
1396
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1397
+ delete require.cache[require.resolve(overrideFile)];
1398
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
1399
+ const mod = require(overrideFile);
1400
+ const fn = typeof mod === 'function' ? mod : (mod && typeof mod.default === 'function' ? mod.default : null);
1401
+ if (fn) {
1402
+ format = 'spec-override';
1403
+ reader = (input: any) => fn(input);
1404
+ }
1405
+ } catch { /* fall through — leave native unavailable */ }
1406
+ }
1407
+ } else if (nh.reader) {
1408
+ const dispatch = createNativeHistoryDispatcher(nh.reader as ReaderId);
1409
+ format = nh.reader;
1410
+ reader = (input: any) => dispatch(input);
1411
+ }
1412
+
1413
+ if (reader) {
1414
+ resolved.scripts = { ...(resolved.scripts || {}) };
1415
+ (resolved.scripts as any).readNativeHistory = reader;
1416
+ (resolved as any).nativeHistory = {
1417
+ format,
1418
+ watchPath: undefined,
1419
+ scripts: { readSession: 'readNativeHistory' },
1420
+ mode: 'native-source',
1421
+ };
1422
+ }
1423
+ }
1424
+ }
1425
+ } catch {
1426
+ // Best-effort — spec wiring failure must not break legacy providers.
1427
+ }
1428
+ }
1429
+
1009
1430
  return resolved;
1010
1431
  }
1011
1432
 
@@ -1016,16 +1437,27 @@ export class ProviderLoader {
1016
1437
  private loadScriptsFromDir(type: string, scriptDir: string): Partial<ProviderScripts> | null {
1017
1438
  const providerDir = this.findProviderDirInternal(type);
1018
1439
  if (!providerDir) {
1019
- this.log(` [loadScriptsFromDir] ${type}: providerDir not found`);
1440
+ // No provider dir for this type a spec-only provider with no
1441
+ // legacy scripts/v1 layout is a normal configuration, not a
1442
+ // problem to surface at INFO. resolve() calls this on every
1443
+ // request; INFO spam every 200ms drowns out the real signal.
1444
+ this.debugLog(`[loadScriptsFromDir] ${type}: providerDir not found`);
1020
1445
  return null;
1021
1446
  }
1022
1447
 
1023
1448
  const dir = path.join(providerDir, scriptDir);
1024
1449
  if (!fs.existsSync(dir)) {
1025
- this.log(` [loadScriptsFromDir] ${type}: dir not found: ${dir}`);
1450
+ this.debugLog(`[loadScriptsFromDir] ${type}: dir not found: ${dir}`);
1026
1451
  return null;
1027
1452
  }
1028
1453
 
1454
+ // Register the provider's *parent root* (e.g. .../adhdev-providers/) so
1455
+ // the require whitelist gates every script + every _shared helper this
1456
+ // provider may reach. Picking the grandparent (one above the category
1457
+ // dir `cli/`) lets sibling helpers in `_shared` resolve while still
1458
+ // blocking `../../etc/...` escapes. Idempotent.
1459
+ registerProviderScriptRootSafely(path.dirname(path.dirname(providerDir)));
1460
+
1029
1461
  // Return cached scripts if available (cleared on reload/watch)
1030
1462
  const cached = this.scriptsCache.get(dir);
1031
1463
  if (cached) return cached;
@@ -1036,7 +1468,7 @@ export class ProviderLoader {
1036
1468
  try {
1037
1469
  delete require.cache[require.resolve(scriptsJs)];
1038
1470
  const loaded = require(scriptsJs);
1039
- this.log(` [loadScriptsFromDir] ${type}: loaded scripts.js from ${dir} (${Object.keys(loaded).length} exports)`);
1471
+ this.debugLog(`[loadScriptsFromDir] ${type}: loaded scripts.js from ${dir} (${Object.keys(loaded).length} exports)`);
1040
1472
  this.scriptsCache.set(dir, loaded);
1041
1473
  return loaded;
1042
1474
  } catch (e) {
@@ -1067,13 +1499,17 @@ export class ProviderLoader {
1067
1499
  awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
1068
1500
  });
1069
1501
 
1502
+ let reloadTimer: ReturnType<typeof setTimeout> | null = null;
1070
1503
  const handleChange = (filePath: string) => {
1071
1504
  if (/[\/\\]fixtures[\/\\]/.test(filePath)) {
1072
1505
  return;
1073
1506
  }
1074
1507
  if (filePath.endsWith('.js') || filePath.endsWith('.json')) {
1075
- this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1076
- this.reload();
1508
+ if (reloadTimer) clearTimeout(reloadTimer);
1509
+ reloadTimer = setTimeout(() => {
1510
+ this.log(`File changed: ${path.basename(filePath)}, reloading...`);
1511
+ this.reload();
1512
+ }, 300);
1077
1513
  }
1078
1514
  };
1079
1515
 
@@ -1124,13 +1560,115 @@ export class ProviderLoader {
1124
1560
  *
1125
1561
  * @returns Whether an update occurred
1126
1562
  */
1563
+ /**
1564
+ * Sync providers from the ADHDev registry (registry.adhf.dev).
1565
+ *
1566
+ * Downloads only providers whose server checksum differs from the locally
1567
+ * cached checksum. Falls back gracefully to the GitHub tarball path if the
1568
+ * registry is unreachable or returns an unexpected response.
1569
+ *
1570
+ * Returns `{ updated: true }` when at least one provider file changed on disk,
1571
+ * `{ updated: false }` when everything is already current, or
1572
+ * `{ updated: false, error }` when the registry couldn't be reached and we
1573
+ * should proceed to the GitHub tarball fallback.
1574
+ */
1575
+ async fetchFromRegistry(): Promise<{ updated: boolean; error?: string }> {
1576
+ if (this.disableUpstream) {
1577
+ this.log('Registry sync skipped (sourceMode=no-upstream)');
1578
+ return { updated: false };
1579
+ }
1580
+ this.log(`Registry sync starting (${this.registryBaseUrl})...`);
1581
+
1582
+ const https = require('https') as typeof import('https');
1583
+ const regMetaPath = path.join(this.upstreamDir, ProviderLoader.REGISTRY_META_FILE);
1584
+
1585
+ // Load cached checksums
1586
+ let cachedChecksums: Record<string, string> = {};
1587
+ try {
1588
+ if (fs.existsSync(regMetaPath)) {
1589
+ cachedChecksums = JSON.parse(fs.readFileSync(regMetaPath, 'utf-8')).checksums ?? {};
1590
+ }
1591
+ } catch { }
1592
+
1593
+ try {
1594
+ // 1. Fetch provider list
1595
+ const listUrl = `${this.registryBaseUrl}/providers`;
1596
+ const listBody = await new Promise<string>((resolve, reject) => {
1597
+ const req = https.get(listUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 10000 }, (res) => {
1598
+ if (res.statusCode !== 200) { reject(new Error(`registry list HTTP ${res.statusCode}`)); 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('registry list timeout')); });
1605
+ });
1606
+
1607
+ const list = JSON.parse(listBody) as { providers: Array<{ type: string; category: string; checksum: string; version: string }> };
1608
+ if (!Array.isArray(list.providers)) throw new Error('unexpected registry response shape');
1609
+
1610
+ let updatedCount = 0;
1611
+
1612
+ for (const entry of list.providers) {
1613
+ const { type, category, checksum, version } = entry;
1614
+ const cacheKey = `${category}/${type}`;
1615
+ if (cachedChecksums[cacheKey] === checksum) continue; // already current
1616
+
1617
+ // Download this provider's manifest
1618
+ const dlUrl = `${this.registryBaseUrl}/providers/${type}/${version}/download`;
1619
+ const manifestBody = await new Promise<string>((resolve, reject) => {
1620
+ const req = https.get(dlUrl, { headers: { 'User-Agent': 'adhdev-daemon', 'Accept': 'application/json' }, timeout: 30000 }, (res) => {
1621
+ if (res.statusCode !== 200) { reject(new Error(`registry download HTTP ${res.statusCode} for ${type}@${version}`)); return; }
1622
+ const chunks: Buffer[] = [];
1623
+ res.on('data', (c: Buffer) => chunks.push(c));
1624
+ res.on('end', () => resolve(Buffer.concat(chunks).toString('utf-8')));
1625
+ });
1626
+ req.on('error', reject);
1627
+ req.on('timeout', () => { req.destroy(); reject(new Error(`download timeout for ${type}`)); });
1628
+ });
1629
+
1630
+ // Verify checksum
1631
+ const actualChecksum = sha256Hex(manifestBody);
1632
+ if (actualChecksum !== checksum) {
1633
+ this.log(`⚠ Registry checksum mismatch for ${type}@${version} — skipping`);
1634
+ continue;
1635
+ }
1636
+
1637
+ // Write to upstream dir
1638
+ const providerDir = path.join(this.upstreamDir, category, type);
1639
+ fs.mkdirSync(providerDir, { recursive: true });
1640
+ fs.writeFileSync(path.join(providerDir, 'provider.json'), manifestBody, 'utf-8');
1641
+
1642
+ cachedChecksums[cacheKey] = checksum;
1643
+ updatedCount++;
1644
+ this.log(`✓ Registry updated: ${category}/${type}@${version}`);
1645
+ }
1646
+
1647
+ // Persist updated checksums
1648
+ fs.mkdirSync(this.upstreamDir, { recursive: true });
1649
+ fs.writeFileSync(regMetaPath, JSON.stringify({
1650
+ checksums: cachedChecksums,
1651
+ syncedAt: new Date().toISOString(),
1652
+ providerCount: list.providers.length,
1653
+ }, null, 2));
1654
+
1655
+ this.log(`Registry sync complete: ${list.providers.length} providers, ${updatedCount} updated`);
1656
+ return { updated: updatedCount > 0 };
1657
+ } catch (e: any) {
1658
+ this.log(`⚠ Registry sync failed (falling back to GitHub tarball): ${e?.message}`);
1659
+ return { updated: false, error: e?.message };
1660
+ }
1661
+ }
1662
+
1127
1663
  async fetchLatest(): Promise<{ updated: boolean; error?: string }> {
1128
1664
  if (this.disableUpstream) {
1129
1665
  this.log('Upstream fetch skipped (sourceMode=no-upstream)');
1130
1666
  return { updated: false };
1131
1667
  }
1132
1668
  const https = require('https') as typeof import('https');
1133
- const { execSync } = require('child_process') as typeof import('child_process');
1669
+ const { exec } = require('child_process') as typeof import('child_process');
1670
+ const { promisify } = require('util');
1671
+ const execAsync = promisify(exec);
1134
1672
 
1135
1673
  const metaPath = path.join(this.upstreamDir, ProviderLoader.META_FILE);
1136
1674
  let prevEtag = '';
@@ -1152,13 +1690,17 @@ export class ProviderLoader {
1152
1690
  return { updated: false };
1153
1691
  }
1154
1692
 
1693
+ // Resolve the tarball target (config → env → vendor default) once so the
1694
+ // HEAD probe and the download below hit the same (possibly self-hosted) URL.
1695
+ const tarballTarget = resolveProviderTarballTarget(this.providerTarballUrl);
1696
+
1155
1697
  try {
1156
1698
  // Step 1: HEAD request to check ETag
1157
1699
  const etag = await new Promise<string>((resolve, reject) => {
1158
1700
  const options = {
1159
1701
  method: 'HEAD',
1160
- hostname: 'github.com',
1161
- path: '/vilmire/adhdev-providers/archive/refs/heads/main.tar.gz',
1702
+ hostname: tarballTarget.hostname,
1703
+ path: tarballTarget.path,
1162
1704
  headers: { 'User-Agent': 'adhdev-launcher' },
1163
1705
  timeout: 10000,
1164
1706
  };
@@ -1203,11 +1745,11 @@ export class ProviderLoader {
1203
1745
  const tmpExtract = path.join(os.tmpdir(), `adhdev-providers-extract-${Date.now()}`);
1204
1746
 
1205
1747
  // Download tarball
1206
- await this.downloadFile(ProviderLoader.GITHUB_TARBALL_URL, tmpTar);
1748
+ await this.downloadFile(tarballTarget.url, tmpTar);
1207
1749
 
1208
1750
  // Extract
1209
1751
  fs.mkdirSync(tmpExtract, { recursive: true });
1210
- execSync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1752
+ await execAsync(`tar -xzf "${tmpTar}" -C "${tmpExtract}"`, { timeout: 30000 });
1211
1753
 
1212
1754
  // Tarball internal structure: adhdev-providers-main/ide/... → strip 1 level
1213
1755
  const extracted = fs.readdirSync(tmpExtract);
@@ -1310,20 +1852,22 @@ export class ProviderLoader {
1310
1852
  etag,
1311
1853
  timestamp,
1312
1854
  lastCheck: new Date(timestamp).toISOString(),
1313
- source: ProviderLoader.GITHUB_TARBALL_URL,
1855
+ source: this.providerTarballUrl,
1314
1856
  }, null, 2));
1315
1857
  } catch { }
1316
1858
  }
1317
1859
 
1318
- /** Count provider files (provider.js or provider.json) */
1860
+ /** Count provider files (provider.v1.json or provider.json — at most one per dir). */
1319
1861
  private countProviders(dir: string): number {
1320
1862
  if (!fs.existsSync(dir)) return 0;
1321
1863
  let count = 0;
1322
1864
  const scan = (d: string) => {
1323
1865
  try {
1324
- for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
1866
+ const entries = fs.readdirSync(d, { withFileTypes: true });
1867
+ const hasManifest = entries.some(e => e.name === 'provider.v1.json' || e.name === 'provider.json');
1868
+ if (hasManifest) count++;
1869
+ for (const entry of entries) {
1325
1870
  if (entry.isDirectory()) scan(path.join(d, entry.name));
1326
- else if (entry.name === 'provider.json') count++;
1327
1871
  }
1328
1872
  } catch { }
1329
1873
  };
@@ -1372,11 +1916,15 @@ export class ProviderLoader {
1372
1916
  return args ? args.map((arg) => /\s/.test(arg) ? JSON.stringify(arg) : arg).join(' ') : '';
1373
1917
  }
1374
1918
  const schemaDef = this.getSettingsSchema(providerType)[key];
1375
- const defaultVal = schemaDef
1376
- ? (key === 'autoApprove' && schemaDef.type === 'boolean'
1377
- ? true
1378
- : schemaDef.default)
1379
- : undefined;
1919
+ // (fix) Previously this hard-coded `autoApprove` boolean default to `true`,
1920
+ // overriding whatever schemaDef.default the provider.json declared. That
1921
+ // surfaced as soon as a provider added an `autoApprove` schema entry with
1922
+ // default=false: the user had never opted in but the daemon treated the
1923
+ // session as auto-approve, which then triggered recordAutoApproval every
1924
+ // time the CLI showed an approval modal — producing a flood of system
1925
+ // "Auto-approved: ..." messages and keeping the session pinned to
1926
+ // generating while modals cycled in and out. Trust the schemaDef.default.
1927
+ const defaultVal = schemaDef ? schemaDef.default : undefined;
1380
1928
 
1381
1929
  const config = this.readConfig();
1382
1930
  const userVal = config?.providerSettings?.[providerType]?.[key];
@@ -1490,10 +2038,17 @@ export class ProviderLoader {
1490
2038
  ...this.getSyntheticSettings(type, provider),
1491
2039
  ...(provider.settings || {}),
1492
2040
  };
2041
+ // (fix) Previously this clause forced `autoApprove.default = true` for any
2042
+ // boolean autoApprove schema, even when the provider.json explicitly set
2043
+ // `default: false`. Combined with the synthetic-settings fallback at
2044
+ // getSyntheticSettings (which also defaults autoApprove to true when the
2045
+ // provider doesn't supply one), that meant CLI providers silently turned on
2046
+ // auto-approval, producing a flood of "Auto-approved: ..." system messages
2047
+ // every time an approval modal appeared and pinning the session to
2048
+ // generating while modals cycled. Trust the provider's declared default.
1493
2049
  if (result.autoApprove?.type === 'boolean') {
1494
2050
  result.autoApprove = {
1495
2051
  ...result.autoApprove,
1496
- default: true,
1497
2052
  public: true,
1498
2053
  label: result.autoApprove.label || 'Auto Approve',
1499
2054
  description: result.autoApprove.description || 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1518,7 +2073,10 @@ export class ProviderLoader {
1518
2073
  if (!provider.settings?.autoApprove) {
1519
2074
  result.autoApprove = {
1520
2075
  type: 'boolean',
1521
- default: true,
2076
+ // (fix) Safe default is *off*. Auto-approving every modal without the
2077
+ // user opting in produced silent-bash-execution surprises and the
2078
+ // "Auto-approved: ..." system-message flood seen on AGY/Codex.
2079
+ default: false,
1522
2080
  public: true,
1523
2081
  label: 'Auto Approve',
1524
2082
  description: 'Automatically approve actionable prompts without sending approval alerts.',
@@ -1581,23 +2139,32 @@ export class ProviderLoader {
1581
2139
  const cat = provider.category;
1582
2140
 
1583
2141
  const searchRoots = this.getProviderRoots();
2142
+ const hasManifest = (dir: string) =>
2143
+ fs.existsSync(path.join(dir, 'provider.v1.json')) || fs.existsSync(path.join(dir, 'provider.json'));
2144
+ const readManifestType = (dir: string): string | null => {
2145
+ for (const file of ['provider.v1.json', 'provider.json']) {
2146
+ const p = path.join(dir, file);
2147
+ if (!fs.existsSync(p)) continue;
2148
+ try {
2149
+ const data = JSON.parse(fs.readFileSync(p, 'utf-8'));
2150
+ if (typeof data?.type === 'string') return data.type;
2151
+ } catch { /* skip */ }
2152
+ }
2153
+ return null;
2154
+ };
1584
2155
  for (const root of searchRoots) {
1585
2156
  if (!fs.existsSync(root)) continue;
1586
2157
  const candidate = this.getProviderDir(root, cat, type);
1587
- if (fs.existsSync(path.join(candidate, 'provider.json'))) return candidate;
2158
+ if (hasManifest(candidate)) return candidate;
1588
2159
  // Scan category dir for type match
1589
2160
  const catDir = path.join(root, cat);
1590
2161
  if (fs.existsSync(catDir)) {
1591
2162
  try {
1592
2163
  for (const entry of fs.readdirSync(catDir, { withFileTypes: true })) {
1593
2164
  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
- }
2165
+ const entryDir = path.join(catDir, entry.name);
2166
+ const manifestType = readManifestType(entryDir);
2167
+ if (manifestType === type) return entryDir;
1601
2168
  }
1602
2169
  } catch { /* skip */ }
1603
2170
  }
@@ -1690,17 +2257,44 @@ export class ProviderLoader {
1690
2257
  return;
1691
2258
  }
1692
2259
 
1693
- // Check if this directory has provider.json
2260
+ // v1-first manifest selection. provider.v1.json (the SDK-shape
2261
+ // manifest with `overrides`, `tui`, `source`, `canonicalHistory`)
2262
+ // wins over provider.json (legacy). Without this branch the v1
2263
+ // file is silently ignored — that's how the codex-cli `overrides`
2264
+ // path and the tui-block builders went un-honored for the first
2265
+ // pass of SDK rollout.
2266
+ const hasV1 = entries.some(e => e.name === 'provider.v1.json');
1694
2267
  const hasJson = entries.some(e => e.name === 'provider.json');
1695
2268
 
1696
- if (hasJson) {
1697
- const jsonPath = path.join(d, 'provider.json');
2269
+ if (hasV1 || hasJson) {
2270
+ const manifestFile = hasV1 ? 'provider.v1.json' : 'provider.json';
2271
+ const jsonPath = path.join(d, manifestFile);
1698
2272
  try {
1699
2273
  const raw = fs.readFileSync(jsonPath, 'utf-8');
1700
2274
  const mod = JSON.parse(raw) as Omit<ProviderModule, 'extensionIdPattern'> & {
1701
2275
  extensionIdPattern?: RegExp | string;
1702
2276
  };
1703
2277
 
2278
+ // Validate v1 manifests against the SDK schema. Failures are
2279
+ // surfaced as a single warning line with all issues attached
2280
+ // so manifest authors don't need to guess which field is wrong.
2281
+ // Loading still proceeds — bricking the daemon on a single
2282
+ // bad field would be worse than running with a known warning.
2283
+ if (hasV1 && mod?.category === 'cli') {
2284
+ try {
2285
+ const { validateCliProviderManifest, formatManifestValidationIssues } =
2286
+ require('./sdk/v1/validators/manifest.js') as typeof import('./sdk/v1/validators/manifest.js');
2287
+ const validation = validateCliProviderManifest(mod);
2288
+ if (!validation.ok) {
2289
+ this.log(`⚠ ${jsonPath}: schema validation failed:\n${formatManifestValidationIssues(validation.issues)}`);
2290
+ }
2291
+ } catch (e: any) {
2292
+ // Validator load failed — log once and continue so a
2293
+ // broken validator can't take down provider loading.
2294
+ this.log(`⚠ ${jsonPath}: validator unavailable: ${e?.message || e}`);
2295
+ }
2296
+ }
2297
+
1704
2298
  // Restore RegExp fields from JSON (extensionIdPattern)
1705
2299
  if (typeof mod.extensionIdPattern === 'string') {
1706
2300
  const flags = mod.extensionIdPattern_flags || '';
@@ -1712,6 +2306,22 @@ export class ProviderLoader {
1712
2306
  ...(extensionIdPattern instanceof RegExp ? { extensionIdPattern } : {}),
1713
2307
  };
1714
2308
 
2309
+ // v1 manifests use `nativeHistory` as the canonical field name.
2310
+ // Legacy v0 manifests use `canonicalHistory`. The daemon's
2311
+ // runtime + downstream code reads `provider.nativeHistory`, so
2312
+ // for legacy manifests we copy `canonicalHistory` into
2313
+ // `nativeHistory` here. We also keep `canonicalHistory`
2314
+ // populated in both directions (deprecated alias) so any
2315
+ // external consumers still reading the old name keep working
2316
+ // during the one-release deprecation window.
2317
+ const nh = (normalizedProvider as any).nativeHistory;
2318
+ const ch = (normalizedProvider as any).canonicalHistory;
2319
+ if (nh && !ch) {
2320
+ (normalizedProvider as any).canonicalHistory = nh;
2321
+ } else if (ch && !nh) {
2322
+ (normalizedProvider as any).nativeHistory = ch;
2323
+ }
2324
+
1715
2325
  const validation = validateProviderDefinition(normalizedProvider);
1716
2326
  for (const warning of validation.warnings) {
1717
2327
  this.log(`⚠ ${jsonPath}: ${warning}`);
@@ -1725,6 +2335,10 @@ export class ProviderLoader {
1725
2335
  const scriptsPath = path.join(d, 'scripts.js');
1726
2336
  if (!hasCompatibility && fs.existsSync(scriptsPath)) {
1727
2337
  try {
2338
+ // Gate the IDE/extension scripts.js (legacy single-file
2339
+ // format) under the same whitelist. `d` here is the
2340
+ // provider dir; its grandparent contains _shared.
2341
+ registerProviderScriptRootSafely(path.dirname(path.dirname(d)));
1728
2342
  delete require.cache[require.resolve(scriptsPath)];
1729
2343
  const scripts = require(scriptsPath) as Partial<ProviderScripts>;
1730
2344
  normalizedProvider.scripts = scripts;
@@ -1733,14 +2347,40 @@ export class ProviderLoader {
1733
2347
  }
1734
2348
  }
1735
2349
 
2350
+ // Classify trust based on which on-disk layer this manifest
2351
+ // came from + whether it ships JavaScript hooks. The dashboard
2352
+ // uses this to render trust badges; non-spec external manifests
2353
+ // need an explicit user confirm before activation.
2354
+ const externalDirAbs = path.join(os.homedir(), '.adhdev', 'external');
2355
+ const layer: 'user' | 'upstream' | 'external' = d.startsWith(externalDirAbs)
2356
+ ? 'external'
2357
+ : (d.startsWith(this.userDir) && !d.includes('.upstream') ? 'user' : 'upstream');
2358
+ try {
2359
+ const { inspectManifestShape, classifyTrust } =
2360
+ require('./provider-trust.js') as typeof import('./provider-trust.js');
2361
+ const shape = inspectManifestShape(mod as Record<string, unknown>);
2362
+ const trust = classifyTrust(layer, shape);
2363
+ (normalizedProvider as any)._sourceLayer = layer;
2364
+ (normalizedProvider as any)._sourceTrust = trust;
2365
+ (normalizedProvider as any)._manifestShape = shape;
2366
+ // For external-namespaced layouts (external/<source>/…) record
2367
+ // which source the manifest came from so dashboards can name
2368
+ // it in the trust badge.
2369
+ if (layer === 'external') {
2370
+ const rel = path.relative(externalDirAbs, d);
2371
+ const firstSeg = rel.split(path.sep)[0];
2372
+ if (firstSeg && firstSeg !== '..') (normalizedProvider as any)._sourceName = firstSeg;
2373
+ }
2374
+ } catch { /* best-effort — trust is enrichment, not gating */ }
2375
+
1736
2376
  const existed = this.providers.has(normalizedProvider.type);
1737
2377
  this.providers.set(normalizedProvider.type, normalizedProvider);
1738
2378
  count++;
1739
- // Identify source tier for debugging
1740
- const source = d.startsWith(this.userDir) && !d.includes('.upstream')
1741
- ? 'user' : 'upstream';
2379
+ const source = (normalizedProvider as any)._sourceLayer ?? 'upstream';
1742
2380
  const overrideWarning = existed && source === 'user' ? ' ⚠ OVERRIDES upstream' : '';
1743
- this.log(` ${existed ? '🔄' : '✅'} ${normalizedProvider.type} (${normalizedProvider.category}) ${normalizedProvider.name} [${source}]${overrideWarning}`);
2381
+ const sourceName = (normalizedProvider as any)._sourceName;
2382
+ const sourceLabel = sourceName ? `${source}/${sourceName}` : source;
2383
+ this.log(` ${existed ? '🔄' : '✅'} ${normalizedProvider.type} (${normalizedProvider.category}) — ${normalizedProvider.name} [${sourceLabel}]${overrideWarning}`);
1744
2384
  }
1745
2385
  } catch (e) {
1746
2386
  this.log(`⚠ Failed to load ${jsonPath}: ${(e as Error).message}`);
@@ -1752,6 +2392,11 @@ export class ProviderLoader {
1752
2392
  for (const entry of entries) {
1753
2393
  if (!entry.isDirectory()) continue;
1754
2394
  if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
2395
+ // `examples/` is a documentation / scaffold tree (e.g. stub-cli),
2396
+ // not a real provider source. SDK authors copy from here when
2397
+ // writing a new provider; daemon-core tests reference the
2398
+ // manifest by path. Keep it off the dashboard's provider list.
2399
+ if (d === dir && entry.name === 'examples') continue;
1755
2400
  if (excludeDirs && d === dir && excludeDirs.includes(entry.name)) continue;
1756
2401
  scan(path.join(d, entry.name));
1757
2402
  }