@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -1,4 +1,4 @@
1
- import { EventType } from '@reticlehq/core';
1
+ import { EventType, URL_RAW, urlForMatch } from '@reticlehq/core';
2
2
  import { asString, asNumber } from '../tools/tools-helpers.js';
3
3
  import { mergeNetworkDetail } from '../input/network-detail.js';
4
4
  /**
@@ -55,7 +55,7 @@ export function matchNet(e, method, urlContains, status, ok) {
55
55
  if (method !== undefined && asString(d['method'])?.toUpperCase() !== method.toUpperCase()) {
56
56
  return false;
57
57
  }
58
- if (urlContains !== undefined && !(asString(d['url']) ?? '').includes(urlContains)) {
58
+ if (urlContains !== undefined && !urlForMatch(d).includes(urlContains)) {
59
59
  return false;
60
60
  }
61
61
  if (status !== undefined && asNumber(d['status']) !== status)
@@ -86,6 +86,23 @@ export function reconcileNet(events) {
86
86
  .map((e) => ({ ...e, data: { ...e.data, status: 'pending', pending: true } }));
87
87
  return [...completed, ...unresolved].sort((a, b) => a.t - b.t);
88
88
  }
89
+ /**
90
+ * Strip the grader-only raw URL before an event is rendered to the agent.
91
+ *
92
+ * `urlRaw` exists so `urlContains` can match a path redaction rewrote. It is a secret-bearing field
93
+ * by construction (the whole reason `url` was redacted) and must never appear in observe, evidence,
94
+ * or a network listing.
95
+ */
96
+ export function withoutUrlRaw(event) {
97
+ if (typeof event !== 'object' || null === event)
98
+ return event;
99
+ const rec = event;
100
+ const data = rec['data'];
101
+ if (typeof data !== 'object' || null === data || !(URL_RAW in data))
102
+ return event;
103
+ const { [URL_RAW]: _raw, ...rest } = data;
104
+ return { ...rec, data: rest };
105
+ }
89
106
  export function projectNetCall(e, includeBodies = true) {
90
107
  const status = e.data['status'];
91
108
  const ms = asNumber(e.data['durationMs']);
@@ -10,3 +10,15 @@
10
10
  * not do what you thought) versus things happened but not that one (you named it wrong).
11
11
  */
12
12
  export declare function describeObserved(noun: string, seen: readonly string[]): string;
13
+ /**
14
+ * The third answer, and the one this file existed to make room for: things happened, and they belong
15
+ * to a document that has since been replaced.
16
+ *
17
+ * `describeObserved` already separates "nothing happened" from "things happened but not that one",
18
+ * because those need different fixes. A window whose evidence was discarded as superseded is a third
19
+ * case needing a third fix, and it is the one that reads most like the first: everything that could
20
+ * have answered the question is gone, so a bare "nothing happened in this window" is available and
21
+ * wrong. The remedy is in the sentence, because an agent that cannot act on a caveat learns to skip
22
+ * it — here the remedy is to drive the action again against the page now on screen.
23
+ */
24
+ export declare function describeSuperseded(noun: string, count: number): string;
@@ -19,3 +19,20 @@ export function describeObserved(noun, seen) {
19
19
  const rest = unique.length - shown.length;
20
20
  return `${noun} seen in this window: ${shown.join(', ')}${rest > 0 ? `, and ${String(rest)} more` : ''}`;
21
21
  }
22
+ /**
23
+ * The third answer, and the one this file existed to make room for: things happened, and they belong
24
+ * to a document that has since been replaced.
25
+ *
26
+ * `describeObserved` already separates "nothing happened" from "things happened but not that one",
27
+ * because those need different fixes. A window whose evidence was discarded as superseded is a third
28
+ * case needing a third fix, and it is the one that reads most like the first: everything that could
29
+ * have answered the question is gone, so a bare "nothing happened in this window" is available and
30
+ * wrong. The remedy is in the sentence, because an agent that cannot act on a caveat learns to skip
31
+ * it — here the remedy is to drive the action again against the page now on screen.
32
+ */
33
+ export function describeSuperseded(noun, count) {
34
+ return (`no ${noun} belong to the document currently on screen: ${String(count)} ${noun} were observed ` +
35
+ `and every one of them belongs to a document that has since been replaced (the page reloaded or ` +
36
+ `navigated). This says nothing about the app — the evidence went away with the page that produced ` +
37
+ `it. Re-drive the action against the page now on screen`);
38
+ }
@@ -15,3 +15,16 @@ import type { Predicate } from './predicate-schema.js';
15
15
  * that reads as the subject of an unrelated test is a half-hour somebody loses later.
