@reticlehq/server 2.4.0 → 2.6.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 (472) hide show
  1. package/SKILL.md +719 -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} +13 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +95 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +9 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +18 -10
  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/cli-doctor.d.ts +1 -0
  20. package/dist/cli/cli-doctor.js +135 -0
  21. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  22. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  23. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  24. package/dist/cli/cli-launch.js +171 -0
  25. package/dist/cli/cli-parse-feedback.d.ts +31 -0
  26. package/dist/cli/cli-parse-feedback.js +56 -0
  27. package/dist/{cli-parse.d.ts → cli/cli-parse.d.ts} +6 -19
  28. package/dist/{cli-parse.js → cli/cli-parse.js} +118 -81
  29. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +12 -0
  30. package/dist/{cli-port.js → cli/cli-port.js} +9 -9
  31. package/dist/cli/cli-update-commands.d.ts +4 -0
  32. package/dist/cli/cli-update-commands.js +93 -0
  33. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  34. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  35. package/dist/cli/doctor-daemon-line.d.ts +37 -0
  36. package/dist/cli/doctor-daemon-line.js +35 -0
  37. package/dist/cli/port-holder.d.ts +43 -0
  38. package/dist/cli/port-holder.js +99 -0
  39. package/dist/cli.d.ts +2 -2
  40. package/dist/cli.js +302 -100
  41. package/dist/cloud/cloud-config.d.ts +0 -1
  42. package/dist/cloud/cloud-config.js +9 -9
  43. package/dist/cloud/cloud-sync.d.ts +1 -7
  44. package/dist/cloud/cloud-sync.js +12 -12
  45. package/dist/crawl/crawl-empty.d.ts +16 -0
  46. package/dist/crawl/crawl-empty.js +24 -0
  47. package/dist/crawl/crawl-tools.js +4 -1
  48. package/dist/crawl/crawl.js +26 -7
  49. package/dist/daemon/daemon-resilience.d.ts +73 -0
  50. package/dist/daemon/daemon-resilience.js +279 -0
  51. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  52. package/dist/daemon/daemon-usefulness.js +62 -0
  53. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  54. package/dist/{daemon.js → daemon/daemon.js} +105 -48
  55. package/dist/daemon/heartbeat.d.ts +115 -0
  56. package/dist/daemon/heartbeat.js +141 -0
  57. package/dist/daemon/idle-grace.d.ts +32 -0
  58. package/dist/daemon/idle-grace.js +41 -0
  59. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +23 -2
  60. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +37 -6
  61. package/dist/daemon/port-presence.d.ts +76 -0
  62. package/dist/daemon/port-presence.js +93 -0
  63. package/dist/daemon/state-dir.d.ts +7 -0
  64. package/dist/daemon/state-dir.js +59 -0
  65. package/dist/daemon/unclean-predecessor.d.ts +23 -0
  66. package/dist/daemon/unclean-predecessor.js +27 -0
  67. package/dist/domain/domain-model.js +2 -2
  68. package/dist/events/body-failures.js +8 -8
  69. package/dist/events/contradictions.d.ts +1 -1
  70. package/dist/events/contradictions.js +60 -10
  71. package/dist/events/echo-mismatch.js +10 -10
  72. package/dist/events/event-filters.js +6 -6
  73. package/dist/events/json-salvage.d.ts +2 -1
  74. package/dist/events/json-salvage.js +6 -6
  75. package/dist/events/observed-in-window.d.ts +12 -0
  76. package/dist/events/observed-in-window.js +21 -0
  77. package/dist/events/predicate-eval.d.ts +53 -20
  78. package/dist/events/predicate-eval.js +179 -55
  79. package/dist/events/predicate-parse.d.ts +23 -0
  80. package/dist/events/predicate-parse.js +54 -0
  81. package/dist/events/predicate.d.ts +7 -2
  82. package/dist/events/predicate.js +126 -35
  83. package/dist/events/reconcile.js +5 -5
  84. package/dist/events/ring-buffer.js +2 -17
  85. package/dist/events/stale-response.js +6 -6
  86. package/dist/events/testid-near-miss.d.ts +22 -0
  87. package/dist/events/testid-near-miss.js +33 -0
  88. package/dist/events/unit-mismatch.js +8 -8
  89. package/dist/flows/affected-tools.js +19 -4
  90. package/dist/flows/affected.js +1 -1
  91. package/dist/flows/annotate-target.d.ts +1 -0
  92. package/dist/flows/annotate-target.js +20 -0
  93. package/dist/flows/annotate-tools.js +25 -3
  94. package/dist/flows/annotate.js +17 -4
  95. package/dist/flows/assertion-integrity.d.ts +2 -1
  96. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  97. package/dist/flows/assertion-tiers-store.js +2 -2
  98. package/dist/flows/attributed-failure.d.ts +17 -0
  99. package/dist/flows/attributed-failure.js +20 -0
  100. package/dist/flows/buddy-status.d.ts +2 -1
  101. package/dist/flows/buddy-status.js +2 -2
  102. package/dist/flows/coverage.d.ts +3 -2
  103. package/dist/flows/coverage.js +2 -2
  104. package/dist/flows/decision.js +24 -9
  105. package/dist/flows/empty-flow.d.ts +6 -0
  106. package/dist/flows/empty-flow.js +24 -0
  107. package/dist/flows/flake-store.d.ts +0 -1
  108. package/dist/flows/flake-store.js +1 -1
  109. package/dist/flows/flake.d.ts +2 -1
  110. package/dist/flows/flake.js +2 -2
  111. package/dist/flows/flow-classify.js +12 -0
  112. package/dist/flows/flow-replay-run.js +4 -4
  113. package/dist/flows/flow-replay.d.ts +6 -8
  114. package/dist/flows/flow-replay.js +132 -48
  115. package/dist/flows/flow-report.js +1 -1
  116. package/dist/flows/flow-scope.d.ts +1 -6
  117. package/dist/flows/flow-step-runners.d.ts +26 -6
  118. package/dist/flows/flow-step-runners.js +156 -6
  119. package/dist/flows/flow-success.js +20 -15
  120. package/dist/flows/flow-tools.js +63 -21
  121. package/dist/flows/flows.js +15 -11
  122. package/dist/flows/gate.d.ts +4 -3
  123. package/dist/flows/gate.js +1 -1
  124. package/dist/flows/heal-run.d.ts +0 -8
  125. package/dist/flows/heal-run.js +5 -5
  126. package/dist/flows/heal.js +3 -3
  127. package/dist/flows/predicate-to-expect.d.ts +38 -0
  128. package/dist/flows/predicate-to-expect.js +95 -0
  129. package/dist/flows/replay.d.ts +16 -1
  130. package/dist/flows/replay.js +58 -22
  131. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  132. package/dist/flows/role-anchor-nearest.js +45 -0
  133. package/dist/flows/role-drift-reason.d.ts +20 -0
  134. package/dist/flows/role-drift-reason.js +39 -0
  135. package/dist/flows/server-verify.d.ts +1 -1
  136. package/dist/flows/server-verify.js +5 -5
  137. package/dist/flows/suite-flakes.d.ts +33 -0
  138. package/dist/flows/suite-flakes.js +32 -0
  139. package/dist/flows/verify-change-tools.js +32 -10
  140. package/dist/flows/verify-change-verdict.d.ts +15 -0
  141. package/dist/flows/verify-change-verdict.js +25 -0
  142. package/dist/flows/watch-batcher.d.ts +3 -2
  143. package/dist/honesty/already-true.d.ts +2 -0
  144. package/dist/honesty/already-true.js +31 -0
  145. package/dist/honesty/blind-spots.d.ts +35 -3
  146. package/dist/honesty/blind-spots.js +57 -12
  147. package/dist/honesty/honesty.d.ts +3 -2
  148. package/dist/honesty/honesty.js +4 -4
  149. package/dist/honesty/uncaptured-bodies.js +1 -1
  150. package/dist/honesty/unread-outcome.js +2 -2
  151. package/dist/honesty/verified.d.ts +31 -3
  152. package/dist/honesty/verified.js +64 -8
  153. package/dist/http-server.js +23 -7
  154. package/dist/hunt/hunt-report.d.ts +2 -1
  155. package/dist/hunt/hunt-report.js +2 -2
  156. package/dist/index.d.ts +9 -6
  157. package/dist/index.js +41 -25
  158. package/dist/init/agent-rules.d.ts +20 -1
  159. package/dist/init/agent-rules.js +64 -8
  160. package/dist/init/app-choice.d.ts +20 -0
  161. package/dist/init/app-choice.js +25 -0
  162. package/dist/init/astro-host.d.ts +21 -0
  163. package/dist/init/astro-host.js +27 -0
  164. package/dist/init/astro-patch.d.ts +16 -0
  165. package/dist/init/astro-patch.js +240 -0
  166. package/dist/init/capabilities.js +4 -1
  167. package/dist/init/cra.d.ts +50 -0
  168. package/dist/init/cra.js +106 -0
  169. package/dist/init/cursor.js +39 -4
  170. package/dist/init/desktop-doctor.d.ts +3 -2
  171. package/dist/init/desktop-doctor.js +4 -4
  172. package/dist/init/detect.d.ts +9 -2
  173. package/dist/init/detect.js +24 -5
  174. package/dist/init/mcp-clients.d.ts +81 -0
  175. package/dist/init/mcp-clients.js +248 -0
  176. package/dist/init/mcp-registered.d.ts +12 -0
  177. package/dist/init/mcp-registered.js +14 -0
  178. package/dist/init/mcp.d.ts +27 -2
  179. package/dist/init/mcp.js +40 -4
  180. package/dist/init/next-patch.d.ts +2 -4
  181. package/dist/init/next-patch.js +109 -19
  182. package/dist/init/node-io.js +29 -6
  183. package/dist/init/plan-framework.d.ts +16 -1
  184. package/dist/init/plan-framework.js +166 -8
  185. package/dist/init/plan.d.ts +85 -6
  186. package/dist/init/plan.js +196 -33
  187. package/dist/init/project-id.d.ts +5 -9
  188. package/dist/init/project-id.js +10 -22
  189. package/dist/init/run.d.ts +23 -1
  190. package/dist/init/run.js +338 -47
  191. package/dist/init/slash-command.d.ts +9 -0
  192. package/dist/init/slash-command.js +10 -1
  193. package/dist/init/snippets.d.ts +7 -10
  194. package/dist/init/snippets.js +44 -15
  195. package/dist/init/vite-config.js +1 -1
  196. package/dist/init/windows-quote.d.ts +38 -0
  197. package/dist/init/windows-quote.js +70 -0
  198. package/dist/init/workspace-apps.d.ts +23 -0
  199. package/dist/init/workspace-apps.js +68 -0
  200. package/dist/input/driven-redaction.js +1 -1
  201. package/dist/input/network-detail.js +8 -8
  202. package/dist/input/network-mock-tools.js +2 -2
  203. package/dist/input/network-mock.js +4 -4
  204. package/dist/input/real-input.js +8 -3
  205. package/dist/input/scroll-find.js +2 -2
  206. package/dist/input/scroll-tools.d.ts +0 -5
  207. package/dist/input/scroll-tools.js +11 -4
  208. package/dist/input/viewport-tools.js +2 -2
  209. package/dist/journal/ambient-store.d.ts +0 -2
  210. package/dist/journal/ambient-store.js +1 -1
  211. package/dist/journal/ambient.js +1 -1
  212. package/dist/journal/attach-journal.d.ts +2 -1
  213. package/dist/journal/deviation-report.js +6 -6
  214. package/dist/journal/deviation-service.js +1 -1
  215. package/dist/journal/envelope-store.d.ts +0 -2
  216. package/dist/journal/envelope-store.js +1 -1
  217. package/dist/journal/envelope.d.ts +2 -3
  218. package/dist/journal/envelope.js +3 -3
  219. package/dist/journal/journal-query.js +1 -1
  220. package/dist/journal/journal-recorder.d.ts +2 -1
  221. package/dist/journal/journal-recorder.js +2 -2
  222. package/dist/journal/rollups.js +9 -9
  223. package/dist/journal/session-end.d.ts +2 -1
  224. package/dist/journal/session-journal.js +4 -4
  225. package/dist/license/license.d.ts +0 -14
  226. package/dist/license/license.js +8 -8
  227. package/dist/log.d.ts +10 -0
  228. package/dist/log.js +11 -1
  229. package/dist/mcp/mcp-is-error.d.ts +13 -0
  230. package/dist/mcp/mcp-is-error.js +18 -0
  231. package/dist/mcp/mcp-outage.d.ts +32 -0
  232. package/dist/mcp/mcp-outage.js +55 -0
  233. package/dist/mcp/mcp-proxy.d.ts +134 -0
  234. package/dist/mcp/mcp-proxy.js +720 -0
  235. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  236. package/dist/{mcp.js → mcp/mcp.js} +139 -37
  237. package/dist/mcp/proxy-handshake.d.ts +3 -0
  238. package/dist/mcp/proxy-handshake.js +54 -0
  239. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  240. package/dist/mcp/proxy-lifecycle.js +70 -0
  241. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  242. package/dist/mcp/tool-catalog-cache.js +55 -0
  243. package/dist/oracles/propose-consequences.d.ts +2 -1
  244. package/dist/oracles/propose-consequences.js +16 -11
  245. package/dist/oracles/self-instrument.d.ts +2 -1
  246. package/dist/oracles/self-instrument.js +1 -1
  247. package/dist/phenomena/phenomena.js +3 -3
  248. package/dist/platform.d.ts +16 -0
  249. package/dist/platform.js +16 -0
  250. package/dist/pool/browser-pool.d.ts +9 -0
  251. package/dist/pool/browser-pool.js +38 -9
  252. package/dist/pool/playwright-launcher.d.ts +15 -2
  253. package/dist/pool/playwright-launcher.js +21 -2
  254. package/dist/project/fs-port.js +1 -1
  255. package/dist/project/project-tools.js +2 -2
  256. package/dist/runs/build-verification-run.js +1 -1
  257. package/dist/runs/repair-prompt.d.ts +0 -6
  258. package/dist/runs/repair-prompt.js +3 -3
  259. package/dist/runs/risk-classify.js +1 -1
  260. package/dist/runs/run-diff.d.ts +0 -2
  261. package/dist/runs/run-diff.js +3 -3
  262. package/dist/runs/run-tools.js +2 -2
  263. package/dist/runs/verification-sync.js +2 -2
  264. package/dist/runs/verify-http.js +1 -1
  265. package/dist/runs/verify-server.js +1 -1
  266. package/dist/session/attachment-history.d.ts +46 -0
  267. package/dist/session/attachment-history.js +65 -0
  268. package/dist/session/command-timeout.d.ts +11 -3
  269. package/dist/session/command-timeout.js +2 -2
  270. package/dist/session/control-envelope.d.ts +6 -0
  271. package/dist/session/control-envelope.js +7 -1
  272. package/dist/session/dev-server-probe.d.ts +13 -0
  273. package/dist/session/dev-server-probe.js +52 -0
  274. package/dist/session/last-act.d.ts +17 -3
  275. package/dist/session/last-act.js +16 -4
  276. package/dist/session/live-control-tools.js +7 -2
  277. package/dist/session/live-control.js +1 -1
  278. package/dist/session/looks-like-dev-server.d.ts +14 -0
  279. package/dist/session/looks-like-dev-server.js +25 -0
  280. package/dist/session/no-session-diagnosis.d.ts +45 -0
  281. package/dist/session/no-session-diagnosis.js +120 -0
  282. package/dist/session/no-session-watch.d.ts +21 -0
  283. package/dist/session/no-session-watch.js +69 -0
  284. package/dist/session/observed-state.d.ts +4 -0
  285. package/dist/session/observed-state.js +19 -1
  286. package/dist/session/output-budget.js +1 -1
  287. package/dist/session/pending-commands.js +1 -0
  288. package/dist/session/session-health.d.ts +8 -1
  289. package/dist/session/session-health.js +8 -7
  290. package/dist/session/session-info.d.ts +43 -8
  291. package/dist/session/session-info.js +46 -1
  292. package/dist/session/session-lease.d.ts +0 -11
  293. package/dist/session/session-lease.js +1 -1
  294. package/dist/session/session-manager.d.ts +6 -0
  295. package/dist/session/session-manager.js +124 -12
  296. package/dist/session/session-readiness.d.ts +1 -1
  297. package/dist/session/session-readiness.js +6 -1
  298. package/dist/session/session-reaper.js +1 -1
  299. package/dist/session/session-reconnect.d.ts +32 -0
  300. package/dist/session/session-reconnect.js +42 -0
  301. package/dist/session/session.d.ts +15 -2
  302. package/dist/session/session.js +77 -26
  303. package/dist/session/state-select.d.ts +1 -1
  304. package/dist/session/state-select.js +1 -1
  305. package/dist/status-payload.d.ts +19 -0
  306. package/dist/status-payload.js +11 -0
  307. package/dist/telemetry/argument-shape.js +10 -1
  308. package/dist/telemetry/browser-mode.d.ts +27 -0
  309. package/dist/telemetry/browser-mode.js +33 -0
  310. package/dist/telemetry/bug-found.d.ts +17 -21
  311. package/dist/telemetry/bug-found.js +120 -8
  312. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  313. package/dist/telemetry/cli-telemetry.js +27 -4
  314. package/dist/telemetry/crash-cause.d.ts +47 -0
  315. package/dist/telemetry/crash-cause.js +104 -0
  316. package/dist/telemetry/daemon-telemetry.d.ts +38 -1
  317. package/dist/telemetry/daemon-telemetry.js +37 -12
  318. package/dist/telemetry/dev-repo.js +4 -4
  319. package/dist/telemetry/error-class.d.ts +26 -0
  320. package/dist/telemetry/error-class.js +52 -0
  321. package/dist/telemetry/error-fingerprint.js +21 -2
  322. package/dist/telemetry/feedback-cli.d.ts +8 -3
  323. package/dist/telemetry/feedback-cli.js +52 -8
  324. package/dist/telemetry/feedback-context.d.ts +23 -6
  325. package/dist/telemetry/feedback-context.js +105 -5
  326. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  327. package/dist/telemetry/feedback-delivery.js +30 -0
  328. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  329. package/dist/telemetry/feedback-outbox.js +87 -0
  330. package/dist/telemetry/feedback.d.ts +22 -1
  331. package/dist/telemetry/feedback.js +67 -8
  332. package/dist/telemetry/git-facts.d.ts +7 -0
  333. package/dist/telemetry/git-facts.js +5 -5
  334. package/dist/telemetry/project-profile.js +2 -1
  335. package/dist/telemetry/session-metrics.d.ts +47 -3
  336. package/dist/telemetry/session-metrics.js +315 -15
  337. package/dist/telemetry/telemetry.d.ts +45 -2
  338. package/dist/telemetry/telemetry.js +150 -37
  339. package/dist/telemetry/verification-of.d.ts +22 -0
  340. package/dist/telemetry/verification-of.js +84 -0
  341. package/dist/tools/act-capsule.js +2 -2
  342. package/dist/tools/act-danger.d.ts +12 -0
  343. package/dist/tools/act-danger.js +20 -3
  344. package/dist/tools/act-helpers.js +4 -4
  345. package/dist/tools/act-tools.d.ts +0 -8
  346. package/dist/tools/act-tools.js +114 -118
  347. package/dist/tools/act-view.js +7 -7
  348. package/dist/tools/alias-args.d.ts +17 -0
  349. package/dist/tools/alias-args.js +25 -0
  350. package/dist/tools/assert-grade.js +9 -9
  351. package/dist/tools/assert-verdict.d.ts +9 -1
  352. package/dist/tools/assert-verdict.js +21 -6
  353. package/dist/tools/browser-tools.js +36 -11
  354. package/dist/tools/contract-tools.d.ts +0 -5
  355. package/dist/tools/contract-tools.js +27 -1
  356. package/dist/tools/coverage-identity.d.ts +34 -0
  357. package/dist/tools/coverage-identity.js +46 -0
  358. package/dist/tools/coverage-tools.js +10 -14
  359. package/dist/tools/dynamic-tools.d.ts +2 -1
  360. package/dist/tools/dynamic-tools.js +96 -19
  361. package/dist/tools/error-recovery.d.ts +34 -2
  362. package/dist/tools/error-recovery.js +255 -4
  363. package/dist/tools/feedback-invite.d.ts +48 -0
  364. package/dist/tools/feedback-invite.js +71 -0
  365. package/dist/tools/feedback-tools.d.ts +12 -1
  366. package/dist/tools/feedback-tools.js +28 -5
  367. package/dist/tools/invoke-tool.d.ts +0 -12
  368. package/dist/tools/invoke-tool.js +126 -33
  369. package/dist/tools/lease-hint.d.ts +17 -0
  370. package/dist/tools/lease-hint.js +24 -0
  371. package/dist/tools/lease-tools.js +6 -5
  372. package/dist/tools/merge-tools.d.ts +10 -1
  373. package/dist/tools/merge-tools.js +9 -3
  374. package/dist/tools/merged-name-redirect.d.ts +13 -0
  375. package/dist/tools/merged-name-redirect.js +55 -0
  376. package/dist/tools/navigate-arrival.d.ts +26 -0
  377. package/dist/tools/navigate-arrival.js +60 -0
  378. package/dist/tools/navigate-result.d.ts +31 -0
  379. package/dist/tools/navigate-result.js +38 -0
  380. package/dist/tools/observe-tools.js +72 -20
  381. package/dist/tools/observed-nothing.d.ts +25 -0
  382. package/dist/tools/observed-nothing.js +33 -0
  383. package/dist/tools/query-paginate.js +2 -2
  384. package/dist/tools/query-shape.d.ts +18 -0
  385. package/dist/tools/query-shape.js +39 -0
  386. package/dist/tools/read-tools.js +81 -20
  387. package/dist/tools/real-input-attempt.d.ts +28 -0
  388. package/dist/tools/real-input-attempt.js +93 -0
  389. package/dist/tools/reconcile-tools.js +7 -7
  390. package/dist/tools/reload-result.d.ts +14 -0
  391. package/dist/tools/reload-result.js +29 -0
  392. package/dist/tools/snapshot-delta.d.ts +4 -5
  393. package/dist/tools/snapshot-delta.js +0 -0
  394. package/dist/tools/tool-kit.d.ts +16 -0
  395. package/dist/tools/tool-surface.d.ts +83 -0
  396. package/dist/tools/tool-surface.js +217 -0
  397. package/dist/tools/tools-helpers.js +6 -6
  398. package/dist/tools/tools.d.ts +15 -0
  399. package/dist/tools/tools.js +53 -17
  400. package/dist/tools/unadvertised-help.d.ts +5 -0
  401. package/dist/tools/unadvertised-help.js +39 -0
  402. package/dist/trace.d.ts +69 -0
  403. package/dist/trace.js +150 -0
  404. package/dist/update/nudge-credit.d.ts +4 -0
  405. package/dist/update/nudge-credit.js +53 -0
  406. package/dist/update/sdk-sync.d.ts +21 -0
  407. package/dist/update/sdk-sync.js +42 -0
  408. package/dist/update/update-checker.js +3 -3
  409. package/dist/update/update-nudge.d.ts +63 -6
  410. package/dist/update/update-nudge.js +128 -15
  411. package/dist/update/updater.d.ts +1 -9
  412. package/dist/update/updater.js +13 -8
  413. package/dist/version/peer-announce.d.ts +4 -0
  414. package/dist/version/peer-announce.js +30 -0
  415. package/dist/{server-version.js → version/server-version.js} +5 -1
  416. package/dist/version/version-nudge.d.ts +28 -0
  417. package/dist/version/version-nudge.js +40 -0
  418. package/dist/version/version-skew.d.ts +66 -0
  419. package/dist/version/version-skew.js +71 -0
  420. package/dist/visual/visual-diff.js +2 -2
  421. package/dist/visual/visual-tools.js +4 -4
  422. package/docs/README.md +55 -0
  423. package/docs/agent-cheatsheet.md +85 -0
  424. package/docs/architecture.md +113 -0
  425. package/docs/benchmarks.md +255 -0
  426. package/docs/debugging.md +102 -0
  427. package/docs/deploy-checks.md +69 -0
  428. package/docs/desktop-apps.md +256 -0
  429. package/docs/enterprise.md +62 -0
  430. package/docs/fixtures-dispatch-receiver.yml +76 -0
  431. package/docs/fixtures.md +45 -0
  432. package/docs/flows.md +166 -0
  433. package/docs/gate-plan.md +98 -0
  434. package/docs/gates.md +117 -0
  435. package/docs/getting-started.md +469 -0
  436. package/docs/human-control.md +63 -0
  437. package/docs/integration-patterns.md +171 -0
  438. package/docs/local-registry.md +126 -0
  439. package/docs/matrix/2.5.0/claude-code-machine.json +19 -0
  440. package/docs/matrix/2.5.0/codex-machine.json +20 -0
  441. package/docs/matrix/2.5.0/cursor-machine.json +22 -0
  442. package/docs/matrix/2.5.0/gemini-machine.json +22 -0
  443. package/docs/matrix/2.5.0/opencode-machine.json +22 -0
  444. package/docs/matrix/2.5.0/vscode-machine.json +22 -0
  445. package/docs/matrix/2.5.0/windsurf-machine.json +22 -0
  446. package/docs/matrix/MATRIX.md +26 -0
  447. package/docs/matrix/README.md +59 -0
  448. package/docs/mint.json +61 -0
  449. package/docs/multi-agent-testing.md +55 -0
  450. package/docs/platform-integration.md +122 -0
  451. package/docs/system-map.md +152 -0
  452. package/docs/telemetry-contract.md +214 -0
  453. package/docs/telemetry.md +191 -0
  454. package/docs/testing.md +82 -0
  455. package/docs/token-efficiency.md +80 -0
  456. package/docs/usage.md +1046 -0
  457. package/package.json +8 -5
  458. package/dist/cli-doctor.d.ts +0 -6
  459. package/dist/cli-doctor.js +0 -60
  460. package/dist/cli-launch.js +0 -112
  461. package/dist/daemon-resilience.d.ts +0 -22
  462. package/dist/daemon-resilience.js +0 -85
  463. package/dist/mcp-proxy.d.ts +0 -67
  464. package/dist/mcp-proxy.js +0 -337
  465. package/dist/tools/profiles.d.ts +0 -37
  466. package/dist/tools/profiles.js +0 -139
  467. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  468. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  469. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  470. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  471. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  472. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -1,4 +1,8 @@
