@reticlehq/server 2.7.0 → 2.9.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 (357) hide show
  1. package/README.md +14 -5
  2. package/SKILL.md +130 -778
  3. package/dist/bridge/bridge.d.ts +23 -0
  4. package/dist/bridge/bridge.js +80 -2
  5. package/dist/bridge/bridge.test-harness.d.ts +5 -0
  6. package/dist/bridge/bridge.test-harness.js +6 -4
  7. package/dist/capsule/causal-summary.d.ts +36 -1
  8. package/dist/capsule/causal-summary.js +33 -7
  9. package/dist/capsule/divergence.js +7 -1
  10. package/dist/cli/chromium-hint.d.ts +33 -5
  11. package/dist/cli/chromium-hint.js +82 -12
  12. package/dist/cli/cli-doctor.js +65 -34
  13. package/dist/cli/cli-flow-commands.js +8 -8
  14. package/dist/cli/cli-kill.d.ts +58 -0
  15. package/dist/cli/cli-kill.js +162 -0
  16. package/dist/cli/cli-parse.d.ts +16 -1
  17. package/dist/cli/cli-parse.js +32 -9
  18. package/dist/cli/cli-port.d.ts +32 -0
  19. package/dist/cli/cli-port.js +82 -35
  20. package/dist/cli/cli-verify.d.ts +21 -0
  21. package/dist/cli/cli-verify.js +47 -3
  22. package/dist/cli/config-discovery.d.ts +35 -0
  23. package/dist/cli/config-discovery.js +163 -0
  24. package/dist/cli/doctor-daemon-line.js +4 -1
  25. package/dist/cli/doctor-project-line.d.ts +37 -0
  26. package/dist/cli/doctor-project-line.js +35 -0
  27. package/dist/cli/doctor-rows.d.ts +51 -0
  28. package/dist/cli/doctor-rows.js +54 -0
  29. package/dist/cli/doctor-sessions-line.d.ts +12 -0
  30. package/dist/cli/doctor-sessions-line.js +69 -0
  31. package/dist/cli/drive-attach.d.ts +99 -0
  32. package/dist/cli/drive-attach.js +203 -0
  33. package/dist/cli/drive-command.d.ts +13 -0
  34. package/dist/cli/drive-command.js +103 -0
  35. package/dist/cli/open-note.d.ts +20 -0
  36. package/dist/cli/open-note.js +37 -0
  37. package/dist/cli/port-holder.d.ts +10 -0
  38. package/dist/cli/port-holder.js +26 -2
  39. package/dist/cli/status-next-action.d.ts +37 -0
  40. package/dist/cli/status-next-action.js +61 -0
  41. package/dist/cli.js +119 -39
  42. package/dist/crawl/crawl-tools.js +12 -2
  43. package/dist/crawl/crawl.d.ts +13 -0
  44. package/dist/crawl/crawl.js +50 -2
  45. package/dist/events/contradictions.d.ts +46 -0
  46. package/dist/events/contradictions.js +35 -22
  47. package/dist/events/declared.d.ts +45 -0
  48. package/dist/events/declared.js +73 -0
  49. package/dist/events/echo-mismatch.d.ts +8 -2
  50. package/dist/events/echo-mismatch.js +33 -5
  51. package/dist/events/event-filters.d.ts +1 -1
  52. package/dist/events/event-filters.js +7 -4
  53. package/dist/events/predicate-eval.d.ts +17 -72
  54. package/dist/events/predicate-eval.js +117 -236
  55. package/dist/events/predicate-parse.js +31 -3
  56. package/dist/events/predicate-route.d.ts +32 -0
  57. package/dist/events/predicate-route.js +102 -0
  58. package/dist/events/predicate-schema.d.ts +99 -0
  59. package/dist/events/predicate-schema.js +309 -0
  60. package/dist/events/predicate.d.ts +8 -0
  61. package/dist/events/predicate.js +296 -22
  62. package/dist/flows/annotate-tools.js +3 -0
  63. package/dist/flows/annotate.js +18 -4
  64. package/dist/flows/coverage.d.ts +38 -5
  65. package/dist/flows/coverage.js +30 -4
  66. package/dist/flows/decision.d.ts +15 -0
  67. package/dist/flows/decision.js +1 -1
  68. package/dist/flows/flow-classify.js +1 -1
  69. package/dist/flows/flow-replay-run.js +8 -1
  70. package/dist/flows/flow-tools.js +21 -5
  71. package/dist/flows/heal-run.js +1 -1
  72. package/dist/flows/replay.js +3 -0
  73. package/dist/honesty/accepted-write.d.ts +3 -0
  74. package/dist/honesty/accepted-write.js +8 -3
  75. package/dist/honesty/already-true.js +15 -0
  76. package/dist/honesty/blind-spots.js +10 -2
  77. package/dist/honesty/honesty.d.ts +11 -0
  78. package/dist/honesty/honesty.js +1 -0
  79. package/dist/honesty/uncaptured-bodies.d.ts +4 -2
  80. package/dist/honesty/uncaptured-bodies.js +9 -2
  81. package/dist/honesty/unread-outcome.d.ts +8 -0
  82. package/dist/honesty/unread-outcome.js +36 -9
  83. package/dist/honesty/unsettled.d.ts +49 -0
  84. package/dist/honesty/unsettled.js +82 -0
  85. package/dist/honesty/verified.d.ts +36 -3
  86. package/dist/honesty/verified.js +54 -11
  87. package/dist/http-server.d.ts +19 -0
  88. package/dist/http-server.js +202 -4
  89. package/dist/impact/impact-recorder.d.ts +23 -0
  90. package/dist/impact/impact-recorder.js +76 -0
  91. package/dist/impact/impact-store.d.ts +47 -0
  92. package/dist/impact/impact-store.js +171 -0
  93. package/dist/index.d.ts +7 -0
  94. package/dist/index.js +47 -10
  95. package/dist/init/agent-rules.d.ts +36 -0
  96. package/dist/init/agent-rules.js +132 -22
  97. package/dist/init/astro-patch.d.ts +2 -1
  98. package/dist/init/astro-patch.js +38 -6
  99. package/dist/init/capabilities.d.ts +22 -0
  100. package/dist/init/capabilities.js +76 -0
  101. package/dist/init/confirm.d.ts +114 -0
  102. package/dist/init/confirm.js +165 -0
  103. package/dist/init/connect-steps.d.ts +2 -0
  104. package/dist/init/connect-steps.js +50 -0
  105. package/dist/init/cra.js +3 -2
  106. package/dist/init/csp-check.d.ts +38 -0
  107. package/dist/init/csp-check.js +92 -0
  108. package/dist/init/csp-doctor.d.ts +25 -0
  109. package/dist/init/csp-doctor.js +49 -0
  110. package/dist/init/cursor.js +6 -9
  111. package/dist/init/detect.d.ts +8 -0
  112. package/dist/init/detect.js +33 -3
  113. package/dist/init/mcp-clients.d.ts +1 -0
  114. package/dist/init/mcp-clients.js +51 -7
  115. package/dist/init/mcp.d.ts +15 -0
  116. package/dist/init/mcp.js +19 -0
  117. package/dist/init/non-js-project.js +29 -5
  118. package/dist/init/plan-framework.d.ts +10 -0
  119. package/dist/init/plan-framework.js +78 -16
  120. package/dist/init/plan.d.ts +47 -6
  121. package/dist/init/plan.js +228 -69
  122. package/dist/init/run.d.ts +24 -2
  123. package/dist/init/run.js +157 -45
  124. package/dist/init/slash-command.d.ts +1 -1
  125. package/dist/init/slash-command.js +54 -6
  126. package/dist/init/snippets.d.ts +45 -5
  127. package/dist/init/snippets.js +194 -42
  128. package/dist/init/vite-config.js +53 -5
  129. package/dist/input/network-mock-tools.d.ts +19 -0
  130. package/dist/input/network-mock-tools.js +37 -5
  131. package/dist/input/scroll-find.d.ts +6 -0
  132. package/dist/input/scroll-find.js +17 -2
  133. package/dist/input/scroll-tools.js +4 -0
  134. package/dist/journal/attach-journal.d.ts +9 -1
  135. package/dist/journal/attach-journal.js +13 -2
  136. package/dist/journal/journal-query.d.ts +13 -0
  137. package/dist/journal/journal-query.js +28 -3
  138. package/dist/journal/workspace-gitignore.d.ts +10 -0
  139. package/dist/journal/workspace-gitignore.js +59 -0
  140. package/dist/license/license.js +1 -1
  141. package/dist/mcp/mcp-proxy.d.ts +35 -30
  142. package/dist/mcp/mcp-proxy.js +306 -104
  143. package/dist/mcp/mcp.d.ts +10 -1
  144. package/dist/mcp/mcp.js +70 -34
  145. package/dist/mcp/proxy-handshake.d.ts +82 -1
  146. package/dist/mcp/proxy-handshake.js +109 -2
  147. package/dist/mcp/proxy-lifecycle.d.ts +11 -2
  148. package/dist/mcp/proxy-lifecycle.js +12 -3
  149. package/dist/mcp/proxy-log.d.ts +63 -0
  150. package/dist/mcp/proxy-log.js +132 -0
  151. package/dist/mcp/server-instructions.d.ts +17 -0
  152. package/dist/mcp/server-instructions.js +56 -0
  153. package/dist/mcp/shared-params.d.ts +27 -0
  154. package/dist/mcp/shared-params.js +36 -0
  155. package/dist/pool/browser-pool.d.ts +22 -2
  156. package/dist/pool/browser-pool.js +44 -4
  157. package/dist/project/learned-routes.d.ts +24 -0
  158. package/dist/project/learned-routes.js +63 -0
  159. package/dist/project/project-store.d.ts +6 -0
  160. package/dist/project/project-store.js +25 -1
  161. package/dist/project/reticle-dir.d.ts +2 -0
  162. package/dist/project/reticle-dir.js +1 -0
  163. package/dist/session/connection-memory.d.ts +59 -0
  164. package/dist/session/connection-memory.js +108 -0
  165. package/dist/session/dev-command.d.ts +26 -0
  166. package/dist/session/dev-command.js +89 -0
  167. package/dist/session/dev-server-probe.d.ts +68 -0
  168. package/dist/session/dev-server-probe.js +102 -7
  169. package/dist/session/last-act.d.ts +26 -0
  170. package/dist/session/last-act.js +35 -1
  171. package/dist/session/live-control-tools.js +42 -5
  172. package/dist/session/no-session-diagnosis.d.ts +67 -2
  173. package/dist/session/no-session-diagnosis.js +236 -27
  174. package/dist/session/no-session-next-action.d.ts +46 -0
  175. package/dist/session/no-session-next-action.js +85 -0
  176. package/dist/session/no-session-watch.d.ts +40 -1
  177. package/dist/session/no-session-watch.js +174 -8
  178. package/dist/session/pending-commands.d.ts +12 -0
  179. package/dist/session/pending-commands.js +13 -1
  180. package/dist/session/ref-provenance.d.ts +54 -0
  181. package/dist/session/ref-provenance.js +61 -0
  182. package/dist/session/session-lease.js +1 -1
  183. package/dist/session/session-manager.d.ts +4 -0
  184. package/dist/session/session-manager.js +35 -5
  185. package/dist/session/session.d.ts +10 -1
  186. package/dist/session/session.js +146 -5
  187. package/dist/telemetry/app-instrumented.d.ts +8 -0
  188. package/dist/telemetry/app-instrumented.js +10 -0
  189. package/dist/telemetry/automation-hint.d.ts +17 -0
  190. package/dist/telemetry/automation-hint.js +71 -0
  191. package/dist/telemetry/bug-found.d.ts +15 -17
  192. package/dist/telemetry/bug-found.js +52 -21
  193. package/dist/telemetry/cli-telemetry.js +7 -1
  194. package/dist/telemetry/daemon-telemetry.js +24 -0
  195. package/dist/telemetry/feedback.js +1 -1
  196. package/dist/telemetry/init-telemetry.js +9 -1
  197. package/dist/telemetry/install-source.d.ts +56 -0
  198. package/dist/telemetry/install-source.js +74 -0
  199. package/dist/telemetry/instrumentation-stall.d.ts +50 -0
  200. package/dist/telemetry/instrumentation-stall.js +90 -0
  201. package/dist/telemetry/run-telemetry.js +4 -1
  202. package/dist/telemetry/session-metrics.d.ts +8 -0
  203. package/dist/telemetry/session-metrics.js +15 -0
  204. package/dist/telemetry/telemetry.d.ts +7 -1
  205. package/dist/telemetry/telemetry.js +26 -2
  206. package/dist/telemetry/tool-refused.d.ts +19 -0
  207. package/dist/telemetry/tool-refused.js +55 -0
  208. package/dist/telemetry/verification-of.js +46 -7
  209. package/dist/temp-dir.d.ts +27 -0
  210. package/dist/temp-dir.js +46 -0
  211. package/dist/tools/act-tools.js +209 -28
  212. package/dist/tools/assert-grade.d.ts +1 -1
  213. package/dist/tools/assert-grade.js +27 -5
  214. package/dist/tools/assert-verdict.js +49 -7
  215. package/dist/tools/browser-tools.js +10 -0
  216. package/dist/tools/contract-tools.js +4 -0
  217. package/dist/tools/dynamic-tools.js +16 -7
  218. package/dist/tools/error-recovery.d.ts +10 -0
  219. package/dist/tools/error-recovery.js +73 -9
  220. package/dist/tools/feedback-invite.js +1 -1
  221. package/dist/tools/invoke-tool.js +159 -26
  222. package/dist/tools/lease-hint.d.ts +30 -8
  223. package/dist/tools/lease-hint.js +65 -14
  224. package/dist/tools/lease-tools.d.ts +40 -1
  225. package/dist/tools/lease-tools.js +181 -83
  226. package/dist/tools/navigate-result.js +1 -1
  227. package/dist/tools/observe-tools.js +10 -4
  228. package/dist/tools/observed-nothing.js +1 -1
  229. package/dist/tools/react-grace.d.ts +52 -0
  230. package/dist/tools/react-grace.js +90 -0
  231. package/dist/tools/read-tools.js +67 -5
  232. package/dist/tools/resolve-target.d.ts +33 -0
  233. package/dist/tools/resolve-target.js +53 -0
  234. package/dist/tools/settle-in-flight.d.ts +94 -0
  235. package/dist/tools/settle-in-flight.js +146 -0
  236. package/dist/tools/snapshot-delta.d.ts +10 -0
  237. package/dist/tools/snapshot-delta.js +194 -9
  238. package/dist/tools/tool-kit.d.ts +8 -0
  239. package/dist/tools/tool-surface.d.ts +60 -0
  240. package/dist/tools/tool-surface.js +86 -1
  241. package/dist/tools/tools.js +61 -8
  242. package/dist/tools/unadvertised-help.js +9 -6
  243. package/docs/README.md +55 -11
  244. package/docs/actions.mdx +120 -0
  245. package/docs/agent-cheatsheet.md +95 -29
  246. package/docs/architecture.md +52 -35
  247. package/docs/benchmarks.md +127 -75
  248. package/docs/best-practices.mdx +216 -0
  249. package/docs/capabilities.mdx +534 -0
  250. package/docs/cli/affected.mdx +53 -0
  251. package/docs/cli/capsules.mdx +70 -0
  252. package/docs/cli/cloud.mdx +153 -0
  253. package/docs/cli/doctor.mdx +109 -0
  254. package/docs/cli/drive.mdx +74 -0
  255. package/docs/cli/feedback.mdx +67 -0
  256. package/docs/cli/gate.mdx +64 -0
  257. package/docs/cli/help.mdx +94 -0
  258. package/docs/cli/hunt.mdx +73 -0
  259. package/docs/cli/identify.mdx +76 -0
  260. package/docs/cli/init.mdx +121 -0
  261. package/docs/cli/kill.mdx +91 -0
  262. package/docs/cli/license.mdx +48 -0
  263. package/docs/cli/mcp.mdx +61 -0
  264. package/docs/cli/open.mdx +74 -0
  265. package/docs/cli/restart.mdx +60 -0
  266. package/docs/cli/rollback.mdx +35 -0
  267. package/docs/cli/serve.mdx +69 -0
  268. package/docs/cli/status.mdx +88 -0
  269. package/docs/cli/stop.mdx +64 -0
  270. package/docs/cli/telemetry.mdx +57 -0
  271. package/docs/cli/update.mdx +52 -0
  272. package/docs/cli/verify.mdx +76 -0
  273. package/docs/cli/version.mdx +39 -0
  274. package/docs/cli/watch.mdx +62 -0
  275. package/docs/cli.mdx +241 -0
  276. package/docs/debugging.md +16 -10
  277. package/docs/deploy-checks.md +45 -12
  278. package/docs/desktop.mdx +384 -0
  279. package/docs/docs.json +364 -0
  280. package/docs/enterprise.md +22 -16
  281. package/docs/faq.mdx +102 -0
  282. package/docs/fixtures.md +13 -7
  283. package/docs/flows.md +29 -23
  284. package/docs/for-agents.md +71 -0
  285. package/docs/frameworks.mdx +238 -0
  286. package/docs/gate-plan.md +48 -42
  287. package/docs/gates.md +28 -22
  288. package/docs/getting-started.md +129 -77
  289. package/docs/human-control.md +24 -18
  290. package/docs/index.mdx +143 -0
  291. package/docs/install-agentic.mdx +224 -0
  292. package/docs/install-manual.mdx +196 -0
  293. package/docs/instrumentation.mdx +238 -0
  294. package/docs/integration-patterns.md +41 -35
  295. package/docs/local-registry.md +19 -13
  296. package/docs/multi-agent-testing.md +19 -13
  297. package/docs/packages/babel-plugin.mdx +79 -0
  298. package/docs/packages/browser.mdx +111 -0
  299. package/docs/packages/core.mdx +116 -0
  300. package/docs/packages/electron.mdx +85 -0
  301. package/docs/packages/eslint-plugin.mdx +86 -0
  302. package/docs/packages/next.mdx +89 -0
  303. package/docs/packages/react.mdx +91 -0
  304. package/docs/packages/server.mdx +98 -0
  305. package/docs/packages/tauri.mdx +63 -0
  306. package/docs/packages/test.mdx +119 -0
  307. package/docs/packages/vite-plugin.mdx +94 -0
  308. package/docs/packages.mdx +218 -0
  309. package/docs/platform-integration.md +35 -26
  310. package/docs/predicates.mdx +248 -0
  311. package/docs/quickstart.mdx +257 -0
  312. package/docs/recipes.mdx +282 -0
  313. package/docs/skill-file.mdx +107 -0
  314. package/docs/state-management.mdx +199 -0
  315. package/docs/style.css +14 -0
  316. package/docs/system-map.md +50 -33
  317. package/docs/telemetry-contract.md +106 -52
  318. package/docs/telemetry-events.mdx +475 -0
  319. package/docs/telemetry.md +63 -55
  320. package/docs/testing.md +44 -6
  321. package/docs/token-efficiency.md +37 -27
  322. package/docs/tools-act-and-wait.mdx +173 -0
  323. package/docs/tools-act-sequence.mdx +137 -0
  324. package/docs/tools-act.mdx +95 -0
  325. package/docs/tools-assert.mdx +129 -0
  326. package/docs/tools-console.mdx +63 -0
  327. package/docs/tools-inspect.mdx +88 -0
  328. package/docs/tools-navigate.mdx +99 -0
  329. package/docs/tools-network.mdx +119 -0
  330. package/docs/tools-observe.mdx +145 -0
  331. package/docs/tools-overview.mdx +160 -0
  332. package/docs/tools-query.mdx +126 -0
  333. package/docs/tools-session-and-feedback.mdx +112 -0
  334. package/docs/tools-sessions.mdx +94 -0
  335. package/docs/tools-snapshot.mdx +129 -0
  336. package/docs/tools-state.mdx +104 -0
  337. package/docs/tools-tools-and-run.mdx +156 -0
  338. package/docs/tools-wait-for.mdx +85 -0
  339. package/docs/troubleshooting.mdx +263 -0
  340. package/docs/usage.md +209 -165
  341. package/docs/vs-chrome-devtools-mcp.mdx +123 -0
  342. package/docs/vs-playwright-mcp.mdx +125 -0
  343. package/docs/vs-screenshots.mdx +137 -0
  344. package/docs/what-is-reticle.mdx +63 -0
  345. package/docs/why-reticle.mdx +109 -0
  346. package/package.json +5 -5
  347. package/docs/desktop-apps.md +0 -256
  348. package/docs/matrix/2.5.0/claude-code-machine.json +0 -19
  349. package/docs/matrix/2.5.0/codex-machine.json +0 -20
  350. package/docs/matrix/2.5.0/cursor-machine.json +0 -22
  351. package/docs/matrix/2.5.0/gemini-machine.json +0 -22
  352. package/docs/matrix/2.5.0/opencode-machine.json +0 -22
  353. package/docs/matrix/2.5.0/vscode-machine.json +0 -22
  354. package/docs/matrix/2.5.0/windsurf-machine.json +0 -22
  355. package/docs/matrix/MATRIX.md +0 -26
  356. package/docs/matrix/README.md +0 -59
  357. package/docs/mint.json +0 -61
