@reticlehq/server 3.4.0 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +16 -8
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-verdict.d.ts +1 -0
  6. package/dist/command/cli/adhoc-verdict.js +6 -3
  7. package/dist/command/cli/answers/open-note.d.ts +2 -0
  8. package/dist/command/cli/answers/open-note.js +22 -0
  9. package/dist/command/cli/auth/cloud-session.d.ts +4 -4
  10. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  11. package/dist/command/cli/cli-flow-commands.js +4 -8
  12. package/dist/command/cli/cli-parse.d.ts +1 -1
  13. package/dist/command/cli/cli-parse.js +13 -11
  14. package/dist/command/cli/cli-verify.d.ts +1 -1
  15. package/dist/command/cli/cli-verify.js +35 -25
  16. package/dist/command/cli/cloud-cli.js +110 -12
  17. package/dist/command/cli/cloud-login.d.ts +9 -1
  18. package/dist/command/cli/cloud-login.js +43 -8
  19. package/dist/command/cli/demo/demo-page.js +11 -3
  20. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  21. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  22. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  23. package/dist/command/cli/drive/drive-attach.js +71 -4
  24. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  25. package/dist/command/cli/drive/drive-command.js +13 -7
  26. package/dist/command/cli/lifecycle/daemon-lifecycle.js +4 -1
  27. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  28. package/dist/command/cli/ports/port-holder.js +24 -0
  29. package/dist/command/cli/setup-mcp-cli.js +15 -5
  30. package/dist/command/cli/tutorial.d.ts +2 -2
  31. package/dist/command/cli/tutorial.js +6 -9
  32. package/dist/command/cli.d.ts +6 -0
  33. package/dist/command/cli.js +45 -23
  34. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  35. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  36. package/dist/command/daemon/daemon-resolve.d.ts +14 -0
  37. package/dist/command/daemon/daemon-resolve.js +33 -2
  38. package/dist/command/setup/agent-writer.d.ts +1 -1
  39. package/dist/command/setup/agent-writer.js +3 -2
  40. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  41. package/dist/command/setup/bringup/connect-lease.js +1 -0
  42. package/dist/command/setup/bringup/dev-server-wait.d.ts +10 -0
  43. package/dist/command/setup/bringup/dev-server-wait.js +20 -1
  44. package/dist/command/setup/init/init-port.d.ts +4 -0
  45. package/dist/command/setup/init/init-port.js +10 -0
  46. package/dist/command/setup/init/init-runtime.d.ts +10 -0
  47. package/dist/command/setup/init/init-runtime.js +71 -9
  48. package/dist/command/setup/node-effects.js +8 -2
  49. package/dist/command/setup/remaining-steps.js +2 -1
  50. package/dist/command/setup/run-setup.d.ts +7 -0
  51. package/dist/command/setup/run-setup.js +38 -25
  52. package/dist/command/setup/setup-command.d.ts +2 -1
  53. package/dist/command/setup/setup-command.js +2 -0
  54. package/dist/command/setup/setup-mcp.d.ts +2 -0
  55. package/dist/command/setup/setup-mcp.js +14 -5
  56. package/dist/command/version/peer-announce.d.ts +6 -2
  57. package/dist/command/version/peer-announce.js +8 -6
  58. package/dist/command/version/version-nudge.d.ts +17 -3
  59. package/dist/command/version/version-nudge.js +25 -4
  60. package/dist/command/version/version-skew.js +11 -2
  61. package/dist/features/crawl/crawl-tools.js +4 -0
  62. package/dist/features/crawl/crawl.d.ts +6 -0
  63. package/dist/features/crawl/crawl.js +9 -1
  64. package/dist/features/exhaust/ledger.d.ts +7 -0
  65. package/dist/features/exhaust/ledger.js +35 -9
  66. package/dist/features/harness/app-findings.d.ts +2 -0
  67. package/dist/features/harness/app-findings.js +52 -0
  68. package/dist/features/harness/drive-report.d.ts +27 -1
  69. package/dist/features/harness/drive-report.js +162 -11
  70. package/dist/features/harness/drivers.d.ts +10 -14
  71. package/dist/features/harness/drivers.js +10 -14
  72. package/dist/features/harness/fill-values.d.ts +3 -69
  73. package/dist/features/harness/fill-values.js +3 -133
  74. package/dist/features/harness/goals.d.ts +21 -0
  75. package/dist/features/harness/goals.js +77 -0
  76. package/dist/features/harness/harness.d.ts +21 -0
  77. package/dist/features/harness/harness.js +69 -2
  78. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  79. package/dist/features/harness/platform/drive-target.js +81 -0
  80. package/dist/features/harness/platform/local-apps.d.ts +94 -0
  81. package/dist/features/harness/platform/local-apps.js +204 -0
  82. package/dist/features/harness/platform/remote-drive.d.ts +180 -0
  83. package/dist/features/harness/platform/remote-drive.js +365 -0
  84. package/dist/features/harness/platform/script.d.ts +48 -0
  85. package/dist/features/harness/platform/script.js +128 -0
  86. package/dist/features/harness/platform/server-driver.d.ts +41 -0
  87. package/dist/features/harness/platform/server-driver.js +179 -0
  88. package/dist/features/harness/platform-config.d.ts +7 -0
  89. package/dist/features/harness/platform-config.js +14 -2
  90. package/dist/features/harness/script/app-controls.d.ts +5 -0
  91. package/dist/features/harness/script/app-controls.js +40 -0
  92. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  93. package/dist/features/harness/script/lane-ids.js +16 -0
  94. package/dist/features/harness/script/script-run.d.ts +41 -0
  95. package/dist/features/harness/script/script-run.js +299 -0
  96. package/dist/features/license/license.d.ts +2 -2
  97. package/dist/features/visual/visual-tools.d.ts +10 -1
  98. package/dist/features/visual/visual-tools.js +40 -3
  99. package/dist/hooks/driven-by.d.ts +4 -0
  100. package/dist/hooks/driven-by.js +16 -0
  101. package/dist/hooks/hook-commands.js +15 -4
  102. package/dist/hooks/tool-log.d.ts +9 -0
  103. package/dist/hooks/tool-log.js +29 -0
  104. package/dist/index.d.ts +1 -0
  105. package/dist/index.js +91 -89
  106. package/dist/judgement/capsule/capsule-store.d.ts +4 -4
  107. package/dist/judgement/runs/artifact/build-verification-run.js +6 -0
  108. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  109. package/dist/judgement/runs/artifact/replay-mapping.js +16 -6
  110. package/dist/judgement/runs/artifact/run-store.d.ts +1 -1
  111. package/dist/judgement/runs/artifact/run-store.js +26 -1
  112. package/dist/judgement/runs/drive-run.d.ts +22 -0
  113. package/dist/judgement/runs/drive-run.js +99 -1
  114. package/dist/judgement/runs/repair-prompt.js +1 -1
  115. package/dist/judgement/runs/reticle-runner.js +8 -1
  116. package/dist/judgement/runs/verification-sync.d.ts +3 -2
  117. package/dist/judgement/runs/verification-sync.js +4 -3
  118. package/dist/language/flows/change/gate.d.ts +10 -0
  119. package/dist/language/flows/change/gate.js +22 -0
  120. package/dist/language/flows/decision.js +34 -0
  121. package/dist/language/flows/flow-anchor.d.ts +54 -8
  122. package/dist/language/flows/flow-anchor.js +88 -3
  123. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  124. package/dist/language/flows/flow-cross-step.js +21 -0
  125. package/dist/language/flows/flow-learning.js +8 -11
  126. package/dist/language/flows/flow-mutate-tools.js +5 -3
  127. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  128. package/dist/language/flows/flow-replay-run.js +101 -94
  129. package/dist/language/flows/flow-replay.d.ts +23 -1
  130. package/dist/language/flows/flow-replay.js +92 -19
  131. package/dist/language/flows/flow-scope.js +5 -1
  132. package/dist/language/flows/flow-step-runners.d.ts +20 -2
  133. package/dist/language/flows/flow-step-runners.js +236 -42
  134. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  135. package/dist/language/flows/flow-store-for-session.js +2 -2
  136. package/dist/language/flows/flow-tools.js +58 -34
  137. package/dist/language/flows/flows.js +5 -2
  138. package/dist/language/flows/heal-run.js +6 -4
  139. package/dist/language/flows/recording/flake.d.ts +2 -2
  140. package/dist/language/flows/recording/tape/recordings.d.ts +30 -4
  141. package/dist/language/flows/recording/tape/recordings.js +40 -8
  142. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  143. package/dist/language/flows/replay-from-hud.js +51 -0
  144. package/dist/language/flows/replay.d.ts +9 -5
  145. package/dist/language/flows/replay.js +27 -9
  146. package/dist/language/flows/suite/suite-flakes.js +2 -0
  147. package/dist/language/flows/suite-selection.d.ts +2 -15
  148. package/dist/language/flows/suite-selection.js +4 -3
  149. package/dist/language/flows/verify-change-tools.js +8 -4
  150. package/dist/launch-chromium.d.ts +27 -5
  151. package/dist/launch-chromium.js +38 -8
  152. package/dist/memory/cloud/cloud-config.d.ts +12 -0
  153. package/dist/memory/cloud/cloud-config.js +15 -0
  154. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  155. package/dist/memory/cloud/cloud-sync.js +12 -1
  156. package/dist/memory/cloud/harness-config.d.ts +26 -1
  157. package/dist/memory/cloud/harness-config.js +99 -4
  158. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  159. package/dist/memory/cloud/hud-notices-source.js +128 -0
  160. package/dist/memory/cloud/memory-scope.d.ts +144 -0
  161. package/dist/memory/cloud/memory-scope.js +205 -0
  162. package/dist/memory/cloud/sync-cycle.d.ts +21 -3
  163. package/dist/memory/cloud/sync-cycle.js +45 -5
  164. package/dist/memory/cloud/sync-daemon.js +7 -5
  165. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  166. package/dist/memory/cloud/sync-disk.js +5 -8
  167. package/dist/memory/impact/account-watch.d.ts +19 -0
  168. package/dist/memory/impact/account-watch.js +34 -0
  169. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  170. package/dist/memory/impact/impact-recorder.js +12 -2
  171. package/dist/memory/impact/impact-store.d.ts +16 -0
  172. package/dist/memory/impact/impact-store.js +142 -26
  173. package/dist/memory/intent/intent-shard.d.ts +2 -2
  174. package/dist/memory/intent/intent-tools.js +88 -5
  175. package/dist/memory/journal/envelope.d.ts +16 -16
  176. package/dist/memory/journal/journal-query.d.ts +14 -0
  177. package/dist/memory/journal/journal-query.js +15 -0
  178. package/dist/memory/journal/journal-recorder.js +7 -1
  179. package/dist/memory/journal/on-disk/retention.d.ts +3 -0
  180. package/dist/memory/journal/on-disk/retention.js +5 -0
  181. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  182. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -4
  183. package/dist/memory/journal/session-end.js +6 -4
  184. package/dist/memory/project/artifact-root-resolver.d.ts +36 -1
  185. package/dist/memory/project/artifact-root-resolver.js +134 -26
  186. package/dist/memory/project/artifact-root.d.ts +42 -5
  187. package/dist/memory/project/artifact-root.js +87 -8
  188. package/dist/memory/project/dir/reticle-dir.d.ts +4 -2
  189. package/dist/memory/project/dir/reticle-dir.js +3 -1
  190. package/dist/memory/project/project-tools.js +22 -3
  191. package/dist/memory/project/session-root.d.ts +24 -1
  192. package/dist/memory/project/session-root.js +41 -5
  193. package/dist/memory/project/sync-status.d.ts +39 -0
  194. package/dist/memory/project/sync-status.js +90 -0
  195. package/dist/memory/recall/memory-tools.js +2 -0
  196. package/dist/memory/recall/project-memory.d.ts +19 -0
  197. package/dist/memory/recall/project-memory.js +45 -6
  198. package/dist/portal/bridge/bridge.d.ts +7 -4
  199. package/dist/portal/bridge/bridge.js +25 -3
  200. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  201. package/dist/portal/bridge/pairing-token.js +17 -0
  202. package/dist/portal/input/real-input.d.ts +81 -10
  203. package/dist/portal/input/real-input.js +207 -22
  204. package/dist/portal/input/viewport-tools.js +19 -9
  205. package/dist/portal/pool/browser-pool.d.ts +22 -9
  206. package/dist/portal/pool/browser-pool.js +70 -35
  207. package/dist/portal/pool/playwright-launcher.d.ts +13 -0
  208. package/dist/portal/pool/playwright-launcher.js +17 -1
  209. package/dist/portal/pool/pool-contract.d.ts +7 -4
  210. package/dist/portal/session/command-payload.d.ts +8 -0
  211. package/dist/portal/session/command-payload.js +19 -0
  212. package/dist/portal/session/dev-server/dev-command.d.ts +12 -0
  213. package/dist/portal/session/dev-server/dev-command.js +61 -12
  214. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  215. package/dist/portal/session/facts/departure-note.js +47 -0
  216. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  217. package/dist/portal/session/facts/session-replaced.js +5 -0
  218. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  219. package/dist/portal/session/first-run-wiring.js +132 -0
  220. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  221. package/dist/portal/session/human/notes-ledger.js +55 -0
  222. package/dist/portal/session/human/review-store.d.ts +12 -0
  223. package/dist/portal/session/human/review-store.js +18 -10
  224. package/dist/portal/session/live-control-tools.d.ts +0 -13
  225. package/dist/portal/session/live-control-tools.js +28 -3
  226. package/dist/portal/session/no-session-diagnosis.d.ts +7 -0
  227. package/dist/portal/session/no-session-diagnosis.js +15 -0
  228. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  229. package/dist/portal/session/no-session-next-action.js +31 -5
  230. package/dist/portal/session/no-session-watch.d.ts +14 -1
  231. package/dist/portal/session/no-session-watch.js +63 -10
  232. package/dist/portal/session/presence/session-mirror.js +3 -0
  233. package/dist/portal/session/serving-directory.d.ts +27 -0
  234. package/dist/portal/session/serving-directory.js +45 -0
  235. package/dist/portal/session/session-health.d.ts +7 -0
  236. package/dist/portal/session/session-health.js +6 -0
  237. package/dist/portal/session/session-info.d.ts +8 -1
  238. package/dist/portal/session/session-info.js +6 -0
  239. package/dist/portal/session/session-manager.d.ts +21 -0
  240. package/dist/portal/session/session-manager.js +44 -8
  241. package/dist/portal/session/session-reaper.js +4 -1
  242. package/dist/portal/session/session-successor.d.ts +5 -0
  243. package/dist/portal/session/session-tools.js +26 -3
  244. package/dist/portal/session/session.d.ts +12 -10
  245. package/dist/portal/session/session.js +30 -31
  246. package/dist/start-options.d.ts +6 -0
  247. package/dist/status-payload.d.ts +10 -0
  248. package/dist/status-payload.js +23 -0
  249. package/dist/surface/http-server.d.ts +1 -1
  250. package/dist/surface/http-server.js +15 -4
  251. package/dist/surface/mcp/mcp-proxy.d.ts +5 -0
  252. package/dist/surface/mcp/mcp-proxy.js +36 -5
  253. package/dist/surface/mcp/mcp.d.ts +11 -6
  254. package/dist/surface/mcp/mcp.js +28 -14
  255. package/dist/surface/mcp/proxy/proxy-log.js +3 -5
  256. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  257. package/dist/surface/mcp/server-instructions.js +14 -4
  258. package/dist/surface/mcp/shared-params.js +2 -2
  259. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  260. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  261. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  262. package/dist/surface/tools/act-sequence-tool.js +2 -2
  263. package/dist/surface/tools/act-tools.js +25 -9
  264. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  265. package/dist/surface/tools/assert/assert-verdict.js +17 -2
  266. package/dist/surface/tools/browser-tools.js +8 -1
  267. package/dist/surface/tools/chat-drives.d.ts +26 -0
  268. package/dist/surface/tools/chat-drives.js +165 -0
  269. package/dist/surface/tools/dynamic-tools.js +29 -2
  270. package/dist/surface/tools/empty-session-list.d.ts +41 -0
  271. package/dist/surface/tools/empty-session-list.js +80 -0
  272. package/dist/surface/tools/error-recovery.d.ts +6 -0
  273. package/dist/surface/tools/error-recovery.js +38 -6
  274. package/dist/surface/tools/explore-tools.d.ts +10 -1
  275. package/dist/surface/tools/explore-tools.js +82 -13
  276. package/dist/surface/tools/feedback-tools.js +14 -0
  277. package/dist/surface/tools/harness-explore.d.ts +96 -14
  278. package/dist/surface/tools/harness-explore.js +227 -202
  279. package/dist/surface/tools/harness-plan.d.ts +12 -0
  280. package/dist/surface/tools/harness-plan.js +31 -0
  281. package/dist/surface/tools/harness-script.d.ts +8 -0
  282. package/dist/surface/tools/harness-script.js +342 -0
  283. package/dist/surface/tools/harness-toolset.d.ts +14 -1
  284. package/dist/surface/tools/harness-toolset.js +77 -6
  285. package/dist/surface/tools/invoke-tool.js +64 -9
  286. package/dist/surface/tools/lease-seed.d.ts +29 -0
  287. package/dist/surface/tools/lease-seed.js +123 -0
  288. package/dist/surface/tools/lease-tools.d.ts +3 -23
  289. package/dist/surface/tools/lease-tools.js +30 -131
  290. package/dist/surface/tools/live-call-text.js +2 -2
  291. package/dist/surface/tools/next-step.d.ts +19 -0
  292. package/dist/surface/tools/next-step.js +159 -0
  293. package/dist/surface/tools/observe-tools.js +2 -4
  294. package/dist/surface/tools/platform-moment.d.ts +12 -0
  295. package/dist/surface/tools/platform-moment.js +168 -0
  296. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  297. package/dist/surface/tools/query-hint-schema.js +10 -0
  298. package/dist/surface/tools/read-tools.js +8 -2
  299. package/dist/surface/tools/real-input-attempt.js +96 -13
  300. package/dist/surface/tools/tool-kit.d.ts +25 -1
  301. package/dist/surface/tools/tool-kit.js +4 -6
  302. package/dist/surface/tools/tools.js +20 -48
  303. package/dist/telemetry/argument-shape.js +1 -1
  304. package/dist/telemetry/hud-metrics.d.ts +2 -0
  305. package/dist/telemetry/hud-metrics.js +17 -0
  306. package/dist/telemetry/onboarding-firsts.d.ts +2 -2
  307. package/dist/telemetry/onboarding-firsts.js +2 -2
  308. package/dist/telemetry/telemetry.d.ts +12 -0
  309. package/dist/telemetry/telemetry.js +43 -14
  310. package/dist/telemetry/verification-of.js +1 -0
  311. package/dist/wire-journal.js +4 -1
  312. package/docs/README.md +1 -1
  313. package/docs/actions.mdx +1 -2
  314. package/docs/agent-cheatsheet.md +1 -1
  315. package/docs/autodrive.md +29 -25
  316. package/docs/cli/cloud.mdx +6 -4
  317. package/docs/cli/doctor.mdx +6 -0
  318. package/docs/cli/drive.mdx +8 -2
  319. package/docs/cli/init.mdx +11 -13
  320. package/docs/cli/status.mdx +5 -1
  321. package/docs/cli/version.mdx +3 -3
  322. package/docs/cli.mdx +14 -8
  323. package/docs/desktop.mdx +5 -1
  324. package/docs/environment.mdx +3 -7
  325. package/docs/getting-started.md +14 -2
  326. package/docs/human-control.md +1 -2
  327. package/docs/index.mdx +4 -3
  328. package/docs/install-agentic.mdx +1 -1
  329. package/docs/install-manual.mdx +2 -2
  330. package/docs/packages/next.mdx +3 -0
  331. package/docs/packages/tauri.mdx +2 -2
  332. package/docs/packages/vite-plugin.mdx +24 -1
  333. package/docs/packages.mdx +1 -1
  334. package/docs/quickstart.mdx +23 -11
  335. package/docs/telemetry.md +1 -1
  336. package/docs/tools/act.mdx +1 -1
  337. package/docs/tools/session-and-feedback.mdx +9 -9
  338. package/docs/usage.md +5 -3
  339. package/docs/what-is-recorded.md +13 -3
  340. package/docs/what-is-reticle.mdx +3 -1
  341. package/package.json +8 -12
  342. package/dist/features/harness/driver.d.ts +0 -50
  343. package/dist/features/harness/driver.js +0 -236
  344. package/dist/features/harness/jev-driver.d.ts +0 -188
  345. package/dist/features/harness/jev-driver.js +0 -678
  346. package/dist/features/harness/openai-driver.d.ts +0 -46
  347. package/dist/features/harness/openai-driver.js +0 -194
  348. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  349. package/dist/memory/project/dir/fill-value-store.js +0 -88
  350. package/docs/local-registry.md +0 -143
