@reticlehq/server 2.12.0 → 2.13.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 (386) hide show
  1. package/SKILL.md +67 -109
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -1
  5. package/dist/bridge/bridge.js +247 -164
  6. package/dist/bridge/pairing-token.d.ts +5 -0
  7. package/dist/bridge/pairing-token.js +28 -0
  8. package/dist/bridge/protocol-skew.d.ts +37 -0
  9. package/dist/bridge/protocol-skew.js +45 -0
  10. package/dist/capsule/capsule-store.d.ts +6 -6
  11. package/dist/capsule/causal-summary.js +7 -2
  12. package/dist/cli/cli-doctor.js +31 -1
  13. package/dist/cli/cli-launch.d.ts +11 -1
  14. package/dist/cli/cli-launch.js +59 -10
  15. package/dist/cli/cli-parse.d.ts +15 -1
  16. package/dist/cli/cli-parse.js +212 -8
  17. package/dist/cli/cli-verify.d.ts +1 -0
  18. package/dist/cli/cli-verify.js +3 -2
  19. package/dist/cli/cloud-cli.js +428 -259
  20. package/dist/cli/cloud-keystore.d.ts +37 -0
  21. package/dist/cli/cloud-keystore.js +97 -0
  22. package/dist/cli/cloud-kit.d.ts +37 -0
  23. package/dist/cli/cloud-kit.js +121 -0
  24. package/dist/cli/cloud-login.d.ts +17 -0
  25. package/dist/cli/cloud-login.js +183 -0
  26. package/dist/cli/cloud-session.d.ts +48 -0
  27. package/dist/cli/cloud-session.js +53 -0
  28. package/dist/cli/config-discovery.js +1 -0
  29. package/dist/cli/daemon-start-options.d.ts +32 -0
  30. package/dist/cli/daemon-start-options.js +38 -0
  31. package/dist/cli/device-flow.d.ts +76 -0
  32. package/dist/cli/device-flow.js +56 -0
  33. package/dist/cli/doctor-rows.d.ts +9 -0
  34. package/dist/cli/doctor-rows.js +9 -0
  35. package/dist/cli/drive-attach.d.ts +13 -0
  36. package/dist/cli/drive-attach.js +18 -0
  37. package/dist/cli/drive-command.js +7 -3
  38. package/dist/cli/mcp-command.js +51 -20
  39. package/dist/cli/project-name.d.ts +30 -0
  40. package/dist/cli/project-name.js +44 -0
  41. package/dist/cli/status-next-action.d.ts +14 -0
  42. package/dist/cli/status-next-action.js +4 -4
  43. package/dist/cli.d.ts +1 -0
  44. package/dist/cli.js +134 -80
  45. package/dist/cloud/cloud-config.d.ts +22 -1
  46. package/dist/cloud/cloud-config.js +64 -5
  47. package/dist/cloud/cloud-sync.d.ts +5 -5
  48. package/dist/cloud/cloud-sync.js +33 -2
  49. package/dist/cloud/sync-cycle.d.ts +110 -0
  50. package/dist/cloud/sync-cycle.js +270 -0
  51. package/dist/cloud/sync-daemon.d.ts +69 -0
  52. package/dist/cloud/sync-daemon.js +260 -0
  53. package/dist/cloud/sync-disk.d.ts +9 -0
  54. package/dist/cloud/sync-disk.js +191 -0
  55. package/dist/cloud/sync-hash.d.ts +2 -0
  56. package/dist/cloud/sync-hash.js +35 -0
  57. package/dist/crawl/crawl-tools.js +5 -3
  58. package/dist/crawl/crawl.d.ts +3 -0
  59. package/dist/crawl/crawl.js +5 -0
  60. package/dist/daemon/daemon-resolve.d.ts +43 -0
  61. package/dist/daemon/daemon-resolve.js +69 -0
  62. package/dist/daemon/dev-servers.d.ts +15 -0
  63. package/dist/daemon/dev-servers.js +68 -0
  64. package/dist/daemon/wake-decision.d.ts +32 -0
  65. package/dist/daemon/wake-decision.js +38 -0
  66. package/dist/domain/domain-tools.js +2 -2
  67. package/dist/events/contradictions.d.ts +18 -0
  68. package/dist/events/contradictions.js +134 -21
  69. package/dist/events/declared.d.ts +7 -4
  70. package/dist/events/declared.js +15 -6
  71. package/dist/events/echo-mismatch.js +64 -14
  72. package/dist/events/event-filters.d.ts +8 -0
  73. package/dist/events/event-filters.js +19 -2
  74. package/dist/events/predicate-eval.js +53 -5
  75. package/dist/events/predicate-precheck.d.ts +8 -0
  76. package/dist/events/predicate-precheck.js +41 -0
  77. package/dist/events/predicate-route.d.ts +14 -0
  78. package/dist/events/predicate-route.js +36 -8
  79. package/dist/events/predicate.d.ts +5 -0
  80. package/dist/events/predicate.js +37 -5
  81. package/dist/events/split-text-miss.d.ts +30 -0
  82. package/dist/events/split-text-miss.js +44 -0
  83. package/dist/flows/flake.d.ts +6 -6
  84. package/dist/flows/flow-memory-consult.d.ts +55 -0
  85. package/dist/flows/flow-memory-consult.js +38 -0
  86. package/dist/flows/flow-replay-run.d.ts +18 -7
  87. package/dist/flows/flow-replay-run.js +239 -22
  88. package/dist/flows/flow-replay.js +11 -2
  89. package/dist/flows/flow-step-runners.d.ts +8 -0
  90. package/dist/flows/flow-step-runners.js +8 -2
  91. package/dist/flows/flow-store-for-session.d.ts +31 -0
  92. package/dist/flows/flow-store-for-session.js +33 -0
  93. package/dist/flows/flow-tools.js +58 -32
  94. package/dist/flows/flows.d.ts +8 -0
  95. package/dist/flows/flows.js +468 -2
  96. package/dist/flows/heal-run.js +5 -2
  97. package/dist/flows/predicate-to-expect.d.ts +17 -12
  98. package/dist/flows/predicate-to-expect.js +30 -12
  99. package/dist/flows/recordings.d.ts +13 -1
  100. package/dist/flows/recordings.js +17 -2
  101. package/dist/flows/replay.d.ts +22 -2
  102. package/dist/flows/replay.js +28 -2
  103. package/dist/flows/verify-change-tools.js +5 -2
  104. package/dist/honesty/blind-spots.d.ts +55 -2
  105. package/dist/honesty/blind-spots.js +63 -3
  106. package/dist/honesty/honesty.d.ts +24 -0
  107. package/dist/honesty/honesty.js +25 -1
  108. package/dist/honesty/instrumentation-gaps.d.ts +9 -0
  109. package/dist/honesty/instrumentation-gaps.js +29 -2
  110. package/dist/honesty/verified.d.ts +2 -1
  111. package/dist/honesty/verified.js +16 -1
  112. package/dist/http-server.js +12 -0
  113. package/dist/impact/impact-recorder.d.ts +31 -8
  114. package/dist/impact/impact-recorder.js +104 -21
  115. package/dist/impact/impact-store.d.ts +15 -1
  116. package/dist/impact/impact-store.js +43 -2
  117. package/dist/index.js +113 -7
  118. package/dist/init/agent-rules.d.ts +13 -6
  119. package/dist/init/agent-rules.js +23 -12
  120. package/dist/init/astro-patch.d.ts +16 -0
  121. package/dist/init/astro-patch.js +28 -0
  122. package/dist/init/capabilities.d.ts +4 -1
  123. package/dist/init/capabilities.js +22 -2
  124. package/dist/init/confirm.d.ts +31 -7
  125. package/dist/init/confirm.js +48 -8
  126. package/dist/init/cra.d.ts +20 -4
  127. package/dist/init/cra.js +57 -17
  128. package/dist/init/csp-check.js +17 -2
  129. package/dist/init/csp-doctor.d.ts +8 -1
  130. package/dist/init/csp-doctor.js +8 -3
  131. package/dist/init/dev-script.d.ts +3 -1
  132. package/dist/init/dev-script.js +8 -5
  133. package/dist/init/format-generated.d.ts +18 -0
  134. package/dist/init/format-generated.js +39 -0
  135. package/dist/init/install-hint.d.ts +9 -0
  136. package/dist/init/install-hint.js +29 -0
  137. package/dist/init/mcp-clients.d.ts +7 -0
  138. package/dist/init/mcp-clients.js +7 -0
  139. package/dist/init/mcp.d.ts +1 -1
  140. package/dist/init/mcp.js +1 -1
  141. package/dist/init/node-io.js +11 -1
  142. package/dist/init/plan-framework.js +81 -45
  143. package/dist/init/plan.d.ts +20 -7
  144. package/dist/init/plan.js +20 -45
  145. package/dist/init/preflight.d.ts +28 -0
  146. package/dist/init/preflight.js +38 -0
  147. package/dist/init/run.d.ts +32 -9
  148. package/dist/init/run.js +141 -119
  149. package/dist/init/slash-command.d.ts +1 -1
  150. package/dist/init/snippets.js +41 -25
  151. package/dist/init/vite-config.d.ts +1 -1
  152. package/dist/init/vite-config.js +39 -25
  153. package/dist/init/workspace-apps.d.ts +17 -10
  154. package/dist/init/workspace-apps.js +110 -0
  155. package/dist/input/network-mock-tools.js +3 -6
  156. package/dist/input/real-input.js +25 -5
  157. package/dist/input/scroll-tools.js +4 -6
  158. package/dist/input/viewport-tools.js +5 -4
  159. package/dist/intent/inline-intent.d.ts +10 -19
  160. package/dist/intent/inline-intent.js +91 -1
  161. package/dist/intent/intent-shard-store.d.ts +72 -0
  162. package/dist/intent/intent-shard-store.js +203 -0
  163. package/dist/intent/intent-shard.d.ts +411 -0
  164. package/dist/intent/intent-shard.js +176 -0
  165. package/dist/intent/intent-store.d.ts +12 -0
  166. package/dist/intent/intent-store.js +21 -0
  167. package/dist/intent/intent-subject.d.ts +45 -0
  168. package/dist/intent/intent-subject.js +177 -0
  169. package/dist/intent/intent-tools.js +65 -2
  170. package/dist/intent/open-intents.d.ts +21 -0
  171. package/dist/intent/open-intents.js +15 -0
  172. package/dist/license/license-env.d.ts +29 -0
  173. package/dist/license/license-env.js +148 -0
  174. package/dist/loopback-agent.d.ts +31 -0
  175. package/dist/loopback-agent.js +57 -0
  176. package/dist/mcp/attach-memory.d.ts +18 -2
  177. package/dist/mcp/attach-memory.js +50 -7
  178. package/dist/mcp/client-identity.d.ts +31 -0
  179. package/dist/mcp/client-identity.js +51 -0
  180. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  181. package/dist/mcp/mcp-post-transport.js +109 -0
  182. package/dist/mcp/mcp-proxy.d.ts +2 -1
  183. package/dist/mcp/mcp-proxy.js +52 -62
  184. package/dist/mcp/mcp.js +7 -1
  185. package/dist/mcp/proxy-backoff.d.ts +11 -0
  186. package/dist/mcp/proxy-backoff.js +13 -0
  187. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  188. package/dist/mcp/proxy-daemon-probe.js +7 -1
  189. package/dist/mcp/proxy-handshake.d.ts +23 -0
  190. package/dist/mcp/proxy-handshake.js +31 -0
  191. package/dist/mcp/tool-catalog-cache.d.ts +8 -0
  192. package/dist/mcp/tool-catalog-cache.js +12 -0
  193. package/dist/memory/memory-tools.d.ts +2 -0
  194. package/dist/memory/memory-tools.js +82 -0
  195. package/dist/memory/project-memory.d.ts +34 -0
  196. package/dist/memory/project-memory.js +96 -0
  197. package/dist/oracles/propose-consequences.js +12 -3
  198. package/dist/pool/browser-pool.d.ts +63 -4
  199. package/dist/pool/browser-pool.js +113 -4
  200. package/dist/pool/playwright-launcher.js +9 -0
  201. package/dist/project/project-tools.js +3 -3
  202. package/dist/project/reticle-dir.d.ts +15 -2
  203. package/dist/project/reticle-dir.js +21 -4
  204. package/dist/runs/run-store.d.ts +10 -0
  205. package/dist/runs/run-store.js +10 -0
  206. package/dist/runs/verification-sync.d.ts +0 -11
  207. package/dist/runs/verification-sync.js +22 -1
  208. package/dist/runs/verify-server.d.ts +8 -1
  209. package/dist/runs/verify-server.js +14 -2
  210. package/dist/session/command-timeout.d.ts +5 -2
  211. package/dist/session/command-timeout.js +5 -4
  212. package/dist/session/lease-visibility.d.ts +67 -0
  213. package/dist/session/lease-visibility.js +76 -0
  214. package/dist/session/no-session-diagnosis.d.ts +23 -0
  215. package/dist/session/no-session-diagnosis.js +58 -17
  216. package/dist/session/no-session-next-action.d.ts +7 -0
  217. package/dist/session/no-session-next-action.js +42 -3
  218. package/dist/session/no-session-watch.d.ts +10 -6
  219. package/dist/session/no-session-watch.js +51 -19
  220. package/dist/session/pending-commands.d.ts +11 -1
  221. package/dist/session/pending-commands.js +12 -2
  222. package/dist/session/resolve-within.d.ts +34 -0
  223. package/dist/session/resolve-within.js +53 -0
  224. package/dist/session/session-info.d.ts +18 -0
  225. package/dist/session/session-info.js +11 -0
  226. package/dist/session/session-manager.d.ts +14 -0
  227. package/dist/session/session-manager.js +61 -3
  228. package/dist/session/session-mirror.d.ts +16 -0
  229. package/dist/session/session-mirror.js +22 -0
  230. package/dist/session/session-recommendation.d.ts +19 -5
  231. package/dist/session/session-recommendation.js +25 -9
  232. package/dist/session/session-successor.js +36 -1
  233. package/dist/session/session-tools.js +2 -2
  234. package/dist/session/session.d.ts +22 -2
  235. package/dist/session/session.js +87 -7
  236. package/dist/session/wall-clock.d.ts +12 -0
  237. package/dist/session/wall-clock.js +14 -0
  238. package/dist/setup/agent-configs.d.ts +96 -0
  239. package/dist/setup/agent-configs.js +333 -0
  240. package/dist/setup/agent-io.d.ts +9 -0
  241. package/dist/setup/agent-io.js +16 -0
  242. package/dist/setup/agent-writer.d.ts +51 -0
  243. package/dist/setup/agent-writer.js +115 -0
  244. package/dist/setup/approval-migration.d.ts +47 -0
  245. package/dist/setup/approval-migration.js +68 -0
  246. package/dist/setup/auto-approve.d.ts +85 -0
  247. package/dist/setup/auto-approve.js +150 -0
  248. package/dist/setup/bridge-port.d.ts +15 -0
  249. package/dist/setup/bridge-port.js +24 -0
  250. package/dist/setup/desktop-shape.d.ts +57 -0
  251. package/dist/setup/desktop-shape.js +63 -0
  252. package/dist/setup/dev-server-wait.d.ts +68 -0
  253. package/dist/setup/dev-server-wait.js +67 -0
  254. package/dist/setup/drive-agent.d.ts +71 -0
  255. package/dist/setup/drive-agent.js +171 -0
  256. package/dist/setup/drive-plan.d.ts +68 -0
  257. package/dist/setup/drive-plan.js +130 -0
  258. package/dist/setup/ensure-daemon.d.ts +29 -0
  259. package/dist/setup/ensure-daemon.js +58 -0
  260. package/dist/setup/init-runtime.d.ts +47 -0
  261. package/dist/setup/init-runtime.js +160 -0
  262. package/dist/setup/interrupt.d.ts +25 -0
  263. package/dist/setup/interrupt.js +40 -0
  264. package/dist/setup/license-key.d.ts +39 -0
  265. package/dist/setup/license-key.js +72 -0
  266. package/dist/setup/listeners.d.ts +37 -0
  267. package/dist/setup/listeners.js +85 -0
  268. package/dist/setup/node-effects.d.ts +60 -0
  269. package/dist/setup/node-effects.js +210 -0
  270. package/dist/setup/page-probe.d.ts +39 -0
  271. package/dist/setup/page-probe.js +49 -0
  272. package/dist/setup/relaunch.d.ts +36 -0
  273. package/dist/setup/relaunch.js +57 -0
  274. package/dist/setup/remaining-steps.d.ts +19 -0
  275. package/dist/setup/remaining-steps.js +31 -0
  276. package/dist/setup/run-setup.d.ts +99 -0
  277. package/dist/setup/run-setup.js +203 -0
  278. package/dist/setup/session-pick.d.ts +47 -0
  279. package/dist/setup/session-pick.js +51 -0
  280. package/dist/setup/setup-command.d.ts +45 -0
  281. package/dist/setup/setup-command.js +199 -0
  282. package/dist/setup/setup-options.d.ts +74 -0
  283. package/dist/setup/setup-options.js +67 -0
  284. package/dist/setup/transcripts.d.ts +16 -0
  285. package/dist/setup/transcripts.js +43 -0
  286. package/dist/status-payload.d.ts +14 -1
  287. package/dist/status-payload.js +23 -1
  288. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  289. package/dist/telemetry/daemon-telemetry.js +52 -5
  290. package/dist/telemetry/error-class.js +1 -1
  291. package/dist/telemetry/feedback-context.d.ts +9 -1
  292. package/dist/telemetry/feedback-context.js +60 -29
  293. package/dist/telemetry/feedback-local.d.ts +15 -0
  294. package/dist/telemetry/feedback-local.js +56 -0
  295. package/dist/telemetry/feedback.d.ts +11 -0
  296. package/dist/telemetry/feedback.js +19 -6
  297. package/dist/telemetry/init-telemetry.d.ts +2 -0
  298. package/dist/telemetry/init-telemetry.js +2 -0
  299. package/dist/telemetry/install-source.d.ts +14 -0
  300. package/dist/telemetry/install-source.js +30 -0
  301. package/dist/telemetry/license-activation.d.ts +7 -1
  302. package/dist/telemetry/license-activation.js +9 -5
  303. package/dist/telemetry/mcp-connection.js +7 -0
  304. package/dist/telemetry/project-profile.d.ts +12 -1
  305. package/dist/telemetry/project-profile.js +11 -6
  306. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  307. package/dist/telemetry/proxy-telemetry.js +21 -0
  308. package/dist/telemetry/session-metrics.d.ts +12 -0
  309. package/dist/telemetry/session-metrics.js +35 -1
  310. package/dist/telemetry/telemetry.js +17 -2
  311. package/dist/telemetry/tool-refused.d.ts +3 -1
  312. package/dist/telemetry/tool-refused.js +34 -2
  313. package/dist/tools/act-capsule.d.ts +10 -0
  314. package/dist/tools/act-capsule.js +8 -2
  315. package/dist/tools/act-danger.d.ts +2 -1
  316. package/dist/tools/act-danger.js +11 -3
  317. package/dist/tools/act-observation.d.ts +41 -0
  318. package/dist/tools/act-observation.js +36 -0
  319. package/dist/tools/act-preflight.d.ts +20 -0
  320. package/dist/tools/act-preflight.js +98 -0
  321. package/dist/tools/act-sequence-retry.d.ts +45 -0
  322. package/dist/tools/act-sequence-retry.js +58 -0
  323. package/dist/tools/act-sequence-tool.d.ts +13 -0
  324. package/dist/tools/act-sequence-tool.js +135 -0
  325. package/dist/tools/act-target.d.ts +14 -0
  326. package/dist/tools/act-target.js +47 -0
  327. package/dist/tools/act-tools.d.ts +21 -1
  328. package/dist/tools/act-tools.js +100 -213
  329. package/dist/tools/assert-verdict.js +22 -16
  330. package/dist/tools/browser-tools.js +6 -2
  331. package/dist/tools/dynamic-tools.js +24 -5
  332. package/dist/tools/error-recovery.d.ts +1 -0
  333. package/dist/tools/error-recovery.js +8 -0
  334. package/dist/tools/invoke-tool.js +59 -9
  335. package/dist/tools/lease-tools.d.ts +30 -4
  336. package/dist/tools/lease-tools.js +169 -5
  337. package/dist/tools/merged-name-redirect.d.ts +13 -0
  338. package/dist/tools/merged-name-redirect.js +22 -0
  339. package/dist/tools/numeric-bounds.d.ts +100 -0
  340. package/dist/tools/numeric-bounds.js +125 -0
  341. package/dist/tools/observe-tools.js +47 -40
  342. package/dist/tools/read-tools.js +28 -7
  343. package/dist/tools/real-input-attempt.d.ts +10 -0
  344. package/dist/tools/real-input-attempt.js +59 -1
  345. package/dist/tools/reconcile-tools.js +4 -4
  346. package/dist/tools/tool-kit.d.ts +12 -0
  347. package/dist/tools/tool-kit.js +6 -1
  348. package/dist/tools/tool-names.d.ts +2 -0
  349. package/dist/tools/tool-names.js +2 -0
  350. package/dist/tools/tools-helpers.d.ts +19 -0
  351. package/dist/tools/tools-helpers.js +46 -0
  352. package/dist/tools/tools.d.ts +2 -0
  353. package/dist/tools/tools.js +56 -8
  354. package/dist/update/reticle-deps.d.ts +9 -0
  355. package/dist/update/reticle-deps.js +27 -0
  356. package/dist/update/sdk-sync.d.ts +1 -2
  357. package/dist/update/sdk-sync.js +1 -21
  358. package/dist/update/update-nudge.d.ts +18 -0
  359. package/dist/update/update-nudge.js +20 -0
  360. package/dist/version/peer-announce.js +4 -2
  361. package/dist/version/sdk-fix.d.ts +47 -0
  362. package/dist/version/sdk-fix.js +127 -0
  363. package/dist/version/version-nudge.d.ts +14 -0
  364. package/dist/version/version-nudge.js +21 -0
  365. package/dist/version/version-skew.d.ts +38 -3
  366. package/dist/version/version-skew.js +83 -7
  367. package/dist/visual/visual-store.d.ts +6 -6
  368. package/dist/visual/visual-store.js +18 -16
  369. package/dist/visual/visual-tools.js +57 -12
  370. package/docs/cli/doctor.mdx +1 -0
  371. package/docs/cli/serve.mdx +4 -2
  372. package/docs/cli.mdx +7 -3
  373. package/docs/desktop.mdx +1 -1
  374. package/docs/docs.json +1 -1
  375. package/docs/getting-started.md +19 -3
  376. package/docs/index.mdx +9 -6
  377. package/docs/install-agentic.mdx +38 -12
  378. package/docs/install-manual.mdx +1 -1
  379. package/docs/packages.mdx +1 -1
  380. package/docs/skill-file.mdx +3 -3
  381. package/docs/telemetry-contract.md +108 -4
  382. package/docs/tools-act-sequence.mdx +16 -0
  383. package/docs/usage.md +1 -1
  384. package/package.json +4 -3
  385. package/dist/init/cursor.d.ts +0 -24
  386. package/dist/init/cursor.js +0 -80
