@reticlehq/server 3.5.0 → 3.7.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 (721) hide show
  1. package/README.md +1 -1
  2. package/SKILL.md +123 -116
  3. package/dist/chromium-launch-options.d.ts +3 -1
  4. package/dist/chromium-launch-options.js +2 -1
  5. package/dist/command/cli/adhoc-suite.d.ts +14 -0
  6. package/dist/command/cli/adhoc-suite.js +42 -7
  7. package/dist/command/cli/adhoc-verdict.d.ts +19 -2
  8. package/dist/command/cli/adhoc-verdict.js +76 -9
  9. package/dist/command/cli/answers/gate-exit.d.ts +23 -0
  10. package/dist/command/cli/answers/gate-exit.js +45 -0
  11. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  12. package/dist/command/cli/answers/gate-hook-message.js +3 -1
  13. package/dist/command/cli/answers/open-note.d.ts +2 -0
  14. package/dist/command/cli/answers/open-note.js +22 -0
  15. package/dist/command/cli/auth/device-flow.d.ts +13 -2
  16. package/dist/command/cli/auth/device-flow.js +17 -0
  17. package/dist/command/cli/cli-doctor.js +14 -0
  18. package/dist/command/cli/cli-flow-commands.d.ts +0 -11
  19. package/dist/command/cli/cli-flow-commands.js +31 -112
  20. package/dist/command/cli/cli-kill.js +3 -2
  21. package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
  22. package/dist/command/cli/cli-parse-grammar.js +75 -0
  23. package/dist/command/cli/cli-parse-verify.d.ts +2 -0
  24. package/dist/command/cli/cli-parse-verify.js +18 -0
  25. package/dist/command/cli/cli-parse.d.ts +19 -28
  26. package/dist/command/cli/cli-parse.js +82 -182
  27. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  28. package/dist/command/cli/cli-update-commands.js +61 -9
  29. package/dist/command/cli/cli-usage.d.ts +33 -0
  30. package/dist/command/cli/cli-usage.js +287 -0
  31. package/dist/command/cli/cli-verify.d.ts +35 -5
  32. package/dist/command/cli/cli-verify.js +114 -35
  33. package/dist/command/cli/cloud-cli.d.ts +13 -0
  34. package/dist/command/cli/cloud-cli.js +192 -341
  35. package/dist/command/cli/cloud-kit.d.ts +12 -0
  36. package/dist/command/cli/cloud-kit.js +41 -2
  37. package/dist/command/cli/cloud-link.d.ts +57 -0
  38. package/dist/command/cli/cloud-link.js +308 -0
  39. package/dist/command/cli/cloud-login.d.ts +22 -0
  40. package/dist/command/cli/cloud-login.js +96 -8
  41. package/dist/command/cli/context-command.d.ts +1 -0
  42. package/dist/command/cli/context-command.js +33 -0
  43. package/dist/command/cli/daemon-start-options.d.ts +3 -0
  44. package/dist/command/cli/daemon-start-options.js +24 -3
  45. package/dist/command/cli/doctor/browser/chromium-hint.d.ts +46 -0
  46. package/dist/command/cli/doctor/browser/chromium-hint.js +99 -1
  47. package/dist/command/cli/doctor/doctor-coverage-line.d.ts +2 -0
  48. package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
  49. package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
  50. package/dist/command/cli/doctor/doctor-project-line.js +5 -4
  51. package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
  52. package/dist/command/cli/doctor/doctor-rows.js +6 -0
  53. package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
  54. package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
  55. package/dist/command/cli/drive/drive-attach.d.ts +27 -2
  56. package/dist/command/cli/drive/drive-attach.js +71 -4
  57. package/dist/command/cli/drive/drive-command.d.ts +2 -1
  58. package/dist/command/cli/drive/drive-command.js +13 -7
  59. package/dist/command/cli/known-regression.d.ts +13 -0
  60. package/dist/command/cli/known-regression.js +34 -0
  61. package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
  62. package/dist/command/cli/launch/sdk-version-match.js +25 -0
  63. package/dist/command/cli/lifecycle/daemon-lifecycle.js +15 -3
  64. package/dist/command/cli/mcp-command.d.ts +6 -0
  65. package/dist/command/cli/mcp-command.js +13 -5
  66. package/dist/command/cli/ports/port-holder.d.ts +10 -0
  67. package/dist/command/cli/ports/port-holder.js +25 -1
  68. package/dist/command/cli/project-name.d.ts +29 -1
  69. package/dist/command/cli/project-name.js +107 -2
  70. package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
  71. package/dist/command/cli/setup-mcp-cli.js +35 -12
  72. package/dist/command/cli/status/status-lines.d.ts +1 -0
  73. package/dist/command/cli/status/status-lines.js +47 -2
  74. package/dist/command/cli/status/workspace-links.d.ts +10 -0
  75. package/dist/command/cli/status/workspace-links.js +29 -0
  76. package/dist/command/cli/try-command.d.ts +96 -0
  77. package/dist/command/cli/try-command.js +362 -0
  78. package/dist/command/cli/tutorial.d.ts +0 -100
  79. package/dist/command/cli/tutorial.js +0 -181
  80. package/dist/command/cli/verify-results.d.ts +19 -0
  81. package/dist/command/cli/verify-results.js +29 -0
  82. package/dist/command/cli-onboarding.d.ts +0 -11
  83. package/dist/command/cli-onboarding.js +0 -15
  84. package/dist/command/cli.d.ts +20 -0
  85. package/dist/command/cli.js +130 -104
  86. package/dist/command/daemon/binding/daemon-status-probe.d.ts +2 -0
  87. package/dist/command/daemon/binding/daemon-status-probe.js +1 -0
  88. package/dist/command/daemon/daemon-resilience.js +4 -4
  89. package/dist/command/daemon/daemon-resolve.d.ts +23 -1
  90. package/dist/command/daemon/daemon-resolve.js +79 -9
  91. package/dist/command/setup/agent-writer.d.ts +1 -1
  92. package/dist/command/setup/agent-writer.js +3 -2
  93. package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
  94. package/dist/command/setup/bringup/bridge-port.js +13 -3
  95. package/dist/command/setup/bringup/connect-lease.d.ts +1 -0
  96. package/dist/command/setup/bringup/connect-lease.js +1 -0
  97. package/dist/command/setup/bringup/dev-server-log-cap.js +2 -0
  98. package/dist/command/setup/bringup/dev-server-wait.d.ts +8 -0
  99. package/dist/command/setup/bringup/dev-server-wait.js +19 -0
  100. package/dist/command/setup/bringup/ensure-daemon.js +9 -2
  101. package/dist/command/setup/init/agent-restart.d.ts +23 -0
  102. package/dist/command/setup/init/agent-restart.js +28 -0
  103. package/dist/command/setup/init/init-port.d.ts +14 -1
  104. package/dist/command/setup/init/init-port.js +32 -2
  105. package/dist/command/setup/init/init-runtime.d.ts +22 -2
  106. package/dist/command/setup/init/init-runtime.js +114 -38
  107. package/dist/command/setup/node-effects.d.ts +2 -0
  108. package/dist/command/setup/node-effects.js +38 -9
  109. package/dist/command/setup/probe/existing-app.d.ts +12 -0
  110. package/dist/command/setup/probe/existing-app.js +14 -0
  111. package/dist/command/setup/remaining-steps.js +2 -1
  112. package/dist/command/setup/run-setup.d.ts +34 -9
  113. package/dist/command/setup/run-setup.js +65 -51
  114. package/dist/command/setup/setup-command.d.ts +25 -1
  115. package/dist/command/setup/setup-command.js +59 -4
  116. package/dist/command/setup/setup-mcp.js +25 -2
  117. package/dist/command/setup/tauri-signing.d.ts +19 -0
  118. package/dist/command/setup/tauri-signing.js +79 -0
  119. package/dist/command/setup/terminal/listeners.d.ts +10 -1
  120. package/dist/command/setup/terminal/listeners.js +37 -6
  121. package/dist/command/update/update-nudge.d.ts +9 -0
  122. package/dist/command/update/update-nudge.js +1 -1
  123. package/dist/command/update/updater.js +11 -15
  124. package/dist/command/version/version-nudge.js +3 -0
  125. package/dist/command/version/version-skew.d.ts +20 -1
  126. package/dist/command/version/version-skew.js +49 -4
  127. package/dist/features/crawl/crawl-tools.js +4 -0
  128. package/dist/features/crawl/crawl.d.ts +6 -0
  129. package/dist/features/crawl/crawl.js +23 -2
  130. package/dist/features/exhaust/ledger.d.ts +21 -14
  131. package/dist/features/exhaust/ledger.js +35 -9
  132. package/dist/features/harness/app-findings.d.ts +2 -0
  133. package/dist/features/harness/app-findings.js +52 -0
  134. package/dist/features/harness/drive-report.d.ts +61 -1
  135. package/dist/features/harness/drive-report.js +394 -26
  136. package/dist/features/harness/drive-runs.d.ts +120 -0
  137. package/dist/features/harness/drive-runs.js +289 -0
  138. package/dist/features/harness/drivers.d.ts +10 -14
  139. package/dist/features/harness/drivers.js +10 -14
  140. package/dist/features/harness/explore/console-pages.d.ts +15 -0
  141. package/dist/features/harness/explore/console-pages.js +146 -0
  142. package/dist/features/harness/explore/disparity.d.ts +19 -0
  143. package/dist/features/harness/explore/disparity.js +71 -0
  144. package/dist/features/harness/explore/fake-app.d.ts +16 -0
  145. package/dist/features/harness/explore/fake-app.js +49 -0
  146. package/dist/features/harness/explore/goal-search.d.ts +26 -0
  147. package/dist/features/harness/explore/goal-search.js +144 -0
  148. package/dist/features/harness/explore/page-tree.d.ts +49 -0
  149. package/dist/features/harness/explore/page-tree.js +148 -0
  150. package/dist/features/harness/explore/site-map.d.ts +34 -0
  151. package/dist/features/harness/explore/site-map.js +150 -0
  152. package/dist/features/harness/fill-values.d.ts +3 -73
  153. package/dist/features/harness/fill-values.js +3 -169
  154. package/dist/features/harness/goals.d.ts +82 -0
  155. package/dist/features/harness/goals.js +330 -0
  156. package/dist/features/harness/harness.d.ts +51 -1
  157. package/dist/features/harness/harness.js +207 -6
  158. package/dist/features/harness/platform/drive-target.d.ts +30 -0
  159. package/dist/features/harness/platform/drive-target.js +81 -0
  160. package/dist/features/harness/platform/local-apps.d.ts +127 -0
  161. package/dist/features/harness/platform/local-apps.js +280 -0
  162. package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
  163. package/dist/features/harness/platform/persona-credentials.js +69 -0
  164. package/dist/features/harness/platform/platform-drives.d.ts +61 -0
  165. package/dist/features/harness/platform/platform-drives.js +114 -0
  166. package/dist/features/harness/platform/refusal.d.ts +52 -0
  167. package/dist/features/harness/platform/refusal.js +71 -0
  168. package/dist/features/harness/platform/remote-drive.d.ts +186 -0
  169. package/dist/features/harness/platform/remote-drive.js +378 -0
  170. package/dist/features/harness/platform/script.d.ts +94 -0
  171. package/dist/features/harness/platform/script.js +260 -0
  172. package/dist/features/harness/platform/server-driver.d.ts +54 -0
  173. package/dist/features/harness/platform/server-driver.js +182 -0
  174. package/dist/features/harness/platform-config.d.ts +41 -1
  175. package/dist/features/harness/platform-config.js +106 -9
  176. package/dist/features/harness/route-guess.d.ts +13 -0
  177. package/dist/features/harness/route-guess.js +49 -0
  178. package/dist/features/harness/script/app-controls.d.ts +5 -0
  179. package/dist/features/harness/script/app-controls.js +40 -0
  180. package/dist/features/harness/script/lane-ids.d.ts +14 -0
  181. package/dist/features/harness/script/lane-ids.js +16 -0
  182. package/dist/features/harness/script/script-run.d.ts +70 -0
  183. package/dist/features/harness/script/script-run.js +606 -0
  184. package/dist/features/license/license.d.ts +2 -2
  185. package/dist/features/visual/step-shots.d.ts +31 -0
  186. package/dist/features/visual/step-shots.js +231 -0
  187. package/dist/features/visual/visual-tools.d.ts +10 -1
  188. package/dist/features/visual/visual-tools.js +62 -17
  189. package/dist/hooks/client-directory.d.ts +18 -0
  190. package/dist/hooks/client-directory.js +57 -0
  191. package/dist/hooks/coding-agents.d.ts +34 -0
  192. package/dist/hooks/coding-agents.js +117 -0
  193. package/dist/hooks/driven-by.d.ts +23 -0
  194. package/dist/hooks/driven-by.js +56 -0
  195. package/dist/hooks/hook-bus.d.ts +4 -0
  196. package/dist/hooks/hook-bus.js +18 -0
  197. package/dist/hooks/hook-commands.js +15 -4
  198. package/dist/hooks/tool-log.d.ts +9 -0
  199. package/dist/hooks/tool-log.js +29 -0
  200. package/dist/index.d.ts +2 -0
  201. package/dist/index.js +80 -243
  202. package/dist/judgement/capsule/capsule-store.d.ts +2 -2
  203. package/dist/judgement/capsule/causal-summary.d.ts +15 -1
  204. package/dist/judgement/capsule/causal-summary.js +33 -0
  205. package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
  206. package/dist/judgement/runs/artifact/build-verification-run.js +25 -3
  207. package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
  208. package/dist/judgement/runs/artifact/gap-summary.js +12 -3
  209. package/dist/judgement/runs/artifact/replay-mapping.d.ts +1 -1
  210. package/dist/judgement/runs/artifact/replay-mapping.js +37 -11
  211. package/dist/judgement/runs/artifact/run-store.d.ts +14 -2
  212. package/dist/judgement/runs/artifact/run-store.js +195 -3
  213. package/dist/judgement/runs/context-tools.js +59 -1
  214. package/dist/judgement/runs/default-run-id.d.ts +3 -0
  215. package/dist/judgement/runs/default-run-id.js +16 -0
  216. package/dist/judgement/runs/drive-run.d.ts +71 -1
  217. package/dist/judgement/runs/drive-run.js +317 -5
  218. package/dist/judgement/runs/repair-prompt.js +1 -1
  219. package/dist/judgement/runs/resume.d.ts +44 -0
  220. package/dist/judgement/runs/resume.js +137 -0
  221. package/dist/judgement/runs/reticle-runner.js +13 -3
  222. package/dist/judgement/runs/runner-port.d.ts +5 -1
  223. package/dist/judgement/runs/runner-port.js +8 -2
  224. package/dist/judgement/runs/verification-sync.d.ts +5 -2
  225. package/dist/judgement/runs/verification-sync.js +40 -18
  226. package/dist/language/flows/change/gate.d.ts +10 -0
  227. package/dist/language/flows/change/gate.js +22 -0
  228. package/dist/language/flows/decision.js +36 -1
  229. package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
  230. package/dist/language/flows/fields/flow-secret-field.js +25 -2
  231. package/dist/language/flows/flow-anchor.d.ts +61 -10
  232. package/dist/language/flows/flow-anchor.js +98 -6
  233. package/dist/language/flows/flow-cross-step.d.ts +15 -0
  234. package/dist/language/flows/flow-cross-step.js +21 -0
  235. package/dist/language/flows/flow-intent.d.ts +1 -0
  236. package/dist/language/flows/flow-journey.d.ts +6 -0
  237. package/dist/language/flows/flow-journey.js +20 -0
  238. package/dist/language/flows/flow-learning.d.ts +4 -0
  239. package/dist/language/flows/flow-learning.js +45 -14
  240. package/dist/language/flows/flow-mutate-tools.js +5 -3
  241. package/dist/language/flows/flow-replay-run.d.ts +23 -9
  242. package/dist/language/flows/flow-replay-run.js +115 -110
  243. package/dist/language/flows/flow-replay-types.d.ts +1 -1
  244. package/dist/language/flows/flow-replay.d.ts +40 -1
  245. package/dist/language/flows/flow-replay.js +110 -22
  246. package/dist/language/flows/flow-result.d.ts +22 -1
  247. package/dist/language/flows/flow-result.js +47 -2
  248. package/dist/language/flows/flow-scope.js +5 -1
  249. package/dist/language/flows/flow-step-runners.d.ts +22 -2
  250. package/dist/language/flows/flow-step-runners.js +247 -47
  251. package/dist/language/flows/flow-store-for-session.d.ts +2 -2
  252. package/dist/language/flows/flow-store-for-session.js +7 -3
  253. package/dist/language/flows/flow-tools.d.ts +10 -0
  254. package/dist/language/flows/flow-tools.js +112 -89
  255. package/dist/language/flows/flows.d.ts +16 -1
  256. package/dist/language/flows/flows.js +74 -12
  257. package/dist/language/flows/heal-run.js +6 -4
  258. package/dist/language/flows/narrow-write.d.ts +7 -0
  259. package/dist/language/flows/narrow-write.js +36 -1
  260. package/dist/language/flows/recording/flake.d.ts +2 -2
  261. package/dist/language/flows/recording/recording-backtrack.js +14 -1
  262. package/dist/language/flows/recording/tape/recordings.d.ts +58 -4
  263. package/dist/language/flows/recording/tape/recordings.js +95 -13
  264. package/dist/language/flows/replay-from-hud.d.ts +20 -0
  265. package/dist/language/flows/replay-from-hud.js +51 -0
  266. package/dist/language/flows/replay.d.ts +9 -5
  267. package/dist/language/flows/replay.js +56 -15
  268. package/dist/language/flows/single-replay.d.ts +7 -0
  269. package/dist/language/flows/single-replay.js +26 -0
  270. package/dist/language/flows/suite/suite-flakes.js +2 -0
  271. package/dist/language/flows/suite-selection.d.ts +2 -15
  272. package/dist/language/flows/suite-selection.js +4 -3
  273. package/dist/language/flows/verify-change-tools.d.ts +1 -0
  274. package/dist/language/flows/verify-change-tools.js +202 -191
  275. package/dist/launch-chromium.d.ts +27 -5
  276. package/dist/launch-chromium.js +38 -8
  277. package/dist/memory/cloud/cloud-config.d.ts +22 -1
  278. package/dist/memory/cloud/cloud-config.js +34 -5
  279. package/dist/memory/cloud/cloud-sync.d.ts +17 -14
  280. package/dist/memory/cloud/cloud-sync.js +15 -4
  281. package/dist/memory/cloud/harness-config.d.ts +75 -1
  282. package/dist/memory/cloud/harness-config.js +115 -24
  283. package/dist/memory/cloud/hud-notices-source.d.ts +21 -0
  284. package/dist/memory/cloud/hud-notices-source.js +128 -0
  285. package/dist/memory/cloud/memory-scope.d.ts +161 -0
  286. package/dist/memory/cloud/memory-scope.js +227 -0
  287. package/dist/memory/cloud/sync-cycle.d.ts +44 -7
  288. package/dist/memory/cloud/sync-cycle.js +129 -15
  289. package/dist/memory/cloud/sync-daemon.d.ts +11 -0
  290. package/dist/memory/cloud/sync-daemon.js +121 -9
  291. package/dist/memory/cloud/sync-disk.d.ts +1 -1
  292. package/dist/memory/cloud/sync-disk.js +31 -10
  293. package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
  294. package/dist/memory/cloud/sync-screenshots.js +105 -0
  295. package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
  296. package/dist/memory/cloud/unsynced-roots.js +166 -0
  297. package/dist/memory/impact/account-watch.d.ts +34 -0
  298. package/dist/memory/impact/account-watch.js +59 -0
  299. package/dist/memory/impact/impact-recorder.d.ts +8 -0
  300. package/dist/memory/impact/impact-recorder.js +12 -2
  301. package/dist/memory/impact/impact-store.d.ts +17 -4
  302. package/dist/memory/impact/impact-store.js +169 -63
  303. package/dist/memory/intent/inline-intent.d.ts +1 -0
  304. package/dist/memory/intent/intent-shard-store.js +16 -4
  305. package/dist/memory/intent/intent-shard.d.ts +14 -2
  306. package/dist/memory/intent/intent-store.d.ts +1 -0
  307. package/dist/memory/intent/intent-tools.js +122 -4
  308. package/dist/memory/intent/open-intents.d.ts +6 -0
  309. package/dist/memory/intent/open-intents.js +10 -1
  310. package/dist/memory/intent/settled-words.d.ts +24 -0
  311. package/dist/memory/intent/settled-words.js +130 -0
  312. package/dist/memory/journal/drive-flow.d.ts +16 -0
  313. package/dist/memory/journal/drive-flow.js +34 -2
  314. package/dist/memory/journal/drive-run-flush.d.ts +2 -0
  315. package/dist/memory/journal/drive-run-flush.js +20 -5
  316. package/dist/memory/journal/envelope.d.ts +8 -8
  317. package/dist/memory/journal/journal-recorder.js +52 -3
  318. package/dist/memory/journal/on-disk/retention.d.ts +8 -0
  319. package/dist/memory/journal/on-disk/retention.js +32 -2
  320. package/dist/memory/journal/on-disk/startup-maintenance.js +3 -1
  321. package/dist/memory/journal/on-disk/workspace-tiers.js +9 -4
  322. package/dist/memory/journal/session-end.d.ts +15 -1
  323. package/dist/memory/journal/session-end.js +42 -9
  324. package/dist/memory/project/artifact-root-resolver.d.ts +26 -1
  325. package/dist/memory/project/artifact-root-resolver.js +125 -16
  326. package/dist/memory/project/artifact-root.d.ts +46 -8
  327. package/dist/memory/project/artifact-root.js +89 -10
  328. package/dist/memory/project/dir/reticle-dir.d.ts +18 -2
  329. package/dist/memory/project/dir/reticle-dir.js +26 -3
  330. package/dist/memory/project/link-directory.d.ts +15 -0
  331. package/dist/memory/project/link-directory.js +44 -0
  332. package/dist/memory/project/project-tools.js +22 -3
  333. package/dist/memory/project/root-claims.d.ts +6 -0
  334. package/dist/memory/project/root-claims.js +39 -0
  335. package/dist/memory/project/session-root.d.ts +26 -1
  336. package/dist/memory/project/session-root.js +49 -5
  337. package/dist/memory/project/sync-status.d.ts +85 -0
  338. package/dist/memory/project/sync-status.js +224 -0
  339. package/dist/memory/recall/memory-tools.js +47 -2
  340. package/dist/memory/recall/platform-memory.d.ts +34 -0
  341. package/dist/memory/recall/platform-memory.js +139 -0
  342. package/dist/memory/recall/project-memory.d.ts +19 -0
  343. package/dist/memory/recall/project-memory.js +80 -13
  344. package/dist/portal/bridge/bridge.d.ts +9 -3
  345. package/dist/portal/bridge/bridge.js +82 -22
  346. package/dist/portal/bridge/harness-request.d.ts +23 -0
  347. package/dist/portal/bridge/harness-request.js +27 -0
  348. package/dist/portal/bridge/pairing-token.d.ts +9 -0
  349. package/dist/portal/bridge/pairing-token.js +17 -0
  350. package/dist/portal/bridge/wrong-path.d.ts +17 -0
  351. package/dist/portal/bridge/wrong-path.js +35 -0
  352. package/dist/portal/input/network-mock-tools.js +11 -2
  353. package/dist/portal/input/real-input.d.ts +83 -10
  354. package/dist/portal/input/real-input.js +218 -30
  355. package/dist/portal/input/viewport-tools.js +3 -3
  356. package/dist/portal/pool/browser-pool.d.ts +70 -9
  357. package/dist/portal/pool/browser-pool.js +190 -37
  358. package/dist/portal/pool/context-permissions.d.ts +33 -0
  359. package/dist/portal/pool/context-permissions.js +33 -0
  360. package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
  361. package/dist/portal/pool/lease-marker-stamp.js +49 -0
  362. package/dist/portal/pool/playwright-launcher.d.ts +35 -0
  363. package/dist/portal/pool/playwright-launcher.js +64 -3
  364. package/dist/portal/pool/pool-contract.d.ts +28 -5
  365. package/dist/portal/session/agent-link.d.ts +8 -0
  366. package/dist/portal/session/agent-link.js +17 -0
  367. package/dist/portal/session/command-payload.d.ts +8 -0
  368. package/dist/portal/session/command-payload.js +24 -0
  369. package/dist/portal/session/daemon-answers.d.ts +3 -3
  370. package/dist/portal/session/daemon-answers.js +4 -2
  371. package/dist/portal/session/dev-server/dev-command.d.ts +21 -0
  372. package/dist/portal/session/dev-server/dev-command.js +87 -12
  373. package/dist/portal/session/facts/departure-note.d.ts +22 -0
  374. package/dist/portal/session/facts/departure-note.js +47 -0
  375. package/dist/portal/session/facts/observed-state.d.ts +16 -18
  376. package/dist/portal/session/facts/observed-state.js +46 -3
  377. package/dist/portal/session/facts/session-replaced.d.ts +5 -0
  378. package/dist/portal/session/facts/session-replaced.js +5 -0
  379. package/dist/portal/session/first-run-wiring.d.ts +42 -0
  380. package/dist/portal/session/first-run-wiring.js +132 -0
  381. package/dist/portal/session/human/live-control.d.ts +9 -2
  382. package/dist/portal/session/human/live-control.js +12 -3
  383. package/dist/portal/session/human/notes-ledger.d.ts +20 -0
  384. package/dist/portal/session/human/notes-ledger.js +55 -0
  385. package/dist/portal/session/human/review-store.d.ts +12 -0
  386. package/dist/portal/session/human/review-store.js +18 -10
  387. package/dist/portal/session/live-control-tools.d.ts +0 -13
  388. package/dist/portal/session/live-control-tools.js +107 -13
  389. package/dist/portal/session/narration-args.d.ts +5 -0
  390. package/dist/portal/session/narration-args.js +17 -0
  391. package/dist/portal/session/no-session-diagnosis.d.ts +8 -1
  392. package/dist/portal/session/no-session-diagnosis.js +46 -9
  393. package/dist/portal/session/no-session-next-action.d.ts +31 -0
  394. package/dist/portal/session/no-session-next-action.js +159 -60
  395. package/dist/portal/session/no-session-watch.d.ts +14 -1
  396. package/dist/portal/session/no-session-watch.js +227 -17
  397. package/dist/portal/session/presence/session-lease.js +1 -1
  398. package/dist/portal/session/presence/session-mirror.js +3 -0
  399. package/dist/portal/session/recorded-gaps.d.ts +97 -0
  400. package/dist/portal/session/recorded-gaps.js +251 -0
  401. package/dist/portal/session/serving-directory.d.ts +27 -0
  402. package/dist/portal/session/serving-directory.js +45 -0
  403. package/dist/portal/session/session-health.d.ts +18 -0
  404. package/dist/portal/session/session-health.js +9 -1
  405. package/dist/portal/session/session-info.d.ts +8 -1
  406. package/dist/portal/session/session-info.js +8 -1
  407. package/dist/portal/session/session-manager.d.ts +39 -0
  408. package/dist/portal/session/session-manager.js +98 -11
  409. package/dist/portal/session/session-reaper.js +4 -1
  410. package/dist/portal/session/session-successor.d.ts +13 -0
  411. package/dist/portal/session/session-successor.js +1 -1
  412. package/dist/portal/session/session-tools.js +26 -3
  413. package/dist/portal/session/session.d.ts +22 -30
  414. package/dist/portal/session/session.js +81 -79
  415. package/dist/portal/session/timing/command-timeout.js +25 -1
  416. package/dist/portal/session/timing/wall-clock.d.ts +1 -1
  417. package/dist/portal/session/timing/wall-clock.js +1 -1
  418. package/dist/portal/session/zero-install-first-call.d.ts +31 -0
  419. package/dist/portal/session/zero-install-first-call.js +88 -0
  420. package/dist/start-options.d.ts +12 -0
  421. package/dist/start-wiring.d.ts +63 -0
  422. package/dist/start-wiring.js +169 -0
  423. package/dist/status-payload.d.ts +23 -1
  424. package/dist/status-payload.js +36 -1
  425. package/dist/surface/http-server.js +18 -2
  426. package/dist/surface/mcp/attach-memory.d.ts +1 -0
  427. package/dist/surface/mcp/attach-memory.js +12 -1
  428. package/dist/surface/mcp/mcp-proxy.d.ts +9 -0
  429. package/dist/surface/mcp/mcp-proxy.js +65 -13
  430. package/dist/surface/mcp/mcp.d.ts +34 -6
  431. package/dist/surface/mcp/mcp.js +61 -17
  432. package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
  433. package/dist/surface/mcp/proxy/drive-calls.js +27 -0
  434. package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
  435. package/dist/surface/mcp/server-instructions.d.ts +4 -2
  436. package/dist/surface/mcp/server-instructions.js +32 -14
  437. package/dist/surface/mcp/shared-params.js +2 -2
  438. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  439. package/dist/surface/mcp/surface-vocabulary.js +1 -0
  440. package/dist/surface/tools/account-tool.d.ts +22 -0
  441. package/dist/surface/tools/account-tool.js +157 -0
  442. package/dist/surface/tools/act/act-danger.d.ts +11 -0
  443. package/dist/surface/tools/act/act-danger.js +38 -1
  444. package/dist/surface/tools/act/act-observation.d.ts +33 -0
  445. package/dist/surface/tools/act/act-observation.js +57 -0
  446. package/dist/surface/tools/act/act-preflight.js +25 -2
  447. package/dist/surface/tools/act/act-target.js +8 -3
  448. package/dist/surface/tools/act/navigation/navigate-arrival.d.ts +8 -0
  449. package/dist/surface/tools/act/navigation/navigate-arrival.js +56 -2
  450. package/dist/surface/tools/act/resolve-target.d.ts +1 -1
  451. package/dist/surface/tools/act/resolve-target.js +18 -3
  452. package/dist/surface/tools/act/screen-request.d.ts +34 -0
  453. package/dist/surface/tools/act/screen-request.js +113 -0
  454. package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
  455. package/dist/surface/tools/act/sequence-grade.js +13 -2
  456. package/dist/surface/tools/act/settle-in-flight.js +25 -3
  457. package/dist/surface/tools/act-sequence-tool.js +138 -29
  458. package/dist/surface/tools/act-tools.d.ts +0 -9
  459. package/dist/surface/tools/act-tools.js +71 -41
  460. package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
  461. package/dist/surface/tools/agent/app-notes-learning.js +80 -0
  462. package/dist/surface/tools/agent/app-notes.d.ts +4 -0
  463. package/dist/surface/tools/agent/app-notes.js +48 -0
  464. package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
  465. package/dist/surface/tools/agent/build-staleness.js +62 -0
  466. package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
  467. package/dist/surface/tools/agent/compact-envelope.js +374 -0
  468. package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
  469. package/dist/surface/tools/agent/for-the-agent.js +44 -0
  470. package/dist/surface/tools/agent/notices.d.ts +50 -0
  471. package/dist/surface/tools/agent/notices.js +152 -0
  472. package/dist/surface/tools/agent/reteach.d.ts +11 -0
  473. package/dist/surface/tools/agent/reteach.js +50 -0
  474. package/dist/surface/tools/args/alias-args.d.ts +5 -0
  475. package/dist/surface/tools/args/alias-args.js +10 -0
  476. package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
  477. package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
  478. package/dist/surface/tools/assert/assert-source.d.ts +15 -0
  479. package/dist/surface/tools/assert/assert-source.js +40 -0
  480. package/dist/surface/tools/assert/assert-verdict.d.ts +1 -1
  481. package/dist/surface/tools/assert/assert-verdict.js +42 -17
  482. package/dist/surface/tools/browser-tools.js +51 -2
  483. package/dist/surface/tools/chat-drives.d.ts +101 -0
  484. package/dist/surface/tools/chat-drives.js +409 -0
  485. package/dist/surface/tools/coverage-tools.d.ts +2 -10
  486. package/dist/surface/tools/coverage-tools.js +2 -53
  487. package/dist/surface/tools/dynamic-tools.js +29 -1
  488. package/dist/surface/tools/empty-session-list.d.ts +51 -0
  489. package/dist/surface/tools/empty-session-list.js +120 -0
  490. package/dist/surface/tools/error-recovery.d.ts +14 -0
  491. package/dist/surface/tools/error-recovery.js +105 -8
  492. package/dist/surface/tools/explore-tools.d.ts +31 -1
  493. package/dist/surface/tools/explore-tools.js +438 -66
  494. package/dist/surface/tools/feedback-tools.d.ts +2 -3
  495. package/dist/surface/tools/feedback-tools.js +18 -3
  496. package/dist/surface/tools/gap-novelty.d.ts +3 -1
  497. package/dist/surface/tools/gap-novelty.js +8 -1
  498. package/dist/surface/tools/harness-explore.d.ts +118 -57
  499. package/dist/surface/tools/harness-explore.js +344 -292
  500. package/dist/surface/tools/harness-gate.d.ts +88 -0
  501. package/dist/surface/tools/harness-gate.js +235 -0
  502. package/dist/surface/tools/harness-hunt.d.ts +43 -0
  503. package/dist/surface/tools/harness-hunt.js +215 -0
  504. package/dist/surface/tools/harness-plan.d.ts +17 -0
  505. package/dist/surface/tools/harness-plan.js +38 -0
  506. package/dist/surface/tools/harness-script.d.ts +30 -0
  507. package/dist/surface/tools/harness-script.js +560 -0
  508. package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
  509. package/dist/surface/tools/harness-tab-guard.js +27 -0
  510. package/dist/surface/tools/harness-toolset.d.ts +22 -1
  511. package/dist/surface/tools/harness-toolset.js +101 -7
  512. package/dist/surface/tools/human-notes.d.ts +16 -0
  513. package/dist/surface/tools/human-notes.js +27 -0
  514. package/dist/surface/tools/invoke-tool.d.ts +5 -2
  515. package/dist/surface/tools/invoke-tool.js +263 -85
  516. package/dist/surface/tools/lease-hint.js +26 -5
  517. package/dist/surface/tools/lease-permissions.d.ts +33 -0
  518. package/dist/surface/tools/lease-permissions.js +73 -0
  519. package/dist/surface/tools/lease-readiness.d.ts +38 -0
  520. package/dist/surface/tools/lease-readiness.js +57 -0
  521. package/dist/surface/tools/lease-seed.d.ts +49 -0
  522. package/dist/surface/tools/lease-seed.js +167 -0
  523. package/dist/surface/tools/lease-tools.d.ts +13 -24
  524. package/dist/surface/tools/lease-tools.js +146 -197
  525. package/dist/surface/tools/live-call-text.js +4 -4
  526. package/dist/surface/tools/merge-tools.js +42 -2
  527. package/dist/surface/tools/merged-name-redirect.js +4 -0
  528. package/dist/surface/tools/next-step.d.ts +29 -0
  529. package/dist/surface/tools/next-step.js +214 -0
  530. package/dist/surface/tools/observe-tools.js +45 -9
  531. package/dist/surface/tools/platform-moment.d.ts +72 -0
  532. package/dist/surface/tools/platform-moment.js +296 -0
  533. package/dist/surface/tools/query-hint-schema.d.ts +7 -0
  534. package/dist/surface/tools/query-hint-schema.js +10 -0
  535. package/dist/surface/tools/read-tools.js +71 -14
  536. package/dist/surface/tools/real-input-attempt.js +96 -13
  537. package/dist/surface/tools/reticle-result.d.ts +120 -0
  538. package/dist/surface/tools/reticle-result.js +364 -0
  539. package/dist/surface/tools/tool-kit.d.ts +83 -3
  540. package/dist/surface/tools/tool-kit.js +77 -7
  541. package/dist/surface/tools/tools.js +43 -59
  542. package/dist/telemetry/account-envelope.d.ts +7 -0
  543. package/dist/telemetry/account-envelope.js +61 -0
  544. package/dist/telemetry/agent-client.d.ts +15 -0
  545. package/dist/telemetry/agent-client.js +57 -0
  546. package/dist/telemetry/app-instrumented.js +9 -2
  547. package/dist/telemetry/argument-shape.d.ts +7 -0
  548. package/dist/telemetry/argument-shape.js +86 -1
  549. package/dist/telemetry/automation-hint.d.ts +2 -3
  550. package/dist/telemetry/automation-hint.js +33 -8
  551. package/dist/telemetry/cli-telemetry.js +7 -2
  552. package/dist/telemetry/daemon-telemetry.js +3 -0
  553. package/dist/telemetry/error-fingerprint.d.ts +10 -1
  554. package/dist/telemetry/error-fingerprint.js +61 -5
  555. package/dist/telemetry/feedback-cli.d.ts +0 -13
  556. package/dist/telemetry/feedback-cli.js +0 -77
  557. package/dist/telemetry/feedback.d.ts +13 -0
  558. package/dist/telemetry/feedback.js +183 -17
  559. package/dist/telemetry/harness-run.d.ts +18 -0
  560. package/dist/telemetry/harness-run.js +80 -0
  561. package/dist/telemetry/hud-events.d.ts +16 -0
  562. package/dist/telemetry/hud-events.js +27 -0
  563. package/dist/telemetry/hud-metrics.d.ts +29 -6
  564. package/dist/telemetry/hud-metrics.js +116 -25
  565. package/dist/telemetry/init-telemetry.js +2 -2
  566. package/dist/telemetry/install-source.d.ts +13 -25
  567. package/dist/telemetry/install-source.js +44 -3
  568. package/dist/telemetry/mcp-connection.d.ts +0 -14
  569. package/dist/telemetry/mcp-connection.js +5 -1
  570. package/dist/telemetry/onboarding-firsts.d.ts +32 -0
  571. package/dist/telemetry/onboarding-firsts.js +81 -6
  572. package/dist/telemetry/platform-linked.d.ts +5 -0
  573. package/dist/telemetry/platform-linked.js +32 -0
  574. package/dist/telemetry/project-profile.js +5 -3
  575. package/dist/telemetry/proxy-telemetry.js +4 -1
  576. package/dist/telemetry/session-metrics.d.ts +15 -4
  577. package/dist/telemetry/session-metrics.js +88 -52
  578. package/dist/telemetry/telemetry.d.ts +38 -2
  579. package/dist/telemetry/telemetry.js +144 -34
  580. package/dist/telemetry/verification-of.d.ts +3 -1
  581. package/dist/telemetry/verification-of.js +13 -4
  582. package/dist/wire-journal.d.ts +2 -2
  583. package/dist/wire-journal.js +20 -6
  584. package/docs/README.md +3 -3
  585. package/docs/actions.mdx +6 -3
  586. package/docs/adapters.md +1 -1
  587. package/docs/agent-cheatsheet.md +25 -28
  588. package/docs/architecture.md +5 -5
  589. package/docs/autodrive.md +33 -28
  590. package/docs/benchmarks.md +5 -5
  591. package/docs/best-practices.mdx +27 -9
  592. package/docs/capabilities.mdx +14 -16
  593. package/docs/cli/affected.mdx +1 -1
  594. package/docs/cli/cloud.mdx +54 -75
  595. package/docs/cli/doctor.mdx +14 -3
  596. package/docs/cli/drive.mdx +20 -14
  597. package/docs/cli/feedback.mdx +3 -3
  598. package/docs/cli/gate.mdx +7 -2
  599. package/docs/cli/help.mdx +50 -55
  600. package/docs/cli/init.mdx +43 -28
  601. package/docs/cli/license.mdx +12 -8
  602. package/docs/cli/mcp.mdx +4 -4
  603. package/docs/cli/open.mdx +3 -3
  604. package/docs/cli/report.mdx +4 -2
  605. package/docs/cli/restart.mdx +2 -2
  606. package/docs/cli/rollback.mdx +14 -8
  607. package/docs/cli/serve.mdx +4 -4
  608. package/docs/cli/setup.mdx +23 -0
  609. package/docs/cli/status.mdx +18 -5
  610. package/docs/cli/stop.mdx +67 -8
  611. package/docs/cli/try.mdx +62 -0
  612. package/docs/cli/update.mdx +4 -3
  613. package/docs/cli/verify.mdx +17 -6
  614. package/docs/cli/version.mdx +5 -4
  615. package/docs/cli.mdx +111 -42
  616. package/docs/debugging.md +3 -3
  617. package/docs/deploy-checks.md +1 -1
  618. package/docs/desktop.mdx +9 -3
  619. package/docs/enterprise.md +11 -10
  620. package/docs/environment.mdx +7 -8
  621. package/docs/faq.mdx +3 -3
  622. package/docs/first-drive.md +5 -4
  623. package/docs/flows.md +35 -27
  624. package/docs/for-agents.md +4 -5
  625. package/docs/frameworks.mdx +32 -21
  626. package/docs/getting-started.md +65 -61
  627. package/docs/human-control.md +16 -16
  628. package/docs/index.mdx +15 -42
  629. package/docs/install-agentic.mdx +22 -37
  630. package/docs/install-manual.mdx +23 -13
  631. package/docs/instrumentation.mdx +5 -3
  632. package/docs/integration-patterns.md +7 -7
  633. package/docs/license-activation.mdx +1 -1
  634. package/docs/multi-agent-testing.md +24 -6
  635. package/docs/packages/babel-plugin.mdx +4 -2
  636. package/docs/packages/browser.mdx +16 -10
  637. package/docs/packages/core.mdx +12 -5
  638. package/docs/packages/electron.mdx +3 -2
  639. package/docs/packages/engine.mdx +1 -1
  640. package/docs/packages/eslint-plugin.mdx +18 -12
  641. package/docs/packages/next.mdx +8 -7
  642. package/docs/packages/open-verification.mdx +1 -1
  643. package/docs/packages/server.mdx +5 -5
  644. package/docs/packages/tauri.mdx +9 -9
  645. package/docs/packages/test.mdx +4 -10
  646. package/docs/packages/vite-plugin.mdx +32 -4
  647. package/docs/packages.mdx +11 -15
  648. package/docs/platform-integration.md +6 -5
  649. package/docs/predicates.mdx +40 -24
  650. package/docs/quickstart.mdx +69 -30
  651. package/docs/required-checks.md +8 -7
  652. package/docs/reticle-manual.md +15 -5
  653. package/docs/skill-file.mdx +17 -79
  654. package/docs/skill-setup.md +9 -9
  655. package/docs/skill.md +200 -0
  656. package/docs/system-map.md +7 -7
  657. package/docs/telemetry.md +23 -25
  658. package/docs/testing.md +4 -4
  659. package/docs/token-efficiency.md +12 -12
  660. package/docs/tools/act-and-wait.mdx +9 -2
  661. package/docs/tools/act-sequence.mdx +6 -7
  662. package/docs/tools/act.mdx +2 -2
  663. package/docs/tools/assert.mdx +18 -17
  664. package/docs/tools/console.mdx +9 -9
  665. package/docs/tools/inspect.mdx +16 -7
  666. package/docs/tools/look.mdx +24 -0
  667. package/docs/tools/navigate.mdx +5 -3
  668. package/docs/tools/network.mdx +2 -2
  669. package/docs/tools/observe.mdx +6 -6
  670. package/docs/tools/overview.mdx +34 -33
  671. package/docs/tools/query.mdx +5 -5
  672. package/docs/tools/session-and-feedback.mdx +22 -13
  673. package/docs/tools/sessions.mdx +8 -8
  674. package/docs/tools/snapshot.mdx +6 -5
  675. package/docs/tools/state.mdx +9 -9
  676. package/docs/tools/tools-and-run.mdx +26 -24
  677. package/docs/tools/verify.mdx +28 -0
  678. package/docs/tools/wait-for.mdx +8 -4
  679. package/docs/troubleshooting.mdx +21 -6
  680. package/docs/usage.md +55 -46
  681. package/docs/use-cases.mdx +3 -3
  682. package/docs/vs-chrome-devtools-mcp.mdx +9 -6
  683. package/docs/vs-playwright-mcp.mdx +3 -1
  684. package/docs/what-is-recorded.md +17 -5
  685. package/docs/what-is-reticle.mdx +5 -3
  686. package/docs/why-reticle.mdx +1 -1
  687. package/package.json +9 -12
  688. package/dist/command/cli/demo/demo-page.d.ts +0 -78
  689. package/dist/command/cli/demo/demo-page.js +0 -241
  690. package/dist/command/cli/demo/demo-run.d.ts +0 -38
  691. package/dist/command/cli/demo/demo-run.js +0 -138
  692. package/dist/features/harness/driver.d.ts +0 -50
  693. package/dist/features/harness/driver.js +0 -236
  694. package/dist/features/harness/jev-driver.d.ts +0 -188
  695. package/dist/features/harness/jev-driver.js +0 -678
  696. package/dist/features/harness/openai-driver.d.ts +0 -58
  697. package/dist/features/harness/openai-driver.js +0 -206
  698. package/dist/judgement/hunt/hunt-report.d.ts +0 -49
  699. package/dist/judgement/hunt/hunt-report.js +0 -84
  700. package/dist/language/flows/buddy-status.d.ts +0 -25
  701. package/dist/language/flows/buddy-status.js +0 -32
  702. package/dist/language/flows/change/watch-batcher.d.ts +0 -18
  703. package/dist/language/flows/change/watch-batcher.js +0 -19
  704. package/dist/memory/cloud/harness-offer.d.ts +0 -44
  705. package/dist/memory/cloud/harness-offer.js +0 -145
  706. package/dist/memory/project/dir/fill-value-store.d.ts +0 -32
  707. package/dist/memory/project/dir/fill-value-store.js +0 -88
  708. package/dist/surface/tools/feature-capture.d.ts +0 -151
  709. package/dist/surface/tools/feature-capture.js +0 -235
  710. package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
  711. package/dist/surface/tools/tool-hit-rate.js +0 -90
  712. package/dist/telemetry/harness-drive.d.ts +0 -4
  713. package/dist/telemetry/harness-drive.js +0 -35
  714. package/dist/telemetry/identify.d.ts +0 -22
  715. package/dist/telemetry/identify.js +0 -73
  716. package/docs/cli/capsules.mdx +0 -70
  717. package/docs/cli/hunt.mdx +0 -73
  718. package/docs/cli/identify.mdx +0 -76
  719. package/docs/cli/kill.mdx +0 -91
  720. package/docs/cli/watch.mdx +0 -62
  721. package/docs/local-registry.md +0 -143