@@ -3,6 +3,7 @@ import { FlowPredicateSchema, Verified } from '@reticlehq/core';
3
3
  import { DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, QueryBy, } from '@reticlehq/core';
4
4
  import { ReticleTool } from '@reticlehq/core';
5
5
  import { formatStepAddress } from 'open-verification';
6
+ import { refFor } from './flow-anchor.js';
6
7
  import { asRecord, asString } from '@reticlehq/core';
7
8
  /**
8
9
  * The note attached when a testid resolves to multiple live elements (the first match is used).
@@ -12,6 +13,9 @@ import { asRecord, asString } from '@reticlehq/core';
12
13
  export function ambiguousTestidNote(value) {
13
14
  return `ambiguous testid '${value}', used first match`;
14
15
  }
16
+ function ambiguousRoleNote(role, name) {
17
+ return `ambiguous ${role} named '${name}', used first match`;
18
+ }
15
19
  /**
16
20
  * The live element refs a QUERY resolved to (empty when it failed or matched nothing). Shared by BOTH
17
21
  * replay engines (replayProgram + flow-replay's step runner) so element extraction — the core of anchor
@@ -87,6 +91,9 @@ export function pathOf(url) {
87
91
  return undefined;
88
92
  }
89
93
  }
94
+ /** What a saved flow could not keep, and the fix. See RecordedStep.unkeptExpect. */
95
+ export const UNKEPT_EXPECT = 'its proved `until` names something a saved flow cannot replay (a session ref such as e12): ' +
96
+ 'name the element by testid, role and name, or text, and the check is kept';
90
97
  /**
91
98
  * Capture an act into every in-flight recording, or do nothing when none is running.
92
99
  *
@@ -96,7 +103,9 @@ export function pathOf(url) {
96
103
  */
