@reticlehq/server 3.2.0 → 3.4.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 (437) hide show
  1. package/LICENSE-ENTERPRISE +1 -1
  2. package/README.md +3 -3
  3. package/SKILL.md +23 -22
  4. package/dist/chromium-launch-options.d.ts +4 -3
  5. package/dist/chromium-launch-options.js +6 -2
  6. package/dist/command/cli/adhoc-suite.d.ts +41 -0
  7. package/dist/command/cli/adhoc-suite.js +159 -0
  8. package/dist/command/cli/adhoc-verdict.d.ts +58 -2
  9. package/dist/command/cli/adhoc-verdict.js +108 -13
  10. package/dist/command/cli/answers/gate-hook-message.d.ts +6 -0
  11. package/dist/command/cli/answers/gate-hook-message.js +6 -0
  12. package/dist/command/cli/auth/cloud-keystore.d.ts +0 -2
  13. package/dist/command/cli/auth/cloud-keystore.js +0 -3
  14. package/dist/command/cli/auth/cloud-session.d.ts +4 -0
  15. package/dist/command/cli/auth/cloud-session.js +2 -0
  16. package/dist/command/cli/cli-doctor.js +30 -8
  17. package/dist/command/cli/cli-flow-commands.d.ts +3 -6
  18. package/dist/command/cli/cli-flow-commands.js +67 -4
  19. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  20. package/dist/command/cli/cli-parse-grammar.js +7 -0
  21. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  22. package/dist/command/cli/cli-parse-verify.js +86 -13
  23. package/dist/command/cli/cli-parse.d.ts +14 -1
  24. package/dist/command/cli/cli-parse.js +54 -8
  25. package/dist/command/cli/cli-update-commands.js +8 -1
  26. package/dist/command/cli/cli-verify.d.ts +80 -0
  27. package/dist/command/cli/cli-verify.js +181 -26
  28. package/dist/command/cli/cloud-cli.js +5 -2
  29. package/dist/command/cli/cloud-kit.d.ts +0 -2
  30. package/dist/command/cli/cloud-kit.js +3 -5
  31. package/dist/command/cli/cloud-login.js +21 -3
  32. package/dist/command/cli/demo/demo-page.js +6 -3
  33. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +5 -0
  34. package/dist/command/cli/doctor/browser/chromium-hint.js +5 -0
  35. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  36. package/dist/command/cli/launch/cli-launch.d.ts +9 -29
  37. package/dist/command/cli/launch/cli-launch.js +27 -49
  38. package/dist/command/cli/launch/sdk-version-match.d.ts +84 -0
  39. package/dist/command/cli/launch/sdk-version-match.js +206 -0
  40. package/dist/command/cli/lifecycle/daemon-lifecycle.js +2 -16
  41. package/dist/command/cli/ports/resolve/cli-port.d.ts +38 -0
  42. package/dist/command/cli/ports/resolve/cli-port.js +169 -15
  43. package/dist/command/cli/report-command.d.ts +16 -0
  44. package/dist/command/cli/report-command.js +97 -0
  45. package/dist/command/cli/setup-mcp-cli.js +13 -13
  46. package/dist/command/cli/tutorial.d.ts +2 -2
  47. package/dist/command/cli/tutorial.js +21 -7
  48. package/dist/command/cli.js +67 -10
  49. package/dist/command/daemon/binding/daemon-status-probe.d.ts +44 -2
  50. package/dist/command/daemon/binding/daemon-status-probe.js +79 -6
  51. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  52. package/dist/command/daemon/daemon-resolve.d.ts +9 -0
  53. package/dist/command/daemon/daemon-resolve.js +1 -1
  54. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  55. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +7 -1
  56. package/dist/command/daemon/lifetime/daemon-usefulness.js +30 -7
  57. package/dist/command/dev/stale-issue-guard.d.ts +3 -4
  58. package/dist/command/dev/stale-issue-guard.js +8 -8
  59. package/dist/command/setup/bringup/connect-lease.d.ts +17 -0
  60. package/dist/command/setup/bringup/connect-lease.js +38 -0
  61. package/dist/command/setup/bringup/dev-server-files.d.ts +68 -0
  62. package/dist/command/setup/bringup/dev-server-files.js +131 -0
  63. package/dist/command/setup/bringup/dev-server-log-cap.d.ts +35 -0
  64. package/dist/command/setup/bringup/dev-server-log-cap.js +121 -0
  65. package/dist/command/setup/bringup/ensure-daemon.d.ts +65 -4
  66. package/dist/command/setup/bringup/ensure-daemon.js +135 -16
  67. package/dist/command/setup/bringup/static-page-server.d.ts +35 -0
  68. package/dist/command/setup/bringup/static-page-server.js +151 -0
  69. package/dist/command/setup/desktop-shape.d.ts +4 -0
  70. package/dist/command/setup/desktop-shape.js +18 -2
  71. package/dist/command/setup/init/init-host.js +8 -0
  72. package/dist/command/setup/init/init-port.d.ts +15 -0
  73. package/dist/command/setup/init/init-port.js +44 -0
  74. package/dist/command/setup/init/init-runtime.d.ts +1 -1
  75. package/dist/command/setup/init/init-runtime.js +39 -2
  76. package/dist/command/setup/node-effects.d.ts +27 -14
  77. package/dist/command/setup/node-effects.js +80 -44
  78. package/dist/command/setup/probe/loopback-probe-urls.d.ts +7 -0
  79. package/dist/command/setup/probe/loopback-probe-urls.js +40 -0
  80. package/dist/command/setup/probe/page-probe.d.ts +13 -2
  81. package/dist/command/setup/probe/page-probe.js +18 -3
  82. package/dist/command/setup/run-setup.d.ts +26 -1
  83. package/dist/command/setup/run-setup.js +96 -13
  84. package/dist/command/setup/session-pick.d.ts +13 -0
  85. package/dist/command/setup/session-pick.js +20 -0
  86. package/dist/command/setup/setup-command.d.ts +2 -14
  87. package/dist/command/setup/setup-command.js +105 -31
  88. package/dist/command/setup/setup-mcp.js +3 -3
  89. package/dist/command/setup/terminal/wait-progress.d.ts +8 -0
  90. package/dist/command/setup/terminal/wait-progress.js +17 -0
  91. package/dist/features/crawl/crawl-empty.js +1 -1
  92. package/dist/features/crawl/crawl-tools.d.ts +6 -0
  93. package/dist/features/crawl/crawl-tools.js +57 -0
  94. package/dist/features/crawl/crawl.d.ts +2 -0
  95. package/dist/features/crawl/crawl.js +171 -156
  96. package/dist/features/exhaust/app-coverage.d.ts +33 -0
  97. package/dist/features/exhaust/app-coverage.js +31 -0
  98. package/dist/features/exhaust/code-coverage.d.ts +47 -0
  99. package/dist/features/exhaust/code-coverage.js +77 -0
  100. package/dist/features/exhaust/exhaust-run.d.ts +33 -0
  101. package/dist/features/exhaust/exhaust-run.js +82 -0
  102. package/dist/features/exhaust/explorer.d.ts +103 -0
  103. package/dist/features/exhaust/explorer.js +195 -0
  104. package/dist/features/exhaust/failure-branches.d.ts +33 -0
  105. package/dist/features/exhaust/failure-branches.js +92 -0
  106. package/dist/features/exhaust/ledger.d.ts +202 -0
  107. package/dist/features/exhaust/ledger.js +252 -0
  108. package/dist/features/exhaust/session-fold.d.ts +31 -0
  109. package/dist/features/exhaust/session-fold.js +79 -0
  110. package/dist/features/exhaust/session-port.d.ts +36 -0
  111. package/dist/features/exhaust/session-port.js +102 -0
  112. package/dist/features/harness/drive-report.d.ts +0 -9
  113. package/dist/features/harness/drive-report.js +18 -0
  114. package/dist/features/harness/driver.js +4 -7
  115. package/dist/features/harness/fill-values.d.ts +6 -0
  116. package/dist/features/harness/fill-values.js +3 -0
  117. package/dist/features/harness/jev-driver.js +36 -22
  118. package/dist/features/harness/openai-driver.js +4 -7
  119. package/dist/features/harness/platform-config.d.ts +10 -0
  120. package/dist/features/harness/platform-config.js +13 -6
  121. package/dist/index.d.ts +10 -1
  122. package/dist/index.js +78 -104
  123. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  124. package/dist/judgement/capsule/capsule-store.js +58 -6
  125. package/dist/judgement/capsule/capsule.js +5 -0
  126. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  127. package/dist/judgement/capsule/causal-summary.js +7 -0
  128. package/dist/judgement/domain/domain-model.js +40 -9
  129. package/dist/judgement/oracles/self-instrument.js +4 -2
  130. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  131. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  132. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  133. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  134. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  135. package/dist/judgement/runs/artifact/render-report.js +4 -1
  136. package/dist/judgement/runs/artifact/replay-mapping.d.ts +2 -2
  137. package/dist/judgement/runs/artifact/replay-mapping.js +44 -9
  138. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  139. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  140. package/dist/judgement/runs/context-tools.js +21 -3
  141. package/dist/judgement/runs/drive-run.js +7 -2
  142. package/dist/judgement/runs/verification-sync.d.ts +3 -1
  143. package/dist/judgement/runs/verification-sync.js +1 -1
  144. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  145. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  146. package/dist/language/flows/change/affected.d.ts +9 -0
  147. package/dist/language/flows/change/affected.js +35 -0
  148. package/dist/language/flows/change/gate.d.ts +20 -0
  149. package/dist/language/flows/change/gate.js +12 -1
  150. package/dist/language/flows/flow-anchor.d.ts +12 -0
  151. package/dist/language/flows/flow-anchor.js +20 -0
  152. package/dist/language/flows/flow-author.d.ts +7 -0
  153. package/dist/language/flows/flow-author.js +19 -0
  154. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  155. package/dist/language/flows/flow-expect-grammar.js +92 -11
  156. package/dist/language/flows/flow-intent.js +1 -1
  157. package/dist/language/flows/flow-journey.d.ts +54 -0
  158. package/dist/language/flows/flow-journey.js +119 -0
  159. package/dist/language/flows/flow-learning.d.ts +3 -0
  160. package/dist/language/flows/flow-learning.js +39 -8
  161. package/dist/language/flows/flow-replay-run.d.ts +33 -12
  162. package/dist/language/flows/flow-replay-run.js +131 -52
  163. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  164. package/dist/language/flows/flow-replay.d.ts +27 -12
  165. package/dist/language/flows/flow-replay.js +127 -111
  166. package/dist/language/flows/flow-result.d.ts +2 -0
  167. package/dist/language/flows/flow-result.js +25 -0
  168. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  169. package/dist/language/flows/flow-step-runners.js +20 -4
  170. package/dist/language/flows/flow-success.d.ts +21 -6
  171. package/dist/language/flows/flow-success.js +50 -138
  172. package/dist/language/flows/flow-tools.js +17 -5
  173. package/dist/language/flows/flows.d.ts +18 -5
  174. package/dist/language/flows/flows.js +101 -16
  175. package/dist/language/flows/heal.d.ts +9 -1
  176. package/dist/language/flows/heal.js +24 -2
  177. package/dist/language/flows/learned-sources.d.ts +21 -0
  178. package/dist/language/flows/learned-sources.js +84 -0
  179. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  180. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  181. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  182. package/dist/language/flows/recording/replay-halt.js +3 -1
  183. package/dist/language/flows/recording/tape/recordings.d.ts +31 -2
  184. package/dist/language/flows/recording/tape/recordings.js +55 -1
  185. package/dist/language/flows/replay.d.ts +19 -0
  186. package/dist/language/flows/replay.js +53 -13
  187. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  188. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  189. package/dist/language/flows/stores/flake-store.js +2 -3
  190. package/dist/language/flows/suite/coverage.d.ts +1 -1
  191. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  192. package/dist/language/flows/verify-change-tools.js +19 -2
  193. package/dist/launch-chromium.d.ts +80 -0
  194. package/dist/launch-chromium.js +218 -0
  195. package/dist/memory/cloud/cloud-config.d.ts +11 -2
  196. package/dist/memory/cloud/cloud-config.js +47 -7
  197. package/dist/memory/cloud/cloud-sync.d.ts +4 -3
  198. package/dist/memory/cloud/cloud-sync.js +7 -10
  199. package/dist/memory/cloud/harness-config.d.ts +2 -0
  200. package/dist/memory/cloud/harness-offer.js +5 -6
  201. package/dist/memory/cloud/harness-switch.js +5 -6
  202. package/dist/memory/cloud/sync-cycle.d.ts +78 -1
  203. package/dist/memory/cloud/sync-cycle.js +321 -53
  204. package/dist/memory/cloud/sync-daemon.d.ts +13 -0
  205. package/dist/memory/cloud/sync-daemon.js +45 -20
  206. package/dist/memory/cloud/sync-disk.js +27 -8
  207. package/dist/memory/impact/impact-store.js +6 -0
  208. package/dist/memory/intent/inline-intent.js +8 -3
  209. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  210. package/dist/memory/intent/intent-shard-store.js +252 -140
  211. package/dist/memory/intent/intent-shard.d.ts +0 -23
  212. package/dist/memory/intent/intent-shard.js +1 -11
  213. package/dist/memory/intent/intent-store.d.ts +13 -8
  214. package/dist/memory/intent/intent-store.js +46 -90
  215. package/dist/memory/intent/intent-tools.js +34 -23
  216. package/dist/memory/journal/ambient-store.js +2 -3
  217. package/dist/memory/journal/deviation-report.js +4 -1
  218. package/dist/memory/journal/deviation-service.js +10 -2
  219. package/dist/memory/journal/drive-flow.d.ts +19 -14
  220. package/dist/memory/journal/drive-flow.js +65 -48
  221. package/dist/memory/journal/envelope-store.js +2 -3
  222. package/dist/memory/journal/journal-recorder.d.ts +21 -0
  223. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  224. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  225. package/dist/memory/journal/on-disk/retention.d.ts +14 -10
  226. package/dist/memory/journal/on-disk/retention.js +13 -12
  227. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  228. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  229. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -11
  230. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -45
  231. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  232. package/dist/memory/journal/on-disk/workspace-tiers.js +90 -0
  233. package/dist/memory/journal/route-template.d.ts +22 -0
  234. package/dist/memory/journal/route-template.js +44 -0
  235. package/dist/memory/journal/session-end.d.ts +13 -2
  236. package/dist/memory/journal/session-end.js +88 -18
  237. package/dist/memory/journal/session-journal.d.ts +8 -1
  238. package/dist/memory/journal/session-journal.js +10 -0
  239. package/dist/memory/project/baselines.d.ts +1 -1
  240. package/dist/memory/project/dir/reticle-dir.d.ts +3 -1
  241. package/dist/memory/project/dir/reticle-dir.js +1 -0
  242. package/dist/memory/project/fs/fs-port.js +44 -5
  243. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  244. package/dist/memory/project/fs/write-atomic.js +48 -0
  245. package/dist/memory/project/project-tools.js +3 -1
  246. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  247. package/dist/memory/recall/prior/connection-memory.js +8 -0
  248. package/dist/memory/recall/registered-projects.d.ts +2 -1
  249. package/dist/portal/bridge/auth-failure-reason.d.ts +1 -1
  250. package/dist/portal/bridge/auth-failure-reason.js +9 -4
  251. package/dist/portal/bridge/bridge.d.ts +8 -0
  252. package/dist/portal/bridge/bridge.js +43 -2
  253. package/dist/portal/input/js-coverage.d.ts +25 -0
  254. package/dist/portal/input/js-coverage.js +30 -0
  255. package/dist/portal/input/real-input.d.ts +11 -0
  256. package/dist/portal/input/real-input.js +31 -5
  257. package/dist/portal/pool/browser-pool.d.ts +10 -0
  258. package/dist/portal/pool/browser-pool.js +10 -0
  259. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  260. package/dist/portal/pool/playwright-launcher.js +21 -13
  261. package/dist/portal/pool/pool-contract.d.ts +2 -0
  262. package/dist/portal/pool/storage-seed.d.ts +2 -1
  263. package/dist/portal/pool/zero-install.d.ts +27 -0
  264. package/dist/portal/pool/zero-install.js +54 -0
  265. package/dist/portal/realm/web-realm.js +6 -0
  266. package/dist/portal/session/dev-server/dev-command.d.ts +2 -2
  267. package/dist/portal/session/dev-server/dev-command.js +30 -12
  268. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  269. package/dist/portal/session/dev-server/dev-server-probe.js +17 -1
  270. package/dist/portal/session/dev-server/route-status-probe.d.ts +24 -0
  271. package/dist/portal/session/dev-server/route-status-probe.js +57 -0
  272. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  273. package/dist/portal/session/dev-server/served-document.js +57 -0
  274. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  275. package/dist/portal/session/facts/background-traffic.js +25 -0
  276. package/dist/portal/session/facts/observed-state.d.ts +11 -0
  277. package/dist/portal/session/facts/observed-state.js +34 -0
  278. package/dist/portal/session/live-control-tools.js +48 -3
  279. package/dist/portal/session/no-session-diagnosis.d.ts +29 -0
  280. package/dist/portal/session/no-session-diagnosis.js +62 -5
  281. package/dist/portal/session/no-session-next-action.d.ts +13 -0
  282. package/dist/portal/session/no-session-next-action.js +48 -9
  283. package/dist/portal/session/no-session-watch.d.ts +18 -0
  284. package/dist/portal/session/no-session-watch.js +88 -2
  285. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  286. package/dist/portal/session/session-health.d.ts +61 -0
  287. package/dist/portal/session/session-health.js +43 -3
  288. package/dist/portal/session/session-info.d.ts +6 -0
  289. package/dist/portal/session/session-manager.d.ts +29 -6
  290. package/dist/portal/session/session-manager.js +42 -5
  291. package/dist/portal/session/session-reaper.d.ts +28 -0
  292. package/dist/portal/session/session-reaper.js +49 -1
  293. package/dist/portal/session/session-successor.d.ts +11 -0
  294. package/dist/portal/session/session-successor.js +14 -1
  295. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  296. package/dist/portal/session/session-verdict-facts.js +15 -0
  297. package/dist/portal/session/session.d.ts +10 -0
  298. package/dist/portal/session/session.js +34 -26
  299. package/dist/surface/http-server.d.ts +2 -1
  300. package/dist/surface/http-server.js +4 -2
  301. package/dist/surface/mcp/mcp-proxy.d.ts +2 -2
  302. package/dist/surface/mcp/mcp-proxy.js +5 -5
  303. package/dist/surface/mcp/mcp.js +4 -2
  304. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +13 -1
  305. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +27 -2
  306. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  307. package/dist/surface/mcp/server-instructions.js +21 -8
  308. package/dist/surface/tools/act/after-match.d.ts +17 -0
  309. package/dist/surface/tools/act/after-match.js +30 -0
  310. package/dist/surface/tools/act/already-true.d.ts +40 -0
  311. package/dist/surface/tools/act/already-true.js +36 -0
  312. package/dist/surface/tools/act/durable.d.ts +69 -0
  313. package/dist/surface/tools/act/durable.js +139 -0
  314. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +19 -6
  315. package/dist/surface/tools/act/navigation/navigate-arrival.js +60 -10
  316. package/dist/surface/tools/act/navigation/navigate-result.d.ts +1 -12
  317. package/dist/surface/tools/act/navigation/navigate-result.js +21 -3
  318. package/dist/surface/tools/act/proved-controls.d.ts +9 -0
  319. package/dist/surface/tools/act/proved-controls.js +11 -0
  320. package/dist/surface/tools/act/sequence-grade.d.ts +5 -0
  321. package/dist/surface/tools/act/sequence-grade.js +32 -1
  322. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  323. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  324. package/dist/surface/tools/act-capsule.d.ts +7 -0
  325. package/dist/surface/tools/act-capsule.js +68 -8
  326. package/dist/surface/tools/act-sequence-tool.js +25 -8
  327. package/dist/surface/tools/act-tools.js +51 -74
  328. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  329. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  330. package/dist/surface/tools/assert/assert-grade.js +26 -3
  331. package/dist/surface/tools/assert/assert-verdict.js +32 -6
  332. package/dist/surface/tools/browser-tools.js +17 -4
  333. package/dist/surface/tools/coverage-tools.js +27 -1
  334. package/dist/surface/tools/dynamic-tools.js +6 -0
  335. package/dist/surface/tools/error-recovery.js +4 -4
  336. package/dist/surface/tools/feedback-invite.js +6 -4
  337. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  338. package/dist/surface/tools/feedback-tools.js +1 -1
  339. package/dist/surface/tools/harness-explore.d.ts +3 -2
  340. package/dist/surface/tools/harness-explore.js +7 -6
  341. package/dist/surface/tools/invoke-tool.js +17 -4
  342. package/dist/surface/tools/lease-hint.d.ts +8 -0
  343. package/dist/surface/tools/lease-hint.js +10 -0
  344. package/dist/surface/tools/lease-tools.d.ts +16 -28
  345. package/dist/surface/tools/lease-tools.js +68 -11
  346. package/dist/surface/tools/lineage-tools.js +1 -1
  347. package/dist/surface/tools/live-call-text.js +4 -1
  348. package/dist/surface/tools/observe-tools.js +23 -7
  349. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  350. package/dist/surface/tools/real-input-attempt.js +1 -1
  351. package/dist/surface/tools/tool-kit.d.ts +5 -0
  352. package/dist/surface/tools/tool-kit.js +6 -1
  353. package/dist/surface/tools/tool-surface.js +17 -5
  354. package/dist/surface/tools/tools.js +68 -29
  355. package/dist/telemetry/argument-shape.d.ts +0 -2
  356. package/dist/telemetry/argument-shape.js +0 -4
  357. package/dist/telemetry/error-class.d.ts +2 -3
  358. package/dist/telemetry/error-class.js +2 -3
  359. package/dist/telemetry/feedback-local.d.ts +1 -0
  360. package/dist/telemetry/feedback-local.js +1 -3
  361. package/dist/telemetry/feedback.d.ts +1 -1
  362. package/dist/telemetry/harness-drive.d.ts +0 -2
  363. package/dist/telemetry/harness-drive.js +0 -4
  364. package/dist/telemetry/hud-metrics.d.ts +32 -0
  365. package/dist/telemetry/hud-metrics.js +95 -0
  366. package/dist/telemetry/session-metrics.d.ts +5 -0
  367. package/dist/telemetry/session-metrics.js +15 -1
  368. package/dist/wire-journal.d.ts +34 -0
  369. package/dist/wire-journal.js +104 -0
  370. package/dist/workspace-packages.d.ts +0 -2
  371. package/dist/workspace-packages.js +1 -9
  372. package/docs/README.md +5 -2
  373. package/docs/actions.mdx +3 -3
  374. package/docs/agent-cheatsheet.md +4 -4
  375. package/docs/architecture.md +6 -6
  376. package/docs/best-practices.mdx +1 -1
  377. package/docs/capabilities.mdx +32 -1
  378. package/docs/cli/cloud.mdx +21 -9
  379. package/docs/cli/doctor.mdx +4 -0
  380. package/docs/cli/feedback.mdx +2 -2
  381. package/docs/cli/gate.mdx +9 -4
  382. package/docs/cli/init.mdx +1 -0
  383. package/docs/cli/report.mdx +44 -0
  384. package/docs/cli/update.mdx +10 -0
  385. package/docs/cli.mdx +9 -0
  386. package/docs/desktop.mdx +14 -10
  387. package/docs/environment.mdx +87 -0
  388. package/docs/faq.mdx +18 -16
  389. package/docs/first-drive.md +5 -13
  390. package/docs/flows.md +32 -2
  391. package/docs/frameworks.mdx +140 -9
  392. package/docs/getting-started.md +3 -3
  393. package/docs/http-transport.md +6 -3
  394. package/docs/human-control.md +2 -2
  395. package/docs/index.mdx +16 -6
  396. package/docs/install-agentic.mdx +37 -1
  397. package/docs/install-manual.mdx +1 -1
  398. package/docs/integration-patterns.md +3 -3
  399. package/docs/multi-agent-testing.md +1 -1
  400. package/docs/packages/babel-plugin.mdx +1 -1
  401. package/docs/packages/browser.mdx +2 -2
  402. package/docs/packages/react.mdx +2 -2
  403. package/docs/packages/tauri.mdx +1 -1
  404. package/docs/packages/vite-plugin.mdx +2 -2
  405. package/docs/packages.mdx +9 -9
  406. package/docs/predicates.mdx +176 -7
  407. package/docs/quickstart.mdx +2 -2
  408. package/docs/recipes.mdx +2 -2
  409. package/docs/state-management.mdx +2 -2
  410. package/docs/system-map.md +1 -1
  411. package/docs/telemetry.md +1 -1
  412. package/docs/token-efficiency.md +3 -3
  413. package/docs/tools/act-and-wait.mdx +1 -0
  414. package/docs/tools/act-sequence.mdx +1 -1
  415. package/docs/tools/console.mdx +1 -1
  416. package/docs/tools/inspect.mdx +1 -1
  417. package/docs/tools/network.mdx +1 -1
  418. package/docs/tools/query.mdx +11 -11
  419. package/docs/tools/sessions.mdx +1 -1
  420. package/docs/tools/snapshot.mdx +1 -1
  421. package/docs/tools/state.mdx +1 -1
  422. package/docs/tools/wait-for.mdx +1 -1
  423. package/docs/usage.md +8 -7
  424. package/docs/use-cases.mdx +157 -0
  425. package/docs/vs-screenshots.mdx +3 -3
  426. package/docs/what-is-recorded.md +62 -0
  427. package/docs/what-is-reticle.mdx +17 -20
  428. package/docs/why-reticle.mdx +14 -21
  429. package/package.json +11 -10
  430. package/dist/judgement/capsule/minimize.d.ts +0 -12
  431. package/dist/judgement/capsule/minimize.js +0 -22
  432. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  433. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
  434. package/dist/judgement/phenomena/phenomena.d.ts +0 -26
  435. package/dist/judgement/phenomena/phenomena.js +0 -101
  436. package/dist/language/flows/flow-report.d.ts +0 -22
  437. package/dist/language/flows/flow-report.js +0 -106
