@reticlehq/server 3.4.0 → 3.6.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/README.md +1 -1
  2. package/SKILL.md +16 -8
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-verdict.d.ts +1 -0
  6. package/dist/command/cli/adhoc-verdict.js +6 -3
  7. package/dist/command/cli/answers/open-note.d.ts +2 -0
  8. package/dist/command/cli/answers/open-note.js +22 -0
  9. package/dist/command/cli/auth/cloud-session.d.ts +4 -4
  10. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  11. package/dist/command/cli/cli-flow-commands.js +4 -8
  12. package/dist/command/cli/cli-parse.d.ts +1 -1
  13. package/dist/command/cli/cli-parse.js +13 -11
  14. package/dist/command/cli/cli-verify.d.ts +1 -1
  15. package/dist/command/cli/cli-verify.js +35 -25
  16. package/dist/command/cli/cloud-cli.js +110 -12
  17. package/dist/command/cli/cloud-login.d.ts +9 -1
  18. package/dist/command/cli/cloud-login.js +43 -8
  19. package/dist/command/cli/demo/demo-page.js +11 -3
  20. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  21. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  22. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  23. package/dist/command/cli/drive/drive-attach.js +71 -4
  24. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  25. package/dist/command/cli/drive/drive-command.js +13 -7
  26. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  27. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  28. package/dist/command/cli/ports/port-holder.js +24 -0
  29. package/dist/command/cli/setup-mcp-cli.js +15 -5
  30. package/dist/command/cli/tutorial.d.ts +2 -2
  31. package/dist/command/cli/tutorial.js +6 -9
  32. package/dist/command/cli.d.ts +6 -0
  33. package/dist/command/cli.js +45 -23
  34. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  35. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  36. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  37. package/dist/command/daemon/daemon-resolve.js +33 -2
  38. package/dist/command/setup/agent-writer.d.ts +1 -1
  39. package/dist/command/setup/agent-writer.js +3 -2
  40. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  41. package/dist/command/setup/bringup/connect-lease.js +1 -0
  42. package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
  43. package/dist/command/setup/bringup/dev-server-wait.js +20 -1
  44. package/dist/command/setup/init/init-port.d.ts +4 -0
  45. package/dist/command/setup/init/init-port.js +10 -0
  46. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  47. package/dist/command/setup/init/init-runtime.js +71 -9
  48. package/dist/command/setup/node-effects.js +8 -2
  49. package/dist/command/setup/remaining-steps.js +2 -1
  50. package/dist/command/setup/run-setup.d.ts +7 -0
  51. package/dist/command/setup/run-setup.js +38 -25
  52. package/dist/command/setup/setup-command.d.ts +2 -1
  53. package/dist/command/setup/setup-command.js +2 -0
  54. package/dist/command/setup/setup-mcp.d.ts +2 -0
  55. package/dist/command/setup/setup-mcp.js +14 -5
  56. package/dist/command/version/peer-announce.d.ts +6 -2
  57. package/dist/command/version/peer-announce.js +8 -6
  58. package/dist/command/version/version-nudge.d.ts +17 -3
  59. package/dist/command/version/version-nudge.js +25 -4
  60. package/dist/command/version/version-skew.js +11 -2
  61. package/dist/features/crawl/crawl-tools.js +4 -0
  62. package/dist/features/crawl/crawl.d.ts +6 -0
  63. package/dist/features/crawl/crawl.js +9 -1
  64. package/dist/features/exhaust/ledger.d.ts +7 -0
  65. package/dist/features/exhaust/ledger.js +35 -9
  66. package/dist/features/harness/app-findings.d.ts +2 -0
  67. package/dist/features/harness/app-findings.js +52 -0
  68. package/dist/features/harness/drive-report.d.ts +27 -1
  69. package/dist/features/harness/drive-report.js +162 -11
  70. package/dist/features/harness/drivers.d.ts +10 -14
  71. package/dist/features/harness/drivers.js +10 -14
  72. package/dist/features/harness/fill-values.d.ts +3 -69
  73. package/dist/features/harness/fill-values.js +3 -133
  74. package/dist/features/harness/goals.d.ts +21 -0
  75. package/dist/features/harness/goals.js +77 -0
  76. package/dist/features/harness/harness.d.ts +21 -0
  77. package/dist/features/harness/harness.js +69 -2
  78. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  79. package/dist/features/harness/platform/drive-target.js +81 -0
  80. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  81. package/dist/features/harness/platform/local-apps.js +204 -0
  82. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  83. package/dist/features/harness/platform/remote-drive.js +365 -0
  84. package/dist/features/harness/platform/script.d.ts +48 -0
  85. package/dist/features/harness/platform/script.js +128 -0
  86. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  87. package/dist/features/harness/platform/server-driver.js +179 -0
  88. package/dist/features/harness/platform-config.d.ts +7 -0
  89. package/dist/features/harness/platform-config.js +14 -2
  90. package/dist/features/harness/script/app-controls.d.ts +5 -0
  91. package/dist/features/harness/script/app-controls.js +40 -0
  92. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  93. package/dist/features/harness/script/lane-ids.js +16 -0
  94. package/dist/features/harness/script/script-run.d.ts +41 -0
  95. package/dist/features/harness/script/script-run.js +299 -0
  96. package/dist/features/license/license.d.ts +2 -2
  97. package/dist/features/visual/visual-tools.d.ts +10 -1
  98. package/dist/features/visual/visual-tools.js +40 -3
  99. package/dist/hooks/driven-by.d.ts +4 -0
  100. package/dist/hooks/driven-by.js +16 -0
  101. package/dist/hooks/hook-commands.js +15 -4
  102. package/dist/hooks/tool-log.d.ts +9 -0
  103. package/dist/hooks/tool-log.js +29 -0
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.js +91 -89
  106. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  107. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  108. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  109. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  110. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  111. package/dist/judgement/runs/artifact/run-store.js +26 -1
  112. package/dist/judgement/runs/drive-run.d.ts +22 -0
  113. package/dist/judgement/runs/drive-run.js +99 -1
  114. package/dist/judgement/runs/repair-prompt.js +1 -1
  115. package/dist/judgement/runs/reticle-runner.js +8 -1
  116. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  117. package/dist/judgement/runs/verification-sync.js +4 -3
  118. package/dist/language/flows/change/gate.d.ts +10 -0
  119. package/dist/language/flows/change/gate.js +22 -0
  120. package/dist/language/flows/decision.js +34 -0
  121. package/dist/language/flows/flow-anchor.d.ts +54 -8
  122. package/dist/language/flows/flow-anchor.js +88 -3
  123. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  124. package/dist/language/flows/flow-cross-step.js +21 -0
  125. package/dist/language/flows/flow-learning.js +8 -11
  126. package/dist/language/flows/flow-mutate-tools.js +5 -3
  127. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  128. package/dist/language/flows/flow-replay-run.js +101 -94
  129. package/dist/language/flows/flow-replay.d.ts +23 -1
  130. package/dist/language/flows/flow-replay.js +92 -19
  131. package/dist/language/flows/flow-scope.js +5 -1
  132. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  133. package/dist/language/flows/flow-step-runners.js +236 -42
  134. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  135. package/dist/language/flows/flow-store-for-session.js +2 -2
  136. package/dist/language/flows/flow-tools.js +58 -34
  137. package/dist/language/flows/flows.js +5 -2
  138. package/dist/language/flows/heal-run.js +6 -4
  139. package/dist/language/flows/recording/flake.d.ts +2 -2
  140. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  141. package/dist/language/flows/recording/tape/recordings.js +40 -8
  142. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  143. package/dist/language/flows/replay-from-hud.js +51 -0
  144. package/dist/language/flows/replay.d.ts +9 -5
  145. package/dist/language/flows/replay.js +27 -9
  146. package/dist/language/flows/suite/suite-flakes.js +2 -0
  147. package/dist/language/flows/suite-selection.d.ts +2 -15
  148. package/dist/language/flows/suite-selection.js +4 -3
  149. package/dist/language/flows/verify-change-tools.js +8 -4
  150. package/dist/launch-chromium.d.ts +27 -5
  151. package/dist/launch-chromium.js +38 -8
  152. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  153. package/dist/memory/cloud/cloud-config.js +15 -0
  154. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  155. package/dist/memory/cloud/cloud-sync.js +12 -1
  156. package/dist/memory/cloud/harness-config.d.ts +26 -1
  157. package/dist/memory/cloud/harness-config.js +99 -4
  158. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  159. package/dist/memory/cloud/hud-notices-source.js +128 -0
  160. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  161. package/dist/memory/cloud/memory-scope.js +205 -0
  162. package/dist/memory/cloud/sync-cycle.d.ts +21 -3
  163. package/dist/memory/cloud/sync-cycle.js +45 -5
  164. package/dist/memory/cloud/sync-daemon.js +7 -5
  165. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  166. package/dist/memory/cloud/sync-disk.js +5 -8
  167. package/dist/memory/impact/account-watch.d.ts +19 -0
  168. package/dist/memory/impact/account-watch.js +34 -0
  169. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  170. package/dist/memory/impact/impact-recorder.js +12 -2
  171. package/dist/memory/impact/impact-store.d.ts +16 -0
  172. package/dist/memory/impact/impact-store.js +142 -26
  173. package/dist/memory/intent/intent-shard.d.ts +2 -2
  174. package/dist/memory/intent/intent-tools.js +88 -5
  175. package/dist/memory/journal/envelope.d.ts +16 -16
  176. package/dist/memory/journal/journal-query.d.ts +14 -0
  177. package/dist/memory/journal/journal-query.js +15 -0
  178. package/dist/memory/journal/journal-recorder.js +7 -1
  179. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  180. package/dist/memory/journal/on-disk/retention.js +5 -0
  181. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  182. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  183. package/dist/memory/journal/session-end.js +6 -4
  184. package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
  185. package/dist/memory/project/artifact-root-resolver.js +134 -26
  186. package/dist/memory/project/artifact-root.d.ts +42 -5
  187. package/dist/memory/project/artifact-root.js +87 -8
  188. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  189. package/dist/memory/project/dir/reticle-dir.js +3 -1
  190. package/dist/memory/project/project-tools.js +22 -3
  191. package/dist/memory/project/session-root.d.ts +24 -1
  192. package/dist/memory/project/session-root.js +41 -5
  193. package/dist/memory/project/sync-status.d.ts +39 -0
  194. package/dist/memory/project/sync-status.js +90 -0
  195. package/dist/memory/recall/memory-tools.js +2 -0
  196. package/dist/memory/recall/project-memory.d.ts +19 -0
  197. package/dist/memory/recall/project-memory.js +45 -6
  198. package/dist/portal/bridge/bridge.d.ts +7 -4
  199. package/dist/portal/bridge/bridge.js +25 -3
  200. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  201. package/dist/portal/bridge/pairing-token.js +17 -0
  202. package/dist/portal/input/real-input.d.ts +81 -10
  203. package/dist/portal/input/real-input.js +207 -22
  204. package/dist/portal/input/viewport-tools.js +19 -9
  205. package/dist/portal/pool/browser-pool.d.ts +22 -9
  206. package/dist/portal/pool/browser-pool.js +70 -35
  207. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  208. package/dist/portal/pool/playwright-launcher.js +17 -1
  209. package/dist/portal/pool/pool-contract.d.ts +7 -4
  210. package/dist/portal/session/command-payload.d.ts +8 -0
  211. package/dist/portal/session/command-payload.js +19 -0
  212. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  213. package/dist/portal/session/dev-server/dev-command.js +61 -12
  214. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  215. package/dist/portal/session/facts/departure-note.js +47 -0
  216. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  217. package/dist/portal/session/facts/session-replaced.js +5 -0
  218. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  219. package/dist/portal/session/first-run-wiring.js +132 -0
  220. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  221. package/dist/portal/session/human/notes-ledger.js +55 -0
  222. package/dist/portal/session/human/review-store.d.ts +12 -0
  223. package/dist/portal/session/human/review-store.js +18 -10
  224. package/dist/portal/session/live-control-tools.d.ts +0 -13
  225. package/dist/portal/session/live-control-tools.js +28 -3
  226. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  227. package/dist/portal/session/no-session-diagnosis.js +15 -0
  228. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  229. package/dist/portal/session/no-session-next-action.js +31 -5
  230. package/dist/portal/session/no-session-watch.d.ts +14 -1
  231. package/dist/portal/session/no-session-watch.js +63 -10
  232. package/dist/portal/session/presence/session-mirror.js +3 -0
  233. package/dist/portal/session/serving-directory.d.ts +27 -0
  234. package/dist/portal/session/serving-directory.js +45 -0
  235. package/dist/portal/session/session-health.d.ts +7 -0
  236. package/dist/portal/session/session-health.js +6 -0
  237. package/dist/portal/session/session-info.d.ts +8 -1
  238. package/dist/portal/session/session-info.js +6 -0
  239. package/dist/portal/session/session-manager.d.ts +21 -0
  240. package/dist/portal/session/session-manager.js +44 -8
  241. package/dist/portal/session/session-reaper.js +4 -1
  242. package/dist/portal/session/session-successor.d.ts +5 -0
  243. package/dist/portal/session/session-tools.js +26 -3
  244. package/dist/portal/session/session.d.ts +12 -10
  245. package/dist/portal/session/session.js +30 -31
  246. package/dist/start-options.d.ts +6 -0
  247. package/dist/status-payload.d.ts +10 -0
  248. package/dist/status-payload.js +23 -0
  249. package/dist/surface/http-server.d.ts +1 -1
  250. package/dist/surface/http-server.js +15 -4
  251. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  252. package/dist/surface/mcp/mcp-proxy.js +36 -5
  253. package/dist/surface/mcp/mcp.d.ts +11 -6
  254. package/dist/surface/mcp/mcp.js +28 -14
  255. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  256. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  257. package/dist/surface/mcp/server-instructions.js +14 -4
  258. package/dist/surface/mcp/shared-params.js +2 -2
  259. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  260. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  261. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  262. package/dist/surface/tools/act-sequence-tool.js +2 -2
  263. package/dist/surface/tools/act-tools.js +25 -9
  264. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  265. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  266. package/dist/surface/tools/browser-tools.js +8 -1
  267. package/dist/surface/tools/chat-drives.d.ts +26 -0
  268. package/dist/surface/tools/chat-drives.js +165 -0
  269. package/dist/surface/tools/dynamic-tools.js +29 -2
  270. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  271. package/dist/surface/tools/empty-session-list.js +80 -0
  272. package/dist/surface/tools/error-recovery.d.ts +6 -0
  273. package/dist/surface/tools/error-recovery.js +38 -6
  274. package/dist/surface/tools/explore-tools.d.ts +10 -1
  275. package/dist/surface/tools/explore-tools.js +82 -13
  276. package/dist/surface/tools/feedback-tools.js +14 -0
  277. package/dist/surface/tools/harness-explore.d.ts +96 -14
  278. package/dist/surface/tools/harness-explore.js +227 -202
  279. package/dist/surface/tools/harness-plan.d.ts +12 -0
  280. package/dist/surface/tools/harness-plan.js +31 -0
  281. package/dist/surface/tools/harness-script.d.ts +8 -0
  282. package/dist/surface/tools/harness-script.js +342 -0
  283. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  284. package/dist/surface/tools/harness-toolset.js +77 -6
  285. package/dist/surface/tools/invoke-tool.js +64 -9
  286. package/dist/surface/tools/lease-seed.d.ts +29 -0
  287. package/dist/surface/tools/lease-seed.js +123 -0
  288. package/dist/surface/tools/lease-tools.d.ts +3 -23
  289. package/dist/surface/tools/lease-tools.js +30 -131
  290. package/dist/surface/tools/live-call-text.js +2 -2
  291. package/dist/surface/tools/next-step.d.ts +19 -0
  292. package/dist/surface/tools/next-step.js +159 -0
  293. package/dist/surface/tools/observe-tools.js +2 -4
  294. package/dist/surface/tools/platform-moment.d.ts +12 -0
  295. package/dist/surface/tools/platform-moment.js +168 -0
  296. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  297. package/dist/surface/tools/query-hint-schema.js +10 -0
  298. package/dist/surface/tools/read-tools.js +8 -2
  299. package/dist/surface/tools/real-input-attempt.js +96 -13
  300. package/dist/surface/tools/tool-kit.d.ts +25 -1
  301. package/dist/surface/tools/tool-kit.js +4 -6
  302. package/dist/surface/tools/tools.js +20 -48
  303. package/dist/telemetry/argument-shape.js +1 -1
  304. package/dist/telemetry/hud-metrics.d.ts +2 -0
  305. package/dist/telemetry/hud-metrics.js +17 -0
  306. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  307. package/dist/telemetry/onboarding-firsts.js +2 -2
  308. package/dist/telemetry/telemetry.d.ts +12 -0
  309. package/dist/telemetry/telemetry.js +43 -14
  310. package/dist/telemetry/verification-of.js +1 -0
  311. package/dist/wire-journal.js +4 -1
  312. package/docs/README.md +1 -1
  313. package/docs/actions.mdx +1 -2
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/autodrive.md +29 -25
  316. package/docs/cli/cloud.mdx +6 -4
  317. package/docs/cli/doctor.mdx +6 -0
  318. package/docs/cli/drive.mdx +8 -2
  319. package/docs/cli/init.mdx +11 -13
  320. package/docs/cli/status.mdx +5 -1
  321. package/docs/cli/version.mdx +3 -3
  322. package/docs/cli.mdx +14 -8
  323. package/docs/desktop.mdx +5 -1
  324. package/docs/environment.mdx +3 -7
  325. package/docs/getting-started.md +14 -2
  326. package/docs/human-control.md +1 -2
  327. package/docs/index.mdx +4 -3
  328. package/docs/install-agentic.mdx +1 -1
  329. package/docs/install-manual.mdx +2 -2
  330. package/docs/packages/next.mdx +3 -0
  331. package/docs/packages/tauri.mdx +2 -2
  332. package/docs/packages/vite-plugin.mdx +24 -1
  333. package/docs/packages.mdx +1 -1
  334. package/docs/quickstart.mdx +23 -11
  335. package/docs/telemetry.md +1 -1
  336. package/docs/tools/act.mdx +1 -1
  337. package/docs/tools/session-and-feedback.mdx +9 -9
  338. package/docs/usage.md +5 -3
  339. package/docs/what-is-recorded.md +13 -3
  340. package/docs/what-is-reticle.mdx +3 -1
  341. package/package.json +8 -12
  342. package/dist/features/harness/driver.d.ts +0 -50
  343. package/dist/features/harness/driver.js +0 -236
  344. package/dist/features/harness/jev-driver.d.ts +0 -188
  345. package/dist/features/harness/jev-driver.js +0 -678
  346. package/dist/features/harness/openai-driver.d.ts +0 -46
  347. package/dist/features/harness/openai-driver.js +0 -194
  348. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  349. package/dist/memory/project/dir/fill-value-store.js +0 -88
  350. package/docs/local-registry.md +0 -143
