@reticlehq/server 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Why the bridge refused a HELLO — the specific reason when there is one.
3
+ *
4
+ * A daemon left running by one project answers the next project's app and rejects it on token, and
5
+ * the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
6
+ * check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
7
+ * need opposite fixes: "your token is wrong" versus "that daemon is not yours, stop it".
8
+ *
9
+ * The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
10
+ * different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
11
+ * would report "different project" on every auth failure — the same confidently-wrong diagnostic
12
+ * this exists to replace. What the daemon knows for certain is which projects it has ALREADY
13
+ * accepted a session from; if it has served X and a HELLO arrives for Y, it demonstrably belongs to
14
+ * someone else.
15
+ *
16
+ * With no evidence — a fresh daemon, or a HELLO naming no project — nothing is inferred and the
17
+ * plain reason stands.
18
+ */
19
+ export declare function authFailureReason(servedProjects: ReadonlySet<string>, helloProject: string | undefined): string;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Why the bridge refused a HELLO — the specific reason when there is one.
3
+ *
4
+ * A daemon left running by one project answers the next project's app and rejects it on token, and
5
+ * the SDK printed `bridge refused the connection: authentication failed`. That sends someone to
6
+ * check a token which is perfectly correct — it is simply a DIFFERENT project's token. The two cases
7
+ * need opposite fixes: "your token is wrong" versus "that daemon is not yours, stop it".
8
+ *
9
+ * The discriminator is EVIDENCE, not derivation. The daemon and the SDK derive project ids by
10
+ * different schemes (the SDK from its Vite root, the daemon from its own cwd), so comparing those
11
+ * would report "different project" on every auth failure — the same confidently-wrong diagnostic
12
+ * this exists to replace. What the daemon knows for certain is which projects it has ALREADY
13
+ * accepted a session from; if it has served X and a HELLO arrives for Y, it demonstrably belongs to
14
+ * someone else.
15
+ *
16
+ * With no evidence — a fresh daemon, or a HELLO naming no project — nothing is inferred and the
17
+ * plain reason stands.
18
+ */
19
+ const PLAIN = 'authentication failed';
20
+ /** WebSocket close reasons are capped at 123 bytes; a longer one throws and closes with nothing. */
21
+ const MAX_REASON_BYTES = 123;
22
+ export function authFailureReason(servedProjects, helloProject) {
23
+ if (helloProject === undefined || 0 === servedProjects.size)
24
+ return PLAIN;
25
+ if (servedProjects.has(helloProject))
26
+ return PLAIN;
27
+ const reason = `this daemon serves a different project — run \`reticle stop\` and retry`;
28
+ return Buffer.byteLength(reason, 'utf8') <= MAX_REASON_BYTES ? reason : PLAIN;
29
+ }
@@ -1,4 +1,4 @@
1
- import type { StartOptions } from './index.js';
1
+ import type { StartOptions } from '../index.js';
2
2
  /** The security contract the bridge/daemon enforce: bind host, pairing token, and WS origin allow-list. */
