@reticlehq/server 3.8.2 → 3.10.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 (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -6,26 +6,92 @@
6
6
  * never rounded up to `yes`, and the run is `yes` only when every journey is.
7
7
  */
8
8
  import { RunFlowStatus, VerdictStatus, Verified, } from '@reticlehq/core';
9
+ /**
10
+ * The shape of the `--results-json` file. 1 had only `verdict` and `results`; 2 adds `steps` per
11
+ * journey and the gated `score`. A consumer checks this before it reads a field that 1 did not have.
12
+ */
13
+ export const RESULTS_SCHEMA_VERSION = 2;
14
+ /**
15
+ * One journey's steps. `planned` is the flow file's step count, when the caller could read it:
16
+ * without it, a replay that stopped at step 2 of 10 would look like a 2-step journey. Replay adds a
17
+ * `success` step for a flow with a success oracle, so the total is the larger of the two.
18
+ */
19
+ function stepCounts(flow, planned) {
20
+ let passed = 0;
21
+ let failed = 0;
22
+ let judgedUnits = 0;
23
+ let swept;
24
+ for (const step of (flow.stepResults ?? [])) {
25
+ // A sequence counts its sub-steps; any other step counts once.
26
+ const units = step.subSteps?.total ?? 1;
27
+ judgedUnits += units;
28
+ const ok = step.ok && step.drift === undefined;
29
+ // Gated: once a step failed, later verdicts are reported apart and never paid.
30
+ if (swept !== undefined) {
31
+ if (ok) {
32
+ swept.passed += units;
33
+ }
34
+ else {
35
+ swept.passed += step.subSteps?.passed ?? 0;
36
+ swept.failed += 1;
37
+ }
38
+ continue;
39
+ }
40
+ if (ok) {
41
+ passed += units;
42
+ }
43
+ else {
44
+ passed += step.subSteps?.passed ?? 0;
45
+ failed += 1;
46
+ swept = { passed: 0, failed: 0 };
47
+ }
48
+ }
49
+ const total = Math.max(planned ?? flow.steps, judgedUnits);
50
+ const counts = { passed, failed, unknown: total - passed - failed };
51
+ if (swept !== undefined && swept.passed + swept.failed > 0) {
52
+ counts.swept = swept;
53
+ }
54
+ return counts;
55
+ }
9
56
  function journeyVerdict(status) {
10
- if (RunFlowStatus.FAIL === status)
57
+ if (RunFlowStatus.FAIL === status) {
11
58
  return Verified.NO;
59
+ }
12
60
  // A heal is consequence-verified before it is reported, so it proves as much as a pass.
13
- if (RunFlowStatus.PASS === status || RunFlowStatus.HEALED === status)
61
+ if (RunFlowStatus.PASS === status || RunFlowStatus.HEALED === status) {
14
62
  return Verified.YES;
63
+ }
15
64
  return Verified.UNKNOWN;
16
65
  }
17
- /** `pushed`: the run reached the platform, so its id resolves there and the results name it. */
18
- export function verifyResults(run, pushed = false) {
66
+ /**
67
+ * `pushed`: the run reached the platform, so its id resolves there and the results name it.
68
+ * `planned`: each flow file's step count, by journey name (see `stepCounts`).
69
+ */
70
+ export function verifyResults(run, pushed = false, planned = new Map()) {
19
71
  const results = run.flows.map((flow) => ({
20
72
  journey: flow.name,
21
73
  verdict: journeyVerdict(flow.status),
22
74
  ...(flow.failureReason === undefined ? {} : { reason: flow.failureReason }),
23
75
  ...(pushed ? { runId: run.runId } : {}),
76
+ steps: stepCounts(flow, planned.get(flow.name)),
24
77
  }));
25
- const verdict = results.some((r) => Verified.NO === r.verdict)
26
- ? Verified.NO
27
- : VerdictStatus.PASS === run.verdict.status && results.every((r) => Verified.YES === r.verdict)
28
- ? Verified.YES
29
- : Verified.UNKNOWN;
30
- return { verdict, results };
78
+ const sum = (key) => results.reduce((n, r) => n + r.steps[key], 0);
79
+ const passed = sum('passed');
80
+ const total = passed + sum('failed') + sum('unknown');
81
+ const score = {
82
+ passed,
83
+ failed: sum('failed'),
84
+ unknown: sum('unknown'),
85
+ total,
86
+ ...(0 === total ? {} : { value: passed / total }),
87
+ };
88
+ let verdict = Verified.UNKNOWN;
89
+ if (results.some((r) => Verified.NO === r.verdict)) {
90
+ verdict = Verified.NO;
91
+ }
92
+ else if (VerdictStatus.PASS === run.verdict.status &&
93
+ results.every((r) => Verified.YES === r.verdict)) {
94
+ verdict = Verified.YES;
95
+ }
96
+ return { schemaVersion: RESULTS_SCHEMA_VERSION, verdict, results, score };
31
97
  }
@@ -1,16 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { PlatformLinkVia } from '@reticlehq/core/telemetry';
2
+ import { OnboardingPhase, PlatformLinkVia } from '@reticlehq/core/telemetry';
3
3
  import { hudSignIn } from './cli/cloud-login.js';
4
- import { handleSetupMcp, handleSetupInstall } from './cli/setup-mcp-cli.js';
4
+ import { agentsBlock, handleSetupMcp, handleSetupInstall, handleSetupWaitAgents, readAgentRows, shouldWaitForAgents, } from './cli/setup-mcp-cli.js';
5
5
  import { reportStepFromCli } from './cli-onboarding.js';
6
6
  import { pathToFileURL } from 'node:url';
7
7
  import { openFailureNote, openLaunchFailureNote } from './cli/answers/open-note.js';
8
+ import { writeCiHint } from './cli/ci-hint.js';
9
+ import { currentAutomationHint, isCiEnv } from '../telemetry/automation-hint.js';
8
10
  import { realpathSync } from 'node:fs';
9
11
  import { join } from 'node:path';
10
12
  import { installCrashGuard } from './setup/setup-command.js';
11
13
  import { handleStatus } from './cli/status-fields.js';
12
14
  export { handleStatus, reportStatus } from './cli/status-fields.js';
13
15
  import { reticleStateHome } from './daemon/daemon.js';
16
+ import { ensureDaemon } from './cli/ensure-daemon.js';
14
17
  import { handleMcp } from './cli/mcp-command.js';
15
18
  import { handleReport } from './cli/report-command.js';
16
19
  import { handleContext } from './cli/context-command.js';
@@ -18,10 +21,11 @@ import { handleHook } from './cli/hook/hook-command.js';
18
21
  import { HOOK_COMMAND } from './cli/cli-parse.js';
19
22
  import { daemonProjectAt, readDaemonRegistry, resolveDaemonForProject, } from './daemon/daemon-resolve.js';
20
23
  import { pickDaemonPortToBind } from './daemon/binding/free-port.js';
24
+ import { exitAfterClose } from './daemon/lifetime/daemon-exit.js';
21
25
  import { isSameOrAbove, portForInit, portFromEnv } from './setup/init/init-port.js';
22
26
  import { daemonStartOptions } from './cli/daemon-start-options.js';
23
27
  import { handleGate, loadNamedFlows, resolveChangedFiles } from './cli/cli-flow-commands.js';
24
- import { RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
28
+ import { DaemonStartupError, RETICLE_DEFAULT_PORT, ReticleDir, ReticleEnv } from '@reticlehq/core';
25
29
  import { loadDotEnv } from '../telemetry/dev-repo.js';
26
30
  import { licenseKeyFromEnvFiles } from '../features/license/license-env.js';
27
31
  import { LICENSE_KEY_ENV } from '../features/license/license.js';
@@ -33,34 +37,41 @@ import { startDaemon } from '../index.js';
33
37
  import { cmdLink, connectInstalls, isCloudCommand, runCloudCommand } from './cli/cloud-cli.js';
34
38
  import { SERVER_VERSION } from './version/identity/server-version.js';
35
39
  import { log } from '../log.js';
36
- import { isAlive, removePid, spawnDaemon, discoverDaemonPort, writeDaemonRegistry, } from './daemon/daemon.js';
37
- import { PortPresence, probePresence, presenceIsUsable, describePresence, } from './daemon/binding/port-presence.js';
38
- import { waitForDaemon, probeDaemon } from '../surface/mcp/mcp-proxy.js';
40
+ import { isAlive, removePid, discoverDaemonPort, writeDaemonRegistry } from './daemon/daemon.js';
41
+ import { probePresence, presenceIsUsable } from './daemon/binding/port-presence.js';
42
+ import { probeDaemon } from '../surface/mcp/mcp-proxy.js';
39
43
  import { installDaemonResilience, recordExitReason, DaemonExitReason, } from './daemon/daemon-resilience.js';
40
44
  import { IdleShutdown, resolveIdleShutdownMs, resolveIdleCheckMs } from './daemon/idle-shutdown.js';
41
45
  import { DaemonHeartbeat, resolveHeartbeatMs } from './daemon/lifetime/heartbeat.js';
42
46
  import { everServedToolCall } from './daemon/lifetime/daemon-usefulness.js';
43
- import { DAEMON_START_FAILED_EVENT } from './daemon/lifetime/startup-failure.js';
44
- import { summarizeStatus, warnOnDaemonSkew, decideOpen, openInBrowser, openCommand, } from './cli/launch/cli-launch.js';
47
+ import { installStartupTrap, recordStartupFailure } from './daemon/lifetime/startup-failure.js';
48
+ import { summarizeStatus, decideOpen, openInBrowser, openCommand, } from './cli/launch/cli-launch.js';
45
49
  import { NAVIGATE_NO_SESSION_ID, navigateLeftTab, openCandidates } from './cli/open-navigate.js';
46
50
  import { fetchStatus } from './daemon/binding/daemon-status-probe.js';
47
51
  import { handleDrive } from './cli/drive/drive-command.js';
48
52
  import { handleVerify } from './cli/cli-verify.js';
53
+ import { handleVerifyAgainst } from './cli/verify-against.js';
54
+ import { handleCompare } from './cli/two-builds/compare-command.js';
55
+ import { handleGrade } from './cli/finished-run/grade-command.js';
56
+ import { FIRST_VERDICT_REASON, bareRunsFirstVerdict } from './cli/first-verdict.js';
57
+ import { cmdFirstVerdict } from './cli/first-verdict-node.js';
58
+ import { reportOnboardingStop } from '../telemetry/onboarding-funnel.js';
49
59
  import { runKill } from './cli/cli-kill.js';
50
60
  import { runInit, buildNodeIo, InitFailure } from '@reticlehq/init';
51
61
  import { continueAfterInit } from './setup/init/init-runtime.js';
52
62
  import { initFirstFlow } from './cli/try-command.js';
53
63
  import { handleDoctor } from './cli/cli-doctor.js';
64
+ import { writeFirstUseWarnings } from './cli/first-use-checks.js';
54
65
  import { serverInitHost } from './setup/init/init-host.js';
55
66
  import { describeLicense } from '../features/license/license.js';
56
67
  import { isLikelyDevServerPort, devServerPortWarning, readProjectPort, readProjectId, workspacePortConflict, projectDirOf, } from './cli/ports/resolve/cli-port.js';
57
68
  import { helpRunsAt, helpVersionNote, nodeSpawner, readTextFile, reexecAtVersion, versionMatchNote, versionToMatch, } from './cli/launch/sdk-version-match.js';
58
- import { DAEMON_INNER_COMMAND, PORT_FLAG, parseCliArgs, CLI_USAGE, dialsTheDaemon, } from './cli/cli-parse.js';
69
+ import { PORT_FLAG, parseCliArgs, CLI_USAGE, dialsTheDaemon } from './cli/cli-parse.js';
59
70
  import { renderHelp } from './cli/cli-usage.js';
60
71
  import { RENAMED_COMMANDS, renamedCommandFor, renamedNote } from './cli/cli-parse-grammar.js';
61
72
  import { handleFeedback, handleTelemetry } from '../telemetry/feedback-cli.js';
62
73
  import { installDaemonTelemetry } from '../telemetry/daemon-telemetry.js';
63
- import { reportCliRun } from '../telemetry/cli-telemetry.js';
74
+ import { reportCliRun, reportGateCompleted } from '../telemetry/cli-telemetry.js';
64
75
  // Re-exported so existing imports (and the CLI tests) keep resolving from '@/cli.js'.
65
76
  export { parseCliArgs, CLI_USAGE };
66
77
  /** Said when `connect` carries on past an init whose setup did not finish. */
@@ -94,6 +105,11 @@ async function handleInit(parsed) {
94
105
  // RETICLE_PORT counts as explicit, as it does for every other command: resolved once here, so the
95
106
  // port written into the project and the port the runtime phase binds are the same number.
96
107
  const explicit = parsed.port ?? portFromEnv(process.env);
108
+ // The bridge port matters only when init goes on to start the app.
109
+ const bridgePort = true === parsed.filesOnly || parsed.dryRun
110
+ ? undefined
111
+ : (explicit ?? readProjectPort(cwd) ?? RETICLE_DEFAULT_PORT);
112
+ await writeFirstUseWarnings(bridgePort);
97
113
  const port = await portForInit(explicit, readProjectPort(cwd), readProjectId(cwd), {
98
114
  // The registry names a daemon's owner; an older daemon that never registered is known by the
99
115
  // projects its connected pages announced.
@@ -134,7 +150,7 @@ async function handleInit(parsed) {
134
150
  deferOutcome: true,
135
151
  continuesToRuntime: true !== parsed.filesOnly && true !== parsed.dryRun,
136
152
  }, io);
137
- await continueAfterInit({ ...parsed, port }, result, io, cwd, initFirstFlow);
153
+ await continueAfterInit({ ...parsed, port }, result, io, cwd, initFirstFlow, (print, mayWait) => agentsBlock(readAgentRows(cwd), print, mayWait && shouldWaitForAgents(process.stdout.isTTY, isCiEnv(process.env))));
138
154
  return result;
139
155
  }
140
156
  // `serve`, `stop` and `restart` live in `cli/lifecycle/daemon-lifecycle.ts`: one idea, and the
@@ -149,12 +165,12 @@ import { handleServe, handleStop, handleRestart } from './cli/lifecycle/daemon-l
149
165
  function handleVersion() {
150
166
  process.stdout.write(`${SERVER_VERSION}\n`);
151
167
  }
152
- /** `reticle license` — show enterprise activation resolved from the environment (offline; nothing leaves). */
168
+ /** `reticle license` (now `status`) — show enterprise activation resolved from the environment (offline; nothing leaves). */
153
169
  function handleLicense() {
154
170
  log('reticle_license', { ...describeLicense(Date.now()) });
155
171
  }
156
172
  /**
157
- * `reticle affected <file...>` — print which saved flows must re-verify for the given changed files.
173
+ * `reticle gate --list <file...>` (was `affected`) — print which saved flows must re-verify for the given changed files.
158
174
  * Environment-side (costs the agent nothing per turn); the foundation of watch/gate. Never throws:
159
175
  * a load error is logged, not fatal.
160
176
  */
@@ -178,32 +194,6 @@ async function handleAffected(files, since) {
178
194
  });
179
195
  }
180
196
  }
181
- /** Ensure a daemon is reachable on `port` (probe the real port; spawn + wait only if nothing's there). */
182
- function ensureDaemon(port) {
183
- return probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus }).then(async (presence) => {
184
- // Attaching to whatever already owns the port is the whole point of a daemon — but it means an
185
- // upgrade does NOT take effect until that daemon dies, and nothing used to say so. Say it here,
186
- // where both versions are in hand, and keep attaching: killing another agent's daemon on a
187
- // version bump is worse than a loud warning.
188
- if (presenceIsUsable(presence)) {
189
- return warnOnDaemonSkew(port);
190
- }
191
- // Not free, not a daemon: a stranger holds the port. Spawning here is guaranteed to fail —
192
- // the child cannot bind — and `waitForDaemon` would then report READY anyway, because its
193
- // probe is the same bare TCP connect and the stranger accepts. That is the "serve reports
194
- // success for a daemon that never bound" shape. Refuse instead, with the sentence `doctor`
195
- // says.
196
- if (PortPresence.FREE !== presence) {
197
- throw new Error(describePresence(presence, port));
198
- }
199
- const scriptPath = process.argv[1];
200
- if (scriptPath === undefined) {
201
- throw new Error('cannot locate the reticle daemon script');
202
- }
203
- spawnDaemon(process.execPath, scriptPath, [DAEMON_INNER_COMMAND, PORT_FLAG, String(port)], port);
204
- return waitForDaemon(port);
205
- });
206
- }
207
197
  /**
208
198
  * `reticle open [url]` — the one-command "show me the app". Resolves the port (the requested one if a
209
199
  * daemon's there, else a running daemon it discovers — so the user never hunts for the port), ensures
@@ -212,6 +202,8 @@ function ensureDaemon(port) {
212
202
  */