@@ -23,7 +23,7 @@ export { AGENT_FLAG, BUG_FLAG, FEEDBACK_KINDS, KIND_FLAG, RATING_FLAG, } from '.
23
23
  export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticle <command>\` once the bin is on your PATH)
24
24
 
25
25
  reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] [--app <dir>]
26
- [--env KEY=VALUE]... [--files-only] (wire Reticle into the project in this directory)
26
+ [--env KEY=VALUE]... [--files-only] [--hooks] (wire Reticle into the project in this directory)
27
27
  init is ONBOARDING: it wires the project, boots the app and proves a session
28
28
  connected. It does not drive. The FIRST RUN is the stage that proves a flow:
29
29
  reticle verify <url> --explore --persona "<who does what>", or the same thing
@@ -56,12 +56,21 @@ export const CLI_USAGE = `usage: npx @reticlehq/server <command> (or \`reticl
56
56
  reticle verify <url> [--port N] [--headed] [--timeout N] [--storage-state <file>] [--session-id <id>] (one-shot: drive the URL, verify saved flows, exit 0=pass)
57
57
  [--explore] [--persona <who>] (no saved flows? let Reticle drive the app itself and record them)
58
58
  [--select <label>] (repeatable: verify only flows carrying these labels — no model, exit 0=pass)
59
- [--expect '<json predicate>'] (one verdict, no saved flows needed — asks
60
- the daemon that is already running, so nothing is bound and nothing is stopped. This
61
- is the path when your client never loaded the reticle_* tools. exit 0 ONLY on
62
- verified:"yes" — "unknown" is not a pass)
59
+ [--expect '<json predicate>' | --expect-file <path>] (one verdict, no saved
60
+ flows needed. --expect-file is the form no shell can mangle: cmd.exe keeps the
61
+ single quotes the docs show, and PowerShell strips the inner double quotes, and
62
+ a predicate that does not parse produces no verdict at all — asks the daemon that
63
+ is already running, so nothing is bound and nothing is stopped. It NEEDS that
64
+ daemon: with none on the port it refuses and names the flag, rather than reporting
65
+ on your saved flows instead. Cannot be combined with --storage-state, which this
66
+ path has nowhere to load. This is the path when your client never loaded the
67
+ reticle_* tools. exit 0 ONLY on verified:"yes" — "unknown" is not a pass)
63
68
  reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)
64
- reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)
69
+ reticle gate [--since <ref>] [--accept-coverage] [file...] (exit non-zero unless passing artifacts cover the affected flows)
70
+ reticle report [--session <id>] [--hook] (what the latest session claimed, and what held)
71
+ reticle setup mcp (register the MCP server with your agents; the installer's registration half)
72
+ reticle capsules (list the saved fail-to-pass bug capsules in .reticle/capsules)
73
+ reticle hunt <dir> (aggregate a directory of crawl reports into one false-green rate)
65
74
  reticle watch [url] (on save, report which saved flows must re-verify)
66
75
  reticle drive <url> [--headless] (foreground mode, for debugging)
67
76
  reticle mcp [--port N] [--drive <url>] [--headless] (MCP stdio proxy; auto-starts daemon if needed)
@@ -102,6 +111,10 @@ const CAPSULES_COMMAND = 'capsules';
102
111
  const GATE_COMMAND = 'gate';
103
112
  /** Hook mode: prose a human can read, and silence when there was simply nothing to check. */
104
113
  const HOOK_FLAG = '--hook';
114
+ const ACCEPT_COVERAGE_FLAG = '--accept-coverage';
115
+ /** `reticle report [--session <id>] [--hook]` — what the latest session claimed, and what held. */
116
+ const REPORT_COMMAND = 'report';
117
+ const SESSION_FLAG = '--session';
105
118
  const WATCH_COMMAND = 'watch';
106
119
  const UPDATE_COMMAND = 'update';
107
120
  const ROLLBACK_COMMAND = 'rollback';
@@ -146,6 +159,7 @@ const KNOWN_COMMANDS = new Set([
146
159
  HUNT_COMMAND,
147
160
  CAPSULES_COMMAND,
148
161
  GATE_COMMAND,
162
+ REPORT_COMMAND,
149
163
  WATCH_COMMAND,
150
164
  UPDATE_COMMAND,
151
165
  ROLLBACK_COMMAND,
@@ -228,6 +242,8 @@ const FILES_ONLY_FLAG = '--files-only';
228
242
  * and `init` runs unattended. Whoever passes this has decided; nothing decides it for them.
229
243
  */
230
244
  const CAPTURE_BODIES_FLAG = '--capture-bodies';
245
+ /** `--hooks`: also install the print-only Claude Code Stop hook (`reticle report --hook`). */
246
+ const HOOKS_FLAG = '--hooks';
231
247
  /**
232
248
  * The rest of the runtime surface.
233
249
  *
@@ -410,6 +426,7 @@ function parseInitFlags(args) {
410
426
  const env = [];
411
427
  let filesOnly = false;
412
428
  let captureBodies = false;
429
+ let hooks = false;
413
430
  let json = false;
414
431
  let open = true;
415
432
  let relaunch = false;
@@ -457,6 +474,9 @@ function parseInitFlags(args) {
457
474
  else if (arg === CAPTURE_BODIES_FLAG) {
458
475
  captureBodies = true;
459
476
  }
477
+ else if (arg === HOOKS_FLAG) {
478
+ hooks = true;
479
+ }
460
480
  else if (arg === JSON_FLAG) {
461
481
  json = true;
462
482
  }
@@ -520,6 +540,7 @@ function parseInitFlags(args) {
520
540
  env,
521
541
  filesOnly,
522
542
  captureBodies,
543
+ hooks,
523
544
  json,
524
545
  open,
525
546
  relaunch,
@@ -561,6 +582,18 @@ function parseTargetArgs(rest) {
561
582
  }
562
583
  return since === undefined ? { files } : { files, since };
563
584
  }
585
+ /**
586
+ * Whether this command talks to a daemon on the resolved port, and so must not run when
587
+ * the workspace port conflict (cli-port) has said the port is a guess. `init` carries a port but resolves the
588
+ * workspace itself, and a tutorial only dials one when asked to run.
589
+ */
590
+ export function dialsTheDaemon(parsed) {
591
+ if ('init' === parsed.kind)
592
+ return false;
593
+ if ('tutorial' === parsed.kind)
594
+ return parsed.run;
595
+ return 'port' in parsed;
596
+ }
564
597
  /**
565
598
  * @param defaultHeadless Whether a browser Reticle launches should be hidden when no flag says
566
599
  * otherwise. Injected rather than read from the environment here, because this module is pure —
@@ -606,6 +639,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
606
639
  env: r.env,
607
640
  filesOnly: r.filesOnly,
608
641
  captureBodies: r.captureBodies,
642
+ hooks: r.hooks,
609
643
  json: r.json,
610
644
  open: r.open,
611
645
  relaunch: r.relaunch,
@@ -772,6 +806,7 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
772
806
  ...(r.storageState !== undefined ? { storageState: r.storageState } : {}),
773
807
  ...(r.sessionId !== undefined ? { sessionId: r.sessionId } : {}),
774
808
  ...(r.expect !== undefined ? { expect: r.expect } : {}),
809
+ ...(r.expectFile !== undefined ? { expectFile: r.expectFile } : {}),
775
810
  ...(true === r.explore ? { explore: true } : {}),
776
811
  ...(r.persona !== undefined ? { persona: r.persona } : {}),
777
812
  ...(r.select !== undefined ? { select: r.select } : {}),
@@ -791,15 +826,26 @@ export function parseCliArgs(argv, defaultPort, defaultHeadless = false) {
791
826
  return { kind: 'affected', files: t.files, ...(since === undefined ? {} : { since }) };
792
827
  }
793
828
  case GATE_COMMAND: {
794
- // `--hook` is stripped before target parsing, which would reject it as an unknown flag.
829
+ // Flags are stripped before target parsing, which would reject them as unknown.
795
830
  const hook = rest.includes(HOOK_FLAG);
796
- const t = parseTargetArgs(rest.filter((a) => a !== HOOK_FLAG));
831
+ const acceptCoverage = rest.includes(ACCEPT_COVERAGE_FLAG);
832
+ const t = parseTargetArgs(rest.filter((a) => a !== HOOK_FLAG && a !== ACCEPT_COVERAGE_FLAG));
797
833
  const since = t.since ?? implicitSince(t.files);
798
834
  return {
799
835
  kind: 'gate',
800
836
  files: t.files,
801
837
  ...(since === undefined ? {} : { since }),
802
838
  ...(hook ? { hook: true } : {}),
839
+ ...(acceptCoverage ? { acceptCoverage: true } : {}),
840
+ };
841
+ }
842
+ case REPORT_COMMAND: {
843
+ const at = rest.indexOf(SESSION_FLAG);
844
+ const session = -1 === at ? undefined : rest[at + 1];
845
+ return {
846
+ kind: 'report',
847
+ hook: rest.includes(HOOK_FLAG),
848
+ ...(session === undefined ? {} : { session }),
803
849
  };
804
850
  }
805
851
  case WATCH_COMMAND: {
@@ -14,6 +14,7 @@ import { log } from '../../log.js';
14
14
  import { readFileSync, writeFileSync, existsSync } from 'node:fs';
15
15
  import { join } from 'node:path';
16
16
  import { reticleDepsOf, sdkSyncCommand } from '../update/sdk-sync.js';
17
+ const WINDOWS_MANUAL_UPDATE = 'irm https://raw.githubusercontent.com/reticlehq/reticle/main/install/install.ps1 | iex';
17
18
  /**
18
19
  * Bring the SDK in the CURRENT project to the version being installed.
19
20
  *
@@ -94,7 +95,13 @@ export async function handleUpdate() {
94
95
  await applyUpdate(target); // calls process.exit; Claude Code restarts
95
96
  }
96
97
  catch (error) {
97
- log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ log('reticle_update_failed', {
100
+ error: message,
101
+ ...('win32' === process.platform && message.includes('spawn EINVAL')
102
+ ? { hint: `run \`${WINDOWS_MANUAL_UPDATE}\` in PowerShell to update manually` }
103
+ : {}),
104
+ });
98
105
  process.exitCode = 1;
