@reticlehq/server 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +63 -13
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +1 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +9 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +2 -11
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +113 -60
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +174 -92
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.js +24 -9
  88. package/dist/flows/empty-flow.d.ts +6 -0
  89. package/dist/flows/empty-flow.js +24 -0
  90. package/dist/flows/flake-store.d.ts +0 -1
  91. package/dist/flows/flake-store.js +1 -1
  92. package/dist/flows/flake.d.ts +2 -1
  93. package/dist/flows/flake.js +2 -2
  94. package/dist/flows/flow-classify.js +12 -0
  95. package/dist/flows/flow-replay-run.js +4 -4
  96. package/dist/flows/flow-replay.d.ts +5 -7
  97. package/dist/flows/flow-replay.js +114 -47
  98. package/dist/flows/flow-report.js +1 -1
  99. package/dist/flows/flow-scope.d.ts +1 -6
  100. package/dist/flows/flow-step-runners.d.ts +26 -6
  101. package/dist/flows/flow-step-runners.js +157 -6
  102. package/dist/flows/flow-success.js +20 -15
  103. package/dist/flows/flow-tools.js +53 -7
  104. package/dist/flows/flows.js +15 -11
  105. package/dist/flows/gate.d.ts +4 -3
  106. package/dist/flows/gate.js +1 -1
  107. package/dist/flows/heal-run.d.ts +0 -8
  108. package/dist/flows/heal-run.js +5 -5
  109. package/dist/flows/heal.js +3 -3
  110. package/dist/flows/predicate-to-expect.d.ts +38 -0
  111. package/dist/flows/predicate-to-expect.js +95 -0
  112. package/dist/flows/replay.d.ts +16 -1
  113. package/dist/flows/replay.js +58 -22
  114. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  115. package/dist/flows/role-anchor-nearest.js +45 -0
  116. package/dist/flows/server-verify.d.ts +1 -1
  117. package/dist/flows/server-verify.js +5 -5
  118. package/dist/flows/verify-change-tools.js +32 -10
  119. package/dist/flows/verify-change-verdict.d.ts +15 -0
  120. package/dist/flows/verify-change-verdict.js +25 -0
  121. package/dist/flows/watch-batcher.d.ts +3 -2
  122. package/dist/honesty/already-true.d.ts +2 -0
  123. package/dist/honesty/already-true.js +31 -0
  124. package/dist/honesty/blind-spots.d.ts +16 -3
  125. package/dist/honesty/blind-spots.js +24 -12
  126. package/dist/honesty/honesty.d.ts +3 -2
  127. package/dist/honesty/honesty.js +4 -4
  128. package/dist/honesty/uncaptured-bodies.js +1 -1
  129. package/dist/honesty/unread-outcome.js +2 -2
  130. package/dist/honesty/verified.d.ts +22 -3
  131. package/dist/honesty/verified.js +48 -8
  132. package/dist/http-server.js +14 -7
  133. package/dist/hunt/hunt-report.d.ts +2 -1
  134. package/dist/hunt/hunt-report.js +2 -2
  135. package/dist/index.d.ts +8 -6
  136. package/dist/index.js +32 -25
  137. package/dist/init/agent-rules.d.ts +2 -1
  138. package/dist/init/agent-rules.js +45 -7
  139. package/dist/init/app-choice.d.ts +20 -0
  140. package/dist/init/app-choice.js +25 -0
  141. package/dist/init/astro-host.d.ts +21 -0
  142. package/dist/init/astro-host.js +27 -0
  143. package/dist/init/astro-patch.d.ts +16 -0
  144. package/dist/init/astro-patch.js +240 -0
  145. package/dist/init/capabilities.js +4 -1
  146. package/dist/init/cra.d.ts +37 -0
  147. package/dist/init/cra.js +90 -0
  148. package/dist/init/cursor.js +39 -4
  149. package/dist/init/desktop-doctor.d.ts +3 -2
  150. package/dist/init/desktop-doctor.js +4 -4
  151. package/dist/init/detect.d.ts +3 -2
  152. package/dist/init/detect.js +16 -5
  153. package/dist/init/mcp-registered.d.ts +12 -0
  154. package/dist/init/mcp-registered.js +14 -0
  155. package/dist/init/mcp.d.ts +27 -2
  156. package/dist/init/mcp.js +40 -4
  157. package/dist/init/next-patch.d.ts +2 -4
  158. package/dist/init/next-patch.js +109 -19
  159. package/dist/init/node-io.js +23 -6
  160. package/dist/init/plan-framework.d.ts +16 -1
  161. package/dist/init/plan-framework.js +132 -8
  162. package/dist/init/plan.d.ts +73 -8
  163. package/dist/init/plan.js +133 -34
  164. package/dist/init/project-id.d.ts +5 -9
  165. package/dist/init/project-id.js +10 -22
  166. package/dist/init/run.d.ts +5 -0
  167. package/dist/init/run.js +210 -36
  168. package/dist/init/slash-command.d.ts +9 -0
  169. package/dist/init/slash-command.js +10 -1
  170. package/dist/init/snippets.d.ts +7 -10
  171. package/dist/init/snippets.js +44 -15
  172. package/dist/init/vite-config.js +1 -1
  173. package/dist/init/workspace-apps.d.ts +23 -0
  174. package/dist/init/workspace-apps.js +68 -0
  175. package/dist/input/driven-redaction.js +1 -1
  176. package/dist/input/network-detail.js +8 -8
  177. package/dist/input/network-mock-tools.js +2 -2
  178. package/dist/input/network-mock.js +4 -4
  179. package/dist/input/real-input.js +8 -3
  180. package/dist/input/scroll-find.js +2 -2
  181. package/dist/input/scroll-tools.d.ts +0 -5
  182. package/dist/input/scroll-tools.js +11 -4
  183. package/dist/input/viewport-tools.js +2 -2
  184. package/dist/journal/ambient-store.d.ts +0 -2
  185. package/dist/journal/ambient-store.js +1 -1
  186. package/dist/journal/ambient.js +1 -1
  187. package/dist/journal/attach-journal.d.ts +2 -1
  188. package/dist/journal/deviation-report.js +6 -6
  189. package/dist/journal/deviation-service.js +1 -1
  190. package/dist/journal/envelope-store.d.ts +0 -2
  191. package/dist/journal/envelope-store.js +1 -1
  192. package/dist/journal/envelope.d.ts +2 -3
  193. package/dist/journal/envelope.js +3 -3
  194. package/dist/journal/journal-query.js +1 -1
  195. package/dist/journal/journal-recorder.d.ts +2 -1
  196. package/dist/journal/journal-recorder.js +2 -2
  197. package/dist/journal/rollups.js +9 -9
  198. package/dist/journal/session-end.d.ts +2 -1
  199. package/dist/journal/session-journal.js +4 -4
  200. package/dist/license/license.d.ts +0 -14
  201. package/dist/license/license.js +8 -8
  202. package/dist/log.d.ts +10 -0
  203. package/dist/log.js +11 -1
  204. package/dist/mcp/mcp-is-error.d.ts +13 -0
  205. package/dist/mcp/mcp-is-error.js +18 -0
  206. package/dist/mcp/mcp-outage.d.ts +31 -0
  207. package/dist/mcp/mcp-outage.js +48 -0
  208. package/dist/mcp/mcp-proxy.d.ts +124 -0
  209. package/dist/mcp/mcp-proxy.js +697 -0
  210. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  211. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  212. package/dist/mcp/proxy-handshake.d.ts +3 -0
  213. package/dist/mcp/proxy-handshake.js +54 -0
  214. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  215. package/dist/mcp/proxy-lifecycle.js +70 -0
  216. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  217. package/dist/mcp/tool-catalog-cache.js +55 -0
  218. package/dist/oracles/propose-consequences.d.ts +2 -1
  219. package/dist/oracles/propose-consequences.js +16 -11
  220. package/dist/oracles/self-instrument.d.ts +2 -1
  221. package/dist/oracles/self-instrument.js +1 -1
  222. package/dist/phenomena/phenomena.js +3 -3
  223. package/dist/platform.d.ts +16 -0
  224. package/dist/platform.js +16 -0
  225. package/dist/pool/playwright-launcher.d.ts +15 -2
  226. package/dist/pool/playwright-launcher.js +21 -2
  227. package/dist/project/fs-port.js +1 -1
  228. package/dist/project/project-tools.js +2 -2
  229. package/dist/runs/build-verification-run.js +1 -1
  230. package/dist/runs/repair-prompt.d.ts +0 -6
  231. package/dist/runs/repair-prompt.js +3 -3
  232. package/dist/runs/risk-classify.js +1 -1
  233. package/dist/runs/run-diff.d.ts +0 -2
  234. package/dist/runs/run-diff.js +3 -3
  235. package/dist/runs/run-tools.js +2 -2
  236. package/dist/runs/verification-sync.js +2 -2
  237. package/dist/runs/verify-http.js +1 -1
  238. package/dist/runs/verify-server.js +1 -1
  239. package/dist/session/command-timeout.d.ts +11 -3
  240. package/dist/session/command-timeout.js +2 -2
  241. package/dist/session/control-envelope.d.ts +6 -0
  242. package/dist/session/control-envelope.js +7 -1
  243. package/dist/session/dev-server-probe.d.ts +13 -0
  244. package/dist/session/dev-server-probe.js +52 -0
  245. package/dist/session/last-act.d.ts +17 -3
  246. package/dist/session/last-act.js +16 -4
  247. package/dist/session/live-control-tools.js +7 -2
  248. package/dist/session/live-control.js +1 -1
  249. package/dist/session/looks-like-dev-server.d.ts +14 -0
  250. package/dist/session/looks-like-dev-server.js +25 -0
  251. package/dist/session/no-session-diagnosis.d.ts +38 -0
  252. package/dist/session/no-session-diagnosis.js +70 -0
  253. package/dist/session/no-session-watch.d.ts +19 -0
  254. package/dist/session/no-session-watch.js +61 -0
  255. package/dist/session/observed-state.d.ts +4 -0
  256. package/dist/session/observed-state.js +19 -1
  257. package/dist/session/output-budget.js +1 -1
  258. package/dist/session/session-health.d.ts +8 -1
  259. package/dist/session/session-health.js +8 -7
  260. package/dist/session/session-info.d.ts +26 -7
  261. package/dist/session/session-info.js +42 -1
  262. package/dist/session/session-lease.d.ts +0 -11
  263. package/dist/session/session-lease.js +1 -1
  264. package/dist/session/session-manager.d.ts +3 -0
  265. package/dist/session/session-manager.js +52 -9
  266. package/dist/session/session-readiness.d.ts +1 -1
  267. package/dist/session/session-readiness.js +6 -1
  268. package/dist/session/session-reaper.js +1 -1
  269. package/dist/session/session-reconnect.d.ts +32 -0
  270. package/dist/session/session-reconnect.js +42 -0
  271. package/dist/session/session.d.ts +14 -2
  272. package/dist/session/session.js +69 -26
  273. package/dist/session/state-select.d.ts +1 -1
  274. package/dist/session/state-select.js +1 -1
  275. package/dist/status-payload.d.ts +19 -0
  276. package/dist/status-payload.js +11 -0
  277. package/dist/telemetry/argument-shape.js +10 -1
  278. package/dist/telemetry/browser-mode.d.ts +27 -0
  279. package/dist/telemetry/browser-mode.js +33 -0
  280. package/dist/telemetry/bug-found.d.ts +17 -21
  281. package/dist/telemetry/bug-found.js +120 -8
  282. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  283. package/dist/telemetry/cli-telemetry.js +27 -4
  284. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  285. package/dist/telemetry/daemon-telemetry.js +12 -2
  286. package/dist/telemetry/dev-repo.js +4 -4
  287. package/dist/telemetry/error-fingerprint.js +21 -2
  288. package/dist/telemetry/feedback-cli.d.ts +1 -1
  289. package/dist/telemetry/feedback-cli.js +4 -4
  290. package/dist/telemetry/feedback-context.d.ts +22 -0
  291. package/dist/telemetry/feedback-context.js +41 -1
  292. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  293. package/dist/telemetry/feedback-delivery.js +30 -0
  294. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  295. package/dist/telemetry/feedback-outbox.js +87 -0
  296. package/dist/telemetry/feedback.d.ts +22 -1
  297. package/dist/telemetry/feedback.js +67 -8
  298. package/dist/telemetry/git-facts.js +1 -1
  299. package/dist/telemetry/session-metrics.d.ts +2 -0
  300. package/dist/telemetry/session-metrics.js +68 -6
  301. package/dist/telemetry/telemetry.d.ts +27 -2
  302. package/dist/telemetry/telemetry.js +119 -29
  303. package/dist/telemetry/verification-of.d.ts +22 -0
  304. package/dist/telemetry/verification-of.js +84 -0
  305. package/dist/tools/act-capsule.js +2 -2
  306. package/dist/tools/act-danger.d.ts +12 -0
  307. package/dist/tools/act-danger.js +20 -3
  308. package/dist/tools/act-helpers.js +4 -4
  309. package/dist/tools/act-tools.d.ts +0 -8
  310. package/dist/tools/act-tools.js +84 -115
  311. package/dist/tools/act-view.js +7 -7
  312. package/dist/tools/alias-args.d.ts +17 -0
  313. package/dist/tools/alias-args.js +25 -0
  314. package/dist/tools/assert-grade.js +9 -9
  315. package/dist/tools/assert-verdict.d.ts +3 -1
  316. package/dist/tools/assert-verdict.js +8 -5
  317. package/dist/tools/browser-tools.js +30 -11
  318. package/dist/tools/contract-tools.js +1 -1
  319. package/dist/tools/coverage-identity.d.ts +34 -0
  320. package/dist/tools/coverage-identity.js +46 -0
  321. package/dist/tools/coverage-tools.js +10 -14
  322. package/dist/tools/dynamic-tools.d.ts +2 -1
  323. package/dist/tools/dynamic-tools.js +90 -18
  324. package/dist/tools/error-recovery.d.ts +20 -2
  325. package/dist/tools/error-recovery.js +179 -4
  326. package/dist/tools/feedback-tools.d.ts +12 -1
  327. package/dist/tools/feedback-tools.js +28 -5
  328. package/dist/tools/invoke-tool.d.ts +0 -6
  329. package/dist/tools/invoke-tool.js +57 -32
  330. package/dist/tools/lease-hint.d.ts +17 -0
  331. package/dist/tools/lease-hint.js +24 -0
  332. package/dist/tools/lease-tools.js +6 -5
  333. package/dist/tools/merge-tools.d.ts +10 -1
  334. package/dist/tools/merge-tools.js +9 -3
  335. package/dist/tools/merged-name-redirect.d.ts +13 -0
  336. package/dist/tools/merged-name-redirect.js +55 -0
  337. package/dist/tools/navigate-result.d.ts +17 -0
  338. package/dist/tools/navigate-result.js +31 -0
  339. package/dist/tools/observe-tools.js +54 -20
  340. package/dist/tools/observed-nothing.d.ts +25 -0
  341. package/dist/tools/observed-nothing.js +33 -0
  342. package/dist/tools/query-paginate.js +2 -2
  343. package/dist/tools/query-shape.d.ts +18 -0
  344. package/dist/tools/query-shape.js +39 -0
  345. package/dist/tools/read-tools.js +81 -20
  346. package/dist/tools/real-input-attempt.d.ts +28 -0
  347. package/dist/tools/real-input-attempt.js +88 -0
  348. package/dist/tools/reconcile-tools.js +7 -7
  349. package/dist/tools/reload-result.d.ts +14 -0
  350. package/dist/tools/reload-result.js +29 -0
  351. package/dist/tools/snapshot-delta.d.ts +4 -5
  352. package/dist/tools/snapshot-delta.js +0 -0
  353. package/dist/tools/tool-kit.d.ts +8 -0
  354. package/dist/tools/tool-surface.d.ts +83 -0
  355. package/dist/tools/tool-surface.js +204 -0
  356. package/dist/tools/tools-helpers.js +6 -6
  357. package/dist/tools/tools.d.ts +15 -0
  358. package/dist/tools/tools.js +53 -17
  359. package/dist/tools/unadvertised-help.d.ts +5 -0
  360. package/dist/tools/unadvertised-help.js +39 -0
  361. package/dist/trace.d.ts +69 -0
  362. package/dist/trace.js +150 -0
  363. package/dist/update/nudge-credit.d.ts +4 -0
  364. package/dist/update/nudge-credit.js +53 -0
  365. package/dist/update/sdk-sync.d.ts +21 -0
  366. package/dist/update/sdk-sync.js +42 -0
  367. package/dist/update/update-checker.js +3 -3
  368. package/dist/update/update-nudge.d.ts +63 -6
  369. package/dist/update/update-nudge.js +128 -15
  370. package/dist/update/updater.d.ts +1 -9
  371. package/dist/update/updater.js +13 -8
  372. package/dist/version/peer-announce.d.ts +4 -0
  373. package/dist/version/peer-announce.js +30 -0
  374. package/dist/{server-version.js → version/server-version.js} +5 -1
  375. package/dist/version/version-nudge.d.ts +28 -0
  376. package/dist/version/version-nudge.js +40 -0
  377. package/dist/version/version-skew.d.ts +66 -0
  378. package/dist/version/version-skew.js +71 -0
  379. package/dist/visual/visual-diff.js +2 -2
  380. package/dist/visual/visual-tools.js +4 -4
  381. package/docs/agent-cheatsheet.md +85 -0
  382. package/docs/architecture.md +113 -0
  383. package/docs/benchmarks.md +255 -0
  384. package/docs/debugging.md +102 -0
  385. package/docs/deploy-checks.md +69 -0
  386. package/docs/desktop-apps.md +256 -0
  387. package/docs/enterprise.md +62 -0
  388. package/docs/fixtures.md +28 -0
  389. package/docs/flows.md +166 -0
  390. package/docs/getting-started.md +469 -0
  391. package/docs/human-control.md +63 -0
  392. package/docs/integration-patterns.md +171 -0
  393. package/docs/local-registry.md +126 -0
  394. package/docs/mint.json +61 -0
  395. package/docs/multi-agent-testing.md +55 -0
  396. package/docs/platform-integration.md +122 -0
  397. package/docs/telemetry-contract.md +214 -0
  398. package/docs/telemetry.md +184 -0
  399. package/docs/testing.md +82 -0
  400. package/docs/token-efficiency.md +80 -0
  401. package/docs/usage.md +1046 -0
  402. package/package.json +8 -5
  403. package/dist/cli-launch.js +0 -112
  404. package/dist/daemon-resilience.d.ts +0 -22
  405. package/dist/daemon-resilience.js +0 -85
  406. package/dist/mcp-proxy.d.ts +0 -67
  407. package/dist/mcp-proxy.js +0 -337
  408. package/dist/tools/profiles.d.ts +0 -37
  409. package/dist/tools/profiles.js +0 -139
  410. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  411. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  412. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  413. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  414. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  415. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  416. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Block until a reloaded page has re-announced itself under the same session id.