213
203
  /** How long `reticle open` waits for the launched page to register before reporting on it. */
214
204
  const OPEN_SESSION_WAIT_MS = 8000;
205
+ /** For the heartbeat's resident memory, in MB. */
206
+ const BYTES_PER_MB = 1024 * 1024;
215
207
  /**
216
208
  * Poll until a NEW session shows up, or the wait runs out. Returns whether one did.
217
209
  *
@@ -342,8 +334,12 @@ function handleDaemonInner(parsed) {
342
334
  hudSignIn: (repaint, link) => hudSignIn(repaint, undefined, undefined, link),
343
335
  hudLink: (root) => cmdLink([], root, PlatformLinkVia.HUD),
344
336
  };
337
+ // Until the daemon is ready, a throw or a rejection is a failed start. Write it to the log with
338
+ // its code, so the next start can say why this one died (#2043).
339
+ const disposeStartupTrap = installStartupTrap(process, log);
345
340
  startDaemon(options)
346
341
  .then((server) => {
342
+ disposeStartupTrap();
347
343
  log('reticle_daemon_ready', { port: parsed.port, pid: process.pid });
348
344
  // Daemon lifecycle telemetry: started, a one-shot project profile, the periodic counter flush,
349
345
  // and the rich summary on shutdown. All of it lives in one module — see daemon-telemetry.ts.
@@ -375,24 +371,15 @@ function handleDaemonInner(parsed) {
375
371
  // to reach a socket that is still open.
376
372
  server.announceShutdown?.();
377
373
  // Awaited before the close/exit chain: `process.exit(0)` kills an in-flight POST, and this is
378
- // the one event carrying the whole session. A failed send resolves anyway (emit swallows its
379
- // own errors), so this can delay the exit by at most the send timeout, never prevent it.
380
- void daemonTelemetry
381
- // The reason rides out on the session summary, which is the only event that fires at this
382
- // exact moment. The proxy emits the matching `mcp_connection_lost` and cannot know it —
383
- // it sees a socket end and nothing more, which is how a scheduled idle exit came to make up
384
- // the large majority of "outages". See SessionSummary.exit.
385
- .shutdown(reason)
386
- .then(() => server.close())
387
- .then(() => {
388
- removePid(parsed.port);
389
- process.exit(0);
390
- })
391
- .catch((err) => {
392
- const message = err instanceof Error ? err.message : String(err);
393
- log('reticle_daemon_close_error', { error: message });
374
+ // the one event carrying the whole session. The reason rides out on the session summary;
375
+ // the proxy sees only a socket end. See SessionSummary.exit. The summary and the close share
376
+ // one bound, so a close that never settles cannot keep this process on its port (#1937).
377
+ void exitAfterClose({
378
+ summary: () => daemonTelemetry.shutdown(reason),
379
+ close: () => server.close(),
380
+ }).then((code) => {
394
381
  removePid(parsed.port);
395
- process.exit(1);
382
+ process.exit(code);
396
383
  });
397
384
  };
398
385
  // Wrapped rather than passed directly: a signal handler receives the signal name as its first
@@ -432,12 +419,13 @@ function handleDaemonInner(parsed) {
432
419
  facts: () => ({
433
420
  sessions: server.bridge.sessions.count(),
434
421
  served: everServedToolCall(),
422
+ rssMb: Math.round(process.memoryUsage().rss / BYTES_PER_MB),
435
423
  }),
436
424
  }).start();
437
425
  })
438
426
  .catch((error) => {
439
- const message = error instanceof Error ? error.message : String(error);
440
- log(DAEMON_START_FAILED_EVENT, { error: message });
427
+ disposeStartupTrap();
428
+ recordStartupFailure(log, error, DaemonStartupError.START);
441
429
  removePid(parsed.port);
442
430
  process.exit(1);
443
431
  });
@@ -515,7 +503,7 @@ export function main() {
515
503
  const colour = true === process.stderr.isTTY && process.env['NO_COLOR'] === undefined;
516
504
  process.stderr.write(`${renamedNote(argv[0] ?? '', renamedTo, colour)}\n`);
517
505
  }
518
- // Cloud subcommands (login/link/project/config/push) are a distinct family with their own async client;
506
+ // Cloud subcommands (connect/sync/runs/config) are a distinct family with their own async client;
519
507
  // handle them before the local typed parser so `reticle login` etc. work as one tool.
520
508
  if (isCloudCommand(argv[0])) {
521
509
  // Asking a command what it does is never a request to run it. These dispatch before the typed
@@ -617,6 +605,17 @@ export function main() {
617
605
  void handleInit(parsed);
618
606
  break;
619
607
  case 'serve':
608
+ // Bare `reticle` in a folder Reticle has not wired: the first verdict, with no agent and no
609
+ // `init`. A wired project, and `reticle serve` itself, keep serving.
610
+ if (bareRunsFirstVerdict(argv, myProjectId !== undefined || projectPort !== undefined || portConflict !== undefined)) {
611
+ void cmdFirstVerdict(parsed.port, !parsed.headless, (row) => reportOnboardingStop({ phase: OnboardingPhase.FIRST_RUN, reason: FIRST_VERDICT_REASON, ...row }, { detach: true }))
612
+ .then((code) => process.exit(code))
613
+ .catch((cause) => {
614
+ process.stderr.write(`reticle: ${cause instanceof Error ? cause.message : String(cause)}\n`);
615
+ process.exit(1);
616
+ });
617
+ break;
618
+ }
620
619
  handleServe(parsed);
621
620
  break;
622
621
  case 'stop':
@@ -633,7 +632,8 @@ export function main() {
633
632
  void handleRestart(parsed.port, parsed.force);
634
633
  break;
635
634
  case 'status':
636
- void handleStatus(parsed.port, parsed.json);
635
+ // Offline: the license key is read from the environment.
636
+ void handleStatus(parsed.port, parsed.json, { ...describeLicense(Date.now()) });
637
637
  break;
638
638
  case 'license':
639
639
  handleLicense();
@@ -655,17 +655,24 @@ export function main() {
655
655
  process.exit(1);
656
656
  break;
657
657
  case 'doctor':
658
- void handleDoctor(parsed.port);
658
+ void handleDoctor(parsed.port, parsed.fix, reportStepFromCli);
659
659
  break;
660
660
  case 'setup-mcp':
661
- handleSetupMcp(reportStepFromCli);
661
+ void writeFirstUseWarnings().then(() => {
662
+ handleSetupMcp(reportStepFromCli, true, shouldWaitForAgents(process.stdout.isTTY, isCiEnv(process.env)));
663
+ });
664
+ break;
665
+ case 'setup-wait-agents':
666
+ void handleSetupWaitAgents(shouldWaitForAgents(process.stdout.isTTY, isCiEnv(process.env)));
662
667
  break;
663
668
  case 'setup-install':
664
- handleSetupInstall({
665
- runtimeSecs: parsed.runtimeSecs,
666
- installSecs: parsed.installSecs,
667
- mcp: parsed.mcp,
668
- }, reportStepFromCli);
669
+ void writeFirstUseWarnings().then(() => {
670
+ handleSetupInstall({
671
+ runtimeSecs: parsed.runtimeSecs,
672
+ installSecs: parsed.installSecs,
673
+ mcp: parsed.mcp,
674
+ }, reportStepFromCli);
675
+ });
669
676
  break;
670
677
  case 'open':
671
678
  handleOpen(parsed.port, parsed.url, parsed.navigate);
@@ -674,13 +681,48 @@ export function main() {
674
681
  handleDrive(parsed, readProjectId(process.cwd()));
675
682
  break;
676
683
  case 'verify':
677
- handleVerify(parsed);
684
+ void writeFirstUseWarnings().then(async () => {
685
+ // `--against` compares two builds through the daemon, and starts one when there is none.
686
+ if (parsed.against !== undefined) {
687
+ await handleVerifyAgainst({ ...parsed, against: parsed.against }, ensureDaemon, {
688
+ line: (text) => process.stdout.write(`${text}\n`),
689
+ fail: (text) => process.stderr.write(`${text}\n`),
690
+ exit: (code) => process.exit(code),
691
+ });
692
+ return;
693
+ }
694
+ handleVerify(parsed, () => {
695
+ writeCiHint({ automation: currentAutomationHint(process.env), machineOutput: false });
696
+ });
697
+ });
698
+ break;
699
+ case 'compare':
700
+ // Two builds through the daemon, which it starts when there is none. Nothing to install.
701
+ void handleCompare(parsed, ensureDaemon, {
702
+ line: (text) => process.stdout.write(`${text}\n`),
703
+ exit: (code) => process.exit(code),
704
+ });
705
+ break;
706
+ case 'grade':
707
+ // Reads the declared state sources and snapshot files. No daemon and no browser.
708
+ void handleGrade(parsed.args, {
709
+ line: (text) => process.stdout.write(`${text}\n`),
710
+ exit: (code) => process.exit(code),
711
+ });
678
712
  break;
679
713
  case 'affected':
680
- void handleAffected(parsed.files, parsed.since);
714
+ void handleAffected(parsed.files, parsed.since).then(() => {
715
+ writeCiHint({ automation: currentAutomationHint(process.env), machineOutput: false });
716
+ });
681
717
  break;
682
718
  case 'gate':
683
- void handleGate(parsed.files, parsed.since, parsed.hook, parsed.acceptCoverage);
719
+ void handleGate(parsed.files, parsed.since, parsed.hook, parsed.acceptCoverage, reportGateCompleted).then(() => {
720
+ // A stop hook's output is read by the agent, so it is a machine output mode.
721
+ writeCiHint({
722
+ automation: currentAutomationHint(process.env),
723
+ machineOutput: true === parsed.hook,
724
+ });
725
+ });
684
726
  break;
685
727
  case 'report':
686
728
  void handleReport(parsed.session, parsed.hook);
@@ -9,7 +9,9 @@
9
9
  */