@@ -30,19 +30,21 @@
30
30
  * Every dependency is injected — the clock, the fetch, the reads and writes — because this is the
31
31
  * one piece of Reticle that talks to a network AND to a disk, and it must be provable without either.
32
32
  */
33
- import { FlowErrorCode, ReticleDir, SYNC_BATCH_LIMITS } from '@reticlehq/core';
33
+ import { FlowErrorCode, ReticleDir, SYNC_BATCH_LIMITS, flowJsonReplacer } from '@reticlehq/core';
34
34
  import { hashPayload } from './sync-hash.js';
35
+ import { syncScreenshots } from './sync-screenshots.js';
35
36
  /** The server's own doors. Kept beside the code that calls them, like the other cloud paths. */
36
37
  const SYNC_PATH = '/v1/sync';
37
38
  const SYNC_STATUS_PATH = '/v1/sync/status';
38
39
  const SYNC_PULL_PATH = '/v1/sync/pull';
40
+ const FLOWS_PATH = '/v1/flows';
39
41
  /**
40
42
  * The derived records that ride along with runs, and the file each one lives in.
41
43
  *
42
- * A list rather than three hand-written blocks so adding a fourth is one line and cannot be
43
- * half-done — the bundle, the hashing and the reporting all walk this.
44
+ * One list, so adding a record is one line and cannot be half-done: the bundle, the hashing, the
45
+ * reporting and the disk source (`sync-disk.ts`) all read it.
44
46
  */