@@ -25,6 +25,8 @@ export interface WaitFacts {
25
25
  readonly quietForMs: number;
26
26
  /** Milliseconds since the wait began. */
27
27
  readonly elapsedMs: number;
28
+ /** Explicit caller limit; otherwise a busy build keeps the default ceiling. */
29
+ readonly budgetMs?: number | undefined;
28
30
  /**
29
31
  * How long silence may mean "still starting" on THIS machine. Defaults to QUIET_MEANS_HUNG_MS.
30
32
  *
@@ -57,6 +59,14 @@ export declare const WAIT_CEILING_MS: number;
57
59
  */
58
60
  export declare function judgeWait(facts: WaitFacts): WaitVerdict;
59
61
  export declare function portBusyMessage(output: string): string | undefined;
62
+ /**
63
+ * The dev server died before serving: say so, and quote the end of what it printed.
64
+ *
65
+ * Without the tail the reason was invisible: a missing `vite` binary printed `vite: command not
66
+ * found` to its own log, setup said only "exited without serving anything", and its first remedy
67
+ * ("start the dev server yourself") failed the same way.
68
+ */
69
+ export declare function devServerDiedMessage(output: string): string;
60
70
  export declare function announcedUrl(output: string): string | undefined;
61
71
  /**
62
72
  * The url to watch: whatever the tool announced, else one composed from a port we OBSERVED our own
@@ -38,7 +38,7 @@ export function judgeWait(facts) {
38
38
  return WaitVerdict.READY;
39
39
  if (facts.launcherExited)
40
40
  return WaitVerdict.DEAD;
41
- if (facts.elapsedMs >= WAIT_CEILING_MS)
41
+ if (facts.elapsedMs >= (facts.budgetMs ?? WAIT_CEILING_MS))
42
42
  return WaitVerdict.HUNG;
43
43
  // Output IS progress. Silence is what distinguishes a build from a wedge.
44
44
  return facts.quietForMs >= (facts.quietMeansHungMs ?? QUIET_MEANS_HUNG_MS)
@@ -61,6 +61,25 @@ export function portBusyMessage(output) {
61
61
  return (`port ${port} is already in use — something else is serving there, so this launcher did not ` +
62
62
  `bind. Pass --url http://localhost:${port} if that process is this app, or free the port and re-run.`);
63
63
  }
64
+ /** How many of the dev server's last lines a failure quotes. Enough for a stack's first frames. */
65
+ const DIED_TAIL_LINES = 6;
66
+ /**
67
+ * The dev server died before serving: say so, and quote the end of what it printed.
68
+ *
69
+ * Without the tail the reason was invisible: a missing `vite` binary printed `vite: command not
70
+ * found` to its own log, setup said only "exited without serving anything", and its first remedy
71
+ * ("start the dev server yourself") failed the same way.
72
+ */
73
+ export function devServerDiedMessage(output) {
74
+ const tail = output
75
+ .split('\n')
76
+ .map((line) => line.trimEnd())
77
+ .filter((line) => 0 < line.trim().length)
78
+ .slice(-DIED_TAIL_LINES);
79
+ return 0 === tail.length
80
+ ? 'The dev server exited without serving anything, and printed nothing.'
81
+ : `The dev server exited without serving anything. The last it printed:\n${tail.map((line) => ` ${line}`).join('\n')}`;
82
+ }
64
83
  /** A url the dev server announced, if it has announced one. Never composed, only read. */
65
84
  const URL_IN_OUTPUT = /https?:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):\d+/;
