@reticlehq/server 2.13.0 → 2.14.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 (451) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/dev-server-wait.d.ts +8 -0
  233. package/dist/setup/dev-server-wait.js +3 -1
  234. package/dist/setup/drive-agent.d.ts +2 -3
  235. package/dist/setup/drive-agent.js +1 -1
  236. package/dist/setup/drive-plan.d.ts +3 -2
  237. package/dist/setup/ensure-daemon.d.ts +2 -1
  238. package/dist/setup/existing-app.d.ts +12 -0
  239. package/dist/setup/existing-app.js +13 -0
  240. package/dist/setup/init-host.d.ts +11 -0
  241. package/dist/setup/init-host.js +26 -0
  242. package/dist/setup/init-runtime.d.ts +4 -10
  243. package/dist/setup/init-runtime.js +23 -16
  244. package/dist/setup/license-key.d.ts +2 -1
  245. package/dist/setup/listeners.d.ts +2 -1
  246. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  247. package/dist/setup/loopback-probe-urls.js +52 -0
  248. package/dist/setup/node-effects.d.ts +3 -0
  249. package/dist/setup/node-effects.js +26 -11
  250. package/dist/setup/page-probe.d.ts +5 -0
  251. package/dist/setup/relaunch.d.ts +2 -1
  252. package/dist/setup/run-setup.d.ts +5 -1
  253. package/dist/setup/run-setup.js +155 -48
  254. package/dist/setup/setup-command.d.ts +3 -2
  255. package/dist/setup/setup-command.js +8 -0
  256. package/dist/setup/setup-options.d.ts +5 -3
  257. package/dist/setup/setup-options.js +3 -2
  258. package/dist/setup/wait-progress.d.ts +28 -0
  259. package/dist/setup/wait-progress.js +43 -0
  260. package/dist/telemetry/app-instrumented.d.ts +2 -1
  261. package/dist/telemetry/app-instrumented.js +1 -1
  262. package/dist/telemetry/automation-hint.d.ts +1 -1
  263. package/dist/telemetry/automation-hint.js +1 -1
  264. package/dist/telemetry/cli-telemetry.js +1 -1
  265. package/dist/telemetry/connect-failure.d.ts +1 -1
  266. package/dist/telemetry/connect-failure.js +1 -1
  267. package/dist/telemetry/crash-cause.d.ts +3 -2
  268. package/dist/telemetry/crash-cause.js +1 -1
  269. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  270. package/dist/telemetry/daemon-telemetry.js +1 -1
  271. package/dist/telemetry/error-class.d.ts +3 -2
  272. package/dist/telemetry/error-class.js +1 -1
  273. package/dist/telemetry/feedback-cli.js +1 -1
  274. package/dist/telemetry/feedback-context.d.ts +3 -2
  275. package/dist/telemetry/feedback-context.js +5 -4
  276. package/dist/telemetry/feedback-local.d.ts +3 -2
  277. package/dist/telemetry/feedback.d.ts +4 -3
  278. package/dist/telemetry/feedback.js +1 -1
  279. package/dist/telemetry/git-facts.d.ts +2 -2
  280. package/dist/telemetry/git-facts.js +1 -1
  281. package/dist/telemetry/identify.d.ts +1 -1
  282. package/dist/telemetry/identify.js +1 -1
  283. package/dist/telemetry/init-telemetry.d.ts +2 -14
  284. package/dist/telemetry/init-telemetry.js +4 -13
  285. package/dist/telemetry/install-source.d.ts +7 -13
  286. package/dist/telemetry/install-source.js +7 -29
  287. package/dist/telemetry/license-activation.d.ts +3 -2
  288. package/dist/telemetry/license-activation.js +1 -1
  289. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  290. package/dist/telemetry/mcp-connection.js +1 -1
  291. package/dist/telemetry/project-profile.d.ts +0 -14
  292. package/dist/telemetry/project-profile.js +1 -1
  293. package/dist/telemetry/proxy-telemetry.js +1 -1
  294. package/dist/telemetry/session-metrics.d.ts +1 -1
  295. package/dist/telemetry/session-metrics.js +1 -1
  296. package/dist/telemetry/telemetry.d.ts +1 -17
  297. package/dist/telemetry/telemetry.js +4 -4
  298. package/dist/telemetry/tool-refused.d.ts +1 -1
  299. package/dist/telemetry/tool-refused.js +1 -1
  300. package/dist/tools/act-observation.d.ts +3 -2
  301. package/dist/tools/act-preflight.js +8 -3
  302. package/dist/tools/act-target.d.ts +4 -0
  303. package/dist/tools/act-target.js +14 -5
  304. package/dist/tools/act-tools.d.ts +0 -2
  305. package/dist/tools/act-tools.js +45 -15
  306. package/dist/tools/assert-source.d.ts +30 -0
  307. package/dist/tools/assert-source.js +21 -0
  308. package/dist/tools/assert-verdict.d.ts +1 -1
  309. package/dist/tools/assert-verdict.js +14 -4
  310. package/dist/tools/browser-tools.js +28 -8
  311. package/dist/tools/complete-snapshot.d.ts +3 -2
  312. package/dist/tools/dynamic-tools.js +8 -0
  313. package/dist/tools/error-recovery.d.ts +10 -0
  314. package/dist/tools/error-recovery.js +23 -0
  315. package/dist/tools/feedback-tools.js +1 -1
  316. package/dist/tools/invoke-tool.js +2 -0
  317. package/dist/tools/lease-tools.d.ts +48 -20
  318. package/dist/tools/lease-tools.js +311 -76
  319. package/dist/tools/lineage-tools.d.ts +14 -0
  320. package/dist/tools/lineage-tools.js +64 -0
  321. package/dist/tools/merge-tools.js +1 -3
  322. package/dist/tools/navigate-arrival.d.ts +45 -1
  323. package/dist/tools/navigate-arrival.js +39 -6
  324. package/dist/tools/navigate-result.d.ts +8 -1
  325. package/dist/tools/navigate-result.js +14 -4
  326. package/dist/tools/numeric-bounds.d.ts +0 -11
  327. package/dist/tools/numeric-bounds.js +4 -4
  328. package/dist/tools/observe-tools.js +22 -26
  329. package/dist/tools/observed-nothing.d.ts +9 -0
  330. package/dist/tools/observed-nothing.js +2 -1
  331. package/dist/tools/read-tools.js +12 -17
  332. package/dist/tools/real-input-attempt.d.ts +14 -6
  333. package/dist/tools/real-input-attempt.js +75 -18
  334. package/dist/tools/reconcile-tools.js +2 -1
  335. package/dist/tools/reload-result.d.ts +6 -1
  336. package/dist/tools/reload-result.js +7 -2
  337. package/dist/tools/resolve-target.d.ts +7 -1
  338. package/dist/tools/resolve-target.js +72 -9
  339. package/dist/tools/settle-in-flight.d.ts +1 -1
  340. package/dist/tools/settle-in-flight.js +9 -2
  341. package/dist/tools/snapshot-delta.d.ts +24 -1
  342. package/dist/tools/snapshot-delta.js +66 -7
  343. package/dist/tools/tool-names.d.ts +1 -0
  344. package/dist/tools/tool-names.js +1 -0
  345. package/dist/tools/tool-surface.js +20 -2
  346. package/dist/tools/tools.js +120 -11
  347. package/dist/tools/verify-next-baton.d.ts +2 -1
  348. package/dist/update/sdk-sync.d.ts +1 -1
  349. package/dist/update/sdk-sync.js +1 -1
  350. package/dist/update/updater.js +1 -1
  351. package/dist/version/sdk-fix.d.ts +2 -1
  352. package/dist/version/version-skew.d.ts +0 -8
  353. package/dist/version/version-skew.js +1 -1
  354. package/docs/README.md +8 -6
  355. package/docs/actions.mdx +1 -1
  356. package/docs/agent-cheatsheet.md +4 -2
  357. package/docs/architecture.md +1 -1
  358. package/docs/cli/mcp.mdx +3 -1
  359. package/docs/cli/watch.mdx +1 -1
  360. package/docs/cli.mdx +1 -1
  361. package/docs/desktop.mdx +1 -1
  362. package/docs/docs.json +8 -3
  363. package/docs/faq.mdx +9 -0
  364. package/docs/first-drive.md +136 -0
  365. package/docs/frameworks.mdx +29 -7
  366. package/docs/gate-plan.md +2 -2
  367. package/docs/gates.md +19 -11
  368. package/docs/getting-started.md +7 -1
  369. package/docs/http-transport.md +164 -0
  370. package/docs/index.mdx +5 -3
  371. package/docs/install-manual.mdx +1 -1
  372. package/docs/packages/tauri.mdx +1 -1
  373. package/docs/packages.mdx +15 -3
  374. package/docs/platform-integration.md +1 -1
  375. package/docs/predicates.mdx +2 -0
  376. package/docs/system-map.md +2 -2
  377. package/docs/telemetry-contract.md +4 -4
  378. package/docs/telemetry-events.mdx +1 -1
  379. package/docs/telemetry.md +3 -3
  380. package/docs/tools-navigate.mdx +14 -6
  381. package/docs/usage.md +4 -4
  382. package/package.json +7 -4
  383. package/dist/init/agent-rules.d.ts +0 -116
  384. package/dist/init/agent-rules.js +0 -279
  385. package/dist/init/app-choice.d.ts +0 -20
  386. package/dist/init/app-choice.js +0 -25
  387. package/dist/init/astro-host.d.ts +0 -21
  388. package/dist/init/astro-host.js +0 -27
  389. package/dist/init/astro-patch.d.ts +0 -33
  390. package/dist/init/astro-patch.js +0 -300
  391. package/dist/init/capabilities.d.ts +0 -40
  392. package/dist/init/capabilities.js +0 -150
  393. package/dist/init/closing-hint.d.ts +0 -36
  394. package/dist/init/closing-hint.js +0 -120
  395. package/dist/init/connect-steps.d.ts +0 -2
  396. package/dist/init/connect-steps.js +0 -50
  397. package/dist/init/cra.d.ts +0 -79
  398. package/dist/init/cra.js +0 -178
  399. package/dist/init/csp-check.d.ts +0 -38
  400. package/dist/init/csp-check.js +0 -107
  401. package/dist/init/csp-doctor.d.ts +0 -32
  402. package/dist/init/csp-doctor.js +0 -54
  403. package/dist/init/desktop-doctor.d.ts +0 -41
  404. package/dist/init/desktop-doctor.js +0 -198
  405. package/dist/init/detect.d.ts +0 -96
  406. package/dist/init/detect.js +0 -209
  407. package/dist/init/dev-script.d.ts +0 -50
  408. package/dist/init/dev-script.js +0 -66
  409. package/dist/init/format-generated.d.ts +0 -18
  410. package/dist/init/format-generated.js +0 -39
  411. package/dist/init/install-hint.d.ts +0 -9
  412. package/dist/init/install-hint.js +0 -29
  413. package/dist/init/mcp-clients.d.ts +0 -89
  414. package/dist/init/mcp-clients.js +0 -299
  415. package/dist/init/mcp-registered.d.ts +0 -12
  416. package/dist/init/mcp-registered.js +0 -14
  417. package/dist/init/mcp.d.ts +0 -89
  418. package/dist/init/mcp.js +0 -129
  419. package/dist/init/next-patch.d.ts +0 -39
  420. package/dist/init/next-patch.js +0 -196
  421. package/dist/init/node-io.d.ts +0 -7
  422. package/dist/init/node-io.js +0 -117
  423. package/dist/init/non-js-project.d.ts +0 -27
  424. package/dist/init/non-js-project.js +0 -84
  425. package/dist/init/patch-kind.d.ts +0 -20
  426. package/dist/init/patch-kind.js +0 -10
  427. package/dist/init/plan-framework.d.ts +0 -64
  428. package/dist/init/plan-framework.js +0 -565
  429. package/dist/init/plan.d.ts +0 -242
  430. package/dist/init/plan.js +0 -703
  431. package/dist/init/preflight.d.ts +0 -28
  432. package/dist/init/preflight.js +0 -38
  433. package/dist/init/project-id.d.ts +0 -12
  434. package/dist/init/project-id.js +0 -25
  435. package/dist/init/refresh-rules.d.ts +0 -39
  436. package/dist/init/refresh-rules.js +0 -51
  437. package/dist/init/run.d.ts +0 -117
  438. package/dist/init/run.js +0 -806
  439. package/dist/init/slash-command.d.ts +0 -27
  440. package/dist/init/slash-command.js +0 -132
  441. package/dist/init/snippets.d.ts +0 -134
  442. package/dist/init/snippets.js +0 -572
  443. package/dist/init/vite-config.d.ts +0 -18
  444. package/dist/init/vite-config.js +0 -114
  445. package/dist/init/windows-quote.d.ts +0 -38
  446. package/dist/init/windows-quote.js +0 -70
  447. package/dist/init/workspace-apps.d.ts +0 -30
  448. package/dist/init/workspace-apps.js +0 -178
  449. package/dist/project/remember-project.d.ts +0 -24
  450. package/dist/project/remember-project.js +0 -40
  451. /package/dist/{init → setup}/confirm.d.ts +0 -0
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { ReticleTool } from '../tools/tool-names.js';
3
3
  import { loadNamedFlows, resolveChangedFiles } from '../cli/cli-flow-commands.js';
