@reticlehq/server 2.12.0 → 2.13.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 (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +60 -0
  253. package/dist/setup/dev-server-wait.js +65 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +187 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
@@ -0,0 +1,187 @@
1
+ /**
2
+ * The half of setup that happens after the files are written.
3
+ *
4
+ * `init` wires a project; this gets the app running with the SDK inside it, proves a session
5
+ * connected, and drives one flow to a verdict. Measured against an agent doing the same steps by
6
+ * hand from SKILL.md across five real applications: 176 model turns, $9.92, forty minutes, and a
7
+ * verdict in two runs out of five — three ended by asking a human to restart their client, having
8
+ * shown them nothing.
9
+ *
10
+ * Every effect is injected. That is not ceremony: the sequence has five phases, each with its own
11
+ * way of going wrong, and the alternative to injecting them is a test that boots a real dev server
12
+ * and a real browser to find out what happens when neither works.
13
+ */
14
+ import { judgeWait, urlToWatch, WaitVerdict } from './dev-server-wait.js';
15
+ import { pickSession } from './session-pick.js';
16
+ import { readPage, describePage } from './page-probe.js';
17
+ import { remainingSteps } from './remaining-steps.js';
18
+ import { AppShape, isDesktop, policyFor } from './desktop-shape.js';
19
+ /** Where a run got to, and why it stopped if it did. */
20
+ export const SetupPhase = {
21
+ DEV_SERVER: 'dev-server',
22
+ CONNECT: 'connect',
23
+ DRIVE: 'drive',
24
+ DONE: 'done',
25
+ };
26
+ const asProgress = (input, o) => ({
27
+ initDone: true,
28
+ devServerUp: undefined !== o.url,
29
+ sessionConnected: undefined !== o.sessionId,
30
+ flowSaved: true === o.flowSaved,
31
+ urlSuppliedByCaller: undefined !== input.suppliedUrl,
32
+ ...(undefined === o.url ? {} : { url: o.url }),
33
+ ...(undefined === input.devCommand ? {} : { devCommand: input.devCommand }),
34
+ });
35
+ const stop = (input, phase, partial, notes) => ({
36
+ ok: false,
37
+ reachedPhase: phase,
38
+ flowSaved: false,
39
+ ...partial,
40
+ notes,
41
+ fallback: remainingSteps(asProgress(input, partial)),
42
+ });
43
+ /**
44
+ * Run the phases. Stops at the first one that cannot continue, and always says what is left.
45
+ *
46
+ * A phase that fails is not an error to throw: the caller is usually an agent, and a thrown
47
+ * exception loses the four things it needs — how far this got, the url, the session, and what to do
48
+ * about it.
49
+ */
50
+ export async function runSetupPhases(input, fx) {
51
+ const notes = [];
52
+ const note = (line) => {
53
+ notes.push(line);
54
+ fx.note(line);
55
+ };
56
+ // ── the app has to be running ────────────────────────────────────────────────────────────────
57
+ let url = input.suppliedUrl;
58
+ if (undefined === url) {
59
+ if (undefined === input.devCommand) {
60
+ note(
61
+ // "stop here rather than invent one" is the SKILL.md rule, and the words are the contract —
62
+ // inventing a dev command is how a setup script runs the wrong thing and reports success.
63
+ 'No dev command: the project names no dev, start or serve script, and there is no --url. ' +
64
+ 'Stopping here rather than invent one — pass --dev-cmd or --url to say what serves this app.');
65
+ return stop(input, SetupPhase.DEV_SERVER, {}, notes);
66
+ }
67
+ await fx.startDevServer(input.devCommand, input.appDir);
68
+ const startedAt = fx.now();
69
+ for (;;) {
70
+ const watching = urlToWatch(fx.devServerOutput(), fx.observedPorts());
71
+ const serving = undefined === watching ? false : (await fx.probePage(watching)).served;
72
+ const verdict = judgeWait({
73
+ output: fx.devServerOutput(),
74
+ launcherExited: fx.devServerExited(),
75
+ serving,
76
+ quietForMs: fx.devServerQuietForMs(),
77
+ elapsedMs: fx.now() - startedAt,
78
+ });
79
+ if (WaitVerdict.READY === verdict && undefined !== watching) {
80
+ url = watching;
81
+ break;
82
+ }
83
+ // A desktop shell serves its webview from inside the app, so there is no port to answer and
84
+ // nothing to be READY. Once it has announced a url, or bound one we can see, that is as far
85
+ // as this phase can get: the session it dials from its own window is the real signal.
86
+ if (isDesktop(input.shape) && undefined !== watching) {
87
+ url = watching;
88
+ break;
89
+ }
90
+ if (WaitVerdict.DEAD === verdict) {
91
+ note('The dev server exited without serving anything.');
92
+ return stop(input, SetupPhase.DEV_SERVER, {}, notes);
93
+ }
94
+ if (WaitVerdict.HUNG === verdict) {
95
+ // Says BOTH were checked. A server that prints nothing but IS listening is the CRA case and
96
+ // must not be failed, so a reader has to be able to tell "we looked at the log" from "we
97
+ // looked at the log AND the ports".
98
+ note('The dev server neither printed a URL nor bound a port, so setup has nothing to open. ' +
99
+ 'Check its log, or pass --url with the address it serves.');
100
+ return stop(input, SetupPhase.DEV_SERVER, {}, notes);
101
+ }
102
+ await fx.sleep(input.pollMs);
103
+ }
104
+ }
105
+ // ── and something has to connect from inside it ──────────────────────────────────────────────
106
+ const policy = policyFor(input.shape);
107
+ // Through `note`, not `fx.note`: a caller reading the result should see it too.
108
+ if (undefined !== policy.note)
109
+ note(policy.note);
110
+ const before = new Set((await fx.listSessions()).map((s) => s.sessionId));
111
+ // Never for a desktop app: its own window is the client, and a browser tab would be a SECOND
112
+ // session that is not the app.
113
+ if (input.openBrowser && policy.openBrowser)
114
+ await fx.openBrowser(url);
115
+ const deadline = fx.now() + (input.connectBudgetMs ?? Math.max(input.phaseTimeoutMs, policy.connectBudgetMs));
116
+ let session = null;
117
+ for (;;) {
118
+ // On a desktop app, only the desktop window counts. AppShape and the runtime a page reports use
119
+ // the same three names, so the shape IS the requirement — see session-pick.
120
+ session = pickSession(await fx.listSessions(), url, before, isDesktop(input.shape) ? input.shape : undefined);
121
+ if (null !== session)
122
+ break;
123
+ if (deadline <= fx.now())
124
+ break;
125
+ await fx.sleep(input.pollMs);
126
+ }
127
+ if (null === session) {
128
+ if (isDesktop(input.shape)) {
129
+ // Nothing outside the app can fetch its webview, so there is no page to describe. What is
130
+ // wrong here is desktop-shaped: the preload, the capture helper, or a CSP that blocks the
131
+ // bridge — all of which `reticle doctor` checks.
132
+ note('The app never dialled in. For a desktop shell that is usually the preload not being required, ' +
133
+ 'the capture helper not installed, or a CSP that blocks the bridge: run `npx @reticlehq/server doctor`.');
134
+ }
135
+ else {
136
+ // What the page contains is the one thing the daemon cannot know, so it is worth one fetch.
137
+ note(describePage(readPage(await fx.probePage(url)), url));
138
+ }
139
+ return stop(input, SetupPhase.CONNECT, { url }, notes);
140
+ }
141
+ // ── and it has to be driven, or nothing has been proved ──────────────────────────────────────
142
+ if (!input.drive) {
143
+ return {
144
+ ok: true,
145
+ reachedPhase: SetupPhase.CONNECT,
146
+ url,
147
+ sessionId: session.sessionId,
148
+ flowSaved: false,
149
+ notes,
150
+ fallback: [],
151
+ };
152
+ }
153
+ // Nothing to drive WITH is not the same as driving and proving nothing, and only the second is a
154
+ // failure of this command. The app is installed, instrumented, booted and connected; the one step
155
+ // left needs a tool this machine does not have, so it is reported and handed to the caller.
156
+ if (!fx.driverAvailable()) {
157
+ note('Installed and connected, but no agent CLI is on this machine (claude, codex, opencode, ' +
158
+ 'cursor-agent or gemini), so nothing drove the app and no verdict was produced.');
159
+ return {
160
+ ok: true,
161
+ reachedPhase: SetupPhase.CONNECT,
162
+ url,
163
+ sessionId: session.sessionId,
164
+ flowSaved: false,
165
+ notes,
166
+ fallback: remainingSteps(asProgress(input, { url, sessionId: session.sessionId, flowSaved: false })),
167
+ };
168
+ }
169
+ const verdict = await fx.drive(url, session);
170
+ const flowSaved = fx.flowsSaved();
171
+ if (null === verdict) {
172
+ note('No agent CLI could drive the app, so nothing was proved.');
173
+ }
174
+ if (!flowSaved) {
175
+ return stop(input, SetupPhase.DRIVE, { url, sessionId: session.sessionId, ...(null === verdict ? {} : { verdict }) }, notes);
176
+ }
177
+ return {
178
+ ok: true,
179
+ reachedPhase: SetupPhase.DONE,
180
+ url,
181
+ sessionId: session.sessionId,
182
+ ...(null === verdict ? {} : { verdict }),
183
+ flowSaved: true,
184
+ notes,
185
+ fallback: [],
186
+ };
187
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Which connected session to drive, out of everything the daemon is holding.
3
+ *
4
+ * This is a false-green guard, which is why it is its own module. Two ways to get it wrong, and
5
+ * both report a successful install for an app that was never verified:
6
+ *
7
+ * - Match "any session" and you pass on somebody else's tab. A daemon usually holds several, and
8
+ * one of them being alive says nothing about whether THIS app connected.
9
+ * - Match "the first session on this url" and you drive whichever the daemon listed first, usually
10
+ * the oldest: a tab whose dev server died yesterday. The HUD then plays to a window nobody is
11
+ * watching, and the verdict describes a page the user cannot see.
12
+ */
13
+ /** The fields of a daemon session this decision actually reads. */
14
+ export interface CandidateSession {
15
+ readonly sessionId: string;
16
+ readonly url?: string;
17
+ readonly hidden?: boolean;
18
+ readonly throttled?: boolean;
19
+ readonly lastSeenMs?: number;
20
+ /** False when the capabilities file init scaffolded was never completed. */
21
+ readonly hasCapabilities?: boolean;
22
+ /** Which shell answered. Absent on an SDK too old to report one — see requiredRuntime below. */
23
+ readonly runtime?: string;
24
+ }
25
+ /**
26
+ * The session to drive, or null when nothing on this url qualifies.
27
+ *
28
+ * Preference order, strongest evidence first:
29
+ * 1. NEW since we opened the tab — definitely this run's, definitely alive
30
+ * 2. visible and not throttled — the tab a human is actually looking at
31
+ * 3. least stale — the best of a bad set
32
+ *
33
+ * `before` is the set of session ids that already existed when this run started; anything outside
34
+ * it belongs to us.
35
+ */
36
+ export declare function pickSession(sessions: readonly CandidateSession[], url: string, before?: ReadonlySet<string>,
37
+ /**
38
+ * The runtime this project's app RUNS as, when it is a desktop one.
39
+ *
40
+ * A desktop shell serves its renderer from an ordinary dev server, so its window and a browser tab
41
+ * open on the same origin are indistinguishable by url. Driving the tab passes every check here —
42
+ * live, on the url, SDK present — while having none of the app's IPC and none of its commands, so
43
+ * the verdict describes a different program that happens to share an address.
44
+ *
45
+ * Undefined for a web project, where the runtime is not a distinction worth making.
46
+ */
47
+ requiredRuntime?: string): CandidateSession | null;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Which connected session to drive, out of everything the daemon is holding.
3
+ *
4
+ * This is a false-green guard, which is why it is its own module. Two ways to get it wrong, and
5
+ * both report a successful install for an app that was never verified:
6
+ *
7
+ * - Match "any session" and you pass on somebody else's tab. A daemon usually holds several, and
8
+ * one of them being alive says nothing about whether THIS app connected.
9
+ * - Match "the first session on this url" and you drive whichever the daemon listed first, usually
10
+ * the oldest: a tab whose dev server died yesterday. The HUD then plays to a window nobody is
11
+ * watching, and the verdict describes a page the user cannot see.
12
+ */
13
+ /** Sorts a hidden tab after a visible one; among equals, the least stale first. */
14
+ const isLive = (s) => true !== s.hidden && true !== s.throttled;
15
+ const staleness = (s) => s.lastSeenMs ?? Number.POSITIVE_INFINITY;
16
+ /**
17
+ * The session to drive, or null when nothing on this url qualifies.
18
+ *
19
+ * Preference order, strongest evidence first:
20
+ * 1. NEW since we opened the tab — definitely this run's, definitely alive
21
+ * 2. visible and not throttled — the tab a human is actually looking at
22
+ * 3. least stale — the best of a bad set
23
+ *
24
+ * `before` is the set of session ids that already existed when this run started; anything outside
25
+ * it belongs to us.
26
+ */
27
+ export function pickSession(sessions, url, before = new Set(),
28
+ /**
29
+ * The runtime this project's app RUNS as, when it is a desktop one.
30
+ *
31
+ * A desktop shell serves its renderer from an ordinary dev server, so its window and a browser tab
32
+ * open on the same origin are indistinguishable by url. Driving the tab passes every check here —
33
+ * live, on the url, SDK present — while having none of the app's IPC and none of its commands, so
34
+ * the verdict describes a different program that happens to share an address.
35
+ *
36
+ * Undefined for a web project, where the runtime is not a distinction worth making.
37
+ */
38
+ requiredRuntime) {
39
+ const wanted = String(url).replace(/\/$/, '');
40
+ const onUrl = sessions
41
+ .filter((s) => (s?.url ?? '').startsWith(wanted))
42
+ // An SDK too old to report a runtime is ACCEPTED rather than excluded: re-running init is the
43
+ // only upgrade path an existing user has, and refusing them their own install to enforce a
44
+ // field their SDK cannot send would break the one route out.
45
+ .filter((s) => undefined === requiredRuntime || undefined === s.runtime || requiredRuntime === s.runtime);
46
+ if (0 === onUrl.length)
47
+ return null;
48
+ const fresh = onUrl.filter((s) => !before.has(s.sessionId));
49
+ const pool = 0 < fresh.length ? fresh : onUrl;
50
+ return pool.find(isLive) ?? [...pool].sort((a, b) => staleness(a) - staleness(b))[0] ?? null;
51
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * The wire: everything init established, plus the world, plus the phases.
3
+ *
4
+ * Kept apart from cli.ts so the assembly is readable in one place and so `handleInit` stays the
5
+ * three lines it was. The only decision here is which effects to hand over; the sequencing lives in
6
+ * run-setup.ts and the pieces it calls.
7
+ */
8
+ import { type SetupInput, type SetupOutcome } from './run-setup.js';
9
+ export interface SetupCommandInput extends Omit<SetupInput, 'shape'> {
10
+ /** Where setup was invoked, which is not the app directory in a monorepo. */
11
+ readonly invokedAt: string;
12
+ readonly bridgePort: number;
13
+ readonly env: Readonly<Record<string, string>>;
14
+ readonly flow?: string | undefined;
15
+ readonly driveBudgetUsd: number;
16
+ readonly driveModel?: string | undefined;
17
+ readonly escalateWeakFlow: boolean;
18
+ /** Register the MCP server with the coding agents init does not itself reach. */
19
+ readonly registerAgents: boolean;
20
+ }
21
+ /**
22
+ * Register with the agents `init` does not reach, and leave the skill where one loads skills from.
23
+ *
24
+ * init covers eight clients and only where it finds them installed, which leaves VS Code's USER
25
+ * scope unwritten — it exists on machines today, and init only ever writes the project-scope file,
26
+ * so a VS Code user has no tools outside the directory they ran init in.
27
+ */
28
+ export declare function registerOtherAgents(print: (line: string) => void): void;
29
+ export interface SetupCommandResult extends SetupOutcome {
30
+ /** Set when a weak flow was re-recorded with the stronger model. */
31
+ readonly escalated?: {
32
+ readonly from: string;
33
+ readonly to: string;
34
+ } | undefined;
35
+ readonly driveTurns?: number | undefined;
36
+ readonly driveCostUsd?: number | undefined;
37
+ }
38
+ /**
39
+ * Run the phases against the real world.
40
+ *
41
+ * The dev server is stopped on every ending except success, where it is handed to the user: an
42
+ * instrumented app they can watch is the deliverable, and killing it would leave them with config
43
+ * files and a dead tab.
44
+ */
45
+ export declare function runSetupCommand(input: SetupCommandInput, print: (line: string) => void): Promise<SetupCommandResult>;
@@ -0,0 +1,199 @@
1
+ /**
2
+ * The wire: everything init established, plus the world, plus the phases.
3
+ *
4
+ * Kept apart from cli.ts so the assembly is readable in one place and so `handleInit` stays the
5
+ * three lines it was. The only decision here is which effects to hand over; the sequencing lives in
6
+ * run-setup.ts and the pieces it calls.
7
+ */
8
+ import { existsSync, readFileSync } from 'node:fs';
9
+ import { homedir } from 'node:os';
10
+ import { join } from 'node:path';
11
+ import { planAgentConfigs } from './agent-configs.js';
12
+ import { AppShape, readShape } from './desktop-shape.js';
13
+ import { stopOnInterrupt } from './interrupt.js';
14
+ import { applyAgentPlan, applyAgentSkills } from './agent-writer.js';
15
+ import { ApprovalOutcome, grantAutoApproval } from './auto-approve.js';
16
+ import { agentIo } from './agent-io.js';
17
+ import { EnsureDaemon, ensureDaemon, nodeEnsureDaemonDeps } from './ensure-daemon.js';
18
+ import { openInBrowser } from '../cli/cli-launch.js';
19
+ import { chooseDriver, DRIVERS, shouldEscalate } from './drive-plan.js';
20
+ import { driveWith } from './drive-agent.js';
21
+ import { binaryExists, flowsSaved, listSessions, OwnedDevServer, probePage, } from './node-effects.js';
22
+ import { runSetupPhases, } from './run-setup.js';
23
+ /** The capabilities file init scaffolds, in the order a project is likely to have it. */
24
+ const CAPABILITY_FILES = ['reticle-dev.tsx', 'reticle-dev.ts', 'reticle-dev.jsx', 'reticle-dev.js'];
25
+ /** `electron` in either dependency list, read the same way init's desktop doctor reads it. */
26
+ function hasElectronDependency(dir) {
27
+ try {
28
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8'));
29
+ if ('object' !== typeof pkg || null === pkg)
30
+ return false;
31
+ const p = pkg;
32
+ return undefined !== { ...p.dependencies, ...p.devDependencies }['electron'];
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ /**
39
+ * Register with the agents `init` does not reach, and leave the skill where one loads skills from.
40
+ *
41
+ * init covers eight clients and only where it finds them installed, which leaves VS Code's USER
42
+ * scope unwritten — it exists on machines today, and init only ever writes the project-scope file,
43
+ * so a VS Code user has no tools outside the directory they ran init in.
44
+ */
45
+ export function registerOtherAgents(print) {
46
+ const platform = process.platform;
47
+ const home = homedir();
48
+ const results = applyAgentPlan(planAgentConfigs({ home, platform, exists: agentIo.exists, readFile: agentIo.readFile }), agentIo);
49
+ const wrote = results.filter((r) => 'created' === r.action || 'merged' === r.action);
50
+ if (0 < wrote.length) {
51
+ print(`registered the MCP server with ${wrote.length} more agent(s): ${wrote.map((r) => r.name).join(', ')}`);
52
+ }
53
+ // A format we will not rewrite is somebody's to edit, so it has to be said rather than skipped.
54
+ for (const manual of results.filter((r) => 'manual' === r.action)) {
55
+ print(`${manual.name}: ${manual.why} — add the reticle entry to ${manual.file} by hand.`);
56
+ }
57
+ const skills = applyAgentSkills(agentIo, { home, platform });
58
+ if (0 < skills.length)
59
+ print(`wrote the /reticle skill for ${skills.length} agent(s)`);
60
+ // Registration alone still leaves an Accept dialog in front of every call, and a verification run
61
+ // makes dozens of them: the loop is only autonomous once the tools are pre-approved.
62
+ const approvals = grantAutoApproval(agentIo, { home, platform });
63
+ const granted = approvals.filter((a) => ApprovalOutcome.GRANTED === a.outcome);
64
+ if (0 < granted.length) {
65
+ print(`pre-approved the reticle tools in ${granted.map((a) => a.name).join(', ')} — no Accept prompt per call`);
66
+ }
67
+ for (const noted of approvals.filter((a) => undefined !== a.warn)) {
68
+ print(`${noted.name}: ${String(noted.warn)}`);
69
+ }
70
+ }
71
+ /**
72
+ * Run the phases against the real world.
73
+ *
74
+ * The dev server is stopped on every ending except success, where it is handed to the user: an
75
+ * instrumented app they can watch is the deliverable, and killing it would leave them with config
76
+ * files and a dead tab.
77
+ */
78
+ export async function runSetupCommand(input, print) {
79
+ if (input.registerAgents)
80
+ registerOtherAgents(print);
81
+ // Before the app is booted, because the app's whole job from here is to dial this port. Without
82
+ // it the phases wait out their budget and then report the SDK as the thing that failed.
83
+ const daemon = await ensureDaemon(input.bridgePort, nodeEnsureDaemonDeps());
84
+ if (EnsureDaemon.UNAVAILABLE === daemon) {
85
+ print(`could not start the Reticle daemon on port ${String(input.bridgePort)}, so nothing is listening for the app to connect to. Run \`npx @reticlehq/server serve --port ${String(input.bridgePort)}\` and try again.`);
86
+ }
87
+ // Which shell this is decides three things the phases would otherwise get wrong: opening a
88
+ // browser (harmful for desktop, where the app's own window is the client), waiting for an HTTP
89
+ // port (a Tauri webview has none), and how long the app gets to appear (a cold `tauri dev` builds
90
+ // Rust first). The same evidence init's desktop doctor reads.
91
+ const shape = readShape({
92
+ hasTauriConf: existsSync(join(input.appDir, 'src-tauri', 'tauri.conf.json')),
93
+ hasElectronDep: hasElectronDependency(input.appDir),
94
+ });
95
+ if (AppShape.WEB !== shape)
96
+ print(`detected a ${shape} app`);
97
+ const server = new OwnedDevServer();
98
+ // Both roots, because in a monorepo `.reticle/` sits at the app root rather than where setup ran.
99
+ const flowRoots = [input.invokedAt, input.appDir];
100
+ let lastDrive;
101
+ let escalated;
102
+ const effects = {
103
+ startDevServer: (command, cwd) => {
104
+ print(`starting: ${command}`);
105
+ server.start(command, cwd, input.env);
106
+ return Promise.resolve();
107
+ },
108
+ devServerOutput: () => server.output(),
109
+ devServerExited: () => server.exited(),
110
+ devServerQuietForMs: () => server.quietForMs(),
111
+ observedPorts: () => server.listeningPorts(),
112
+ probePage,
113
+ openBrowser: async (url) => {
114
+ const failure = await openInBrowser(url);
115
+ if (null !== failure) {
116
+ print(`could not open a browser (${failure}). On a machine with none — CI, a container, an SSH ` +
117
+ 'session — take a tab Reticle owns instead: reticle_run({ tool: "reticle_lease", args: ' +
118
+ `{ action: "acquire", url: "${url}" } }).`);
119
+ }
120
+ },
121
+ listSessions: () => listSessions(input.bridgePort),
122
+ // Asked before the drive rather than inferred from its result: "nothing to drive with" and
123
+ // "drove and proved nothing" are different answers and only the second is a failure.
124
+ driverAvailable: () => null !==
125
+ chooseDriver(DRIVERS, (bin) => ({ present: binaryExists(bin), runs: binaryExists(bin) })),
126
+ drive: (url, session) => {
127
+ const driver = chooseDriver(DRIVERS, (bin) => ({
128
+ present: binaryExists(bin),
129
+ // Present is not enough: a CLI that does not run produces an empty session that looks
130
+ // exactly like success.
131
+ runs: binaryExists(bin),
132
+ }));
133
+ if (null === driver)
134
+ return Promise.resolve(null);
135
+ // Only when the SESSION says they were never finished. init fills this file for a
136
+ // conventional app — it detects a state library and the testids — so opening it otherwise
137
+ // spends a turn on work already done, and grants write access nobody needed.
138
+ const capabilitiesFile = false === session.hasCapabilities
139
+ ? CAPABILITY_FILES.map((f) => join(input.appDir, 'src', f)).find((p) => existsSync(p))
140
+ : undefined;
141
+ print('');
142
+ print(` ▸ WATCH ${url} NOW — the HUD is on, and you are about to see Reticle drive your app.`);
143
+ print('');
144
+ const request = {
145
+ url,
146
+ sessionId: session.sessionId,
147
+ tabThrottled: false,
148
+ budgetUsd: input.driveBudgetUsd,
149
+ ...(undefined === input.flow ? {} : { flow: input.flow }),
150
+ ...(undefined === input.driveModel ? {} : { model: input.driveModel }),
151
+ ...(undefined === capabilitiesFile ? {} : { unfinishedCapabilitiesFile: capabilitiesFile }),
152
+ };
153
+ lastDrive = driveWith(driver, request, input.appDir);
154
+ // A weak flow only ACTS, so it passes when the feature is broken — and setup replays saved
155
+ // flows, which turns one weak recording into a permanent green. Re-record it rather than
156
+ // hand the trade to the user.
157
+ if (shouldEscalate({
158
+ escalationEnabled: input.escalateWeakFlow,
159
+ fasterModel: input.driveModel,
160
+ flowSaved: flowsSaved(flowRoots),
161
+ ...(undefined === lastDrive.grade ? { grade: undefined } : { grade: lastDrive.grade }),
162
+ })) {
163
+ print(`the saved flow graded \`${lastDrive.grade ?? 'unknown'}\` — re-recording with the default model`);
164
+ const stronger = driveWith(driver, { ...request, model: undefined }, input.appDir);
165
+ escalated = { from: lastDrive.grade ?? 'unknown', to: stronger.grade ?? 'unknown' };
166
+ if (undefined !== stronger.grade)
167
+ lastDrive = stronger;
168
+ }
169
+ if (undefined !== lastDrive.incomplete) {
170
+ print(`the drive did not finish, and ${lastDrive.incomplete}`);
171
+ }
172
+ return Promise.resolve('' === lastDrive.text ? null : lastDrive.text);
173
+ },
174
+ flowsSaved: () => flowsSaved(flowRoots),
175
+ now: () => Date.now(),
176
+ sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
177
+ note: print,
178
+ };
179
+ // A `finally` does not run on SIGINT, and this phase owns a detached dev server — see interrupt.ts.
180
+ const releaseSignals = stopOnInterrupt(() => {
181
+ server.stop();
182
+ }, process);
183
+ try {
184
+ const outcome = await runSetupPhases({ ...input, shape }, effects);
185
+ // The app stays up only when there is something worth watching.
186
+ if (outcome.ok)
187
+ server.handOver();
188
+ return {
189
+ ...outcome,
190
+ ...(undefined === escalated ? {} : { escalated }),
191
+ ...(undefined === lastDrive?.turns ? {} : { driveTurns: lastDrive.turns }),
192
+ ...(undefined === lastDrive?.costUsd ? {} : { driveCostUsd: lastDrive.costUsd }),
193
+ };
194
+ }
195
+ finally {
196
+ releaseSignals();
197
+ server.stop();
198
+ }
199
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * The contract between the agent and the script.
3
+ *
4
+ * Everything setup does is deterministic except for the handful of things that require reading a
5
+ * repository and a human's request and understanding both. Those are exactly what an agent is good
6
+ * at and code is not, so they arrive here as ARGUMENTS rather than as steps somebody walks through:
7
+ * the agent decides, the script executes, and neither does the other's job.
8
+ *
9
+ * What only an agent can know, and therefore what belongs in this type:
10
+ *
11
+ * --flow which journey proves the thing the user cares about. Code can enumerate buttons; it
12
+ * cannot know that checkout matters and the theme toggle does not. Measured, naming
13
+ * it took one app's drive from a ten-minute timeout to 138 seconds, because the turns
14
+ * go into FINDING a flow and naming one removes that search.
15
+ * --app which app in a monorepo the user meant. init can find the ones that are servable;
16
+ * only the request says which of them is being worked on.
17
+ * --env what the app needs to get past its own front door. A key in .env.example, a mock
18
+ * backend named in the README, the variable that skips an auth wall: all readable by
19
+ * an agent, none inferable by a script. Without this a real app sits on a login
20
+ * screen and there is no flow to drive at all.
21
+ * --dev-cmd the command, when the project's own scripts do not say it plainly.
22
+ * --url the app is already running here, so do not start anything.
23
+ *
24
+ * Everything else on this type is an operator's dial, not an agent's judgement.
25
+ */
26
+ /** The bridge port. NOT the dev server's, and conflating them is a documented setup failure. */
27
+ export declare const DEFAULT_BRIDGE_PORT = 4400;
28
+ /** Per-phase budget. The drive gets its own, much larger one. */
29
+ export declare const DEFAULT_PHASE_TIMEOUT_MS = 120000;
30
+ /** What the drive may spend before it is stopped. */
31
+ export declare const DEFAULT_DRIVE_BUDGET_USD = 3;
32
+ export interface SetupOptions {
33
+ /** The journey to drive, in the caller's own words. */
34
+ readonly flow?: string | undefined;
35
+ /** Which app in a monorepo. */
36
+ readonly app?: string | undefined;
37
+ /** Environment the app needs in order to reach a usable state. */
38
+ readonly env?: Readonly<Record<string, string>> | undefined;
39
+ /** The dev command, when the project's scripts do not say it plainly. */
40
+ readonly devCommand?: string | undefined;
41
+ /** The app is already served here. */
42
+ readonly url?: string | undefined;
43
+ readonly bridgePort: number;
44
+ readonly phaseTimeoutMs: number;
45
+ readonly driveBudgetUsd: number;
46
+ readonly driveModel?: string | undefined;
47
+ /** Register the MCP with the other coding agents on this machine. */
48
+ readonly registerAgents: boolean;
49
+ /** Open a browser. False for CI, a headless box, or a tab the user already has. */
50
+ readonly openBrowser: boolean;
51
+ /** Drive a flow. False leaves step five to the caller, and is the only honest way to skip it. */
52
+ readonly drive: boolean;
53
+ /** Re-record with the stronger model when the saved flow is graded weaker than `asserted`. */
54
+ readonly escalateWeakFlow: boolean;
55
+ /** Restart a dev server whose bundle predates the build-config edit. */
56
+ readonly restartStaleDevServer: boolean;
57
+ /** Write files and stop, which is what `init` did before it grew the runtime phases. */
58
+ readonly filesOnly: boolean;
59
+ /** A license key, written to .env and never echoed. */
60
+ readonly licenseKey?: string | undefined;
61
+ }
62
+ export declare const DEFAULT_SETUP_OPTIONS: SetupOptions;
63
+ /** One `KEY=VALUE`, as an agent would pass it. Everything after the first `=` is the value. */
64
+ export declare function parseEnvAssignment(pair: string): {
65
+ key: string;
66
+ value: string;
67
+ } | null;
68
+ /**
69
+ * Fold repeated `--env KEY=VALUE` into one map, ignoring what cannot be a variable.
70
+ *
71
+ * A malformed pair is dropped rather than guessed at: an agent that meant `--env` and typed
72
+ * something else should see its variable missing, not see setup invent one.
73
+ */
74
+ export declare function collectEnv(pairs: readonly string[]): Record<string, string>;