@reticlehq/server 3.7.0 → 3.8.1

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 (578) hide show
  1. package/SKILL.md +59 -68
  2. package/bin/reticle.js +9 -2
  3. package/dist/command/cli/adhoc-suite.js +1 -1
  4. package/dist/command/cli/adhoc-verdict.d.ts +12 -0
  5. package/dist/command/cli/adhoc-verdict.js +62 -4
  6. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  7. package/dist/command/cli/answers/gate-hook-message.js +5 -1
  8. package/dist/command/cli/auth/cloud-session.d.ts +11 -19
  9. package/dist/command/cli/auth/cloud-session.js +23 -0
  10. package/dist/command/cli/auth/device-flow.d.ts +3 -37
  11. package/dist/command/cli/cli-doctor.js +66 -21
  12. package/dist/command/cli/cli-flow-commands.js +53 -11
  13. package/dist/command/cli/cli-kill.d.ts +8 -0
  14. package/dist/command/cli/cli-kill.js +43 -3
  15. package/dist/command/cli/cli-parse-feedback.d.ts +4 -0
  16. package/dist/command/cli/cli-parse-feedback.js +13 -1
  17. package/dist/command/cli/cli-parse-grammar.d.ts +1 -2
  18. package/dist/command/cli/cli-parse-grammar.js +1 -6
  19. package/dist/command/cli/cli-parse-serve.d.ts +24 -0
  20. package/dist/command/cli/cli-parse-serve.js +124 -0
  21. package/dist/command/cli/cli-parse.d.ts +12 -11
  22. package/dist/command/cli/cli-parse.js +100 -123
  23. package/dist/command/cli/cli-usage.d.ts +0 -11
  24. package/dist/command/cli/cli-usage.js +43 -16
  25. package/dist/command/cli/cli-verify.d.ts +1 -1
  26. package/dist/command/cli/cli-verify.js +29 -14
  27. package/dist/command/cli/cloud-cli.js +27 -9
  28. package/dist/command/cli/cloud-kit.js +3 -3
  29. package/dist/command/cli/cloud-link.d.ts +1 -20
  30. package/dist/command/cli/cloud-link.js +2 -0
  31. package/dist/command/cli/cloud-login.d.ts +6 -2
  32. package/dist/command/cli/cloud-login.js +13 -41
  33. package/dist/command/cli/config/checkout.d.ts +10 -0
  34. package/dist/command/cli/config/checkout.js +78 -0
  35. package/dist/command/cli/doctor/doctor-agent-link.d.ts +23 -0
  36. package/dist/command/cli/doctor/doctor-agent-link.js +28 -0
  37. package/dist/command/cli/doctor/doctor-coverage-line.js +1 -7
  38. package/dist/command/cli/doctor/doctor-rows.d.ts +4 -0
  39. package/dist/command/cli/doctor/doctor-rows.js +4 -0
  40. package/dist/command/cli/drive/drive-attach.d.ts +5 -0
  41. package/dist/command/cli/drive/drive-attach.js +21 -1
  42. package/dist/command/cli/hook/codex-transcript.d.ts +20 -0
  43. package/dist/command/cli/hook/codex-transcript.js +224 -0
  44. package/dist/command/cli/hook/episode-structure.d.ts +24 -0
  45. package/dist/command/cli/hook/episode-structure.js +138 -0
  46. package/dist/command/cli/hook/hook-command.d.ts +9 -0
  47. package/dist/command/cli/hook/hook-command.js +211 -0
  48. package/dist/command/cli/hook/hook-fast.d.ts +31 -0
  49. package/dist/command/cli/hook/hook-fast.js +136 -0
  50. package/dist/command/cli/hook/prompt-hook.d.ts +30 -0
  51. package/dist/command/cli/hook/prompt-hook.js +199 -0
  52. package/dist/command/cli/hook/tool-hook.d.ts +21 -0
  53. package/dist/command/cli/hook/tool-hook.js +259 -0
  54. package/dist/command/cli/launch/cli-launch.d.ts +2 -0
  55. package/dist/command/cli/launch/cli-launch.js +22 -2
  56. package/dist/command/cli/lifecycle/daemon-lifecycle.js +28 -2
  57. package/dist/command/cli/mcp-command.d.ts +9 -0
  58. package/dist/command/cli/mcp-command.js +127 -41
  59. package/dist/command/cli/open-navigate.d.ts +43 -0
  60. package/dist/command/cli/open-navigate.js +85 -0
  61. package/dist/command/cli/ports/port-holder.d.ts +16 -4
  62. package/dist/command/cli/ports/port-holder.js +58 -12
  63. package/dist/command/cli/ports/resolve/cli-port.js +18 -2
  64. package/dist/command/cli/project-name.d.ts +1 -7
  65. package/dist/command/cli/project-name.js +1 -0
  66. package/dist/command/cli/report-command.d.ts +8 -2
  67. package/dist/command/cli/report-command.js +95 -18
  68. package/dist/command/cli/status-fields.d.ts +9 -2
  69. package/dist/command/cli/status-fields.js +148 -4
  70. package/dist/command/cli/try-command.d.ts +3 -4
  71. package/dist/command/cli/try-command.js +6 -11
  72. package/dist/command/cli/verify-results.d.ts +4 -1
  73. package/dist/command/cli/verify-results.js +3 -1
  74. package/dist/command/cli.d.ts +1 -7
  75. package/dist/command/cli.js +79 -160
  76. package/dist/command/daemon/daemon-resilience.d.ts +6 -1
  77. package/dist/command/daemon/daemon-resilience.js +31 -18
  78. package/dist/command/daemon/daemon.d.ts +11 -0
  79. package/dist/command/daemon/daemon.js +70 -2
  80. package/dist/command/daemon/lifetime/daemon-usefulness.d.ts +2 -0
  81. package/dist/command/daemon/lifetime/daemon-usefulness.js +13 -2
  82. package/dist/command/daemon/wake-decision.js +4 -2
  83. package/dist/command/daemon/win32-escape.d.ts +32 -0
  84. package/dist/command/daemon/win32-escape.js +119 -0
  85. package/dist/command/setup/agent-configs.d.ts +9 -0
  86. package/dist/command/setup/agent-configs.js +9 -0
  87. package/dist/command/setup/auto-approve.d.ts +10 -3
  88. package/dist/command/setup/auto-approve.js +51 -3
  89. package/dist/command/setup/bringup/dev-server-files.d.ts +1 -9
  90. package/dist/command/setup/bringup/dev-server-wait.js +5 -1
  91. package/dist/command/setup/bringup/ensure-daemon.js +4 -7
  92. package/dist/command/setup/init/init-host.js +6 -0
  93. package/dist/command/setup/init/init-port.js +5 -4
  94. package/dist/command/setup/init/init-runtime.js +7 -1
  95. package/dist/command/setup/node-effects.d.ts +8 -0
  96. package/dist/command/setup/node-effects.js +36 -4
  97. package/dist/command/setup/probe/dev-port.d.ts +26 -0
  98. package/dist/command/setup/probe/dev-port.js +117 -0
  99. package/dist/command/setup/probe/page-probe.d.ts +1 -1
  100. package/dist/command/setup/probe/page-probe.js +11 -3
  101. package/dist/command/setup/remaining-steps.d.ts +0 -7
  102. package/dist/command/setup/remaining-steps.js +3 -2
  103. package/dist/command/setup/run-setup.d.ts +11 -0
  104. package/dist/command/setup/run-setup.js +33 -5
  105. package/dist/command/setup/setup-command.d.ts +2 -0
  106. package/dist/command/setup/setup-command.js +22 -4
  107. package/dist/command/update/updater.d.ts +1 -6
  108. package/dist/command/update/updater.js +4 -5
  109. package/dist/command/update/version-seen.d.ts +24 -0
  110. package/dist/command/update/version-seen.js +65 -0
  111. package/dist/command/version/peer-announce.d.ts +9 -1
  112. package/dist/command/version/peer-announce.js +25 -2
  113. package/dist/command/version/sdk-fix.d.ts +5 -6
  114. package/dist/command/version/sdk-fix.js +17 -32
  115. package/dist/command/version/version-skew.d.ts +16 -1
  116. package/dist/command/version/version-skew.js +84 -10
  117. package/dist/features/crawl/crawl-tools.js +2 -2
  118. package/dist/features/crawl/crawl.js +2 -0
  119. package/dist/features/exhaust/ledger.d.ts +15 -96
  120. package/dist/features/exhaust/ledger.js +3 -3
  121. package/dist/features/harness/drive-runs.d.ts +28 -4
  122. package/dist/features/harness/drive-runs.js +99 -7
  123. package/dist/features/harness/drivers.d.ts +1 -1
  124. package/dist/features/harness/drivers.js +1 -1
  125. package/dist/features/harness/harness.d.ts +5 -0
  126. package/dist/features/harness/harness.js +24 -12
  127. package/dist/features/harness/platform/platform-drives.d.ts +7 -1
  128. package/dist/features/harness/platform/platform-drives.js +12 -7
  129. package/dist/features/harness/platform/refusal.d.ts +1 -1
  130. package/dist/features/harness/platform/refusal.js +4 -4
  131. package/dist/features/harness/platform/remote-drive.js +70 -28
  132. package/dist/features/harness/platform/server-driver.js +22 -1
  133. package/dist/features/harness/platform-config.d.ts +1 -10
  134. package/dist/features/harness/platform-config.js +0 -20
  135. package/dist/features/license/license.d.ts +2 -14
  136. package/dist/features/visual/step-shots.d.ts +33 -1
  137. package/dist/features/visual/step-shots.js +155 -1
  138. package/dist/features/visual/visual-tools.js +12 -4
  139. package/dist/hooks/coding-agents.d.ts +6 -0
  140. package/dist/hooks/coding-agents.js +24 -3
  141. package/dist/hooks/episode-stamp.d.ts +32 -0
  142. package/dist/hooks/episode-stamp.js +61 -0
  143. package/dist/hooks/hook-commands.js +19 -9
  144. package/dist/index.d.ts +5 -1
  145. package/dist/index.js +66 -24
  146. package/dist/judgement/app-graph/journal-steps.d.ts +8 -0
  147. package/dist/judgement/app-graph/journal-steps.js +92 -0
  148. package/dist/judgement/capsule/capsule-store.d.ts +2 -39
  149. package/dist/judgement/domain/domain-model.js +1 -10
  150. package/dist/judgement/domain/domain-tools.js +4 -2
  151. package/dist/judgement/runs/artifact/run-store.d.ts +28 -8
  152. package/dist/judgement/runs/artifact/run-store.js +153 -28
  153. package/dist/judgement/runs/default-run-id.d.ts +3 -1
  154. package/dist/judgement/runs/default-run-id.js +15 -3
  155. package/dist/judgement/runs/drive-run.d.ts +3 -3
  156. package/dist/judgement/runs/drive-run.js +11 -7
  157. package/dist/judgement/runs/run-ledger.d.ts +21 -0
  158. package/dist/judgement/runs/run-ledger.js +143 -0
  159. package/dist/judgement/runs/runner-port.d.ts +1 -1
  160. package/dist/judgement/runs/runner-port.js +8 -2
  161. package/dist/judgement/runs/verification-sync.d.ts +4 -3
  162. package/dist/judgement/runs/verification-sync.js +27 -5
  163. package/dist/language/flows/affected-tools.js +2 -1
  164. package/dist/language/flows/annotate-notes/annotate.js +8 -3
  165. package/dist/language/flows/annotate-tools.js +1 -1
  166. package/dist/language/flows/change/affected.d.ts +8 -0
  167. package/dist/language/flows/change/affected.js +20 -7
  168. package/dist/language/flows/decision.js +19 -4
  169. package/dist/language/flows/fields/flow-secret-field.d.ts +9 -0
  170. package/dist/language/flows/fields/flow-secret-field.js +67 -5
  171. package/dist/language/flows/flow-anchor.d.ts +5 -0
  172. package/dist/language/flows/flow-anchor.js +4 -0
  173. package/dist/language/flows/flow-cross-step.d.ts +7 -1
  174. package/dist/language/flows/flow-cross-step.js +9 -0
  175. package/dist/language/flows/flow-expect-grammar.js +15 -10
  176. package/dist/language/flows/flow-intent.js +6 -1
  177. package/dist/language/flows/flow-journey.d.ts +8 -0
  178. package/dist/language/flows/flow-journey.js +13 -0
  179. package/dist/language/flows/flow-lease.d.ts +27 -0
  180. package/dist/language/flows/flow-lease.js +59 -0
  181. package/dist/language/flows/flow-replay-run.d.ts +1 -87
  182. package/dist/language/flows/flow-replay-run.js +111 -310
  183. package/dist/language/flows/flow-replay.js +21 -19
  184. package/dist/language/flows/flow-replayable.d.ts +38 -0
  185. package/dist/language/flows/flow-replayable.js +179 -0
  186. package/dist/language/flows/flow-start-path.d.ts +108 -0
  187. package/dist/language/flows/flow-start-path.js +313 -0
  188. package/dist/language/flows/flow-step-runners.d.ts +23 -2
  189. package/dist/language/flows/flow-step-runners.js +191 -32
  190. package/dist/language/flows/flow-store-for-session.d.ts +8 -1
  191. package/dist/language/flows/flow-store-for-session.js +6 -0
  192. package/dist/language/flows/flow-tools.d.ts +2 -23
  193. package/dist/language/flows/flow-tools.js +63 -113
  194. package/dist/language/flows/flows.d.ts +4 -6
  195. package/dist/language/flows/flows.js +90 -15
  196. package/dist/language/flows/heal-run.d.ts +10 -4
  197. package/dist/language/flows/heal-run.js +26 -9
  198. package/dist/language/flows/recording/flake.d.ts +3 -27
  199. package/dist/language/flows/recording/flake.js +1 -1
  200. package/dist/language/flows/recording/recording-backtrack.js +1 -4
  201. package/dist/language/flows/recording/tape/recordings.d.ts +11 -1
  202. package/dist/language/flows/recording/tape/recordings.js +9 -3
  203. package/dist/language/flows/replay.d.ts +25 -5
  204. package/dist/language/flows/replay.js +83 -16
  205. package/dist/language/flows/stores/assertion-tiers-store.js +1 -1
  206. package/dist/language/flows/suite/coverage.d.ts +9 -0
  207. package/dist/language/flows/suite/coverage.js +12 -1
  208. package/dist/language/flows/suite-selection.d.ts +25 -0
  209. package/dist/language/flows/suite-selection.js +41 -9
  210. package/dist/machine/git-tracks.d.ts +6 -0
  211. package/dist/machine/git-tracks.js +36 -0
  212. package/dist/memory/cloud/background-sync.d.ts +25 -0
  213. package/dist/memory/cloud/background-sync.js +79 -0
  214. package/dist/memory/cloud/cloud-config.d.ts +12 -1
  215. package/dist/memory/cloud/cloud-config.js +39 -13
  216. package/dist/memory/cloud/cloud-sync.d.ts +3 -24
  217. package/dist/memory/cloud/cloud-sync.js +5 -2
  218. package/dist/memory/cloud/harness-config.d.ts +0 -7
  219. package/dist/memory/cloud/memory-scope.d.ts +19 -0
  220. package/dist/memory/cloud/memory-scope.js +48 -0
  221. package/dist/memory/cloud/sync-answer.d.ts +43 -0
  222. package/dist/memory/cloud/sync-answer.js +42 -0
  223. package/dist/memory/cloud/sync-cycle.d.ts +40 -10
  224. package/dist/memory/cloud/sync-cycle.js +97 -144
  225. package/dist/memory/cloud/sync-daemon.d.ts +50 -3
  226. package/dist/memory/cloud/sync-daemon.js +473 -83
  227. package/dist/memory/cloud/sync-describe.d.ts +28 -0
  228. package/dist/memory/cloud/sync-describe.js +92 -0
  229. package/dist/memory/cloud/sync-disk.d.ts +9 -1
  230. package/dist/memory/cloud/sync-disk.js +137 -33
  231. package/dist/memory/cloud/sync-flow-versions.d.ts +26 -0
  232. package/dist/memory/cloud/sync-flow-versions.js +42 -0
  233. package/dist/memory/impact/impact-store.js +32 -15
  234. package/dist/memory/intent/intent-shard.d.ts +41 -266
  235. package/dist/memory/intent/intent-tools.js +20 -61
  236. package/dist/memory/intent/record-request.d.ts +27 -0
  237. package/dist/memory/intent/record-request.js +88 -0
  238. package/dist/memory/intent/settled-words.js +9 -1
  239. package/dist/memory/journal/drive-flow.d.ts +3 -0
  240. package/dist/memory/journal/drive-flow.js +11 -41
  241. package/dist/memory/journal/envelope-store.js +1 -1
  242. package/dist/memory/journal/envelope.d.ts +6 -154
  243. package/dist/memory/journal/episode-log.d.ts +107 -0
  244. package/dist/memory/journal/episode-log.js +302 -0
  245. package/dist/memory/journal/journal-recorder.d.ts +7 -0
  246. package/dist/memory/journal/journal-recorder.js +22 -9
  247. package/dist/memory/journal/on-disk/ambient-file.d.ts +1 -7
  248. package/dist/memory/journal/on-disk/ambient-file.js +1 -1
  249. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +24 -3
  250. package/dist/memory/journal/on-disk/workspace-gitignore.js +38 -7
  251. package/dist/memory/journal/on-disk/workspace-tiers.js +7 -0
  252. package/dist/memory/journal/self-observation.js +3 -1
  253. package/dist/memory/journal/session-end.js +23 -9
  254. package/dist/memory/journal/session-journal.js +39 -20
  255. package/dist/memory/project/artifact-root-resolver.d.ts +14 -1
  256. package/dist/memory/project/artifact-root-resolver.js +68 -23
  257. package/dist/memory/project/artifact-root.d.ts +17 -3
  258. package/dist/memory/project/artifact-root.js +47 -9
  259. package/dist/memory/project/fs/fs-port.d.ts +5 -0
  260. package/dist/memory/project/fs/fs-port.js +13 -0
  261. package/dist/memory/project/learned-routes.d.ts +3 -4
  262. package/dist/memory/project/learned-routes.js +3 -11
  263. package/dist/memory/project/link-directory.js +5 -2
  264. package/dist/memory/project/root-claims.d.ts +3 -1
  265. package/dist/memory/project/root-claims.js +41 -7
  266. package/dist/memory/project/session-root.d.ts +1 -1
  267. package/dist/memory/project/session-root.js +1 -15
  268. package/dist/memory/project/sync-status.js +5 -6
  269. package/dist/memory/recall/memory-tools.js +2 -2
  270. package/dist/memory/recall/platform-memory.d.ts +3 -1
  271. package/dist/memory/recall/platform-memory.js +9 -1
  272. package/dist/memory/recall/project-memory.js +15 -19
  273. package/dist/portal/bridge/auth-failure-reason.d.ts +13 -0
  274. package/dist/portal/bridge/auth-failure-reason.js +19 -0
  275. package/dist/portal/bridge/bridge.d.ts +19 -4
  276. package/dist/portal/bridge/bridge.js +150 -177
  277. package/dist/portal/bridge/harness-request.d.ts +15 -0
  278. package/dist/portal/bridge/harness-request.js +19 -0
  279. package/dist/portal/bridge/origin/origin-follow.d.ts +46 -0
  280. package/dist/portal/bridge/origin/origin-follow.js +110 -0
  281. package/dist/portal/bridge/pairing-token.d.ts +6 -0
  282. package/dist/portal/bridge/pairing-token.js +47 -16
  283. package/dist/portal/bridge/protocol-skew.d.ts +27 -2
  284. package/dist/portal/bridge/protocol-skew.js +66 -2
  285. package/dist/portal/bridge/session-nonces.d.ts +46 -0
  286. package/dist/portal/bridge/session-nonces.js +72 -0
  287. package/dist/portal/input/viewport-tools.d.ts +31 -0
  288. package/dist/portal/input/viewport-tools.js +106 -15
  289. package/dist/portal/pool/bounded-close.d.ts +23 -0
  290. package/dist/portal/pool/bounded-close.js +38 -0
  291. package/dist/portal/pool/browser-pool.d.ts +44 -39
  292. package/dist/portal/pool/browser-pool.js +247 -95
  293. package/dist/portal/pool/context-permissions.d.ts +9 -1
  294. package/dist/portal/pool/context-permissions.js +26 -0
  295. package/dist/portal/pool/lease-emulation.d.ts +37 -0
  296. package/dist/portal/pool/lease-emulation.js +29 -0
  297. package/dist/portal/pool/playwright-launcher.d.ts +6 -0
  298. package/dist/portal/pool/playwright-launcher.js +74 -7
  299. package/dist/portal/pool/pool-contract.d.ts +23 -1
  300. package/dist/portal/pool/pool-lease.d.ts +128 -0
  301. package/dist/portal/pool/pool-lease.js +49 -0
  302. package/dist/portal/pool/zero-install.d.ts +8 -1
  303. package/dist/portal/pool/zero-install.js +24 -2
  304. package/dist/portal/production/production-activation.d.ts +136 -0
  305. package/dist/portal/production/production-activation.js +173 -0
  306. package/dist/portal/production/production-drive.d.ts +52 -0
  307. package/dist/portal/production/production-drive.js +301 -0
  308. package/dist/portal/production/production-hello.d.ts +23 -0
  309. package/dist/portal/production/production-hello.js +23 -0
  310. package/dist/portal/realm/web-realm.js +42 -15
  311. package/dist/portal/session/control-envelope.d.ts +1 -1
  312. package/dist/portal/session/dev-server/served-document.d.ts +0 -5
  313. package/dist/portal/session/dev-server/served-document.js +6 -8
  314. package/dist/portal/session/facts/handshake-facts.d.ts +8 -1
  315. package/dist/portal/session/first-run-wiring.d.ts +3 -36
  316. package/dist/portal/session/first-run-wiring.js +10 -103
  317. package/dist/portal/session/live-control-tools.js +61 -19
  318. package/dist/portal/session/no-session-next-action.d.ts +11 -0
  319. package/dist/portal/session/no-session-next-action.js +40 -15
  320. package/dist/portal/session/no-session-watch.d.ts +7 -0
  321. package/dist/portal/session/no-session-watch.js +67 -23
  322. package/dist/portal/session/output-budget.d.ts +10 -0
  323. package/dist/portal/session/output-budget.js +16 -0
  324. package/dist/portal/session/presence/session-lease.d.ts +2 -0
  325. package/dist/portal/session/presence/session-lease.js +8 -0
  326. package/dist/portal/session/recorded-gaps.d.ts +15 -11
  327. package/dist/portal/session/recorded-gaps.js +39 -18
  328. package/dist/portal/session/serving-directory.d.ts +24 -2
  329. package/dist/portal/session/serving-directory.js +93 -5
  330. package/dist/portal/session/session-info.d.ts +26 -0
  331. package/dist/portal/session/session-info.js +20 -0
  332. package/dist/portal/session/session-manager.d.ts +35 -3
  333. package/dist/portal/session/session-manager.js +69 -14
  334. package/dist/portal/session/session-successor.js +1 -9
  335. package/dist/portal/session/session-tools.js +22 -4
  336. package/dist/portal/session/session.d.ts +10 -10
  337. package/dist/portal/session/session.js +37 -29
  338. package/dist/portal/session/zero-install-first-call.d.ts +7 -2
  339. package/dist/portal/session/zero-install-first-call.js +43 -20
  340. package/dist/start-wiring.d.ts +2 -2
  341. package/dist/start-wiring.js +17 -3
  342. package/dist/status-payload.d.ts +11 -1
  343. package/dist/status-payload.js +7 -1
  344. package/dist/surface/agent-presence-grace.d.ts +11 -0
  345. package/dist/surface/agent-presence-grace.js +34 -0
  346. package/dist/surface/http-server.d.ts +12 -1
  347. package/dist/surface/http-server.js +64 -15
  348. package/dist/surface/mcp/faults/mcp-outage.d.ts +12 -2
  349. package/dist/surface/mcp/faults/mcp-outage.js +19 -1
  350. package/dist/surface/mcp/mcp-proxy.d.ts +17 -74
  351. package/dist/surface/mcp/mcp-proxy.js +194 -267
  352. package/dist/surface/mcp/mcp.d.ts +20 -3
  353. package/dist/surface/mcp/mcp.js +88 -25
  354. package/dist/surface/mcp/proxy/drive-calls.d.ts +44 -0
  355. package/dist/surface/mcp/proxy/drive-calls.js +114 -5
  356. package/dist/surface/mcp/proxy/proxy-boot.d.ts +11 -0
  357. package/dist/surface/mcp/proxy/proxy-boot.js +1 -0
  358. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +6 -0
  359. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +14 -1
  360. package/dist/surface/mcp/proxy/proxy-handshake.d.ts +34 -2
  361. package/dist/surface/mcp/proxy/proxy-handshake.js +76 -8
  362. package/dist/surface/mcp/server-instructions.js +35 -16
  363. package/dist/surface/mcp/shared-params.d.ts +8 -0
  364. package/dist/surface/mcp/shared-params.js +15 -1
  365. package/dist/surface/mcp/sse-frame-parser.d.ts +11 -0
  366. package/dist/surface/mcp/sse-frame-parser.js +18 -0
  367. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  368. package/dist/surface/mcp/surface-vocabulary.js +13 -0
  369. package/dist/surface/mcp/tool-catalog-cache.d.ts +2 -0
  370. package/dist/surface/mcp/tool-catalog-cache.js +14 -4
  371. package/dist/surface/tools/account-tool.d.ts +4 -3
  372. package/dist/surface/tools/account-tool.js +20 -31
  373. package/dist/surface/tools/act/act-danger.d.ts +2 -0
  374. package/dist/surface/tools/act/act-danger.js +41 -14
  375. package/dist/surface/tools/act/act-observation.d.ts +11 -0
  376. package/dist/surface/tools/act/act-observation.js +33 -0
  377. package/dist/surface/tools/act/act-preflight.js +10 -3
  378. package/dist/surface/tools/act/act-sequence-retry.js +2 -1
  379. package/dist/surface/tools/act/act-target.js +19 -5
  380. package/dist/surface/tools/act/durable.js +1 -1
  381. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +9 -7
  382. package/dist/surface/tools/act/navigation/navigate-arrival.js +11 -14
  383. package/dist/surface/tools/act/unsettled-find.d.ts +36 -0
  384. package/dist/surface/tools/act/unsettled-find.js +71 -0
  385. package/dist/surface/tools/act-sequence-tool.js +53 -24
  386. package/dist/surface/tools/act-tools.d.ts +2 -3
  387. package/dist/surface/tools/act-tools.js +49 -47
  388. package/dist/surface/tools/agent/compact-envelope.d.ts +2 -2
  389. package/dist/surface/tools/agent/for-the-agent.js +41 -3
  390. package/dist/surface/tools/assert/assert-grade.js +8 -3
  391. package/dist/surface/tools/assert/assert-verdict.js +4 -2
  392. package/dist/surface/tools/browser-tools.js +7 -4
  393. package/dist/surface/tools/chat-drives.js +50 -25
  394. package/dist/surface/tools/coverage-tools.js +1 -1
  395. package/dist/surface/tools/dynamic-tools.d.ts +8 -0
  396. package/dist/surface/tools/dynamic-tools.js +89 -34
  397. package/dist/surface/tools/empty-session-list.d.ts +18 -25
  398. package/dist/surface/tools/empty-session-list.js +67 -38
  399. package/dist/surface/tools/error-recovery.d.ts +17 -7
  400. package/dist/surface/tools/error-recovery.js +53 -20
  401. package/dist/surface/tools/explore-tools.d.ts +10 -0
  402. package/dist/surface/tools/explore-tools.js +72 -19
  403. package/dist/surface/tools/feedback-tools.js +23 -2
  404. package/dist/surface/tools/gap-novelty.d.ts +6 -0
  405. package/dist/surface/tools/gap-novelty.js +9 -0
  406. package/dist/surface/tools/gaps/capability-absences.js +4 -3
  407. package/dist/surface/tools/harness-explore.d.ts +1 -1
  408. package/dist/surface/tools/harness-explore.js +6 -4
  409. package/dist/surface/tools/harness-gate.d.ts +43 -21
  410. package/dist/surface/tools/harness-gate.js +55 -81
  411. package/dist/surface/tools/harness-script.d.ts +2 -0
  412. package/dist/surface/tools/harness-script.js +11 -6
  413. package/dist/surface/tools/harness-toolset.js +3 -4
  414. package/dist/surface/tools/invoke-tool.d.ts +0 -8
  415. package/dist/surface/tools/invoke-tool.js +164 -178
  416. package/dist/surface/tools/lease-hint.d.ts +9 -0
  417. package/dist/surface/tools/lease-hint.js +22 -3
  418. package/dist/surface/tools/lease-permissions.d.ts +1 -1
  419. package/dist/surface/tools/lease-readiness.d.ts +55 -0
  420. package/dist/surface/tools/lease-readiness.js +150 -2
  421. package/dist/surface/tools/lease-tools.d.ts +3 -75
  422. package/dist/surface/tools/lease-tools.js +124 -188
  423. package/dist/surface/tools/map-tools.d.ts +20 -0
  424. package/dist/surface/tools/map-tools.js +145 -0
  425. package/dist/surface/tools/merge-tools.js +11 -7
  426. package/dist/surface/tools/next-step.js +6 -3
  427. package/dist/surface/tools/observe-tools.js +46 -35
  428. package/dist/surface/tools/platform-moment.d.ts +3 -12
  429. package/dist/surface/tools/platform-moment.js +12 -24
  430. package/dist/surface/tools/read/complete-snapshot.d.ts +7 -1
  431. package/dist/surface/tools/read/complete-snapshot.js +13 -2
  432. package/dist/surface/tools/read/query-paginate.js +6 -0
  433. package/dist/surface/tools/read/query-shape.d.ts +9 -0
  434. package/dist/surface/tools/read/query-shape.js +15 -0
  435. package/dist/surface/tools/read/snapshot-unmounted-note.d.ts +1 -0
  436. package/dist/surface/tools/read/snapshot-unmounted-note.js +73 -0
  437. package/dist/surface/tools/read-tools.js +5 -5
  438. package/dist/surface/tools/real-input-attempt.d.ts +11 -0
  439. package/dist/surface/tools/real-input-attempt.js +206 -38
  440. package/dist/surface/tools/reconcile-tools.js +21 -8
  441. package/dist/surface/tools/reticle-result.d.ts +3 -0
  442. package/dist/surface/tools/reticle-result.js +8 -1
  443. package/dist/surface/tools/run-report.d.ts +22 -0
  444. package/dist/surface/tools/run-report.js +141 -0
  445. package/dist/surface/tools/session-ports.d.ts +57 -0
  446. package/dist/surface/tools/session-ports.js +102 -0
  447. package/dist/surface/tools/tool-kit.d.ts +58 -4
  448. package/dist/surface/tools/tool-kit.js +140 -6
  449. package/dist/surface/tools/tool-surface.d.ts +2 -0
  450. package/dist/surface/tools/tool-surface.js +45 -0
  451. package/dist/surface/tools/tools.js +49 -87
  452. package/dist/telemetry/automation-hint.d.ts +5 -0
  453. package/dist/telemetry/automation-hint.js +7 -0
  454. package/dist/telemetry/cli-telemetry.d.ts +22 -0
  455. package/dist/telemetry/cli-telemetry.js +80 -11
  456. package/dist/telemetry/daemon-telemetry.d.ts +3 -1
  457. package/dist/telemetry/daemon-telemetry.js +9 -2
  458. package/dist/telemetry/feedback-cli.d.ts +1 -1
  459. package/dist/telemetry/feedback-cli.js +8 -3
  460. package/dist/telemetry/feedback.d.ts +1 -1
  461. package/dist/telemetry/feedback.js +51 -4
  462. package/dist/telemetry/harness-run.d.ts +3 -3
  463. package/dist/telemetry/harness-run.js +18 -7
  464. package/dist/telemetry/hud-events.d.ts +1 -1
  465. package/dist/telemetry/hud-metrics.d.ts +6 -3
  466. package/dist/telemetry/hud-metrics.js +20 -8
  467. package/dist/telemetry/install-source.js +1 -1
  468. package/dist/telemetry/mcp-connection.d.ts +1 -1
  469. package/dist/telemetry/mcp-connection.js +21 -2
  470. package/dist/telemetry/onboarding-firsts.d.ts +6 -1
  471. package/dist/telemetry/onboarding-firsts.js +41 -1
  472. package/dist/telemetry/proxy-telemetry.d.ts +42 -0
  473. package/dist/telemetry/proxy-telemetry.js +70 -1
  474. package/dist/telemetry/session-metrics.d.ts +1 -3
  475. package/dist/telemetry/session-metrics.js +16 -12
  476. package/dist/telemetry/telemetry.d.ts +9 -1
  477. package/dist/telemetry/telemetry.js +16 -1
  478. package/dist/telemetry/verification-of.d.ts +3 -1
  479. package/dist/telemetry/verification-of.js +7 -1
  480. package/dist/wire-journal.js +12 -2
  481. package/docs/README.md +4 -2
  482. package/docs/actions.mdx +7 -6
  483. package/docs/adapters.md +2 -1
  484. package/docs/agent-cheatsheet.md +19 -5
  485. package/docs/architecture.md +13 -10
  486. package/docs/autodrive.md +6 -5
  487. package/docs/benchmarks.md +2 -2
  488. package/docs/capabilities.mdx +1 -1
  489. package/docs/cli/affected.mdx +1 -1
  490. package/docs/cli/cloud.mdx +6 -6
  491. package/docs/cli/doctor.mdx +3 -1
  492. package/docs/cli/feedback.mdx +11 -7
  493. package/docs/cli/gate.mdx +1 -1
  494. package/docs/cli/help.mdx +1 -1
  495. package/docs/cli/init.mdx +20 -16
  496. package/docs/cli/mcp.mdx +5 -2
  497. package/docs/cli/open.mdx +5 -2
  498. package/docs/cli/report.mdx +1 -1
  499. package/docs/cli/restart.mdx +1 -1
  500. package/docs/cli/rollback.mdx +4 -4
  501. package/docs/cli/serve.mdx +7 -5
  502. package/docs/cli/setup.mdx +1 -1
  503. package/docs/cli/status.mdx +6 -2
  504. package/docs/cli/stop.mdx +3 -3
  505. package/docs/cli/telemetry.mdx +5 -1
  506. package/docs/cli/try.mdx +8 -8
  507. package/docs/cli/update.mdx +8 -5
  508. package/docs/cli/verify.mdx +12 -5
  509. package/docs/cli/version.mdx +1 -1
  510. package/docs/cli.mdx +10 -6
  511. package/docs/debugging.md +1 -1
  512. package/docs/deploy-checks.md +8 -6
  513. package/docs/desktop.mdx +14 -18
  514. package/docs/enterprise.md +6 -6
  515. package/docs/environment.mdx +5 -3
  516. package/docs/faq.mdx +9 -6
  517. package/docs/first-drive.md +19 -9
  518. package/docs/flows.md +3 -4
  519. package/docs/for-agents.md +1 -1
  520. package/docs/frameworks.mdx +34 -11
  521. package/docs/getting-started.md +46 -29
  522. package/docs/http-transport.md +2 -2
  523. package/docs/human-control.md +8 -8
  524. package/docs/index.mdx +2 -2
  525. package/docs/install-agentic.mdx +18 -19
  526. package/docs/install-manual.mdx +8 -4
  527. package/docs/integration-patterns.md +5 -5
  528. package/docs/multi-agent-testing.md +7 -5
  529. package/docs/packages/browser.mdx +34 -2
  530. package/docs/packages/core.mdx +2 -1
  531. package/docs/packages/electron.mdx +15 -3
  532. package/docs/packages/eslint-plugin.mdx +1 -1
  533. package/docs/packages/next.mdx +17 -5
  534. package/docs/packages/react.mdx +8 -1
  535. package/docs/packages/server.mdx +3 -3
  536. package/docs/packages/tauri.mdx +3 -3
  537. package/docs/packages/vite-plugin.mdx +1 -1
  538. package/docs/packages.mdx +19 -6
  539. package/docs/platform-integration.md +12 -4
  540. package/docs/predicates.mdx +18 -7
  541. package/docs/production-mode.md +108 -0
  542. package/docs/prompt-capture.md +99 -0
  543. package/docs/quickstart.mdx +14 -12
  544. package/docs/recipes.mdx +9 -8
  545. package/docs/required-checks.md +3 -2
  546. package/docs/skill-file.mdx +4 -3
  547. package/docs/skill-setup.md +2 -2
  548. package/docs/skill.md +62 -70
  549. package/docs/state-files.md +1 -0
  550. package/docs/state-management.mdx +11 -9
  551. package/docs/system-map.md +14 -13
  552. package/docs/telemetry.md +32 -24
  553. package/docs/testing.md +9 -5
  554. package/docs/tools/act-and-wait.mdx +17 -13
  555. package/docs/tools/act-sequence.mdx +40 -66
  556. package/docs/tools/act.mdx +7 -5
  557. package/docs/tools/assert.mdx +1 -1
  558. package/docs/tools/look.mdx +1 -1
  559. package/docs/tools/navigate.mdx +1 -1
  560. package/docs/tools/network.mdx +13 -11
  561. package/docs/tools/overview.mdx +3 -3
  562. package/docs/tools/query.mdx +3 -3
  563. package/docs/tools/session-and-feedback.mdx +9 -6
  564. package/docs/tools/sessions.mdx +7 -0
  565. package/docs/tools/snapshot.mdx +9 -7
  566. package/docs/tools/tools-and-run.mdx +11 -10
  567. package/docs/tools/verify.mdx +1 -1
  568. package/docs/troubleshooting.mdx +21 -9
  569. package/docs/usage.md +58 -43
  570. package/docs/use-cases.mdx +2 -2
  571. package/docs/vs-chrome-devtools-mcp.mdx +5 -5
  572. package/docs/vs-playwright-mcp.mdx +5 -4
  573. package/docs/what-is-recorded.md +12 -7
  574. package/docs/what-is-reticle.mdx +2 -2
  575. package/docs/why-reticle.mdx +1 -1
  576. package/package.json +9 -12
  577. package/dist/command/dev/stale-issue-guard.d.ts +0 -34
  578. package/dist/command/dev/stale-issue-guard.js +0 -69
