@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,7 +1,7 @@
1
1
  import { TESTID_FOUND_UNDER_SCHEMA } from './query-hint-schema.js';
2
2
  import { z } from 'zod';
3
- import { EMPTY_SESSION_LIST_OUTPUT, emptySessionList, nextStepBesideLiveSessions, } from './empty-session-list.js';
4
- import { AttrNamesSchema, QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
3
+ import { EMPTY_SESSION_LIST_OUTPUT, emptySessionList, NEXT_STEP_FIELD, nextStepBesideLiveSessions, } from './empty-session-list.js';
4
+ import { AttrNamesSchema, NearestTargetSchema, QueryBy, ReticleCommand, SnapshotMode, } from '@reticlehq/core';
5
5
  import { ReticleTool } from '@reticlehq/core';
6
6
  import { withSizeCost } from '../../portal/session/output-budget.js';
7
7
  import { applySnapshotDelta, SnapshotCache } from './read/snapshot-delta.js';
@@ -40,6 +40,7 @@ import { buildCoverageTools } from './coverage-tools.js';
40
40
  import { VERIFY_CHANGE_TOOLS } from '../../language/flows/verify-change-tools.js';
41
41
  import { CRAWL_TOOLS } from '../../features/crawl/crawl-tools.js';
42
42
  import { EXPLORE_TOOLS } from './explore-tools.js';
43
+ import { COMPARE_TOOLS } from './compare-tools.js';
43
44
  import { SCROLL_TOOLS } from '../../portal/input/scroll-tools.js';
44
45
  import { NETWORK_MOCK_TOOLS } from '../../portal/input/network-mock-tools.js';
45
46
  import { VIEWPORT_TOOLS } from '../../portal/input/viewport-tools.js';
@@ -145,11 +146,12 @@ export const RAW_TOOLS = withCompactEnvelope([
145
146
  })));
146
147
  const sessions = await list();
147
148
  // An empty list is a dead end on its own; see empty-session-list.ts for what it carries.
148
- if (0 === sessions.length)
149
+ if (0 === sessions.length) {
149
150
  return emptySessionList(deps, sessions);
151
+ }
150
152
  // Another project's tab is up. This caller's project may still never have been wired.
151
153
  const nextStep = nextStepBesideLiveSessions(deps, sessions);
152
- return nextStep === undefined ? { sessions } : { sessions, next_step: nextStep };
154
+ return nextStep === undefined ? { sessions } : { sessions, [NEXT_STEP_FIELD]: nextStep };
153
155
  },
154
156
  },
