@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
@@ -41,7 +41,7 @@ import { describeSync } from './sync-describe.js';
41
41
  import { diskSink, diskSource, policyScreenshots, readCloudState } from './sync-disk.js';
42
42
  import { resolveProjectCloud } from './cloud-config.js';
43
43
  import { watch } from 'node:fs';
44
- import { BackgroundSyncState, backgroundSyncGate, } from './background-sync.js';
44
+ import { BackgroundSyncState, backgroundSyncGate, closeCycleGate, } from './background-sync.js';
45
45
  import { unmatchedRoots, unsyncedRoots } from './unsynced-roots.js';
46
46
  /**
47
47
  * The backstop: how often the daemon cycles when nothing under `.reticle` has changed. Long, because
@@ -186,38 +186,39 @@ export function startSyncDaemon(deps) {
186
186
  let wasLinked;
187
187
  /** The project the last cycle synced to: a re-link to another one is announced too. */
188
188
  let wasProject;
189
- /** The last count logged per root: a root is announced when its count changes, never per tick. */
190
- const announced = new Map();
189
+ /** The aggregate last logged: unlinked roots are announced as one line per change, never per tick. */
190
+ let announcedUnsynced;
191
191
  let unsynced = [];
192
192
  /** Which roots pushed this cycle, and whether each is linked by its own link file. */
193
193
  let rootsLinked = new Map();
194
194
  /** The linked roots that send flows: a link with `sync.flows` off keeps every flow local. */
195
195
  let rootsSendFlows = new Set();
