@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
@@ -10,9 +10,18 @@
10
10
  * Bounded and best-effort by construction: a navigation to a page that is not instrumented, or not
11
11
  * there at all, must still return promptly with `confirmed:false` rather than hanging. The clock and
12
12
  * the sleep are injected so this is testable without waiting on a real one.
13
+ *
14
+ * The bound is the CALLER'S to set. It was a fixed 5s that `reticle_navigate` did not expose, and a
15
+ * Nuxt SPA reattaching under HMR was measured coming back in 30–60s — so every navigation to it
16
+ * answered `confirmed:false` while the app was still on its way, and nothing the agent could pass
17
+ * would make the daemon wait. `assert` / `wait_for` / `act_and_wait` all spend the caller's
18
+ * `timeout_ms` (resolve-within.ts); this is the same budget applied to the same wait.
19
+ */
20
+ /**
21
+ * The default when the caller gives no `timeout_ms`: long enough for a dev server to serve a page
22
+ * and the SDK to dial back; short enough to not hang. A caller who knows the app is slower says so.
13
23
  */
14
- /** Long enough for a dev server to serve a page and the SDK to dial back; short enough to not hang. */
15
- const ARRIVAL_TIMEOUT_MS = 5_000;
24
+ export const ARRIVAL_TIMEOUT_MS = 5_000;
16
25
  const POLL_MS = 100;
17
26
  /**
18
27
  * Compare only origin + pathname.
@@ -32,12 +41,36 @@ function samePage(a, b) {
32
41
  return false;
33
42
  }
34
43
  }
35
- function findArrival(sessions, target) {
44
+ /**
45
+ * The sessions already on `target` — the set `ArrivalScope.priorIds` is built from, sampled before
46
+ * the navigation is dispatched.
47
+ *
48
+ * Shares `samePage` with the arrival scan on purpose: "was it already there" and "is it there now"
49
+ * are the same question asked at two moments, and two spellings of it would eventually disagree.
50
+ */
51
+ export function idsAtTarget(sessions, target) {
52
+ const ids = new Set();
36
53
  for (const s of sessions.all()) {
37
54
  if (samePage(s.url, target))
55
+ ids.add(s.id);
56
+ }
57
+ return ids;
58
+ }
59
+ function findArrival(sessions, target, scope) {
60
+ let arrived = null;
61
+ for (const s of sessions.all()) {
62
+ if (!samePage(s.url, target))
63
+ continue;
64
+ // The tab we drove is the answer whenever it is there, not a tie broken by scan order.
65
+ if (s.id === scope.navigatedId)
38
66
  return { sessionId: s.id };
67
+ if (scope.priorIds.has(s.id))
68
+ continue;
69
+ // Keep looking rather than returning: the driven session may still be ahead of us.
70
+ if (null === arrived)
71
+ arrived = { sessionId: s.id };
39
72
  }
40
- return null;
73
+ return arrived;
41
74
  }