3
3
  interface BridgeSecurity {
4
4
  host?: string;
@@ -1,6 +1,6 @@
1
1
  import { ReticleEnv } from '@reticlehq/core';
2
2
  import { defaultPairingTokenDir, nodePairingTokenDeps, readOrCreatePairingToken, } from './pairing-token.js';
3
- import { log } from './log.js';
3
+ import { log } from '../log.js';
4
4
  /**
5
5
  * Resolve the bridge's security options from explicit options first, then the environment. Shared by
6
6
  * both `start` and `startDaemon` so the token/host/origin contract is enforced identically on
@@ -1,5 +1,5 @@
1
1
  import * as http from 'node:http';
2
- import { Session, SessionManager } from './session/session.js';
2
+ import { Session, SessionManager } from '../session/session.js';
3
3
  /** A human clicked ▶ on a saved flow in the panel — replay it with no agent. Wired by the daemon. */
4
4
  type ReplayRequestHandler = (sessionId: string, flowName: string) => void;
5
5
  /** Called once a browser session connects, so the daemon can push it the replayable-flow list. */
@@ -1,9 +1,31 @@
1
1
  import * as http from 'node:http';
2
+ import { authFailureReason } from './auth-failure-reason.js';
2
3
  import { WebSocketServer } from 'ws';
3
- import { EventType, HumanControlKind, RETICLE_WS_PATH, ReticleMessageSchema, LOOPBACK_HOST, MessageKind, ReticleEnv, RETICLE_PROTOCOL_VERSION, TRANSPORT_LIMITS, isLoopbackHostname, isOpaqueOrigin, OPAQUE_ORIGIN, isHighValueEvent, RATE_CAP_HIGH_VALUE_RESERVE_RATIO, } from '@reticlehq/core';
4
- import { Session, SessionManager } from './session/session.js';
4
+ import { EventType, HumanControlKind, RETICLE_WS_PATH, ReticleMessageSchema, LOOPBACK_HOST, MessageKind, ReticleEnv, RETICLE_PROTOCOL_VERSION, TRANSPORT_LIMITS, isLocalPage, isLoopbackHostname, isOpaqueOrigin, OPAQUE_ORIGIN, isHighValueEvent, RATE_CAP_HIGH_VALUE_RESERVE_RATIO, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
5
+ import { Session, SessionManager } from '../session/session.js';
5
6
  import { tokensMatch } from './token-auth.js';
6
- import { log } from './log.js';
7
+ import { log } from '../log.js';
8
+ import { describeSkew, sdkFix, SkewPair } from '../version/version-skew.js';
9
+ import { noteVersionSkew } from '../version/version-nudge.js';
10
+ import { SERVER_VERSION } from '../version/server-version.js';
11
+ /**
12
+ * Take the mutable half of a session's identity from a repeat HELLO.
13
+ *
14
+ * Lives here rather than on Session because this is where the protocol decision is made — the caller
15
+ * has already established that the hello belongs to this same session — and because these fields are
16
+ * the session's public surface, read by the tools on every listing.
17
+ *
18
+ * `hasCapabilities` is the one that matters: it is announced in the hello, which the SDK sends at
19
+ * connect(), while capabilities are registered AFTER connect by design (registerStore needs a live
20
+ * SDK to subscribe through). Without this refresh, an app that declared its entire testable surface
21
+ * reported `hasCapabilities: false` forever.
22
+ */
23
+ function refreshIdentity(session, hello) {
24
+ session.url = hello.url;
25
+ session.title = hello.title;
26
+ session.adapters = hello.adapters;
27
+ session.hasCapabilities = hello.hasCapabilities ?? false;
28
+ }
7
29
  /** WS close codes + reasons the bridge sends to the SDK (1008 = policy violation, 1013 = try again later). */
8
30
  const WS_CLOSE = {
9
31
  TOO_MANY_HANDSHAKES: [1013, 'too many pending handshakes'],
@@ -28,7 +50,7 @@ function replayRequest(event) {
28
50
  if (event.data['kind'] !== HumanControlKind.REPLAY)
29
51
  return undefined;
30
52
  const name = event.data['text'];
31
- return typeof name === 'string' && name.length > 0 ? name : undefined;
53
+ return 'string' === typeof name && name.length > 0 ? name : undefined;
32
54
  }
33
55
  /**
34
56
  * Canonical comparable form of a WS `Origin`. Web origins collapse to scheme://host[:port]. An
@@ -58,7 +80,7 @@ function helloProtocolMismatch(text) {
58
80
  try {
59
81
  const json = JSON.parse(text);
60
82
  if (json.kind === MessageKind.HELLO &&
61
- typeof json.protocolVersion === 'number' &&
83
+ 'number' === typeof json.protocolVersion &&
62
84
  json.protocolVersion !== RETICLE_PROTOCOL_VERSION) {
63
85
  return json.protocolVersion;
64
86
  }
@@ -70,7 +92,7 @@ function helloProtocolMismatch(text) {
70
92
  }
71
93
  /** Normalize ws RawData (string | Buffer | Buffer[] | ArrayBuffer) into a UTF-8 string. */
72
94
  function rawToString(raw) {
73
- if (typeof raw === 'string')
95
+ if ('string' === typeof raw)
74
96
  return raw;
75
97
  if (Array.isArray(raw))
76
98
  return Buffer.concat(raw).toString('utf8');
@@ -90,6 +112,12 @@ export class Bridge {
90
112
  #wss;
91
113
  #clock;
92
114
  #token;
115
+ /**
116
+ * Projects this daemon has ACCEPTED a session from. Evidence, not derivation: it is what lets a
117
+ * leaked daemon say "I belong to another project" instead of "authentication failed". See
118
+ * authFailureReason.
119
+ */
120
+ #servedProjects = new Set();
93
121
  #allowedOrigins;
94
122
  #maxMessagesPerSecond;
95
123
  #maxSessions;
@@ -106,7 +134,7 @@ export class Bridge {
106
134
  if ((options.token?.length ?? 0) > TRANSPORT_LIMITS.MAX_TOKEN_LENGTH) {
107
135
  throw new Error(`Reticle pairing token exceeds ${String(TRANSPORT_LIMITS.MAX_TOKEN_LENGTH)} characters`);
108
136
  }
109
- if (!isLoopbackHostname(host) && (options.token === undefined || options.token.length === 0)) {
137
+ if (!isLoopbackHostname(host) && (options.token === undefined || 0 === options.token.length)) {
110
138
  throw new Error('a pairing token is required when the Reticle bridge binds beyond localhost');
111
139
  }
112
140
  this.#clock = options.clock ?? (() => Date.now());
@@ -118,7 +146,7 @@ export class Bridge {
118
146
  // Binding beyond localhost means the browser dials in from a non-loopback Origin. With no
119
147
  // allow-list, #originAllowed rejects every such Origin at the WS handshake, so the bridge comes
120
148
  // up but accepts nothing — a silent, fail-closed footgun. Refuse to start with a clear message.
121
- if (!isLoopbackHostname(host) && this.#allowedOrigins.size === 0) {
149
+ if (!isLoopbackHostname(host) && 0 === this.#allowedOrigins.size) {
122
150
  throw new Error(`${ReticleEnv.ALLOWED_ORIGINS} must list the app origin(s) when the Reticle bridge binds ` +
123
151
  'beyond localhost — otherwise every non-loopback browser is rejected at the WebSocket ' +
124
152
  'origin check and no connection can be established.');
@@ -229,7 +257,7 @@ export class Bridge {
229
257
  const overRate = messagesInWindow > this.#maxMessagesPerSecond;
230
258
  const text = rawToString(raw);
231
259
  const parsed = this.#parse(text);
232
- if (parsed === null) {
260
+ if (null === parsed) {
233
261
  const got = helloProtocolMismatch(text);
234
262
  if (got !== null) {
235
263
  log('protocol_version_mismatch', { got, expected: RETICLE_PROTOCOL_VERSION });
@@ -266,12 +294,28 @@ export class Bridge {
266
294
  }
267
295
  if (parsed.kind === MessageKind.HELLO) {
268
296
  if (session !== undefined) {
269
- socket.close(...WS_CLOSE.HELLO_DUPLICATE);
297
+ // A repeat hello on the SAME socket for the SAME session is an identity refresh, not a
298
+ // violation: the SDK re-announces when the app registers capabilities, which happens after
299
+ // connect by design. Closing the socket on it made `hasCapabilities` permanently false for
300
+ // every app that declared anything. A hello bearing a DIFFERENT session id is still a
301
+ // protocol violation — that is the case this guard exists for.
302
+ if (parsed.sessionId !== session.id) {
303
+ socket.close(...WS_CLOSE.HELLO_DUPLICATE);
304
+ return;
305
+ }
306
+ refreshIdentity(session, parsed);
270
307
  return;
271
308
  }
272
309
  if (this.#token !== undefined && !tokensMatch(this.#token, parsed.token)) {
273
- log('authentication_failed', {});
274
- socket.close(...WS_CLOSE.AUTH_FAILED);
310
+ // A daemon left running by ANOTHER project answers this app and rejects it on token. The
311
+ // token is not wrong, it is somebody else's — and "authentication failed" sends the user to
312
+ // check the one thing that is fine. See auth-failure-reason.
313
+ const reason = authFailureReason(this.#servedProjects, parsed.projectId);
314
+ log('authentication_failed', {
315
+ served: [...this.#servedProjects],
316
+ ...(parsed.projectId === undefined ? {} : { helloProject: parsed.projectId }),
317
+ });
318
+ socket.close(WS_CLOSE.AUTH_FAILED[0], reason);
275
319
  return;
276
320
  }
277
321
  const existing = this.sessions.get(parsed.sessionId);
@@ -282,9 +326,42 @@ export class Bridge {
282
326
  clearTimeout(helloTimer);
283
327
  releasePending();
284
328
  session = new Session(parsed, socket, this.#clock);
329
+ // Recorded on ACCEPTANCE, so it is evidence of what this daemon really serves.
330
+ if (parsed.projectId !== undefined)
331
+ this.#servedProjects.add(parsed.projectId);
285
332
  this.#onSessionCreate?.(session); // attach the durable journal before any events stream in
286
333
  const replaced = this.sessions.add(session);
287
- replaced?.disconnect('session replaced by a newer connection');
334
+ if (replaced !== undefined) {
335
+ // Name the newcomer. A field report had a live session vanish during `reticle_lease` and the
336
+ // only evidence was "session replaced by a newer connection" in the page console — which
337
+ // says a replacement happened but not BY WHAT, so the cause had to be inferred. A session
338
+ // is only ever replaced by one carrying the SAME id, and knowing which URL claimed it is
339
+ // the difference between a diagnosis and a guess.
340
+ log('session_replaced', {
341
+ sessionId: session.id,
342
+ byUrl: session.url,
343
+ previousUrl: replaced.url,
344
+ });
345
+ replaced.disconnect(`session replaced by a newer connection claiming the same id (${session.id}) from ${session.url}`);
346
+ }
347
+ // The daemon is the single judge of skew, and HELLO is where the page announces itself.
348
+ // Reported on the session (reticle_sessions) AND queued for the next tool result, because an
349
+ // agent driving a flow never calls reticle_sessions and would never learn.
350
+ const skew = describeSkew({
351
+ what: 'the page',
352
+ version: parsed.sdkVersion,
353
+ contract: parsed.contract,
354
+ fix: sdkFix(SERVER_VERSION),
355
+ }, { version: SERVER_VERSION, contract: CONTRACT_FINGERPRINT });
356
+ if (skew !== undefined) {
357
+ log('version_skew', {
358
+ sessionId: session.id,
359
+ sdk: parsed.sdkVersion,
360
+ daemon: SERVER_VERSION,
361
+ });
362
+ session.versionSkew = skew;
363
+ noteVersionSkew(SkewPair.SDK, skew);
364
+ }
288
365
  log('session_connected', { sessionId: session.id, url: session.url });
289
366
  this.#onSessionReady?.(session); // daemon pushes the replayable-flow list to the panel
290
367
  return;
@@ -330,7 +407,7 @@ export class Bridge {
330
407
  if (origin === undefined)
331
408
  return this.#token !== undefined;
332
409
  const normalized = normalizeOrigin(origin);
333
- if (normalized === null)
410
+ if (null === normalized)
334
411
  return false;
335
412
  if (this.#allowedOrigins.has(normalized))
336
413
  return true;
@@ -339,7 +416,20 @@ export class Bridge {
339
416
  // Without this branch `new URL` throws INSIDE the WS upgrade handler, crashing the process.
340
417
  if (isOpaqueOrigin(normalized))
341
418
  return this.#token !== undefined;
342
- return isLoopbackHostname(new URL(normalized).hostname);
419
+ // `isLocalPage`, not `isLoopbackHostname` — the SAME rule the SDK applies on the page side.
420
+ //
421
+ // They had drifted, and the drift made a whole platform unreachable. Tauri v2 uses an opaque
422
+ // `tauri://localhost` on macOS/Linux, but on WINDOWS the webview needs a real http origin and
423
+ // Tauri serves `http://tauri.localhost`. `isLocalPage` was taught that hostname; this check was
424
+ // not. So a Windows Tauri app passed its own gate, dialed the bridge, and every handshake came
425
+ // back 403 — Reticle could not connect to a Tauri app on Windows at all, which is precisely the
426
+ // platform the release notes admitted had never been executed.
427
+ //
428
+ // No weaker than before: `.localhost` is reserved for loopback by RFC 6761, so the name cannot
429
+ // resolve to a remote host, and this is the argument core already made when it accepted the same
430
+ // hostname for the page.
431
+ const url = new URL(normalized);
432
+ return isLocalPage(url.protocol, url.hostname);
343
433
  }
344
434
  #parse(text) {
345
435
  let json;
@@ -1,16 +1,6 @@
1
1
  import { type ElementQuery } from '@reticlehq/core';
2
2
  import type { Bridge } from './bridge.js';
3
- import { type ToolDeps } from './tools/tools.js';
4
- /** The app-advertised testable surface a FakeBrowser reports for an reticle_capabilities round-trip. */
5
- export declare const FAKE_CAPABILITIES: {
6
- testids: string[];
7
- signals: string[];
8
- stores: string[];
9
- flows: {
10
- name: string;
11
- steps: string[];
12
- }[];
13
- };
3
+ import { type ToolDeps } from '../tools/tools.js';
14
4
  /** A stand-in for the real @reticlehq/browser SDK: replies to commands and emits events. */
15
5
  export declare class FakeBrowser {
16
6
  #private;
@@ -37,6 +27,11 @@ export declare class FakeBrowser {
37
27
  }[];
38
28
  constructor(port: number, sessionId: string, hasCapabilities?: boolean);
39
29
  open(): Promise<void>;
30
+ /** Re-send the HELLO on the live socket, the way the SDK does when capabilities register late. */
31
+ reannounce(overrides?: {
32
+ sessionId?: string;
33
+ hasCapabilities?: boolean;
34
+ }): void;
40
35
  emit(type: string, data: Record<string, unknown>, ref?: string): void;
41
36
  close(): void;
42
37
  }
@@ -1,14 +1,14 @@
1
1
  import { WebSocket } from 'ws';
2
2
  import { RETICLE_WS_PATH, ReticleCommand, LOOPBACK_HOST, MessageKind, } from '@reticlehq/core';
3
- import { BaselineStore } from './project/baselines.js';
4
- import { createNodeFileSystem } from './project/fs-port.js';
5
- import { RecordingStore } from './flows/recordings.js';
6
- import { FlowStore } from './flows/flows.js';
7
- import { ProjectStore } from './project/project-store.js';
8
- import { AnnotationStore } from './flows/annotation-store.js';
9
- import { TOOLS } from './tools/tools.js';
3
+ import { BaselineStore } from '../project/baselines.js';
4
+ import { createNodeFileSystem } from '../project/fs-port.js';
5
+ import { RecordingStore } from '../flows/recordings.js';
6
+ import { FlowStore } from '../flows/flows.js';
7
+ import { ProjectStore } from '../project/project-store.js';
8
+ import { AnnotationStore } from '../flows/annotation-store.js';
9
+ import { TOOLS } from '../tools/tools.js';
10
10
  /** The app-advertised testable surface a FakeBrowser reports for an reticle_capabilities round-trip. */
11
- export const FAKE_CAPABILITIES = {
11
+ const FAKE_CAPABILITIES = {
12
12
  testids: ['toast'],
13
13
  signals: ['webhook:received'],
14
14
  stores: ['cart'],
@@ -59,6 +59,18 @@ export class FakeBrowser {
59
59
  });
60
60
  });
61
61
  }
62
+ /** Re-send the HELLO on the live socket, the way the SDK does when capabilities register late. */
63
+ reannounce(overrides = {}) {
64
+ this.#send({
65
+ kind: MessageKind.HELLO,
66
+ protocolVersion: 1,
67
+ sessionId: overrides.sessionId ?? this.sessionId,
68
+ url: 'http://localhost:3000/checkout',
69
+ title: 'Checkout',
70
+ adapters: [],
71
+ hasCapabilities: overrides.hasCapabilities ?? this.hasCapabilities,
72
+ });
73
+ }
62
74
  emit(type, data, ref) {
63
75
  this.#send({
64
76
  kind: MessageKind.EVENT,
@@ -129,7 +141,7 @@ export class FakeBrowser {
129
141
  }
130
142
  else if (name === ReticleCommand.STATE_READ) {
131
143
  result = {
132
- stores: { workspace: { tab: args['store'] === 'workspace' ? 'open' : 'all' } },
144
+ stores: { workspace: { tab: 'workspace' === args['store'] ? 'open' : 'all' } },
133
145
  storeNames: ['workspace'],
134
146
  component: args['ref'] !== undefined ? { component: 'PayButton', hooks: [0] } : undefined,
135
147
  };
@@ -49,7 +49,7 @@ export async function readOrCreatePairingToken(dir, deps) {
49
49
  }
50
50
  try {
51
51
  const token = deps.randomToken().trim();
52
- if (token.length === 0)
52
+ if (0 === token.length)
53
53
  return undefined;
54
54
  await deps.mkdir(dir);
55
55
  await deps.writeFile(path, token);
@@ -10,6 +10,12 @@ import type { FileSystemPort } from '../project/fs-port.js';
10
10
  * fix lands, it is exactly a regression flow. Capsules are ordinary flow files plus evidence, so they
11
11
  * replay through the existing machinery rather than needing a second runner.
12
12
  */
13
+ /**
14
+ * The capsule file-format version. Declared ABOVE the schema so the schema, the writer and this
15
+ * constant cannot disagree: the number was previously written as a bare `1` in three places — here,
16
+ * in the schema literal, and in act-capsule's writer — with the named one used by nobody.
17
+ */
18
+ export declare const CAPSULE_VERSION = 1;
13
19
  export declare const CapsuleSchema: z.ZodObject<{
14
20
  version: z.ZodLiteral<1>;
15
21
  id: z.ZodString;
@@ -29,8 +35,8 @@ export declare const CapsuleSchema: z.ZodObject<{
29
35
  version: 1;
30
36
  id: string;
31
37
  expected: string;
32
- steps: FlowStep[];
33
38
  observed: string;
39
+ steps: FlowStep[];
34
40
  createdAt: number;
35
41
  flow?: string | undefined;
36
42
  }, {
@@ -38,13 +44,12 @@ export declare const CapsuleSchema: z.ZodObject<{
38
44
  version: 1;
39
45
  id: string;
40
46
  expected: string;
41
- steps: FlowStep[];
42
47
  observed: string;
48
+ steps: FlowStep[];
43
49
  createdAt: number;
44
50
  flow?: string | undefined;
45
51
  }>;
46
52
  export type Capsule = z.infer<typeof CapsuleSchema>;
47
- export declare const CAPSULE_VERSION = 1;
48
53
  /** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
49
54
  export declare function isValidCapsuleId(id: string): boolean;
50
55
  export declare class CapsuleStore {
@@ -11,8 +11,14 @@ import { reticleDirPaths } from '../project/reticle-dir.js';
11
11
  * fix lands, it is exactly a regression flow. Capsules are ordinary flow files plus evidence, so they
12
12
  * replay through the existing machinery rather than needing a second runner.
13
13
  */
14
+ /**
15
+ * The capsule file-format version. Declared ABOVE the schema so the schema, the writer and this
16
+ * constant cannot disagree: the number was previously written as a bare `1` in three places — here,
17
+ * in the schema literal, and in act-capsule's writer — with the named one used by nobody.
18
+ */
19
+ export const CAPSULE_VERSION = 1;
14
20
  export const CapsuleSchema = z.object({
15
- version: z.literal(1),
21
+ version: z.literal(CAPSULE_VERSION),
16
22
  id: z.string(),
17
23
  /** The flow the failure happened in, when it came from one (a bare assert has no flow). */
18
24
  flow: z.string().optional(),
@@ -26,7 +32,6 @@ export const CapsuleSchema = z.object({
26
32
  /** Minimal steps to reproduce (prefix-trimmed). */
27
33
  steps: z.array(FlowStepSchema),
28
34
  });
29
- export const CAPSULE_VERSION = 1;
30
35
  /** Capsule ids are filenames — refuse anything that could escape the capsules directory. */
31
36
  export function isValidCapsuleId(id) {
32
37
  return /^[a-zA-Z0-9._-]+$/.test(id) && id !== '.' && id !== '..' && id.length <= 128;
@@ -1,13 +1,13 @@
1
- import { EventType } from '@reticlehq/core';
1
+ import { ConsequenceKind, EventType } from '@reticlehq/core';
2
2
  import { causalSummary } from './causal-summary.js';
3
3
  import { firstDivergence } from './divergence.js';
4
4
  /** Observed signal/state changes the flow never declared — the side effects. */
5
5
  function blastRadius(expected, observed) {
6
6
  const declaredSignals = new Set(expected
7
- .filter((l) => l.kind === 'signal')
7
+ .filter((l) => ConsequenceKind.SIGNAL === l.kind)
8
8
  .map((l) => l.name));
9
9
  const declaredStates = new Set(expected
10
- .filter((l) => l.kind === 'state')
10
+ .filter((l) => ConsequenceKind.STATE === l.kind)
11
11
  .map((l) => l.name));
12
12
  const radius = [];
13
13
  const add = (value) => {
@@ -26,9 +26,17 @@ export interface StorageDiff {
26
26
  to?: unknown;
27
27
  }
28
28
  export interface CausalSummary {
29
+ /**
30
+ * `total`/`errors` describe the APP's traffic. `ignoredDevTooling` is the count of calls the dev
31
+ * toolchain made about itself (Next's error overlay, HMR, the Vite client — see
32
+ * `DevToolingChannel`), which Reticle deliberately leaves out of the settle decision and out of
33
+ * contradiction hunting. Present only when there were any: a trim is never silent, so an agent can
34
+ * always see that something was ignored and go read it in the timeline.
35
+ */
29
36
  net: {
30
37
  total: number;
31
38
  errors: number;
39
+ ignoredDevTooling?: number;
32
40
  headline?: string;
33
41
  };
34
42
  consoleErrors: number;
@@ -1,6 +1,6 @@
1
- import { EventType, PerfMetric } from '@reticlehq/core';
1
+ import { EventType, PerfMetric, isDevToolingUrl } from '@reticlehq/core';
2
2
  function pushUnique(list, value) {
3
- if (typeof value === 'string' && value.length > 0 && !list.includes(value))
3
+ if ('string' === typeof value && value.length > 0 && !list.includes(value))
4
4
  list.push(value);
5
5
  }
6
6
  /** Keep a diff value bounded so the per-act summary never bloats: primitives capped to a short string. */
@@ -10,11 +10,11 @@ function truncate(text) {
10
10
  return text.length > MAX_DIFF_LEN ? `${text.slice(0, MAX_DIFF_LEN - 1)}…` : text;
11
11
  }
12
12
  function capValue(value) {
13
- if (value === undefined || value === null)
13
+ if (value === undefined || null === value)
14
14
  return value;
15
- if (typeof value === 'string')
15
+ if ('string' === typeof value)
16
16
  return truncate(value);
17
- if (typeof value === 'number' || typeof value === 'boolean')
17
+ if ('number' === typeof value || 'boolean' === typeof value)
18
18
  return value;
19
19
  // Objects/arrays: a shallow, length-capped JSON repr (never a deep dump on the hot path).
20
20
  try {
@@ -40,6 +40,7 @@ function stateSettle(diffs) {
40
40
  export function causalSummary(events) {
41
41
  let netTotal = 0;
42
42
  let netErrors = 0;
43
+ let ignoredDevTooling = 0;
43
44
  let headline;
44
45
  let consoleErrors = 0;
45
46
  const statePathsChanged = [];
@@ -54,9 +55,13 @@ export function causalSummary(events) {
54
55
  const data = event.data;
55
56
  switch (event.type) {
56
57
  case EventType.NET_REQUEST: {
58
+ if (isDevToolingUrl('string' === typeof data['url'] ? data['url'] : undefined)) {
59
+ ignoredDevTooling += 1;
60
+ break;
61
+ }
57
62
  netTotal += 1;
58
63
  const status = data['status'];
59
- const failed = data['ok'] === false || (typeof status === 'number' && status >= 400);
64
+ const failed = false === data['ok'] || ('number' === typeof status && status >= 400);
60
65
  if (failed) {
61
66
  netErrors += 1;
62
67
  // Headline is the first failing request — the thing most worth the agent's eye.
@@ -73,9 +78,9 @@ export function causalSummary(events) {
73
78
  // The subscribed-store observer emits { name, path, value, old } — `value` is the AFTER side
74
79
  // (`new` is accepted too for any producer that uses it). Presence of either makes it a real
75
80
  // before→after diff rather than a bare reading.
76
- const path = typeof data['path'] === 'string' ? data['path'] : data['name'];
81
+ const path = 'string' === typeof data['path'] ? data['path'] : data['name'];
77
82
  const hasAfter = 'value' in data || 'new' in data;
78
- if (typeof path === 'string' && path.length > 0 && ('old' in data || hasAfter)) {
83
+ if ('string' === typeof path && path.length > 0 && ('old' in data || hasAfter)) {
79
84
  const after = 'value' in data ? data['value'] : data['new'];
80
85
  stateDiffs.push({
81
86
  path,
@@ -89,20 +94,20 @@ export function causalSummary(events) {
89
94
  case EventType.STORAGE_CHANGE: {
90
95
  pushUnique(storageKeysChanged, data['key']);
91
96
  const key = data['key'];
92
- if (typeof key === 'string' && key.length > 0) {
97
+ if ('string' === typeof key && key.length > 0) {
93
98
  storageDiffs.push({ key, from: capValue(data['old']), to: capValue(data['new']) });
94
99
  }
95
100
  break;
96
101
  }
97
102
  case EventType.ROUTE_CHANGE:
98
- if (typeof data['pathname'] === 'string')
103
+ if ('string' === typeof data['pathname'])
99
104
  route = data['pathname'];
100
105
  break;
101
106
  case EventType.SIGNAL:
102
107
  pushUnique(signals, data['name']);
103
108
  break;
104
109
  case EventType.PERF:
105
- if (data['metric'] === PerfMetric.CLS && typeof data['value'] === 'number') {
110
+ if (data['metric'] === PerfMetric.CLS && 'number' === typeof data['value']) {
106
111
  layoutShift = Math.max(layoutShift ?? 0, data['value']);
107
112
  }
108
113
  else if (data['metric'] === PerfMetric.LONGTASK) {
@@ -114,7 +119,12 @@ export function causalSummary(events) {
114
119
  }
115
120
  }
116
121
  return {
117
- net: { total: netTotal, errors: netErrors, ...(headline === undefined ? {} : { headline }) },
122
+ net: {
123
+ total: netTotal,
124
+ errors: netErrors,
125
+ ...(0 === ignoredDevTooling ? {} : { ignoredDevTooling }),
126
+ ...(headline === undefined ? {} : { headline }),
127
+ },
118
128
  consoleErrors,
119
129
  statePathsChanged,
120
130
  storageKeysChanged,
@@ -1,4 +1,4 @@
1
- import { type ReticleEvent } from '@reticlehq/core';
1
+ import { ConsequenceKind, type ReticleEvent } from '@reticlehq/core';
2
2
  /**
3
3
  * First-divergence — the brain of the divergence capsule (Tier 2). Given the chain the flow DECLARED
4
4
  * should happen (its mustHold, as ordered expected links) and what was OBSERVED, walk the declaration and
@@ -6,15 +6,20 @@ import { type ReticleEvent } from '@reticlehq/core';
6
6
  * fault lives — the agent fixes it without re-exploring. Pure; the acceptance bar is that for every
7
7
  * hidden-500 / hung-request fixture it names the correct link.
8
8
  */
9
+ /**
10
+ * The links a capsule can walk. Exactly core's ConsequenceKind — a link is a thing the app provably
11
+ * DID, which is the same set signal/net/state names everywhere else. Spelled through the shared
12
+ * constant so the two can never drift apart.
13
+ */
9
14
  export type ExpectedLink = {
10
- kind: 'signal';
15
+ kind: typeof ConsequenceKind.SIGNAL;
11
16
  name: string;
12
17
  } | {
13
- kind: 'net';
18
+ kind: typeof ConsequenceKind.NET;
14
19
  urlContains: string;
15
20
  status?: number;
16
21
  } | {
17
- kind: 'state';
22
+ kind: typeof ConsequenceKind.STATE;
18
23
  name: string;
19
24
  };
20
25
  export interface Divergence {
@@ -1,28 +1,28 @@
1
- import { EventType } from '@reticlehq/core';
1
+ import { ConsequenceKind, EventType } from '@reticlehq/core';
2
2
  function net(events) {
3
3
  return events.filter((e) => e.type === EventType.NET_REQUEST);
4
4
  }
5
5
  function satisfies(link, events) {
6
6
  switch (link.kind) {
7
- case 'signal':
7
+ case ConsequenceKind.SIGNAL:
8
8
  return events.some((e) => e.type === EventType.SIGNAL && e.data['name'] === link.name);
9
- case 'state':
9
+ case ConsequenceKind.STATE:
10
10
  return events.some((e) => e.type === EventType.STATE_CHANGE && e.data['name'] === link.name);
11
- case 'net':
12
- return net(events).some((e) => typeof e.data['url'] === 'string' &&
11
+ case ConsequenceKind.NET:
12
+ return net(events).some((e) => 'string' === typeof e.data['url'] &&
13
13
  e.data['url'].includes(link.urlContains) &&
14
14
  (link.status === undefined || e.data['status'] === link.status));
15
15
  }
16
16
  }
17
17
  /** Describe what WAS observed for a link that failed — the closest attempt, for a side-by-side. */
18
18
  function observedFor(link, events) {
19
- if (link.kind === 'net') {
20
- const match = net(events).find((e) => typeof e.data['url'] === 'string' && e.data['url'].includes(link.urlContains));
19
+ if (ConsequenceKind.NET === link.kind) {
20
+ const match = net(events).find((e) => 'string' === typeof e.data['url'] && e.data['url'].includes(link.urlContains));
21
21
  if (match === undefined)
22
22
  return `no request to ${link.urlContains}`;
23
23
  return `${link.urlContains} responded ${String(match.data['status'])} (expected ${String(link.status)})`;
24
24
  }
25
- if (link.kind === 'signal')
25
+ if (ConsequenceKind.SIGNAL === link.kind)
26
26
  return `signal "${link.name}" never fired`;
27
27
  return `state "${link.name}" never changed`;
28
28
  }