97
104
  export function captureAct(recordings, args, res,
98
105
  /** Where this step ran, so the ambient tape can be cut into journeys. See RecordedStep.route. */
99
- route) {
106
+ route,
107
+ /** The session that acted: only recordings it started take the step. */
108
+ session) {
100
109
  // NO `active()` gate. `RecordingStore.capture` opens the AMBIENT tape on its first step, and that
101
110
  // branch was unreachable from here: this returned early whenever nothing was open, and the only
102
111
  // thing that opened anything was `reticle_record { action: "start" }`. So the comment on `capture`
@@ -111,15 +120,18 @@ route) {
111
120
  // success means — the large majority of calls carry one — and dropping it produced a flow graded
112
121
  // "assertion-free: it will pass even if the feature is broken", which is the regression-suite
113
122
  // story failing at its last step. See enforceableExpect below.
114
- const expect = enforceableExpect(args['until'] ?? args['predicate']);
123
+ const declared = args['until'] ?? args['predicate'];
124
+ const expect = enforceableExpect(declared);
115
125
  if (expect !== undefined)
116
126
  step.expect = expect;
127
+ else if (declared !== undefined)
128
+ step.unkeptExpect = UNKEPT_EXPECT;
117
129
  if (route !== undefined)
118
130
  step.page = route;
119
131
  const intent = asString(args['intent'])?.trim();
120
132
  if (intent !== undefined && intent.length > 0)
121
133
  step.intent = intent;
122
- recordings.capture(step, route);
134
+ recordings.capture(step, route, session);
123
135
  }
