@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
@@ -10,19 +10,22 @@
10
10
  import { log } from '../../log.js';
11
11
  import { isAlive, spawnDaemon, reticleStateHome } from '../daemon/daemon.js';
12
12
  import { PortPresence, probePresence, presenceIsUsable, describePresence, } from '../daemon/binding/port-presence.js';
13
- import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, } from '../../surface/mcp/mcp-proxy.js';
13
+ import { waitForDaemon, startMcpProxy, probeDaemon, proxyLog, setProxyLogPort, flushProxyExit, reportWakeRefused, } from '../../surface/mcp/mcp-proxy.js';
14
14
  import { installProxyResilience } from '../daemon/daemon-resilience.js';
15
15
  import { readProjectId, readProjectPort } from './ports/resolve/cli-port.js';
16
16
  import { resolveMcpPort, daemonProjectAt, relocationReason, } from '../daemon/daemon-resolve.js';
17
17
  import { daemonSpawnArgs } from './daemon-start-options.js';
18
18
  import { WakeAction, decideWake } from '../daemon/wake-decision.js';
19
+ import { runKill } from './cli-kill.js';
19
20
  import { pickDaemonPortToBind } from '../daemon/binding/free-port.js';
20
- import { fetchStatus } from '../daemon/binding/daemon-status-probe.js';
21
+ import { fetchStatus, summarizeStatus } from '../daemon/binding/daemon-status-probe.js';
22
+ import { isNewerVersion } from '../update/update-nudge.js';
21
23
  import { migrateApprovals } from '../setup/approval-migration.js';
22
- import { ReticleEnv } from '@reticlehq/core';
24
+ import { ProxyExitReason, ReticleEnv, WakeRefusalPresence } from '@reticlehq/core';
23
25
  import { agentIo } from '../setup/agent-io.js';
24
26
  import { SERVER_VERSION } from '../version/identity/server-version.js';
25
27
  import { homedir } from 'node:os';