196
- /**
197
- * Count what every root still owes, and say once per change where runs sit that nothing sends.
198
- * Linked roots are listed too (the platform report and `doctor` read them), but only an unlinked
199
- * one earns a log line: a linked root's pending runs are the next cycle's job.
200
- */
196
+ /** Count what every root owes; unlinked roots are one aggregate line per change, never per tick. */
201
197
  const refreshUnsynced = async () => {
202
198
  const linked = rootsLinked;
203
199
  const sendsFlows = rootsSendFlows;
204
200
  unsynced = await unsyncedRoots(
205
201
  // With what no project claimed, which no cycle pushes: the dashboard's unsent list left it out.
206
202
  [...linked.keys(), ...unmatchedRoots(homedir())], (r) => Promise.resolve(true === linked.get(r)), (r) => Promise.resolve(sendsFlows.has(r)));
207
- for (const entry of unsynced) {
208
- const held = entry.runs + entry.flows;
209
- if (entry.linked || announced.get(entry.root) === held) {
210
- continue;
211
- }
212
- announced.set(entry.root, held);
213
- log('reticle_cloud_unsynced_root', {
214
- root: entry.root,
215
- runs: entry.runs,
216
- flows: entry.flows,
217
- linked: entry.linked,
218
- fix: "run `reticle connect` in that folder's project: this work is on this machine only",
219
- });
203
+ const unlinked = unsynced.filter((entry) => !entry.linked);
204
+ const runs = unlinked.reduce((total, entry) => total + entry.runs, 0);
205
+ const flows = unlinked.reduce((total, entry) => total + entry.flows, 0);
206
+ const signature = JSON.stringify([unlinked.map((e) => [e.root, e.runs, e.flows]), runs, flows]);
207
+ if (0 === unlinked.length) {
208
+ announcedUnsynced = undefined;
209
+ return;
210
+ }
211
+ if (announcedUnsynced === signature) {
212
+ return;
220
213
  }
214
+ announcedUnsynced = signature;
215
+ log('reticle_cloud_unsynced_root', {
216
+ count: unlinked.length,
217
+ runs,
218
+ flows,
219
+ examples: unlinked.slice(0, 3).map((entry) => entry.root),
220
+ fix: 'run `reticle connect` in each project that owns these folders: this work is on this machine only',
221
+ });
221
222
  };
222
223
  /**
223
224
  * The cloud as this tick may use it: unchanged when the gate lets the root send, else with no
@@ -778,12 +779,10 @@ export function attachCloudSync(deps) {
778
779
  // and on the backstop timer.
779
780
  // Watching them too means one recursive watcher per linked repo — add it if siblings lag.
780
781
  watch: (onChange) => watchReticleRoot(deps.reticleRoot, onChange),
781
- // Sent only while somebody is signed in, to a linked project that did not switch it off.
782
- ...(signedIn === undefined
783
- ? {}
784
- : {
785
- gate: (root, cloud) => backgroundSyncGate({ reticleRoot: root, cloud, env: deps.env, signedIn }),
786
- }),
782
+ // Sent only to a linked project that did not switch it off; see `closeCycleGate` for no sign-in.
783
+ gate: (root, cloud) => signedIn === undefined
784
+ ? closeCycleGate(root, deps.env)
785
+ : backgroundSyncGate({ reticleRoot: root, cloud, env: deps.env, signedIn }),
787
786
  ...(deps.request === undefined ? {} : { request: deps.request }),
788
787
  ...(deps.intervalMs === undefined ? {} : { intervalMs: deps.intervalMs }),
789
788
  });
@@ -20,6 +20,11 @@ const HTTP_OK = 200;
20
20
  const NOT_THERE = new Set([404, 405]);
21
21
  /** Refused for good: too large (413), too many for one run (409). Retrying cannot change it. */
22
22
  const REFUSED_FOR_GOOD = new Set([409, 413]);
23
+ /**
24
+ * The platform takes only JPEG and answers 400 to anything else, which stops the cycle. A desktop
25
+ * capture from a page SDK before 3.7 is a PNG, so it stays on this machine.
26
+ */
27
+ const isJpeg = (image) => 0xff === image[0] && 0xd8 === image[1];
23
28
  /** The screenshots a run artifact lists, read defensively: a malformed entry is skipped. */
24
29
  function listed(payload) {
25
30
  const raw = 'object' === typeof payload && null !== payload
@@ -78,7 +83,7 @@ export async function syncScreenshots(deps) {
78
83
  done.add(keyOf(shot));
79
84
  sent[run.runId] = [...done];
80
85
  };
81
- if (image.byteLength > STEP_SCREENSHOT.MAX_BYTES) {
86
+ if (image.byteLength > STEP_SCREENSHOT.MAX_BYTES || !isJpeg(image)) {
82
87
  markDone();
83
88
  continue;
84
89
  }
@@ -19,7 +19,8 @@ export function makeJournalAttach(deps) {
19
19
  log('journal_skipped_unsafe_session_id', { sessionId: session.id });
20
20
  return;
21
21
  }
22
- const journal = new SessionJournal(deps.fs, session.artifactRoot ?? deps.reticleRoot, session.id);
22
+ // The session's clock stamps each record's `t`, so it also bounds a cut that left nothing to parse.
23
+ const journal = new SessionJournal(deps.fs, session.artifactRoot ?? deps.reticleRoot, session.id, { now: () => session.elapsed() });
23
24
  // Same SessionJournal is both the write sink and the read fall-through for queries after eviction.
24
25
  session.setJournal(new JournalRecorder(journal, { now: () => session.elapsed() }), journal);
25
26
  };
@@ -44,6 +44,13 @@ export interface DriveProgram {
44
44
  *
45
45
  * So it is not a tool and not a rule. It runs at teardown, in code, on every session.
46
46
  */
47
+ /**
48
+ * The claims one tape step carries: its own `expect`, and the `expect` of each of its sub-steps.
49
+ *
50
+ * A `reticle_act { steps }` journey is one tape step, and each claim is on its sub-step. When only
51
+ * the step's own `expect` was read, a journey with four held claims was "unproven" and was not saved.
52
+ */
53
+ export declare function claimsOf(step: TapeStep): Predicate[];
47
54
  /** The one condition on saving, and the reason the whole thing is safe. */
48
55
  export declare function carriesAnAssertion(steps: readonly TapeStep[]): boolean;
49
56
  /** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
@@ -1,4 +1,4 @@
1
- import { ActionType, QueryBy, REPLAY_PROGRAM_VERSION, asFlowName, defaultIsSensitiveKey, } from '@reticlehq/core';
1
+ import { ActionType, FlowPredicateSchema, QueryBy, REPLAY_PROGRAM_VERSION, asFlowName, asRecord, isCredentialFieldName, } from '@reticlehq/core';
2
2
  import { journeySentence } from '@reticlehq/core';
3
3
  /**
4
4
  * What a session drove, saved as a replayable flow — without anybody asking for it.
@@ -11,9 +11,23 @@ import { journeySentence } from '@reticlehq/core';
11
11
  *
12
12
  * So it is not a tool and not a rule. It runs at teardown, in code, on every session.
13
13
  */
14
+ /**
15
+ * The claims one tape step carries: its own `expect`, and the `expect` of each of its sub-steps.
16
+ *
17
+ * A `reticle_act { steps }` journey is one tape step, and each claim is on its sub-step. When only
18
+ * the step's own `expect` was read, a journey with four held claims was "unproven" and was not saved.
19
+ */
20
+ export function claimsOf(step) {
21
+ const subs = Array.isArray(step.args['steps']) ? step.args['steps'] : [];
22
+ const subClaims = subs.flatMap((sub) => {
23
+ const parsed = FlowPredicateSchema.safeParse(asRecord(sub)['expect']);
24
+ return parsed.success ? [parsed.data] : [];
25
+ });
26
+ return step.expect === undefined ? subClaims : [step.expect, ...subClaims];
27
+ }
14
28
  /** The one condition on saving, and the reason the whole thing is safe. */
15
29
  export function carriesAnAssertion(steps) {
16
- return steps.some((step) => step.expect !== undefined);
30
+ return steps.some((step) => 0 < claimsOf(step).length);
17
31
  }
18
32
  /** Squeeze any string into one safe, lower-case filename segment. */
19
33
  const segment = (value, max) => value
@@ -28,10 +42,12 @@ const segment = (value, max) => value
28
42
  * said nothing about what it was for.
29
43
  */
30
44
  function provedSentence(steps) {
31
- return journeySentence(steps.flatMap((step) => (step.expect === undefined ? [] : [step.expect])));
45
+ return journeySentence(steps.flatMap(claimsOf));
32
46
  }
33
47
  /** Every auto-saved flow's name starts here, which is what lets a merge tell them from hand-named ones. */
34
48
  export const DRIVE_FLOW_PREFIX = 'drive-';
49
+ /** The most characters one segment of core's FLOW_NAME_PATTERN holds. */
50
+ const FLOW_NAME_MAX_LENGTH = 64;
35
51
  /**
36
52
  * A readable name for a journey, led by WHY it was driven.
37
53
  *
@@ -52,7 +68,10 @@ export function driveFlowName(route, steps = []) {
52
68
  // Minted here: every character has just been forced through `segment`, so this IS the validated
53
69
  // boundary the brand is meant to be created at.
54
70
  const parts = [lead, ...('' !== why || 0 === leg.length ? [] : [leg])].filter((p) => '' !== p);
55
- return asFlowName(`${DRIVE_FLOW_PREFIX}${0 === parts.length ? 'journey' : parts.join('-')}`);
71
+ // Cut to the flow-name limit: a sentence and a route together went over it, the save refused
72
+ // the name, and session end dropped the flow with nothing said.
73
+ const body = segment(parts.join('-'), FLOW_NAME_MAX_LENGTH - DRIVE_FLOW_PREFIX.length);
74
+ return asFlowName(`${DRIVE_FLOW_PREFIX}${'' === body ? 'journey' : body}`);
56
75
  }
57
76
  /** Why session end saved no flow, as reported on the `flow_recorded` step. */
58
77
  export const DriveFlowSkipReason = {
@@ -106,16 +125,25 @@ function segmentsByRoute(steps) {
106
125
  return out;
107
126
  }
108
127
  /**
109
- * A fill into a field the network channel would redact — the sign-in a journey must not carry.
128
+ * A fill into a field whose name says it holds a credential — the sign-in a journey must not carry.
110
129
  * Read off the recorder's anchor args: a testid names the field by `value`, a role by `name`.
111
130
  */
112
131
  function typesASecret(step) {
113
- const action = step.args['action'];
114
- if (ActionType.FILL !== action && ActionType.TYPE !== action)
132
+ // A sign-in sent as one reticle_act sequence carries its fills as sub-steps.
133
+ const subs = step.args['steps'];
134
+ if (Array.isArray(subs)) {
135
+ return subs.some((sub) => fillsASecret(asRecord(sub)));
136
+ }
137
+ return fillsASecret(step.args);
138
+ }
139
+ function fillsASecret(args) {
140
+ const action = args['action'];
141
+ if (ActionType.FILL !== action && ActionType.TYPE !== action) {
115
142
  return false;
116
- const by = step.args['by'];
117
- const field = QueryBy.TESTID === by ? step.args['value'] : step.args['name'];
118
- return 'string' === typeof field && defaultIsSensitiveKey(field);
143
+ }
144
+ const by = args['by'];
145
+ const field = QueryBy.TESTID === by ? args['value'] : args['name'];
146
+ return 'string' === typeof field && isCredentialFieldName(field);
119
147
  }
120
148
  /**
121
149
  * The tape without the attempts an agent made again. An action that declared nothing, followed at
@@ -126,11 +154,13 @@ function typesASecret(step) {
126
154
  function withoutRetries(steps) {
127
155
  return steps.filter((step, i) => {
128
156
  const next = steps[i + 1];
129
- if (next === undefined || step.expect !== undefined || next.expect === undefined)
157
+ if (next === undefined || step.expect !== undefined || next.expect === undefined) {
130
158
  return true;
159
+ }
131
160
  // Tried again where it was tried: a page later it is a step of the journey, not a retry.
132
- if (step.route !== next.route || step.page !== next.page || step.mocked !== next.mocked)
161
+ if (step.route !== next.route || step.page !== next.page || step.mocked !== next.mocked) {
133
162
  return true;
163
+ }
134
164
  const { expect: _declared, ...again } = next.args;
135
165
  const { expect: _none, ...first } = step.args;
136
166
  return step.tool !== next.tool || JSON.stringify(first) !== JSON.stringify(again);
@@ -138,8 +168,9 @@ function withoutRetries(steps) {
138
168
  }
139
169
  /** A flow per journey, so two journeys in one session are two regression tests rather than one. */
140
170
  export function driveFlowsFrom(tape) {
141
- if (tape === undefined || 0 === tape.steps.length)
171
+ if (tape === undefined || 0 === tape.steps.length) {
142
172
  return { programs: [], outcome: {} };
173
+ }
143
174
  const programs = [];
144
175
  let unproven = 0;
145
176
  let mocked = 0;
@@ -30,8 +30,10 @@ export interface JournalReadLoss {
30
30
  /** Parsed records evicted from the retained set. Exact, unlike `droppedBytes`. */
31
31
  droppedEvents: number;
32
32
  /**
33
- * Latest timestamp of parsed cache evictions, inclusive. Omitted after a byte cut skips
34
- * unparsed records: their time range is unknown even when newer bytes survive.
33
+ * Latest `t` the loss can reach, inclusive: a parsed record that was evicted, or the bound of a
34
+ * byte cut. Omitted while a cut has no bound, which means every window is lost. A cut has no
35
+ * bound until a record after it is parsed, and none at all when the clock can have started
36
+ * again across it.
35
37
  */
36
38
  lostThroughT?: number;
37
39
  /** Human-and-agent readable summary. Present so a consumer never has to compose one. */
@@ -42,6 +42,13 @@ export interface SessionJournalOptions {
42
42
  maxReadBytes?: number;
43
43
  maxRetainedBytes?: number;
44
44
  maxRetainedEvents?: number;
45
+ /**
46
+ * The session's elapsed-ms clock, the same clock that stamps each record's `t`. It bounds a cut
47
+ * that left no readable record: every skipped byte was on disk at the read, so its `t` is at or
48
+ * before the time of the read. Without it, such a cut stays unbounded until a later read parses
49
+ * a record.
50
+ */
51
+ now?: () => number;
45
52
  }
46
53
  /**
47
54
  * Default byte budget for one serialized writer's event ledger (#986).
@@ -146,11 +146,13 @@ export class SessionJournal {
146
146
  #droppedBytes = 0;
147
147
  /** Parsed records evicted from the retained set. Exact — these were counted, not estimated. */
148
148
  #droppedEvents = 0;
149
- /**
150
- * Latest parsed eviction timestamp, or +Infinity after unparsed bytes were skipped.
151
- * -Infinity means no loss. Later intact reads cannot narrow an unknown discarded time range.
152
- */
149
+ /** Latest `t` that a cut or an eviction can reach, inclusive. -Infinity means no loss. */
153
150
  #lostThroughT = Number.NEGATIVE_INFINITY;
151
+ /** A cut skipped bytes and no record after it has been parsed yet, so its bound is unknown. */
152
+ #cutPending = false;
153
+ /** The last record parsed, so a cut can tell whether the clock started again across it. */
154
+ #lastParsed;
155
+ #now;
154
156
  constructor(fs, root, sessionId, options = {}) {
155
157
  if (!isValidSessionId(sessionId)) {
156
158
  throw new Error(`refusing to journal an unsafe session id: ${sessionId}`);
@@ -171,6 +173,7 @@ export class SessionJournal {
171
173
  this.#maxRetainedBytes = boundedOption(options.maxRetainedBytes, JOURNAL_READ_LIMITS.MAX_RETAINED_BYTES, 'maxRetainedBytes');
172
174
  this.#maxRetainedEvents = boundedOption(options.maxRetainedEvents, JOURNAL_READ_LIMITS.MAX_RETAINED_EVENTS, 'maxRetainedEvents');
173
175
  this.#eventBytesCap = cap;
176
+ this.#now = options.now;
174
177
  }
175
178
  /**
176
179
  * Append a batch, or refuse it at the byte cap and say so.
@@ -416,10 +419,15 @@ export class SessionJournal {
416
419
  // never skips, so its start IS the offset we asked for.
417
420
  const from = chunk.from ?? this.#eventBytesConsumed;
418
421
  const skipped = Math.max(0, from - this.#eventBytesConsumed);
419
- this.#ingestTail(chunk.text);
422
+ // Before the ingest, so the first record parsed after the cut bounds it.
420
423
  if (skipped > 0) {
421
424
  this.#noteCut(skipped);
422
425
  }
426
+ this.#ingestTail(chunk.text);
427
+ const now = this.#now;
428
+ if (this.#cutPending && now !== undefined) {
429
+ this.#boundCut({ t: now() });
430
+ }
423
431
  // Resume at the end of the last COMPLETE line, counted forward over the bytes that actually
424
432
  // ARRIVED. Not from `chunk.size`: that is the file's end as `fstat` reported it, and a read
425
433
  // truncated or cut short returns less — a cursor there would skip every unread byte, silently
@@ -494,6 +502,10 @@ export class SessionJournal {
494
502
  }
495
503
  const result = ReticleEventSchema.safeParse(parsed);
496
504
  if (result.success) {
505
+ if (this.#cutPending) {
506
+ this.#boundCut(result.data);
507
+ }
508
+ this.#lastParsed = result.data;
497
509
  this.#eventCache.push(result.data);
498
510
  // Its cost in the file, newline included — so the retained bound is in the same unit the
499
511
  // read ceiling and the loss report are.
@@ -543,13 +555,37 @@ export class SessionJournal {
543
555
  }
544
556
  }
545
557
  /**
546
- * Skipped bytes have unknown timestamps: page clocks can reset while the journal id survives.
547
- * A surviving tail cannot bound records that were never parsed. Later intact reads cannot
548
- * reconstruct that history; finite bounds apply only to parsed cache evictions.
558
+ * Skipped bytes are not parsed, so their `t` is not known at the cut. The first record parsed
559
+ * after the cut, or the time of the read, can bound them (see `#boundCut`). Until then, the cut
560
+ * reaches every window.
549
561
  */
550
562
  #noteCut(skippedBytes) {
551
563
  this.#droppedBytes += skippedBytes;
552
- this.#lostThroughT = Number.POSITIVE_INFINITY;
564
+ this.#cutPending = true;
565
+ }
566
+ /**
567
+ * Bound the pending cut at `next`: the first record parsed after it, or the time of the read.
568
+ *
569
+ * The file is append-only, and one connection stamps `t` from one monotonic clock
570
+ * (`Session.elapsed`). So when the clock did not start again across the cut, each skipped record
571
+ * has a `t` at or before `next.t`.
572
+ *
573
+ * A reload or a reconnect keeps the session id and starts the clock again, and one window can
574
+ * span it: a window opened at t=150 on the old connection can hold skipped old records at t=200
575
+ * while the new connection's records start at t=10. A bound from the new clock then clears a
576
+ * window that lost evidence. So the cut stays unbounded, for good, when the reset cannot be
577
+ * ruled out: no record was parsed before the cut, or `t` or `seq` went down, or the document
578
+ * changed. This check is conservative but not complete. A reconnect in the same document whose
579
+ * new clock already passed the old `t` shows none of the three signs.
580
+ */
581
+ #boundCut(next) {
582
+ const last = this.#lastParsed;
583
+ this.#cutPending = false;
584
+ if (last === undefined || clockStartedAgain(last, next)) {
585
+ this.#lostThroughT = Number.POSITIVE_INFINITY;
586
+ return;
587
+ }
588
+ this.#lostThroughT = Math.max(this.#lostThroughT, next.t);
553
589
  }
554
590
  /**
555
591
  * What these reads could not produce, or `undefined` when nothing was lost.
@@ -562,7 +598,7 @@ export class SessionJournal {
562
598
  if (0 === this.#droppedBytes && 0 === this.#droppedEvents) {
563
599
  return undefined;
564
600
  }
565
- const bounded = Number.isFinite(this.#lostThroughT);
601
+ const bounded = !this.#cutPending && Number.isFinite(this.#lostThroughT);
566
602
  return {
567
603
  droppedBytes: this.#droppedBytes,
568
604
  droppedEvents: this.#droppedEvents,
@@ -621,6 +657,18 @@ export class SessionJournal {
621
657
  return out;
622
658
  }
623
659
  }
660
+ /** Did the clock start again between `last` and `next`? A field one record lacks proves nothing. */
661
+ function clockStartedAgain(last, next) {
662
+ if (next.t < last.t) {
663
+ return true;
664
+ }
665
+ if (last.seq !== undefined && next.seq !== undefined && next.seq < last.seq) {
666
+ return true;
667
+ }
668
+ return (last.documentId !== undefined &&
669
+ next.documentId !== undefined &&
670
+ next.documentId !== last.documentId);
671
+ }
624
672
  /**
625
673
  * The closure report a session's sidecar holds — details when it can be read, the bare fact when not.
626
674
  *
@@ -1,3 +1,12 @@
1
+ /**
2
+ * The `.reticle` folder a caller meant by `root`. Agents pass the project folder, the one they work
3
+ * in, so a folder that holds a `.reticle` stands for it — and so does any existing directory, since
4
+ * a fresh worktree has none until the first run writes one, the same way a `.reticle` path is
5
+ * accepted before it exists. A path nowhere on disk is the caller's to fix, and the refusals name
6
+ * the path to pass. Openings exported so error-recovery classifies both as the caller's mistake.
7
+ */
8
+ export declare const ARTIFACT_ROOT_ABSOLUTE_REFUSAL: string;
9
+ export declare const ARTIFACT_ROOT_MISSING_REFUSAL: string;
1
10
  /** Claim `root` for the session a lease registers. Takes a `.reticle` folder or the project folder holding one. */
2
11
  export declare function claimArtifactRoot(leaseId: string, root: string): void;
3
12
  /** Every root a lease claimed in this process, released or not. Sync reads it to find the work. */
@@ -17,20 +17,25 @@ const claims = new Map();
17
17
  const everClaimed = new Set();
18
18
  /**
19
19
  * The `.reticle` folder a caller meant by `root`. Agents pass the project folder, the one they work
20
- * in, so a folder that holds a `.reticle` stands for it. Anything else is refused with the path to pass.
20
+ * in, so a folder that holds a `.reticle` stands for it — and so does any existing directory, since
21
+ * a fresh worktree has none until the first run writes one, the same way a `.reticle` path is
22
+ * accepted before it exists. A path nowhere on disk is the caller's to fix, and the refusals name
23
+ * the path to pass. Openings exported so error-recovery classifies both as the caller's mistake.
21
24
  */
25
+ export const ARTIFACT_ROOT_ABSOLUTE_REFUSAL = `root must be an absolute path to a ${ReticleDir.ROOT} folder`;
26
+ export const ARTIFACT_ROOT_MISSING_REFUSAL = `root must be an existing directory, or the ${ReticleDir.ROOT} folder inside one`;
22
27
  function resolveArtifactRoot(root) {
23
28
  if (!isAbsolute(root)) {
24
- throw new Error(`root must be an absolute path to a ${ReticleDir.ROOT} folder, got ${root}`);
29
+ throw new Error(`${ARTIFACT_ROOT_ABSOLUTE_REFUSAL}, got ${root}`);
25
30
  }
26
31
  if (ReticleDir.ROOT === basename(root)) {
27
32
  return root;
28
33
  }
29
34
  const inside = join(root, ReticleDir.ROOT);
30
- if (isDirectory(inside)) {
35
+ if (isDirectory(inside) || isDirectory(root)) {
31
36
  return inside;
32
37
  }
33
- throw new Error(`root must be the ${ReticleDir.ROOT} folder, or a project folder that contains one, got ${root}. Pass ${inside}`);
38
+ throw new Error(`${ARTIFACT_ROOT_MISSING_REFUSAL}, got ${root}. Pass ${inside}`);
34
39
  }
35
40
  function isDirectory(path) {
36
41
  try {
@@ -57,6 +57,18 @@ export declare const WS_CLOSE_REASON: {
57
57
  */
58
58
  readonly INVALID_HELLO_CLOSE: "invalid hello: version skew. Put SDK, build plugin and server on one release.";
59
59
  };
60
+ /** WS close codes + reasons the bridge sends to the SDK (1008 = policy violation, 1013 = try again later). */
61
+ export declare const WS_CLOSE: {
62
+ readonly TOO_MANY_HANDSHAKES: readonly [1013, "too many pending handshakes"];
63
+ readonly HELLO_TIMEOUT: readonly [1008, "hello timeout"];
64
+ readonly INVALID_MESSAGE: readonly [1008, "invalid message"];
65
+ readonly INVALID_HELLO: readonly [1008, "invalid hello: version skew. Put SDK, build plugin and server on one release."];
66
+ readonly HELLO_DUPLICATE: readonly [1008, "hello already received"];
67
+ readonly AUTH_FAILED: readonly [1008, "authentication failed — restart the dev server; a reload keeps the old pairing token"];
68
+ readonly SESSION_LIMIT: readonly [1013, "session limit reached"];
69
+ readonly PROTOCOL_MISMATCH: readonly [1008, "protocol version mismatch — @reticlehq/browser and server disagree"];
70
+ readonly HANDLER_FAILED: readonly [1011, "the Reticle daemon failed handling a message — reload the page to reconnect"];
71
+ };
60
72
  interface BridgeOptions {
61
73
  port: number;
62
74
  host?: string;
@@ -107,10 +119,7 @@ export declare class Bridge {
107
119
  */
108
120
  attachSessionReady(handler: SessionReadyHandler): void;
109
121
  /** Register a callback fired the instant a session is created — used to attach the durable journal. */
110
- /**
111
- * Teardown handler fired when a session is removed (tab closed/disconnected). Without it the journal's
112
- * batched tail (< one flush batch) never reaches disk and the learned ambient map is discarded.
113
- */
122
+ /** Teardown when a session is removed (tab closed/disconnected). Additive — every handler runs. */
114
123
  attachSessionEnd(handler: (session: Session) => Promise<void>): void;
115
124
  /**
116
125
  * Register what to do when the panel's sync button is pressed.
@@ -101,7 +101,7 @@ export const WS_CLOSE_REASON = {
101
101
  INVALID_HELLO_CLOSE: 'invalid hello: version skew. Put SDK, build plugin and server on one release.',
102
102
  };
103
103
  /** WS close codes + reasons the bridge sends to the SDK (1008 = policy violation, 1013 = try again later). */
104
- const WS_CLOSE = {
104
+ export const WS_CLOSE = {
105
105
  TOO_MANY_HANDSHAKES: [1013, 'too many pending handshakes'],
106
106
  HELLO_TIMEOUT: [1008, 'hello timeout'],
107
107
  INVALID_MESSAGE: [1008, 'invalid message'],
@@ -190,8 +190,10 @@ export class Bridge {
190
190
  */
191
191
  #onSessionCreate = [];
192
192
  #onProjectAdopted = [];
193
- /** Fired when a session is removed — flushes its journal tail + persists what it learned. */
194
- #onSessionEnd;
193
+ // Fired when a session is removed. ADDITIVE: a single slot meant one teardown silently displaced
194
+ // the other — the same trap the create-side note describes. The durable journal flushes its tail;
195
+ // the page-error journal drops the session's dedupe memory.
196
+ #onSessionEnd = [];
195
197
  /** Wired by the daemon: push to the dashboard now, because somebody asked in the panel. */
196
198
  #onSyncRequest;
197
199
  #onHarnessRequest;
@@ -737,7 +739,9 @@ export class Bridge {
737
739
  log('session_disconnected', { sessionId: ended.id });
738
740
  // Best-effort teardown: flush the journal tail + persist ambient learning. Never awaited here
739
741
  // (the socket is already closing) and never allowed to reject into the close handler.
740
- void this.#onSessionEnd?.(ended).catch(() => undefined);
742
+ for (const handler of this.#onSessionEnd) {
743
+ void handler(ended).catch(() => undefined);
744
+ }
741
745
  // AFTER teardown is started, and deliberately not awaited on it. The payload says a
742
746
  // session ended, which is already true here; making it wait for the journal flush would
743
747
  // put a hook in the path of a socket close for no gain to the consumer.
@@ -835,12 +839,9 @@ export class Bridge {
835
839
  this.#onSessionReady.push(handler);
836
840
  }
837
841
  /** Register a callback fired the instant a session is created — used to attach the durable journal. */
838
- /**
839
- * Teardown handler fired when a session is removed (tab closed/disconnected). Without it the journal's
840
- * batched tail (< one flush batch) never reaches disk and the learned ambient map is discarded.
841
- */
842
+ /** Teardown when a session is removed (tab closed/disconnected). Additive — every handler runs. */
842
843
  attachSessionEnd(handler) {
843
- this.#onSessionEnd = handler;
844
+ this.#onSessionEnd.push(handler);
844
845
  }
845
846
  /**
846
847
  * Register what to do when the panel's sync button is pressed.
@@ -0,0 +1,7 @@
1
+ import { type NetworkDetail } from './network-detail.js';
2
+ /** The part of a CDP session this needs, kept structural so it can be tested with a fake. */
3
+ export interface CdpLike {
4
+ send(method: string, params?: Record<string, unknown>): Promise<unknown>;
5
+ on(event: string, handler: (event: unknown) => void): void;
6
+ }
7
+ export declare function attachDownloadCapture(cdp: CdpLike, emit: (detail: NetworkDetail) => void, pageUrl: () => string): Promise<void>;
@@ -0,0 +1,92 @@
1
+ import { buildNetworkDetail } from './network-detail.js';
2
+ import { drivenRedactionPolicy } from './driven-redaction.js';
3
+ /**
4
+ * Capture a file the server sends as a download, on a page Reticle drives.
5
+ *
6
+ * A click that becomes a download leaves the page as a navigation the browser turns into a file.
7
+ * Measured on Chromium, nothing the page can see records it: no page `request`/`response` event, no
8
+ * CDP `Network.responseReceived` on the page target, and a Playwright route does not intercept it.
9
+ * So a `net` check on a working export missed. CDP request interception at the response stage does
10
+ * see it, with the status and headers, so this listens there.
11
+ *
12
+ * Document responses only, and every paused response is let through at once: the capture must never
13
+ * change or delay what the page receives. Chromium only; a browser without the Fetch domain gets no
14
+ * capture rather than an error.
15
+ */
16
+ const DOCUMENT_RESPONSES = { patterns: [{ resourceType: 'Document', requestStage: 'Response' }] };
17
+ /**
18
+ * The body is read only for a text attachment of known, small size. Reading it holds the whole file
19
+ * in memory, so a large or binary download keeps its status and headers and gets no body: a body
20
+ * check on it stays undecidable rather than costing the run its memory.
21
+ */
22
+ const MAX_BODY_READ_BYTES = 1024 * 1024;
23
+ const TEXTUAL = /text\/|json|csv|xml/i;
24
+ function readableBodySize(headers) {
25
+ const length = Number(headers['content-length']);
26
+ return (Number.isFinite(length) &&
27
+ length <= MAX_BODY_READ_BYTES &&
28
+ TEXTUAL.test(headers['content-type'] ?? ''));
29
+ }
30
+ async function readBody(cdp, requestId) {
31
+ try {
32
+ const got = (await cdp.send('Fetch.getResponseBody', { requestId }));
33
+ if ('string' !== typeof got.body) {
34
+ return undefined;
35
+ }
36
+ return true === got.base64Encoded ? Buffer.from(got.body, 'base64').toString('utf8') : got.body;
37
+ }
38
+ catch {
39
+ return undefined;
40
+ }
41
+ }
42
+ function headersOf(raw) {
43
+ const headers = {};
44
+ if (!Array.isArray(raw)) {
45
+ return headers;
46
+ }
47
+ for (const entry of raw) {
48
+ const { name, value } = (entry ?? {});
49
+ if ('string' === typeof name && 'string' === typeof value) {
50
+ headers[name.toLowerCase()] = value;
51
+ }
52
+ }
53
+ return headers;
54
+ }
55
+ function isAttachment(headers) {
56
+ return /^\s*attachment\b/i.test(headers['content-disposition'] ?? '');
57
+ }
58
+ async function onPaused(cdp, paused, emit, pageUrl) {
59
+ const requestId = 'string' === typeof paused.requestId ? paused.requestId : undefined;
60
+ try {
61
+ const headers = headersOf(paused.responseHeaders);
62
+ const url = paused.request?.url;
63
+ const status = paused.responseStatusCode;
64
+ if (isAttachment(headers) && 'string' === typeof url && 'number' === typeof status) {
65
+ const method = paused.request?.method;
66
+ const responseBody = requestId !== undefined && readableBodySize(headers)
67
+ ? await readBody(cdp, requestId)
68
+ : undefined;
69
+ emit(buildNetworkDetail({
70
+ url,
71
+ ...('string' === typeof method ? { method } : {}),
72
+ status,
73
+ headers,
74
+ resourceType: 'document',
75
+ ...(responseBody === undefined ? {} : { responseBody }),
76
+ pageUrl: pageUrl(),
77
+ }, drivenRedactionPolicy()));
78
+ }
79
+ }
80
+ catch {
81
+ // Recording is best-effort; letting the response through below is not.
82
+ }
83
+ if (requestId !== undefined) {
84
+ await cdp.send('Fetch.continueRequest', { requestId }).catch(() => undefined);
85
+ }
86
+ }
87
+ export async function attachDownloadCapture(cdp, emit, pageUrl) {
88
+ cdp.on('Fetch.requestPaused', (event) => {
89
+ void onPaused(cdp, event ?? {}, emit, pageUrl);
90
+ });
91
+ await cdp.send('Fetch.enable', DOCUMENT_RESPONSES);
92
+ }