@@ -1,22 +1,25 @@
1
1
  import * as http from 'node:http';
2
- import { localInitializeResponse, degradedInstructions, isHandshakeLine, drainLines, MAX_STDIN_LINE_BYTES, } from './proxy/proxy-handshake.js';
2
+ import { stateDirProblem } from '../../command/daemon/state-dir.js';
3
+ import { reticleStateHome } from '../../command/daemon/daemon.js';
4
+ import { localInitializeResponse, degradedInstructions, isHandshakeLine, drainLines, MAX_STDIN_LINE_BYTES, parseLine as parseJsonRpc, HandshakeReplay, } from './proxy/proxy-handshake.js';
5
+ export { HandshakeReplay, RECONNECT_INITIALIZE_ID } from './proxy/proxy-handshake.js';
3
6
  import { isToolCallRequest, isToolsListRequest, ToolCatalogCache } from './tool-catalog-cache.js';
4
7
  import { rememberEnumerated, rememberProxyStarted, rememberToolCalled } from './attach-memory.js';
5
8
  import { toolsChangedNotification } from './proxy/proxy-handshake.js';
6
- import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, } from '@reticlehq/core';
7
- import { SseFrameParser } from './sse-frame-parser.js';
8
- export { SseFrameParser } from './sse-frame-parser.js';
9
- export { probeDaemon, waitForDaemon, waitForDaemonBind } from './proxy/proxy-daemon-probe.js';
9
+ import { LOOPBACK_HOST, MCP_SSE_PATH, MCP_SHUTDOWN_EVENT, ReticleEnv, CONTRACT_FINGERPRINT, ProxyExitReason, PROXY_ID_PARAM, } from '@reticlehq/core';
10
+ import { SseFrameParser, buildSessionUrl } from './sse-frame-parser.js';
11
+ export { SseFrameParser, buildSessionUrl } from './sse-frame-parser.js';
12
+ export { daemonProcessAlive, probeDaemon, waitForDaemon, waitForDaemonBind, } from './proxy/proxy-daemon-probe.js';
10
13
  import { probeDaemon } from './proxy/proxy-daemon-probe.js';