124
136
  /**
125
137
  * Capture an act_and_wait once its verdict is known, keeping `until` as the step's expectation only
@@ -127,15 +139,15 @@ route) {
127
139
  * a regression test asserting something never once observed to hold because of the action. The
128
140
  * action itself is always recorded: it happened, and replay needs it to reach the next step.
129
141
  */
130
- export function captureVerdictedAct(recordings, args, res, route, verified) {
142
+ export function captureVerdictedAct(recordings, args, res, route, verified, session) {
131
143
  const { until: _until, predicate: _predicate, ...unproved } = args;
132
- captureAct(recordings, Verified.YES === verified ? args : unproved, res, route);
144
+ captureAct(recordings, Verified.YES === verified ? args : unproved, res, route, session);
133
145
  }
134
146
  /** Fold a PASSING standalone assertion into the step it proved. See RecordingStore.attachExpect. */
135
- export function captureAssertion(recordings, raw) {
147
+ export function captureAssertion(recordings, raw, session) {
136
148
  const expect = enforceableExpect(raw);
137
149
  if (expect !== undefined)
138
- recordings.attachExpect(expect);
150
+ recordings.attachExpect(expect, session);
139
151
  }
140
152
  /**
141
153
  * A declared predicate, as a recorded expectation — or nothing.
@@ -207,6 +219,10 @@ function compileAnchorArgs(r, ref, action, actArgs) {
207
219
  };
208
220
  if (source !== undefined)
209
221
  roleArgs['source'] = source;
222
+ if ('number' === typeof r['nth'] && 'number' === typeof r['of']) {
223
+ roleArgs['nth'] = r['nth'];
224
+ roleArgs['of'] = r['of'];
225
+ }
210
226
  return { args: roleArgs, stable: true };
211
227
  }
212
228
  const component = asString(r['component']);
@@ -275,11 +291,13 @@ async function resolveRef(session, step) {
275
291
  const result = await session.command(ReticleCommand.QUERY, query);
276
292
  if (!result.ok)
277
293
  throw new Error(result.error ?? 'query failed');
278
- const ref = queryRefs(result)[0];
294
+ const refs = queryRefs(result);
295
+ const meant = refFor(step, refs);
296
+ const ref = meant ?? refs[0];
279
297
  if (ref === undefined) {
280
298
  throw new Error(`${value} named '${String(name)}' did not resolve in current page`);
281
299
  }
282
- return { ref };
300
+ return meant === undefined ? { ref, note: ambiguousRoleNote(value, String(name)) } : { ref };
283
301
  }
284
302
  if (by === QueryBy.COMPONENT) {
285
303
  const component = asString(step.component);
@@ -26,6 +26,8 @@ import { FlakeStore } from '../stores/flake-store.js';
26
26
  export async function recordSuiteFlakes(fs, reticleRoot, runs) {
27
27
  const flakes = new FlakeStore(fs, reticleRoot);
28
28
  for (const { replay } of runs) {
29
+ if (replay.status === ReplayStatus.UNVERIFIABLE)
30
+ continue;
29
31
  await flakes.record(replay.name, ReplayStatus.OK === replay.status).catch(() => undefined);
30
32
  }
31
33
  return flakes.flakyFlows().catch(() => []);
@@ -1,17 +1,4 @@
1
- /**
2
- * Which flows a suite run replays, resolved against the store.
3
- *
4
- * Shared by the MCP tool and the `reticle verify` command so the two cannot disagree about what a
5
- * selection means.
6
- *
7
- * Lives beside the flow store rather than under `suite/` on purpose: it needs `flowsForSession`, and
8
- * from `suite/` that import made `suite` and `flows` need EACH OTHER -- a mutual pair, which the
9
- * reach guard forbids growing because two directories that need each other cannot be read, moved or
10
- * tested apart. Here it adds no edge at all: `flows` reaching itself is nothing, and `cli -> flows`
11
- * already existed. The pure rule lives in core (`selectFlows`); this is the part that has to read
12
- * the files, because a label lives in one and `list` returns only names.
13
- */
14
- import { type ProjectId } from '@reticlehq/core';
1
+ import type { ProjectTarget } from '../../memory/project/session-root.js';
15
2
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
16
3
  /**
17
4
  * Resolve what a suite run should replay, and what it is holding back.
@@ -23,7 +10,7 @@ import type { ToolDeps } from '../../surface/tools/tool-kit.js';
23
10
  * would turn a corrupt file into missing coverage nobody is told about, and replay reports the parse
24
11
  * failure far better than a silent omission does.
25
12
  */
26
- export declare function resolveSuiteSelection(deps: ToolDeps, projectId: ProjectId | undefined, args: Record<string, unknown>): Promise<{
13
+ export declare function resolveSuiteSelection(deps: ToolDeps, target: ProjectTarget, args: Record<string, unknown>): Promise<{
27
14
  run: string[];
28
15
  quarantined: string[];
29
16
  unmatched: string[];
@@ -11,7 +11,7 @@
11
11
  * already existed. The pure rule lives in core (`selectFlows`); this is the part that has to read
12
12
  * the files, because a label lives in one and `list` returns only names.
13
13
  */
14
- import { FLOW_FILE_VERSION, orderFlows, selectFlows, } from '@reticlehq/core';
14
+ import { FLOW_FILE_VERSION, orderFlows, selectFlows } from '@reticlehq/core';
15
15
  import { flowsForSession } from './flow-store-for-session.js';
16
16
  /**
17
17
  * Resolve what a suite run should replay, and what it is holding back.
@@ -23,8 +23,9 @@ import { flowsForSession } from './flow-store-for-session.js';
23
23
  * would turn a corrupt file into missing coverage nobody is told about, and replay reports the parse
24
24
  * failure far better than a silent omission does.
25
25
  */
26
- export async function resolveSuiteSelection(deps, projectId, args) {
27
- const store = flowsForSession(deps, projectId).flows;
26
+ export async function resolveSuiteSelection(deps, target, args) {
27
+ const { projectId } = target;
28
+ const store = flowsForSession(deps, target).flows;
28
29
  const names = await store.list(projectId);
29
30
  const asStrings = (value) => Array.isArray(value) ? value.filter((v) => 'string' === typeof v) : [];
30
31
  const wantedNames = asStrings(args['names']);
@@ -2,7 +2,7 @@ import { z } from 'zod';
2
2
  import { sessionRoot, sessionProjectId } from '../../memory/project/session-root.js';
3
3
  import { verdictForSuite } from '../../judgement/outcome/verify-change-verdict.js';
4
4
  import { attributedFailures } from '../../judgement/outcome/attributed-failure.js';
5
- import { Verified } from '@reticlehq/core';
5
+ import { Verified, isAdvisory } from '@reticlehq/core';
6
6
  import { ReticleTool } from '@reticlehq/core';
7
7
  import { asNumber, asRecord, asString } from '@reticlehq/core';
8
8
  import { workerCountSchema } from '../../surface/tools/args/numeric-bounds.js';
@@ -49,7 +49,7 @@ export const VERIFY_CHANGE_TOOLS = [
49
49
  contradictions: z
50
50
  .array(z.unknown())
51
51
  .optional()
52
- .describe('Channels that disagreed DURING the replay. Any entry forces verified:"no" even when every flow passed — a suite that goes green over a failed write is the false green this exists to catch. OMITTED when clean; see `measured` when it could not be looked for.'),
52
+ .describe('Findings from channels inspected during replay. Advisory entries are reported without changing the verdict; verdict-deciding entries can turn a green suite into no or unknown. OMITTED when clean; see `measured` when it could not be looked for.'),
53
53
  untouched: z
54
54
  .array(z.unknown())
55
55
  .optional()
@@ -170,8 +170,9 @@ export const VERIFY_CHANGE_TOOLS = [
170
170
  // The suite went green. That is exactly when the remaining two channels are worth reading:
171
171
  // a replay that passes while a write fails is the false green this whole product is about.
172
172
  const extra = await inspectAfterReplay(deps, args, cursor);
173
- if (extra.contradictions.length > 0) {
174
- const kinds = extra.contradictions.map((c) => c.kind).join(', ');
173
+ const decidingContradictions = extra.contradictions.filter((c) => !isAdvisory(c.kind));
174
+ if (decidingContradictions.length > 0) {
175
+ const kinds = decidingContradictions.map((c) => c.kind).join(', ');
175
176
  const earned = attributedFailures(affected, unknownProvenance).length > 0;
176
177
  return {
177
178
  verified: earned ? Verified.NO : Verified.UNKNOWN,
@@ -197,6 +198,7 @@ export const VERIFY_CHANGE_TOOLS = [
197
198
  flowsRun: affected,
198
199
  suite,
199
200
  unknownProvenance,
201
+ ...(0 === extra.contradictions.length ? {} : { contradictions: extra.contradictions }),
200
202
  measured: extra.measured,
201
203
  };
202
204
  }
@@ -207,6 +209,7 @@ export const VERIFY_CHANGE_TOOLS = [
207
209
  flowsRun: affected,
208
210
  suite,
209
211
  unknownProvenance,
212
+ ...(0 === extra.contradictions.length ? {} : { contradictions: extra.contradictions }),
210
213
  ...(extra.untouched === undefined ? {} : { untouched: extra.untouched }),
211
214
  measured: extra.measured,
212
215
  };
@@ -243,6 +246,7 @@ async function inspectAfterReplay(deps, args, cursor) {
243
246
  currentEditEpoch: session.currentEditEpoch,
244
247
  appOrigin: session.url,
245
248
  background: session.background,
249
+ namedNetUrls: [],
246
250
  // The replay is the action, and the cursor is where it started.
247
251
  actionSince: cursor,
248
252
  });
@@ -14,7 +14,7 @@
14
14
  * line there corrupts the client's stream.
15
15
  */
16
16
  import { type ChromiumLaunchOptions } from './chromium-launch-options.js';
17
- import { type ChromiumProbe } from './command/cli/doctor/browser/chromium-hint.js';
17
+ import { type ChromiumPathProblem, type ChromiumProbe } from './command/cli/doctor/browser/chromium-hint.js';
18
18
  /** The installed-browser channels Playwright can drive in place of its own build. */
19
19
  export declare const ChromiumChannel: {
20
20
  readonly CHROME: "chrome";
@@ -31,6 +31,8 @@ export interface ChromiumTargetDeps {
31
31
  /** Where the bundled playwright expects its Chromium build. */
32
32
  executablePath: () => string;
33
33
  exists: (path: string) => boolean;
34
+ /** What stops the executable RETICLE_CHROMIUM_PATH names from launching, if anything. */
35
+ pathProblem: (path: string) => ChromiumPathProblem | undefined;
34
36
  platform: NodeJS.Platform;
35
37
  env: NodeJS.ProcessEnv;
36
38
  /** Run the pinned Chromium install; resolves true when it succeeded. */
@@ -38,16 +40,29 @@ export interface ChromiumTargetDeps {
38
40
  /** A progress line for a human — stderr in production. */
39
41
  announce: (line: string) => void;
40
42
  }
41
- /** Which browser to launch: the bundled build (no channel), an installed channel, or none at all. */
43
+ /**
44
+ * Which browser to launch: the bundled build (no channel), an installed channel, the executable
45
+ * RETICLE_CHROMIUM_PATH names, or none at all. `unusableConfigured` is a named executable that
46
+ * cannot be launched, and why.
47
+ */
42
48
  export type ChromiumTarget = {
43
49
  found: true;
44
50
  channel?: ChromiumChannel;
51
+ executablePath?: string;
45
52
  } | {
46
53
  found: false;
54
+ unusableConfigured?: {
55
+ path: string;
56
+ problem: ChromiumPathProblem;
57
+ };
47
58
  };
48
59
  /**
49
60
  * Decide which browser a launch will use, installing the pinned Chromium only as the last resort.
50
- * Order: bundled build on disk → installed Chrome → installed Edge → install, then bundled.
61
+ * Order: RETICLE_CHROMIUM_PATH → bundled build on disk → installed Chrome → installed Edge →
62
+ * install, then bundled.
63
+ *
64
+ * A named executable ends the search either way. It is set on a machine where the usual places are
65
+ * empty or unusable, so falling back from a typo to a download would only hide the typo.
51
66
  */
52
67
  export declare function resolveChromiumTarget(deps: ChromiumTargetDeps): Promise<ChromiumTarget>;
53
68
  /** The subset of Playwright's `BrowserType` this file needs, so tests can pass a fake. */
@@ -67,14 +82,21 @@ export declare function announceOnce(write: (line: string) => void): (line: stri
67
82
  * turns into the pinned install command, so the user still gets the one line that fixes it.
68
83
  */
69
84
  export declare function launchChromium<B>(chromium: LaunchableChromium<B>, headless: boolean, deps?: ChromiumTargetDeps): Promise<B>;
85
+ /** What the probes read off the machine, injected so a test can name a browser that is not there. */
86
+ interface ProbeMachine {
87
+ env: NodeJS.ProcessEnv;
88
+ exists: (path: string) => boolean;
89
+ pathProblem: (path: string) => ChromiumPathProblem | undefined;
90
+ }
70
91
  /**
71
92
  * The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
72
93
  * and the automatic install. Without this the preflight would refuse a lease on a machine the
73
94
  * launcher itself could serve. Falls back to the plain probe so a real refusal keeps its evidence.
74
95
  */
75
- export declare function probeLaunchableChromium(): Promise<ChromiumProbe>;
96
+ export declare function probeLaunchableChromium(machine?: ProbeMachine): Promise<ChromiumProbe>;
76
97
  /**
77
98
  * `doctor`'s probe: the plain Chromium probe, plus the installed browser that stands in when the
78
99
  * bundled build is missing. Never installs — a diagnostic that downloads 150 MiB is not one.
79
100
  */
80
- export declare function probeChromiumWithFallback(): Promise<ChromiumProbe>;
101
+ export declare function probeChromiumWithFallback(machine?: ProbeMachine): Promise<ChromiumProbe>;
102
+ export {};
@@ -18,7 +18,7 @@ import { existsSync } from 'node:fs';
18
18
  import { createRequire } from 'node:module';
19
19
  import { dirname, join } from 'node:path';
20
20
  import { chromiumLaunchOptions } from './chromium-launch-options.js';
21
- import { bundledPlaywrightVersion, chromiumInstallCommand, probeChromium, } from './command/cli/doctor/browser/chromium-hint.js';
21
+ import { bundledPlaywrightVersion, chromiumInstallCommand, chromiumPathProblem, configuredChromiumPath, configuredChromiumProbe, configuredChromiumRefusal, probeChromium, } from './command/cli/doctor/browser/chromium-hint.js';
22
22
  /** The installed-browser channels Playwright can drive in place of its own build. */
23
23
  export const ChromiumChannel = {
24
24
  CHROME: 'chrome',
@@ -72,9 +72,20 @@ const installingLine = (command) => `[reticle] No Chromium, Chrome or Edge found
72
72
  const INSTALL_FAILED_LINE = '[reticle] The Chromium install did not complete.\n';
73
73
  /**
74
74
  * Decide which browser a launch will use, installing the pinned Chromium only as the last resort.
75
- * Order: bundled build on disk → installed Chrome → installed Edge → install, then bundled.
75
+ * Order: RETICLE_CHROMIUM_PATH → bundled build on disk → installed Chrome → installed Edge →
76
+ * install, then bundled.
77
+ *
78
+ * A named executable ends the search either way. It is set on a machine where the usual places are
79
+ * empty or unusable, so falling back from a typo to a download would only hide the typo.
76
80
  */
77
81
  export async function resolveChromiumTarget(deps) {
82
+ const configured = configuredChromiumPath(deps.env);
83
+ if (configured !== undefined) {
84
+ const problem = deps.pathProblem(configured);
85
+ return problem === undefined
86
+ ? { found: true, executablePath: configured }
87
+ : { found: false, unusableConfigured: { path: configured, problem } };
88
+ }
78
89
  let bundled;
79
90
  try {
80
91
  bundled = deps.executablePath();
@@ -168,6 +179,7 @@ function defaultDeps(chromium) {
168
179
  return {
169
180
  executablePath: () => chromium.executablePath(),
170
181
  exists: existsSync,
182
+ pathProblem: (path) => chromiumPathProblem(path, process.platform),
171
183
  platform: process.platform,
172
184
  env: process.env,
173
185
  install: installPinnedChromium,
@@ -181,15 +193,30 @@ function defaultDeps(chromium) {
181
193
  */
182
194
  export async function launchChromium(chromium, headless, deps = defaultDeps(chromium)) {
183
195
  const target = await resolveChromiumTarget(deps);
184
- const channel = target.found ? target.channel : undefined;
185
- return chromium.launch(chromiumLaunchOptions(headless, channel));
196
+ // Refused here rather than launched: Playwright's error for a missing executable is the one
197
+ // `chromiumLaunchHint` answers with the install command, which cannot fix a wrong path.
198
+ if (!target.found && target.unusableConfigured !== undefined) {
199
+ const { path, problem } = target.unusableConfigured;
200
+ throw new Error(configuredChromiumRefusal(path, problem));
201
+ }
202
+ return target.found
203
+ ? chromium.launch(chromiumLaunchOptions(headless, target.channel, target.executablePath))
204
+ : chromium.launch(chromiumLaunchOptions(headless));
186
205
  }
206
+ const THIS_MACHINE = {
207
+ env: process.env,
208
+ exists: existsSync,
209
+ pathProblem: (path) => chromiumPathProblem(path, process.platform),
210
+ };
187
211
  /**
188
212
  * The lease preflight's probe: "can a launch succeed?", which now includes the Chrome/Edge fallback
189
213
  * and the automatic install. Without this the preflight would refuse a lease on a machine the
190
214
  * launcher itself could serve. Falls back to the plain probe so a real refusal keeps its evidence.
191
215
  */
192
- export async function probeLaunchableChromium() {
216
+ export async function probeLaunchableChromium(machine = THIS_MACHINE) {
217
+ const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
218
+ if (configured !== undefined)
219
+ return configured;
193
220
  try {
194
221
  const { chromium } = await import('playwright');
195
222
  const target = await resolveChromiumTarget(defaultDeps(chromium));
@@ -205,14 +232,17 @@ export async function probeLaunchableChromium() {
205
232
  * `doctor`'s probe: the plain Chromium probe, plus the installed browser that stands in when the
206
233
  * bundled build is missing. Never installs — a diagnostic that downloads 150 MiB is not one.
207
234
  */
208
- export async function probeChromiumWithFallback() {
235
+ export async function probeChromiumWithFallback(machine = THIS_MACHINE) {
236
+ const configured = configuredChromiumProbe(machine.env, machine.pathProblem);
237
+ if (configured !== undefined)
238
+ return configured;
209
239
  const probe = await probeChromium();
210
240
  if (probe.exists)
211
241
  return probe;
212
242
  const channel = findInstalledChannel({
213
- exists: existsSync,
243
+ exists: machine.exists,
214
244
  platform: process.platform,
215
- env: process.env,
245
+ env: machine.env,
216
246
  });
217
247
  return channel === undefined ? probe : { ...probe, fallback: CHANNEL_LABEL[channel] };
218
248
  }
@@ -78,6 +78,18 @@ export declare function resolveProjectCloud(fs: FileSystemPort, reticleRoot: str
78
78
  * needs no exported key — see `withLinkedCredential`.
79
79
  */
80
80
  export declare const linkedCloudPort: (fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv) => () => Promise<CloudConfig | null>;
81
+ /**
82
+ * The API key a tab's own project resolves to, so a credential is only ever used for its own tabs.
83
+ * A tab whose project cannot be resolved has no key.
84
+ */
85
+ export declare const sessionApiKeyPort: (fs: FileSystemPort, homeDir: string, env: NodeJS.ProcessEnv, rootOf: (id: string) => string) => (sessionId: string) => Promise<string | undefined>;
86
+ /** The platform and key a tab's own project resolves to, or undefined when it is not linked. */
87
+ export declare const sessionCloudPort: (fs: FileSystemPort, homeDir: string, env: NodeJS.ProcessEnv, rootOf: (id: string) => string) => (sessionId: string) => Promise<CloudConfig | undefined>;
88
+ /**
89
+ * The credential for pushing RUNS, or null when this project opted out with `sync.runs: false`.
90
+ * `reticle verify` used the port above and pushed its run and progress regardless of the opt-out.
91
+ */
92
+ export declare const linkedRunsCloudPort: (fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv) => () => Promise<CloudConfig | null>;
81
93
  /**
82
94
  * The environment with this project's resolved credential written into it, for the readers that
83
95
  * take an env record (the panel's model config and harness switch). Handed `process.env` alone,
@@ -182,6 +182,21 @@ export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
182
182
  * needs no exported key — see `withLinkedCredential`.
183
183
  */
184
184
  export const linkedCloudPort = (fs, reticleRoot, homeDir, env) => async () => (await resolveProjectCloud(fs, reticleRoot, homeDir, env)).config;
185
+ /**
186
+ * The API key a tab's own project resolves to, so a credential is only ever used for its own tabs.
187
+ * A tab whose project cannot be resolved has no key.
188
+ */
189
+ export const sessionApiKeyPort = (fs, homeDir, env, rootOf) => async (sessionId) => (await sessionCloudPort(fs, homeDir, env, rootOf)(sessionId))?.apiKey;
190
+ /** The platform and key a tab's own project resolves to, or undefined when it is not linked. */
191
+ export const sessionCloudPort = (fs, homeDir, env, rootOf) => async (sessionId) => (await resolveProjectCloud(fs, rootOf(sessionId), homeDir, env)).config ?? undefined;
192
+ /**
193
+ * The credential for pushing RUNS, or null when this project opted out with `sync.runs: false`.
194
+ * `reticle verify` used the port above and pushed its run and progress regardless of the opt-out.
195
+ */
196
+ export const linkedRunsCloudPort = (fs, reticleRoot, homeDir, env) => async () => {
197
+ const cloud = await resolveProjectCloud(fs, reticleRoot, homeDir, env);
198
+ return cloud.policy.runs ? cloud.config : null;
199
+ };
185
200
  /**
186
201
  * The environment with this project's resolved credential written into it, for the readers that
187
202
  * take an env record (the panel's model config and harness switch). Handed `process.env` alone,
@@ -1,13 +1,3 @@
1
- /**
2
- * Optional cloud sync for saved flows. "Logged in" here means the two cloud env vars are set (written by
3
- * `reticle login` later; settable by hand today): the hosted URL and an API key from the Reticle
4
- * dashboard. When present, a freshly-saved flow is pushed to `POST /v1/flows` so the team's regression
5
- * suite lives in the cloud — surviving refactors and runnable in CI. When absent, sync is a no-op and
6
- * everything stays 100% local (the "no phone-home" default: nothing leaves the machine unless you opt in).
7
- *
8
- * Sync is best-effort: a network failure NEVER fails the local save. The flow is already on disk; the
9
- * cloud copy is an enhancement, so a push error is logged and swallowed.
10
- */
11
1
  import { z } from 'zod';
12
2
  import { type FlowFile, type ReticleVerificationRun, type RunRecord, type VerifyProgressEvent } from '@reticlehq/core';
13
3
  /**
@@ -40,6 +30,19 @@ export declare function cloudFetch(url: string, init: {
40
30
  headers: Record<string, string>;
41
31
  body?: string;
42
32
  }, timeoutMs?: number): Promise<Response>;
33
+ /**
34
+ * One sync request, status and body, under the same timeout every cloud call has. The sync daemon
35
+ * and `reticle sync` each called a bare `fetch`: one accepted-but-silent connection left the daemon's
36
+ * cycle running forever, and the dashboard stopped updating with nothing logged.
37
+ */
38
+ export declare function syncRequest(url: string, init: {
39
+ method: string;
40
+ headers: Record<string, string>;
41
+ body?: string;
42
+ }, timeoutMs?: number): Promise<{
43
+ status: number;
44
+ text: string;
45
+ }>;
43
46
  export declare const SyncOutcome: {
44
47
  readonly SYNCED: "synced";
45
48
  readonly SKIPPED: "skipped";
@@ -123,20 +126,20 @@ declare const ServerVerificationSchema: z.ZodObject<{
123
126
  }>, "many">;
124
127
  summary: z.ZodString;
125
128
  }, "strip", z.ZodTypeAny, {
126
- verificationId: string;
127
- verdict: string | null;
128
129
  flows: {
129
130
  status: string;
130
131
  name: string;
131
132
  }[];
132
- summary: string;
133
- }, {
134
133
  verificationId: string;
135
134
  verdict: string | null;
135
+ summary: string;
136
+ }, {
136
137
  flows: {
137
138
  status: string;
138
139
  name: string;
139
140
  }[];
141
+ verificationId: string;
142
+ verdict: string | null;
140
143
  summary: string;
141
144
  }>;
142
145
  export type ServerVerification = z.infer<typeof ServerVerificationSchema>;
@@ -8,6 +8,7 @@
8
8
  * Sync is best-effort: a network failure NEVER fails the local save. The flow is already on disk; the
9
9
  * cloud copy is an enhancement, so a push error is logged and swallowed.
10
10
  */
11
+ import { shareableRun } from '@reticlehq/core/artifacts';
11
12
  import { z } from 'zod';
12
13
  import { VERIFY_PROGRESS_MAX_EVENTS, platformCredentialFrom, } from '@reticlehq/core';
13
14
  /**
@@ -97,6 +98,15 @@ export async function cloudFetch(url, init, timeoutMs = CLOUD_FETCH_TIMEOUT_MS)
97
98
  throw err;
98
99
  }
99
100
  }
101
+ /**
102
+ * One sync request, status and body, under the same timeout every cloud call has. The sync daemon
103
+ * and `reticle sync` each called a bare `fetch`: one accepted-but-silent connection left the daemon's
104
+ * cycle running forever, and the dashboard stopped updating with nothing logged.
105
+ */
106
+ export async function syncRequest(url, init, timeoutMs = CLOUD_FETCH_TIMEOUT_MS) {
107
+ const res = await cloudFetch(url, init, timeoutMs);
108
+ return { status: res.status, text: await res.text() };
109
+ }
100
110
  export const SyncOutcome = {
101
111
  SYNCED: 'synced',
102
112
  SKIPPED: 'skipped',
@@ -143,7 +153,8 @@ export async function syncRunToCloud(run, config, fetchImpl) {
143
153
  'content-type': 'application/json',
144
154
  authorization: `Bearer ${config.apiKey}`,
145
155
  },
146
- body: JSON.stringify(run),
156
+ // The prompt context leaves the machine only when the project opted in.
157
+ body: JSON.stringify(shareableRun(run)),
147
158
  });
148
159
  return res.ok
149
160
  ? { outcome: SyncOutcome.SYNCED, status: res.status }
@@ -5,8 +5,33 @@ export interface HarnessConfigView {
5
5
  harnessEntitled: boolean;
6
6
  /** Whether the platform holds a key for `provider`. Entitlement is not readiness — see the offer. */
7
7
  providerReady: boolean;
8
+ credits?: {
9
+ used: number;
10
+ limit: number;
11
+ };
8
12
  }
9
13
  export interface ConfigSource {
10
14
  read(): HarnessConfigView | undefined;
11
15
  }
12
- export declare function harnessConfigSource(load: () => Promise<HarnessConfigView | undefined>, now?: () => number): ConfigSource;
16
+ export interface MutableConfigSource extends ConfigSource {
17
+ applyWrite(enabled: boolean): void;
18
+ recheck(): void;
19
+ subscribe(listener: () => void): () => void;
20
+ /** Re-read in the background, telling listeners only when the answer changed. */
21
+ poll(): void;
22
+ }
23
+ export declare function harnessConfigSource(load: () => Promise<HarnessConfigView | undefined>, now?: () => number): MutableConfigSource;
24
+ export type ConfigsByRoot = (root: string) => MutableConfigSource;
25
+ /**
26
+ * One live config source per project root, created on first ask; `onChange` hears every update.
27
+ *
28
+ * The platform read arrives as `load`, so this cache never reaches for the harness feature that
29
+ * performs it: the daemon, which owns both, joins them. It sat inline in `start` and `startDaemon`.
30
+ */
31
+ export declare function harnessConfigsByRoot(load: (root: string) => Promise<HarnessConfigView | undefined>, onChange?: (root: string) => void): ConfigsByRoot;
32
+ /**
33
+ * The HUD's harness switch, written to the platform with this project's credential. Nothing is
34
+ * awaited: an accepted write updates the cache at once, and a refused or failed one re-reads the
35
+ * platform so the switch springs back to the truth rather than staying where the user left it.
36
+ */
37
+ export declare function applyHarnessSwitch(configs: ConfigsByRoot, root: string, enabled: boolean, envFor: () => Promise<Record<string, string | undefined>>): void;