@reticlehq/server 3.8.2 → 3.10.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 (477) hide show
  1. package/LICENSE +201 -104
  2. package/NOTICE +5 -0
  3. package/README.md +1 -1
  4. package/SKILL.md +13 -12
  5. package/bin/launch.js +13 -0
  6. package/bin/package.json +3 -0
  7. package/bin/reticle.js +66 -8
  8. package/dist/chromium-launch-options.d.ts +25 -0
  9. package/dist/chromium-launch-options.js +21 -1
  10. package/dist/command/cli/adhoc-suite.js +2 -0
  11. package/dist/command/cli/adhoc-verdict.d.ts +2 -43
  12. package/dist/command/cli/adhoc-verdict.js +50 -160
  13. package/dist/command/cli/answers/gate-exit.d.ts +6 -2
  14. package/dist/command/cli/answers/gate-exit.js +12 -2
  15. package/dist/command/cli/answers/mcp-connection-hint.d.ts +2 -0
  16. package/dist/command/cli/answers/mcp-connection-hint.js +6 -0
  17. package/dist/command/cli/ci-hint.d.ts +44 -0
  18. package/dist/command/cli/ci-hint.js +72 -0
  19. package/dist/command/cli/cli-doctor.d.ts +6 -1
  20. package/dist/command/cli/cli-doctor.js +46 -5
  21. package/dist/command/cli/cli-flow-commands.d.ts +7 -1
  22. package/dist/command/cli/cli-flow-commands.js +39 -24
  23. package/dist/command/cli/cli-kill.d.ts +21 -0
  24. package/dist/command/cli/cli-kill.js +47 -4
  25. package/dist/command/cli/cli-parse-compare.d.ts +22 -0
  26. package/dist/command/cli/cli-parse-compare.js +75 -0
  27. package/dist/command/cli/cli-parse-grade.d.ts +36 -0
  28. package/dist/command/cli/cli-parse-grade.js +75 -0
  29. package/dist/command/cli/cli-parse-grammar.d.ts +2 -0
  30. package/dist/command/cli/cli-parse-grammar.js +18 -20
  31. package/dist/command/cli/cli-parse-verify.d.ts +10 -0
  32. package/dist/command/cli/cli-parse-verify.js +63 -14
  33. package/dist/command/cli/cli-parse.d.ts +22 -17
  34. package/dist/command/cli/cli-parse.js +54 -32
  35. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  36. package/dist/command/cli/cli-update-commands.js +29 -20
  37. package/dist/command/cli/cli-usage.js +61 -31
  38. package/dist/command/cli/cli-verify.d.ts +17 -3
  39. package/dist/command/cli/cli-verify.js +59 -21
  40. package/dist/command/cli/cloud-cli.js +2 -119
  41. package/dist/command/cli/daemon-link/daemon-client.d.ts +45 -0
  42. package/dist/command/cli/daemon-link/daemon-client.js +205 -0
  43. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +6 -1
  44. package/dist/command/cli/doctor/browser/chromium-hint.js +11 -5
  45. package/dist/command/cli/doctor/doctor-daemon-line.js +6 -3
  46. package/dist/command/cli/doctor/doctor-fix.d.ts +44 -0
  47. package/dist/command/cli/doctor/doctor-fix.js +89 -0
  48. package/dist/command/cli/doctor/doctor-launcher-line.d.ts +23 -0
  49. package/dist/command/cli/doctor/doctor-launcher-line.js +60 -0
  50. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  51. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  52. package/dist/command/cli/doctor-repairs.d.ts +64 -0
  53. package/dist/command/cli/doctor-repairs.js +214 -0
  54. package/dist/command/cli/drive/drive-command.js +3 -7
  55. package/dist/command/cli/ensure-daemon.d.ts +26 -0
  56. package/dist/command/cli/ensure-daemon.js +57 -0
  57. package/dist/command/cli/finished-run/grade-command.d.ts +23 -0
  58. package/dist/command/cli/finished-run/grade-command.js +180 -0
  59. package/dist/command/cli/first-use-checks.d.ts +41 -0
  60. package/dist/command/cli/first-use-checks.js +127 -0
  61. package/dist/command/cli/first-verdict-node.d.ts +3 -0
  62. package/dist/command/cli/first-verdict-node.js +189 -0
  63. package/dist/command/cli/first-verdict.d.ts +126 -0
  64. package/dist/command/cli/first-verdict.js +267 -0
  65. package/dist/command/cli/launch/sdk-version-match.d.ts +3 -2
  66. package/dist/command/cli/launch/sdk-version-match.js +3 -2
  67. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +1 -1
  68. package/dist/command/cli/lifecycle/daemon-lifecycle.js +7 -6
  69. package/dist/command/cli/mcp-command.js +13 -4
  70. package/dist/command/cli/ports/port-holder.d.ts +11 -0
  71. package/dist/command/cli/ports/port-holder.js +36 -4
  72. package/dist/command/cli/setup-mcp-cli.d.ts +27 -1
  73. package/dist/command/cli/setup-mcp-cli.js +98 -23
  74. package/dist/command/cli/status/status-lines.d.ts +2 -0
  75. package/dist/command/cli/status/status-lines.js +72 -17
  76. package/dist/command/cli/status-fields.d.ts +5 -1
  77. package/dist/command/cli/status-fields.js +24 -6
  78. package/dist/command/cli/try-command.d.ts +7 -0
  79. package/dist/command/cli/try-command.js +11 -3
  80. package/dist/command/cli/two-builds/compare-command.d.ts +48 -0
  81. package/dist/command/cli/two-builds/compare-command.js +314 -0
  82. package/dist/command/cli/verify-against.d.ts +61 -0
  83. package/dist/command/cli/verify-against.js +187 -0
  84. package/dist/command/cli/verify-no-session.d.ts +2 -0
  85. package/dist/command/cli/verify-no-session.js +13 -0
  86. package/dist/command/cli/verify-results.d.ts +35 -2
  87. package/dist/command/cli/verify-results.js +76 -10
  88. package/dist/command/cli.js +112 -70
  89. package/dist/command/daemon/binding/daemon-status-probe.d.ts +3 -1
  90. package/dist/command/daemon/binding/daemon-status-probe.js +4 -2
  91. package/dist/command/daemon/daemon-argv.d.ts +9 -0
  92. package/dist/command/daemon/daemon-argv.js +11 -0
  93. package/dist/command/daemon/daemon-resolve.d.ts +8 -1
  94. package/dist/command/daemon/daemon-resolve.js +27 -1
  95. package/dist/command/daemon/daemon.d.ts +22 -4
  96. package/dist/command/daemon/daemon.js +52 -10
  97. package/dist/command/daemon/lifetime/daemon-exit.d.ts +24 -0
  98. package/dist/command/daemon/lifetime/daemon-exit.js +39 -0
  99. package/dist/command/daemon/lifetime/death-cause.d.ts +15 -0
  100. package/dist/command/daemon/lifetime/death-cause.js +149 -0
  101. package/dist/command/daemon/lifetime/heartbeat.d.ts +7 -15
  102. package/dist/command/daemon/lifetime/heartbeat.js +11 -15
  103. package/dist/command/daemon/lifetime/startup-failure.d.ts +29 -0
  104. package/dist/command/daemon/lifetime/startup-failure.js +62 -0
  105. package/dist/command/daemon/port-state.d.ts +14 -0
  106. package/dist/command/daemon/port-state.js +31 -0
  107. package/dist/command/setup/agent-configs.d.ts +2 -0
  108. package/dist/command/setup/agent-configs.js +42 -15
  109. package/dist/command/setup/agent-next-steps.d.ts +77 -0
  110. package/dist/command/setup/agent-next-steps.js +284 -0
  111. package/dist/command/setup/agent-writer.js +11 -6
  112. package/dist/command/setup/bringup/dev-server-files.d.ts +9 -0
  113. package/dist/command/setup/bringup/dev-server-files.js +17 -0
  114. package/dist/command/setup/bringup/static-page-server.d.ts +14 -2
  115. package/dist/command/setup/bringup/static-page-server.js +43 -9
  116. package/dist/command/setup/init/init-host.js +2 -0
  117. package/dist/command/setup/init/init-runtime.d.ts +6 -1
  118. package/dist/command/setup/init/init-runtime.js +63 -21
  119. package/dist/command/setup/mcp-launcher.d.ts +3 -0
  120. package/dist/command/setup/mcp-launcher.js +11 -0
  121. package/dist/command/setup/node-effects.d.ts +3 -2
  122. package/dist/command/setup/node-effects.js +9 -3
  123. package/dist/command/setup/remaining-steps.d.ts +8 -0
  124. package/dist/command/setup/remaining-steps.js +10 -0
  125. package/dist/command/setup/run-setup.d.ts +2 -0
  126. package/dist/command/setup/run-setup.js +9 -1
  127. package/dist/command/setup/setup-command.js +36 -15
  128. package/dist/command/setup/setup-mcp.d.ts +8 -0
  129. package/dist/command/setup/setup-mcp.js +47 -20
  130. package/dist/command/setup/start-dev-server.d.ts +8 -0
  131. package/dist/command/setup/start-dev-server.js +43 -0
  132. package/dist/command/update/updater.js +6 -0
  133. package/dist/command/version/identity/server-version.d.ts +2 -0
  134. package/dist/command/version/identity/server-version.js +2 -0
  135. package/dist/command/version/version-skew.d.ts +7 -0
  136. package/dist/command/version/version-skew.js +14 -3
  137. package/dist/features/compare/action-plan.d.ts +260 -0
  138. package/dist/features/compare/action-plan.js +543 -0
  139. package/dist/features/compare/behaviour-compare.d.ts +24 -0
  140. package/dist/features/compare/behaviour-compare.js +85 -0
  141. package/dist/features/compare/behaviour-diff.d.ts +97 -0
  142. package/dist/features/compare/behaviour-diff.js +202 -0
  143. package/dist/features/compare/behaviour-report.d.ts +40 -0
  144. package/dist/features/compare/behaviour-report.js +128 -0
  145. package/dist/features/compare/build-readings.d.ts +106 -0
  146. package/dist/features/compare/build-readings.js +250 -0
  147. package/dist/features/compare/compare-diff.d.ts +62 -0
  148. package/dist/features/compare/compare-diff.js +177 -0
  149. package/dist/features/compare/compare-report-html.d.ts +6 -0
  150. package/dist/features/compare/compare-report-html.js +102 -0
  151. package/dist/features/compare/compare-report.d.ts +15 -0
  152. package/dist/features/compare/compare-report.js +73 -0
  153. package/dist/features/compare/compare-run.d.ts +60 -0
  154. package/dist/features/compare/compare-run.js +123 -0
  155. package/dist/features/compare/outside/effect-lines.d.ts +26 -0
  156. package/dist/features/compare/outside/effect-lines.js +103 -0
  157. package/dist/features/compare/outside/state-sources.d.ts +76 -0
  158. package/dist/features/compare/outside/state-sources.js +164 -0
  159. package/dist/features/compare/outside/value-dictionary.d.ts +51 -0
  160. package/dist/features/compare/outside/value-dictionary.js +227 -0
  161. package/dist/features/compare/visible-content.d.ts +20 -0
  162. package/dist/features/compare/visible-content.js +125 -0
  163. package/dist/features/crawl/crawl-empty.js +2 -1
  164. package/dist/features/crawl/crawl-exhaustive.d.ts +55 -0
  165. package/dist/features/crawl/crawl-exhaustive.js +138 -0
  166. package/dist/features/crawl/crawl-tools.d.ts +9 -2
  167. package/dist/features/crawl/crawl-tools.js +28 -12
  168. package/dist/features/crawl/crawl.d.ts +8 -9
  169. package/dist/features/crawl/crawl.js +61 -65
  170. package/dist/features/exhaust/app-coverage.d.ts +5 -1
  171. package/dist/features/exhaust/app-coverage.js +3 -1
  172. package/dist/features/exhaust/code-coverage.js +8 -4
  173. package/dist/features/exhaust/edge-cases.d.ts +133 -0
  174. package/dist/features/exhaust/edge-cases.js +310 -0
  175. package/dist/features/exhaust/edges/edge-drive.d.ts +42 -0
  176. package/dist/features/exhaust/edges/edge-drive.js +364 -0
  177. package/dist/features/exhaust/edges/edge-run.d.ts +59 -0
  178. package/dist/features/exhaust/edges/edge-run.js +169 -0
  179. package/dist/features/exhaust/explorer.d.ts +87 -14
  180. package/dist/features/exhaust/explorer.js +159 -25
  181. package/dist/features/exhaust/failure-branches.d.ts +5 -0
  182. package/dist/features/exhaust/failure-branches.js +29 -11
  183. package/dist/features/exhaust/invariants.d.ts +112 -0
  184. package/dist/features/exhaust/invariants.js +188 -0
  185. package/dist/features/exhaust/ledger.d.ts +68 -0
  186. package/dist/features/exhaust/ledger.js +121 -13
  187. package/dist/features/exhaust/session-fold.d.ts +10 -0
  188. package/dist/features/exhaust/session-fold.js +46 -3
  189. package/dist/features/exhaust/session-port.d.ts +3 -1
  190. package/dist/features/exhaust/session-port.js +97 -28
  191. package/dist/features/grade/grade-answer.d.ts +99 -0
  192. package/dist/features/grade/grade-answer.js +112 -0
  193. package/dist/features/grade/grade-noise.d.ts +27 -0
  194. package/dist/features/grade/grade-noise.js +55 -0
  195. package/dist/features/grade/grade-renumber.d.ts +12 -0
  196. package/dist/features/grade/grade-renumber.js +86 -0
  197. package/dist/features/grade/grade-state.d.ts +50 -0
  198. package/dist/features/grade/grade-state.js +93 -0
  199. package/dist/features/grade/grade-suggest.d.ts +33 -0
  200. package/dist/features/grade/grade-suggest.js +98 -0
  201. package/dist/features/license/license.d.ts +11 -71
  202. package/dist/features/license/license.js +43 -136
  203. package/dist/features/visual/desktop-capture.d.ts +33 -0
  204. package/dist/features/visual/desktop-capture.js +94 -0
  205. package/dist/features/visual/step-shots.js +139 -35
  206. package/dist/features/visual/visual-tools.d.ts +0 -18
  207. package/dist/features/visual/visual-tools.js +67 -99
  208. package/dist/import-graph.js +5 -6
  209. package/dist/index.d.ts +3 -1
  210. package/dist/index.js +19 -7
  211. package/dist/judgement/capsule/causal-summary.js +9 -2
  212. package/dist/judgement/runs/artifact/replay-mapping.d.ts +13 -1
  213. package/dist/judgement/runs/artifact/replay-mapping.js +1 -1
  214. package/dist/judgement/runs/reticle-runner.js +16 -2
  215. package/dist/judgement/runs/run-ledger.d.ts +7 -5
  216. package/dist/judgement/runs/run-ledger.js +98 -69
  217. package/dist/judgement/runs/runner-port.d.ts +2 -0
  218. package/dist/judgement/runs/runner-port.js +23 -5
  219. package/dist/judgement/runs/verification-sync.d.ts +6 -3
  220. package/dist/judgement/runs/verification-sync.js +21 -10
  221. package/dist/language/flows/affected-tools.d.ts +1 -1
  222. package/dist/language/flows/affected-tools.js +49 -30
  223. package/dist/language/flows/change/git-changed.d.ts +2 -1
  224. package/dist/language/flows/change/git-changed.js +19 -2
  225. package/dist/language/flows/fields/flow-secret-field.d.ts +1 -13
  226. package/dist/language/flows/fields/flow-secret-field.js +77 -21
  227. package/dist/language/flows/flow-anchor.d.ts +60 -2
  228. package/dist/language/flows/flow-anchor.js +125 -20
  229. package/dist/language/flows/flow-classify.d.ts +20 -0
  230. package/dist/language/flows/flow-classify.js +90 -12
  231. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  232. package/dist/language/flows/flow-cross-step.js +25 -1
  233. package/dist/language/flows/flow-journey.d.ts +38 -4
  234. package/dist/language/flows/flow-journey.js +138 -8
  235. package/dist/language/flows/flow-replay-run.d.ts +0 -16
  236. package/dist/language/flows/flow-replay-run.js +76 -14
  237. package/dist/language/flows/flow-replay-types.d.ts +4 -2
  238. package/dist/language/flows/flow-replay.d.ts +19 -1
  239. package/dist/language/flows/flow-replay.js +169 -52
  240. package/dist/language/flows/flow-replayable.js +7 -1
  241. package/dist/language/flows/flow-start-path.d.ts +34 -5
  242. package/dist/language/flows/flow-start-path.js +202 -15
  243. package/dist/language/flows/flow-step-runners.d.ts +14 -13
  244. package/dist/language/flows/flow-step-runners.js +184 -109
  245. package/dist/language/flows/flow-store-for-session.d.ts +26 -25
  246. package/dist/language/flows/flow-store-for-session.js +51 -5
  247. package/dist/language/flows/flow-tools.js +46 -33
  248. package/dist/language/flows/flows.js +176 -76
  249. package/dist/language/flows/recording/tape/recordings.d.ts +21 -16
  250. package/dist/language/flows/recording/tape/recordings.js +112 -49
  251. package/dist/language/flows/replay.d.ts +36 -12
  252. package/dist/language/flows/replay.js +193 -50
  253. package/dist/language/flows/steps/page-steps.d.ts +68 -0
  254. package/dist/language/flows/steps/page-steps.js +198 -0
  255. package/dist/language/flows/suite/sequential-run.d.ts +68 -0
  256. package/dist/language/flows/suite/sequential-run.js +96 -0
  257. package/dist/language/flows/verify-change-tools.js +26 -15
  258. package/dist/launch-chromium.d.ts +1 -0
  259. package/dist/launch-chromium.js +49 -12
  260. package/dist/memory/cloud/background-sync.d.ts +13 -0
  261. package/dist/memory/cloud/background-sync.js +24 -3
  262. package/dist/memory/cloud/sync-daemon.js +26 -27
  263. package/dist/memory/cloud/sync-screenshots.js +6 -1
  264. package/dist/memory/journal/attach-journal.js +2 -1
  265. package/dist/memory/journal/drive-flow.d.ts +7 -0
  266. package/dist/memory/journal/drive-flow.js +44 -13
  267. package/dist/memory/journal/journal-recorder.d.ts +4 -2
  268. package/dist/memory/journal/session-journal.d.ts +7 -0
  269. package/dist/memory/journal/session-journal.js +58 -10
  270. package/dist/memory/project/root-claims.d.ts +9 -0
  271. package/dist/memory/project/root-claims.js +9 -4
  272. package/dist/portal/bridge/bridge.d.ts +13 -4
  273. package/dist/portal/bridge/bridge.js +10 -9
  274. package/dist/portal/input/download-capture.d.ts +7 -0
  275. package/dist/portal/input/download-capture.js +92 -0
  276. package/dist/portal/input/network-detail.d.ts +6 -0
  277. package/dist/portal/input/network-detail.js +12 -1
  278. package/dist/portal/input/network-mock-tools.js +49 -13
  279. package/dist/portal/input/network-mock.d.ts +8 -0
  280. package/dist/portal/input/network-mock.js +30 -10
  281. package/dist/portal/input/page-capture.d.ts +24 -0
  282. package/dist/portal/input/page-capture.js +33 -0
  283. package/dist/portal/input/real-input.d.ts +35 -18
  284. package/dist/portal/input/real-input.js +99 -33
  285. package/dist/portal/input/viewport-tools.d.ts +8 -0
  286. package/dist/portal/input/viewport-tools.js +81 -21
  287. package/dist/portal/pool/browser-pool.d.ts +16 -30
  288. package/dist/portal/pool/browser-pool.js +64 -52
  289. package/dist/portal/pool/context-permissions.d.ts +34 -17
  290. package/dist/portal/pool/context-permissions.js +94 -1
  291. package/dist/portal/pool/csp-widen.d.ts +62 -0
  292. package/dist/portal/pool/csp-widen.js +132 -0
  293. package/dist/portal/pool/determinism.d.ts +52 -0
  294. package/dist/portal/pool/determinism.js +259 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +34 -1
  296. package/dist/portal/pool/lease-emulation.js +18 -0
  297. package/dist/portal/pool/lease-marker-stamp.d.ts +6 -0
  298. package/dist/portal/pool/lease-marker-stamp.js +15 -1
  299. package/dist/portal/pool/lease-page-error.d.ts +56 -0
  300. package/dist/portal/pool/lease-page-error.js +160 -0
  301. package/dist/portal/pool/page-error-journal.d.ts +63 -0
  302. package/dist/portal/pool/page-error-journal.js +208 -0
  303. package/dist/portal/pool/playwright-launcher.js +71 -15
  304. package/dist/portal/pool/pool-contract.d.ts +49 -1
  305. package/dist/portal/pool/pool-lease.d.ts +70 -2
  306. package/dist/portal/pool/pool-lease.js +23 -0
  307. package/dist/portal/pool/zero-install.d.ts +8 -0
  308. package/dist/portal/pool/zero-install.js +91 -1
  309. package/dist/portal/session/dev-server/dev-command.d.ts +11 -2
  310. package/dist/portal/session/dev-server/dev-command.js +110 -16
  311. package/dist/portal/session/dev-server/dev-server-probe.d.ts +7 -0
  312. package/dist/portal/session/dev-server/dev-server-probe.js +9 -0
  313. package/dist/portal/session/dev-server/project-ports.d.ts +21 -0
  314. package/dist/portal/session/dev-server/project-ports.js +62 -0
  315. package/dist/portal/session/facts/departed-network.d.ts +66 -0
  316. package/dist/portal/session/facts/departed-network.js +142 -0
  317. package/dist/portal/session/no-session-diagnosis.d.ts +9 -0
  318. package/dist/portal/session/no-session-diagnosis.js +76 -27
  319. package/dist/portal/session/no-session-next-action.d.ts +14 -0
  320. package/dist/portal/session/no-session-next-action.js +56 -7
  321. package/dist/portal/session/no-session-watch.d.ts +6 -0
  322. package/dist/portal/session/no-session-watch.js +41 -4
  323. package/dist/portal/session/page-commands/undeclared-command.d.ts +9 -3
  324. package/dist/portal/session/page-commands/undeclared-command.js +16 -7
  325. package/dist/portal/session/recorded-gaps.d.ts +1 -1
  326. package/dist/portal/session/recorded-gaps.js +5 -2
  327. package/dist/portal/session/serving-directory.d.ts +10 -0
  328. package/dist/portal/session/serving-directory.js +39 -8
  329. package/dist/portal/session/session-manager.d.ts +10 -0
  330. package/dist/portal/session/session-manager.js +211 -81
  331. package/dist/portal/session/session.js +4 -3
  332. package/dist/portal/session/zero-install-first-call.d.ts +20 -1
  333. package/dist/portal/session/zero-install-first-call.js +34 -2
  334. package/dist/start-wiring.d.ts +30 -2
  335. package/dist/start-wiring.js +74 -9
  336. package/dist/surface/mcp/mcp-proxy.js +10 -2
  337. package/dist/surface/mcp/mcp.js +8 -5
  338. package/dist/surface/mcp/peer/agents-connected.d.ts +6 -0
  339. package/dist/surface/mcp/peer/agents-connected.js +58 -0
  340. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +17 -5
  341. package/dist/surface/mcp/shared-params.d.ts +2 -0
  342. package/dist/surface/mcp/shared-params.js +31 -0
  343. package/dist/surface/tools/act/act-observation.d.ts +40 -2
  344. package/dist/surface/tools/act/act-observation.js +118 -12
  345. package/dist/surface/tools/act/act-preflight.js +38 -18
  346. package/dist/surface/tools/act/act-sequence-retry.d.ts +23 -2
  347. package/dist/surface/tools/act/act-sequence-retry.js +61 -5
  348. package/dist/surface/tools/act/act-target.js +3 -1
  349. package/dist/surface/tools/act/after-match.d.ts +10 -0
  350. package/dist/surface/tools/act/after-match.js +40 -0
  351. package/dist/surface/tools/act/already-true.js +1 -1
  352. package/dist/surface/tools/act/navigation/reload-result.d.ts +12 -1
  353. package/dist/surface/tools/act/navigation/reload-result.js +25 -2
  354. package/dist/surface/tools/act/resolve-target.d.ts +17 -1
  355. package/dist/surface/tools/act/resolve-target.js +59 -9
  356. package/dist/surface/tools/act/sequence-grade.d.ts +15 -1
  357. package/dist/surface/tools/act/sequence-grade.js +45 -27
  358. package/dist/surface/tools/act/settle-in-flight.js +27 -15
  359. package/dist/surface/tools/act-command.d.ts +23 -0
  360. package/dist/surface/tools/act-command.js +50 -0
  361. package/dist/surface/tools/act-sequence-tool.js +81 -50
  362. package/dist/surface/tools/act-tools.d.ts +1 -13
  363. package/dist/surface/tools/act-tools.js +83 -95
  364. package/dist/surface/tools/agent/build-staleness.d.ts +1 -1
  365. package/dist/surface/tools/agent/build-staleness.js +1 -1
  366. package/dist/surface/tools/assert/assert-verdict.js +18 -12
  367. package/dist/surface/tools/browser-tools.js +87 -13
  368. package/dist/surface/tools/compare-tools.d.ts +11 -0
  369. package/dist/surface/tools/compare-tools.js +94 -0
  370. package/dist/surface/tools/coverage-tools.js +1 -1
  371. package/dist/surface/tools/dynamic-tools.js +11 -3
  372. package/dist/surface/tools/empty-session-list.d.ts +2 -0
  373. package/dist/surface/tools/empty-session-list.js +4 -2
  374. package/dist/surface/tools/error-recovery.d.ts +12 -1
  375. package/dist/surface/tools/error-recovery.js +131 -40
  376. package/dist/surface/tools/explore-tools.js +1 -1
  377. package/dist/surface/tools/invoke-tool.js +29 -21
  378. package/dist/surface/tools/lease-coverage-tools.d.ts +49 -0
  379. package/dist/surface/tools/lease-coverage-tools.js +128 -0
  380. package/dist/surface/tools/lease-hint.d.ts +42 -1
  381. package/dist/surface/tools/lease-hint.js +52 -3
  382. package/dist/surface/tools/lease-readiness.d.ts +47 -0
  383. package/dist/surface/tools/lease-readiness.js +126 -0
  384. package/dist/surface/tools/lease-seed.d.ts +16 -1
  385. package/dist/surface/tools/lease-seed.js +37 -4
  386. package/dist/surface/tools/lease-tools.d.ts +3 -41
  387. package/dist/surface/tools/lease-tools.js +104 -124
  388. package/dist/surface/tools/lease-url-identity.d.ts +46 -0
  389. package/dist/surface/tools/lease-url-identity.js +97 -0
  390. package/dist/surface/tools/merge-tools.js +99 -2
  391. package/dist/surface/tools/merged-name-redirect.js +5 -1
  392. package/dist/surface/tools/observe-tools.js +15 -9
  393. package/dist/surface/tools/read/console-pre-attach.d.ts +19 -0
  394. package/dist/surface/tools/read/console-pre-attach.js +23 -0
  395. package/dist/surface/tools/read-tools.js +33 -30
  396. package/dist/surface/tools/real-input-attempt.d.ts +0 -8
  397. package/dist/surface/tools/real-input-attempt.js +143 -28
  398. package/dist/surface/tools/repeat-hint.d.ts +40 -0
  399. package/dist/surface/tools/repeat-hint.js +171 -0
  400. package/dist/surface/tools/run-report.d.ts +2 -2
  401. package/dist/surface/tools/run-report.js +27 -15
  402. package/dist/surface/tools/session-ports.d.ts +14 -0
  403. package/dist/surface/tools/session-ports.js +53 -0
  404. package/dist/surface/tools/tool-kit.d.ts +18 -0
  405. package/dist/surface/tools/tool-kit.js +9 -3
  406. package/dist/surface/tools/tool-surface.js +0 -1
  407. package/dist/surface/tools/tools.js +54 -18
  408. package/dist/surface/tools/type-spelling.d.ts +13 -0
  409. package/dist/surface/tools/type-spelling.js +36 -0
  410. package/dist/telemetry/agent-client.d.ts +6 -0
  411. package/dist/telemetry/agent-client.js +10 -0
  412. package/dist/telemetry/argument-shape.js +17 -7
  413. package/dist/telemetry/cli-telemetry.d.ts +10 -1
  414. package/dist/telemetry/cli-telemetry.js +15 -0
  415. package/dist/telemetry/init-telemetry.d.ts +2 -1
  416. package/dist/telemetry/init-telemetry.js +47 -2
  417. package/dist/telemetry/install-trace.js +23 -4
  418. package/dist/telemetry/onboarding-funnel.d.ts +19 -2
  419. package/dist/telemetry/onboarding-funnel.js +25 -3
  420. package/dist/telemetry/proxy-telemetry.d.ts +2 -1
  421. package/dist/telemetry/proxy-telemetry.js +2 -2
  422. package/dist/telemetry/session-metrics.d.ts +1 -1
  423. package/dist/telemetry/session-metrics.js +67 -27
  424. package/dist/telemetry/telemetry.d.ts +3 -1
  425. package/dist/telemetry/telemetry.js +60 -28
  426. package/dist/telemetry/tool-refused.d.ts +6 -3
  427. package/dist/telemetry/tool-refused.js +15 -3
  428. package/docs/actions.mdx +3 -3
  429. package/docs/agent-cheatsheet.md +1 -1
  430. package/docs/architecture.md +3 -6
  431. package/docs/capabilities.mdx +61 -1
  432. package/docs/cli/affected.mdx +9 -7
  433. package/docs/cli/cloud.mdx +3 -3
  434. package/docs/cli/compare.mdx +94 -0
  435. package/docs/cli/doctor.mdx +41 -2
  436. package/docs/cli/grade.mdx +97 -0
  437. package/docs/cli/help.mdx +4 -2
  438. package/docs/cli/license.mdx +13 -12
  439. package/docs/cli/restart.mdx +7 -5
  440. package/docs/cli/rollback.mdx +5 -5
  441. package/docs/cli/setup.mdx +13 -2
  442. package/docs/cli/status.mdx +4 -0
  443. package/docs/cli/stop.mdx +3 -3
  444. package/docs/cli/update.mdx +2 -2
  445. package/docs/cli/verify.mdx +23 -1
  446. package/docs/cli.mdx +34 -24
  447. package/docs/deploy-checks.md +25 -0
  448. package/docs/desktop.mdx +1 -1
  449. package/docs/enterprise.md +17 -22
  450. package/docs/environment.mdx +1 -0
  451. package/docs/first-drive.md +1 -1
  452. package/docs/flows.md +11 -1
  453. package/docs/frameworks.mdx +9 -9
  454. package/docs/getting-started.md +1 -1
  455. package/docs/install-manual.mdx +16 -1
  456. package/docs/license-activation.mdx +3 -3
  457. package/docs/packages/engine.mdx +1 -1
  458. package/docs/packages/server.mdx +1 -1
  459. package/docs/packages/tauri.mdx +1 -1
  460. package/docs/packages/test.mdx +1 -1
  461. package/docs/packages.mdx +1 -1
  462. package/docs/platform-integration.md +1 -1
  463. package/docs/predicates.mdx +1 -1
  464. package/docs/quickstart.mdx +68 -49
  465. package/docs/skill-setup.md +1 -1
  466. package/docs/skill.md +14 -13
  467. package/docs/telemetry.md +6 -4
  468. package/docs/tools/navigate.mdx +1 -1
  469. package/docs/tools/verify.mdx +4 -1
  470. package/docs/troubleshooting.mdx +1 -1
  471. package/docs/usage.md +17 -14
  472. package/package.json +18 -14
  473. package/LICENSE-ENTERPRISE +0 -37
  474. package/dist/features/ee/audit-log.d.ts +0 -14
  475. package/dist/features/ee/audit-log.js +0 -11
  476. package/dist/features/exhaust/exhaust-run.d.ts +0 -33
  477. package/dist/features/exhaust/exhaust-run.js +0 -82
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { AppRuntime, ReticleCommand, RETICLE_CAPTURE_FILE_PREFIX, VISUAL_NO_PROVIDER_RECOMMENDATION, RETICLE_TAURI_INERT_MARKER, TAURI_INERT_RECOMMENDATION, VisualReason, } from '@reticlehq/core';
2
+ import { AppRuntime, ReticleCommand, VISUAL_NO_PROVIDER_RECOMMENDATION, VisualReason, realmOf, } from '@reticlehq/core';
3
3
  import { ReticleTool } from '@reticlehq/core';
