@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
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Learn a flow step's source file from the element its anchor resolved to on a replay.
3
+ *
4
+ * A step recorded before the build stamped the app names no file, so a scoped re-verify cannot say
5
+ * which edits it covers. Every replay already asks the page for each anchor's element, and that
6
+ * element carries its stamp now: this records the answer at the one place every resolution passes
7
+ * through (the session's QUERY), rather than threading it through each step runner.
8
+ */
9
+ import { ReticleCommand, asRecord, asString } from '@reticlehq/core';
10
+ import { anchorQueryArgs } from './flow-step-runners.js';
11
+ /** `src/Pay.tsx:12:4` or `src/Pay.tsx:12` → `{file, line}`; anything else is not a location. */
12
+ export function parseCompactSource(value) {
13
+ const match = /^(.+?):(\d+)(?::\d+)?$/.exec(value);
14
+ const file = match?.[1];
15
+ const line = match?.[2];
16
+ if (file === undefined || line === undefined)
17
+ return undefined;
18
+ return { file, line: Number(line) };
19
+ }
20
+ function keyOf(args) {
21
+ if (args === undefined)
22
+ return undefined;
23
+ const by = asString(args['by']);
24
+ const value = asString(args['value']);
25
+ if (by === undefined || value === undefined)
26
+ return undefined;
27
+ return JSON.stringify([by, value, asString(args['name']) ?? null]);
28
+ }
29
+ /** The one element a query found, when exactly one was found and it carries a stamp. */
30
+ function soleSource(result) {
31
+ const elements = asRecord(result)?.['elements'];
32
+ if (!Array.isArray(elements) || elements.length !== 1)
33
+ return undefined;
34
+ const source = asString(asRecord(elements[0])?.['source']);
35
+ return source === undefined ? undefined : parseCompactSource(source);
36
+ }
37
+ /** Wrap a replay session so every anchor QUERY that resolves to one stamped element is remembered. */
38
+ export function recordingSources(inner) {
39
+ const sources = new Map();
40
+ const session = new Proxy(inner, {
41
+ // Bound to the target, never the proxy: a real Session keeps `#private` state, and a method
42
+ // called with the proxy as `this` throws reading it.
43
+ get(target, prop) {
44
+ if (prop !== 'command') {
45
+ const value = Reflect.get(target, prop, target);
46
+ return 'function' === typeof value ? value.bind(target) : value;
47
+ }
48
+ return async (name, args) => {
49
+ const result = await target.command(name, args);
50
+ const key = name === ReticleCommand.QUERY ? keyOf(args) : undefined;
51
+ const source = result.ok ? soleSource(result.result) : undefined;
52
+ if (key !== undefined && source !== undefined)
53
+ sources.set(key, source);
54
+ return result;
55
+ };
56
+ },
57
+ });
58
+ return { session, sources };
59
+ }
60
+ function learnSteps(steps, sources) {
61
+ let changed = false;
62
+ const next = steps.map((step) => {
63
+ const nested = step.steps === undefined ? undefined : learnSteps(step.steps, sources);
64
+ const learned = step.source === undefined
65
+ ? sources.get(keyOf(anchorQueryArgs(step.anchor) ?? undefined) ?? '')
66
+ : undefined;
67
+ if (learned === undefined && nested?.changed !== true)
68
+ return step;
69
+ changed = true;
70
+ return {
71
+ ...step,
72
+ ...(learned === undefined ? {} : { source: learned }),
73
+ ...(nested === undefined ? {} : { steps: nested.steps }),
74
+ };
75
+ });
76
+ return { steps: next, changed };
77
+ }
78
+ /** The flow with every sourceless step given the file its anchor resolved to; undefined if none was. */
79
+ export function withLearnedSources(flow, sources) {
80
+ if (0 === sources.size)
81
+ return undefined;
82
+ const { steps, changed } = learnSteps(flow.steps, sources);
83
+ return changed ? { ...flow, steps } : undefined;
84
+ }
@@ -1,4 +1,4 @@
1
- import { type FlowFile } from '@reticlehq/core';
1
+ import { type ProjectId, type FlowFile } from '@reticlehq/core';
2
2
  import { type FlowResult } from './flow-result.js';
3
3
  /**
4
4
  * Change one thing about a saved flow, and write the same file that load resolved.
@@ -21,12 +21,12 @@ import { type FlowResult } from './flow-result.js';
21
21
  * every rule the document has ever been subject to.
22
22
  */
23
23
  export interface NarrowWritePort {
24
- load: (name: string, projectId?: string) => Promise<FlowResult<FlowFile>>;
25
- resolvePath: (name: string, projectId?: string) => Promise<string | null>;
24
+ load: (name: string, projectId?: ProjectId) => Promise<FlowResult<FlowFile>>;
25
+ resolvePath: (name: string, projectId?: ProjectId) => Promise<string | null>;
26
26
  write: (path: string, contents: string) => Promise<void>;
27
27
  serialize: (flow: FlowFile) => string;
28
28
  }
29
- export declare function changeInPlace<T>(port: NarrowWritePort, name: string, projectId: string | undefined, change: (flow: FlowFile) => {
29
+ export declare function changeInPlace<T>(port: NarrowWritePort, name: string, projectId: ProjectId | undefined, change: (flow: FlowFile) => {
30
30
  next: FlowFile;
31
31
  value: T;
32
32
  }): Promise<FlowResult<T>>;
@@ -7,13 +7,6 @@
7
7
  * stop/save can warn instead of writing a flow that fails on the next click.
8
8
  */
9
9
  import { type ReticleEvent } from '@reticlehq/core';
10
- /**
11
- * The page a recording step sat on, comparable across path routers and hash routers.
12
- *
13
- * A startPath is stored as pathname+hash (`/#/search`); a ROUTE_CHANGE carries them separately.
14
- * Without this, every hash-router page compares as `/` and a backtrack is invisible.
15
- */
16
- export declare function recordingRouteOf(pathname: string, hash?: string): string;
17
10
  /**
18
11
  * Warn when the journey left a page and came back. Linear A→B→C is silent: that still cannot
19
12
  * replay either if a later click needs a previous page, but the reported failure is specifically
@@ -17,7 +17,7 @@ function asString(value) {
17
17
  * A startPath is stored as pathname+hash (`/#/search`); a ROUTE_CHANGE carries them separately.
18
18
  * Without this, every hash-router page compares as `/` and a backtrack is invisible.
19
19
  */
20
- export function recordingRouteOf(pathname, hash = '') {
20
+ function recordingRouteOf(pathname, hash = '') {
21
21
  const hashIdx = pathname.indexOf('#/');
22
22
  if (hashIdx >= 0) {
23
23
  return routePathOf(pathname.slice(0, hashIdx) || '/', pathname.slice(hashIdx));
@@ -12,7 +12,7 @@
12
12
  */
13
13
  import type { FlowStepResult } from '@reticlehq/core';
14
14
  /** Set on a replay that stopped early; omitted entirely otherwise, so a clean pass stays flat. */
15
- export interface ReplayHalt {
15
+ interface ReplayHalt {
16
16
  /** Index of the step that stopped the run. */
17
17
  atStep: number;
18
18
  /** How many of the flow's steps were never attempted because of it. */
@@ -24,3 +24,4 @@ export interface ReplayHalt {
24
24
  * defect restated as its own fix.
25
25
  */
26
26
  export declare function haltedFrom(results: readonly FlowStepResult[], total: number): ReplayHalt | undefined;
27
+ export {};
@@ -1,4 +1,4 @@
1
- import { type FlowExpect } from '@reticlehq/core';
1
+ import { type Predicate } from '@reticlehq/core';
2
2
  /** One captured agent action, normalized for replay. */
3
3
  export interface RecordedStep {
4
4
  /** ReticleTool.ACT | ReticleTool.ACT_SEQUENCE */
@@ -8,7 +8,7 @@ export interface RecordedStep {
8
8
  /** false if any ref could not be normalized to a testid (replay only valid in-session). */
9
9
  stable: boolean;
10
10
  /** Optional post-condition annotation carried into the on-disk flow's expect. */
11
- expect?: FlowExpect;
11
+ expect?: Predicate;
12
12
  /**
13
13
  * The route this step ran on. RECORDER-INTERNAL: it never reaches the on-disk flow.
14
14
  *
@@ -1,9 +1,8 @@
1
1
  import { REDACTED_FILL, secretEnvKey } from './fields/flow-secret-field.js';
2
+ import { FlowPredicateSchema } from '@reticlehq/core';
2
3
  import { DANGEROUS_ACTION_CONFIRM_ARG, ReticleCommand, QueryBy, } from '@reticlehq/core';
3
4
  import { ReticleTool } from '@reticlehq/core';
4
5
  import { formatStepAddress } from 'open-verification';
5
- import { predicateToExpect, enforcedOnReplay } from '../../judgement/outcome/predicate-to-expect.js';
6
- import { PredicateSchema } from '@reticlehq/engine/question/predicate/predicate.js';
7
6
  import { asRecord, asString } from '@reticlehq/core';
8
7
  /**
9
8
  * The note attached when a testid resolves to multiple live elements (the first match is used).
@@ -93,7 +92,7 @@ route) {
93
92
  // that is never recorded cannot contribute to either.
94
93
  const step = compileActStep(args, res);
95
94
  // Keep the assertion the agent actually made. `act_and_wait { until }` IS the agent saying what
96
- // success means — 12 of 14 calls in a day carried one — and dropping it produced a flow graded
95
+ // success means — the large majority of calls carry one — and dropping it produced a flow graded
97
96
  // "assertion-free: it will pass even if the feature is broken", which is the regression-suite
98
97
  // story failing at its last step. Only kinds FlowExpect can express survive; see
99
98
  // predicate-to-expect.ts.
@@ -105,16 +104,21 @@ route) {
105
104
  /**
106
105
  * A declared predicate, as a recorded expectation — or nothing.
107
106
  *
108
- * The ONE place this conversion happens, because it is the rule that decides whether a saved flow
109
- * proves anything, and two copies of it would drift. Only kinds a replay actually CHECKS survive:
110
- * recording an unenforced assertion grades the flow "asserted" while nothing verifies it, which is a
111
- * false green inside the feature built to prevent them. See enforcedOnReplay.
107
+ * It is now the SAME OBJECT. A step's `expect` is a `Predicate`, so what the agent declared in
108
+ * `until` is what the flow file stores, and there is no conversion left to lose anything in.
109
+ *
110
+ * What was here before was a lossy round trip through a flat struct with one slot per kind. An
111
+ * `allOf[netA, netB]` saved as one of the two with nothing said; anything the struct could not
112
+ * express was dropped, and a filter then removed the kinds replay would not have checked anyway.
113
+ * Every one of those was a claim the agent made and the file did not keep.
112
114
  */
113
115
  function enforceableExpect(raw) {
114
116
  if (raw === undefined)
115
117
  return undefined;
116
- const parsed = PredicateSchema.safeParse(raw);
117
- return parsed.success ? enforcedOnReplay(predicateToExpect(parsed.data)) : undefined;
118
+ // The flow file's own schema, so capture keeps exactly what a load would accept — a predicate
119
+ // holding a session ref is refused by both, rather than recorded here and rejected on read.
120
+ const parsed = FlowPredicateSchema.safeParse(raw);
121
+ return parsed.success ? parsed.data : undefined;
118
122
  }
119
123
  /**
120
124
  * Compile a single reticle_act invocation into a normalized RecordedStep using the action result. Anchor
@@ -1,22 +1,22 @@
1
- import type { FlowExpect } from '@reticlehq/core';
1
+ import type { Predicate } from '@reticlehq/core';
2
2
  export declare class AnnotationStore {
3
3
  #private;
4
4
  /** Testids whose CONTENT must not be asserted at replay (mark-dynamic). Copy, never the live array. */
5
5
  dynamic(name: string): string[];
6
6
  /** The flow's golden end-condition (success-state), or undefined. */
7
- success(name: string): FlowExpect | undefined;
7
+ success(name: string): Predicate | undefined;
8
8
  /** The flow's declared business goal (intent annotation), or undefined. */
9
9
  intent(name: string): string | undefined;
10
10
  /** Per-step expect predicates compiled from assert-* annotations, keyed by step index. */
11
- stepExpect(name: string): Map<number, FlowExpect>;
11
+ stepExpect(name: string): Map<number, Predicate>;
12
12
  /** Append a dynamic testid (mark-dynamic). De-duped so repeated marks stay idempotent. */
13
13
  addDynamic(name: string, testid: string): void;
14
14
  /** Set the flow's success expectation (success-state); a later set overwrites. */
15
- setSuccess(name: string, expect: FlowExpect): void;
15
+ setSuccess(name: string, expect: Predicate): void;
16
16
  /** Set the flow's declared business goal (intent); a later set overwrites. */
17
17
  setIntent(name: string, text: string): void;
18
18
  /** Set one step's expect (assert-signal / assert-visible); a later set on the same index wins. */
19
- setStepExpect(name: string, index: number, expect: FlowExpect): void;
19
+ setStepExpect(name: string, index: number, expect: Predicate): void;
20
20
  /** Drop a recording's annotations (called after reticle_flow_save folds them onto disk). */
21
21
  clear(name: string): void;
22
22
  }
@@ -1,4 +1,5 @@
1
1
  import { dirname } from 'node:path';
2
+ import { writeFileAtomic } from '../../../memory/project/fs/write-atomic.js';
2
3
  import { z } from 'zod';
3
4
  import { reticleDirPaths } from '../../../memory/project/dir/reticle-dir.js';
4
5
  import { withFileLock } from '../../../memory/project/file-lock.js';
@@ -77,9 +78,7 @@ export class AssertionTiersStore {
77
78
  async save(tiers) {
78
79
  await this.#fs.mkdir(dirname(this.#path));
79
80
  const body = `${JSON.stringify({ version: ASSERTION_TIERS_VERSION, flows: tiers }, null, 2)}\n`;
80
- const tmp = `${this.#path}.tmp`;
81
- await this.#fs.writeFile(tmp, body);
82
- await this.#fs.rename(tmp, this.#path);
81
+ await writeFileAtomic(this.#fs, this.#path, body);
83
82
  }
84
83
  /** Record one flow's assertion shape + covered sources after it PASSED. Never breaks a replay. */
85
84
  async recordPassing(name, steps, sources = []) {
@@ -1,4 +1,5 @@
1
1
  import { dirname } from 'node:path';
2
+ import { writeFileAtomic } from '../../../memory/project/fs/write-atomic.js';
2
3
  import { reticleDirPaths } from '../../../memory/project/dir/reticle-dir.js';
3
4
  import { FlakeFileSchema, emptyRecord, isFlaky, recordOutcome, } from '../recording/flake.js';
4
5
  const FLAKE_FILE_VERSION = 1;
@@ -38,9 +39,7 @@ export class FlakeStore {
38
39
  async save(ledger) {
39
40
  await this.#fs.mkdir(dirname(this.#path));
40
41
  const body = `${JSON.stringify({ version: FLAKE_FILE_VERSION, flows: ledger }, null, 2)}\n`;
41
- const tmp = `${this.#path}.tmp`;
42
- await this.#fs.writeFile(tmp, body);
43
- await this.#fs.rename(tmp, this.#path);
42
+ await writeFileAtomic(this.#fs, this.#path, body);
44
43
  }
45
44
  /** Accrue one unchanged-code replay outcome for a flow and persist. */
46
45
  async record(flow, passed) {
@@ -24,7 +24,7 @@ interface CoverageDimension {
24
24
  /** Declared-but-never-exercised members — what "everything" is silently missing. */
25
25
  uncovered: string[];
26
26
  }
27
- export interface Coverage {
27
+ interface Coverage {
28
28
  testids: CoverageDimension;
29
29
  signals: CoverageDimension;
30
30
  flows: CoverageDimension;
@@ -9,7 +9,7 @@
9
9
  * - one flow throwing never sinks the run — it is captured as that item's outcome, because a suite that
10
10
  * aborts on the first crash tells you nothing about the other 199 flows.
11
11
  */
12
- export interface Outcome<T> {
12
+ interface Outcome<T> {
13
13
  ok: boolean;
14
14
  value?: T;
15
15
  error?: string;
@@ -24,3 +24,4 @@ export declare function mapWithConcurrency<I, T>(items: readonly I[], limit: num
24
24
  * and never below 1. Explicit `requested` (the tool's `parallel` arg) wins when it is a sane number.
25
25
  */
26
26
  export declare function resolveConcurrency(flowCount: number, poolCapacity: number, requested?: number): number;
27
+ export {};
@@ -1,3 +1,17 @@
1
+ /**
2
+ * Which flows a suite run replays, resolved against the store.
3
+ *
4
+ * Shared by the MCP tool and the `reticle verify` command so the two cannot disagree about what a
5
+ * selection means.
6
+ *
7
+ * Lives beside the flow store rather than under `suite/` on purpose: it needs `flowsForSession`, and
8
+ * from `suite/` that import made `suite` and `flows` need EACH OTHER -- a mutual pair, which the
9
+ * reach guard forbids growing because two directories that need each other cannot be read, moved or
10
+ * tested apart. Here it adds no edge at all: `flows` reaching itself is nothing, and `cli -> flows`
11
+ * already existed. The pure rule lives in core (`selectFlows`); this is the part that has to read
12
+ * the files, because a label lives in one and `list` returns only names.
13
+ */
14
+ import { type ProjectId } from '@reticlehq/core';
1
15
  import type { ToolDeps } from '../../surface/tools/tool-kit.js';
2
16
  /**
3
17
  * Resolve what a suite run should replay, and what it is holding back.
@@ -9,7 +23,7 @@ import type { ToolDeps } from '../../surface/tools/tool-kit.js';
9
23
  * would turn a corrupt file into missing coverage nobody is told about, and replay reports the parse
10
24
  * failure far better than a silent omission does.
11
25
  */
12
- export declare function resolveSuiteSelection(deps: ToolDeps, projectId: string | undefined, args: Record<string, unknown>): Promise<{
26
+ export declare function resolveSuiteSelection(deps: ToolDeps, projectId: ProjectId | undefined, args: Record<string, unknown>): Promise<{
13
27
  run: string[];
14
28
  quarantined: string[];
15
29
  unmatched: string[];
@@ -11,7 +11,7 @@
11
11
  * already existed. The pure rule lives in core (`selectFlows`); this is the part that has to read
12
12
  * the files, because a label lives in one and `list` returns only names.
13
13
  */
14
- import { FLOW_FILE_VERSION, orderFlows, selectFlows } from '@reticlehq/core';
14
+ import { FLOW_FILE_VERSION, orderFlows, selectFlows, } from '@reticlehq/core';
15
15
  import { flowsForSession } from './flow-store-for-session.js';
16
16
  /**
17
17
  * Resolve what a suite run should replay, and what it is holding back.
@@ -7,6 +7,7 @@ import { ReticleTool } from '@reticlehq/core';
7
7
  import { asNumber, asRecord, asString } from '@reticlehq/core';
8
8
  import { workerCountSchema } from '../../surface/tools/args/numeric-bounds.js';
9
9
  import { loadNamedFlows, resolveChangedFiles } from '../../command/cli/cli-flow-commands.js';
10
+ import { projectDirFor } from '../../memory/project/session-root.js';
10
11
  import { affectedSavedFlows } from './change/flow-sources.js';
11
12
  import { FLOW_TOOLS } from './flow-tools.js';
12
13
  import { findContradictions } from '@reticlehq/engine/disagreement/contradictions.js';
@@ -62,10 +63,12 @@ export const VERIFY_CHANGE_TOOLS = [
62
63
  ? args['files'].filter((f) => 'string' === typeof f)
63
64
  : [];
64
65
  const since = asString(args['since']);
65
- // The daemon's cwd is not the project — it is wherever the MCP host started it, which for a
66
- // globally-registered server is `/` or `$HOME`. `deps.reticleRoot` is the project directory
67
- // this daemon was configured with, and it is the right tree to diff.
68
- const changed = await resolveChangedFiles(files, since, deps.reticleRoot);
66
+ // The tree to diff is the SESSION's project, and it is neither of the two things this line
67
+ // used to say it was: `deps.reticleRoot` is the daemon's `.reticle` SUBDIRECTORY, and the
68
+ // daemon's cwd is wherever the MCP host started it — `/` or `$HOME` for a globally-registered
69
+ // server. Both answer about the wrong repository while reporting a clean "nothing changed",
70
+ // which reads as a pass. `projectDirFor` is the directory holding the resolved root.
71
+ const changed = await resolveChangedFiles(files, since, projectDirFor(deps, asString(args['sessionId'])));
69
72
  const changedFiles = changed.files;
70
73
  // Same root flow_save wrote to. A read that resolved differently would report "no flows
71
74
  // covered this change" over flows that exist, which reads as a clean result rather than
@@ -181,6 +184,19 @@ export const VERIFY_CHANGE_TOOLS = [
181
184
  measured: extra.measured,
182
185
  };
183
186
  }
187
+ // The mirror of `earnedNo`: a green from flows that ran only because their sources are unknown
188
+ // says nothing about these files either. At least one attributed flow has to have passed.
189
+ if (0 === attributedFailures(affected, unknownProvenance).length) {
190
+ return {
191
+ verified: Verified.UNKNOWN,
192
+ because: `the flows that ran passed, but Reticle cannot tell which sources any of them cover (${affected.join(', ')}), so this says nothing about the files you changed. Drive the change directly; the drive is saved as a flow with its sources stamped`,
193
+ changedFiles,
194
+ flowsRun: affected,
195
+ suite,
196
+ unknownProvenance,
197
+ measured: extra.measured,
198
+ };
199
+ }
184
200
  return {
185
201
  verified: Verified.YES,
186
202
  because: `all ${String(passed)} flows covering these files passed${provenanceNote}${extra.untouched === undefined ? '' : `, with ${String(extra.untouched.length)} controls left undriven`}`,
@@ -223,6 +239,7 @@ async function inspectAfterReplay(deps, args, cursor) {
223
239
  currentDocumentId: session.currentDocumentId,
224
240
  currentEditEpoch: session.currentEditEpoch,
225
241
  appOrigin: session.url,
242
+ background: session.background,
226
243
  // The replay is the action, and the cursor is where it started.
227
244
  actionSince: cursor,
228
245
  });
@@ -2,7 +2,7 @@
2
2
  * Is this machine signed in to a Reticle workspace?
3
3
  *
4
4
  * Asked by the HUD, and there are two sources that must not be reimplemented per caller: the session
5
- * file `reticle login` writes to `~/.reticle/session.json`, and `RETICLE_CLOUD_KEY` for an agent that
5
+ * file `reticle login` writes to `~/.reticle/session.json`, and `RETICLE_API_KEY` for an agent that
6
6
  * was handed a key instead. Everything that gates on login — the defect sync button, the prompt to sign
7
7
  * in — resolves through here.
8
8
  *
@@ -2,7 +2,7 @@
2
2
  * Is this machine signed in to a Reticle workspace?
3
3
  *
4
4
  * Asked by the HUD, and there are two sources that must not be reimplemented per caller: the session
5
- * file `reticle login` writes to `~/.reticle/session.json`, and `RETICLE_CLOUD_KEY` for an agent that
5
+ * file `reticle login` writes to `~/.reticle/session.json`, and `RETICLE_API_KEY` for an agent that
6
6
  * was handed a key instead. Everything that gates on login — the defect sync button, the prompt to sign
7
7
  * in — resolves through here.
8
8
  *
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { readFileSync } from 'node:fs';
13
13
  import { join } from 'node:path';
14
- import { ReticleDir } from '@reticlehq/core';
14
+ import { ReticleDir, apiKeyFrom } from '@reticlehq/core';
15
15
  import { CloudEnv } from './cloud-sync.js';
16
16
  import { SESSION_FILE } from '../../command/cli/cloud-kit.js';
17
17
  /** Signed OUT is the safe answer: it shows the way in, where a wrong "signed in" hides it. */
@@ -57,9 +57,10 @@ function fromSessionFile(home) {
57
57
  * acting.
58
58
  */
59
59
  export function readAccountState(home, env) {
60
- const key = env[CloudEnv.KEY];
60
+ // Through the resolver, so a machine still exporting the key's previous name is still signed in.
61
+ const key = apiKeyFrom(env);
61
62
  const url = env[CloudEnv.URL];
62
- if ('string' === typeof key && key.length > 0) {
63
+ if (key !== undefined) {
63
64
  return {
64
65
  signedIn: true,
65
66
  ...('string' === typeof url && url.length > 0 ? { host: url.replace(/\/+$/, '') } : {}),
@@ -67,4 +67,13 @@ export declare const credentialSlot: (url: string, projectId: string, orgId?: st
67
67
  * so the single-project / CI flow is unchanged. `homeDir` is injected (testable; `os.homedir()` at call).
68
68
  */
69
69
  export declare function resolveProjectCloud(fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv): Promise<ProjectCloud>;
70
+ /**
71
+ * The linked credential, as a port the tool surface can be handed.
72
+ *
73
+ * Lives here, beside the resolver it wraps, rather than in the daemon bootstrap: `index.ts` sits
74
+ * exactly on the thousand-line cap, so a caller that needs this in three lines has to put those
75
+ * three lines somewhere they belong. The harness reads it so a machine that has run `reticle link`
76
+ * needs no exported key — see `withLinkedCredential`.
77
+ */
78
+ export declare const linkedCloudPort: (fs: FileSystemPort, reticleRoot: string, homeDir: string, env: NodeJS.ProcessEnv) => () => Promise<CloudConfig | null>;
70
79
  export {};
@@ -145,3 +145,12 @@ export async function resolveProjectCloud(fs, reticleRoot, homeDir, env) {
145
145
  const config = key !== null ? { url: link.url.replace(/\/+$/, ''), apiKey: key } : null;
146
146
  return { config, policy, verify: link.verify, projectId: link.projectId };
147
147
  }
148
+ /**
149
+ * The linked credential, as a port the tool surface can be handed.
150
+ *
151
+ * Lives here, beside the resolver it wraps, rather than in the daemon bootstrap: `index.ts` sits
152
+ * exactly on the thousand-line cap, so a caller that needs this in three lines has to put those
153
+ * three lines somewhere they belong. The harness reads it so a machine that has run `reticle link`
154
+ * needs no exported key — see `withLinkedCredential`.
155
+ */
156
+ export const linkedCloudPort = (fs, reticleRoot, homeDir, env) => async () => (await resolveProjectCloud(fs, reticleRoot, homeDir, env)).config;
@@ -10,10 +10,15 @@
10
10
  */
11
11
  import { z } from 'zod';
12
12
  import { type FlowFile, type ReticleVerificationRun, type RunRecord, type VerifyProgressEvent } from '@reticlehq/core';
13
- /** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
13
+ /**
14
+ * Env var names — the presence of BOTH is what "logged in" means for cloud sync.
15
+ *
16
+ * `KEY` names the CURRENT spelling only. The previous one is still honoured, through `apiKeyFrom`
17
+ * below, which is the one place that knows there are two.
18
+ */
14
19
  export declare const CloudEnv: {
15
20
  readonly URL: "RETICLE_CLOUD_URL";
16
- readonly KEY: "RETICLE_CLOUD_KEY";
21
+ readonly KEY: "RETICLE_API_KEY";
17
22
  };
18
23
  export interface CloudConfig {
19
24
  url: string;
@@ -170,8 +175,6 @@ export declare function submitServerVerification(body: {
170
175
  * allowed to influence a verdict or an exit code.
171
176
  */
172
177
  export declare function syncProgressToCloud(runId: string, events: readonly VerifyProgressEvent[], config: CloudConfig | null, fetchImpl: FetchLike): Promise<SyncResult>;
173
- /** How long events accumulate before a flush. See the batching note above. */
174
- export declare const PROGRESS_FLUSH_MS = 2000;
175
178
  /**
176
179
  * A listener that buffers progress and ships it on a timer.
177
180
  *
@@ -9,11 +9,16 @@
9
9
  * cloud copy is an enhancement, so a push error is logged and swallowed.
10
10
  */
11
11
  import { z } from 'zod';
12
- import { VERIFY_PROGRESS_MAX_EVENTS, } from '@reticlehq/core';
13
- /** Env var names — the presence of BOTH is what "logged in" means for cloud sync. */
12
+ import { VERIFY_PROGRESS_MAX_EVENTS, apiKeyFrom, } from '@reticlehq/core';
13
+ /**
14
+ * Env var names — the presence of BOTH is what "logged in" means for cloud sync.
15
+ *
16
+ * `KEY` names the CURRENT spelling only. The previous one is still honoured, through `apiKeyFrom`
17
+ * below, which is the one place that knows there are two.
18
+ */
14
19
  export const CloudEnv = {
15
20
  URL: 'RETICLE_CLOUD_URL',
16
- KEY: 'RETICLE_CLOUD_KEY',
21
+ KEY: 'RETICLE_API_KEY',
17
22
  };
18
23
  /** Paths the OSS server pushes to (match the cloud app's contract). */
19
24
  const CLOUD_FLOWS_PATH = '/v1/flows';
@@ -25,10 +30,10 @@ const CLOUD_PROGRESS_PATH = '/v1/connect/progress';
25
30
  /** Resolve cloud credentials from the environment, or null when not logged in (sync disabled). */
26
31
  export function resolveCloudConfig(env) {
27
32
  const url = env[CloudEnv.URL];
28
- const apiKey = env[CloudEnv.KEY];
33
+ const apiKey = apiKeyFrom(env);
29
34
  if (typeof url !== 'string' || 0 === url.length)
30
35
  return null;
31
- if (typeof apiKey !== 'string' || 0 === apiKey.length)
36
+ if (apiKey === undefined)
32
37
  return null;
33
38
  return { url: url.replace(/\/+$/, ''), apiKey };
34
39
  }
@@ -288,7 +293,7 @@ export async function syncProgressToCloud(runId, events, config, fetchImpl) {
288
293
  }
289
294
  }
290
295
  /** How long events accumulate before a flush. See the batching note above. */
291
- export const PROGRESS_FLUSH_MS = 2_000;
296
+ const PROGRESS_FLUSH_MS = 2_000;
292
297
  /**
293
298
  * A listener that buffers progress and ships it on a timer.
294
299
  *
@@ -0,0 +1,12 @@
1
+ /** Narrowed to what this file stores: the caller's own type is richer and irrelevant here. */
2
+ export interface HarnessConfigView {
3
+ provider: string;
4
+ harnessEnabled: boolean;
5
+ harnessEntitled: boolean;
6
+ /** Whether the platform holds a key for `provider`. Entitlement is not readiness — see the offer. */
7
+ providerReady: boolean;
8
+ }
9
+ export interface ConfigSource {
10
+ read(): HarnessConfigView | undefined;
11
+ }
12
+ export declare function harnessConfigSource(load: () => Promise<HarnessConfigView | undefined>, now?: () => number): ConfigSource;
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The harness switch, cached for a HUD that reads it inline.
3
+ *
4
+ * `fetchPlatformConfig` already asks the platform and parses the answer; this adds the one thing the
5
+ * impact snapshot needs and the drive path does not — a SYNCHRONOUS read. The snapshot is rebuilt
6
+ * dozens of times a session, the answer changes about as often as somebody opens a settings page,
7
+ * and a panel that awaited the network on every repaint would be a panel that stutters.
8
+ *
9
+ * Deliberately the same shape as `harnessOfferSource` next door rather than a shared abstraction:
10
+ * two callers with the same caching problem is not yet a pattern, and the day one of them needs a
11
+ * different staleness rule the shared version becomes the awkward one.
12
+ *
13
+ * Every failure answers `undefined`, which the HUD must read as "we have not heard" and render as NO
14
+ * control — never as "off". Showing a switch that cannot be honoured is worse than showing none.
15
+ *
16
+ * The loader arrives as a PORT rather than an import. Reading the platform lives in
17
+ * `features/harness`, and a cloud-memory file reaching into a feature is the reach the directory
18
+ * guard refuses — correctly, and for the same reason it refused the tool surface reaching the other
19
+ * way. Whoever wires the daemon owns both sides and can hand one to the other.
20
+ */
21
+ /** How long a cached answer is trusted. A person who just changed it expects the panel to notice. */
22
+ const FRESH_MS = 30 * 1_000;
23
+ export function harnessConfigSource(load, now = () => Date.now()) {
24
+ let cached;
25
+ // `undefined` rather than 0: "never asked" is a different state from "asked at the epoch", and
26
+ // conflating them made a source with an injected clock never take its first read.
27
+ let fetchedAt;
28
+ let inFlight = false;
29
+ const refresh = () => {
30
+ if (inFlight)
31
+ return;
32
+ inFlight = true;
33
+ void load()
34
+ .then((cfg) => {
35
+ // A failed read keeps the LAST good answer rather than blanking the control mid-session:
36
+ // one dropped request is not evidence that somebody changed their mind.
37
+ if (cfg !== undefined)
38
+ cached = cfg;
39
+ fetchedAt = now();
40
+ })
41
+ .catch(() => {
42
+ fetchedAt = now();
43
+ })
44
+ .finally(() => {
45
+ inFlight = false;
46
+ });
47
+ };
48
+ // Asked at construction, not first read: the daemon pushes a snapshot the instant a session
49
+ // attaches, and on a page nobody drives that is the only push there will ever be.
50
+ refresh();
51
+ return {
52
+ read() {
53
+ if (fetchedAt === undefined || FRESH_MS <= now() - fetchedAt)
54
+ refresh();
55
+ return cached;
56
+ },
57
+ };
58
+ }