@reticlehq/server 3.1.0 → 3.3.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 (384) hide show
  1. package/SKILL.md +30 -28
  2. package/dist/command/cli/adhoc-suite.d.ts +19 -0
  3. package/dist/command/cli/adhoc-suite.js +88 -0
  4. package/dist/command/cli/adhoc-verdict.d.ts +23 -2
  5. package/dist/command/cli/adhoc-verdict.js +18 -10
  6. package/dist/command/cli/auth/device-flow.d.ts +2 -2
  7. package/dist/command/cli/cli-doctor.js +37 -5
  8. package/dist/command/cli/cli-flow-commands.d.ts +2 -1
  9. package/dist/command/cli/cli-kill.js +1 -1
  10. package/dist/command/cli/cli-parse-grammar.d.ts +7 -0
  11. package/dist/command/cli/cli-parse-grammar.js +7 -0
  12. package/dist/command/cli/cli-parse-verify.d.ts +2 -5
  13. package/dist/command/cli/cli-parse-verify.js +86 -13
  14. package/dist/command/cli/cli-parse.d.ts +8 -1
  15. package/dist/command/cli/cli-parse.js +38 -7
  16. package/dist/command/cli/cli-verify.d.ts +59 -0
  17. package/dist/command/cli/cli-verify.js +131 -19
  18. package/dist/command/cli/cloud-cli.js +7 -6
  19. package/dist/command/cli/cloud-kit.d.ts +0 -2
  20. package/dist/command/cli/cloud-kit.js +3 -7
  21. package/dist/command/cli/config/config-discovery.d.ts +12 -0
  22. package/dist/command/cli/config/config-discovery.js +19 -0
  23. package/dist/command/cli/doctor/doctor-rows.d.ts +12 -0
  24. package/dist/command/cli/doctor/doctor-rows.js +12 -0
  25. package/dist/command/cli/doctor/doctor-sessions-line.d.ts +2 -1
  26. package/dist/command/cli/drive/drive-command.js +1 -1
  27. package/dist/command/cli/launch/cli-launch.d.ts +3 -23
  28. package/dist/command/cli/launch/cli-launch.js +29 -70
  29. package/dist/command/cli/lifecycle/daemon-lifecycle.d.ts +33 -0
  30. package/dist/command/cli/lifecycle/daemon-lifecycle.js +198 -0
  31. package/dist/command/cli/mcp-command.js +5 -3
  32. package/dist/command/cli/ports/resolve/cli-port.d.ts +15 -1
  33. package/dist/command/cli/ports/resolve/cli-port.js +50 -2
  34. package/dist/command/cli/report-command.d.ts +16 -0
  35. package/dist/command/cli/report-command.js +97 -0
  36. package/dist/command/cli/status/status-lines.d.ts +28 -0
  37. package/dist/command/cli/status/status-lines.js +117 -0
  38. package/dist/command/cli.d.ts +1 -1
  39. package/dist/command/cli.js +29 -177
  40. package/dist/command/daemon/binding/daemon-status-probe.d.ts +32 -0
  41. package/dist/command/daemon/binding/daemon-status-probe.js +93 -0
  42. package/dist/command/daemon/binding/loopback-reach.d.ts +42 -0
  43. package/dist/command/daemon/binding/loopback-reach.js +63 -0
  44. package/dist/command/daemon/binding/port-presence.d.ts +1 -1
  45. package/dist/command/daemon/daemon-resilience.d.ts +13 -1
  46. package/dist/command/daemon/daemon-resilience.js +12 -1
  47. package/dist/command/daemon/daemon.d.ts +2 -11
  48. package/dist/command/daemon/daemon.js +8 -11
  49. package/dist/command/daemon/idle-shutdown.d.ts +2 -1
  50. package/dist/command/daemon/lifetime/heartbeat.d.ts +8 -0
  51. package/dist/command/daemon/lifetime/heartbeat.js +10 -0
  52. package/dist/command/daemon/lifetime/log-cap.d.ts +68 -0
  53. package/dist/command/daemon/lifetime/log-cap.js +115 -0
  54. package/dist/command/setup/agent-notice-memory.d.ts +16 -0
  55. package/dist/command/setup/agent-notice-memory.js +65 -0
  56. package/dist/command/setup/bringup/dev-server-wait.d.ts +1 -0
  57. package/dist/command/setup/bringup/dev-server-wait.js +16 -0
  58. package/dist/command/setup/bringup/ensure-daemon.js +1 -1
  59. package/dist/command/setup/init/init-host.js +8 -0
  60. package/dist/command/setup/init/init-runtime.js +13 -1
  61. package/dist/command/setup/node-effects.js +1 -1
  62. package/dist/command/setup/probe/page-probe.js +9 -2
  63. package/dist/command/setup/run-setup.d.ts +25 -7
  64. package/dist/command/setup/run-setup.js +51 -9
  65. package/dist/command/setup/setup-command.js +31 -3
  66. package/dist/command/setup/terminal/confirm.js +2 -1
  67. package/dist/command/update/updater.d.ts +27 -0
  68. package/dist/command/update/updater.js +35 -2
  69. package/dist/features/crawl/crawl-tools.js +3 -1
  70. package/dist/features/crawl/crawl.d.ts +2 -0
  71. package/dist/features/crawl/crawl.js +3 -1
  72. package/dist/features/harness/drive-report.d.ts +73 -0
  73. package/dist/features/harness/drive-report.js +196 -0
  74. package/dist/features/harness/driver.js +29 -8
  75. package/dist/features/harness/drivers.d.ts +19 -0
  76. package/dist/features/harness/drivers.js +19 -0
  77. package/dist/features/harness/fill-values.d.ts +67 -0
  78. package/dist/features/harness/fill-values.js +153 -0
  79. package/dist/features/harness/jev-driver.d.ts +188 -0
  80. package/dist/features/harness/jev-driver.js +661 -0
  81. package/dist/features/harness/openai-driver.d.ts +46 -0
  82. package/dist/features/harness/openai-driver.js +197 -0
  83. package/dist/features/harness/platform-config.d.ts +62 -0
  84. package/dist/features/harness/platform-config.js +97 -0
  85. package/dist/features/visual/visual-tools.js +26 -2
  86. package/dist/index.d.ts +1 -1
  87. package/dist/index.js +80 -130
  88. package/dist/judgement/capsule/capsule-store.d.ts +31 -2
  89. package/dist/judgement/capsule/capsule-store.js +58 -6
  90. package/dist/judgement/capsule/capsule.js +5 -0
  91. package/dist/judgement/capsule/causal-summary.d.ts +13 -1
  92. package/dist/judgement/capsule/causal-summary.js +7 -0
  93. package/dist/judgement/domain/domain-model.js +38 -9
  94. package/dist/judgement/domain/domain-tools.js +4 -2
  95. package/dist/judgement/outcome/assertion-integrity.d.ts +3 -3
  96. package/dist/judgement/phenomena/phenomena.d.ts +2 -1
  97. package/dist/judgement/runs/artifact/gap-report.d.ts +17 -0
  98. package/dist/judgement/runs/artifact/gap-report.js +46 -0
  99. package/dist/judgement/runs/artifact/gap-summary.d.ts +58 -0
  100. package/dist/judgement/runs/artifact/gap-summary.js +85 -0
  101. package/dist/judgement/runs/artifact/render-report.js +4 -1
  102. package/dist/judgement/runs/artifact/to-artifact.d.ts +7 -2
  103. package/dist/judgement/runs/artifact/to-artifact.js +1 -1
  104. package/dist/judgement/runs/context-tools.js +21 -3
  105. package/dist/judgement/runs/drive-run.js +7 -2
  106. package/dist/judgement/runs/verification-sync.d.ts +2 -2
  107. package/dist/judgement/runs/verification-sync.js +9 -2
  108. package/dist/language/flows/affected-tools.js +6 -3
  109. package/dist/language/flows/annotate-notes/annotate.d.ts +0 -15
  110. package/dist/language/flows/annotate-notes/annotate.js +66 -18
  111. package/dist/language/flows/flow-anchor.d.ts +12 -0
  112. package/dist/language/flows/flow-anchor.js +20 -0
  113. package/dist/language/flows/flow-expect-grammar.d.ts +2 -2
  114. package/dist/language/flows/flow-expect-grammar.js +92 -11
  115. package/dist/language/flows/flow-intent.js +1 -1
  116. package/dist/language/flows/flow-learning.d.ts +3 -0
  117. package/dist/language/flows/flow-learning.js +39 -8
  118. package/dist/language/flows/flow-replay-run.d.ts +55 -10
  119. package/dist/language/flows/flow-replay-run.js +217 -58
  120. package/dist/language/flows/flow-replay-types.d.ts +9 -0
  121. package/dist/language/flows/flow-replay.d.ts +29 -11
  122. package/dist/language/flows/flow-replay.js +221 -203
  123. package/dist/language/flows/flow-result.d.ts +18 -1
  124. package/dist/language/flows/flow-result.js +18 -2
  125. package/dist/language/flows/flow-step-runners.d.ts +26 -10
  126. package/dist/language/flows/flow-step-runners.js +20 -4
  127. package/dist/language/flows/flow-store-for-session.d.ts +2 -1
  128. package/dist/language/flows/flow-success.d.ts +21 -6
  129. package/dist/language/flows/flow-success.js +50 -138
  130. package/dist/language/flows/flow-tools.js +8 -4
  131. package/dist/language/flows/flows.d.ts +15 -10
  132. package/dist/language/flows/flows.js +17 -4
  133. package/dist/language/flows/heal.d.ts +9 -1
  134. package/dist/language/flows/heal.js +24 -2
  135. package/dist/language/flows/learned-sources.d.ts +21 -0
  136. package/dist/language/flows/learned-sources.js +84 -0
  137. package/dist/language/flows/narrow-write.d.ts +4 -4
  138. package/dist/language/flows/recording/recording-backtrack.d.ts +0 -7
  139. package/dist/language/flows/recording/recording-backtrack.js +1 -1
  140. package/dist/language/flows/recording/replay-halt.d.ts +2 -1
  141. package/dist/language/flows/recording/tape/recordings.d.ts +2 -2
  142. package/dist/language/flows/replay.js +13 -9
  143. package/dist/language/flows/stores/annotation-store.d.ts +5 -5
  144. package/dist/language/flows/stores/assertion-tiers-store.js +2 -3
  145. package/dist/language/flows/stores/flake-store.js +2 -3
  146. package/dist/language/flows/suite/coverage.d.ts +1 -1
  147. package/dist/language/flows/suite/parallel-suite.d.ts +2 -1
  148. package/dist/language/flows/suite-selection.d.ts +15 -1
  149. package/dist/language/flows/suite-selection.js +1 -1
  150. package/dist/language/flows/verify-change-tools.js +21 -4
  151. package/dist/memory/cloud/account-state.d.ts +1 -1
  152. package/dist/memory/cloud/account-state.js +5 -4
  153. package/dist/memory/cloud/cloud-config.d.ts +9 -0
  154. package/dist/memory/cloud/cloud-config.js +9 -0
  155. package/dist/memory/cloud/cloud-sync.d.ts +7 -4
  156. package/dist/memory/cloud/cloud-sync.js +11 -6
  157. package/dist/memory/cloud/harness-config.d.ts +12 -0
  158. package/dist/memory/cloud/harness-config.js +58 -0
  159. package/dist/memory/cloud/harness-offer.d.ts +44 -0
  160. package/dist/memory/cloud/harness-offer.js +146 -0
  161. package/dist/memory/cloud/harness-switch.d.ts +11 -0
  162. package/dist/memory/cloud/harness-switch.js +53 -0
  163. package/dist/memory/cloud/sync-cycle.d.ts +12 -0
  164. package/dist/memory/cloud/sync-cycle.js +29 -4
  165. package/dist/memory/cloud/sync-daemon.d.ts +35 -2
  166. package/dist/memory/cloud/sync-daemon.js +45 -0
  167. package/dist/memory/cloud/sync-disk.js +11 -6
  168. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  169. package/dist/memory/impact/impact-recorder.js +2 -1
  170. package/dist/memory/impact/impact-store.d.ts +5 -0
  171. package/dist/memory/impact/impact-store.js +25 -0
  172. package/dist/memory/intent/inline-intent.js +8 -3
  173. package/dist/memory/intent/intent-shard-store.d.ts +33 -47
  174. package/dist/memory/intent/intent-shard-store.js +253 -141
  175. package/dist/memory/intent/intent-shard.d.ts +22 -23
  176. package/dist/memory/intent/intent-shard.js +1 -1
  177. package/dist/memory/intent/intent-store.d.ts +13 -8
  178. package/dist/memory/intent/intent-store.js +46 -90
  179. package/dist/memory/intent/intent-tools.js +34 -23
  180. package/dist/memory/journal/ambient-store.js +2 -3
  181. package/dist/memory/journal/attach-journal.d.ts +11 -0
  182. package/dist/memory/journal/attach-journal.js +1 -1
  183. package/dist/memory/journal/deviation-report.js +4 -1
  184. package/dist/memory/journal/deviation-service.js +10 -2
  185. package/dist/memory/journal/drive-flow.d.ts +2 -2
  186. package/dist/memory/journal/drive-flow.js +21 -12
  187. package/dist/memory/journal/envelope-store.js +2 -3
  188. package/dist/memory/journal/journal-query.d.ts +35 -1
  189. package/dist/memory/journal/journal-query.js +46 -0
  190. package/dist/memory/journal/journal-recorder.d.ts +71 -2
  191. package/dist/memory/journal/journal-recorder.js +45 -7
  192. package/dist/memory/journal/on-disk/retain-policy.d.ts +34 -0
  193. package/dist/memory/journal/on-disk/retain-policy.js +44 -0
  194. package/dist/memory/journal/on-disk/retention.d.ts +91 -2
  195. package/dist/memory/journal/on-disk/retention.js +181 -11
  196. package/dist/memory/journal/on-disk/startup-maintenance.d.ts +42 -0
  197. package/dist/memory/journal/on-disk/startup-maintenance.js +14 -0
  198. package/dist/memory/journal/on-disk/workspace-gitignore.d.ts +0 -2
  199. package/dist/memory/journal/on-disk/workspace-gitignore.js +6 -38
  200. package/dist/memory/journal/on-disk/workspace-tiers.d.ts +52 -0
  201. package/dist/memory/journal/on-disk/workspace-tiers.js +88 -0
  202. package/dist/memory/journal/route-template.d.ts +22 -0
  203. package/dist/memory/journal/route-template.js +44 -0
  204. package/dist/memory/journal/self-observation.d.ts +3 -0
  205. package/dist/memory/journal/self-observation.js +67 -0
  206. package/dist/memory/journal/session-end.d.ts +17 -4
  207. package/dist/memory/journal/session-end.js +85 -7
  208. package/dist/memory/journal/session-journal.d.ts +122 -5
  209. package/dist/memory/journal/session-journal.js +489 -32
  210. package/dist/memory/project/artifact-root-resolver.d.ts +3 -0
  211. package/dist/memory/project/artifact-root-resolver.js +89 -0
  212. package/dist/memory/project/artifact-root.d.ts +39 -2
  213. package/dist/memory/project/artifact-root.js +58 -1
  214. package/dist/memory/project/baselines.d.ts +1 -1
  215. package/dist/memory/project/dir/fill-value-store.d.ts +32 -0
  216. package/dist/memory/project/dir/fill-value-store.js +88 -0
  217. package/dist/memory/project/dir/reticle-dir.d.ts +15 -5
  218. package/dist/memory/project/dir/reticle-dir.js +11 -0
  219. package/dist/memory/project/fs/fs-port.d.ts +22 -1
  220. package/dist/memory/project/fs/fs-port.js +72 -5
  221. package/dist/memory/project/fs/write-atomic.d.ts +26 -0
  222. package/dist/memory/project/fs/write-atomic.js +48 -0
  223. package/dist/memory/project/learned-routes.d.ts +16 -2
  224. package/dist/memory/project/learned-routes.js +26 -10
  225. package/dist/memory/project/project-for-root.d.ts +30 -0
  226. package/dist/memory/project/project-for-root.js +43 -0
  227. package/dist/memory/project/project-tools.js +10 -5
  228. package/dist/memory/project/session-root.d.ts +35 -6
  229. package/dist/memory/project/session-root.js +70 -8
  230. package/dist/memory/recall/prior/connection-memory.d.ts +6 -0
  231. package/dist/memory/recall/prior/connection-memory.js +8 -0
  232. package/dist/memory/recall/registered-projects.d.ts +2 -1
  233. package/dist/portal/bridge/auth-failure-reason.d.ts +9 -0
  234. package/dist/portal/bridge/auth-failure-reason.js +20 -3
  235. package/dist/portal/bridge/bridge.d.ts +6 -1
  236. package/dist/portal/bridge/bridge.js +36 -2
  237. package/dist/portal/input/real-input.js +2 -1
  238. package/dist/portal/pool/browser-pool.d.ts +10 -0
  239. package/dist/portal/pool/browser-pool.js +10 -0
  240. package/dist/portal/pool/playwright-launcher.js +1 -0
  241. package/dist/portal/pool/pool-contract.d.ts +2 -0
  242. package/dist/portal/pool/storage-seed.d.ts +2 -1
  243. package/dist/portal/pool/zero-install.d.ts +27 -0
  244. package/dist/portal/pool/zero-install.js +54 -0
  245. package/dist/portal/realm/web-realm.js +6 -0
  246. package/dist/portal/session/dev-server/dev-server-probe.d.ts +2 -1
  247. package/dist/portal/session/dev-server/served-document.d.ts +21 -0
  248. package/dist/portal/session/dev-server/served-document.js +57 -0
  249. package/dist/portal/session/facts/background-traffic.d.ts +9 -0
  250. package/dist/portal/session/facts/background-traffic.js +25 -0
  251. package/dist/portal/session/facts/session-replaced.d.ts +19 -0
  252. package/dist/portal/session/facts/session-replaced.js +22 -0
  253. package/dist/portal/session/live-control-tools.js +48 -3
  254. package/dist/portal/session/no-session-diagnosis.d.ts +1 -0
  255. package/dist/portal/session/no-session-diagnosis.js +67 -60
  256. package/dist/portal/session/no-session-next-action.js +3 -2
  257. package/dist/portal/session/no-session-watch.js +14 -10
  258. package/dist/portal/session/presence/attachment-history.d.ts +13 -2
  259. package/dist/portal/session/rebind.d.ts +18 -0
  260. package/dist/portal/session/rebind.js +38 -0
  261. package/dist/portal/session/session-health.d.ts +61 -0
  262. package/dist/portal/session/session-health.js +40 -1
  263. package/dist/portal/session/session-info.d.ts +6 -0
  264. package/dist/portal/session/session-manager.d.ts +13 -0
  265. package/dist/portal/session/session-manager.js +42 -3
  266. package/dist/portal/session/session-successor.d.ts +11 -0
  267. package/dist/portal/session/session-successor.js +14 -1
  268. package/dist/portal/session/session-verdict-facts.d.ts +28 -0
  269. package/dist/portal/session/session-verdict-facts.js +15 -0
  270. package/dist/portal/session/session.d.ts +26 -10
  271. package/dist/portal/session/session.js +48 -49
  272. package/dist/status-payload.d.ts +12 -1
  273. package/dist/status-payload.js +4 -1
  274. package/dist/surface/http-server.js +3 -5
  275. package/dist/surface/mcp/faults/mcp-outage.d.ts +2 -1
  276. package/dist/surface/mcp/faults/mcp-outage.js +20 -4
  277. package/dist/surface/mcp/mcp-proxy.d.ts +10 -1
  278. package/dist/surface/mcp/mcp-proxy.js +23 -12
  279. package/dist/surface/mcp/mcp.d.ts +10 -1
  280. package/dist/surface/mcp/mcp.js +59 -29
  281. package/dist/surface/mcp/proxy/proxy-daemon-probe.d.ts +15 -2
  282. package/dist/surface/mcp/proxy/proxy-daemon-probe.js +22 -5
  283. package/dist/surface/mcp/proxy/proxy-log.d.ts +5 -11
  284. package/dist/surface/mcp/proxy/proxy-log.js +7 -22
  285. package/dist/surface/mcp/proxy-idle-exit.d.ts +2 -1
  286. package/dist/surface/mcp/server-instructions.js +12 -1
  287. package/dist/surface/tools/act/act-preflight.js +6 -1
  288. package/dist/surface/tools/act/already-true.d.ts +40 -0
  289. package/dist/surface/tools/act/already-true.js +36 -0
  290. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +6 -0
  291. package/dist/surface/tools/act/navigation/navigate-arrival.js +13 -0
  292. package/dist/surface/tools/act/sequence-grade.js +13 -0
  293. package/dist/surface/tools/act/settle-in-flight.d.ts +6 -2
  294. package/dist/surface/tools/act/settle-in-flight.js +29 -6
  295. package/dist/surface/tools/act-capsule.js +49 -8
  296. package/dist/surface/tools/act-tools.js +25 -24
  297. package/dist/surface/tools/args/numeric-bounds.d.ts +15 -2
  298. package/dist/surface/tools/args/numeric-bounds.js +30 -3
  299. package/dist/surface/tools/assert/assert-grade.js +14 -2
  300. package/dist/surface/tools/assert/assert-verdict.js +74 -21
  301. package/dist/surface/tools/browser-tools.js +4 -3
  302. package/dist/surface/tools/coverage-tools.js +5 -2
  303. package/dist/surface/tools/dynamic-tools.js +43 -46
  304. package/dist/surface/tools/error-recovery.d.ts +6 -0
  305. package/dist/surface/tools/error-recovery.js +15 -0
  306. package/dist/surface/tools/explore-tools.js +80 -7
  307. package/dist/surface/tools/feedback-tools.d.ts +2 -1
  308. package/dist/surface/tools/feedback-tools.js +50 -3
  309. package/dist/surface/tools/first-sentence.d.ts +24 -0
  310. package/dist/surface/tools/first-sentence.js +0 -0
  311. package/dist/surface/tools/harness-explore.d.ts +125 -1
  312. package/dist/surface/tools/harness-explore.js +401 -15
  313. package/dist/surface/tools/harness-plan.d.ts +76 -0
  314. package/dist/surface/tools/harness-plan.js +156 -0
  315. package/dist/surface/tools/harness-toolset.js +12 -1
  316. package/dist/surface/tools/invoke-tool.js +82 -9
  317. package/dist/surface/tools/lease-hint.d.ts +8 -0
  318. package/dist/surface/tools/lease-hint.js +10 -0
  319. package/dist/surface/tools/lease-tools.d.ts +16 -28
  320. package/dist/surface/tools/lease-tools.js +53 -5
  321. package/dist/surface/tools/lineage-tools.js +1 -1
  322. package/dist/surface/tools/live-call-text.d.ts +6 -0
  323. package/dist/surface/tools/live-call-text.js +36 -2
  324. package/dist/surface/tools/observe-tools.js +69 -15
  325. package/dist/surface/tools/read/query-windowed.d.ts +1 -1
  326. package/dist/surface/tools/read/query-windowed.js +1 -1
  327. package/dist/surface/tools/read-tools.js +1 -2
  328. package/dist/surface/tools/real-input-attempt.d.ts +0 -5
  329. package/dist/surface/tools/real-input-attempt.js +1 -1
  330. package/dist/surface/tools/tool-kit.d.ts +23 -3
  331. package/dist/surface/tools/tool-kit.js +6 -1
  332. package/dist/surface/tools/tool-surface.js +17 -5
  333. package/dist/surface/tools/tools-helpers.d.ts +8 -7
  334. package/dist/surface/tools/tools-helpers.js +11 -13
  335. package/dist/surface/tools/tools.js +70 -30
  336. package/dist/telemetry/bug-found.js +32 -2
  337. package/dist/telemetry/feedback-context.js +3 -3
  338. package/dist/telemetry/feedback-local.d.ts +1 -0
  339. package/dist/telemetry/feedback-local.js +1 -3
  340. package/dist/telemetry/feedback.d.ts +1 -1
  341. package/dist/telemetry/harness-drive.d.ts +6 -0
  342. package/dist/telemetry/harness-drive.js +39 -0
  343. package/dist/telemetry/hud-metrics.d.ts +32 -0
  344. package/dist/telemetry/hud-metrics.js +95 -0
  345. package/dist/telemetry/mcp-connection.d.ts +14 -1
  346. package/dist/telemetry/mcp-connection.js +17 -2
  347. package/dist/telemetry/onboarding-firsts.d.ts +30 -1
  348. package/dist/telemetry/onboarding-firsts.js +54 -12
  349. package/dist/telemetry/session-metrics.d.ts +5 -0
  350. package/dist/telemetry/session-metrics.js +15 -1
  351. package/dist/telemetry/verification-of.js +31 -3
  352. package/dist/wire-journal.d.ts +31 -0
  353. package/dist/wire-journal.js +100 -0
  354. package/docs/README.md +1 -0
  355. package/docs/agent-cheatsheet.md +7 -3
  356. package/docs/autodrive.md +42 -2
  357. package/docs/benchmarks.md +1 -1
  358. package/docs/capabilities.mdx +1 -1
  359. package/docs/cli/cloud.mdx +11 -9
  360. package/docs/cli/doctor.mdx +1 -0
  361. package/docs/cli/init.mdx +1 -0
  362. package/docs/cli/report.mdx +44 -0
  363. package/docs/cli/status.mdx +35 -20
  364. package/docs/cli.mdx +8 -18
  365. package/docs/desktop.mdx +1 -1
  366. package/docs/faq.mdx +1 -1
  367. package/docs/flows.md +11 -0
  368. package/docs/frameworks.mdx +113 -1
  369. package/docs/install-agentic.mdx +36 -0
  370. package/docs/install-manual.mdx +1 -1
  371. package/docs/packages/tauri.mdx +1 -1
  372. package/docs/packages.mdx +1 -1
  373. package/docs/predicates.mdx +144 -4
  374. package/docs/system-map.md +6 -4
  375. package/docs/telemetry.md +2 -1
  376. package/docs/token-efficiency.md +2 -2
  377. package/docs/tools/overview.mdx +1 -1
  378. package/docs/tools/query.mdx +10 -10
  379. package/docs/troubleshooting.mdx +8 -2
  380. package/docs/usage.md +2 -2
  381. package/docs/what-is-recorded.md +62 -0
  382. package/package.json +5 -5
  383. package/dist/judgement/outcome/predicate-to-expect.d.ts +0 -44
  384. package/dist/judgement/outcome/predicate-to-expect.js +0 -148
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { NoSessionAction, QueryBy, ReticleCommand, SnapshotMode } from '@reticlehq/core';
2
+ import { DiscoveryInvite, AttrNamesSchema, EventType, NoSessionAction, QueryBy, ReticleCommand, SnapshotMode, } from '@reticlehq/core';
3
3
  import { ReticleTool } from '@reticlehq/core';
