@reticlehq/server 2.13.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (451) hide show
  1. package/SKILL.md +8 -18
  2. package/bin/reticle.js +3 -0
  3. package/dist/bridge/auth-failure-reason.js +12 -1
  4. package/dist/bridge/bridge-security.d.ts +8 -0
  5. package/dist/bridge/bridge-security.js +6 -1
  6. package/dist/bridge/bridge.d.ts +3 -0
  7. package/dist/bridge/bridge.js +23 -1
  8. package/dist/bridge/bridge.test-harness.d.ts +10 -1
  9. package/dist/bridge/bridge.test-harness.js +7 -1
  10. package/dist/bridge/pairing-token.d.ts +17 -0
  11. package/dist/bridge/pairing-token.js +26 -0
  12. package/dist/bridge/token-auth.d.ts +7 -0
  13. package/dist/bridge/token-auth.js +7 -1
  14. package/dist/capsule/capsule-store.d.ts +4 -4
  15. package/dist/capsule/capsule.d.ts +1 -1
  16. package/dist/capsule/capsule.js +3 -3
  17. package/dist/capsule/causal-summary.d.ts +21 -1
  18. package/dist/capsule/causal-summary.js +8 -6
  19. package/dist/capsule/divergence.d.ts +1 -1
  20. package/dist/capsule/divergence.js +26 -10
  21. package/dist/chromium-launch-options.d.ts +2 -1
  22. package/dist/cli/adhoc-verdict.d.ts +58 -0
  23. package/dist/cli/adhoc-verdict.js +115 -0
  24. package/dist/cli/chromium-hint.d.ts +18 -4
  25. package/dist/cli/chromium-hint.js +54 -6
  26. package/dist/cli/cli-doctor.js +1 -2
  27. package/dist/cli/cli-flow-commands.d.ts +16 -2
  28. package/dist/cli/cli-flow-commands.js +28 -9
  29. package/dist/cli/cli-parse-feedback.js +1 -1
  30. package/dist/cli/cli-parse.d.ts +2 -1
  31. package/dist/cli/cli-parse.js +38 -0
  32. package/dist/cli/cli-port.d.ts +5 -13
  33. package/dist/cli/cli-port.js +5 -38
  34. package/dist/cli/cli-update-commands.js +4 -4
  35. package/dist/cli/cli-verify.d.ts +24 -7
  36. package/dist/cli/cli-verify.js +87 -13
  37. package/dist/cli/cloud-keystore.d.ts +2 -1
  38. package/dist/cli/cloud-login.d.ts +2 -1
  39. package/dist/cli/cloud-session.d.ts +2 -1
  40. package/dist/cli/cloud-session.js +1 -1
  41. package/dist/cli/daemon-start-options.d.ts +2 -1
  42. package/dist/cli/device-flow.d.ts +2 -2
  43. package/dist/cli/doctor-daemon-line.d.ts +2 -1
  44. package/dist/cli/doctor-project-line.d.ts +2 -1
  45. package/dist/cli/doctor-rows.d.ts +2 -1
  46. package/dist/cli/drive-attach.d.ts +4 -3
  47. package/dist/cli/gate-hook-message.d.ts +2 -1
  48. package/dist/cli/status-next-action.js +10 -11
  49. package/dist/cli.d.ts +1 -0
  50. package/dist/cli.js +21 -9
  51. package/dist/cloud/account-state.d.ts +20 -0
  52. package/dist/cloud/account-state.js +65 -0
  53. package/dist/cloud/cloud-sync.d.ts +46 -3
  54. package/dist/cloud/cloud-sync.js +116 -0
  55. package/dist/cloud/sync-cycle.d.ts +5 -4
  56. package/dist/cloud/sync-cycle.js +1 -1
  57. package/dist/cloud/sync-daemon.d.ts +3 -19
  58. package/dist/cloud/sync-daemon.js +2 -2
  59. package/dist/crawl/crawl-tools.js +1 -1
  60. package/dist/crawl/crawl.d.ts +5 -5
  61. package/dist/crawl/crawl.js +138 -4
  62. package/dist/daemon/daemon-resilience.d.ts +3 -2
  63. package/dist/daemon/daemon-resilience.js +43 -4
  64. package/dist/daemon/daemon-resolve.d.ts +0 -9
  65. package/dist/daemon/daemon-resolve.js +2 -2
  66. package/dist/daemon/dev-servers.d.ts +1 -1
  67. package/dist/daemon/dev-servers.js +1 -1
  68. package/dist/daemon/loopback-alias.d.ts +2 -1
  69. package/dist/daemon/startup-failure.d.ts +11 -0
  70. package/dist/daemon/startup-failure.js +46 -0
  71. package/dist/events/contradictions.d.ts +10 -16
  72. package/dist/events/contradictions.js +149 -17
  73. package/dist/events/declared.d.ts +13 -1
  74. package/dist/events/declared.js +59 -3
  75. package/dist/events/echo-mismatch.js +75 -6
  76. package/dist/events/lineage.d.ts +52 -0
  77. package/dist/events/lineage.js +122 -0
  78. package/dist/events/net-evidence.d.ts +21 -0
  79. package/dist/events/net-evidence.js +81 -0
  80. package/dist/events/predicate-console.d.ts +13 -0
  81. package/dist/events/predicate-console.js +138 -0
  82. package/dist/events/predicate-element.d.ts +13 -0
  83. package/dist/events/predicate-element.js +214 -0
  84. package/dist/events/predicate-eval.d.ts +15 -3
  85. package/dist/events/predicate-eval.js +128 -110
  86. package/dist/events/predicate-parse.js +34 -1
  87. package/dist/events/predicate-request-body.d.ts +50 -0
  88. package/dist/events/predicate-request-body.js +120 -0
  89. package/dist/events/predicate-route.d.ts +21 -1
  90. package/dist/events/predicate-route.js +149 -21
  91. package/dist/events/predicate-schema.d.ts +8 -1
  92. package/dist/events/predicate-schema.js +34 -0
  93. package/dist/events/predicate.d.ts +2 -0
  94. package/dist/events/predicate.js +52 -159
  95. package/dist/events/split-text-miss.d.ts +6 -4
  96. package/dist/events/split-text-miss.js +12 -5
  97. package/dist/flows/affected-tools.js +22 -9
  98. package/dist/flows/annotate.d.ts +2 -1
  99. package/dist/flows/annotate.js +19 -1
  100. package/dist/flows/coverage.d.ts +2 -2
  101. package/dist/flows/flake.d.ts +3 -3
  102. package/dist/flows/flow-expect-grammar.d.ts +33 -0
  103. package/dist/flows/flow-expect-grammar.js +155 -0
  104. package/dist/flows/flow-replay-run.d.ts +1 -1
  105. package/dist/flows/flow-replay-run.js +28 -15
  106. package/dist/flows/flow-replay.d.ts +10 -0
  107. package/dist/flows/flow-replay.js +57 -22
  108. package/dist/flows/flow-secret-field.d.ts +14 -0
  109. package/dist/flows/flow-secret-field.js +22 -0
  110. package/dist/flows/flow-step-runners.d.ts +1 -0
  111. package/dist/flows/flow-step-runners.js +91 -34
  112. package/dist/flows/flow-success.js +22 -0
  113. package/dist/flows/flow-tools.js +27 -8
  114. package/dist/flows/flow-upload-resolve.d.ts +23 -0
  115. package/dist/flows/flow-upload-resolve.js +46 -0
  116. package/dist/flows/flows.d.ts +15 -0
  117. package/dist/flows/flows.js +17 -14
  118. package/dist/flows/git-changed.d.ts +23 -4
  119. package/dist/flows/git-changed.js +32 -6
  120. package/dist/flows/heal-run.js +2 -2
  121. package/dist/flows/predicate-to-expect.d.ts +3 -2
  122. package/dist/flows/predicate-to-expect.js +11 -5
  123. package/dist/flows/recording-backtrack.d.ts +24 -0
  124. package/dist/flows/recording-backtrack.js +73 -0
  125. package/dist/flows/recordings.d.ts +6 -0
  126. package/dist/flows/replay-halt.d.ts +26 -0
  127. package/dist/flows/replay-halt.js +30 -0
  128. package/dist/flows/replay.d.ts +0 -8
  129. package/dist/flows/replay.js +15 -3
  130. package/dist/flows/suite-flakes.d.ts +2 -1
  131. package/dist/flows/verify-change-tools.js +16 -4
  132. package/dist/honesty/already-true.d.ts +14 -0
  133. package/dist/honesty/already-true.js +47 -4
  134. package/dist/honesty/blind-spots.d.ts +3 -1
  135. package/dist/honesty/blind-spots.js +24 -1
  136. package/dist/honesty/body-capture-remedy.d.ts +42 -0
  137. package/dist/honesty/body-capture-remedy.js +95 -0
  138. package/dist/honesty/feature-capture.d.ts +3 -2
  139. package/dist/honesty/feature-capture.js +1 -1
  140. package/dist/honesty/gap-ledger.d.ts +1 -1
  141. package/dist/honesty/gap-ledger.js +1 -1
  142. package/dist/honesty/instrumentation-gaps.d.ts +8 -1
  143. package/dist/honesty/instrumentation-gaps.js +12 -2
  144. package/dist/honesty/observability.d.ts +4 -3
  145. package/dist/honesty/tool-hit-rate.d.ts +3 -2
  146. package/dist/honesty/uncaptured-bodies.d.ts +3 -1
  147. package/dist/honesty/uncaptured-bodies.js +6 -2
  148. package/dist/honesty/unsettled.d.ts +9 -0
  149. package/dist/honesty/unsettled.js +45 -0
  150. package/dist/honesty/verified.d.ts +32 -1
  151. package/dist/honesty/verified.js +37 -10
  152. package/dist/impact/impact-store.d.ts +5 -14
  153. package/dist/impact/impact-store.js +49 -6
  154. package/dist/index.js +1 -1
  155. package/dist/input/network-detail.d.ts +26 -1
  156. package/dist/input/network-detail.js +44 -12
  157. package/dist/input/network-mock-tools.js +24 -17
  158. package/dist/input/real-input.js +1 -1
  159. package/dist/input/viewport-tools.js +25 -13
  160. package/dist/intent/inline-intent.d.ts +0 -10
  161. package/dist/intent/inline-intent.js +1 -1
  162. package/dist/intent/intent-shard-store.d.ts +3 -2
  163. package/dist/intent/intent-shard-store.js +1 -1
  164. package/dist/intent/intent-shard.d.ts +14 -14
  165. package/dist/intent/intent-shard.js +4 -4
  166. package/dist/intent/intent-store.d.ts +1 -1
  167. package/dist/intent/intent-store.js +1 -1
  168. package/dist/intent/intent-subject.d.ts +2 -1
  169. package/dist/intent/open-intents.d.ts +3 -2
  170. package/dist/license/license.d.ts +4 -4
  171. package/dist/license/license.js +1 -1
  172. package/dist/mcp/attach-memory.d.ts +2 -1
  173. package/dist/mcp/client-identity.d.ts +2 -1
  174. package/dist/mcp/mcp-outage.d.ts +1 -1
  175. package/dist/mcp/mcp-outage.js +1 -1
  176. package/dist/mcp/mcp-proxy.d.ts +3 -1
  177. package/dist/mcp/mcp-proxy.js +32 -7
  178. package/dist/mcp/mcp.js +3 -1
  179. package/dist/mcp/proxy-handshake.d.ts +2 -1
  180. package/dist/mcp/proxy-idle-exit.d.ts +45 -0
  181. package/dist/mcp/proxy-idle-exit.js +94 -0
  182. package/dist/mcp/server-instructions.d.ts +2 -1
  183. package/dist/memory/project-memory.d.ts +2 -3
  184. package/dist/memory/project-memory.js +1 -1
  185. package/dist/oracles/propose-consequences.js +5 -5
  186. package/dist/oracles/self-instrument.d.ts +2 -2
  187. package/dist/oracles/self-instrument.js +1 -1
  188. package/dist/platform.d.ts +3 -5
  189. package/dist/platform.js +3 -5
  190. package/dist/pool/browser-pool.d.ts +92 -0
  191. package/dist/pool/browser-pool.js +119 -5
  192. package/dist/pool/playwright-launcher.js +29 -2
  193. package/dist/pool/storage-seed.d.ts +23 -0
  194. package/dist/pool/storage-seed.js +198 -0
  195. package/dist/project/artifact-root.d.ts +2 -1
  196. package/dist/project/memory-fs.d.ts +2 -1
  197. package/dist/project/reticle-dir.js +1 -1
  198. package/dist/project/session-root.d.ts +8 -0
  199. package/dist/project/session-root.js +9 -2
  200. package/dist/runs/reticle-runner.d.ts +14 -1
  201. package/dist/runs/reticle-runner.js +39 -2
  202. package/dist/session/attachment-history.d.ts +2 -1
  203. package/dist/session/fake-session.d.ts +37 -0
  204. package/dist/session/fake-session.js +81 -0
  205. package/dist/session/lease-visibility.d.ts +2 -1
  206. package/dist/session/live-control-tools.js +6 -0
  207. package/dist/session/no-session-diagnosis.d.ts +17 -1
  208. package/dist/session/no-session-diagnosis.js +32 -6
  209. package/dist/session/no-session-next-action.d.ts +14 -0
  210. package/dist/session/no-session-next-action.js +18 -0
  211. package/dist/session/no-session-watch.js +46 -3
  212. package/dist/session/registered-projects.d.ts +54 -0
  213. package/dist/session/registered-projects.js +73 -0
  214. package/dist/session/review-store.d.ts +2 -1
  215. package/dist/session/session-health.d.ts +62 -1
  216. package/dist/session/session-health.js +82 -2
  217. package/dist/session/session-manager.d.ts +11 -1
  218. package/dist/session/session-manager.js +18 -3
  219. package/dist/session/session-replaced.d.ts +24 -0
  220. package/dist/session/session-replaced.js +29 -0
  221. package/dist/session/session-successor.js +20 -4
  222. package/dist/session/session.d.ts +13 -5
  223. package/dist/session/session.js +31 -23
  224. package/dist/setup/agent-configs.d.ts +3 -2
  225. package/dist/setup/agent-writer.d.ts +3 -2
  226. package/dist/setup/approval-migration.d.ts +2 -1
  227. package/dist/setup/approval-migration.js +1 -1
  228. package/dist/setup/auto-approve.d.ts +11 -19
  229. package/dist/setup/auto-approve.js +15 -12
  230. package/dist/{init → setup}/confirm.js +1 -1
  231. package/dist/setup/desktop-shape.d.ts +3 -2
  232. package/dist/setup/dev-server-wait.d.ts +8 -0
  233. package/dist/setup/dev-server-wait.js +3 -1
  234. package/dist/setup/drive-agent.d.ts +2 -3
  235. package/dist/setup/drive-agent.js +1 -1
  236. package/dist/setup/drive-plan.d.ts +3 -2
  237. package/dist/setup/ensure-daemon.d.ts +2 -1
  238. package/dist/setup/existing-app.d.ts +12 -0
  239. package/dist/setup/existing-app.js +13 -0
  240. package/dist/setup/init-host.d.ts +11 -0
  241. package/dist/setup/init-host.js +26 -0
  242. package/dist/setup/init-runtime.d.ts +4 -10
  243. package/dist/setup/init-runtime.js +23 -16
  244. package/dist/setup/license-key.d.ts +2 -1
  245. package/dist/setup/listeners.d.ts +2 -1
  246. package/dist/setup/loopback-probe-urls.d.ts +18 -0
  247. package/dist/setup/loopback-probe-urls.js +52 -0
  248. package/dist/setup/node-effects.d.ts +3 -0
  249. package/dist/setup/node-effects.js +26 -11
  250. package/dist/setup/page-probe.d.ts +5 -0
  251. package/dist/setup/relaunch.d.ts +2 -1
  252. package/dist/setup/run-setup.d.ts +5 -1
  253. package/dist/setup/run-setup.js +155 -48
  254. package/dist/setup/setup-command.d.ts +3 -2
  255. package/dist/setup/setup-command.js +8 -0
  256. package/dist/setup/setup-options.d.ts +5 -3
  257. package/dist/setup/setup-options.js +3 -2
  258. package/dist/setup/wait-progress.d.ts +28 -0
  259. package/dist/setup/wait-progress.js +43 -0
  260. package/dist/telemetry/app-instrumented.d.ts +2 -1
  261. package/dist/telemetry/app-instrumented.js +1 -1
  262. package/dist/telemetry/automation-hint.d.ts +1 -1
  263. package/dist/telemetry/automation-hint.js +1 -1
  264. package/dist/telemetry/cli-telemetry.js +1 -1
  265. package/dist/telemetry/connect-failure.d.ts +1 -1
  266. package/dist/telemetry/connect-failure.js +1 -1
  267. package/dist/telemetry/crash-cause.d.ts +3 -2
  268. package/dist/telemetry/crash-cause.js +1 -1
  269. package/dist/telemetry/daemon-telemetry.d.ts +3 -2
  270. package/dist/telemetry/daemon-telemetry.js +1 -1
  271. package/dist/telemetry/error-class.d.ts +3 -2
  272. package/dist/telemetry/error-class.js +1 -1
  273. package/dist/telemetry/feedback-cli.js +1 -1
  274. package/dist/telemetry/feedback-context.d.ts +3 -2
  275. package/dist/telemetry/feedback-context.js +5 -4
  276. package/dist/telemetry/feedback-local.d.ts +3 -2
  277. package/dist/telemetry/feedback.d.ts +4 -3
  278. package/dist/telemetry/feedback.js +1 -1
  279. package/dist/telemetry/git-facts.d.ts +2 -2
  280. package/dist/telemetry/git-facts.js +1 -1
  281. package/dist/telemetry/identify.d.ts +1 -1
  282. package/dist/telemetry/identify.js +1 -1
  283. package/dist/telemetry/init-telemetry.d.ts +2 -14
  284. package/dist/telemetry/init-telemetry.js +4 -13
  285. package/dist/telemetry/install-source.d.ts +7 -13
  286. package/dist/telemetry/install-source.js +7 -29
  287. package/dist/telemetry/license-activation.d.ts +3 -2
  288. package/dist/telemetry/license-activation.js +1 -1
  289. package/dist/telemetry/machine-snapshot.d.ts +1 -1
  290. package/dist/telemetry/mcp-connection.js +1 -1
  291. package/dist/telemetry/project-profile.d.ts +0 -14
  292. package/dist/telemetry/project-profile.js +1 -1
  293. package/dist/telemetry/proxy-telemetry.js +1 -1
  294. package/dist/telemetry/session-metrics.d.ts +1 -1
  295. package/dist/telemetry/session-metrics.js +1 -1
  296. package/dist/telemetry/telemetry.d.ts +1 -17
  297. package/dist/telemetry/telemetry.js +4 -4
  298. package/dist/telemetry/tool-refused.d.ts +1 -1
  299. package/dist/telemetry/tool-refused.js +1 -1
  300. package/dist/tools/act-observation.d.ts +3 -2
  301. package/dist/tools/act-preflight.js +8 -3
  302. package/dist/tools/act-target.d.ts +4 -0
  303. package/dist/tools/act-target.js +14 -5
  304. package/dist/tools/act-tools.d.ts +0 -2
  305. package/dist/tools/act-tools.js +45 -15
  306. package/dist/tools/assert-source.d.ts +30 -0
  307. package/dist/tools/assert-source.js +21 -0
  308. package/dist/tools/assert-verdict.d.ts +1 -1
  309. package/dist/tools/assert-verdict.js +14 -4
  310. package/dist/tools/browser-tools.js +28 -8
  311. package/dist/tools/complete-snapshot.d.ts +3 -2
  312. package/dist/tools/dynamic-tools.js +8 -0
  313. package/dist/tools/error-recovery.d.ts +10 -0
  314. package/dist/tools/error-recovery.js +23 -0
  315. package/dist/tools/feedback-tools.js +1 -1
  316. package/dist/tools/invoke-tool.js +2 -0
  317. package/dist/tools/lease-tools.d.ts +48 -20
  318. package/dist/tools/lease-tools.js +311 -76
  319. package/dist/tools/lineage-tools.d.ts +14 -0
  320. package/dist/tools/lineage-tools.js +64 -0
  321. package/dist/tools/merge-tools.js +1 -3
  322. package/dist/tools/navigate-arrival.d.ts +45 -1
  323. package/dist/tools/navigate-arrival.js +39 -6
  324. package/dist/tools/navigate-result.d.ts +8 -1
  325. package/dist/tools/navigate-result.js +14 -4
  326. package/dist/tools/numeric-bounds.d.ts +0 -11
  327. package/dist/tools/numeric-bounds.js +4 -4
  328. package/dist/tools/observe-tools.js +22 -26
  329. package/dist/tools/observed-nothing.d.ts +9 -0
  330. package/dist/tools/observed-nothing.js +2 -1
  331. package/dist/tools/read-tools.js +12 -17
  332. package/dist/tools/real-input-attempt.d.ts +14 -6
  333. package/dist/tools/real-input-attempt.js +75 -18
  334. package/dist/tools/reconcile-tools.js +2 -1
  335. package/dist/tools/reload-result.d.ts +6 -1
  336. package/dist/tools/reload-result.js +7 -2
  337. package/dist/tools/resolve-target.d.ts +7 -1
  338. package/dist/tools/resolve-target.js +72 -9
  339. package/dist/tools/settle-in-flight.d.ts +1 -1
  340. package/dist/tools/settle-in-flight.js +9 -2
  341. package/dist/tools/snapshot-delta.d.ts +24 -1
  342. package/dist/tools/snapshot-delta.js +66 -7
  343. package/dist/tools/tool-names.d.ts +1 -0
  344. package/dist/tools/tool-names.js +1 -0
  345. package/dist/tools/tool-surface.js +20 -2
  346. package/dist/tools/tools.js +120 -11
  347. package/dist/tools/verify-next-baton.d.ts +2 -1
  348. package/dist/update/sdk-sync.d.ts +1 -1
  349. package/dist/update/sdk-sync.js +1 -1
  350. package/dist/update/updater.js +1 -1
  351. package/dist/version/sdk-fix.d.ts +2 -1
  352. package/dist/version/version-skew.d.ts +0 -8
  353. package/dist/version/version-skew.js +1 -1
  354. package/docs/README.md +8 -6
  355. package/docs/actions.mdx +1 -1
  356. package/docs/agent-cheatsheet.md +4 -2
  357. package/docs/architecture.md +1 -1
  358. package/docs/cli/mcp.mdx +3 -1
  359. package/docs/cli/watch.mdx +1 -1
  360. package/docs/cli.mdx +1 -1
  361. package/docs/desktop.mdx +1 -1
  362. package/docs/docs.json +8 -3
  363. package/docs/faq.mdx +9 -0
  364. package/docs/first-drive.md +136 -0
  365. package/docs/frameworks.mdx +29 -7
  366. package/docs/gate-plan.md +2 -2
  367. package/docs/gates.md +19 -11
  368. package/docs/getting-started.md +7 -1
  369. package/docs/http-transport.md +164 -0
  370. package/docs/index.mdx +5 -3
  371. package/docs/install-manual.mdx +1 -1
  372. package/docs/packages/tauri.mdx +1 -1
  373. package/docs/packages.mdx +15 -3
  374. package/docs/platform-integration.md +1 -1
  375. package/docs/predicates.mdx +2 -0
  376. package/docs/system-map.md +2 -2
  377. package/docs/telemetry-contract.md +4 -4
  378. package/docs/telemetry-events.mdx +1 -1
  379. package/docs/telemetry.md +3 -3
  380. package/docs/tools-navigate.mdx +14 -6
  381. package/docs/usage.md +4 -4
  382. package/package.json +7 -4
  383. package/dist/init/agent-rules.d.ts +0 -116
  384. package/dist/init/agent-rules.js +0 -279
  385. package/dist/init/app-choice.d.ts +0 -20
  386. package/dist/init/app-choice.js +0 -25
  387. package/dist/init/astro-host.d.ts +0 -21
  388. package/dist/init/astro-host.js +0 -27
  389. package/dist/init/astro-patch.d.ts +0 -33
  390. package/dist/init/astro-patch.js +0 -300
  391. package/dist/init/capabilities.d.ts +0 -40
  392. package/dist/init/capabilities.js +0 -150
  393. package/dist/init/closing-hint.d.ts +0 -36
  394. package/dist/init/closing-hint.js +0 -120
  395. package/dist/init/connect-steps.d.ts +0 -2
  396. package/dist/init/connect-steps.js +0 -50
  397. package/dist/init/cra.d.ts +0 -79
  398. package/dist/init/cra.js +0 -178
  399. package/dist/init/csp-check.d.ts +0 -38
  400. package/dist/init/csp-check.js +0 -107
  401. package/dist/init/csp-doctor.d.ts +0 -32
  402. package/dist/init/csp-doctor.js +0 -54
  403. package/dist/init/desktop-doctor.d.ts +0 -41
  404. package/dist/init/desktop-doctor.js +0 -198
  405. package/dist/init/detect.d.ts +0 -96
  406. package/dist/init/detect.js +0 -209
  407. package/dist/init/dev-script.d.ts +0 -50
  408. package/dist/init/dev-script.js +0 -66
  409. package/dist/init/format-generated.d.ts +0 -18
  410. package/dist/init/format-generated.js +0 -39
  411. package/dist/init/install-hint.d.ts +0 -9
  412. package/dist/init/install-hint.js +0 -29
  413. package/dist/init/mcp-clients.d.ts +0 -89
  414. package/dist/init/mcp-clients.js +0 -299
  415. package/dist/init/mcp-registered.d.ts +0 -12
  416. package/dist/init/mcp-registered.js +0 -14
  417. package/dist/init/mcp.d.ts +0 -89
  418. package/dist/init/mcp.js +0 -129
  419. package/dist/init/next-patch.d.ts +0 -39
  420. package/dist/init/next-patch.js +0 -196
  421. package/dist/init/node-io.d.ts +0 -7
  422. package/dist/init/node-io.js +0 -117
  423. package/dist/init/non-js-project.d.ts +0 -27
  424. package/dist/init/non-js-project.js +0 -84
  425. package/dist/init/patch-kind.d.ts +0 -20
  426. package/dist/init/patch-kind.js +0 -10
  427. package/dist/init/plan-framework.d.ts +0 -64
  428. package/dist/init/plan-framework.js +0 -565
  429. package/dist/init/plan.d.ts +0 -242
  430. package/dist/init/plan.js +0 -703
  431. package/dist/init/preflight.d.ts +0 -28
  432. package/dist/init/preflight.js +0 -38
  433. package/dist/init/project-id.d.ts +0 -12
  434. package/dist/init/project-id.js +0 -25
  435. package/dist/init/refresh-rules.d.ts +0 -39
  436. package/dist/init/refresh-rules.js +0 -51
  437. package/dist/init/run.d.ts +0 -117
  438. package/dist/init/run.js +0 -806
  439. package/dist/init/slash-command.d.ts +0 -27
  440. package/dist/init/slash-command.js +0 -132
  441. package/dist/init/snippets.d.ts +0 -134
  442. package/dist/init/snippets.js +0 -572
  443. package/dist/init/vite-config.d.ts +0 -18
  444. package/dist/init/vite-config.js +0 -114
  445. package/dist/init/windows-quote.d.ts +0 -38
  446. package/dist/init/windows-quote.js +0 -70
  447. package/dist/init/workspace-apps.d.ts +0 -30
  448. package/dist/init/workspace-apps.js +0 -178
  449. package/dist/project/remember-project.d.ts +0 -24
  450. package/dist/project/remember-project.js +0 -40
  451. /package/dist/{init → setup}/confirm.d.ts +0 -0
