@reticlehq/server 2.3.0 → 2.5.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 (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -1,104 +1,34 @@
1
1
  /**
2
- * Action tools — reticle_act, reticle_act_sequence, reticle_act_and_wait — plus the native-input machinery
3
- * (asBox / tryRealInput). Split out of tools.ts to keep that file under the line cap; assembled back
4
- * into the tool list there via...ACT_TOOLS.
2
+ * Action tools — reticle_act, reticle_act_sequence, reticle_act_and_wait. Split out of tools.ts to keep
3
+ * that file under the line cap and assembled back into the tool list there via ...ACT_TOOLS; the
4
+ * native-input attempt itself lives in real-input-attempt.ts for the same reason.
5
5
  */
6
6
  import { z } from 'zod';
7
- import { compileActStep, compileSequenceStep } from '../flows/replay.js';
8
- import { ActionType, ActionWarning, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, InputModeReason, ReticleCommand, } from '@reticlehq/core';
9
- import { isPointerAction } from '../input/real-input.js';
7
+ import { aliasParam } from './alias-args.js';
8
+ import { captureAct, compileSequenceStep } from '../flows/replay.js';
9
+ import { ActionType, ActionWarning, DEFAULT_ASSERT_TIMEOUT_MS, InputMode, ReticleCommand, Verified, PredicateKind, } from '@reticlehq/core';
10
+ import { assertNativeInputSupported } from './act-danger.js';
10
11
  import { leanActResult, mutatedWithin } from './act-view.js';
11
12
  import { ReticleTool } from './tool-names.js';
12
13
  import { buildReactionReport, summarizeReaction } from '../events/reaction.js';
13
14
  import { causalSummary } from '../capsule/causal-summary.js';
14
15
  import { findContradictions } from '../events/contradictions.js';
15
16
  import { decideVerified } from '../honesty/verified.js';
17
+ import { readsDomState } from '../honesty/already-true.js';
16
18
  import { saveFailedAssertCapsule } from './act-capsule.js';
17
19
  import { buildDivergenceCapsule } from '../capsule/capsule.js';
18
20
  import { predicateToExpectedLinks } from '../capsule/predicate-to-links.js';
19
21
  import { buildHonestyBlock } from '../honesty/honesty.js';
20
- import { buildCoverageStatement, blindSpotsFromState, impeachesCapture, } from '../honesty/blind-spots.js';
22
+ import { buildCoverageStatement, blindSpotsFromState, Coverage, impeachesCapture, } from '../honesty/blind-spots.js';
21
23
  import { hasAcceptedWrite } from '../honesty/accepted-write.js';
22
24
  import { hasUnreadWriteOutcome } from '../honesty/unread-outcome.js';
23
- import { assertDragNotDestructive, assertNotDestructive } from './act-danger.js';
24
25
  import { evaluatePredicate, waitForPredicate, provenExpectedLinks, PredicateSchema, } from '../events/predicate.js';
25
26
  import { healthEnvelope, refuseIfThrottled } from '../session/session-health.js';
26
- import { pausedShortCircuit, pausedOutputShape, withControl } from '../session/control-envelope.js';
27
+ import { pausedShortCircuit, pausedOutputShape, withControl, PAUSED_NO_VERDICT, } from '../session/control-envelope.js';
27
28
  import { asString, asNumber, asRecord, sourceOf } from './tools-helpers.js';
28
- import { sessionIdShape, commandOrThrow } from './tool-kit.js';
29
- import { asActionType, asBox, gradeOf } from './act-helpers.js';
30
- /** Synthetic outcome with a diagnostic reason (provider configured but native input skipped). */
31
- function synthetic(reason) {
32
- return reason === undefined
33
- ? { result: undefined, settled: false }
34
- : { result: undefined, settled: false, reason };
35
- }
36
- /**
37
- * Attempt to drive a pointer action via native input. Returns a synthetic outcome (with a
38
- * `reason` when a provider is configured) whenever the synthetic path should run — no matching
39
- * page, unresolvable box, declined, etc. A throw inside the provider becomes a synthetic fallback
40
- * flagged with `fellBack`. `result` is defined only on a real success.
41
- */
42
- async function tryRealInput(deps, session, ref, action, args) {
43
- const provider = deps.realInput;
44
- if (provider === undefined)
45
- return synthetic(); // real input not configured — no diagnostic
46
- if (!isPointerAction(action))
47
- return synthetic(InputModeReason.NOT_POINTER); // fill/type stay synthetic
48
- const inner = asRecord(args['args']);
49
- // "Don't click, run the code": a click/dblclick runs the occlusion-honest SYNTHETIC path by default
50
- // even with a provider configured — no coordinate gesture to be intercepted by the HUD or missed
51
- // off-screen. Opt into a trusted native click with args.native:true (file pickers, clipboard,
52
- // isTrusted-gated handlers). hover/drag genuinely need native pointer state, so they stay real.
53
- if ((action === ActionType.CLICK || action === ActionType.DBLCLICK) && inner['native'] !== true) {
54
- return synthetic(InputModeReason.SYNTHETIC_CLICK_PREFERRED);
55
- }
56
- if (!(await provider.isAvailableFor(session.url)))
57
- return synthetic(InputModeReason.PAGE_NOT_CORRELATED);
58
- const inspected = await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, { ref });
59
- assertNotDestructive(action, inner, inspected);
60
- const box = asBox(inspected);
61
- if (box === undefined)
62
- return synthetic(InputModeReason.ELEMENT_NOT_LOCATABLE);
63
- let toBox;
64
- if (action === ActionType.DRAG) {
65
- const toRef = asString(inner['toRef']);
66
- if (toRef === undefined)
67
- return synthetic(InputModeReason.DRAG_TARGET_UNRESOLVED);
68
- const targetInspected = await commandOrThrow(deps, session.id, ReticleCommand.INSPECT, {
69
- ref: toRef,
70
- });
71
- // A drag is judged on BOTH ends: dropping onto "Trash" is destructive however innocent the
72
- // thing being dragged looks.
73
- assertDragNotDestructive(inner, inspected, targetInspected);
74
- toBox = asBox(targetInspected);
75
- if (toBox === undefined)
76
- return synthetic(InputModeReason.DRAG_TARGET_UNRESOLVED);
77
- }
78
- const performArgs = {};
79
- const value = asString(inner['value']);
80
- if (value !== undefined)
81
- performArgs.value = value;
82
- const text = asString(inner['text']);
83
- if (text !== undefined)
84
- performArgs.text = text;
85
- if (toBox !== undefined)
86
- performArgs.toBox = toBox;
87
- try {
88
- const performed = await provider.perform(session.url, action, box, performArgs);
89
- if (!performed.performed)
90
- return synthetic(InputModeReason.PROVIDER_DECLINED);
91
- return { result: { performed: true, center: performed.center, action }, settled: true };
92
- }
93
- catch {
94
- return {
95
- result: undefined,
96
- settled: false,
97
- fellBack: true,
98
- reason: InputModeReason.PROVIDER_ERROR,
99
- };
100
- }
101
- }
29
+ import { sessionIdShape } from './tool-kit.js';
30
+ import { asActionType, gradeOf } from './act-helpers.js';
31
+ import { tryRealInput } from './real-input-attempt.js';
102
32
  /**
103
33
  * Narrow the wire's `action` to a real ActionType, or undefined.
104
34
  *
@@ -107,18 +37,29 @@ async function tryRealInput(deps, session, ref, action, args) {
107
37
  * as a generic failure rather than "that is not an action". Validate at the boundary, per the project's
108
38
  * unknown-plus-narrowing rule, so a typo is rejected where it can still be explained.
109
39
  */