155
157
  {
@@ -177,6 +179,10 @@ export const RAW_TOOLS = withCompactEnvelope([
177
179
  .boolean()
178
180
  .optional()
179
181
  .describe('Also measure the page for what a person would see broken: text cut off with no ellipsis, controls drawn over each other, text under WCAG contrast. Returns `audit: [{ kind, ref, what, detail }]`. Off by default; it forces layout.'),
182
+ charts: z
183
+ .boolean()
184
+ .optional()
185
+ .describe('Also read each SVG chart as geometry: every bar, dot, line and slice as its box in percent of the chart box, with no colour. A wrong value moves a shape; a chart drawn larger or in other colours reads the same. Returns `charts: [{ label, shapes, cut? }]`. Off by default; it forces layout.'),
180
186
  ...sessionIdShape,
181
187
  },
182
188
  outputSchema: {
@@ -259,6 +265,7 @@ export const RAW_TOOLS = withCompactEnvelope([
259
265
  mode,
260
266
  ...(true === args['ids'] ? { ids: true } : {}),
261
267
  ...(true === args['audit'] ? { audit: true } : {}),
268
+ ...(true === args['charts'] ? { charts: true } : {}),
262
269
  }).then((raw) => withSizeCost(noteUnmountedRoot(noteHiddenPage(noteEmptyLeanTree(applySnapshotDelta(raw, {
263
270
  sessionId: resolved.id,
264
271
  scope: asString(args['scope']) ?? '',
@@ -284,7 +291,7 @@ export const RAW_TOOLS = withCompactEnvelope([
284
291
  name: z
285
292
  .string()
286
293
  .optional()
287
- .describe('Accessible name filter — narrows results when `by` is role and the page has many elements of that role.'),
294
+ .describe('Exact accessible-name filter - narrows results when `by` is role and the page has many elements of that role. Matches the whole name; use `text` for a partial match.'),
288
295
  scope: z
289
296
  .string()
290
297
  .optional()
@@ -398,6 +405,10 @@ export const RAW_TOOLS = withCompactEnvelope([
398
405
  .optional()
399
406
  .describe('Present when a ROLE+NAME search missed and that role DOES carry a nearly-matching name — role+name is exact, so "Mesh" does not find "2 Mesh". Retry with one of these spellings; no snapshot needed.'),
400
407
  testidFoundUnder: TESTID_FOUND_UNDER_SCHEMA,
408
+ nearest: z
409
+ .array(NearestTargetSchema)
410
+ .optional()
411
+ .describe('Present when a search missed and the page has visible targets like the one asked for: up to 3, the asked role first, then by name similarity. Each has a ref to retry with. nameTruncated is true when the name was cut. Advice only, never a match.'),
401
412
  })
402
413
  .optional()
403
414
  .describe('Present only on zero matches — tells you what IS on the page so you can diagnose the miss.'),
@@ -417,13 +428,18 @@ export const RAW_TOOLS = withCompactEnvelope([
417
428
  .boolean()
418
429
  .optional()
419
430
  .describe('True when a `scope` was given but resolved to nothing — zero matches then means the scope is gone, NOT that the element is absent. The search did not widen to the whole page. Re-check the scope before concluding anything.'),
431
+ scopeMatches: z
432
+ .number()
433
+ .optional()
434
+ .describe('Present when a CSS `scope` matched more than one element: how many it matched. The search covered all of them. To read one item, scope to its ref.'),
420
435
  },
421
436
  handler: (deps, rawArgs) => {
422
437
  // Accept the predicate's named-field spelling too — see query-shape.ts.
423
438
  const args = normalizeQueryArgs(rawArgs);
424
439
  const empty = emptyQueryRefusal(args);
425
- if (empty !== undefined)
440
+ if (empty !== undefined) {
426
441
  return Promise.reject(empty);
442
+ }
427
443
  const session = deps.sessions.resolve(asString(args['sessionId']));
428
444
  return commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.QUERY, {
429
445
  by: args['by'],
@@ -466,6 +482,7 @@ export const RAW_TOOLS = withCompactEnvelope([
466
482
  // reticle_crawl — autonomous click-everything + anomaly report. See crawl-tools.ts.
467
483
  ...CRAWL_TOOLS,
468
484
  ...EXPLORE_TOOLS,
485
+ ...COMPARE_TOOLS,
469
486
  // reticle_scroll_to — reveal a virtualized off-screen row. See scroll-tools.ts.
470
487
  ...SCROLL_TOOLS,
471
488
  // Session lifecycle: reticle_session — tune the presenter session (idle-end). See session-tools.ts.
@@ -513,8 +530,12 @@ export const MERGE_PLANS = [
513
530
  },
514
531
  {
515
532
  name: ReticleTool.RECORD,
516
- description: 'Timeline recording: { action: "start" } begins recording under a name, "stop" ends it and returns the reaction report plus a compiled replayable program.',
517
- members: { start: ReticleTool.RECORD_START, stop: ReticleTool.RECORD_STOP },
533
+ description: 'Timeline recording: { action: "start" } begins recording under a name, "stop" ends it and returns the reaction report plus a compiled replayable program, "replay" runs that program again by recordingName (destructive controls need confirmDangerous:true on each replay).',
534
+ members: {
535
+ start: ReticleTool.RECORD_START,
536
+ stop: ReticleTool.RECORD_STOP,
537
+ replay: ReticleTool.REPLAY,
538
+ },
518
539
  },
519
540
  {
520
541
  name: ReticleTool.FLOW,
@@ -552,7 +573,7 @@ export const MERGE_PLANS = [
552
573
  },
553
574
  {
554
575
  name: ReticleTool.VERIFY,
555
- description: 'The Reticle Harness is { action: "explore" }: when the user asks for the Harness, or asks Reticle to drive and verify the app for you, call it and do not drive the app yourself. Every other action says what is proved, and what is not. Start from a change: { action: "affected" } names which saved flows must re-verify for the files you edited (pass `files`, and/or `since` for a git ref), and "change" goes further and actually replays them, answering with one `verified` plus `because`. `unknown` there is the honest answer when NO saved flow covers the change: nothing ran, so nothing was proved, and it is never reported as green. Without a change to start from: "flows" replays every saved flow for one consolidated suite verdict (deterministic, no LLM per flow), and "coverage" lists the interactive controls you have and have NOT driven this session — an untouched list still holding the controls your change affects means you are not done. "crawl" is the no-script option: it drives every reachable control itself and reports single-channel faults (console errors, failed requests, dead controls) and CONTRADICTIONS, two channels disagreeing about the same click — the false greens a human cannot see, because a human watches the screen and the screen looks correct. "explore" is the Reticle Harness: it drives the app for you on the platform — pass a `persona` and it completes that whole journey — and RECORDS what it drove, so the next run replays it with no model in the loop and none of the driving costs your context. "mutate" grades the FLOW rather than the app: it breaks the endpoint a saved flow declared it depends on and replays it, and a flow that stays GREEN through a broken subject is a click sequence, not a test. "heal" repairs a flow whose LOCATOR drifted (a renamed testid), re-asserting the saved consequence before it writes and refusing when that stops firing — it heals a locator, never an intent. DESTRUCTIVE: crawl and explore really click, and may navigate or mutate state; "mutate" really fails a request on a driven page, and always puts it back.',
576
+ description: 'The Reticle Harness is { action: "explore" }: when the user asks for the Harness, or asks Reticle to drive and verify the app for you, call it and do not drive the app yourself. Every other action says what is proved, and what is not. Start from a change: { action: "affected" } names which saved flows must re-verify for the files you edited (pass `files`, and/or `since` for a git ref), and "change" goes further and actually replays them, answering with one `verified` plus `because`. `unknown` there is the honest answer when NO saved flow covers the change: nothing ran, so nothing was proved, and it is never reported as green. Without a change to start from: "flows" replays every saved flow for one consolidated suite verdict (deterministic, no LLM per flow), and "coverage" lists the interactive controls you have and have NOT driven this session — an untouched list still holding the controls your change affects means you are not done. "crawl" is the no-script option: it drives every reachable control itself and reports single-channel faults (console errors, failed requests, dead controls) and CONTRADICTIONS, two channels disagreeing about the same click — the false greens a human cannot see, because a human watches the screen and the screen looks correct. "explore" is the Reticle Harness: it drives the app for you on the platform — pass a `persona` and it completes that whole journey — and RECORDS what it drove, so the next run replays it with no model in the loop and none of the driving costs your context. "mutate" grades the FLOW rather than the app: it breaks the endpoint a saved flow declared it depends on and replays it, and a flow that stays GREEN through a broken subject is a click sequence, not a test. "heal" repairs a flow whose LOCATOR drifted (a renamed testid), re-asserting the saved consequence before it writes and refusing when that stops firing — it heals a locator, never an intent. "compare" drives two running builds (`reference`, `candidate`, optional `base`) the same way and lists what users would see differ and what the app sent; poll with `runId`. DESTRUCTIVE: crawl, explore and compare really click, and may navigate or mutate state; "mutate" really fails a request on a driven page, and always puts it back.',
556
577
  members: {
557
578
  change: ReticleTool.VERIFY_CHANGE,
558
579
  flows: ReticleTool.FLOW_VERIFY,
@@ -570,6 +591,9 @@ export const MERGE_PLANS = [
570
591
  // An action rather than a tenth tool, and safe to fold in because heal only rebinds a LOCATOR:
571
592
  // it re-asserts the saved consequence before writing and refuses when that stops firing.
572
593
  heal: ReticleTool.FLOW_HEAL,
594
+ // Two running builds, driven the same way from outside the app: what differs. The same run
595
+ // and JSON as `reticle compare --json`.
596
+ compare: ReticleTool.VERIFY_COMPARE,
573
597
  },
574
598
  // No default action ON PURPOSE. The name implies no single member, and one of them really
575
599
  // clicks: a fallthrough that guessed wrong would drive the app rather than read it. Every action
@@ -578,8 +602,12 @@ export const MERGE_PLANS = [
578
602
  },
579
603
  {
580
604
  name: ReticleTool.LEASE,
581
- description: 'Isolated headless contexts from the shared pool: { action: "acquire" } leases one and navigates it to the app URL, "release" closes it and frees the slot.',
582
- members: { acquire: ReticleTool.LEASE_ACQUIRE, release: ReticleTool.LEASE_RELEASE },
605
+ description: 'Isolated headless contexts from the shared pool: { action: "acquire" } leases one and navigates it to the app URL, "release" closes it and frees the slot, "coverage" starts or takes JS coverage of the leased page (which code ran).',
606
+ members: {
607
+ acquire: ReticleTool.LEASE_ACQUIRE,
608
+ release: ReticleTool.LEASE_RELEASE,
609
+ coverage: ReticleTool.LEASE_COVERAGE,
610
+ },
583
611
  },
584
612
  ];
585
613
  /**
@@ -590,6 +618,7 @@ export const RETIRED_FROM_SURFACE = [
590
618
  ReticleTool.RUN_RECORD, // auto-recording on flow_replay already persists run outcomes
591
619
  ReticleTool.REFRESH, // absorbed into reticle_navigate { reload: true }
592
620
  ReticleTool.WAIT_READY, // server-internal: the first live call already blocks for the session
621
+ ReticleTool.EXPLORE, // the interactive snapshot plus the console give the same answer
593
622
  ];
594
623
  /**
595
624
  * An empty `interactive` tree is a claim, and it is usually the wrong one.
@@ -603,14 +632,17 @@ export const RETIRED_FROM_SURFACE = [
603
632
  * The browser counts what leanness passed over, so the tool can say which of the two it is.
604
633
  */
605
634
  function noteEmptyLeanTree(result, mode) {
606
- if (SnapshotMode.INTERACTIVE !== mode)
635
+ if (SnapshotMode.INTERACTIVE !== mode) {
607
636
  return result;
637
+ }
608
638
  const row = asRecord(result);
609
- if (0 !== asNumber(row['nodes']))
639
+ if (0 !== asNumber(row['nodes'])) {
610
640
  return result;
641
+ }
611
642
  const skipped = asNumber(row['leanSkipped']) ?? 0;
612
- if (0 === skipped)
643
+ if (0 === skipped) {
613
644
  return result;
645
+ }
614
646
  return {
615
647
  ...row,
616
648
  note: `no element on this page carries an interactive ARIA role, so this mode found nothing — ` +
@@ -635,18 +667,22 @@ function noteEmptyLeanTree(result, mode) {
635
667
  * empty tree it replaces.
636
668
  */
637
669
  function noteHiddenPage(result, mode) {
638
- if (SnapshotMode.STATUS === mode)
670
+ if (SnapshotMode.STATUS === mode) {
639
671
  return result;
672
+ }
640
673
  const row = asRecord(result);
641
- if (0 !== asNumber(row['nodes']))
674
+ if (0 !== asNumber(row['nodes'])) {
642
675
  return result;
676
+ }
643
677
  // A note already there is the more specific one (leanness), and two explanations for one empty
644
678
  // tree is worse than the better of them alone.
645
- if (row['note'] !== undefined)
679
+ if (row['note'] !== undefined) {
646
680
  return result;
681
+ }
647
682
  const skipped = asNumber(row['hiddenSkipped']) ?? 0;
648
- if (0 === skipped)
683
+ if (0 === skipped) {
649
684
  return result;
685
+ }
650
686
  return {
651
687
  ...row,
652
688
  note: `this is NOT an empty page: ${String(skipped)} subtree(s) were skipped because their root ` +
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The `fill` spelling mapped onto the `type` action.
3
+ *
4
+ * The tool documents `{ text }` for type and `{ value }` for fill/select, and agents mix them up
5
+ * constantly — `reticle_act` with `{ action: "type", value: "…" }` then fails at run time with
6
+ * "type requires a string `text`", after the action window opened (reticlehq/reticle#2126, second
7
+ * item). When `text` is absent and `value` is a string, `value` becomes `text`, in the flat args
8
+ * and in the nested `args` object. A non-string `value` is left alone so the browser's own
9
+ * refusal keeps its accurate message.
10
+ *
11
+ * Shared by the dispatch point (act-command.ts) and the tests.
12
+ */
13
+ export declare function normalizeTypeSpelling(actArgs: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,36 @@
1
+ /**
2
+ * The `fill` spelling mapped onto the `type` action.
3
+ *
4
+ * The tool documents `{ text }` for type and `{ value }` for fill/select, and agents mix them up
5
+ * constantly — `reticle_act` with `{ action: "type", value: "…" }` then fails at run time with
6
+ * "type requires a string `text`", after the action window opened (reticlehq/reticle#2126, second
7
+ * item). When `text` is absent and `value` is a string, `value` becomes `text`, in the flat args
8
+ * and in the nested `args` object. A non-string `value` is left alone so the browser's own
9
+ * refusal keeps its accurate message.
10
+ *
11
+ * Shared by the dispatch point (act-command.ts) and the tests.
12
+ */
13
+ import { ActionType } from '@reticlehq/core';
14
+ export function normalizeTypeSpelling(actArgs) {
15
+ if (ActionType.TYPE !== actArgs['action']) {
16
+ return actArgs;
17
+ }
18
+ const out = { ...actArgs };
19
+ const mapValueToText = (record) => {
20
+ if (record['text'] !== undefined || 'string' !== typeof record['value']) {
21
+ return false;
22
+ }
23
+ record['text'] = record['value'];
24
+ delete record['value'];
25
+ return true;
26
+ };
27
+ mapValueToText(out);
28
+ const nested = out['args'];
29
+ if (null !== nested && 'object' === typeof nested && !Array.isArray(nested)) {
30
+ const nestedCopy = { ...nested };
31
+ if (mapValueToText(nestedCopy)) {
32
+ out['args'] = nestedCopy;
33
+ }
34
+ }
35
+ return out;
36
+ }
@@ -11,5 +11,11 @@ import { AgentClient, LaunchMethod } from '@reticlehq/core/telemetry';
11
11
  export declare function envAgentClient(env: NodeJS.ProcessEnv): AgentClient | undefined;
12
12
  /** The client for an event being built now. */
13
13
  export declare function currentAgentClient(env: NodeJS.ProcessEnv): AgentClient | undefined;
14
+ /**
15
+ * The folder in `${CLAUDE_PLUGIN_DATA}` that holds the Claude Code plugin's pinned server copies.
16
+ * `plugins/reticle/scripts/mcp-server.mjs` cannot import this file, so it keeps a copy of the name.
17
+ * `plugin-mcp-launch.test.ts` fails when the two differ.
18
+ */
19
+ export declare const PLUGIN_SERVER_DIR = "reticle-plugin-server";
14
20
  /** How the process was launched, from the path of its entry script. Only the bucket leaves. */
15
21
  export declare function launchMethodOf(entry: string | undefined): LaunchMethod;
@@ -28,6 +28,12 @@ export function envAgentClient(env) {
28
28
  export function currentAgentClient(env) {
29
29
  return agentClientOf(callingClientName()) ?? envAgentClient(env);
30
30
  }
31
+ /**
32
+ * The folder in `${CLAUDE_PLUGIN_DATA}` that holds the Claude Code plugin's pinned server copies.
33
+ * `plugins/reticle/scripts/mcp-server.mjs` cannot import this file, so it keeps a copy of the name.
34
+ * `plugin-mcp-launch.test.ts` fails when the two differ.
35
+ */
36
+ export const PLUGIN_SERVER_DIR = 'reticle-plugin-server';
31
37
  /**
32
38
  * Where a global install puts packages, by package manager. A path that matches none of these but
33
39
  * still sits in a `node_modules` is a project's own dependency.
@@ -49,6 +55,10 @@ export function launchMethodOf(entry) {
49
55
  const path = entry.replace(/\\/g, '/').toLowerCase();
50
56
  if (path.includes('/_npx/') || path.includes('/dlx-'))
51
57
  return LaunchMethod.NPX;
58
+ // The Claude Code plugin's own pinned copy: in a `node_modules`, but not the user's project.
59
+ if (path.includes(`/${PLUGIN_SERVER_DIR}/`)) {
60
+ return LaunchMethod.OTHER;
61
+ }
52
62
  if (GLOBAL_DIRS.some((dir) => path.includes(dir)))
53
63
  return LaunchMethod.GLOBAL;
54
64
  if (path.includes('/node_modules/'))
@@ -18,7 +18,7 @@
18
18
  * exact values allowed — anything outside the list is reported as `other`, so a schema change can
19
19
  * never quietly start forwarding free text.
20
20
  */
21
- import { ActionType, FeedbackKind, QueryBy, SnapshotMode } from '@reticlehq/core';
21
+ import { ActionType, FeedbackKind, QueryBy, ReticleTool, SnapshotMode } from '@reticlehq/core';
22
22
  /**
23
23
  * Parameters whose values are enums WE define, and the exact values allowed on the wire. An
24
24
  * unrecognized value reports `other` rather than itself; that fallback is what makes this safe
@@ -35,6 +35,9 @@ const SAFE_ENUM_PARAMS = new Map([
35
35
  ['by', new Set(Object.values(QueryBy))],
36
36
  ['mode', new Set(Object.values(SnapshotMode))],
37
37
  ['kind', new Set(Object.values(FeedbackKind))],
38
+ // `reticle_run { tool }` names the tool it calls. Our own tool names, so the session summary can
39
+ // say which tools agents reach through it. A name that is not a Reticle tool reports `other`.
40
+ ['tool', new Set(Object.values(ReticleTool))],
38
41
  // `level` has no core enum — console levels come from the DOM console API, not from us. Listed
39
42
  // literally BECAUSE there is nothing to derive from, which is a different situation from the four
40
43
  // above and the reason it is called out rather than left looking like an oversight.
@@ -49,10 +52,12 @@ export const OTHER_VALUE = 'other';
49
52
  */
50
53
  export function allowParamValues(name, values) {
51
54
  const allowed = SAFE_ENUM_PARAMS.get(name);
52
- if (allowed === undefined)
55
+ if (allowed === undefined) {
53
56
  return;
54
- for (const value of values)
57
+ }
58
+ for (const value of values) {
55
59
  allowed.add(value);
60
+ }
56
61
  }
57
62
  /**
58
63
  * The reportable form of one parameter: `name` on its own, or `name:value` when the parameter is a
@@ -60,10 +65,12 @@ export function allowParamValues(name, values) {
60
65
  */
61
66
  export function describeParam(name, value) {
62
67
  const allowed = SAFE_ENUM_PARAMS.get(name);
63
- if (allowed === undefined)
68
+ if (allowed === undefined) {
64
69
  return name;
65
- if (typeof value !== 'string')
70
+ }
71
+ if (typeof value !== 'string') {
66
72
  return name;
73
+ }
67
74
  return `${name}:${allowed.has(value) ? value : OTHER_VALUE}`;
68
75
  }
69
76
  /**
@@ -96,10 +103,12 @@ export function describeCliFlags(argv) {
96
103
  const flags = new Set();
97
104
  for (const arg of argv) {
98
105
  // A bare `--` ends the options: everything after it is passed through to something else.
99
- if (END_OF_OPTIONS === arg)
106
+ if (END_OF_OPTIONS === arg) {
100
107
  break;
101
- if (!arg.startsWith('--'))
108
+ }
109
+ if (!arg.startsWith('--')) {
102
110
  continue;
111
+ }
103
112
  // `--port=9000` — split so the NAME is reported and the value is discarded with everything else.
104
113
  const name = arg.split('=')[0] ?? arg;
105
114
  flags.add(KNOWN_CLI_FLAGS.has(name) ? name : OTHER_VALUE);
@@ -117,6 +126,7 @@ const KNOWN_CLI_FLAGS = new Set([
117
126
  '--accept-coverage',
118
127
  '--agent',
119
128
  '--app',
129
+ '--base',
120
130
  '--bug',
121
131
  '--capture-bodies',
122
132
  '--confirm-dangerous',
@@ -4,7 +4,8 @@
4
4
  * Lives in its own module so `cli.ts` stays a dispatcher, and so the `_daemon` exclusion below sits
5
5
  * next to the explanation of why it exists rather than buried in a startup function.
6
6
  */
7
- import { TelemetryActor } from '@reticlehq/core/telemetry';
7
+ import { TelemetryActor, type GateCompleted } from '@reticlehq/core/telemetry';
8
+ import { type Telemetry } from './telemetry.js';
8
9
  /** True when this command represents a person typing something. Exported for the gate. */
9
10
  export declare function isHumanCliCommand(command: string): boolean;
10
11
  /**
@@ -23,3 +24,11 @@ export declare function cliActorOf(env: NodeJS.ProcessEnv): TelemetryActor;
23
24
  */
24
25
  export declare function claimInstallReport(command: string, firstRun: boolean, stateDir: string): boolean;
25
26
  export declare function reportCliRun(argv: readonly string[]): void;
27
+ /**
28
+ * `gate_completed`: how one `reticle gate` run ended.
29
+ *
30
+ * `cli_command_run` says that the gate ran. Only this event says whether it passed. The gate is a
31
+ * one-shot command that exits immediately, so the send is DETACHED, like `init_completed`.
32
+ * Best-effort: a metric never changes what the gate decides.
33
+ */
34
+ export declare function reportGateCompleted(gate: GateCompleted, telemetry?: Pick<Telemetry, 'emit'>): void;
@@ -161,3 +161,18 @@ export function reportCliRun(argv) {
161
161
  flags: describeCliFlags(argv),
162
162
  });
163
163
  }
164
+ /**
165
+ * `gate_completed`: how one `reticle gate` run ended.
166
+ *
167
+ * `cli_command_run` says that the gate ran. Only this event says whether it passed. The gate is a
168
+ * one-shot command that exits immediately, so the send is DETACHED, like `init_completed`.
169
+ * Best-effort: a metric never changes what the gate decides.
170
+ */
171
+ export function reportGateCompleted(gate, telemetry = getTelemetry()) {
172
+ try {
173
+ void telemetry.emit(TelemetryEventKind.GATE_COMPLETED, { gate, detach: true });
174
+ }
175
+ catch {
176
+ /* a metric never fails the gate */
177
+ }
178
+ }
@@ -11,6 +11,7 @@
11
11
  * event loop alive and tax the command by most of a second.
12
12
  */
13
13
  import { type InitOutcome } from '@reticlehq/core/telemetry';
14
- export { InitFailure } from '@reticlehq/init';
14
+ import { InitFailure } from '@reticlehq/init';
15
+ export { InitFailure };
15
16
  /** Report one init outcome. Best-effort; setup must never fail because a metric did. */
16
17
  export declare function reportInitOutcome(init: InitOutcome): void;
@@ -10,12 +10,56 @@
10
10
  * same treatment `cli_command_run` gets, for the same reason: an in-process fetch would keep the
11
11
  * event loop alive and tax the command by most of a second.
12
12
  */
13
- import { TelemetryEventKind } from '@reticlehq/core/telemetry';
13
+ import { OnboardingPhase, OnboardingStop, TelemetryEventKind, } from '@reticlehq/core/telemetry';
14
+ import { InitFailure } from '@reticlehq/init';
14
15
  import { getTelemetry } from './telemetry.js';
15
16
  import { INSTALL_STATE_DIR, resolveInstallSource } from './install-source.js';
17
+ import { reportOnboardingStop } from './onboarding-funnel.js';
16
18
  // The failure vocabulary lives in `@reticlehq/init`, which is what classifies a failed run.
17
19
  // Re-exported so every consumer here is unchanged.
18
- export { InitFailure } from '@reticlehq/init';
20
+ export { InitFailure };
21
+ const PROJECT_DETECTED = 'project_detected';
22
+ const INSTRUMENTED = 'instrumented';
23
+ const APP_CONNECTED = 'app_connected';
24
+ /**
25
+ * Each init failure as one stop, with the first-run step it blocked. A `Record` over the whole
26
+ * enum, so a new `InitFailure` does not compile until it names its stop.
27
+ */
28
+ const INIT_STOP = {
29
+ [InitFailure.NO_PACKAGE_JSON]: { step: PROJECT_DETECTED, stop: OnboardingStop.NO_WEB_APP },
30
+ [InitFailure.MALFORMED_PACKAGE_JSON]: {
31
+ step: PROJECT_DETECTED,
32
+ stop: OnboardingStop.SETUP_FAILED,
33
+ },
34
+ [InitFailure.PREFLIGHT]: { step: PROJECT_DETECTED, stop: OnboardingStop.SETUP_FAILED },
35
+ [InitFailure.DEPENDENCY_INSTALL]: { step: INSTRUMENTED, stop: OnboardingStop.SETUP_FAILED },
36
+ [InitFailure.MCP_REGISTRATION]: { step: INSTRUMENTED, stop: OnboardingStop.MCP_NOT_REGISTERED },
37
+ [InitFailure.OTHER]: { step: INSTRUMENTED, stop: OnboardingStop.UNCLASSIFIED },
38
+ [InitFailure.BRIDGE_OCCUPIED]: { step: APP_CONNECTED, stop: OnboardingStop.SETUP_FAILED },
39
+ [InitFailure.DEV_SERVER]: { step: APP_CONNECTED, stop: OnboardingStop.DEV_SERVER_FAILED },
40
+ [InitFailure.APP_CONNECTION]: { step: APP_CONNECTED, stop: OnboardingStop.APP_NOT_CONNECTED },
41
+ [InitFailure.RUNTIME_ERROR]: { step: APP_CONNECTED, stop: OnboardingStop.UNCLASSIFIED },
42
+ [InitFailure.INTERRUPTED]: { step: APP_CONNECTED, stop: OnboardingStop.INTERRUPTED },
43
+ };
44
+ const INIT_STOP_UNKNOWN = INIT_STOP[InitFailure.OTHER];
45
+ function initStop(reason) {
46
+ if (reason !== undefined && Object.hasOwn(INIT_STOP, reason)) {
47
+ return INIT_STOP[reason];
48
+ }
49
+ return INIT_STOP_UNKNOWN;
50
+ }
51
+ /** The stop row of a failed init, or nothing for a run that worked. */
52
+ function reportInitStop(init) {
53
+ if (init.ok) {
54
+ return;
55
+ }
56
+ reportOnboardingStop({
57
+ phase: OnboardingPhase.FIRST_RUN,
58
+ ...initStop(init.reason),
59
+ ...(init.reason === undefined ? {} : { reason: init.reason }),
60
+ ...(init.stack === undefined ? {} : { stack: init.stack }),
61
+ }, { detach: true });
62
+ }
19
63
  /** Report one init outcome. Best-effort; setup must never fail because a metric did. */
20
64
  export function reportInitOutcome(init) {
21
65
  try {
@@ -27,6 +71,7 @@ export function reportInitOutcome(init) {
27
71
  // marker, same refusal to infer — see install-source.ts.
28
72
  installSource: resolveInstallSource(process.env, INSTALL_STATE_DIR),
29
73
  });
74
+ reportInitStop(init);
30
75
  }
31
76
  catch {
32
77
  /* a metric must never break `reticle init` */
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, rmSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
- import { OnboardingStepSchema } from '@reticlehq/core/telemetry';
3
+ import { OnboardingStepSchema, OnboardingStop } from '@reticlehq/core/telemetry';
4
4
  import { reportOnboardingStep } from './onboarding-funnel.js';
5
5
  /**
6
6
  * What the one-line installer could not report itself.
@@ -27,6 +27,22 @@ const TRACE_FILE = 'install-trace.jsonl';
27
27
  * file must not turn into thousands of events. Far above any real install, which writes three.
28
28
  */
29
29
  const MAX_LINES = 32;
30
+ /**
31
+ * The stop that each installer failure reason names. The keys are the reasons that `install.sh`
32
+ * and `install.ps1` write. A reason not in this table gets no stop.
33
+ */
34
+ const INSTALLER_STOP = {
35
+ no_node: OnboardingStop.NO_NODE,
36
+ node_too_old: OnboardingStop.NODE_TOO_OLD,
37
+ no_npm: OnboardingStop.CLI_INSTALL_FAILED,
38
+ npm_install: OnboardingStop.CLI_INSTALL_FAILED,
39
+ not_on_path: OnboardingStop.CLI_INSTALL_FAILED,
40
+ };
41
+ function installerStop(reason) {
42
+ return reason !== undefined && Object.hasOwn(INSTALLER_STOP, reason)
43
+ ? INSTALLER_STOP[reason]
44
+ : undefined;
45
+ }
30
46
  /**
31
47
  * Drain the installer's breadcrumbs, once.
32
48
  *
@@ -36,8 +52,9 @@ const MAX_LINES = 32;
36
52
  */
37
53
  export function drainInstallTrace(stateDir) {
38
54
  const path = join(stateDir, TRACE_FILE);
39
- if (!existsSync(path))
55
+ if (!existsSync(path)) {
40
56
  return 0;
57
+ }
41
58
  let drained = 0;
42
59
  try {
43
60
  const lines = readFileSync(path, 'utf8')
@@ -55,9 +72,11 @@ export function drainInstallTrace(stateDir) {
55
72
  // deliberate belt-and-braces: this is the one path where the payload is a file a person can
56
73
  // edit, and rule 3 is that unvalidated text never reaches the wire.
57
74
  const step = OnboardingStepSchema.safeParse(parsed);
58
- if (!step.success)
75
+ if (!step.success) {
59
76
  continue;
60
- void reportOnboardingStep(step.data);
77
+ }
78
+ const stop = installerStop(step.data.reason);
79
+ void reportOnboardingStep({ ...step.data, ...(stop === undefined ? {} : { stop }) });
61
80
  drained += 1;
62
81
  }
63
82
  }
@@ -1,4 +1,4 @@
1
- import { OnboardingPhase, type OnboardingStep } from '@reticlehq/core/telemetry';
1
+ import { OnboardingPhase, type OnboardingStep, type OnboardingStop } from '@reticlehq/core/telemetry';
2
2
  /**
3
3
  * The one place an onboarding step is reported.
4
4
  *
@@ -11,7 +11,24 @@ import { OnboardingPhase, type OnboardingStep } from '@reticlehq/core/telemetry'
11
11
  * arriving as a step name would be a rule-3 leak that no reader would ever notice, because a funnel
12
12
  * with one weird row still renders.
13
13
  */
14
- export declare function reportOnboardingStep(step: OnboardingStep): Promise<boolean>;
14
+ export declare function reportOnboardingStep(step: OnboardingStep, opts?: {
15
+ detach?: boolean;
16
+ }): Promise<boolean>;
17
+ /** One stop: the rung it blocked and its name. The status is always `failed`. */
18
+ export type StopRow = Omit<OnboardingStep, 'status' | 'stop'> & {
19
+ stop: OnboardingStop;
20
+ };
21
+ /**
22
+ * Report one stop on the path to a first verdict, once for each process and stop name.
23
+ *
24
+ * A daemon can fail to launch Chromium on every lease. One row says the stop happened here; more
25
+ * rows count retries, not people. Use `detach` from a CLI that exits next, or the row is lost.
26
+ */
27
+ export declare function reportOnboardingStop(row: StopRow, opts?: {
28
+ detach?: boolean;
29
+ }): void;
30
+ /** Tests only: forget the stops this process sent. */
31
+ export declare function resetOnboardingStops(): void;
15
32
  /**
16
33
  * Time a step and report it, whatever happens to it.
17
34
  *
@@ -12,13 +12,35 @@ import { getTelemetry } from './telemetry.js';
12
12
  * arriving as a step name would be a rule-3 leak that no reader would ever notice, because a funnel
13
13
  * with one weird row still renders.
14
14
  */
15
- export async function reportOnboardingStep(step) {
15
+ export async function reportOnboardingStep(step, opts = {}) {
16
16
  const parsed = OnboardingStepSchema.safeParse(step);
17
17
  // A malformed step is DROPPED rather than sent or thrown. Sending it would put unvalidated text on
18
18
  // the wire; throwing would let a metric change what the user's install does, which rule 5 forbids.
19
- if (!parsed.success)
19
+ if (!parsed.success) {
20
20
  return false;
21
- return getTelemetry().emit(TelemetryEventKind.ONBOARDING_STEP, { onboarding: parsed.data });
21
+ }
22
+ return getTelemetry().emit(TelemetryEventKind.ONBOARDING_STEP, {
23
+ onboarding: parsed.data,
24
+ ...(true === opts.detach ? { detach: true } : {}),
25
+ });
26
+ }
27
+ const stopsSent = new Set();
28
+ /**
29
+ * Report one stop on the path to a first verdict, once for each process and stop name.
30
+ *
31
+ * A daemon can fail to launch Chromium on every lease. One row says the stop happened here; more
32
+ * rows count retries, not people. Use `detach` from a CLI that exits next, or the row is lost.
33
+ */
34
+ export function reportOnboardingStop(row, opts = {}) {
35
+ if (stopsSent.has(row.stop)) {
36
+ return;
37
+ }
38
+ stopsSent.add(row.stop);
39
+ void reportOnboardingStep({ ...row, status: OnboardingStepStatus.FAILED }, opts);
40
+ }
41
+ /** Tests only: forget the stops this process sent. */
42
+ export function resetOnboardingStops() {
43
+ stopsSent.clear();
22
44
  }
23
45
  /**
24
46
  * Time a step and report it, whatever happens to it.
@@ -10,6 +10,7 @@
10
10
  * never arrived: the POST was killed every time and nothing threw.
11
11
  */
12
12
  import { type ProxyExitReason, type WakeRefusalPresence } from '@reticlehq/core/telemetry';
13
+ import type { DaemonDeath } from '@reticlehq/core';
13
14
  import { type SessionMetrics } from './session-metrics.js';
14
15
  import { type Telemetry } from './telemetry.js';
15
16
  export declare function flushProxySessionMetrics(telemetry?: Telemetry, metrics?: SessionMetrics): Promise<void>;
@@ -37,7 +38,7 @@ export declare function reportProxyExited(reason: ProxyExitReason, telemetry?: T
37
38
  /** Everything the proxy sends on its way out. Awaited, then the caller exits. */
38
39
  export declare function flushProxyExit(reason: ProxyExitReason): Promise<void>;
39
40
  /** Send `daemon_died_unclean`. Fire-and-forget is fine for callers: the process lives on. */
40
- export declare function reportDaemonDiedUnclean(ageMs: number, telemetry?: Telemetry): Promise<void>;
41
+ export declare function reportDaemonDiedUnclean(ageMs: number, cause?: Omit<DaemonDeath, 'ageMs'>, telemetry?: Telemetry): Promise<void>;
41
42
  /**
42
43
  * Send `wake_refused`, once per process. A dormant proxy refuses again on every client request, and
43
44
  * one event per request would count the client's retries, not the refusal.
@@ -73,9 +73,9 @@ export async function flushProxyExit(reason) {
73
73
  await Promise.allSettled([flushProxySessionMetrics(), reportProxyExited(reason)]);
74
74
  }
75
75
  /** Send `daemon_died_unclean`. Fire-and-forget is fine for callers: the process lives on. */
76
- export async function reportDaemonDiedUnclean(ageMs, telemetry = getTelemetry()) {
76
+ export async function reportDaemonDiedUnclean(ageMs, cause, telemetry = getTelemetry()) {
77
77
  await telemetry.emit(TelemetryEventKind.DAEMON_DIED_UNCLEAN, {
78
- daemonDeath: { ageMs: Math.max(0, Math.round(ageMs)) },
78
+ daemonDeath: { ...cause, ageMs: Math.max(0, Math.round(ageMs)) },
79
79
  });
80
80
  }
81
81
  /**