@reticlehq/server 2.13.0 → 2.14.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 (451) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/dev-server-wait.d.ts +8 -0
  233. package/dist/setup/dev-server-wait.js +3 -1
  234. package/dist/setup/drive-agent.d.ts +2 -3
  235. package/dist/setup/drive-agent.js +1 -1
  236. package/dist/setup/drive-plan.d.ts +3 -2
  237. package/dist/setup/ensure-daemon.d.ts +2 -1
  238. package/dist/setup/existing-app.d.ts +12 -0
  239. package/dist/setup/existing-app.js +13 -0
  240. package/dist/setup/init-host.d.ts +11 -0
  241. package/dist/setup/init-host.js +26 -0
  242. package/dist/setup/init-runtime.d.ts +4 -10
  243. package/dist/setup/init-runtime.js +23 -16
  244. package/dist/setup/license-key.d.ts +2 -1
  245. package/dist/setup/listeners.d.ts +2 -1
  246. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  247. package/dist/setup/loopback-probe-urls.js +52 -0
  248. package/dist/setup/node-effects.d.ts +3 -0
  249. package/dist/setup/node-effects.js +26 -11
  250. package/dist/setup/page-probe.d.ts +5 -0
  251. package/dist/setup/relaunch.d.ts +2 -1
  252. package/dist/setup/run-setup.d.ts +5 -1
  253. package/dist/setup/run-setup.js +155 -48
  254. package/dist/setup/setup-command.d.ts +3 -2
  255. package/dist/setup/setup-command.js +8 -0
  256. package/dist/setup/setup-options.d.ts +5 -3
  257. package/dist/setup/setup-options.js +3 -2
  258. package/dist/setup/wait-progress.d.ts +28 -0
  259. package/dist/setup/wait-progress.js +43 -0
  260. package/dist/telemetry/app-instrumented.d.ts +2 -1
  261. package/dist/telemetry/app-instrumented.js +1 -1
  262. package/dist/telemetry/automation-hint.d.ts +1 -1
  263. package/dist/telemetry/automation-hint.js +1 -1
  264. package/dist/telemetry/cli-telemetry.js +1 -1
  265. package/dist/telemetry/connect-failure.d.ts +1 -1
  266. package/dist/telemetry/connect-failure.js +1 -1
  267. package/dist/telemetry/crash-cause.d.ts +3 -2
  268. package/dist/telemetry/crash-cause.js +1 -1
  269. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  270. package/dist/telemetry/daemon-telemetry.js +1 -1
  271. package/dist/telemetry/error-class.d.ts +3 -2
  272. package/dist/telemetry/error-class.js +1 -1
  273. package/dist/telemetry/feedback-cli.js +1 -1
  274. package/dist/telemetry/feedback-context.d.ts +3 -2
  275. package/dist/telemetry/feedback-context.js +5 -4
  276. package/dist/telemetry/feedback-local.d.ts +3 -2
  277. package/dist/telemetry/feedback.d.ts +4 -3
  278. package/dist/telemetry/feedback.js +1 -1
  279. package/dist/telemetry/git-facts.d.ts +2 -2
  280. package/dist/telemetry/git-facts.js +1 -1
  281. package/dist/telemetry/identify.d.ts +1 -1
  282. package/dist/telemetry/identify.js +1 -1
  283. package/dist/telemetry/init-telemetry.d.ts +2 -14
  284. package/dist/telemetry/init-telemetry.js +4 -13
  285. package/dist/telemetry/install-source.d.ts +7 -13
  286. package/dist/telemetry/install-source.js +7 -29
  287. package/dist/telemetry/license-activation.d.ts +3 -2
  288. package/dist/telemetry/license-activation.js +1 -1
  289. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  290. package/dist/telemetry/mcp-connection.js +1 -1
  291. package/dist/telemetry/project-profile.d.ts +0 -14
  292. package/dist/telemetry/project-profile.js +1 -1
  293. package/dist/telemetry/proxy-telemetry.js +1 -1
  294. package/dist/telemetry/session-metrics.d.ts +1 -1
  295. package/dist/telemetry/session-metrics.js +1 -1
  296. package/dist/telemetry/telemetry.d.ts +1 -17
  297. package/dist/telemetry/telemetry.js +4 -4
  298. package/dist/telemetry/tool-refused.d.ts +1 -1
  299. package/dist/telemetry/tool-refused.js +1 -1
  300. package/dist/tools/act-observation.d.ts +3 -2
  301. package/dist/tools/act-preflight.js +8 -3
  302. package/dist/tools/act-target.d.ts +4 -0
  303. package/dist/tools/act-target.js +14 -5
  304. package/dist/tools/act-tools.d.ts +0 -2
  305. package/dist/tools/act-tools.js +45 -15
  306. package/dist/tools/assert-source.d.ts +30 -0
  307. package/dist/tools/assert-source.js +21 -0
  308. package/dist/tools/assert-verdict.d.ts +1 -1
  309. package/dist/tools/assert-verdict.js +14 -4
  310. package/dist/tools/browser-tools.js +28 -8
  311. package/dist/tools/complete-snapshot.d.ts +3 -2
  312. package/dist/tools/dynamic-tools.js +8 -0
  313. package/dist/tools/error-recovery.d.ts +10 -0
  314. package/dist/tools/error-recovery.js +23 -0
  315. package/dist/tools/feedback-tools.js +1 -1
  316. package/dist/tools/invoke-tool.js +2 -0
  317. package/dist/tools/lease-tools.d.ts +48 -20
  318. package/dist/tools/lease-tools.js +311 -76
  319. package/dist/tools/lineage-tools.d.ts +14 -0
  320. package/dist/tools/lineage-tools.js +64 -0
  321. package/dist/tools/merge-tools.js +1 -3
  322. package/dist/tools/navigate-arrival.d.ts +45 -1
  323. package/dist/tools/navigate-arrival.js +39 -6
  324. package/dist/tools/navigate-result.d.ts +8 -1
  325. package/dist/tools/navigate-result.js +14 -4
  326. package/dist/tools/numeric-bounds.d.ts +0 -11
  327. package/dist/tools/numeric-bounds.js +4 -4
  328. package/dist/tools/observe-tools.js +22 -26
  329. package/dist/tools/observed-nothing.d.ts +9 -0
  330. package/dist/tools/observed-nothing.js +2 -1
  331. package/dist/tools/read-tools.js +12 -17
  332. package/dist/tools/real-input-attempt.d.ts +14 -6
  333. package/dist/tools/real-input-attempt.js +75 -18
  334. package/dist/tools/reconcile-tools.js +2 -1
  335. package/dist/tools/reload-result.d.ts +6 -1
  336. package/dist/tools/reload-result.js +7 -2
  337. package/dist/tools/resolve-target.d.ts +7 -1
  338. package/dist/tools/resolve-target.js +72 -9
  339. package/dist/tools/settle-in-flight.d.ts +1 -1
  340. package/dist/tools/settle-in-flight.js +9 -2
  341. package/dist/tools/snapshot-delta.d.ts +24 -1
  342. package/dist/tools/snapshot-delta.js +66 -7
  343. package/dist/tools/tool-names.d.ts +1 -0
  344. package/dist/tools/tool-names.js +1 -0
  345. package/dist/tools/tool-surface.js +20 -2
  346. package/dist/tools/tools.js +120 -11
  347. package/dist/tools/verify-next-baton.d.ts +2 -1
  348. package/dist/update/sdk-sync.d.ts +1 -1
  349. package/dist/update/sdk-sync.js +1 -1
  350. package/dist/update/updater.js +1 -1
  351. package/dist/version/sdk-fix.d.ts +2 -1
  352. package/dist/version/version-skew.d.ts +0 -8
  353. package/dist/version/version-skew.js +1 -1
  354. package/docs/README.md +8 -6
  355. package/docs/actions.mdx +1 -1
  356. package/docs/agent-cheatsheet.md +4 -2
  357. package/docs/architecture.md +1 -1
  358. package/docs/cli/mcp.mdx +3 -1
  359. package/docs/cli/watch.mdx +1 -1
  360. package/docs/cli.mdx +1 -1
  361. package/docs/desktop.mdx +1 -1
  362. package/docs/docs.json +8 -3
  363. package/docs/faq.mdx +9 -0
  364. package/docs/first-drive.md +136 -0
  365. package/docs/frameworks.mdx +29 -7
  366. package/docs/gate-plan.md +2 -2
  367. package/docs/gates.md +19 -11
  368. package/docs/getting-started.md +7 -1
  369. package/docs/http-transport.md +164 -0
  370. package/docs/index.mdx +5 -3
  371. package/docs/install-manual.mdx +1 -1
  372. package/docs/packages/tauri.mdx +1 -1
  373. package/docs/packages.mdx +15 -3
  374. package/docs/platform-integration.md +1 -1
  375. package/docs/predicates.mdx +2 -0
  376. package/docs/system-map.md +2 -2
  377. package/docs/telemetry-contract.md +4 -4
  378. package/docs/telemetry-events.mdx +1 -1
  379. package/docs/telemetry.md +3 -3
  380. package/docs/tools-navigate.mdx +14 -6
  381. package/docs/usage.md +4 -4
  382. package/package.json +7 -4
  383. package/dist/init/agent-rules.d.ts +0 -116
  384. package/dist/init/agent-rules.js +0 -279
  385. package/dist/init/app-choice.d.ts +0 -20
  386. package/dist/init/app-choice.js +0 -25
  387. package/dist/init/astro-host.d.ts +0 -21
  388. package/dist/init/astro-host.js +0 -27
  389. package/dist/init/astro-patch.d.ts +0 -33
  390. package/dist/init/astro-patch.js +0 -300
  391. package/dist/init/capabilities.d.ts +0 -40
  392. package/dist/init/capabilities.js +0 -150
  393. package/dist/init/closing-hint.d.ts +0 -36
  394. package/dist/init/closing-hint.js +0 -120
  395. package/dist/init/connect-steps.d.ts +0 -2
  396. package/dist/init/connect-steps.js +0 -50
  397. package/dist/init/cra.d.ts +0 -79
  398. package/dist/init/cra.js +0 -178
  399. package/dist/init/csp-check.d.ts +0 -38
  400. package/dist/init/csp-check.js +0 -107
  401. package/dist/init/csp-doctor.d.ts +0 -32
  402. package/dist/init/csp-doctor.js +0 -54
  403. package/dist/init/desktop-doctor.d.ts +0 -41
  404. package/dist/init/desktop-doctor.js +0 -198
  405. package/dist/init/detect.d.ts +0 -96
  406. package/dist/init/detect.js +0 -209
  407. package/dist/init/dev-script.d.ts +0 -50
  408. package/dist/init/dev-script.js +0 -66
  409. package/dist/init/format-generated.d.ts +0 -18
  410. package/dist/init/format-generated.js +0 -39
  411. package/dist/init/install-hint.d.ts +0 -9
  412. package/dist/init/install-hint.js +0 -29
  413. package/dist/init/mcp-clients.d.ts +0 -89
  414. package/dist/init/mcp-clients.js +0 -299
  415. package/dist/init/mcp-registered.d.ts +0 -12
  416. package/dist/init/mcp-registered.js +0 -14
  417. package/dist/init/mcp.d.ts +0 -89
  418. package/dist/init/mcp.js +0 -129
  419. package/dist/init/next-patch.d.ts +0 -39
  420. package/dist/init/next-patch.js +0 -196
  421. package/dist/init/node-io.d.ts +0 -7
  422. package/dist/init/node-io.js +0 -117
  423. package/dist/init/non-js-project.d.ts +0 -27
  424. package/dist/init/non-js-project.js +0 -84
  425. package/dist/init/patch-kind.d.ts +0 -20
  426. package/dist/init/patch-kind.js +0 -10
  427. package/dist/init/plan-framework.d.ts +0 -64
  428. package/dist/init/plan-framework.js +0 -565
  429. package/dist/init/plan.d.ts +0 -242
  430. package/dist/init/plan.js +0 -703
  431. package/dist/init/preflight.d.ts +0 -28
  432. package/dist/init/preflight.js +0 -38
  433. package/dist/init/project-id.d.ts +0 -12
  434. package/dist/init/project-id.js +0 -25
  435. package/dist/init/refresh-rules.d.ts +0 -39
  436. package/dist/init/refresh-rules.js +0 -51
  437. package/dist/init/run.d.ts +0 -117
  438. package/dist/init/run.js +0 -806
  439. package/dist/init/slash-command.d.ts +0 -27
  440. package/dist/init/slash-command.js +0 -132
  441. package/dist/init/snippets.d.ts +0 -134
  442. package/dist/init/snippets.js +0 -572
  443. package/dist/init/vite-config.d.ts +0 -18
  444. package/dist/init/vite-config.js +0 -114
  445. package/dist/init/windows-quote.d.ts +0 -38
  446. package/dist/init/windows-quote.js +0 -70
  447. package/dist/init/workspace-apps.d.ts +0 -30
  448. package/dist/init/workspace-apps.js +0 -178
  449. package/dist/project/remember-project.d.ts +0 -24
  450. package/dist/project/remember-project.js +0 -40
  451. /package/dist/{init → setup}/confirm.d.ts +0 -0
