@reticlehq/server 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reticlehq/server",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Reticle bridge + MCP server. Hosts the browser WS endpoint and exposes MCP tools to the coding agent.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -22,7 +22,7 @@
22
22
  "@modelcontextprotocol/sdk": "^1.13.0",
23
23
  "ws": "^8.18.0",
24
24
  "zod": "^3.24.1",
25
- "@reticlehq/core": "2.3.0"
25
+ "@reticlehq/core": "2.5.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@types/pngjs": "^6.0.5",
@@ -45,7 +45,9 @@
45
45
  "files": [
46
46
  "dist",
47
47
  "README.md",
48
- "LICENSE-ENTERPRISE"
48
+ "LICENSE-ENTERPRISE",
49
+ "SKILL.md",
50
+ "docs"
49
51
  ],
50
52
  "keywords": [
51
53
  "mcp",
@@ -54,7 +56,7 @@
54
56
  "cli"
55
57
  ],
56
58
  "reticle": {
57
- "changelog": "Smart MCP proxy with auto-start daemon, auto-update, and rollback support. Agents can call reticle_version_info, reticle_apply_update, and reticle_rollback.",
59
+ "changelog": "An MCP server that stays up when the daemon dies, one tool surface instead of five profiles (`RETICLE_TOOL_PROFILE` retired for `RETICLE_ADVERTISE_ALL_TOOLS`), and tools that refuse instead of answering wrongly.",
58
60
  "breakingChanges": []
59
61
  },
60
62
  "publishConfig": {
@@ -68,6 +70,7 @@
68
70
  "dev": "tsc -b --watch",
69
71
  "typecheck": "tsc -b",
70
72
  "lint": "eslint src",
71
- "test:unit": "vitest run src --passWithNoTests"
73
+ "test:unit": "vitest run src --passWithNoTests",
74
+ "test:coverage": "vitest run src --passWithNoTests --coverage.enabled --coverage.provider=v8 --coverage.reporter=text-summary --coverage.include='src/**/*.ts' --coverage.exclude='src/**/*.test.ts'"
72
75
  }
73
76
  }