1
1
  import { z } from 'zod';
2
+ import { navigateResult } from './navigate-result.js';
3
+ import { awaitArrival } from './navigate-arrival.js';
4
+ import { reloadResult } from './reload-result.js';
5
+ import { waitForReconnect, RELOAD_RECONNECT_TIMEOUT_MS } from '../session/session-reconnect.js';
2
6
  import { ReticleCommand } from '@reticlehq/core';
3
7
  import { ReticleTool } from './tool-names.js';
4
8
  import { asString } from './tools-helpers.js';
@@ -7,7 +11,7 @@ export const BROWSER_TOOLS = [
7
11
  {
8
12
  name: ReticleTool.NAVIGATE,
9
13
  example: { url: '/settings' },
10
- description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). The SDK reconnects automatically after the page loads. Use reticle_sessions to confirm the new tab is connected before acting.',
14
+ description: 'Navigate the connected browser tab to a URL, or reload it in place with { reload: true } (add { hard: true } to bypass the cache). `ok` means the navigation was DISPATCHED — the SDK is torn down by the navigation, so the page itself cannot report on it. The daemon then waits briefly for the SDK to reconnect: `confirmed:true` with a new `sessionId` means the page arrived and you can act immediately. `confirmed:false` means it did not arrive within the window — the page may be slow, uninstrumented, or not there; check reticle_sessions before acting.',
11
15
  inputSchema: {
12
16
  url: z.string().optional().describe('The URL to navigate to. Omit when using reload.'),
13
17
  reload: z
@@ -24,17 +28,38 @@ export const BROWSER_TOOLS = [
24
28
  ok: z.boolean(),
25
29
  url: z.string().optional(),
26
30
  reason: z.string().optional(),
31
+ // Present on a dispatched navigation: nothing here can see the new document, so arrival is
32
+ // reported as unconfirmed rather than implied by `ok`. See navigate-result.ts.
33
+ confirmed: z.boolean().optional(),
34
+ /** The session the SDK reconnected as, when arrival was confirmed — it is a NEW id. */
35
+ sessionId: z.string().optional(),
36
+ note: z.string().optional(),
27
37
  },
28
38
  handler: async (deps, args) => {
29
39
  // reload:true is the absorbed reticle_refresh — same command, one fewer advertised tool.
30
- if (args['reload'] === true) {
40
+ if (true === args['reload']) {
41
+ const before = deps.sessions.resolve(asString(args['sessionId']));
31
42
  await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
32
- hard: args['hard'] === true,
43
+ hard: true === args['hard'],
33
44
  });
34
- return { ok: true };
45
+ // WAIT for the page to come back, rather than telling the agent to. The id survives the
46
+ // reload, but the seconds between dispatch and the new HELLO are seconds in which every call
47
+ // lands in the old, disconnected session — measured as reticle_run failing 5 of 5 on a page
48
+ // that was healthy immediately afterwards. Returns on the first poll in the common case.
49
+ const back = await waitForReconnect({
50
+ current: () => deps.sessions.get(before.id),
51
+ previous: before,
52
+ timeoutMs: RELOAD_RECONNECT_TIMEOUT_MS,
53
+ now: deps.now,
54
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
55
+ });
56
+ // Not a bare `{ ok: true }`. The URL branch below already discloses that `ok` means
57
+ // DISPATCHED — the reload branch had identical semantics and said nothing, on the path most
58
+ // likely to need it. See reload-result.
59
+ return reloadResult(back);
35
60
  }
36
61
  const url = asString(args['url']);
37
- if (url === undefined || url.length === 0)
62
+ if (url === undefined || 0 === url.length)
38
63
  return { ok: false, reason: 'url required' };
39
64
  // Record navigate as an action. Its window is usually empty (the page unloads and the SDK
40
65
  // reconnects), but the action record itself — "navigated to X" — is the causal fact worth keeping.
@@ -42,11 +67,11 @@ export const BROWSER_TOOLS = [
42
67
  session.beginAction(ReticleTool.NAVIGATE, { url });
43
68
  try {
44
69
  const result = (await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.NAVIGATE, { url }));
45
- return {
46
- ok: result.ok === true,
47
- ...(typeof result.url === 'string' ? { url: result.url } : {}),
48
- ...(typeof result.reason === 'string' ? { reason: result.reason } : {}),
49
- };
70
+ // `ok` is the browser accepting the instruction, not the page arriving — see navigate-result.
71
+ // The daemon is the only party that CAN see arrival (the SDK reconnects to it), so it looks,
72
+ // briefly, instead of telling the agent to go poll reticle_sessions itself.
73
+ const arrival = true === result.ok ? await awaitArrival(deps.sessions, url) : null;
74
+ return navigateResult(result, arrival);
50
75
  }
51
76
  finally {
52
77
  session.finishAction();
@@ -68,7 +93,7 @@ export const BROWSER_TOOLS = [
68
93
  },
69
94
  handler: async (deps, args) => {
70
95
  await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.REFRESH, {
71
- hard: args['hard'] === true,
96
+ hard: true === args['hard'],
72
97
  });
73
98
  return { ok: true };
74
99
  },
@@ -1,7 +1,2 @@
1
1
  import type { ToolDef } from './tools.js';
2
- /**
3
- * The capability-contract tools. `reticle_capabilities` reads the live session, or the
4
- * git-checked `.reticle/contract.json` when `{ fromDisk:true }`; `reticle_contract_save` persists the
5
- * live registry to that file (pretty-printed, stable key order — diffable in PRs).
6
- */
7
2
  export declare const CONTRACT_TOOLS: ToolDef[];
@@ -9,6 +9,31 @@ import { reticleDirPaths, readContract, writeContract } from '../project/reticle
9
9
  * git-checked `.reticle/contract.json` when `{ fromDisk:true }`; `reticle_contract_save` persists the
10
10
  * live registry to that file (pretty-printed, stable key order — diffable in PRs).
11
11
  */
12
+ /**
13
+ * Refuse to persist one project's surface into another project's repo.
14
+ *
15
+ * `writeContract` writes to the DAEMON's `.reticle/`, which is the directory the daemon was started
16
+ * in — not the session's project. A daemon started above several apps therefore saves app A's
17
+ * testids into app B's checkout, reports `saved: true`, and hands back a path that looks right. A
18
+ * contract is git-checked and diffable, so that is a change somebody commits.
19
+ *
20
+ * Refuse rather than redirect: a session advertises a projectId, not a project DIRECTORY, so there
21
+ * is no honest way to work out where the correct `.reticle/` lives. Naming both ids and stopping is
22
+ * the whole of what can be said truthfully.
23
+ *
24
+ * Silence is not a mismatch. An unstamped build carries no projectId, and a daemon above an
25
+ * uninitialised directory has none either; neither is evidence of two projects.
26
+ */
27
+ function assertSameProject(deps, sessionId) {
28
+ const mine = deps.projectId;
29
+ const theirs = deps.sessions.resolve(sessionId).projectId;
30
+ if (mine === undefined || theirs === undefined || mine === theirs)
31
+ return;
32
+ throw new Error(`refusing to save: this session belongs to project '${theirs}', but this daemon writes to ` +
33
+ `project '${mine}' (.reticle/ in the directory it was started in). Saving would put one ` +
34
+ "app's testable surface into another app's git-checked contract. Run a daemon from " +
35
+ `'${theirs}'s own directory and save there.`);
36
+ }
12
37
  export const CONTRACT_TOOLS = [
13
38
  {
14
39
  name: ReticleTool.CAPABILITIES,
@@ -29,7 +54,7 @@ export const CONTRACT_TOOLS = [
29
54
  governance: z.unknown().optional(),
30
55
  },
31
56
  handler: async (deps, args) => {
32
- if (args[FROM_DISK_ARG] === true) {
57
+ if (true === args[FROM_DISK_ARG]) {
33
58
  const r = await readContract(deps.fs, deps.reticleRoot);
34
59
  if (!r.ok)
35
60
  throw new Error(r.reason === ContractReadError.MISSING
@@ -54,6 +79,7 @@ export const CONTRACT_TOOLS = [
54
79
  handler: async (deps, args) => {
55
80
  const res = await commandOrThrow(deps, asString(args['sessionId']), ReticleCommand.CAPABILITIES, {});
56
81
  const caps = CapabilitiesSchema.parse(res);
82
+ assertSameProject(deps, asString(args['sessionId']));
57
83
  await writeContract(deps.fs, deps.reticleRoot, caps, deps.now);
58
84
  return {
59
85
  saved: true,
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Which controls has the agent actually driven — across re-renders.
3
+ *
4
+ * Coverage was counted by REF, and a ref is invalidated whenever the DOM changes. Reported from a
5
+ * sweep of a Next app-router project: `total: 34, exercised: 0` after four successful acts. App
6
+ * router replaces a whole route segment on navigation, so every control the agent drove is a new
7
+ * element by the time coverage is asked. The number is then 0 forever on any framework that replaces
8
+ * nodes rather than reconciling in place — and an agent told it has exercised none of 34 controls
9
+ * re-drives ground it already covered, which is the exact opposite of what this tool is for.
10
+ *
11
+ * Bench-app hid it: it reconciles in place, so refs survive there and the number looked right.
12
+ *
13
+ * The LABEL is the identity that survives a re-render — `button "Deploy"` names the same control
14
+ * before and after — and it is already parsed for the untouched list.
15
+ *
16
+ * One caveat, stated because it is a real limitation: two controls sharing a label are
17
+ * indistinguishable here, so driving one marks both. That errs toward "covered", which is the
18
+ * unsafe direction for a coverage number — but it is bounded to same-labelled controls, and the
19
+ * alternative it replaces reported ZERO coverage on entire frameworks.
20
+ */
21
+ interface CoverageControl {
22
+ ref: string;
23
+ label: string;
24
+ }
25
+ interface CoverageTally {
26
+ /** Controls on the page the agent has driven, by ref or by surviving label. */
27
+ exercised: number;
28
+ /** Controls the agent drove that are on the page no longer, and no label explains. */
29
+ droveGone: number;
30
+ /** The controls still untouched, in document order. */
31
+ untouched: CoverageControl[];
32
+ }
33
+ export declare function exercisedCount(controls: readonly CoverageControl[], actedRefs: ReadonlySet<string>, actedLabels: ReadonlySet<string>): CoverageTally;
34
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Which controls has the agent actually driven — across re-renders.
3
+ *
4
+ * Coverage was counted by REF, and a ref is invalidated whenever the DOM changes. Reported from a
5
+ * sweep of a Next app-router project: `total: 34, exercised: 0` after four successful acts. App
6
+ * router replaces a whole route segment on navigation, so every control the agent drove is a new
7
+ * element by the time coverage is asked. The number is then 0 forever on any framework that replaces
8
+ * nodes rather than reconciling in place — and an agent told it has exercised none of 34 controls
9
+ * re-drives ground it already covered, which is the exact opposite of what this tool is for.
10
+ *
11
+ * Bench-app hid it: it reconciles in place, so refs survive there and the number looked right.
12
+ *
13
+ * The LABEL is the identity that survives a re-render — `button "Deploy"` names the same control
14
+ * before and after — and it is already parsed for the untouched list.
15
+ *
16
+ * One caveat, stated because it is a real limitation: two controls sharing a label are
17
+ * indistinguishable here, so driving one marks both. That errs toward "covered", which is the
18
+ * unsafe direction for a coverage number — but it is bounded to same-labelled controls, and the
19
+ * alternative it replaces reported ZERO coverage on entire frameworks.
20
+ */
21
+ export function exercisedCount(controls, actedRefs, actedLabels) {
22
+ // An empty label is not an identity — anonymous controls would otherwise collapse into one bucket
23
+ // and a single act would mark every one of them driven.
24
+ // Matched on the whole label (`button "Save"`) OR on a testid the label contains. The snapshot
25
+ // renders a control's testid inside its label, and a testid is the identity most likely to survive
26
+ // a re-render — matching only the full label meant a control with a testid but no accessible name
27
+ // was never recognised, and coverage read `exercised: 0` after real work.
28
+ const droveLabel = (label) => {
29
+ if (0 === label.length)
30
+ return false;
31
+ if (actedLabels.has(label))
32
+ return true;
33
+ for (const acted of actedLabels) {
34
+ if (acted.length > 0 && !acted.includes('"') && label.includes(acted))
35
+ return true;
36
+ }
37
+ return false;
38
+ };
39
+ const untouched = controls.filter((control) => !actedRefs.has(control.ref) && !droveLabel(control.label));
40
+ // A driven ref that is not on the page is only "gone" if no present control carries its label —
41
+ // otherwise it is the same control after a re-render, which is the case this exists to fix.
42
+ const presentRefs = new Set(controls.map((control) => control.ref));
43
+ const explainedByLabel = new Set(controls.filter((control) => droveLabel(control.label)).map((control) => control.label));
44
+ const droveGone = [...actedRefs].filter((ref) => !presentRefs.has(ref) && 0 === explainedByLabel.size).length;
45
+ return { exercised: controls.length - untouched.length, droveGone, untouched };
46
+ }
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { ReticleCommand, SnapshotMode } from '@reticlehq/core';
3
3
  import { ReticleTool } from './tool-names.js';
4
4
  import { asString } from './tools-helpers.js';
5
+ import { exercisedCount } from './coverage-identity.js';
5
6
  import { commandOrThrow, sessionIdShape } from './tool-kit.js';
6
7
  /**
7
8
  * `reticle_coverage` — which interactive controls this session has driven, and which it has not.
@@ -28,7 +29,7 @@ export function parseControls(tree) {
28
29
  const seen = new Set();
29
30
  for (const line of tree.split('\n')) {
30
31
  const match = LINE_WITH_REF.exec(line);
31
- if (match === null)
32
+ if (null === match)
32
33
  continue;
33
34
  const [, label, ref] = match;
34
35
  if (label === undefined || ref === undefined || seen.has(ref))
@@ -71,21 +72,16 @@ export const COVERAGE_TOOLS = [
71
72
  mode: SnapshotMode.INTERACTIVE,
72
73
  });
73
74
  const tree = asString(result['tree']) ?? '';
74
- const acted = session.actedRefs();
75
- const controls = parseControls(tree);
76
- const present = new Set(controls.map((control) => control.ref));
77
- const untouched = controls.filter((control) => !acted.has(control.ref));
78
- // Drives that landed on controls no longer in the DOM. Without this the number is biased
79
- // against the actions that WORKED: archive, delete, submit and navigate all remove their own
80
- // control, so clicking one and asking again reported `exercised: 0` — measured on the Electron
81
- // demo right after a successful archive. An agent reading "0" concludes it has done nothing
82
- // and re-drives ground it already covered.
83
- const drovenGone = [...acted].filter((ref) => !present.has(ref)).length;
75
+ // Matched by ref AND by label — see coverage-identity. A ref dies with the next re-render, so
76
+ // on a framework that replaces nodes this reported `exercised: 0` however much work was done.
77
+ // `droveGone` keeps the other honesty: archive/delete/submit remove their own control, so a
78
+ // drive that WORKED must not read as no coverage at all.
79
+ const { exercised, droveGone, untouched } = exercisedCount(parseControls(tree), session.actedRefs(), session.actedLabels());
84
80
  return {
85
- total: controls.length,
86
- exercised: controls.length - untouched.length,
81
+ total: parseControls(tree).length,
82
+ exercised,
87
83
  untouched,
88
- ...(drovenGone > 0 ? { alsoDroveGone: drovenGone } : {}),
84
+ ...(droveGone > 0 ? { alsoDroveGone: droveGone } : {}),
89
85
  };
90
86
  },
91
87
  },
@@ -1,6 +1,7 @@
1
1
  import type { ToolDef } from './tools.js';
2
+ import { type ToolSurfaceOrigin } from './tool-surface.js';
2
3
  /**
3
4
  * Build the two dynamic meta-tools over the full tool table. `reticle_run` dispatches through the same
4
5
  * `runTool` chokepoint as a direct call, so session-health splicing and every other invariant hold.
5
6
  */
6
- export declare function buildDynamicTools(allTools: ToolDef[]): ToolDef[];
7
+ export declare function buildDynamicTools(allTools: ToolDef[], profile?: ToolSurfaceOrigin): ToolDef[];
@@ -1,6 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { runTool } from './invoke-tool.js';
3
+ import { buildErrorPayload } from './error-recovery.js';
4
+ import { mergedNameRedirect, mergedNameMessage } from './merged-name-redirect.js';
3
5
  import { ReticleTool } from './tool-names.js';
6
+ import { TOOL_PROFILE_ENV } from './tool-surface.js';
4
7
  import { getSessionMetrics } from '../telemetry/session-metrics.js';
5
8
  /**
6
9
  * On-demand tool loading for MCP — the answer to the per-turn tool-definition tax.
@@ -36,12 +39,39 @@ function paramInfo(shape) {
36
39
  description: schema.description ?? '',
37
40
  }));
38
41
  }
42
+ /**
43
+ * The one wording for "you named a parameter that does not exist", shared by both checks in
44
+ * `reticle_run` — the one over the WRAPPED tool's `args`, and the one over reticle_run's own
45
+ * top-level keys. Two spellings of the same refusal is how one of them ends up not existing.
46
+ */
47
+ function unknownParamsError(toolName, unknown) {
48
+ return `unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')} — NOT applied, so any result would be an answer to a different question`;
49
+ }
50
+ /** The keys not declared by `shape`, in call order. */
51
+ function unknownKeys(args, shape) {
52
+ const declared = new Set(Object.keys(shape));
53
+ return Object.keys(args).filter((key) => !declared.has(key));
54
+ }
39
55
  /**
40
56
  * Build the two dynamic meta-tools over the full tool table. `reticle_run` dispatches through the same
41
57
  * `runTool` chokepoint as a direct call, so session-health splicing and every other invariant hold.
42
58
  */
43
- export function buildDynamicTools(allTools) {
59
+ export function buildDynamicTools(allTools, profile) {
44
60
  const byName = new Map(allTools.map((t) => [t.name, t]));
61
+ // The profile is a DAEMON-startup decision, so an agent that exported RETICLE_TOOL_PROFILE into its
62
+ // own environment sees no change and has, until now, no way to tell. Reported with the catalog.
63
+ const profileBlock = profile === undefined
64
+ ? {}
65
+ : {
66
+ profile: {
67
+ ...profile,
68
+ // NOT "every tool is reachable via reticle_run under any profile" — measured false:
69
+ // `full` advertises all 46 tools DIRECTLY and carries no meta-tools at all, so
70
+ // reticle_run does not exist there. Saying otherwise sends the agent to a tool the
71
+ // profile does not have.
72
+ note: `The profile is read once at daemon startup — change ${TOOL_PROFILE_ENV} and restart the daemon, or it has no effect. Under this profile every unadvertised tool is reachable through reticle_run; under \`full\` there are no meta-tools because every tool is advertised directly.`,
73
+ },
74
+ };
45
75
  const reticleTools = {
46
76
  name: ReticleTool.TOOLS,
47
77
  description: 'Discover Reticle tools on demand. Call with no arguments to list every tool (name + one-line summary); call with names:["reticle_network", …] to load full descriptions and parameters for specific tools. Then invoke them with reticle_run. This avoids paying for every tool definition on every turn. To make a verification REUSABLE (record once, replay free forever), the flow workflow lives here: reticle_record{action:"start"} → act → reticle_flow_save → reticle_flow_verify (and reticle_flow_heal on drift). Load those names when you want to save or re-run a flow.',
@@ -53,11 +83,12 @@ export function buildDynamicTools(allTools) {
53
83
  },
54
84
  handler: (_deps, args) => {
55
85
  const names = Array.isArray(args['names'])
56
- ? args['names'].filter((n) => typeof n === 'string')
86
+ ? args['names'].filter((n) => 'string' === typeof n)
57
87
  : undefined;
58
- if (names === undefined || names.length === 0) {
88
+ if (names === undefined || 0 === names.length) {
59
89
  return Promise.resolve({
60
90
  tools: allTools.map((t) => ({ name: t.name, summary: firstSentence(t.description) })),
91
+ ...profileBlock,
61
92
  next: 'Load params with reticle_tools { names:[…] }, then call reticle_run { tool, args }.',
62
93
  });
63
94
  }
@@ -71,35 +102,81 @@ export function buildDynamicTools(allTools) {
71
102
  });
72
103
  },
73
104
  };
105
+ const runShape = {
106
+ tool: z.string().describe('Tool name to invoke, e.g. reticle_network.'),
107
+ args: z.record(z.unknown()).optional().describe('Arguments object for that tool.'),
108
+ // Accepted AND FORWARDED, not merely tolerated. reticle_run is the only way to reach an
109
+ // unadvertised tool, so on a machine running several projects it has to be aimable — and
110
+ // `sessionId` is the shape an agent already uses on every other tool. Reported across 6 of 6
111
+ // apps: it took this key, dropped it, resolved by the daemon's cwd project, and failed with
112
+ // "no browser session for project X" while naming the very session it had been given.
113
+ sessionId: z
114
+ .string()
115
+ .optional()
116
+ .describe('Target tab for the invoked tool. Forwarded to it — an explicit args.sessionId wins.'),
117
+ };
74
118
  const reticleRun = {
75
119
  name: ReticleTool.RUN,
76
120
  description: "Invoke any Reticle tool by name (discover names/params first with reticle_tools). On an unknown tool or bad arguments it returns the available names or the tool's params, so you can correct and retry.",
77
- inputSchema: {
78
- tool: z.string().describe('Tool name to invoke, e.g. reticle_network.'),
79
- args: z.record(z.unknown()).optional().describe('Arguments object for that tool.'),
80
- },
121
+ inputSchema: runShape,
81
122
  handler: async (deps, args) => {
82
- const name = typeof args['tool'] === 'string' ? args['tool'] : '';
83
- const callArgs = typeof args['args'] === 'object' && args['args'] !== null
123
+ // reticle_run's OWN parameters, checked first and with the same wording as the inner check.
124
+ // It refused an unknown key inside `args` and silently dropped one beside them: an agent that
125
+ // wrote `reticle_run { tool, args, sessionId }` — the shape it uses on every other tool — had
126
+ // its sessionId ignored and got a confident answer about whichever session auto-selection
127
+ // picked. Every other tool on the surface refuses this; the escape hatch has to as well.
128
+ const strayTop = unknownKeys(args, runShape);
129
+ if (strayTop.length > 0) {
130
+ return {
131
+ error: unknownParamsError(ReticleTool.RUN, strayTop),
132
+ hint: `pass them inside args: reticle_run { tool, args: { ${strayTop.join(', ')}: … } } if the target tool declares them`,
133
+ };
134
+ }
135
+ const name = 'string' === typeof args['tool'] ? args['tool'] : '';
136
+ const given = 'object' === typeof args['args'] && args['args'] !== null
84
137
  ? args['args']
85
138
  : {};
139
+ const aimed = args['sessionId'];
86
140
  const target = byName.get(name);
87
141
  if (target === undefined) {
88
142
  // A non-zero count here means our advertised surface is confusing the agent — it reached for
89
143
  // something it believed existed. That is a docs/naming defect, and it was invisible.
90
- getSessionMetrics().recordUnknownTool();
144
+ //
145
+ // The NAME goes with it: the count says the surface confused someone, the name says which
146
+ // capability they expected. It was already in hand here and thrown away, so the one place
147
+ // the product could learn what agents want was discarding it. Safe — a name from our own
148
+ // tool namespace, never app data.
149
+ getSessionMetrics().recordUnknownTool(name);
150
+ // ...and for 22 of those names the capability DOES exist, it just moved when tools merged.
151
+ // Answering "unknown tool" there is simply wrong; see merged-name-redirect.
152
+ const moved = mergedNameRedirect(name);
153
+ if (moved !== undefined) {
154
+ return {
155
+ error: mergedNameMessage(name, moved),
156
+ tool: moved.tool,
157
+ ...(moved.action === undefined ? {} : { action: moved.action }),
158
+ };
159
+ }
91
160
  return { error: `unknown tool '${name}'`, available: allTools.map((t) => t.name) };
92
161
  }
162
+ // The outer aim, forwarded ONLY to a tool that declares a session — injecting it into one that
163
+ // does not would trip the unknown-key check below and refuse a call the caller got right. An
164
+ // inner args.sessionId wins as the more specific instruction, and an absent aim stays absent so
165
+ // auto-selection still applies.
166
+ const callArgs = 'string' === typeof aimed &&
167
+ given['sessionId'] === undefined &&
168
+ undefined !== target.inputSchema['sessionId']
169
+ ? { ...given, sessionId: aimed }
170
+ : given;
93
171
  // The escape hatch is where a typo is MOST likely, because an unadvertised tool's parameters
94
172
  // are not in front of the agent at all — and it is the one path the SDK's own validation never
95
173
  // sees, since `reticle_run`'s own args (`tool`, `args`) are perfectly valid. Left unchecked,
96
174
  // `reticle_run { tool: "reticle_clock", args: { action: "freeze" } }` returned
97
175
  // `{"frozen":false}`: a well-formed answer to a question nobody asked.
98
- const declared = new Set(Object.keys(target.inputSchema));
99
- const unknown = Object.keys(callArgs).filter((key) => !declared.has(key));
176
+ const unknown = unknownKeys(callArgs, target.inputSchema);
100
177
  if (unknown.length > 0) {
101
178
  return {
102
- error: `unknown ${unknown.length === 1 ? 'parameter' : 'parameters'} for ${name}: ${unknown.join(', ')} — NOT applied, so any result would be an answer to a different question`,
179
+ error: unknownParamsError(name, unknown),
103
180
  tool: name,
104
181
  params: paramInfo(target.inputSchema),
105
182
  ...(target.example === undefined ? {} : { example: target.example }),
@@ -110,12 +187,12 @@ export function buildDynamicTools(allTools) {
110
187
  return await runTool(target, deps, callArgs);
111
188
  }
112
189
  catch (error) {
113
- return {
114
- error: error instanceof Error ? error.message : String(error),
115
- tool: name,
116
- params: paramInfo(target.inputSchema),
117
- hint: 'fix the arguments and call reticle_run again',
118
- };
190
+ // The SAME payload the direct tool path builds — recovery included. Answering every failure
191
+ // with "fix the arguments" threw that away, and under the default profile nearly every tool
192
+ // is reached through here: a stale ref, a paused session, a missing pairing token all came
193
+ // back as the agent's arguments being wrong, which is advice that spends the retry.
194
+ const message = error instanceof Error ? error.message : String(error);
195
+ return { ...buildErrorPayload(message), tool: name };
119
196
  }
120
197
  },
121
198
  };
@@ -17,11 +17,43 @@ export declare const RECOVERY: {
17
17
  readonly MISSING_BASELINE: string;
18
18
  readonly MISSING_RECORDING: string;
19
19
  readonly STALE_REF: string;
20
+ readonly INVALID_NAME: string;
21
+ readonly NO_SUCH_OPTION: string;
20
22
  readonly BAD_ARGUMENTS: string;
23
+ readonly WRONG_TARGET: string;
24
+ readonly NOT_EDITABLE: string;
25
+ readonly CONFIRM_DANGEROUS: string;
26
+ readonly UNSUPPORTED_SURFACE: string;
21
27
  readonly TOKEN_REQUIRED: string;
28
+ readonly SCOPE_MISMATCH: string;
29
+ readonly SESSION_GONE: string;
30
+ readonly COMMAND_TIMEOUT: string;
31
+ readonly FLOW_STEP_MISSING: string;
32
+ readonly NO_POOL: string;
22
33
  };
23
- /** The actionable next move for a known error message, or undefined when none is recognized. */
34
+ /**
35
+ * The actionable next move for a known error message, or undefined when none is recognized.
36
+ *
37
+ * A message that already carries its own next action gets NO second one. The no-session diagnosis
38
+ * inspects the machine and says which of three causes this actually is; pairing that with the
39
+ * generic "ask the human to start their app" produced a result that contradicted itself — the error
40
+ * saying a server is running and the recovery saying to go start one.
41
+ */
24
42
  export declare function recoveryFor(message: string): string | undefined;
43
+ /**
44
+ * A serialized zod issue array, rendered as a sentence. Anything else is returned untouched.
45
+ *
46
+ * This is the one boundary every agent-visible tool failure crosses, which is why the rewrite lives
47
+ * here rather than in each tool. `parsePredicate` solved this for the three predicate tools by never
48
+ * producing the array; it cannot help anywhere else, because the MCP SDK validates a tool's schema
49
+ * BEFORE our handler runs. So `reticle_session` with no action — the commonest mistake there is —
50
+ * never reaches the friendly "unknown action … expected: [tune, yield, …]" its handler already
51
+ * builds, and the agent gets the raw array instead. Driven live against the shipped daemon.
52
+ *
53
+ * Conservative by construction: it only rewrites a JSON ARRAY whose every element carries both
54
+ * `code` and `message`. A tool that legitimately returns JSON is left alone.
55
+ */
56
+ export declare function zodArrayAsSentence(message: string): string;
25
57
  /**
26
58
  * The ask attached to errors we do NOT recognize. An unrecognized error is, by definition, the case
27
59
  * where Reticle failed in a way nobody anticipated — the single highest-value moment to hear from the
@@ -43,5 +75,5 @@ interface ErrorPayload {
43
75
  * known; the feedback ask is added when it is NOT — the two are mutually exclusive by design, so the
44
76
  * agent always gets exactly one next move.
45
77
  */
46
- export declare function buildErrorPayload(message: string): ErrorPayload;
78
+ export declare function buildErrorPayload(rawMessage: string): ErrorPayload;
47
79
  export {};