@@ -0,0 +1,37 @@
1
+ /**
2
+ * One complete, inert `Session` for tests that only care about two or three of its methods.
3
+ *
4
+ * Test-only. Every consumer of `Session` used to hand-roll a `Partial<Session>` and cast it through
5
+ * `as Session`, and the cast is the problem: it silences the type system, so a method added to
6
+ * `Session` breaks nothing at compile time and then throws at runtime inside files that have
7
+ * nothing to do with the change. That has now happened four times -- `occludedBy` (#227),
8
+ * `bufferHealth` (#228), the case recorded at the top of `assert-contradiction-plumbing.test.ts`,
9
+ * and `lostSince`, which needed the identical one-line stub added to seven files (#726).
10
+ *
11
+ * This returns a REAL `Session`, built with an inert socket and a frozen clock, so a new method
12
+ * arrives with a working default instead of `undefined`. `Session` holds `#private` fields, so a
13
+ * structural stub cannot be one -- constructing the real thing is what makes the guarantee hold.
14
+ *
15
+ * Overrides are applied as own properties, shadowing the prototype method for that instance. Pass
16
+ * only the behaviour a test actually asserts on; leave the rest to the defaults.
17
+ */
18
+ import { type HelloMessage } from '@reticlehq/core';
19
+ import { Session } from './session.js';
20
+ /**
21
+ * The handshake a fake session reports.
22
+ *
23
+ * `sessionId: 'demo'` matches what the hand-rolled stubs used, so tests asserting on the id in a
24
+ * message keep passing without an override.
25
+ */
26
+ export declare function fakeHello(overrides?: Partial<HelloMessage>): HelloMessage;
27
+ /**
28
+ * A real `Session` with inert defaults, plus whatever this test needs it to do differently.
29
+ *
30
+ * @param overrides Written onto the instance after construction, shadowing the real member for
31
+ * this session only. `runtime`, `engine`, `brand` and `actionCount` are getter-only accessors on
32
+ * the prototype -- a plain assignment to one throws -- so those are redefined as instance values
33
+ * instead. Overriding them is what a test wants: the real ones are fed by `applyHealth` and the
34
+ * journal, which a fake session has neither of.
35
+ * @param hello Handshake fields, when a test cares about the id, url or redact keys.
36
+ */
37
+ export declare function createFakeSession(overrides?: Partial<Session>, hello?: Partial<HelloMessage>): Session;
@@ -0,0 +1,81 @@
1
+ /**
2
+ * One complete, inert `Session` for tests that only care about two or three of its methods.
3
+ *
4
+ * Test-only. Every consumer of `Session` used to hand-roll a `Partial<Session>` and cast it through
5
+ * `as Session`, and the cast is the problem: it silences the type system, so a method added to
6
+ * `Session` breaks nothing at compile time and then throws at runtime inside files that have
7
+ * nothing to do with the change. That has now happened four times -- `occludedBy` (#227),
8
+ * `bufferHealth` (#228), the case recorded at the top of `assert-contradiction-plumbing.test.ts`,
9
+ * and `lostSince`, which needed the identical one-line stub added to seven files (#726).
10
+ *
11
+ * This returns a REAL `Session`, built with an inert socket and a frozen clock, so a new method
12
+ * arrives with a working default instead of `undefined`. `Session` holds `#private` fields, so a
13
+ * structural stub cannot be one -- constructing the real thing is what makes the guarantee hold.
14
+ *
15
+ * Overrides are applied as own properties, shadowing the prototype method for that instance. Pass
16
+ * only the behaviour a test actually asserts on; leave the rest to the defaults.
17
+ */
18
+ import { MessageKind, RETICLE_PROTOCOL_VERSION } from '@reticlehq/core';
19
+ import { Session } from './session.js';
20
+ /** Accepts and discards. A fake session is never expected to reach a peer. */
21
+ const INERT_SOCKET = {
22
+ send: () => undefined,
23
+ close: () => undefined,
24
+ };
25
+ /** Frozen at 0, so `elapsed()`, `lastSeenMs()` and `agentIdleMs()` are all deterministic zeroes. */
26
+ const FROZEN_CLOCK = () => 0;
27
+ /**
28
+ * The handshake a fake session reports.
29
+ *
30
+ * `sessionId: 'demo'` matches what the hand-rolled stubs used, so tests asserting on the id in a
31
+ * message keep passing without an override.
32
+ */
33
+ export function fakeHello(overrides = {}) {
34
+ return {
35
+ kind: MessageKind.HELLO,
36
+ protocolVersion: RETICLE_PROTOCOL_VERSION,
37
+ sessionId: 'demo',
38
+ url: 'http://localhost/',
39
+ title: 'Demo',
40
+ adapters: [],
41
+ ...overrides,
42
+ };
43
+ }
44
+ /** Whether `key` resolves to a getter somewhere on the prototype chain rather than a plain slot. */
45
+ function isAccessor(target, key) {
46
+ let proto = target;
47
+ while (null !== proto) {
48
+ const descriptor = Object.getOwnPropertyDescriptor(proto, key);
49
+ if (undefined !== descriptor)
50
+ return undefined !== descriptor.get;
51
+ proto = Object.getPrototypeOf(proto);
52
+ }
53
+ return false;
54
+ }
55
+ /**
56
+ * A real `Session` with inert defaults, plus whatever this test needs it to do differently.
57
+ *
58
+ * @param overrides Written onto the instance after construction, shadowing the real member for
59
+ * this session only. `runtime`, `engine`, `brand` and `actionCount` are getter-only accessors on
60
+ * the prototype -- a plain assignment to one throws -- so those are redefined as instance values
61
+ * instead. Overriding them is what a test wants: the real ones are fed by `applyHealth` and the
62
+ * journal, which a fake session has neither of.
63
+ * @param hello Handshake fields, when a test cares about the id, url or redact keys.
64
+ */
65
+ export function createFakeSession(overrides = {}, hello = {}) {
66
+ const session = new Session(fakeHello(hello), INERT_SOCKET, FROZEN_CLOCK);
67
+ for (const [key, value] of Object.entries(overrides)) {
68
+ if (isAccessor(session, key)) {
69
+ Object.defineProperty(session, key, {
70
+ value,
71
+ configurable: true,
72
+ enumerable: true,
73
+ writable: true,
74
+ });
75
+ }
76
+ else {
77
+ session[key] = value;
78
+ }
79
+ }
80
+ return session;
81
+ }
@@ -15,7 +15,7 @@
15
15
  * message to tabs that are ALREADY connected, not a new capability.
