@reticlehq/server 2.11.0 → 2.13.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 (513) hide show
  1. package/SKILL.md +93 -111
  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 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -10,14 +10,17 @@
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 { diagnoseNoSession } from './no-session-diagnosis.js';
13
+ import { explainNoSession } from './no-session-diagnosis.js';
14
14
  import { detectDevCommand } from './dev-command.js';
15
15
  import { nextActionFor, renderNextAction } from './no-session-next-action.js';
16
16
  import { readProjectFramework, readProjectId, readProjectPort } from '../cli/cli-port.js';
17
17
  import { discoverProjectConfigs } from '../cli/config-discovery.js';
18
18
  import { hasProjectConnectedBefore, rememberConnected } from './connection-memory.js';
19
- import { reticleStateHome } from '../daemon/daemon.js';
19
+ import { isAlive, reticleStateHome } from '../daemon/daemon.js';
20
+ import { daemonsServingProjectElsewhere, splitBrainNote } from '../daemon/daemon-resolve.js';
20
21
  import { stallUptime } from './stall-clock.js';
22
+ import { probeDaemon } from '../mcp/mcp-proxy.js';
23
+ import { findOccupiedSiblings } from '../cli/sibling-ports.js';
21
24
  /** Slow enough to be free, fast enough that a dev server started 15s ago is already reflected. */
22
25
  const REFRESH_MS = 15_000;
23
26
  /** The URL an auto-attach opens. `localhost` for the same reason the probe uses it: both stacks. */