99
106
  }
100
107
  }
@@ -11,6 +11,8 @@
11
11
  */
12
12
  import { type ReticleVerificationRun } from '@reticlehq/core';
13
13
  import { type RunningServer } from '../../index.js';
14
+ import { PortPresence } from '../daemon/binding/port-presence.js';
15
+ import { type CloudConfig } from '../../memory/cloud/cloud-sync.js';
14
16
  import { type VerifyProgressListener } from '../../judgement/runs/reticle-runner.js';
15
17
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
16
18
  /** The live capabilities runVerify needs — faked in tests so the logic runs without a browser. */
@@ -46,6 +48,11 @@ export interface VerifyPorts {
46
48
  out: (line: string) => void;
47
49
  fail: (line: string) => void;
48
50
  exit: (code: number) => void;
51
+ /**
52
+ * The platform credential for this repo: the stored key for a linked repo, else the exported
53
+ * one. A port so the push is testable, and so this reads what every other caller reads.
54
+ */
55
+ cloud: () => Promise<CloudConfig | null>;
49
56
  }
50
57
  interface VerifyArgs {
51
58
  url: string;
@@ -104,10 +111,83 @@ export declare function urlParts(url: string): {
104
111
  * first bullet — not this change.
105
112
  */
106
113
  export declare function portBusyMessage(port: number): string;
114
+ /**
115
+ * What `--expect` is answered with when the port is not serving a daemon.
116
+ *
117
+ * It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
118
+ * so on a free or foreign port the flag was dropped without a word and the command carried on into
119
+ * the saved-flows path — where it reported, accurately and about something else entirely, that there
120
+ * were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
121
+ *
122
+ * That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
123
+ * for the people with no other route to a verdict, and it failed in the one way they could not
124
+ * diagnose: the refusal they got was true.
125
+ */
126
+ export declare function expectNeedsDaemonMessage(port: number, presence: PortPresence): string;
127
+ /**
128
+ * The model key is set here but not in the daemon, which is where the drive runs. The daemon is
129
+ * usually the one `init` or the agent's MCP client started, from an environment without the key.
130
+ */
131
+ export declare function daemonLacksKeyMessage(port: number): string;
132
+ /** What `verify` does next, once the options and the state of the port are both known. */
133
+ export declare const VerifyRoute: {
134
+ /** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
135
+ readonly ADHOC: "adhoc";
136
+ /** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
137
+ readonly ADHOC_SUITE: "adhoc-suite";
138
+ /** Ask the daemon that already owns the port to explore the url and record flows. */
139
+ readonly ADHOC_EXPLORE: "adhoc-explore";
140
+ /** Boot our own daemon, drive the url, replay the flows saved on disk. */
141
+ readonly FLOWS: "flows";
142
+ /** Answer the caller, and run nothing. */
143
+ readonly REFUSE: "refuse";
144
+ };
145
+ export type VerifyRoute = (typeof VerifyRoute)[keyof typeof VerifyRoute];
146
+ export type VerifyPlan = {
147
+ route: typeof VerifyRoute.ADHOC;
148
+ } | {
149
+ route: typeof VerifyRoute.ADHOC_SUITE;
150
+ } | {
151
+ route: typeof VerifyRoute.ADHOC_EXPLORE;
152
+ } | {
153
+ route: typeof VerifyRoute.FLOWS;
154
+ } | {
155
+ route: typeof VerifyRoute.REFUSE;
156
+ message: string;
157
+ };
158
+ /**
159
+ * All four routes this command has, in one pure function.
160
+ *
161
+ * They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
162
+ * answered as a daemon, which is how one of the four went missing: a predicate on any other port
163
+ * state fell through to a path that cannot see it. Stating the cases together is what makes an
164
+ * unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
165
+ * browser — which is where an argument-level decision belongs.
166
+ */
167
+ export declare function routeVerify(args: {
168
+ /** True when the caller supplied a predicate, by `--expect` or `--expect-file`. */
169
+ hasPredicate: boolean;
170
+ /**
171
+ * True when the caller asked for something only a browser of OUR OWN can do: `--headed` or
172
+ * `--storage-state`. The running daemon's tab can honour neither, so replaying there would
173
+ * silently ignore the request.
174
+ */
175
+ wantsOwnBrowser?: boolean;
176
+ /**
177
+ * `--explore`: a model drives the url and records flows. The daemon on the port runs the same
178
+ * drive (`reticle_verify { action: "explore" }`), so this is NOT a reason to need our own browser —
179
+ * it used to be, and `init` leaves exactly that daemon behind before recommending this command.
180
+ */
181
+ explore?: boolean;
182
+ presence: PortPresence;
183
+ port: number;
184
+ }): VerifyPlan;
107
185
  export declare function handleVerify(parsed: {
108
186
  url: string;
109
187
  /** A parsed predicate for a flow-free, one-shot verdict against the running daemon. */
110
188
  expect?: unknown;
189
+ /** The same predicate in a file, which is the form no shell can mangle. Resolved below. */
190
+ expectFile?: string;
111
191
  headless: boolean;
112
192
  timeoutMs?: number;
113
193
  storageState?: string;
@@ -10,17 +10,22 @@
10
10
  * — is unit-tested without launching a real browser.
11
11
  */
12
12
  import { join, basename } from 'node:path';
13
+ import { readFileSync } from 'node:fs';
13
14
  import { runAdhocVerdict } from './adhoc-verdict.js';
15
+ import { runAdhocExplore, runAdhocSuite } from './adhoc-suite.js';
14
16
  import { exploreApp, harnessAvailable, MSG_NO_HARNESS_KEY, } from '../../surface/tools/harness-explore.js';
15
17
  import { resolveSuiteSelection } from '../../language/flows/suite-selection.js';
16
18
  import { readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
17
19
  import { randomUUID } from 'node:crypto';
18
20
  import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, VerifyPhase, } from '@reticlehq/core';
19
21
  import { start } from '../../index.js';
20
- import { probePresence, PortPresence } from '../daemon/binding/port-presence.js';
22
+ import { probePresence, describePresence, PortPresence, } from '../daemon/binding/port-presence.js';
23
+ import { EXPECT_FLAG } from './cli-parse-grammar.js';
21
24
  import { probeDaemon } from '../../surface/mcp/mcp-proxy.js';
22
- import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
23
- import { cloudFetch, createProgressReporter, resolveCloudConfig, syncProgressToCloud, syncRunToCloud, SyncOutcome, } from '../../memory/cloud/cloud-sync.js';
25
+ import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
26
+ import { cloudFetch, createProgressReporter, syncProgressToCloud, syncRunToCloud, SyncOutcome, } from '../../memory/cloud/cloud-sync.js';
27
+ import { linkedCloudPort } from '../../memory/cloud/cloud-config.js';
28
+ import { homedir } from 'node:os';
24
29
  import { ReticleRunner } from '../../judgement/runs/reticle-runner.js';
25
30
  import { createRunnerPort } from '../../judgement/runs/runner-port.js';
26
31
  import { RunStore } from '../../judgement/runs/artifact/run-store.js';
@@ -125,7 +130,7 @@ export async function runVerify(args, ports) {
125
130
  * live progress by it until the finished run replaces the whole picture.
126
131
  */
127
132
  const streamId = randomUUID();
128
- const cloud = resolveCloudConfig(process.env);
133
+ const cloud = await ports.cloud().catch(() => null);
129
134
  const progress = createProgressReporter(streamId, cloud, cloudFetch);
130
135
  let run;
131
136
  try {
@@ -137,7 +142,7 @@ export async function runVerify(args, ports) {
137
142
  progress.stop();
138
143
  await progress.flush().catch(() => undefined);
139
144
  }
140
- await pushRunToCloud(run, ports); // best-effort; opt-in; never changes the verdict or exit code
145
+ await pushRunToCloud(run, cloud, ports); // best-effort; opt-in; never changes the verdict or exit code
141
146
  /*
142
147
  * The last event anybody watching is waiting for, sent after the artifact has actually landed —
143
148
  * so "pushed" on a dashboard means the run is really there, not that we were about to try.
@@ -181,13 +186,12 @@ async function explore(conn, args, ports) {
181
186
  return true;
182
187
  }
183
188
  /**
184
- * Best-effort push of a finished run to the cloud dashboard. Opt-in: only fires when the user has set
185
- * RETICLE_CLOUD_URL + RETICLE_API_KEY (the "shifted to server" step). Absent → no-op, nothing leaves the
186
- * machine (the no-phone-home default). A push failure NEVER changes the verdict or exit code — the run is
189
+ * Best-effort push of a finished run to the cloud dashboard. Opt-in: only fires when this repo is
190
+ * linked or RETICLE_API_KEY is set. Absent → no-op, nothing leaves the machine (the no-phone-home
191
+ * default). A push failure NEVER changes the verdict or exit code — the run is
187
192
  * already reported locally; the cloud copy is an enhancement.
188
193
  */
189
- async function pushRunToCloud(run, ports) {
190
- const config = resolveCloudConfig(process.env);
194
+ async function pushRunToCloud(run, config, ports) {
191
195
  if (null === config)
192
196
  return;
193
197
  const result = await syncRunToCloud(run, config, cloudFetch);
@@ -365,8 +369,106 @@ export function portBusyMessage(port) {
365
369
  "your agent's MCP link and the proxy usually respawns a daemon into the gap before verify can " +
366
370
  'bind — prefer one of the above.');
367
371
  }
372
+ /**
373
+ * What `--expect` is answered with when the port is not serving a daemon.
374
+ *
375
+ * It used to be answered with nothing at all. The predicate was read only inside the daemon branch,
376
+ * so on a free or foreign port the flag was dropped without a word and the command carried on into
377
+ * the saved-flows path — where it reported, accurately and about something else entirely, that there
378
+ * were no saved flows to verify. Byte-identical output to the same command with no `--expect` on it.
379
+ *
380
+ * That is the documented recovery for a client that never loaded the `reticle_*` tools, so it failed
381
+ * for the people with no other route to a verdict, and it failed in the one way they could not
382
+ * diagnose: the refusal they got was true.
383
+ */
384
+ export function expectNeedsDaemonMessage(port, presence) {
385
+ return (`${EXPECT_FLAG} asks the Reticle daemon that already owns port ${String(port)} for the ` +
386
+ 'verdict — it binds nothing and launches nothing, which is what lets it answer while the port ' +
387
+ `is busy. Right now ${describePresence(presence, port)}.\n\n` +
388
+ ' The predicate did not run, and nothing was proved.\n\n' +
389
+ ' • Start a daemon on that port, pointed at the app, then re-run this command: ' +
390
+ `npx @reticlehq/server serve --port ${String(port)} --drive <url>\n` +
391
+ ` • Or drop ${EXPECT_FLAG}, and verify drives the url itself and replays your saved flows — ` +
392
+ 'a different question, and it proves nothing about your predicate.');
393
+ }
394
+ /**
395
+ * The model key is set here but not in the daemon, which is where the drive runs. The daemon is
396
+ * usually the one `init` or the agent's MCP client started, from an environment without the key.
397
+ */
398
+ export function daemonLacksKeyMessage(port) {
399
+ return (`${ReticleEnv.HARNESS_KEY} is set in this shell, but the daemon on port ${String(port)} was ` +
400
+ 'started without it, and the drive runs inside the daemon. Restart it from this shell so it ' +
401
+ `inherits the key: npx @reticlehq/server restart --port ${String(port)}`);
402
+ }
403
+ /** What `verify` does next, once the options and the state of the port are both known. */
404
+ export const VerifyRoute = {
405
+ /** Ask the daemon that already owns the port for a one-shot verdict on the predicate. */
406
+ ADHOC: 'adhoc',
407
+ /** Ask the daemon that already owns the port to replay the saved flows. See runAdhocSuite. */
408
+ ADHOC_SUITE: 'adhoc-suite',
409
+ /** Ask the daemon that already owns the port to explore the url and record flows. */
410
+ ADHOC_EXPLORE: 'adhoc-explore',
411
+ /** Boot our own daemon, drive the url, replay the flows saved on disk. */
412
+ FLOWS: 'flows',
413
+ /** Answer the caller, and run nothing. */
414
+ REFUSE: 'refuse',
415
+ };
416
+ /**
417
+ * All four routes this command has, in one pure function.
418
+ *
419
+ * They used to be a pair of nested `if`s that consulted `--expect` only after the port had already
420
+ * answered as a daemon, which is how one of the four went missing: a predicate on any other port
421
+ * state fell through to a path that cannot see it. Stating the cases together is what makes an
422
+ * unhandled one visible, and it lets every one of them be driven with no port, no daemon and no
423
+ * browser — which is where an argument-level decision belongs.
424
+ */
425
+ export function routeVerify(args) {
426
+ if (args.hasPredicate) {
427
+ return args.presence === PortPresence.DAEMON
428
+ ? { route: VerifyRoute.ADHOC }
429
+ : { route: VerifyRoute.REFUSE, message: expectNeedsDaemonMessage(args.port, args.presence) };
430
+ }
431
+ if (args.presence !== PortPresence.DAEMON)
432
+ return { route: VerifyRoute.FLOWS };
433
+ if (true === args.explore && true !== args.wantsOwnBrowser) {
434
+ return { route: VerifyRoute.ADHOC_EXPLORE };
435
+ }
436
+ // No predicate, but there may be SAVED FLOWS, and a daemon already owns the port: replay them
437
+ // there rather than refusing, unless the request needs a browser that path never opens.
438
+ return true === args.wantsOwnBrowser
439
+ ? { route: VerifyRoute.REFUSE, message: portBusyMessage(args.port) }
440
+ : { route: VerifyRoute.ADHOC_SUITE };
441
+ }
368
442
  export function handleVerify(parsed) {
369
443
  const now = () => Date.now();
444
+ /*
445
+ * `--expect-file` is `--expect` with the shell taken out of the loop.
446
+ *
447
+ * Resolved here rather than in the parser because the parser is pure and reads no disk, and
448
+ * because the two failures a reader can hit — the file is not there, and what is in it is not
449
+ * JSON — are worth saying apart. Either one ends the run before anything binds: a predicate that
450
+ * could not be read produces NO verdict, and exiting 1 with the reason is the only honest answer.
451
+ */
452
+ let expectation = parsed.expect;
453
+ if (parsed.expectFile !== undefined) {
454
+ let text;
455
+ try {
456
+ text = readFileSync(parsed.expectFile, 'utf8');
457
+ }
458
+ catch {
459
+ process.stderr.write(`--expect-file: cannot read ${parsed.expectFile}\n`);
460
+ process.exit(1);
461
+ return;
462
+ }
463
+ try {
464
+ expectation = JSON.parse(text);
465
+ }
466
+ catch {
467
+ process.stderr.write(`--expect-file: ${parsed.expectFile} is not valid JSON, so no predicate was read\n`);
468
+ process.exit(1);
469
+ return;
470
+ }
471
+ }
370
472
  const reticleRoot = join(process.cwd(), ReticleDir.ROOT);
371
473
  const projectName = basename(process.cwd()) || DEFAULT_PROJECT_NAME;
372
474
  const ports = {
@@ -383,26 +485,43 @@ export function handleVerify(parsed) {
383
485
  out: (line) => process.stdout.write(`${line}\n`),
384
486
  fail: (line) => process.stderr.write(`${line}\n`),
385
487
  exit: (code) => process.exit(code),
488
+ cloud: linkedCloudPort(createNodeFileSystem(), reticleRoot, homedir(), process.env),
386
489
  };
387
490
  // Asked BEFORE anything binds. The listen failure arrives asynchronously on the server object,
388
491
  // long after `start` has resolved, so no `.catch` on that promise can ever see it — which is why
389
492
  // this used to reach the user as a raw node stack rather than as an answer.
390
493
  void (async () => {
391
494
  const port = parsed.port ?? RETICLE_DEFAULT_PORT;
392
- if ((await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus })) ===
393
- PortPresence.DAEMON) {
495
+ const presence = await probePresence(port, { tcpOpen: probeDaemon, status: fetchStatus });
496
+ const plan = routeVerify({
497
+ hasPredicate: expectation !== undefined,
498
+ wantsOwnBrowser: !parsed.headless || parsed.storageState !== undefined,
499
+ explore: true === parsed.explore || parsed.persona !== undefined,
500
+ presence,
501
+ port,
502
+ });
503
+ switch (plan.route) {
504
+ // Either the busy port, or a predicate this state cannot answer. Both are refusals that name
505
+ // what was asked for — never a report about a check nobody requested.
506
+ case VerifyRoute.REFUSE:
507
+ ports.fail(plan.message);
508
+ ports.exit(EXIT_FAIL);
509
+ return;
394
510
  // A daemon owning the port is the NORMAL state after a working install, and it used to be the
395
511
  // end of the road: `verify` refused, the other verdict paths need saved flows a first-install
396
512
  // project does not have, and stopping the daemon cuts the agent's own MCP link. An agent could
397
513
  // hold a live, correctly-wired app and have no way to reach a verdict at all.
398
514
  //
399
- // With `--expect` there is now a way, and it does not need the port: ask the daemon that
400
- // already owns it. See runAdhocVerdict.
401
- if (parsed.expect !== undefined) {
515
+ // With `--expect` there is a way, and it does not need the port: ask the daemon that already
516
+ // owns it. See runAdhocVerdict.
517
+ case VerifyRoute.ADHOC: {
402
518
  const verdict = await runAdhocVerdict({
403
519
  port,
404
520
  ...(parsed.url !== undefined && '' !== parsed.url ? { url: parsed.url } : {}),
405
- predicate: parsed.expect,
521
+ predicate: expectation,
522
+ // Read fresh, not from `presence`: that probe asks whether a daemon is on the port, and
523
+ // this asks whether a TAB is — the difference between asserting and opening the url first.
524
+ sessions: async () => summarizeStatus(await fetchStatus(port)).sessions,
406
525
  ...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
407
526
  ...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
408
527
  });
@@ -411,16 +530,52 @@ export function handleVerify(parsed) {
411
530
  ports.exit(verdict.code);
412
531
  return;
413
532
  }
414
- ports.fail(portBusyMessage(port));
415
- ports.exit(1);
416
- return;
533
+ // No predicate, a daemon on the port, and saved flows to run: the other half of the same
534
+ // dead end. The whole promise of a saved suite is that it runs again without an agent.
535
+ case VerifyRoute.ADHOC_SUITE: {
536
+ const suite = await runAdhocSuite({
537
+ port,
538
+ ...(parsed.select === undefined ? {} : { select: parsed.select }),
539
+ ...(parsed.sessionId === undefined ? {} : { sessionId: parsed.sessionId }),
540
+ ...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
541
+ });
542
+ for (const line of suite.lines)
543
+ ports.out(line);
544
+ ports.exit(suite.code);
545
+ return;
546
+ }
547
+ // `--explore` with a daemon on the port — the state `init` leaves behind right before it names
548
+ // this command as the first run. The daemon drives and records; this process only asks.
549
+ case VerifyRoute.ADHOC_EXPLORE: {
550
+ const explored = await runAdhocExplore({
551
+ port,
552
+ url: parsed.url,
553
+ ...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
554
+ sessions: async () => summarizeStatus(await fetchStatus(port)).sessions,
555
+ ...((t) => (t === undefined || 0 === t.length ? {} : { token: t }))(readOrCreatePairingTokenSync(defaultPairingTokenDir())),
556
+ });
557
+ for (const line of explored.lines)
558
+ ports.out(line);
559
+ // The drive runs INSIDE the daemon, so the key has to be in ITS environment. A reader who
560
+ // exported it in this shell and got "set ANTHROPIC_API_KEY" back has been told to do the
561
+ // thing they just did; say where the key is actually missing.
562
+ if (0 !== explored.code && harnessAvailable(process.env)) {
563
+ const refusedForKey = explored.lines.some((line) => line.includes(MSG_NO_HARNESS_KEY));
564
+ if (refusedForKey)
565
+ ports.fail(daemonLacksKeyMessage(port));
566
+ }
567
+ ports.exit(explored.code);
568
+ return;
569
+ }
570
+ case VerifyRoute.FLOWS:
571
+ await runVerify({
572
+ url: parsed.url,
573
+ timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
574
+ ...(true === parsed.explore ? { explore: true } : {}),
575
+ ...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
576
+ ...(parsed.select === undefined ? {} : { select: parsed.select }),
577
+ }, ports);
578
+ return;
417
579
  }
418
- await runVerify({
419
- url: parsed.url,
420
- timeoutMs: parsed.timeoutMs ?? DEFAULT_SESSION_TIMEOUT_MS,
421
- ...(true === parsed.explore ? { explore: true } : {}),
422
- ...(parsed.persona === undefined ? {} : { persona: parsed.persona }),
423
- ...(parsed.select === undefined ? {} : { select: parsed.select }),
424
- }, ports);
425
580
  })();
426
581
  }
@@ -489,7 +489,7 @@ const cmdSync = async (argv) => {
489
489
  const reticleRoot = join(process.cwd(), RETICLE_DIR);
490
490
  const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
491
491
  if (null === cloud.config) {
492
- err('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
492
+ err(cloud.reason ?? 'cloud not attached here: run `reticle link`, or set RETICLE_API_KEY');
493
493
  return 1;
494
494
  }
495
495
  const config = cloud.config;
@@ -521,7 +521,10 @@ const cmdSync = async (argv) => {
521
521
  flows: report.flowsSent,
522
522
  records: report.derivedSent,
523
523
  ...(report.runsRejected.length > 0 ? { rejected: report.runsRejected } : {}),
524
+ ...(report.refused.length > 0 ? { refused: report.refused } : {}),
524
525
  },
526
+ ...(report.held.length > 0 ? { notSent: report.held } : {}),
527
+ ...(report.notRetried.length > 0 ? { notRetried: report.notRetried } : {}),
525
528
  pulled: report.pulled,
526
529
  ...(report.morePending ? { morePending: true } : {}),
527
530
  ...(report.error === undefined ? {} : { error: report.error }),
@@ -546,7 +549,7 @@ const repoCloud = async () => {
546
549
  const fs = createNodeFileSystem();
547
550
  const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
548
551
  if (null === cloud.config)
549
- throw new Error('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
552
+ throw new Error(cloud.reason ?? 'cloud not attached here: run `reticle link`, or set RETICLE_API_KEY');
550
553
  return cloud.config;
551
554
  };
552
555
  /** `reticle runs` — the linked project's recent run artifacts (the key scopes it server-side). */
@@ -1,6 +1,4 @@
1
1
  import { type Session } from './auth/cloud-session.js';
2
- /** Where `reticle login` dials when nothing says otherwise: the hosted service. */
3
- export declare const DEFAULT_URL = "https://app.reticle.sh";
4
2
  export declare const RETICLE_DIR = ".reticle";
5
3
  export declare const SESSION_FILE = "session.json";
6
4
  export declare const CREDENTIALS_FILE = "credentials.json";
@@ -8,15 +8,13 @@
8
8
  * `cloud-login` is the first to move — and it is a real seam rather than a file-size dodge: nothing
9
9
  * here knows what a project, a credential or a link is.
10
10
  */
11
- import { apiKeyFrom } from '@reticlehq/core';
11
+ import { DEFAULT_PLATFORM_URL, apiKeyFrom, cloudUrlFrom } from '@reticlehq/core';
12
12
  import { readFile } from 'node:fs/promises';
13
13
  import { homedir } from 'node:os';
14
14
  import { join } from 'node:path';
15
15
  import { z } from 'zod';
16
16
  import { cloudFetch } from '../../memory/cloud/cloud-sync.js';
17
17
  import { normalizeUrl, readSessionFrom, resolveSessionFor, sessionPath, } from './auth/cloud-session.js';
18
- /** Where `reticle login` dials when nothing says otherwise: the hosted service. */
19
- export const DEFAULT_URL = 'https://app.reticle.sh';
20
18
  export const RETICLE_DIR = '.reticle';
21
19
  export const SESSION_FILE = 'session.json';
22
20
  export const CREDENTIALS_FILE = 'credentials.json';
@@ -72,7 +70,7 @@ export const baseUrl = (session, explicit) => {
72
70
  // and it cannot leak into a sibling process the way an exported variable does.
73
71
  if (explicit !== undefined && explicit.length > 0)
74
72
  return normalizeUrl(explicit);
75
- const env = process.env['RETICLE_CLOUD_URL'];
73
+ const env = cloudUrlFrom(process.env);
76
74
  if (env !== undefined && env.length > 0)
77
75
  return env.replace(/\/+$/, '');
78
76
  if (null !== session && session.url.length > 0)
@@ -81,7 +79,7 @@ export const baseUrl = (session, explicit) => {
81
79
  // saying because that default was wrong for everyone except us. The default is now the hosted
82
80
  // service, so the fallback IS the intended path — and a warning printed on the correct path is
83
81
  // how people learn to ignore stderr, which is where the real problems are written.
84
- return DEFAULT_URL;
82
+ return DEFAULT_PLATFORM_URL;
85
83
  };
86
84
  /** Bearer for a command: an explicit api key (agent) wins, else the human login token. */
87
85
  export const bearer = (session) => apiKeyFrom(process.env) ?? session?.token ?? null;