@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
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The self-update command pair — `reticle update` and `reticle rollback`.
3
+ *
4
+ * Split out of cli.ts, which sits at the 600-line cap: these two are one cohesive unit (swap the
5
+ * installed version, restart) and the file they came from is the CLI's dispatch table, which grows
6
+ * for entirely different reasons.
7
+ */
8
+ import { checkForUpdate } from '../update/update-checker.js';
9
+ import { updateTarget } from '../update/update-nudge.js';
10
+ import { applyUpdate, rollback } from '../update/updater.js';
11
+ import { SERVER_VERSION } from '../version/server-version.js';
12
+ import { log } from '../log.js';
13
+ import { readFileSync } from 'node:fs';
14
+ import { join } from 'node:path';
15
+ import { reticleDepsOf, sdkSyncCommand } from '../update/sdk-sync.js';
16
+ import { detectPackageManager } from '../init/detect.js';
17
+ import { buildNodeIo } from '../init/node-io.js';
18
+ /**
19
+ * Bring the SDK in the CURRENT project to the version being installed.
20
+ *
21
+ * Best-effort and non-fatal on purpose: `reticle update` is run from wherever the human happens to
22
+ * be, which is often not an app at all. A directory with no manifest, or one that has none of our
23
+ * packages, simply has nothing to sync — that is a normal outcome, not a failure, and it must never
24
+ * stop the CLI half from happening.
25
+ */
26
+ function syncProjectSdk(target) {
27
+ const cwd = process.cwd();
28
+ let manifest;
29
+ try {
30
+ manifest = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'));
31
+ }
32
+ catch {
33
+ return; // not an app directory
34
+ }
35
+ const packages = reticleDepsOf(manifest);
36
+ const io = buildNodeIo(cwd);
37
+ const pm = detectPackageManager(new Set(io.rootFiles()), new Set(io.listDirs('node_modules')));
38
+ const cmd = sdkSyncCommand(pm, packages, target);
39
+ if (null === cmd) {
40
+ log('reticle_update_sdk', { synced: false, reason: 'no @reticlehq packages in this project' });
41
+ return;
42
+ }
43
+ const ok = io.exec(cmd.command, cmd.args);
44
+ log('reticle_update_sdk', { synced: ok, packages, to: target, packageManager: pm });
45
+ if (!ok) {
46
+ log('reticle_update_sdk_failed', {
47
+ hint: `run \`${cmd.command} ${cmd.args.join(' ')}\` by hand, then restart the dev server`,
48
+ });
49
+ }
50
+ }
51
+ /** `reticle update` — install the latest server version, sync the app's SDK, and restart. */
52
+ export async function handleUpdate() {
53
+ try {
54
+ const manifest = await checkForUpdate(SERVER_VERSION, () => Date.now());
55
+ // Direction, not inequality: the registry being DIFFERENT is not the registry being newer, and
56
+ // the old gate happily installed a downgrade — reported by a user. See updateTarget.
57
+ const target = updateTarget(manifest);
58
+ if (target === undefined) {
59
+ log('reticle_update', {
60
+ ok: false,
61
+ message: 'already on the latest version',
62
+ version: SERVER_VERSION,
63
+ });
64
+ return;
65
+ }
66
+ log('reticle_update', { ok: true, from: SERVER_VERSION, to: target });
67
+ // The app's SDK FIRST, then the CLI. `reticle update` used to swap only the CLI, so the command
68
+ // whose job is keeping an install current was itself a way to produce a version-skewed pair —
69
+ // and the skew message told people to run it to fix an outdated SDK, which it could not do.
70
+ //
71
+ // Before the CLI swap because `applyUpdate` never returns (it execs and exits). If this half
72
+ // fails the daemon is still the older one, which is the direction the HELLO check names clearly
73
+ // and a re-run fixes; the reverse would leave a new daemon talking to an old page.
74
+ syncProjectSdk(target);
75
+ await applyUpdate(target); // calls process.exit; Claude Code restarts
76
+ }
77
+ catch (error) {
78
+ log('reticle_update_failed', { error: error instanceof Error ? error.message : String(error) });
79
+ process.exitCode = 1;
80
+ }
81
+ }
82
+ /** `reticle rollback` — restore the previous server version and restart. */
83
+ export async function handleRollback() {
84
+ try {
85
+ await rollback(); // calls process.exit
86
+ }
87
+ catch (error) {
88
+ log('reticle_rollback_failed', {
89
+ error: error instanceof Error ? error.message : String(error),
90
+ });
91
+ process.exitCode = 1;
92
+ }
93
+ }
@@ -12,18 +12,18 @@
12
12
  import { join, basename } from 'node:path';