@@ -0,0 +1,99 @@
1
+ import { ElementState, PredicateKind, type ElementDescriptor, type ElementQuery } from '@reticlehq/core';
2
+ import { z } from 'zod';
3
+ export type Predicate = {
4
+ kind: typeof PredicateKind.ELEMENT;
5
+ query: ElementQuery;
6
+ state?: ElementState;
7
+ absent?: boolean;
8
+ } | {
9
+ kind: typeof PredicateKind.TEXT;
10
+ contains: string;
11
+ visible?: boolean;
12
+ absent?: boolean;
13
+ /**
14
+ * Restrict the match to a subtree, as a CSS selector or a ref — the same field, and the same
15
+ * meaning, as `scope` on an element query.
16
+ *
17
+ * Without it the match is page-wide, and a word that appears both in a background tab label
18
+ * and in the dialog that just opened satisfies the predicate BEFORE the action runs, so
19
+ * `act_and_wait` reports `already_true` for an action that did exactly the right thing.
20
+ */
21
+ scope?: string;
22
+ } | {
23
+ kind: typeof PredicateKind.NET;
24
+ method?: string;
25
+ urlContains?: string;
26
+ status?: number;
27
+ /** Did the call succeed? The honest field for IPC, which has no status code. */
28
+ ok?: boolean;
29
+ since?: number;
30
+ count?: number;
31
+ /** A substring the RESPONSE body must contain — what the server answered, not what was sent. */
32
+ bodyContains?: string;
33
+ } | {
34
+ kind: typeof PredicateKind.ROUTE;
35
+ pathname?: string;
36
+ contains?: string;
37
+ since?: number;
38
+ } | {
39
+ kind: typeof PredicateKind.CONSOLE;
40
+ level?: string;
41
+ absent?: boolean;
42
+ since?: number;
43
+ } | {
44
+ kind: typeof PredicateKind.ANIMATION;
45
+ name?: string;
46
+ target?: string;
47
+ completed?: boolean;
48
+ since?: number;
49
+ } | {
50
+ kind: typeof PredicateKind.SIGNAL;
51
+ name?: string;
52
+ dataMatches?: Record<string, unknown>;
53
+ since?: number;
54
+ } | {
55
+ kind: typeof PredicateKind.STATE;
56
+ store?: string;
57
+ path: string;
58
+ equals?: unknown;
59
+ } | {
60
+ kind: typeof PredicateKind.SETTLED;
61
+ quietMs?: number;
62
+ } | {
63
+ kind: typeof PredicateKind.ALL_OF;
64
+ predicates: Predicate[];
65
+ } | {
66
+ kind: typeof PredicateKind.ANY_OF;
67
+ predicates: Predicate[];
68
+ } | {
69
+ kind: typeof PredicateKind.NOT;
70
+ predicate: Predicate;
71
+ };
72
+ export interface ResidualQueryChecks {
73
+ /** Dropped fields this side CAN check, as [field, wanted value] pairs. */
74
+ checks: [string, string][];
75
+ /** Dropped fields with no descriptor to check them against — refuse rather than ignore. */
76
+ unusable: string[];
77
+ }
78
+ /**
79
+ * Split a query's dropped fields into the ones the server can still enforce and the ones it cannot.
80
+ *
81
+ * The alternative — refuse every dropped field — breaks calls that work today and that our own
82
+ * cheatsheet advertises (`{ role: "button", text: "Save" }`), and breaks them into no verdict at all.
83
+ * Enforcing what we can and refusing only the rest keeps those calls working AS WRITTEN, which is the
84
+ * outcome the caller was already assuming.
85
+ */
86
+ export declare function residualQueryChecks(query: ElementQuery): ResidualQueryChecks;
87
+ /** Does this element satisfy every field the locator dropped? */
88
+ export declare function satisfiesResiduals(element: ElementDescriptor, checks: readonly [string, string][]): boolean;
89
+ /** How the element's own reading of a dropped field should be REPORTED back on a failure. */
90
+ export declare function describeResidual(element: ElementDescriptor, field: string): string;
91
+ export declare const PredicateSchema: z.ZodType<Predicate>;
92
+ /**
93
+ * The fields a given predicate kind accepts, read off the schema itself.
94
+ *
95
+ * Derived rather than listed so the two can never disagree: a rejection message that names a stale
96
+ * field set is worse than one that names none, because the agent trusts it and retries into the same
97
+ * wall. Empty for a kind that is not in the union.
98
+ */
99
+ export declare function predicateFieldsFor(kind: string): readonly string[];
@@ -0,0 +1,309 @@
1
+ import { ElementQuerySchema, ElementState, PredicateKind, QueryBy, } from '@reticlehq/core';
2
+ import { z } from 'zod';
3
+ /**
4
+ * Spellings an agent plausibly reaches for, mapped to the real field.
5
+ *
6
+ * `route` spells its field `pathname` while `state`, in the same union, spells its `path`. Every one
7
+ * of these was silently DROPPED before, and because these kinds have all-optional fields, dropping
8
+ * the only key supplied left a predicate that asserts nothing and passes on anything.
9
+ */
10
+ const PREDICATE_ALIASES = {
11
+ // `text`/`value` on a `text` predicate: the kind is called "text", so `text:` is the first thing
12
+ // anyone writes for it, and `value` follows from the element query's own `value` field. Both were
13
+ // hard rejections, and a rejected predicate produces NO verdict at all — the drive ends with
14
+ // nothing rather than with a failure, which is the worst outcome of the three.
15
+ [PredicateKind.TEXT]: { text: 'contains', value: 'contains' },
16
+ // `urlContains`/`url` reported from the field: an agent that had just written
17
+ // `net { urlContains }` applied the same word to `route`, which spells it `contains`, and got
18
+ // `unrecognized_keys` with no list of what would have worked. The parallel it assumed is a fair
19
+ // one — route's `contains` matches the WHOLE route (path + query + fragment), so "the URL
20
+ // contains this" is precisely what it does — and asserting a redirect after login is the single
21
+ // most common thing an agent reaches for here.
22
+ [PredicateKind.ROUTE]: { path: 'pathname', urlContains: 'contains', url: 'contains' },
23
+ [PredicateKind.NET]: { url: 'urlContains' },
24
+ [PredicateKind.SIGNAL]: { data: 'dataMatches' },
25
+ // `of` on a composite: it reads naturally, several assertion libraries spell it that way, and it has
26
+ // no other meaning here. Observed twice in one drive on a real app, and each rejection cost a round
27
+ // trip AND produced no verdict — a composite is what an agent reaches for precisely when it has two
28
+ // things to prove at once, so failing it is expensive at the worst moment.
29
+ [PredicateKind.ALL_OF]: { of: 'predicates' },
30
+ [PredicateKind.ANY_OF]: { of: 'predicates' },
31
+ };
32
+ /**
33
+ * Element-query fields an agent writes FLAT on an `element` predicate instead of nested under
34
+ * `query`. `reticle_query` takes exactly these at the top level, so an agent that has just located
35
+ * something writes the same words again when it asserts on it — and got `query: Required` plus an
36
+ * `unknown field` list for its trouble. Lifting them is unambiguous: they have no other meaning on
37
+ * this kind.
38
+ */
39
+ const ELEMENT_QUERY_FIELDS = [
40
+ 'by',
41
+ 'value',
42
+ 'role',
43
+ 'name',
44
+ 'text',
45
+ 'label',
46
+ 'placeholder',
47
+ 'testid',
48
+ 'alt',
49
+ 'component',
50
+ ];
51
+ /**
52
+ * The locator fields the browser actually CONSUMES for a given query — mirrors the precedence in
53
+ * `findIn` (packages/browser/src/dom/query.ts).
54
+ *
55
+ * An element query is not a conjunction. It is a first-match dispatch: `by`+`value` wins, then the
56
+ * component/source anchor, then `role` (which alone also consumes `name`), then the first of
57
+ * text/label/placeholder/testid/alt that is present. Every OTHER field the caller wrote is dropped on
58
+ * the floor, silently, and the match reported as if the whole query had been honoured.
59
+ *
60
+ * Duplicated here on purpose. The alternative is to send the question to the browser, and the browser
61
+ * cannot answer it: by the time a match comes back, the fields it ignored are indistinguishable from
62
+ * the fields it used.
63
+ */
64
+ function usedQueryFields(query) {
65
+ const used = new Set();
66
+ if (query.by !== undefined && query.value !== undefined) {
67
+ used.add('by').add('value');
68
+ if (QueryBy.ROLE === query.by)
69
+ used.add('name');
70
+ if (QueryBy.COMPONENT === query.by)
71
+ used.add('component');
72
+ return used;
73
+ }
74
+ if (query.component !== undefined || query.source !== undefined)
75
+ return used.add('component');
76
+ if (query.role !== undefined)
77
+ return used.add('role').add('name');
78
+ for (const field of ['text', 'label', 'placeholder', 'testid', 'alt']) {
79
+ if (query[field] !== undefined)
80
+ return used.add(field);
81
+ }
82
+ return used;
83
+ }
84
+ /**
85
+ * How a dropped field is checked back on the server, against the descriptor the match returned.
86
+ *
87
+ * `value` is the field this exists for: `{ role: "textbox", name: "GST amount", value: "274.58" }`
88
+ * read as a locator has no `by`, so the value half was discarded and the predicate collapsed to "a
89
+ * textbox named GST amount exists" — trivially true against an EMPTY field. Comparison is TRIMMED and
90
+ * exact: an input's value is a value, not prose, and a trailing space in either the app or the
91
+ * predicate is not a finding anybody wants. `""` asserts the field is empty, which describe() reports
92
+ * by omitting the field entirely.
93
+ *
94
+ * `role`/`name`/`value` compare against exactly what `reticle_query` REPORTS, so the words an agent
95
+ * copies out of a query result are the words that match here. `text` is a substring match, matching
96
+ * Testing Library's `exact: false`, and falls back to the name because describe() omits `text` when it
97
+ * equals the accessible name.
98
+ */
99
+ const RESIDUAL_CHECKS = {
100
+ value: (element, want) => (element.value ?? '').trim() === want.trim(),
101
+ role: (element, want) => element.role === want,
102
+ name: (element, want) => element.name.trim() === want.trim(),
103
+ text: (element, want) => (element.text ?? element.name).includes(want),
104
+ };
105
+ /**
106
+ * Split a query's dropped fields into the ones the server can still enforce and the ones it cannot.
107
+ *
108
+ * The alternative — refuse every dropped field — breaks calls that work today and that our own
109
+ * cheatsheet advertises (`{ role: "button", text: "Save" }`), and breaks them into no verdict at all.
110
+ * Enforcing what we can and refusing only the rest keeps those calls working AS WRITTEN, which is the
111
+ * outcome the caller was already assuming.
112
+ */
113
+ export function residualQueryChecks(query) {
114
+ const used = usedQueryFields(query);
115
+ const checks = [];
116
+ const unusable = [];
117
+ for (const field of ELEMENT_QUERY_FIELDS) {
118
+ const want = query[field];
119
+ if (want === undefined || used.has(field))
120
+ continue;
121
+ if ('string' === typeof want && RESIDUAL_CHECKS[field] !== undefined)
122
+ checks.push([field, want]);
123
+ else
124
+ unusable.push(field);
125
+ }
126
+ return { checks, unusable };
127
+ }
128
+ /** Does this element satisfy every field the locator dropped? */
129
+ export function satisfiesResiduals(element, checks) {
130
+ return checks.every(([field, want]) => true === RESIDUAL_CHECKS[field]?.(element, want));
131
+ }
132
+ /** How the element's own reading of a dropped field should be REPORTED back on a failure. */
133
+ export function describeResidual(element, field) {
134
+ const reading = 'value' === field
135
+ ? (element.value ?? '')
136
+ : 'text' === field
137
+ ? (element.text ?? element.name)
138
+ : 'role' === field
139
+ ? element.role
140
+ : element.name;
141
+ return `${element.role} "${element.name}" ${field}=${JSON.stringify(reading)}`;
142
+ }
143
+ /** Rename known aliases before parse; an explicit canonical key always wins. */
144
+ function applyPredicateAliases(input) {
145
+ if (typeof input !== 'object' || null === input || Array.isArray(input))
146
+ return input;
147
+ const obj = input;
148
+ const kind = 'string' === typeof obj['kind'] ? obj['kind'] : '';
149
+ const aliases = PREDICATE_ALIASES[kind];
150
+ let out = obj;
151
+ if (aliases !== undefined) {
152
+ out = { ...obj };
153
+ for (const [from, to] of Object.entries(aliases)) {
154
+ if (out[from] === undefined)
155
+ continue;
156
+ if (out[to] === undefined)
157
+ out[to] = out[from];
158
+ delete out[from];
159
+ }
160
+ }
161
+ return PredicateKind.ELEMENT === kind ? liftElementQuery(out) : out;
162
+ }
163
+ /**
164
+ * Fold flat query fields into `query`. An explicit `query` wins outright — a caller that supplied
165
+ * both told us which one it meant, and merging the two would invent a locator neither side wrote.
166
+ */
167
+ function liftElementQuery(obj) {
168
+ const loose = ELEMENT_QUERY_FIELDS.filter((field) => obj[field] !== undefined);
169
+ if (0 === loose.length)
170
+ return obj;
171
+ const out = { ...obj };
172
+ const query = {};
173
+ for (const field of loose) {
174
+ query[field] = out[field];
175
+ delete out[field];
176
+ }
177
+ if (out['query'] === undefined)
178
+ out['query'] = query;
179
+ return out;
180
+ }
181
+ /**
182
+ * Strict on every branch. A key that is nobody's spelling is now a schema error naming it, instead of
183
+ * a stripped field and a green — see predicate-strict.test.ts for the MCP session that found this.
184
+ *
185
+ * Built on demand rather than at module scope: the `allOf`/`anyOf`/`not` branches reference
186
+ * `PredicateSchema` itself, which does not exist yet while this module is initialising. Calling it
187
+ * after init is what makes the union introspectable — see `predicateFieldsFor`.
188
+ */
189
+ function predicateUnion() {
190
+ return z.discriminatedUnion('kind', [
191
+ z
192
+ .object({
193
+ kind: z.literal(PredicateKind.ELEMENT),
194
+ query: ElementQuerySchema,
195
+ state: z.nativeEnum(ElementState).optional(),
196
+ absent: z.boolean().optional(),
197
+ })
198
+ .strict(),
199
+ z
200
+ .object({
201
+ kind: z.literal(PredicateKind.TEXT),
202
+ contains: z.string(),
203
+ visible: z.boolean().optional(),
204
+ absent: z.boolean().optional(),
205
+ scope: z.string().optional(),
206
+ })
207
+ .strict(),
208
+ z
209
+ .object({
210
+ kind: z.literal(PredicateKind.NET),
211
+ method: z.string().optional(),
212
+ urlContains: z.string().optional(),
213
+ status: z.number().optional(),
214
+ ok: z.boolean().optional(),
215
+ since: z.number().optional(),
216
+ count: z.number().int().nonnegative().optional(),
217
+ /**
218
+ * A substring the call's RESPONSE body must contain - what the server answered.
219
+ *
220
+ * The only channel that can catch a UI echoing its own input instead of the server's answer.
221
+ * Reported from a real payments UI: a refund posted `{"amount":"1187.01"}`, the server read it
222
+ * as paise and answered 200 with `{"refunded":11.87}`, and the page displayed the number the
223
+ * user had typed. Request fired, exactly once, status 200, console clean, page settled — every
224
+ * assertable channel green on a hundred-fold wrong refund, so the verdict was `yes`.
225
+ *
226
+ * A substring rather than a JSON path, deliberately: `"refunded":11.87` is the whole assertion
227
+ * for the money case, it needs no schema for the body, and it works the same on JSON, form
228
+ * encoding and plain text. A path-and-equals form can be added later if a real case needs one;
229
+ * this is the shape that turns "a blob I read" into a verdict.
230
+ *
231
+ * Requires body capture (`reticle({ captureNetworkBodies: true })`), and says so when the body
232
+ * was never recorded rather than reporting an ordinary mismatch.
233
+ */
234
+ bodyContains: z.string().min(1).optional(),
235
+ })
236
+ .strict(),
237
+ z
238
+ .object({
239
+ kind: z.literal(PredicateKind.ROUTE),
240
+ pathname: z.string().optional(),
241
+ contains: z.string().optional(),
242
+ since: z.number().optional(),
243
+ })
244
+ .strict(),
245
+ z
246
+ .object({
247
+ kind: z.literal(PredicateKind.CONSOLE),
248
+ level: z.string().optional(),
249
+ absent: z.boolean().optional(),
250
+ since: z.number().optional(),
251
+ })
252
+ .strict(),
253
+ z
254
+ .object({
255
+ kind: z.literal(PredicateKind.ANIMATION),
256
+ name: z.string().optional(),
257
+ target: z.string().optional(),
258
+ completed: z.boolean().optional(),
259
+ since: z.number().optional(),
260
+ })
261
+ .strict(),
262
+ z
263
+ .object({
264
+ kind: z.literal(PredicateKind.SIGNAL),
265
+ name: z.string().optional(),
266
+ dataMatches: z.record(z.unknown()).optional(),
267
+ since: z.number().optional(),
268
+ })
269
+ .strict(),
270
+ z
271
+ .object({
272
+ kind: z.literal(PredicateKind.STATE),
273
+ store: z.string().optional(),
274
+ path: z.string(),
275
+ equals: z.unknown().optional(),
276
+ })
277
+ .strict(),
278
+ z
279
+ .object({
280
+ kind: z.literal(PredicateKind.SETTLED),
281
+ quietMs: z.number().positive().optional(),
282
+ })
283
+ .strict(),
284
+ z
285
+ .object({ kind: z.literal(PredicateKind.ALL_OF), predicates: z.array(PredicateSchema) })
286
+ .strict(),
287
+ z
288
+ .object({ kind: z.literal(PredicateKind.ANY_OF), predicates: z.array(PredicateSchema) })
289
+ .strict(),
290
+ z.object({ kind: z.literal(PredicateKind.NOT), predicate: PredicateSchema }).strict(),
291
+ ]);
292
+ }
293
+ export const PredicateSchema = z.lazy(() => z.preprocess(applyPredicateAliases, predicateUnion()));
294
+ /**
295
+ * The fields a given predicate kind accepts, read off the schema itself.
296
+ *
297
+ * Derived rather than listed so the two can never disagree: a rejection message that names a stale
298
+ * field set is worse than one that names none, because the agent trusts it and retries into the same
299
+ * wall. Empty for a kind that is not in the union.
300
+ */
301
+ export function predicateFieldsFor(kind) {
302
+ for (const option of predicateUnion().options) {
303
+ const literal = option.shape['kind'];
304
+ if (literal instanceof z.ZodLiteral && literal.value === kind) {
305
+ return Object.keys(option.shape).filter((field) => 'kind' !== field);
306
+ }
307
+ }
308
+ return [];
309
+ }
@@ -11,6 +11,14 @@ export interface PredicateSession {
11
11
  onEvent(listener: (event: ReticleEvent) => void): () => void;
12
12
  /** Milliseconds since connect — the same clock that stamps event `t` (injected, testable). */
13
13
  elapsed(): number;
14
+ /**
15
+ * Where the app is RIGHT NOW — the session's live URL, kept current across SPA navigation.
16
+ *
17
+ * Read by the `route` predicate when the window holds no route change, which is the only way
18
+ * "did the session survive a reload?" can be answered at all. Optional: a fake session that never
19
+ * navigates simply omits it and route falls back to change-only, as before.
20
+ */
21
+ url?: string;
14
22
  /**
15
23
  * Learned per-ref ambient-churn counts (real-time regions that churn with no action driving them).
16
24
  * The settle oracle drops events on learned-ambient refs so a chat/ticker page can still go quiet.