4
4
  import { sessionIdShape } from '../../surface/tools/tool-kit.js';
5
5
  import { ratioSchema } from '../../surface/tools/args/numeric-bounds.js';
@@ -21,10 +21,7 @@ function captureRoot(deps, sessionId) {
21
21
  return deps.reticleRoot;
22
22
  }
23
23
  }
24
- import { trackCaptureDirectory } from './capture-cleanup.js';
25
- import { readFile, unlink } from 'node:fs/promises';
26
- import { tmpdir } from 'node:os';
27
- import { basename, dirname } from 'node:path';
24
+ import { desktopCapture } from './desktop-capture.js';
28
25
  const rectShape = z.object({
29
26
  x: z.number(),
30
27
  y: z.number(),
@@ -37,6 +34,38 @@ function screenshotProvider(deps) {
37
34
  ? p
38
35
  : undefined;
39
36
  }
37
+ /**
38
+ * Whether this call can photograph the session: a lease of this caller, a desktop shell, or a page
39
+ * that the driven browser has.
40
+ *
41
+ * A desktop shell counts before its capture is known to work. Only a CAPTURE command can tell, and
42
+ * a person who connected a desktop app most likely wants that window, not a lease beside it.
43
+ */
44
+ async function canCapture(deps, session) {
45
+ // A lease of this caller. Another agent's lease is never this caller's to photograph (#1226).
46
+ if (true === deps.pool?.heldBy(session.id, deps.attachId)) {
47
+ return true;
48
+ }
49
+ if (realmOf(session.runtime).isDesktopShell) {
50
+ return true;
51
+ }
52
+ // A driven browser photographs only a page it has. Having a driven browser is not enough.
53
+ const driven = screenshotProvider(deps);
54
+ if (driven === undefined) {
55
+ return false;
56
+ }
57
+ return driven.isAvailableFor(session.url);
58
+ }
59
+ /**
60
+ * The one session this call photographs, chosen once (#1959).
61
+ *
62
+ * capture() and captureRoot() each resolve the id again. With no id, each could choose another
63
+ * session, and capture() never tried a lease at all. After this, they all get the same, named id.
64
+ */
65
+ async function captureTarget(deps, args) {
66
+ const session = await deps.sessions.resolveServing(asString(args['sessionId']), (s) => canCapture(deps, s));
67
+ return session.id;
68
+ }
40
69
  /** The "you need a driven browser" envelope, shared by both visual tools. */
41
70
  const noProvider = {
42
71
  ok: false,
@@ -45,8 +74,9 @@ const noProvider = {
45
74
  };
46
75
  /** The answer when no pixels came back: the shell's own recommendation over the web one. */
47
76
  function unavailable(reason, recommendation) {
48
- if (recommendation !== undefined)
77
+ if (recommendation !== undefined) {
49
78
  return { ok: false, reason, recommendation };
79
+ }
50
80
  return reason === VisualReason.NO_PROVIDER ? noProvider : { ok: false, reason };
51
81
  }
52
82
  function asBox(value) {
@@ -55,10 +85,12 @@ function asBox(value) {
55
85
  const y = asNumber(b['y']);
56
86
  const w = asNumber(b['width']);
57
87
  const h = asNumber(b['height']);
58
- if (x === undefined || y === undefined || w === undefined || h === undefined)
88
+ if (x === undefined || y === undefined || w === undefined || h === undefined) {
59
89
  return undefined;
60
- if (w <= 0 || h <= 0)
90
+ }
91
+ if (w <= 0 || h <= 0) {
61
92
  return undefined;
93
+ }
62
94
  return { x, y, width: w, height: h };
63
95
  }
64
96
  /** Build capture options from args: explicit clip > ref (resolved via INSPECT) > fullPage. */
@@ -67,59 +99,21 @@ async function buildOpts(deps, sessionId, args) {
67
99
  if (clipArg !== undefined) {
68
100
  const c = asRecord(clipArg);
69
101
  const box = asBox({ box: c });
70
- if (box !== undefined)
102
+ if (box !== undefined) {
71
103
  return { clip: box };
104
+ }
72
105
  }
73
106
  const ref = asString(args['ref']);
74
107
  if (ref !== undefined) {
75
108
  const session = deps.sessions.resolve(sessionId);
76
109
  const res = await session.command(ReticleCommand.INSPECT, { ref });
77
110
  const box = res.ok ? asBox(res.result) : undefined;
78
- if (box !== undefined)
111
+ if (box !== undefined) {
79
112
  return { clip: box };
113
+ }
80
114
  }
81
115
  return true === args['fullPage'] ? { fullPage: true } : {};
82
116
  }
83
- /**
84
- * Ask the desktop shell to photograph its own window.
85
- *
86
- * Returns undefined for a plain web page (no capture helper) and for a desktop app that did not
87
- * install `@reticlehq/electron/main`. Deliberately NOT a screen-region capture: photographing
88
- * the glass returns whatever window is on top, which would save a picture of the user's editor as a
89
- * visual baseline — the exact false green Reticle exists to eliminate.
90
- */
91
- async function desktopCapture(deps, sessionId, fullPage) {
92
- const session = deps.sessions.resolve(sessionId);
93
- // A session that cannot take commands (no live browser behind it) simply has no pixels to give.
94
- if (typeof session.command !== 'function')
95
- return {};
96
- const res = await session.command(ReticleCommand.CAPTURE, { fullPage });
97
- if (!res.ok)
98
- return {};
99
- const r = asRecord(res.result);
100
- if (r['ok'] !== true) {
101
- // The shell refused a full-page capture it cannot do. That is a different answer from "capture
102
- // broke", and the caller has to hear it — otherwise they read `fullPage` as honoured.
103
- if (r['reason'] === VisualReason.FULL_PAGE_UNSUPPORTED)
104
- return { reason: VisualReason.FULL_PAGE_UNSUPPORTED };
105
- // A Tauri release build with the capture compiled out: say that, never the web answer.
106
- return true === asString(r['reason'])?.includes(RETICLE_TAURI_INERT_MARKER)
107
- ? { reason: VisualReason.NO_PROVIDER, recommendation: TAURI_INERT_RECOMMENDATION }
108
- : {};
109
- }
110
- const path = asString(r['path']);
111
- if (path === undefined || !isCapturePath(path))
112
- return {};
113
- try {
114
- const bytes = await readFile(path);
115
- await unlink(path).catch(() => undefined);
116
- trackCaptureDirectory(path);
117
- return isCompletePng(bytes) ? { png: new Uint8Array(bytes) } : {};
118
- }
119
- catch {
120
- return {};
121
- }
122
- }
123
117
  /**
124
118
  * One picture of the tab a chat-requested drive is driving, or undefined when there is none to take.
125
119
  *
@@ -131,14 +125,16 @@ async function desktopCapture(deps, sessionId, fullPage) {
131
125
  export async function driveFrame(deps, sessionId, jpegQuality) {
132
126
  try {
133
127
  const leased = await deps.pool?.screenshotLease(sessionId, { jpegQuality });
134
- if (leased !== undefined)
128
+ if (leased !== undefined) {
135
129
  return leased;
130
+ }
136
131
  // The window `reticle drive` opened: neither a lease nor a desktop shell, but its page is ours.
137
132
  const url = deps.sessions.resolve(sessionId).url;
138
133
  if (true === (await deps.realInput?.isAvailableFor(url))) {
139
134
  const shot = await deps.realInput?.screenshot?.(url, { jpegQuality });
140
- if (shot !== undefined)
135
+ if (shot !== undefined) {
141
136
  return shot;
137
+ }
142
138
  }
143
139
  return (await desktopCapture(deps, sessionId, false)).png;
144
140
  }
@@ -146,42 +142,6 @@ export async function driveFrame(deps, sessionId, jpegQuality) {
146
142
  return undefined;
147
143
  }
148
144
  }
149
- /**
150
- * Only read a capture the shell wrote. The path arrives from the PAGE, and the daemon must not
151
- * become a file-read oracle for whatever a compromised renderer names.
152
- *
153
- * Two layouts are accepted, and the pair is the whole subtlety:
154
- *
155
- * - `<tmp>/<prefix><dir>/<prefix><n>.png` — the shell's own PRIVATE capture directory, created 0700
156
- * so no other local user can read the screenshot or pre-place a symlink at the path we write.
157
- * - `<tmp>/<prefix><pid>-<n>.png` — the legacy flat layout. Kept because the shell ships as a
158
- * SEPARATE package on the user's own upgrade schedule: an app still on the older
159
- * `@reticlehq/electron` talking to a newer daemon must keep getting screenshots, not silently
160
- * start returning no image. It grants the daemon nothing it did not already have.
161
- *
162
- * Every comparison is an exact match against an UNRESOLVED path, which is what refuses traversal:
163
- * `<tmp>/<prefix>x/../../etc/<prefix>passwd.png` has a grandparent of `<tmp>/<prefix>x/..`, not
164
- * `<tmp>`. Exactly one directory level is allowed, so a deeper nesting is refused too.
165
- */
166
- export function isCapturePath(path) {
167
- if (!basename(path).startsWith(RETICLE_CAPTURE_FILE_PREFIX))
168
- return false;
169
- const parent = dirname(path);
170
- if (parent === tmpdir())
171
- return true;
172
- return dirname(parent) === tmpdir() && basename(parent).startsWith(RETICLE_CAPTURE_FILE_PREFIX);
173
- }
174
- /**
175
- * A PNG is complete only if it ends with IEND. This check exists because a truncated image once
176
- * sailed through as a saved baseline: the tool answered `saved: true` with a path, and the file was
177
- * undecodable. A screenshot that cannot be read must fail loudly, never bank a corrupt baseline that
178
- * a later visual_diff will compare against.
179
- */
180
- function isCompletePng(bytes) {
181
- if (bytes.byteLength < 12)
182
- return false;
183
- return 'IEND' === bytes.subarray(bytes.byteLength - 8, bytes.byteLength - 4).toString('ascii');
184
- }
185
145
  /**
186
146
  * Which runtime this session is, for scoping a visual artifact.
187
147
  *
@@ -232,8 +192,9 @@ async function capture(deps, sessionId, args) {
232
192
  // A driven browser renders the session's URL in a BROWSER — so the pixels are web even when the
233
193
  // session named is a desktop window. Scoping those under the desktop runtime would corrupt that
234
194
  // runtime's baseline with a picture of a different renderer.
235
- if (png !== undefined)
195
+ if (png !== undefined) {
236
196
  return { png, runtime: AppRuntime.WEB };
197
+ }
237
198
  }
238
199
  // The window's own backing store, via the Electron/Tauri adapter — the only route whose pixels
239
200
  // really are the desktop app.
@@ -246,8 +207,9 @@ async function capture(deps, sessionId, args) {
246
207
  }
247
208
  return { png: desktop.png, runtime: runtimeOf(deps, sessionId) };
248
209
  }
249
- if (desktop.reason !== undefined)
210
+ if (desktop.reason !== undefined) {
250
211
  return desktop;
212
+ }
251
213
  /*
252
214
  * A LEASED page is a real browser page, so the pixels were always there — the visual tools simply
253
215
  * had no route to them. Without this, `reticle_screenshot` answered "no provider" for every context
@@ -261,20 +223,23 @@ async function capture(deps, sessionId, args) {
261
223
  // shot of a lease saved the whole viewport and answered `saved: true`.
262
224
  await deps.pool?.screenshotLease(sessionId, await buildOpts(deps, sessionId, args));
263
225
  // A leased page is a real browser page, whatever the session is.
264
- if (leased !== undefined)
226
+ if (leased !== undefined) {
265
227
  return { png: leased, runtime: AppRuntime.WEB };
228
+ }
266
229
  return {
267
230
  reason: provider === undefined ? VisualReason.NO_PROVIDER : VisualReason.CAPTURE_FAILED,
268
231
  };
269
232
  }
270
233
  function rectsFrom(value) {
271
- if (!Array.isArray(value))
234
+ if (!Array.isArray(value)) {
272
235
  return undefined;
236
+ }
273
237
  const rects = [];
274
238
  for (const r of value) {
275
239
  const box = asBox({ box: r });
276
- if (box !== undefined)
240
+ if (box !== undefined) {
277
241
  rects.push(box);
242
+ }
278
243
  }
279
244
  return rects.length > 0 ? rects : undefined;
280
245
  }
@@ -316,10 +281,11 @@ export const VISUAL_TOOLS = [
316
281
  recommendation: z.string().optional(),
317
282
  },
318
283
  handler: async (deps, args) => {
319
- const sessionId = asString(args['sessionId']);
284
+ const sessionId = await captureTarget(deps, args);
320
285
  const { png, reason, recommendation, runtime } = await capture(deps, sessionId, args);
321
- if (png === undefined)
286
+ if (png === undefined) {
322
287
  return unavailable(reason, recommendation);
288
+ }
323
289
  const name = asString(args['name']) ?? 'default';
324
290
  // Beside the app that was captured. The tool RETURNS this path to the agent, so a wrong root
325
291
  // is not only a stray PNG — it is a path the agent is told to look at in a tree that has
@@ -369,22 +335,24 @@ export const VISUAL_TOOLS = [
369
335
  },
370
336
  handler: async (deps, args) => {
371
337
  const baseline = asString(args['baseline']) ?? '';
372
- const store = new VisualStore(deps.fs, captureRoot(deps, asString(args['sessionId'])));
338
+ const sessionId = await captureTarget(deps, args);
339
+ const store = new VisualStore(deps.fs, captureRoot(deps, sessionId));
373
340
  // Capture FIRST, then fetch the baseline for the runtime that produced these pixels. Reading it
374
341
  // by the session's runtime looked equivalent and is not: the route decides the renderer, so a
375
342
  // desktop session captured through a driven browser must be compared against the WEB baseline,
376
343
  // not the desktop one. Comparing across renderers is a confident wrong diff in one direction
377
344
  // and a silently overwritten baseline in the other.
378
- const sessionId = asString(args['sessionId']);
379
345
  const captured = await capture(deps, sessionId, args);
380
346
  const { png: current, runtime } = captured;
381
- if (current === undefined)
347
+ if (current === undefined) {
382
348
  return unavailable(captured.reason, captured.recommendation);
349
+ }
383
350
  // A baseline from another runtime is not missing by accident — it is a different rendering, and
384
351
  // "no baseline for this one" is the honest answer.
385
352
  const baselineBytes = await store.readBaseline(baseline, runtime);
386
- if (baselineBytes === undefined)
353
+ if (baselineBytes === undefined) {
387
354
  return { ok: false, reason: VisualReason.BASELINE_MISSING };
355
+ }
388
356
  const masks = rectsFrom(args['masks']);
389
357
  const threshold = asNumber(args['threshold']);
390
358
  const maxRatio = asNumber(args['maxRatio']);
@@ -75,12 +75,11 @@ export function importsOf(sourceRoot, file) {
75
75
  export function reachableFrom(sourceRoot, entry) {
76
76
  /*
77
77
  * The entry is normalised the same way `resolveImport` normalises everything this walker
78
- * DISCOVERS, because callers build it with `join()` and on Windows that yields `features\ee\x.ts`
79
- * while every other key in the map is `features/ee/x.ts`. A caller then filters the result with
80
- * `startsWith('features/ee/')` and the seed — the one path it did not walk to — silently drops
81
- * out. That is how `ee-boundary`, the guard that says the free build cannot reach the
82
- * separately-licensed code, passed on Windows while checking nothing at all: both of its real
83
- * assertions expect an EMPTY list, so a walker that finds nothing agrees with them. Only its
78
+ * DISCOVERS, because callers build it with `join()` and on Windows that yields `features\x\y.ts`
79
+ * while every other key in the map is `features/x/y.ts`. A caller then filters the result with
80
+ * `startsWith('features/x/')` and the seed — the one path it did not walk to — silently drops
81
+ * out. That is how a boundary guard passed on Windows while checking nothing at all: its real
82
+ * assertions expected an EMPTY list, so a walker that finds nothing agrees with them. Only its
84
83
  * negative control could see it, and that is the whole reason the negative control exists.
85
84
  *
86
85
  * Both separators, not `sep`: on a POSIX machine `sep` is `/`, so splitting on it leaves a
package/dist/index.d.ts CHANGED
@@ -102,8 +102,10 @@ export { registerContradictionFold, registeredContradictionFolds, } from '@retic
102
102
  export type { ContradictionFold } from '@reticlehq/engine/disagreement/contradiction-folds.js';
103
103
  export { MCP_SSE_PATH, MCP_MESSAGE_PATH } from '@reticlehq/core';
104
104
  export { BrowserPool, DEFAULT_LEASE_TTL_MS } from './portal/pool/browser-pool.js';
105
- export type { Lease, Launcher, PooledBrowser } from './portal/pool/browser-pool.js';
105
+ export type { Lease, Launcher, PooledBrowser, PooledPageError, } from './portal/pool/browser-pool.js';
106
106
  export { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
107
+ export { makePageErrorJournal, uncaughtIdentity } from './portal/pool/page-error-journal.js';
108
+ export type { JournalableSession, PageErrorJournal } from './portal/pool/page-error-journal.js';
107
109
  export { appendReticleParams } from './surface/tools/lease-tools.js';
108
110
  export { writePid, removePid, isRunning, logPath, readPid, isAlive, } from './command/daemon/daemon.js';
109
111
  export { classifyDaemonLife, DaemonEnd, DAEMON_HEARTBEAT_EVENT, DAEMON_HEARTBEAT_MS, type DaemonLife, } from './command/daemon/lifetime/heartbeat.js';
package/dist/index.js CHANGED
@@ -54,11 +54,12 @@ import { AGENT_ALREADY_DRIVING_ELSEWHERE, shouldGreetWithLeaseNotice, } from './
54
54
  import { LeaseReaper } from './portal/pool/lease-reaper.js';
55
55
  import { findProjectConfig, readJournalEnabled, readProjectId, } from './command/cli/ports/resolve/cli-port.js';
56
56
  import { probeLaunchableChromium } from './launch-chromium.js';
57
+ import { portAcceptsConnection } from './portal/session/dev-server/dev-server-probe.js';
57
58
  import { attachJournal } from './wire-journal.js';
58
59
  import { reportOnboardingStep } from './telemetry/onboarding-funnel.js';
59
60
  import { readRetainPolicy } from './memory/journal/on-disk/retain-policy.js';
60
61
  import { log } from './log.js';
61
- import { createBrowserPool, knownProjectRoots, loadHarnessConfig, makeNetworkDetailRouter, platformEnvFor, readerConnect, resolveRealInput, sdkFixForProject, } from './start-wiring.js';
62
+ import { attachPageErrorJournal, createBrowserPool, knownProjectRoots, loadHarnessConfig, makeNetworkDetailRouter, platformEnvFor, readerConnect, resolveRealInput, sdkFixForProject, } from './start-wiring.js';
62
63
  export { replayVerdictLine } from './language/flows/replay-from-hud.js';
63
64
  // Re-exported from the contract, where the names now live: a consumer importing the server should
64
65
  // not have to know that the vocabulary moved.
@@ -149,14 +150,18 @@ export async function start(options = {}) {
149
150
  // Then to the agent's own git worktree: every worktree declares the same projectId.
150
151
  bridge.sessions.setCheckoutScope(liveCheckoutSource());
151
152
  const baselines = new BaselineStore();
152
- const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
153
+ const recordings = new RecordingStore(() => currentDrivenBy() !== undefined, (session) => bridge.sessions.successorId(session));
153
154
  // drive precedence: driveUrl (launch+own a browser) → CDP (attach) → none.
154
155
  let pool;
155
156
  let leaseReaper;
156
157
  // Route CDP-authoritative network detail (drive path only) onto the driven session's journal: the
157
158
  // page and the SDK session share an origin, so a NET_DETAIL is pushed to the matching connected session.
158
159
  const routeNetworkDetail = makeNetworkDetailRouter(bridge, options.driveUrl);
159
- const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail);
160
+ // Journal the uncaught page errors the pool hears — an import-time throw lands before any SDK
161
+ // exists to see it, and it was previously invisible to the agent (#1966). The bridge hook is
162
+ // registered here; the pool gets the sink at its creation below.
163
+ const pageErrorJournal = attachPageErrorJournal(bridge);
164
+ const { realInput, owned } = await resolveRealInput(options, () => bridge.close(), routeNetworkDetail, readerConnect(port, security.token));
160
165
  let chatDrives;
161
166
  if (options.mcp !== false) {
162
167
  // cwd/Date.now are confined to start — never inside reticle-dir.ts's pure logic (rule 7).
@@ -184,7 +189,7 @@ export async function start(options = {}) {
184
189
  const project = new ProjectStore(fs, reticleRoot, { now });
185
190
  attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
186
191
  const annotations = new AnnotationStore();
187
- pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
192
+ pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token), (leaseId, error) => pageErrorJournal.onPoolPageError(leaseId, error), (leaseId) => pageErrorJournal.onLeaseClosed(leaseId), bridge.nonces, (leaseId, dialog) => pageErrorJournal.onPoolDialog(leaseId, dialog));
188
193
  leaseReaper = new LeaseReaper(pool);
189
194
  leaseReaper.start();
190
195
  /*
@@ -223,6 +228,7 @@ export async function start(options = {}) {
223
228
  bridgePort: port,
224
229
  nonces: bridge.nonces,
225
230
  browserProbe: probeLaunchableChromium,
231
+ portAccepts: portAcceptsConnection,
226
232
  // The daemon's OWN project, so a tool can tell "this session is mine" from "this session
227
233
  // belongs to a sibling app under the same daemon". contract_save refuses on the second.
228
234
  ...(activeProjectId === undefined ? {} : { projectId: activeProjectId }),
@@ -346,7 +352,11 @@ export async function startDaemon(options = {}) {
346
352
  }, AGENT_DETACH_GRACE_MS));
347
353
  const reaper = new SessionReaper(bridge.sessions);
348
354
  reaper.start();
349
- const { realInput, owned } = await resolveRealInput(options, () => shared.close(), makeNetworkDetailRouter(bridge, options.driveUrl));
355
+ // Same page-error journal the `start` path wires: an import-time crash reaches the agent through
356
+ // it instead of reading as a blank page (#1966). Kept beside the other shared wiring so the two
357
+ // entry points cannot drift.
358
+ const pageErrorJournal = attachPageErrorJournal(bridge);
359
+ const { realInput, owned } = await resolveRealInput(options, () => shared.close(), makeNetworkDetailRouter(bridge, options.driveUrl), readerConnect(port, security.token));
350
360
  const fs = createNodeFileSystem();
351
361
  const reticleRoot = options.reticleRoot ?? join(process.cwd(), ReticleDir.ROOT);
352
362
  const now = options.now ?? (() => Date.now());
@@ -357,7 +367,7 @@ export async function startDaemon(options = {}) {
357
367
  const flows = new FlowStore(fs, reticleRoot, { now }, { onWrote: () => syncNudge.run?.() });
358
368
  // Built here rather than inside `deps` below, so teardown can save what a drive recorded. Both
359
369
  // paths pass the same pair — `daemon-parity.test.ts` is what keeps them from drifting apart.
360
- const recordings = new RecordingStore(() => currentDrivenBy() !== undefined);
370
+ const recordings = new RecordingStore(() => currentDrivenBy() !== undefined, (session) => bridge.sessions.successorId(session));
361
371
  /*
362
372
  * Bound AFTER the sync daemon exists, read only when a run is actually written.
363
373
  *
@@ -380,7 +390,7 @@ export async function startDaemon(options = {}) {
380
390
  const project = new ProjectStore(fs, reticleRoot, { now });
381
391
  attachRouteLearning(bridge, projectStoreResolver(fs, project, reticleRoot, now));
382
392
  const annotations = new AnnotationStore();
383
- const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token));
393
+ const pool = createBrowserPool(options.headless ?? true, readerConnect(port, security.token), (leaseId, error) => pageErrorJournal.onPoolPageError(leaseId, error), (leaseId) => pageErrorJournal.onLeaseClosed(leaseId), bridge.nonces, (leaseId, dialog) => pageErrorJournal.onPoolDialog(leaseId, dialog));
384
394
  const leaseReaper = new LeaseReaper(pool);
385
395
  leaseReaper.start();
386
396
  /*
@@ -453,6 +463,7 @@ export async function startDaemon(options = {}) {
453
463
  bridgePort: port,
454
464
  nonces: bridge.nonces,
455
465
  browserProbe: probeLaunchableChromium,
466
+ portAccepts: portAcceptsConnection,
456
467
  ...agentWiring(bridge.sessions, leaseUrl),
457
468
  // A finished verification should not sit behind a one-minute timer — see ToolDeps.onRunPersisted.
458
469
  onRunPersisted: () => cloudSync.nudge(),
@@ -587,6 +598,7 @@ export { registerContradictionFold, registeredContradictionFolds, } from '@retic
587
598
  export { MCP_SSE_PATH, MCP_MESSAGE_PATH } from '@reticlehq/core';
588
599
  export { BrowserPool, DEFAULT_LEASE_TTL_MS } from './portal/pool/browser-pool.js';
589
600
  export { playwrightLauncher, resolveMaxContexts } from './portal/pool/playwright-launcher.js';
601
+ export { makePageErrorJournal, uncaughtIdentity } from './portal/pool/page-error-journal.js';
590
602
  export { appendReticleParams } from './surface/tools/lease-tools.js';
591
603
  export { writePid, removePid, isRunning, logPath, readPid, isAlive, } from './command/daemon/daemon.js';
592
604
  // The daemon's own liveness vocabulary, exported so a GATE can read a daemon log back and say how
@@ -94,6 +94,8 @@ export function causalSummary(events, context = {}) {
94
94
  const signals = [];
95
95
  const fieldsChanged = [];
96
96
  let layoutShift;
97
+ // The shift inside this window, summed from each entry's own `shift` (#1784).
98
+ let windowShift;
97
99
  let longTasks = 0;
98
100
  let ignoredBookkeeping = 0;
99
101
  for (const event of events) {
@@ -166,7 +168,11 @@ export function causalSummary(events, context = {}) {
166
168
  pushUnique(fieldsChanged, data['field']);
167
169
  break;
168
170
  case EventType.PERF:
169
- if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
171
+ if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['shift']) {
172
+ windowShift = (windowShift ?? 0) + data['shift'];
173
+ }
174
+ else if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
175
+ // An SDK older than #1784 sends only the running total: the most it allows is its max.
170
176
  layoutShift = Math.max(layoutShift ?? 0, data['value']);
171
177
  }
172
178
  else if (data['metric'] === PerfMetric.LONGTASK) {
@@ -182,6 +188,7 @@ export function causalSummary(events, context = {}) {
182
188
  // store, and shrinking it because the evidence was trimmed would make the transient window read as
183
189
  // narrower than it was — the one number here that must survive the trim intact.
184
190
  const settle = stateSettle(stateDiffs);
191
+ const shiftInWindow = windowShift ?? layoutShift;
185
192
  return {
186
193
  net: {
187
194
  total: netTotal,
@@ -204,7 +211,7 @@ export function causalSummary(events, context = {}) {
204
211
  ? {}
205
212
  : { fieldsChanged: cap(fieldsChanged, 'fieldsChanged', elided) }),
206
213
  ...(0 === Object.keys(elided).length ? {} : { elided }),
207
- ...(layoutShift === undefined ? {} : { layoutShift }),
214
+ ...(shiftInWindow === undefined ? {} : { layoutShift: shiftInWindow }),
208
215
  longTasks,
209
216
  ...(true === context.stateUnwatched ? { stateUnwatched: true } : {}),
210
217
  ...(true === context.truncated ? { truncated: true } : {}),
@@ -4,8 +4,20 @@
4
4
  * perspective a flow that drifted no longer behaves, so it must not read as green. The actionable
5
5
  * "why" is lifted from the replay's decision envelope (or its error) into failureReason.
6
6
  */
7
- import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult } from '@reticlehq/core';
7
+ import { ReplayStatus, RunFlowStatus, type FlowFile, type FlowReplayResult, type RunFlowResult, type RunStepResult } from '@reticlehq/core';
8
8
  /** OK → PASS; UNVERIFIABLE → SKIPPED; DRIFT/ERROR → FAIL (a healed flow is produced by the heal path, not plain replay). */
9
9
  export declare function runFlowStatusOf(status: ReplayStatus): RunFlowStatus;
10
+ /** One step as synced: what it did, where, and whether it held. */
11
+ /**
12
+ * A run step with how far a sequence got. Kept out of the shared run schema on purpose: that schema
13
+ * ships in the page SDK, where every byte counts, and only the CLI score in this same process reads
14
+ * the field. A schema reader strips it, which loses nothing it needs.
15
+ */
16
+ export interface ScoredRunStep extends RunStepResult {
17
+ subSteps?: {
18
+ passed: number;
19
+ total: number;
20
+ };
21
+ }
10
22
  /** Map one replay (plus its measured duration) into a RunFlowResult for the artifact's flows[]. */
11
23
  export declare function mapReplayToFlowResult(replay: FlowReplayResult, durationMs: number, flow?: FlowFile): RunFlowResult;
@@ -15,7 +15,6 @@ export function runFlowStatusOf(status) {
15
15
  return RunFlowStatus.FAIL;
16
16
  }
17
17
  const clip = (text) => text.length > RUN_TEXT_MAX ? `${text.slice(0, RUN_TEXT_MAX - 1)}…` : text;
18
- /** One step as synced: what it did, where, and whether it held. */
19
18
  function stepResultOf(step) {
20
19
  return {
21
20
  step: step.step,
@@ -26,6 +25,7 @@ function stepResultOf(step) {
26
25
  ...(step.consequence === undefined ? {} : { consequence: clip(step.consequence) }),
27
26
  ...(step.error === undefined ? {} : { error: clip(step.error) }),
28
27
  ...(step.drift === undefined ? {} : { drift: step.drift.reasonKind }),
28
+ ...(step.subSteps === undefined ? {} : { subSteps: step.subSteps }),
29
29
  };
30
30
  }
31
31
  /**
@@ -10,12 +10,26 @@
10
10
  * same artifact byte-for-byte.
11
11
  */
12
12
  import { reportRunTelemetry } from '../../telemetry/run-telemetry.js';
13
- import { boundFlowName, RunFlowStatus, VerifyPhase, } from '@reticlehq/core';
13
+ import { boundFlowName, RunFlowStatus, VerifyPhase, ReplayStatus, } from '@reticlehq/core';
14
14
  import { buildVerificationRun, } from './artifact/build-verification-run.js';
15
15
  import { mapReplayToFlowResult } from './artifact/replay-mapping.js';
16
16
  import { buildRepairPackets } from './repair-prompt.js';
17
17
  import { withShotRun } from './default-run-id.js';
18
18
  import { buildRisks, classifyChangedFiles, } from './risk-classify.js';
19
+ /**
20
+ * A flow whose replay threw (its browser session was lost, the app stopped answering) proves
21
+ * nothing, and it must not take the other flows of the suite down with it. It is graded as a replay
22
+ * that could not be completed: skipped, never a pass, with the error as the reason.
23
+ */
24
+ function replayThatCouldNotRun(name, error) {
25
+ const message = error instanceof Error ? error.message : String(error);
26
+ return {
27
+ name,
28
+ status: ReplayStatus.UNVERIFIABLE,
29
+ steps: [],
30
+ unverifiable: { reason: `the replay stopped before it could be graded: ${message}` },
31
+ };
32
+ }
19
33
  export class ReticleRunner {
20
34
  #port;
21
35
  constructor(port) {
@@ -58,7 +72,7 @@ export class ReticleRunner {
58
72
  name: boundFlowName(name),
59
73
  at: start,
60
74
  });
61
- const replay = await withShotRun(runId, () => this.#port.replayFlow(name));
75
+ const replay = await withShotRun(runId, () => this.#port.replayFlow(name)).catch((error) => replayThatCouldNotRun(name, error));
62
76
  replays.push(replay);
63
77
  const mapped = mapReplayToFlowResult(replay, this.#port.now() - start);
64
78
  flows.push(mapped);
@@ -1,10 +1,12 @@
1
1
  import { Verified, type JournalAction, type RunReport } from '@reticlehq/core';
2
+ /** A run with no agent call for this long is dropped. A working agent calls much more often. */
3
+ export declare const RUN_IDLE_LIMIT_MS: number;
2
4
  export declare class RunLedger {
3
5
  #private;
4
- /** An agent call starts. The first one starts the run, and a call from another agent starts a new one. */
6
+ /** An agent call starts. The agent's first call starts its run. A run idle too long is dropped. */
5
7
  begin(now: number, owner: string): void;
6
8
  /** An agent call ended. The first one that left a verdict sets the time to the first verdict. */
7
- after(now: number): void;
9
+ after(now: number, owner?: string): void;
8
10
  /** A journal record from the agent's own call. A record with no verdict is not a claim. */
9
11
  verdict(action: JournalAction): void;
10
12
  flowSaved(name: string): void;
@@ -13,9 +15,9 @@ export declare class RunLedger {
13
15
  memoryWritten(count?: number): void;
14
16
  /** Notes given to the agent. Each one counts once, however often it is shown. */
15
17
  memoryRead(notes: readonly string[]): void;
16
- isEmpty(): boolean;
17
- /** The report, without the cloud line (the caller knows the project). Empties the ledger. */
18
- take(now: number): RunReport;
18
+ isEmpty(owner?: string): boolean;
19
+ /** The owner's report, without the cloud line (the caller knows the project). Ends that run. */
20
+ take(now: number, owner?: string): RunReport;
19
21
  }
20
22
  /** The daemon's ledger. */
21
23
  export declare const runLedger: RunLedger;