40
+ /**
41
+ * The action vocabulary, derived from ActionType — never retyped.
42
+ *
43
+ * The description used to list thirteen actions while ActionType had seventeen: blur, upload, drag
44
+ * and webmcp were real, callable, and undocumented, because a hand-copied list drifts the moment
45
+ * someone adds an arm. Deriving both the schema and the prose from the enum makes that impossible.
46
+ *
47
+ * The handler already refused an unknown action with a good message; the schema now refuses it
48
+ * one layer earlier, before any session is resolved or any work is done.
49
+ */
50
+ const ACTION_TYPE_VALUES = Object.values(ActionType);
51
+ const ACTION_TYPE_LIST = ACTION_TYPE_VALUES.join(' | ');
52
+ const actionTypeEnum = z.enum(ACTION_TYPE_VALUES);
110
53
  export const ACT_TOOLS = [
111
54
  {
112
55
  name: ReticleTool.ACT,
113
56
  example: { ref: 'e42', action: 'fill', args: { value: 'hello' } },
114
- description: 'Execute one action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView. Returns immediately with a `since` cursor — observe the reaction with reticle_observe. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). inputModeReason explains any real→synthetic choice so it is never silent. Full model (real-input, throttled tabs, `reticle drive`): docs/usage.md §18.',
57
+ description: 'Execute one action against a ref: click|dblclick|hover|focus|fill|type|clear|select|check|uncheck|submit|press|scrollIntoView. Returns immediately with a `since` cursor — observe the reaction with reticle_observe. For a VERDICT instead of a cursor use reticle_act_and_wait { until } — the same action plus the assertion, in one call; prefer it whenever the action is supposed to cause something. Carries effect:{dispatched,targetMatched,visible,enabled,focusMoved,valueChanged,domMutatedWithin,occluded,occludedBy,scrolledIntoView} to tell "action missed" from "app didn\'t react"; dispatched=landed, settled=a real frame flushed, and a settle timeout never fails the tool. Fields at their uninformative default are OMITTED so a clean action collapses to its consequence: an absent dispatched/targetMatched/visible/enabled means true, an absent occluded/scrolledIntoView/valueChanged/defaultPrevented means false, an absent focusMoved/occludedBy means null. occluded=true means the click point is covered by another element (a real user could not click it) — synthetic dispatch still delivered the event; scrolledIntoView=true means an off-viewport target was scrolled in first. inputMode is "real" (native CDP, no synthetic effect block) or "synthetic"; clicks default to the occlusion-honest synthetic path even when CDP is configured — pass args.native:true to force a trusted native click (file pickers, clipboard). inputModeReason explains any real→synthetic choice so it is never silent. Full model (real-input, throttled tabs, `reticle drive`): docs/usage.md §18.',
115
58
  inputSchema: {
116
59
  ref: z
117
60
  .string()
118
61
  .describe(`Element ref (e.g. 'e42') from reticle_snapshot/reticle_query — stable until the element leaves the DOM, so no re-snapshot between actions.`),
119
- action: z
120
- .string()
121
- .describe('Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView'),
62
+ action: actionTypeEnum.describe(`Action to perform: ${ACTION_TYPE_LIST}`),
122
63
  args: z
123
64
  .record(z.unknown())
124
65
  .optional()
@@ -163,10 +104,8 @@ export const ACT_TOOLS = [
163
104
  return paused;
164
105
  refuseIfThrottled(session, args['refuseWhenThrottled']);
165
106
  const since = session.elapsed();
166
- session.lastAct.markCursor(since); // honesty: wait_for/assert default their floor to this cursor
167
- // Cleared up front: a throw between here and the result must not leave the PREVIOUS act's
168
- // measurement standing, which would judge this window with the last one's evidence.
169
- session.lastAct.markEffect(asString(args['action']), undefined);
107
+ // The act cursor + effect are marked only once the action has actually DISPATCHED (below, on
108
+ // each success path) — a refused act must leave nothing behind for the next observe to judge.
170
109
  const ref = asString(args['ref']) ?? '';
171
110
  // Open the journal's action-attribution window BEFORE dispatching, so it covers the native path
172
111
  // too. It used to open only on the synthetic path, and the native path returned above it — so a
@@ -181,10 +120,11 @@ export const ACT_TOOLS = [
181
120
  // drive native pointer input when a provider is available; otherwise fall back.
182
121
  const real = await tryRealInput(deps, session, ref, action, args);
183
122
  if (real.result !== undefined) {
184
- if (deps.recordings.active().length > 0) {
185
- deps.recordings.capture(compileActStep(args, real.result));
186
- }
123
+ captureAct(deps.recordings, args, real.result);
187
124
  settledOutcome = real.settled ?? undefined;
125
+ // Native input reports no synthetic effect block, so nothing measured in-target: undefined
126
+ // (the weaker empty-window test), never a fabricated zero.
127
+ session.lastAct.markActed(since, action, undefined);
188
128
  return withControl(session, {
189
129
  since,
190
130
  inputMode: InputMode.REAL,
@@ -202,17 +142,15 @@ export const ACT_TOOLS = [
202
142
  });
203
143
  if (!result.ok)
204
144
  throw new Error(result.error ?? 'act failed');
205
- if (deps.recordings.active().length > 0) {
206
- deps.recordings.capture(compileActStep(args, result.result));
207
- }
145
+ captureAct(deps.recordings, args, result.result);
208
146
  // lift dispatch/settle status to the envelope (a settle timeout is NOT a failure).
209
147
  const r = asRecord(result.result);
210
- if (typeof r['settled'] === 'boolean')
148
+ if ('boolean' === typeof r['settled'])
211
149
  settledOutcome = r['settled'];
212
150
  // Keep what only this call measured, so the observe that judges this window can ask whether
213
151
  // anything happened INSIDE the target — the one fact that separates a dead control from a
214
152
  // page that was merely busy with something else.
215
- session.lastAct.markEffect(asString(args['action']), mutatedWithin(r));
153
+ session.lastAct.markActed(since, action, mutatedWithin(r));
216
154
  return withControl(session, {
217
155
  since,
218
156
  inputMode: InputMode.SYNTHETIC,
@@ -222,13 +160,13 @@ export const ACT_TOOLS = [
222
160
  settled: r['settled'] ?? null,
223
161
  settleReason: r['settleReason'] ?? null,
224
162
  result: leanActResult(result.result),
225
- ...(real.fellBack === true ? { warning: ActionWarning.REAL_INPUT_FELL_BACK } : {}),
163
+ ...(true === real.fellBack ? { warning: ActionWarning.REAL_INPUT_FELL_BACK } : {}),
226
164
  ...healthEnvelope(session),
227
165
  });
228
166
  }
229
167
  finally {
230
168
  // Close the window on every exit (settle or throw), recording the action + settle outcome.
231
- session.finishAction(undefined, settledOutcome, settledOutcome === true ? session.elapsed() - since : undefined);
169
+ session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
232
170
  }
233
171
  },
234
172
  },
@@ -258,12 +196,15 @@ export const ACT_TOOLS = [
258
196
  if (paused !== undefined)
259
197
  return paused;
260
198
  const since = session.elapsed();
261
- session.lastAct.markCursor(since); // honesty: a later wait_for/assert floors at this cursor
262
199
  session.beginAction(ReticleTool.ACT_SEQUENCE, asRecord(args));
263
200
  try {
264
201
  const result = await session.command(ReticleCommand.ACT_SEQUENCE, { steps: args['steps'] });
265
202
  if (!result.ok)
266
203
  throw new Error(result.error ?? 'act_sequence failed');
204
+ // Marked only once the sequence ran: a refused sequence must leave no cursor behind for a
205
+ // later observe to judge. No single action and no in-target measurement here — per-step
206
+ // effects live in steps[] — so the effect is cleared rather than inherited from an earlier act.
207
+ session.lastAct.markActed(since, undefined, undefined);
267
208
  if (deps.recordings.active().length > 0) {
268
209
  deps.recordings.capture(compileSequenceStep(args, result.result));
269
210
  }
@@ -283,7 +224,11 @@ export const ACT_TOOLS = [
283
224
  },
284
225
  {
285
226
  name: ReticleTool.ACT_AND_WAIT,
286
- example: { ref: 'e42', action: 'click', until: { kind: 'signal', name: 'todos:loaded' } },
227
+ example: {
228
+ ref: 'e42',
229
+ action: 'click',
230
+ until: { kind: PredicateKind.SIGNAL, name: 'todos:loaded' },
231
+ },
287
232
  description: 'Act on a ref, then wait for a predicate to hold — one hop for the act->observe->assert loop. ' +
288
233
  'Omit `until` to wait for the page to settle (network + DOM idle) — use this instead of a fixed sleep. ' +
289
234
  'Returns { effect } (the action result), { verdict } (predicate pass/evidence/near-miss), ' +
@@ -294,13 +239,12 @@ export const ACT_TOOLS = [
294
239
  ref: z
295
240
  .string()
296
241
  .describe(`Element ref (e.g. 'e42') from reticle_snapshot/reticle_query — stable until the element leaves the DOM, so no re-snapshot between actions.`),
297
- action: z
298
- .string()
299
- .describe('Action to perform: click | dblclick | hover | focus | fill | type | clear | select | check | uncheck | submit | press | scrollIntoView'),
242
+ action: actionTypeEnum.describe(`Action to perform: ${ACTION_TYPE_LIST}`),
300
243
  args: z
301
244
  .record(z.unknown())
302
245
  .optional()
303
246
  .describe('Action-specific arguments: { value } for fill/select, { text } for type/press, { confirmDangerous: true } for a potentially destructive control.'),
247
+ predicate: PredicateSchema.optional().describe('Alias for `until` (the name reticle_assert / reticle_wait_for use).'),
304
248
  until: PredicateSchema.optional().describe('Predicate to wait for after the action completes (same shape as reticle_assert). OMIT to wait for the page to SETTLE — network + DOM idle — the deterministic default instead of a sleep. To assert a consequence AND settle, allOf them: { kind: "allOf", predicates: [<your predicate>, { kind: "settled" }] }.'),
305
249
  timeout_ms: z
306
250
  .number()
@@ -374,26 +318,44 @@ export const ACT_TOOLS = [
374
318
  handler: async (deps, args) => {
375
319
  const session = deps.sessions.resolve(asString(args['sessionId']));
376
320
  // Live-control: refuse to drive the page (no act, no predicate eval) while paused.
321
+ //
322
+ // A VERDICT rides out with the refusal. This is the one tool here that promises `verified`,
323
+ // and the bare pause payload omitted it — so an agent reading `result.verified` got undefined
324
+ // from a call that carried no error, which is neither yes, no, nor unknown. It also broke this
325
+ // tool's own outputSchema, where `because` is required.
377
326
  const paused = pausedShortCircuit(session);
378
- if (paused !== undefined)
379
- return paused;
327
+ if (paused !== undefined) {
328
+ return { ...paused, verified: Verified.UNKNOWN, because: PAUSED_NO_VERDICT };
329
+ }
380
330
  refuseIfThrottled(session, args['refuseWhenThrottled']);
381
331
  // Omitting `until` waits for the page to settle (idle) — the deterministic default vs a sleep.
382
- const until = args['until'] !== undefined
383
- ? PredicateSchema.parse(args['until'])
384
- : { kind: 'settled' };
332
+ // `predicate` is what reticle_assert / reticle_wait_for call this — see alias-args.ts.
333
+ const withUntil = aliasParam(args, 'until', ['predicate']);
334
+ const until = withUntil['until'] !== undefined
335
+ ? PredicateSchema.parse(withUntil['until'])
336
+ : { kind: PredicateKind.SETTLED };
385
337
  const timeout = asNumber(args['timeout_ms']) ?? DEFAULT_ASSERT_TIMEOUT_MS;
338
+ // Before anything is driven: this path cannot honour a native-input request, and taking the
339
+ // argument and ignoring it told the agent its trusted click had happened. See act-danger.
340
+ assertNativeInputSupported(asRecord(args['args']));
386
341
  const since = session.elapsed();
387
342
  // `dropped` is cumulative for the SESSION, so comparing against a pre-action reading is what
388
343
  // asks "did the buffer lose anything while THIS action was observed?" — the question the
389
344
  // honesty block means. Reading the raw total pinned every later verdict to `unknown` after a
390
345
  // single early eviction (measured on the Next.js demo at dropped:51, windows intact).
391
346
  const droppedBefore = session.bufferHealth().dropped;
392
- session.lastAct.markCursor(since);
347
+ // The cursor + effect are marked after the act dispatches (below) — a refused act leaves none.
393
348
  // The attribution window stays open across the settle wait below, so post-dispatch async events
394
349
  // (the whole point of act_and_wait) attribute to this action. finishAction fires after the wait.
395
350
  session.beginAction(ReticleTool.ACT_AND_WAIT, asRecord(args));
396
351
  let settledOutcome;
352
+ // Was the declared consequence ALREADY TRUE? Only asked for predicates that read live DOM
353
+ // state — event-based ones are floored at this act's cursor and cannot be satisfied by the
354
+ // past, so they need no pre-check and pay nothing. One extra query, on the path where a green
355
+ // is otherwise unfalsifiable. See honesty/already-true.
356
+ const alreadyTrue = until !== undefined && readsDomState(until)
357
+ ? (await evaluatePredicate(session, until, since, false)).pass
358
+ : false;
397
359
  try {
398
360
  const actResult = await session.command(ReticleCommand.ACT, {
399
361
  ref: args['ref'],
@@ -402,12 +364,16 @@ export const ACT_TOOLS = [
402
364
  });
403
365
  if (!actResult.ok)
404
366
  throw new Error(actResult.error ?? 'act failed');
367
+ captureAct(deps.recordings, args, actResult.result);
368
+ // Dispatched — now this act owns the cursor and the effect. Marking its OWN measurement also
369
+ // stops the spread below from inheriting an earlier reticle_act's action and mutation count.
370
+ session.lastAct.markActed(since, asString(args['action']), mutatedWithin(asRecord(actResult.result)));
405
371
  // Honesty: floor the predicate at this act's cursor so a stale buffered event can't satisfy it.
406
372
  const verdict = timeout > 0
407
373
  ? await waitForPredicate(session, until, timeout, since)
408
374
  : await evaluatePredicate(session, until, since);
409
375
  const r = asRecord(actResult.result);
410
- if (typeof r['settled'] === 'boolean')
376
+ if ('boolean' === typeof r['settled'])
411
377
  settledOutcome = r['settled'];
412
378
  // Where the acted element is written. Captured at act time alongside the anchor, so it is
413
379
  // available even when the action unmounted its own target.
@@ -442,7 +408,7 @@ export const ACT_TOOLS = [
442
408
  grade: gradeOf(gradedLinks),
443
409
  attribution: 'window',
444
410
  truncated: session.bufferHealth().dropped > droppedBefore,
445
- coveragePartial: coverage.coverage === 'partial',
411
+ coveragePartial: Coverage.PARTIAL === coverage.coverage,
446
412
  ...(impeaching.note === undefined ? {} : { blindSpots: [impeaching.note] }),
447
413
  });
448
414
  const capsuleSaved = await saveFailedAssertCapsule({
@@ -467,6 +433,9 @@ export const ACT_TOOLS = [
467
433
  const outcomeUnread = hasUnreadWriteOutcome(windowEvents);
468
434
  const decision = decideVerified({
469
435
  pass: verdict.pass,
436
+ ...(alreadyTrue ? { alreadyTrue } : {}),
437
+ // An assertion nobody could evaluate must not be reported as one the app failed.
438
+ ...(verdict.inconclusive === undefined ? {} : { inconclusive: verdict.inconclusive }),
470
439
  honesty,
471
440
  contradictions,
472
441
  ...(outcomePending ? { outcomePending } : {}),
@@ -505,7 +474,7 @@ export const ACT_TOOLS = [
505
474
  });
506
475
  }
507
476
  finally {
508
- session.finishAction(undefined, settledOutcome, settledOutcome === true ? session.elapsed() - since : undefined);
477
+ session.finishAction(undefined, settledOutcome, true === settledOutcome ? session.elapsed() - since : undefined);
509
478
  }
510
479
  },
511
480
  },
@@ -23,11 +23,11 @@ const NULL_NOISE = new Set(['occludedBy', 'focusMoved']);
23
23
  function leanEffect(effect) {
24
24
  const out = {};
25
25
  for (const [k, v] of Object.entries(effect)) {
26
- if (TRUE_NOISE.has(k) && v === true)
26
+ if (TRUE_NOISE.has(k) && true === v)
27
27
  continue;
28
- if (FALSE_NOISE.has(k) && v === false)
28
+ if (FALSE_NOISE.has(k) && false === v)
29
29
  continue;
30
- if (NULL_NOISE.has(k) && v === null)
30
+ if (NULL_NOISE.has(k) && null === v)
31
31
  continue;
32
32
  out[k] = v;
33
33
  }
@@ -35,11 +35,11 @@ function leanEffect(effect) {
35
35
  }
36
36
  /** Return a copy of an act command result with its `effect` block leaned. Non-objects pass through. */
37
37
  export function leanActResult(result) {
38
- if (result === null || typeof result !== 'object')
38
+ if (null === result || typeof result !== 'object')
39
39
  return result;
40
40
  const r = result;
41
41
  const effect = r['effect'];
42
- if (effect === null || typeof effect !== 'object')
42
+ if (null === effect || typeof effect !== 'object')
43
43
  return result;
44
44
  return { ...r, effect: leanEffect(effect) };
45
45
  }
@@ -51,8 +51,8 @@ export function leanActResult(result) {
51
51
  */
52
52
  export function mutatedWithin(result) {
53
53
  const effect = result['effect'];
54
- if (typeof effect !== 'object' || effect === null)
54
+ if (typeof effect !== 'object' || null === effect)
55
55
  return undefined;
56
56
  const value = effect['domMutatedWithin'];
57
- return typeof value === 'number' ? value : 0;
57
+ return 'number' === typeof value ? value : 0;
58
58
  }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Accepting a neighbouring tool's spelling for the same parameter.
3
+ *
4
+ * Reticle's surface has a few places where one concept has two names, and every one of them is a
5
+ * rejected call for an agent that learned the other spelling from the tool next door:
6
+ *
7
+ * reticle_act_and_wait { until } vs reticle_wait_for / reticle_assert { predicate }
8
+ * reticle_annotate { flow } vs every other flow tool { flowName }
9
+ *
10
+ * Both cross a path the product prescribes — `act_and_wait` is the most-used tool, and
11
+ * `reticle_flow_save`'s own description tells the agent to go and call `reticle_annotate`.
12
+ *
13
+ * Aliasing, not renaming: the canonical name stays the contract, an explicit canonical value always
14
+ * wins, and a call carrying neither is left untouched so the schema error still names what was
15
+ * missing instead of being replaced by a guess.
16
+ */
17
+ export declare function aliasParam(args: Record<string, unknown>, canonical: string, aliases: readonly string[]): Record<string, unknown>;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Accepting a neighbouring tool's spelling for the same parameter.
3
+ *
4
+ * Reticle's surface has a few places where one concept has two names, and every one of them is a
5
+ * rejected call for an agent that learned the other spelling from the tool next door:
6
+ *
7
+ * reticle_act_and_wait { until } vs reticle_wait_for / reticle_assert { predicate }
8
+ * reticle_annotate { flow } vs every other flow tool { flowName }
9
+ *
10
+ * Both cross a path the product prescribes — `act_and_wait` is the most-used tool, and
11
+ * `reticle_flow_save`'s own description tells the agent to go and call `reticle_annotate`.
12
+ *
13
+ * Aliasing, not renaming: the canonical name stays the contract, an explicit canonical value always
14
+ * wins, and a call carrying neither is left untouched so the schema error still names what was
15
+ * missing instead of being replaced by a guess.
16
+ */
17
+ export function aliasParam(args, canonical, aliases) {
18
+ if (args[canonical] !== undefined)
19
+ return args;
20
+ for (const alias of aliases) {
21
+ if (args[alias] !== undefined)
22
+ return { ...args, [canonical]: args[alias] };
23
+ }
24
+ return args;
25
+ }
@@ -10,18 +10,18 @@
10
10
  *
11
11
  * Pure: no IO, no clock.
12
12
  */
13
- import { isConsequenceKind, isPresenceKind } from '@reticlehq/core';
13
+ import { isConsequenceKind, isPresenceKind, PredicateKind } from '@reticlehq/core';
14
14
  import { HonestyGrade } from '../honesty/honesty.js';
15
15
  export const PRESENCE_ONLY_ADVICE = 'This predicate only checks element/text presence, not an observable consequence. A locator healed to the wrong element (or a stale render) can satisfy it while the feature is broken. Prefer a { signal } or { net } assertion — or allOf it with one — so green means the feature actually worked.';
16
16
  function walk(predicate) {
17
- if (predicate.kind === 'allOf' || predicate.kind === 'anyOf') {
17
+ if (PredicateKind.ALL_OF === predicate.kind || PredicateKind.ANY_OF === predicate.kind) {
18
18
  const subs = predicate.predicates.map(walk);
19
19
  return {
20
20
  consequence: subs.some((s) => s.consequence),
21
21
  presence: subs.some((s) => s.presence),
22
22
  };
23
23
  }
24
- if (predicate.kind === 'not')
24
+ if (PredicateKind.NOT === predicate.kind)
25
25
  return walk(predicate.predicate);
26
26
  // Leaf: classify against the single source of truth in core. Kinds that are neither consequence
27
27
  // nor presence (route/console/settled/animation) correctly return false for both.
@@ -50,12 +50,12 @@ export const DERIVED_IPC_STATUS_ADVICE = 'This asserts on a status code Reticle
50
50
  * Recognised by the `ipc://` scheme, so a genuine HTTP status is never second-guessed.
51
51
  */
52
52
  export function assertsDerivedIpcStatus(predicate) {
53
- if (predicate.kind === 'allOf' || predicate.kind === 'anyOf') {
53
+ if (PredicateKind.ALL_OF === predicate.kind || PredicateKind.ANY_OF === predicate.kind) {
54
54
  return predicate.predicates.some(assertsDerivedIpcStatus);
55
55
  }
56
- if (predicate.kind === 'not')
56
+ if (PredicateKind.NOT === predicate.kind)
57
57
  return assertsDerivedIpcStatus(predicate.predicate);
58
- if (predicate.kind !== 'net')
58
+ if (predicate.kind !== PredicateKind.NET)
59
59
  return false;
60
60
  return (predicate.status !== undefined &&
61
61
  predicate.ok === undefined &&
@@ -74,11 +74,11 @@ export function isPresenceOnlyAssertion(predicate) {
74
74
  */
75
75
  export function gradeOfPredicate(predicate) {
76
76
  switch (predicate.kind) {
77
- case 'signal':
77
+ case PredicateKind.SIGNAL:
78
78
  return HonestyGrade.SIGNAL;
79
- case 'net':
79
+ case PredicateKind.NET:
80
80
  return HonestyGrade.NET;
81
- case 'state':
81
+ case PredicateKind.STATE:
82
82
  return HonestyGrade.STATE;
83
83
  default:
84
84
  return HonestyGrade.PRESENCE;
@@ -14,7 +14,9 @@ import { type Contradiction } from '../events/contradictions.js';
14
14
  * consult it. Asserting a moment earlier — while the POST was still open — is the same story with a
15
15
  * different name: `request-never-settled`, also unreported.
16
16
  */
17
- export declare function assertVerdict(session: Session, predicate: Predicate, pass: boolean, since: number): Promise<{
17
+ export declare function assertVerdict(session: Session, predicate: Predicate, pass: boolean, since: number,
18
+ /** Set when the assertion was never evaluated — see honesty/verified.ts. */
19
+ inconclusive?: string): Promise<{
18
20
  decision: Record<string, unknown>;
19
21
  contradictions: Contradiction[];
20
22
  coverage: Record<string, unknown>;
@@ -1,5 +1,5 @@
1
1
  import { findContradictions } from '../events/contradictions.js';
2
- import { blindSpotsFromState, buildCoverageStatement, impeachesCapture, } from '../honesty/blind-spots.js';
2
+ import { blindSpotsFromState, buildCoverageStatement, Coverage, impeachesCapture, } from '../honesty/blind-spots.js';
3
3
  import { buildHonestyBlock } from '../honesty/honesty.js';
4
4
  import { hasAcceptedWrite } from '../honesty/accepted-write.js';
5
5
  import { hasUnreadWriteOutcome } from '../honesty/unread-outcome.js';
@@ -18,7 +18,9 @@ import { gradeOfPredicate } from './assert-grade.js';
18
18
  * consult it. Asserting a moment earlier — while the POST was still open — is the same story with a
19
19
  * different name: `request-never-settled`, also unreported.
20
20
  */
21
- export async function assertVerdict(session, predicate, pass, since) {
21
+ export async function assertVerdict(session, predicate, pass, since,
22
+ /** Set when the assertion was never evaluated — see honesty/verified.ts. */
23
+ inconclusive) {
22
24
  // Scope caveat, stated because it is a real limitation and not a bug: blind spots are tracked per
23
25
  // SESSION, not per assertion window, so a cross-origin iframe seen once marks every later verdict
24
26
  // in that session partial — including ones about a region it cannot affect. That errs toward
@@ -28,9 +30,9 @@ export async function assertVerdict(session, predicate, pass, since) {
28
30
  const statement = buildCoverageStatement(spots);
29
31
  // Omitted entirely when coverage is full, so an intact page pays nothing and the field's PRESENCE
30
32
  // is the warning.
31
- const coverage = statement.coverage === 'partial'
33
+ const coverage = Coverage.PARTIAL === statement.coverage
32
34
  ? {
33
- coverage: statement.note ?? 'partial',
35
+ coverage: statement.note ?? Coverage.PARTIAL,
34
36
  coverage_spots: statement.spots.map((sp) => ({ kind: sp.kind, count: sp.count })),
35
37
  }
36
38
  : {};
@@ -48,10 +50,11 @@ export async function assertVerdict(session, predicate, pass, since) {
48
50
  const outcomeUnread = hasUnreadWriteOutcome(windowEvents);
49
51
  const decision = decideVerified({
50
52
  pass,
53
+ ...(inconclusive === undefined ? {} : { inconclusive }),
51
54
  honesty: buildHonestyBlock({
52
55
  grade: gradeOfPredicate(predicate),
53
56
  attribution: 'window',
54
- coveragePartial: statement.coverage === 'partial',
57
+ coveragePartial: Coverage.PARTIAL === statement.coverage,
55
58
  ...(impeaching.note === undefined ? {} : { blindSpots: [impeaching.note] }),
56
59
  }),
57
60
  contradictions,
@@ -1,4 +1,7 @@
1
1
  import { z } from 'zod';
2
+ import { navigateResult } from './navigate-result.js';
3
+ import { reloadResult } from './reload-result.js';
4
+ import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS } from '../session/session-reconnect.js';
2
5
  import { ReticleCommand } from '@reticlehq/core';
3
6
  import { ReticleTool } from './tool-names.js';
4
7
  import { asString } from './tools-helpers.js';
@@ -7,7 +10,7 @@ export const BROWSER_TOOLS = [
7
10
  {
8
11
  name: ReticleTool.NAVIGATE,
9
12
  example: { url: '/settings' },
10
- description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). The SDK reconnects automatically after the page loads. Use reticle_sessions to confirm the new tab is connected before acting.',
13
+ description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED, not that the page arrived — the SDK is torn down by the navigation, so nothing can observe the new document. Call reticle_sessions to confirm a session reconnected at the new URL before acting.',
11
14
  inputSchema: {
12
15
  url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
13
16
  reload: z
@@ -24,17 +27,36 @@ export const BROWSER_TOOLS = [
24
27
  ok: z.boolean(),
25
28
  url: z.string().optional(),
26
29
  reason: z.string().optional(),
30
+ // Present on a dispatched navigation: nothing here can see the new document, so arrival is
31
+ // reported as unconfirmed rather than implied by `ok`. See navigate-result.ts.
32
+ confirmed: z.boolean().optional(),
33
+ note: z.string().optional(),
27
34
  },
28
35
  handler: async (deps, args) => {
29
36
  // reload:true is the absorbed reticle_refresh — same command, one fewer advertised tool.
30
- if (args['reload'] === true) {
37
+ if (true === args['reload']) {
38
+ const before = deps.sessions.resolve(asString(args['sessionId']));
31
39
  await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
32
- hard: args['hard'] === true,
40
+ hard: true === args['hard'],
33
41
  });
34
- return { ok: true };
42
+ // WAIT for the page to come back, rather than telling the agent to. The id survives the
43
+ // reload, but the seconds between dispatch and the new HELLO are seconds in which every call
44
+ // lands in the old, disconnected session — measured as reticle_run failing 5 of 5 on a page
45
+ // that was healthy immediately afterwards. Returns on the first poll in the common case.
46
+ const back = await waitForReconnect({
47
+ current: () => deps.sessions.get(before.id),
48
+ previous: before,
49
+ timeoutMs: RELOAD_RECONNECT_TIMEOUT_MS,
50
+ now: deps.now,
51
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
52
+ });
53
+ // Not a bare `{ ok: true }`. The URL branch below already discloses that `ok` means
54
+ // DISPATCHED — the reload branch had identical semantics and said nothing, on the path most
55
+ // likely to need it. See reload-result.
56
+ return reloadResult(back);
35
57
  }
36
58
  const url = asString(args['url']);
37
- if (url === undefined || url.length === 0)
59
+ if (url === undefined || 0 === url.length)
38
60
  return { ok: false, reason: 'url required' };
39
61
  // Record navigate as an action. Its window is usually empty (the page unloads and the SDK
40
62
  // reconnects), but the action record itself — "navigated to X" — is the causal fact worth keeping.
@@ -42,11 +64,8 @@ export const BROWSER_TOOLS = [
42
64
  session.beginAction(ReticleTool.NAVIGATE, { url });
43
65
  try {
44
66
  const result = (await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.NAVIGATE, { url }));
45
- return {
46
- ok: result.ok === true,
47
- ...(typeof result.url === 'string' ? { url: result.url } : {}),
48
- ...(typeof result.reason === 'string' ? { reason: result.reason } : {}),
49
- };
67
+ // `ok` is the browser accepting the instruction, not the page arriving — see navigate-result.
68
+ return navigateResult(result);
50
69
  }
51
70
  finally {
52
71
  session.finishAction();
@@ -68,7 +87,7 @@ export const BROWSER_TOOLS = [
68
87
  },
69
88
  handler: async (deps, args) => {
70
89
  await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
71
- hard: args['hard'] === true,
90
+ hard: true === args['hard'],
72
91
  });
73
92
  return { ok: true };
74
93
  },