@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
@@ -1,7 +1,9 @@
1
1
  import { NO_SESSION_CONNECTED_ERROR } from '@reticlehq/core';
2
+ import { notePendingNoSessionReason } from '../telemetry/tool-refused.js';
2
3
  import { declareDrivenRedactionKeys, forgetDrivenRedactionKeys, } from '../input/driven-redaction.js';
3
4
  import { Session } from './session.js';
4
5
  import { AttachmentHistory } from './attachment-history.js';
6
+ import { pickDocumentSuccessor } from './session-successor.js';
5
7
  /** The scheme://host:port of a URL, or undefined if it can't be parsed. Used to compare origins. */
6
8
  export function originOf(url) {
7
9
  if (url === undefined)
@@ -61,6 +63,14 @@ function scopeMissError(connected, scope) {
61
63
  */
62
64
  /** How many bridge-initiated closes to remember for diagnosis. */
63
65
  const MAX_REMEMBERED_CLOSURES = 5;
66
+ /**
67
+ * How many recently-departed session ids to remember for successor rebind.
68
+ *
69
+ * A full-document navigation HELLO's back under a new id while the agent still holds the old one.
70
+ * The tombstone is how `resolve(oldId)` finds the unique same-origin successor instead of refusing.
71
+ * Bounded so a long-lived daemon cannot accumulate one record per tab forever.
72
+ */
73
+ const MAX_TOMBSTONES = 32;
64
74
  export class SessionManager {
65
75
  #sessions = new Map();
66
76
  /**
@@ -69,6 +79,13 @@ export class SessionManager {
69
79
  * Owned here because this is the one place that sees both halves — every add and every remove.
70
80
  */
71
81
  #attachment = new AttachmentHistory();
82
+ /**
83
+ * Recently departed sessions, keyed by the id the agent still holds.
84
+ *
85
+ * Insertion order is LRU: the oldest is dropped when the cap is hit. A reconnect under the same
86
+ * id forgets its tombstone — the live session is the answer then.
87
+ */
88
+ #tombstones = new Map();
72
89
  /**
73
90
  * The active project's scope, set once from the daemon's .reticle.json. When a tool resolves a session
74
91
  * without passing its own scope, this is applied — so auto-selection is project-scoped by default
@@ -95,13 +112,30 @@ export class SessionManager {
95
112
  this.#recordConnection?.(session.projectId);
96
113
  const previous = this.#sessions.get(session.id);
97
114
  this.#sessions.set(session.id, session);
115
+ // Who can SEE this session's HUD feed. Wired here because `add` is the one method every path
116
+ // that registers a session goes through, and read lazily so a tab that opens later still counts.
117
+ // Optional-call for the same reason `pushImpact` is one at the dispatch chokepoint: a test
118
+ // double is a partial Session, and wiring a courtesy channel must never break registration.
119
+ session.setViewers?.(() => this.#viewersFor(session));
98
120
  // Publish what this app declared sensitive to the driven-path rule. Here rather than in the
99
121
  // bridge because EVERY path that registers a session goes through this method, and a declaration
100
122
  // that silently fails to register is a leak nothing would report.
101
123
  declareDrivenRedactionKeys(session.id, session.redactKeys);
102
124
  this.#attachment.attached(session.id);
125
+ this.#tombstones.delete(session.id);
103
126
  return previous;
104
127
  }
128
+ /**
129
+ * The other tabs of the same app as `driven`.
130
+ *
131
+ * Scoped the same way auto-selection is: by the stable projectId when the driven tab stamped one,
132
+ * else by origin. A drive session is frequently a headless pooled context (see Session.#viewers),
133
+ * so without this the human's own tab is the one place the report never reaches.
134
+ */
135
+ #viewersFor(driven) {
136
+ const scope = driven.projectId === undefined ? { url: driven.url } : { projectId: driven.projectId };
137
+ return scopeSessions([...this.#sessions.values()], scope).filter((s) => s !== driven);
138
+ }
105
139
  remove(session) {
106
140
  if (this.#sessions.get(session.id) !== session)
107
141
  return false;
@@ -110,8 +144,40 @@ export class SessionManager {
110
144
  // Recorded, not forgotten: a session that comes back needs its gap measured, and a listing after
111
145
  // the reconnect is exactly where that matters.
112
146
  this.#attachment.detached(session.id);
147
+ this.#rememberTombstone(session);
113
148
  return this.#sessions.delete(session.id);
114
149
  }
150
+ #rememberTombstone(session) {
151
+ const origin = originOf(session.url);
152
+ if (origin === undefined)
153
+ return;
154
+ if (this.#tombstones.has(session.id))
155
+ this.#tombstones.delete(session.id);
156
+ this.#tombstones.set(session.id, {
157
+ id: session.id,
158
+ url: session.url,
159
+ ...(session.projectId === undefined ? {} : { projectId: session.projectId }),
160
+ });
161
+ while (this.#tombstones.size > MAX_TOMBSTONES) {
162
+ const oldest = this.#tombstones.keys().next().value;
163
+ if (oldest === undefined)
164
+ break;
165
+ this.#tombstones.delete(oldest);
166
+ }
167
+ }
168
+ #successorOf(sessionId) {
169
+ const departed = this.#tombstones.get(sessionId);
170
+ if (departed === undefined)
171
+ return undefined;
172
+ const picked = pickDocumentSuccessor(this.all().map((s) => ({
173
+ id: s.id,
174
+ url: s.url,
175
+ ...(s.projectId === undefined ? {} : { projectId: s.projectId }),
176
+ })), departed);
177
+ if (picked === undefined)
178
+ return undefined;
179
+ return this.#sessions.get(picked.id);
180
+ }
115
181
  get(sessionId) {
116
182
  return this.#sessions.get(sessionId);
117
183
  }
@@ -169,6 +235,13 @@ export class SessionManager {
169
235
  * bridge constructed without a daemon (every unit test) keeps the plain message.
170
236
  */
171
237
  #noSessionHint;
238
+ /**
239
+ * The CODE for the same diagnosis `#noSessionHint` renders as prose.
240
+ *
241
+ * Registered from the same `explainNoSession` call as the hint, so the two cannot describe
242
+ * different branches. See no-session-watch.ts.
243
+ */
244
+ #noSessionReason;
172
245
  /** Wire the diagnosis provider (daemon boot). Absent ⇒ the plain, static message. */
173
246
  setNoSessionHint(hint) {
174
247
  this.#noSessionHint = hint;
@@ -184,6 +257,12 @@ export class SessionManager {
184
257
  noSessionHint() {
185
258
  return this.#noSessionHint?.();
186
259
  }
260
+ setNoSessionReason(reason) {
261
+ this.#noSessionReason = reason;
262
+ }
263
+ noSessionReason() {
264
+ return this.#noSessionReason?.();
265
+ }
187
266
  /**
188
267
  * The same diagnosis with the prose taken out — one action kind and one literal command.
189
268
  *
@@ -213,6 +292,22 @@ export class SessionManager {
213
292
  lastClosure() {
214
293
  return this.#recentClosures[this.#recentClosures.length - 1];
215
294
  }
295
+ /**
296
+ * The id of the session that most recently went away, if any.
297
+ *
298
+ * Distinct from {@link lastClosure}, which only records closes the bridge itself initiated
299
+ * (handshake refusals, auth failures) — an ordinary disconnect never reaches it. This reads the
300
+ * tombstone ring `remove()` maintains, so it covers every departure.
301
+ *
302
+ * Read by the no-session diagnosis, which needs to say something about the session that actually
303
+ * vanished rather than about the daemon's lifetime history (#611).
304
+ */
305
+ lastDeparted() {
306
+ let last;
307
+ for (const id of this.#tombstones.keys())
308
+ last = id;
309
+ return last;
310
+ }
216
311
  /**
217
312
  * A dead `sessionId`, answered with what the caller needs to recover — not with an errand.
218
313
  *
@@ -239,7 +334,7 @@ export class SessionManager {
239
334
  }
240
335
  resolve(sessionId, scope) {
241
336
  if (sessionId !== undefined) {
242
- const found = this.#sessions.get(sessionId);
337
+ const found = this.#sessions.get(sessionId) ?? this.#successorOf(sessionId);
243
338
  if (found === undefined) {
244
339
  throw new Error(this.#unknownSessionError(sessionId));
245
340
  }
@@ -258,6 +353,10 @@ export class SessionManager {
258
353
  const refusal = closure === undefined
259
354
  ? ''
260
355
  : ` NOTE: the bridge REFUSED or closed a connection recently — "${closure.reason}". The app is probably still running and trying to connect: it was turned away, and the SDK does not retry after a policy close. The reason above names the fix — do not go looking for a stopped dev server.`;
356
+ // Hand the branch code to the refusal that is about to be reported for this throw. The
357
+ // diagnosis is computed HERE and the refusal is classified from the message downstream, so
358
+ // without this the largest refusal cohort stays one undifferentiated bucket (#615).
359
+ notePendingNoSessionReason(this.#noSessionReason?.());
261
360
  throw new Error(`${hint ?? NO_SESSION_CONNECTED_ERROR}${refusal}`);
262
361
  }
263
362
  // Scope to the agent's active project FIRST, so a stray tab from another app/origin (e.g. a
@@ -308,8 +407,15 @@ export class SessionManager {
308
407
  'several apps leaves it unable to tell them apart.');
309
408
  }
310
409
  // Multiple sessions: score each (lower = better candidate for auto-selection).
311
- // 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat).
312
- const scored = all.map((s) => ({ s, score: s.throttled() ? 1 : 0, ms: s.lastSeenMs() }));
410
+ // 0 = non-throttled (visible + recently-heard), 1 = throttled (hidden or stale heartbeat),
411
+ // 2 = attached but not answering commands. A wedged tab keeps streaming events, so recency
412
+ // RATES IT HIGHEST — it was beating a healthy sibling on the one signal scoring looked at, and
413
+ // every call auto-targeted the one tab guaranteed to time out.
414
+ const scored = all.map((s) => ({
415
+ s,
416
+ score: s.unresponsive() ? 2 : s.throttled() ? 1 : 0,
417
+ ms: s.lastSeenMs(),
418
+ }));
313
419
  const bestScore = Math.min(...scored.map((x) => x.score));
314
420
  const candidates = scored.filter((x) => x.score === bestScore);
315
421
  // Sort candidates by recency (ascending lastSeenMs = most recently active first).
@@ -326,7 +432,8 @@ export class SessionManager {
326
432
  // desktop), the gap requirement is dropped: every session is already in "background" mode
327
433
  // so we just pick the one with the freshest heartbeat and let the agent proceed. Requiring
328
434
  // a gap here only produces spurious "ambiguous" errors while the user works elsewhere.
329
- const allThrottled = 1 === bestScore;
435
+ // Nothing healthy left to choose between (all throttled, or all wedged): drop the gap check.
436
+ const allThrottled = bestScore >= 1;
330
437
  const RECENCY_GAP_MS = allThrottled ? 0 : 1_000;
331
438
  const clearWinner = runnerUp === undefined || best.ms + RECENCY_GAP_MS < runnerUp.ms;
332
439
  if (!clearWinner) {
@@ -0,0 +1,16 @@
1
+ /**
2
+ * What a tab that is only WATCHING gets to see of a drive happening somewhere else.
3
+ *
4
+ * Its own module because it is its own question — the rest of session.ts is about a session Reticle
5
+ * is DRIVING, and this is about the one it is not. Split out when session.ts crossed the 1000-line
6
+ * cap; the cap did its job, because these two had no reason to share a file beyond history.
7
+ */
8
+ /**
9
+ * How a mirrored row is introduced on a tab that is only WATCHING.
10
+ *
11
+ * The row happened somewhere else, and a HUD that prints it unlabelled tells the reader their own
12
+ * tab was driven. Naming the session is also the handle they need to address it.
13
+ */
14
+ export declare function mirroredNarration(sessionId: string, text: string): string;
15
+ /** The pushes that are a REPORT of what happened, and so are worth showing to a watching tab. */
16
+ export declare const MIRRORED_COMMANDS: ReadonlySet<string>;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * What a tab that is only WATCHING gets to see of a drive happening somewhere else.
3
+ *
4
+ * Its own module because it is its own question — the rest of session.ts is about a session Reticle
5
+ * is DRIVING, and this is about the one it is not. Split out when session.ts crossed the 1000-line
6
+ * cap; the cap did its job, because these two had no reason to share a file beyond history.
7
+ */
8
+ import { ReticleCommand } from '@reticlehq/core';
9
+ /**
10
+ * How a mirrored row is introduced on a tab that is only WATCHING.
11
+ *
12
+ * The row happened somewhere else, and a HUD that prints it unlabelled tells the reader their own
13
+ * tab was driven. Naming the session is also the handle they need to address it.
14
+ */
15
+ export function mirroredNarration(sessionId, text) {
16
+ return `${sessionId} \u2192 ${text}`;
17
+ }
18
+ /** The pushes that are a REPORT of what happened, and so are worth showing to a watching tab. */
19
+ export const MIRRORED_COMMANDS = new Set([
20
+ ReticleCommand.NARRATE,
21
+ ReticleCommand.IMPACT,
22
+ ]);
@@ -6,12 +6,26 @@ export interface RecommendationInputs {
6
6
  hidden: boolean;
7
7
  throttled: boolean;
8
8
  focused: boolean;
9
+ /**
10
+ * The shell this session runs in, as the SDK reported it at handshake. Undefined for an older SDK
11
+ * that does not report one — and an unknown runtime keeps the WEB advice, because a browser tab is
12
+ * what most sessions are and withholding a usable escape hatch is the worse error.
13
+ */
14
+ runtime?: string | undefined;
9
15
  }
10
16
  /**
11
- * A human-readable escape-hatch hint when a tab is hidden/throttled and may be
12
- * un-scriptable/un-focusable from here. Returns undefined for a healthy tab so the field stays
13
- * ABSENT (not empty). Keys on `hidden || throttled` — the same disjunction as Session.throttled —
14
- * so it never disagrees with the `throttled` flag the agent already sees. A merely-unfocused but
15
- * live tab is still scriptable, so blur alone does not trigger it. Pure.
17
+ * A human-readable hint when a tab is hidden or throttled. Returns undefined for a healthy tab so
18
+ * the field stays ABSENT (not empty). A merely-unfocused but live tab is still scriptable, so blur
19
+ * alone does not trigger it. Pure.
20
+ *
21
+ * HIDDEN and THROTTLED get DIFFERENT answers, which is the fix. They used to share one message that
22
+ * said "may be un-focusable; acquire a guaranteed scriptable context" — correct for a background
23
+ * tab, and actively harmful for a visible one. A visible throttled tab is driveable; measured in the
24
+ * field, one took a sign-in and two clean net-grade verdicts with no retries, while that message had
25
+ * already sent the agent into a lease the watching human could not see. One flag, advice that cost
26
+ * the product's main trust surface, and nothing bought.
27
+ *
28
+ * Hidden outranks throttled when both are set: a background tab is the stronger fact, and its
29
+ * failure mode (events landing on a page that never advances) is the one worth warning about.
16
30
  */
17
31
  export declare function buildSessionRecommendation(inputs: RecommendationInputs): string | undefined;
@@ -1,13 +1,29 @@
1
- import { UNSCRIPTABLE_TAB_RECOMMENDATION } from '@reticlehq/core';
1
+ import { AppRuntime, DESKTOP_WINDOW_BACKGROUNDED, HIDDEN_TAB_RECOMMENDATION, THROTTLED_TAB_RECOMMENDATION, } from '@reticlehq/core';
2
2
  /**
3
- * A human-readable escape-hatch hint when a tab is hidden/throttled and may be
4
- * un-scriptable/un-focusable from here. Returns undefined for a healthy tab so the field stays
5
- * ABSENT (not empty). Keys on `hidden || throttled` — the same disjunction as Session.throttled —
6
- * so it never disagrees with the `throttled` flag the agent already sees. A merely-unfocused but
7
- * live tab is still scriptable, so blur alone does not trigger it. Pure.
3
+ * A human-readable hint when a tab is hidden or throttled. Returns undefined for a healthy tab so
4
+ * the field stays ABSENT (not empty). A merely-unfocused but live tab is still scriptable, so blur
5
+ * alone does not trigger it. Pure.
6
+ *
7
+ * HIDDEN and THROTTLED get DIFFERENT answers, which is the fix. They used to share one message that
8
+ * said "may be un-focusable; acquire a guaranteed scriptable context" — correct for a background
9
+ * tab, and actively harmful for a visible one. A visible throttled tab is driveable; measured in the
10
+ * field, one took a sign-in and two clean net-grade verdicts with no retries, while that message had
11
+ * already sent the agent into a lease the watching human could not see. One flag, advice that cost
12
+ * the product's main trust surface, and nothing bought.
13
+ *
14
+ * Hidden outranks throttled when both are set: a background tab is the stronger fact, and its
15
+ * failure mode (events landing on a page that never advances) is the one worth warning about.
8
16
  */
9
17
  export function buildSessionRecommendation(inputs) {
10
- if (inputs.hidden || inputs.throttled)
11
- return UNSCRIPTABLE_TAB_RECOMMENDATION;
12
- return undefined;
18
+ if (!inputs.hidden && !inputs.throttled)
19
+ return undefined;
20
+ // A desktop window gets the one answer it can act on. Checked BEFORE hidden/throttled are told
21
+ // apart, because the distinction between them is about browser tab lifecycle and neither of its
22
+ // two answers exists for an app whose window is the client. See DESKTOP_WINDOW_BACKGROUNDED.
23
+ if (AppRuntime.ELECTRON === inputs.runtime || AppRuntime.TAURI === inputs.runtime) {
24
+ return DESKTOP_WINDOW_BACKGROUNDED;
25
+ }
26
+ if (inputs.hidden)
27
+ return HIDDEN_TAB_RECOMMENDATION;
28
+ return THROTTLED_TAB_RECOMMENDATION;
13
29
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The session that took over after a full-document navigation.
3
+ *
4
+ * Reload keeps the id (sessionStorage). A click that loads a new document often does not: the SDK
5
+ * is torn down with the page and HELLO's back under a new id, while the agent still holds the old
6
+ * one. `navigate` already waits for that HELLO (`awaitArrival`) and returns the new id.
7
+ * `act_and_wait` and `assert` did not, so an MPA drive ended at the first link.
8
+ *
9
+ * Unique at the departed origin (and project, when known). Two live tabs at that origin is still a
10
+ * guess, and a guess here would drive the wrong app.
11
+ */
12
+ import type { Session } from './session.js';
13
+ export interface SessionIdentity {
14
+ id: string;
15
+ url: string;
16
+ projectId?: string;
17
+ }
18
+ export interface SuccessorClock {
19
+ now: () => number;
20
+ sleep: (ms: number) => Promise<void>;
21
+ }
22
+ /**
23
+ * The one live session that can honestly take over from `departed`, or undefined when none or more
24
+ * than one could.
25
+ *
26
+ * A same-id reconnect is a successor: the id survived in sessionStorage, the document did not.
27
+ */
28
+ export declare function pickDocumentSuccessor(live: readonly SessionIdentity[], departed: SessionIdentity): SessionIdentity | undefined;
29
+ export interface SuccessorRegistry {
30
+ get(id: string): Session | undefined;
31
+ all(): readonly Session[];
32
+ }
33
+ /**
34
+ * Poll until a successor of `departed` is live, or the budget runs out. Null on timeout — never
35
+ * throws, because failing to follow is a legitimate `observation_lost`, not an error.
36
+ */
37
+ export declare function awaitDocumentSuccessor(sessions: SuccessorRegistry, departed: Session, timeoutMs: number, clock?: SuccessorClock): Promise<Session | null>;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * The session that took over after a full-document navigation.
3
+ *
4
+ * Reload keeps the id (sessionStorage). A click that loads a new document often does not: the SDK
5
+ * is torn down with the page and HELLO's back under a new id, while the agent still holds the old
6
+ * one. `navigate` already waits for that HELLO (`awaitArrival`) and returns the new id.
7
+ * `act_and_wait` and `assert` did not, so an MPA drive ended at the first link.
8
+ *
9
+ * Unique at the departed origin (and project, when known). Two live tabs at that origin is still a
10
+ * guess, and a guess here would drive the wrong app.
11
+ */
12
+ import { RETICLE_URL_PARAM } from '@reticlehq/core';
13
+ const POLL_MS = 25;
14
+ const REAL_CLOCK = {
15
+ now: () => Date.now(),
16
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
17
+ };
18
+ function originOf(url) {
19
+ try {
20
+ return new URL(url).origin;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ /**
27
+ * The session a URL explicitly CLAIMS to be, via `__reticle_session`.
28
+ *
29
+ * A leased tab carries this marker so the pool can find the context it opened, and an app can set it
30
+ * deliberately so a battery addresses a known id. Either way it is a claim of identity, not a
31
+ * coincidence of origin — which is what makes it the right thing to gate succession on.
32
+ */
33
+ function claimedSessionOf(url) {
34
+ try {
35
+ return new URL(url).searchParams.get(RETICLE_URL_PARAM.SESSION) ?? undefined;
36
+ }
37
+ catch {
38
+ return undefined;
39
+ }
40
+ }
41
+ /**
42
+ * Whether one session may inherit another's identity.
43
+ *
44
+ * Origin alone is not enough. A leased tab is an isolated context that an AGENT owns and no human
45
+ * can see; the developer's own tab at the same origin is a different trust and visibility domain
46
+ * entirely. Letting one succeed the other meant an expired lease silently redirected the next call
47
+ * into somebody's real browser tab — observed, twice, and reported as success.
48
+ *
49
+ * So a session that claims an explicit identity only ever succeeds to one claiming the SAME
50
+ * identity. A session that claims none is unaffected, which is the ordinary reload case this whole
51
+ * mechanism exists for.
52
+ */
53
+ function mayInherit(departed, candidate) {
54
+ const claimed = claimedSessionOf(departed.url);
55
+ if (claimed === undefined)
56
+ return true;
57
+ return claimedSessionOf(candidate.url) === claimed;
58
+ }
59
+ function identityOf(session) {
60
+ return {
61
+ id: session.id,
62
+ url: session.url,
63
+ ...(session.projectId === undefined ? {} : { projectId: session.projectId }),
64
+ };
65
+ }
66
+ /**
67
+ * The one live session that can honestly take over from `departed`, or undefined when none or more
68
+ * than one could.
69
+ *
70
+ * A same-id reconnect is a successor: the id survived in sessionStorage, the document did not.
71
+ */
72
+ export function pickDocumentSuccessor(live, departed) {
73
+ const origin = originOf(departed.url);
74
+ if (origin === undefined)
75
+ return undefined;
76
+ const atOrigin = live.filter((s) => originOf(s.url) === origin);
77
+ const sameId = atOrigin.find((s) => s.id === departed.id);
78
+ if (sameId !== undefined)
79
+ return sameId;
80
+ const others = atOrigin.filter((s) => s.id !== departed.id &&
81
+ (departed.projectId === undefined || s.projectId === departed.projectId) &&
82
+ mayInherit(departed, s));
83
+ if (1 !== others.length)
84
+ return undefined;
85
+ return others[0];
86
+ }
87
+ /**
88
+ * Poll until a successor of `departed` is live, or the budget runs out. Null on timeout — never
89
+ * throws, because failing to follow is a legitimate `observation_lost`, not an error.
90
+ */
91
+ export async function awaitDocumentSuccessor(sessions, departed, timeoutMs, clock = REAL_CLOCK) {
92
+ const deadline = clock.now() + timeoutMs;
93
+ const departedIdentity = identityOf(departed);
94
+ for (;;) {
95
+ const sameId = sessions.get(departed.id);
96
+ if (sameId !== undefined && sameId !== departed)
97
+ return sameId;
98
+ // The departed session often still sits in the registry until the socket close lands. Leave it
99
+ // out of the pick, or same-id-first would keep returning the page that just unloaded.
100
+ const picked = pickDocumentSuccessor(sessions
101
+ .all()
102
+ .filter((s) => s !== departed)
103
+ .map(identityOf), departedIdentity);
104
+ if (picked !== undefined) {
105
+ const live = sessions.get(picked.id);
106
+ if (live !== undefined && live !== departed)
107
+ return live;
108
+ }
109
+ if (clock.now() >= deadline)
110
+ return null;
111
+ await clock.sleep(POLL_MS);
112
+ }
113
+ }
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { ReticleCommand } from '@reticlehq/core';
3
3
  import { ReticleTool } from '../tools/tool-names.js';
4
4
  import { asNumber, asString } from '../tools/tools-helpers.js';
5
+ import { idleMsSchema } from '../tools/numeric-bounds.js';
5
6
  /**
6
7
  * Session lifecycle controls. The presenter session begins on the agent's first action and ends
7
8
  * either when the agent ends it (reticle_end_session) or after an idle window — and the human keeps the
@@ -13,8 +14,7 @@ export const SESSION_TOOLS = [
13
14
  name: ReticleTool.SESSION_TUNE,
14
15
  description: 'Tune the presenter session for this app. { idleEndMs } sets how long the session stays open after you go quiet before the panel shows the human you are WAITING (your turn). Default 5min — the SLOW backstop; signal handback IMMEDIATELY with reticle_session{action:"yield"} instead of waiting for this. Lower it for snappier auto-handback, raise it for a slow app where long gaps between your tool calls are normal. Enforced SERVER-SIDE (immune to background-tab throttling); it also fires if you (the MCP client) disconnect — so a forgotten or crashed session never leaves the HUD reading "live". Going quiet then acting again revives the session automatically. Returns { applied, idleEndMs }.',
15
16
  inputSchema: {
16
- idleEndMs: z
17
- .number()
17
+ idleEndMs: idleMsSchema
18
18
  .optional()
19
19
  .describe('Idle window in milliseconds after which the panel shows WAITING (your turn). Default: 300000 (5 min) — the slow backstop; prefer reticle_session{action:"yield"}. Raise for slow apps.'),
20
20
  sessionId: z
@@ -2,10 +2,11 @@ import type { WebSocket } from 'ws';
2
2
  import type { ImpactSnapshot } from '@reticlehq/core';
3
3
  import { LastAct } from './last-act.js';
4
4
  import { GapLedger } from '../honesty/gap-ledger.js';
5
+ import { CaptureLedger } from '../honesty/feature-capture.js';
5
6
  import { type PageRuntime } from './command-timeout.js';
6
7
  import { type SessionHealth } from './session-health.js';
7
8
  export type { SessionHealth };
8
- import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
9
+ import { PresenterTone, SessionState, type BrowserBrand, type CommandResult, type JournalAction, type HelloMessage, type HumanControlData, type ReticleEvent } from '@reticlehq/core';
9
10
  import type { JournalReader, JournalRecorder } from '../journal/journal-recorder.js';
10
11
  import { type EventQueryOptions } from '../journal/journal-query.js';
11
12
  import { type AmbientCounts } from '../journal/ambient.js';
@@ -25,6 +26,17 @@ export declare class Session {
25
26
  readonly id: string;
26
27
  /** Stable build-stamped project identity; undefined for v1.0 SDKs that omit it. */
27
28
  readonly projectId: string | undefined;
29
+ /**
30
+ * The `.reticle` directory this session's evidence belongs in — impact counters AND capsules.
31
+ *
32
+ * Stamped once when the session is created, from the project it declared in HELLO, rather than
33
+ * resolved per call: the answer cannot change while the tab is connected, and resolving it once
34
+ * means every counter that fires for this session agrees about where it goes.
35
+ *
36
+ * Undefined when nothing could name a project — the caller then falls back to the daemon's own
37
+ * root, which is what every counter did unconditionally before this existed.
38
+ */
39
+ artifactRoot: string | undefined;
28
40
  url: string;
29
41
  title: string;
30
42
  adapters: string[];
@@ -61,8 +73,19 @@ export declare class Session {
61
73
  /** The attachable health block — single source of truth for the tools. */
62
74
  health(): SessionHealth;
63
75
  info(): SessionInfo;
76
+ /**
77
+ * Attached but not answering. A tab can be `connected` with a fresh `lastSeenMs` and still answer
78
+ * nothing: `lastSeenMs` measures the events the SDK streams, not the commands it replies to. Reuse
79
+ * and auto-selection read that as health, so `reticle open` handed a wedged tab back forever and
80
+ * `reticle_session{action:"end"}` — a flag on the record, not on the page — could not help.
81
+ */
82
+ unresponsive(): boolean;
64
83
  /** Wall-clock age of the session in milliseconds. */
65
84
  staleMs(): number;
85
+ /** The document the most recent stamped event was observed under. See `#documentId`. */
86
+ get currentDocumentId(): string | undefined;
87
+ /** The edit epoch the most recent stamped event was observed under. See `#editEpoch`. */
88
+ get currentEditEpoch(): number | undefined;
66
89
  /** Re-stamp an incoming event with server-relative time, buffer it, and fan out. */
67
90
  pushEvent(event: ReticleEvent, byteSize?: number): void;
68
91
  recordActedRef(ref: string): void;
@@ -100,8 +123,28 @@ export declare class Session {
100
123
  * action id. Ids are minted independently of command correlation ids so the journal is self-contained.
101
124
  */
102
125
  beginAction(tool: string, args: Record<string, unknown>): string;
126
+ /**
127
+ * Record one action WITHOUT opening an attribution window — for a tool that returns a verdict but
128
+ * drives nothing, so no event it observes was caused by it. See `JournalRecorder.recordAction`.
129
+ */
130
+ recordAction(tool: string, args: Record<string, unknown>, effect?: unknown): string;
131
+ /**
132
+ * How many actions this session has dispatched.
133
+ *
134
+ * The counter that mints journal action ids, not a second one — so it equals the journal's own
135
+ * length, and `reticle_context`'s `step`, whenever this session journals at all.
136
+ */
137
+ get actionCount(): number;
103
138
  /** Close the active action window, persisting its action record with the settle outcome. */
104
139
  finishAction(effect?: unknown, settled?: boolean, settledInMs?: number): void;
140
+ /**
141
+ * The durable action ledger for this session — the ledger the run context is folded out of.
142
+ *
143
+ * `[]` when nothing journals this session (journaling disabled, or an id that is not a safe path
144
+ * segment). An absent ledger reads as an empty run, never as a missing one: the fold simply has
145
+ * nothing to say, which is the honest answer.
146
+ */
147
+ readJournalActions(): Promise<JournalAction[]>;
105
148
  /** Flush any buffered journal events to disk (call on session end). */
106
149
  flushJournal(): Promise<void>;
107
150
  eventsSince(cursor: number): ReticleEvent[];
@@ -120,6 +163,11 @@ export declare class Session {
120
163
  * end of it.
121
164
  */
122
165
  readonly gaps: GapLedger;
166
+ /**
167
+ * The read-only calls the journal does not keep, so "was `reticle_context` ever called" has
168
+ * something to fold. See honesty/feature-capture.ts — it is the only state that instrument adds.
169
+ */
170
+ readonly capture: CaptureLedger;
123
171
  /**
124
172
  * Stamp the wall-clock of the latest AGENT command (called whenever a tool resolves this session).
125
173
  * If the reaper had auto-ended the session, a fresh command means the agent is alive again, so the
@@ -157,10 +205,10 @@ export declare class Session {
157
205
  command(name: string, args?: Record<string, unknown>, timeoutMs?: number): Promise<CommandResult>;
158
206
  handleResult(result: CommandResult): void;
159
207
  /** Reject everything still in flight — used on disconnect. */
160
- rejectAll(reason: string): void;
208
+ rejectAll(reason: string, replaced?: boolean): void;
161
209
  succeededBy(next: Session): void;
162
210
  /** End this transport without letting a stale socket remove its replacement session. */
163
- disconnect(reason: string): void;
211
+ disconnect(reason: string, replaced?: boolean): void;
164
212
  getState(): SessionState;
165
213
  isPaused(): boolean;
166
214
  isEnded(): boolean;
@@ -213,6 +261,8 @@ export declare class Session {
213
261
  * without needing an explicit polling loop.
214
262
  */
215
263
  ageWarning(): string | undefined;
264
+ /** Wired by SessionManager.add, the one place every session registers. */
265
+ setViewers(read: () => Session[]): void;
216
266
  }
217
267
  /**
218
268
  * Re-exported from session-manager.ts so the public import path (`./session.js`) is unchanged for