@@ -56,6 +59,7 @@ export function startNoSessionWatch(options) {
56
59
  * `options.probe` returns only the serving set, so this stays empty for injected probes.
57
60
  */
58
61
  let slowListeners = [];
62
+ let siblingListeners = [];
59
63
  let running = false;
60
64
  /** Ports auto-attach has already spent its one attempt on. Bounded: never a loop, never a retry. */
61
65
  const attempted = new Set();
@@ -66,6 +70,27 @@ export function startNoSessionWatch(options) {
66
70
  // is routinely written by `init` AFTER this daemon started, so re-read rather than cache. See the
67
71
  // `initialized` comment below, which this shares.
68
72
  const isWired = () => options.initialized || readProjectId(directory) !== undefined;
73
+ /**
74
+ * Resolve the daemon's current project scope once for both halves of a sessions response.
75
+ *
76
+ * This remains a live read because `init` commonly writes the config after the daemon starts.
77
+ */
78
+ const projectScopeFacts = () => {
79
+ const initialized = isWired();
80
+ if (initialized)
81
+ return { initialized };
82
+ const discovery = discoverProjectConfigs(directory);
83
+ const elsewhere = discovery.found.filter((config) => config.directory !== directory);
84
+ return 0 === elsewhere.length
85
+ ? { initialized, searchedDirectories: discovery.searched }
86
+ : {
87
+ initialized,
88
+ configsElsewhere: elsewhere.map((config) => ({
89
+ directory: config.directory,
90
+ ...(config.projectId === undefined ? {} : { projectId: config.projectId }),
91
+ })),
92
+ };
93
+ };
69
94
  // Read at boot: the daemon's own identity does not change under it, and this is the key the
70
95
  // durable bit is stored under.
71
96
  const stateDir = options.stateDir ?? reticleStateHome();
@@ -78,6 +103,19 @@ export function startNoSessionWatch(options) {
78
103
  * is the same over-confident claim this whole file exists to remove, pointing the other way.
79
104
  */
80
105
  const connectedBefore = () => hasProjectConnectedBefore(stateDir, options.port, readProjectId(directory));
106
+ /**
107
+ * The split brain, asked fresh every time like everything else here.
108
+ *
109
+ * Cheap — a directory listing of `~/.reticle` and a few small JSON reads — and only ever reached
110
+ * on a daemon with no session, which is the state this whole file exists for. Reading it once at
111
+ * boot would miss the ordinary case entirely: the app connects to the other daemon SECONDS after
112
+ * this one starts, which is precisely the window the agent then spends being told to start a dev
113
+ * server that is already running.
114
+ */
115
+ const splitBrain = () => {
116
+ const projectId = readProjectId(directory);
117
+ return splitBrainNote(options.port, daemonsServingProjectElsewhere(projectId, options.port, stateDir, isAlive, (port) => hasProjectConnectedBefore(stateDir, port, projectId)));
118
+ };
81
119
  // Every path that registers a session goes through SessionManager.add, so this is the one hook
82
120
  // that makes the bit durable. Recorded per port + projectId so a shared 4400 cannot make one
83
121
  // project's success into evidence about another's.
@@ -129,6 +167,12 @@ export function startNoSessionWatch(options) {
129
167
  : probe())
130
168
  .then(async (ports) => {
131
169
  listening = ports;
170
+ siblingListeners =
171
+ options.occupiedSiblings !== undefined
172
+ ? await options.occupiedSiblings()
173
+ : options.probe === undefined
174
+ ? await findOccupiedSiblings(options.port, probeDaemon)
175
+ : [];
132
176
  await autoAttach(ports);
133
177
  })
134
178
  .catch(() => {
@@ -141,81 +185,87 @@ export function startNoSessionWatch(options) {
141
185
  refresh();
142
186
  const timer = setInterval(refresh, REFRESH_MS);
143
187
  timer.unref();
144
- const nextAction = () => nextActionFor({
145
- everConnected: options.sessions.everConnected(),
146
- initialized: isWired(),
147
- previouslyConnected: connectedBefore(),
148
- listening,
149
- // Read when asked, like everything else here: a `package.json` can gain a dev script, and a
150
- // daemon that cached "there is none" at boot would keep saying so for the rest of the day.
151
- dev: detectDevCommand(directory),
188
+ const nextAction = (scope) => {
189
+ const split = splitBrain();
190
+ return nextActionFor({
191
+ everConnected: options.sessions.everConnected(),
192
+ initialized: scope.initialized,
193
+ ...(scope.configsElsewhere === undefined ? {} : { configsElsewhere: scope.configsElsewhere }),
194
+ previouslyConnected: connectedBefore(),
195
+ ...(split === undefined ? {} : { splitBrain: split }),
196
+ listening,
197
+ // Read when asked, like everything else here: a `package.json` can gain a dev script, and a
198
+ // daemon that cached "there is none" at boot would keep saying so for the rest of the day.
199
+ dev: detectDevCommand(directory),
200
+ });
201
+ };
202
+ options.sessions.setNoSessionNextAction(() => nextAction(projectScopeFacts()));
203
+ // ONE call for both registrations below. The prose and the branch code have to come from the
204
+ // same evaluation or they can describe different branches - the facts are read when asked, so two
205
+ // calls a moment apart can genuinely disagree (#615).
206
+ const explain = () => {
207
+ const scope = projectScopeFacts();
208
+ return explainNoSession({
209
+ everConnected: options.sessions.everConnected(),
210
+ // Read WHEN ASKED, for the same reason `projectPort` below is: `.reticle.json` is routinely
211
+ // written by `init` after this daemon started — that is the ordinary first-install order — and
212
+ // the boot-time answer is then permanently stale. Reported from the field as `reticle status`
213
+ // saying the project had never been through `init` about a project whose config named its
214
+ // framework and its projectId, and whose real problem was a dev server older than the plugin.
215
+ // The boot value still counts: it is the one the daemon scoped its sessions with.
216
+ ...scope,
217
+ listening,
218
+ slowListeners,
219
+ port: options.port,
220
+ // The directory `initialized` was decided in. Named in the message because "there is no
221
+ // `.reticle.json`" is a claim about ONE directory, and a reader standing somewhere else
222
+ // cannot tell whether it is a claim about their app at all.
223
+ directory,
224
+ // The one fact that outranks every absence below it, and the reason a fresh daemon stopped
225
+ // claiming that an install which has demonstrably worked has never worked.
226
+ previouslyConnected: connectedBefore(),
227
+ // Ranks the causes. Read when asked, like the rest: `init` writes this file after the daemon
228
+ // starts on an ordinary first install.
229
+ ...(() => {
230
+ const framework = readProjectFramework(directory);
231
+ return framework === undefined ? {} : { framework };
232
+ })(),
233
+ // Decided from the session that actually went away, not from a lifetime tally: the lease
234
+ // sentence is only right when the thing that vanished WAS a lease.
235
+ leaseExpired: (() => {
236
+ const departed = options.sessions.lastDeparted();
237
+ return departed === undefined ? false : (options.wasReapedLease?.(departed) ?? false);
238
+ })(),
239
+ // How long this daemon has been waiting with no app. The diagnosis uses it to surface
240
+ // "install never finished" — the same condition telemetry already knows about.
241
+ ...(() => {
242
+ const upMs = stallUptime(Date.now());
243
+ return upMs === undefined ? {} : { daemonUpMs: upMs };
244
+ })(),
245
+ // Read here rather than at boot: `.reticle.json` can be written by `init` after this daemon
246
+ // started, which is the ordinary first-install order, and a port cached from before it existed
247
+ // would make the daemon confidently report no mismatch on the one run where there is one.
248
+ ...(() => {
249
+ const configured = readProjectPort(directory);
250
+ return configured === undefined ? {} : { projectPort: configured };
251
+ })(),
252
+ ...(0 === siblingListeners.length ? {} : { siblingListeners }),
253
+ });
254
+ };
255
+ options.sessions.setNoSessionHint(() => {
256
+ const scope = projectScopeFacts();
257
+ return (explain().message +
258
+ // Prose for the human, then the literal command for the agent. Both consume the same scope
259
+ // facts so a discovered workspace config cannot become an `init` recommendation below it.
260
+ ` ${renderNextAction(nextAction(scope))}` +
261
+ (attachFailure ?? ''));
152
262
  });
153
- options.sessions.setNoSessionNextAction(nextAction);
154
- options.sessions.setNoSessionHint(() => diagnoseNoSession({
155
- everConnected: options.sessions.everConnected(),
156
- // Read WHEN ASKED, for the same reason `projectPort` below is: `.reticle.json` is routinely
157
- // written by `init` after this daemon started — that is the ordinary first-install order — and
158
- // the boot-time answer is then permanently stale. Reported from the field as `reticle status`
159
- // saying the project had never been through `init` about a project whose config named its
160
- // framework and its projectId, and whose real problem was a dev server older than the plugin.
161
- // The boot value still counts: it is the one the daemon scoped its sessions with.
162
- initialized: isWired(),
163
- listening,
164
- slowListeners,
165
- port: options.port,
166
- // The directory `initialized` was decided in. Named in the message because "there is no
167
- // `.reticle.json`" is a claim about ONE directory, and a reader standing somewhere else
168
- // cannot tell whether it is a claim about their app at all.
169
- directory,
170
- // Where the config actually is, when it is not here — and where we looked, when it is
171
- // nowhere. Computed only on the branch that needs it: a wired daemon has its answer already,
172
- // and a workspace scan on every hint would be a directory walk per tool refusal.
173
- ...(isWired()
174
- ? {}
175
- : (() => {
176
- const discovery = discoverProjectConfigs(directory);
177
- const elsewhere = discovery.found.filter((c) => c.directory !== directory);
178
- return 0 === elsewhere.length
179
- ? { searchedDirectories: discovery.searched }
180
- : {
181
- configsElsewhere: elsewhere.map((c) => ({
182
- directory: c.directory,
183
- ...(c.projectId === undefined ? {} : { projectId: c.projectId }),
184
- })),
185
- };
186
- })()),
187
- // The one fact that outranks every absence below it, and the reason a fresh daemon stopped
188
- // claiming that an install which has demonstrably worked has never worked.
189
- previouslyConnected: connectedBefore(),
190
- // Ranks the causes. Read when asked, like the rest: `init` writes this file after the daemon
191
- // starts on an ordinary first install.
192
- ...(() => {
193
- const framework = readProjectFramework(directory);
194
- return framework === undefined ? {} : { framework };
195
- })(),
196
- leaseExpired: (options.reapedLeases?.() ?? 0) > 0,
197
- // How long this daemon has been waiting with no app. The diagnosis uses it to surface
198
- // "install never finished" — the same condition telemetry already knows about.
199
- ...(() => {
200
- const upMs = stallUptime(Date.now());
201
- return upMs === undefined ? {} : { daemonUpMs: upMs };
202
- })(),
203
- // Read here rather than at boot: `.reticle.json` can be written by `init` after this daemon
204
- // started, which is the ordinary first-install order, and a port cached from before it existed
205
- // would make the daemon confidently report no mismatch on the one run where there is one.
206
- ...(() => {
207
- const configured = readProjectPort(directory);
208
- return configured === undefined ? {} : { projectPort: configured };
209
- })(),
210
- }) +
211
- // Prose for the human, then the literal command for the agent. Appended rather than folded
212
- // into the diagnosis so that file stays pure and its cases stay independently pinned.
213
- ` ${renderNextAction(nextAction())}` +
214
- (attachFailure ?? ''));
263
+ options.sessions.setNoSessionReason(() => explain().reason);
215
264
  return () => {
216
265
  clearInterval(timer);
217
266
  options.sessions.setConnectionRecorder(undefined);
218
267
  options.sessions.setNoSessionHint(undefined);
268
+ options.sessions.setNoSessionReason(undefined);
219
269
  options.sessions.setNoSessionNextAction(undefined);
220
270
  };
221
271
  }
@@ -226,8 +276,8 @@ export function startNoSessionWatch(options) {
226
276
  * adjacent blocks in the bootstrap.
227
277
  */
228
278
  export function wireSessionScope(sessions, activeProjectId, port,
229
- /** Reader for the pool's aged-out-lease count; omitted when this daemon runs no pool. */
230
- reapedLeases,
279
+ /** Asks the pool whether a session id was a lease it aged out; omitted when there is no pool. */
280
+ wasReapedLease,
231
281
  /** Opens a URL in a Reticle-owned browser (the pool). Omitted ⇒ auto-attach is off. */
232
282
  attach) {
233
283
  if (activeProjectId !== undefined)
@@ -236,7 +286,7 @@ attach) {
236
286
  sessions,
237
287
  port,
238
288
  initialized: activeProjectId !== undefined,
239
- ...(reapedLeases === undefined ? {} : { reapedLeases }),
289
+ ...(wasReapedLease === undefined ? {} : { wasReapedLease }),
240
290
  ...(attach === undefined ? {} : { attach }),
241
291
  });
242
292
  }
@@ -50,7 +50,10 @@ export function estimateTokens(text) {
50
50
  }
51
51
  export function sizeCost(payload) {
52
52
  const json = JSON.stringify(payload) ?? '';
53
- return { bytes: json.length, tokens: estimateTokens(json) };
53
+ // Actual UTF-8 wire bytes, not json.length (UTF-16 code units) — the field is labeled `bytes`
54
+ // and drives the large-snapshot recommendation, so a multibyte-heavy payload was under-counted.
55
+ const bytes = Buffer.byteLength(json, 'utf8');
56
+ return { bytes, tokens: estimateTokens(json) };
54
57
  }
55
58
  /**
56
59
  * Attach a `cost` size preview to a read result. Pure: the cost is computed over the result BEFORE
@@ -11,6 +11,16 @@ import type { CommandResult } from '@reticlehq/core';
11
11
  */
12
12
  export declare class CommandTimeoutError extends Error {
13
13
  }
14
+ /**
15
+ * The transport under this command was displaced by a newer connection claiming the same id.
16
+ *
17
+ * Typed, not a string to match on, because the two failures need OPPOSITE handling and the only
18
+ * thing separating them used to be the wording of a message. A read can simply be re-asked on the
19
+ * successor; a WRITE cannot — nobody can prove it did not land — so the honest answer is that the
20
+ * outcome went unobserved, which is a verdict this engine already has a word for.
21
+ */
22
+ export declare class SessionReplacedError extends Error {
23
+ }
14
24
  export declare class PendingCommands {
15
25
  #private;
16
26
  /** Next correlation id. Monotonic per session, so a reply can never match an earlier command. */
@@ -20,5 +30,5 @@ export declare class PendingCommands {
20
30
  /** Resolve the command this reply belongs to. Unknown ids are ignored, never thrown on. */
21
31
  settle(result: CommandResult): void;
22
32
  /** Reject everything still in flight — used on disconnect, so no caller waits on a dead socket. */
23
- rejectAll(reason: string): void;
33
+ rejectAll(reason: string, replaced?: boolean): void;
24
34
  }
@@ -10,6 +10,16 @@
10
10
  */
11
11
  export class CommandTimeoutError extends Error {
12
12
  }
13
+ /**
14
+ * The transport under this command was displaced by a newer connection claiming the same id.
15
+ *
16
+ * Typed, not a string to match on, because the two failures need OPPOSITE handling and the only
17
+ * thing separating them used to be the wording of a message. A read can simply be re-asked on the
18
+ * successor; a WRITE cannot — nobody can prove it did not land — so the honest answer is that the
19
+ * outcome went unobserved, which is a verdict this engine already has a word for.
20
+ */
21
+ export class SessionReplacedError extends Error {
22
+ }
13
23
  export class PendingCommands {
14
24
  #pending = new Map();
15
25
  #seq = 0;
@@ -39,10 +49,10 @@ export class PendingCommands {
39
49
  pending.resolve(result);
40
50
  }
41
51
  /** Reject everything still in flight — used on disconnect, so no caller waits on a dead socket. */
42
- rejectAll(reason) {
52
+ rejectAll(reason, replaced = false) {
43
53
  for (const [id, pending] of this.#pending) {
44
54
  clearTimeout(pending.timer);
45
- pending.reject(new Error(reason));
55
+ pending.reject(replaced ? new SessionReplacedError(reason) : new Error(reason));
46
56
  this.#pending.delete(id);
47
57
  }
48
58
  }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Resolve a session, or spend the caller's stated budget waiting for one to arrive.
3
+ *
4
+ * Every tool that takes a `timeout_ms` used to resolve the session as its FIRST statement, so an
5
+ * empty session map threw before the budget was ever consulted. A caller that said "wait up to
6
+ * thirty seconds for the app" was refused in under a millisecond, and the only way to actually wait
7
+ * was to poll `reticle_sessions` blind.
8
+ *
9
+ * That is the wrong answer to the commonest situation in the product. The moment an agent has run
10
+ * `init` and restarted the dev server is exactly the moment there is no session yet and one is about
11
+ * to appear — and `no_session` is the refusal agents hit most. An agent that gets refused rarely
12
+ * retries; it concludes and moves on. Spending the budget it already asked for turns the most common
13
+ * dead end into a wait that ends in a verdict.
14
+ *
15
+ * Deliberately narrow in two ways.
16
+ *
17
+ * Only the EMPTY case waits. A `sessionId` that names a session which is not connected while others
18
+ * are is a different error with a different fix, and waiting on it would hide a typo behind a
19
+ * timeout.
20
+ *
21
+ * The original error is what gets thrown on expiry, never a generic timeout message. That error
22
+ * carries the daemon's whole diagnosis — the port scan, the next action, the literal command — and
23
+ * it is the single most valuable string this product emits to an agent that has nothing connected.
24
+ * Replacing it with "timed out" would trade the answer for a stopwatch reading.
25
+ */
26
+ /** Just the part of the session manager this needs, so a test does not have to build one. */
27
+ export interface ResolvableSessions<S> {
28
+ resolve: (sessionId?: string) => S;
29
+ count: () => number;
30
+ }
31
+ export declare function resolveSessionWithin<S>(sessions: ResolvableSessions<S>, sessionId: string | undefined, timeoutMs: number, deps: {
32
+ now: () => number;
33
+ sleep: (ms: number) => Promise<void>;
34
+ }): Promise<S>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Resolve a session, or spend the caller's stated budget waiting for one to arrive.
3
+ *
4
+ * Every tool that takes a `timeout_ms` used to resolve the session as its FIRST statement, so an
5
+ * empty session map threw before the budget was ever consulted. A caller that said "wait up to
6
+ * thirty seconds for the app" was refused in under a millisecond, and the only way to actually wait
7
+ * was to poll `reticle_sessions` blind.
8
+ *
9
+ * That is the wrong answer to the commonest situation in the product. The moment an agent has run
10
+ * `init` and restarted the dev server is exactly the moment there is no session yet and one is about
11
+ * to appear — and `no_session` is the refusal agents hit most. An agent that gets refused rarely
12
+ * retries; it concludes and moves on. Spending the budget it already asked for turns the most common
13
+ * dead end into a wait that ends in a verdict.
14
+ *
15
+ * Deliberately narrow in two ways.
16
+ *
17
+ * Only the EMPTY case waits. A `sessionId` that names a session which is not connected while others
18
+ * are is a different error with a different fix, and waiting on it would hide a typo behind a
19
+ * timeout.
20
+ *
21
+ * The original error is what gets thrown on expiry, never a generic timeout message. That error
22
+ * carries the daemon's whole diagnosis — the port scan, the next action, the literal command — and
23
+ * it is the single most valuable string this product emits to an agent that has nothing connected.
24
+ * Replacing it with "timed out" would trade the answer for a stopwatch reading.
25
+ */
26
+ /** How often to re-ask. A session appearing is an out-of-band event; this is a plain poll. */
27
+ const POLL_MS = 250;
28
+ export async function resolveSessionWithin(sessions, sessionId, timeoutMs, deps) {
29
+ try {
30
+ return sessions.resolve(sessionId);
31
+ }
32
+ catch (first) {
33
+ // Nothing to wait FOR unless the map is empty, and nothing to wait WITH unless a budget was
34
+ // given. Either way the caller gets the same error it would have got before.
35
+ if (timeoutMs <= 0 || sessions.count() > 0)
36
+ throw first;
37
+ const deadline = deps.now() + timeoutMs;
38
+ for (;;) {
39
+ const remaining = deadline - deps.now();
40
+ if (remaining <= 0)
41
+ throw first;
42
+ await deps.sleep(Math.min(POLL_MS, remaining));
43
+ try {
44
+ return sessions.resolve(sessionId);
45
+ }
46
+ catch {
47
+ // Still nothing. Keep the FIRST error: it was raised when the daemon had most recently
48
+ // computed its diagnosis, and re-throwing the newest one would report the same fact with a
49
+ // staler explanation.
50
+ }
51
+ }
52
+ }
53
+ }
@@ -45,6 +45,15 @@ export declare function healthEnvelope(session: Session): HealthEnvelope;
45
45
  * warn-only so background testing never breaks.
46
46
  */
47
47
  export declare function refuseIfThrottled(session: Session, refuse: unknown): void;
48
+ /**
49
+ * Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
50
+ * returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
51
+ * generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
52
+ */
53
+ export declare function annotateStarvedFailure<V extends {
54
+ pass?: boolean;
55
+ failureReason?: string;
56
+ }>(session: Session, verdict: V): V;
48
57
  /** The page's own visibility/runtime report, narrowed out of an untrusted PAGE_HEALTH payload. */
49
58
  interface HealthReport {
50
59
  hidden: boolean | undefined;
@@ -35,6 +35,29 @@ export function refuseIfThrottled(session, refuse) {
35
35
  throw new Error(`refusing to act: ${THROTTLED_WARNING}`);
36
36
  }
37
37
  }
38
+ /**
39
+ * The sentence appended to a FAILED predicate verdict waited out on a throttled tab.
40
+ *
41
+ * A starved tab and a missing element read identically as a bare near-miss: after a hard reload a
42
+ * backgrounded tab can sit on its loading state forever (hydration starved, nothing painted), and
43
+ * `wait_for { text }` timing out there looks exactly like "the code did not render" (#521). The
44
+ * health envelope already rode alongside saying `throttled:true`, but nothing connected it to the
45
+ * verdict an agent actually gates on. Appended, not replacing: the original diagnosis still leads,
46
+ * and on a healthy tab the failure says exactly what it always said.
47
+ */
48
+ const STARVED_WAIT_NOTE = ' (the tab was throttled during this wait, so starvation can look exactly like absence: bring it to the front and re-check before concluding this is genuinely missing)';
49
+ /**
50
+ * Suffix the starvation note onto a FAILED predicate verdict when the tab is throttled. A pass is
51
+ * returned untouched — starvation is context for a failure, not a reason to doubt a hold. Pure and
52
+ * generic over the verdict shape so assert/wait_for/act_and_wait share one rule.
53
+ */
54
+ export function annotateStarvedFailure(session, verdict) {
55
+ if (true === verdict.pass || verdict.failureReason === undefined)
56
+ return verdict;
57
+ if (true !== session.throttled())
58
+ return verdict;
59
+ return { ...verdict, failureReason: `${verdict.failureReason}${STARVED_WAIT_NOTE}` };
60
+ }
38
61
  /**
39
62
  * Narrow a PAGE_HEALTH payload. Every field is optional on the wire — an older SDK does not report
40
63
  * `runtime` at all — so each is returned as undefined rather than defaulted here, letting the caller
@@ -8,6 +8,15 @@ export interface SessionInfo {
8
8
  title?: string;
9
9
  adapters: string[];
10
10
  hasCapabilities: boolean;
11
+ /**
12
+ * Which shell answered: `web`, `electron` or `tauri`.
13
+ *
14
+ * Absent — never defaulted to `web` — on an SDK too old to report one, because the guess is wrong
15
+ * on exactly the machines this exists to tell apart. Two windows on the same url are otherwise
16
+ * indistinguishable, and a browser tab is not a desktop app: it has none of its IPC, and it does
17
+ * not render like it.
18
+ */
19
+ runtime?: string;
11
20
  /** Present only when the page's SDK version differs from the daemon's — see version-skew.ts. */
12
21
  versionSkew?: string;
13
22
  /** ms since the SDK last reported anything (silence ⇒ likely throttled). */
@@ -19,6 +28,13 @@ export interface SessionInfo {
19
28
  recommendation?: string;
20
29
  stale?: boolean;
21
30
  cleanup_suggestion?: string;
31
+ /**
32
+ * present ONLY when this tab has stopped answering commands — attached, streaming events, and
33
+ * missing every command budget. Never `false`: absence means "answering, or not asked yet".
34
+ */
35
+ unresponsive?: true;
36
+ /** present with `unresponsive` — the in-protocol way out of a wedged tab. */
37
+ unresponsive_suggestion?: string;
22
38
  /** present only when the human has flagged bugs on this tab — count of pending review marks. */
23
39
  pendingMarks?: number;
24
40
  /** present with pendingMarks — nudges the agent to drain them with reticle_review. */
@@ -47,11 +63,13 @@ interface SessionView {
47
63
  title: string;
48
64
  adapters: string[];
49
65
  hasCapabilities: boolean;
66
+ runtime: string | undefined;
50
67
  versionSkew: string | undefined;
51
68
  hidden: boolean;
52
69
  health: () => SessionHealth;
53
70
  staleMs: () => number;
54
71
  pendingMarkCount: () => number;
72
+ unresponsive: () => boolean;
55
73
  }
56
74
  /**
57
75
  * Project a live Session into the shape the tools return.
@@ -25,6 +25,8 @@ export function buildSessionInfo(session) {
25
25
  ...('' === session.title.trim() ? {} : { title: session.title }),
26
26
  adapters: session.adapters,
27
27
  hasCapabilities: session.hasCapabilities,
28
+ // Omitted when the page never said, so absence stays readable as "unknown" rather than "web".
29
+ ...(session.runtime === undefined ? {} : { runtime: session.runtime }),
28
30
  // On every listing, not buried in a log — skew explains failures that read as app bugs.
29
31
  ...(session.versionSkew === undefined ? {} : { versionSkew: session.versionSkew }),
30
32
  hidden: session.hidden,
@@ -35,6 +37,15 @@ export function buildSessionInfo(session) {
35
37
  base.cleanup_suggestion =
36
38
  'Call reticle_session{action:"end"} to free this session before starting new work.';
37
39
  }
40
+ // A tab that is attached and answering nothing. Said out loud because every health field beside it
41
+ // reads fine — that combination is exactly what made the wedge invisible.
42
+ if (session.unresponsive()) {
43
+ base.unresponsive = true;
44
+ base.unresponsive_suggestion =
45
+ 'This tab is connected but has stopped answering commands, so every call against it will time ' +
46
+ 'out. Ending the session does not revive the page. Reload the tab, or run `reticle open <url>` ' +
47
+ 'to get a fresh one — it will no longer hand this tab back.';
48
+ }
38
49
  // Surface human bug reports in reticle_sessions (only when > 0, so a clean session adds nothing).
39
50
  const marks = session.pendingMarkCount();
40
51
  if (marks > 0) {
@@ -1,3 +1,4 @@
1
+ import type { NoSessionReason } from '@reticlehq/core';
1
2
  import { Session, type SessionInfo } from './session.js';
2
3
  import { AttachmentHistory } from './attachment-history.js';
3
4
  import type { NoSessionNextAction } from './no-session-next-action.js';
@@ -37,6 +38,8 @@ export declare class SessionManager {
37
38
  * already computed for the error path; this exposes it to the read path too.
38
39
  */
39
40
  noSessionHint(): string | undefined;
41
+ setNoSessionReason(reason: (() => NoSessionReason | undefined) | undefined): void;
42
+ noSessionReason(): NoSessionReason | undefined;
40
43
  setNoSessionNextAction(next: (() => NoSessionNextAction | undefined) | undefined): void;
41
44
  noSessionNextAction(): NoSessionNextAction | undefined;
42
45
  everConnected(): boolean;
@@ -47,6 +50,17 @@ export declare class SessionManager {
47
50
  at: number;
48
51
  reason: string;
49
52
  } | undefined;
53
+ /**
54
+ * The id of the session that most recently went away, if any.
55
+ *
56
+ * Distinct from {@link lastClosure}, which only records closes the bridge itself initiated
57
+ * (handshake refusals, auth failures) — an ordinary disconnect never reaches it. This reads the
58
+ * tombstone ring `remove()` maintains, so it covers every departure.
59
+ *
60
+ * Read by the no-session diagnosis, which needs to say something about the session that actually
61
+ * vanished rather than about the daemon's lifetime history (#611).
62
+ */
63
+ lastDeparted(): string | undefined;
50
64
  resolve(sessionId?: string, scope?: ResolveScope): Session;
51
65
  }
52
66
  export {};