@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -9,7 +9,6 @@
9
9
  * The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
10
10
  * the thin Playwright adapter that satisfies these interfaces lives separately.
11
11
  */
12
- /** The minimal page surface the pool drives. Real Playwright `Page` satisfies this. */
13
12
  export interface PooledPage {
14
13
  goto(url: string, opts?: {
15
14
  timeoutMs?: number;
@@ -17,6 +16,27 @@ export interface PooledPage {
17
16
  close(): Promise<void>;
18
17
  /** Fires when THIS page's renderer crashes — lets the pool reclaim just this lease, not the fleet. */
19
18
  onCrash(handler: () => void): void;
19
+ /**
20
+ * Fires for each console message the page logs. OPTIONAL: a fake that does not implement it makes
21
+ * the pool record nothing, which is the correct degradation — an absent dial address must read as
22
+ * "the page said nothing", never as "the page dialled correctly".
23
+ */
24
+ onConsole?(handler: (text: string) => void): void;
25
+ /**
26
+ * Capture the page as a PNG. OPTIONAL, like `onConsole`: a fake that does not implement it makes
27
+ * the pool report "no provider", which is the correct degradation — an absent screenshotter must
28
+ * read as "this context cannot be captured", never as a blank image that a visual diff would then
29
+ * compare against and pass.
30
+ */
31
+ screenshot?(opts?: {
32
+ fullPage?: boolean;
33
+ }): Promise<Uint8Array>;
34
+ /**
35
+ * Move the real pointer to (x, y) so CSS `:hover` applies. OPTIONAL, like `screenshot`: a fake
36
+ * that does not implement it makes hover refuse rather than dispatch a synthetic mouseover that
37
+ * reports dispatched/settled while the styles never ran.
38
+ */
39
+ hover?(x: number, y: number): Promise<void>;
20
40
  }
21
41
  /** An isolated browsing context (cookies/storage). Real Playwright `BrowserContext` satisfies this. */
22
42
  export interface PooledContext {
@@ -69,6 +89,16 @@ export declare class BrowserPool {
69
89
  queuedCount(): number;
70
90
  /** The sessionIds of every currently leased context — used to report/group leased sessions. */
71
91
  leasedSessionIds(): string[];
92
+ /**
93
+ * The public session id of an active lease on this origin, if we already hold one.
94
+ *
95
+ * The lease TOOL reuses that id instead of minting a second context: a second acquire on the same
96
+ * origin used to leave both tabs connected and poison default session resolution (#600). The pool's
97
+ * own `acquire` still mints — the parallel suite needs isolation on purpose.
98
+ *
99
+ * Prefers the alias the agent was given, when the app registered under its own name.
100
+ */
101
+ leaseIdOnOrigin(origin: string): string | undefined;
72
102
  /**
73
103
  * Release a lease by its sessionId (closes the context, frees the slot). No-op if unknown.
74
104
  *
@@ -96,16 +126,50 @@ export declare class BrowserPool {
96
126
  * Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
97
127
  * forever. Returns the released sessionIds. This is the fault-tolerance backstop for the pool.
98
128
  */
129
+ /**
130
+ * Capture a leased page, or undefined when this session is not a lease or cannot be captured.
131
+ *
132
+ * A lease IS a real browser page, so the pixels were always available — the visual tools simply had
133
+ * no route to them and answered "no provider" for every context an agent can actually get. That
134
+ * made visual regression impossible on exactly the isolated contexts the pool exists to hand out.
135
+ *
136
+ * Touches the lease like any other tool call, so capturing keeps it alive rather than letting the
137
+ * reaper take it out from under a diff.
138
+ */
139
+ screenshotLease(sessionId: string, opts?: {
140
+ fullPage?: boolean;
141
+ }): Promise<Uint8Array | undefined>;
142
+ /**
143
+ * Move the real pointer on a leased page, or false when this session is not a lease or cannot
144
+ * hover. Same optional-capability contract as screenshotLease: absence must read as "could not
145
+ * hover", never as a successful dispatch of a synthetic mouseover.
146
+ *
147
+ * Touches the lease like any other tool call, so hovering keeps it alive.
148
+ */
149
+ hoverLease(sessionId: string, x: number, y: number): Promise<boolean>;
99
150
  sweepExpired(): Promise<string[]>;
100
151
  /**
101
152
  * How many leases this pool has reclaimed for going stale.
102
153
  *
103
- * Read by the no-session diagnosis: an aged-out lease used to be reported as "the tab was closed
104
- * … ask the human to reopen the app", which is wrong on every clause and sent one reporter looking
105
- * for a port mismatch (#157). A count is enough — the message says "likeliest", not "certainly",
106
- * because nothing here knows WHICH session the caller just lost.
154
+ * Kept for reporting. NOT a basis for diagnosis: it is a lifetime total that never resets, so
155
+ * `count > 0` latches true forever after the first reap. Ask `wasReapedLease` instead, which
156
+ * answers about a specific session.
107
157
  */
108
158
  reapedLeaseCount(): number;
159
+ /**
160
+ * Did THIS session id belong to a lease this pool aged out?
161
+ *
162
+ * The question the no-session diagnosis actually has. It used to ask `reapedLeaseCount() > 0`,
163
+ * which meant that once any lease had ever aged out, every closed human tab for the rest of the
164
+ * daemon's life was reported as an expired lease — and the recovery that followed
165
+ * (`reticle_lease {acquire}`) would open an unauthenticated headless context and lose the app
166
+ * session (#611).
167
+ *
168
+ * Only the last {@link MAX_REMEMBERED_REAPED_LEASES} are remembered, so this answers `false` for
169
+ * a lease reaped long enough ago that thousands have followed. That direction is the safe one:
170
+ * it falls back to the generic "the tab was closed" wording rather than inventing a lease.
171
+ */
172
+ wasReapedLease(sessionId: string): boolean;
109
173
  /**
110
174
  * Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
111
175
  * waits FIFO until a slot frees (or until `signal` aborts, if provided).
@@ -114,6 +178,12 @@ export declare class BrowserPool {
114
178
  signal?: AbortSignal;
115
179
  sessionId?: string;
116
180
  }): Promise<Lease>;
181
+ /**
182
+ * The websocket address this lease's page reported it could not reach, if it reported one.
183
+ *
184
+ * Undefined means the page said nothing — never that it dialled the right place.
185
+ */
186
+ dialFailureUrl(sessionId: string): string | undefined;
117
187
  /** Close every context and the browser. Pending waiters are rejected (the pool is terminal now). */
118
188
  shutdown(): Promise<void>;
119
189
  }
@@ -9,10 +9,17 @@
9
9
  * The browser is injected via a `Launcher` so all lifecycle logic is testable without real Chromium;
10
10
  * the thin Playwright adapter that satisfies these interfaces lives separately.
11
11
  */
12
+ /** The minimal page surface the pool drives. Real Playwright `Page` satisfies this. */
13
+ import { unreachableUrlIn } from '@reticlehq/core';
12
14
  /** Default lease time-to-live: a lease untouched for this long is presumed orphaned and reclaimed. */
13
15
  export const DEFAULT_LEASE_TTL_MS = 5 * 60_000;
14
16
  /** Default cap on how long a lease's initial navigation may take before it fails (frees the slot). */
15
17
  const DEFAULT_NAV_TIMEOUT_MS = 30_000;
18
+ /**
19
+ * How many reaped lease ids to remember. Only the most recent departure is ever asked about, so
20
+ * this is slack for a burst sweep, not a history.
21
+ */
22
+ const MAX_REMEMBERED_REAPED_LEASES = 64;
16
23
  /**
17
24
  * Owns a single browser and leases isolated contexts out of it, capped at `maxContexts`.
18
25
  * Not exported as a singleton — the daemon owns one instance.
@@ -29,6 +36,18 @@ export class BrowserPool {
29
36
  #closed = false;
30
37
  /** Leases reclaimed for going stale — see reapedLeaseCount(). */
31
38
  #reapedLeases = 0;
39
+ /**
40
+ * The session ids of recently reaped leases, most recent last.
41
+ *
42
+ * The count above answers "has anything ever aged out", which is not a
43
+ * question any caller actually has: it latches true for the life of the
44
+ * daemon (#611). The ids answer the question the no-session diagnosis is
45
+ * really asking — was THIS session a lease that aged out — so a closed
46
+ * human tab is no longer blamed on an expired lease forever after.
47
+ *
48
+ * Bounded because a long-lived daemon reaps unboundedly many.
49
+ */
50
+ #reapedLeaseIds = [];
32
51
  #active = new Map();
33
52
  /** Registered-session-id → lease id, for apps that keep their own session name. See `alias`. */
34
53
  #aliases = new Map();
@@ -71,6 +90,34 @@ export class BrowserPool {
71
90
  leasedSessionIds() {
72
91
  return [...this.#active.keys()];
73
92
  }
93
+ /**
94
+ * The public session id of an active lease on this origin, if we already hold one.
95
+ *
96
+ * The lease TOOL reuses that id instead of minting a second context: a second acquire on the same
97
+ * origin used to leave both tabs connected and poison default session resolution (#600). The pool's
98
+ * own `acquire` still mints — the parallel suite needs isolation on purpose.
99
+ *
100
+ * Prefers the alias the agent was given, when the app registered under its own name.
101
+ */
102
+ leaseIdOnOrigin(origin) {
103
+ for (const [leaseId, lease] of this.#active) {
104
+ let leaseOrigin;
105
+ try {
106
+ leaseOrigin = new URL(lease.url).origin;
107
+ }
108
+ catch {
109
+ continue;
110
+ }
111
+ if (leaseOrigin !== origin)
112
+ continue;
113
+ for (const [alias, id] of this.#aliases) {
114
+ if (id === leaseId)
115
+ return alias;
116
+ }
117
+ return leaseId;
118
+ }
119
+ return undefined;
120
+ }
74
121
  /**
75
122
  * Release a lease by its sessionId (closes the context, frees the slot). No-op if unknown.
76
123
  *
@@ -114,6 +161,50 @@ export class BrowserPool {
114
161
  * Reclaim leases untouched for longer than the TTL — a hung/crashed agent never holds a slot
115
162
  * forever. Returns the released sessionIds. This is the fault-tolerance backstop for the pool.
116
163
  */
164
+ /**
165
+ * Capture a leased page, or undefined when this session is not a lease or cannot be captured.
166
+ *
167
+ * A lease IS a real browser page, so the pixels were always available — the visual tools simply had
168
+ * no route to them and answered "no provider" for every context an agent can actually get. That
169
+ * made visual regression impossible on exactly the isolated contexts the pool exists to hand out.
170
+ *
171
+ * Touches the lease like any other tool call, so capturing keeps it alive rather than letting the
172
+ * reaper take it out from under a diff.
173
+ */
174
+ async screenshotLease(sessionId, opts = {}) {
175
+ const lease = this.#active.get(sessionId);
176
+ if (lease === undefined || lease.page.screenshot === undefined)
177
+ return undefined;
178
+ this.touch(sessionId);
179
+ try {
180
+ return await lease.page.screenshot(opts);
181
+ }
182
+ catch {
183
+ // A capture that fails is not a broken lease. Report "could not capture" and leave the lease
184
+ // usable — the alternative is losing a working context to one bad frame.
185
+ return undefined;
186
+ }
187
+ }
188
+ /**
189
+ * Move the real pointer on a leased page, or false when this session is not a lease or cannot
190
+ * hover. Same optional-capability contract as screenshotLease: absence must read as "could not
191
+ * hover", never as a successful dispatch of a synthetic mouseover.
192
+ *
193
+ * Touches the lease like any other tool call, so hovering keeps it alive.
194
+ */
195
+ async hoverLease(sessionId, x, y) {
196
+ const lease = this.#active.get(sessionId);
197
+ if (lease === undefined || lease.page.hover === undefined)
198
+ return false;
199
+ this.touch(sessionId);
200
+ try {
201
+ await lease.page.hover(x, y);
202
+ return true;
203
+ }
204
+ catch {
205
+ return false;
206
+ }
207
+ }
117
208
  async sweepExpired() {
118
209
  const now = this.#now();
119
210
  const expired = [...this.#active.entries()]
@@ -122,19 +213,39 @@ export class BrowserPool {
122
213
  for (const sessionId of expired)
123
214
  await this.#release(sessionId);
124
215
  this.#reapedLeases += expired.length;
216
+ for (const sessionId of expired) {
217
+ this.#reapedLeaseIds.push(sessionId);
218
+ if (this.#reapedLeaseIds.length > MAX_REMEMBERED_REAPED_LEASES)
219
+ this.#reapedLeaseIds.shift();
220
+ }
125
221
  return expired;
126
222
  }
127
223
  /**
128
224
  * How many leases this pool has reclaimed for going stale.
129
225
  *
130
- * Read by the no-session diagnosis: an aged-out lease used to be reported as "the tab was closed
131
- * … ask the human to reopen the app", which is wrong on every clause and sent one reporter looking
132
- * for a port mismatch (#157). A count is enough — the message says "likeliest", not "certainly",
133
- * because nothing here knows WHICH session the caller just lost.
226
+ * Kept for reporting. NOT a basis for diagnosis: it is a lifetime total that never resets, so
227
+ * `count > 0` latches true forever after the first reap. Ask `wasReapedLease` instead, which
228
+ * answers about a specific session.
134
229
  */
135
230
  reapedLeaseCount() {
136
231
  return this.#reapedLeases;
137
232
  }
233
+ /**
234
+ * Did THIS session id belong to a lease this pool aged out?
235
+ *
236
+ * The question the no-session diagnosis actually has. It used to ask `reapedLeaseCount() > 0`,
237
+ * which meant that once any lease had ever aged out, every closed human tab for the rest of the
238
+ * daemon's life was reported as an expired lease — and the recovery that followed
239
+ * (`reticle_lease {acquire}`) would open an unauthenticated headless context and lose the app
240
+ * session (#611).
241
+ *
242
+ * Only the last {@link MAX_REMEMBERED_REAPED_LEASES} are remembered, so this answers `false` for
243
+ * a lease reaped long enough ago that thousands have followed. That direction is the safe one:
244
+ * it falls back to the generic "the tab was closed" wording rather than inventing a lease.
245
+ */
246
+ wasReapedLease(sessionId) {
247
+ return this.#reapedLeaseIds.includes(sessionId);
248
+ }
138
249
  /**
139
250
  * Lease an isolated context, navigate it to `url`, and return a handle. If the pool is at capacity,
140
251
  * waits FIFO until a slot frees (or until `signal` aborts, if provided).
@@ -147,6 +258,7 @@ export class BrowserPool {
147
258
  await this.#waitForSlot(opts.signal);
148
259
  const sessionId = opts.sessionId ?? this.#genId();
149
260
  let context;
261
+ let pending;
150
262
  try {
151
263
  const browser = await this.#ensureBrowser();
152
264
  context = await browser.newContext();
@@ -156,6 +268,19 @@ export class BrowserPool {
156
268
  page.onCrash(() => {
157
269
  void this.#release(sessionId);
158
270
  });
271
+ // Listen BEFORE goto: the SDK's connect attempt races the navigation's own resolution, and a
272
+ // listener attached afterwards misses the first failures — the only ones logged before the
273
+ // retry backoff stretches out.
274
+ page.onConsole?.((text) => {
275
+ const dialled = unreachableUrlIn(text);
276
+ if (dialled === undefined)
277
+ return;
278
+ const active = this.#active.get(sessionId);
279
+ if (active !== undefined)
280
+ active.dialFailureUrl = dialled;
281
+ else
282
+ pending = dialled; // logged during goto, before the lease is registered below
283
+ });
159
284
  await page.goto(url, { timeoutMs: this.#navTimeout });
160
285
  // The browser can crash WHILE goto is resolving; #onCrash then clears #active and zeroes
161
286
  // #occupied. Registering the lease now would resurrect a dead entry against a crashed browser with
@@ -163,7 +288,13 @@ export class BrowserPool {
163
288
  // no longer the live browser, bail — the catch below closes the context and returns the slot.
164
289
  if (this.#browser !== browser)
165
290
  throw new Error('browser crashed during navigation');
166
- this.#active.set(sessionId, { context, page, url, touchedAt: this.#now() });
291
+ this.#active.set(sessionId, {
292
+ context,
293
+ page,
294
+ url,
295
+ touchedAt: this.#now(),
296
+ ...(pending === undefined ? {} : { dialFailureUrl: pending }),
297
+ });
167
298
  return {
168
299
  sessionId,
169
300
  url,
@@ -179,6 +310,14 @@ export class BrowserPool {
179
310
  throw err;
180
311
  }
181
312
  }
313
+ /**
314
+ * The websocket address this lease's page reported it could not reach, if it reported one.
315
+ *
316
+ * Undefined means the page said nothing — never that it dialled the right place.
317
+ */
318
+ dialFailureUrl(sessionId) {
319
+ return this.#active.get(sessionId)?.dialFailureUrl;
320
+ }
182
321
  /** Close every context and the browser. Pending waiters are rejected (the pool is terminal now). */
183
322
  async shutdown() {
184
323
  this.#closed = true; // set first so any woken waiter rejects instead of relaunching a browser
@@ -6,6 +6,7 @@
6
6
  * Headless by default — the pool exists for fault-tolerant headless multi-agent testing.
7
7
  */
8
8
  import { BrowserLaunchKind } from '@reticlehq/core';
9
+ import { chromiumLaunchOptions } from '../chromium-launch-options.js';
9
10
  import { getSessionMetrics } from '../telemetry/session-metrics.js';
10
11
  import { classifyConnectFailure } from '../telemetry/connect-failure.js';
11
12
  import { chromiumInstallCommand, bundledPlaywrightVersion } from '../cli/chromium-hint.js';
@@ -39,7 +40,17 @@ function wrapBrowser(browser) {
39
40
  return {
40
41
  goto: (url, opts) => page.goto(url, gotoOptions(opts?.timeoutMs)),
41
42
  close: () => page.close(),
43
+ // Playwright returns a Buffer; Uint8Array is what the visual store and differ take.
44
+ screenshot: async (opts) => new Uint8Array(await page.screenshot({ fullPage: true === opts?.fullPage })),
45
+ // Three moves, same as performGesture: a single move to the center can be a no-op if
46
+ // the pointer was already there, and CSS :hover needs a native hit-test to apply.
47
+ hover: async (x, y) => {
48
+ await page.mouse.move(x, y);
49
+ await page.mouse.move(x + 1, y);
50
+ await page.mouse.move(x, y);
51
+ },
42
52
  onCrash: (handler) => page.on('crash', handler),
53
+ onConsole: (handler) => page.on('console', (msg) => handler(msg.text())),
43
54
  };
44
55
  },
45
56
  close: () => context.close(),
@@ -72,7 +83,7 @@ export function playwrightLauncher(opts = {}) {
72
83
  // number that actually costs memory and the one that explains a slow machine.
73
84
  const settle = getSessionMetrics().recordConnectAttempt(BrowserLaunchKind.POOLED);
74
85
  try {
75
- const browser = wrapBrowser(await chromium.launch({ headless }));
86
+ const browser = wrapBrowser(await chromium.launch(chromiumLaunchOptions(headless)));
76
87
  settle();
77
88
  return browser;
78
89
  }
@@ -36,7 +36,10 @@ export interface FileSystemPort {
36
36
  /** Modification time in epoch ms — for recency-based retention pruning. Rejects if absent. */
37
37
  stat(path: string): Promise<{
38
38
  mtimeMs: number;
39
+ size: number;
39
40
  }>;
41
+ /** Resolve symlinks to their real path — for upload trust-boundary checks. Rejects if absent. */
42
+ realpath(path: string): Promise<string>;
40
43
  /** ENOENT classifier — narrows unknown without `any`, so callers can distinguish missing-file. */
41
44
  isNotFound(error: unknown): boolean;
42
45
  }
@@ -1,4 +1,4 @@
1
- import { access, appendFile, mkdir, open, readdir, readFile, rename, rm, stat, writeFile, } from 'node:fs/promises';
1
+ import { access, appendFile, mkdir, open, readdir, readFile, realpath, rename, rm, stat, writeFile, } from 'node:fs/promises';
2
2
  /** Production adapter — the single import site of node:fs/promises in the server. */
3
3
  export function createNodeFileSystem() {
4
4
  return {
@@ -46,8 +46,9 @@ export function createNodeFileSystem() {
46
46
  },
47
47
  stat: async (path) => {
48
48
  const s = await stat(path);
49
- return { mtimeMs: s.mtimeMs };
49
+ return { mtimeMs: s.mtimeMs, size: s.size };
50
50
  },
51
+ realpath: (path) => realpath(path),
51
52
  isNotFound: (error) => 'ENOENT' === error?.code,
52
53
  };
53
54
  }
@@ -78,7 +78,12 @@ export function createMemoryFs() {
78
78
  return Promise.resolve();
79
79
  },
80
80
  stat(path) {
81
- return written.has(norm(path)) ? Promise.resolve({ mtimeMs: 0 }) : Promise.reject(notFound());
81
+ return written.has(norm(path))
82
+ ? Promise.resolve({ mtimeMs: 0, size: 0 })
83
+ : Promise.reject(notFound());
84
+ },
85
+ realpath(path) {
86
+ return written.has(norm(path)) ? Promise.resolve(norm(path)) : Promise.reject(notFound());
82
87
  },
83
88
  isNotFound(error) {
84
89
  return 'ENOENT' === error?.code;
@@ -1,9 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { ProjectReadError, RunKind, RunStatus } from '@reticlehq/core';
2
+ import { ProjectReadError, RunStatus } from '@reticlehq/core';
3
3
  import { ReticleTool } from '../tools/tool-names.js';
4
+ import { countSchema } from '../tools/numeric-bounds.js';
4
5
  import { sessionIdShape } from '../tools/tool-kit.js';
5
6
  import { asNumber, asString } from '../tools/tools-helpers.js';
6
- import { fetchProjectRegressionFromCloud, resolveCloudConfig } from '../cloud/cloud-sync.js';
7
+ import { cloudFetch, fetchProjectRegressionFromCloud, resolveCloudConfig, } from '../cloud/cloud-sync.js';
7
8
  import { RunStore } from '../runs/run-store.js';
8
9
  import { diffRuns as diffVerificationRuns, } from '../runs/run-diff.js';
9
10
  const REGRESSION_STATUSES = new Set([
@@ -45,7 +46,7 @@ async function cloudRegression(deps, sessionId) {
45
46
  catch {
46
47
  projectId = undefined;
47
48
  }
48
- const report = await fetchProjectRegressionFromCloud(config, projectId, (url, init) => fetch(url, init));
49
+ const report = await fetchProjectRegressionFromCloud(config, projectId, cloudFetch);
49
50
  return report ?? undefined;
50
51
  }
51
52
  /** The two most-recent runs for `name`, oldest-first, or undefined if there are fewer than two. */
@@ -84,11 +85,10 @@ async function lastTwoRunArtifacts(deps) {
84
85
  export const PROJECT_TOOLS = [
85
86
  {
86
87
  name: ReticleTool.PROJECT,
87
- description: 'Read cross-run history from .reticle/project.json — the memory of how past runs behaved. With { name } it also returns the last run for that flow plus a diff-vs-last summary (status change, regressed flag, consoleErrors/driftSteps deltas) so you can answer "did it behave like last time?". `diff` is the lightweight status/console/drift delta vs the previous run of that NAME; `runDiff` is the per-flow duration/status delta between the two most-recent full verification artifacts ("step 3: 412ms -> 987ms (+140%), +2 requests"). When logged in to Reticle Cloud, also returns `cloud`: the team\'s server-side regression report (broken/changed flows vs before) — the durable memory a fresh or context-lost agent can rely on even when local history is empty. Returns { runs, learned?, lastRun?, diff?, runDiff?, cloud? } or { error, reason, cloud? }.',
88
+ description: 'Read cross-run history from .reticle/project.json — the memory of how past runs behaved. With { name } it also returns the last run for that flow plus a diff-vs-last summary (status change, regressed flag, consoleErrors/driftSteps deltas) so you can answer "did it behave like last time?". `diff` is the lightweight status/console/drift delta vs the previous run of that NAME; `runDiff` is the per-flow duration/status delta between the two most-recent full verification artifacts ("step 3: 412ms -> 987ms (+140%), +2 requests"). When logged in to Reticle, also returns `cloud`: the team\'s server-side regression report (broken/changed flows vs before) — the durable memory a fresh or context-lost agent can rely on even when local history is empty. Returns { runs, learned?, lastRun?, diff?, runDiff?, cloud? } or { error, reason, cloud? }.',
88
89
  inputSchema: {
89
90
  name: z.string().optional().describe('Filter runs by this name. Omit to return all runs.'),
90
- limit: z
91
- .number()
91
+ limit: countSchema
92
92
  .optional()
93
93
  .describe('Most-recent N runs to return. Defaults to 25 — the full history is unbounded and grows with every run (measured at ~20KB / ~5,000 tokens on a modest project), which is a large slice of an agent context for data it mostly does not read. `totalRuns` always reports the true count, so a cap is never silent.'),
94
94
  ...sessionIdShape,
@@ -152,33 +152,4 @@ export const PROJECT_TOOLS = [
152
152
  });
153
153
  },
154
154
  },
155
- {
156
- name: ReticleTool.RUN_RECORD,
157
- description: 'Explicitly record a run outcome into .reticle/project.json (the manual companion to the auto-record on reticle_flow_replay). Use it to log the result of an assertion sequence or a manual journey so future runs can diff against it. Returns { recorded:true, name, status }.',
158
- inputSchema: {
159
- name: z.string().describe('Run name for grouping in reticle_project history.'),
160
- status: z.nativeEnum(RunStatus).describe('Outcome: pass | fail | drift | error'),
161
- kind: z.nativeEnum(RunKind).optional(),
162
- summary: z.string().optional().describe('One-line human summary of what this run covered.'),
163
- ...sessionIdShape,
164
- },
165
- outputSchema: {
166
- recorded: z.boolean(),
167
- runName: z.string(),
168
- status: z.string(),
169
- },
170
- handler: async (deps, args) => {
171
- const name = asString(args['name']) ?? '';
172
- const status = args['status'];
173
- const kindArg = args['kind'];
174
- const summary = asString(args['summary']);
175
- await deps.project.recordRun({
176
- kind: 'string' === typeof kindArg ? kindArg : RunKind.MANUAL,
177
- name,
178
- status,
179
- ...(summary !== undefined ? { summary } : {}),
180
- });
181
- return { recorded: true, runName: name, status };
182
- },
183
- },
184
155
  ];
@@ -15,6 +15,8 @@ export interface ReticleDirPaths {
15
15
  project: string;
16
16
  /**.../.reticle/impact.json (what Reticle has done for this user, local only) */
17
17
  impact: string;
18
+ /**.../.reticle/intent.json (what changes were supposed to make true — git-checked) */
19
+ intent: string;
18
20
  /**.../.reticle/visual (PNG baselines + diffs) */
19
21
  visual: string;
20
22
  /**.../.reticle/runs (verification-run artifacts) */
@@ -53,9 +55,22 @@ export declare function isValidSessionId(sessionId: string): sessionId is Sessio
53
55
  */
54
56
  export declare function isValidRunId(runId: string): runId is RunId;
55
57
  /** The PNG baseline path for `name`.reticle/visual/<name>.png). */
56
- export declare function visualPath(root: string, name: string): string;
58
+ export declare function visualPath(root: string, name: string, runtime?: string): string;
59
+ /**
60
+ * The directory a visual artifact belongs in, scoped by the runtime that produced it.
61
+ *
62
+ * An Electron window, a Tauri webview and a browser tab render the same url differently — different
63
+ * chrome, different fonts, a different device pixel ratio, a webview that is not the browser at all.
64
+ * One shared baseline makes every cross-runtime diff wrong, and the quiet direction is the dangerous
65
+ * one: a baseline overwritten from another runtime turns a later real regression into a pass.
66
+ *
67
+ * Scoped the way flows already are. A DESKTOP runtime gets a subdir; web, an unknown runtime, and
68
+ * everything captured before this existed stay on the flat path — the SDK has no screenshotter, so
69
+ * every pre-existing baseline came from a driven browser, and moving them would orphan them.
70
+ */
71
+ export declare function visualDir(root: string, runtime?: string): string;
57
72
  /** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
58
- export declare function visualDiffPath(root: string, name: string): string;
73
+ export declare function visualDiffPath(root: string, name: string, runtime?: string): string;
59
74
  /**
60
75
  * Path to a flow file. With a `projectId`, the flow lives in a per-project subdir
61
76
  * (`.reticle/flows/<projectId>/<name>.json`) so a shared daemon serving many apps can't collide two
@@ -1,4 +1,5 @@
1
1
  import { join } from 'node:path';
2
+ import { AppRuntime } from '@reticlehq/core';
2
3
  import { CONTRACT_FILE_VERSION, ContractFileSchema, ContractReadError, FLOW_NAME_PATTERN, ReticleDir, } from '@reticlehq/core';
3
4
  export function reticleDirPaths(root) {
4
5
  return {
@@ -8,6 +9,7 @@ export function reticleDirPaths(root) {
8
9
  baselines: join(root, ReticleDir.BASELINES_SUBDIR),
9
10
  project: join(root, ReticleDir.PROJECT_FILE),
10
11
  impact: join(root, ReticleDir.IMPACT_FILE),
12
+ intent: join(root, ReticleDir.INTENT_FILE),
11
13
  visual: join(root, ReticleDir.VISUAL_SUBDIR),
12
14
  runs: join(root, ReticleDir.RUNS_SUBDIR),
13
15
  sessions: join(root, ReticleDir.SESSIONS_SUBDIR),
@@ -50,12 +52,28 @@ export function isValidRunId(runId) {
50
52
  return FLOW_NAME_PATTERN.test(runId) && !runId.includes('..');
51
53
  }
52
54
  /** The PNG baseline path for `name`.reticle/visual/<name>.png). */
53
- export function visualPath(root, name) {
54
- return join(root, ReticleDir.VISUAL_SUBDIR, `${name}.png`);
55
+ export function visualPath(root, name, runtime) {
56
+ return join(visualDir(root, runtime), `${name}.png`);
57
+ }
58
+ /**
59
+ * The directory a visual artifact belongs in, scoped by the runtime that produced it.
60
+ *
61
+ * An Electron window, a Tauri webview and a browser tab render the same url differently — different
62
+ * chrome, different fonts, a different device pixel ratio, a webview that is not the browser at all.
63
+ * One shared baseline makes every cross-runtime diff wrong, and the quiet direction is the dangerous
64
+ * one: a baseline overwritten from another runtime turns a later real regression into a pass.
65
+ *
66
+ * Scoped the way flows already are. A DESKTOP runtime gets a subdir; web, an unknown runtime, and
67
+ * everything captured before this existed stay on the flat path — the SDK has no screenshotter, so
68
+ * every pre-existing baseline came from a driven browser, and moving them would orphan them.
69
+ */
70
+ export function visualDir(root, runtime) {
71
+ const visual = join(root, ReticleDir.VISUAL_SUBDIR);
72
+ return runtime === undefined || AppRuntime.WEB === runtime ? visual : join(visual, runtime);
55
73
  }
56
74
  /** The overlay-diff PNG path for `name`.reticle/visual/<name>.diff.png). */
57
- export function visualDiffPath(root, name) {
58
- return join(root, ReticleDir.VISUAL_SUBDIR, `${name}.diff.png`);
75
+ export function visualDiffPath(root, name, runtime) {
76
+ return join(visualDir(root, runtime), `${name}.diff.png`);
59
77
  }
60
78
  /**
61
79
  * Path to a flow file. With a `projectId`, the flow lives in a per-project subdir
@@ -0,0 +1,2 @@
1
+ import type { ToolDef } from '../tools/tool-kit.js';
2
+ export declare const CONTEXT_TOOLS: ToolDef[];