10
10
  export declare function fetchStatus(port: number, host?: string,
11
11
  /** Also tell the daemon a setup run is waiting on it, so it does not idle out mid-wait. */
12
- hold?: boolean): Promise<unknown>;
12
+ hold?: boolean,
13
+ /** A longer limit, for a caller that must not mistake a busy daemon for a stuck one. */
14
+ timeoutMs?: number): Promise<unknown>;
13
15
  /** One connected tab as `reticle status` reports it — the at-a-glance health line. */
14
16
  interface StatusSession {
15
17
  sessionId: string;
@@ -42,13 +42,15 @@ const ReticleStatusSchema = z
42
42
  */
43
43
  export function fetchStatus(port, host = LOOPBACK_HOST,
44
44
  /** Also tell the daemon a setup run is waiting on it, so it does not idle out mid-wait. */
45
- hold = false) {
45
+ hold = false,
46
+ /** A longer limit, for a caller that must not mistake a busy daemon for a stuck one. */
47
+ timeoutMs = STATUS_PROBE_TIMEOUT_MS) {
46
48
  return new Promise((resolve) => {
47
49
  const req = http.get({
48
50
  host,
49
51
  port,
50
52
  path: hold ? `${STATUS_PATH}?${STATUS_HOLD_QUERY}=1` : STATUS_PATH,
51
- timeout: STATUS_PROBE_TIMEOUT_MS,
53
+ timeout: timeoutMs,
52
54
  // Shares the proxy's keep-alive agent: the proxy calls this on every reconnect, and a socket
53
55
  // per probe is the same churn the POST leg was fixed for.
54
56
  agent: loopbackAgent,
@@ -0,0 +1,9 @@
1
+ /** The argv word that runs the daemon itself. `serve` and the MCP proxy spawn it; a person never types it. */
2
+ export declare const DAEMON_INNER_COMMAND = "_daemon";
3
+ /**
4
+ * True when `commandLine` runs a Reticle daemon (#1937).
5
+ *
6
+ * The MCP proxy (`reticle mcp`) and every other process fail this test, so nothing that relies on
7
+ * it can stop the agent's proxy or a stranger's process.
8
+ */
9
+ export declare function isReticleDaemonCommand(commandLine: string): boolean;
@@ -0,0 +1,11 @@
1
+ /** The argv word that runs the daemon itself. `serve` and the MCP proxy spawn it; a person never types it. */
2
+ export const DAEMON_INNER_COMMAND = '_daemon';
3
+ /**
4
+ * True when `commandLine` runs a Reticle daemon (#1937).
5
+ *
6
+ * The MCP proxy (`reticle mcp`) and every other process fail this test, so nothing that relies on
7
+ * it can stop the agent's proxy or a stranger's process.
8
+ */
9
+ export function isReticleDaemonCommand(commandLine) {
10
+ return commandLine.trim().split(/\s+/).includes(DAEMON_INNER_COMMAND);
11
+ }
@@ -98,7 +98,14 @@ export declare function projectlessNote(myPort: number, others: readonly {
98
98
  * record naming a daemon that is not the one being asked. The wording says which side is empty,
99
99
  * since the failure is invisible precisely because both sides look healthy from where they stand.
100
100
  */
101
- export declare function splitBrainNote(myPort: number, elsewhere: readonly number[]): string | undefined;
101
+ export declare function splitBrainNote(myPort: number, elsewhere: readonly number[],
102
+ /** This project and the registry home, to tell whether the app's daemon belongs to another project. */
103
+ owner?: {
104
+ projectId: string | undefined;
105
+ home: string;
106
+ },
107
+ /** Where `RETICLE_PORT` is read from; injectable so the note is testable. */
108
+ env?: Readonly<Record<string, string | undefined>>): string | undefined;
102
109
  /**
103
110
  * The other stance on the same split, for a command standing on the port that HAS the app.
104
111
  *
@@ -20,6 +20,7 @@ import { readdirSync, readFileSync } from 'node:fs';
20
20
  import { join } from 'node:path';
21
21
  import { DaemonRegistryEntrySchema, daemonRegistryPort, pickDaemonPort, } from '@reticlehq/core/artifacts';
22
22
  import { ReticleEnv, RETICLE_DEFAULT_PORT } from '@reticlehq/core';
23
+ import { portFromEnv } from '../setup/init/init-port.js';
23
24
  const RETICLE_PORT_ENV = ReticleEnv.PORT;
24
25
  /**
25
26
  * Every well-formed registry entry in `home`.
@@ -151,11 +152,36 @@ export function projectlessNote(myPort, others) {
151
152
  * record naming a daemon that is not the one being asked. The wording says which side is empty,
152
153
  * since the failure is invisible precisely because both sides look healthy from where they stand.
153
154
  */
154
- export function splitBrainNote(myPort, elsewhere) {
155
+ export function splitBrainNote(myPort, elsewhere,
156
+ /** This project and the registry home, to tell whether the app's daemon belongs to another project. */
157
+ owner,
158
+ /** Where `RETICLE_PORT` is read from; injectable so the note is testable. */
159
+ env = process.env) {
155
160
  if (0 === elsewhere.length)
156
161
  return undefined;
157
162
  const listed = elsewhere.map((port) => `:${String(port)}`).join(', ');
158
163
  const first = elsewhere[0] ?? myPort;
164
+ // The app landed on ANOTHER project's daemon (#1652): two projects share a port. `resolveMcpPort`
165
+ // never adopts a daemon registered to a different project, so RETICLE_PORT would only relocate the
166
+ // agent again. The step that works is giving this project its own port, so its app comes here.
167
+ // Owner by port alone is safe: `elsewhere` is already liveness-filtered (`daemonsServingProjectElsewhere`).
168
+ const holder = owner === undefined ? undefined : daemonProjectAt(first, owner.home);
169
+ if (owner !== undefined && holder !== undefined && !adoptable(holder, owner.projectId)) {
170
+ // Port resolution is env-first (`--port`, `RETICLE_PORT`, `.reticle.json`, 4400), so a
171
+ // `RETICLE_PORT` left in the agent's MCP config from the old advice outranks the new `port`.
172
+ const envFirst = portFromEnv(env) === undefined
173
+ ? 'Give this project its own port instead: set'
174
+ : `Give this project its own port instead: first remove ${RETICLE_PORT_ENV} from the agent's ` +
175
+ 'MCP config, because it takes precedence over `.reticle.json`, then set';
176
+ return (`this project's app is connected to the Reticle daemon on :${String(first)}, which is ` +
177
+ `registered to another project, "${holder}", while this project's daemon is on ` +
178
+ `:${String(myPort)} — the two projects share a port, and the agent is attached to the empty ` +
179
+ `half. Pointing the agent at :${String(first)} will not help: an MCP server never attaches to ` +
180
+ `another project's daemon. ${envFirst} \`port\` in its ` +
181
+ `.reticle.json to one no other project uses (for example ${String(myPort)}), then restart the ` +
182
+ `dev server so the app connects to this project's daemon, and restart the agent session. ` +
183
+ `Leave :${String(first)} running; it serves "${holder}".`);
184
+ }
159
185
  // The agent moves, not the app (#1546). The daemon the app is on is a working session this agent
160
186
  // did not start, and it may be a person's, another agent's or another project's. Stopping it was
161
187
  // the first thing this used to say. It comes last now, with what it costs.
@@ -1,3 +1,4 @@
1
+ import { type DeathCause } from './lifetime/death-cause.js';
1
2
  import { type EscapeRequest, type EscapeResult } from './win32-escape.js';
2
3
  export { MAX_DAEMON_LOG_BYTES, recoverOversizedLog } from './lifetime/log-cap.js';
3
4
  /** Env override for the whole state directory — see ReticleEnv.STATE_DIR. */
@@ -22,6 +23,11 @@ export declare function rotateDaemonLog(path: string, deps: {
22
23
  renameFile(from: string, to: string): void;
23
24
  }, max?: number): boolean;
24
25
  export declare function readPid(port: number, home?: string): number | null;
26
+ /**
27
+ * Whether a process with this pid exists. `EPERM` means it exists and this process may not signal
28
+ * it, which a daemon that WMI started can answer on Windows; only `ESRCH` means it is gone. Reading
29
+ * every throw as "dead" reclaimed a live daemon and started a second one on its port.
30
+ */
25
31
  export declare function isAlive(pid: number): boolean;
26
32
  export declare function writePid(port: number): void;
27
33
  /**
@@ -49,13 +55,22 @@ export declare function isRunning(port: number): boolean;
49
55
  * (lowest port, deterministic), or null when none is running.
50
56
  */
51
57
  export declare function discoverDaemonPort(): number | null;
58
+ /**
59
+ * Does this pidfile name a daemon that is still running?
60
+ *
61
+ * The pid must be alive AND the file must be newer than the last boot. A pid alone is not enough:
62
+ * the OS reuses pids, and after a reboot a pidfile named an unrelated Bluetooth service on Windows.
63
+ * The port was free, but serve skipped the start because that pid was alive, and the daemon never
64
+ * came up. A file whose age cannot be read is judged by its pid alone, as before.
65
+ */
66
+ export declare function pidFileIsLive(path: string, pid: number, pidAlive: (pid: number) => boolean, bootMs?: () => number): boolean;
52
67
  /**
53
68
  * Sweep ~/.reticle for daemon-<port>.pid files whose process is no longer alive and delete them, so a
54
69
  * crashed daemon never leaves a stale pidfile that confuses discovery or makes a port look "taken".
55
70
  * Returns the ports reclaimed. `home` and `pidAlive` are injectable for testing (default to the real
56
71
  * ~/.reticle and the process.kill(pid,0) liveness probe).
57
72
  */
58
- export declare function reclaimStaleDaemons(home?: string, pidAlive?: (pid: number) => boolean): number[];
73
+ export declare function reclaimStaleDaemons(home?: string, pidAlive?: (pid: number) => boolean, bootMs?: () => number): number[];
59
74
  /** The minimal shape of a spawned child that spawnDaemon uses. */
60
75
  export interface SpawnedChild {
61
76
  readonly pid?: number | undefined;
@@ -76,15 +91,18 @@ export interface SpawnDaemonDeps {
76
91
  env?: Record<string, string | undefined>;
77
92
  }): SpawnedChild;
78
93
  pidAlive(pid: number): boolean;
94
+ /** When the machine last booted, epoch ms. Optional: the default reads `os.uptime()`. */
95
+ readonly bootTimeMs?: () => number;
79
96
  /** `process.platform`. Optional so a test that does not care runs the detached start. */
80
97
  readonly platform?: string;
81
98
  /** Windows only: start the daemon outside the Job Object of this process. See win32-escape.ts. */
82
99
  readonly escapeJob?: (request: EscapeRequest) => EscapeResult;
83
100
  /**
84
- * A predecessor died without a clean stop; `ageMs` is how long it had run. Optional so a test that
85
- * does not care reports nothing. The default sends `daemon_died_unclean`.
101
+ * A predecessor died without a clean stop; `ageMs` is how long it had run, and `cause` is what its
102
+ * own log says about how it ended. Optional so a test that does not care reports nothing. The
103
+ * default sends `daemon_died_unclean`.
86
104
  */
87
- onUncleanDeath?(ageMs: number): void;
105
+ onUncleanDeath?(ageMs: number, cause?: DeathCause): void;
88
106
  }
89
107
  /**
90
108
  * Spawn the reticle daemon as a detached background process, redirecting output to the log file.