28
+ import { platformKey } from '../setup/agent-configs.js';
26
29
  /**
27
30
  * Whether the daemon this proxy starts hides its browser. A daemon on a port nobody chose — the
28
31
  * requested one served another project, so this one was moved — was opening a visible browser the
@@ -31,6 +34,41 @@ import { homedir } from 'node:os';
31
34
  export function spawnsHeadless(headless, requested, resolved) {
32
35
  return headless || requested !== resolved;
33
36
  }
37
+ const RELEASE_VERSION = /^\d+\.\d+\.\d+/;
38
+ /** The first release whose `/status` names the agents attached to it (`mcpPeers`). */
39
+ const MCP_PEERS_SINCE = '3.6.0';
40
+ /**
41
+ * Should a proxy stop the older daemon it found, and start its own release?
42
+ *
43
+ * Only an OLDER daemon, read from `/status`, with no tab attached and no other agent attached. A
44
+ * tab or an agent can have a call in flight, so that daemon stays and the skew is described as
45
+ * before. A daemon before `MCP_PEERS_SINCE` does not report its agents, so it stays too. A status
46
+ * that does not parse, or a version that does not compare, keeps the daemon.
47
+ */
48
+ export function retiresOlderDaemon(status, proxyVersion) {
49
+ if (typeof status !== 'object' || null === status) {
50
+ return false;
51
+ }
52
+ const fields = status;
53
+ const version = fields['version'];
54
+ if ('string' !== typeof version) {
55
+ return false;
56
+ }
57
+ // Only a plain release number compares; anything else keeps the daemon.
58
+ if (!RELEASE_VERSION.test(version) || !isNewerVersion(proxyVersion, version)) {
59
+ return false;
60
+ }
61
+ // An older daemon never sends `mcpPeers`, so it cannot say that no agent is attached.
62
+ if (isNewerVersion(MCP_PEERS_SINCE, version)) {
63
+ return false;
64
+ }
65
+ const peers = fields['mcpPeers'];
66
+ // A daemon sends `mcpPeers` only when an agent is attached.
67
+ if (Array.isArray(peers) && 0 < peers.length) {
68
+ return false;
69
+ }
70
+ return 0 === summarizeStatus(status).sessionCount;
71
+ }
34
72
  /**
35
73
  * MCP proxy mode: ensures the daemon is running, then bridges Claude Code's
36
74
  * stdin/stdout to the daemon's SSE endpoint. This is the recommended way to
@@ -46,10 +84,9 @@ export async function handleMcp(opts) {
46
84
  // takes the requested port when it is free or unclaimed, and relocates to an OS-assigned port
47
85
  // rather than adopting a daemon that belongs to somebody else.
48
86
  //
49
- // Synchronous-looking on purpose: a probe is one loopback connect, and doing it here keeps the
50
- // proxy's transport built around a single fixed number. Making the proxy chase a moving port would
51
- // mean reworking the reconnect path, which is the one piece whose failure reads to a user as
52
- // "the MCP server disappeared mid-session".
87
+ // The reconnect path keeps one port. Only a WAKE, which already starts from a dormant proxy,
88
+ // resolves again and moves the proxy: see `ensure`. The reconnect path is the one piece whose
89
+ // failure reads to a user as "the MCP server disappeared mid-session", so it does not chase.
53
90
  // Read once and reused by the wake path below: the boot resolution and every later wake must be
54
91
  // defending the SAME identity, and computing it twice is how they drift apart.
55
92
  // Before the port work, because it is the only moment we are guaranteed to get on a machine that
@@ -61,24 +98,29 @@ export async function handleMcp(opts) {
61
98
  migrateApprovals({
62
99
  io: agentIo,
63
100
  home: homedir(),
64
- platform: process.platform,
101
+ platform: platformKey(process.platform),
65
102
  stateHome: reticleStateHome(),
66
103
  version: SERVER_VERSION,
67
104
  log,
68
105
  });
69
106
  }
70
107
  const projectId = readProjectId(process.cwd());
71
- const port = await resolveMcpPort(opts.port, projectId, reticleStateHome(), {
108
+ const resolvePort = () => resolveMcpPort(opts.port, projectId, reticleStateHome(), {
72
109
  alive: isAlive,
73
110
  daemonPresent: async (p) => presenceIsUsable(await probePresence(p, { tcpOpen: probeDaemon, status: fetchStatus })),
74
111
  pickPort: (p) => pickDaemonPortToBind(p),
75
112
  }, readProjectPort(process.cwd()));
76
- if (port !== opts.port) {
113
+ const noteRelocated = (to) => {
77
114
  log('reticle_mcp_port_relocated', {
78
115
  requested: opts.port,
79
- port,
80
- reason: relocationReason(opts.port, port, projectId, reticleStateHome(), isAlive),
116
+ port: to,
117
+ reason: relocationReason(opts.port, to, projectId, reticleStateHome(), isAlive),
81
118
  });
119
+ };
120
+ // `let`: a wake that finds the port taken resolves again, and the proxy follows (#1651, #1652).
121
+ let port = await resolvePort();
122
+ if (port !== opts.port) {
123
+ noteRelocated(port);
82
124
  }
83
125
  // The proxy IS the MCP server the editor launched. Nothing respawns it, so an uncaught throw here
84
126
  // is what a user experiences as "the MCP server disconnected — open /mcp and reconnect". Log it,
@@ -88,7 +130,11 @@ export async function handleMcp(opts) {
88
130
  // swallows stderr, so wiring these to `log` meant a crash was handled and then unrecorded — the
89
131
  // failure a user reports as "it disconnected" left nothing behind to read.
90
132
  setProxyLogPort(port);
91
- const resilience = installProxyResilience(process, proxyLog);
133
+ // The storm exit is a proxy exit too: report it before the process goes.
134
+ // Only the client pipes count toward the storm exit; see installProxyResilience.
135
+ const resilience = installProxyResilience(process, proxyLog, undefined, () => {
136
+ void flushProxyExit(ProxyExitReason.CLIENT_GONE_STORM).finally(() => process.exit(0));
137
+ }, [process.stdin, process.stdout]);
92
138
  /**
93
139
  * Make sure a daemon is on the port, spawning one if not.
94
140
  *
@@ -97,7 +143,10 @@ export async function handleMcp(opts) {
97
143
  * a dead port until the budget ran out and the agent's MCP server exited with it. Reticle simply
98
144
  * disappeared mid-session with nothing said. Respawning here makes the reconnect self-healing.
99
145
  */