4
4
  import { withSizeCost } from '../../portal/session/output-budget.js';
5
5
  import { applySnapshotDelta, SnapshotCache } from './read/snapshot-delta.js';
@@ -109,7 +109,7 @@ export const RAW_TOOLS = [
109
109
  lastOutage: z.object({ startedMs: z.number(), durationMs: z.number() }).optional(),
110
110
  })
111
111
  .optional()
112
- .describe('Present only when this tab has dropped and reconnected at least once: how long it has been attached in total, how many outages occurred, and when the last one started and how long it lasted. A verdict over this window spans a gap where nothing was observed.'),
112
+ .describe('Continuity of the SDK-to-daemon LINK for this tab, present for any tab the daemon has a record of: how long this attachment has lasted, how many times it dropped and came back, and when the last gap started and how long it lasted. `outages > 0` means a verdict over this window spans a gap where nothing was observed. These are link counters, NOT app health: they say nothing about whether the page origin is still serving, so a tab whose dev server has died reports `outages: 0` with a growing `connectedSinceMs` and is not evidence the app is up.'),
113
113
  }))
114
114
  .describe('Connected browser sessions with health state. `projectId` groups sessions by app (stable across port changes); `leased` marks a pool-managed headless context vs a human tab.'),
115
115
  why: z