@@ -4,6 +4,7 @@ import { GapLedger } from '../honesty/gap-ledger.js';
4
4
  import { CaptureLedger } from '../honesty/feature-capture.js';
5
5
  import { commandTimeoutMessage } from './command-timeout.js';
6
6
  import { readHealthEvent } from './session-health.js';
7
+ import { MIRRORED_COMMANDS, mirroredNarration } from './session-mirror.js';
7
8
  /** The HUD does not need 200 frames to watch a counter climb; the last state always lands. */
8
9
  const IMPACT_PUSH_DEBOUNCE_MS = 700;
9
10
  import { PendingCommands, CommandTimeoutError } from './pending-commands.js';
@@ -52,6 +53,12 @@ const REBINDABLE_COMMANDS = new Set([
52
53
  ]);
53
54
  /** Prefix on minted action ids (a1, a2, …) — the journal's action identity, independent of commands. */
54
55
  const ACTION_ID_PREFIX = 'a';
56
+ /**
57
+ * How many consecutive unanswered commands make a tab unresponsive. One is a page that was merely
58
+ * busy for its budget; two in a row with no reply between them is a wedge, and the cost of being
59
+ * wrong about that is one extra browser tab.
60
+ */
61
+ const UNRESPONSIVE_AFTER_TIMEOUTS = 2;
55
62
  /** ws readyState for an OPEN socket — guard fire-and-forget pushes against a closing tab. */
