@viewportai/daemon 0.1.0

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 (350) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +157 -0
  3. package/bin/vpd.js +3 -0
  4. package/dist/adapters/claude.d.ts +119 -0
  5. package/dist/adapters/claude.d.ts.map +1 -0
  6. package/dist/adapters/claude.js +621 -0
  7. package/dist/adapters/claude.js.map +1 -0
  8. package/dist/adapters/codex-event-normalizers.d.ts +10 -0
  9. package/dist/adapters/codex-event-normalizers.d.ts.map +1 -0
  10. package/dist/adapters/codex-event-normalizers.js +208 -0
  11. package/dist/adapters/codex-event-normalizers.js.map +1 -0
  12. package/dist/adapters/codex-sdk-loader.d.ts +47 -0
  13. package/dist/adapters/codex-sdk-loader.d.ts.map +1 -0
  14. package/dist/adapters/codex-sdk-loader.js +19 -0
  15. package/dist/adapters/codex-sdk-loader.js.map +1 -0
  16. package/dist/adapters/codex.d.ts +56 -0
  17. package/dist/adapters/codex.d.ts.map +1 -0
  18. package/dist/adapters/codex.js +267 -0
  19. package/dist/adapters/codex.js.map +1 -0
  20. package/dist/adapters/gemini-cli.d.ts +49 -0
  21. package/dist/adapters/gemini-cli.d.ts.map +1 -0
  22. package/dist/adapters/gemini-cli.js +181 -0
  23. package/dist/adapters/gemini-cli.js.map +1 -0
  24. package/dist/adapters/pty.d.ts +56 -0
  25. package/dist/adapters/pty.d.ts.map +1 -0
  26. package/dist/adapters/pty.js +223 -0
  27. package/dist/adapters/pty.js.map +1 -0
  28. package/dist/agents/aider.d.ts +9 -0
  29. package/dist/agents/aider.d.ts.map +1 -0
  30. package/dist/agents/aider.js +37 -0
  31. package/dist/agents/aider.js.map +1 -0
  32. package/dist/agents/built-in.d.ts +4 -0
  33. package/dist/agents/built-in.d.ts.map +1 -0
  34. package/dist/agents/built-in.js +6 -0
  35. package/dist/agents/built-in.js.map +1 -0
  36. package/dist/agents/claude.d.ts +12 -0
  37. package/dist/agents/claude.d.ts.map +1 -0
  38. package/dist/agents/claude.js +93 -0
  39. package/dist/agents/claude.js.map +1 -0
  40. package/dist/agents/codex.d.ts +6 -0
  41. package/dist/agents/codex.d.ts.map +1 -0
  42. package/dist/agents/codex.js +64 -0
  43. package/dist/agents/codex.js.map +1 -0
  44. package/dist/agents/command-detection.d.ts +6 -0
  45. package/dist/agents/command-detection.d.ts.map +1 -0
  46. package/dist/agents/command-detection.js +12 -0
  47. package/dist/agents/command-detection.js.map +1 -0
  48. package/dist/agents/gemini.d.ts +6 -0
  49. package/dist/agents/gemini.d.ts.map +1 -0
  50. package/dist/agents/gemini.js +36 -0
  51. package/dist/agents/gemini.js.map +1 -0
  52. package/dist/cli/agent-commands.d.ts +2 -0
  53. package/dist/cli/agent-commands.d.ts.map +1 -0
  54. package/dist/cli/agent-commands.js +87 -0
  55. package/dist/cli/agent-commands.js.map +1 -0
  56. package/dist/cli/args.d.ts +9 -0
  57. package/dist/cli/args.d.ts.map +1 -0
  58. package/dist/cli/args.js +34 -0
  59. package/dist/cli/args.js.map +1 -0
  60. package/dist/cli/command-shared.d.ts +53 -0
  61. package/dist/cli/command-shared.d.ts.map +1 -0
  62. package/dist/cli/command-shared.js +239 -0
  63. package/dist/cli/command-shared.js.map +1 -0
  64. package/dist/cli/commands.d.ts +20 -0
  65. package/dist/cli/commands.d.ts.map +1 -0
  66. package/dist/cli/commands.js +20 -0
  67. package/dist/cli/commands.js.map +1 -0
  68. package/dist/cli/daemon-client.d.ts +30 -0
  69. package/dist/cli/daemon-client.d.ts.map +1 -0
  70. package/dist/cli/daemon-client.js +161 -0
  71. package/dist/cli/daemon-client.js.map +1 -0
  72. package/dist/cli/daemon-lifecycle.d.ts +47 -0
  73. package/dist/cli/daemon-lifecycle.d.ts.map +1 -0
  74. package/dist/cli/daemon-lifecycle.js +262 -0
  75. package/dist/cli/daemon-lifecycle.js.map +1 -0
  76. package/dist/cli/daemon-settings.d.ts +9 -0
  77. package/dist/cli/daemon-settings.d.ts.map +1 -0
  78. package/dist/cli/daemon-settings.js +168 -0
  79. package/dist/cli/daemon-settings.js.map +1 -0
  80. package/dist/cli/directory-commands.d.ts +4 -0
  81. package/dist/cli/directory-commands.d.ts.map +1 -0
  82. package/dist/cli/directory-commands.js +190 -0
  83. package/dist/cli/directory-commands.js.map +1 -0
  84. package/dist/cli/hook-command.d.ts +14 -0
  85. package/dist/cli/hook-command.d.ts.map +1 -0
  86. package/dist/cli/hook-command.js +96 -0
  87. package/dist/cli/hook-command.js.map +1 -0
  88. package/dist/cli/install-command.d.ts +2 -0
  89. package/dist/cli/install-command.d.ts.map +1 -0
  90. package/dist/cli/install-command.js +91 -0
  91. package/dist/cli/install-command.js.map +1 -0
  92. package/dist/cli/lifecycle-commands.d.ts +10 -0
  93. package/dist/cli/lifecycle-commands.d.ts.map +1 -0
  94. package/dist/cli/lifecycle-commands.js +524 -0
  95. package/dist/cli/lifecycle-commands.js.map +1 -0
  96. package/dist/cli/listen-target.d.ts +13 -0
  97. package/dist/cli/listen-target.d.ts.map +1 -0
  98. package/dist/cli/listen-target.js +102 -0
  99. package/dist/cli/listen-target.js.map +1 -0
  100. package/dist/cli/orchestration-commands.d.ts +8 -0
  101. package/dist/cli/orchestration-commands.d.ts.map +1 -0
  102. package/dist/cli/orchestration-commands.js +340 -0
  103. package/dist/cli/orchestration-commands.js.map +1 -0
  104. package/dist/cli/permission-commands.d.ts +2 -0
  105. package/dist/cli/permission-commands.d.ts.map +1 -0
  106. package/dist/cli/permission-commands.js +138 -0
  107. package/dist/cli/permission-commands.js.map +1 -0
  108. package/dist/cli/runtime-toolchain.d.ts +35 -0
  109. package/dist/cli/runtime-toolchain.d.ts.map +1 -0
  110. package/dist/cli/runtime-toolchain.js +184 -0
  111. package/dist/cli/runtime-toolchain.js.map +1 -0
  112. package/dist/cli/service-commands.d.ts +19 -0
  113. package/dist/cli/service-commands.d.ts.map +1 -0
  114. package/dist/cli/service-commands.js +273 -0
  115. package/dist/cli/service-commands.js.map +1 -0
  116. package/dist/cli/session-commands.d.ts +3 -0
  117. package/dist/cli/session-commands.d.ts.map +1 -0
  118. package/dist/cli/session-commands.js +146 -0
  119. package/dist/cli/session-commands.js.map +1 -0
  120. package/dist/cli/setup-command.d.ts +12 -0
  121. package/dist/cli/setup-command.d.ts.map +1 -0
  122. package/dist/cli/setup-command.js +223 -0
  123. package/dist/cli/setup-command.js.map +1 -0
  124. package/dist/cli/supervisor-protocol.d.ts +20 -0
  125. package/dist/cli/supervisor-protocol.d.ts.map +1 -0
  126. package/dist/cli/supervisor-protocol.js +5 -0
  127. package/dist/cli/supervisor-protocol.js.map +1 -0
  128. package/dist/cli/supervisor.d.ts +10 -0
  129. package/dist/cli/supervisor.d.ts.map +1 -0
  130. package/dist/cli/supervisor.js +218 -0
  131. package/dist/cli/supervisor.js.map +1 -0
  132. package/dist/cli/worktree-commands.d.ts +2 -0
  133. package/dist/cli/worktree-commands.d.ts.map +1 -0
  134. package/dist/cli/worktree-commands.js +250 -0
  135. package/dist/cli/worktree-commands.js.map +1 -0
  136. package/dist/cli/ws-client.d.ts +20 -0
  137. package/dist/cli/ws-client.d.ts.map +1 -0
  138. package/dist/cli/ws-client.js +103 -0
  139. package/dist/cli/ws-client.js.map +1 -0
  140. package/dist/core/agent-registry.d.ts +128 -0
  141. package/dist/core/agent-registry.d.ts.map +1 -0
  142. package/dist/core/agent-registry.js +131 -0
  143. package/dist/core/agent-registry.js.map +1 -0
  144. package/dist/core/config-schema.d.ts +77 -0
  145. package/dist/core/config-schema.d.ts.map +1 -0
  146. package/dist/core/config-schema.js +66 -0
  147. package/dist/core/config-schema.js.map +1 -0
  148. package/dist/core/config.d.ts +111 -0
  149. package/dist/core/config.d.ts.map +1 -0
  150. package/dist/core/config.js +244 -0
  151. package/dist/core/config.js.map +1 -0
  152. package/dist/core/daemon.d.ts +113 -0
  153. package/dist/core/daemon.d.ts.map +1 -0
  154. package/dist/core/daemon.js +197 -0
  155. package/dist/core/daemon.js.map +1 -0
  156. package/dist/core/discovered-sessions.d.ts +7 -0
  157. package/dist/core/discovered-sessions.d.ts.map +1 -0
  158. package/dist/core/discovered-sessions.js +39 -0
  159. package/dist/core/discovered-sessions.js.map +1 -0
  160. package/dist/core/error-codes.d.ts +31 -0
  161. package/dist/core/error-codes.d.ts.map +1 -0
  162. package/dist/core/error-codes.js +30 -0
  163. package/dist/core/error-codes.js.map +1 -0
  164. package/dist/core/errors.d.ts +16 -0
  165. package/dist/core/errors.d.ts.map +1 -0
  166. package/dist/core/errors.js +43 -0
  167. package/dist/core/errors.js.map +1 -0
  168. package/dist/core/events.d.ts +183 -0
  169. package/dist/core/events.d.ts.map +1 -0
  170. package/dist/core/events.js +61 -0
  171. package/dist/core/events.js.map +1 -0
  172. package/dist/core/interfaces.d.ts +115 -0
  173. package/dist/core/interfaces.d.ts.map +1 -0
  174. package/dist/core/interfaces.js +9 -0
  175. package/dist/core/interfaces.js.map +1 -0
  176. package/dist/core/logger.d.ts +11 -0
  177. package/dist/core/logger.d.ts.map +1 -0
  178. package/dist/core/logger.js +17 -0
  179. package/dist/core/logger.js.map +1 -0
  180. package/dist/core/metrics.d.ts +24 -0
  181. package/dist/core/metrics.d.ts.map +1 -0
  182. package/dist/core/metrics.js +32 -0
  183. package/dist/core/metrics.js.map +1 -0
  184. package/dist/core/output.d.ts +13 -0
  185. package/dist/core/output.d.ts.map +1 -0
  186. package/dist/core/output.js +22 -0
  187. package/dist/core/output.js.map +1 -0
  188. package/dist/core/permission-coordinator.d.ts +67 -0
  189. package/dist/core/permission-coordinator.d.ts.map +1 -0
  190. package/dist/core/permission-coordinator.js +209 -0
  191. package/dist/core/permission-coordinator.js.map +1 -0
  192. package/dist/core/session-manager.d.ts +121 -0
  193. package/dist/core/session-manager.d.ts.map +1 -0
  194. package/dist/core/session-manager.js +354 -0
  195. package/dist/core/session-manager.js.map +1 -0
  196. package/dist/core/session-state-file.d.ts +20 -0
  197. package/dist/core/session-state-file.d.ts.map +1 -0
  198. package/dist/core/session-state-file.js +49 -0
  199. package/dist/core/session-state-file.js.map +1 -0
  200. package/dist/core/types.d.ts +250 -0
  201. package/dist/core/types.d.ts.map +1 -0
  202. package/dist/core/types.js +88 -0
  203. package/dist/core/types.js.map +1 -0
  204. package/dist/directories/manager.d.ts +32 -0
  205. package/dist/directories/manager.d.ts.map +1 -0
  206. package/dist/directories/manager.js +86 -0
  207. package/dist/directories/manager.js.map +1 -0
  208. package/dist/discovery/claude.d.ts +29 -0
  209. package/dist/discovery/claude.d.ts.map +1 -0
  210. package/dist/discovery/claude.js +55 -0
  211. package/dist/discovery/claude.js.map +1 -0
  212. package/dist/discovery/codex.d.ts +11 -0
  213. package/dist/discovery/codex.d.ts.map +1 -0
  214. package/dist/discovery/codex.js +365 -0
  215. package/dist/discovery/codex.js.map +1 -0
  216. package/dist/discovery/gemini.d.ts +15 -0
  217. package/dist/discovery/gemini.d.ts.map +1 -0
  218. package/dist/discovery/gemini.js +151 -0
  219. package/dist/discovery/gemini.js.map +1 -0
  220. package/dist/discovery/jsonl-reader.d.ts +122 -0
  221. package/dist/discovery/jsonl-reader.d.ts.map +1 -0
  222. package/dist/discovery/jsonl-reader.js +622 -0
  223. package/dist/discovery/jsonl-reader.js.map +1 -0
  224. package/dist/discovery/watcher.d.ts +29 -0
  225. package/dist/discovery/watcher.d.ts.map +1 -0
  226. package/dist/discovery/watcher.js +383 -0
  227. package/dist/discovery/watcher.js.map +1 -0
  228. package/dist/hooks/index.d.ts +10 -0
  229. package/dist/hooks/index.d.ts.map +1 -0
  230. package/dist/hooks/index.js +8 -0
  231. package/dist/hooks/index.js.map +1 -0
  232. package/dist/hooks/installers/base.d.ts +27 -0
  233. package/dist/hooks/installers/base.d.ts.map +1 -0
  234. package/dist/hooks/installers/base.js +9 -0
  235. package/dist/hooks/installers/base.js.map +1 -0
  236. package/dist/hooks/installers/claude.d.ts +18 -0
  237. package/dist/hooks/installers/claude.d.ts.map +1 -0
  238. package/dist/hooks/installers/claude.js +120 -0
  239. package/dist/hooks/installers/claude.js.map +1 -0
  240. package/dist/hooks/router.d.ts +63 -0
  241. package/dist/hooks/router.d.ts.map +1 -0
  242. package/dist/hooks/router.js +259 -0
  243. package/dist/hooks/router.js.map +1 -0
  244. package/dist/hooks/supervision.d.ts +27 -0
  245. package/dist/hooks/supervision.d.ts.map +1 -0
  246. package/dist/hooks/supervision.js +67 -0
  247. package/dist/hooks/supervision.js.map +1 -0
  248. package/dist/hooks/types.d.ts +171 -0
  249. package/dist/hooks/types.d.ts.map +1 -0
  250. package/dist/hooks/types.js +148 -0
  251. package/dist/hooks/types.js.map +1 -0
  252. package/dist/index.d.ts +17 -0
  253. package/dist/index.d.ts.map +1 -0
  254. package/dist/index.js +117 -0
  255. package/dist/index.js.map +1 -0
  256. package/dist/permissions/engine.d.ts +23 -0
  257. package/dist/permissions/engine.d.ts.map +1 -0
  258. package/dist/permissions/engine.js +43 -0
  259. package/dist/permissions/engine.js.map +1 -0
  260. package/dist/plugins/loader.d.ts +44 -0
  261. package/dist/plugins/loader.d.ts.map +1 -0
  262. package/dist/plugins/loader.js +177 -0
  263. package/dist/plugins/loader.js.map +1 -0
  264. package/dist/server/auth.d.ts +32 -0
  265. package/dist/server/auth.d.ts.map +1 -0
  266. package/dist/server/auth.js +78 -0
  267. package/dist/server/auth.js.map +1 -0
  268. package/dist/server/discovered-watch-key.d.ts +5 -0
  269. package/dist/server/discovered-watch-key.d.ts.map +1 -0
  270. package/dist/server/discovered-watch-key.js +31 -0
  271. package/dist/server/discovered-watch-key.js.map +1 -0
  272. package/dist/server/hello-builder.d.ts +17 -0
  273. package/dist/server/hello-builder.d.ts.map +1 -0
  274. package/dist/server/hello-builder.js +71 -0
  275. package/dist/server/hello-builder.js.map +1 -0
  276. package/dist/server/http-server.d.ts +30 -0
  277. package/dist/server/http-server.d.ts.map +1 -0
  278. package/dist/server/http-server.js +561 -0
  279. package/dist/server/http-server.js.map +1 -0
  280. package/dist/server/message-normalizers.d.ts +11 -0
  281. package/dist/server/message-normalizers.d.ts.map +1 -0
  282. package/dist/server/message-normalizers.js +104 -0
  283. package/dist/server/message-normalizers.js.map +1 -0
  284. package/dist/server/pairing-offers.d.ts +78 -0
  285. package/dist/server/pairing-offers.d.ts.map +1 -0
  286. package/dist/server/pairing-offers.js +502 -0
  287. package/dist/server/pairing-offers.js.map +1 -0
  288. package/dist/server/rate-limiter.d.ts +21 -0
  289. package/dist/server/rate-limiter.d.ts.map +1 -0
  290. package/dist/server/rate-limiter.js +61 -0
  291. package/dist/server/rate-limiter.js.map +1 -0
  292. package/dist/server/ring-buffer.d.ts +34 -0
  293. package/dist/server/ring-buffer.d.ts.map +1 -0
  294. package/dist/server/ring-buffer.js +73 -0
  295. package/dist/server/ring-buffer.js.map +1 -0
  296. package/dist/server/security.d.ts +22 -0
  297. package/dist/server/security.d.ts.map +1 -0
  298. package/dist/server/security.js +123 -0
  299. package/dist/server/security.js.map +1 -0
  300. package/dist/server/ws-command-handlers.d.ts +25 -0
  301. package/dist/server/ws-command-handlers.d.ts.map +1 -0
  302. package/dist/server/ws-command-handlers.js +218 -0
  303. package/dist/server/ws-command-handlers.js.map +1 -0
  304. package/dist/server/ws-daemon-event-bridge.d.ts +22 -0
  305. package/dist/server/ws-daemon-event-bridge.d.ts.map +1 -0
  306. package/dist/server/ws-daemon-event-bridge.js +321 -0
  307. package/dist/server/ws-daemon-event-bridge.js.map +1 -0
  308. package/dist/server/ws-limits.d.ts +2 -0
  309. package/dist/server/ws-limits.d.ts.map +1 -0
  310. package/dist/server/ws-limits.js +12 -0
  311. package/dist/server/ws-limits.js.map +1 -0
  312. package/dist/server/ws-protocol.d.ts +248 -0
  313. package/dist/server/ws-protocol.d.ts.map +1 -0
  314. package/dist/server/ws-protocol.js +157 -0
  315. package/dist/server/ws-protocol.js.map +1 -0
  316. package/dist/server/ws-server.d.ts +26 -0
  317. package/dist/server/ws-server.d.ts.map +1 -0
  318. package/dist/server/ws-server.js +290 -0
  319. package/dist/server/ws-server.js.map +1 -0
  320. package/dist/startup-agents.d.ts +6 -0
  321. package/dist/startup-agents.d.ts.map +1 -0
  322. package/dist/startup-agents.js +97 -0
  323. package/dist/startup-agents.js.map +1 -0
  324. package/dist/startup-prereqs.d.ts +29 -0
  325. package/dist/startup-prereqs.d.ts.map +1 -0
  326. package/dist/startup-prereqs.js +209 -0
  327. package/dist/startup-prereqs.js.map +1 -0
  328. package/dist/startup-watchers.d.ts +7 -0
  329. package/dist/startup-watchers.d.ts.map +1 -0
  330. package/dist/startup-watchers.js +196 -0
  331. package/dist/startup-watchers.js.map +1 -0
  332. package/dist/startup.d.ts +20 -0
  333. package/dist/startup.d.ts.map +1 -0
  334. package/dist/startup.js +335 -0
  335. package/dist/startup.js.map +1 -0
  336. package/dist/tracking/git-tracker.d.ts +52 -0
  337. package/dist/tracking/git-tracker.d.ts.map +1 -0
  338. package/dist/tracking/git-tracker.js +277 -0
  339. package/dist/tracking/git-tracker.js.map +1 -0
  340. package/dist/tracking/noop-tracker.d.ts +27 -0
  341. package/dist/tracking/noop-tracker.d.ts.map +1 -0
  342. package/dist/tracking/noop-tracker.js +42 -0
  343. package/dist/tracking/noop-tracker.js.map +1 -0
  344. package/docs/configuration.md +75 -0
  345. package/docs/developer-workflows.md +107 -0
  346. package/docs/protocol-matrix.json +155 -0
  347. package/docs/releasing.md +65 -0
  348. package/docs/security.md +48 -0
  349. package/docs/testing.md +112 -0
  350. package/package.json +84 -0