11
14
  import { SERVER_VERSION } from '../../command/version/identity/server-version.js';
12
15
  import { buildServerInstructions, localizeInstructions } from './server-instructions.js';
13
16
  import { hasAnyProjectConnectedBefore } from '../../memory/recall/prior/connection-memory.js';
14
- import { reticleStateHome } from '../../command/daemon/daemon.js';
15
17
  import { projectIdsAt } from '../../command/cli/ports/resolve/cli-port.js';
16
- import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM } from '../../command/version/peer-announce.js';
18
+ import { PEER_VERSION_PARAM, PEER_CONTRACT_PARAM, agentSessionQuery, } from '../../command/version/peer-announce.js';
17
19
  import { onStreamDrop, onClientRequest, onReconnectBudgetSpent, onQueueExpired, OnDrop, OnRequest, } from './proxy/proxy-lifecycle.js';
18
20
  import { describePresence, probePresence } from '../../command/daemon/binding/port-presence.js';
19
- import { flushProxySessionMetrics } from '../../telemetry/proxy-telemetry.js';
21
+ import { PROXY_ID, flushProxyExit, noteProxyDrop, noteProxyToolCall, } from '../../telemetry/proxy-telemetry.js';
22
+ export { flushProxyExit, reportWakeRefused } from '../../telemetry/proxy-telemetry.js';
20
23
  import { detectStack } from '../../telemetry/feedback-context.js';