100
- const ensure = async () => {
146
+ // Resolved when a new daemon process starts, so the proxy can answer the handshake at once.
147
+ let markSpawning = () => undefined;
148
+ const spawning = new Promise((resolve) => (markSpawning = resolve));
149
+ const ensure = async (mayRelocate = true) => {
101
150
  // The same question every other surface asks. It used to be a bare TCP connect, so a stranger on
102
151
  // the bridge port answered "a daemon is here" — the proxy connected, the stream ended, and each
103
152
  // client request woke into the identical non-answer. Rejecting here is what puts the proxy
@@ -112,9 +161,34 @@ export async function handleMcp(opts) {
112
161
  // APPLICATION. That is worse than the disconnect it was reintroduced under the cover of, because
113
162
  // a disconnect is visible and this is a false green wearing a valid session.
114
163
  const wake = decideWake(presence, daemonProjectAt(port, reticleStateHome()), projectId);
115
- if (WakeAction.USE === wake)
116
- return;
164
+ if (WakeAction.USE === wake) {
165
+ // A proxy from a newer release stops an older daemon that nobody is using, and starts its own.
166
+ // Otherwise every tool refused on the skew until a person ran `reticle restart`.
167
+ const status = await fetchStatus(port);
168
+ if (!retiresOlderDaemon(status, SERVER_VERSION)) {
169
+ return port;
170
+ }
171
+ log('reticle_mcp_retiring_older_daemon', { port, proxyVersion: SERVER_VERSION });
172
+ // Not stopped: use it. The daemon describes the skew to this agent, as before.
173
+ if (!(await runKill(port, false))) {
174
+ return port;
175
+ }
176
+ }
177
+ if (WakeAction.REFUSE === wake && mayRelocate) {
178
+ // Somebody else took the port after our daemon left it. Resolve again, as at boot: our own
179
+ // daemon wherever it moved, else a free port. Once per wake, so it cannot loop.
180
+ const moved = await resolvePort();
181
+ if (moved !== port) {
182
+ noteRelocated(moved);
183
+ port = moved;
184
+ setProxyLogPort(port);
185
+ return ensure(false);
186
+ }
187
+ }
117
188
  if (WakeAction.REFUSE === wake) {
189
+ void reportWakeRefused(PortPresence.DAEMON === presence
190
+ ? WakeRefusalPresence.OTHER_PROJECT
191
+ : WakeRefusalPresence.FOREIGN);
118
192
  throw new Error(PortPresence.DAEMON === presence
119
193
  ? `the daemon on port ${String(port)} belongs to a different project, so this one will ` +
120
194
  'not adopt it. Stop it, or start this project on its own port.'
@@ -123,6 +197,7 @@ export async function handleMcp(opts) {
123
197
  const scriptPath = process.argv[1];
124
198
  if (scriptPath === undefined) {
125
199
  log('reticle_mcp_no_script', {});
200
+ await flushProxyExit(ProxyExitReason.WAKE_FAILED);
126
201
  process.exit(1);
127
202
  }
128
203
  const daemonArgs = daemonSpawnArgs({
@@ -133,6 +208,7 @@ export async function handleMcp(opts) {
133
208
  ...(httpPort !== undefined ? { httpPort } : {}),
134
209
  ...(httpToken !== undefined ? { httpToken } : {}),
135
210
  });
211
+ markSpawning();
136
212
  spawnDaemon(process.execPath, scriptPath, daemonArgs, port);
137
213
  // Announce the daemon only once the PORT ACCEPTS. This line used to be written the instant the
138
214
  // child was spawned, which on a Windows first bootstrap meant `reticle_mcp_daemon_started`
@@ -141,6 +217,7 @@ export async function handleMcp(opts) {
141
217
  // none: a client that believes it stops waiting for the thing that has not happened.
142
218
  await waitForDaemon(port);
143
219
  log('reticle_mcp_daemon_started', { port, ...(driveUrl !== undefined ? { driveUrl } : {}) });
220
+ return port;
144
221
  };
145
222
  // Start the proxy WHATEVER happened to the daemon.
146
223
  //
@@ -150,35 +227,44 @@ export async function handleMcp(opts) {
150
227
  // editor shows a server that failed to start. Nothing about that is unrecoverable: the proxy
151
228
  // answers `initialize` itself, serves the cached catalog, and its wake path retries a daemon on
152
229
  // every client request. Present-and-complaining beats absent, because absent needs a human.
153
- // `void`: the chain handles its own failure and the process must not wait on it — the proxy is
154
- // started from `finally` either way.
155
- void ensure()
156
- .catch((err) => {
230
+ //
231
+ // The proxy starts NOW, beside the daemon start, and dials when the start is done. A client that
232
+ // does not wait for MCP servers gets the handshake and the tool list before the daemon is up.
233
+ const ready = ensure().catch((err) => {
157
234
  log('reticle_mcp_daemon_unavailable', {
158
235
  error: err instanceof Error ? err.message : String(err),
159
236
  note: 'serving anyway — the next tool call will try to start a daemon again',
160
237
  });
161
- })
162
- .finally(() => {
163
- // Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
164
- //
165
- // The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
166
- // fails — which is precisely the case this block exists to tolerate, a daemon that is not
167
- // there yet — and with nothing attached that reject became an unhandledRejection. Reported
168
- // from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
169
- // because the stack of a refused socket is entirely node internals and the privacy filter
170
- // keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
171
- // crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
172
- // on serving from cache, waking a daemon on the next request — which is exactly why this
173
- // must be logged as the expected condition it is rather than reported as a defect.
174
- void startMcpProxy(port, ensure, undefined, () => {
175
- resilience.noteUsableSession();
176
- }).catch((err) => {
177
- log('reticle_mcp_proxy_first_connect_failed', {
178
- port,
179
- error: err instanceof Error ? err.message : String(err),
180
- note: 'serving from cache; the next client request will try to start a daemon',
181
- });
238
+ });
239
+ // Also `void`: the proxy runs for the life of the process and is never awaited by anyone.
240
+ //
241
+ // The `.catch` is load-bearing despite that. `startMcpProxy` rejects when its FIRST connect
242
+ // fails — which is precisely the case this block exists to tolerate, a daemon that is not
243
+ // there yet — and with nothing attached that reject became an unhandledRejection. Reported
244
+ // from a win32 user as a crash reading `connect ECONNREFUSED` with an EMPTY frame list,
245
+ // because the stack of a refused socket is entirely node internals and the privacy filter
246
+ // keeps only Reticle frames. So the one path we most want diagnosable arrived as an anonymous
247
+ // crash. The proxy itself is unaffected — it has already installed its stdin reader and goes
248
+ // on serving from cache, waking a daemon on the next request — which is exactly why this
249
+ // must be logged as the expected condition it is rather than reported as a defect.
250
+ void startMcpProxy(port, ensure, undefined, () => {
251
+ resilience.noteUsableSession();
252
+ }, {
253
+ ready,
254
+ spawning,
255
+ // Loaded only when needed: the tool table is large, and a warm start never uses it.
256
+ tools: async () => {
257
+ const [{ staticToolCatalog }, { resolveToolSurface }] = await Promise.all([
258
+ import('../../surface/mcp/mcp.js'),
259
+ import('../../surface/tools/tool-surface.js'),
260
+ ]);
261
+ return staticToolCatalog(resolveToolSurface());
262
+ },
263
+ }).catch((err) => {
264
+ log('reticle_mcp_proxy_first_connect_failed', {
265
+ port,
266
+ error: err instanceof Error ? err.message : String(err),
267
+ note: 'serving from cache; the next client request will try to start a daemon',
182
268
  });
183
269
  });
184
270
  }
@@ -0,0 +1,43 @@
1
+ import { type ToolCaller } from './adhoc-verdict.js';
2
+ /**
3
+ * `reticle open <url> --navigate`: move the tab that is already on this origin to `url`.
4
+ *
5
+ * The default leaves that tab where it is, on purpose: `open` is often run by a person whose tab it
6
+ * is (see `decideOpen`). But a caller with no MCP tools, a shell loop or an agent whose MCP link
7
+ * timed out, then had no way past the first page of a multi-page site short of restarting the
8
+ * daemon (#1140). This is the explicit opt-in. It sends the same `reticle_navigate` an agent would,
9
+ * over the daemon's own SSE transport, exactly as `verify --expect` does, and waits for arrival.
10
+ */
11
+ export interface OpenNavigateOptions {
12
+ port: number;
13
+ sessionId: string;
14
+ url: string;
15
+ /**
16
+ * The daemon's pairing token. Omitted, it is read here, the way `verify --expect` reads it, so the
17
+ * CLI entry point does not reach into the bridge itself. A test that injects `connect` gets none.
18
+ */
19
+ token?: string;
20
+ /** Injected so a test does not need a live daemon. */
21
+ connect?: (endpoint: URL) => Promise<ToolCaller>;
22
+ }
23
+ /** Accepted by the browser, but no page on the url was observed: not `navigated`, and not an error. */
24
+ export declare const NAVIGATE_UNCONFIRMED_NOTE: string;
25
+ /**
26
+ * Navigate the session and describe the outcome as fields for the `reticle_open` line. `navigated`
27
+ * is set only when the tool says the page arrived; otherwise the tool's own reason is passed on,
28
+ * never a success the navigate did not report.
29
+ */
30
+ export declare function navigateLeftTab(options: OpenNavigateOptions): Promise<Record<string, unknown>>;
31
+ /** `--navigate` found a tab on the origin, but the tab never said which session it is. */
32
+ export declare const NAVIGATE_NO_SESSION_ID: string;
33
+ /**
34
+ * The tabs `open` may pick from.
35
+ *
36
+ * `--navigate` MOVES a tab, so it may only pick one of this project's: on a daemon serving two
37
+ * projects, the origin match alone could hand it the other project's tab. A tab that reports no
38
+ * project is kept, since nothing says it is someone else's. Reuse and the left-as-is note only read,
39
+ * so without `--navigate`, or with no project to scope by, every tab stays a candidate.
40
+ */
41
+ export declare function openCandidates<T extends {
42
+ projectId?: string | undefined;
43
+ }>(sessions: T[], navigate: boolean, myProject: string | undefined): T[];
@@ -0,0 +1,85 @@
1
+ import { ReticleTool } from '@reticlehq/core';
2
+ import { connectOverSse, endpointFor, verdictOf } from './adhoc-verdict.js';
3
+ import { readOrCreatePairingTokenSync, defaultPairingTokenDir, } from '../../portal/bridge/pairing-token.js';
4
+ /** Named, as the repository asks of every user-facing string. */
5
+ const DAEMON_UNREACHABLE = 'could not reach the daemon to navigate';
6
+ const NAVIGATE_FAILED = 'navigate failed';
7
+ const NO_ARRIVAL_REPORTED = 'reticle_navigate did not report arrival';
8
+ /** Accepted by the browser, but no page on the url was observed: not `navigated`, and not an error. */
9
+ export const NAVIGATE_UNCONFIRMED_NOTE = 'the browser accepted the navigation, but no page on that url reconnected in the wait window, so ' +
10
+ 'arrival was not observed. Re-run `reticle status` before relying on the tab being there.';
11
+ /**
12
+ * Navigate the session and describe the outcome as fields for the `reticle_open` line. `navigated`
13
+ * is set only when the tool says the page arrived; otherwise the tool's own reason is passed on,
14
+ * never a success the navigate did not report.
15
+ */
16
+ export async function navigateLeftTab(options) {
17
+ const connect = options.connect ?? connectOverSse;
18
+ let caller;
19
+ try {
20
+ const token = options.token ??
21
+ (options.connect === undefined
22
+ ? readOrCreatePairingTokenSync(defaultPairingTokenDir())
23
+ : undefined);
24
+ caller = await connect(endpointFor(options.port, token));
25
+ }
26
+ catch (error) {
27
+ return {
28
+ error: `${DAEMON_UNREACHABLE}: ${error instanceof Error ? error.message : String(error)}`,
29
+ };
30
+ }
31
+ try {
32
+ const result = await caller.call(ReticleTool.NAVIGATE, {
33
+ url: options.url,
34
+ sessionId: options.sessionId,
35
+ });
36
+ // `ok` is the field every navigate answer carries, so a text-only answer is read too.
37
+ const report = verdictOf(result, 'ok') ?? {};
38
+ const landedOn = 'string' === typeof report['landedOn'] ? report['landedOn'] : undefined;
39
+ // A navigation reconnects as a NEW session; the id to act on next is the arrival's, not ours.
40
+ const arrivedAs = 'string' === typeof report['sessionId'] ? report['sessionId'] : options.sessionId;
41
+ if (true === report['ok'] && false === report['confirmed'] && landedOn === undefined) {
42
+ return { requested: options.url, sessionId: arrivedAs, note: NAVIGATE_UNCONFIRMED_NOTE };
43
+ }
44
+ if (true === report['ok']) {
45
+ return {
46
+ navigated: options.url,
47
+ sessionId: arrivedAs,
48
+ ...(landedOn === undefined ? {} : { landedOn }),
49
+ };
50
+ }
51
+ const reason = 'string' === typeof report['reason']
52
+ ? report['reason']
53
+ : 'string' === typeof report['error']
54
+ ? report['error']
55
+ : NO_ARRIVAL_REPORTED;
56
+ return {
57
+ error: `${NAVIGATE_FAILED} (${options.url}): ${reason}`,
58
+ sessionId: options.sessionId,
59
+ };
60
+ }
61
+ catch (error) {
62
+ return {
63
+ error: `${NAVIGATE_FAILED} (${options.url}): ${error instanceof Error ? error.message : String(error)}`,
64
+ };
65
+ }
66
+ finally {
67
+ await caller.close().catch(() => undefined);
68
+ }
69
+ }
70
+ /** `--navigate` found a tab on the origin, but the tab never said which session it is. */
71
+ export const NAVIGATE_NO_SESSION_ID = '--navigate could not move it: that tab reported no session id. Drive it with reticle_navigate, ' +
72
+ 'or open the url in the browser yourself.';
73
+ /**
74
+ * The tabs `open` may pick from.
75
+ *
76
+ * `--navigate` MOVES a tab, so it may only pick one of this project's: on a daemon serving two
77
+ * projects, the origin match alone could hand it the other project's tab. A tab that reports no
78
+ * project is kept, since nothing says it is someone else's. Reuse and the left-as-is note only read,
79
+ * so without `--navigate`, or with no project to scope by, every tab stays a candidate.
80
+ */
81
+ export function openCandidates(sessions, navigate, myProject) {
82
+ if (!navigate || myProject === undefined)
83
+ return sessions;
84
+ return sessions.filter((s) => s.projectId === undefined || s.projectId === myProject);
85
+ }
@@ -26,7 +26,7 @@ export interface PortHolder {
26
26
  * identify the holder, rather than asserting the holder is not ours — a claim it has no evidence
27
27
  * for, and one that was wrong for every Windows user.
28
28
  */
29
- export declare function captureLookup(command: string, args: readonly string[]): string | null;
29
+ export declare function captureLookup(command: string, args: readonly string[], timeoutMs?: number): string | null;
30
30
  /**
31
31
  * The FIRST listener in `lsof -nP -iTCP:<port> -sTCP:LISTEN -F pc` output, or null.
32
32
  *
@@ -53,11 +53,23 @@ ourPid?: number | null): string;
53
53
  export declare function findPortHolder(port: number, exec: (command: string, args: readonly string[]) => string | null): PortHolder | null;
54
54
  /** The path in `lsof -a -p <pid> -d cwd -Fn` output, or null when it printed none. */
55
55
  export declare function parseProcessCwd(stdout: string): string | null;
56
+ /**
57
+ * The folder that holds the `node_modules` a command line runs from, or null.
58
+ *
59
+ * A dev server started from a project runs a script under that project's `node_modules`, so the
60
+ * folder before it is the project. The path starts at the last drive letter before it, because an
61
+ * unquoted node path can come first. No `node_modules` is no evidence, and the answer is null.
62
+ * ponytail: compares as written; a drive letter in another case does not match.
63
+ */
64
+ export declare function parseCommandLineRoot(commandLine: string): string | null;
56
65
  /**
57
66
  * The working directory of whatever LISTENS on `port`, or null.
58
67
  *
59
68
  * How the daemon tells which checkout served a page when every git worktree declares the same
60
- * projectId: the dev server runs inside exactly one of them. Null wherever `lsof` cannot answer —
61
- * Windows, a slim container — and the caller then keeps the answer it had without this.
69
+ * projectId: the dev server runs inside exactly one of them. Null wherever the lookup cannot answer
70
+ * — a slim container with no `lsof` — and the caller then keeps the answer it had without this.
71
+ *
72
+ * Windows has no `lsof`, and no command shows another process's working directory. There the
73
+ * answer is the project folder in the listener's command line, from PowerShell.
62
74
  */
63
- export declare function findListenerCwd(port: number, exec: (command: string, args: readonly string[]) => string | null): string | null;
75
+ export declare function findListenerCwd(port: number, exec: (command: string, args: readonly string[], timeoutMs?: number) => string | null, platform?: NodeJS.Platform): string | null;
@@ -25,9 +25,14 @@ const LOOKUP_TIMEOUT_MS = 2_000;
25
25
  * identify the holder, rather than asserting the holder is not ours — a claim it has no evidence
26
26
  * for, and one that was wrong for every Windows user.
27
27
  */
28
- export function captureLookup(command, args) {
28
+ export function captureLookup(command, args, timeoutMs = LOOKUP_TIMEOUT_MS) {
29
29
  try {
30
- const result = spawnSync(command, [...args], { encoding: 'utf8', timeout: LOOKUP_TIMEOUT_MS });
30
+ // The daemon has no console: without windowsHide, each lookup opens a visible window on Windows.
31
+ const result = spawnSync(command, [...args], {
32
+ encoding: 'utf8',
33
+ timeout: timeoutMs,
34
+ windowsHide: true,
35
+ });
31
36
  return 0 === result.status && 'string' === typeof result.stdout ? result.stdout : null;
32
37
  }
33
38
  catch {
@@ -52,20 +57,25 @@ export function parsePortHolder(stdout) {
52
57
  let command;
53
58
  for (const raw of stdout.split('\n')) {
54
59
  const line = raw.trim();
55
- if (0 === line.length)
60
+ if (0 === line.length) {
56
61
  continue;
62
+ }
57
63
  const value = line.slice(1);
58
64
  if (line.startsWith(PID_FIELD)) {
59
- if (pid !== undefined)
60
- break; // a second record: the first one is the answer
65
+ // A second record: the first one is the answer.
66
+ if (pid !== undefined) {
67
+ break;
68
+ }
61
69
  const parsed = Number(value);
62
- if (!Number.isInteger(parsed) || parsed <= 0)
70
+ if (!Number.isInteger(parsed) || parsed <= 0) {
63
71
  return null;
72
+ }
64
73
  pid = parsed;
65
74
  continue;
66
75
  }
67
- if (line.startsWith(COMMAND_FIELD) && command === undefined)
76
+ if (line.startsWith(COMMAND_FIELD) && command === undefined) {
68
77
  command = value;
78
+ }
69
79
  }
70
80
  return pid === undefined || command === undefined ? null : { pid, command };
71
81
  }
@@ -126,22 +136,58 @@ const NAME_FIELD = 'n';
126
136
  /** The path in `lsof -a -p <pid> -d cwd -Fn` output, or null when it printed none. */
127
137
  export function parseProcessCwd(stdout) {
128
138
  for (const raw of stdout.split('\n')) {
129
- if (raw.startsWith(NAME_FIELD) && raw.length > NAME_FIELD.length)
139
+ if (raw.startsWith(NAME_FIELD) && raw.length > NAME_FIELD.length) {
130
140
  return raw.slice(1);
141
+ }
131
142
  }
132
143
  return null;
133
144
  }
145
+ /** A Windows PowerShell cold start alone can take more than the lsof bound. */
146
+ const WINDOWS_LOOKUP_TIMEOUT_MS = 8_000;
147
+ /** The listener's command line: Windows does not show another process's working directory. */
148
+ const windowsListenerScript = (port) => `$c = Get-NetTCPConnection -State Listen -LocalPort ${String(port)} -ErrorAction Stop | ` +
149
+ 'Select-Object -First 1; ' +
150
+ '(Get-CimInstance Win32_Process -Filter "ProcessId=$($c.OwningProcess)").CommandLine';
151
+ const NODE_MODULES = /[\\/]node_modules[\\/]/i;
152
+ const DRIVE_START = /[A-Za-z]:[\\/]/g;
153
+ /**
154
+ * The folder that holds the `node_modules` a command line runs from, or null.
155
+ *
156
+ * A dev server started from a project runs a script under that project's `node_modules`, so the
157
+ * folder before it is the project. The path starts at the last drive letter before it, because an
158
+ * unquoted node path can come first. No `node_modules` is no evidence, and the answer is null.
159
+ * ponytail: compares as written; a drive letter in another case does not match.
160
+ */
161
+ export function parseCommandLineRoot(commandLine) {
162
+ const end = commandLine.search(NODE_MODULES);
163
+ if (end < 0) {
164
+ return null;
165
+ }
166
+ const head = commandLine.slice(0, end);
167
+ const starts = [...head.matchAll(DRIVE_START)];
168
+ const start = starts[starts.length - 1]?.index;
169
+ return start === undefined ? null : head.slice(start);
170
+ }
134
171
  /**
135
172
  * The working directory of whatever LISTENS on `port`, or null.
136
173
  *
137
174
  * How the daemon tells which checkout served a page when every git worktree declares the same
138
- * projectId: the dev server runs inside exactly one of them. Null wherever `lsof` cannot answer —
139
- * Windows, a slim container — and the caller then keeps the answer it had without this.
175
+ * projectId: the dev server runs inside exactly one of them. Null wherever the lookup cannot answer
176
+ * — a slim container with no `lsof` — and the caller then keeps the answer it had without this.
177
+ *
178
+ * Windows has no `lsof`, and no command shows another process's working directory. There the
179
+ * answer is the project folder in the listener's command line, from PowerShell.
140
180
  */
141
- export function findListenerCwd(port, exec) {
181
+ export function findListenerCwd(port, exec, platform = process.platform) {
182
+ if ('win32' === platform) {
183
+ const args = ['-NoProfile', '-NonInteractive', '-Command', windowsListenerScript(port)];
184
+ const line = exec('powershell', args, WINDOWS_LOOKUP_TIMEOUT_MS);
185
+ return null === line ? null : parseCommandLineRoot(line.trim());
186
+ }
142
187
  const holder = findPortHolder(port, exec);
143
- if (null === holder)
188
+ if (null === holder) {
144
189
  return null;
190
+ }
145
191
  const out = exec('lsof', ['-a', '-p', String(holder.pid), '-d', 'cwd', '-Fn']);
146
192
  return null === out ? null : parseProcessCwd(out);
147
193
  }
@@ -10,7 +10,7 @@
10
10
  import { readFileSync } from 'node:fs';
11
11
  import { dirname, join, resolve } from 'node:path';
12
12
  import { SILENT_HOST, buildNodeIo, deriveProjectId, findWorkspaceApps, isDesktopProject, packageName, } from '@reticlehq/init';
13
- import { asProjectId } from '@reticlehq/core';
13
+ import { asProjectId, mainCheckoutTwin } from '@reticlehq/core';
14
14
  /** The project config `reticle init` writes. */
15
15
  export const RETICLE_CONFIG_BASENAME = '.reticle.json';
16
16
  /**
@@ -35,9 +35,25 @@ const MAX_CONFIG_SEARCH_DEPTH = 6;
35
35
  * Returns undefined when nothing is found, which keeps every caller's existing default intact.
36
36
  */
37
37
  export function findProjectConfig(cwd) {
38
- const dir = findProjectConfigDir(cwd);
38
+ const dir = findProjectConfigDir(cwd) ?? configDirInMainCheckout(cwd);
39
39
  return dir === undefined ? undefined : readConfigAt(dir);
40
40
  }
41
+ /**
42
+ * The main checkout's config folder, for a git worktree that has no `.reticle.json` of its own (it
43
+ * is git-ignored, or not committed yet). Every worktree is the same project, so it keeps one id and
44
+ * one port. Only the config's VALUES come from here; the worktree stays the project folder.
45
+ */
46
+ function configDirInMainCheckout(cwd) {
47
+ const twin = mainCheckoutTwin(resolve(cwd), (path) => {
48
+ try {
49
+ return readFileSync(path, 'utf8');
50
+ }
51
+ catch {
52
+ return undefined; // Missing or a directory: not the `.git` file this looks for.
53
+ }
54
+ });
55
+ return twin === undefined ? undefined : findProjectConfigDir(twin);
56
+ }
41
57
  /** The directory holding the nearest `.reticle.json` at or above `cwd`, by the same walk. */
42
58
  function findProjectConfigDir(cwd) {
43
59
  let dir = resolve(cwd);
@@ -34,13 +34,7 @@ localProjectId?: string) => string;
34
34
  export declare const CreatedProjectSchema: z.ZodObject<{
35
35
  projectId: z.ZodString;
36
36
  name: z.ZodString;
37
- }, "strip", z.ZodTypeAny, {
38
- name: string;
39
- projectId: string;
40
- }, {
41
- name: string;
42
- projectId: string;
43
- }>;
37
+ }, z.core.$strip>;
44
38
  /** ` in workspace "<name>"`, or nothing when no name is known. */
45
39
  export declare const inWorkspace: (name: string | undefined) => string;
46
40
  /**
@@ -83,6 +83,7 @@ export function repoIdentity(dir) {
83
83
  cwd: dir,
84
84
  encoding: 'utf8',
85
85
  stdio: ['ignore', 'pipe', 'ignore'],
86
+ windowsHide: true,
86
87
  }).trim();
87
88
  const remote = git('config', '--get', 'remote.origin.url')
88
89
  .replace(/\.git$/, '')
@@ -1,3 +1,5 @@
1
+ /** What the Stop hook tells an agent that tried to stop while its Harness drive runs. */
2
+ export declare function keepAliveReason(runId: string): string;
1
3
  export interface ReportInput {
2
4
  cwd: string;
3
5
  now: number;
@@ -5,12 +7,16 @@ export interface ReportInput {
5
7
  treeChanged: boolean;
6
8
  session?: string;
7
9
  hook?: boolean;
10
+ /** The user's home, whose `~/.reticle/unmatched` is read too. Absent: the project's root alone. */
11
+ home?: string;
8
12
  }
9
13
  export declare function reportFor(input: ReportInput): Promise<{
10
14
  lines: string[];
15
+ block?: string;
11
16
  }>;
12
17
  /**
13
- * The command. As a hook it prints Claude Code's `systemMessage` shape and always exits 0: it is
14
- * print-only by design, so the human sees the gap and the agent is never blocked by it.
18
+ * The command. As a hook it prints Claude Code's `systemMessage` shape and always exits 0, so the
19
+ * human sees the gap. It blocks the stop in one case only: a Harness drive the agent started is still
20
+ * running, bounded by `KEEP_ALIVE_MAX_MS`.
15
21
  */
16
22
  export declare function handleReport(session: string | undefined, hook: boolean): Promise<void>;