4
+ import { sessionRoot, sessionProjectId } from '../project/session-root.js';
4
5
  import { affectedSavedFlows } from './flow-sources.js';
5
6
  /**
6
7
  * `reticle_affected` — which saved flows must re-verify for a set of changed files.
@@ -43,20 +44,32 @@ export const AFFECTED_TOOLS = [
43
44
  ? args['files'].filter((f) => 'string' === typeof f)
44
45
  : [];
45
46
  const since = 'string' === typeof args['since'] ? args['since'] : undefined;
46
- const changedFiles = await resolveChangedFiles(files, since);
47
- const flows = await loadNamedFlows(deps.fs, deps.reticleRoot);
47
+ // Same address as verify_change, resolved the same way: `deps.reticleRoot` is the configured
48
+ // project directory (the daemon's cwd is not), and the session names which project's flows.
49
+ const changed = await resolveChangedFiles(files, since, deps.reticleRoot);
50
+ const changedFiles = changed.files;
51
+ // No `sessionId` on this tool's surface, and it does not need one: passing `undefined`
52
+ // resolves the single connected session, and falls back exactly as before when it cannot.
53
+ // Adding an argument here would be a surface change to fix an addressing bug.
54
+ const flows = await loadNamedFlows(deps.fs, sessionRoot(deps, undefined), sessionProjectId(deps, undefined));
48
55
  const result = affectedSavedFlows(flows, changedFiles);
49
56
  // Three different situations produced the same empty answer: no files changed, no flows saved,
50
57
  // and a git ref that resolved to nothing. Only the first means "nothing to re-verify"; the
51
58
  // others mean the question could not be answered, and re-running nothing on that basis is how a
52
59
  // regression ships. Say which one it is.
53
- const why = 0 === flows.length
54
- ? 'no saved flows exist yet, so nothing COULD be affected — record one with reticle_record then reticle_flow_save'
55
- : 0 === changedFiles.length
56
- ? since === undefined && 0 === files.length
57
- ? 'no files were given and no `since` ref was passed, so nothing was compared — pass the files you edited, or a git ref'
58
- : 'nothing changed against that input, so no saved flow needs re-verifying'
59
- : undefined;
60
+ const why =
61
+ // A git failure OUTRANKS every other explanation: the others describe a question that was
62
+ // answered, and this one is a question that was not. Reporting "nothing changed" over a
63
+ // mistyped ref or a shallow clone is how a regression ships past the tool meant to catch it.
64
+ changed.failed
65
+ ? `could not read the diff for \`${String(since)}\`, so NOTHING was compared and this result proves nothing: ${changed.reason ?? 'git failed'}`
66
+ : 0 === flows.length
67
+ ? 'no saved flows exist yet, so nothing COULD be affected — record one with reticle_record then reticle_flow_save'
68
+ : 0 === changedFiles.length
69
+ ? since === undefined && 0 === files.length
70
+ ? 'no files were given and no `since` ref was passed, so nothing was compared — pass the files you edited, or a git ref'
71
+ : 'nothing changed against that input, so no saved flow needs re-verifying'
72
+ : undefined;
60
73
  return {
61
74
  changedFiles,
62
75
  ...result,
@@ -7,7 +7,8 @@ import { type Annotation, type AnnotateOutcome } from '@reticlehq/core';
7
7
  * assert-signal → step.expect.signal (+ signalData) on the LAST step (needs ≥1 step)
8
8
  * assert-visible → step.expect.element.testid on the LAST step (needs ≥1 step)
9
9
  * mark-dynamic → flow.dynamic[] += testid flow-level, allowed with 0 steps
10
- * success-state → flow.success = { signal | element } flow-level (signal XOR testid; both → signal)
10
+ * success-state → flow.success = { signal | state | net | console | element | text } flow-level,
11
+ * resolved in that precedence order (a consequence beats a presence check)
11
12
  *
12
13
  * FIRST CUT: only the four structured kinds above. Free natural-language annotation → predicate
13
14
  * compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
@@ -7,7 +7,8 @@ import { AnnotationErrorCode, AnnotationKind, AnnotationTarget, COMPILED_PREDICA
7
7
  * assert-signal → step.expect.signal (+ signalData) on the LAST step (needs ≥1 step)
8
8
  * assert-visible → step.expect.element.testid on the LAST step (needs ≥1 step)
9
9
  * mark-dynamic → flow.dynamic[] += testid flow-level, allowed with 0 steps
10
- * success-state → flow.success = { signal | element } flow-level (signal XOR testid; both → signal)
10
+ * success-state → flow.success = { signal | state | net | console | element | text } flow-level,
11
+ * resolved in that precedence order (a consequence beats a presence check)
11
12
  *
12
13
  * FIRST CUT: only the four structured kinds above. Free natural-language annotation → predicate
13
14
  * compilation is explicitly FUTURE — an NL string never reaches here (AnnotationSchema
@@ -73,6 +74,19 @@ export function compileAnnotation(a, stepCount) {
73
74
  if (a.testid !== undefined) {
74
75
  return flowSuccess(a, { element: { testid: a.testid } });
75
76
  }
77
+ // Last, and above only MISSING_FIELD. An element is a locator and text is content; where a
78
+ // caller offered both, the locator is the more stable anchor. But text beats nothing at all,
79
+ // which is what an app whose only observable is a rendered value used to get (#811).
80
+ if (a.text !== undefined) {
81
+ const text = { contains: a.text.contains };
82
+ if (a.text.scope !== undefined)
83
+ text.scope = a.text.scope;
84
+ if (a.text.absent !== undefined)
85
+ text.absent = a.text.absent;
86
+ if (a.text.visible !== undefined)
87
+ text.visible = a.text.visible;
88
+ return flowSuccess(a, { text });
89
+ }
76
90
  return { result: { ok: false, code: AnnotationErrorCode.MISSING_FIELD } };
77
91
  }
78
92
  case AnnotationKind.INTENT:
@@ -153,6 +167,10 @@ export function describeCompiled(a) {
153
167
  const level = a.console.level ?? 'error';
154
168
  return `${COMPILED_PREDICATE_PREFIX} succeed when ${true === a.console.absent ? `no console.${level}` : `console.${level}`}`;
155
169
  }
170
+ if (a.testid === undefined && a.text !== undefined) {
171
+ const where = a.text.scope === undefined ? '' : ` in ${a.text.scope}`;
172
+ return `${COMPILED_PREDICATE_PREFIX} succeed when ${true === a.text.absent ? 'text is gone:' : 'text shows:'} ${JSON.stringify(a.text.contains)}${where}`;
173
+ }
156
174
  return `${COMPILED_PREDICATE_PREFIX} succeed when ${a.testid ?? ''} visible`;
157
175
  case AnnotationKind.INTENT:
158
176
  return `${COMPILED_PREDICATE_PREFIX} intent: ${a.text}`;
@@ -9,7 +9,7 @@ interface DeclaredSurface {
9
9
  signals: readonly string[];
10
10
  flows: readonly string[];
11
11
  }
12
- export interface CoverageDimension {
12
+ interface CoverageDimension {
13
13
  total: number;
14
14
  covered: number;
15
15
  /**
@@ -34,7 +34,7 @@ export interface Coverage {
34
34
  /** The gate's outcome when the project has recorded no flows at all — not a pass, and not a failure. */