16
16
  */
17
17
  export declare function declaresState(predicate: Predicate): boolean;
18
+ /**
19
+ * Does the caller's predicate judge the DOM — an `element` or `text` clause anywhere in the tree?
20
+ *
21
+ * The question behind it is where a verdict is allowed to point. A DOM predicate has its own locus:
22
+ * the element it matched, whose `file:line` the descriptor already carries. A `signal`/`net`/`state`
23
+ * failure has none — there is no node for "the request was never made" — and that is the one case
24
+ * where the verdict borrows the last driven control's line, because the handler that should have
25
+ * fired lives there.
26
+ *
27
+ * Walks composites for the same reason `declaresState` does: `allOf[{element}, {signal}]` looked at
28
+ * the DOM exactly as much as a bare `{element}` did.
29
+ */
30
+ export declare function declaresDom(predicate: Predicate): boolean;
@@ -24,3 +24,25 @@ export function declaresState(predicate) {
24
24
  return declaresState(predicate.predicate);
25
25
  return false;
26
26
  }
27
+ /**
28
+ * Does the caller's predicate judge the DOM — an `element` or `text` clause anywhere in the tree?
29
+ *
30
+ * The question behind it is where a verdict is allowed to point. A DOM predicate has its own locus:
31
+ * the element it matched, whose `file:line` the descriptor already carries. A `signal`/`net`/`state`
32
+ * failure has none — there is no node for "the request was never made" — and that is the one case
33
+ * where the verdict borrows the last driven control's line, because the handler that should have
34
+ * fired lives there.
35
+ *
36
+ * Walks composites for the same reason `declaresState` does: `allOf[{element}, {signal}]` looked at
37
+ * the DOM exactly as much as a bare `{element}` did.
38
+ */
39
+ export function declaresDom(predicate) {
40
+ if (predicate.kind === PredicateKind.ELEMENT || predicate.kind === PredicateKind.TEXT)
41
+ return true;
42
+ if (predicate.kind === PredicateKind.ALL_OF || predicate.kind === PredicateKind.ANY_OF) {
43
+ return predicate.predicates.some(declaresDom);
44
+ }
45
+ if (predicate.kind === PredicateKind.NOT)
46
+ return declaresDom(predicate.predicate);
47
+ return false;
48
+ }
@@ -1,5 +1,6 @@
1
- import { EventType, PredicateKind, StreamDirection, isDevToolingUrl, } from '@reticlehq/core';
1
+ import { EventType, PredicateKind, StreamDirection, isDevToolingUrl, urlForMatch, REDACTED_VALUE, } from '@reticlehq/core';
2
2
  import { describeObserved } from './observed-in-window.js';
3
+ import { withoutUrlRaw } from './event-filters.js';
3
4
  // The predicate SHAPE — the discriminated union, its aliases and its zod schema — lives in
4
5
  // predicate-schema.ts. Re-exported here so every existing importer of this module is unaffected:
5
6
  // the two halves are one public surface, split only because the file outgrew the size backstop.
@@ -140,6 +141,26 @@ function describeCall(e) {
140
141
  const status = num(e.data['status']);
141
142
  return status === undefined ? head : `${head} → ${String(status)}`;
142
143
  }
