@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,8 +1,8 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { z } from 'zod';
3
- import { type ToolDeps } from './tools/tools.js';
4
- import type { ToolDef } from './tools/tools.js';
5
- import { type ToolProfile } from './tools/profiles.js';
3
+ import { type ToolDeps } from '../tools/tools.js';
4
+ import type { ToolDef } from '../tools/tools.js';
5
+ import { type ToolSurface } from '../tools/tool-surface.js';
6
6
  /**
7
7
  * Merge the runtime-spliced envelope (health/lease/age/control) into a session-bound tool's declared
8
8
  * outputSchema so structuredContent validation keeps those fields instead of dropping them. The tool's
@@ -27,12 +27,15 @@ export declare function encodeResult(result: unknown, encoding: string): string;
27
27
  * therefore insensitive to it — every profile appeared to reach every tool, so reverting the fix here
28
28
  * left the suite green. Exercise the real function or the guard is theatre.
29
29
  *
30
- * Every TRIMMED profile keeps the meta-tools, so a tool that is not advertised is still reachable via
31
- * reticle_run. Without them a trim is not a trim but a hard removal: under `standard`, 11 tools were
32
- * uncallable — including reticle_annotate, which reticle_flow_save's own description tells the agent to
33
- * call. `full` advertises everything and needs no hatch.
30
+ * Both surfaces keep the meta-tools. On the default that is what makes a trim a trim rather than a
31
+ * hard removal — an unadvertised tool is still reachable through reticle_run; under the old
32
+ * `standard` profile 11 tools were uncallable, including reticle_annotate, which reticle_flow_save's
33
+ * own description tells the agent to call. On ALL they are kept too: `reticle_tools` is not an
34
+ * escape hatch there, it is parameter lookup, and every recovery message this server emits says
35
+ * "Call reticle_tools { names: [...] } for its parameters" — leaving it out made our own advice a
36
+ * dead end on the one surface that advertises everything.
34
37
  */
35
- export declare function advertisedTools(profile: ToolProfile): ToolDef[];
38
+ export declare function advertisedTools(surface: ToolSurface): ToolDef[];
36
39
  /**
37
40
  * Exactly what gets advertised for one tool under a profile: trimmed description, lean input shape,
38
41
  * and (non-lean only) the output schema.
@@ -41,10 +44,10 @@ export declare function advertisedTools(profile: ToolProfile): ToolDef[];
41
44
  * are trimmed later and would make a guard that cannot fail — a mistake already made once here, with
42
45
  * an abbreviation test that read raw defs and would have passed with the bug fully present.
43
46
  */