35
35
  export declare const NO_FLOWS = "no_flows";
36
36
  /** The gate's flow-coverage line. `pct` and `outcome` are mutually exclusive by construction. */
37
- export interface FlowCoverageReport {
37
+ interface FlowCoverageReport {
38
38
  covered: number;
39
39
  total: number;
40
40
  pct?: number;
@@ -15,7 +15,7 @@ declare const FlakeRecordSchema: z.ZodObject<{
15
15
  runs: number;
16
16
  fails: number;
17
17
  }>;
18
- export type FlakeRecord = z.infer<typeof FlakeRecordSchema>;
18
+ type FlakeRecord = z.infer<typeof FlakeRecordSchema>;
19
19
  /** Per-flow map (flow name → record). */
20
20
  export type FlakeLedger = Record<string, FlakeRecord>;
21
21
  export declare const FlakeFileSchema: z.ZodObject<{
@@ -31,17 +31,17 @@ export declare const FlakeFileSchema: z.ZodObject<{
31
31
  fails: number;
32
32
  }>>;
33
33
  }, "strip", z.ZodTypeAny, {
34
- version: 1;
35
34
  flows: Record<string, {
36
35
  runs: number;
37
36
  fails: number;
38
37
  }>;
39
- }, {
40
38
  version: 1;
39
+ }, {
41
40
  flows: Record<string, {
42
41
  runs: number;
43
42
  fails: number;
44
43
  }>;
44
+ version: 1;
45
45
  }>;
46
46
  export declare function emptyRecord(): FlakeRecord;
47
47
  /** Accrue one unchanged-code replay outcome. */
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Accept the assertion grammar a drive already uses, in a saved flow file.
3
+ *
4
+ * FlowExpect is a flat object (`signal` is a name string; `net`/`state` sit beside it). Agents write
5
+ * the `act_and_wait` shape instead — `{ kind: "allOf", predicates: [...] }`, or sibling channels
6
+ * with `signal: { name, count }` — and replay answered `flow_parse_failed` / "malformed". That sent
7
+ * people looking for a JSON syntax error in a file they had just written, and the workaround was to
8
+ * drop every channel but one.
9
+ *
10
+ * Coercion happens at load: the on-disk schema does not move, FLOW_FILE_VERSION stays 1, and a
11
+ * re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
12
+ * to an empty expect that would grade asserted-while-unchecked.
13
+ */
14
+ import { type FlowFile } from '@reticlehq/core';
15
+ import type { ZodError } from 'zod';
16
+ import type { FlowResult } from './flows.js';
17
+ export declare const FlowParseNote: {
18
+ readonly NOT_JSON: "flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save";
19
+ readonly MALFORMED: "flow file is malformed — fix or regenerate it with reticle_flow_save";
20
+ readonly UNSUPPORTED_SHAPE: "valid JSON, unsupported expect shape";
21
+ readonly UNENFORCED: "valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)";
22
+ };
23
+ export type CoerceExpectResult = {
24
+ ok: true;
25
+ value: unknown;
26
+ } | {
27
+ ok: false;
28
+ detail: string;
29
+ };
30
+ export declare function coerceFlowExpect(raw: unknown): CoerceExpectResult;
31
+ export declare function coerceFlowFileExpects(raw: unknown): CoerceExpectResult;
32
+ export declare function describeFlowZodFailure(error: ZodError): string;
33
+ export declare function parseFlowFileText(text: string): FlowResult<FlowFile>;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Accept the assertion grammar a drive already uses, in a saved flow file.
3
+ *
4
+ * FlowExpect is a flat object (`signal` is a name string; `net`/`state` sit beside it). Agents write
5
+ * the `act_and_wait` shape instead — `{ kind: "allOf", predicates: [...] }`, or sibling channels
6
+ * with `signal: { name, count }` — and replay answered `flow_parse_failed` / "malformed". That sent
7
+ * people looking for a JSON syntax error in a file they had just written, and the workaround was to
8
+ * drop every channel but one.
9
+ *
10
+ * Coercion happens at load: the on-disk schema does not move, FLOW_FILE_VERSION stays 1, and a
11
+ * re-save writes the canonical shape. A kind a saved flow cannot enforce is refused, not stripped
12
+ * to an empty expect that would grade asserted-while-unchecked.
13
+ */
14
+ import { FlowErrorCode, FlowFileSchema } from '@reticlehq/core';
15
+ import { PredicateSchema } from '../events/predicate.js';
16
+ import { predicateToExpect } from './predicate-to-expect.js';
17
+ export const FlowParseNote = {
18
+ NOT_JSON: 'flow file is not valid JSON — fix the syntax or regenerate it with reticle_flow_save',
19
+ MALFORMED: 'flow file is malformed — fix or regenerate it with reticle_flow_save',
20
+ UNSUPPORTED_SHAPE: 'valid JSON, unsupported expect shape',
21
+ UNENFORCED: 'valid JSON, but this expect uses a predicate kind a saved flow cannot enforce (settled, route, animation, anyOf, not)',
22
+ };
23
+ function isRecord(value) {
24
+ return 'object' === typeof value && null !== value && !Array.isArray(value);
25
+ }
26
+ /**
27
+ * Flatten `signal: { name, count }` into the on-disk fields. Other channels already match FlowExpect.
28
+ * Leave a signal object with no name alone so the schema failure can name the key.
29
+ */
30
+ function flattenSignalObject(raw) {
31
+ const signal = raw['signal'];
32
+ if (!isRecord(signal))
33
+ return raw;
34
+ const name = signal['name'];
35
+ if ('string' !== typeof name)
36
+ return raw;
37
+ const out = { ...raw, signal: name };
38
+ if ('number' === typeof signal['count'])
39
+ out['signalCount'] = signal['count'];
40
+ if (undefined !== signal['dataMatches'])
41
+ out['signalData'] = signal['dataMatches'];
42
+ return out;
43
+ }
44
+ export function coerceFlowExpect(raw) {
45
+ if (isRecord(raw) && 'kind' in raw) {
46
+ const parsed = PredicateSchema.safeParse(raw);
47
+ if (!parsed.success) {
48
+ return { ok: false, detail: describePredicateExpectFailure(parsed.error) };
49
+ }
50
+ const expect = predicateToExpect(parsed.data);
51
+ if (undefined === expect)
52
+ return { ok: false, detail: FlowParseNote.UNENFORCED };
53
+ return { ok: true, value: expect };
54
+ }
55
+ if (!isRecord(raw))
56
+ return { ok: true, value: raw };
57
+ return { ok: true, value: flattenSignalObject(raw) };
58
+ }
59
+ function describePredicateExpectFailure(error) {
60
+ const issue = error.issues[0];
61
+ if (undefined === issue)
62
+ return FlowParseNote.UNSUPPORTED_SHAPE;
63
+ const key = issue.path[0];
64
+ if ('string' === typeof key || 'number' === typeof key) {
65
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} key "${String(key)}"`;
66
+ }
67
+ return FlowParseNote.UNSUPPORTED_SHAPE;
68
+ }
69
+ export function coerceFlowFileExpects(raw) {
70
+ if (!isRecord(raw))
71
+ return { ok: true, value: raw };
72
+ const next = { ...raw };
73
+ const stepsIn = next['steps'];
74
+ if (Array.isArray(stepsIn)) {
75
+ const steps = [];
76
+ for (let i = 0; i < stepsIn.length; i++) {
77
+ const step = stepsIn[i];
78
+ if (!isRecord(step) || undefined === step['expect']) {
79
+ steps.push(step);
80
+ continue;
81
+ }
82
+ const coerced = coerceFlowExpect(step['expect']);
83
+ if (!coerced.ok) {
84
+ return {
85
+ ok: false,
86
+ detail: `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(i)}: ${coerced.detail}`,
87
+ };
88
+ }
89
+ steps.push({ ...step, expect: coerced.value });
90
+ }
91
+ next['steps'] = steps;
92
+ }
93
+ if (undefined !== next['success']) {
94
+ const coerced = coerceFlowExpect(next['success']);
95
+ if (!coerced.ok) {
96
+ return {
97
+ ok: false,
98
+ detail: `${FlowParseNote.UNSUPPORTED_SHAPE} at success: ${coerced.detail}`,
99
+ };
100
+ }
101
+ next['success'] = coerced.value;
102
+ }
103
+ return { ok: true, value: next };
104
+ }
105
+ export function describeFlowZodFailure(error) {
106
+ const issue = error.issues[0];
107
+ if (undefined === issue)
108
+ return FlowParseNote.MALFORMED;
109
+ const path = issue.path;
110
+ // A strict object reports an unrecognized key as `unrecognized_keys` on the OBJECT's path, with
111
+ // the offending names in `keys` — not as an issue whose path ends in the key. Reading only the
112
+ // path shape reported the one failure this exists to name as a bare "malformed", which sends the
113
+ // author back to the file to find a typo we had already identified.
114
+ if ('unrecognized_keys' === issue.code) {
115
+ const named = issue.keys.map((k) => `"${k}"`).join(', ');
116
+ if ('steps' === path[0] && 'number' === typeof path[1] && 'expect' === path[2]) {
117
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(path[1])} key ${named}`;
118
+ }
119
+ if ('success' === path[0])
120
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} at success key ${named}`;
121
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} — unrecognized key ${named}`;
122
+ }
123
+ if ('steps' === path[0] &&
124
+ 'number' === typeof path[1] &&
125
+ 'expect' === path[2] &&
126
+ 'string' === typeof path[3]) {
127
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} at step ${String(path[1])} key "${path[3]}"`;
128
+ }
129
+ if ('success' === path[0] && 'string' === typeof path[1]) {
130
+ return `${FlowParseNote.UNSUPPORTED_SHAPE} at success key "${path[1]}"`;
131
+ }
132
+ return FlowParseNote.MALFORMED;
133
+ }
134
+ export function parseFlowFileText(text) {
135
+ let parsed;
136
+ try {
137
+ parsed = JSON.parse(text);
138
+ }
139
+ catch {
140
+ return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: FlowParseNote.NOT_JSON };
141
+ }
142
+ const coerced = coerceFlowFileExpects(parsed);
143
+ if (!coerced.ok) {
144
+ return { ok: false, code: FlowErrorCode.PARSE_FAILED, detail: coerced.detail };
145
+ }
146
+ const result = FlowFileSchema.safeParse(coerced.value);
147
+ if (!result.success) {
148
+ return {
149
+ ok: false,
150
+ code: FlowErrorCode.PARSE_FAILED,
151
+ detail: describeFlowZodFailure(result.error),
152
+ };
153
+ }
154
+ return { ok: true, value: result.data };
155
+ }
@@ -8,7 +8,7 @@ export declare function latestRecordedFlow(events: ReticleEvent[]): {
8
8
  flow: import('@reticlehq/core').FlowFile;
9
9
  } | undefined;
10
10
  /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
11
- export declare function flowErrorMessage(code: FlowErrorCode): string;
11
+ export declare function flowErrorMessage(code: FlowErrorCode, detail?: string): string;
12
12
  /** The slice of a session the start-path logic reads: the live URL plus the event buffer. */
13
13
  interface StartPathSession {
14
14
  url?: string;
@@ -1,5 +1,8 @@
1
+ import { resolveFlowUploads } from './flow-upload-resolve.js';
1
2
  import { EventType, FLOW_SIGNAL_TIMEOUT_MS, FlowErrorCode, RecordedFlowSchema, ReplayStatus, ReticleCommand, RunKind, RunStatus, } from '@reticlehq/core';
3
+ import { haltedFrom } from './replay-halt.js';
2
4
  import { asRecord, asString } from '../tools/tools-helpers.js';
5
+ import { routeOfEvent, routeOfUrl } from '../events/predicate-route.js';
3
6
  import { carryReticleIdentity } from '../tools/lease-tools.js';
4
7
  import { replayFlow } from './flow-replay.js';
5
8
  import { anchorQueryArgs } from './flow-step-runners.js';
@@ -22,6 +25,7 @@ import { resolveProjectCloud } from '../cloud/cloud-config.js';
22
25
  import { consultSubjectFor, selectConsulted } from './flow-memory-consult.js';
23
26
  import { log } from '../log.js';
24
27
  import { flowsForSession } from './flow-store-for-session.js';
28
+ import { FlowParseNote } from './flow-expect-grammar.js';
25
29
  export function latestRecordedFlow(events) {
26
30
  for (let i = events.length - 1; i >= 0; i--) {
27
31
  const event = events[i];
@@ -34,14 +38,16 @@ export function latestRecordedFlow(events) {
34
38
  return undefined;
35
39
  }
36
40
  /** Map a structured FlowErrorCode to a legible one-line message for the agent. */
37
- export function flowErrorMessage(code) {
41
+ export function flowErrorMessage(code, detail) {
42
+ if (FlowErrorCode.PARSE_FAILED === code && undefined !== detail)
43
+ return detail;
38
44
  switch (code) {
39
45
  case FlowErrorCode.INVALID_NAME:
40
46
  return 'invalid flow name — use a single safe segment (letters/digits/-/_), no path separators';
41
47
  case FlowErrorCode.NOT_FOUND:
42
48
  return 'no such flow on disk — run reticle_flow{action:"list"} to see saved flows';
43
49
  case FlowErrorCode.PARSE_FAILED:
44
- return 'flow file is malformed — fix or regenerate it with reticle_flow_save';
50
+ return FlowParseNote.MALFORMED;
45
51
  case FlowErrorCode.NO_RECORDING:
46
52
  return 'no compiled recording by that name — record one (reticle_record{action:"start"|"stop"}) first';
47
53
  }
@@ -93,22 +99,17 @@ recordRoot) {
93
99
  */
94
100
  function currentPathOf(session) {
95
101
  const routes = session.eventsSince(0).filter((e) => e.type === EventType.ROUTE_CHANGE);
96
- const data = routes.at(-1)?.data ?? {};
102
+ const last = routes.at(-1);
97
103
  // pathname + hash, because `startPath` is compared against this and must stay NAVIGABLE. Reading
98
104
  // the pathname alone made both sides `/` on a hash router — always "same path", so the hint never
99
105
  // fired however far the tab had drifted, on the router desktop renderers use by default.
100
- const observed = asString(data['pathname']) ?? asString(data['to']);
106
+ const observed = last === undefined ? undefined : routeOfEvent(last);
101
107
  if (observed !== undefined)
102
- return `${observed}${asString(data['hash']) ?? ''}`;
108
+ return `${observed.docPath}${observed.hash}`;
103
109
  if (session.url === undefined)
104
110
  return undefined;
105
- try {
106
- const parsed = new URL(session.url);
107
- return `${parsed.pathname}${parsed.hash}`;
108
- }
109
- catch {
110
- return undefined;
111
- }
111
+ const fromUrl = routeOfUrl(session.url);
112
+ return fromUrl === undefined ? undefined : `${fromUrl.docPath}${fromUrl.hash}`;
112
113
  }
113
114
  /** Pathname equality up to a trailing slash — a router normalising one must not read as "elsewhere". */
114
115
  function samePath(a, b) {
@@ -318,7 +319,7 @@ export async function replayNamedFlow(deps, args) {
318
319
  name,
319
320
  status: ReplayStatus.ERROR,
320
321
  steps: [],
321
- error: { code: loaded.code, message: flowErrorMessage(loaded.code) },
322
+ error: { code: loaded.code, message: flowErrorMessage(loaded.code, loaded.detail) },
322
323
  };
323
324
  }
324
325
  // What this flow is FOR, from the shared ledger — so a failure can report the business outcome
@@ -341,13 +342,23 @@ export async function replayNamedFlow(deps, args) {
341
342
  // Floor the success oracle at the start of THIS replay so a stale signal from a prior run
342
343
  // in the same session can't fake a pass.
343
344
  const replayFloor = session.elapsed();
344
- const steps = await replayFlow(session, loaded.value, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous']);
345
+ // A recorded upload names a path on disk; the browser can only take bytes. Resolved once, before
346
+ // step 1, through the same helper the live `reticle_act` uses. See resolveFlowUploads.
347
+ const replayable = await resolveFlowUploads(deps, loaded.value);
348
+ const steps = await replayFlow(session, replayable, waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, true === args['confirmDangerous']);
349
+ // Computed HERE, before the synthetic success row is appended below: once that row is pushed,
350
+ // `steps.length` no longer counts only the flow's own steps and the arithmetic is wrong.
351
+ const halted = haltedFrom(steps, loaded.value.steps.length);
345
352
  // "green means intent satisfied": when every step ran clean, assert the flow's success
346
353
  // end-condition as a real consequence. A signal/net success that never fires FAILS the replay
347
354
  // even though all locators resolved — the regression a healed-but-wrong locator ships green.
348
355
  const stepsClean = steps.length > 0 && steps.every((s) => s.ok && s.drift === undefined);
349
356
  if (stepsClean && loaded.value.success !== undefined) {
350
- const verdict = await assertSuccess(session, loaded.value.success, dynamicTestids(loaded.value), waitForPredicate, FLOW_SIGNAL_TIMEOUT_MS, replayFloor);
357
+ const verdict = await assertSuccess(session, loaded.value.success, dynamicTestids(loaded.value), waitForPredicate,
358
+ // The flow's own declaration, not the built-in floor: an app slow enough to need a longer
359
+ // step wait is slow enough that its OUTCOME lands late too, and greening every step only to
360
+ // fail the success oracle at 4s is the same false red one layer down.
361
+ loaded.value.signalTimeoutMs ?? FLOW_SIGNAL_TIMEOUT_MS, replayFloor);
351
362
  const row = {
352
363
  step: steps.length,
353
364
  tool: SUCCESS_STEP_TOOL,
@@ -412,6 +423,8 @@ export async function replayNamedFlow(deps, args) {
412
423
  return errored;
413
424
  }
414
425
  const result = { name, status, steps };
426
+ if (halted !== undefined)
427
+ result.halted = halted;
415
428
  if (knows !== undefined)
416
429
  result.knows = knows;
417
430
  // A green that cannot go red is not a pass. `reticle_flow_verify` already refuses to count these,
@@ -49,6 +49,16 @@ export type Sleep = (ms: number) => Promise<void>;
49
49
  export declare function nearestIsAmbiguous(missing: string, present: string[]): boolean;
50
50
  /** Build the legible-drift record for a testid anchor that resolved to zero live elements. */
51
51
  export declare function testidDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
52
+ /**
53
+ * Build the drift for a step whose `expect.element` testid was absent after the action ran.
54
+ *
55
+ * Deliberately not `testidDrift`: that one means "this step's anchor is gone", and reusing it here
56
+ * put the assertion's target in the result's `anchor` field — the field documented as the value the
57
+ * step is bound to. A caller then reads `testid_not_found` naming something that was never the
58
+ * step's locator and goes hunting for a rename, while the truth is the step ran and its
59
+ * consequence did not hold.
60
+ */
61
+ export declare function expectElementDrift(value: string, hint: QueryEmptyHint | undefined): Drift;
52
62
  export declare function resolveQuery(session: FlowReplaySession, queryArgs: Record<string, unknown>, sleep: Sleep, now?: () => number): Promise<{
53
63
  refs: string[];
54
64
  hint?: QueryEmptyHint;
@@ -1,8 +1,9 @@
1
1
  import { span } from '../trace.js';
2
- import { routePathOf } from '../events/predicate-route.js';
2
+ import { routeOfEvent } from '../events/predicate-route.js';
3
3
  import { AnchorKind, DriftReason, EventType, ReticleCommand, QueryBy, PredicateKind, } from '@reticlehq/core';
4
4
  import { asRecord, asString } from '../tools/tools-helpers.js';
5
5
  import { replayActionArgs, ambiguousTestidNote, queryRefs } from './replay.js';
6
+ import { anchorFieldName } from './flow-secret-field.js';
6
7
  import { degradedStepResult, isDegradedAnchor, runComponentStep, runRoleStep, runSequenceStep, } from './flow-step-runners.js';
7
8
  import { successToPredicate } from './flow-success.js';
8
9
  import { ReticleTool } from '../tools/tool-names.js';
@@ -138,6 +139,27 @@ export function testidDrift(value, hint) {
138
139
  drift.ambiguous = true;
139
140
  return drift;
140
141
  }
142
+ /**
143
+ * Build the drift for a step whose `expect.element` testid was absent after the action ran.
144
+ *
145
+ * Deliberately not `testidDrift`: that one means "this step's anchor is gone", and reusing it here
146
+ * put the assertion's target in the result's `anchor` field — the field documented as the value the
147
+ * step is bound to. A caller then reads `testid_not_found` naming something that was never the
148
+ * step's locator and goes hunting for a rename, while the truth is the step ran and its
149
+ * consequence did not hold.
150
+ */
151
+ export function expectElementDrift(value, hint) {
152
+ const present = hint?.presentTestids ?? [];
153
+ const drift = {
154
+ reasonKind: DriftReason.EXPECT_ELEMENT_NOT_FOUND,
155
+ reason: `expect.element testid "${value}" not present after the action`,
156
+ anchor: value,
157
+ nearest: nearestTestid(value, present),
158
+ };
159
+ if (nearestIsAmbiguous(value, present))
160
+ drift.ambiguous = true;
161
+ return drift;
162
+ }
141
163
  /**
142
164
  * Re-resolve any QUERY against the live DOM, tolerating an in-flight render: QUERY, and while it
143
165
  * returns zero refs, sleep and retry up to ANCHOR_SETTLE_ATTEMPTS. Returns as soon as refs appear,
@@ -198,15 +220,13 @@ function currentRoute(session) {
198
220
  const last = routes.at(-1);
199
221
  if (last === undefined)
200
222
  return undefined;
201
- const data = last.data ?? {};
202
223
  // The ROUTER's path. This field answers "which page did this step run on", and the document
203
224
  // pathname is `/` on every page of a hash-routed app — so a whole desktop replay reported `/` for
204
- // every step. Sixth place the same reading was wrong; see routePathOf.
205
- const pathname = asString(data['pathname']) ?? asString(data['to']);
206
- if (pathname === undefined || 0 === pathname.length)
225
+ // every step. Sixth place the same reading was wrong; see routeOfEvent.
226
+ const parts = routeOfEvent(last);
227
+ if (parts === undefined || 0 === parts.docPath.length)
207
228
  return undefined;
208
- const routed = routePathOf(pathname, asString(data['hash']) ?? '');
209
- return routed.length > 0 ? routed : undefined;
229
+ return parts.routePath.length > 0 ? parts.routePath : undefined;
210
230
  }
211
231
  /** Pathname only (drop origin + query) so a net URL stays terse in the journey. */
212
232
  function trimUrl(url) {
@@ -226,10 +246,9 @@ function summarizeConsequence(events) {
226
246
  const parts = [];
227
247
  const lastRoute = events.filter((e) => e.type === EventType.ROUTE_CHANGE).at(-1);
228
248
  if (lastRoute !== undefined) {
229
- const data = lastRoute.data ?? {};
230
- const to = asString(data['pathname']) ?? asString(data['to']);
231
- if (to !== undefined && to.length > 0)
232
- parts.push(`→ ${to}`);
249
+ const routed = routeOfEvent(lastRoute);
250
+ if (routed !== undefined && routed.routePath.length > 0)
251
+ parts.push(`→ ${routed.routePath}`);
233
252
  }
234
253
  const signals = new Set();
235
254
  for (const e of events) {
@@ -301,9 +320,11 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
301
320
  act = await session.command(ReticleCommand.ACT, {
302
321
  ref,
303
322
  action: step.action ?? '',
304
- // `value` is the anchor's own name — the field this step types into — so a redacted fill can
305
- // be supplied from RETICLE_SECRET_<FIELD> without the flow carrying the secret.
306
- args: replayActionArgs(step.args, confirmDangerous, value),
323
+ // The field this step types into — from the anchor, so a redacted fill can be supplied from
324
+ // RETICLE_SECRET_<FIELD> without the flow carrying the secret. The testid runner used to pass
325
+ // the testid string here and the other two runners passed nothing, so a role-anchored login
326
+ // typed the literal placeholder.
327
+ args: replayActionArgs(step.args, confirmDangerous, anchorFieldName(step.anchor)),
307
328
  });
308
329
  }
309
330
  finally {
@@ -326,9 +347,12 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
326
347
  return {
327
348
  step: index,
328
349
  tool: step.tool,
329
- anchor: expectTestid,
350
+ // The step's OWN anchor, not the expectation's target. Replay stops at the first drift, so
351
+ // this result is the only thing the caller sees about why the run ended — naming the
352
+ // assertion here reads as "step N's locator drifted" and hides that the action did run.
353
+ anchor: value,
330
354
  ok: false,
331
- drift: testidDrift(expectTestid, expectRefs.hint),
355
+ drift: expectElementDrift(expectTestid, expectRefs.hint),
332
356
  };
333
357
  }
334
358
  }
@@ -350,10 +374,13 @@ async function runTestidStep(session, step, index, value, dynamic, confirmDanger
350
374
  * nothing was looking.
351
375
  */
352
376
  async function assertStepExpect(session, expect, dynamic, waitForSignal, timeoutMs, since) {
353
- // `element` is deliberately dropped: the step runner already asserts expect.element.testid against
354
- // the live DOM before we get here, and re-asserting it through the predicate engine would be a
355
- // second round trip for the same fact.
356
- const { element: _element, ...consequences } = expect;
377
+ // A testid is already asserted against the live DOM by the step runner. A role/name locator is
378
+ // not that path — stripping every element made a recorded `until` by button name a no-op, so a
379
+ // flow that proved the control at capture time could not go red when it was gone.
380
+ const consequences = { ...expect };
381
+ if (undefined !== consequences.element?.testid) {
382
+ delete consequences.element;
383
+ }
357
384
  const predicate = successToPredicate(consequences, dynamic);
358
385
  if (predicate === undefined)
359
386
  return undefined;
@@ -379,6 +406,9 @@ function expectLabel(expect) {
379
406
  return `state:${expect.state.path}`;
380
407
  if (expect.console !== undefined)
381
408
  return `console:${expect.console.level ?? '*'}`;
409
+ if (undefined !== expect.element) {
410
+ return expect.element.testid ?? expect.element.name ?? expect.element.role ?? 'element';
411
+ }
382
412
  return 'expect';
383
413
  }
384
414
  /** Run one signal-anchored step: wait for the signal predicate, else drift (no nearest for signals). */
@@ -423,6 +453,11 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
423
453
  // Floor for signal steps: signals that fire during THIS replay, never a prior flow/run in the same
424
454
  // session. Captured once, before any step, so a back-to-back suite verify cannot cross-satisfy.
425
455
  const replayFloor = session.elapsed();
456
+ // How long a step waits for its consequence: the step's own declaration, else the flow's, else the
457
+ // caller's default. Resolved per step rather than once, because one slow step in an otherwise fast
458
+ // journey is the common shape — a file import, a model-backed endpoint — and making the whole flow
459
+ // wait for the slowest step would trade a false red for a slow suite. See FlowStep.timeoutMs.
460
+ const waitFor = (step) => step.timeoutMs ?? flow.signalTimeoutMs ?? signalTimeoutMs;
426
461
  let index = 0;
427
462
  for (const step of flow.steps) {
428
463
  const label = anchorLabel(step.anchor);
@@ -443,7 +478,7 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
443
478
  return runSequenceStep(session, step, index, subSteps, confirmDangerous, sleep);
444
479
  }
445
480
  if (step.anchor.kind === AnchorKind.SIGNAL) {
446
- return runSignalStep(session, step, index, label, waitForSignal, signalTimeoutMs, replayFloor);
481
+ return runSignalStep(session, step, index, label, waitForSignal, waitFor(step), replayFloor);
447
482
  }
448
483
  if (step.anchor.kind === AnchorKind.COMPONENT) {
449
484
  return runComponentStep(session, step, index, step.anchor, confirmDangerous, sleep);
@@ -459,7 +494,7 @@ export async function replayFlow(session, flow, waitForSignal, signalTimeoutMs,
459
494
  // console and store truth alike — deterministically, in the same cheap replay loop, with no LLM.
460
495
  const stepExpect = step.expect;
461
496
  if (result.ok && result.drift === undefined && stepExpect !== undefined) {
462
- const expectDrift = await assertStepExpect(session, stepExpect, dynamic, waitForSignal, signalTimeoutMs, cursorBefore);
497
+ const expectDrift = await assertStepExpect(session, stepExpect, dynamic, waitForSignal, waitFor(step), cursorBefore);
463
498
  if (expectDrift !== undefined) {
464
499
  result.ok = false;
465
500
  result.drift = expectDrift;