16
16
  */
17
17
  /** The minimum a session has to expose to be a candidate watcher. Structural, so tests need no SDK. */
18
- export interface WatcherCandidate {
18
+ interface WatcherCandidate {
19
19
  id: string;
20
20
  projectId: string | undefined;
21
21
  }
@@ -65,3 +65,4 @@ export declare function shouldGreetWithLeaseNotice(session: WatcherCandidate, le
65
65
  * Pure: no IO, no clock, no session objects. The caller does the posting.
66
66
  */
67
67
  export declare function watchersToNotify(sessions: readonly WatcherCandidate[], leasedSessionIds: readonly string[], projectId: string | undefined): string[];
68
+ export {};
@@ -157,6 +157,12 @@ export const LIVE_CONTROL_TOOLS = [
157
157
  // said nothing; the second means an agent is waiting on a channel that does not exist.
158
158
  return Promise.resolve(noteEmptyRead({ messages }, 'messages', {
159
159
  noun: 'messages from the human since the last poll',
160
+ // The HUD no longer carries a text input — two boxes on one screen, and nothing said which
161
+ // was which. Nothing in the shipped product produces one of these today, so an empty queue
162
+ // here is structural rather than a quiet human, and a caller polling it deserves to know
163
+ // that rather than infer silence.
164
+ caveat: 'the HUD has no message box, so nothing currently sends on this channel — an empty ' +
165
+ 'queue here is not evidence about what the human thinks',
160
166
  }));
161
167
  },
162
168
  },
@@ -18,7 +18,7 @@
18
18
  * Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
19
19
  * the hot resolve() path and this stays unit-testable.
20
20
  */
21
- import { NoSessionReason } from '@reticlehq/core';
21
+ import { NoSessionReason } from '@reticlehq/core/telemetry';
22
22
  import { type LeaseBrowserState } from './lease-availability.js';
23
23
  export interface NoSessionFacts {
24
24
  /** Whether ANY session has connected to this daemon since it booted. */
@@ -128,6 +128,15 @@ export interface NoSessionFacts {
128
128
  * which is the common case and must not gain a paragraph.
129
129
  */
130
130
  siblingListeners?: readonly number[];
131
+ /**
132
+ * The URL of the session that most recently disappeared, when the tombstone still holds it.
133
+ *
134
+ * A route that 500s tears the page down and the SDK never reconnects. `everConnected` already
135
+ * knew a tab HAD been here; without this the diagnosis could not say WHERE, so a server error
136
+ * and a closed tab read as the same empty list (#808). Absent when nothing was ever connected,
137
+ * or the ring has forgotten — inventing a URL is worse than the silence this exists to end.
138
+ */
139
+ lastKnownUrl?: string;
131
140
  }
132
141
  /**
133
142
  * Every branch ends with this, and `recoveryFor` keys on it to suppress the generic no-session
@@ -135,6 +144,13 @@ export interface NoSessionFacts {
135
144
  * generic one that contradicts it.
136
145
  */
137
146
  export declare const SELF_RECOVERING_MARKER = "Then call reticle_sessions again \u2014 it will appear within a second of the page loading.";
147
+ /**
148
+ * The last URL a connected session was on when it disappeared without a successor HELLO.
149
+ *
150
+ * Shared with the observation-lost verdict so the two surfaces cannot drift into naming different
151
+ * routes for the same teardown (#808).
152
+ */
153
+ export declare function pageTornDownWhileOn(url: string): string;
138
154
  /**
139
155
  * The diagnosis, and the CODE for the branch that produced it.
140
156
  *
@@ -18,7 +18,7 @@
18
18
  * Pure: everything it needs is passed in, so the probe that discovers listening ports stays out of
19
19
  * the hot resolve() path and this stays unit-testable.
20
20
  */
21
- import { NoSessionReason } from '@reticlehq/core';
21
+ import { NoSessionReason } from '@reticlehq/core/telemetry';
22
22
  import { leaseCaveat } from './lease-availability.js';
23
23
  import { DEV_SERVER_PORTS } from '../cli/cli-port.js';
24
24
  import { siblingListenerNote } from '../cli/sibling-ports.js';
@@ -75,13 +75,32 @@ function leaseAdvice(base, facts) {
75
75
  const SELF_SERVE = 'You do not have to wait for the human: reticle_lease {action:"acquire", url} opens a browser ' +
76
76
  'Reticle drives itself, and returns a sessionId you can use immediately (reach it with ' +
77
77
  'reticle_run {tool:"reticle_lease"} if it is not advertised directly; release it when you finish).';
78
+ /** The hedge that is true for every vanished tab we did not watch leave. */
79
+ const TAB_GONE_WHAT = 'The tab was closed, navigated away, or hard-reloaded.';
78
80
  /**
79
- * The ports the scan actually covers, rendered for the message.
81
+ * The last URL a connected session was on when it disappeared without a successor HELLO.
80
82
  *
81
- * Derived from DEV_SERVER_PORTS rather than re-typed: a message that lists ports the scan does not
82
- * check, or omits ones it does, is a new version of the same defect — a confident claim about
83
- * evidence that was never gathered.
83
+ * Shared with the observation-lost verdict so the two surfaces cannot drift into naming different
84
+ * routes for the same teardown (#808).
85
+ */
86
+ export function pageTornDownWhileOn(url) {
87
+ return `the page was torn down while on ${url}`;
88
+ }
89
+ /**
90
+ * What happened to the tab, with the last URL named when we still hold it.
91
+ *
92
+ * Without the URL this is the sentence the field has always seen. With it, a route that tore the
93
+ * page down is no longer the same dead end as "the human closed the tab".
84
94
  */
95
+ function tabGoneWhat(url) {
96
+ if (undefined === url || '' === url)
97
+ return TAB_GONE_WHAT;
98
+ const torn = pageTornDownWhileOn(url);
99
+ const first = torn.charAt(0);
100
+ if ('' === first)
101
+ return TAB_GONE_WHAT;
102
+ return `${first.toUpperCase()}${torn.slice(1)}. ${TAB_GONE_WHAT}`;
103
+ }
85
104
  /**
86
105
  * The one cause that produces a PERFECTLY healthy everything and still never connects.
87
106
  *
@@ -138,6 +157,13 @@ const RESTARTED_LEAD = 'no browser session connected. This daemon has served non
138
157
  'durably. So the wiring is correct and the port is correct: what is failing now is the SDK ' +
139
158
  'reaching initialise on the page. Re-running the install is the wrong move here: it cannot help ' +
140
159
  'on a project that has demonstrably connected, and it can overwrite a working config.';
160
+ /**
161
+ * The ports the scan actually covers, rendered for the message.
162
+ *
163
+ * Derived from DEV_SERVER_PORTS rather than re-typed: a message that lists ports the scan does not
164
+ * check, or omits ones it does, is a new version of the same defect — a confident claim about
165
+ * evidence that was never gathered.
166
+ */
141
167
  const SCANNED_PORTS = [...DEV_SERVER_PORTS].join(', ');
142
168
  /**
143
169
  * How to say "open the app" to an agent that may have no `reticle` binary.
@@ -349,7 +375,7 @@ export function explainNoSession(facts) {
349
375
  `carry on.${alreadyListeningClause(listening)} ${RETRY}`);
350
376
  }
351
377
  return reason(NoSessionReason.TAB_GONE, 'no browser session connected, but one WAS connected to this daemon earlier, so the wiring ' +
352
- 'is correct. The tab was closed, navigated away, or hard-reloaded. Ask the human to reopen ' +
378
+ `is correct. ${tabGoneWhat(facts.lastKnownUrl)} Ask the human to reopen ` +
353
379
  `the app (or run ${OPEN_CMD_BARE}), or reload the tab.${alreadyListeningClause(listening)} ` +
354
380
  `${leaseAdvice(SELF_SERVE, facts)} ${RETRY}`);
355
381
  }
@@ -51,6 +51,20 @@ interface NextActionFacts {
51
51
  * that works.
52
52
  */
53
53
  previouslyConnected?: boolean;
54
+ /**
55
+ * A page reached this daemon and was REFUSED on the pairing token.
56
+ *
57
+ * Positive evidence, and the only fact here that proves the app is both running and instrumented:
58
+ * nothing dials the bridge but an SDK, so a refused hello means the wiring works and the daemon
59
+ * would not serve it. That happens whenever an IDE registers the MCP server GLOBALLY -- the daemon
60
+ * starts with a cwd of `/` or `$HOME`, finds no `.reticle.json` there, and refuses every page
61
+ * while `doctor` run from the app directory reports the project wired (#685).
62
+ *
63
+ * Without it, `initialized: false` plus a listening port reads as "the app may carry no SDK" and
64
+ * the action handed back is `reticle init` -- over a config that already works, which is the one
65
+ * action that cannot help and can overwrite it.
66
+ */
67
+ authRefused?: boolean;
54
68
  }
55
69
  export declare function nextActionFor(facts: NextActionFacts): NoSessionNextAction;
56
70
  /** The same action as one sentence, for the prose message the human reads. */
@@ -103,6 +103,24 @@ export function nextActionFor(facts) {
103
103
  // a second port is the exact confusion the probe exists to prevent.
104
104
  const ports = facts.listening.join(', ');
105
105
  const only = 1 === facts.listening.length ? facts.listening[0] : undefined;
106
+ // A refused dial outranks the absence of a local config, for the same reason `previouslyConnected`
107
+ // does: it is positive evidence about the app, and `initialized` only ever answers "is there a
108
+ // `.reticle.json` in the ONE directory this daemon stands in". A globally-registered daemon stands
109
+ // in `/` or `$HOME`, so that answer is no for every project it serves -- and every page it turns
110
+ // away is proof that the project it turned away is wired.
111
+ if (!facts.initialized && true === facts.authRefused) {
112
+ return {
113
+ action: NoSessionAction.OPEN_APP,
114
+ ...(only === undefined ? {} : { port: only }),
115
+ reason: `a page dialled this daemon and was refused on the pairing token, so the app IS running ` +
116
+ `and instrumented (${ports} listening) — this is a SCOPE problem, not an install problem. ` +
117
+ 'This daemon was started outside the project (an IDE that registers the MCP server ' +
118
+ 'globally starts it in `/` or your home directory), so it has no `.reticle.json` to match ' +
119
+ 'the page against. Do NOT run `reticle init`: the config it would write is already correct ' +
120
+ 'somewhere else. Take a context you own instead with reticle_lease {action:"acquire", url, ' +
121
+ "projectId}, or restart the daemon from the app's own directory.",
122
+ };
123
+ }
106
124
  if (!facts.initialized && true !== facts.previouslyConnected) {
107
125
  return {
108
126
  action: NoSessionAction.RUN_INIT,
@@ -10,6 +10,8 @@
10
10
  * daemon that outlives the agent by hours has no business scanning ports it does not need.
11
11
  */
12
12
  import { probeDevServers, probeDevServerStates } from './dev-server-probe.js';
13
+ import { homedir } from 'node:os';
14
+ import { registeredElsewhere } from './registered-projects.js';
13
15
  import { explainNoSession } from './no-session-diagnosis.js';
14
16
  import { detectDevCommand } from './dev-command.js';
15
17
  import { nextActionFor, renderNextAction } from './no-session-next-action.js';
@@ -21,6 +23,7 @@ import { daemonsServingProjectElsewhere, splitBrainNote } from '../daemon/daemon
21
23
  import { stallUptime } from './stall-clock.js';
22
24
  import { probeDaemon } from '../mcp/mcp-proxy.js';
23
25
  import { findOccupiedSiblings } from '../cli/sibling-ports.js';
26
+ import { WS_CLOSE_REASON } from '../bridge/bridge.js';
24
27
  /** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
25
28
  const REFRESH_MS = 15_000;
26
29
  /** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
@@ -81,15 +84,32 @@ export function startNoSessionWatch(options) {
81
84
  return { initialized };
82
85
  const discovery = discoverProjectConfigs(directory);
83
86
  const elsewhere = discovery.found.filter((config) => config.directory !== directory);
84
- return 0 === elsewhere.length
85
- ? { initialized, searchedDirectories: discovery.searched }
86
- : {
87
+ if (elsewhere.length > 0) {
88
+ return {
87
89
  initialized,
88
90
  configsElsewhere: elsewhere.map((config) => ({
89
91
  directory: config.directory,
90
92
  ...(config.projectId === undefined ? {} : { projectId: config.projectId }),
91
93
  })),
92
94
  };
95
+ }
96
+ // The walk found nothing — which is the honest answer from a daemon standing at `/` or `$HOME`,
97
+ // where several IDEs start a GLOBALLY registered MCP server. There is nothing above `/` and no
98
+ // repo root at either, so the walk cannot reach a project it has nonetheless paired with before.
99
+ // The registry can. See registeredElsewhere: "I am standing in the wrong place" and "you never
100
+ // installed this" are opposite diagnoses with opposite fixes, and without this we gave the
101
+ // second one — six times, on projects that were already correctly instrumented.
102
+ const registered = registeredElsewhere(homedir(), directory);
103
+ if (registered.length > 0) {
104
+ return {
105
+ initialized,
106
+ configsElsewhere: registered.map((project) => ({
107
+ directory: project.directory,
108
+ ...(project.projectId === undefined ? {} : { projectId: project.projectId }),
109
+ })),
110
+ };
111
+ }
112
+ return { initialized, searchedDirectories: discovery.searched };
93
113
  };
94
114
  // Read at boot: the daemon's own identity does not change under it, and this is the key the
95
115
  // durable bit is stored under.
@@ -185,6 +205,19 @@ export function startNoSessionWatch(options) {
185
205
  refresh();
186
206
  const timer = setInterval(refresh, REFRESH_MS);
187
207
  timer.unref();
208
+ /**
209
+ * Did the most recent bridge-initiated close refuse a page on its token?
210
+ *
211
+ * The bridge records it (`noteClosure(WS_CLOSE_REASON.AUTH_FAILED)`) and nothing read it as a
212
+ * DIAGNOSIS. It is the one fact that proves an app is running and instrumented: only an SDK dials
213
+ * the bridge, so a refused hello means the wiring works and this daemon would not serve it.
214
+ *
215
+ * Called optionally because this watch is constructed against a structural slice of the manager,
216
+ * and several callers pass a double that predates `lastClosure`. A manager that cannot answer has
217
+ * recorded no refusal, which falls through to the behaviour that was there before -- the safe
218
+ * direction for a fact whose only job is to SUPPRESS an `init` suggestion.
219
+ */
220
+ const lastCloseWasAuthFailure = () => options.sessions.lastClosure?.()?.reason === WS_CLOSE_REASON.AUTH_FAILED;
188
221
  const nextAction = (scope) => {
189
222
  const split = splitBrain();
190
223
  return nextActionFor({
@@ -192,6 +225,9 @@ export function startNoSessionWatch(options) {
192
225
  initialized: scope.initialized,
193
226
  ...(scope.configsElsewhere === undefined ? {} : { configsElsewhere: scope.configsElsewhere }),
194
227
  previouslyConnected: connectedBefore(),
228
+ // Read when asked, like every other fact here: a page can dial at any moment, and a daemon
229
+ // that cached "nothing has been refused" at boot would keep saying so.
230
+ authRefused: lastCloseWasAuthFailure(),
195
231
  ...(split === undefined ? {} : { splitBrain: split }),
196
232
  listening,
197
233
  // Read when asked, like everything else here: a `package.json` can gain a dev script, and a
@@ -236,6 +272,13 @@ export function startNoSessionWatch(options) {
236
272
  const departed = options.sessions.lastDeparted();
237
273
  return departed === undefined ? false : (options.wasReapedLease?.(departed) ?? false);
238
274
  })(),
275
+ // The URL of the session that went away, when the tombstone still holds it. Optional: a
276
+ // stub SessionManager in tests has no lastKnown, and a daemon that never saw a session
277
+ // has none to name.
278
+ ...(() => {
279
+ const known = options.sessions.lastKnown?.();
280
+ return undefined === known ? {} : { lastKnownUrl: known.url };
281
+ })(),
239
282
  // How long this daemon has been waiting with no app. The diagnosis uses it to surface
240
283
  // "install never finished" — the same condition telemetry already knows about.
241
284
  ...(() => {
@@ -0,0 +1,54 @@
1
+ /** One project this machine has paired before, in the shape the no-session facts already carry. */
2
+ export interface RegisteredProject {
3
+ directory: string;
4
+ projectId?: string;
5
+ }
6
+ /**
7
+ * Projects this machine has paired before, read from `~/.reticle/projects.json`.
8
+ *
9
+ * The no-session diagnosis learns where projects are by WALKING: up from the daemon's cwd, then
10
+ * across the declared workspaces of whatever repo root it lands in. That walk is the right first
11
+ * answer and it has one blind spot, which happens to be the most reported install problem we have.
12
+ *
13
+ * Several IDEs register the MCP server GLOBALLY, so the daemon starts with a cwd of `/` or `$HOME`.
14
+ * From there the walk finds nothing — there is nothing above `/`, and no repo root at either — so
15
+ * `configsElsewhere` comes back empty and the diagnosis falls through to the branches that reason
16
+ * from "no config anywhere", whose advice is `reticle init`. Reported six separate times, on a
17
+ * project that was already correctly instrumented; one reporter burned a turn re-running `init` over
18
+ * a working config while `doctor`, run from the app directory, said `project ✓ wired here`.
19
+ *
20
+ * The registry is the evidence the walk cannot reach: every project that has ever paired with this
21
+ * machine is in it, with its directory. A daemon that cannot SEE a project can still KNOW of it, and
22
+ * "I am standing in the wrong place" is a completely different diagnosis from "you never installed
23
+ * this" — opposite fixes, and we were giving the second one.
24
+ *
25
+ * Read fresh rather than cached: a project can pair for the first time while this daemon is up, and
26
+ * a stale empty read would keep the wrong diagnosis for the life of the process. Fails soft to
27
+ * empty, because a diagnosis is the last thing that should throw.
28
+ */
29
+ export declare function registeredProjects(home: string): RegisteredProject[];
30
+ /**
31
+ * Is this daemon standing somewhere a project could never live?
32
+ *
33
+ * THE NARROWING THAT MAKES THIS SAFE, and it is not optional. An empty config walk has two causes:
34
+ * the daemon is parked outside every project (the global-registration case this fixes), or it is
35
+ * standing in a real directory that genuinely has not been wired yet (a new project, where
36
+ * `reticle init` is exactly the right advice). Both look identical from the walk.
37
+ *
38
+ * Consulting the registry for BOTH would break the second: any developer machine has registered
39
+ * projects, so a brand-new unwired project would be told "your config is elsewhere, restart the
40
+ * daemon there" instead of "run init". That trades the reported false negative for an unreported
41
+ * one, on the more common path — a bad trade, and caught here by two existing tests going red.
42
+ *
43
+ * The filesystem root and the user's home directory are the discriminator. Neither is ever an app
44
+ * directory, and both are exactly where an IDE's global MCP registration starts a daemon. Anywhere
45
+ * else, an empty walk still means what it always meant.
46
+ */
47
+ export declare function isUnscopedRoot(directory: string, home: string): boolean;
48
+ /**
49
+ * The registered projects that are NOT the directory this daemon is standing in.
50
+ *
51
+ * Same rule the config walk applies: a project at the daemon's own cwd is not "elsewhere", and
52
+ * reporting it as such would tell a correctly-scoped daemon to go and look somewhere else.
53
+ */
54
+ export declare function registeredElsewhere(home: string, directory: string): RegisteredProject[];
@@ -0,0 +1,73 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { join, parse, resolve } from 'node:path';
3
+ import { PROJECT_REGISTRY_FILE, ReticleDir, parseProjectRegistry, projectCandidates, } from '@reticlehq/core';
4
+ /**
5
+ * Projects this machine has paired before, read from `~/.reticle/projects.json`.
6
+ *
7
+ * The no-session diagnosis learns where projects are by WALKING: up from the daemon's cwd, then
8
+ * across the declared workspaces of whatever repo root it lands in. That walk is the right first
9
+ * answer and it has one blind spot, which happens to be the most reported install problem we have.
10
+ *
11
+ * Several IDEs register the MCP server GLOBALLY, so the daemon starts with a cwd of `/` or `$HOME`.
12
+ * From there the walk finds nothing — there is nothing above `/`, and no repo root at either — so
13
+ * `configsElsewhere` comes back empty and the diagnosis falls through to the branches that reason
14
+ * from "no config anywhere", whose advice is `reticle init`. Reported six separate times, on a
15
+ * project that was already correctly instrumented; one reporter burned a turn re-running `init` over
16
+ * a working config while `doctor`, run from the app directory, said `project ✓ wired here`.
17
+ *
18
+ * The registry is the evidence the walk cannot reach: every project that has ever paired with this
19
+ * machine is in it, with its directory. A daemon that cannot SEE a project can still KNOW of it, and
20
+ * "I am standing in the wrong place" is a completely different diagnosis from "you never installed
21
+ * this" — opposite fixes, and we were giving the second one.
22
+ *
23
+ * Read fresh rather than cached: a project can pair for the first time while this daemon is up, and
24
+ * a stale empty read would keep the wrong diagnosis for the life of the process. Fails soft to
25
+ * empty, because a diagnosis is the last thing that should throw.
26
+ */
27
+ export function registeredProjects(home) {
28
+ try {
29
+ const path = join(home, ReticleDir.ROOT, PROJECT_REGISTRY_FILE);
30
+ if (!existsSync(path))
31
+ return [];
32
+ const registry = parseProjectRegistry(JSON.parse(readFileSync(path, 'utf8')));
33
+ return projectCandidates(registry).map((candidate) => ({
34
+ directory: candidate.directory,
35
+ projectId: candidate.projectId,
36
+ }));
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ }
42
+ /**
43
+ * Is this daemon standing somewhere a project could never live?
44
+ *
45
+ * THE NARROWING THAT MAKES THIS SAFE, and it is not optional. An empty config walk has two causes:
46
+ * the daemon is parked outside every project (the global-registration case this fixes), or it is
47
+ * standing in a real directory that genuinely has not been wired yet (a new project, where
48
+ * `reticle init` is exactly the right advice). Both look identical from the walk.
49
+ *
50
+ * Consulting the registry for BOTH would break the second: any developer machine has registered
51
+ * projects, so a brand-new unwired project would be told "your config is elsewhere, restart the
52
+ * daemon there" instead of "run init". That trades the reported false negative for an unreported
53
+ * one, on the more common path — a bad trade, and caught here by two existing tests going red.
54
+ *
55
+ * The filesystem root and the user's home directory are the discriminator. Neither is ever an app
56
+ * directory, and both are exactly where an IDE's global MCP registration starts a daemon. Anywhere
57
+ * else, an empty walk still means what it always meant.
58
+ */
59
+ export function isUnscopedRoot(directory, home) {
60
+ const at = resolve(directory);
61
+ return at === resolve(home) || at === parse(at).root;
62
+ }
63
+ /**
64
+ * The registered projects that are NOT the directory this daemon is standing in.
65
+ *
66
+ * Same rule the config walk applies: a project at the daemon's own cwd is not "elsewhere", and
67
+ * reporting it as such would tell a correctly-scoped daemon to go and look somewhere else.
68
+ */
69
+ export function registeredElsewhere(home, directory) {
70
+ if (!isUnscopedRoot(directory, home))
71
+ return [];
72
+ return registeredProjects(home).filter((project) => project.directory !== directory);
73
+ }
@@ -24,7 +24,7 @@ export interface ReviewMark {
24
24
  /** Tests only — assertions on `m1` must not depend on which test ran first. */
25
25
  export declare function resetMarkIdsForTest(): void;
26
26
  /** What `resolve` actually did, including WHICH mark — so a caller can verify it hit the right one. */
27
- export interface ResolveOutcome {
27
+ interface ResolveOutcome {
28
28
  resolved: boolean;
29
29
  id: string;
30
30
  /** The retired mark's note. Absent when nothing was resolved. */
@@ -56,3 +56,4 @@ export declare class ReviewStore {
56
56
  */
57
57
  resolveDetail(id: string): ResolveOutcome;
58
58
  }
59
+ export {};
@@ -1,4 +1,4 @@
1
- import { BrowserBrand } from '@reticlehq/core';
1
+ import { BrowserBrand, type ReticleEvent } from '@reticlehq/core';
2
2
  import type { Session } from './session.js';
3
3
  /**
4
4
  * The health block spliced onto act/assert results. Defined here rather than in session.ts: this is
@@ -10,7 +10,58 @@ export interface SessionHealth {
10
10
  focused: boolean;
11
11
  /** present only when hidden/throttled — points at the `reticle drive` escape hatch. */
12
12
  recommendation?: string;
13
+ /**
14
+ * How long the OLDEST unanswered request has been in flight, when that is long enough to matter.
15
+ *
16
+ * Present only past `PENDING_NAVIGATION_NOTICE_MS`, so a healthy session costs nothing. Both
17
+ * reporters on the hung-server issue said this alone would have been enough for them: one sat on
18
+ * an RSC fetch stuck pending for 170+ seconds across many calls, the other on a dev server that
19
+ * accepted connections and never sent a body. Reticle held the evidence both times and the truth
20
+ * was eventually established with `curl`.
21
+ */
22
+ pendingNavigationMs?: number;
23
+ /**
24
+ * Present only when the page's SDK version differs from the daemon's — see version-skew.ts.
25
+ *
26
+ * On the HEALTH block, not only on `reticle_sessions`, because skew causes SILENT action failures
27
+ * and the fields it contradicts ride on the act result. Reported from the field: `act_and_wait`
28
+ * returning `dispatched: true, settled: true, domMutatedWithin: 12-40ms` while React state never
29
+ * changed, across eight attempts and three interaction strategies. The banner existed the whole
30
+ * time — on `reticle_sessions` and `reticle_lease`, which is not the surface an agent reads on
31
+ * every call.
32
+ */
33
+ versionSkew?: string;
13
34
  }
35
+ /**
36
+ * How long a request may be in flight before it is worth reporting on session health.
37
+ *
38
+ * A drive always has requests in the air, so a low bar puts a scary number on every healthy session
39
+ * and trains agents to ignore the field — the same way a guard that fires on routine controls trains
40
+ * them to pass `confirmDangerous` reflexively.
41
+ *
42
+ * 10s, and it is a HEURISTIC with no clean separation available: "a slow endpoint" and "a server
43
+ * that will never answer" are the same observation until one of them finishes. The number is chosen
44
+ * to sit between the two things actually measured, not derived from anything.
45
+ *
46
+ * Below it: this repo's own battery runs its API with `REFLECT_MS=6000` on purpose, and at the 5s
47
+ * this first shipped with, a healthy app with one slow route was permanently non-nominal and carried
48
+ * a scary number on every result.
49
+ *
50
+ * Above it: both reported wedges — 10.4s on one, 170s+ on the other. 10.4s clears this by 400ms,
51
+ * which is thin, and an app with an 8-second endpoint will not be warned about a wedge until 10s.
52
+ * That is the trade: an over-firing field gets ignored, and a field nobody reads catches nothing.
53
+ */
54
+ export declare const PENDING_NAVIGATION_NOTICE_MS = 10000;
55
+ /**
56
+ * The age of the oldest request that STARTED and never completed, or undefined when there is none
57
+ * worth reporting.
58
+ *
59
+ * Matched on request id, the way `detectHungRequests` and the route oracle's `unansweredIn` do: a
60
+ * NET_PENDING with no NET_REQUEST carrying the same id. Three readings of one fact is two too many,
61
+ * but they read different windows — this one is the whole session, which is the point: a wedge that
62
+ * began before the current action is exactly the case a per-window reading cannot see.
63
+ */
64
+ export declare function pendingNavigationMs(events: readonly ReticleEvent[], nowMs: number): number | undefined;
14
65
  /** The evidence-completeness block spliced onto observe/network/console results. */
15
66
  interface BufferEnvelope {
16
67
  buffer?: {
@@ -49,10 +100,20 @@ export declare function refuseIfThrottled(session: Session, refuse: unknown): vo
49
100
  * Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
50
101
  * returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
51
102
  * generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
103
+ *
104
+ * The sentence FOLLOWS the field, and does not decide for itself. `annotateThrottledMiss`
105
+ * (predicate.ts) owns the question of whether this failure was reached by not having seen
106
+ * something, which is the only reading a starved tab casts doubt on; it stamps
107
+ * `THROTTLED_STARVED_NOTE` when it was. Deciding again here got two answers to one question:
108
+ * an `absent: true` assertion that MATCHED 13 elements was graded honestly in the field an agent
109
+ * gates on, and told in prose that the tab may never have rendered. It also doubled up on a failure
110
+ * that already carried a more specific reason (an unreadable locator, a superseded window), where
111
+ * the concrete diagnosis is the one that should lead.
52
112
  */
53
113
  export declare function annotateStarvedFailure<V extends {
54
114
  pass?: boolean;
55
115
  failureReason?: string;
116
+ inconclusive?: string;
56
117
  }>(session: Session, verdict: V): V;
57
118
  /** The page's own visibility/runtime report, narrowed out of an untrusted PAGE_HEALTH payload. */
58
119
  interface HealthReport {