13
13
  import { randomUUID } from 'node:crypto';
14
14
  import { RETICLE_DEFAULT_PORT, ReticleEnv, bridgeWsUrl, isLoopbackHostname, ReticleDir, RunAgentKind, RunFramework, RunProfile, RunTrigger, VerdictStatus, } from '@reticlehq/core';
15
- import { start } from './index.js';
16
- import { resolveCloudConfig, syncRunToCloud, SyncOutcome } from './cloud/cloud-sync.js';
17
- import { ReticleRunner } from './runs/reticle-runner.js';
18
- import { createRunnerPort } from './runs/runner-port.js';
19
- import { RunStore } from './runs/run-store.js';
20
- import { renderRunReport } from './runs/render-report.js';
21
- import { BaselineStore } from './project/baselines.js';
22
- import { RecordingStore } from './flows/recordings.js';
23
- import { FlowStore } from './flows/flows.js';
24
- import { ProjectStore } from './project/project-store.js';
25
- import { AnnotationStore } from './flows/annotation-store.js';
26
- import { createNodeFileSystem } from './project/fs-port.js';
15
+ import { start } from '../index.js';
16
+ import { resolveCloudConfig, syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
17
+ import { ReticleRunner } from '../runs/reticle-runner.js';
18
+ import { createRunnerPort } from '../runs/runner-port.js';
19
+ import { RunStore } from '../runs/run-store.js';
20
+ import { renderRunReport } from '../runs/render-report.js';
21
+ import { BaselineStore } from '../project/baselines.js';
22
+ import { RecordingStore } from '../flows/recordings.js';
23
+ import { FlowStore } from '../flows/flows.js';
24
+ import { ProjectStore } from '../project/project-store.js';
25
+ import { AnnotationStore } from '../flows/annotation-store.js';
26
+ import { createNodeFileSystem } from '../project/fs-port.js';
27
27
  const EXIT_PASS = 0;
28
28
  const EXIT_FAIL = 1;
29
29
  const DEFAULT_SESSION_TIMEOUT_MS = 15_000;
@@ -63,7 +63,7 @@ export async function runVerify(args, ports) {
63
63
  return;
64
64
  }
65
65
  const names = await conn.listFlows();
66
- if (names.length === 0) {
66
+ if (0 === names.length) {
67
67
  ports.fail(MSG_NO_FLOWS);
68
68
  ports.exit(EXIT_FAIL);
69
69
  return;
@@ -89,7 +89,7 @@ export async function runVerify(args, ports) {
89
89
  */
90
90
  async function pushRunToCloud(run, ports) {
91
91
  const config = resolveCloudConfig(process.env);
92
- if (config === null)
92
+ if (null === config)
93
93
  return;
94
94
  const result = await syncRunToCloud(run, config, (url, init) => fetch(url, init));
95
95
  if (result.outcome === SyncOutcome.SYNCED) {
@@ -7,15 +7,16 @@
7
7
  * is `<repo>/.reticle/cloud.json`. Auth for a command = `RETICLE_CLOUD_KEY` env (agent) OR the login token.
8
8
  */
9
9
  import { mkdir, writeFile, readFile } from 'node:fs/promises';
10
+ import { NodePlatform } from '../platform.js';
10
11
  import { spawn } from 'node:child_process';
11
12
  import { setTimeout as sleep } from 'node:timers/promises';
12
13
  import { homedir } from 'node:os';
13
14
  import { join } from 'node:path';
14
15
  import { z } from 'zod';
15
- import { createNodeFileSystem } from './project/fs-port.js';
16
- import { RunStore } from './runs/run-store.js';
17
- import { resolveProjectCloud } from './cloud/cloud-config.js';
18
- import { syncRunToCloud, SyncOutcome } from './cloud/cloud-sync.js';
16
+ import { createNodeFileSystem } from '../project/fs-port.js';
17
+ import { RunStore } from '../runs/run-store.js';
18
+ import { resolveProjectCloud } from '../cloud/cloud-config.js';
19
+ import { syncRunToCloud, SyncOutcome } from '../cloud/cloud-sync.js';
19
20
  const DEFAULT_URL = 'http://localhost:8890';
20
21
  const RETICLE_DIR = '.reticle';
21
22
  const SESSION_FILE = 'session.json';
@@ -162,8 +163,12 @@ const DevicePollSchema = z.object({
162
163
  });
163
164
  /** Best-effort open the approval page in the default browser; the printed URL is the headless fallback. */
164
165
  const openBrowser = (target) => {
165
- const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
166
- const args = process.platform === 'win32' ? ['/c', 'start', '', target] : [target];
166
+ const cmd = NodePlatform.MACOS === process.platform
167
+ ? 'open'
168
+ : NodePlatform.WINDOWS === process.platform
169
+ ? 'cmd'
170
+ : 'xdg-open';
171
+ const args = NodePlatform.WINDOWS === process.platform ? ['/c', 'start', '', target] : [target];
167
172
  try {
168
173
  const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
169
174
  child.on('error', () => undefined);
@@ -193,18 +198,18 @@ const cmdLoginDevice = async () => {
193
198
  for (;;) {
194
199
  await sleep(intervalMs);
195
200
  const poll = DevicePollSchema.parse(await api('POST', `${url}/v1/auth/device/token`, null, { deviceCode: started.deviceCode }));
196
- if (poll.status === 'approved' && poll.token !== undefined && poll.org !== undefined) {
201
+ if ('approved' === poll.status && poll.token !== undefined && poll.org !== undefined) {
197
202
  await writeSession(url, poll.token, poll.org.name);
198
203
  return 0;
199
204
  }
200
- if (poll.status === 'pending') {
205
+ if ('pending' === poll.status) {
201
206
  if (Date.now() > started.expiresAt) {
202
207
  err('device login expired — run `reticle login` again');
203
208
  return 1;
204
209
  }
205
210
  continue;
206
211
  }
207
- err(poll.status === 'denied'
212
+ err('denied' === poll.status
208
213
  ? 'device login was denied in the browser'
209
214
  : 'device login expired — run `reticle login` again');
210
215
  return 1;
@@ -264,7 +269,7 @@ const cmdWhoami = async () => {
264
269
  verify: cloud.verify,
265
270
  },
266
271
  });
267
- if (cloud.config === null)
272
+ if (null === cloud.config)
268
273
  hint('this repo is not attached — run `reticle link`');
269
274
  return 0;
270
275
  };
@@ -273,18 +278,18 @@ const cmdProject = async (argv) => {
273
278
  const session = await readSession();
274
279
  const token = bearer(session);
275
280
  const url = baseUrl(session);
276
- if (token === null) {
281
+ if (null === token) {
277
282
  err('run `reticle login` first, or set RETICLE_CLOUD_KEY');
278
283
  return 2;
279
284
  }
280
285
  const sub = argv[0];
281
- if (sub === 'ls') {
286
+ if ('ls' === sub) {
282
287
  emit(await api('GET', `${url}/v1/projects`, token));
283
288
  return 0;
284
289
  }
285
- if (sub === 'create') {
290
+ if ('create' === sub) {
286
291
  const name = argv.slice(1).join(' ').trim();
287
- if (name.length === 0) {
292
+ if (0 === name.length) {
288
293
  err('usage: reticle project create <name>');
289
294
  return 2;
290
295
  }
@@ -293,17 +298,17 @@ const cmdProject = async (argv) => {
293
298
  hint(`next: \`reticle link --project ${created.projectId}\` to bind this repo`);
294
299
  return 0;
295
300
  }
296
- if (sub === 'rename') {
301
+ if ('rename' === sub) {
297
302
  const id = argv[1];
298
303
  const name = argv.slice(2).join(' ').trim();
299
- if (id === undefined || name.length === 0) {
304
+ if (id === undefined || 0 === name.length) {
300
305
  err('usage: reticle project rename <projectId> <new name>');
301
306
  return 2;
302
307
  }
303
308
  emit(await api('PATCH', `${url}/v1/projects/${encodeURIComponent(id)}`, token, { name }));
304
309
  return 0;
305
310
  }
306
- if (sub === 'rm' || sub === 'delete') {
311
+ if ('rm' === sub || 'delete' === sub) {
307
312
  const id = argv[1];
308
313
  if (id === undefined) {
309
314
  err('usage: reticle project rm <projectId>');
@@ -357,7 +362,7 @@ const cmdLink = async (argv) => {
357
362
  await mkdir(reticleDir, { recursive: true });
358
363
  const linkPath = join(reticleDir, CLOUD_LINK_FILE);
359
364
  const prev = await readJson(linkPath);
360
- const prevObj = typeof prev === 'object' && prev !== null ? prev : {};
365
+ const prevObj = 'object' === typeof prev && prev !== null ? prev : {};
361
366
  const cloudJson = {
362
367
  projectId,
363
368
  projectName,
@@ -369,7 +374,7 @@ const cmdLink = async (argv) => {
369
374
  await mkdir(home(), { recursive: true });
370
375
  const credPath = join(home(), CREDENTIALS_FILE);
371
376
  const creds = (await readJson(credPath)) ?? {};
372
- const credObj = typeof creds === 'object' && creds !== null ? creds : {};
377
+ const credObj = 'object' === typeof creds && creds !== null ? creds : {};
373
378
  credObj[projectId] = key;
374
379
  await writeFile(credPath, `${JSON.stringify(credObj, null, 2)}\n`);
375
380
  emit({ linked: projectName, projectId, cloudJson: linkPath, credentials: credPath });
@@ -381,15 +386,15 @@ const cmdConfig = async (argv) => {
381
386
  const f = flags(argv);
382
387
  const linkPath = join(process.cwd(), RETICLE_DIR, CLOUD_LINK_FILE);
383
388
  const raw = await readJson(linkPath);
384
- if (raw === null || typeof raw !== 'object') {
389
+ if (null === raw || typeof raw !== 'object') {
385
390
  err('no .reticle/cloud.json here — run `reticle link` first');
386
391
  return 2;
387
392
  }
388
393
  const cfg = raw;
389
- const sync = typeof cfg['sync'] === 'object' && cfg['sync'] !== null
394
+ const sync = 'object' === typeof cfg['sync'] && cfg['sync'] !== null
390
395
  ? cfg['sync']
391
396
  : { runs: true, memory: true, flows: true };
392
- const onoff = (v) => v === 'on' ? true : v === 'off' ? false : undefined;
397
+ const onoff = (v) => 'on' === v ? true : 'off' === v ? false : undefined;
393
398
  for (const k of ['runs', 'memory', 'flows']) {
394
399
  if (f[k] === undefined)
395
400
  continue;
@@ -417,7 +422,7 @@ const cmdPush = async () => {
417
422
  const fs = createNodeFileSystem();
418
423
  const reticleRoot = join(process.cwd(), RETICLE_DIR);
419
424
  const cloud = await resolveProjectCloud(fs, reticleRoot, homedir(), process.env);
420
- if (cloud.config === null) {
425
+ if (null === cloud.config) {
421
426
  err('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
422
427
  return 1;
423
428
  }
@@ -448,7 +453,7 @@ const cmdPush = async () => {
448
453
  const repoCloud = async () => {
449
454
  const fs = createNodeFileSystem();
450
455
  const cloud = await resolveProjectCloud(fs, join(process.cwd(), RETICLE_DIR), homedir(), process.env);
451
- if (cloud.config === null)
456
+ if (null === cloud.config)
452
457
  throw new Error('cloud not attached here — run `reticle link` (or set RETICLE_CLOUD_URL/KEY)');
453
458
  return cloud.config;
454
459
  };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * What `doctor` says about the daemon it found — pure, so it can be tested without a socket.
3
+ *
4
+ * Doctor already stopped lying about the PORT: `probePresence` separates "a daemon is here" from "a
5
+ * stranger holds it" from "nothing is listening", which was the load-bearing half of the report. It
6
+ * still did not say WHICH daemon, though the `/status` payload it already fetches carries `version`
7
+ * and `contract` and it discarded both.
8
+ *
9
+ * That is the gap worth closing, because skew is invisible everywhere else: a CLI and a daemon on
10
+ * different contracts connect anyway and then disagree about behaviour, which reaches the agent as a
11
+ * bare `-32000` naming no version at all. Doctor is the command a human runs at exactly that moment.
12
+ *
13
+ * Same split as `classifyPort`/`probePresence`: the rule lives here and is tested; the fetching stays
14
+ * at the call site.
15
+ */
16
+ /** The fields of `/status` this line cares about. Both optional — an old daemon reports neither. */
17
+ export interface DaemonIdentity {
18
+ version?: string;
19
+ contract?: string;
20
+ }
21
+ export interface DaemonLine {
22
+ /** The `daemon ✓ …` line itself. */
23
+ text: string;
24
+ /** A skew sentence to print underneath, when this CLI and that daemon disagree. */
25
+ skew?: string;
26
+ }
27
+ /**
28
+ * Build the daemon line for a port that answered `/status`.
29
+ *
30
+ * `pid` comes from the pid file and is genuinely optional — a daemon started by another user, or one
31
+ * whose pid file was cleaned up, still answers. Printing `(pid null)` would be worse than printing
32
+ * nothing, which is the same class of mistake as the port lie this command already fixed.
33
+ */
34
+ export declare function daemonLine(port: number, pid: number | null, peer: DaemonIdentity, self: {
35
+ version: string;
36
+ contract: string;
37
+ }): DaemonLine;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * What `doctor` says about the daemon it found — pure, so it can be tested without a socket.
3
+ *
4
+ * Doctor already stopped lying about the PORT: `probePresence` separates "a daemon is here" from "a
5
+ * stranger holds it" from "nothing is listening", which was the load-bearing half of the report. It
6
+ * still did not say WHICH daemon, though the `/status` payload it already fetches carries `version`
7
+ * and `contract` and it discarded both.
8
+ *
9
+ * That is the gap worth closing, because skew is invisible everywhere else: a CLI and a daemon on
10
+ * different contracts connect anyway and then disagree about behaviour, which reaches the agent as a
11
+ * bare `-32000` naming no version at all. Doctor is the command a human runs at exactly that moment.
12
+ *
13
+ * Same split as `classifyPort`/`probePresence`: the rule lives here and is tested; the fetching stays
14
+ * at the call site.
15
+ */
16
+ import { describeSkew } from '../version/version-skew.js';
17
+ const FIX = 'Restart it so both ends are the same build: `reticle stop`, then let your agent reconnect.';
18
+ /**
19
+ * Build the daemon line for a port that answered `/status`.
20
+ *
21
+ * `pid` comes from the pid file and is genuinely optional — a daemon started by another user, or one
22
+ * whose pid file was cleaned up, still answers. Printing `(pid null)` would be worse than printing
23
+ * nothing, which is the same class of mistake as the port lie this command already fixed.
24
+ */
25
+ export function daemonLine(port, pid, peer, self) {
26
+ const parts = [];
27
+ if (null !== pid)
28
+ parts.push(`pid ${String(pid)}`);
29
+ if (peer.version !== undefined)
30
+ parts.push(`v${peer.version}`);
31
+ const detail = 0 === parts.length ? '' : ` (${parts.join(', ')})`;
32
+ const skew = describeSkew({ what: 'the daemon on this port', version: peer.version, contract: peer.contract, fix: FIX }, self);
33
+ const line = { text: ` daemon ✓ running on :${String(port)}${detail}` };
34
+ return skew === undefined ? line : { ...line, skew };
35
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Who is holding the bridge port.
3
+ *
4
+ * `doctor` is what a user runs when the agent cannot reach the bridge, and the commonest real cause
5
+ * is that something else is on 4400. It already stopped saying "nothing is listening" for that case,
6
+ * but it did not say WHICH process — so the reader's next move was a shell command they had to know.
7
+ *
8
+ * Naming the holder is also the safe answer to a trap this project has hit for real: the obvious
9
+ * lookup, `lsof -ti tcp:4400 | xargs kill -9`, ALSO kills the agent's own `reticle mcp` proxy,
10
+ * because that proxy holds a client connection to the same port and `-ti` does not filter to
11
+ * listeners. Every invocation here is `-sTCP:LISTEN`, and the message never suggests `-ti`.
12
+ *
13
+ * The parser is pure and the lookup takes an injected exec, so the rule is tested on every platform
14
+ * while the shelling out stays at the call site.
15
+ */
16
+ export interface PortHolder {
17
+ pid: number;
18
+ command: string;
19
+ }
20
+ /**
21
+ * The FIRST listener in `lsof -nP -iTCP:<port> -sTCP:LISTEN -F pc` output, or null.
22
+ *
23
+ * First, not "the only one": a dual-stack listener (IPv4 + IPv6) is reported twice, and on a port
24
+ * conflict the answer the reader needs is "something is there, here is what" rather than an
25
+ * exhaustive list. A second `p` record ends the first.
26
+ *
27
+ * Null on anything incomplete. Half an answer — a pid with no command — prints worse than the
28
+ * un-named message it would replace, which is the same mistake as the port lie this command fixed.
29
+ */
30
+ export declare function parsePortHolder(stdout: string): PortHolder | null;
31
+ /** What `doctor` prints for a port held by something that is not a Reticle daemon. */
32
+ export declare function describeForeignHolder(port: number, holder: PortHolder | null,
33
+ /**
34
+ * The pid we recorded for this port, from the daemon pid file.
35
+ *
36
+ * When it matches the process actually holding the port, the port is held by OUR daemon — one
37
+ * that accepts TCP and never answers `/status`, i.e. wedged. That classifies as FOREIGN, which is
38
+ * right for "can anything bind this?" and wrong to say out loud: the reader goes hunting a
39
+ * stranger, and the fix that works (`reticle stop`, it respawns on the next call) is never named.
40
+ */
41
+ ourPid?: number | null): string;
42
+ /** The lookup itself. `exec` is injected so the shelling out never runs in a unit test. */
43
+ export declare function findPortHolder(port: number, exec: (command: string, args: readonly string[]) => string | null): PortHolder | null;
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Who is holding the bridge port.
3
+ *
4
+ * `doctor` is what a user runs when the agent cannot reach the bridge, and the commonest real cause
5
+ * is that something else is on 4400. It already stopped saying "nothing is listening" for that case,
6
+ * but it did not say WHICH process — so the reader's next move was a shell command they had to know.
7
+ *
8
+ * Naming the holder is also the safe answer to a trap this project has hit for real: the obvious
9
+ * lookup, `lsof -ti tcp:4400 | xargs kill -9`, ALSO kills the agent's own `reticle mcp` proxy,
10
+ * because that proxy holds a client connection to the same port and `-ti` does not filter to
11
+ * listeners. Every invocation here is `-sTCP:LISTEN`, and the message never suggests `-ti`.
12
+ *
13
+ * The parser is pure and the lookup takes an injected exec, so the rule is tested on every platform
14
+ * while the shelling out stays at the call site.
15
+ */
16
+ /** `lsof -F pc` emits one field per line, prefixed by its selector: `p<pid>`, `c<command>`. */
17
+ const PID_FIELD = 'p';
18
+ const COMMAND_FIELD = 'c';
19
+ /**
20
+ * The FIRST listener in `lsof -nP -iTCP:<port> -sTCP:LISTEN -F pc` output, or null.
21
+ *
22
+ * First, not "the only one": a dual-stack listener (IPv4 + IPv6) is reported twice, and on a port
23
+ * conflict the answer the reader needs is "something is there, here is what" rather than an
24
+ * exhaustive list. A second `p` record ends the first.
25
+ *
26
+ * Null on anything incomplete. Half an answer — a pid with no command — prints worse than the
27
+ * un-named message it would replace, which is the same mistake as the port lie this command fixed.
28
+ */
29
+ export function parsePortHolder(stdout) {
30
+ let pid;
31
+ let command;
32
+ for (const raw of stdout.split('\n')) {
33
+ const line = raw.trim();
34
+ if (0 === line.length)
35
+ continue;
36
+ const value = line.slice(1);
37
+ if (line.startsWith(PID_FIELD)) {
38
+ if (pid !== undefined)
39
+ break; // a second record: the first one is the answer
40
+ const parsed = Number(value);
41
+ if (!Number.isInteger(parsed) || parsed <= 0)
42
+ return null;
43
+ pid = parsed;
44
+ continue;
45
+ }
46
+ if (line.startsWith(COMMAND_FIELD) && command === undefined)
47
+ command = value;
48
+ }
49
+ return pid === undefined || command === undefined ? null : { pid, command };
50
+ }
51
+ /** What `doctor` prints for a port held by something that is not a Reticle daemon. */
52
+ export function describeForeignHolder(port, holder,
53
+ /**
54
+ * The pid we recorded for this port, from the daemon pid file.
55
+ *
56
+ * When it matches the process actually holding the port, the port is held by OUR daemon — one
57
+ * that accepts TCP and never answers `/status`, i.e. wedged. That classifies as FOREIGN, which is
58
+ * right for "can anything bind this?" and wrong to say out loud: the reader goes hunting a
59
+ * stranger, and the fix that works (`reticle stop`, it respawns on the next call) is never named.
60
+ */
61
+ ourPid) {
62
+ const tail = 'a daemon cannot bind it. Stop that process, or run Reticle on a different port ' +
63
+ '(`--port`), then retry.';
64
+ if (null === holder) {
65
+ /**
66
+ * We could not identify the holder — no `lsof` (every Windows user, and 35% of them by the
67
+ * 2026-08 telemetry; also a slim container), or the lookup timed out.
68
+ *
69
+ * This used to assert the port was held by "another process that is not a Reticle daemon",
70
+ * which is a claim the null case has NO evidence for. It is also the likeliest thing to be
71
+ * wrong: reaching here at all means presence is FOREIGN — the port accepts TCP and never
72
+ * answers `/status` — and the commonest cause of that is OUR OWN daemon, wedged. So on the one
73
+ * platform where the lookup cannot run, `doctor` sent the reader hunting a stranger and never
74
+ * named `reticle stop`, the fix that actually works.
75
+ *
76
+ * Say what is known, and name the recorded pid when there is one.
77
+ */
78
+ const ours = ourPid !== null && ourPid !== undefined
79
+ ? ` Reticle's own records show pid ${String(ourPid)} for this port — if that process is ` +
80
+ 'still around it is a wedged Reticle daemon, and `reticle stop` clears it.'
81
+ : '';
82
+ return `port ${String(port)} is held by a process Reticle could not identify — ${tail}${ours}`;
83
+ }
84
+ if (ourPid !== null && ourPid !== undefined && ourPid === holder.pid) {
85
+ return (`port ${String(port)} is held by YOUR Reticle daemon (pid ${String(holder.pid)}), and it is ` +
86
+ 'not responding — running but wedged, so nothing can use it and nothing else can bind the ' +
87
+ 'port. Stop it with `reticle stop`; the next tool call starts a fresh one.');
88
+ }
89
+ return (`port ${String(port)} is held by pid ${String(holder.pid)} ("${holder.command}"), which is not ` +
90
+ `a Reticle daemon — ${tail}`);
91
+ }
92
+ /** The lookup itself. `exec` is injected so the shelling out never runs in a unit test. */
93
+ export function findPortHolder(port, exec) {
94
+ // -n/-P skip DNS and service-name lookups (slow, and irrelevant here). -sTCP:LISTEN is the part
95
+ // that matters: without it this also reports the agent's own MCP proxy, which is a client of the
96
+ // port, not its owner.
97
+ const out = exec('lsof', ['-nP', `-iTCP:${String(port)}`, '-sTCP:LISTEN', '-F', 'pc']);
98
+ return null === out ? null : parsePortHolder(out);
99
+ }
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { parseCliArgs, CLI_USAGE } from './cli-parse.js';
2
+ import { parseCliArgs, CLI_USAGE } from './cli/cli-parse.js';
3
3
  export { parseCliArgs, CLI_USAGE };
4
- export type { CliResult } from './cli-parse.js';
4
+ export type { CliResult } from './cli/cli-parse.js';