144
+ /**
145
+ * What a miss should say when the displayed URL was redacted. Matching uses `urlRaw` when present;
146
+ * an older SDK has no copy, and this is the sentence both reporters asked for.
147
+ */
148
+ const REDACTED_PATH_HINT = 'this path segment was redacted — the literal you matched may be here, try bodyContains';
149
+ function observedNetCalls(events, urlContains) {
150
+ const calls = events.filter((e) => e.type === EventType.NET_REQUEST);
151
+ const base = describeObserved('calls', calls.map(describeCall));
152
+ if (urlContains === undefined)
153
+ return base;
154
+ const encoded = encodeURIComponent(REDACTED_VALUE);
155
+ const redacted = calls.some((e) => {
156
+ const url = str(e.data['url']) ?? '';
157
+ return url.includes(REDACTED_VALUE) || url.includes(encoded);
158
+ });
159
+ return redacted ? `${base}; ${REDACTED_PATH_HINT}` : base;
160
+ }
161
+ function netEvidence(data) {
162
+ return withoutUrlRaw({ data }).data;
163
+ }
143
164
  /**
144
165
  * How much of a response body a failure may quote. Enough to see the value that differed on the
145
166
  * bodies this field is used against (a JSON answer), and not a whole payload in every verdict.
@@ -160,6 +181,35 @@ function describeNetFilter(p) {
160
181
  const { kind: _kind, count: _count, since: _since, ...filter } = p;
161
182
  return JSON.stringify(filter);
162
183
  }
184
+ /** Same, for a signal: the matcher as applied, with the cardinality and the floor taken out. */
185
+ function describeSignalFilter(p) {
186
+ const { kind: _kind, count: _count, since: _since, ...filter } = p;
187
+ return JSON.stringify(filter);
188
+ }
189
+ /**
190
+ * Exact cardinality, once, for every channel that can count its matches.
191
+ *
192
+ * `net` and `signal` are the same assertion over different evidence — "this happened EXACTLY n
193
+ * times" — and the interesting half is the same on both: an over-count is `decided`, because a
194
+ * window only accumulates and a count cannot come back down, so waiting out the rest of the budget
195
+ * to report a double-fire buys nothing but latency.
196
+ *
197
+ * `noun` names the population in prose ("network call(s)", "signal(s)"), `filter` is the matcher as
198
+ * applied, and `assertion` is the oracle that judged it.
199
+ */
200
+ function evalExactCount(args) {
201
+ const { matched, want, noun, filter, assertion } = args;
202
+ if (matched === want)
203
+ return { pass: true, evidence: { matched } };
204
+ return {
205
+ pass: false,
206
+ ...(matched > want ? { decided: true } : {}),
207
+ failureReason: `expected ${String(want)} ${noun} matching ${filter}, saw ${String(matched)}`,
208
+ observed: `${String(matched)} matching ${noun}`,
209
+ expected: `exactly ${String(want)} matching ${filter}`,
210
+ assertion,
211
+ };
212
+ }
163
213
  /**
164
214
  * URL suffixes only the DOCUMENT fetches, which the network observer therefore never records.
165
215
  *
@@ -285,6 +335,12 @@ export function evalNet(events, p) {
285
335
  * a UI wiring bug that did not exist, when the defect was the value the server answered with.
286
336
  */
287
337
  let bodyMismatch;