@@ -125,6 +125,10 @@ export const RAW_TOOLS = [
125
125
  })
126
126
  .optional()
127
127
  .describe("Present ONLY when `sessions` is empty: the same answer as `why`, executable. `command` is the LITERAL command to run, sourced from this project's own package.json scripts and lockfile — it is absent, never guessed, when the project declares no dev script. `action` is one of daemon_split | start_dev_server | run_init | open_app | reopen_app. `daemon_split` outranks the rest and means the app IS running and instrumented, on a DIFFERENT daemon than the one you are attached to — do not start or re-init anything, read `reason`."),
128
+ talk_to_us: z
129
+ .string()
130
+ .optional()
131
+ .describe('Present ONLY when `sessions` is empty: a call with the founder for the person you are working with. Offer it; never act on it.'),
128
132
  lastKnown: z
129
133
  .object({
130
134
  sessionId: z.string(),
@@ -158,6 +162,8 @@ export const RAW_TOOLS = [
158
162
  ...(why === undefined ? {} : { why }),
159
163
  ...(next === undefined ? {} : { next_action: next }),
160
164
  ...(undefined === lastKnown ? {} : { lastKnown }),
165
+ // Setup that never connects is where most people give up; it is the talk worth having.
166
+ talk_to_us: DiscoveryInvite.AGENT,
161
167
  };
162
168
  }
163
169
  return { sessions };
@@ -261,7 +267,7 @@ export const RAW_TOOLS = [
261
267
  scope: asString(args['scope']) ?? '',
262
268
  mode,
263
269
  diff: true === args['diff'],
264
- }, deps.snapshots ?? SNAPSHOT_CACHE), mode), mode), mode)));
270
+ }, deps.snapshots ?? SNAPSHOT_CACHE), mode), mode), mode, resolved)));
265
271
  },