56
63
  const WS_OPEN = 1;
57
64
  /**
@@ -62,6 +69,17 @@ export class Session {
62
69
  id;
63
70
  /** Stable build-stamped project identity; undefined for v1.0 SDKs that omit it. */
64
71
  projectId;
72
+ /**
73
+ * The `.reticle` directory this session's evidence belongs in — impact counters AND capsules.
74
+ *
75
+ * Stamped once when the session is created, from the project it declared in HELLO, rather than
76
+ * resolved per call: the answer cannot change while the tab is connected, and resolving it once
77
+ * means every counter that fires for this session agrees about where it goes.
78
+ *
79
+ * Undefined when nothing could name a project — the caller then falls back to the daemon's own
80
+ * root, which is what every counter did unconditionally before this existed.
81
+ */
82
+ artifactRoot;
65
83
  url;
66
84
  title;
67
85
  adapters;
@@ -82,6 +100,8 @@ export class Session {
82
100
  #listeners = new Set();
83
101
  #disconnectListeners = new Set();
84
102
  #actionSeq = 0;
103
+ /** Consecutive missed command budgets — free liveness evidence. Any reply resets it. */
104
+ #unansweredCommands = 0;
85
105
  #lastSeenAt;
86
106
  #hidden = false;
87
107
  /** Which shell the page reported (PAGE_HEALTH). Undefined until the first report lands. */
@@ -204,6 +224,9 @@ export class Session {
204
224
  hidden: this.#hidden,
205
225
  throttled: base.throttled,
206
226
  focused: base.focused,
227
+ // Without this the desktop branch is unreachable in production: every unit test would pass and
228
+ // every real Electron window would still be told to open a browser.
229
+ runtime: this.#runtime,
207
230
  });