338
+ /**
339
+ * The prefix of a TRUNCATED body the needle was not found in. Held apart from `bodyMismatch`
340
+ * because the two are different verdicts: a full body without the needle decides the assertion,
341
+ * a truncated one cannot (#614).
342
+ */
343
+ let truncatedBody;
288
344
  const matches = events.filter((e) => {
289
345
  if (e.type !== EventType.NET_REQUEST || e.t < since)
290
346
  return false;
@@ -292,7 +348,7 @@ export function evalNet(events, p) {
292
348
  if (p.method !== undefined && str(d['method'])?.toUpperCase() !== p.method.toUpperCase()) {
293
349
  return false;
294
350
  }
295
- if (p.urlContains !== undefined && !(str(d['url']) ?? '').includes(p.urlContains)) {
351
+ if (p.urlContains !== undefined && !urlForMatch(d).includes(p.urlContains)) {
296
352
  return false;
297
353
  }
298
354
  if (p.status !== undefined) {
@@ -321,6 +377,15 @@ export function evalNet(events, p) {
321
377
  return false;
322
378
  }
323
379
  if (!response.includes(p.bodyContains)) {
380
+ // A needle missing from a body we only hold the FIRST N BYTES of is undecidable, not
381
+ // absent: the rest of the response was never recorded, so nothing here can say whether it
382
+ // was in there (#614). Grading it `pass: false` with "the response value is what differed"
383
+ // is the inversion the honesty rules exist to prevent — an unknown reported as decided,
384
+ // against a response that was very likely correct.
385
+ if (true === d['responseBodyTruncated']) {
386
+ truncatedBody ??= response;
387
+ return false;
388
+ }
324
389
  bodyMismatch ??= response;
325
390
  return false;
326
391
  }
@@ -348,6 +413,18 @@ export function evalNet(events, p) {
348
413
  assertion: 'net.bodyContains',
349
414
  };
350
415
  }
416
+ // Ranked ABOVE the mismatch branch: when both a truncated and a full body missed the needle,
417
+ // the honest verdict is the undecidable one. Deciding on the full body would report a failure
418
+ // the truncated call may well contradict.
419
+ if (truncatedBody !== undefined && 0 === matches.length) {
420
+ return {
421
+ pass: false,
422
+ inconclusive: `a call matching ${describeNetFilter(p)} was answered with a body that was TRUNCATED before it was recorded, and ${JSON.stringify(p.bodyContains)} is not in the part that was kept — so this is undecidable, not a failure. Raise the capture cap or assert on something inside the recorded prefix`,
423
+ observed: `the first ${String(truncatedBody.length)} characters of a truncated response body ${JSON.stringify(clipBody(truncatedBody))}`,
424
+ expected: `a response body containing ${JSON.stringify(p.bodyContains)}`,
425
+ assertion: 'net.bodyContains',
426
+ };
427
+ }
351
428
  if (bodyMismatch !== undefined && 0 === matches.length) {
352
429
  // The call is there and its body is there; only the VALUE differs. Counting it as zero matches
353
430
  // points at the wiring, which is the one place the defect is not.
@@ -363,26 +440,24 @@ export function evalNet(events, p) {
363
440
  // useEffect-double-fire / retry-storm regression class, where the request DID fire (presence passes)
364
441
  // but fired the WRONG number of times. Without `count`, the matcher is presence-only (≥1).
365
442
  if (p.count !== undefined) {
366
- return matches.length === p.count
367
- ? { pass: true, evidence: { matched: matches.length } }
368
- : 0 === matches.length && targetsUnobservedChannel(p) && !sawSubresources
369
- ? {
370
- pass: false,
371
- failureReason: unobservedChannelReason(p),
372
- inconclusive: unobservedChannelReason(p),
373
- assertion: 'net.count',
374
- }
375
- : {
376
- pass: false,
377
- // Monotonic: more matches than asked for can never become exactly the number asked for.
378
- // This is the double-submit — two writes 59ms apart — so the finding that matters most was
379
- // also the one that took the caller's whole budget to report.
380
- ...(matches.length > p.count ? { decided: true } : {}),
381
- failureReason: `expected ${String(p.count)} network call(s) matching ${describeNetFilter(p)}, saw ${String(matches.length)}`,
382
- observed: `${String(matches.length)} matching network call(s)`,
383
- expected: `exactly ${String(p.count)} matching ${describeNetFilter(p)}`,
384
- assertion: 'net.count',
385
- };
443
+ if (matches.length !== p.count &&
444
+ 0 === matches.length &&
445
+ targetsUnobservedChannel(p) &&
446
+ !sawSubresources) {
447
+ return {
448
+ pass: false,
449
+ failureReason: unobservedChannelReason(p),
450
+ inconclusive: unobservedChannelReason(p),
451
+ assertion: 'net.count',
452
+ };
453
+ }
454
+ return evalExactCount({
455
+ matched: matches.length,
456
+ want: p.count,
457
+ noun: 'network call(s)',
458
+ filter: describeNetFilter(p),
459
+ assertion: 'net.count',
460
+ });
386
461
  }
387
462
  const hit = matches[0];
388
463
  if (hit === undefined && targetsUnobservedChannel(p) && !sawSubresources) {
@@ -391,19 +466,19 @@ export function evalNet(events, p) {
391
466
  pass: false,
392
467
  failureReason: reason,
393
468
  inconclusive: reason,
394
- observed: describeObserved('calls', events.filter((e) => e.type === EventType.NET_REQUEST).map(describeCall)),
469
+ observed: observedNetCalls(events, p.urlContains),
395
470
  expected: `at least one call matching ${describeNetFilter(p)}`,
396
471
  assertion: 'net.unobserved-channel',
397
472
  };
398
473
  }
399
474
  return hit !== undefined
400
- ? { pass: true, evidence: hit.data }
475
+ ? { pass: true, evidence: netEvidence(hit.data) }
401
476
  : {
402
477
  pass: false,
403
478
  failureReason: `no network call matched ${JSON.stringify(p)}`,
404
479
  // Same reasoning as the signal miss: "no matching call" cannot be told apart from "the app
405
480
  // made no calls at all", and those need different fixes.
406
- observed: describeObserved('calls', events.filter((e) => e.type === EventType.NET_REQUEST).map(describeCall)),
481
+ observed: observedNetCalls(events, p.urlContains),
407
482
  expected: `at least one call matching ${JSON.stringify(p)}`,
408
483
  assertion: 'net.present',
409
484
  };
@@ -527,7 +602,7 @@ export function evalAnimation(events, p) {
527
602
  };
528
603
  }
529
604
  export function evalSignal(events, p) {
530
- const hit = events.find((e) => {
605
+ const isMatch = (e) => {
531
606
  if (e.type !== EventType.SIGNAL)
532
607
  return false;
533
608
  if (p.name !== undefined && str(e.data['name']) !== p.name)
@@ -538,7 +613,22 @@ export function evalSignal(events, p) {
538
613
  return false;
539
614
  }
540
615
  return true;
541
- });
616
+ };
617
+ // `count` (exact) turns presence into a cardinality assertion, exactly as it does on `net`. The
618
+ // double-fire is invisible to every state-only oracle — a handler wired twice leaves the store in
619
+ // the right shape and fires the signal twice — and so is the wrong-name fire, where the intended
620
+ // signal fires once beside a mistyped sibling and a presence check cannot say which is which.
621
+ // Counting only what the MATCHER matched is what separates them. Omit = presence (≥1).
622
+ if (p.count !== undefined) {
623
+ return evalExactCount({
624
+ matched: events.filter(isMatch).length,
625
+ want: p.count,
626
+ noun: 'signal(s)',
627
+ filter: describeSignalFilter(p),
628
+ assertion: 'signal.count',
629
+ });
630
+ }
631
+ const hit = events.find(isMatch);
542
632
  if (hit !== undefined)
543
633
  return { pass: true, evidence: hit.data };
544
634
  // Near-miss: show signals that fired with the same name (so the agent sees the real data).
@@ -14,10 +14,4 @@
14
14
  */
15
15
  import { z } from 'zod';
16
16
  import { PredicateSchema } from './predicate-eval.js';
17
- /**
18
- * Parse a predicate, or throw an Error whose message is a sentence.
19
- *
20
- * Deliberately still THROWS: every call site already handles a throw, and turning this into a
21
- * result type would mean touching three handlers to gain nothing the message does not already say.
22
- */
23
17
  export declare function parsePredicate(input: unknown): z.infer<typeof PredicateSchema>;
@@ -67,7 +67,35 @@ function describeIssue(issue) {
67
67
  * Deliberately still THROWS: every call site already handles a throw, and turning this into a
68
68
  * result type would mean touching three handlers to gain nothing the message does not already say.
69
69
  */
70
+ /**
71
+ * The placeholder `verify_next` puts in the `until` it suggests, sent back unchanged.
72
+ *
73
+ * The nudge hands the agent a ready-made `act_and_wait` call with `ref` and `action` filled in from
74
+ * the act that dispatched, and leaves the consequence blank on purpose: naming it is the one part
75
+ * only the agent can know, and guessing would be Reticle inventing the assertion.
76
+ *
77
+ * Sent verbatim it parses as a perfectly valid text predicate that simply never matches, so the
78
+ * verdict came back `verified:"no"` / "the declared consequence did not hold" — blaming the app for
79
+ * a placeholder the agent forgot to replace, and sending it to hunt a defect that does not exist.
80
+ * Measured on a live app before this guard: a confident, wrong, actionable-looking answer, which is
81
+ * the exact failure the rest of this file exists to avoid.
82
+ */
83
+ const UNFILLED_PLACEHOLDER = /^<name the consequence/i;
84
+ function placeholderValue(input) {
85
+ if ('object' !== typeof input || null === input)
86
+ return undefined;
87
+ const value = input['value'];
88
+ return 'string' === typeof value && UNFILLED_PLACEHOLDER.test(value) ? value : undefined;
89
+ }
70
90
  export function parsePredicate(input) {
91
+ const unfilled = placeholderValue(input);
92
+ if (unfilled !== undefined) {
93
+ throw new Error(`that predicate still carries the placeholder from verify_next ("${unfilled}"). Nothing ran, ` +
94
+ 'and no verdict was produced — which is deliberate: as written it would have failed and ' +
95
+ 'blamed the app for a value you had not filled in yet. Replace it with the consequence ' +
96
+ 'this action actually causes, and prefer one the action CHANGES: a signal, a request, a ' +
97
+ 'route, or store state. Text on screen that was already there proves nothing.');
98
+ }
71
99
  const parsed = PredicateSchema.safeParse(input);
72
100
  if (parsed.success)
73
101
  return parsed.data;
@@ -78,7 +106,7 @@ export function parsePredicate(input) {
78
106
  // back is how the zod array became unreadable in the first place.
79
107
  const issues = parsed.error.issues.slice(0, 3).map(describeIssue).join('; ');
80
108
  throw new Error(`that predicate did not parse (kind "${kind}"): ${issues}. Nothing ran — the predicate was ` +
81
- `not evaluated, so no verdict was produced. ${accepted(kind)} ` +
109
+ `not evaluated, so no verdict was produced. ${accepted(kind, parsed.error.issues)} ` +
82
110
  `A valid ${kind} predicate looks like: ${exampleFor(kind)}`);
83
111
  }
84
112
  /**
@@ -88,12 +116,24 @@ export function parsePredicate(input) {
88
116
  * round trip on the one call path that produces verdicts. Naming the accepted fields — or, when the
89
117
  * kind itself is the mistake, the accepted kinds — makes the retry informed instead.
90
118
  */
91
- function accepted(kind) {
119
+ function accepted(kind, issues) {
92
120
  const fields = predicateFieldsFor(kind);
93
121
  if (0 < fields.length) {
94
122
  // A field whose value is an object is the one an agent cannot guess from its name alone, so
95
123
  // expand it in the same breath rather than making the shape a second round trip.
96
- const nested = Object.entries(predicateNestedFieldsFor(kind))
124
+ //
125
+ // Only the field the rejection actually points at, though. Expanding every object-valued field
126
+ // of the kind makes the sentence longer the more the schema grows, and buries the one clause
127
+ // that answers the question asked — the same argument this file already makes for showing one
128
+ // example per kind instead of a generic one. `element` is the only kind with an object-valued
129
+ // field today, so the two agree except when the mistake is somewhere else entirely, which is
130
+ // the case pinned in the tests.
131
+ const all = predicateNestedFieldsFor(kind);
132
+ const blamed = new Set(issues
133
+ .map((issue) => issue.path[0])
134
+ .filter((field) => 'string' === typeof field));
135
+ const nested = Object.entries(all)
136
+ .filter(([field]) => blamed.has(field))
97
137
  .map(([field, keys]) => ` ${field} accepts: ${keys.join(', ')}.`)
98
138
  .join('');
99
139
  return `${kind} accepts: ${fields.join(', ')}.${nested}`;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The reason this predicate cannot be evaluated, or undefined when it can.
3
+ *
4
+ * Only checks what is knowable WITHOUT the page — a locator that names fields the resolver drops is
5
+ * wrong whatever the DOM contains, which is what makes it safe to refuse before acting. Anything
6
+ * that depends on what is actually rendered is still decided after the action, where it belongs.
7
+ */
8
+ export declare function unevaluablePredicateReason(predicate: unknown): string | undefined;
@@ -0,0 +1,41 @@
1
+ import { residualQueryChecks } from './predicate-schema.js';
2
+ /** Composite predicates nest; the unusable one can be at any depth. */
3
+ const NESTED_KEYS = ['predicates', 'predicate'];
4
+ const isRecord = (v) => 'object' === typeof v && null !== v;
5
+ /** Every element query in a predicate tree, in the order a reader would meet them. */
6
+ function elementQueries(predicate, found = []) {
7
+ if (!isRecord(predicate))
8
+ return found;
9
+ if (isRecord(predicate['query']))
10
+ found.push(predicate['query']);
11
+ for (const key of NESTED_KEYS) {
12
+ const nested = predicate[key];
13
+ if (Array.isArray(nested))
14
+ for (const child of nested)
15
+ elementQueries(child, found);
16
+ else if (isRecord(nested))
17
+ elementQueries(nested, found);
18
+ }
19
+ return found;
20
+ }
21
+ /**
22
+ * The reason this predicate cannot be evaluated, or undefined when it can.
23
+ *
24
+ * Only checks what is knowable WITHOUT the page — a locator that names fields the resolver drops is
25
+ * wrong whatever the DOM contains, which is what makes it safe to refuse before acting. Anything
26
+ * that depends on what is actually rendered is still decided after the action, where it belongs.
27
+ */
28
+ export function unevaluablePredicateReason(predicate) {
29
+ for (const query of elementQueries(predicate)) {
30
+ const residual = residualQueryChecks(query);
31
+ if (0 === residual.unusable.length)
32
+ continue;
33
+ return (`the element locator ignores ${residual.unusable.map((f) => `\`${f}\``).join(', ')} ` +
34
+ `in ${JSON.stringify(query)} — it resolves by the first of by+value, component/source, role, ` +
35
+ 'text, label, placeholder, testid, alt that is present, and nothing here can check the rest. ' +
36
+ 'Assert them one locator at a time, or move the extra field into the locator. ' +
37
+ 'Nothing was acted on: this predicate could never have been evaluated, so refusing it costs ' +
38
+ 'you nothing and spending the action on it would have cost you the verdict.');
39
+ }
40
+ return undefined;
41
+ }
@@ -17,6 +17,20 @@ export declare const RouteDecidedBy: {
17
17
  readonly CHANGE: "route-change";
18
18
  readonly CURRENT: "current-route";
19
19
  };
20
+ /**
21
+ * The path the router is actually on.
22
+ *
23
+ * Under a hash router the document pathname is `/` on every page and the whole route lives in the
24
+ * fragment — which is why `contains` below matches the full route. `pathname` was left comparing the
25
+ * document path, so `{ pathname: '/posts/12/show' }` could never pass on a HashRouter app, the
26
+ * standard router for a packaged Electron/Tauri renderer. That is a guaranteed false red: Reticle
27
+ * telling a user their working app is broken.
28
+ *
29
+ * The fragment WINS rather than being accepted alongside the document path. Honouring both would
30
+ * make `{ pathname: '/' }` trivially true on every hash-routed page — a green that says nothing
31
+ * about where the app is, which is the failure this predicate exists to prevent.
32
+ */
33
+ export declare function routePathOf(pathname: string, hash: string): string;
20
34
  export declare function evalRoute(events: ReticleEvent[], p: Extract<Predicate, {
21
35
  kind: typeof PredicateKind.ROUTE;
22
36
  }>,
@@ -17,6 +17,26 @@ export const RouteDecidedBy = {
17
17
  CHANGE: 'route-change',
18
18
  CURRENT: 'current-route',
19
19
  };
20
+ /**
21
+ * The path the router is actually on.
22
+ *
23
+ * Under a hash router the document pathname is `/` on every page and the whole route lives in the
24
+ * fragment — which is why `contains` below matches the full route. `pathname` was left comparing the
25
+ * document path, so `{ pathname: '/posts/12/show' }` could never pass on a HashRouter app, the
26
+ * standard router for a packaged Electron/Tauri renderer. That is a guaranteed false red: Reticle
27
+ * telling a user their working app is broken.
28
+ *
29
+ * The fragment WINS rather than being accepted alongside the document path. Honouring both would
30
+ * make `{ pathname: '/' }` trivially true on every hash-routed page — a green that says nothing
31
+ * about where the app is, which is the failure this predicate exists to prevent.
32
+ */
33
+ export function routePathOf(pathname, hash) {
34
+ if (!hash.startsWith('#/'))
35
+ return pathname;
36
+ const withoutHash = hash.slice(1);
37
+ const query = withoutHash.indexOf('?');
38
+ return -1 === query ? withoutHash : withoutHash.slice(0, query);
39
+ }
20
40
  /**
21
41
  * The session's live URL, split the way a route-change event already carries it.
22
42
  *
@@ -30,6 +50,7 @@ function readCurrentRoute(url) {
30
50
  const hash = parsed?.hash ?? '';
31
51
  return {
32
52
  pathname,
53
+ routePath: routePathOf(pathname, hash),
33
54
  full: `${pathname}${search}${hash}`,
34
55
  decidedBy: RouteDecidedBy.CURRENT,
35
56
  data: { pathname, search, hash, url, decidedBy: RouteDecidedBy.CURRENT },
@@ -55,12 +76,17 @@ currentUrl) {
55
76
  const routes = events.filter((e) => e.type === EventType.ROUTE_CHANGE);
56
77
  const last = routes.at(-1);
57
78
  const reading = last !== undefined
58
- ? {
59
- pathname: str(last.data['pathname']) ?? str(last.data['to']) ?? '',
60
- full: `${str(last.data['pathname']) ?? str(last.data['to']) ?? ''}${str(last.data['search']) ?? ''}${str(last.data['hash']) ?? ''}`,
61
- decidedBy: RouteDecidedBy.CHANGE,
62
- data: { ...last.data, decidedBy: RouteDecidedBy.CHANGE },
63
- }
79
+ ? (() => {
80
+ const changedPath = str(last.data['pathname']) ?? str(last.data['to']) ?? '';
81
+ const changedHash = str(last.data['hash']) ?? '';
82
+ return {
83
+ pathname: changedPath,
84
+ routePath: routePathOf(changedPath, changedHash),
85
+ full: `${changedPath}${str(last.data['search']) ?? ''}${changedHash}`,
86
+ decidedBy: RouteDecidedBy.CHANGE,
87
+ data: { ...last.data, decidedBy: RouteDecidedBy.CHANGE },
88
+ };
89
+ })()
64
90
  : currentUrl === undefined || 0 === currentUrl.length
65
91
  ? undefined
66
92
  : readCurrentRoute(currentUrl);
@@ -73,11 +99,13 @@ currentUrl) {
73
99
  assertion: 'route.changed',
74
100
  };
75
101
  }
76
- const pathname = reading.pathname;
102
+ // The ROUTER's path, not the document's — see routePathOf.
103
+ const pathname = reading.routePath;
77
104
  if (p.pathname !== undefined && pathname !== p.pathname) {
78
105
  return {
79
106
  pass: false,
80
- failureReason: `${describeRouteSource(reading)} is '${pathname}', expected '${p.pathname}'`,
107
+ // No ` is` after the clause: it read "route changed to is '/'".
108
+ failureReason: `${describeRouteSource(reading)} '${pathname}', expected '${p.pathname}'`,
81
109
  observed: `${describeRouteSource(reading)} '${pathname}'`,
82
110
  expected: `route '${p.pathname}'`,
83
111
  assertion: 'route.pathname',
@@ -56,6 +56,11 @@ export type Predicate = {
56
56
  kind: typeof PredicateKind.SIGNAL;
57
57
  name?: string;
58
58
  dataMatches?: Record<string, unknown>;
59
+ /**
60
+ * Exact number of matching signals in the window — the same cardinality assertion `net`
61
+ * carries, on the channel the app itself speaks. Omit for presence (≥1).
62
+ */
63
+ count?: number;
59
64
  since?: number;
60
65
  } | {
61
66
  kind: typeof PredicateKind.STATE;
@@ -103,6 +108,45 @@ export declare const PredicateSchema: z.ZodType<Predicate>;
103
108
  * wall. Empty for a kind that is not in the union.
104
109
  */
105
110
  export declare function predicateFieldsFor(kind: string): readonly string[];
111
+ /**
112
+ * Is this tool parameter ACTUALLY the predicate union?
113
+ *
114
+ * Not a name check. `until` is overloaded on this surface — the act/assert family means a predicate
115
+ * by it, while reticle_observe / _network / _console mean a NUMBER, an upper cursor bound — so any
116
+ * caller keying on the word would treat a numeric parameter as a predicate and describe it to the
117
+ * agent as an object. Keying on the schema cannot make that mistake.
118
+ *
119
+ * Lives beside the schema because two callers now ask the question: the lean tool surface, which
120
+ * compacts these parameters, and `reticle_tools`, which spells their grammar out on request. Two
121
+ * spellings of "is this a predicate" is one chance for the surface and the grammar to disagree.
122
+ */
123
+ export declare function isPredicateParam(schema: z.ZodTypeAny): boolean;
124
+ /**
125
+ * Every kind's fields, in one block an agent can write a predicate from.
126
+ *
127
+ * The tool surface advertises the KIND list and points at `reticle_tools` for the fields, because
128
+ * inlining the 12-variant recursive union in the declared JSON Schema costs thousands of characters
129
+ * per predicate parameter, re-sent every turn, to describe a grammar most calls use one variant of.
130
+ * That trade is right — but the pointer has to land somewhere, and it landed on a parameter
131
+ * description reading "same shape as reticle_assert". So the grammar was reachable from nothing, and
132
+ * an agent that could not find `route`'s fields fell back to a `text` check where a route check was
133
+ * meant: the weaker oracle, which is the expensive half of an undiscoverable grammar.
134
+ *
135
+ * Derived from the schema, like `predicateFieldsFor` and for the same reason: a hand-written grammar
136
+ * that drifts is worse than none, because the agent trusts it and retries into the same wall.
137
+ */
138
+ export declare function predicateGrammar(): Readonly<Record<string, string>>;
139
+ /**
140
+ * The keys of a field that is an object, once its wrappers are peeled. Empty for anything else.
141
+ *
142
+ * Exported so the wrapper handling is asserted against THIS function rather than through the
143
+ * rendered sentence. Reached only that way, a wrapper it fails to peel returns `[]`, the old
144
+ * message prints unchanged, and nothing reddens — a regression with no symptom.
145
+ *
146
+ * No top-level predicate field is currently declared behind a wrapper, so the real schema exercises
147
+ * none of these paths; that is exactly why they are worth a case each.
148
+ */
149
+ export declare function nestedKeysOf(schema: z.ZodTypeAny | undefined): readonly string[];
106
150
  /**
107
151
  * One level into the fields that are themselves objects — `{ query: ['by', 'value', ...] }`.
108
152
  *