266
272
  },
267
273
  {
@@ -290,10 +296,7 @@ export const RAW_TOOLS = [
290
296
  .boolean()
291
297
  .optional()
292
298
  .describe('Return the `scope` element ITSELF instead of searching inside it. Use when the target is a plain layout container with no role, name, testid or text of its own — which is routinely the element that carries the handler, and is otherwise unreachable because every query excludes its own scope root. Requires `scope`.'),
293
- attrs: z
294
- .array(z.string())
295
- .optional()
296
- .describe("Attribute names to return per match, e.g. ['href'] to inventory links or ['src'] for images. Absent attributes are omitted; credential-bearing names are redacted."),
299
+ attrs: AttrNamesSchema.optional().describe("Attribute NAMES to return per match, e.g. ['href'] for links, ['src'] for images. Projects, never filters: 'name=value' is refused. Absent omitted; credentials redacted."),
297
300
  limit: countSchema
298
301
  .optional()
299
302
  .describe('Cap the returned descriptors to the first N (cuts tokens on broad queries). If more matched, the result carries total + truncated:true so the trim is never silent — narrow with name/scope.'),
@@ -371,6 +374,12 @@ export const RAW_TOOLS = [
371
374
  .optional()
372
375
  .describe('Structural clusters on the page — what IS here, to diagnose the miss.'),
373
376
  presentTestids: z.array(z.string()).optional(),
377
+ // Declared for the same reason presentRegions had to be: undeclared here, it is stripped
378
+ // from structuredContent without a word — and this field exists precisely to say a word.
379
+ presentTestidsTotal: z
380
+ .number()
381
+ .optional()
382
+ .describe('Present only when presentTestids was cut at its cap: how many there were. The list is document order, so a region low on the page is what got dropped — absence from the list proves nothing.'),
374
383
  knownEmptyState: z.boolean(),
375
384
  splitText: z
376
385
  .object({
@@ -504,7 +513,12 @@ export const MERGE_PLANS = [
504
513
  },
505
514
  {
506
515
  name: ReticleTool.SESSION,
507
- description: 'Session lifecycle and the human channel, by action: "tune" adjusts the presenter session (e.g. idle-end window); "yield" hands control back to the human between turns (mode: waiting|ask) and is MANDATORY before you stop driving; "end" terminates the session for good; "resume" clears a human pause; "messages" drains the human→agent inbox; "review" lists/resolves the mistakes a human pinned to elements; "narrate" states your intent on the presenter HUD.',
516
+ description:
517
+ // `yield` leads, and that is not a style choice. A catalogue prints ONE line per tool, so
518
+ // whatever comes first is what an agent discovers; when this sentence opened with `tune` the
519
+ // MANDATORY handback sat past the cut and no agent reading the catalogue ever learned it
520
+ // existed. The rule is: if a tool has an obligation, the obligation goes first.
521
+ 'Session lifecycle and the human channel, by action: "yield" hands control back to the human and is MANDATORY before you stop driving (mode: waiting|ask, between turns); "tune" adjusts the presenter session (e.g. idle-end window); "end" terminates the session for good; "resume" clears a human pause; "messages" drains the human→agent inbox; "review" lists/resolves the mistakes a human pinned to elements; "narrate" states your intent on the presenter HUD.',
508
522
  members: {
509
523
  tune: ReticleTool.SESSION_TUNE,
510
524
  yield: ReticleTool.YIELD,
@@ -609,24 +623,31 @@ function noteEmptyLeanTree(result, mode) {
609
623
  * wrapper or two is ordinary, so this is deliberately not `=== 0`.
610
624
  */
611
625
  const UNMOUNTED_ROOT_MAX_ELEMENTS = 3;
612
- /**
613
- * The third cause of an empty tree, and the one the walk cannot see: the app is not mounted.
614
- *
615
- * Reported from the field with the react-three-fiber crash. The source-mapping stamp threw inside
616
- * R3F's commit phase, React unmounted the entire tree, and the page went white — and the snapshot
617
- * answered `{ tree: "", nodes: 0 }`, which is also what a page that has not rendered yet answers.
618
- * The reporter spent a diagnosis pass separating the two, and said that detecting a dead root would
619
- * have pointed straight at the cause.
620
- *
621
- * The other two notes here explain an empty tree by what the WALK passed over. This one cannot: a
622
- * walk that visited nothing has nothing to have skipped. It reads the DOM count instead, which is
623
- * why the browser now reports it, and it fires only when the skip counts are silent — a page whose
624
- * elements were all hidden has plenty of DOM and belongs to `noteHiddenPage`.
625
- *
626
- * Like both of its neighbours it names a fact and hands over the next read, and does not diagnose:
627
- * "not mounted" is certain from the count, WHY is not, and the console is where the answer is.
628
- */
629
- function noteUnmountedRoot(result, mode) {
626
+ function crashEvidence(session) {
627
+ const since = asRecord(session)['eventsSince'];
628
+ if ('function' !== typeof since)
629
+ return undefined;
630
+ let events;
631
+ try {
632
+ events = since.call(session, 0);
633
+ }
634
+ catch {
635
+ return undefined;
636
+ }
637
+ if (!Array.isArray(events))
638
+ return undefined;
639
+ const errors = events.filter((e) => {
640
+ const type = asString(asRecord(e)['type']);
641
+ return EventType.CONSOLE_ERROR === type || EventType.ERROR_UNCAUGHT === type;
642
+ });
643
+ if (0 === errors.length)
644
+ return undefined;
645
+ const first = errors
646
+ .map((e) => asString(asRecord(asRecord(e)['data'])['message']))
647
+ .find((m) => undefined !== m && '' !== m);
648
+ return { count: errors.length, first: first ?? '' };
649
+ }
650
+ function noteUnmountedRoot(result, mode, session) {
630
651
  if (SnapshotMode.STATUS === mode)
631
652
  return result;
632
653
  const row = asRecord(result);
@@ -637,12 +658,31 @@ function noteUnmountedRoot(result, mode) {
637
658
  const elements = asNumber(row['domElements']);
638
659
  if (elements === undefined || elements > UNMOUNTED_ROOT_MAX_ELEMENTS)
639
660
  return result;
661
+ const mount = `the tree is empty because there is almost nothing in the DOM: ${String(elements)} ` +
662
+ `element(s) under this scope. That is a mount container with nothing rendered into it, not a ` +
663
+ `page whose contents were skipped — so the app is UNMOUNTED rather than slow, and waiting ` +
664
+ `will not change it. `;
665
+ // An uncaught error beside an empty mount container is the difference between "has not started"
666
+ // and "started and died", which are the two readings of this count and want opposite next moves.
667
+ // Nothing here is framework-specific, and deliberately so: the condition is a known mount
668
+ // container that holds nothing with an uncaught error beside it, which is as true of a Vue or
669
+ // Svelte root as a React one.
670
+ const crash = crashEvidence(session);
671
+ if (crash !== undefined) {
672
+ return {
673
+ ...row,
674
+ note: mount +
675
+ `The app did not merely fail to start — it CRASHED: ${String(crash.count)} uncaught ` +
676
+ `error(s) were logged in this session` +
677
+ ('' === crash.first ? '' : `, the first: "${crash.first}"`) +
678
+ `. Read reticle_console for the rest, and check anything that runs inside a framework ` +
679
+ `commit phase. Loading the app again will reproduce it rather than fix it.`,
680
+ };
681
+ }
640
682
  return {
641
683
  ...row,
642
- note: `the tree is empty because there is almost nothing in the DOM: ${String(elements)} ` +
643
- `element(s) under this scope. That is a mount container with nothing rendered into it, not a ` +
644
- `page whose contents were skipped — so the app is UNMOUNTED rather than slow, and waiting ` +
645
- `will not change it. An app that was rendering and then stopped has usually thrown: read ` +
684
+ note: mount +
685
+ `An app that was rendering and then stopped has usually thrown: read ` +
646
686
  `reticle_console for an uncaught error, and check anything that runs inside a framework ` +
647
687
  `commit phase. If the app has genuinely not started yet, load it and snapshot again.`,
648
688
  };
@@ -18,6 +18,12 @@ import { ReticleTool } from '@reticlehq/core';
18
18
  import { BugAttribution, BugSource, ContradictionKind, isAbsenceDerived, } from '@reticlehq/core';
19
19
  /** Cap per tool call — a pathological crawl must not turn one call into a thousand events. */
20
20
  const MAX_BUGS_PER_CALL = 25;
21
+ /** The kinds in a replay's `regressed` list — bare strings, not the `{ kind }` objects above. */
22
+ function regressedKinds(value) {
23
+ if (!Array.isArray(value))
24
+ return [];
25
+ return value.filter((kind) => 'string' === typeof kind && kind.length > 0);
26
+ }
21
27
  function kindsOf(value) {
22
28
  if (!Array.isArray(value))
23
29
  return [];
@@ -127,7 +133,12 @@ export function bugsInResult(toolName, result) {
127
133
  const verdict = verdictOf(result);
128
134
  const passed = true === verdict;
129
135
  // 1. Contradictions — channels disagreeing. Invisible to a human watching the screen.
130
- for (const kind of kindsOf(result['contradictions'])) {
136
+ //
137
+ // `crossStep` is read alongside `contradictions` because a flow replay spells them that way: a
138
+ // step's window closes when the step ends, so a request fired at step 2 and still unanswered at
139
+ // step 5 sits outside every per-step window and is exactly the shape a long journey produces.
140
+ // Every whole-span contradiction a replay found was unreportable for want of this one name.
141
+ for (const kind of [...kindsOf(result['contradictions']), ...kindsOf(result['crossStep'])]) {
131
142
  // A contradiction is two of the APP's own channels disagreeing — never the agent's phrasing.
132
143
  // Omitted for the one kind Reticle can produce on its own: see contradictionAttribution.
133
144
  bugs.push({
@@ -176,7 +187,26 @@ export function bugsInResult(toolName, result) {
176
187
  attribution: BugAttribution.UNCLASSIFIED,
177
188
  });
178
189
  }
179
- // 4. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
190
+ // 4. A guarded defect that came BACK. The flow observed it, then observed it gone, and the app
191
+ // has now reacquired it — a claim no other detector in this file can make, because only a saved
192
+ // flow carries what it has already seen. It is the replay feature's stated payoff and it was
193
+ // counted nowhere: `regressed` is a list of KINDS, not of anomaly objects, so neither of the
194
+ // array readers above could see it.
195
+ for (const kind of regressedKinds(result['regressed'])) {
196
+ bugs.push({
197
+ source: BugSource.REPLAY,
198
+ kind,
199
+ // The replay carries no assertion of its own, so there is no green for this to have hidden
200
+ // behind. Whether it was a false green is a question about the run that reacquired the defect,
201
+ // not about the run that noticed.
202
+ falseGreen: passed,
203
+ tool: toolName,
204
+ // Same kind, same owner, wherever it arrives — the line `attributionOf` draws is the one the
205
+ // verdict already uses, and a defect does not change whose fault it was by coming back.
206
+ attribution: attributionOf(kind),
207
+ });
208
+ }
209
+ // 5. Replay failures — a saved flow that used to pass no longer does. That is a regression caught,
180
210
  // which is a different and stronger claim than "a check failed".
181
211
  if ('fail' === result['status'] && Array.isArray(result['failures'])) {
182
212
  for (const failure of result['failures'].slice(0, MAX_BUGS_PER_CALL)) {
@@ -78,9 +78,9 @@ export function detectStack(cwd, read = readFile) {
78
78
  }
79
79
  // Otherwise look for the app. The daemon's cwd is wherever the agent's client happened to launch,
80
80
  // which for a real repo is the ROOT while the app sits in `frontend/`, `web/`, or a declared
81
- // workspace. Reading one directory and giving up is why this returned nothing for every project
82
- // that actually had Reticle set up: 0 of 77 instrumented projects, and 0 of 166 `size: huge` ones.
83
- // The bigger the repo, the more certainly we failed.
81
+ // workspace. Reading one directory and giving up is why this returned nothing for essentially
82
+ // every project that actually had Reticle set up, and why the bigger the repo, the more certainly
83
+ // we failed. (The measurement that established that came from the field and stays in `plan/`.)
84
84
  //
85
85
  // `findWorkspaceApps` is init's discovery, reused rather than reimplemented: it already reads
86
86
  // declared workspaces (pnpm-workspace.yaml, package.json `workspaces`) AND scans top-level
@@ -1,4 +1,5 @@
1
1
  import type { Feedback } from '@reticlehq/core/telemetry';
2
+ /** Reports live beside the rest of the workspace, not in a temp dir somebody has to be told about. */
2
3
  interface SavedFeedback {
3
4
  path: string;
4
5
  /** A ready-to-run command that turns the file into an issue. */
@@ -13,8 +13,6 @@
13
13
  import { mkdirSync, writeFileSync } from 'node:fs';
14
14
  import { join } from 'node:path';
15
15
  import { ReticleDir } from '@reticlehq/core';
16
- /** Reports live beside the rest of the workspace, not in a temp dir somebody has to be told about. */
17
- const FEEDBACK_SUBDIR = 'feedback';
18
16
  /** Filesystem-safe, sortable, and unique enough for one report per second per kind. */
19
17
  const stamp = (now) => new Date(now).toISOString().replace(/[:.]/g, '-').replace('Z', '');
20
18
  const asMarkdown = (input, text, context) => {
@@ -39,7 +37,7 @@ const asMarkdown = (input, text, context) => {
39
37
  */
40
38
  export function saveFeedbackLocally(cwd, input, text, context, now = Date.now()) {
41
39
  try {
42
- const dir = join(cwd, ReticleDir.ROOT, FEEDBACK_SUBDIR);
40
+ const dir = join(cwd, ReticleDir.ROOT, ReticleDir.FEEDBACK_SUBDIR);
43
41
  mkdirSync(dir, { recursive: true });
44
42
  const path = join(dir, `${stamp(now)}-${input.kind ?? 'note'}.md`);
45
43
  writeFileSync(path, asMarkdown(input, text, context), 'utf8');
@@ -27,7 +27,7 @@ import { type SessionFacts } from './feedback-context.js';
27
27
  export declare const FEEDBACK_ENV = "RETICLE_FEEDBACK";
28
28
  /** What replaces a redacted match. Visible on purpose: a reader must see that something was removed. */
29
29
  export declare const REDACTED = "[redacted]";
30
- export interface Redaction {
30
+ interface Redaction {
31
31
  text: string;
32
32
  /** Which rules fired, deduped. Surfaced back to the author so a redaction is never silent. */
33
33
  removed: string[];
@@ -0,0 +1,6 @@
1
+ /** Run `drive` with every verdict underneath it attributed to the harness. */
2
+ export declare function withHarnessDrive<T>(drive: () => Promise<T>): Promise<T>;
3
+ /** True while a harness drive is in progress. */
4
+ export declare function harnessDriving(): boolean;
5
+ /** Tests only — a leaked span would mis-attribute every verdict after it. */
6
+ export declare function resetHarnessDrive(): void;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Whether the tool call running right now belongs to one of OUR drives.
3
+ *
4
+ * `reticle_verify { action: "explore" }` hands the app to a model inside the daemon, which drives
5
+ * through the same dispatch chokepoint every agent uses. That is the right design — the inner
6
+ * `act_and_wait` and `assert` calls are real verifications and must be counted — but it left every
7
+ * verdict a drive produced indistinguishable from one the user's own agent earned, under the same
8
+ * session and the same `actor: agent`. So "did this install ever verify anything by itself" had no
9
+ * answer, and any per-session rate read high by however much we drove.
10
+ *
11
+ * Module state, one flag, set for the span of the drive. The same one-hop pattern `tool-refused.ts`
12
+ * already uses for its pending reason, and for the same reason: the fact is produced in one place
13
+ * and needed in another, with a chokepoint in between that has no business taking an extra argument
14
+ * for a metric.
15
+ *
16
+ * A COUNTER rather than a boolean, so a drive nested inside a drive cannot clear the flag on its way
17
+ * out and leave the outer one unmarked.
18
+ */
19
+ let depth = 0;
20
+ /** Run `drive` with every verdict underneath it attributed to the harness. */
21
+ export async function withHarnessDrive(drive) {
22
+ depth += 1;
23
+ try {
24
+ return await drive();
25
+ }
26
+ finally {
27
+ // In a `finally` because a drive that THREW still drove: leaving the flag raised would attribute
28
+ // the user's own next verdict to a harness that is no longer running.
29
+ depth -= 1;
30
+ }
31
+ }
32
+ /** True while a harness drive is in progress. */
33
+ export function harnessDriving() {
34
+ return depth > 0;
35
+ }
36
+ /** Tests only — a leaked span would mis-attribute every verdict after it. */
37
+ export function resetHarnessDrive() {
38
+ depth = 0;
39
+ }
@@ -0,0 +1,32 @@
1
+ import { HudPanel, HudView, type HudUseData } from '@reticlehq/core';
2
+ /**
3
+ * How people use the in-page HUD, rolled into the session summary.
4
+ *
5
+ * Windowed like the tool histograms beside it: a periodic flush reports what happened since the
6
+ * last one, so a dashboard sums across a session's events. Names only — every key is a control id
7
+ * the wire schema already checked against core's closed list.
8
+ */
9
+ /** How many steps of the journey one window keeps. A journey longer than this is cut, and says so. */
10
+ export declare const HUD_JOURNEY_MAX = 60;
11
+ export interface HudSummary {
12
+ /** Presses per control; a toggle counts under `id:on` / `id:off` so a switch reads both ways. */
13
+ hudControls?: Record<string, number>;
14
+ /** Milliseconds the HUD spent in each view during the window. */
15
+ hudViewMs?: Partial<Record<HudView, number>>;
16
+ /** Milliseconds each panel was showing during the window. */
17
+ hudPanelMs?: Partial<Record<HudPanel, number>>;
18
+ /** The window's presses and view changes, in order, cut at HUD_JOURNEY_MAX. */
19
+ hudJourney?: string[];
20
+ /** Present, and true, only when the journey was cut. */
21
+ hudJourneyCut?: true;
22
+ }
23
+ export declare class HudMetrics {
24
+ #private;
25
+ constructor(now: () => number);
26
+ record(use: HudUseData): void;
27
+ get empty(): boolean;
28
+ /** The window so far. Open view and panel are counted up to now, and keep running after. */
29
+ summarize(): HudSummary;
30
+ /** Start the next window. The current view and panel carry over, timed from now. */
31
+ reset(): void;
32
+ }
@@ -0,0 +1,95 @@
1
+ import { isHudControl } from '@reticlehq/core/hud';
2
+ import { HudPanel, HudView } from '@reticlehq/core';
3
+ /**
4
+ * How people use the in-page HUD, rolled into the session summary.
5
+ *
6
+ * Windowed like the tool histograms beside it: a periodic flush reports what happened since the
7
+ * last one, so a dashboard sums across a session's events. Names only — every key is a control id
8
+ * the wire schema already checked against core's closed list.
9
+ */
10
+ /** How many steps of the journey one window keeps. A journey longer than this is cut, and says so. */
11
+ export const HUD_JOURNEY_MAX = 60;
12
+ export class HudMetrics {
13
+ #now;
14
+ #controls = new Map();
15
+ #viewMs = new Map();
16
+ #panelMs = new Map();
17
+ #journey = [];
18
+ #cut = false;
19
+ /** Did anything happen this window. A HUD merely sitting open is not worth a flush of its own. */
20
+ #changed = false;
21
+ #view;
22
+ #panel;
23
+ constructor(now) {
24
+ this.#now = now;
25
+ }
26
+ record(use) {
27
+ // A control the HUD never rendered is dropped here, the one place HUD use is counted, so free
28
+ // text cannot ride in on the id.
29
+ const control = use.control !== undefined && isHudControl(use.control) ? use.control : undefined;
30
+ if (control === undefined && use.view === undefined && use.panel === undefined)
31
+ return;
32
+ this.#changed = true;
33
+ if (control !== undefined) {
34
+ const key = use.toggle === undefined ? control : `${control}:${use.toggle}`;
35
+ this.#controls.set(key, (this.#controls.get(key) ?? 0) + 1);
36
+ this.#step(key);
37
+ }
38
+ if (use.view !== undefined && use.view !== this.#view?.value) {
39
+ this.#close(this.#view, this.#viewMs);
40
+ this.#view = { value: use.view, since: this.#now() };
41
+ this.#step(`view:${use.view}`);
42
+ }
43
+ if (use.panel !== undefined && use.panel !== this.#panel?.value) {
44
+ this.#close(this.#panel, this.#panelMs);
45
+ this.#panel = { value: use.panel, since: this.#now() };
46
+ if (use.panel !== HudPanel.NONE)
47
+ this.#step(`panel:${use.panel}`);
48
+ }
49
+ }
50
+ get empty() {
51
+ return !this.#changed;
52
+ }
53
+ /** The window so far. Open view and panel are counted up to now, and keep running after. */
54
+ summarize() {
55
+ const viewMs = this.#withOpen(this.#view, this.#viewMs);
56
+ const panelMs = this.#withOpen(this.#panel, this.#panelMs);
57
+ return {
58
+ ...(this.#controls.size > 0 ? { hudControls: Object.fromEntries(this.#controls) } : {}),
59
+ ...(viewMs.size > 0 ? { hudViewMs: Object.fromEntries(viewMs) } : {}),
60
+ ...(panelMs.size > 0 ? { hudPanelMs: Object.fromEntries(panelMs) } : {}),
61
+ ...(this.#journey.length > 0 ? { hudJourney: [...this.#journey] } : {}),
62
+ ...(this.#cut ? { hudJourneyCut: true } : {}),
63
+ };
64
+ }
65
+ /** Start the next window. The current view and panel carry over, timed from now. */
66
+ reset() {
67
+ const at = this.#now();
68
+ this.#controls.clear();
69
+ this.#viewMs.clear();
70
+ this.#panelMs.clear();
71
+ this.#journey.length = 0;
72
+ this.#cut = false;
73
+ this.#changed = false;
74
+ if (this.#view !== undefined)
75
+ this.#view = { ...this.#view, since: at };
76
+ if (this.#panel !== undefined)
77
+ this.#panel = { ...this.#panel, since: at };
78
+ }
79
+ #step(entry) {
80
+ if (this.#journey.length < HUD_JOURNEY_MAX)
81
+ this.#journey.push(entry);
82
+ else
83
+ this.#cut = true;
84
+ }
85
+ #close(open, into) {
86
+ if (open === undefined)
87
+ return;
88
+ into.set(open.value, (into.get(open.value) ?? 0) + Math.max(0, this.#now() - open.since));
89
+ }
90
+ #withOpen(open, closed) {
91
+ const out = new Map(closed);
92
+ this.#close(open, out);
93
+ return out;
94
+ }
95
+ }
@@ -1,5 +1,18 @@
1
1
  export declare function markDaemonStart(now: number): void;
2
- /** Report one client attaching. Best-effort; a metric must never affect whether a client connects. */
2
+ /**
3
+ * Report one client attaching, BY NAME. Best-effort; a metric must never affect whether a client
4
+ * connects.
5
+ *
6
+ * The name is required in practice, and that is the fix rather than a restriction. `connection.client`
7
+ * was defined and documented and never once populated: the only caller reported from
8
+ * `transport.connect().then(...)`, which resolves when the SSE stream opens — before the client has
9
+ * said who it is. `clientInfo` first exists at `oninitialized`, so the event carried every fact about
10
+ * the connection except the one that makes "which agent client converts best" answerable at all.
11
+ *
12
+ * An unnamed call reports NOTHING. A stream that opens and never completes a handshake is a probe,
13
+ * not a client attaching, and counting it was what put an unattributable row exactly where the name
14
+ * belonged.
15
+ */
3
16
  export declare function reportMcpConnected(client?: string, now?: () => number): void;
4
17
  /** Tests only. */
5
18
  export declare function resetMcpConnections(): void;
@@ -19,8 +19,23 @@ export function markDaemonStart(now) {
19
19
  daemonStartedAt = now;
20
20
  connectsSeen = 0;
21
21
  }
22
- /** Report one client attaching. Best-effort; a metric must never affect whether a client connects. */
22
+ /**
23
+ * Report one client attaching, BY NAME. Best-effort; a metric must never affect whether a client
24
+ * connects.
25
+ *
26
+ * The name is required in practice, and that is the fix rather than a restriction. `connection.client`
27
+ * was defined and documented and never once populated: the only caller reported from
28
+ * `transport.connect().then(...)`, which resolves when the SSE stream opens — before the client has
29
+ * said who it is. `clientInfo` first exists at `oninitialized`, so the event carried every fact about
30
+ * the connection except the one that makes "which agent client converts best" answerable at all.
31
+ *
32
+ * An unnamed call reports NOTHING. A stream that opens and never completes a handshake is a probe,
33
+ * not a client attaching, and counting it was what put an unattributable row exactly where the name
34
+ * belonged.
35
+ */
23
36
  export function reportMcpConnected(client, now = () => Date.now()) {
37
+ if (client === undefined)
38
+ return;
24
39
  try {
25
40
  const reconnect = connectsSeen > 0;
26
41
  connectsSeen += 1;
@@ -30,7 +45,7 @@ export function reportMcpConnected(client, now = () => Date.now()) {
30
45
  // A large value on the FIRST connect is the interesting case: Reticle was started and then
31
46
  // sat there. On a reconnect it is just how long the daemon has been up.
32
47
  daemonAgeMs: daemonStartedAt === undefined ? 0 : Math.max(0, now() - daemonStartedAt),
33
- ...(client !== undefined ? { client } : {}),
48
+ client,
34
49
  // WHAT THE AGENT HAD TO LOOK AT. Most clients that attach never call a tool, and that cohort
35
50
  // emits nothing at all — an agent that reads the instructions, learns nothing is wired and
36
51
  // stops has refused nothing, so `tool_refused` cannot see it either. Read off the same flag
@@ -1,5 +1,21 @@
1
1
  /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
2
- export declare function noteOnboardingFirst(toolName: string, args: Record<string, unknown>): void;
2
+ export declare function noteOnboardingFirst(toolName: string): void;
3
+ /**
4
+ * The first ACTION of this run, and the fact that this install has been driven at all.
5
+ *
6
+ * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
7
+ * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
8
+ * one call as two different firsts — and the worry was backwards: this server's instructions tell
9
+ * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
10
+ * the ONBOARD phase read as "looked, never acted, then produced a verdict from nowhere". A call that
11
+ * both acts and proves genuinely did both, and `first_act` and `first_verdict` are different steps
12
+ * in different positions of the same funnel.
13
+ *
14
+ * Taking the decision from the caller is what keeps it from happening again: the dispatcher already
15
+ * owns the set of tools that drive the page, and a second copy of that set here is a second thing to
16
+ * update when a fourth driving tool arrives.
17
+ */
18
+ export declare function noteActed(args: Record<string, unknown>): void;
3
19
  /**
4
20
  * The first verdict of this run.
5
21
  *
@@ -7,5 +23,18 @@ export declare function noteOnboardingFirst(toolName: string, args: Record<strin
7
23
  * and only the site that already decided "this was a verification" can say so. Reported from there.
8
24
  */
9
25
  export declare function noteFirstVerdict(): void;
26
+ /**
27
+ * Something was driven on the user's own project — the FIRST_RUN rung between "the install worked"
28
+ * and "they used it".
29
+ *
30
+ * Declared in the closed step list from the day the funnel was written and emitted by no code at
31
+ * all, so any chart rendered over the declared order showed a 100% cliff in the middle of the
32
+ * conversion story. It reads as catastrophe and was an unwired emit.
33
+ *
34
+ * Reported from `noteActed`, so the one place that knows a tool drove the page is the one place that
35
+ * says so. That includes a drive we run on the user's behalf, whose own actions cross the same
36
+ * chokepoint — a drive that is refused before its first action drove nothing and says nothing.
37
+ */
38
+ export declare function noteDriven(): void;
10
39
  /** Arm again for the next daemon run — the tests' seam, mirroring resetAppInstrumented. */
11
40
  export declare function resetOnboardingFirsts(): void;
@@ -14,9 +14,9 @@ import { reportOnboardingStep } from './onboarding-funnel.js';
14
14
  * The flags are module state rather than per-session, because a person who tours in one tab and
15
15
  * drives in another has still only crossed each of these once.
16
16
  */
17
- const seen = { look: false, act: false, verdict: false };
17
+ const seen = { look: false, act: false, verdict: false, driven: false };
18
18
  /** A tool call the daemon just ran. Called from the dispatch chokepoint, which sees every one. */
19
- export function noteOnboardingFirst(toolName, args) {
19
+ export function noteOnboardingFirst(toolName) {
20
20
  if (!seen.look && LOOK_TOOLS.has(toolName)) {
21
21
  seen.look = true;
22
22
  void reportOnboardingStep({
@@ -25,16 +25,35 @@ export function noteOnboardingFirst(toolName, args) {
25
25
  status: OnboardingStepStatus.COMPLETED,
26
26
  });
27
27
  }
28
- // An ACT, not a verdict tool that happens to act: `act_and_wait` is counted below as the verdict
29
- // it produces, and counting it here too would report one call as two different firsts.
30
- if (!seen.act && toolName === ReticleTool.ACT && args['action'] !== ActionType.HOVER) {
31
- seen.act = true;
32
- void reportOnboardingStep({
33
- phase: OnboardingPhase.ONBOARD,
34
- step: 'first_act',
35
- status: OnboardingStepStatus.COMPLETED,
36
- });
37
- }
28
+ }
29
+ /**
30
+ * The first ACTION of this run, and the fact that this install has been driven at all.
31
+ *
32
+ * Called from the dispatch chokepoint's own action branch rather than matched on a tool name here.
33
+ * The name test was `toolName === ReticleTool.ACT`, on the worry that `act_and_wait` would report
34
+ * one call as two different firsts — and the worry was backwards: this server's instructions tell
35
+ * every agent to PREFER `act_and_wait`, so the step measured the tool we ask agents not to use and
36
+ * the ONBOARD phase read as "looked, never acted, then produced a verdict from nowhere". A call that
37
+ * both acts and proves genuinely did both, and `first_act` and `first_verdict` are different steps
38
+ * in different positions of the same funnel.
39
+ *
40
+ * Taking the decision from the caller is what keeps it from happening again: the dispatcher already
41
+ * owns the set of tools that drive the page, and a second copy of that set here is a second thing to
42
+ * update when a fourth driving tool arrives.
43
+ */
44
+ export function noteActed(args) {
45
+ // A hover changes nothing and is not an action anybody drove.
46
+ if (args['action'] === ActionType.HOVER)
47
+ return;
48
+ noteDriven();
49
+ if (seen.act)
50
+ return;
51
+ seen.act = true;
52
+ void reportOnboardingStep({
53
+ phase: OnboardingPhase.ONBOARD,
54
+ step: 'first_act',
55
+ status: OnboardingStepStatus.COMPLETED,
56
+ });
38
57
  }
39
58
  /**
40
59
  * The first verdict of this run.
@@ -52,6 +71,28 @@ export function noteFirstVerdict() {
52
71
  status: OnboardingStepStatus.COMPLETED,
53
72
  });
54
73
  }
74
+ /**
75
+ * Something was driven on the user's own project — the FIRST_RUN rung between "the install worked"
76
+ * and "they used it".
77
+ *
78
+ * Declared in the closed step list from the day the funnel was written and emitted by no code at
79
+ * all, so any chart rendered over the declared order showed a 100% cliff in the middle of the
80
+ * conversion story. It reads as catastrophe and was an unwired emit.
81
+ *
82
+ * Reported from `noteActed`, so the one place that knows a tool drove the page is the one place that
83
+ * says so. That includes a drive we run on the user's behalf, whose own actions cross the same
84
+ * chokepoint — a drive that is refused before its first action drove nothing and says nothing.
85
+ */
86
+ export function noteDriven() {
87
+ if (seen.driven)
88
+ return;
89
+ seen.driven = true;
90
+ void reportOnboardingStep({
91
+ phase: OnboardingPhase.FIRST_RUN,
92
+ step: 'driven',
93
+ status: OnboardingStepStatus.COMPLETED,
94
+ });
95
+ }
55
96
  /** Looking: the tools that read the page without changing it. */
56
97
  const LOOK_TOOLS = new Set([
57
98
  ReticleTool.SNAPSHOT,
@@ -64,4 +105,5 @@ export function resetOnboardingFirsts() {
64
105
  seen.look = false;
65
106
  seen.act = false;
66
107
  seen.verdict = false;
108
+ seen.driven = false;
67
109
  }