21
24
  /**
22
25
  * The same `/status` probe `doctor`, `status` and `kill` ask with. Reused rather than re-written:
@@ -32,7 +35,8 @@ export { accountProxyLogWrite, proxyLog, proxyLogPath, PROXY_LOG_CHECK_BYTES, re
32
35
  import { proxyLog } from './proxy/proxy-log.js';
33
36
  import { OutageReason, OutageStage, reportMcpOutage } from './faults/mcp-outage.js';
34
37
  import { postToSession } from './proxy/mcp-post-transport.js';
35
- import { MSG_DRIVE_MAY_BE_RUNNING, isHarnessDriveCall } from './proxy/drive-calls.js';
38
+ import { PendingRequests, streamLossReplies, transportLossReply } from './proxy/drive-calls.js';
39
+ export { PendingRequests, streamLossReplies } from './proxy/drive-calls.js';
36
40
  import { reconnectDelayMs } from './proxy/proxy-backoff.js';
37
41
  import { PROXY_IDLE_EXIT_EVENT, ProxyIdleExit, resolveProxyIdleExitMs } from './proxy-idle-exit.js';
38
42
  export { reconnectDelayMs } from './proxy/proxy-backoff.js';
@@ -55,18 +59,6 @@ export const MAX_RECONNECT_ATTEMPTS = (() => {
55
59
  const raw = Number(process.env[ReticleEnv.RECONNECT_ATTEMPTS]);
56
60
  return Number.isInteger(raw) && raw > 0 ? raw : DEFAULT_MAX_RECONNECT_ATTEMPTS;
57
61
  })();
58
- /** JSON-RPC id the proxy uses for its own replayed `initialize` — never one the client could send. */
59
- export const RECONNECT_INITIALIZE_ID = '__reticle_proxy_reinit';
60
- function parseJsonRpc(line) {
61
- try {
62
- const parsed = JSON.parse(line);
63
- return 'object' === typeof parsed && parsed !== null ? parsed : null;
64
- }
65
- catch {
66
- return null;
67
- }
68
- }
69
- const INITIALIZE_METHOD = 'initialize';
70
62
  /**
71
63
  * How long the handshake waits on the daemon before the proxy answers it.
72
64
  *
@@ -75,105 +67,6 @@ const INITIALIZE_METHOD = 'initialize';
75
67
  * past 25s with nothing on stderr, and the reported client gave up at 60s having run no tools.
76
68
  */