3
+ *
4
+ * A reload tears the SDK down. The id survives (see session-continuity), and the daemon replaces the
5
+ * old Session with a new one when the page says HELLO again — so "is it back?" is exactly "does a
6
+ * DIFFERENT object hold this id now?". Identity, not a flag: the returning page always constructs a
7
+ * new Session, and nothing else can claim an id it already owns.
8
+ *
9
+ * Polls a live getter rather than subscribing, because there is no reconnect event to subscribe to
10
+ * and inventing one for this would be a wire change for a two-second wait.
11
+ */
12
+ /**
13
+ * How often to look for the reconnected session.
14
+ *
15
+ * 25ms, not 100. What is being polled is an in-memory map lookup — no I/O, no browser round-trip —
16
+ * while the thing being waited for is an EVENT (the reloaded page's HELLO). At 100ms the agent paid
17
+ * up to a full interval of dead time after the page was already back, on every navigate-with-reload.
18
+ * Measured across the fixture fleet, `reticle_navigate` clustered at 105 / 202-206 / 308 / 407ms —
19
+ * the poll grid, visible in the data. Four times the checks of something this cheap costs nothing
20
+ * anyone can measure.
21
+ */
22
+ const DEFAULT_POLL_MS = 25;
23
+ /**
24
+ * How long a reload is worth waiting for before answering "not confirmed".
25
+ *
26
+ * A production build is back in well under a second; a cold dev-server route rebuild is the slow
27
+ * case this budget is sized for. It is a CEILING, not a delay — the common path returns on the first
28
+ * or second poll.
29
+ */
30
+ export const RELOAD_RECONNECT_TIMEOUT_MS = 5000;
31
+ export async function waitForReconnect(opts) {
32
+ const poll = opts.pollMs ?? DEFAULT_POLL_MS;
33
+ const start = opts.now();
34
+ for (;;) {
35
+ const live = opts.current();
36
+ if (live !== undefined && live !== opts.previous)
37
+ return true;
38
+ if (opts.now() - start >= opts.timeoutMs)
39
+ return false;
40
+ await opts.sleep(poll);
41
+ }
42
+ }
@@ -3,7 +3,7 @@ import { LastAct } from './last-act.js';
3
3
  import { type PageRuntime } from './command-timeout.js';