@@ -1,6 +1,8 @@
1
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
+ import { netEvidence } from './net-evidence.js';
4
+ import { checkRequestBody, newRequestBodyState, requestBodyVerdict, } from './predicate-request-body.js';
5
+ export { evalConsole } from './predicate-console.js';
4
6
  // The predicate SHAPE — the discriminated union, its aliases and its zod schema — lives in
5
7
  // predicate-schema.ts. Re-exported here so every existing importer of this module is unaffected:
6
8
  // the two halves are one public surface, split only because the file outgrew the size backstop.
@@ -103,7 +105,7 @@ function structurallyEqual(got, want) {
103
105
  return keys.every((k) => k in b && structurallyEqual(a[k], b[k]));
104
106
  }
105
107
  /** Shallow JSON pattern match: each key in `pattern` must match (see matchValue). */
106
- function dataMatches(actual, pattern) {
108
+ export function dataMatches(actual, pattern) {
107
109
  for (const [key, want] of Object.entries(pattern)) {
108
110
  if (!matchValue(actual[key], want))
109
111
  return false;
@@ -158,16 +160,13 @@ function observedNetCalls(events, urlContains) {
158
160
  });
159
161
  return redacted ? `${base}; ${REDACTED_PATH_HINT}` : base;
160
162
  }
161
- function netEvidence(data) {
162
- return withoutUrlRaw({ data }).data;
163
- }
164
163
  /**
165
164
  * How much of a response body a failure may quote. Enough to see the value that differed on the
166
165
  * bodies this field is used against (a JSON answer), and not a whole payload in every verdict.
167
166
  */
168
167
  const MAX_BODY_IN_FAILURE = 200;
169
168
  /** Enough of a body to see what differed, without paying for a whole payload in the verdict. */
170
- function clipBody(body) {
169
+ export function clipBody(body) {
171
170
  return body.length <= MAX_BODY_IN_FAILURE ? body : `${body.slice(0, MAX_BODY_IN_FAILURE)}…`;
172
171
  }
173
172
  /**
@@ -177,7 +176,7 @@ function clipBody(body) {
177
176
  * applied too, so the caller was shown a predicate it had not written and told nothing matched it.
178
177
  * A printed filter that is narrower than the real one is worse than none: it is believed.
179
178
  */
180
- function describeNetFilter(p) {
179
+ export function describeNetFilter(p) {
181
180
  const { kind: _kind, count: _count, since: _since, ...filter } = p;
182
181
  return JSON.stringify(filter);
183
182
  }
@@ -251,6 +250,45 @@ const DOCUMENT_ONLY_SUFFIXES = [
251
250
  '.eot',
252
251
  '.webmanifest',
253
252
  ];
253
+ /**
254
+ * File suffixes a click typically downloads or navigates to as a document, never as fetch/XHR.
255
+ *
256
+ * Sibling of DOCUMENT_ONLY_SUFFIXES, not a member of it. Those are subresources: once resource
257
+ * timing is live, a miss is evidence. A click on `<a href="/export.pdf">` is a navigation (or a
258
+ * Content-Disposition attachment). Resource timing of stylesheets does not make it visible, so
259
+ * grading the miss as "0 network calls" / a 404 is the lie #805 exists to stop — even on a page
260
+ * whose observer is running. `.js` / `.json` stay off this list for the same reason they stay off
261
+ * the other: they are routinely fetched.
262
+ */
263
+ const NATIVE_DOWNLOAD_SUFFIXES = [
264
+ '.pdf',
265
+ '.csv',
266
+ '.tsv',
267
+ '.zip',
268
+ '.gz',
269
+ '.tar',
270
+ '.xlsx',
271
+ '.xls',
272
+ '.docx',
273
+ '.doc',
274
+ '.pptx',
275
+ '.ppt',
276
+ '.ods',
277
+ '.odt',
278
+ '.rtf',
279
+ '.7z',
280
+ '.rar',
281
+ ];
282
+ /**
283
+ * Does this filter target a class of request the observer cannot see?
284
+ *
285
+ * Read off `urlContains` only, and only when the pattern ENDS in one of the suffixes — a filter of
286
+ * `/api/` that happens to contain `.css` somewhere in a query string is still an ordinary XHR
287
+ * target. Query strings and fragments are stripped first, since `favicon.ico?v=2` is the same asset.
288
+ */
289
+ function filterPath(urlContains) {
290
+ return (urlContains.split('#')[0] ?? '').split('?')[0]?.toLowerCase() ?? '';
291
+ }
254
292
  /**
255
293
  * Does this filter target a class of request the observer cannot see?
256
294
  *
@@ -259,10 +297,36 @@ const DOCUMENT_ONLY_SUFFIXES = [
259
297
  * target. Query strings and fragments are stripped first, since `favicon.ico?v=2` is the same asset.
260
298
  */
261
299
  function targetsUnobservedChannel(p) {
262
- if (p.urlContains === undefined)
300
+ const url = p.urlContains;
301
+ if (undefined === url)
263
302
  return false;
264
- const path = (p.urlContains.split('#')[0] ?? '').split('?')[0]?.toLowerCase() ?? '';
265
- return DOCUMENT_ONLY_SUFFIXES.some((suffix) => path.endsWith(suffix));
303
+ return DOCUMENT_ONLY_SUFFIXES.some((suffix) => filterPath(url).endsWith(suffix));
304
+ }
305
+ function targetsNativeDownload(p) {
306
+ const url = p.urlContains;
307
+ if (undefined === url)
308
+ return false;
309
+ return NATIVE_DOWNLOAD_SUFFIXES.some((suffix) => filterPath(url).endsWith(suffix));
310
+ }
311
+ function nativeDownloadReason(p) {
312
+ return (`no fetch or XHR matched ${describeNetFilter(p)}. A native download or document navigation ` +
313
+ `(an <a href> to a file, a Content-Disposition attachment, <a download>) never goes through ` +
314
+ `fetch or XMLHttpRequest, so an empty net window is not a 404 and is not evidence the export ` +
315
+ `failed. This is unobservable on the net channel. Assert the link href, or check the file ` +
316
+ `outside the browser — Reticle cannot see the bytes land`);
317
+ }
318
+ function nativeDownloadMiss(events, p) {
319
+ if (!targetsNativeDownload(p))
320
+ return undefined;
321
+ const reason = nativeDownloadReason(p);
322
+ return {
323
+ pass: false,
324
+ failureReason: reason,
325
+ inconclusive: reason,
326
+ observed: observedNetCalls(events, p.urlContains),
327
+ expected: `a fetch or XHR matching ${describeNetFilter(p)}`,
328
+ assertion: 'net.native-download',
329
+ };
266
330
  }
267
331
  /**
268
332
  * Initiator types the browser SDK's resource-timing observer stamps onto document-initiated records
@@ -308,6 +372,34 @@ function unobservedChannelReason(p) {
308
372
  `Nothing here says the app is wrong. Check it outside the browser, or assert something the ` +
309
373
  `document does not fetch on its own`);
310
374
  }
375
+ /**
376
+ * Appended to a zero-match `net` negative whose window starts at SDK attach.
377
+ *
378
+ * Every event's `t` is stamped `performance.now() - #start`, where `#start` is taken when the SDK
379
+ * is constructed, so `t` is never negative and a window with `since === 0` begins AT attach — never
380
+ * before it. Whatever the page did between navigation start and attach left no event at all, so
381
+ * "no matching call" over such a window cannot be told apart from "the call was made while nothing
382
+ * was watching yet".
383
+ *
384
+ * The gap is routine rather than exotic: a `fetch` from an effect in a root provider, or a classic
385
+ * `<script>` at the end of `<body>`, fires before a deferred module script has run. Reported from
386
+ * the field, the verdict then read as proof the request was never made, and reporters went looking
387
+ * for the defect in code that was working — restarting dev servers and re-reading providers to
388
+ * establish the request was invisible rather than absent.
389
+ *
390
+ * Same argument as DOCUMENT_ONLY_SUFFIXES one axis over: that one is a channel Reticle does not
391
+ * watch, this is a stretch of TIME it was not yet watching.
392
+ *
393
+ * The grade is deliberately NOT downgraded to `inconclusive`. A missing API call is the finding this
394
+ * oracle exists to make, and it is the strongest grade available for the startup class — session
395
+ * restore, feature flags, bootstrap config — which is exactly the class that silently breaks on
396
+ * reload. Every window an action opens carries `since > 0` and is untouched. What changes is only
397
+ * what the negative CLAIMS: it stops asserting the request never happened, and names the assertion
398
+ * that can settle it, because the state such a request produces IS observable after attach.
399
+ */
400
+ const PRE_ATTACH_CAVEAT = ' — note that this window starts where the SDK attached, and requests made before that are never ' +
401
+ 'captured, so a miss here cannot tell a call that was never made apart from one made before the ' +
402
+ 'page connected; if the call is expected during startup, assert on the state it produces instead';
311
403
  export function evalNet(events, p) {
312
404
  const since = p.since ?? 0;
313
405
  /**
@@ -341,6 +433,7 @@ export function evalNet(events, p) {
341
433
  * a truncated one cannot (#614).
342
434
  */
343
435
  let truncatedBody;
436
+ const requestState = newRequestBodyState();
344
437
  const matches = events.filter((e) => {
345
438
  if (e.type !== EventType.NET_REQUEST || e.t < since)
346
439
  return false;
@@ -390,6 +483,8 @@ export function evalNet(events, p) {
390
483
  return false;
391
484
  }
392
485
  }
486
+ if (!checkRequestBody(d, p, requestState))
487
+ return false;
393
488
  return true;
394
489
  });
395
490
  if (unobservableStatus && 0 === matches.length) {
@@ -413,6 +508,9 @@ export function evalNet(events, p) {
413
508
  assertion: 'net.bodyContains',
414
509
  };
415
510
  }
511
+ const requestVerdict = requestBodyVerdict(requestState, p, matches.length);
512
+ if (requestVerdict !== undefined)
513
+ return requestVerdict;
416
514
  // Ranked ABOVE the mismatch branch: when both a truncated and a full body missed the needle,
417
515
  // the honest verdict is the undecidable one. Deciding on the full body would report a failure
418
516
  // the truncated call may well contradict.
@@ -440,6 +538,11 @@ export function evalNet(events, p) {
440
538
  // useEffect-double-fire / retry-storm regression class, where the request DID fire (presence passes)
441
539
  // but fired the WRONG number of times. Without `count`, the matcher is presence-only (≥1).
442
540
  if (p.count !== undefined) {
541
+ if (0 === matches.length) {
542
+ const download = nativeDownloadMiss(events, p);
543
+ if (download !== undefined)
544
+ return download;
545
+ }
443
546
  if (matches.length !== p.count &&
444
547
  0 === matches.length &&
445
548
  targetsUnobservedChannel(p) &&
@@ -451,15 +554,27 @@ export function evalNet(events, p) {
451
554
  assertion: 'net.count',
452
555
  };
453
556
  }
454
- return evalExactCount({
557
+ const counted = evalExactCount({
455
558
  matched: matches.length,
456
559
  want: p.count,
457
560
  noun: 'network call(s)',
458
561
  filter: describeNetFilter(p),
459
562
  assertion: 'net.count',
460
563
  });
564
+ // Same blind head, second door: "saw 0" over a whole-session window is the same claim the
565
+ // presence branch makes, and just as unable to see a startup call. A `count: 0` assertion is
566
+ // left alone on purpose — it PASSES here, and turning that green into a non-pass is a grade
567
+ // change, not a wording one.
568
+ return 0 === matches.length && 0 === since && counted.failureReason !== undefined
569
+ ? { ...counted, failureReason: `${counted.failureReason}${PRE_ATTACH_CAVEAT}` }
570
+ : counted;
461
571
  }
462
572
  const hit = matches[0];
573
+ if (hit === undefined) {
574
+ const download = nativeDownloadMiss(events, p);
575
+ if (download !== undefined)
576
+ return download;
577
+ }
463
578
  if (hit === undefined && targetsUnobservedChannel(p) && !sawSubresources) {
464
579
  const reason = unobservedChannelReason(p);
465
580
  return {
@@ -472,10 +587,10 @@ export function evalNet(events, p) {
472
587
  };
473
588
  }
474
589
  return hit !== undefined
475
- ? { pass: true, evidence: netEvidence(hit.data) }
590
+ ? { pass: true, evidence: netEvidence(hit.data, p) }
476
591
  : {
477
592
  pass: false,
478
- failureReason: `no network call matched ${JSON.stringify(p)}`,
593
+ failureReason: `no network call matched ${JSON.stringify(p)}${0 === since ? PRE_ATTACH_CAVEAT : ''}`,
479
594
  // Same reasoning as the signal miss: "no matching call" cannot be told apart from "the app
480
595
  // made no calls at all", and those need different fixes.
481
596
  observed: observedNetCalls(events, p.urlContains),
@@ -483,103 +598,6 @@ export function evalNet(events, p) {
483
598
  assertion: 'net.present',
484
599
  };
485
600
  }
486
- /** The only console levels Reticle instruments (console.info/debug/trace are NOT patched). */
487
- const CONSOLE_LEVEL_TYPE = {
488
- log: EventType.CONSOLE_LOG,
489
- warn: EventType.CONSOLE_WARN,
490
- error: EventType.CONSOLE_ERROR,
491
- };
492
- export function evalConsole(events, p) {
493
- const since = p.since ?? 0;
494
- // Reticle only instruments console.log/warn/error. A level outside that set is never captured,
495
- // so its events can't exist — and an `absent` assertion on it would verify NOTHING while
496
- // reporting green. Fail loudly instead of false-passing.
497
- if (p.level !== undefined && p.level !== 'error' && CONSOLE_LEVEL_TYPE[p.level] === undefined) {
498
- return {
499
- pass: false,
500
- failureReason: `console level '${p.level}' is not captured — Reticle instruments console.log, console.warn, console.error only`,
501
- observed: `level '${p.level}' is not instrumented, so no event of it can ever exist`,
502
- expected: 'a level Reticle captures: log, warn, or error',
503
- assertion: 'console.uninstrumented-level',
504
- };
505
- }
506
- const matches = events.filter((e) => {
507
- if (e.t < since)
508
- return false;
509
- const isErr = e.type === EventType.CONSOLE_ERROR || e.type === EventType.ERROR_UNCAUGHT;
510
- if (p.level === undefined) {
511
- return (e.type === EventType.CONSOLE_LOG ||
512
- e.type === EventType.CONSOLE_WARN ||
513
- e.type === EventType.CONSOLE_ERROR ||
514
- e.type === EventType.ERROR_UNCAUGHT);
515
- }
516
- if ('error' === p.level)
517
- return isErr;
518
- return e.type === CONSOLE_LEVEL_TYPE[p.level];
519
- });
520
- // A text match narrows the population to entries whose captured message contains the substring.
521
- // With `absent: true` this is the whole point: "THIS message did not appear", not "no messages
522
- // appeared" — the difference between a regression check and a fragile one that any unrelated
523
- // warning anywhere in the app breaks.
524
- const wanted = 'contains' in p ? p.contains : undefined;
525
- // Captured messages are strings (stringifyArgs in the browser observer), but a malformed or
526
- // foreign event must not crash the evaluator: non-strings stringify defensively, and objects
527
- // go through JSON.stringify rather than a default toString that would print '[object Object]'.
528
- const asText = (v) => {
529
- if ('string' === typeof v)
530
- return v;
531
- try {
532
- return JSON.stringify(v) ?? '';
533
- }
534
- catch {
535
- return '';
536
- }
537
- };
538
- const matching = wanted !== undefined
539
- ? matches.filter((e) => asText(e.data['message']).includes(wanted))
540
- : matches;
541
- if (true === p.absent) {
542
- if (wanted !== undefined && 0 === matching.length && matches.length > 0) {
543
- // Other entries exist but none carries the substring: exactly the pass an absence-with-match
544
- // asserts. Name both counts so the caller can tell this from a silent window.
545
- return {
546
- pass: true,
547
- evidence: { absent: true, contains: wanted },
548
- };
549
- }
550
- return 0 === matching.length
551
- ? { pass: true, evidence: { absent: true } }
552
- : {
553
- pass: false,
554
- failureReason: wanted !== undefined
555
- ? `expected no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} but found ${String(matching.length)}`
556
- : `expected no ${p.level ?? 'console'} entries but found ${String(matches.length)}`,
557
- observed: wanted !== undefined
558
- ? `${String(matching.length)} ${p.level ?? 'console'} entr${1 === matching.length ? 'y' : 'ies'} containing ${JSON.stringify(wanted)}`
559
- : `${String(matches.length)} ${p.level ?? 'console'} entr${1 === matches.length ? 'y' : 'ies'}`,
560
- expected: wanted !== undefined
561
- ? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)}`
562
- : `no ${p.level ?? 'console'} entries`,
563
- assertion: wanted !== undefined ? 'console.absent-contains' : 'console.absent',
564
- evidence: matching.map((e) => e.data),
565
- };
566
- }
567
- return matching.length > 0
568
- ? { pass: true, evidence: matching.map((e) => e.data) }
569
- : {
570
- pass: false,
571
- failureReason: wanted !== undefined
572
- ? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} found`
573
- : `no ${p.level ?? 'console'} entries found`,
574
- observed: wanted !== undefined
575
- ? `no ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)} in the window`
576
- : `no ${p.level ?? 'console'} entries in the window`,
577
- expected: wanted !== undefined
578
- ? `at least one ${p.level ?? 'console'} entry containing ${JSON.stringify(wanted)}`
579
- : `at least one ${p.level ?? 'console'} entry`,
580
- assertion: 'console.present',
581
- };
582
- }
583
601
  export function evalAnimation(events, p) {
584
602
  const wantType = true === p.completed ? EventType.ANIM_END : EventType.ANIM_START;
585
603
  const hit = events.find((e) => {
@@ -106,9 +106,42 @@ export function parsePredicate(input) {
106
106
  // back is how the zod array became unreadable in the first place.
107
107
  const issues = parsed.error.issues.slice(0, 3).map(describeIssue).join('; ');
108
108
  throw new Error(`that predicate did not parse (kind "${kind}"): ${issues}. Nothing ran — the predicate was ` +
109
- `not evaluated, so no verdict was produced. ${accepted(kind, parsed.error.issues)} ` +
109
+ `not evaluated, so no verdict was produced. ${accepted(kind, parsed.error.issues)}` +
110
+ `${misplacedCallFields(input)} ` +
110
111
  `A valid ${kind} predicate looks like: ${exampleFor(kind)}`);
111
112
  }
113
+ /**
114
+ * Arguments of the CALL that agents write inside the predicate instead.
115
+ *
116
+ * Reported from the field on `timeout_ms`, nested in `until` on an `act_and_wait` that had already
117
+ * timed out once at the default budget. The rejection said "unknown field", which reads as "there is
118
+ * no such thing" — so the obvious retry is to delete it and silently give up the longer budget that
119
+ * was the point of writing it. It is not an unknown field; it is a real argument one level too deep.
120
+ */
121
+ const CALL_LEVEL_FIELDS = [
122
+ 'timeout_ms',
123
+ 'sessionId',
124
+ 'ref',
125
+ 'action',
126
+ 'args',
127
+ 'target',
128
+ 'intent',
129
+ ];
130
+ /** The clause naming where a misplaced call argument goes, or '' when nothing was misplaced. */
131
+ function misplacedCallFields(input) {
132
+ if ('object' !== typeof input || null === input)
133
+ return '';
134
+ const written = Object.keys(input);
135
+ const misplaced = CALL_LEVEL_FIELDS.filter((field) => written.includes(field));
136
+ if (0 === misplaced.length)
137
+ return '';
138
+ const list = misplaced.join(', ');
139
+ // Named as a MOVE, not as a removal. Deleting the field is the reading of "unknown field" that
140
+ // costs the caller the thing it asked for.
141
+ return (` ${list} ${1 === misplaced.length ? 'is an argument' : 'are arguments'} of the CALL, not of ` +
142
+ `the predicate: ${1 === misplaced.length ? 'move it' : 'move them'} up beside \`until\` rather ` +
143
+ 'than dropping it — nesting is why the predicate did not parse.');
144
+ }
112
145
  /**
113
146
  * What WOULD have worked, in the same breath as what did not.
114
147
  *
@@ -0,0 +1,50 @@
1
+ import { type EvalResult } from './predicate-eval.js';
2
+ import type { PredicateKind } from '@reticlehq/core';
3
+ import type { Predicate } from './predicate-schema.js';
4
+ /** The net predicate as `describeNetFilter` types it. */
5
+ type NetPredicate = Extract<Predicate, {
6
+ kind: typeof PredicateKind.NET;
7
+ }>;
8
+ /** A net predicate, narrowed to the fields this module reads. */
9
+ interface RequestBodyPredicate {
10
+ requestBodyContains?: string;
11
+ requestBodyMatches?: Record<string, unknown>;
12
+ }
13
+ /**
14
+ * What the filtering pass learned about calls that matched everything else.
15
+ *
16
+ * Accumulated during the single pass over events rather than recomputed after it, the way the
17
+ * response-side trackers already are.
18
+ */
19
+ export interface RequestBodyState {
20
+ /** A call matched everything else but carried no recorded request body. */
21
+ unrecorded: boolean;
22
+ /** The request body a clause was checked against and did not match. */
23
+ mismatch?: string;
24
+ /** The prefix of a TRUNCATED request body the clause did not hold over — undecidable, not absent. */
25
+ truncated?: string;
26
+ /**
27
+ * A key whose CAPTURED value is `[REDACTED]`, so the clause cannot be judged at all.
28
+ *
29
+ * Request bodies go through the same redaction path as responses before they are ever recorded,
30
+ * so a clause on a sensitive key is unsatisfiable whatever the app sent. Grading that a mismatch
31
+ * would blame the application for the SDK's own redaction and send someone to fix a correct
32
+ * payload.
33
+ */
34
+ redactedField?: string;
35
+ }
36
+ export declare function newRequestBodyState(): RequestBodyState;
37
+ /**
38
+ * Does this event satisfy the predicate's request-body clauses? Records why not in `state`.
39
+ *
40
+ * Returns true when there is nothing to check, so callers can apply it unconditionally.
41
+ */
42
+ export declare function checkRequestBody(data: Record<string, unknown>, predicate: RequestBodyPredicate, state: RequestBodyState): boolean;
43
+ /**
44
+ * The verdict for a request-body clause nothing satisfied, or `undefined` to fall through.
45
+ *
46
+ * Ranked the way the response side ranks its own: the two nobody could have answered (redacted,
47
+ * unrecorded, truncated) come before the one that WAS answered and came out false.
48
+ */
49
+ export declare function requestBodyVerdict(state: RequestBodyState, predicate: NetPredicate, matchCount: number): EvalResult | undefined;
50
+ export {};
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Asserting on what the UI SENT, not on what came back (#798).
3
+ *
4
+ * `net.bodyContains` searches the response only, and the comment on it says why: searching the
5
+ * request too would let it pass on the very defect it exists to catch (the app sent `1187.01`, so
6
+ * that string is in the request whatever the server then did with it). That reasoning is right, and
7
+ * it leaves a real gap. For a filter, a search box or a form, the thing under test IS the outgoing
8
+ * payload — "applying this filter actually sends it" had no verdict at all, only
9
+ * `reticle_network { bodies: true }` and a person reading `requestBody` by eye.
10
+ *
11
+ * So these are separate fields rather than a mode on `bodyContains`: neither assertion can then be
12
+ * satisfied by the wrong half of the exchange.
13
+ *
14
+ * Extracted from `predicate-eval.ts`, which was at the 1000-line cap.
15
+ */
16
+ import { REDACTED_VALUE } from '@reticlehq/core';
17
+ import { clipBody, dataMatches, describeNetFilter, str, } from './predicate-eval.js';
18
+ export function newRequestBodyState() {
19
+ return { unrecorded: false };
20
+ }
21
+ /**
22
+ * Does this event satisfy the predicate's request-body clauses? Records why not in `state`.
23
+ *
24
+ * Returns true when there is nothing to check, so callers can apply it unconditionally.
25
+ */
26
+ export function checkRequestBody(data, predicate, state) {
27
+ const { requestBodyContains, requestBodyMatches } = predicate;
28
+ if (undefined === requestBodyContains && undefined === requestBodyMatches)
29
+ return true;
30
+ const sent = str(data['requestBody']);
31
+ if (sent === undefined) {
32
+ state.unrecorded = true;
33
+ return false;
34
+ }
35
+ const wasTruncated = true === data['requestBodyTruncated'];
36
+ const note = () => {
37
+ // The same rule the response side keeps: a needle missing from a body we hold only the first N
38
+ // bytes of is undecidable, not absent.
39
+ if (wasTruncated)
40
+ state.truncated ??= sent;
41
+ else
42
+ state.mismatch ??= sent;
43
+ return false;
44
+ };
45
+ if (requestBodyContains !== undefined && !sent.includes(requestBodyContains))
46
+ return note();
47
+ if (requestBodyMatches === undefined)
48
+ return true;
49
+ let payload;
50
+ try {
51
+ payload = JSON.parse(sent);
52
+ }
53
+ catch {
54
+ // Not JSON (a form post, plain text). A shallow key match cannot be applied, and guessing at
55
+ // form encoding here would answer a different question than the one asked. Use
56
+ // `requestBodyContains` for those.
57
+ return note();
58
+ }
59
+ if (null === payload || typeof payload !== 'object' || Array.isArray(payload))
60
+ return note();
61
+ const actual = payload;
62
+ const redacted = Object.keys(requestBodyMatches).find((key) => REDACTED_VALUE === actual[key]);
63
+ if (redacted !== undefined) {
64
+ state.redactedField ??= redacted;
65
+ return false;
66
+ }
67
+ return dataMatches(actual, requestBodyMatches) ? true : note();
68
+ }
69
+ /**
70
+ * The verdict for a request-body clause nothing satisfied, or `undefined` to fall through.
71
+ *
72
+ * Ranked the way the response side ranks its own: the two nobody could have answered (redacted,
73
+ * unrecorded, truncated) come before the one that WAS answered and came out false.
74
+ */
75
+ export function requestBodyVerdict(state, predicate, matchCount) {
76
+ if (matchCount > 0)
77
+ return undefined;
78
+ const wanted = JSON.stringify(predicate.requestBodyContains ?? predicate.requestBodyMatches);
79
+ if (state.redactedField !== undefined) {
80
+ const field = JSON.stringify(state.redactedField);
81
+ return {
82
+ pass: false,
83
+ inconclusive: `a call matching ${describeNetFilter(predicate)} was made, but its ${field} was REDACTED before the body was recorded, so this clause cannot be judged — a redacted field is unknown, not different. Assert on a non-sensitive key, or on the effect the value had`,
84
+ observed: `a matching request whose ${field} is ${REDACTED_VALUE}`,
85
+ expected: `a request body matching ${wanted}`,
86
+ assertion: 'net.requestBodyMatches',
87
+ };
88
+ }
89
+ if (state.unrecorded) {
90
+ return {
91
+ pass: false,
92
+ failureReason: `a call matched but its REQUEST body was not recorded, so the request-body clause could not be checked — enable it where the app calls connect(): reticle({ captureNetworkBodies: true })`,
93
+ observed: 'a matching call with no recorded request body',
94
+ expected: `a request body matching ${wanted}`,
95
+ assertion: 'net.requestBody',
96
+ };
97
+ }
98
+ if (state.truncated !== undefined) {
99
+ return {
100
+ pass: false,
101
+ inconclusive: `a call matching ${describeNetFilter(predicate)} was sent with a request body that was TRUNCATED before it was recorded, and the clause did not hold over the part that was kept — so this is undecidable, not a failure`,
102
+ observed: `the first ${String(state.truncated.length)} characters of a truncated request body ${JSON.stringify(clipBody(state.truncated))}`,
103
+ expected: `a request body matching ${wanted}`,
104
+ assertion: 'net.requestBody',
105
+ };
106
+ }
107
+ if (state.mismatch !== undefined) {
108
+ // Named separately from "no call matched", for the reason the response side already learned: the
109
+ // request DID fire, and reporting zero matches sends the caller to check the url and the method,
110
+ // which are both fine, instead of to the value the UI actually sent.
111
+ return {
112
+ pass: false,
113
+ failureReason: `a call matching ${describeNetFilter(predicate)} was sent with ${JSON.stringify(clipBody(state.mismatch))}, which does not match the request-body clause — the request fired, the payload is what differed`,
114
+ observed: `request body ${JSON.stringify(clipBody(state.mismatch))}`,
115
+ expected: `a request body matching ${wanted}`,
116
+ assertion: 'net.requestBody',
117
+ };
118
+ }
119
+ return undefined;
120
+ }
@@ -31,6 +31,24 @@ export declare const RouteDecidedBy: {
31
31
  * about where the app is, which is the failure this predicate exists to prevent.
32
32
  */
33
33
  export declare function routePathOf(pathname: string, hash: string): string;
34
+ /**
35
+ * The resolved parts of a route, from a `ROUTE_CHANGE` event or a session URL.
36
+ *
37
+ * `routePath` is where the ROUTER is (the fragment path under a hash router). `docPath` + `hash`
38
+ * is the NAVIGABLE value a `reticle_navigate` can be pointed at. Call sites pick; collapsing
39
+ * them into one string is how a hash-router miss lands as "every page is `/`".
40
+ */
41
+ export interface RouteParts {
42
+ routePath: string;
43
+ docPath: string;
44
+ hash: string;
45
+ search: string;
46
+ full: string;
47
+ }
48
+ /** A `ROUTE_CHANGE` event, or its `data` — one reading, so the sixth copy cannot drift. */
49
+ export declare function routeOfEvent(eventOrData: ReticleEvent | Record<string, unknown>): RouteParts | undefined;
50
+ /** Same shape from a session URL (the fallback when the tab hard-loaded and emitted no route event). */
51
+ export declare function routeOfUrl(url: string): RouteParts | undefined;
34
52
  export declare function evalRoute(events: ReticleEvent[], p: Extract<Predicate, {
35
53
  kind: typeof PredicateKind.ROUTE;
36
54
  }>,
@@ -43,4 +61,6 @@ export declare function evalRoute(events: ReticleEvent[], p: Extract<Predicate,
43
61
  * `reticle_snapshot({ mode: 'status' }).route` one call later. Optional so a caller with no session
44
62
  * (a replayed window) keeps the old answer instead of inventing a route.
45
63
  */
46
- currentUrl?: string): EvalResult;
64
+ currentUrl?: string,
65
+ /** Event-time floor for the window, so a request already in flight before it is not counted. */
66
+ since?: number): EvalResult;