208
231
  return recommendation === undefined ? base : { ...base, recommendation };
209
232
  }
@@ -215,13 +238,24 @@ export class Session {
215
238
  title: this.title,
216
239
  adapters: this.adapters,
217
240
  hasCapabilities: this.hasCapabilities,
241
+ runtime: this.#runtime,
218
242
  versionSkew: this.versionSkew,
219
243
  hidden: this.#hidden,
220
244
  health: () => this.health(),
221
245
  staleMs: () => this.staleMs(),
222
246
  pendingMarkCount: () => this.#review.pendingCount(),
247
+ unresponsive: () => this.unresponsive(),
223
248
  });
224
249
  }
250
+ /**
251
+ * Attached but not answering. A tab can be `connected` with a fresh `lastSeenMs` and still answer
252
+ * nothing: `lastSeenMs` measures the events the SDK streams, not the commands it replies to. Reuse
253
+ * and auto-selection read that as health, so `reticle open` handed a wedged tab back forever and
254
+ * `reticle_session{action:"end"}` — a flag on the record, not on the page — could not help.
255
+ */
256
+ unresponsive() {
257
+ return this.#unansweredCommands >= UNRESPONSIVE_AFTER_TIMEOUTS;
258
+ }
225
259
  /** Wall-clock age of the session in milliseconds. */