44
- export declare function advertisedConfig(tool: ToolDef, advertised: readonly ToolDef[], profile: ToolProfile): {
47
+ export declare function advertisedConfig(tool: ToolDef, advertised: readonly ToolDef[], profile: ToolSurface): {
45
48
  description: string;
46
49
  inputSchema: z.ZodRawShape;
47
50
  outputSchema?: z.ZodRawShape;
48
51
  };
49
52
  export declare function installFriendlyArgErrors(server: McpServer, examples: Map<string, string>, allowedKeys?: Map<string, ReadonlySet<string>>): void;
50
- export declare function createMcpServer(deps: ToolDeps, profile?: ToolProfile): McpServer;
53
+ export declare function createMcpServer(deps: ToolDeps, profile?: ToolSurface): McpServer;
@@ -1,18 +1,23 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { z } from 'zod';
4
+ import { PredicateSchema } from '../events/predicate.js';
4
5
  import { isToonable, resultToToon } from '@reticlehq/core';
5
- import { TOOLS } from './tools/tools.js';
6
- import { filterTools, TOOL_PROFILE } from './tools/profiles.js';
7
- import { buildDynamicTools } from './tools/dynamic-tools.js';
8
- import { runTool, SESSION_BOUND_TOOLS } from './tools/invoke-tool.js';
9
- import { sessionEnvelopeShape } from './tools/tool-kit.js';
10
- import { buildErrorPayload } from './tools/error-recovery.js';
11
- import { log } from './log.js';
12
- import { SERVER_VERSION } from './server-version.js';
13
- import { MCP_SERVER_NAME } from './init/mcp.js';
14
- import { setMcpClientNameHook } from './telemetry/feedback-context.js';
15
- import { getSessionMetrics } from './telemetry/session-metrics.js';
6
+ import { TOOLS } from '../tools/tools.js';
7
+ import { filterTools, describeToolSurface, TOOL_SURFACE, } from '../tools/tool-surface.js';
8
+ import { buildDynamicTools } from '../tools/dynamic-tools.js';
9
+ import { runTool, SESSION_BOUND_TOOLS } from '../tools/invoke-tool.js';
10
+ import { sessionEnvelopeShape } from '../tools/tool-kit.js';
11
+ import { buildErrorPayload } from '../tools/error-recovery.js';
12
+ import { resultIsError } from './mcp-is-error.js';
13
+ import { unadvertisedToolHelp } from '../tools/unadvertised-help.js';
14
+ /** The JSON-RPC method the SDK registers its tool dispatcher under. */
15
+ const CALL_TOOL_METHOD = 'tools/call';
16
+ import { log } from '../log.js';
17
+ import { SERVER_VERSION } from '../version/server-version.js';
18
+ import { MCP_SERVER_NAME } from '../init/mcp.js';
19
+ import { setMcpClientNameHook } from '../telemetry/feedback-context.js';
20
+ import { getSessionMetrics } from '../telemetry/session-metrics.js';
16
21
  /**
17
22
  * Merge the runtime-spliced envelope (health/lease/age/control) into a session-bound tool's declared
18
23
  * outputSchema so structuredContent validation keeps those fields instead of dropping them. The tool's
@@ -25,6 +30,27 @@ export function withSessionEnvelope(name, outputSchema) {
25
30
  return { ...sessionEnvelopeShape, ...outputSchema };
26
31
  }
27
32
  const SERVER_INFO = { name: MCP_SERVER_NAME, version: SERVER_VERSION };
33
+ /**
34
+ * What every agent is told at `initialize`, before it has called a single tool.
35
+ *
36
+ * This is the only channel that reaches an agent with NO dependency on the project: no CLAUDE.md, no
37
+ * AGENTS.md, no pasted skill, no successful `reticle init`. An agent whose setup half-worked has read
38
+ * none of those files and is precisely the agent whose report we need — so the standing instruction
39
+ * to send feedback has to live here, where it arrives with the connection itself.
40
+ *
41
+ * Kept to a handful of lines: it is prepended to every session's context, so each sentence is paid
42
+ * for on every turn, forever.
43
+ */
44
+ const SERVER_INSTRUCTIONS = `Reticle verifies a running web app from the inside: look (reticle_snapshot / reticle_query), act and prove in one hop (reticle_act_and_wait), observe (reticle_state / reticle_network / reticle_console), assert (reticle_assert). Verify a user-facing change against the real app before you call it done, and never weaken a check to make it pass.
45
+
46
+ Only reticle_act_and_wait and reticle_assert produce a verdict. reticle_act and everything else move or read the app and prove nothing, so a drive that ends without one of those two has no result however many tools it used. Prefer reticle_act_and_wait({ ref, action, until }) — it names the expected consequence BEFORE the action, which is the difference between a check and a rationalisation. A verdict of verified:"unknown" is not a pass: it means Reticle drove the app and could not tell what happened. Report it as unknown.
47
+
48
+ Feedback is first-class here, not a courtesy. Reticle is built FOR agents, so your experience is the only signal that decides what gets built or fixed — and it disappears when your context does. Send it with \`reticle_feedback\` the moment you notice, then carry on with your task:
49
+ - Reticle broke, lied, or could not see something you needed (\`bug\` / \`gap\` / \`ambiguity\`).
50
+ - You wished it could do something, or something works but costs too many calls (\`feature_request\` / \`improvement\`).
51
+ - The install, the wiring, or the setup itself gave you trouble — including anything you had to work around by hand.
52
+
53
+ Nothing is too minor, and a report costs one call. If the tools are unreachable (setup unfinished, daemon down), file the same report from the shell instead: \`reticle feedback --agent --kind <bug|gap|ambiguity|feature_request|improvement> "what happened"\`. Report defects in RETICLE — a bug you find in the app under test is Reticle working, and belongs in your answer to the user.`;
28
54
  /** First sentence of a description (purpose only) for lean profiles — keeps per-turn def cost
29
55
  * down. Cuts at the first sentence-ending period or newline; falls back to a 160-char cap. The
30
56
  * first clause retains the essentials (enum hints like "role | text | label" live there). */
@@ -65,7 +91,23 @@ export function firstSentence(description) {
65
91
  * Across the three tools that take a predicate that is 72% of the entire advertised input schema — a
66
92
  * cost re-sent on every request, forever, to describe a grammar most calls use one variant of.
67
93
  */
68
- const PREDICATE_PARAMS = new Set(['predicate', 'until']);
94
+ /**
95
+ * Is this parameter ACTUALLY the predicate union?
96
+ *
97
+ * This used to be a name check — `new Set(['predicate', 'until'])` — and `until` is overloaded on
98
+ * this surface: the act/assert family means a predicate by it, while reticle_observe / _network /
99
+ * _console mean a NUMBER, an upper cursor bound. So all three had their numeric parameter advertised
100
+ * to the agent as `Predicate object: { kind, ...fields }`, typed as a record, under the DEFAULT
101
+ * profile. An agent believing the schema passes an object, `asNumber` yields undefined, the bound is
102
+ * silently dropped and the tool answers over the wrong window — a wrong answer wearing the shape of
103
+ * an answer.
104
+ *
105
+ * Keying on the schema cannot make that mistake: a parameter is compacted because it IS a predicate.
106
+ */
107
+ function isPredicateParam(schema) {
108
+ const inner = schema instanceof z.ZodOptional ? schema.unwrap() : schema;
109
+ return inner === PredicateSchema || inner === PredicateSchema.optional();
110
+ }
69
111
  /**
70
112
  * What a lean profile advertises in place of the full predicate union.
71
113
  *
@@ -113,17 +155,22 @@ const COMPACT_PREDICATE_DESCRIPTION = `${PREDICATE_KINDS}${PREDICATE_FIELD_GRAMM
113
155
  */
114
156
  function leanZodShape(shape, predicateAnchor) {
115
157
  const out = {};
158
+ // The navigation hint is said ONCE per turn, so it is anchored to one parameter — not one tool.
159
+ // A tool can carry two predicate parameters (a canonical name plus a neighbouring tool's alias),
160
+ // and anchoring per-tool spelled the hint out on both.
161
+ let hintSpent = predicateAnchor !== undefined;
116
162
  for (const [key, schema] of Object.entries(shape)) {
117
- if (PREDICATE_PARAMS.has(key)) {
118
- // Every tool keeps the kinds; only the navigation hint is anchored to one.
163
+ if (isPredicateParam(schema)) {
164
+ // Every tool keeps the kinds; only the navigation hint is anchored.
119
165
  const compact = z
120
166
  .record(z.unknown())
121
- .describe(predicateAnchor === undefined ? COMPACT_PREDICATE_DESCRIPTION : PREDICATE_KINDS);
167
+ .describe(hintSpent ? PREDICATE_KINDS : COMPACT_PREDICATE_DESCRIPTION);
168
+ hintSpent = true;
122
169
  out[key] = schema.isOptional() ? compact.optional() : compact;
123
170
  continue;
124
171
  }
125
172
  const desc = schema.description;
126
- out[key] = typeof desc === 'string' ? schema.describe(firstSentence(desc)) : schema;
173
+ out[key] = 'string' === typeof desc ? schema.describe(firstSentence(desc)) : schema;
127
174
  }
128
175
  return out;
129
176
  }
@@ -151,18 +198,19 @@ export function encodeResult(result, encoding) {
151
198
  * therefore insensitive to it — every profile appeared to reach every tool, so reverting the fix here
152
199
  * left the suite green. Exercise the real function or the guard is theatre.
153
200
  *
154
- * Every TRIMMED profile keeps the meta-tools, so a tool that is not advertised is still reachable via
155
- * reticle_run. Without them a trim is not a trim but a hard removal: under `standard`, 11 tools were
156
- * uncallable — including reticle_annotate, which reticle_flow_save's own description tells the agent to
157
- * call. `full` advertises everything and needs no hatch.
201
+ * Both surfaces keep the meta-tools. On the default that is what makes a trim a trim rather than a
202
+ * hard removal — an unadvertised tool is still reachable through reticle_run; under the old
203
+ * `standard` profile 11 tools were uncallable, including reticle_annotate, which reticle_flow_save's
204
+ * own description tells the agent to call. On ALL they are kept too: `reticle_tools` is not an
205
+ * escape hatch there, it is parameter lookup, and every recovery message this server emits says
206
+ * "Call reticle_tools { names: [...] } for its parameters" — leaving it out made our own advice a
207
+ * dead end on the one surface that advertises everything.
158
208
  */
159
- export function advertisedTools(profile) {
160
- if (profile === TOOL_PROFILE.DYNAMIC)
161
- return buildDynamicTools(TOOLS);
162
- if (profile === TOOL_PROFILE.FULL)
163
- return TOOLS;
164
- const base = filterTools(TOOLS, profile === TOOL_PROFILE.HYBRID ? TOOL_PROFILE.CORE : profile);
165
- return [...base, ...buildDynamicTools(TOOLS)];
209
+ export function advertisedTools(surface) {
210
+ // The catalog reports which surface is live and what chose it — the only way an agent can see that
211
+ // a setting did not take (the daemon read the value it started with).
212
+ const origin = describeToolSurface(surface);
213
+ return [...filterTools(TOOLS, surface), ...buildDynamicTools(TOOLS, origin)];
166
214
  }
167
215
  /**
168
216
  * Exactly what gets advertised for one tool under a profile: trimmed description, lean input shape,
@@ -173,11 +221,15 @@ export function advertisedTools(profile) {
173
221
  * an abbreviation test that read raw defs and would have passed with the bug fully present.
174
222
  */
175
223
  export function advertisedConfig(tool, advertised, profile) {
176
- const terse = profile === TOOL_PROFILE.CORE ||
177
- profile === TOOL_PROFILE.STANDARD ||
178
- profile === TOOL_PROFILE.HYBRID;
224
+ // Hybrid is the terse one. NOT `profile !== FULL`, which reads equivalent and is not: it also
225
+ // trims `dynamic`, whose two meta-tools are the ONLY thing that profile advertises — so the
226
+ // instructions for using them (`names:[…]` loads full params on demand) would be cut to a first
227
+ // sentence and the agent would have nothing left to learn the surface from. Measured when it
228
+ // happened: dynamic's tools/list fell 1,543 -> 849 bytes, which looks like a saving and is a
229
+ // capability loss.
230
+ const terse = profile === TOOL_SURFACE.DEFAULT;
179
231
  // The first advertised tool carrying a predicate spells the grammar out; the rest point at it.
180
- const anchor = advertised.find((t) => Object.keys(t.inputSchema).some((k) => PREDICATE_PARAMS.has(k)))?.name;
232
+ const anchor = advertised.find((t) => Object.values(t.inputSchema).some((schema) => isPredicateParam(schema)))?.name;
181
233
  const outputSchema = terse ? undefined : withSessionEnvelope(tool.name, tool.outputSchema);
182
234
  return {
183
235
  description: withExample(terse ? firstSentence(tool.description) : tool.description, tool.example),
@@ -189,7 +241,7 @@ export function advertisedConfig(tool, advertised, profile) {
189
241
  }
190
242
  /** Argument keys the tool never declared. Empty when the tool's shape is unknown to us. */
191
243
  function unknownKeys(args, allowed) {
192
- if (allowed === undefined || typeof args !== 'object' || args === null)
244
+ if (allowed === undefined || typeof args !== 'object' || null === args)
193
245
  return [];
194
246
  return Object.keys(args).filter((key) => !allowed.has(key));
195
247
  }
@@ -207,7 +259,7 @@ export function installFriendlyArgErrors(server, examples, allowedKeys = new Map
207
259
  const unknown = unknownKeys(args, allowedKeys.get(toolName));
208
260
  if (unknown.length > 0) {
209
261
  const example = examples.get(toolName);
210
- throw new McpError(ErrorCode.InvalidParams, `Unknown ${unknown.length === 1 ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')}. ` +
262
+ throw new McpError(ErrorCode.InvalidParams, `Unknown ${1 === unknown.length ? 'parameter' : 'parameters'} for ${toolName}: ${unknown.join(', ')}. ` +
211
263
  `They were NOT applied — a result computed without them would look like an answer. ` +
212
264
  (example === undefined
213
265
  ? `Call reticle_tools { names: ["${toolName}"] } for its parameters.`
@@ -228,9 +280,44 @@ export function installFriendlyArgErrors(server, examples, allowedKeys = new Map
228
280
  }
229
281
  };
230
282
  }
231
- export function createMcpServer(deps, profile = TOOL_PROFILE.HYBRID) {
283
+ /**
284
+ * Answer a call for a real-but-un-advertised tool with the way to make it work.
285
+ *
286
+ * Wraps the SDK's own `tools/call` handler rather than registering the missing tools: registering
287
+ * them would put their schemas back into `tools/list`, which is the exact cost the trim exists to
288
+ * avoid. Delegates untouched for every other name, so an advertised tool's errors stay the SDK's.
289
+ */
290
+ function installUnadvertisedToolHelp(server, advertised, known) {
291
+ const inner = server.server;
292
+ const handlers = inner._requestHandlers;
293
+ const original = handlers?.get(CALL_TOOL_METHOD);
294
+ if (handlers === undefined || original === undefined)
295
+ return;
296
+ handlers.set(CALL_TOOL_METHOD, async (request, extra) => {
297
+ const name = toolNameOf(request);
298
+ const help = name === undefined ? undefined : unadvertisedToolHelp(name, advertised, known);
299
+ if (help !== undefined)
300
+ throw new McpError(ErrorCode.InvalidParams, help);
301
+ return original(request, extra);
302
+ });
303
+ }
304
+ /** The requested tool name, when the request has the shape we expect. */
305
+ function toolNameOf(request) {
306
+ if (typeof request !== 'object' || null === request)
307
+ return undefined;
308
+ const params = request.params;
309
+ if (typeof params !== 'object' || null === params)
310
+ return undefined;
311
+ const name = params.name;
312
+ return 'string' === typeof name ? name : undefined;
313
+ }
314
+ export function createMcpServer(deps, profile = TOOL_SURFACE.DEFAULT) {
232
315
  const encoding = (process.env[ENCODING_ENV] ?? '').toLowerCase();
233
- const server = new McpServer(SERVER_INFO);
316
+ // Which surface this daemon advertises. The 18-tool default and the 48-tool full surface are
317
+ // different products from inside an agent's context, so outcomes are only comparable when the
318
+ // session says which one it saw.
319
+ getSessionMetrics().recordSurface(profile);
320
+ const server = new McpServer(SERVER_INFO, { instructions: SERVER_INSTRUCTIONS });
234
321
  // Which agent is on the other end, taken from its own `initialize` handshake. Registered as a lazy
235
322
  // hook rather than read here: the handshake has not happened yet at construction time, and a
236
323
  // feedback report filed twenty tool calls later is exactly when we want the answer.
@@ -238,8 +325,10 @@ export function createMcpServer(deps, profile = TOOL_PROFILE.HYBRID) {
238
325
  const info = server.server.getClientVersion();
239
326
  // Record it as a side effect of the first read so the session summary can report WHICH agents
240
327
  // drove this daemon — the multi-agent story, visible without a second plumbing path.
328
+ // Name AND version: the version was already in this handshake and was being dropped, so a
329
+ // regression in agent behaviour could never be attributed to a client build.
241
330
  if (info?.name !== undefined)
242
- getSessionMetrics().recordClient(info.name);
331
+ getSessionMetrics().recordClient(info.name, info.version);
243
332
  return info === undefined ? undefined : { name: info.name, version: info.version };
244
333
  });
245
334
  // Cast once to our bridge type so every per-tool call site is typed without `any`.
@@ -285,13 +374,21 @@ export function createMcpServer(deps, profile = TOOL_PROFILE.HYBRID) {
285
374
  try {
286
375
  const result = await runTool(tool, deps, args);
287
376
  const text = encodeResult(result, encoding);
377
+ // A tool that RETURNS `{ error, recovery }` refused just as surely as one that threw, and
378
+ // `isError` is the field a caller branches on. Without this it was set only on the throw
379
+ // path, so half the surface reported a refusal as a success. See mcp-is-error.
380
+ const isError = resultIsError(result);
288
381
  if (tool.outputSchema !== undefined) {
289
382
  return {
383
+ ...(isError ? { isError: true } : {}),
290
384
  content: [{ type: 'text', text }],
291
385
  structuredContent: result,
292
386
  };
293
387
  }
294
- return { content: [{ type: 'text', text }] };
388
+ return {
389
+ ...(isError ? { isError: true } : {}),
390
+ content: [{ type: 'text', text }],
391
+ };
295
392
  }
296
393
  catch (error) {
297
394
  const message = error instanceof Error ? error.message : String(error);
@@ -308,5 +405,10 @@ export function createMcpServer(deps, profile = TOOL_PROFILE.HYBRID) {
308
405
  }
309
406
  });
310
407
  }
408
+ // AFTER the registration loop, and that ordering is the whole trick: the SDK installs its
409
+ // `tools/call` dispatcher lazily on the FIRST registerTool, so wrapping before this point finds
410
+ // nothing to wrap and silently does nothing. Verified live rather than assumed — the first attempt
411
+ // sat here doing exactly that.
412
+ installUnadvertisedToolHelp(server, new Set(advertised.map((tool) => tool.name)), new Set(TOOLS.map((tool) => tool.name)));
311
413
  return server;
312
414
  }
@@ -0,0 +1,3 @@
1
+ /** True when this queued client line is the handshake we answered ourselves. */
2
+ export declare function isHandshakeLine(line: string): boolean;
3
+ export declare function localInitializeResponse(line: string): string | null;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Answer `initialize` when the daemon cannot.
3
+ *
4
+ * The proxy queues every client message until the daemon's SSE endpoint frame arrives. `initialize`
5
+ * is a client message, so when the daemon port is held by something that accepts connections and
6
+ * never serves SSE — a wedged daemon, a foreign process, a daemon leaked by another project — the
7
+ * handshake waits on a thing that is never coming. Reported from the field as "initialize never
8
+ * answers, no tools run at all"; reproduced with a listener that simply never responds.
9
+ *
10
+ * A hang is the worst possible outcome here: no tools, no diagnosis, nothing to retry. Completing
11
+ * the handshake locally gives the agent a working surface whose FIRST tool call reports the real
12
+ * problem through the no-session diagnostics that already exist.
13
+ *
14
+ * Safe because the proxy replays the client's own `initialize` to the daemon whenever a session is
15
+ * finally established (see `replayLines`), so the daemon still gets its handshake in order.
16
+ */
17
+ import { MCP_SERVER_NAME } from '../init/mcp.js';
18
+ import { SERVER_VERSION } from '../version/server-version.js';
19
+ /** The version we answer with if the client proposed none. */
20
+ const FALLBACK_PROTOCOL_VERSION = '2024-11-05';
21
+ function parseLine(line) {
22
+ try {
23
+ const parsed = JSON.parse(line);
24
+ return 'object' === typeof parsed && parsed !== null ? parsed : null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ /** True when this queued client line is the handshake we answered ourselves. */
31
+ export function isHandshakeLine(line) {
32
+ const msg = parseLine(line);
33
+ return 'initialize' === msg?.method || 'notifications/initialized' === msg?.method;
34
+ }
35
+ export function localInitializeResponse(line) {
36
+ const msg = parseLine(line);
37
+ if (null === msg || msg.method !== 'initialize')
38
+ return null;
39
+ // A notification carries no id and expects no reply; answering one is a protocol error.
40
+ if (msg.id === undefined || null === msg.id)
41
+ return null;
42
+ const proposed = msg.params?.protocolVersion;
43
+ return JSON.stringify({
44
+ jsonrpc: '2.0',
45
+ id: msg.id,
46
+ result: {
47
+ // Echo what the client asked for: answering with a version it did not offer is its own
48
+ // handshake failure.
49
+ protocolVersion: 'string' === typeof proposed ? proposed : FALLBACK_PROTOCOL_VERSION,
50
+ capabilities: { tools: {} },
51
+ serverInfo: { name: MCP_SERVER_NAME, version: SERVER_VERSION },
52
+ },
53
+ });
54
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * When the proxy may start a daemon — the rule that keeps an idle install from looping forever.
3
+ *
4
+ * Two changes collided to produce a real regression, and this is the seam that prevents it coming
5
+ * back. A daemon may now shut itself down when it has served nothing and no browser ever connected
6
+ * (see daemon-usefulness.ts). The proxy separately learned to respawn a dead daemon, because one
7
+ * that crashed or was stopped used to take the agent's whole Reticle surface with it. Together they
8
+ * loop: the daemon exits as useless, the proxy immediately brings back a daemon that is equally
9
+ * useless, which exits, forever. Measured with a 4s grace: four processes in 200 seconds. At the
10
+ * real 300s grace that is a new process every five minutes for the 74% of installs that never call a
11
+ * tool, for as long as the editor is open.
12
+ *
13
+ * The rule that resolves it: a dropped stream is not demand. Reattach to a daemon that is already
14
+ * there; otherwise go dormant and let the next thing the CLIENT asks for bring Reticle back.
15
+ *
16
+ * Extracted as pure functions because the decision used to live inside a closure where no test could
17
+ * reach it — and a guard that re-implements the decision instead of calling it is insensitive to the
18
+ * thing it claims to guard.
19
+ */
20
+ /** What to do when the SSE stream drops. */
21
+ export declare const OnDrop: {
22
+ /** A daemon is listening — reattach to it. */
23
+ readonly REATTACH: "reattach";
24
+ /** Nothing is listening. Do NOT spawn: wait to be needed. */
25
+ readonly DORMANT: "dormant";
26
+ };
27
+ export type OnDrop = (typeof OnDrop)[keyof typeof OnDrop];
28
+ /**
29
+ * A stream drop only ever justifies reattaching, never spawning. This is the whole fix: the proxy
30
+ * used to spawn here, which turned a daemon's own idle shutdown into a permanent respawn loop.
31
+ */
32
+ export declare function onStreamDrop(daemonListening: boolean): OnDrop;
33
+ /**
34
+ * What to do when the reconnect budget runs out. Always DORMANT — never exit.
35
+ *
36
+ * The proxy used to `process.exit(1)` here, justified as "let the agent host respawn the proxy". No
37
+ * host does that: a stdio MCP server that exits is marked DISCONNECTED, its tools disappear from the
38
+ * session, and a human has to open /mcp and reconnect by hand. That is the worst thing this product
39
+ * does to anyone, and it was deliberate.
40
+ *
41
+ * Exiting bought nothing. Dormant already covers every case exit was meant to: the handshake is
42
+ * answered locally, `tools/list` is answered from the catalog cache, and the next client request
43
+ * WAKES a daemon. The budget still exists — it decides when to stop burning CPU on retries, not
44
+ * whether the server survives.
45
+ */
46
+ export declare function onReconnectBudgetSpent(): OnDrop;
47
+ /** What to do with a client message. */
48
+ export declare const OnRequest: {
49
+ /** Connected — post it straight through. */
50
+ readonly SEND: "send";
51
+ /** Reconnecting already; hold it until the endpoint arrives. */
52
+ readonly QUEUE: "queue";
53
+ /** Dormant: this is demand. Start a daemon, reattach, then flush. */
54
+ readonly WAKE: "wake";
55
+ };
56
+ export type OnRequest = (typeof OnRequest)[keyof typeof OnRequest];
57
+ /**
58
+ * The client asking for something is the ONLY event that may start a daemon.
59
+ *
60
+ * `connected` means a session endpoint is in hand. When it is not, a dormant proxy wakes (spawning
61
+ * if needed) and a merely-reconnecting one queues — the difference between "nothing is coming back
62
+ * on its own" and "something already is".
63
+ */
64
+ export declare function onClientRequest(connected: boolean, dormant: boolean): OnRequest;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * When the proxy may start a daemon — the rule that keeps an idle install from looping forever.
3
+ *
4
+ * Two changes collided to produce a real regression, and this is the seam that prevents it coming
5
+ * back. A daemon may now shut itself down when it has served nothing and no browser ever connected
6
+ * (see daemon-usefulness.ts). The proxy separately learned to respawn a dead daemon, because one
7
+ * that crashed or was stopped used to take the agent's whole Reticle surface with it. Together they
8
+ * loop: the daemon exits as useless, the proxy immediately brings back a daemon that is equally
9
+ * useless, which exits, forever. Measured with a 4s grace: four processes in 200 seconds. At the
10
+ * real 300s grace that is a new process every five minutes for the 74% of installs that never call a
11
+ * tool, for as long as the editor is open.
12
+ *
13
+ * The rule that resolves it: a dropped stream is not demand. Reattach to a daemon that is already
14
+ * there; otherwise go dormant and let the next thing the CLIENT asks for bring Reticle back.
15
+ *
16
+ * Extracted as pure functions because the decision used to live inside a closure where no test could
17
+ * reach it — and a guard that re-implements the decision instead of calling it is insensitive to the
18
+ * thing it claims to guard.
19
+ */
20
+ /** What to do when the SSE stream drops. */
21
+ export const OnDrop = {
22
+ /** A daemon is listening — reattach to it. */
23
+ REATTACH: 'reattach',
24
+ /** Nothing is listening. Do NOT spawn: wait to be needed. */
25
+ DORMANT: 'dormant',
26
+ };
27
+ /**
28
+ * A stream drop only ever justifies reattaching, never spawning. This is the whole fix: the proxy
29
+ * used to spawn here, which turned a daemon's own idle shutdown into a permanent respawn loop.
30
+ */
31
+ export function onStreamDrop(daemonListening) {
32
+ return daemonListening ? OnDrop.REATTACH : OnDrop.DORMANT;
33
+ }
34
+ /**
35
+ * What to do when the reconnect budget runs out. Always DORMANT — never exit.
36
+ *
37
+ * The proxy used to `process.exit(1)` here, justified as "let the agent host respawn the proxy". No
38
+ * host does that: a stdio MCP server that exits is marked DISCONNECTED, its tools disappear from the
39
+ * session, and a human has to open /mcp and reconnect by hand. That is the worst thing this product
40
+ * does to anyone, and it was deliberate.
41
+ *
42
+ * Exiting bought nothing. Dormant already covers every case exit was meant to: the handshake is
43
+ * answered locally, `tools/list` is answered from the catalog cache, and the next client request
44
+ * WAKES a daemon. The budget still exists — it decides when to stop burning CPU on retries, not
45
+ * whether the server survives.
46
+ */
47
+ export function onReconnectBudgetSpent() {
48
+ return OnDrop.DORMANT;
49
+ }
50
+ /** What to do with a client message. */
51
+ export const OnRequest = {
52
+ /** Connected — post it straight through. */
53
+ SEND: 'send',
54
+ /** Reconnecting already; hold it until the endpoint arrives. */
55
+ QUEUE: 'queue',
56
+ /** Dormant: this is demand. Start a daemon, reattach, then flush. */
57
+ WAKE: 'wake',
58
+ };
59
+ /**
60
+ * The client asking for something is the ONLY event that may start a daemon.
61
+ *
62
+ * `connected` means a session endpoint is in hand. When it is not, a dormant proxy wakes (spawning
63
+ * if needed) and a merely-reconnecting one queues — the difference between "nothing is coming back
64
+ * on its own" and "something already is".
65
+ */
66
+ export function onClientRequest(connected, dormant) {
67
+ if (connected)
68
+ return OnRequest.SEND;
69
+ return dormant ? OnRequest.WAKE : OnRequest.QUEUE;
70
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The tool catalog the proxy has already seen, so a locally-answered handshake is not toolless.
3
+ *
4
+ * When the daemon does not answer in time the proxy answers `initialize` itself — a hang gives the
5
+ * agent no tools AND no diagnosis, so that is right. But it left the client CONNECTED WITH NO TOOLS,
6
+ * because `tools/list` then had nothing behind it. Measured over one editor session: 25 stream drops,
7
+ * 11 dormant, 4 reconnects, 4 local handshakes — and each of those four is a state where a human has
8
+ * to notice and reconnect by hand.
9
+ *
10
+ * Every `tools/list` response the daemon has ever sent passes through this proxy, so the catalog is
11
+ * already in hand. Serving it back is the difference between "Reticle is here but useless" and
12
+ * "Reticle is here, and the first call will tell you what is wrong".
13
+ *
14
+ * In-memory and per-process on purpose: a catalog persisted from a different Reticle version would be
15
+ * its own confidently-wrong answer, and a proxy that has never seen one has nothing honest to serve —
16
+ * so it says nothing rather than inventing a list the agent would then call into.
17
+ */
18
+ export declare class ToolCatalogCache {
19
+ #private;
20
+ /** Watch a line coming BACK from the daemon; remember it if it is a tool catalog. */
21
+ observe(line: string): void;
22
+ has(): boolean;
23
+ /**
24
+ * A response to `request` from the cached catalog, or null when this is not a tools/list request or
25
+ * nothing has been cached. The response carries the CALLER'S id — a JSON-RPC reply under any other
26
+ * id is not an answer, it is noise the client will never match.
27
+ */
28
+ answer(request: string): string | null;
29
+ }
@@ -0,0 +1,55 @@
1
+ /**
2
+ * The tool catalog the proxy has already seen, so a locally-answered handshake is not toolless.
3
+ *
4
+ * When the daemon does not answer in time the proxy answers `initialize` itself — a hang gives the
5
+ * agent no tools AND no diagnosis, so that is right. But it left the client CONNECTED WITH NO TOOLS,
6
+ * because `tools/list` then had nothing behind it. Measured over one editor session: 25 stream drops,
7
+ * 11 dormant, 4 reconnects, 4 local handshakes — and each of those four is a state where a human has
8
+ * to notice and reconnect by hand.
9
+ *
10
+ * Every `tools/list` response the daemon has ever sent passes through this proxy, so the catalog is
11
+ * already in hand. Serving it back is the difference between "Reticle is here but useless" and
12
+ * "Reticle is here, and the first call will tell you what is wrong".
13
+ *
14
+ * In-memory and per-process on purpose: a catalog persisted from a different Reticle version would be
15
+ * its own confidently-wrong answer, and a proxy that has never seen one has nothing honest to serve —
16
+ * so it says nothing rather than inventing a list the agent would then call into.
17
+ */
18
+ const LIST_METHOD = 'tools/list';
19
+ function parse(line) {
20
+ try {
21
+ const value = JSON.parse(line);
22
+ return 'object' === typeof value && value !== null ? value : null;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ export class ToolCatalogCache {
29
+ #tools;
30
+ /** Watch a line coming BACK from the daemon; remember it if it is a tool catalog. */
31
+ observe(line) {
32
+ const msg = parse(line);
33
+ const tools = msg?.result?.tools;
34
+ if (Array.isArray(tools) && tools.length > 0)
35
+ this.#tools = tools;
36
+ }
37
+ has() {
38
+ return this.#tools !== undefined;
39
+ }
40
+ /**
41
+ * A response to `request` from the cached catalog, or null when this is not a tools/list request or
42
+ * nothing has been cached. The response carries the CALLER'S id — a JSON-RPC reply under any other
43
+ * id is not an answer, it is noise the client will never match.
44
+ */
45
+ answer(request) {
46
+ if (this.#tools === undefined)
47
+ return null;
48
+ const msg = parse(request);
49
+ if (msg?.method !== LIST_METHOD)
50
+ return null;
51
+ if (msg.id === undefined || null === msg.id)
52
+ return null;
53
+ return JSON.stringify({ jsonrpc: '2.0', id: msg.id, result: { tools: this.#tools } });
54
+ }
55
+ }
@@ -7,7 +7,7 @@ import { type ReticleEvent } from '@reticlehq/core';
7
7
  * proposal is one existing-tool call. The learned RANKING across projects is the paid layer
8
8
  * (OSS-VS-SERVER); these local rules are OSS.
9
9
  */
10
- export interface ProposedConsequence {
10
+ interface ProposedConsequence {
11
11
  /** A predicate object, directly usable as a mustHold. */
12
12
  predicate: Record<string, unknown>;
13
13
  /** Ranking tier: 0 strongest (signal) … 2 weakest (presence). */
@@ -19,3 +19,4 @@ export interface ProposedConsequence {
19
19
  }
20
20
  /** Propose ranked consequences from a recorded event window. Deduped; strongest first. */
21
21
  export declare function proposeConsequences(events: readonly ReticleEvent[]): ProposedConsequence[];
22
+ export {};