77
69
  const LOCAL_HANDSHAKE_MS = 12_000;
78
- const INITIALIZED_METHOD = 'notifications/initialized';
79
- /**
80
- * JSON-RPC requests forwarded on behalf of the client that have not been answered yet.
81
- *
82
- * Only used at shutdown, where it is the difference between "the client closed stdin" and "the client
83
- * closed stdin and we dropped its `tools/list` on the floor while reporting exit 0". A request has an
84
- * id AND a method; a notification has no id and is owed nothing; a response has an id and no method.
85
- */
86
- export class PendingRequests {
87
- #ids = new Set();
88
- /** The pending requests that start or poll a Harness drive, which outlives a lost connection. */
89
- #drives = new Set();
90
- observeOutbound(line) {
91
- const msg = parseJsonRpc(line);
92
- if (null === msg || msg.id === undefined || msg.method === undefined)
93
- return;
94
- const id = JSON.stringify(msg.id);
95
- this.#ids.add(id);
96
- if (isHarnessDriveCall(msg))
97
- this.#drives.add(id);
98
- }
99
- /** Whether this unanswered request asked for a Harness drive. */
100
- isDrive(id) {
101
- return this.#drives.has(JSON.stringify(id));
102
- }
103
- observeInbound(line) {
104
- const msg = parseJsonRpc(line);
105
- if (null === msg || msg.id === undefined)
106
- return;
107
- this.#ids.delete(JSON.stringify(msg.id));
108
- }
109
- get unanswered() {
110
- return [...this.#ids];
111
- }
112
- /**
113
- * Claim ONE unanswered id, so exactly one path may answer it. False means somebody already did —
114
- * a duplicate response to one id is a protocol violation, not a belt-and-braces retry.
115
- */
116
- take(id) {
117
- this.#drives.delete(JSON.stringify(id));
118
- return this.#ids.delete(JSON.stringify(id));
119
- }
120
- /** Take every unanswered id and forget them, so the same request is failed at most once. */
121
- drain() {
122
- const ids = [...this.#ids];
123
- this.#ids.clear();
124
- return ids;
125
- }
126
- /** Forget which requests were drives, once their replies are written. */
127
- clearDrives() {
128
- this.#drives.clear();
129
- }
130
- }
131
- /**
132
- * JSON-RPC errors for every request that was in flight when the stream died.
133
- *
134
- * These are unrecoverable BY CONSTRUCTION: the daemon builds a fresh MCP session per SSE
135
- * connection, so the reconnected session has never seen them. Re-sending them is not an option
136
- * either — a `reticle_act` that already clicked would click twice, and inventing a second action in
137
- * somebody's app is worse than reporting a failure.
138
- *
139
- * So the proxy answers them itself, under each request's own id. Found by brute force: killing the
140
- * daemon under a live client left 20 of 20 concurrent calls hanging until the client's own timeout,
141
- * with the MCP server still perfectly alive. An agent cannot tell that apart from "still working".
142
- */
143
- export function streamLossReplies(pending, reason, nextStep) {
144
- const replies = pending.drain().map((id) => {
145
- const parsed = JSON.parse(id);
146
- return transportLossReply(parsed, reason, nextStep, pending.isDrive(parsed));
147
- });
148
- pending.clearDrives();
149
- return replies;
150
- }
151
- /**
152
- * -32001: a server-defined error. Not -32603 (internal): nothing went wrong INSIDE the call, the
153
- * transport under it went away, and the distinction is what tells a caller the action may be safe
154
- * to retry.
155
- */
156
- const TRANSPORT_LOSS_CODE = -32001;
157
- /**
158
- * The one shape every "the transport ate your call" answer takes, whichever leg died.
159
- *
160
- * `nextStep` is appended when the caller knows one. Without it this reply describes a condition and
161
- * stops there, which is all a first-run user got out of their first ever tool call.
162
- */
163
- function transportLossReply(id, reason, nextStep, drive = false) {
164
- return JSON.stringify({
165
- jsonrpc: '2.0',
166
- id,
167
- error: {
168
- code: TRANSPORT_LOSS_CODE,
169
- message: (drive
170
- ? `the daemon connection dropped (${reason}) before this call was answered. ${MSG_DRIVE_MAY_BE_RUNNING}`
171
- : `the daemon connection dropped (${reason}) before this call was answered — it did NOT ` +
172
- 'complete. Reticle is recovering the connection; retry if the action is safe to repeat.') +
173
- (nextStep === undefined ? '' : ` ${nextStep}`),
174
- },
175
- });
176
- }
177
70
  /**
178
71
  * How long stdin closing gives the daemon to answer what is still in flight.
179
72
  *
@@ -223,72 +116,8 @@ export function shouldRetryFirstConnect(error, elapsedMs) {
223
116
  */
224
117
  export function queueExpiryNextStep(port) {
225
118
  return (`No Reticle daemon answered on port ${String(port)}. Retry the call — the next request starts ` +
226
- 'one. If it keeps failing, run `reticle doctor` in a terminal: it reports what is holding the ' +
227
- 'port and whether the daemon can start at all.');
228
- }
229
- /**
230
- * Makes a reconnect invisible to the client.
231
- *
232
- * The daemon builds a FRESH `McpServer` per SSE connection, so a reconnected session has never seen
233
- * the client's `initialize` — every subsequent `tools/call` would hit an uninitialized server. The
234
- * proxy therefore remembers the handshake the client sent once and replays it into each new session.
235
- * The replay is re-issued under a reserved id so the daemon's response to it can be dropped rather
236
- * than reaching the client's stdout, where a duplicate JSON-RPC id would be a protocol violation.
237
- */
238
- export class HandshakeReplay {
239
- #initialize = null;
240
- #initialized = null;
241
- #pendingReplayId = null;
242
- /** Record one outbound client line. Cheap enough to call for every stdin message. */
243
- observeOutbound(line) {
244
- const msg = parseJsonRpc(line);
245
- if (null === msg)
246
- return;
247
- // The first initialize is the session's identity; a client re-issuing one does not change it.
248
- if (msg.method === INITIALIZE_METHOD && null === this.#initialize)
249
- this.#initialize = line;
250
- else if (msg.method === INITIALIZED_METHOD)
251
- this.#initialized = line;
252
- }
253
- /** Lines to POST into a freshly-established session, before any queued client traffic. */
254
- replayLines() {
255
- if (null === this.#initialize)
256
- return [];
257
- const msg = parseJsonRpc(this.#initialize);
258
- if (null === msg)
259
- return [];
260
- this.#pendingReplayId = RECONNECT_INITIALIZE_ID;
261
- const reinit = { ...msg, id: RECONNECT_INITIALIZE_ID };
262
- return null === this.#initialized
263
- ? [JSON.stringify(reinit)]
264
- : [JSON.stringify(reinit), this.#initialized];
265
- }
266
- /** True when this inbound daemon line is the echo of our replayed handshake and must not be forwarded. */
267
- shouldSuppressInbound(line) {
268
- if (null === this.#pendingReplayId)
269
- return false;
270
- const msg = parseJsonRpc(line);
271
- if (null === msg || msg.id !== this.#pendingReplayId)
272
- return false;
273
- this.#pendingReplayId = null; // one response per replay — anything later is not ours to swallow
274
- return true;
275
- }
276
- }
277
- /**
278
- * Turn an `endpoint` frame's data into a POST target, or null when there is nothing usable in it.
279
- *
280
- * An empty (or whitespace) data field is the shape a malformed daemon response takes, and it used to
281
- * be accepted: `postUrl` became `''`, which is not `null`, so the proxy considered itself CONNECTED
282
- * and posted every subsequent message to the empty string. Each one failed, the queue never armed
283
- * its deadline because nothing was ever queued, and the agent held a session that could not carry a
284
- * single call. Refusing the frame keeps the proxy in its disconnected path, which already knows how
285
- * to reconnect and how to answer what it cannot send.
286
- */
287
- export function buildSessionUrl(rawData, port) {
288
- const trimmed = rawData.trim();
289
- if ('' === trimmed)
290
- return null;
291
- return trimmed.startsWith('/') ? `http://${LOOPBACK_HOST}:${port}${trimmed}` : trimmed;
119
+ 'one. If it keeps failing, run `npx @reticlehq/server doctor` in a terminal: it reports what ' +
120
+ 'is holding the port and whether the daemon can start at all.');
292
121
  }
293
122
  /**
294
123
  * Bridge stdio ↔ SSE: connects to the running daemon's MCP endpoint and forwards
@@ -307,9 +136,12 @@ export function buildSessionUrl(rawData, port) {
307
136
  * an unreadable state home must never be the reason a handshake fails, so it degrades to the
308
137
  * fresh-install wording rather than throwing.
309
138
  */
310
- function proxyInstructions(port) {
139
+ function proxyInstructions(port, advertised) {
311
140
  try {
312
- return buildServerInstructions(instructionStateAt(port));
141
+ return buildServerInstructions({
142
+ ...instructionStateAt(port),
143
+ ...(advertised === undefined ? {} : { advertised }),
144
+ });
313
145
  }
314
146
  catch {
315
147
  return buildServerInstructions({ previouslyConnected: false });
@@ -325,32 +157,42 @@ export function instructionStateAt(port, liveSessions = 0) {
325
157
  }
326
158
  /** An `initialize` reply re-briefed for the AGENT's directory; see localizeInstructions. */
327
159
  function withLocalBriefing(line, appHere) {
328
- if (!line.includes('"instructions"'))
160
+ if (!line.includes('"instructions"')) {
329
161
  return line;
162
+ }
330
163
  try {
331
164
  const msg = JSON.parse(line);
332
- if (null === msg || 'object' !== typeof msg || !('result' in msg))
165
+ if (null === msg || 'object' !== typeof msg || !('result' in msg)) {
333
166
  return line;
167
+ }
334
168
  const result = msg.result;
335
- if (null === result || 'object' !== typeof result || !('instructions' in result))
169
+ if (null === result || 'object' !== typeof result || !('instructions' in result)) {
336
170
  return line;
171
+ }
337
172
  const { instructions } = result;
338
- if ('string' !== typeof instructions)
173
+ if ('string' !== typeof instructions) {
339
174
  return line;
175
+ }
340
176
  const localized = localizeInstructions(instructions, appHere);
341
- if (localized === instructions)
177
+ if (localized === instructions) {
342
178
  return line;
179
+ }
343
180
  return JSON.stringify({ ...msg, result: { ...result, instructions: localized } });
344
181
  }
345
182
  catch {
346
183
  return line;
347
184
  }
348
185
  }
349
- export function startMcpProxy(port,
186
+ function withStateDirDiagnostic(reason) {
187
+ const problem = stateDirProblem(reticleStateHome());
188
+ return problem === undefined ? reason : `${reason}\n${problem}`;
189
+ }
190
+ export function startMcpProxy(bootPort,
350
191
  /**
351
192
  * Bring a daemon back before reconnecting. Without it the proxy retried a DEAD port until the
352
193
  * budget ran out and then killed itself, taking the agent's whole Reticle surface with it — the
353
- * failure mode for a daemon that crashed, was stopped, or shut itself down as idle.
194
+ * failure mode for a daemon that crashed, was stopped, or shut itself down as idle. A returned
195
+ * port is where the daemon now is; the proxy dials it from then on.
354
196
  */
355
197
  ensureDaemon,
356
198
  /** Observe the real shutdown boundary in transport tests without terminating their worker. */
@@ -363,7 +205,13 @@ exitProcess = (code) => process.exit(code),
363
205
  * drops it, and an `endpoint` frame is not. Optional, because a transport test drives this
364
206
  * function with no resilience installed.
365
207
  */
366
- onUsableSession = () => undefined) {
208
+ onUsableSession = () => undefined,
209
+ /**
210
+ * The daemon start that runs while the proxy already reads stdin. A client that does not wait
211
+ * for MCP servers gets the handshake and the tool list before the daemon is up.
212
+ */
213
+ boot) {
214
+ let port = bootPort;
367
215
  return new Promise((_resolve, reject) => {
368
216
  // A client just started us, which is the only honest evidence that Reticle is registered with
369
217
  // one at all: a config entry proves somebody wrote JSON, not that a client ever read it. Paired
@@ -401,8 +249,9 @@ onUsableSession = () => undefined) {
401
249
  */
402
250
  let queueTimer;
403
251
  const clearQueueTimer = () => {
404
- if (queueTimer !== undefined)
252
+ if (queueTimer !== undefined) {
405
253
  clearTimeout(queueTimer);
254
+ }
406
255
  queueTimer = undefined;
407
256
  };
408
257
  /**
@@ -415,8 +264,9 @@ onUsableSession = () => undefined) {
415
264
  */
416
265
  const expireQueue = (reason) => {
417
266
  clearQueueTimer();
418
- if (stopped || 0 === stdinQueue.length)
267
+ if (stopped || 0 === stdinQueue.length) {
419
268
  return;
269
+ }
420
270
  // Drop what we could never send, and answer it, so the caller is never left guessing.
421
271
  stdinQueue.splice(0);
422
272
  for (const reply of streamLossReplies(pending, reason, queueExpiryNextStep(port))) {
@@ -436,8 +286,9 @@ onUsableSession = () => undefined) {
436
286
  });
437
287
  };
438
288
  const armQueueTimer = () => {
439
- if (queueTimer !== undefined)
440
- return; // the oldest entry owns the deadline
289
+ if (queueTimer !== undefined) {
290
+ return;
291
+ } // the oldest entry owns the deadline
441
292
  queueTimer = setTimeout(() => {
442
293
  queueTimer = undefined;
443
294
  expireQueue('no daemon could be reached');
@@ -447,13 +298,14 @@ onUsableSession = () => undefined) {
447
298
  const replay = new HandshakeReplay();
448
299
  const appHere = detectStack(process.cwd()).stack !== undefined;
449
300
  const pending = new PendingRequests();
450
- const quit = (code) => {
451
- if (stopped)
301
+ const quit = (code, reason) => {
302
+ if (stopped) {
452
303
  return;
304
+ }
453
305
  stopped = true;
454
306
  idleExit.stop();
455
307
  // Await: fire-and-forget here is how daemon_stopped never arrived.
456
- void flushProxySessionMetrics().finally(() => exitProcess(code));
308
+ void flushProxyExit(reason).finally(() => exitProcess(code));
457
309
  };
458
310
  const idleExit = new ProxyIdleExit({
459
311
  graceMs: resolveProxyIdleExitMs(process.env[ReticleEnv.MCP_PROXY_IDLE]),
@@ -464,7 +316,7 @@ onUsableSession = () => undefined) {
464
316
  idleMs,
465
317
  note: 'the client link was quiet for the configured idle window with no work in flight; exiting the abandoned proxy',
466
318
  });
467
- quit(0);
319
+ quit(0, ProxyExitReason.IDLE);
468
320
  },
469
321
  });
470
322
  idleExit.start();
@@ -482,12 +334,9 @@ onUsableSession = () => undefined) {
482
334
  /**
483
335
  * What the outage had already cost when the proxy went dormant.
484
336
  *
485
- * The wake path clears `attempts` before dialling, and it must: a wake is a fresh retry budget,
486
- * and sharing the previous outage's spent budget would put the proxy dormant again on its first
487
- * failure. But the recovery report is guarded on `attempts > 0`, so clearing it also erased the
488
- * evidence that there was anything to recover FROM — and dormancy is the commonest outage class
489
- * we have, which made every recovery of that class invisible. Carried across the wake so the
490
- * budget resets and the accounting does not.
337
+ * `wake` clears `attempts`, because a wake gets a fresh retry budget. The recovery report needs
338
+ * `attempts > 0`, so the clear also hid every recovery from dormancy, the most common outage.
339
+ * This count goes across the wake: the budget resets, and the accounting does not.
491
340
  */
492
341
  let attemptsBeforeDormant = 0;
493
342
  /**
@@ -507,12 +356,14 @@ onUsableSession = () => undefined) {
507
356
  */
508
357
  const forward = (url, line) => {
509
358
  void postToSession(url, line).then((failure) => {
510
- if (null === failure)
359
+ if (null === failure) {
511
360
  return;
361
+ }
512
362
  const msg = parseJsonRpc(line);
513
363
  const drive = null !== msg && pending.isDrive(msg.id);
514
- if (null === msg || msg.id === undefined || !pending.take(msg.id))
364
+ if (null === msg || msg.id === undefined || !pending.take(msg.id)) {
515
365
  return;
366
+ }
516
367
  // No outage is reported here, deliberately. A POST leg that died while the SSE stream is
517
368
  // still up is not the agent losing its tools: `postSocketFailures` on the session summary
518
369
  // already counts it, and reporting it as a lost stream both double-counted and — because
@@ -545,8 +396,9 @@ onUsableSession = () => undefined) {
545
396
  // Report the recovery BEFORE the counter is cleared: `attempts` is what coming back actually
546
397
  // cost, and it is the only number that can falsify the drop event. See OutageStage.RECOVERED.
547
398
  const cost = attempts + attemptsBeforeDormant;
548
- if (cost > 0)
399
+ if (cost > 0) {
549
400
  reportMcpOutage(OutageStage.RECOVERED, { reason: lastDropReason, attempts: cost });
401
+ }
550
402
  attempts = 0;
551
403
  attemptsBeforeDormant = 0;
552
404
  // The disconnects behind us were reconnects, not a runaway. Counting them for the life of
@@ -555,11 +407,13 @@ onUsableSession = () => undefined) {
555
407
  onUsableSession();
556
408
  // The new session's McpServer has never seen the client's initialize — replay it first, then
557
409
  // flush whatever the client sent while we were reconnecting.
558
- for (const line of replay.replayLines())
410
+ for (const line of replay.replayLines()) {
559
411
  forward(url, line);
412
+ }
560
413
  clearQueueTimer();
561
- for (const queued of stdinQueue.splice(0))
414
+ for (const queued of stdinQueue.splice(0)) {
562
415
  forward(url, queued);
416
+ }
563
417
  // The client may be holding a tool list we invented. See toolsChangedNotification.
564
418
  const relist = toolsChangedNotification(toolsAnnouncedLocally);
565
419
  if (relist !== null) {
@@ -587,10 +441,12 @@ onUsableSession = () => undefined) {
587
441
  emit(data);
588
442
  }
589
443
  }
590
- function scheduleReconnect(rawReason, detail) {
591
- if (stopped)
444
+ function scheduleReconnect(rawReason, detail, cause) {
445
+ if (stopped) {
592
446
  return;
447
+ }
593
448
  postUrl = null;
449
+ noteProxyDrop();
594
450
  // A stream the daemon warned us about is a planned shutdown, not a fault. The socket looks
595
451
  // identical either way from here, so this flag is the only thing that can tell them apart —
596
452
  // and without it every scheduled retirement was counted as an outage the agent suffered.
@@ -610,18 +466,19 @@ onUsableSession = () => undefined) {
610
466
  // what the old one owed and silence here is permanent.
611
467
  stdinQueue.splice(0);
612
468
  const lost = streamLossReplies(pending, reason);
613
- for (const reply of lost)
469
+ for (const reply of lost) {
614
470
  emit(reply);
471
+ }
615
472
  // How many calls this drop actually killed. Zero means the agent could not feel it.
616
473
  const pendingLost = lost.length;
617
474
  // Once per process: this session has now lost its tools at least once, which is the number
618
475
  // that says whether the transport work actually landed for real users. `pendingLost` is the
619
476
  // part an agent can FEEL — almost every measured drop killed nothing in flight.
620
- reportMcpOutage(OutageStage.FIRST, { reason, attempts, pendingLost });
477
+ reportMcpOutage(OutageStage.FIRST, { reason, attempts, pendingLost, ...cause });
621
478
  // The severe stage: we are about to stop retrying. Reported so the share of sessions where
622
479
  // MCP never came back on its own is a number rather than an anecdote.
623
480
  if (attempts > MAX_RECONNECT_ATTEMPTS) {
624
- reportMcpOutage(OutageStage.BUDGET_SPENT, { reason, attempts, pendingLost });
481
+ reportMcpOutage(OutageStage.BUDGET_SPENT, { reason, attempts, pendingLost, ...cause });
625
482
  // Stop RETRYING, never stop SERVING. See onReconnectBudgetSpent: exiting here made the
626
483
  // client mark the MCP server disconnected and left a human to reconnect it by hand.
627
484
  dormant = onReconnectBudgetSpent() === OnDrop.DORMANT;
@@ -669,7 +526,7 @@ onUsableSession = () => undefined) {
669
526
  function connect(first) {
670
527
  // Announce this process to the daemon on the connect we already make — it is the single judge
671
528
  // of version skew, and this is the only moment it learns the agent's MCP server exists.
672
- const announce = `${MCP_SSE_PATH}?${PEER_VERSION_PARAM}=${encodeURIComponent(SERVER_VERSION)}&${PEER_CONTRACT_PARAM}=${encodeURIComponent(CONTRACT_FINGERPRINT)}`;
529
+ const announce = `${MCP_SSE_PATH}?${PEER_VERSION_PARAM}=${encodeURIComponent(SERVER_VERSION)}&${PEER_CONTRACT_PARAM}=${encodeURIComponent(CONTRACT_FINGERPRINT)}&${PROXY_ID_PARAM}=${PROXY_ID}${agentSessionQuery(process.env)}`;
673
530
  /**
674
531
  * ONE reconnect per connect attempt, whoever notices the failure first.
675
532
  *
@@ -698,24 +555,27 @@ onUsableSession = () => undefined) {
698
555
  let responded = false;
699
556
  const req = http.get({ host: LOOPBACK_HOST, port, path: announce }, (res) => {
700
557
  responded = true;
701
- if (!first)
558
+ if (!first) {
702
559
  proxyLog('reticle_mcp_proxy_reconnected', { port });
560
+ }
703
561
  res.setEncoding('utf8');
704
- const drop = (reason, detail) => {
705
- if (settled)
562
+ const drop = (reason, detail, cause) => {
563
+ if (settled) {
706
564
  return;
565
+ }
707
566
  settled = true;
708
567
  // What the dead session owed is settled inside scheduleReconnect, so the other half of the
709
568
  // socket's death pays exactly the same debt when it is the one that notices first.
710
- scheduleReconnect(reason, detail);
569
+ scheduleReconnect(reason, detail, cause ?? { responded });
711
570
  };
712
571
  const sse = new SseFrameParser();
713
572
  res.on('data', (chunk) => {
714
- for (const frame of sse.push(chunk))
573
+ for (const frame of sse.push(chunk)) {
715
574
  onSseEvent(frame.event, frame.data, port);
575
+ }
716
576
  });
717
577
  res.on('end', () => drop('sse_ended'));
718
- res.on('error', (err) => drop('sse_error', err.message));
578
+ res.on('error', (err) => drop('sse_error', err.message, { error: err, responded }));
719
579
  // A socket dying under us (daemon killed, network stack reset) emits NEITHER `end` nor
720
580
  // `error` — only `aborted`/`close`. Listening for just the first two is why the proxy sat
721
581
  // there afterwards holding a dead stream. `settled` keeps the clean case single-fire.
@@ -725,9 +585,14 @@ onUsableSession = () => undefined) {
725
585
  // The very first connect failing means there is no daemon to talk to — that is a startup
726
586
  // failure the caller handles. Later ones are just the daemon bouncing: keep retrying.
727
587
  req.on('error', (err) => {
728
- if (settled)
588
+ if (settled) {
729
589
  return;
590
+ }
730
591
  settled = true;
592
+ if (first && !responded && boot !== undefined) {
593
+ bootFailed(); // The start is done, so nothing is still booting on this port.
594
+ return;
595
+ }
731
596
  if (first && !responded) {
732
597
  // A refused port on the very first connect is a daemon still booting, not a failure. Keep
733
598
  // chasing it: nothing else will, because only a fresh client request re-probes the port
@@ -747,10 +612,57 @@ onUsableSession = () => undefined) {
747
612
  reject(err);
748
613
  return;
749
614
  }
750
- scheduleReconnect('connect_error', err.message);
615
+ // A reset of a stream that was already serving also lands here, because the request sees
616
+ // the error before the response closes. That is a dropped stream, not a refused connect.
617
+ const reason = responded ? OutageReason.SSE_RESET : OutageReason.CONNECT_ERROR;
618
+ scheduleReconnect(reason, err.message, { error: err, responded });
619
+ });
620
+ }
621
+ /** The ONLY path that starts a daemon (see proxy-lifecycle.ts). `endpoint` flushes the queue. */
622
+ function wake() {
623
+ dormant = false;
624
+ // Budget reset, accounting preserved. See attemptsBeforeDormant.
625
+ attemptsBeforeDormant += attempts;
626
+ attempts = 0;
627
+ void (ensureDaemon?.() ?? Promise.resolve())
628
+ .then((resolved) => {
629
+ port = resolved ?? port;
630
+ return connect(false);
631
+ })
632
+ .catch((err) => {
633
+ dormant = true;
634
+ const reason = withStateDirDiagnostic(err instanceof Error ? err.message : String(err));
635
+ proxyLog('reticle_mcp_proxy_wake_failed', { port, error: reason });
636
+ // The failure IS the answer to everything the wake was for. Do not wait for the timer.
637
+ expireQueue(reason);
638
+ });
639
+ }
640
+ if (boot === undefined) {
641
+ connect(true);
642
+ }
643
+ else {
644
+ // Dial when the start is done, at the port it settled on. A failed start dials too, because
645
+ // only the dial can tell the two cases apart. A refused dial means no daemon: see bootFailed.
646
+ // A process that accepts and then drops the stream is a daemon that flaps, and the normal
647
+ // retry loop backs off from it and logs each retry.
648
+ void boot.ready
649
+ .catch(() => undefined)
650
+ .then((resolved) => {
651
+ port = resolved ?? port;
652
+ connect(true);
751
653
  });
752
654
  }
753
- connect(true);
655
+ /**
656
+ * The first dial after the start was refused, so no daemon is there. For example, it died
657
+ * before or just after it served. The first-connect retry never starts a daemon, so a queued
658
+ * call waited until the queue expired. Go dormant, and wake now for a request that waits.
659
+ */
660
+ function bootFailed() {
661
+ dormant = true;
662
+ if (0 < stdinQueue.length) {
663
+ wake();
664
+ }
665
+ }
754
666
  // ── stdin reader ─────────────────────────────────────────────────────────
755
667
  process.stdin.setEncoding('utf8');
756
668
  let handshakeAnswered = false;
@@ -770,13 +682,15 @@ onUsableSession = () => undefined) {
770
682
  * and `handshakeAnswered` keeps us from answering twice.
771
683
  */
772
684
  const armLocalHandshake = (line) => {
773
- if (handshakeAnswered)
685
+ if (handshakeAnswered) {
774
686
  return;
687
+ }
775
688
  // Only an `initialize` REQUEST is ours to answer. Asked with no instructions because the ones
776
689
  // we will actually send name the reason the daemon failed, and the reason is not known until
777
690
  // the deliver closure runs — see degradedInstructions.
778
- if (null === localInitializeResponse(line, ''))
691
+ if (null === localInitializeResponse(line, '')) {
779
692
  return;
693
+ }
780
694
  // Take the debt NOW, not when the timer fires.
781
695
  //
782
696
  // Three paths can answer this id — the daemon, the stream-loss drain, and the timer below —
@@ -789,18 +703,27 @@ onUsableSession = () => undefined) {
789
703
  // established first, the `endpoint` handler sets `handshakeAnswered` before the queue is
790
704
  // flushed, which disarms the timer and leaves the daemon's own reply the only one.
791
705
  const claimed = parseJsonRpc(line);
792
- if (claimed?.id !== undefined)
706
+ if (claimed?.id !== undefined) {
793
707
  pending.take(claimed.id);
794
- deliverHandshakeLocally = (reason) => {
795
- if (handshakeAnswered || postUrl !== null)
708
+ }
709
+ deliverHandshakeLocally = (reason, tools) => {
710
+ if (handshakeAnswered || postUrl !== null) {
796
711
  return;
712
+ }
797
713
  // Built here, not at arm time, so the notice can name WHY no daemon answered. The client
798
714
  // reads `instructions` exactly once, at initialize, and this response is the only one it
799
715
  // will ever get — so a handshake that does not say it is unbacked never says it at all.
800
- // See degradedInstructions for the field report this closes.
801
- const response = localInitializeResponse(line, degradedInstructions(proxyInstructions(port), port, reason));
802
- if (null === response)
716
+ // See degradedInstructions for the field report this closes. With `tools`, the daemon is
717
+ // starting: the instructions are the healthy ones, and the list is the daemon's own.
718
+ const response = localInitializeResponse(line, tools === undefined
719
+ ? degradedInstructions(proxyInstructions(port), port, withStateDirDiagnostic(reason))
720
+ : proxyInstructions(port, tools.map((tool) => tool.name)));
721
+ if (null === response) {
803
722
  return;
723
+ }
724
+ if (tools !== undefined) {
725
+ catalog.remember(tools);
726
+ }
804
727
  handshakeAnswered = true;
805
728
  deliverHandshakeLocally = undefined;
806
729
  // The catalog we are about to serve is ours, not the daemon's, so it has to be corrected
@@ -812,19 +735,35 @@ onUsableSession = () => undefined) {
812
735
  // handshake: `replayLines` re-issues it under a distinct id and suppresses the echo.
813
736
  for (let i = stdinQueue.length - 1; i >= 0; i--) {
814
737
  const queued = stdinQueue[i];
815
- if (queued !== undefined && isHandshakeLine(queued))
738
+ if (queued !== undefined && isHandshakeLine(queued)) {
816
739
  stdinQueue.splice(i, 1);
740
+ }
817
741
  }
818
742
  proxyLog('reticle_mcp_local_handshake', { port, reason });
819
743
  emit(response);
744
+ // A `tools/list` that came before this answer is in the queue. Answer it from the catalog.
745
+ for (let i = stdinQueue.length - 1; i >= 0; i--) {
746
+ const cached = catalog.answer(stdinQueue[i] ?? '');
747
+ if (cached !== null) {
748
+ stdinQueue.splice(i, 1);
749
+ emit(cached);
750
+ }
751
+ }
820
752
  };
821
753
  setTimeout(() => deliverHandshakeLocally?.('daemon did not answer in time'), LOCAL_HANDSHAKE_MS).unref();
754
+ // Our own daemon is starting on a free port. Answer now: some clients start their turn before
755
+ // a daemon can be up, and they do not wait for MCP servers.
756
+ void boot?.spawning
757
+ .then(boot.tools)
758
+ .then((tools) => deliverHandshakeLocally?.('the daemon is starting', tools))
759
+ .catch(() => undefined);
822
760
  };
823
761
  let stdinBuffer = '';
824
762
  let stdinDiscarding = false;
825
763
  process.stdin.on('data', (chunk) => {
826
- if (stopped)
764
+ if (stopped) {
827
765
  return;
766
+ }
828
767
  idleExit.noteTraffic();
829
768
  // Not `(buffer + chunk).split()` inline — that rescans everything held on every chunk, so one
830
769
  // large line cost O(n²) in its own size and pinned the event loop for tens of seconds, during
@@ -841,21 +780,25 @@ onUsableSession = () => undefined) {
841
780
  }
842
781
  for (const line of drained.lines) {
843
782
  const trimmed = line.trim();
844
- if ('' === trimmed)
783
+ if ('' === trimmed) {
845
784
  continue;
785
+ }
846
786
  replay.observeOutbound(trimmed);
847
787
  pending.observeOutbound(trimmed);
848
788
  // The ARRIVAL of the request, not the delivery of an answer: "this client asked for the tool
849
789
  // list" is the fact `status` reports on, and a request that arrived and then failed is a
850
790
  // different problem with its own diagnosis. Recording the answer instead would let a host
851
791
  // that never asks look identical to one that asked and was refused.
852
- if (isToolsListRequest(trimmed))
792
+ if (isToolsListRequest(trimmed)) {
853
793
  rememberEnumerated(reticleStateHome(), port);
794
+ }
854
795
  // The hop every other check misses. Recorded on the REQUEST rather than a reply, because
855
796
  // the question is whether anything ever left the agent — a call that leaves and gets no
856
797
  // answer is a different fault from one that was never made, and only this side sees it.
857
- if (isToolCallRequest(trimmed))
798
+ if (isToolCallRequest(trimmed)) {
858
799
  rememberToolCalled(reticleStateHome(), port);
800
+ noteProxyToolCall();
801
+ }
859
802
  const action = onClientRequest(postUrl !== null, dormant);
860
803
  if (action === OnRequest.SEND && postUrl !== null) {
861
804
  forward(postUrl, trimmed);
@@ -878,26 +821,8 @@ onUsableSession = () => undefined) {
878
821
  // Answer it ourselves after a bounded wait; the daemon still receives the client's own
879
822
  // initialize when a session is finally established (replayLines).
880
823
  armLocalHandshake(trimmed);
881
- // WAKE is the ONLY path allowed to start a daemon — see proxy-lifecycle.ts. The queue is
882
- // flushed once the new session's `endpoint` frame arrives.
883
824
  if (action === OnRequest.WAKE) {
884
- dormant = false;
885
- // Budget reset, accounting preserved. See attemptsBeforeDormant.
886
- attemptsBeforeDormant += attempts;
887
- attempts = 0;
888
- void (ensureDaemon?.() ?? Promise.resolve())
889
- .then(() => connect(false))
890
- .catch((err) => {
891
- dormant = true;
892
- const reason = err instanceof Error ? err.message : String(err);
893
- proxyLog('reticle_mcp_proxy_wake_failed', { port, error: reason });
894
- // The wake is the only thing that can start a daemon, so its failure IS the answer to
895
- // everything it was woken for. It used to be logged and nothing else: the request sat
896
- // out the full QUEUE_WAIT_MS and was then refused by a timer that could only say "no
897
- // daemon could be reached" — twenty seconds later, and less true, than the sentence
898
- // already in hand.
899
- expireQueue(reason);
900
- });
825
+ wake();
901
826
  }
902
827
  }
903
828
  });
@@ -909,10 +834,11 @@ onUsableSession = () => undefined) {
909
834
  // between the writes made it "work", which is the signature of a teardown race, not of a client
910
835
  // that asked for too much. Drain first, and let the exit status carry the truth if it fails.
911
836
  process.stdin.on('end', () => {
912
- if (stopped)
837
+ if (stopped) {
913
838
  return;
839
+ }
914
840
  if (0 === pending.unanswered.length) {
915
- quit(0);
841
+ quit(0, ProxyExitReason.STDIN_END);
916
842
  return;
917
843
  }
918
844
  const deadline = Date.now() + SHUTDOWN_DRAIN_MS;
@@ -920,14 +846,15 @@ onUsableSession = () => undefined) {
920
846
  const stuck = pending.unanswered;
921
847
  if (0 === stuck.length) {
922
848
  clearInterval(poll);
923
- quit(0);
849
+ quit(0, ProxyExitReason.STDIN_END);
924
850
  return;
925
851
  }
926
- if (Date.now() < deadline)
852
+ if (Date.now() < deadline) {
927
853
  return;
854
+ }
928
855
  clearInterval(poll);
929
856
  proxyLog('reticle_mcp_proxy_unanswered_at_exit', { port, ids: stuck });
930
- quit(1);
857
+ quit(1, ProxyExitReason.STDIN_END);
931
858
  }, SHUTDOWN_DRAIN_POLL_MS);
932
859
  });
933
860
  });