@@ -0,0 +1,290 @@
1
+ /**
2
+ * WebSocket server — real-time protocol for monitoring and controlling sessions.
3
+ *
4
+ * Implements the Viewport wire protocol (plan/03_protocol.md):
5
+ * - hello, launch, kill, prompt, respond-permission
6
+ * - subscribe/unsubscribe with reconnect replay via ring buffer
7
+ * - session-update with sequence numbers
8
+ * - rollback, branch-retry, squash-merge
9
+ * - ack responses for all client commands
10
+ */
11
+ import { logger } from '../core/logger.js';
12
+ import { RingBuffer } from './ring-buffer.js';
13
+ import { RateLimiter } from './rate-limiter.js';
14
+ import { IncomingMessageSchema } from './ws-protocol.js';
15
+ import { sendHello } from './hello-builder.js';
16
+ import { ViewportError } from '../core/errors.js';
17
+ import { createWsCommandHandlers } from './ws-command-handlers.js';
18
+ import { metrics } from '../core/metrics.js';
19
+ import { registerWsDaemonEventBridge } from './ws-daemon-event-bridge.js';
20
+ import { extractTokenFromRequest } from './auth.js';
21
+ import { isHostAllowed, isOriginAllowed } from './security.js';
22
+ import { ErrorCodes } from '../core/error-codes.js';
23
+ import { resolveMaxWsClients } from './ws-limits.js';
24
+ const log = logger.child({ module: 'ws-server' });
25
+ const MAX_WS_MESSAGE_BYTES = 1_048_576;
26
+ const MAX_CLIENT_PENDING_BYTES = 4 * 1_048_576;
27
+ const COMMAND_TIMEOUT_MS = 60_000;
28
+ export function registerWsServer(app, daemon, registry, wsOptions) {
29
+ const hookRouter = wsOptions?.hookRouter;
30
+ const supervision = wsOptions?.supervision;
31
+ const auth = wsOptions?.auth;
32
+ const securityProfile = wsOptions?.securityProfile;
33
+ const ringBuffers = new Map();
34
+ const clients = new Set();
35
+ const rateLimiter = new RateLimiter();
36
+ let clientIdCounter = 0;
37
+ const maxWsClients = resolveMaxWsClients(wsOptions?.maxClients);
38
+ // Streaming state tracking: per-session flag for whether chunks are flowing
39
+ const sessionStreaming = new Map();
40
+ // Backpressure: drop non-critical updates for slow clients
41
+ const HIGH_WATERMARK_BYTES = 1024 * 1024;
42
+ function getOrCreateBuffer(sessionId) {
43
+ let buffer = ringBuffers.get(sessionId);
44
+ if (!buffer) {
45
+ buffer = new RingBuffer();
46
+ ringBuffers.set(sessionId, buffer);
47
+ }
48
+ return buffer;
49
+ }
50
+ function broadcastUpdate(sessionId, update) {
51
+ const buffer = getOrCreateBuffer(sessionId);
52
+ const entry = buffer.push(sessionId, update);
53
+ const msg = JSON.stringify({
54
+ type: 'session-update',
55
+ sessionId,
56
+ seq: entry.seq,
57
+ update,
58
+ });
59
+ const isDroppable = update.updateType === 'agent-thought-chunk' || update.updateType === 'agent-message-chunk';
60
+ for (const client of clients) {
61
+ if (client.subscriptions.has(sessionId)) {
62
+ if (isDroppable && client.pendingBytes > HIGH_WATERMARK_BYTES) {
63
+ log.debug({ sessionId, pendingBytes: client.pendingBytes }, 'Dropping chunk for slow client');
64
+ continue;
65
+ }
66
+ client.send(msg);
67
+ }
68
+ }
69
+ metrics.increment('ws.updates.broadcast');
70
+ }
71
+ function sendAck(client, requestId, status, error, extra) {
72
+ if (!requestId)
73
+ return;
74
+ const msg = { type: 'ack', requestId, status };
75
+ if (error)
76
+ msg.error = error;
77
+ if (extra)
78
+ Object.assign(msg, extra);
79
+ client.send(JSON.stringify(msg));
80
+ }
81
+ const cleanupBridge = registerWsDaemonEventBridge({
82
+ daemon,
83
+ registry,
84
+ clients,
85
+ ringBuffers,
86
+ sessionStreaming,
87
+ broadcastUpdate,
88
+ hookRouter,
89
+ supervision,
90
+ });
91
+ // ---------------------------------------------------------------------------
92
+ // Message handlers + dispatch
93
+ // ---------------------------------------------------------------------------
94
+ const handlers = createWsCommandHandlers({
95
+ daemon,
96
+ hookRouter,
97
+ supervision,
98
+ sendAck,
99
+ getOrCreateBuffer,
100
+ });
101
+ async function handleMessage(client, msg) {
102
+ try {
103
+ const handler = handlers[msg.type];
104
+ await withTimeout(handler(client, msg), COMMAND_TIMEOUT_MS);
105
+ }
106
+ catch (err) {
107
+ log.error({ type: msg.type, err }, 'WS handler error');
108
+ if (isTimeoutError(err)) {
109
+ metrics.increment('ws.messages.handler_timeout');
110
+ }
111
+ const errorCode = err instanceof ViewportError ? err.code : 'INTERNAL_ERROR';
112
+ const effectiveErrorCode = isTimeoutError(err) ? ErrorCodes.COMMAND_TIMEOUT : errorCode;
113
+ sendAck(client, msg.requestId, 'error', err instanceof Error ? err.message : 'Unknown error', { errorCode: effectiveErrorCode });
114
+ }
115
+ }
116
+ // ---------------------------------------------------------------------------
117
+ // WebSocket route
118
+ // ---------------------------------------------------------------------------
119
+ app.get('/ws', { websocket: true }, async (socketRaw, request) => {
120
+ const socket = socketRaw;
121
+ if (clients.size >= maxWsClients) {
122
+ metrics.increment('ws.connections.rejected.max_clients');
123
+ socket.terminate();
124
+ return;
125
+ }
126
+ if (securityProfile) {
127
+ const hostHeader = request.headers.host;
128
+ const originHeader = typeof request.headers.origin === 'string' ? request.headers.origin : undefined;
129
+ const hostAllowed = isHostAllowed(hostHeader, securityProfile);
130
+ const originAllowed = isOriginAllowed(originHeader, securityProfile);
131
+ if (!hostAllowed || !originAllowed) {
132
+ metrics.increment('ws.connections.rejected.security_profile');
133
+ socket.terminate();
134
+ return;
135
+ }
136
+ }
137
+ if (auth) {
138
+ const token = extractTokenFromRequest({
139
+ authorization: typeof request.headers.authorization === 'string'
140
+ ? request.headers.authorization
141
+ : undefined,
142
+ url: request.url,
143
+ allowQueryToken: true,
144
+ });
145
+ if (!token || !(await auth.validate(token))) {
146
+ metrics.increment('ws.connections.rejected.unauthorized');
147
+ socket.terminate();
148
+ return;
149
+ }
150
+ }
151
+ const clientId = String(++clientIdCounter);
152
+ const client = {
153
+ send: (data) => {
154
+ try {
155
+ if (client.pendingBytes > MAX_CLIENT_PENDING_BYTES) {
156
+ log.warn({ clientId, pendingBytes: client.pendingBytes }, 'Terminating slow client due to sustained backpressure');
157
+ socket.terminate();
158
+ return;
159
+ }
160
+ const bytes = Buffer.byteLength(data);
161
+ if (bytes > MAX_WS_MESSAGE_BYTES) {
162
+ metrics.increment('ws.messages.rejected_outbound_too_large');
163
+ log.warn({ clientId, bytes, limit: MAX_WS_MESSAGE_BYTES }, 'Dropping oversized outbound WS payload');
164
+ return;
165
+ }
166
+ client.pendingBytes += bytes;
167
+ socket.send(data, () => {
168
+ client.pendingBytes -= bytes;
169
+ });
170
+ }
171
+ catch (err) {
172
+ log.debug({ clientId, err }, 'WS send failed (client may have disconnected)');
173
+ }
174
+ },
175
+ subscriptions: new Set(),
176
+ watchedDiscoveredSessions: new Set(),
177
+ pendingBytes: 0,
178
+ };
179
+ clients.add(client);
180
+ metrics.increment('ws.connections.total');
181
+ metrics.gauge('ws.clients.connected', clients.size);
182
+ sendHello(client, daemon, registry);
183
+ // Keepalive: ping every 30s, terminate if pong not received
184
+ let alive = true;
185
+ const pingInterval = setInterval(() => {
186
+ if (!alive) {
187
+ log.debug({ clientId }, 'Client failed pong — terminating');
188
+ socket.terminate();
189
+ return;
190
+ }
191
+ alive = false;
192
+ socket.ping();
193
+ }, 30_000);
194
+ socket.on('pong', () => {
195
+ alive = true;
196
+ });
197
+ socket.on('message', async (raw) => {
198
+ const rawSize = typeof raw === 'string' ? Buffer.byteLength(raw) : raw.length;
199
+ metrics.increment('ws.messages.received');
200
+ metrics.gauge('ws.last_message_bytes', rawSize);
201
+ if (rawSize > MAX_WS_MESSAGE_BYTES) {
202
+ metrics.increment('ws.messages.rejected_too_large');
203
+ client.send(JSON.stringify({
204
+ type: 'ack',
205
+ status: 'error',
206
+ error: `Payload exceeds ${MAX_WS_MESSAGE_BYTES} byte limit`,
207
+ errorCode: ErrorCodes.PAYLOAD_TOO_LARGE,
208
+ }));
209
+ return;
210
+ }
211
+ let parsed;
212
+ try {
213
+ parsed = JSON.parse(typeof raw === 'string' ? raw : raw.toString('utf-8'));
214
+ }
215
+ catch {
216
+ metrics.increment('ws.messages.invalid_json');
217
+ client.send(JSON.stringify({
218
+ type: 'ack',
219
+ status: 'error',
220
+ error: 'Invalid JSON',
221
+ errorCode: ErrorCodes.INVALID_JSON,
222
+ }));
223
+ return;
224
+ }
225
+ if (parsed &&
226
+ typeof parsed === 'object' &&
227
+ typeof parsed['type'] === 'string' &&
228
+ typeof parsed['requestId'] !== 'string') {
229
+ metrics.increment('ws.messages.missing_request_id');
230
+ client.send(JSON.stringify({
231
+ type: 'ack',
232
+ status: 'error',
233
+ error: 'Missing required requestId',
234
+ errorCode: ErrorCodes.MISSING_REQUEST_ID,
235
+ }));
236
+ return;
237
+ }
238
+ const result = IncomingMessageSchema.safeParse(parsed);
239
+ if (!result.success) {
240
+ metrics.increment('ws.messages.invalid_schema');
241
+ const reqId = parsed?.requestId;
242
+ sendAck(client, typeof reqId === 'string' ? reqId : undefined, 'error', `Invalid message: ${result.error.issues[0]?.message ?? 'validation failed'}`, { errorCode: ErrorCodes.INVALID_MESSAGE });
243
+ return;
244
+ }
245
+ // Rate limit check
246
+ if (!rateLimiter.check(clientId, result.data.type)) {
247
+ metrics.increment('ws.messages.rate_limited');
248
+ sendAck(client, result.data.requestId, 'error', `Rate limited: too many ${result.data.type} requests`, { errorCode: ErrorCodes.RATE_LIMITED });
249
+ return;
250
+ }
251
+ await handleMessage(client, result.data);
252
+ });
253
+ socket.on('close', () => {
254
+ clearInterval(pingInterval);
255
+ clients.delete(client);
256
+ metrics.gauge('ws.clients.connected', clients.size);
257
+ rateLimiter.removeClient(clientId);
258
+ // Release supervision — if this was the last supervisor for a session,
259
+ // any pending hook permission requests will fall through to the terminal.
260
+ if (supervision) {
261
+ const released = supervision.removeClient(client);
262
+ for (const sessionId of released) {
263
+ hookRouter?.releaseSession(sessionId);
264
+ }
265
+ }
266
+ });
267
+ });
268
+ // Clean up intervals when Fastify shuts down
269
+ app.addHook('onClose', () => {
270
+ cleanupBridge();
271
+ });
272
+ }
273
+ function withTimeout(promise, timeoutMs) {
274
+ return new Promise((resolve, reject) => {
275
+ const timer = setTimeout(() => {
276
+ reject(new Error(`Command timed out after ${timeoutMs}ms`));
277
+ }, timeoutMs);
278
+ promise.then((value) => {
279
+ clearTimeout(timer);
280
+ resolve(value);
281
+ }, (err) => {
282
+ clearTimeout(timer);
283
+ reject(err);
284
+ });
285
+ });
286
+ }
287
+ function isTimeoutError(err) {
288
+ return err instanceof Error && err.message.startsWith('Command timed out after ');
289
+ }
290
+ //# sourceMappingURL=ws-server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-server.js","sourceRoot":"","sources":["../../src/server/ws-server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ import type { Daemon } from './core/daemon.js';
2
+ import { AgentRegistry } from './core/agent-registry.js';
3
+ export declare function loadAgents(daemon: Daemon): Promise<AgentRegistry>;
4
+ export declare function decodeAutoRegisterEntry(entry: string): string;
5
+ export declare function autoRegisterDirectories(daemon: Daemon, registry: AgentRegistry): Promise<void>;
6
+ //# sourceMappingURL=startup-agents.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-agents.d.ts","sourceRoot":"","sources":["../src/startup-agents.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAKzD,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CA4DvE;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,aAAa,GACtB,OAAO,CAAC,IAAI,CAAC,CAgCf"}
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { logger } from './core/output.js';
4
+ import { AgentRegistry } from './core/agent-registry.js';
5
+ import { decodeProjectDir } from './discovery/jsonl-reader.js';
6
+ import { BUILT_IN_AGENTS } from './agents/built-in.js';
7
+ import { loadPluginAgents } from './plugins/loader.js';
8
+ export async function loadAgents(daemon) {
9
+ const registry = new AgentRegistry();
10
+ for (const def of BUILT_IN_AGENTS) {
11
+ registry.register(def);
12
+ }
13
+ try {
14
+ const plugins = await loadPluginAgents({ projectDir: process.cwd() });
15
+ for (const plugin of plugins) {
16
+ registry.register(plugin.definition);
17
+ logger.log(`Plugin ${plugin.manifest.name}: loaded (${plugin.definition.id})`);
18
+ }
19
+ }
20
+ catch (err) {
21
+ logger.warn('Plugin load failed:', err);
22
+ }
23
+ daemon.configManager.setAgentRegistry(registry);
24
+ const availability = await registry.detectAvailable();
25
+ for (const def of registry.getAll()) {
26
+ const available = availability.get(def.id) ?? false;
27
+ const discoveryIndependent = def.id === 'codex';
28
+ let discoveryRegistered = false;
29
+ if (def.createDiscovery && (available || discoveryIndependent)) {
30
+ try {
31
+ const discovery = await def.createDiscovery();
32
+ if (discovery) {
33
+ daemon.registerDiscovery(discovery);
34
+ discoveryRegistered = true;
35
+ }
36
+ }
37
+ catch (err) {
38
+ logger.error(` Failed to create discovery for ${def.id}:`, err);
39
+ }
40
+ }
41
+ if (!available) {
42
+ logger.log(`Agent ${def.displayName}: not available (${def.detection.description})`);
43
+ if (def.id === 'codex' && discoveryRegistered) {
44
+ logger.log(' Codex discovery is active; install @openai/codex-sdk@latest (or @openai/codex@latest) to enable launching/resuming sessions.');
45
+ }
46
+ continue;
47
+ }
48
+ logger.log(`Agent ${def.displayName}: available`);
49
+ try {
50
+ const adapter = await def.createAdapter();
51
+ if (adapter) {
52
+ daemon.registerAdapter(adapter);
53
+ }
54
+ }
55
+ catch (err) {
56
+ logger.error(` Failed to create adapter for ${def.id}:`, err);
57
+ }
58
+ }
59
+ return registry;
60
+ }
61
+ export function decodeAutoRegisterEntry(entry) {
62
+ return entry.startsWith('-') ? decodeProjectDir(entry) : decodeProjectDir(`-${entry}`);
63
+ }
64
+ export async function autoRegisterDirectories(daemon, registry) {
65
+ const watchDirs = registry.getAllWatchDirs();
66
+ for (const watchDir of watchDirs) {
67
+ try {
68
+ const entries = await fs.readdir(watchDir);
69
+ for (const entry of entries) {
70
+ const projectDir = path.join(watchDir, entry);
71
+ try {
72
+ const stat = await fs.stat(projectDir);
73
+ if (!stat.isDirectory())
74
+ continue;
75
+ const candidate = decodeAutoRegisterEntry(entry);
76
+ try {
77
+ await fs.access(candidate);
78
+ }
79
+ catch {
80
+ continue;
81
+ }
82
+ if (!daemon.directoryManager.getByPath(candidate)) {
83
+ await daemon.directoryManager.register(candidate);
84
+ logger.log(`Auto-registered: ${candidate}`);
85
+ }
86
+ }
87
+ catch {
88
+ // Skip invalid entries.
89
+ }
90
+ }
91
+ }
92
+ catch {
93
+ // Watch dir doesn't exist — not an error.
94
+ }
95
+ }
96
+ }
97
+ //# sourceMappingURL=startup-agents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-agents.js","sourceRoot":"","sources":["../src/startup-agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAE1C,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAEvD,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc;IAC7C,MAAM,QAAQ,GAAG,IAAI,aAAa,EAAE,CAAC;IAErC,KAAK,MAAM,GAAG,IAAI,eAAe,EAAE,CAAC;QAClC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,gBAAgB,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACtE,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACrC,MAAM,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,QAAQ,CAAC,IAAI,aAAa,MAAM,CAAC,UAAU,CAAC,EAAE,GAAG,CAAC,CAAC;QACjF,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,IAAI,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;IAED,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAEhD,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,eAAe,EAAE,CAAC;IAEtD,KAAK,MAAM,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,KAAK,CAAC;QACpD,MAAM,oBAAoB,GAAG,GAAG,CAAC,EAAE,KAAK,OAAO,CAAC;QAChD,IAAI,mBAAmB,GAAG,KAAK,CAAC;QAChC,IAAI,GAAG,CAAC,eAAe,IAAI,CAAC,SAAS,IAAI,oBAAoB,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,eAAe,EAAE,CAAC;gBAC9C,IAAI,SAAS,EAAE,CAAC;oBACd,MAAM,CAAC,iBAAiB,CAAC,SAAS,CAAC,CAAC;oBACpC,mBAAmB,GAAG,IAAI,CAAC;gBAC7B,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,oCAAoC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;QAED,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,MAAM,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,WAAW,oBAAoB,GAAG,CAAC,SAAS,CAAC,WAAW,GAAG,CAAC,CAAC;YACrF,IAAI,GAAG,CAAC,EAAE,KAAK,OAAO,IAAI,mBAAmB,EAAE,CAAC;gBAC9C,MAAM,CAAC,GAAG,CACR,gIAAgI,CACjI,CAAC;YACJ,CAAC;YACD,SAAS;QACX,CAAC;QAED,MAAM,CAAC,GAAG,CAAC,SAAS,GAAG,CAAC,WAAW,aAAa,CAAC,CAAC;QAElD,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,aAAa,EAAE,CAAC;YAC1C,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,kCAAkC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,OAAO,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;AACzF,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,MAAc,EACd,QAAuB;IAEvB,MAAM,SAAS,GAAG,QAAQ,CAAC,eAAe,EAAE,CAAC;IAE7C,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAC3C,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;gBAC9C,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;oBACvC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;wBAAE,SAAS;oBAElC,MAAM,SAAS,GAAG,uBAAuB,CAAC,KAAK,CAAC,CAAC;oBAEjD,IAAI,CAAC;wBACH,MAAM,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;oBAC7B,CAAC;oBAAC,MAAM,CAAC;wBACP,SAAS;oBACX,CAAC;oBAED,IAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;wBAClD,MAAM,MAAM,CAAC,gBAAgB,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;wBAClD,MAAM,CAAC,GAAG,CAAC,oBAAoB,SAAS,EAAE,CAAC,CAAC;oBAC9C,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,wBAAwB;gBAC1B,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,0CAA0C;QAC5C,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,29 @@
1
+ interface PrereqSnapshot {
2
+ preferredAgents: Set<string>;
3
+ hasClaudeSessions: boolean;
4
+ hasCodexSessions: boolean;
5
+ claudeSdkInstalled: boolean;
6
+ codexSdkInstalled: boolean;
7
+ geminiCliInstalled: boolean;
8
+ }
9
+ export interface PrereqIssue {
10
+ id: 'claude-sdk' | 'codex-sdk' | 'gemini-cli';
11
+ autoInstall: boolean;
12
+ prompt: string;
13
+ packages?: readonly string[];
14
+ hint?: string;
15
+ }
16
+ export interface PrereqInstallResult {
17
+ id: PrereqIssue['id'];
18
+ ok: boolean;
19
+ error?: string;
20
+ }
21
+ export declare function detectPrereqIssues(snapshot: PrereqSnapshot): PrereqIssue[];
22
+ export declare function assessAgentPrerequisites(): Promise<PrereqIssue[]>;
23
+ export declare function installPrerequisites(issues: PrereqIssue[]): Promise<PrereqInstallResult[]>;
24
+ export declare function maybeOfferAgentPrerequisites(options: {
25
+ silent: boolean;
26
+ asJson: boolean;
27
+ }): Promise<void>;
28
+ export {};
29
+ //# sourceMappingURL=startup-prereqs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"startup-prereqs.d.ts","sourceRoot":"","sources":["../src/startup-prereqs.ts"],"names":[],"mappings":"AAkBA,UAAU,cAAc;IACtB,eAAe,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC7B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,YAAY,GAAG,WAAW,GAAG,YAAY,CAAC;IAC9C,WAAW,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,WAAW,CAAC,IAAI,CAAC,CAAC;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,cAAc,GAAG,WAAW,EAAE,CA0C1E;AAED,wBAAsB,wBAAwB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAUvE;AAED,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAUhG;AA2GD,wBAAsB,4BAA4B,CAAC,OAAO,EAAE;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqChB"}
@@ -0,0 +1,209 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { logger } from './core/output.js';
7
+ import { loadConfig, BUILT_IN_DEFAULTS } from './core/config.js';
8
+ import { claudeProjectsDir } from './discovery/jsonl-reader.js';
9
+ import { codexSessionsDir } from './discovery/codex.js';
10
+ import { resolveNpmInvocationFromNode } from './cli/runtime-toolchain.js';
11
+ import { CODEX_SDK_PACKAGE_CANDIDATES, isCodexSdkAvailable } from './adapters/codex-sdk-loader.js';
12
+ import { commandExists } from './agents/command-detection.js';
13
+ const CLAUDE_SDK_PACKAGE_CANDIDATES = ['@anthropic-ai/claude-agent-sdk'];
14
+ const PROMPT_TIMEOUT_MS = 15_000;
15
+ const TIMED_OUT_TOKEN = '__VPD_INSTALL_PROMPT_TIMEOUT__';
16
+ export function detectPrereqIssues(snapshot) {
17
+ const issues = [];
18
+ const wantsClaude = snapshot.preferredAgents.has('claude') ||
19
+ snapshot.hasClaudeSessions ||
20
+ snapshot.preferredAgents.size === 0;
21
+ const wantsCodex = snapshot.preferredAgents.has('codex') || snapshot.hasCodexSessions;
22
+ const wantsGemini = snapshot.preferredAgents.has('gemini');
23
+ if (wantsClaude && !snapshot.claudeSdkInstalled) {
24
+ issues.push({
25
+ id: 'claude-sdk',
26
+ autoInstall: true,
27
+ prompt: '\nClaude is configured or existing Claude sessions were detected, but @anthropic-ai/claude-agent-sdk is missing.\nInstall it now so daemon can launch/resume Claude sessions? [Y/n] ',
28
+ packages: CLAUDE_SDK_PACKAGE_CANDIDATES,
29
+ hint: 'npm install @anthropic-ai/claude-agent-sdk@latest',
30
+ });
31
+ }
32
+ if (wantsCodex && !snapshot.codexSdkInstalled) {
33
+ issues.push({
34
+ id: 'codex-sdk',
35
+ autoInstall: true,
36
+ prompt: '\nCodex is configured or existing Codex sessions were detected, but @openai/codex-sdk is missing.\nInstall it now so daemon can launch/resume Codex sessions? [Y/n] ',
37
+ packages: CODEX_SDK_PACKAGE_CANDIDATES,
38
+ hint: 'npm install @openai/codex-sdk@latest',
39
+ });
40
+ }
41
+ if (wantsGemini && !snapshot.geminiCliInstalled) {
42
+ issues.push({
43
+ id: 'gemini-cli',
44
+ autoInstall: false,
45
+ prompt: '\nGemini is configured, but the `gemini` CLI is not available on PATH.\nInstall Gemini CLI and re-run `vpd install` and `vpd start`.',
46
+ hint: 'Install Gemini CLI, then ensure `gemini` resolves from your PATH.',
47
+ });
48
+ }
49
+ return issues;
50
+ }
51
+ export async function assessAgentPrerequisites() {
52
+ const snapshot = {
53
+ preferredAgents: await resolvePreferredAgentsFromConfig(),
54
+ hasClaudeSessions: await hasJsonlFiles(claudeProjectsDir()),
55
+ hasCodexSessions: await hasJsonlFiles(codexSessionsDir()),
56
+ claudeSdkInstalled: await isClaudeSdkInstalled(),
57
+ codexSdkInstalled: await isCodexSdkAvailable(),
58
+ geminiCliInstalled: await commandExists('gemini'),
59
+ };
60
+ return detectPrereqIssues(snapshot);
61
+ }
62
+ export async function installPrerequisites(issues) {
63
+ const results = [];
64
+ for (const issue of issues) {
65
+ if (!issue.autoInstall || !issue.packages || issue.packages.length === 0) {
66
+ continue;
67
+ }
68
+ const result = installPackageCandidates(issue.packages);
69
+ results.push({ id: issue.id, ok: result.ok, error: result.error });
70
+ }
71
+ return results;
72
+ }
73
+ async function hasJsonlFiles(root, limit = 400) {
74
+ const queue = [root];
75
+ let scanned = 0;
76
+ while (queue.length > 0 && scanned < limit) {
77
+ const current = queue.shift();
78
+ if (!current)
79
+ continue;
80
+ let entries;
81
+ try {
82
+ entries = await fs.readdir(current, { withFileTypes: true });
83
+ }
84
+ catch {
85
+ continue;
86
+ }
87
+ for (const entry of entries) {
88
+ scanned += 1;
89
+ if (entry.isDirectory()) {
90
+ queue.push(path.join(current, entry.name));
91
+ }
92
+ else if (entry.isFile() && entry.name.endsWith('.jsonl')) {
93
+ return true;
94
+ }
95
+ if (scanned >= limit)
96
+ break;
97
+ }
98
+ }
99
+ return false;
100
+ }
101
+ async function resolvePreferredAgentsFromConfig() {
102
+ const preferred = new Set([BUILT_IN_DEFAULTS.agent ?? 'claude']);
103
+ const cfg = await loadConfig();
104
+ if (typeof cfg.defaults?.agent === 'string' && cfg.defaults.agent.trim()) {
105
+ preferred.add(cfg.defaults.agent.trim());
106
+ }
107
+ for (const entry of Object.values(cfg.directories ?? {})) {
108
+ const agent = entry?.config?.agent;
109
+ if (typeof agent === 'string' && agent.trim()) {
110
+ preferred.add(agent.trim());
111
+ }
112
+ }
113
+ return preferred;
114
+ }
115
+ async function isClaudeSdkInstalled() {
116
+ try {
117
+ await import('@anthropic-ai/claude-agent-sdk');
118
+ return true;
119
+ }
120
+ catch {
121
+ return false;
122
+ }
123
+ }
124
+ function daemonPackageRoot() {
125
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
126
+ }
127
+ function installPackageCandidates(packages) {
128
+ const daemonRoot = daemonPackageRoot();
129
+ const npmInvocation = (() => {
130
+ try {
131
+ return resolveNpmInvocationFromNode(process.execPath);
132
+ }
133
+ catch {
134
+ return { command: 'npm', argsPrefix: [] };
135
+ }
136
+ })();
137
+ let lastError;
138
+ for (const packageName of packages) {
139
+ const result = spawnSync(npmInvocation.command, [...npmInvocation.argsPrefix, 'install', `${packageName}@latest`], {
140
+ cwd: daemonRoot,
141
+ stdio: 'inherit',
142
+ env: process.env,
143
+ });
144
+ if (!result.error && (result.status ?? 1) === 0) {
145
+ return { ok: true };
146
+ }
147
+ lastError = result.error
148
+ ? result.error instanceof Error
149
+ ? result.error.message
150
+ : String(result.error)
151
+ : `${packageName} install exited with code ${result.status ?? 1}`;
152
+ }
153
+ return { ok: false, error: lastError };
154
+ }
155
+ async function promptInstall(question) {
156
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
157
+ try {
158
+ const answer = await Promise.race([
159
+ rl.question(question),
160
+ new Promise((resolve) => setTimeout(() => resolve(TIMED_OUT_TOKEN), PROMPT_TIMEOUT_MS)),
161
+ ]);
162
+ if (answer === TIMED_OUT_TOKEN)
163
+ return null;
164
+ const normalized = answer.trim().toLowerCase();
165
+ return normalized.length === 0 || normalized === 'y' || normalized === 'yes';
166
+ }
167
+ finally {
168
+ rl.close();
169
+ }
170
+ }
171
+ export async function maybeOfferAgentPrerequisites(options) {
172
+ if (options.silent || options.asJson)
173
+ return;
174
+ if (process.env['VPD_SKIP_INTERACTIVE_INSTALL_PROMPTS'] === '1')
175
+ return;
176
+ if (process.env['TSX_WATCH'])
177
+ return;
178
+ const issues = await assessAgentPrerequisites();
179
+ if (issues.length === 0)
180
+ return;
181
+ for (const issue of issues) {
182
+ if (!issue.autoInstall) {
183
+ logger.warn(issue.prompt);
184
+ continue;
185
+ }
186
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
187
+ if (issue.hint)
188
+ logger.warn(`${issue.prompt}\n${issue.hint}`);
189
+ continue;
190
+ }
191
+ const decision = await promptInstall(issue.prompt);
192
+ if (decision === null) {
193
+ logger.log('\nDependency install prompt timed out; continuing startup.');
194
+ continue;
195
+ }
196
+ if (!decision)
197
+ continue;
198
+ if (!issue.packages || issue.packages.length === 0)
199
+ continue;
200
+ logger.log(`\nInstalling dependency (${issue.packages.join(' -> ')}) ...`);
201
+ const result = installPackageCandidates(issue.packages);
202
+ if (!result.ok) {
203
+ logger.warn(`Dependency install failed (${result.error ?? 'unknown error'}). ${issue.hint ?? 'Please install manually and retry.'}`);
204
+ continue;
205
+ }
206
+ logger.log('Dependency installed. Continuing daemon startup.\n');
207
+ }
208
+ }
209
+ //# sourceMappingURL=startup-prereqs.js.map