226
260
  staleMs() {
227
261
  return this.#clock() - this.#startedAt;
@@ -264,7 +298,7 @@ export class Session {
264
298
  this.#review.add(parsed.data, this.elapsed());
265
299
  // The impact record's `marks` field existed and nothing ever wrote it: the report would
266
300
  // have shown a permanent zero next to a page covered in pins.
267
- recordImpact({ marks: 1 });
301
+ recordImpact({ marks: 1 }, {}, this.artifactRoot);
268
302
  }
269
303
  }
270
304
  if (event.type === EventType.ROUTE_CHANGE) {
@@ -551,6 +585,8 @@ export class Session {
551
585
  // the app taking its time — the split telemetry reports in aggregate, made visible per call.
552
586
  return span('browser.command', { command: name, sessionId: this.id }, () => awaited)
553
587
  .then((result) => {
588
+ // Answered — whatever the reply said, the command channel is alive.
589
+ this.#unansweredCommands = 0;
554
590
  // The label is only knowable from the REPLY — the request carries a ref, and a ref dies with
555
591
  // the next re-render. Recorded here so coverage survives frameworks that replace nodes.
556
592
  if (name === ReticleCommand.ACT)
@@ -566,6 +602,8 @@ export class Session {
566
602
  // simply not been answered in budget was read as "you were replaced, ask again" — and
567
603
  // against a page re-dialling in a loop that turned one read into hundreds of commands on the
568
604
  // wire, minutes of wall clock, and an answer still quoting the budget it had blown.
605
+ if (error instanceof CommandTimeoutError)
606
+ this.#unansweredCommands += 1;
569
607
  const next = error instanceof CommandTimeoutError ? undefined : this.#liveSuccessor();
570
608
  if (next === undefined || !REBINDABLE_COMMANDS.has(name))
571
609
  throw error;
@@ -580,11 +618,14 @@ export class Session {
580
618
  });
581
619
  }
582
620
  handleResult(result) {
621
+ // Any reply at all clears the wedge, including one for a command that already timed out: it is
622
+ // still proof that bridge→page→bridge works right now, which is the only thing being claimed.
623
+ this.#unansweredCommands = 0;
583
624
  this.#pending.settle(result);
584
625
  }
585
626
  /** Reject everything still in flight — used on disconnect. */
586
- rejectAll(reason) {
587
- this.#pending.rejectAll(reason);
627
+ rejectAll(reason, replaced = false) {
628
+ this.#pending.rejectAll(reason, replaced);
588
629
  for (const listener of this.#disconnectListeners)
589
630
  listener();
590
631
  this.#disconnectListeners.clear();
@@ -631,11 +672,11 @@ export class Session {
631
672
  return undefined;
632
673
  }
633
674
  /** End this transport without letting a stale socket remove its replacement session. */
634
- disconnect(reason) {
675
+ disconnect(reason, replaced = false) {
635
676
  // "Longest run" means the longest SESSION, and it used to be fed a single tool call's duration
636
677
  // - so the report's superlative was a number in milliseconds that never grew past a click.
637
- recordImpact({}, { runMs: this.elapsed() });
638
- this.rejectAll(reason);
678
+ recordImpact({}, { runMs: this.elapsed() }, this.artifactRoot);
679
+ this.rejectAll(reason, replaced);
639
680
  try {
640
681
  this.#socket.close(1008, reason);
641
682
  }
@@ -764,8 +805,47 @@ export class Session {
764
805
  const minutes = Math.floor(ageMs / 60_000);
765
806
  return `Session ${this.id} has been open for ${String(minutes)} minutes. If your task is complete, call reticle_session{action:"end"} now.`;
766
807
  }
767
- /** Fire-and-forget command send — NOT registered in #pending (no correlated result expected). */
808
+ /**
809
+ * The tabs that should SEE what this session is told, supplied by the SessionManager.
810
+ *
811
+ * `reticle drive` attaches to the running daemon and is handed a pooled HEADLESS context, so the
812
+ * whole HUD feed — the narration, the counters, the bug that was just raised — went to a browser
813
+ * with nobody in front of it, while the developer's own tab showed nothing. The feed is now a
814
+ * per-project broadcast: the driven tab is still the only one DRIVEN, and any other tab of the
815
+ * same app is a viewer.
816
+ */
817
+ #viewers;
818
+ /** Wired by SessionManager.add, the one place every session registers. */
819
+ setViewers(read) {
820
+ this.#viewers = read;
821
+ }
822
+ /**
823
+ * Fire-and-forget command send — NOT registered in #pending (no correlated result expected).
824
+ *
825
+ * Mirrored to this project's other tabs, for the two commands that are a REPORT of what happened
826
+ * (narration, impact). PRESENTER is deliberately not among them: it is the glow and the lifecycle
827
+ * that say "the agent is driving THIS tab", and a viewer that showed it would be claiming
828
+ * something untrue about itself.
829
+ */
768
830
  #post(name, args) {
831
+ this.#send(name, args);
832
+ if (!MIRRORED_COMMANDS.has(name))
833
+ return;
834
+ for (const viewer of this.#viewers?.() ?? []) {
835
+ if (viewer === this)
836
+ continue;
837
+ // Straight to the raw send, so a mirrored row is never mirrored again.
838
+ viewer.#send(name, name === ReticleCommand.NARRATE ? this.#labelled(args) : args);
839
+ }
840
+ }
841
+ /** A narration row re-addressed to a tab that is watching rather than being driven. */
842
+ #labelled(args) {
843
+ const text = args['text'];
844
+ if ('string' !== typeof text)
845
+ return args;
846
+ return { ...args, text: mirroredNarration(this.id, text) };
847
+ }
848
+ #send(name, args) {
769
849
  if (this.#socket.readyState !== WS_OPEN)
770
850
  return;
771
851
  // Shares the same counter as tracked commands, so a fire-and-forget id can never collide with
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The real clock, named once.
3
+ *
4
+ * The logic that needs a clock takes one as an argument so a test can drive it without waiting
5
+ * (CLAUDE.md rule 7). That leaves the production callers each constructing the same pair of
6
+ * closures, which is how two of them end up with different sleep implementations. One export, one
7
+ * place to look.
8
+ */
9
+ export declare const WALL_CLOCK: {
10
+ now: () => number;
11
+ sleep: (ms: number) => Promise<void>;
12
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The real clock, named once.
3
+ *
4
+ * The logic that needs a clock takes one as an argument so a test can drive it without waiting
5
+ * (CLAUDE.md rule 7). That leaves the production callers each constructing the same pair of
6
+ * closures, which is how two of them end up with different sleep implementations. One export, one
7
+ * place to look.
8
+ */
9
+ export const WALL_CLOCK = {
10
+ now: () => Date.now(),
11
+ sleep: (ms) => new Promise((resolve) => {
12
+ setTimeout(resolve, ms).unref?.();
13
+ }),
14
+ };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The coding agents `init`'s own MCP registration does not reach, and where their config lives.
3
+ *
4
+ * `init` covers eight clients and only where it already finds them. That leaves real holes: VS
5
+ * Code's USER-scope mcp.json exists on machines today and init only ever writes the project-scope
6
+ * one, so a VS Code user has no tools outside the directory they ran init in. Zed, Warp, Kiro, Amp,
7
+ * Copilot CLI, Amazon Q, Factory Droid, Cline and Roo are not covered at all.
8
+ *
9
+ * Three rules, and the second is what keeps this honest:
10
+ *
11
+ * 1. A path with OFFICIAL documentation is written even when the agent is absent, so a later
12
+ * install finds itself already wired.
13
+ * 2. A path we cannot evidence is REFUSED. Cline's and Roo's live under VS Code globalStorage,
14
+ * which moves under Insiders, portable installs and a custom --user-data-dir; they are written
15
+ * only where the host's standard layout is present. A config file at a guessed location is one
16
+ * nobody reads, which is indistinguishable from success.
17
+ * 3. A format we cannot merge safely is never rewritten. TOML, an existing YAML, JSONC carrying
18
+ * comments: we print the snippet instead. Reformatting somebody's config to add one entry is
19
+ * not ours to do.
20
+ */
21
+ export declare const AgentConfidence: {
22
+ /** Documented by the vendor. Safe to create before the agent exists. */
23
+ readonly OFFICIAL: "official";
24
+ /** Community-sourced. Only written where the host's own layout proves the path. */
25
+ readonly COMMUNITY: "community";
26
+ };
27
+ export type AgentConfidence = (typeof AgentConfidence)[keyof typeof AgentConfidence];
28
+ export declare const AgentAction: {
29
+ readonly CREATE: "create";
30
+ readonly CREATE_YAML: "create-yaml";
31
+ readonly MERGE: "merge";
32
+ readonly ALREADY: "already";
33
+ readonly MANUAL: "manual";
34
+ readonly SKIP: "skip";
35
+ };
36
+ export type AgentAction = (typeof AgentAction)[keyof typeof AgentAction];
37
+ /** Per-platform relative paths, from the user's home directory. */
38
+ export interface PlatformPaths {
39
+ readonly darwin: string;
40
+ readonly linux: string;
41
+ readonly win32: string;
42
+ }
43
+ export interface AgentClient {
44
+ readonly id: string;
45
+ readonly name: string;
46
+ readonly confidence: AgentConfidence;
47
+ /** `json` merges; `yaml-manual` is only ever created fresh, never edited. */
48
+ readonly format: 'json' | 'yaml-manual';
49
+ /** Where the server map lives. Getting this wrong writes a file the client silently ignores. */
50
+ readonly key: string;
51
+ readonly entry?: Readonly<Record<string, unknown>> | undefined;
52
+ readonly paths: PlatformPaths;
53
+ /** Existence proves the agent is installed. */
54
+ readonly marker: PlatformPaths;
55
+ /** The HOST's directory, when the marker belongs to an extension rather than the app. */
56
+ readonly parentMarker?: PlatformPaths | undefined;
57
+ /** Where this agent loads reusable skills from, when it has such a place. */
58
+ readonly skillDir?: string | undefined;
59
+ }
60
+ export declare const AGENT_CLIENTS: readonly AgentClient[];
61
+ /** What to do for one client, decided without touching the disk. */
62
+ export interface AgentPlanStep {
63
+ readonly id: string;
64
+ readonly name: string;
65
+ readonly file: string;
66
+ readonly action: AgentAction;
67
+ readonly key?: string | undefined;
68
+ readonly entry?: Readonly<Record<string, unknown>> | undefined;
69
+ readonly why: string;
70
+ }
71
+ export interface AgentPlanInput {
72
+ readonly home: string;
73
+ readonly platform: keyof PlatformPaths;
74
+ readonly exists: (path: string) => boolean;
75
+ readonly readFile: (path: string) => string;
76
+ }
77
+ /**
78
+ * Join for the platform the CALLER named, never the one this process happens to run on.
79
+ *
80
+ * Every planner here takes `platform` as an argument and claims to be pure over it — the doc below
81
+ * says the Windows rows are "testable from any machine". `node:path`'s bare `join` broke that claim
82
+ * silently in both directions: a mac planning win32 rows emitted `/` separators into paths Windows
83
+ * cannot use, and a Windows host planning linux rows emitted `\`. The second half is what turned
84
+ * the whole Windows CI job red — the pure planners returned `\home\u\.claude\settings.json`,
85
+ * every injected filesystem is keyed by the POSIX path the caller asked for, so every lookup missed
86
+ * and every row came back `absent`/`skip`. Twenty-eight tests failed for a reason that had nothing
87
+ * to do with what they were testing, which left the platform with the most users unwatched.
88
+ *
89
+ * A separator is part of the answer, so it belongs to the platform being planned for.
90
+ */
91
+ export declare const joinFor: (os: keyof PlatformPaths) => ((...parts: string[]) => string);
92
+ /**
93
+ * Decide what to do for every client. Pure: the filesystem arrives as two functions, which is what
94
+ * makes the Windows rows testable from any machine.
95
+ */
96
+ export declare function planAgentConfigs(input: AgentPlanInput): AgentPlanStep[];
@@ -0,0 +1,333 @@
1
+ /**
2
+ * The coding agents `init`'s own MCP registration does not reach, and where their config lives.
3
+ *
4
+ * `init` covers eight clients and only where it already finds them. That leaves real holes: VS
5
+ * Code's USER-scope mcp.json exists on machines today and init only ever writes the project-scope
6
+ * one, so a VS Code user has no tools outside the directory they ran init in. Zed, Warp, Kiro, Amp,
7
+ * Copilot CLI, Amazon Q, Factory Droid, Cline and Roo are not covered at all.
8
+ *
9
+ * Three rules, and the second is what keeps this honest:
10
+ *
11
+ * 1. A path with OFFICIAL documentation is written even when the agent is absent, so a later
12
+ * install finds itself already wired.
13
+ * 2. A path we cannot evidence is REFUSED. Cline's and Roo's live under VS Code globalStorage,
14
+ * which moves under Insiders, portable installs and a custom --user-data-dir; they are written
15
+ * only where the host's standard layout is present. A config file at a guessed location is one
16
+ * nobody reads, which is indistinguishable from success.
17
+ * 3. A format we cannot merge safely is never rewritten. TOML, an existing YAML, JSONC carrying
18
+ * comments: we print the snippet instead. Reformatting somebody's config to add one entry is
19
+ * not ours to do.
20
+ */
21
+ import { posix, win32 } from 'node:path';
22
+ export const AgentConfidence = {
23
+ /** Documented by the vendor. Safe to create before the agent exists. */
24
+ OFFICIAL: 'official',
25
+ /** Community-sourced. Only written where the host's own layout proves the path. */
26
+ COMMUNITY: 'community',
27
+ };
28
+ export const AgentAction = {
29
+ CREATE: 'create',
30
+ CREATE_YAML: 'create-yaml',
31
+ MERGE: 'merge',
32
+ ALREADY: 'already',
33
+ MANUAL: 'manual',
34
+ SKIP: 'skip',
35
+ };
36
+ const NPX = 'npx';
37
+ const SERVER_PACKAGE = '@reticlehq/server';
38
+ const MCP_SUBCOMMAND = 'mcp';
39
+ const RETICLE_KEY = 'reticle';
40
+ /** The stdio entry, in the shape most clients expect. */
41
+ const STDIO = { command: NPX, args: [SERVER_PACKAGE, MCP_SUBCOMMAND] };
42
+ const VSCODE_USER = {
43
+ darwin: 'Library/Application Support/Code/User',
44
+ linux: '.config/Code/User',
45
+ win32: 'AppData/Roaming/Code/User',
46
+ };
47
+ export const AGENT_CLIENTS = [
48
+ {
49
+ id: 'vscode-user',
50
+ name: 'VS Code (user scope)',
51
+ confidence: AgentConfidence.OFFICIAL,
52
+ format: 'json',
53
+ key: 'servers',
54
+ entry: { type: 'stdio', ...STDIO },
55
+ paths: {
56
+ darwin: `${VSCODE_USER.darwin}/mcp.json`,
57
+ linux: `${VSCODE_USER.linux}/mcp.json`,
58
+ win32: `${VSCODE_USER.win32}/mcp.json`,
59
+ },
60
+ marker: VSCODE_USER,
61
+ },
62
+ {
63
+ id: 'zed',
64
+ name: 'Zed',
65
+ confidence: AgentConfidence.OFFICIAL,
66
+ format: 'json',
67
+ // NOT `mcpServers`. Zed calls them context servers, and the wrong key writes a file it ignores.
68
+ key: 'context_servers',
69
+ entry: { ...STDIO, env: {} },
70
+ paths: {
71
+ darwin: '.config/zed/settings.json',
72
+ linux: '.config/zed/settings.json',
73
+ win32: 'AppData/Roaming/Zed/settings.json',
74
+ },
75
+ marker: { darwin: '.config/zed', linux: '.config/zed', win32: 'AppData/Roaming/Zed' },
76
+ skillDir: '.agents/skills/reticle',
77
+ },
78
+ {
79
+ id: 'copilot-cli',
80
+ name: 'GitHub Copilot CLI',
81
+ confidence: AgentConfidence.OFFICIAL,
82
+ format: 'json',
83
+ key: 'mcpServers',
84
+ entry: STDIO,
85
+ paths: {
86
+ darwin: '.copilot/mcp-config.json',
87
+ linux: '.copilot/mcp-config.json',
88
+ win32: '.copilot/mcp-config.json',
89
+ },
90
+ marker: { darwin: '.copilot', linux: '.copilot', win32: '.copilot' },
91
+ },
92
+ {
93
+ id: 'warp',
94
+ name: 'Warp',
95
+ confidence: AgentConfidence.OFFICIAL,
96
+ format: 'json',
97
+ key: 'mcpServers',
98
+ entry: STDIO,
99
+ paths: { darwin: '.warp/.mcp.json', linux: '.warp/.mcp.json', win32: '.warp/.mcp.json' },
100
+ marker: { darwin: '.warp', linux: '.warp', win32: '.warp' },
101
+ },
102
+ {
103
+ id: 'factory-droid',
104
+ name: 'Factory Droid',
105
+ confidence: AgentConfidence.OFFICIAL,
106
+ format: 'json',
107
+ key: 'mcpServers',
108
+ entry: { type: 'stdio', ...STDIO },
109
+ paths: { darwin: '.factory/mcp.json', linux: '.factory/mcp.json', win32: '.factory/mcp.json' },
110
+ marker: { darwin: '.factory', linux: '.factory', win32: '.factory' },
111
+ },
112
+ {
113
+ id: 'kiro',
114
+ name: 'Kiro',
115
+ confidence: AgentConfidence.OFFICIAL,
116
+ format: 'json',
117
+ key: 'mcpServers',
118
+ entry: STDIO,
119
+ paths: {
120
+ darwin: '.kiro/settings/mcp.json',
121
+ linux: '.kiro/settings/mcp.json',
122
+ win32: '.kiro/settings/mcp.json',
123
+ },
124
+ marker: { darwin: '.kiro', linux: '.kiro', win32: '.kiro' },
125
+ skillDir: '.kiro/steering',
126
+ },
127
+ {
128
+ id: 'amazon-q',
129
+ name: 'Amazon Q Developer CLI',
130
+ confidence: AgentConfidence.OFFICIAL,
131
+ format: 'json',
132
+ key: 'mcpServers',
133
+ entry: STDIO,
134
+ paths: {
135
+ darwin: '.aws/amazonq/mcp.json',
136
+ linux: '.aws/amazonq/mcp.json',
137
+ win32: '.aws/amazonq/mcp.json',
138
+ },
139
+ marker: { darwin: '.aws/amazonq', linux: '.aws/amazonq', win32: '.aws/amazonq' },
140
+ },
141
+ {
142
+ id: 'cline-cli',
143
+ name: 'Cline CLI',
144
+ confidence: AgentConfidence.OFFICIAL,
145
+ format: 'json',
146
+ key: 'mcpServers',
147
+ entry: STDIO,
148
+ paths: { darwin: '.cline/mcp.json', linux: '.cline/mcp.json', win32: '.cline/mcp.json' },
149
+ marker: { darwin: '.cline', linux: '.cline', win32: '.cline' },
150
+ },
151
+ {
152
+ id: 'amp',
153
+ name: 'Amp',
154
+ confidence: AgentConfidence.OFFICIAL,
155
+ format: 'json',
156
+ // A DOTTED key at the TOP level, not nested under "amp". Nesting writes a file Amp ignores.
157
+ key: 'amp.mcpServers',
158
+ entry: STDIO,
159
+ paths: {
160
+ darwin: '.config/amp/settings.json',
161
+ linux: '.config/amp/settings.json',
162
+ win32: '.config/amp/settings.json',
163
+ },
164
+ marker: { darwin: '.config/amp', linux: '.config/amp', win32: '.config/amp' },
165
+ },
166
+ {
167
+ id: 'cline',
168
+ name: 'Cline (VS Code extension)',
169
+ confidence: AgentConfidence.COMMUNITY,
170
+ format: 'json',
171
+ key: 'mcpServers',
172
+ entry: STDIO,
173
+ paths: {
174
+ darwin: `${VSCODE_USER.darwin}/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`,
175
+ linux: `${VSCODE_USER.linux}/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`,
176
+ win32: `${VSCODE_USER.win32}/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`,
177
+ },
178
+ marker: {
179
+ darwin: `${VSCODE_USER.darwin}/globalStorage/saoudrizwan.claude-dev`,
180
+ linux: `${VSCODE_USER.linux}/globalStorage/saoudrizwan.claude-dev`,
181
+ win32: `${VSCODE_USER.win32}/globalStorage/saoudrizwan.claude-dev`,
182
+ },
183
+ parentMarker: VSCODE_USER,
184
+ },
185
+ {
186
+ id: 'roo-code',
187
+ name: 'Roo Code',
188
+ confidence: AgentConfidence.COMMUNITY,
189
+ format: 'json',
190
+ key: 'mcpServers',
191
+ entry: STDIO,
192
+ paths: {
193
+ darwin: `${VSCODE_USER.darwin}/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`,
194
+ linux: `${VSCODE_USER.linux}/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`,
195
+ win32: `${VSCODE_USER.win32}/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`,
196
+ },
197
+ marker: {
198
+ darwin: `${VSCODE_USER.darwin}/globalStorage/rooveterinaryinc.roo-cline`,
199
+ linux: `${VSCODE_USER.linux}/globalStorage/rooveterinaryinc.roo-cline`,
200
+ win32: `${VSCODE_USER.win32}/globalStorage/rooveterinaryinc.roo-cline`,
201
+ },
202
+ parentMarker: VSCODE_USER,
203
+ },
204
+ {
205
+ id: 'continue',
206
+ name: 'Continue',
207
+ confidence: AgentConfidence.OFFICIAL,
208
+ // YAML, whose `mcpServers` is a LIST of named entries. We create one; we never rewrite one.
209
+ format: 'yaml-manual',
210
+ key: 'mcpServers',
211
+ paths: {
212
+ darwin: '.continue/config.yaml',
213
+ linux: '.continue/config.yaml',
214
+ win32: '.continue/config.yaml',
215
+ },
216
+ marker: { darwin: '.continue', linux: '.continue', win32: '.continue' },
217
+ },
218
+ ];
219
+ const forPlatform = (p, os) => p[os];
220
+ /**
221
+ * Join for the platform the CALLER named, never the one this process happens to run on.
222
+ *
223
+ * Every planner here takes `platform` as an argument and claims to be pure over it — the doc below
224
+ * says the Windows rows are "testable from any machine". `node:path`'s bare `join` broke that claim
225
+ * silently in both directions: a mac planning win32 rows emitted `/` separators into paths Windows
226
+ * cannot use, and a Windows host planning linux rows emitted `\`. The second half is what turned
227
+ * the whole Windows CI job red — the pure planners returned `\home\u\.claude\settings.json`,
228
+ * every injected filesystem is keyed by the POSIX path the caller asked for, so every lookup missed
229
+ * and every row came back `absent`/`skip`. Twenty-eight tests failed for a reason that had nothing
230
+ * to do with what they were testing, which left the platform with the most users unwatched.
231
+ *
232
+ * A separator is part of the answer, so it belongs to the platform being planned for.
233
+ */
234
+ export const joinFor = (os) => 'win32' === os ? win32.join : posix.join;
235
+ /**
236
+ * Decide what to do for every client. Pure: the filesystem arrives as two functions, which is what
237
+ * makes the Windows rows testable from any machine.
238
+ */
239
+ export function planAgentConfigs(input) {
240
+ const { home, platform, exists, readFile } = input;
241
+ return AGENT_CLIENTS.map((c) => {
242
+ const join = joinFor(platform);
243
+ const file = join(home, forPlatform(c.paths, platform));
244
+ const detected = exists(join(home, forPlatform(c.marker, platform)));
245
+ // A NEW yaml file has nobody's formatting to destroy. An existing one does: `mcpServers` there
246
+ // is a list of named entries, and a naive rewrite loses comments, anchors and ordering.
247
+ if ('yaml-manual' === c.format) {
248
+ return exists(file)
249
+ ? {
250
+ id: c.id,
251
+ name: c.name,
252
+ file,
253
+ action: AgentAction.MANUAL,
254
+ why: 'its config is YAML and already exists, so rewriting it would lose comments and ordering',
255
+ }
256
+ : {
257
+ id: c.id,
258
+ name: c.name,
259
+ file,
260
+ action: AgentAction.CREATE_YAML,
261
+ why: 'no config yet, so a valid one can be written outright',
262
+ };
263
+ }
264
+ const layoutKnown = c.parentMarker !== undefined && exists(join(home, forPlatform(c.parentMarker, platform)));
265
+ if (!detected && c.confidence === AgentConfidence.COMMUNITY && !layoutKnown) {
266
+ return {
267
+ id: c.id,
268
+ name: c.name,
269
+ file,
270
+ action: AgentAction.SKIP,
271
+ why: "not installed, and without its host's standard layout on this machine its path is a guess: a guessed path creates a file nobody reads",
272
+ };
273
+ }
274
+ if (!exists(file)) {
275
+ return {
276
+ id: c.id,
277
+ name: c.name,
278
+ file,
279
+ action: AgentAction.CREATE,
280
+ key: c.key,
281
+ entry: c.entry,
282
+ why: detected
283
+ ? 'installed, with no config yet'
284
+ : 'a documented path, so a later install finds it already wired',
285
+ };
286
+ }
287
+ let parsed;
288
+ try {
289
+ parsed = JSON.parse(readFile(file));
290
+ }
291
+ catch {
292
+ parsed = undefined;
293
+ }
294
+ if ('object' !== typeof parsed || null === parsed) {
295
+ return {
296
+ id: c.id,
297
+ name: c.name,
298
+ file,
299
+ action: AgentAction.MANUAL,
300
+ key: c.key,
301
+ entry: c.entry,
302
+ why: "the existing config is not plain JSON (comments or trailing commas), and rewriting it would reformat somebody else's file",
303
+ };
304
+ }
305
+ const bucket = parsed[c.key];
306
+ const existing = 'object' === typeof bucket && null !== bucket
307
+ ? bucket[RETICLE_KEY]
308
+ : undefined;
309
+ if (existing !== undefined) {
310
+ const ours = JSON.stringify(existing) === JSON.stringify(c.entry);
311
+ return {
312
+ id: c.id,
313
+ name: c.name,
314
+ file,
315
+ action: ours ? AgentAction.ALREADY : AgentAction.MANUAL,
316
+ key: c.key,
317
+ entry: c.entry,
318
+ why: ours
319
+ ? 'already registered'
320
+ : 'a different `reticle` entry is there, and it is not ours to replace',
321
+ };
322
+ }
323
+ return {
324
+ id: c.id,
325
+ name: c.name,
326
+ file,
327
+ action: AgentAction.MERGE,
328
+ key: c.key,
329
+ entry: c.entry,
330
+ why: 'adding our key beside the servers already there',
331
+ };
332
+ });
333
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The real filesystem, in the shape the agent writer and the approval grants expect.
3
+ *
4
+ * Its own module so the MCP server can reach it without importing `setup-command`, which would pull
5
+ * the whole drive machinery — child processes, dev-server supervision, page probing — into the
6
+ * startup path of the one process whose latency a user experiences as "Reticle is slow to connect".
7
+ */
8
+ import type { AgentWriterIo } from './agent-writer.js';
9
+ export declare const agentIo: AgentWriterIo;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * The real filesystem, in the shape the agent writer and the approval grants expect.
3
+ *
4
+ * Its own module so the MCP server can reach it without importing `setup-command`, which would pull
5
+ * the whole drive machinery — child processes, dev-server supervision, page probing — into the
6
+ * startup path of the one process whose latency a user experiences as "Reticle is slow to connect".
7
+ */
8
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
9
+ export const agentIo = {
10
+ exists: (path) => existsSync(path),
11
+ readFile: (path) => readFileSync(path, 'utf8'),
12
+ writeFile: (path, contents) => writeFileSync(path, contents),
13
+ mkdirp: (dir) => {
14
+ mkdirSync(dir, { recursive: true });
15
+ },
16
+ };