4
4
  import { type SessionHealth } from './session-health.js';
5
5
  export type { SessionHealth };
6
- import { PresenterTone, SessionState, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
6
+ import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
7
7
  import type { JournalReader, JournalRecorder } from '../journal/journal-recorder.js';
8
8
  import { type EventQueryOptions } from '../journal/journal-query.js';
9
9
  import { type AmbientCounts } from '../journal/ambient.js';
@@ -27,6 +27,8 @@ export declare class Session {
27
27
  title: string;
28
28
  adapters: string[];
29
29
  hasCapabilities: boolean;
30
+ /** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
31
+ versionSkew?: string;
30
32
  /**
31
33
  * Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
32
34
  * DRIVEN path can redact them too — a request body the daemon captures from the network stack
@@ -46,10 +48,12 @@ export declare class Session {
46
48
  * `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
47
49
  * the desktop-specific timeout diagnosis rather than breaking.
48
50
  */
49
- applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string): void;
51
+ applyHealth(hidden: boolean, focused: boolean, runtime?: string, engine?: string, brand?: BrowserBrand): void;
50
52
  /** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
51
53
  get runtime(): PageRuntime | undefined;
52
54
  get engine(): string | undefined;
55
+ /** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
56
+ get brand(): BrowserBrand | undefined;
53
57
  /** Throttled if the tab is hidden OR we have not heard from it recently. */
54
58
  throttled(): boolean;
55
59
  /** The attachable health block — single source of truth for the tools. */
@@ -60,6 +64,14 @@ export declare class Session {
60
64
  /** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
61
65
  pushEvent(event: ReticleEvent, byteSize?: number): void;
62
66
  recordActedRef(ref: string): void;
67
+ actedLabels(): ReadonlySet<string>;
68
+ /**
69
+ * Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
70
+ *
71
+ * Same shape the interactive snapshot emits, so coverage can match a control the agent drove
72
+ * against the same control after a re-render gave it a different ref.
73
+ */
74
+ private recordActedLabelFrom;
63
75
  actedRefs(): ReadonlySet<string>;
64
76
  noteRateLimited(dropped: number): void;
65
77
  blindSpots(): Readonly<Record<string, number>>;
@@ -2,7 +2,8 @@ import { LastAct } from './last-act.js';
2
2
  import { commandTimeoutMessage } from './command-timeout.js';
3
3
  import { readHealthEvent } from './session-health.js';
4
4
  import { PendingCommands } from './pending-commands.js';
5
- import { EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
5
+ import { span } from '../trace.js';
6
+ import { AppRuntime, EventType, HumanControlDataSchema, HumanControlKind, HumanMarkDataSchema, ReticleCommand, MessageKind, parseEventPayload, PresenterTone, SESSION_HEALTH, SESSION_LEASE, SESSION_LIFECYCLE, SessionState, } from '@reticlehq/core';
6
7
  import { RingBuffer } from '../events/ring-buffer.js';
7
8
  import { filterEvents, mergeEventsBySeq, } from '../journal/journal-query.js';
8
9
  import {} from '../journal/ambient.js';
@@ -13,6 +14,7 @@ import { ReviewStore } from './review-store.js';
13
14
  import { buildSessionRecommendation } from './session-recommendation.js';
14
15
  import { buildPresenterArgs } from './presenter-args.js';
15
16
  import { buildSessionLease } from './session-lease.js';
17
+ import { buildSessionInfo } from './session-info.js';
16
18
  const DEFAULT_COMMAND_TIMEOUT_MS = 8000;
17
19
  /** Prefix on correlated command ids (c1, c2, …) — distinguishes them from mark ids. */
18
20
  const COMMAND_ID_PREFIX = 'c';
@@ -32,6 +34,8 @@ export class Session {
32
34
  title;
33
35
  adapters;
34
36
  hasCapabilities;
37
+ /** Set when the page's SDK version differs from the daemon's (see version-skew.ts). */
38
+ versionSkew;
35
39
  /**
36
40
  * Extra key names this app declared sensitive via `connect({ redact: { keys } })`. Held so the
37
41
  * DRIVEN path can redact them too — a request body the daemon captures from the network stack
@@ -50,6 +54,8 @@ export class Session {
50
54
  /** Which shell the page reported (PAGE_HEALTH). Undefined until the first report lands. */
51
55
  #runtime;
52
56
  #engine;
57
+ /** Which browser the page said it is (chrome/edge/arc/…). Undefined on an SDK too old to report one. */
58
+ #brand;
53
59
  #focused = true;
54
60
  /** Liveness: wall-clock of the last AGENT command (distinct from browser chatter / lastSeen). */
55
61
  #lastAgentActivityAt;
@@ -101,14 +107,18 @@ export class Session {
101
107
  * `runtime` is optional so an older SDK (which does not report it) still works — it simply loses
102
108
  * the desktop-specific timeout diagnosis rather than breaking.
103
109
  */
104
- applyHealth(hidden, focused, runtime, engine) {
110
+ applyHealth(hidden, focused, runtime, engine, brand) {
105
111
  this.#hidden = hidden;
106
112
  this.#focused = focused;
107
- if (runtime === 'electron' || runtime === 'tauri' || runtime === 'web') {
113
+ if (AppRuntime.ELECTRON === runtime ||
114
+ AppRuntime.TAURI === runtime ||
115
+ AppRuntime.WEB === runtime) {
108
116
  this.#runtime = runtime;
109
117
  }
110
118
  if (engine !== undefined)
111
119
  this.#engine = engine;
120
+ if (brand !== undefined)
121
+ this.#brand = brand;
112
122
  }
113
123
  /** The shell (web/electron/tauri) and rendering engine the SDK reported. Undefined on an older SDK. */
114
124
  get runtime() {
@@ -117,6 +127,10 @@ export class Session {
117
127
  get engine() {
118
128
  return this.#engine;
119
129
  }
130
+ /** The browser brand the SDK reported. Absent (never guessed) when the page did not say. */
131
+ get brand() {
132
+ return this.#brand;
133
+ }
120
134
  /** Throttled if the tab is hidden OR we have not heard from it recently. */
121
135
  throttled() {
122
136
  return this.#hidden || this.lastSeenMs() > SESSION_HEALTH.STALE_THRESHOLD_MS;
@@ -137,29 +151,19 @@ export class Session {
137
151
  return recommendation === undefined ? base : { ...base, recommendation };
138
152
  }
139
153
  info() {
140
- const base = {
141
- sessionId: this.id,
154
+ return buildSessionInfo({
155
+ id: this.id,
142
156
  url: this.url,
143
- ...(this.projectId === undefined ? {} : { projectId: this.projectId }),
157
+ projectId: this.projectId,
144
158
  title: this.title,
145
159
  adapters: this.adapters,
146
160
  hasCapabilities: this.hasCapabilities,
161
+ versionSkew: this.versionSkew,
147
162
  hidden: this.#hidden,
148
- ...this.health(),
149
- };
150
- if (this.staleMs() > SESSION_LEASE.STALE_AFTER_MS) {
151
- base.stale = true;
152
- base.cleanup_suggestion =
153
- 'Call reticle_session{action:"end"} to free this session before starting new work.';
154
- }
155
- // Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
156
- const marks = this.#review.pendingCount();
157
- if (marks > 0) {
158
- base.pendingMarks = marks;
159
- const s = marks === 1 ? '' : 's';
160
- base.review_suggestion = `The human flagged ${String(marks)} issue${s} on this tab — call reticle_session{action:"review"} to see and fix ${marks === 1 ? 'it' : 'them'}.`;
161
- }
162
- return base;
163
+ health: () => this.health(),
164
+ staleMs: () => this.staleMs(),
165
+ pendingMarkCount: () => this.#review.pendingCount(),
166
+ });
163
167
  }
164
168
  /** Wall-clock age of the session in milliseconds. */
165
169
  staleMs() {
@@ -169,7 +173,7 @@ export class Session {
169
173
  pushEvent(event, byteSize) {
170
174
  if (event.type === EventType.PAGE_HEALTH) {
171
175
  const r = readHealthEvent(event.data);
172
- this.applyHealth(r.hidden ?? this.#hidden, r.focused ?? this.#focused, r.runtime, r.engine);
176
+ this.applyHealth(r.hidden ?? this.#hidden, r.focused ?? this.#focused, r.runtime, r.engine, r.brand);
173
177
  }
174
178
  // The in-page half of Reticle reporting its own failure — recorded like a tool error
175
179
  // (fingerprinted, variables stripped) so a broken observer is as visible as a broken tool.
@@ -195,7 +199,7 @@ export class Session {
195
199
  // pushState/replaceState/popstate; without this the URL stays frozen at the hello value, and
196
200
  // URL-based CDP correlation (real input) silently breaks after the first client-side nav.
197
201
  const to = event.data['to'];
198
- if (typeof to === 'string' && to.length > 0)
202
+ if ('string' === typeof to && to.length > 0)
199
203
  this.url = to;
200
204
  }
201
205
  const t = this.elapsed();
@@ -218,6 +222,35 @@ export class Session {
218
222
  recordActedRef(ref) {
219
223
  this.#observed.recordActedRef(ref);
220
224
  }
225
+ actedLabels() {
226
+ return this.#observed.actedLabels();
227
+ }
228
+ /**
229
+ * Derive the snapshot-shaped label (`button "Deploy"`) from an act reply and remember it.
230
+ *
231
+ * Same shape the interactive snapshot emits, so coverage can match a control the agent drove
232
+ * against the same control after a re-render gave it a different ref.
233
+ */
234
+ recordActedLabelFrom(result) {
235
+ const payload = result.result;
236
+ if (typeof payload !== 'object' || null === payload)
237
+ return;
238
+ const record = payload;
239
+ // The TESTID first: it is the strongest identity a control has and it survives any re-render.
240
+ // Coverage previously matched only on `role "name"`, so a control with a testid but no accessible
241
+ // name — or on a stack where the act reply carried neither — was unrecognisable after a
242
+ // re-render, and coverage read `exercised: 0` however much work had been done.
243
+ const testid = record['testid'];
244
+ if ('string' === typeof testid && testid.length > 0)
245
+ this.#observed.recordActedLabel(testid);
246
+ const role = record['role'];
247
+ const name = record['name'];
248
+ if (typeof role !== 'string' || typeof name !== 'string')
249
+ return;
250
+ if (0 === role.length || 0 === name.length)
251
+ return;
252
+ this.#observed.recordActedLabel(`${role} "${name}"`);
253
+ }
221
254
  actedRefs() {
222
255
  return this.#observed.actedRefs();
223
256
  }
@@ -348,7 +381,7 @@ export class Session {
348
381
  // still wrong, and it made the number depend on which tool the agent happened to use.
349
382
  if (name === ReticleCommand.ACT) {
350
383
  const ref = args['ref'];
351
- if (typeof ref === 'string')
384
+ if ('string' === typeof ref)
352
385
  this.recordActedRef(ref);
353
386
  }
354
387
  const id = this.#pending.nextId(COMMAND_ID_PREFIX);
@@ -369,7 +402,17 @@ export class Session {
369
402
  }));
370
403
  this.#socket.send(payload);
371
404
  const sentAt = Date.now(); // browser-leg timing; see recordBrowserLatency for why it is split out
372
- return awaited.finally(() => recordBrowserLatency(Date.now() - sentAt));
405
+ // Traced because this is the boundary that decides whether a slow tool call is OUR overhead or
406
+ // the app taking its time — the split telemetry reports in aggregate, made visible per call.
407
+ return span('browser.command', { command: name, sessionId: this.id }, () => awaited)
408
+ .then((result) => {
409
+ // The label is only knowable from the REPLY — the request carries a ref, and a ref dies with
410
+ // the next re-render. Recorded here so coverage survives frameworks that replace nodes.
411
+ if (name === ReticleCommand.ACT)
412
+ this.recordActedLabelFrom(result);
413
+ return result;
414
+ })
415
+ .finally(() => recordBrowserLatency(Date.now() - sentAt));
373
416
  }
374
417
  handleResult(result) {
375
418
  this.#pending.settle(result);
@@ -490,7 +533,7 @@ export class Session {
490
533
  // still wrong, and it made the number depend on which tool the agent happened to use.
491
534
  if (name === ReticleCommand.ACT) {
492
535
  const ref = args['ref'];
493
- if (typeof ref === 'string')
536
+ if ('string' === typeof ref)
494
537
  this.recordActedRef(ref);
495
538
  }
496
539
  const id = this.#pending.nextId(COMMAND_ID_PREFIX);
@@ -4,4 +4,4 @@
4
4
  * without paying the truncation tax); the server re-exports it for its back-compat fallback path
5
5
  * (an older browser that returns the whole store) and for the predicate engine's `state` assertion.
6
6
  */
7
- export { selectPath, capDepth, type PathSelection } from '@reticlehq/core';
7
+ export { selectPath, capDepth, projectComponentState, type PathSelection } from '@reticlehq/core';
@@ -4,4 +4,4 @@
4
4
  * without paying the truncation tax); the server re-exports it for its back-compat fallback path
5
5
  * (an older browser that returns the whole store) and for the predicate engine's `state` assertion.
6
6
  */
7
- export { selectPath, capDepth } from '@reticlehq/core';
7
+ export { selectPath, capDepth, projectComponentState } from '@reticlehq/core';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * What `GET /status` answers — the daemon describing itself.
3
+ *
4
+ * Carries the daemon's OWN version, because a daemon outlives every agent attached to it: after an
5
+ * upgrade the new CLI attaches to the old daemon and serves its code, and until this field existed
6
+ * there was no surface anywhere — not /status, not `reticle status` — naming the version actually
7
+ * answering requests. See describeDaemonSkew.
8
+ */
9
+ import type { SessionInfo } from './session/session-info.js';
10
+ interface StatusPayload {
11
+ running: true;
12
+ version: string;
13
+ /** The wire contract this daemon speaks — what another process compares against, not the version. */
14
+ contract: string;
15
+ sessionCount: number;
16
+ sessions: SessionInfo[];
17
+ }
18
+ export declare function statusPayload(sessionCount: number, sessions: SessionInfo[]): StatusPayload;
19
+ export {};
@@ -0,0 +1,11 @@
1
+ import { CONTRACT_FINGERPRINT } from '@reticlehq/core';
2
+ import { SERVER_VERSION } from './version/server-version.js';
3
+ export function statusPayload(sessionCount, sessions) {
4
+ return {
5
+ running: true,
6
+ version: SERVER_VERSION,
7
+ contract: CONTRACT_FINGERPRINT,
8
+ sessionCount,
9
+ sessions,
10
+ };
11
+ }
@@ -57,10 +57,19 @@ export function describeParam(name, value) {
57
57
  return name;
58
58
  return `${name}:${allowed.has(value) ? value : OTHER_VALUE}`;
59
59
  }
60
+ /**
61
+ * Parameters that carry no information about how the tool is USED, and so are not counted.
62
+ *
63
+ * `sessionId` is accepted by nearly every tool and passed on nearly every call, so counting it
64
+ * produced a row per tool saying only "the agent addressed a session" — in one real day's export it
65
+ * was a third of the toolParams block. This histogram exists to answer "is anyone using the argument
66
+ * we shipped last release", and a parameter every call carries can never answer it.
67
+ */
68
+ const UNINFORMATIVE_PARAMS = new Set(['sessionId']);
60
69
  /** Every parameter an agent actually passed to one tool call, reduced to names (+ safe enums). */
61
70
  export function describeToolParams(args) {
62
71
  return Object.keys(args)
63
- .filter((key) => args[key] !== undefined)
72
+ .filter((key) => args[key] !== undefined && !UNINFORMATIVE_PARAMS.has(key))
64
73
  .map((key) => describeParam(key, args[key]))
65
74
  .sort();
66
75
  }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Was this verification watched by a person, or run by a machine?
3
+ *
4
+ * A verification's cost and its meaning both depend on it. A headless run is CI or an unattended
5
+ * agent loop — it should be fast, and there is nobody to notice a wrong answer. A headed run has a
6
+ * human in front of it. And the most common case of all is neither: the SDK in somebody's own dev
7
+ * server, in their own browser, which Reticle never launched at all.
8
+ *
9
+ * Recorded once at daemon start, because that is when the decision is made and it does not change
10
+ * for the daemon's life. Defaults to ATTACHED, which is the honest answer when Reticle launched
11
+ * nothing: claiming "headless" for a browser we did not open would be a guess about somebody's
12
+ * machine.
13
+ */
14
+ export declare const BrowserMode: {
15
+ /** Reticle launched the browser with no display — CI, or an unattended agent loop. */
16
+ readonly HEADLESS: "headless";
17
+ /** Reticle launched a visible browser — a human is watching. */
18
+ readonly HEADED: "headed";
19
+ /** Reticle launched nothing: the SDK connected from a browser somebody else opened. */
20
+ readonly ATTACHED: "attached";
21
+ };
22
+ export type BrowserMode = (typeof BrowserMode)[keyof typeof BrowserMode];
23
+ /** Record how this daemon drives a browser. Called once, at start. */
24
+ export declare function setBrowserMode(mode: BrowserMode): void;
25
+ export declare function getBrowserMode(): BrowserMode;
26
+ /** Tests only — restore the default so cases do not leak into each other. */
27
+ export declare function resetBrowserMode(): void;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Was this verification watched by a person, or run by a machine?
3
+ *
4
+ * A verification's cost and its meaning both depend on it. A headless run is CI or an unattended
5
+ * agent loop — it should be fast, and there is nobody to notice a wrong answer. A headed run has a
6
+ * human in front of it. And the most common case of all is neither: the SDK in somebody's own dev
7
+ * server, in their own browser, which Reticle never launched at all.
8
+ *
9
+ * Recorded once at daemon start, because that is when the decision is made and it does not change
10
+ * for the daemon's life. Defaults to ATTACHED, which is the honest answer when Reticle launched
11
+ * nothing: claiming "headless" for a browser we did not open would be a guess about somebody's
12
+ * machine.
13
+ */
14
+ export const BrowserMode = {
15
+ /** Reticle launched the browser with no display — CI, or an unattended agent loop. */
16
+ HEADLESS: 'headless',
17
+ /** Reticle launched a visible browser — a human is watching. */
18
+ HEADED: 'headed',
19
+ /** Reticle launched nothing: the SDK connected from a browser somebody else opened. */
20
+ ATTACHED: 'attached',
21
+ };
22
+ let current = BrowserMode.ATTACHED;
23
+ /** Record how this daemon drives a browser. Called once, at start. */
24
+ export function setBrowserMode(mode) {
25
+ current = mode;
26
+ }
27
+ export function getBrowserMode() {
28
+ return current;
29
+ }
30
+ /** Tests only — restore the default so cases do not leak into each other. */
31
+ export function resetBrowserMode() {
32
+ current = BrowserMode.ATTACHED;
33
+ }
@@ -1,20 +1,3 @@
1
- /**
2
- * Counting the defects Reticle actually finds.
3
- *
4
- * Every other metric in this product measures whether Reticle is USED. This one measures whether it
5
- * WORKS — and it is the only number that can honestly be put in front of an investor or published,
6
- * because it counts outcomes for users rather than activity by them.
7
- *
8
- * It is read off tool RESULTS at the single chokepoint every tool already crosses, rather than
9
- * emitted from the four places that produce findings. That is deliberate: the detectors are pure
10
- * functions and should stay that way, and reading results means a fifth source of findings is covered
11
- * the day it starts returning one of these shapes, instead of the day someone remembers to instrument
12
- * it.
13
- *
14
- * What is sent is the KIND from Reticle's own findings vocabulary — `signal-contradicted`,
15
- * `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
16
- * anyone's app: that a class of defect was found, never what it was found in.
17
- */
18
1
  import { type BugFound } from '@reticlehq/core';
19
2
  /**
20
3
  * A defect as the CLASSIFIER sees it — everything except `repeat`.
@@ -24,10 +7,23 @@ import { type BugFound } from '@reticlehq/core';
24
7
  */
25
8
  export type BugCandidate = Omit<BugFound, 'repeat'>;
26
9
  /**
27
- * The defects in one tool result.
10
+ * `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
11
+ *
12
+ * It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
13
+ * `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
14
+ * poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
15
+ * it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
16
+ * one defect that genuinely was a bad agent predicate carried none. One session would have published
17
+ * "2 defects in the app" against a true count of 0.
18
+ *
19
+ * A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
20
+ * the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
21
+ * publishing a false accusation about somebody's product is not.
28
22
  *
29
- * Ordering matters for the false-green flag: a contradiction found alongside a PASSING assertion is
30
- * the case the product exists for — the screen looked right, the write failed, and every other tool
31
- * on the market would have called it green. That pairing is what `falseGreen` marks.
23
+ * Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
24
+ * today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
25
+ * a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
26
+ * Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
27
+ * output is no owner at all.
32
28
  */
33
29
  export declare function bugsInResult(toolName: string, result: Record<string, unknown>): BugCandidate[];
@@ -15,6 +15,7 @@
15
15
  * `console-error`, `duplicate-request`. Never a selector, a URL, an element, or a description of
16
16
  * anyone's app: that a class of defect was found, never what it was found in.
17
17
  */
18
+ import { ReticleTool } from '../tools/tool-names.js';
18
19
  import { BugSource, ContradictionKind } from '@reticlehq/core';
19
20
  /** Cap per tool call — a pathological crawl must not turn one call into a thousand events. */
20
21
  const MAX_BUGS_PER_CALL = 25;
@@ -24,7 +25,7 @@ function kindsOf(value) {
24
25
  return value
25
26
  .map((entry) => {
26
27
  const kind = entry?.kind;
27
- return typeof kind === 'string' ? kind : undefined;
28
+ return 'string' === typeof kind ? kind : undefined;
28
29
  })
29
30
  .filter((kind) => kind !== undefined);
30
31
  }
@@ -35,16 +36,111 @@ function kindsOf(value) {
35
36
  * the case the product exists for — the screen looked right, the write failed, and every other tool
36
37
  * on the market would have called it green. That pairing is what `falseGreen` marks.
37
38
  */
39
+ /**
40
+ * The verdict this result carries, whatever envelope it arrived in.
41
+ *
42
+ * `reticle_assert` reports a top-level boolean `pass`. `reticle_act_and_wait` — the tool agents
43
+ * actually reach for, and the one whose description calls it the act->observe->assert loop — reports
44
+ * `verdict.pass` with the summary at `verified`. Reading only `result.pass` meant a FAILED
45
+ * act_and_wait produced no bug at all, and a contradiction found next to a PASSING one was recorded
46
+ * with `falseGreen: false` — deflating the single number this product exists to publish, by the
47
+ * shape of its own envelope.
48
+ */
49
+ function verdictOf(result) {
50
+ if ('boolean' === typeof result['pass'])
51
+ return result['pass'];
52
+ const verdict = result['verdict'];
53
+ if ('object' === typeof verdict && verdict !== null) {
54
+ const nested = verdict['pass'];
55
+ if ('boolean' === typeof nested)
56
+ return nested;
57
+ }
58
+ // `verified` is the field the agent is told to gate on; "unknown" is deliberately not a verdict.
59
+ const verified = result['verified'];
60
+ if ('yes' === verified)
61
+ return true;
62
+ if ('no' === verified)
63
+ return false;
64
+ return undefined;
65
+ }
66
+ /** The assertion label, from either envelope — act_and_wait nests it under `verdict`. */
67
+ function assertionOf(result) {
68
+ if (result['assertion'] !== undefined)
69
+ return result['assertion'];
70
+ const verdict = result['verdict'];
71
+ return 'object' === typeof verdict && verdict !== null
72
+ ? verdict['assertion']
73
+ : undefined;
74
+ }
75
+ /**
76
+ * A verdict that could not be reached is not a defect found. `inconclusive` is set when the
77
+ * assertion was never evaluated — an under-specified call, or nothing instrumented to read — so
78
+ * counting it would put the agent's own mistakes into the number we publish.
79
+ */
80
+ function isInconclusive(result) {
81
+ const verdict = result['verdict'];
82
+ return ('object' === typeof verdict &&
83
+ verdict !== null &&
84
+ 'string' === typeof verdict['inconclusive']);
85
+ }
86
+ /** A suite row whose replay never STARTED — see SuiteFlowResult.couldNotRun. */
87
+ function couldNotRun(failure) {
88
+ return ('object' === typeof failure &&
89
+ failure !== null &&
90
+ true === failure.couldNotRun);
91
+ }
92
+ /**
93
+ * `bug.attribution` was REMOVED, deliberately, and this note is here so it is not re-added casually.
94
+ *
95
+ * It shipped twice and was wrong both times. First it stamped `app` on every contradiction, including
96
+ * `request-never-settled` — which Reticle raises from the ABSENCE of a settle event, so a dev-overlay
97
+ * poll manufactured it with the app doing nothing wrong. That was fixed, and a real drive then found
98
+ * it still inverted: across two full runs, EVERY `attribution: 'app'` was a misattribution, while the
99
+ * one defect that genuinely was a bad agent predicate carried none. One session would have published
100
+ * "2 defects in the app" against a true count of 0.
101
+ *
102
+ * A metric that is confidently wrong about WHOSE fault a defect is, is worse than no metric: it is
103
+ * the number a founder steers on, and it points at the customer. Counting nothing is recoverable;
104
+ * publishing a false accusation about somebody's product is not.
105
+ *
106
+ * Bringing it back needs evidence that actually separates the cases. It does not exist in the payload
107
+ * today: `element.present` covers "the button is missing", "the API is down" and "the agent mistyped
108
+ * a testid" identically, and `signal.absent` covers both a signal the app never fired and a ref
109
+ * Reticle drove into nothing. Until a verdict carries the reason it came out that way, the honest
110
+ * output is no owner at all.
111
+ */
38
112
  export function bugsInResult(toolName, result) {
113
+ // `reticle_run` is a WRAPPER: its handler calls runTool on the real tool, which already reported
114
+ // that result's defects under the real tool's name — then the outer chokepoint reports the very
115
+ // same object again as `reticle_run`. One sweep of 34 events was a perfect mirror image:
116
+ // flow-regression x8/reticle_flow_verify AND x8/reticle_run; assertion-failed x8/reticle_verify_change
117
+ // AND x8/reticle_run. Sixteen of the thirty-four were echoes, and a headline number that doubles
118
+ // because of HOW a tool was reached is not a measurement.
119
+ //
120
+ // Nothing is lost by staying silent here: when the inner tool refuses or throws, the wrapper
121
+ // returns { error, tool, params, hint }, which carries no verdict and no failures to classify.
122
+ if (toolName === ReticleTool.RUN)
123
+ return [];
39
124
  const bugs = [];
40
- const passed = result['pass'] === true;
125
+ const verdict = verdictOf(result);
126
+ const passed = true === verdict;
41
127
  // 1. Contradictions — channels disagreeing. Invisible to a human watching the screen.
42
128
  for (const kind of kindsOf(result['contradictions'])) {
43
- bugs.push({ source: BugSource.CONTRADICTION, kind, falseGreen: passed, tool: toolName });
129
+ // A contradiction is two of the APP's own channels disagreeing — never the agent's phrasing.
130
+ // Omitted for the one kind Reticle can produce on its own: see contradictionAttribution.
131
+ bugs.push({
132
+ source: BugSource.CONTRADICTION,
133
+ kind,
134
+ falseGreen: passed,
135
+ tool: toolName,
136
+ });
44
137
  }
45
138
  // 2. Crawl anomalies — found autonomously, with nobody writing a test. Crawl reports its own
46
139
  // contradictions inside `anomalies` too, so classify by kind rather than by which array it sat in.
47
140
  for (const kind of kindsOf(result['anomalies'])) {
141
+ // Crawl reports contradictions in this array too, so the same kind must get the same owner
142
+ // wherever it arrives — a `request-never-settled` found by the crawler is no better evidence
143
+ // about whose fault it was than one found by an assertion.
48
144
  bugs.push({
49
145
  source: BugSource.CRAWL,
50
146
  kind,
@@ -52,28 +148,44 @@ export function bugsInResult(toolName, result) {
52
148
  // — even though no assertion was made. See the definition on `falseGreen`.
53
149
  falseGreen: CONTRADICTION_KINDS.has(kind),
54
150
  tool: toolName,
151
+ // Nobody wrote a predicate here, so there is no bad request to blame: a crawl anomaly is a
152
+ // fault the app produced on its own.
55
153
  });
56
154
  }
57
155
  // 3. A failed assertion — the agent declared a consequence and it did not hold.
58
156
  // Only when nothing above already explained it, or one defect would be counted twice.
59
- if (result['pass'] === false && bugs.length === 0) {
60
- const reason = result['assertion'];
157
+ // A red the APP earned, not one Reticle produced. `verified: "unknown"` is the honesty layer
158
+ // saying nothing was proved either way, and a top-level `error` is a refusal — measured over one
159
+ // sweep, every bug_found emitted was one of these two: "no session connected" from reticle_run and
160
+ // an unverifiable suite from reticle_verify_change, counted as defects in the user's app.
161
+ const reticleFailure = 'unknown' === result['verified'] || 'string' === typeof result['error'];
162
+ if (false === verdict && 0 === bugs.length && !isInconclusive(result) && !reticleFailure) {
163
+ const reason = assertionOf(result);
164
+ const kind = 'string' === typeof reason && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed';
61
165
  bugs.push({
62
166
  source: BugSource.ASSERTION,
63
- kind: typeof reason === 'string' && reason.length > 0 ? reason.slice(0, 64) : 'assertion-failed',
167
+ kind,
64
168
  falseGreen: false,
65
169
  tool: toolName,
170
+ // Omitted when the label cannot say whose fault it was — see ASSERTION_ATTRIBUTION.
66
171
  });
67
172
  }
68
173
  // 4. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
69
174
  // which is a different and stronger claim than "a check failed".
70
- if (result['status'] === 'fail' && Array.isArray(result['failures'])) {
71
- for (const _failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
175
+ if ('fail' === result['status'] && Array.isArray(result['failures'])) {
176
+ for (const failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
177
+ // A suite that failed because nothing could RUN is not a regression in the user's app. This
178
+ // rule never looked past `status`, so a project whose flow files did not resolve reported one
179
+ // "defect found" per flow — Reticle's own failure, published as the customer's.
180
+ if (couldNotRun(failure))
181
+ continue;
72
182
  bugs.push({
73
183
  source: BugSource.REPLAY,
74
184
  kind: 'flow-regression',
75
185
  falseGreen: false,
76
186
  tool: toolName,
187
+ // A saved flow that used to pass and no longer does is a regression in the app. Rows that
188
+ // never RAN are skipped above precisely so Reticle's own failures do not land here.
77
189
  });
78
190
  }
79
191
  }