66
85
  export function announcedUrl(output) {
@@ -12,4 +12,8 @@ export declare function portForInit(explicit: number | undefined, configured: nu
12
12
  daemonProjects: (port: number) => Promise<readonly string[]>;
13
13
  daemonPresent: (port: number) => Promise<boolean>;
14
14
  pickPort: (preferred: number) => Promise<number>;
15
+ /** The daemon on `port` was started from this project's directory or one above it. */
16
+ daemonStartedHere?: (port: number) => boolean;
15
17
  }): Promise<number | undefined>;
18
+ /** Is `dir` the same directory as `cwd`, or an ancestor of it? */
19
+ export declare function isSameOrAbove(dir: string, cwd: string): boolean;
@@ -8,6 +8,7 @@
8
8
  *
9
9
  * Undefined means the default.
10
10
  */
11
+ import { relative, isAbsolute } from 'node:path';
11
12
  import { RETICLE_DEFAULT_PORT, ReticleEnv } from '@reticlehq/core';
12
13
  const MAX_PORT = 65_535;
13
14
  /**
@@ -34,6 +35,10 @@ export async function portForInit(explicit, configured, projectId, deps) {
34
35
  // config as a `port` nobody chose.
35
36
  if (!(await deps.daemonPresent(RETICLE_DEFAULT_PORT)))
36
37
  return undefined;
38
+ // Started here — by this project's own editor MCP, typically — so it is ours whatever project id
39
+ // it recorded. Moving off it strands that agent: an MCP resolves its port once, at startup.
40
+ if (true === deps.daemonStartedHere?.(RETICLE_DEFAULT_PORT))
41
+ return undefined;
37
42
  // The bridge's own rule: it refuses a page only when it has served exactly ONE other project. A
38
43
  // daemon serving nobody, several projects, or ours accepts this one. A project that has never
39
44
  // been through init has no id yet, so a single named holder is somebody else.
@@ -42,3 +47,8 @@ export async function portForInit(explicit, configured, projectId, deps) {
42
47
  return undefined;
43
48
  return deps.pickPort(RETICLE_DEFAULT_PORT + 1);
44
49
  }
50
+ /** Is `dir` the same directory as `cwd`, or an ancestor of it? */
51
+ export function isSameOrAbove(dir, cwd) {
52
+ const rel = relative(dir, cwd);
53
+ return '' === rel || (!rel.startsWith('..') && !isAbsolute(rel));
54
+ }
@@ -6,6 +6,16 @@
6
6
  * every command harder to read.