45
- const DERIVED_RECORDS = [
47
+ export const DERIVED_RECORDS = [
46
48
  { kind: 'impact', file: ReticleDir.IMPACT_FILE },
47
49
  { kind: 'flake', file: ReticleDir.FLAKE_FILE },
48
50
  { kind: 'intent', file: ReticleDir.INTENT_FILE },
@@ -50,6 +52,10 @@ const DERIVED_RECORDS = [
50
52
  // cannot tell a server a page that drifted from one that always behaved that way.
51
53
  { kind: 'envelopes', file: ReticleDir.ENVELOPES_FILE },
52
54
  { kind: 'assertion-tiers', file: ReticleDir.TIERS_FILE },
55
+ // Reticle Coverage: what the platform's Overview shows as controls proved and routes reached.
56
+ { kind: 'coverage', file: ReticleDir.COVERAGE_FILE },
57
+ // The notes a human pinned in the HUD, pending and resolved: the review trail a team reads.
58
+ { kind: 'notes', file: ReticleDir.NOTES_FILE },
53
59
  ];
54
60
  const asJson = (text) => {
55
61
  try {
@@ -75,6 +81,8 @@ const flowName = (flow, index) => {
75
81
  const FLOW_VERSION_FIX = 'update the platform or remove `compare` from the flow';
76
82
  const RUN_VERSION_FIX = 'update the platform';
77
83
  /** The server's per-part answer: accepted count and rejection list, each read defensively. */
84
+ /** A 200 that never confirmed the runs it was sent. Kept queued, and said, rather than dropped. */
85
+ export const MSG_RUNS_UNCONFIRMED = 'sync 200 without a runs result: the server did not confirm the runs it was sent, so they stay queued';
78
86
  function partResult(body, part) {
79
87
  const raw = isRecord(body[part]) ? body[part] : {};
80
88
  const accepted = 'number' === typeof raw['accepted'] ? raw['accepted'] : 0;
@@ -125,7 +133,29 @@ function batchRuns(runs) {
125
133
  * Run one cycle. Never throws: the local record is already safe on disk, and a sync that can take
126
134
  * down the thing it is backing up is worse than no sync.
127
135
  */
128
- export async function runSyncCycle(deps) {
136
+ /** The command that moves a folder's earlier runs to the link it has now. */
137
+ export const MOVE_HISTORY_COMMAND = 'reticle sync --move-history';
138
+ /**
139
+ * The record this cycle keeps, for this link. Re-linked to another cloud, workspace or project, it
140
+ * starts afresh from now: what the old link was sent says nothing about the new one, and the runs
141
+ * made under the old link are that workspace's (a person's email can be in their steps), so they go
142
+ * only when the person moves them. A first link, or a record from before links were kept, is
143
+ * adopted as it is.
144
+ */
145
+ function stateForLink(deps) {
146
+ const { state, link } = deps;
147
+ if (link === undefined)
148
+ return state;
149
+ if (state.link === undefined || state.link === link) {
150
+ const { linkedAt, ...rest } = state;
151
+ return true === deps.moveHistory || linkedAt === undefined
152
+ ? { ...rest, link }
153
+ : { ...rest, link, linkedAt };
154
+ }
155
+ return { link, ...(true === deps.moveHistory ? {} : { linkedAt: deps.now() }) };
156
+ }
157
+ export async function runSyncCycle(given) {
158
+ const deps = { ...given, state: stateForLink(given) };
129
159
  const empty = {
130
160
  ok: false,
131
161
  runsSent: 0,
@@ -231,7 +261,14 @@ export async function runSyncCycle(deps) {
231
261
  notRetried.push({ runId: run.runId, reason: prior.reason });
232
262
  return false;
233
263
  });
264
+ // Made before this folder was linked here: the previous link's, kept until the person moves it.
265
+ const linkedAt = deps.state.linkedAt;
266
+ const earlier = unsent.filter((run) => linkedAt !== undefined && !((numberAt(run.payload, 'createdAt') ?? 0) >= linkedAt));
267
+ if (0 < earlier.length)
268
+ heldBack.push(`${String(earlier.length)} run${1 === earlier.length ? '' : 's'} made before this folder was linked to this workspace stay${1 === earlier.length ? 's' : ''} on this machine (recorded under its previous link): run \`${MOVE_HISTORY_COMMAND}\` to send ${1 === earlier.length ? 'it' : 'them'} here`);
234
269
  const sendable = unsent.filter((run) => {
270
+ if (earlier.includes(run))
271
+ return false;
235
272
  const version = numberAt(run.payload, 'schemaVersion');
236
273
  if (runVersions === undefined || version === undefined || runVersions.includes(version))
237
274
  return true;
@@ -242,25 +279,51 @@ export async function runSyncCycle(deps) {
242
279
  const derivedOffered = [];
243
280
  const unsupported = [];
244
281
  const bundle = {};
282
+ // A folder re-linked elsewhere keeps what it derived under the old link (its counts, defects,
283
+ // notes) here with the runs: those records add up the whole history and cannot be split by time.
284
+ // ponytail: held until `--move-history`; a per-link reset of the counters would let new ones go.
285
+ const derivedHeld = [];
245
286
  for (const { kind } of DERIVED_RECORDS) {
246
287
  const payload = deps.source.derived(kind);
247
288
  if (payload === undefined)
248
289
  continue;
290
+ if (linkedAt !== undefined) {
291
+ derivedHeld.push(kind);
292
+ continue;
293
+ }
249
294
  if (!acceptedKinds.has(kind)) {
250
295
  unsupported.push(kind);
251
296
  continue;
252
297
  }
253
- // The one comparison the whole protocol rests on. Same hash, do not send it.
254
- if (hashPayload(payload) === hashes[kind])
298
+ // The one comparison the whole protocol rests on. Same hash, do not send it: the platform's,
299
+ // or the one it accepted from here while it still holds the record (a null means it does not).
300
+ const hash = hashPayload(payload);
301
+ if (hash === hashes[kind])
302
+ continue;
303
+ if ('string' === typeof hashes[kind] && hash === deps.state.sentDerivedHashes?.[kind])
255
304
  continue;
256
305
  bundle[kind] = payload;
257
306
  derivedOffered.push(kind);
258
307
  }
259
308
  if (unsupported.length > 0)
260
309
  heldBack.push(`this platform does not accept ${unsupported.join(', ')} yet`);
310
+ if (0 < derivedHeld.length)
311
+ heldBack.push(`the ${derivedHeld.join(', ')} record${1 === derivedHeld.length ? '' : 's'} made under this folder's previous link stay${1 === derivedHeld.length ? 's' : ''} on this machine: run \`${MOVE_HISTORY_COMMAND}\` to send ${1 === derivedHeld.length ? 'it' : 'them'} here`);
261
312
  // Flows and capsules are upserted by name/id, so the whole set goes whenever it differs from the
262
- // set the platform last answered for, and rides along with anything else being sent.
263
- const flows = deps.source.flows().filter((flow, index) => {
313
+ // set the platform last answered for — and only then. Riding along with every run push re-sent
314
+ // ~1.3 MB per push on a real repo, and a POST that size timed out and failed the run with it.
315
+ // Made before this folder was linked here, like the runs: the previous link's, kept until moved.
316
+ // A flow carries the steps of whoever drove it, so it is that workspace's as much as a run is.
317
+ // One read for both halves: the disk source rebuilds every flow per read, so a second read's
318
+ // objects are never `includes` of the first's, and every held-back flow went out anyway.
319
+ const flowsAll = deps.source.flows();
320
+ const madeBefore = (flow) => linkedAt !== undefined && !((numberAt(flow, 'createdAt') ?? 0) >= linkedAt);
321
+ const flowsBefore = flowsAll.filter(madeBefore);
322
+ if (0 < flowsBefore.length)
323
+ heldBack.push(`${String(flowsBefore.length)} flow${1 === flowsBefore.length ? '' : 's'} made before this folder was linked to this workspace stay${1 === flowsBefore.length ? 's' : ''} on this machine: run \`${MOVE_HISTORY_COMMAND}\` to send ${1 === flowsBefore.length ? 'it' : 'them'} here`);
324
+ const flows = flowsAll.filter((flow, index) => {
325
+ if (madeBefore(flow))
326
+ return false;
264
327
  const version = numberAt(flow, 'version');
265
328
  if (flowVersions === undefined || version === undefined || flowVersions.includes(version))
266
329
  return true;
@@ -290,10 +353,9 @@ export async function runSyncCycle(deps) {
290
353
  };
291
354
  const flowsChanged = due('flow', flowsHash, flows.length, deps.state.sentFlowsHash);
292
355
  const capsulesChanged = due('capsule', capsulesHash, capsulesAll.length, deps.state.sentCapsulesHash);
293
- const ridesAlong = sendable.length > 0 || derivedOffered.length > 0 || flowsChanged || capsulesChanged;
294
- if (ridesAlong && flows.length > 0)
356
+ if (flowsChanged)
295
357
  bundle['flows'] = flows;
296
- const capsules = ridesAlong ? capsulesAll : [];
358
+ const capsules = capsulesChanged ? capsulesAll : [];
297
359
  if (capsules.length > 0)
298
360
  bundle['capsules'] = capsules;
299
361
  let runsSent = 0;
@@ -315,12 +377,23 @@ export async function runSyncCycle(deps) {
315
377
  const body = 0 === i ? { ...bundle } : {};
316
378
  if (batch.length > 0)
317
379
  body['runs'] = batch.map((r) => r.payload);
318
- const pushed = await call(SYNC_PATH, { method: 'POST', body: JSON.stringify(body) });
380
+ // The flows ride here too: a default flag stays out of them, as on disk.
381
+ const pushed = await call(SYNC_PATH, {
382
+ method: 'POST',
383
+ body: JSON.stringify(body, flowJsonReplacer),
384
+ });
319
385
  if (200 !== pushed.status) {
320
386
  pushError = `sync ${String(pushed.status)}: ${pushed.text.slice(0, 200)}`;
321
387
  break;
322
388
  }
323
389
  const answer = isRecord(pushed.json) ? pushed.json : {};
390
+ // Runs were sent and the answer says nothing about them: a proxy's 200 page, an empty body.
391
+ // Delivered means the server said so. Recording them as sent here dropped them for good while
392
+ // the cycle reported ok; they stay queued, and the reason is the cycle's error.
393
+ if (batch.length > 0 && !isRecord(answer['runs'])) {
394
+ pushError = MSG_RUNS_UNCONFIRMED;
395
+ break;
396
+ }
324
397
  const runs = partResult(answer, 'runs');
325
398
  runsSent += runs.accepted;
326
399
  // Accepted means the server has it. A rejected run was refused by index, so it is exactly as
@@ -375,18 +448,40 @@ export async function runSyncCycle(deps) {
375
448
  const state = isRecord(answer['state']) ? answer['state'] : {};
376
449
  for (const kind of derivedOffered) {
377
450
  const verdict = state[kind];
378
- if ('accepted' === verdict)
451
+ if ('accepted' === verdict) {
379
452
  derivedSent.push(kind);
453
+ nextState.sentDerivedHashes = {
454
+ ...nextState.sentDerivedHashes,
455
+ [kind]: hashPayload(bundle[kind]),
456
+ };
457
+ }
380
458
  else if ('string' === typeof verdict)
381
459
  refused.push({ part: kind, reason: `${kind}: ${verdict}` });
382
460
  }
383
461
  }
462
+ // A flow the platform took from here and that is gone from disk is deleted there by name: it
463
+ // does not infer a deletion from a missing name, since another clone may still hold the flow.
464
+ const onDisk = new Set(deps.source.flows().map(flowName));
465
+ const kept = [];
466
+ for (const name of deps.projectId === undefined ? [] : (deps.state.sentFlowNames ?? [])) {
467
+ if (onDisk.has(name)) {
468
+ kept.push(name);
469
+ continue;
470
+ }
471
+ const gone = await call(`${FLOWS_PATH}/${encodeURIComponent(name)}?projectId=${encodeURIComponent(deps.projectId ?? '')}`, { method: 'DELETE' });
472
+ if (2 !== Math.floor(gone.status / 100) && 404 !== gone.status)
473
+ kept.push(name);
474
+ }
475
+ if (deps.projectId !== undefined)
476
+ nextState.sentFlowNames = kept;
384
477
  if (requests > 0 && pushError === undefined) {
385
478
  nextState.lastPushAt = deps.now();
386
479
  // The platform answered for these sets, refusals included (reported above, and not re-offered
387
480
  // until the set changes — the same rule as a refused run).
388
- if (ridesAlong && flows.length > 0)
481
+ if (flowsChanged)
389
482
  nextState.sentFlowsHash = flowsHash;
483
+ if (flowsChanged && deps.projectId !== undefined)
484
+ nextState.sentFlowNames = [...new Set([...kept, ...flows.map(flowName)])];
390
485
  if (capsules.length > 0)
391
486
  nextState.sentCapsulesHash = capsulesHash;
392
487
  }
@@ -416,6 +511,22 @@ export async function runSyncCycle(deps) {
416
511
  */
417
512
  nextState.sentRunIds = allRuns.map((r) => r.runId).filter((id) => known.has(id));
418
513
  nextState.sentRunHashes = heldHashes(allRuns, recorded);
514
+ // 2b. The step screenshots of every run the platform now holds, once each.
515
+ let screenshotsSent = 0;
516
+ const readImage = deps.source.screenshot;
517
+ if (readImage !== undefined) {
518
+ const shots = await syncScreenshots({
519
+ runs: allRuns,
520
+ accepted: (runId) => known.has(runId),
521
+ readImage,
522
+ state: deps.state,
523
+ post: (path, body) => call(path, { method: 'POST', body }),
524
+ });
525
+ screenshotsSent = shots.sent;
526
+ Object.assign(nextState, shots.state);
527
+ if (shots.error !== undefined)
528
+ heldBack.push(shots.error);
529
+ }
419
530
  // 3. COLLECT — always, even when there was nothing to send.
420
531
  const query = nextState.cursor === undefined ? '' : `?since=${encodeURIComponent(nextState.cursor)}`;
421
532
  const pull = await call(`${SYNC_PULL_PATH}${query}`);
@@ -464,6 +575,7 @@ export async function runSyncCycle(deps) {
464
575
  0 === setsNotRetried.length &&
465
576
  !heldArtifacts,
466
577
  runsSent,
578
+ ...(0 === screenshotsSent ? {} : { screenshotsSent }),
467
579
  runsRejected,
468
580
  flowsSent,
469
581
  capsulesSent,
@@ -537,6 +649,8 @@ export function describeSync(report) {
537
649
  sent.push(`${String(report.flowsSent)} flow(s)`);
538
650
  if (report.capsulesSent > 0)
539
651
  sent.push(`${String(report.capsulesSent)} capsule(s)`);
652
+ if ((report.screenshotsSent ?? 0) > 0)
653
+ sent.push(`${String(report.screenshotsSent)} screenshot(s)`);
540
654
  if (report.derivedSent.length > 0)
541
655
  sent.push(report.derivedSent.join(', '));
542
656
  /*
@@ -1,5 +1,6 @@
1
1
  import { type SyncReport } from './sync-cycle.js';
2
2
  import { type ProjectCloud } from './cloud-config.js';
3
+ import { type UnsyncedRoot } from './unsynced-roots.js';
3
4
  import type { FileSystemPort } from '../project/fs/fs-port.js';
4
5
  interface SyncDaemonDeps {
5
6
  reticleRoot: string;
@@ -20,6 +21,11 @@ interface SyncDaemonDeps {
20
21
  cloudFor?: (root: string) => Promise<ProjectCloud>;
21
22
  now?: () => number;
22
23
  intervalMs?: number;
24
+ /**
25
+ * Call `onChange` when this root's link file changes; returns how to stop. A re-link was read only
26
+ * on the next idle tick, so a daemon kept syncing to the old link for up to a minute.
27
+ */
28
+ watchLink?: (onChange: () => void) => () => void;
23
29
  /** Injected for the test; the real one is `fetch`. */
24
30
  request?: (url: string, init: {
25
31
  method: string;
@@ -58,6 +64,11 @@ export interface SyncDaemon {
58
64
  * time — the artifact is still on disk and the next daemon for this project sends it.
59
65
  */
60
66
  flush: () => Promise<void>;
67
+ /**
68
+ * Every root this daemon knows with runs the platform does not have, as of the last cycle — linked
69
+ * or not. An unlinked one is the case nothing will ever send, and it used to be skipped silently.
70
+ */
71
+ unsyncedRoots: () => readonly UnsyncedRoot[];
61
72
  }
62
73
  /**
63
74
  * Start the loop. Safe to call for an UNLINKED project: it resolves the link on every tick and does
@@ -21,11 +21,21 @@
21
21
  * laptop on a train would otherwise fill the log with the same line four hundred times and teach
22
22
  * everyone to ignore it.
23
23
  */
24
+ import { reticleDirPaths } from '../project/dir/reticle-dir.js';
25
+ import { syncRequest } from './cloud-sync.js';
24
26
  import { log } from '../../log.js';
27
+ import { homedir } from 'node:os';
25
28
  import { emitSyncHook } from '../../hooks/hook-emit.js';
29
+ import { MemoryReadHeader, MemoryReadPurpose, memoryReadUrl, scopedMemoryEntries, } from './memory-scope.js';
30
+ import { IntentShardStore } from '../intent/intent-shard-store.js';
31
+ import { applySettledWords, keptWordsInRequest, settledWordsIn, } from '../intent/settled-words.js';
32
+ import { createNodeFileSystem } from '../project/fs/fs-port.js';
33
+ import { flushPendingWrites } from '../../hooks/hook-bus.js';
26
34
  import { describeSync, runSyncCycle } from './sync-cycle.js';
27
35
  import { diskSink, diskSource, readCloudState } from './sync-disk.js';
28
- import { resolveProjectCloud } from './cloud-config.js';
36
+ import { CLOUD_LINK_FILE, resolveProjectCloud } from './cloud-config.js';
37
+ import { watch } from 'node:fs';
38
+ import { unmatchedRoots, unsyncedRoots } from './unsynced-roots.js';
29
39
  /** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
30
40
  const DAEMON_SYNC_INTERVAL_MS = 60_000;
31
41
  /**
@@ -69,10 +79,7 @@ const movedAnything = (report) => report.runsSent > 0 ||
69
79
  report.capsulesSent > 0 ||
70
80
  report.derivedSent.length > 0 ||
71
81
  report.pulled > 0;
72
- const defaultRequest = async (url, init) => {
73
- const res = await fetch(url, init);
74
- return { status: res.status, text: await res.text() };
75
- };
82
+ const defaultRequest = (url, init) => syncRequest(url, init);
76
83
  /**
77
84
  * Start the loop. Safe to call for an UNLINKED project: it resolves the link on every tick and does
78
85
  * nothing until one appears, which is what lets `reticle link` take effect without a restart.
@@ -102,18 +109,62 @@ export function startSyncDaemon(deps) {
102
109
  * so too. Once per transition, never per tick — a line every minute is a line people stop reading.
103
110
  */
104
111
  let wasLinked;
112
+ /** The project the last cycle synced to: a re-link to another one is announced too. */
113
+ let wasProject;
114
+ /** The last count logged per root: a root is announced when its count changes, never per tick. */
115
+ const announced = new Map();
116
+ let unsynced = [];
117
+ /** Which roots pushed this cycle, and whether each is linked by its own link file. */
118
+ let rootsLinked = new Map();
119
+ /** The linked roots that send flows: a link with `sync.flows` off keeps every flow local. */
120
+ let rootsSendFlows = new Set();
121
+ /**
122
+ * Count what every root still owes, and say once per change where runs sit that nothing sends.
123
+ * Linked roots are listed too (the platform report and `doctor` read them), but only an unlinked
124
+ * one earns a log line: a linked root's pending runs are the next cycle's job.
125
+ */
126
+ const refreshUnsynced = async () => {
127
+ const linked = rootsLinked;
128
+ const sendsFlows = rootsSendFlows;
129
+ unsynced = await unsyncedRoots(
130
+ // With what no project claimed, which no cycle pushes: the dashboard's unsent list left it out.
131
+ [...linked.keys(), ...unmatchedRoots(homedir())], (r) => Promise.resolve(true === linked.get(r)), (r) => Promise.resolve(sendsFlows.has(r)));
132
+ for (const entry of unsynced) {
133
+ const held = entry.runs + entry.flows;
134
+ if (entry.linked || announced.get(entry.root) === held)
135
+ continue;
136
+ announced.set(entry.root, held);
137
+ log('reticle_cloud_unsynced_root', {
138
+ root: entry.root,
139
+ runs: entry.runs,
140
+ flows: entry.flows,
141
+ linked: entry.linked,
142
+ fix: "run `reticle connect` in that folder's project: this work is on this machine only",
143
+ });
144
+ }
145
+ };
105
146
  /** One root's bundle. The state, source and sink are already per-root; only the caller was not. */
106
147
  const pushRoot = async (root, cloud) => {
107
148
  if (null === cloud.config)
108
149
  return undefined;
109
150
  const full = diskSource(root);
151
+ // The team's decisions come down too: an author's record kept the words a maintainer set aside,
152
+ // and re-sent them every cycle as a new disagreement.
153
+ if (cloud.policy.memory)
154
+ await pullSettledWords(root, cloud.config, cloud.projectId).catch(() => 0);
110
155
  return runSyncCycle({
111
156
  config: cloud.config,
157
+ ...(cloud.link === undefined ? {} : { link: cloud.link }),
158
+ // Flows deleted here are deleted there, for the project they were sent to.
159
+ ...(cloud.policy.flows && null !== cloud.projectId ? { projectId: cloud.projectId } : {}),
112
160
  source: {
113
161
  runs: () => (cloud.policy.runs ? full.runs() : []),
114
162
  flows: () => (cloud.policy.flows ? full.flows() : []),
115
163
  capsules: () => (cloud.policy.capsules ? full.capsules() : []),
116
164
  derived: (kind) => (cloud.policy.memory ? full.derived(kind) : undefined),
165
+ ...(cloud.policy.screenshots && full.screenshot !== undefined
166
+ ? { screenshot: full.screenshot }
167
+ : {}),
117
168
  },
118
169
  sink: diskSink(root),
119
170
  state: readCloudState(root),
@@ -121,6 +172,23 @@ export function startSyncDaemon(deps) {
121
172
  request,
122
173
  });
123
174
  };
175
+ /** Bring the dashboard's memory decisions down to this root's records. Returns how many changed. */
176
+ const pullSettledWords = async (root, config, projectId) => {
177
+ const res = await request(memoryReadUrl(config.url, { projectId }), {
178
+ method: 'GET',
179
+ // The sync, not an agent: without this every cycle counted as an agent consulting memory.
180
+ headers: {
181
+ authorization: `Bearer ${config.apiKey}`,
182
+ [MemoryReadHeader.PURPOSE]: MemoryReadPurpose.SYNC,
183
+ },
184
+ });
185
+ if (200 !== res.status)
186
+ return 0;
187
+ const entries = scopedMemoryEntries(JSON.parse(res.text), projectId) ?? [];
188
+ const settled = settledWordsIn(entries);
189
+ await keptWordsInRequest(reticleDirPaths(root).request, settled);
190
+ return applySettledWords(new IntentShardStore(createNodeFileSystem(), root, { now }), settled);
191
+ };
124
192
  /**
125
193
  * Push every other linked root this machine knows about.
126
194
  *
@@ -145,10 +213,19 @@ export function startSyncDaemon(deps) {
145
213
  for (const root of list) {
146
214
  if (root === deps.reticleRoot)
147
215
  continue;
216
+ rootsLinked.set(root, false);
148
217
  try {
149
218
  const cloud = await cloudFor(root);
150
- if (null === cloud.config)
219
+ // Linked HERE, by its own link file. An unlinked root resolves to the environment's key,
220
+ // which is the right answer for the root this daemon stands in and somebody else's project
221
+ // for every other one: a daemon started with RETICLE_API_KEY pushed every project this
222
+ // machine had seen into that one, a status and a pull per root on every cycle.
223
+ // Never silently: `refreshUnsynced` names this root if it holds runs.
224
+ if (null === cloud.config || null === cloud.projectId)
151
225
  continue;
226
+ rootsLinked.set(root, true);
227
+ if (cloud.policy.flows)
228
+ rootsSendFlows.add(root);
152
229
  const report = await pushRoot(root, cloud);
153
230
  if (report === undefined)
154
231
  continue;
@@ -178,11 +255,18 @@ export function startSyncDaemon(deps) {
178
255
  if (running)
179
256
  return undefined;
180
257
  running = true;
258
+ rootsLinked = new Map();
259
+ rootsSendFlows = new Set();
181
260
  try {
182
261
  const cloud = await deps.cloud();
183
262
  const linked = null !== cloud.config;
184
- if (linked !== wasLinked) {
263
+ const own = deps.reticleRoot;
264
+ rootsLinked.set(own, linked);
265
+ if (linked && cloud.policy.flows)
266
+ rootsSendFlows.add(own);
267
+ if (linked !== wasLinked || (linked && cloud.projectId !== wasProject)) {
185
268
  wasLinked = linked;
269
+ wasProject = cloud.projectId;
186
270
  if (linked)
187
271
  log('reticle_cloud_linked', {
188
272
  projectId: cloud.projectId,
@@ -193,7 +277,7 @@ export function startSyncDaemon(deps) {
193
277
  else
194
278
  log('reticle_cloud_unlinked', {
195
279
  root: deps.reticleRoot,
196
- fix: 'run `reticle link` in this directory, or start the daemon in the linked one — nothing is being synced from here',
280
+ fix: 'run `reticle connect` in this directory, or start the daemon in the linked one — nothing is being synced from here',
197
281
  });
198
282
  }
199
283
  // Every OTHER linked root, first. Their failures are logged and never abort this daemon's own
@@ -251,6 +335,7 @@ export function startSyncDaemon(deps) {
251
335
  return undefined;
252
336
  }
253
337
  finally {
338
+ await refreshUnsynced().catch(() => undefined);
254
339
  running = false;
255
340
  }
256
341
  };
@@ -270,20 +355,30 @@ export function startSyncDaemon(deps) {
270
355
  timer.unref?.();
271
356
  };
272
357
  schedule(FIRST_CYCLE_DELAY_MS);
358
+ // A nudge, not an immediate cycle: the link file and its key are written one after the other.
359
+ const unwatch = deps.watchLink?.(() => schedule(NUDGE_DELAY_MS));
273
360
  return {
274
- syncNow: cycle,
361
+ // A sync somebody asked for writes what is held on a timer first: a drive's run waits a few
362
+ // seconds after its last verdict, and a push straight after `session end` sent "0 of 0".
363
+ syncNow: async () => {
364
+ await flushPendingWrites();
365
+ return cycle();
366
+ },
367
+ unsyncedRoots: () => unsynced,
275
368
  nudge: () => {
276
369
  // A cycle already in flight will not pick this up, so the nudge is still scheduled behind it
277
370
  // rather than dropped — otherwise the run that arrived during a slow cycle waits a full tick.
278
371
  schedule(NUDGE_DELAY_MS);
279
372
  },
280
373
  stop: () => {
374
+ unwatch?.();
281
375
  stopped = true;
282
376
  if (timer !== undefined)
283
377
  clearTimeout(timer);
284
378
  timer = undefined;
285
379
  },
286
380
  flush: async () => {
381
+ unwatch?.();
287
382
  stopped = true;
288
383
  if (timer !== undefined)
289
384
  clearTimeout(timer);
@@ -318,5 +413,22 @@ export function attachCloudSync(deps) {
318
413
  // reporting nothing — indistinguishable, on the dashboard, from nobody having verified anything.
319
414
  otherRoots: () => Promise.resolve(deps.otherRoots()),
320
415
  cloudFor: (root) => resolveProjectCloud(deps.fs, root, deps.homeDir, deps.env),
416
+ // ponytail: this root only; a sibling root's re-link is still read on the next tick.
417
+ watchLink: (onChange) => watchLinkFile(deps.reticleRoot, onChange),
321
418
  });
322
419
  }
420
+ /** Watch a root for its link file changing. A root with no `.reticle` yet is picked up per tick. */
421
+ function watchLinkFile(reticleRoot, onChange) {
422
+ try {
423
+ const watcher = watch(reticleRoot, (_event, name) => {
424
+ if (CLOUD_LINK_FILE === String(name))
425
+ onChange();
426
+ });
427
+ watcher.on('error', () => watcher.close());
428
+ watcher.unref();
429
+ return () => watcher.close();
430
+ }
431
+ catch {
432
+ return () => undefined;
433
+ }
434
+ }
@@ -1,4 +1,4 @@
1
- import type { CloudSyncState, PulledIssues, SyncSink, SyncSource } from './sync-cycle.js';
1
+ import { type CloudSyncState, type PulledIssues, type SyncSink, type SyncSource } from './sync-cycle.js';
2
2
  /** What the cycle reads, backed by a real `.reticle` directory. */
3
3
  export declare function diskSource(reticleRoot: string): SyncSource;
4
4
  /** What the cycle writes back into `.reticle/`. */
@@ -10,9 +10,12 @@
10
10
  * one unsynced record, not a crashed sync. There is no state here worth defending against a parse
11
11
  * error — the file will be rewritten on the next tool call anyway.
12
12
  */
13
+ import { shareableRun } from '@reticlehq/core/artifacts';
13
14
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from 'node:fs';
14
- import { dirname, join } from 'node:path';
15
+ import { basename, dirname, join } from 'node:path';
16
+ import { stepShotDir } from '../project/dir/reticle-dir.js';
15
17
  import { IntentDir, ReticleDir } from '@reticlehq/core';
18
+ import { DERIVED_RECORDS, } from './sync-cycle.js';
16
19
  import { subjectFor } from '../intent/intent-subject.js';
17
20
  const JSON_SUFFIX = '.json';
18
21
  /** Read + parse, or undefined for absent/unreadable/malformed. Never throws. */
@@ -79,13 +82,7 @@ function readFlows(root) {
79
82
  return [];
80
83
  }
81
84
  }
82
- const DERIVED_FILE = {
83
- impact: ReticleDir.IMPACT_FILE,
84
- flake: ReticleDir.FLAKE_FILE,
85
- intent: ReticleDir.INTENT_FILE,
86
- envelopes: ReticleDir.ENVELOPES_FILE,
87
- 'assertion-tiers': ReticleDir.TIERS_FILE,
88
- };
85
+ const DERIVED_FILE = Object.fromEntries(DERIVED_RECORDS.map((record) => [record.kind, record.file]));
89
86
  /** The directory the sharded intent store writes into, beside the legacy flat file. */
90
87
  const INTENT_SUBDIR = ReticleDir.INTENT_SUBDIR;
91
88
  const INTENT_INDEX = IntentDir.INDEX_FILE;
@@ -106,6 +103,8 @@ const INTENT_INDEX = IntentDir.INDEX_FILE;
106
103
  * Returns undefined when there is no memory at all, so a project with nothing to say sends nothing
107
104
  * rather than an empty envelope the server has to store.
108
105
  */
106
+ /** The longest reason the platform keeps for a statement. */
107
+ const MAX_REASON = 2000;
109
108
  function readIntent(reticleRoot) {
110
109
  const legacy = readJson(join(reticleRoot, ReticleDir.INTENT_FILE));
111
110
  const merged = {
@@ -143,7 +142,16 @@ function readIntent(reticleRoot) {
143
142
  * answers start disagreeing — and the one with the evidence should be the one that decides.
144
143
  * Records that already name a subject keep it: an agent's explicit choice outranks inference.
145
144
  */
146
- const placed = Object.fromEntries(Object.entries(merged).map(([id, record]) => {
145
+ const placed = Object.fromEntries(Object.entries(merged).map(([id, given]) => {
146
+ // The record's `why` is what the platform keeps as `reason`.
147
+ const why = given.why;
148
+ // The run that proved it, under the name the platform reads (it does not read `provenBy`).
149
+ const run = given.provenBy?.runId;
150
+ const record = {
151
+ ...given,
152
+ ...('string' === typeof why && 0 < why.length ? { reason: why.slice(0, MAX_REASON) } : {}),
153
+ ...('string' === typeof run && 0 < run.length ? { provedBy: run } : {}),
154
+ };
147
155
  const held = record;
148
156
  if ('string' === typeof held.subject && held.subject.length > 0)
149
157
  return [id, record];
@@ -170,7 +178,8 @@ export function diskSource(reticleRoot) {
170
178
  runs: () => readJsonDir(join(reticleRoot, ReticleDir.RUNS_SUBDIR))
171
179
  .map((payload) => {
172
180
  const id = payload?.runId;
173
- return 'string' === typeof id ? { runId: id, payload } : undefined;
181
+ // The prompt context leaves the machine only when the project opted in.
182
+ return 'string' === typeof id ? { runId: id, payload: shareableRun(payload) } : undefined;
174
183
  })
175
184
  // A run artifact with no id cannot be diffed against the server's list, so sending it would
176
185
  // mean re-sending it every cycle forever. Dropped rather than uploaded repeatedly.
@@ -181,6 +190,18 @@ export function diskSource(reticleRoot) {
181
190
  capsules: () => readJsonDir(join(reticleRoot, ReticleDir.CAPSULES_SUBDIR)),
182
191
  // Intent is the only derived record that is no longer one file. See readIntent.
183
192
  derived: (kind) => 'intent' === kind ? readIntent(reticleRoot) : readJson(join(reticleRoot, DERIVED_FILE[kind])),
193
+ // The file name comes from a run artifact on disk, so it is held to one segment of the run's
194
+ // own directory before it is read.
195
+ screenshot: (runId, file) => {
196
+ try {
197
+ if (basename(file) !== file)
198
+ return undefined;
199
+ return new Uint8Array(readFileSync(join(stepShotDir(reticleRoot, runId), file)));
200
+ }
201
+ catch {
202
+ return undefined;
203
+ }
204
+ },
184
205
  };
185
206
  }
186
207
  /** What the cycle writes back into `.reticle/`. */
@@ -0,0 +1,32 @@
1
+ /** This machine's record of what went up, kept in `.reticle/cloud-state.json`. */
2
+ export interface ScreenshotSyncState {
3
+ /** Per run, `seq:sha256` of every screenshot the platform holds or refused for good. */
4
+ sentScreenshots?: Record<string, string[]>;
5
+ /** Runs whose screenshots an older platform could not take. Kept local, never offered again. */
6
+ screenshotsHeldLocal?: string[];
7
+ }
8
+ export interface ScreenshotSyncDeps {
9
+ /** Every run on disk, with the payload the sync just sent. */
10
+ runs: ReadonlyArray<{
11
+ runId: string;
12
+ payload: unknown;
13
+ }>;
14
+ /** Whether the platform holds this run now. */
15
+ accepted: (runId: string) => boolean;
16
+ /** One image from the run's directory, or undefined when it is gone. */
17
+ readImage: (runId: string, file: string) => Uint8Array | undefined;
18
+ state: ScreenshotSyncState;
19
+ /** POST a JSON body to a path on the platform, with the sync's own credentials. */
20
+ post: (path: string, body: string) => Promise<{
21
+ status: number;
22
+ text: string;
23
+ }>;
24
+ }
25
+ export interface ScreenshotSyncResult {
26
+ sent: number;
27
+ state: ScreenshotSyncState;
28
+ /** Why this cycle stopped early, when it did. Kept local; the next cycle tries again. */
29
+ error?: string;
30
+ }
31
+ /** Send what is owed. Never throws; the record is bounded by the runs still on disk. */
32
+ export declare function syncScreenshots(deps: ScreenshotSyncDeps): Promise<ScreenshotSyncResult>;