@@ -1,112 +0,0 @@
1
- import * as http from 'node:http';
2
- import { spawn } from 'node:child_process';
3
- import { isOpaqueOrigin, LOOPBACK_HOST, STATUS_PATH } from '@reticlehq/core';
4
- /**
5
- * Reduce the daemon's /status JSON to the compact view `reticle status` prints. Pure: narrows the
6
- * untrusted wire payload (never `any`) and tolerates a missing/partial body so a malformed response
7
- * degrades to "running, 0 sessions" instead of throwing.
8
- */
9
- export function summarizeStatus(payload) {
10
- if (typeof payload !== 'object' || payload === null)
11
- return { sessionCount: 0, sessions: [] };
12
- const obj = payload;
13
- const raw = Array.isArray(obj['sessions']) ? obj['sessions'] : [];
14
- const sessions = raw
15
- .map((s) => {
16
- if (typeof s !== 'object' || s === null)
17
- return null;
18
- const r = s;
19
- const sessionId = typeof r['sessionId'] === 'string' ? r['sessionId'] : '';
20
- if (sessionId === '')
21
- return null;
22
- return {
23
- sessionId,
24
- url: typeof r['url'] === 'string' ? r['url'] : '',
25
- throttled: r['throttled'] === true,
26
- stale: r['stale'] === true,
27
- pendingMarks: typeof r['pendingMarks'] === 'number' ? r['pendingMarks'] : 0,
28
- };
29
- })
30
- .filter((s) => s !== null);
31
- const sessionCount = typeof obj['sessionCount'] === 'number' ? obj['sessionCount'] : sessions.length;
32
- return { sessionCount, sessions };
33
- }
34
- /** How long the daemon /status probe waits before giving up — a local loopback call is near-instant. */
35
- const STATUS_PROBE_TIMEOUT_MS = 1000;
36
- /** GET the daemon's /status JSON. Resolves to the parsed body, or undefined on any failure. */
37
- export function fetchStatus(port) {
38
- return new Promise((resolve) => {
39
- const req = http.get({ host: LOOPBACK_HOST, port, path: STATUS_PATH, timeout: STATUS_PROBE_TIMEOUT_MS }, (res) => {
40
- let body = '';
41
- res.setEncoding('utf8');
42
- res.on('data', (chunk) => (body += chunk));
43
- res.on('end', () => {
44
- try {
45
- resolve(JSON.parse(body));
46
- }
47
- catch {
48
- resolve(undefined);
49
- }
50
- });
51
- });
52
- req.on('error', () => resolve(undefined));
53
- req.on('timeout', () => {
54
- req.destroy();
55
- resolve(undefined);
56
- });
57
- });
58
- }
59
- function sameOrigin(a, b) {
60
- try {
61
- const origin = new URL(a).origin;
62
- // Two opaque origins (desktop webviews, file:// docs) BOTH stringify to "null", so comparing them
63
- // would call every desktop app the same app and reuse the wrong session. Never match on opaque.
64
- if (isOpaqueOrigin(origin))
65
- return false;
66
- return origin === new URL(b).origin;
67
- }
68
- catch {
69
- return false;
70
- }
71
- }
72
- /**
73
- * Decide what `reticle open [url]` does, given the currently-connected tabs. Pure.
74
- * - no url + a tab connected → reuse it (the app is already open; don't spawn a duplicate).
75
- * - no url + nothing connected → ask for one.
76
- * - url + a tab already on that origin → reuse it (idempotent — re-running never piles up tabs).
77
- * - url + no matching tab → open it.
78
- */
79
- export function decideOpen(sessions, url) {
80
- if (url === undefined) {
81
- const first = sessions[0];
82
- return first !== undefined ? { action: 'reuse', url: first.url } : { action: 'need-url' };
83
- }
84
- const match = sessions.find((s) => s.url === url || sameOrigin(s.url, url));
85
- return match !== undefined ? { action: 'reuse', url: match.url } : { action: 'open', url };
86
- }
87
- /**
88
- * Percent-encode the cmd.exe metacharacters that `start` re-parses so a URL can't break out into
89
- * command execution on Windows (`?next=x&calc` → command chaining). `%` is left untouched so an
90
- * already-encoded URL isn't double-encoded; the browser decodes the escapes back to the real URL.
91
- */
92
- function encodeForWindowsStart(url) {
93
- return url.replace(/[&^|<>()"'!]/g, (ch) => `%${ch.charCodeAt(0).toString(16).toUpperCase()}`);
94
- }
95
- /** The OS command that opens a URL in the default browser, per platform. Pure — unit-tested. */
96
- export function openCommand(url, platform) {
97
- if (platform === 'darwin')
98
- return { cmd: 'open', args: [url] };
99
- if (platform === 'win32') {
100
- return { cmd: 'cmd', args: ['/c', 'start', '', encodeForWindowsStart(url)] };
101
- }
102
- return { cmd: 'xdg-open', args: [url] };
103
- }
104
- /** Launch the default browser at `url` (detached). The spawn is injected so tests stay hermetic. */
105
- export function openInBrowser(url, platform = process.platform, run = defaultRun) {
106
- const { cmd, args } = openCommand(url, platform);
107
- run(cmd, args);
108
- }
109
- function defaultRun(cmd, args) {
110
- const child = spawn(cmd, args, { detached: true, stdio: 'ignore' });
111
- child.unref();
112
- }
@@ -1,140 +0,0 @@
1
- /**
2
- * Pure CLI argument parsing — the command/flag grammar, the CliResult union, and parseCliArgs.
3
- * Split out of cli.ts (which keeps the side-effecting handlers + dispatch) to stay under the
4
- * file-size cap and keep the parser pure + unit-testable. Re-exported from cli.ts so existing
5
- * imports are unchanged.
6
- */
7
- export declare const CLI_USAGE = "usage:\n reticle init [--dry-run] [--port N] [--no-mcp] [--no-install] (wire Reticle into the project in this directory)\n reticle serve [--port N] [--drive <url>] [--headed] [--http] [--http-port N] [--http-token T]\n reticle stop [--port N] [--quiet]\n reticle status [--port N]\n reticle doctor [--port N] (diagnose setup: Chromium, daemon, port \u2014 one command)\n reticle open [url] [--port N] (show the app: reuse the connected tab, else open one)\n reticle verify <url> [--headed] [--timeout N] [--storage-state <file>] (one-shot: drive the URL, verify saved flows, exit 0=pass)\n reticle affected [--since <ref>] [file...] (which saved flows must re-verify for the changed files)\n reticle gate [--since <ref>] [file...] (exit non-zero unless passing artifacts cover the affected flows)\n reticle watch [url] (on save, report which saved flows must re-verify)\n reticle drive <url> [--headed] (foreground mode \u2014 for debugging)\n reticle mcp [--port N] [--drive <url>] [--headed] (MCP stdio proxy \u2014 auto-starts daemon if needed)\n reticle update (install the latest server version and restart)\n reticle rollback (restore the previous server version and restart)\n reticle license (show enterprise license status: active | eval | missing)\n reticle telemetry [status|enable|disable] (anonymous usage metrics \u2014 status shows what's sent + the policy)\n reticle feedback [--rating 1-5] [--bug] \"message\" (tell us what worked and what didn't \u2014 prints exactly what it sends)\n reticle identify --context company|side_project|open_source|learning [--company N] [--email E] [--forget]\n (OPT-IN: tell us who you are, e.g. for support or an enterprise trial)\n\nCloud (link this project to Reticle Cloud \u2014 runs/flows recorded on the dashboard):\n reticle login --email <e> [--code <c>] [--org <n>] (sign in: mails a code, then exchanges it)\n reticle link [--project <name|id>] (bind this repo: mints a scoped key, writes .reticle/cloud.json)\n reticle whoami (who am I signed in as, and is this repo attached?)\n reticle project <ls|create <name>> (list or create cloud projects)\n reticle config [--runs on|off] [--memory on|off] [--flows on|off] [--verify local|server]\n reticle push (send local run artifacts to the dashboard)\n reticle runs | regression | share <runId> (read cloud state; regression exits 3 if any flow broke)";
8
- export declare const COMPANY_FLAG = "--company";
9
- export declare const EMAIL_FLAG = "--email";
10
- export declare const CONTEXT_FLAG = "--context";
11
- export declare const FORGET_FLAG = "--forget";
12
- /** `reticle feedback --rating 4 "the words"` — the human half of the channel. */
13
- export declare const RATING_FLAG = "--rating";
14
- export declare const BUG_FLAG = "--bug";
15
- /** The `reticle telemetry` sub-actions. Bare `reticle telemetry` means `status`. */
16
- export declare const TelemetryAction: {
17
- readonly STATUS: "status";
18
- readonly ENABLE: "enable";
19
- readonly DISABLE: "disable";
20
- };
21
- export type TelemetryAction = (typeof TelemetryAction)[keyof typeof TelemetryAction];
22
- export declare const DAEMON_INNER_COMMAND = "_daemon";
23
- /**
24
- * Every subcommand a person can type, as one closed vocabulary. Telemetry reports which one ran, and
25
- * this is what keeps that property low-cardinality and non-identifying: an argument we do not
26
- * recognize reports as `unknown` rather than being echoed, so a typo — which may be a path, a URL, or
27
- * a flow name — can never reach the wire just because someone misspelled `status`.
28
- *
29
- * Lives here rather than in the telemetry module because this is where the command names are already
30
- * defined; a second list somewhere else would drift the first time a command is added.
31
- */
32
- export declare const UNKNOWN_COMMAND = "unknown";
33
- /** The subcommand name if we recognize it, else `unknown`. Bare `reticle` reports `help`. */
34
- export declare function knownCommand(arg: string | undefined): string;
35
- export declare const HEADED_FLAG = "--headed";
36
- export declare const PORT_FLAG = "--port";
37
- export declare const DRIVE_FLAG = "--drive";
38
- export declare const HTTP_FLAG = "--http";
39
- export declare const HTTP_PORT_FLAG = "--http-port";
40
- export declare const HTTP_TOKEN_FLAG = "--http-token";
41
- export type CliResult = {
42
- kind: 'init';
43
- port: number | undefined;
44
- mcp: boolean;
45
- dryRun: boolean;
46
- install: boolean;
47
- } | {
48
- kind: 'serve';
49
- port: number;
50
- driveUrl?: string;
51
- headless: boolean;
52
- http: boolean;
53
- httpPort?: number;
54
- httpToken?: string;
55
- } | {
56
- kind: 'stop';
57
- port: number;
58
- quiet: boolean;
59
- } | {
60
- kind: 'status';
61
- port: number;
62
- } | {
63
- kind: 'license';
64
- } | {
65
- kind: 'telemetry';
66
- action: TelemetryAction;
67
- } | {
68
- kind: 'feedback';
69
- text: string;
70
- rating?: number;
71
- bug: boolean;
72
- } | {
73
- kind: 'identify';
74
- context?: string;
75
- company?: string;
76
- email?: string;
77
- forget: boolean;
78
- } | {
79
- kind: 'version';
80
- } | {
81
- kind: 'help';
82
- } | {
83
- kind: 'doctor';
84
- port: number;
85
- } | {
86
- kind: 'open';
87
- port: number;
88
- url?: string;
89
- } | {
90
- kind: '_daemon';
91
- port: number;
92
- driveUrl?: string;
93
- headless: boolean;
94
- http: boolean;
95
- httpPort?: number;
96
- httpToken?: string;
97
- } | {
98
- kind: 'drive';
99
- port: number;
100
- driveUrl: string;
101
- headless: boolean;
102
- } | {
103
- kind: 'verify';
104
- url: string;
105
- headless: boolean;
106
- timeoutMs?: number;
107
- storageState?: string;
108
- } | {
109
- kind: 'affected';
110
- files: string[];
111
- since?: string;
112
- } | {
113
- kind: 'hunt';
114
- dir: string;
115
- } | {
116
- kind: 'capsules';
117
- } | {
118
- kind: 'gate';
119
- files: string[];
120
- since?: string;
121
- } | {
122
- kind: 'watch';
123
- url?: string;
124
- } | {
125
- kind: 'update';
126
- } | {
127
- kind: 'rollback';
128
- } | {
129
- kind: 'mcp';
130
- port: number;
131
- driveUrl?: string;
132
- headless: boolean;
133
- http: boolean;
134
- httpPort?: number;
135
- httpToken?: string;
136
- } | {
137
- kind: 'error';
138
- message: string;
139
- };
140
- export declare function parseCliArgs(argv: string[], defaultPort: number): CliResult;
@@ -1,22 +0,0 @@
1
- /**
2
- * Process-level resilience for the long-running daemon. The daemon serves many agents at once, so a
3
- * single stray error must not take the whole fleet down:
4
- *
5
- * - unhandledRejection (a fire-and-forget promise nobody awaited — the common case in async WS/pool
6
- * code) → LOG and keep running. One agent's async slip-up can't crash the daemon for everyone.
7
- * - uncaughtException (a synchronous throw escaped all try/catch) → the process state is undefined
8
- * per Node's guidance, so LOG a clear reason and exit cleanly; the next `reticle mcp` respawns a fresh
9
- * daemon, which beats crashing silently or limping along corrupt.
10
- */
11
- export interface ProcessLike {
12
- on(event: string, listener: (arg: unknown) => void): unknown;
13
- }
14
- type LogFn = (event: string, data: Record<string, unknown>) => void;
15
- /** The two ways a failure reaches the top of the process. Named so the analytics can tell them apart. */
16
- export declare const CrashKind: {
17
- readonly UNHANDLED_REJECTION: "unhandled_rejection";
18
- readonly UNCAUGHT_EXCEPTION: "uncaught_exception";
19
- };
20
- export type CrashKind = (typeof CrashKind)[keyof typeof CrashKind];
21
- export declare function installDaemonResilience(proc: ProcessLike, log: LogFn, onFatal: () => void): void;
22
- export {};
@@ -1,85 +0,0 @@
1
- /**
2
- * Process-level resilience for the long-running daemon. The daemon serves many agents at once, so a
3
- * single stray error must not take the whole fleet down:
4
- *
5
- * - unhandledRejection (a fire-and-forget promise nobody awaited — the common case in async WS/pool
6
- * code) → LOG and keep running. One agent's async slip-up can't crash the daemon for everyone.
7
- * - uncaughtException (a synchronous throw escaped all try/catch) → the process state is undefined
8
- * per Node's guidance, so LOG a clear reason and exit cleanly; the next `reticle mcp` respawns a fresh
9
- * daemon, which beats crashing silently or limping along corrupt.
10
- */
11
- import { TelemetryActor, TelemetryEventKind } from '@reticlehq/core';
12
- import { errorSkeleton, errorTypeOf, fingerprintCrash, MAX_REPORTED_FRAMES, reticleFrames, } from './telemetry/error-fingerprint.js';
13
- import { getSessionMetrics } from './telemetry/session-metrics.js';
14
- import { machineSnapshot } from './telemetry/machine-snapshot.js';
15
- import { getTelemetry } from './telemetry/telemetry.js';
16
- function describe(value) {
17
- return value instanceof Error ? value.message : String(value);
18
- }
19
- /** The two ways a failure reaches the top of the process. Named so the analytics can tell them apart. */
20
- export const CrashKind = {
21
- UNHANDLED_REJECTION: 'unhandled_rejection',
22
- UNCAUGHT_EXCEPTION: 'uncaught_exception',
23
- };
24
- /**
25
- * Report a crash with enough detail to actually diagnose it.
26
- *
27
- * This is the only place in the product that learns about a crash at all. An uncaught exception in a
28
- * long-running daemon takes down every agent attached to it, and the only other record is a line in a
29
- * local log nobody sends us.
30
- *
31
- * The first version sent a hash and nothing else, which made crashes rankable and undiagnosable — you
32
- * could see that forty machines hit `a3f2c1d8e9b0` and had no way to learn what that was. So it now
33
- * carries the four things an RCA needs: WHERE (our own frames, with function names and line numbers),
34
- * WHAT (the message with every variable part stripped), WHEN (the tool in flight), and WHY-ish (the
35
- * agent's approach run before it). All of it is our code and our vocabulary; the user's stack frames,
36
- * their paths, and the contents of their message are removed before any of it is built.
37
- *
38
- * Best-effort and wrapped: reporting a crash must never be the reason a crash gets worse.
39
- */
40
- function reportCrash(kind, value) {
41
- try {
42
- const errorType = errorTypeOf(value);
43
- const stack = value instanceof Error ? (value.stack ?? '') : '';
44
- const message = describe(value);
45
- const { breadcrumb, inFlight } = getSessionMetrics().trail;
46
- const machine = machineSnapshot();
47
- void getTelemetry().emit(TelemetryEventKind.RUNTIME_CRASHED, {
48
- // A crash is ALWAYS reached through something the agent asked for — the daemon does nothing on
49
- // its own — so attributing it to the agent is accurate rather than a guess.
50
- actor: TelemetryActor.AGENT,
51
- crash: {
52
- kind,
53
- errorType,
54
- fingerprint: fingerprintCrash(errorType, stack, message),
55
- // The skeleton, not the message: every quoted string, path, URL, id and number is replaced
56
- // by `*` first. This is what makes the fingerprint mean something to a human reading a chart.
57
- message: errorSkeleton(message).slice(0, 300),
58
- // Our own frames, our own published code. The file, the line, and the function — which is the
59
- // whole of "where did it break". Frames from the user's app never survive this.
60
- frames: reticleFrames(stack).slice(0, MAX_REPORTED_FRAMES),
61
- ...(inFlight !== undefined ? { tool: inFlight } : {}),
62
- ...(breadcrumb.length > 0 ? { breadcrumb } : {}),
63
- // Crashes cluster hard by runtime version and architecture, and we were recording neither.
64
- nodeVersion: process.versions.node,
65
- arch: process.arch,
66
- // "Out of memory" and "our bug" look identical in a stack trace. This is what tells them apart.
67
- ...(machine !== undefined ? { machine } : {}),
68
- },
69
- });
70
- }
71
- catch {
72
- /* a crash report must never be the reason a crash gets worse */
73
- }
74
- }
75
- export function installDaemonResilience(proc, log, onFatal) {
76
- proc.on('unhandledRejection', (reason) => {
77
- log('reticle_daemon_unhandled_rejection', { reason: describe(reason) });
78
- reportCrash(CrashKind.UNHANDLED_REJECTION, reason);
79
- });
80
- proc.on('uncaughtException', (err) => {
81
- log('reticle_daemon_uncaught_exception', { error: describe(err) });
82
- reportCrash(CrashKind.UNCAUGHT_EXCEPTION, err);
83
- onFatal();
84
- });
85
- }
@@ -1,35 +0,0 @@
1
- /** One parsed Server-Sent-Events frame: the event name (defaulted to "message") and its data. */
2
- export interface SseFrame {
3
- event: string;
4
- data: string;
5
- }
6
- /**
7
- * Incremental SSE frame parser for the MCP front door.
8
- *
9
- * A single SSE field can split across TCP reads (`da` in one chunk, `ta: {...}` in the next), and a
10
- * server may send CRLF or bare CR line endings — so the framing is stateful and edge-case-prone, yet it
11
- * carried every MCP message the agent sends and was only ever exercised end-to-end. Pulled out of the
12
- * socket handler as a pure, chunk-fed parser so those boundaries are unit-testable: feed raw chunks,
13
- * get back each complete frame (a blank line terminates a frame; `event:` names it, `data:` lines
14
- * accumulate newline-joined; `id:`/`retry:`/comments are ignored — not needed for the bridge).
15
- */
16
- export declare class SseFrameParser {
17
- #private;
18
- push(chunk: string): SseFrame[];
19
- }
20
- /**
21
- * Returns true if something is already listening on the reticle port.
22
- * Uses a plain TCP probe so we don't create a side-effectful SSE session
23
- * inside the daemon just to check reachability.
24
- */
25
- export declare function probeDaemon(port: number): Promise<boolean>;
26
- /** Poll until the daemon's HTTP port accepts connections or the deadline is reached. */
27
- export declare function waitForDaemon(port: number): Promise<void>;
28
- export declare function buildSessionUrl(rawData: string, port: number): string;
29
- /**
30
- * Bridge stdio ↔ SSE: connects to the running daemon's MCP endpoint and forwards
31
- * Claude Code's stdin/stdout JSON-RPC through it. Never resolves — runs until
32
- * stdin closes or the SSE stream ends (at which point the process exits so
33
- * Claude Code restarts the proxy fresh).
34
- */
35
- export declare function startMcpProxy(port: number): Promise<never>;
package/dist/mcp-proxy.js DELETED
@@ -1,190 +0,0 @@
1
- import * as http from 'node:http';
2
- import * as net from 'node:net';
3
- import { LOOPBACK_HOST, MCP_SSE_PATH } from '@reticlehq/core';
4
- import { log } from './log.js';
5
- const DEFAULT_DAEMON_READY_TIMEOUT_MS = 10_000;
6
- /**
7
- * How long to wait for the spawned daemon's port to accept connections before giving up. The default
8
- * suits a normal machine; a slow CI/VM (heavy headless-browser launch) can raise it via the
9
- * RETICLE_DAEMON_READY_TIMEOUT_MS env var. Invalid/absent values fall back to the default.
10
- */
11
- const envDaemonReadyTimeoutMs = Number(process.env['RETICLE_DAEMON_READY_TIMEOUT_MS']);
12
- const DAEMON_READY_TIMEOUT_MS = Number.isFinite(envDaemonReadyTimeoutMs) && envDaemonReadyTimeoutMs > 0
13
- ? envDaemonReadyTimeoutMs
14
- : DEFAULT_DAEMON_READY_TIMEOUT_MS;
15
- const DAEMON_POLL_INTERVAL_MS = 100;
16
- function delay(ms) {
17
- return new Promise((resolve) => setTimeout(resolve, ms));
18
- }
19
- /**
20
- * Incremental SSE frame parser for the MCP front door.
21
- *
22
- * A single SSE field can split across TCP reads (`da` in one chunk, `ta: {...}` in the next), and a
23
- * server may send CRLF or bare CR line endings — so the framing is stateful and edge-case-prone, yet it
24
- * carried every MCP message the agent sends and was only ever exercised end-to-end. Pulled out of the
25
- * socket handler as a pure, chunk-fed parser so those boundaries are unit-testable: feed raw chunks,
26
- * get back each complete frame (a blank line terminates a frame; `event:` names it, `data:` lines
27
- * accumulate newline-joined; `id:`/`retry:`/comments are ignored — not needed for the bridge).
28
- */
29
- export class SseFrameParser {
30
- #buffer = '';
31
- #event = '';
32
- #data = '';
33
- push(chunk) {
34
- this.#buffer += chunk;
35
- // Normalise CRLF/CR so the splitter only handles \n, then hold the trailing partial line for the
36
- // next chunk (it may complete later).
37
- const normalised = this.#buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
38
- const lines = normalised.split('\n');
39
- this.#buffer = lines.pop() ?? '';
40
- const frames = [];
41
- for (const line of lines) {
42
- if (line === '') {
43
- if (this.#data !== '') {
44
- frames.push({ event: this.#event !== '' ? this.#event : 'message', data: this.#data });
45
- }
46
- this.#event = '';
47
- this.#data = '';
48
- }
49
- else if (line.startsWith('event:')) {
50
- this.#event = line.slice(6).trim();
51
- }
52
- else if (line.startsWith('data:')) {
53
- const val = line.slice(5).trim();
54
- this.#data = this.#data !== '' ? `${this.#data}\n${val}` : val;
55
- }
56
- }
57
- return frames;
58
- }
59
- }
60
- /**
61
- * Returns true if something is already listening on the reticle port.
62
- * Uses a plain TCP probe so we don't create a side-effectful SSE session
63
- * inside the daemon just to check reachability.
64
- */
65
- export function probeDaemon(port) {
66
- return new Promise((resolve) => {
67
- const socket = new net.Socket();
68
- socket.setTimeout(500);
69
- socket.on('connect', () => {
70
- socket.destroy();
71
- resolve(true);
72
- });
73
- socket.on('error', () => resolve(false));
74
- socket.on('timeout', () => {
75
- socket.destroy();
76
- resolve(false);
77
- });
78
- socket.connect(port, LOOPBACK_HOST);
79
- });
80
- }
81
- /** Poll until the daemon's HTTP port accepts connections or the deadline is reached. */
82
- export async function waitForDaemon(port) {
83
- const deadline = Date.now() + DAEMON_READY_TIMEOUT_MS;
84
- while (Date.now() < deadline) {
85
- const reachable = await probeDaemon(port);
86
- if (reachable)
87
- return;
88
- await delay(DAEMON_POLL_INTERVAL_MS);
89
- }
90
- throw new Error(`reticle daemon did not become ready on port ${port} within ${DAEMON_READY_TIMEOUT_MS}ms`);
91
- }
92
- function postToSession(url, body) {
93
- return new Promise((resolve) => {
94
- const parsed = new URL(url);
95
- const bodyBuf = Buffer.from(body, 'utf8');
96
- const options = {
97
- host: parsed.hostname,
98
- port: parsed.port !== '' ? parseInt(parsed.port, 10) : 80,
99
- path: `${parsed.pathname}${parsed.search}`,
100
- method: 'POST',
101
- headers: {
102
- 'Content-Type': 'application/json',
103
- 'Content-Length': bodyBuf.byteLength,
104
- },
105
- };
106
- const req = http.request(options, (res) => {
107
- const status = res.statusCode ?? 0;
108
- if (status < 200 || status >= 300) {
109
- // A non-2xx from the daemon MCP endpoint used to be swallowed, hanging the JSON-RPC call
110
- // client-side with no diagnostic. Surface it (the forward is still fire-and-forget).
111
- log('reticle_mcp_proxy_post_non2xx', { status, path: options.path });
112
- }
113
- res.resume(); // drain so the socket is reused
114
- resolve();
115
- });
116
- req.on('error', (err) => {
117
- log('reticle_mcp_proxy_post_error', { error: err.message });
118
- resolve();
119
- });
120
- req.write(bodyBuf);
121
- req.end();
122
- });
123
- }
124
- export function buildSessionUrl(rawData, port) {
125
- return rawData.startsWith('/') ? `http://${LOOPBACK_HOST}:${port}${rawData}` : rawData;
126
- }
127
- /**
128
- * Bridge stdio ↔ SSE: connects to the running daemon's MCP endpoint and forwards
129
- * Claude Code's stdin/stdout JSON-RPC through it. Never resolves — runs until
130
- * stdin closes or the SSE stream ends (at which point the process exits so
131
- * Claude Code restarts the proxy fresh).
132
- */
133
- export function startMcpProxy(port) {
134
- return new Promise((_resolve, reject) => {
135
- let postUrl = null;
136
- const stdinQueue = [];
137
- // ── SSE reader ──────────────────────────────────────────────────────────
138
- const req = http.get({ host: LOOPBACK_HOST, port, path: MCP_SSE_PATH }, (res) => {
139
- res.setEncoding('utf8');
140
- const sse = new SseFrameParser();
141
- res.on('data', (chunk) => {
142
- for (const frame of sse.push(chunk))
143
- onSseEvent(frame.event, frame.data, port);
144
- });
145
- res.on('end', () => {
146
- log('reticle_mcp_proxy_sse_ended', { port });
147
- process.exit(0);
148
- });
149
- res.on('error', (err) => {
150
- log('reticle_mcp_proxy_sse_error', { error: err.message });
151
- process.exit(1);
152
- });
153
- });
154
- req.on('error', (err) => reject(err));
155
- function onSseEvent(event, data, p) {
156
- if (event === 'endpoint') {
157
- const url = buildSessionUrl(data, p);
158
- postUrl = url;
159
- // Flush messages that arrived before the session URL was known
160
- for (const queued of stdinQueue.splice(0)) {
161
- void postToSession(url, queued);
162
- }
163
- return;
164
- }
165
- if (event === 'message') {
166
- process.stdout.write(`${data}\n`);
167
- }
168
- }
169
- // ── stdin reader ─────────────────────────────────────────────────────────
170
- process.stdin.setEncoding('utf8');
171
- let stdinBuffer = '';
172
- process.stdin.on('data', (chunk) => {
173
- stdinBuffer += chunk;
174
- const lines = stdinBuffer.split('\n');
175
- stdinBuffer = lines.pop() ?? '';
176
- for (const line of lines) {
177
- const trimmed = line.trim();
178
- if (trimmed === '')
179
- continue;
180
- if (postUrl === null) {
181
- stdinQueue.push(trimmed);
182
- }
183
- else {
184
- void postToSession(postUrl, trimmed);
185
- }
186
- }
187
- });
188
- process.stdin.on('end', () => process.exit(0));
189
- });
190
- }
@@ -1,37 +0,0 @@
1
- import type { ToolDef } from './tools.js';
2
- /**
3
- * Which MCP tool surface to expose. The advertised tool DEFINITIONS are re-sent to the model on
4
- * every turn, so a smaller surface is a per-turn token saving that compounds across a loop. Fewer
5
- * tools also makes the model wander less (fewer turns, higher accuracy). See bench/agent-loop-and-replay.md.
6
- *
7
- * MEASURED surface sizes (assert them with profile-reachability.test.ts rather than trusting prose —
8
- * every count previously written here was wrong, and the token figures below derive from those counts):
9
- * core 15 · standard 32 · hybrid 15 · full 43 · dynamic 2
10
- * core and hybrid are both 14 because a trimmed profile now also advertises the two meta-tools, which
11
- * is what keeps an un-advertised tool reachable through reticle_run.
12
- *
13
- * core — the verify loop a coding agent actually needs: navigate→look→act→observe→assert,
14
- * WITH direct network + console + state observability (the highest-signal checks).
15
- * The recommended profile for agent-driven verification.
16
- * standard — core + common extras (inspect, sequences, animations, flows, session lifecycle,
17
- * scroll, baselines, …). For agents that need more than the bare loop.
18
- * hybrid — THE DEFAULT: core verify+oracle tools advertised directly + 2 meta-tools for on-demand
19
- * reach to everything else. Core accuracy/detection at ~64% less schema tax than full.
20
- * full — all tools advertised directly. Opt in via RETICLE_TOOL_PROFILE=full for hard-call scripts.
21
- */
22
- export declare const TOOL_PROFILE: {
23
- /** dynamic — advertise only 2 meta-tools (reticle_tools + reticle_run); load real tools on demand.
24
- * Fixed ~hundreds of tokens/turn regardless of how many tools exist. See dynamic-tools.ts. */
25
- readonly DYNAMIC: "dynamic";
26
- /** hybrid — the core verify tools advertised directly (so the agent acts reliably) PLUS the 2
27
- * meta-tools for on-demand reach to every other tool. Core accuracy + full reach at ~core cost. */
28
- readonly HYBRID: "hybrid";
29
- readonly CORE: "core";
30
- readonly STANDARD: "standard";
31
- readonly FULL: "full";
32
- };
33
- export type ToolProfile = (typeof TOOL_PROFILE)[keyof typeof TOOL_PROFILE];
34
- export declare const TOOL_PROFILE_ENV = "RETICLE_TOOL_PROFILE";
35
- export declare const CORE_TOOL_NAMES: ReadonlySet<string>;
36
- export declare function resolveToolProfile(explicit?: string): ToolProfile;
37
- export declare function filterTools(tools: ToolDef[], profile: ToolProfile): ToolDef[];