7
7
  */
8
8
  import { type InitResult } from '@reticlehq/init';
9
+ /**
10
+ * Does the served document name the SDK, so its absence means a stale dev server?
11
+ *
12
+ * A Next App Router page names its client components in the flight data inlined into the HTML —
13
+ * `reticle-dev.tsx`, measured on Next 16 under Turbopack and webpack alike. Reported from a Next app
14
+ * whose dev server was already running before init: setup waited on a page that could never connect
15
+ * and said nothing, because Next was assumed to carry the SDK only in its bundle. The Pages Router
16
+ * names chunks, not modules, so it stays silent.
17
+ */
18
+ export declare function htmlCarriesSdk(framework: string, exists: (path: string) => boolean): boolean;
9
19
  /** Just enough of the parsed command to decide and run. */
10
20
  interface InitRuntimeArgs {
11
21
  readonly port: number | undefined;
@@ -6,8 +6,9 @@
6
6
  * every command harder to read.
7
7
  */
8
8
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
9
- import { RETICLE_DEFAULT_PORT } from '@reticlehq/core';
10
- import { FEEDBACK_HINT, Framework } from '@reticlehq/init';
9
+ import { join } from 'node:path';
10
+ import { InitConfirmation, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
11
+ import { FEEDBACK_HINT, Framework, InitFailure } from '@reticlehq/init';
11
12
  import { confirmInstall, nodeConfirmDeps } from '../terminal/confirm.js';
12
13
  import { writeLicenseKey } from '../license-key.js';
13
14
  import { registerOtherAgents, runSetupCommand } from '../setup-command.js';
@@ -20,6 +21,8 @@ import { probeDaemon } from '../../../surface/mcp/proxy/proxy-daemon-probe.js';
20
21
  import { fetchStatus } from '../../daemon/binding/daemon-status-probe.js';
21
22
  import { collectEnv, DEFAULT_PHASE_TIMEOUT_MS } from '../setup-options.js';
22
23
  import { staticPageDevCommand } from '../bringup/static-page-server.js';
24
+ import { SetupPhase } from '../run-setup.js';
25
+ import { reportInitOutcome } from '../../../telemetry/init-telemetry.js';
23
26
  /** How often the runtime phases look again: fast enough not to be the wait, slow enough to be free. */
24
27
  const POLL_MS = 250;
25
28
  /**
@@ -28,6 +31,22 @@ const POLL_MS = 250;
28
31
  * of the document can never see it. See `htmlCarriesSdk` in run-setup.ts.
29
32
  */
30
33
  const HTML_CARRIES_SDK = new Set([Framework.VITE, Framework.HTML]);
34
+ /** The connect component `init` writes for a Next App Router project. */
35
+ const NEXT_APP_CONNECT = ['app', 'src/app'].flatMap((d) => ['tsx', 'jsx', 'js'].map((ext) => `${d}/reticle-dev.${ext}`));
36
+ /**
37
+ * Does the served document name the SDK, so its absence means a stale dev server?
38
+ *
39
+ * A Next App Router page names its client components in the flight data inlined into the HTML —
40
+ * `reticle-dev.tsx`, measured on Next 16 under Turbopack and webpack alike. Reported from a Next app
41
+ * whose dev server was already running before init: setup waited on a page that could never connect
42
+ * and said nothing, because Next was assumed to carry the SDK only in its bundle. The Pages Router
43
+ * names chunks, not modules, so it stays silent.
44
+ */
45
+ export function htmlCarriesSdk(framework, exists) {
46
+ if (HTML_CARRIES_SDK.has(framework))
47
+ return true;
48
+ return Framework.NEXT === framework && NEXT_APP_CONNECT.some(exists);
49
+ }
31
50
  /** Registration is global MCP wiring, so both flags that disown it are read here. */
32
51
  function wantsAgents(parsed) {
33
52
  return false !== parsed.agents && false !== parsed.mcp;
@@ -59,9 +78,9 @@ function printRelaunch(parsed, io, cwd) {
59
78
  }
60
79
  export async function continueAfterInit(parsed, result, io, cwd) {
61
80
  const port = parsed.port ?? RETICLE_DEFAULT_PORT;
62
- // Before anything else: the key belongs in .env whichever way this run ends, and the CLI folds a
63
- // project-local .env into the environment on every invocation.
64
- if (undefined !== parsed.licenseKey) {
81
+ // Real installs persist the key whichever way setup ends. A preview must leave .env and
82
+ // .gitignore untouched, just as it leaves project wiring and agent configuration untouched.
83
+ if (!parsed.dryRun && undefined !== parsed.licenseKey) {
65
84
  const written = writeLicenseKey(cwd, parsed.licenseKey, licenseIo);
66
85
  io.print(written.message);
67
86
  }
@@ -79,9 +98,12 @@ export async function continueAfterInit(parsed, result, io, cwd) {
79
98
  // has: nothing reaches back into a machine that installed Reticle a version ago, so the
80
99
  // upgrade path is re-running init, and the light form of init has to be enough to carry it.
81
100
  // A dry run writes nothing anywhere, including here.
82
- if (true === parsed.filesOnly && wantsAgents(parsed))
101
+ if (!parsed.dryRun && true === parsed.filesOnly && wantsAgents(parsed)) {
83
102
  registerOtherAgents(io.print);
103
+ }
84
104
  return confirmInstall(result, io, nodeConfirmDeps(port)).then(() => {
105
+ if (true === parsed.json)
106
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
85
107
  if (!result.ok)
86
108
  process.exit(1);
87
109
  });
@@ -96,8 +118,15 @@ export async function continueAfterInit(parsed, result, io, cwd) {
96
118
  if (context === undefined) {
97
119
  // Nothing was established, so there is nothing to run against. init has already said why.
98
120
  ask();
121
+ if (true === parsed.json)
122
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
99
123
  process.exit(1);
100
124
  }
125
+ const reportFailure = (reason) => {
126
+ if (result.outcome !== undefined) {
127
+ reportInitOutcome({ ...result.outcome, ok: false, reason });
128
+ }
129
+ };
101
130
  // A PENDING connect step is not a reason to skip the runtime phase, and treating it as one made
102
131
  // `init` stop with "paste this snippet" while never looking at the app.
103
132
  //
@@ -113,10 +142,17 @@ export async function continueAfterInit(parsed, result, io, cwd) {
113
142
  // Before the connect wait, never after: a bridge held by a stranger makes a session impossible,
114
143
  // so going ahead spends the entire budget and then reports what reads as an instrumentation
115
144
  // problem — the one place that is fine. See bridge-port.ts.
116
- const refusal = bridgeOccupied(await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }), port);
145
+ const refusal = bridgeOccupied(await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }).catch((error) => {
146
+ reportFailure(InitFailure.RUNTIME_ERROR);
147
+ throw error;
148
+ }), port);
117
149
  if (refusal !== undefined) {
150
+ reportFailure(InitFailure.BRIDGE_OCCUPIED);
118
151
  io.print(refusal);
119
152
  ask();
153
+ if (true === parsed.json) {
154
+ process.stdout.write(`${JSON.stringify({ ok: false, reason: InitFailure.BRIDGE_OCCUPIED }, null, 2)}\n`);
155
+ }
120
156
  process.exit(1);
121
157
  }
122
158
  return runSetupCommand({
@@ -136,9 +172,12 @@ export async function continueAfterInit(parsed, result, io, cwd) {
136
172
  // keeps deciding, so nobody who passed nothing waits less than they used to.
137
173
  ...(undefined === parsed.timeoutSeconds
138
174
  ? {}
139
- : { connectBudgetMs: parsed.timeoutSeconds * 1000 }),
175
+ : {
176
+ connectBudgetMs: parsed.timeoutSeconds * 1000,
177
+ startupBudgetMs: parsed.timeoutSeconds * 1000,
178
+ }),
140
179
  pollMs: POLL_MS,
141
- htmlCarriesSdk: HTML_CARRIES_SDK.has(context.framework),
180
+ htmlCarriesSdk: htmlCarriesSdk(context.framework, (rel) => existsSync(join(context.appDir, rel))),
142
181
  // A plain page with no dev script gets Reticle's own static server, so the page is actually
143
182
  // served and the connect can be proved, instead of stopping at "start the app yourself".
144
183
  ...(() => {
@@ -153,6 +192,26 @@ export async function continueAfterInit(parsed, result, io, cwd) {
153
192
  })(),
154
193
  ...(undefined === parsed.url ? {} : { suppliedUrl: parsed.url }),
155
194
  }, (line) => io.print(line)).then((outcome) => {
195
+ if (result.outcome !== undefined) {
196
+ const init = {
197
+ ...result.outcome,
198
+ ok: outcome.ok,
199
+ };
200
+ if (outcome.ok) {
201
+ delete init.reason;
202
+ init.confirmation = InitConfirmation.CONNECTED;
203
+ }
204
+ else {
205
+ // A failed startup does not establish whether a daemon or instrumented page exists.
206
+ // Keep the watcher's no_daemon/no_session/no_page classifications for observed facts.
207
+ delete init.confirmation;
208
+ init.reason =
209
+ outcome.reachedPhase === SetupPhase.DEV_SERVER
210
+ ? InitFailure.DEV_SERVER
211
+ : InitFailure.APP_CONNECTION;
212
+ }
213
+ reportInitOutcome(init);
214
+ }
156
215
  // One object, so an agent reads a result instead of interpreting a report.
157
216
  if (true === parsed.json) {
158
217
  process.stdout.write(`${JSON.stringify(outcome, null, 2)}\n`);
@@ -201,5 +260,8 @@ export async function continueAfterInit(parsed, result, io, cwd) {
201
260
  io.print(` ${i + 1}. ${step}`);
202
261
  ask();
203
262
  process.exit(1);
263
+ }, (error) => {
264
+ reportFailure(InitFailure.RUNTIME_ERROR);
265
+ throw error;
204
266
  });
205
267
  }
@@ -23,6 +23,8 @@ import { reticleStateHome } from '../daemon/daemon.js';
23
23
  import { descendants, parseLsofPorts, parseNetstatListeners, parseWmicProcesses, } from './terminal/listeners.js';
24
24
  import { isAddressMiss, loopbackProbeUrls } from './probe/loopback-probe-urls.js';
25
25
  const WINDOWS = 'win32' === process.platform;
26
+ /** Astro otherwise daemonizes automatically when it detects an agent terminal. */
27
+ const ASTRO_DEV_BACKGROUND_ENV = 'ASTRO_DEV_BACKGROUND';
26
28
  /** A page fetch that is slow is a page fetch that failed, for our purposes. */
27
29
  const PROBE_TIMEOUT_MS = 5_000;
28
30
  /** The supervisor that owns the server's output and keeps its log bounded. */
@@ -64,7 +66,10 @@ export class OwnedDevServer {
64
66
  detached: !WINDOWS,
65
67
  // Nothing of ours: the supervisor holds the server's pipes, and init holds none of its.
66
68
  stdio: 'ignore',
67
- env: { ...process.env, ...env },
69
+ // Reticle already provides the detached supervisor. Astro's agent auto-backgrounding
70
+ // would escape its process group and leave the recorded handover PID pointing at a dead
71
+ // launcher, so re-init and cleanup could no longer find or stop the actual server.
72
+ env: { ...process.env, ...env, [ASTRO_DEV_BACKGROUND_ENV]: '1' },
68
73
  });
69
74
  this.log = log;
70
75
  this.cwd = cwd;
@@ -101,7 +106,8 @@ export class OwnedDevServer {
101
106
  catch {
102
107
  /* not written yet */
103
108
  }
104
- return Date.now() - lastOutputAt;
109
+ // Filesystem timestamps can include a fractional millisecond beyond Date.now()'s integer.
110
+ return Math.max(0, Date.now() - lastOutputAt);
105
111
  }
106
112
  /** Ports anything in this server's process tree is listening on. */
107
113
  listeningPorts() {
@@ -34,7 +34,8 @@ export function remainingSteps(p) {
34
34
  '`reticle_*` tools, ending in `reticle_act_and_wait` or `reticle_assert` — those two are ' +
35
35
  'what produce a verdict. Or hand the whole drive to Reticle with `reticle_verify { action: ' +
36
36
  '"explore", persona: "<who does what>" }`, which records what it drove so later runs replay ' +
37
- 'with no model in the loop; that route needs ANTHROPIC_API_KEY.');
37
+ 'with no model in the loop; that route runs on the Reticle platform and needs a linked ' +
38
+ 'project (`reticle connect`); every plan, Free included, has monthly Harness credits.');
38
39
  }
39
40
  // Whether anything is actually outstanding, read BEFORE the docs pointer is added -- that line is
40
41
  // an offer, not a task, and a run with nothing left still gets it.
@@ -55,6 +55,11 @@ export interface DaemonReason {
55
55
  export interface OwnedBrowser {
56
56
  readonly sessionId: string;
57
57
  readonly release: () => Promise<void>;
58
+ /**
59
+ * The page never dialled in, so the lease injected Reticle's own reader. That session proves the
60
+ * reader, not this install, and must never end onboarding as "connected".
61
+ */
62
+ readonly zeroInstall?: boolean;
58
63
  }
59
64
  export interface SetupEffects {
60
65
  /** Start the dev server. Resolves once started; the caller owns stopping it. */
@@ -132,6 +137,8 @@ export interface SetupInput {
132
137
  * before setup could say what was wrong.
133
138
  */
134
139
  readonly connectBudgetMs?: number | undefined;
140
+ /** Explicit --timeout for startup; absent preserves the adaptive build wait. */
141
+ readonly startupBudgetMs?: number | undefined;
135
142
  readonly openBrowser: boolean;
136
143
  /** Web, Electron or Tauri. Desktop changes three things; see desktop-shape.ts. */
137
144
  readonly shape: AppShape;
@@ -13,7 +13,7 @@
13
13
  * way of going wrong, and the alternative to injecting them is a test that boots a real dev server
14
14
  * and a real browser to find out what happens when neither works.
15
15
  */
16
- import { judgeWait, portBusyMessage, QUIET_MEANS_HUNG_MS, urlToWatch, WaitVerdict, } from './bringup/dev-server-wait.js';
16
+ import { judgeWait, devServerDiedMessage, portBusyMessage, QUIET_MEANS_HUNG_MS, urlToWatch, WaitVerdict, } from './bringup/dev-server-wait.js';
17
17
  import { connectProgressLine, waitProgressLine } from './terminal/wait-progress.js';
18
18
  /**
19
19
  * Windows gets longer to say something before silence counts as a wedge.
@@ -159,6 +159,7 @@ export async function runSetupPhases(input, fx) {
159
159
  serving,
160
160
  quietForMs: fx.devServerQuietForMs(),
161
161
  elapsedMs: fx.now() - startedAt,
162
+ budgetMs: input.startupBudgetMs,
162
163
  quietMeansHungMs: WINDOWS_QUIET_MEANS_HUNG_MS_APPLIES
163
164
  ? WINDOWS_QUIET_MEANS_HUNG_MS
164
165
  : QUIET_MEANS_HUNG_MS,
@@ -176,16 +177,22 @@ export async function runSetupPhases(input, fx) {
176
177
  break;
177
178
  }
178
179
  if (WaitVerdict.DEAD === verdict) {
179
- note(portBusyMessage(fx.devServerOutput()) ??
180
- 'The dev server exited without serving anything.');
180
+ note(portBusyMessage(fx.devServerOutput()) ?? devServerDiedMessage(fx.devServerOutput()));
181
181
  return stop(input, SetupPhase.DEV_SERVER, {}, notes);
182
182
  }
183
183
  if (WaitVerdict.HUNG === verdict) {
184
184
  // Says BOTH were checked. A server that prints nothing but IS listening is the CRA case and
185
185
  // must not be failed, so a reader has to be able to tell "we looked at the log" from "we
186
186
  // looked at the log AND the ports".
187
- note('The dev server neither printed a URL nor bound a port, so setup has nothing to open. ' +
188
- 'Check its log, or pass --url with the address it serves.');
187
+ const within = input.startupBudgetMs !== undefined && fx.now() - startedAt >= input.startupBudgetMs
188
+ ? ` within ${String(input.startupBudgetMs)}ms`
189
+ : undefined;
190
+ note((undefined === watching
191
+ ? `The dev server neither printed a URL nor bound a port${within ?? ''}, so setup has nothing to open. `
192
+ : undefined !== within
193
+ ? `The dev server did not become ready${within}. `
194
+ : `The dev server stopped making progress before ${watching} became ready. `) +
195
+ 'Check its log, increase --timeout for a slow build, or pass --url with the address it serves.');
189
196
  return stop(input, SetupPhase.DEV_SERVER, {}, notes);
190
197
  }
191
198
  await fx.sleep(input.pollMs);
@@ -227,6 +234,23 @@ export async function runSetupPhases(input, fx) {
227
234
  // dialled the bridge" over a correct install. With a lease to fall back on it is entered too, and
228
235
  // only the system browser is skipped.
229
236
  const canLease = undefined !== fx.openLease;
237
+ const injected = new Set();
238
+ /**
239
+ * The lease to keep, or undefined. A lease whose page only connected through Reticle's injected
240
+ * reader is handed straight back and its session excluded: it would otherwise be picked below and
241
+ * report a plain server with no SDK as an instrumented app.
242
+ */
243
+ const keepLease = async (owned) => {
244
+ if ('failed' in owned) {
245
+ note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
246
+ return undefined;
247
+ }
248
+ if (true !== owned.zeroInstall)
249
+ return owned;
250
+ injected.add(owned.sessionId);
251
+ await releaseLease(owned);
252
+ return undefined;
253
+ };
230
254
  if (null === alreadyConnected && policy.openBrowser && (input.openBrowser || canLease)) {
231
255
  // SERVED is the precondition, not SDK_PRESENT: a url that answers nothing is the only state
232
256
  // where a window is certainly useless. Whether the SDK is in the served HTML is a DIFFERENT
@@ -255,14 +279,9 @@ export async function runSetupPhases(input, fx) {
255
279
  const launcherFailed = input.openBrowser ? await fx.openBrowser(url) : undefined;
256
280
  openedBrowser = input.openBrowser;
257
281
  if (undefined !== fx.openLease && (!input.openBrowser || undefined !== launcherFailed)) {
258
- const owned = await fx.openLease(url);
259
- if ('failed' in owned) {
260
- note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
261
- }
262
- else {
263
- lease = owned;
282
+ lease = await keepLease(await fx.openLease(url));
283
+ if (undefined !== lease)
264
284
  openedBrowser = true;
265
- }
266
285
  }
267
286
  }
268
287
  }
@@ -283,7 +302,8 @@ export async function runSetupPhases(input, fx) {
283
302
  while (null === session) {
284
303
  // On a desktop app, only the desktop window counts. AppShape and the runtime a page reports use
285
304
  // the same three names, so the shape IS the requirement — see session-pick.
286
- session = pickSession(await fx.listSessions(), url, before, requiredRuntime);
305
+ const listedNow = (await fx.listSessions()).filter((s) => !injected.has(s.sessionId));
306
+ session = pickSession(listedNow, url, before, requiredRuntime);
287
307
  if (null !== session)
288
308
  break;
289
309
  if (deadline <= fx.now())
@@ -296,13 +316,7 @@ export async function runSetupPhases(input, fx) {
296
316
  !isDesktop(input.shape) &&
297
317
  fx.now() - waitStartedAt >= SYSTEM_BROWSER_GRACE_MS) {
298
318
  leaseFallbackTried = true;
299
- const owned = await fx.openLease(url);
300
- if ('failed' in owned) {
301
- note(`Could not open ${url} in a Reticle-owned browser either: ${owned.failed}`);
302
- }
303
- else {
304
- lease = owned;
305
- }
319
+ lease = await keepLease(await fx.openLease(url));
306
320
  continue;
307
321
  }
308
322
  // Not in silence: a desktop run used to print one line and then nothing for ten minutes.
@@ -363,9 +377,8 @@ export async function runSetupPhases(input, fx) {
363
377
  // `<url>` stays a placeholder in this string on purpose: `guidance-commands-run` feeds every
364
378
  // command we print to the real parser, and an interpolated value reads there as a missing operand.
365
379
  // The live url is named in the sentence instead, where a reader needs it anyway.
366
- // Both routes, because `explore` needs a model: without ANTHROPIC_API_KEY it answers "No model
367
- // configured to drive the app". Naming only that one hands the reader a dead end on any machine
368
- // without a key, which is the same defect this release spent its time removing everywhere else.
380
+ // Both routes, because `explore` runs on the platform: without a linked project it refuses.
381
+ // Naming only that one hands the reader a dead end on any machine without one.
369
382
  // A heading and a numbered list rather than one paragraph: a reader scans for what to do next.
370
383
  //
371
384
  // This was a single 524-character note, measured on a real first run: eighty-three words, no
@@ -385,8 +398,8 @@ export async function runSetupPhases(input, fx) {
385
398
  note(' 1. Drive one flow and end it with `reticle_act_and_wait` or `reticle_assert`. Those two ' +
386
399
  'produce a verdict; nothing else does.');
387
400
  note(' 2. Or hand over the whole drive: `reticle_verify { action: "explore", persona: "<who does ' +
388
- 'what>" }` records what it drove, so later runs replay with no model in the loop. Needs ' +
389
- 'ANTHROPIC_API_KEY.');
401
+ 'what>" }` records what it drove, so later runs replay with no model in the loop. Runs on ' +
402
+ 'the Reticle platform: needs a linked project (`reticle connect`); Free includes monthly Harness credits.');
390
403
  return {
391
404
  ok: true,
392
405
  reachedPhase: SetupPhase.CONNECT,
@@ -30,7 +30,8 @@ interface SetupCommandInput extends Omit<SetupInput, 'shape'> {
30
30
  /** The bridge's pairing token, for the MCP transport a connect-proof lease is opened over. */
31
31
  readonly pairingToken?: string | undefined;
32
32
  }
33
- export declare function registerOtherAgents(print: (line: string) => void): void;
33
+ /** Returns how many agents it wrote a config for, so a caller does not then say "none found". */
34
+ export declare function registerOtherAgents(print: (line: string) => void): number;
34
35
  type SetupCommandResult = SetupOutcome;
35
36
  export declare function runSetupCommand(input: SetupCommandInput, print: (line: string) => void): Promise<SetupCommandResult>;
36
37
  export {};
@@ -89,6 +89,7 @@ function hasElectronDependency(dir) {
89
89
  */
90
90
  /** `agent`, `agents`. */
91
91
  const agentWord = (n) => (1 === n ? 'agent' : 'agents');
92
+ /** Returns how many agents it wrote a config for, so a caller does not then say "none found". */
92
93
  export function registerOtherAgents(print) {
93
94
  const platform = process.platform;
94
95
  const home = homedir();
@@ -124,6 +125,7 @@ export function registerOtherAgents(print) {
124
125
  for (const noted of approvals.filter((a) => undefined !== a.warn)) {
125
126
  print(` ⚠ ${noted.name}: ${String(noted.warn)}`);
126
127
  }
128
+ return wrote.length;
127
129
  }
128
130
  /**
129
131
  * Run the phases against the real world.
@@ -32,6 +32,8 @@ export interface SetupMcpResult {
32
32
  readonly detected: readonly string[];
33
33
  readonly registered: readonly string[];
34
34
  readonly alreadyThere: readonly string[];
35
+ /** Automatic registration was attempted but failed. */
36
+ readonly failed: readonly string[];
35
37
  /**
36
38
  * Clients whose config this will NOT rewrite, and which therefore still need a human.
37
39
  *
@@ -4,6 +4,7 @@ export function setupMcp(io) {
4
4
  const detected = [];
5
5
  const registered = [];
6
6
  const alreadyThere = [];
7
+ const failed = [];
7
8
  const manual = [];
8
9
  /*
9
10
  * Claude Code first, because nothing else can find it.
@@ -30,6 +31,8 @@ export function setupMcp(io) {
30
31
  const cmd = claudeAddCommand();
31
32
  if (io.runCli(cmd.command, cmd.args))
32
33
  registered.push(McpClient.CLAUDE_CODE);
34
+ else
35
+ failed.push(McpClient.CLAUDE_CODE);
33
36
  }
34
37
  }
35
38
  for (const client of detectMcpClients(io)) {
@@ -47,8 +50,13 @@ export function setupMcp(io) {
47
50
  manual.push({ id: client.id, configPath: client.configPath, docs: spec.docs });
48
51
  continue;
49
52
  }
50
- io.writeFile(client.configPath, merged.content);
51
- registered.push(client.id);
53
+ try {
54
+ io.writeFile(client.configPath, merged.content);
55
+ registered.push(client.id);
56
+ }
57
+ catch {
58
+ failed.push(client.id);
59
+ }
52
60
  }
53
61
  /*
54
62
  * Both INSTALL steps, reported from the one place that knows the answer.
@@ -69,7 +77,8 @@ export function setupMcp(io) {
69
77
  * SKIPPED when there was nothing to register with — not failed. Nothing went wrong; there was
70
78
  * simply no agent here, and counting that as our failure would hide the ones that are.
71
79
  *
72
- * FAILED when every client we found needs a hand. That case used to fall through to COMPLETED,
80
+ * FAILED when no detected client is usable, whether registration failed or needs a hand.
81
+ * That case used to fall through to COMPLETED,
73
82
  * so the cohort with the tools registered NOWHERE was counted among the successful installs, and
74
83
  * the one number that says "did this machine get Reticle" read yes for the people it read no
75
84
  * for. Registering some and not others is still COMPLETED: the agent they are using may well be
@@ -79,11 +88,11 @@ export function setupMcp(io) {
79
88
  ? OnboardingStepStatus.SKIPPED
80
89
  : 0 === registered.length && alreadyThere.length > 0
81
90
  ? OnboardingStepStatus.SKIPPED
82
- : 0 === registered.length && 0 === alreadyThere.length && manual.length > 0
91
+ : 0 === registered.length && 0 === alreadyThere.length
83
92
  ? OnboardingStepStatus.FAILED
84
93
  : OnboardingStepStatus.COMPLETED,
85
94
  });
86
- return { detected, registered, alreadyThere, manual };
95
+ return { detected, registered, alreadyThere, failed, manual };
87
96
  }
88
97
  /** Everything this machine could be asked about, for the report when nothing was found. */
89
98
  export function knownClientLabels() {
@@ -1,4 +1,8 @@
1
1
  export declare const PEER_VERSION_PARAM = "peerVersion";
2
2
  export declare const PEER_CONTRACT_PARAM = "peerContract";
3
- /** Compare an attaching agent process against this daemon, and queue the nudge when they disagree. */
4
- export declare function noteAgentPeer(version: string | null, contract: string | null): void;
3
+ /**
4
+ * Compare an attaching agent process against this daemon: the skew to tell THAT connection, or
5
+ * undefined when they agree. Returned rather than queued daemon-wide, because it is true only for the
6
+ * agent that announced it (#1136).
7
+ */
8
+ export declare function agentPeerSkew(version: string | null, contract: string | null): string | undefined;
@@ -13,12 +13,15 @@
13
13
  */
14
14
  import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
15
15
  import { SERVER_VERSION } from './identity/server-version.js';
16
- import { daemonFix, describeSkew, SkewPair } from './version-skew.js';
17
- import { noteVersionSkew } from './version-nudge.js';
16
+ import { daemonFix, describeSkew } from './version-skew.js';
18
17
  export const PEER_VERSION_PARAM = 'peerVersion';
19
18
  export const PEER_CONTRACT_PARAM = 'peerContract';
20
- /** Compare an attaching agent process against this daemon, and queue the nudge when they disagree. */
21
- export function noteAgentPeer(version, contract) {
19
+ /**
20
+ * Compare an attaching agent process against this daemon: the skew to tell THAT connection, or
21
+ * undefined when they agree. Returned rather than queued daemon-wide, because it is true only for the
22
+ * agent that announced it (#1136).
23
+ */
24
+ export function agentPeerSkew(version, contract) {
22
25
  const skew = describeSkew({
23
26
  what: "the agent's MCP server",
24
27
  version: version ?? undefined,
@@ -27,6 +30,5 @@ export function noteAgentPeer(version, contract) {
27
30
  // MCP server, so the daemon is the newer half whenever the announcing agent is behind.
28
31
  fix: daemonFix(SERVER_VERSION, version ?? undefined),
29
32
  }, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
30
- if (skew !== undefined)
31
- noteVersionSkew(SkewPair.DAEMON, skew);
33
+ return skew;
32
34
  }
@@ -11,7 +11,7 @@
11
11
  * result, then goes quiet until the skew CHANGES. Told, not nagged: a banner on every call is one an
12
12
  * agent learns to skip, and it would cost those tokens on every call for the rest of the session.
13
13
  */
14
- import type { SkewPair } from './version-skew.js';
14
+ import { SkewPair } from './version-skew.js';
15
15
  interface VersionSkewNudge {
16
16
  pair: SkewPair;
17
17
  action: string;
@@ -21,8 +21,20 @@ interface VersionSkewNudge {
21
21
  * a second tab on a third version is news; the same tab reconnecting is not.
22
22
  */
23
23
  export declare function noteVersionSkew(pair: SkewPair, action: string): void;
24
- /** The next undelivered skew, once. */
25
- export declare function takeVersionSkew(): VersionSkewNudge | undefined;
24
+ /**
25
+ * A daemon skew bound to the one MCP connection whose server announced it, told once (#1136).
26
+ *
27
+ * The page pair stays in the daemon-wide queue: it is a fact about a tab, true for whoever asks.
28
+ * This pair is a fact about the CALLER, and telling it to a different caller is advice to restart
29
+ * a server that is fine, or to run `reticle stop` and cut every agent on the daemon.
30
+ */
31
+ export declare function connectionSkew(action: string): {
32
+ take(): string | undefined;
33
+ };
34
+ /** The next undelivered skew, once: this connection's own first, then the daemon-wide queue. */
35
+ export declare function takeVersionSkew(connection?: {
36
+ take(): string | undefined;
37
+ }): VersionSkewNudge | undefined;
26
38
  /**
27
39
  * Splice pending skew onto a tool-error payload.
28
40
  *
@@ -36,6 +48,8 @@ export declare function takeVersionSkewOnto(payload: {
36
48
  error: string;
37
49
  recovery?: string;
38
50
  feedback?: string;
51
+ }, connection?: {
52
+ take(): string | undefined;
39
53
  }): Record<string, unknown>;
40
54
  /** Tests only — drop the module state so each case starts clean. */
41
55
  export declare function resetVersionSkew(): void;