42
75
  const REAL_CLOCK = {
43
76
  now: () => Date.now(),
@@ -47,10 +80,10 @@ const REAL_CLOCK = {
47
80
  * Poll until a session is present at `target`, or the budget runs out. Returns `null` on timeout —
48
81
  * never throws, because a failure to confirm is a legitimate answer, not an error.
49
82
  */
50
- export async function awaitArrival(sessions, target, timeoutMs = ARRIVAL_TIMEOUT_MS, clock = REAL_CLOCK) {
83
+ export async function awaitArrival(sessions, target, scope, timeoutMs = ARRIVAL_TIMEOUT_MS, clock = REAL_CLOCK) {
51
84
  const deadline = clock.now() + timeoutMs;
52
85
  for (;;) {
53
- const found = findArrival(sessions, target);
86
+ const found = findArrival(sessions, target, scope);
54
87
  if (found !== null)
55
88
  return found;
56
89
  if (clock.now() >= deadline)
@@ -28,4 +28,11 @@ export declare function navigateResult(result: {
28
28
  * one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
29
29
  * `reticle_sessions` poll from every single navigation.
30
30
  */
31
- arrival?: NavigateArrival | null): Record<string, unknown>;
31
+ arrival: NavigateArrival | null,
32
+ /**
33
+ * The budget the daemon spent waiting for arrival, in ms. Reported on `confirmed:false` so the
34
+ * agent can tell "Reticle stopped waiting" from "the page never came back": the first is fixed by
35
+ * asking for a longer `timeout_ms`, the second is not. Without it the two read identically, and a
36
+ * slow SPA (30–60s to reattach under HMR) looked like a failed navigation on every call.
37
+ */
38
+ waitedMs: number): Record<string, unknown>;
@@ -8,7 +8,14 @@ export function navigateResult(result,
8
8
  * one place that CAN answer this — the SDK reconnects to it — so making it answer removes a
9
9
  * `reticle_sessions` poll from every single navigation.
10
10
  */
11
- arrival = null) {
11
+ arrival,
12
+ /**
13
+ * The budget the daemon spent waiting for arrival, in ms. Reported on `confirmed:false` so the
14
+ * agent can tell "Reticle stopped waiting" from "the page never came back": the first is fixed by
15
+ * asking for a longer `timeout_ms`, the second is not. Without it the two read identically, and a
16
+ * slow SPA (30–60s to reattach under HMR) looked like a failed navigation on every call.
17
+ */
18
+ waitedMs) {
12
19
  const ok = true === result.ok;
13
20
  const base = {
14
21
  ok,
@@ -30,9 +37,12 @@ arrival = null) {
30
37
  return {
31
38
  ...base,
32
39
  confirmed: false,
40
+ waitedMs,
33
41
  note: 'ok means the navigation was DISPATCHED, not that the page arrived. The SDK is torn down by ' +
34
- 'the navigation itself, so nothing here can see the new document. Call reticle_sessions to ' +
35
- 'confirm a session reconnected at the new URL before acting; if none appears, the page did ' +
36
- 'not load or is not instrumented.',
42
+ 'the navigation itself, so nothing here can see the new document, and no session reconnected ' +
43
+ `at the new URL within ${waitedMs}ms. A slow app may still be on its way (an SPA reattaching ` +
44
+ 'under HMR can take 30-60s): navigate again with a larger timeout_ms, or call reticle_sessions ' +
45
+ 'to confirm a session reconnected before acting; if none appears, the page did not load or is ' +
46
+ 'not instrumented.',
37
47
  };
38
48
  }
@@ -31,14 +31,8 @@ export declare const MAX_BLOCKING_WAIT_MS: number;
31
31
  * in the name of bounding one that never worked.
32
32
  */
33
33
  export declare const MAX_STATE_DEPTH = 256;
34
- /** A crawl that clicks forever is not a bound. */
35
- export declare const MAX_CRAWL_STEPS = 500;
36
34
  /** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
37
35
  export declare const MAX_SCROLLS = 200;
38
- /** Fake-clock jump. One day of timers is a test; a billion ms is not. */
39
- export declare const MAX_ADVANCE_MS = 86400000;
40
- /** Presenter idle window. One hour is a slow app; larger values never fire. */
41
- export declare const MAX_IDLE_MS = 3600000;
42
36
  /** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
43
37
  export declare const MIN_VIEWPORT_PX = 64;
44
38
  export declare const MAX_VIEWPORT_PX = 10000;
@@ -92,9 +86,4 @@ export declare const windowMsSchema: z.ZodNumber;
92
86
  * asking for a thousand workers cannot be honoured.
93
87
  */
94
88
  export declare const workerCountSchema: z.ZodNumber;
95
- /**
96
- * A row index / list length in a virtualized table. Those lists are large on purpose; the ring
97
- * buffer's 2000 is the wrong ceiling. A billion-row claim is not a list, it is a hang.
98
- */
99
- export declare const MAX_LIST_INDEX = 1000000;
100
89
  export declare const listIndexSchema: z.ZodNumber;
@@ -52,13 +52,13 @@ export const MAX_BLOCKING_WAIT_MS = MCP_SDK_DEFAULT_REQUEST_TIMEOUT_MS - 5_000;
52
52
  */
53
53
  export const MAX_STATE_DEPTH = 256;
54
54
  /** A crawl that clicks forever is not a bound. */
55
- export const MAX_CRAWL_STEPS = 500;
55
+ const MAX_CRAWL_STEPS = 500;
56
56
  /** Scroll-to-find steps. Default is 20; hundreds is searching, thousands is a hang. */
57
57
  export const MAX_SCROLLS = 200;
58
58
  /** Fake-clock jump. One day of timers is a test; a billion ms is not. */
59
- export const MAX_ADVANCE_MS = 86_400_000;
59
+ const MAX_ADVANCE_MS = 86_400_000;
60
60
  /** Presenter idle window. One hour is a slow app; larger values never fire. */
61
- export const MAX_IDLE_MS = 3_600_000;
61
+ const MAX_IDLE_MS = 3_600_000;
62
62
  /** Viewport CSS px — matches the clamp the handler already applied, now as a refusal. */
63
63
  export const MIN_VIEWPORT_PX = 64;
64
64
  export const MAX_VIEWPORT_PX = 10_000;
@@ -121,5 +121,5 @@ export const workerCountSchema = z
121
121
  * A row index / list length in a virtualized table. Those lists are large on purpose; the ring
122
122
  * buffer's 2000 is the wrong ceiling. A billion-row claim is not a list, it is a hang.
123
123
  */
124
- export const MAX_LIST_INDEX = 1_000_000;
124
+ const MAX_LIST_INDEX = 1_000_000;
125
125
  export const listIndexSchema = z.number().finite().int().nonnegative().max(MAX_LIST_INDEX);
@@ -5,7 +5,7 @@
5
5
  import { noteEmptyRead } from './observed-nothing.js';
6
6
  import { z } from 'zod';
7
7
  import { aliasParam } from './alias-args.js';
8
- import { CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, } from '@reticlehq/core';
8
+ import { CONSOLE_ATTACH_NOTE, CONSOLE_LEVELS, ReticleCommand, DEFAULT_ASSERT_TIMEOUT_MS, PredicateKind, Verified, } from '@reticlehq/core';
9
9
  import { ReticleTool } from './tool-names.js';
10
10
  import { countSchema, cursorSchema, httpStatusSchema, timeoutMsSchema, windowMsSchema, } from './numeric-bounds.js';
11
11
  import { buildReactionReport } from '../events/reaction.js';
@@ -19,11 +19,12 @@ import { applyEventBudget, costHint, withSizeCost, DEFAULT_QUERY_LIMIT, } from '
19
19
  import { annotateStarvedFailure, healthEnvelope, bufferEnvelope, } from '../session/session-health.js';
20
20
  import { assertsDerivedIpcStatus, DERIVED_IPC_STATUS_ADVICE, isPresenceOnlyAssertion, PRESENCE_ONLY_ADVICE, } from './assert-grade.js';
21
21
  import { assertVerdict } from './assert-verdict.js';
22
- import { assertSource } from './assert-source.js';
22
+ import { assertionSource } from './assert-source.js';
23
23
  import { isChangeUndeclared } from '../honesty/undeclared-change.js';
24
24
  import { openSessionIntents } from '../intent/open-intents.js';
25
25
  import { dischargeInlineIntent, inlineVerdictId, linkInlineIntent, } from '../intent/inline-intent.js';
26
26
  import { bodiesNotCaptured } from '../honesty/uncaptured-bodies.js';
27
+ import { bodyClauseRefusal } from '../honesty/body-capture-remedy.js';
27
28
  import { withControl } from '../session/control-envelope.js';
28
29
  import { asString, asNumber, asRecord } from './tools-helpers.js';
29
30
  import { intentArg, sessionIdShape, commandOrThrow } from './tool-kit.js';
@@ -38,23 +39,6 @@ const bufferOutputShape = {
38
39
  .optional()
39
40
  .describe('Present only when the event buffer evicted events — a negative result may then be a false negative.'),
40
41
  };
41
- /**
42
- * The file:line an assertion may report — see `assertSource`.
43
- *
44
- * Neither `reticle_assert` nor `reticle_wait_for` drives anything, so the last act's source is about
45
- * some earlier action and not about this verdict. The pointer comes from the assertion's own matched
46
- * evidence; the last driven control is borrowed only for a RED whose predicate has no DOM clause at
47
- * all, which is the failure that genuinely has no element to point at.
48
- */
49
- function assertionSource(session, predicate, verdict) {
50
- const source = assertSource({
51
- predicate,
52
- evidence: verdict.evidence,
53
- pass: verdict.pass,
54
- lastActSource: session.lastAct.source(),
55
- });
56
- return source === undefined ? {} : { source };
57
- }
58
42
  /**
59
43
  * Drop `sessionId` from an event in a response the caller scoped to ONE session.
60
44
  *
@@ -78,7 +62,6 @@ function withoutConstantSessionId(event) {
78
62
  * it (it reports which levels are present), but on a quiet page the answer is identical to a genuine
79
63
  * all-clear. Refusing the value outright is the only reading with no ambiguity.
80
64
  */
81
- const CONSOLE_LEVEL_LIST = CONSOLE_LEVELS.join(' | ');
82
65
  const consoleLevelEnum = z.enum(CONSOLE_LEVELS);
83
66
  export const OBSERVE_TOOLS = [
84
67
  {
@@ -253,6 +236,11 @@ export const OBSERVE_TOOLS = [
253
236
  const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), waitBudget, WALL_CLOCK);
254
237
  // `until` is act_and_wait's name for this — see alias-args.ts.
255
238
  const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
239
+ // Refused up front rather than waited out: a body clause this session cannot answer would
240
+ // burn the whole timeout to report something knowable now. See #801(C).
241
+ const bodyRefusal = bodyClauseRefusal(predicate, session);
242
+ if (bodyRefusal !== undefined)
243
+ throw new Error(bodyRefusal);
256
244
  // Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
257
245
  const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
258
246
  const verdict = await waitForPredicate(session, predicate, waitBudget, since);
@@ -280,7 +268,7 @@ export const OBSERVE_TOOLS = [
280
268
  // is the most common thing an agent wants to assert. A field report reached us from an agent
281
269
  // that guessed `urlContains` on route (net's spelling) and got unrecognized_keys.
282
270
  'Predicate to evaluate. Kinds: { signal, name|dataMatches|count } ' +
283
- '{ net, urlContains|method|status|count|bodyContains } ' +
271
+ '{ net, urlContains|method|status|count|bodyContains|requestBodyContains|requestBodyMatches } ' +
284
272
  '{ state, path|equals } { route, pathname (exact) | contains (path+query+hash) } ' +
285
273
  '{ element, testid|role|text } { text } { console, level|contains|absent } { animation, name } ' +
286
274
  '{ settled } — combine with { allOf | anyOf | not }. Prefer a signal/net/state consequence ' +
@@ -350,6 +338,11 @@ export const OBSERVE_TOOLS = [
350
338
  const session = await resolveSessionWithin(deps.sessions, asString(args['sessionId']), timeout, WALL_CLOCK);
351
339
  // `until` is act_and_wait's name for this — see alias-args.ts.
352
340
  const predicate = parsePredicate(aliasParam(args, 'predicate', ['until'])['predicate']);
341
+ // Refused up front rather than waited out: a body clause this session cannot answer would
342
+ // burn the whole timeout to report something knowable now. See #801(C).
343
+ const bodyRefusal = bodyClauseRefusal(predicate, session);
344
+ if (bodyRefusal !== undefined)
345
+ throw new Error(bodyRefusal);
353
346
  // Honesty: explicit since wins; else default to the last act's cursor; else the whole buffer.
354
347
  const since = asNumber(args['since']) ?? session.lastAct.cursor() ?? 0;
355
348
  // Declared BEFORE the verdict, so the undeclared-change read below finds it open and stays
@@ -492,7 +485,7 @@ export const OBSERVE_TOOLS = [
492
485
  return withSizeCost(noteEmptyRead({
493
486
  calls,
494
487
  ...(droppedOldest > 0 ? { total: matched.length, droppedOldest } : {}),
495
- ...(bodies ? bodiesNotCaptured(calls) : {}),
488
+ ...(bodies ? bodiesNotCaptured(calls, session.sdkVersion) : {}),
496
489
  ...buffer,
497
490
  }, 'calls', { noun: 'network calls' }));
498
491
  },
@@ -502,9 +495,7 @@ export const OBSERVE_TOOLS = [
502
495
  example: { level: 'error' },
503
496
  description: 'Console/error log. Fast path for "were there any errors during this flow?". When a level filter matches nothing, returns a `hint` { totalInWindow, byLevel } so 0 errors is distinguishable from a silent page.',
504
497
  inputSchema: {
505
- level: consoleLevelEnum
506
- .optional()
507
- .describe(`Log level filter: ${CONSOLE_LEVEL_LIST}. Omit to return all levels.`),
498
+ level: consoleLevelEnum.optional().describe('Log level filter. Omit to return all levels.'),
508
499
  since: cursorSchema
509
500
  .optional()
510
501
  .describe('Cursor from a prior reticle_act — scopes the query to log entries after that act.'),
@@ -556,7 +547,12 @@ export const OBSERVE_TOOLS = [
556
547
  // returning read here was already wired to.
557
548
  return withSizeCost(noteEmptyRead(droppedOldest > 0
558
549
  ? { logs, total: matched.length, droppedOldest, ...buffer }
559
- : { logs, ...buffer }, 'logs', { noun: 'console lines' }));
550
+ : { logs, ...buffer }, 'logs',
551
+ // Same gate as the predicate path, for the same reason and at the same price: the blind
552
+ // stretch is between page load and attach, so only a window that starts at attach can
553
+ // contain it. A read since an action already began after the channel was live, and
554
+ // stapling 369 bytes onto every quiet read would be paying for a caveat that is not true.
555
+ { noun: 'console lines', ...(0 === since ? { caveat: CONSOLE_ATTACH_NOTE } : {}) }));
560
556
  },
561
557
  },
562
558
  {
@@ -15,6 +15,15 @@ interface EmptyReadContext {
15
15
  windowMs?: number;
16
16
  /** What was being counted, in the agent's words: "console lines", "network calls". */
17
17
  noun: string;
18
+ /**
19
+ * What this channel could NOT have seen, when it has a structural blind spot.
20
+ *
21
+ * "The observation ran and found none" is only the whole truth for a channel that was watching
22
+ * the entire window. The console is not one: it does not exist until the SDK patches it, so a
23
+ * clean read cannot speak for what the page logged before that. Saying so here is the difference
24
+ * between a result and a confident false green — omitted for every channel that has no such gap.
25
+ */
26
+ caveat?: string;
18
27
  }
19
28
  /**
20
29
  * Add `observed: true` and a note when `key` holds an EMPTY array. Returns the result untouched
@@ -28,6 +28,7 @@ export function noteEmptyRead(result, key, context) {
28
28
  ...result,
29
29
  // The distinction the field sweep could not make: this is a RESULT, not a failure to look.
30
30
  observed: true,
31
- note: `no ${context.noun}${window}; the observation ran and found none, which is a result, not a missing reading`,
31
+ note: `no ${context.noun}${window}; the observation ran and found none, which is a result, not a missing reading` +
32
+ (context.caveat === undefined ? '' : ` — ${context.caveat}`),
32
33
  };
33
34
  }
@@ -8,6 +8,7 @@ import { EventType, ReticleCommand, REPLAY_PROGRAM_VERSION, SnapshotMode, Storag
8
8
  import { ReticleTool } from './tool-names.js';
9
9
  import { advanceMsSchema, depthSchema } from './numeric-bounds.js';
10
10
  import { proposeConsequences } from '../oracles/propose-consequences.js';
11
+ import { recordingBacktrackWarning, routesFromRecording } from '../flows/recording-backtrack.js';
11
12
  import { replayProgram } from '../flows/replay.js';
12
13
  import { diffLines } from '../project/baselines.js';
13
14
  import { selectPath, capDepth, projectComponentState } from '../session/state-select.js';
@@ -16,6 +17,7 @@ import { buildReactionReport, summarizeReaction } from '../events/reaction.js';
16
17
  import { asString, asNumber, parseInteractive } from './tools-helpers.js';
17
18
  import { sessionIdShape, commandOrThrow, snapshotTree } from './tool-kit.js';
18
19
  import { bufferEnvelope } from '../session/session-health.js';
20
+ import { routeOfUrl } from '../events/predicate-route.js';
19
21
  /** The route part of a session URL. A host belongs to the machine, not to the journey. */
20
22
  /**
21
23
  * The page a recording started on, as a NAVIGABLE path: pathname + hash.
@@ -28,13 +30,8 @@ import { bufferEnvelope } from '../session/session-health.js';
28
30
  function pathnameOf(url) {
29
31
  if (url === undefined)
30
32
  return undefined;
31
- try {
32
- const parsed = new URL(url);
33
- return `${parsed.pathname}${parsed.hash}`;
34
- }
35
- catch {
36
- return undefined;
37
- }
33
+ const parts = routeOfUrl(url);
34
+ return parts === undefined ? undefined : `${parts.docPath}${parts.hash}`;
38
35
  }
39
36
  /**
40
37
  * What record-stop says about a step that compiled to no anchor at all.
@@ -58,7 +55,6 @@ function unanchoredWarning(count) {
58
55
  }
59
56
  /** Severities the presenter HUD can render. */
60
57
  const HUD_LEVELS = ['info', 'warn', 'error'];
61
- const HUD_LEVEL_LIST = HUD_LEVELS.join(' | ');
62
58
  const hudLevelEnum = z.enum(HUD_LEVELS);
63
59
  /**
64
60
  * How deep an unscoped `reticle_state` read goes before collapsing to size markers.
@@ -171,7 +167,7 @@ export const READ_TOOLS = [
171
167
  },
172
168
  {
173
169
  name: ReticleTool.RECORD_STOP,
174
- description: 'Stop the recording identified by `recordingName` and return both the reaction report for the span and a compiled, replayable { program: { version, steps:[{tool,args,stable}] } } of the agent acts captured during it.',
170
+ description: 'Stop the recording identified by `recordingName` and return both the reaction report for the span and a compiled, replayable { program: { version, steps:[{tool,args,stable}] } } of the agent acts captured during it. `warning` is present when the window left a page and returned to it — replay has no navigation steps, so the next click will look for a control on a page the tab is no longer on.',
175
171
  inputSchema: {
176
172
  recordingName: z
177
173
  .string()
@@ -201,11 +197,13 @@ export const READ_TOOLS = [
201
197
  : `no active recording named '${name}'; in progress: ${active.map((r) => `'${r}'`).join(', ')}`);
202
198
  }
203
199
  const events = session.eventsSince(rec.cursor);
200
+ const routes = routesFromRecording(rec.startPath, events);
204
201
  const program = {
205
202
  name,
206
203
  version: REPLAY_PROGRAM_VERSION,
207
204
  steps: rec.steps,
208
205
  ...(rec.startPath === undefined ? {} : { startPath: rec.startPath }),
206
+ ...(0 === routes.length ? {} : { routes }),
209
207
  };
210
208
  deps.recordings.saveCompiled(program);
211
209
  const unstable = rec.steps.filter((s) => !s.stable).length;
@@ -224,15 +222,14 @@ export const READ_TOOLS = [
224
222
  timeline_omitted: `${String(events.length)} event(s) were recorded and are not included here. Call reticle_observe { since: ${String(rec.cursor)} } for the raw timeline.`,
225
223
  }
226
224
  : {};
225
+ const unanchored = 0 < unstable ? unanchoredWarning(unstable) : undefined;
226
+ const backtrack = recordingBacktrackWarning(routes);
227
+ const warning = [unanchored, backtrack].filter((part) => part !== undefined);
227
228
  const body = {
228
229
  recordingName: name,
229
230
  program,
230
231
  ...timeline,
231
- ...(unstable > 0
232
- ? {
233
- warning: unanchoredWarning(unstable),
234
- }
235
- : {}),
232
+ ...(0 === warning.length ? {} : { warning: warning.join(' ') }),
236
233
  ...(proposedConsequences.length > 0 ? { proposedConsequences } : {}),
237
234
  ...digest,
238
235
  };
@@ -285,9 +282,7 @@ export const READ_TOOLS = [
285
282
  .describe('Short sentence describing your next action, shown on the presenter HUD for the developer watching.'),
286
283
  // Derived, like every other advertised vocabulary here: a free string let a typo through to
287
284
  // a HUD that then rendered an unknown severity.
288
- level: hudLevelEnum
289
- .optional()
290
- .describe(`Display severity: ${HUD_LEVEL_LIST}. Default: info.`),
285
+ level: hudLevelEnum.optional().describe('Display severity. Default: info.'),
291
286
  ...sessionIdShape,
292
287
  },
293
288
  outputSchema: { ok: z.boolean() },
@@ -12,15 +12,21 @@
12
12
  * bridge, so the browser side sees a normal `{ content, name, type }` call and `assertUploadArgs`
13
13
  * keeps its invariant (no fabricated bytes, no silently-dropped keys).
14
14
  *
15
- * Trust boundary: scoped to the project root (one level above `deps.reticleRoot`), resolved
16
- * through `realpath` so symlinks cannot escape it. Sensitive files (.env*, .git/, *.pem, id_*,
17
- * .npmrc) are denied with a message that says why. The cap is derived from the bridge's own
18
- * MAX_MESSAGE_BYTES with the base64 4/3 inflation factor applied, so the encoded payload always
19
- * fits in one WebSocket frame.
15
+ * Trust boundary: scoped to the project root (one level above `deps.reticleRoot`) plus any
16
+ * opt-in `uploadRoots` declared in `.reticle.json`, resolved through `realpath` so symlinks
17
+ * cannot escape an allowed root. Sensitive files (.env*, .git/, *.pem, id_*, .npmrc) are denied
18
+ * with a message that says why. The cap is derived from the bridge's own MAX_MESSAGE_BYTES with
19
+ * the base64 4/3 inflation factor applied, so the encoded payload always fits in one WebSocket
20
+ * frame.
20
21
  */
21
22
  import { ActionType, InputModeReason } from '@reticlehq/core';
22
23
  import type { Session } from '../session/session.js';
23
24
  import { type ToolDeps } from './tool-kit.js';
25
+ /**
26
+ * Expand a leading `~` against the caller's home directory. Absolute and relative paths pass
27
+ * through unchanged. `home` is injected so unit tests do not depend on the machine's real home.
28
+ */
29
+ export declare function expandUserPath(rawPath: string, home: string): string;
24
30
  /**
25
31
  * If the action is `upload` AND the inner args carry `path`, read the file from disk and rewrite
26
32
  * the args to `{ content, name, type, __base64: true }` before the ACT command reaches the browser.
@@ -30,7 +36,9 @@ import { type ToolDeps } from './tool-kit.js';
30
36
  * This is the SINGLE interception point — called from session.command() so flow replay, crawl,
31
37
  * and every other ACT dispatch site are covered without per-site wiring.
32
38
  */
33
- export declare function rewriteUploadArgs(deps: Pick<ToolDeps, 'fs' | 'reticleRoot'>, action: string, innerArgs: Record<string, unknown>): Promise<Record<string, unknown>>;
39
+ export declare function rewriteUploadArgs(deps: Pick<ToolDeps, 'fs' | 'reticleRoot'>, action: string, innerArgs: Record<string, unknown>,
40
+ /** Injected for tests; production uses the process home directory. */
41
+ home?: string): Promise<Record<string, unknown>>;
34
42
  interface RealActResult {
35
43
  /** Defined only on a successful native action; `undefined` means the synthetic path runs. */
36
44
  result: unknown;
@@ -12,11 +12,12 @@
12
12
  * bridge, so the browser side sees a normal `{ content, name, type }` call and `assertUploadArgs`
13
13
  * keeps its invariant (no fabricated bytes, no silently-dropped keys).
14
14
  *
15
- * Trust boundary: scoped to the project root (one level above `deps.reticleRoot`), resolved
16
- * through `realpath` so symlinks cannot escape it. Sensitive files (.env*, .git/, *.pem, id_*,
17
- * .npmrc) are denied with a message that says why. The cap is derived from the bridge's own
18
- * MAX_MESSAGE_BYTES with the base64 4/3 inflation factor applied, so the encoded payload always
19
- * fits in one WebSocket frame.
15
+ * Trust boundary: scoped to the project root (one level above `deps.reticleRoot`) plus any
16
+ * opt-in `uploadRoots` declared in `.reticle.json`, resolved through `realpath` so symlinks
17
+ * cannot escape an allowed root. Sensitive files (.env*, .git/, *.pem, id_*, .npmrc) are denied
18
+ * with a message that says why. The cap is derived from the bridge's own MAX_MESSAGE_BYTES with
19
+ * the base64 4/3 inflation factor applied, so the encoded payload always fits in one WebSocket
20
+ * frame.
20
21
  */
21
22
  import { ActionType, InputModeReason, ReticleCommand, TRANSPORT_LIMITS } from '@reticlehq/core';
22
23
  import { boxCenter, isPointerAction } from '../input/real-input.js';
@@ -25,7 +26,9 @@ import { NATIVE_INPUT_ARG } from '@reticlehq/core';
25
26
  import { asString, asRecord } from './tools-helpers.js';
26
27
  import { commandOrThrow } from './tool-kit.js';
27
28
  import { asBox } from './act-helpers.js';
28
- import { isAbsolute, join, relative, extname, basename } from 'node:path';
29
+ import { isAbsolute, join, relative, extname, basename, resolve } from 'node:path';
30
+ import { homedir } from 'node:os';
31
+ import { RETICLE_CONFIG_BASENAME } from '../cli/cli-port.js';
29
32
  /**
30
33
  * Minimal extension → MIME-type table for the file types agents most commonly upload.
31
34
  * Falls back to `application/octet-stream` for anything not listed here — the browser and
@@ -92,28 +95,79 @@ function isDeniedPath(resolvedPath) {
92
95
  * that configures the socket so they can never drift.
93
96
  */
94
97
  const UPLOAD_MAX_BYTES = Math.floor((TRANSPORT_LIMITS.MAX_MESSAGE_BYTES / (4 / 3)) * 0.75);
98
+ /**
99
+ * Expand a leading `~` against the caller's home directory. Absolute and relative paths pass
100
+ * through unchanged. `home` is injected so unit tests do not depend on the machine's real home.
101
+ */
102
+ export function expandUserPath(rawPath, home) {
103
+ if ('~' === rawPath)
104
+ return home;
105
+ if (rawPath.startsWith('~/') || rawPath.startsWith('~\\'))
106
+ return join(home, rawPath.slice(2));
107
+ return rawPath;
108
+ }
109
+ /** True when `real` is inside `root` after both have been realpath'd / resolved. */
110
+ function isInsideRoot(real, root) {
111
+ const rel = relative(root, real);
112
+ return !rel.startsWith('..') && !isAbsolute(rel);
113
+ }
114
+ /**
115
+ * Opt-in extra upload directories from `.reticle.json` (`uploadRoots`). Absent, empty, or unreadable
116
+ * config means project-root only — the previous default, kept on purpose.
117
+ */
118
+ async function readUploadRoots(projectRoot, fs, home) {
119
+ const configPath = join(projectRoot, RETICLE_CONFIG_BASENAME);
120
+ let raw;
121
+ try {
122
+ raw = await fs.readFile(configPath);
123
+ }
124
+ catch {
125
+ return [];
126
+ }
127
+ let parsed;
128
+ try {
129
+ parsed = JSON.parse(raw);
130
+ }
131
+ catch {
132
+ return [];
133
+ }
134
+ if ('object' !== typeof parsed || null === parsed || Array.isArray(parsed))
135
+ return [];
136
+ const listed = parsed['uploadRoots'];
137
+ if (!Array.isArray(listed))
138
+ return [];
139
+ const roots = [];
140
+ for (const entry of listed) {
141
+ if ('string' !== typeof entry || 0 === entry.trim().length)
142
+ continue;
143
+ roots.push(resolve(expandUserPath(entry.trim(), home)));
144
+ }
145
+ return roots;
146
+ }
95
147
  /**
96
148
  * Resolve and validate a caller-supplied upload path.
97
149
  *
98
- * 1. Resolve to absolute (join against project root for relative paths).
150
+ * 1. Expand a leading `~`, then resolve to absolute (join against project root for relative paths).
99
151
  * 2. Call `realpath` to follow symlinks — `relative()` is lexical and a symlink inside the tree
100
152
  * can point outside it; realpath is the only reliable check.
101
- * 3. Confirm the real path is within the project root.
153
+ * 3. Confirm the real path is within the project root or an opt-in `uploadRoots` entry.
102
154
  * 4. Confirm the path does not match the sensitive-file deny-list.
103
155
  *
104
156
  * Returns the resolved real path on success. Throws with a user-readable message on any violation.
105
157
  */
106
- async function resolveUploadPath(rawPath, projectRoot, fs) {
107
- const abs = isAbsolute(rawPath) ? rawPath : join(projectRoot, rawPath);
158
+ async function resolveUploadPath(rawPath, projectRoot, fs, uploadRoots, home) {
159
+ const expanded = expandUserPath(rawPath, home);
160
+ const abs = isAbsolute(expanded) ? expanded : join(projectRoot, expanded);
108
161
  // realpath resolves symlinks; it also rejects ENOENT, so we get a clear missing-file error.
109
162
  const real = await fs.realpath(abs).catch(() => {
110
163
  throw new Error(`upload path '${rawPath}' could not be read: file not found or not accessible.`);
111
164
  });
112
- const rel = relative(projectRoot, real);
113
- if (rel.startsWith('..') || isAbsolute(rel)) {
114
- throw new Error(`upload path '${rawPath}' resolves to '${real}', which is outside the project root ` +
115
- `'${projectRoot}' — only files within the project directory may be uploaded. ` +
116
- 'Use a path relative to the project root, or an absolute path inside it.');
165
+ const allowed = [projectRoot, ...uploadRoots];
166
+ if (!allowed.some((root) => isInsideRoot(real, root))) {
167
+ const listed = allowed.map((r) => `'${r}'`).join(', ');
168
+ throw new Error(`upload path '${rawPath}' resolves to '${real}', which is outside every allowed upload root ` +
169
+ `(${listed}). Paths must sit under the project directory or an entry in ` +
170
+ `\`.reticle.json\`'s \`uploadRoots\` (e.g. ["~/Downloads"]).`);
117
171
  }
118
172
  if (isDeniedPath(real)) {
119
173
  throw new Error(`upload path '${rawPath}' matches a sensitive-file pattern and cannot be read by the daemon. ` +
@@ -130,16 +184,19 @@ async function resolveUploadPath(rawPath, projectRoot, fs) {
130
184
  * This is the SINGLE interception point — called from session.command() so flow replay, crawl,
131
185
  * and every other ACT dispatch site are covered without per-site wiring.
132
186
  */
133
- export async function rewriteUploadArgs(deps, action, innerArgs) {
187
+ export async function rewriteUploadArgs(deps, action, innerArgs,
188
+ /** Injected for tests; production uses the process home directory. */
189
+ home = homedir()) {
134
190
  if (action !== ActionType.UPLOAD)
135
191
  return innerArgs;
136
192
  const rawPath = asString(innerArgs['path']);
137
193
  if (rawPath === undefined)
138
194
  return innerArgs; // no path → browser handles as inline upload
139
195
  // Project root is one level above .reticle/
140
- const projectRoot = join(deps.reticleRoot, '..');
196
+ const projectRoot = resolve(join(deps.reticleRoot, '..'));
197
+ const uploadRoots = await readUploadRoots(projectRoot, deps.fs, home);
141
198
  // Blocker 4 fix: stat FIRST, before allocating read buffer, so a huge file fails fast.
142
- const realPath = await resolveUploadPath(rawPath, projectRoot, deps.fs);
199
+ const realPath = await resolveUploadPath(rawPath, projectRoot, deps.fs, uploadRoots, home);
143
200
  const fileStat = await deps.fs.stat(realPath).catch(() => {
144
201
  throw new Error(`upload path '${rawPath}' could not be read: file not found or not accessible.`);
145
202
  });
@@ -1,3 +1,4 @@
1
+ import { bodyCaptureRemedy } from '../honesty/body-capture-remedy.js';
1
2
  import { z } from 'zod';
2
3
  import { EventType, ReticleCommand, SnapshotMode, urlForMatch } from '@reticlehq/core';
3
4
  import { ReticleTool } from './tool-names.js';
@@ -86,7 +87,7 @@ export const RECONCILE_TOOLS = [
86
87
  compared: 0,
87
88
  note: 0 === total
88
89
  ? 'no responses in this window matched — nothing was compared'
89
- : `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared. Enable it where your app calls connect(): \`reticle.connect({ captureNetworkBodies: true })\`, then re-run`,
90
+ : `${String(total)} response(s) matched but NONE carried a recorded body, so nothing was compared. ${bodyCaptureRemedy(session.sdkVersion)}`,
90
91
  });
91
92
  }
92
93
  // The whole page, INCLUDING whatever the snapshot's node cap cut off. A comparison against a
@@ -11,4 +11,9 @@
11
11
  * remembers the id in sessionStorage so a reload rejoins the SAME session instead of appearing as a
12
12
  * new one. This is the remaining half: saying that the page is not back yet.
13
13
  */
14
- export declare function reloadResult(reconnected?: boolean): Record<string, unknown>;
14
+ /**
15
+ * `waitedMs` is the budget that expired, reported on `confirmed:false` for the same reason the URL
16
+ * branch reports it (navigate-result.ts): so "Reticle stopped waiting" is distinguishable from "the
17
+ * page never came back", and the fix — a larger `timeout_ms` — is visible in the result.
18
+ */
19
+ export declare function reloadResult(reconnected: boolean, waitedMs: number): Record<string, unknown>;
@@ -22,8 +22,13 @@ const RELOAD_NOTE = 'ok means the reload was DISPATCHED, not that the page came
22
22
  */
23
23
  const RELOAD_RECONNECTED_NOTE = 'the page came back and re-announced itself under the same session id, so this session is live ' +
24
24
  'again — no re-selection needed. Anything captured before the reload is gone with the old document.';
25
- export function reloadResult(reconnected = false) {
25
+ /**
26
+ * `waitedMs` is the budget that expired, reported on `confirmed:false` for the same reason the URL
27
+ * branch reports it (navigate-result.ts): so "Reticle stopped waiting" is distinguishable from "the
28
+ * page never came back", and the fix — a larger `timeout_ms` — is visible in the result.
29
+ */
30
+ export function reloadResult(reconnected, waitedMs) {
26
31
  return reconnected
27
32
  ? { ok: true, confirmed: true, note: RELOAD_RECONNECTED_NOTE }
28
- : { ok: true, confirmed: false, note: RELOAD_NOTE };
33
+ : { ok: true, confirmed: false, waitedMs, note: RELOAD_NOTE };
29
34
  }