@reticlehq/server 3.6.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 (614) hide show
  1. package/SKILL.md +122 -123
  2. package/dist/command/cli/adhoc-suite.d.ts +14 -0
  3. package/dist/command/cli/adhoc-suite.js +42 -7
  4. package/dist/command/cli/adhoc-verdict.d.ts +18 -2
  5. package/dist/command/cli/adhoc-verdict.js +70 -6
  6. package/dist/command/cli/answers/gate-exit.d.ts +23 -0
  7. package/dist/command/cli/answers/gate-exit.js +45 -0
  8. package/dist/command/cli/answers/gate-hook-message.d.ts +2 -0
  9. package/dist/command/cli/answers/gate-hook-message.js +3 -1
  10. package/dist/command/cli/auth/device-flow.d.ts +13 -2
  11. package/dist/command/cli/auth/device-flow.js +17 -0
  12. package/dist/command/cli/cli-doctor.js +14 -0
  13. package/dist/command/cli/cli-flow-commands.d.ts +0 -11
  14. package/dist/command/cli/cli-flow-commands.js +28 -105
  15. package/dist/command/cli/cli-kill.js +3 -2
  16. package/dist/command/cli/cli-parse-grammar.d.ts +24 -0
  17. package/dist/command/cli/cli-parse-grammar.js +75 -0
  18. package/dist/command/cli/cli-parse-verify.d.ts +2 -0
  19. package/dist/command/cli/cli-parse-verify.js +18 -0
  20. package/dist/command/cli/cli-parse.d.ts +19 -28
  21. package/dist/command/cli/cli-parse.js +82 -182
  22. package/dist/command/cli/cli-update-commands.d.ts +1 -1
  23. package/dist/command/cli/cli-update-commands.js +61 -9
  24. package/dist/command/cli/cli-usage.d.ts +33 -0
  25. package/dist/command/cli/cli-usage.js +287 -0
  26. package/dist/command/cli/cli-verify.d.ts +34 -4
  27. package/dist/command/cli/cli-verify.js +81 -12
  28. package/dist/command/cli/cloud-cli.d.ts +13 -0
  29. package/dist/command/cli/cloud-cli.js +145 -333
  30. package/dist/command/cli/cloud-kit.d.ts +12 -0
  31. package/dist/command/cli/cloud-kit.js +41 -2
  32. package/dist/command/cli/cloud-link.d.ts +57 -0
  33. package/dist/command/cli/cloud-link.js +308 -0
  34. package/dist/command/cli/cloud-login.d.ts +15 -1
  35. package/dist/command/cli/cloud-login.js +67 -13
  36. package/dist/command/cli/context-command.d.ts +1 -0
  37. package/dist/command/cli/context-command.js +33 -0
  38. package/dist/command/cli/daemon-start-options.d.ts +3 -0
  39. package/dist/command/cli/daemon-start-options.js +24 -3
  40. package/dist/command/cli/doctor/doctor-coverage-line.d.ts +2 -0
  41. package/dist/command/cli/doctor/doctor-coverage-line.js +47 -0
  42. package/dist/command/cli/doctor/doctor-project-line.d.ts +0 -12
  43. package/dist/command/cli/doctor/doctor-project-line.js +5 -4
  44. package/dist/command/cli/doctor/doctor-rows.d.ts +6 -0
  45. package/dist/command/cli/doctor/doctor-rows.js +6 -0
  46. package/dist/command/cli/doctor/doctor-signing-line.d.ts +38 -0
  47. package/dist/command/cli/doctor/doctor-signing-line.js +114 -0
  48. package/dist/command/cli/known-regression.d.ts +13 -0
  49. package/dist/command/cli/known-regression.js +34 -0
  50. package/dist/command/cli/launch/sdk-version-match.d.ts +12 -0
  51. package/dist/command/cli/launch/sdk-version-match.js +25 -0
  52. package/dist/command/cli/lifecycle/daemon-lifecycle.js +11 -2
  53. package/dist/command/cli/mcp-command.d.ts +6 -0
  54. package/dist/command/cli/mcp-command.js +13 -5
  55. package/dist/command/cli/ports/port-holder.js +1 -1
  56. package/dist/command/cli/project-name.d.ts +29 -1
  57. package/dist/command/cli/project-name.js +107 -2
  58. package/dist/command/cli/setup-mcp-cli.d.ts +17 -1
  59. package/dist/command/cli/setup-mcp-cli.js +29 -8
  60. package/dist/command/cli/status/status-lines.d.ts +1 -0
  61. package/dist/command/cli/status/status-lines.js +47 -2
  62. package/dist/command/cli/status/workspace-links.d.ts +10 -0
  63. package/dist/command/cli/status/workspace-links.js +29 -0
  64. package/dist/command/cli/try-command.d.ts +96 -0
  65. package/dist/command/cli/try-command.js +362 -0
  66. package/dist/command/cli/tutorial.d.ts +0 -100
  67. package/dist/command/cli/tutorial.js +0 -178
  68. package/dist/command/cli/verify-results.d.ts +19 -0
  69. package/dist/command/cli/verify-results.js +29 -0
  70. package/dist/command/cli-onboarding.d.ts +0 -11
  71. package/dist/command/cli-onboarding.js +0 -15
  72. package/dist/command/cli.d.ts +14 -0
  73. package/dist/command/cli.js +103 -83
  74. package/dist/command/daemon/daemon-resilience.js +4 -4
  75. package/dist/command/daemon/daemon-resolve.d.ts +9 -1
  76. package/dist/command/daemon/daemon-resolve.js +49 -10
  77. package/dist/command/setup/bringup/bridge-port.d.ts +3 -1
  78. package/dist/command/setup/bringup/bridge-port.js +13 -3
  79. package/dist/command/setup/bringup/dev-server-log-cap.js +2 -0
  80. package/dist/command/setup/bringup/ensure-daemon.js +9 -2
  81. package/dist/command/setup/init/agent-restart.d.ts +23 -0
  82. package/dist/command/setup/init/agent-restart.js +28 -0
  83. package/dist/command/setup/init/init-port.d.ts +10 -1
  84. package/dist/command/setup/init/init-port.js +22 -2
  85. package/dist/command/setup/init/init-runtime.d.ts +12 -2
  86. package/dist/command/setup/init/init-runtime.js +96 -37
  87. package/dist/command/setup/node-effects.d.ts +2 -0
  88. package/dist/command/setup/node-effects.js +36 -8
  89. package/dist/command/setup/probe/existing-app.d.ts +12 -0
  90. package/dist/command/setup/probe/existing-app.js +14 -0
  91. package/dist/command/setup/remaining-steps.js +1 -1
  92. package/dist/command/setup/run-setup.d.ts +29 -9
  93. package/dist/command/setup/run-setup.js +34 -28
  94. package/dist/command/setup/setup-command.d.ts +23 -0
  95. package/dist/command/setup/setup-command.js +57 -4
  96. package/dist/command/setup/setup-mcp.js +25 -2
  97. package/dist/command/setup/tauri-signing.d.ts +19 -0
  98. package/dist/command/setup/tauri-signing.js +79 -0
  99. package/dist/command/setup/terminal/listeners.d.ts +10 -1
  100. package/dist/command/setup/terminal/listeners.js +37 -6
  101. package/dist/command/update/update-nudge.d.ts +9 -0
  102. package/dist/command/update/update-nudge.js +1 -1
  103. package/dist/command/update/updater.js +11 -15
  104. package/dist/command/version/version-nudge.js +3 -0
  105. package/dist/command/version/version-skew.d.ts +20 -1
  106. package/dist/command/version/version-skew.js +38 -2
  107. package/dist/features/crawl/crawl.js +14 -1
  108. package/dist/features/exhaust/ledger.d.ts +14 -14
  109. package/dist/features/harness/drive-report.d.ts +39 -5
  110. package/dist/features/harness/drive-report.js +271 -54
  111. package/dist/features/harness/drive-runs.d.ts +120 -0
  112. package/dist/features/harness/drive-runs.js +289 -0
  113. package/dist/features/harness/drivers.d.ts +1 -1
  114. package/dist/features/harness/drivers.js +1 -1
  115. package/dist/features/harness/explore/console-pages.d.ts +15 -0
  116. package/dist/features/harness/explore/console-pages.js +146 -0
  117. package/dist/features/harness/explore/disparity.d.ts +19 -0
  118. package/dist/features/harness/explore/disparity.js +71 -0
  119. package/dist/features/harness/explore/fake-app.d.ts +16 -0
  120. package/dist/features/harness/explore/fake-app.js +49 -0
  121. package/dist/features/harness/explore/goal-search.d.ts +26 -0
  122. package/dist/features/harness/explore/goal-search.js +144 -0
  123. package/dist/features/harness/explore/page-tree.d.ts +49 -0
  124. package/dist/features/harness/explore/page-tree.js +148 -0
  125. package/dist/features/harness/explore/site-map.d.ts +34 -0
  126. package/dist/features/harness/explore/site-map.js +150 -0
  127. package/dist/features/harness/goals.d.ts +68 -7
  128. package/dist/features/harness/goals.js +289 -36
  129. package/dist/features/harness/harness.d.ts +30 -1
  130. package/dist/features/harness/harness.js +141 -7
  131. package/dist/features/harness/platform/local-apps.d.ts +36 -3
  132. package/dist/features/harness/platform/local-apps.js +81 -5
  133. package/dist/features/harness/platform/persona-credentials.d.ts +20 -0
  134. package/dist/features/harness/platform/persona-credentials.js +69 -0
  135. package/dist/features/harness/platform/platform-drives.d.ts +61 -0
  136. package/dist/features/harness/platform/platform-drives.js +114 -0
  137. package/dist/features/harness/platform/refusal.d.ts +52 -0
  138. package/dist/features/harness/platform/refusal.js +71 -0
  139. package/dist/features/harness/platform/remote-drive.d.ts +8 -2
  140. package/dist/features/harness/platform/remote-drive.js +22 -9
  141. package/dist/features/harness/platform/script.d.ts +57 -11
  142. package/dist/features/harness/platform/script.js +144 -12
  143. package/dist/features/harness/platform/server-driver.d.ts +17 -4
  144. package/dist/features/harness/platform/server-driver.js +29 -26
  145. package/dist/features/harness/platform-config.d.ts +38 -5
  146. package/dist/features/harness/platform-config.js +93 -8
  147. package/dist/features/harness/route-guess.d.ts +13 -0
  148. package/dist/features/harness/route-guess.js +49 -0
  149. package/dist/features/harness/script/script-run.d.ts +30 -1
  150. package/dist/features/harness/script/script-run.js +355 -48
  151. package/dist/features/license/license.d.ts +2 -2
  152. package/dist/features/visual/step-shots.d.ts +31 -0
  153. package/dist/features/visual/step-shots.js +231 -0
  154. package/dist/features/visual/visual-tools.js +22 -14
  155. package/dist/hooks/client-directory.d.ts +18 -0
  156. package/dist/hooks/client-directory.js +57 -0
  157. package/dist/hooks/coding-agents.d.ts +34 -0
  158. package/dist/hooks/coding-agents.js +117 -0
  159. package/dist/hooks/driven-by.d.ts +21 -2
  160. package/dist/hooks/driven-by.js +42 -2
  161. package/dist/hooks/hook-bus.d.ts +4 -0
  162. package/dist/hooks/hook-bus.js +18 -0
  163. package/dist/index.d.ts +1 -0
  164. package/dist/index.js +26 -183
  165. package/dist/judgement/capsule/capsule-store.d.ts +2 -2
  166. package/dist/judgement/capsule/causal-summary.d.ts +15 -1
  167. package/dist/judgement/capsule/causal-summary.js +33 -0
  168. package/dist/judgement/runs/artifact/build-verification-run.d.ts +1 -1
  169. package/dist/judgement/runs/artifact/build-verification-run.js +20 -4
  170. package/dist/judgement/runs/artifact/gap-summary.d.ts +0 -18
  171. package/dist/judgement/runs/artifact/gap-summary.js +12 -3
  172. package/dist/judgement/runs/artifact/replay-mapping.js +25 -9
  173. package/dist/judgement/runs/artifact/run-store.d.ts +13 -1
  174. package/dist/judgement/runs/artifact/run-store.js +175 -8
  175. package/dist/judgement/runs/context-tools.js +59 -1
  176. package/dist/judgement/runs/default-run-id.d.ts +3 -0
  177. package/dist/judgement/runs/default-run-id.js +16 -0
  178. package/dist/judgement/runs/drive-run.d.ts +53 -5
  179. package/dist/judgement/runs/drive-run.js +234 -20
  180. package/dist/judgement/runs/resume.d.ts +44 -0
  181. package/dist/judgement/runs/resume.js +137 -0
  182. package/dist/judgement/runs/reticle-runner.js +5 -2
  183. package/dist/judgement/runs/runner-port.d.ts +5 -1
  184. package/dist/judgement/runs/runner-port.js +8 -2
  185. package/dist/judgement/runs/verification-sync.d.ts +2 -0
  186. package/dist/judgement/runs/verification-sync.js +36 -15
  187. package/dist/language/flows/decision.js +2 -1
  188. package/dist/language/flows/fields/flow-secret-field.d.ts +12 -0
  189. package/dist/language/flows/fields/flow-secret-field.js +25 -2
  190. package/dist/language/flows/flow-anchor.d.ts +7 -2
  191. package/dist/language/flows/flow-anchor.js +10 -3
  192. package/dist/language/flows/flow-intent.d.ts +1 -0
  193. package/dist/language/flows/flow-journey.d.ts +6 -0
  194. package/dist/language/flows/flow-journey.js +20 -0
  195. package/dist/language/flows/flow-learning.d.ts +4 -0
  196. package/dist/language/flows/flow-learning.js +37 -3
  197. package/dist/language/flows/flow-replay-run.js +16 -18
  198. package/dist/language/flows/flow-replay-types.d.ts +1 -1
  199. package/dist/language/flows/flow-replay.d.ts +18 -1
  200. package/dist/language/flows/flow-replay.js +22 -7
  201. package/dist/language/flows/flow-result.d.ts +22 -1
  202. package/dist/language/flows/flow-result.js +47 -2
  203. package/dist/language/flows/flow-step-runners.d.ts +2 -0
  204. package/dist/language/flows/flow-step-runners.js +12 -6
  205. package/dist/language/flows/flow-store-for-session.js +5 -1
  206. package/dist/language/flows/flow-tools.d.ts +10 -0
  207. package/dist/language/flows/flow-tools.js +61 -62
  208. package/dist/language/flows/flows.d.ts +16 -1
  209. package/dist/language/flows/flows.js +69 -10
  210. package/dist/language/flows/narrow-write.d.ts +7 -0
  211. package/dist/language/flows/narrow-write.js +36 -1
  212. package/dist/language/flows/recording/recording-backtrack.js +14 -1
  213. package/dist/language/flows/recording/tape/recordings.d.ts +29 -1
  214. package/dist/language/flows/recording/tape/recordings.js +60 -10
  215. package/dist/language/flows/replay.js +29 -6
  216. package/dist/language/flows/single-replay.d.ts +7 -0
  217. package/dist/language/flows/single-replay.js +26 -0
  218. package/dist/language/flows/verify-change-tools.d.ts +1 -0
  219. package/dist/language/flows/verify-change-tools.js +200 -193
  220. package/dist/memory/cloud/cloud-config.d.ts +10 -1
  221. package/dist/memory/cloud/cloud-config.js +19 -5
  222. package/dist/memory/cloud/cloud-sync.js +3 -3
  223. package/dist/memory/cloud/harness-config.d.ts +49 -0
  224. package/dist/memory/cloud/harness-config.js +16 -20
  225. package/dist/memory/cloud/memory-scope.d.ts +17 -0
  226. package/dist/memory/cloud/memory-scope.js +22 -0
  227. package/dist/memory/cloud/sync-cycle.d.ts +32 -4
  228. package/dist/memory/cloud/sync-cycle.js +113 -12
  229. package/dist/memory/cloud/sync-daemon.d.ts +11 -0
  230. package/dist/memory/cloud/sync-daemon.js +114 -4
  231. package/dist/memory/cloud/sync-disk.js +26 -2
  232. package/dist/memory/cloud/sync-screenshots.d.ts +32 -0
  233. package/dist/memory/cloud/sync-screenshots.js +105 -0
  234. package/dist/memory/cloud/unsynced-roots.d.ts +47 -0
  235. package/dist/memory/cloud/unsynced-roots.js +166 -0
  236. package/dist/memory/impact/account-watch.d.ts +16 -1
  237. package/dist/memory/impact/account-watch.js +26 -1
  238. package/dist/memory/impact/impact-store.d.ts +1 -4
  239. package/dist/memory/impact/impact-store.js +32 -42
  240. package/dist/memory/intent/inline-intent.d.ts +1 -0
  241. package/dist/memory/intent/intent-shard-store.js +16 -4
  242. package/dist/memory/intent/intent-shard.d.ts +12 -0
  243. package/dist/memory/intent/intent-store.d.ts +1 -0
  244. package/dist/memory/intent/intent-tools.js +42 -2
  245. package/dist/memory/intent/open-intents.d.ts +6 -0
  246. package/dist/memory/intent/open-intents.js +10 -1
  247. package/dist/memory/intent/settled-words.d.ts +24 -0
  248. package/dist/memory/intent/settled-words.js +130 -0
  249. package/dist/memory/journal/drive-flow.d.ts +16 -0
  250. package/dist/memory/journal/drive-flow.js +34 -2
  251. package/dist/memory/journal/drive-run-flush.d.ts +2 -0
  252. package/dist/memory/journal/drive-run-flush.js +20 -5
  253. package/dist/memory/journal/journal-recorder.js +46 -3
  254. package/dist/memory/journal/on-disk/retention.d.ts +5 -0
  255. package/dist/memory/journal/on-disk/retention.js +27 -2
  256. package/dist/memory/journal/on-disk/workspace-tiers.js +2 -0
  257. package/dist/memory/journal/session-end.d.ts +15 -1
  258. package/dist/memory/journal/session-end.js +38 -7
  259. package/dist/memory/project/artifact-root-resolver.js +33 -5
  260. package/dist/memory/project/artifact-root.d.ts +4 -3
  261. package/dist/memory/project/artifact-root.js +2 -2
  262. package/dist/memory/project/dir/reticle-dir.d.ts +14 -0
  263. package/dist/memory/project/dir/reticle-dir.js +23 -2
  264. package/dist/memory/project/link-directory.d.ts +15 -0
  265. package/dist/memory/project/link-directory.js +44 -0
  266. package/dist/memory/project/root-claims.d.ts +6 -0
  267. package/dist/memory/project/root-claims.js +39 -0
  268. package/dist/memory/project/session-root.d.ts +2 -0
  269. package/dist/memory/project/session-root.js +9 -1
  270. package/dist/memory/project/sync-status.d.ts +46 -0
  271. package/dist/memory/project/sync-status.js +135 -1
  272. package/dist/memory/recall/memory-tools.js +46 -3
  273. package/dist/memory/recall/platform-memory.d.ts +34 -0
  274. package/dist/memory/recall/platform-memory.js +139 -0
  275. package/dist/memory/recall/project-memory.js +36 -8
  276. package/dist/portal/bridge/bridge.d.ts +9 -5
  277. package/dist/portal/bridge/bridge.js +66 -23
  278. package/dist/portal/bridge/harness-request.d.ts +23 -0
  279. package/dist/portal/bridge/harness-request.js +27 -0
  280. package/dist/portal/bridge/wrong-path.d.ts +17 -0
  281. package/dist/portal/bridge/wrong-path.js +35 -0
  282. package/dist/portal/input/network-mock-tools.js +11 -2
  283. package/dist/portal/input/real-input.d.ts +2 -0
  284. package/dist/portal/input/real-input.js +13 -10
  285. package/dist/portal/pool/browser-pool.d.ts +48 -0
  286. package/dist/portal/pool/browser-pool.js +120 -2
  287. package/dist/portal/pool/context-permissions.d.ts +33 -0
  288. package/dist/portal/pool/context-permissions.js +33 -0
  289. package/dist/portal/pool/lease-marker-stamp.d.ts +23 -0
  290. package/dist/portal/pool/lease-marker-stamp.js +49 -0
  291. package/dist/portal/pool/playwright-launcher.d.ts +27 -5
  292. package/dist/portal/pool/playwright-launcher.js +59 -14
  293. package/dist/portal/pool/pool-contract.d.ts +21 -1
  294. package/dist/portal/session/agent-link.d.ts +8 -0
  295. package/dist/portal/session/agent-link.js +17 -0
  296. package/dist/portal/session/command-payload.js +6 -1
  297. package/dist/portal/session/daemon-answers.d.ts +3 -3
  298. package/dist/portal/session/daemon-answers.js +4 -2
  299. package/dist/portal/session/dev-server/dev-command.d.ts +9 -0
  300. package/dist/portal/session/dev-server/dev-command.js +27 -1
  301. package/dist/portal/session/facts/observed-state.d.ts +16 -18
  302. package/dist/portal/session/facts/observed-state.js +46 -3
  303. package/dist/portal/session/human/live-control.d.ts +9 -2
  304. package/dist/portal/session/human/live-control.js +12 -3
  305. package/dist/portal/session/live-control-tools.js +82 -13
  306. package/dist/portal/session/narration-args.d.ts +5 -0
  307. package/dist/portal/session/narration-args.js +17 -0
  308. package/dist/portal/session/no-session-diagnosis.d.ts +1 -1
  309. package/dist/portal/session/no-session-diagnosis.js +31 -9
  310. package/dist/portal/session/no-session-next-action.d.ts +20 -0
  311. package/dist/portal/session/no-session-next-action.js +134 -61
  312. package/dist/portal/session/no-session-watch.js +179 -22
  313. package/dist/portal/session/presence/session-lease.js +1 -1
  314. package/dist/portal/session/recorded-gaps.d.ts +97 -0
  315. package/dist/portal/session/recorded-gaps.js +251 -0
  316. package/dist/portal/session/session-health.d.ts +11 -0
  317. package/dist/portal/session/session-health.js +3 -1
  318. package/dist/portal/session/session-info.js +2 -1
  319. package/dist/portal/session/session-manager.d.ts +18 -0
  320. package/dist/portal/session/session-manager.js +54 -3
  321. package/dist/portal/session/session-successor.d.ts +8 -0
  322. package/dist/portal/session/session-successor.js +1 -1
  323. package/dist/portal/session/session.d.ts +15 -28
  324. package/dist/portal/session/session.js +60 -61
  325. package/dist/portal/session/timing/command-timeout.js +25 -1
  326. package/dist/portal/session/timing/wall-clock.d.ts +1 -1
  327. package/dist/portal/session/timing/wall-clock.js +1 -1
  328. package/dist/portal/session/zero-install-first-call.d.ts +31 -0
  329. package/dist/portal/session/zero-install-first-call.js +88 -0
  330. package/dist/start-options.d.ts +7 -1
  331. package/dist/start-wiring.d.ts +63 -0
  332. package/dist/start-wiring.js +169 -0
  333. package/dist/status-payload.d.ts +13 -1
  334. package/dist/status-payload.js +13 -1
  335. package/dist/surface/http-server.js +7 -1
  336. package/dist/surface/mcp/attach-memory.d.ts +1 -0
  337. package/dist/surface/mcp/attach-memory.js +12 -1
  338. package/dist/surface/mcp/mcp-proxy.d.ts +4 -0
  339. package/dist/surface/mcp/mcp-proxy.js +29 -8
  340. package/dist/surface/mcp/mcp.d.ts +23 -0
  341. package/dist/surface/mcp/mcp.js +34 -4
  342. package/dist/surface/mcp/proxy/drive-calls.d.ts +10 -0
  343. package/dist/surface/mcp/proxy/drive-calls.js +27 -0
  344. package/dist/surface/mcp/proxy/mcp-post-transport.js +5 -0
  345. package/dist/surface/mcp/server-instructions.js +22 -14
  346. package/dist/surface/mcp/shared-params.js +1 -1
  347. package/dist/surface/mcp/surface-vocabulary.d.ts +2 -0
  348. package/dist/surface/mcp/surface-vocabulary.js +1 -0
  349. package/dist/surface/tools/account-tool.d.ts +22 -0
  350. package/dist/surface/tools/account-tool.js +157 -0
  351. package/dist/surface/tools/act/act-danger.d.ts +11 -0
  352. package/dist/surface/tools/act/act-danger.js +38 -1
  353. package/dist/surface/tools/act/act-observation.d.ts +33 -0
  354. package/dist/surface/tools/act/act-observation.js +57 -0
  355. package/dist/surface/tools/act/act-preflight.js +25 -2
  356. package/dist/surface/tools/act/act-target.js +8 -3
  357. package/dist/surface/tools/act/resolve-target.d.ts +1 -1
  358. package/dist/surface/tools/act/resolve-target.js +18 -3
  359. package/dist/surface/tools/act/screen-request.d.ts +34 -0
  360. package/dist/surface/tools/act/screen-request.js +113 -0
  361. package/dist/surface/tools/act/sequence-grade.d.ts +8 -0
  362. package/dist/surface/tools/act/sequence-grade.js +13 -2
  363. package/dist/surface/tools/act-sequence-tool.js +136 -27
  364. package/dist/surface/tools/act-tools.d.ts +0 -9
  365. package/dist/surface/tools/act-tools.js +48 -34
  366. package/dist/surface/tools/agent/app-notes-learning.d.ts +7 -0
  367. package/dist/surface/tools/agent/app-notes-learning.js +80 -0
  368. package/dist/surface/tools/agent/app-notes.d.ts +4 -0
  369. package/dist/surface/tools/agent/app-notes.js +48 -0
  370. package/dist/surface/tools/agent/build-staleness.d.ts +7 -0
  371. package/dist/surface/tools/agent/build-staleness.js +62 -0
  372. package/dist/surface/tools/agent/compact-envelope.d.ts +80 -0
  373. package/dist/surface/tools/agent/compact-envelope.js +374 -0
  374. package/dist/surface/tools/agent/for-the-agent.d.ts +12 -0
  375. package/dist/surface/tools/agent/for-the-agent.js +44 -0
  376. package/dist/surface/tools/agent/notices.d.ts +50 -0
  377. package/dist/surface/tools/agent/notices.js +152 -0
  378. package/dist/surface/tools/agent/reteach.d.ts +11 -0
  379. package/dist/surface/tools/agent/reteach.js +50 -0
  380. package/dist/surface/tools/args/alias-args.d.ts +5 -0
  381. package/dist/surface/tools/args/alias-args.js +10 -0
  382. package/dist/surface/tools/args/confirm-dangerous-arg.d.ts +6 -0
  383. package/dist/surface/tools/args/confirm-dangerous-arg.js +6 -0
  384. package/dist/surface/tools/assert/assert-source.d.ts +15 -0
  385. package/dist/surface/tools/assert/assert-source.js +40 -0
  386. package/dist/surface/tools/assert/assert-verdict.js +25 -15
  387. package/dist/surface/tools/browser-tools.js +43 -1
  388. package/dist/surface/tools/chat-drives.d.ts +75 -0
  389. package/dist/surface/tools/chat-drives.js +265 -21
  390. package/dist/surface/tools/coverage-tools.d.ts +2 -10
  391. package/dist/surface/tools/coverage-tools.js +2 -53
  392. package/dist/surface/tools/dynamic-tools.js +29 -1
  393. package/dist/surface/tools/empty-session-list.d.ts +10 -0
  394. package/dist/surface/tools/empty-session-list.js +45 -5
  395. package/dist/surface/tools/error-recovery.d.ts +8 -0
  396. package/dist/surface/tools/error-recovery.js +69 -5
  397. package/dist/surface/tools/explore-tools.d.ts +23 -2
  398. package/dist/surface/tools/explore-tools.js +377 -74
  399. package/dist/surface/tools/feedback-tools.d.ts +2 -3
  400. package/dist/surface/tools/feedback-tools.js +4 -3
  401. package/dist/surface/tools/gap-novelty.d.ts +3 -1
  402. package/dist/surface/tools/gap-novelty.js +8 -1
  403. package/dist/surface/tools/harness-explore.d.ts +52 -73
  404. package/dist/surface/tools/harness-explore.js +177 -146
  405. package/dist/surface/tools/harness-gate.d.ts +88 -0
  406. package/dist/surface/tools/harness-gate.js +235 -0
  407. package/dist/surface/tools/harness-hunt.d.ts +43 -0
  408. package/dist/surface/tools/harness-hunt.js +215 -0
  409. package/dist/surface/tools/harness-plan.d.ts +5 -0
  410. package/dist/surface/tools/harness-plan.js +7 -0
  411. package/dist/surface/tools/harness-script.d.ts +22 -0
  412. package/dist/surface/tools/harness-script.js +256 -38
  413. package/dist/surface/tools/harness-tab-guard.d.ts +5 -0
  414. package/dist/surface/tools/harness-tab-guard.js +27 -0
  415. package/dist/surface/tools/harness-toolset.d.ts +8 -0
  416. package/dist/surface/tools/harness-toolset.js +27 -4
  417. package/dist/surface/tools/human-notes.d.ts +16 -0
  418. package/dist/surface/tools/human-notes.js +27 -0
  419. package/dist/surface/tools/invoke-tool.d.ts +5 -2
  420. package/dist/surface/tools/invoke-tool.js +220 -93
  421. package/dist/surface/tools/lease-hint.js +26 -5
  422. package/dist/surface/tools/lease-permissions.d.ts +33 -0
  423. package/dist/surface/tools/lease-permissions.js +73 -0
  424. package/dist/surface/tools/lease-readiness.d.ts +38 -0
  425. package/dist/surface/tools/lease-readiness.js +57 -0
  426. package/dist/surface/tools/lease-seed.d.ts +20 -0
  427. package/dist/surface/tools/lease-seed.js +45 -1
  428. package/dist/surface/tools/lease-tools.d.ts +10 -1
  429. package/dist/surface/tools/lease-tools.js +119 -69
  430. package/dist/surface/tools/live-call-text.js +2 -2
  431. package/dist/surface/tools/merge-tools.js +42 -2
  432. package/dist/surface/tools/merged-name-redirect.js +4 -0
  433. package/dist/surface/tools/next-step.d.ts +11 -1
  434. package/dist/surface/tools/next-step.js +73 -18
  435. package/dist/surface/tools/observe-tools.js +43 -5
  436. package/dist/surface/tools/platform-moment.d.ts +60 -0
  437. package/dist/surface/tools/platform-moment.js +132 -4
  438. package/dist/surface/tools/read-tools.js +65 -14
  439. package/dist/surface/tools/reticle-result.d.ts +120 -0
  440. package/dist/surface/tools/reticle-result.js +364 -0
  441. package/dist/surface/tools/tool-kit.d.ts +68 -2
  442. package/dist/surface/tools/tool-kit.js +73 -1
  443. package/dist/surface/tools/tools.js +25 -13
  444. package/dist/telemetry/account-envelope.d.ts +7 -0
  445. package/dist/telemetry/account-envelope.js +61 -0
  446. package/dist/telemetry/agent-client.d.ts +15 -0
  447. package/dist/telemetry/agent-client.js +57 -0
  448. package/dist/telemetry/app-instrumented.js +9 -2
  449. package/dist/telemetry/argument-shape.d.ts +7 -0
  450. package/dist/telemetry/argument-shape.js +86 -1
  451. package/dist/telemetry/automation-hint.d.ts +2 -3
  452. package/dist/telemetry/automation-hint.js +33 -8
  453. package/dist/telemetry/cli-telemetry.js +7 -2
  454. package/dist/telemetry/daemon-telemetry.js +3 -0
  455. package/dist/telemetry/error-fingerprint.d.ts +10 -1
  456. package/dist/telemetry/error-fingerprint.js +61 -5
  457. package/dist/telemetry/feedback-cli.d.ts +0 -13
  458. package/dist/telemetry/feedback-cli.js +0 -77
  459. package/dist/telemetry/feedback.d.ts +13 -0
  460. package/dist/telemetry/feedback.js +183 -17
  461. package/dist/telemetry/harness-run.d.ts +18 -0
  462. package/dist/telemetry/harness-run.js +80 -0
  463. package/dist/telemetry/hud-events.d.ts +16 -0
  464. package/dist/telemetry/hud-events.js +27 -0
  465. package/dist/telemetry/hud-metrics.d.ts +27 -6
  466. package/dist/telemetry/hud-metrics.js +99 -25
  467. package/dist/telemetry/init-telemetry.js +2 -2
  468. package/dist/telemetry/install-source.d.ts +13 -25
  469. package/dist/telemetry/install-source.js +44 -3
  470. package/dist/telemetry/mcp-connection.d.ts +0 -14
  471. package/dist/telemetry/mcp-connection.js +5 -1
  472. package/dist/telemetry/onboarding-firsts.d.ts +32 -0
  473. package/dist/telemetry/onboarding-firsts.js +81 -6
  474. package/dist/telemetry/platform-linked.d.ts +5 -0
  475. package/dist/telemetry/platform-linked.js +32 -0
  476. package/dist/telemetry/project-profile.js +5 -3
  477. package/dist/telemetry/proxy-telemetry.js +4 -1
  478. package/dist/telemetry/session-metrics.d.ts +15 -4
  479. package/dist/telemetry/session-metrics.js +88 -52
  480. package/dist/telemetry/telemetry.d.ts +26 -2
  481. package/dist/telemetry/telemetry.js +102 -21
  482. package/dist/telemetry/verification-of.d.ts +3 -1
  483. package/dist/telemetry/verification-of.js +12 -4
  484. package/dist/wire-journal.d.ts +2 -2
  485. package/dist/wire-journal.js +16 -5
  486. package/docs/README.md +2 -2
  487. package/docs/actions.mdx +6 -3
  488. package/docs/adapters.md +1 -1
  489. package/docs/agent-cheatsheet.md +24 -27
  490. package/docs/architecture.md +5 -5
  491. package/docs/autodrive.md +13 -12
  492. package/docs/benchmarks.md +5 -5
  493. package/docs/best-practices.mdx +27 -9
  494. package/docs/capabilities.mdx +14 -16
  495. package/docs/cli/affected.mdx +1 -1
  496. package/docs/cli/cloud.mdx +54 -75
  497. package/docs/cli/doctor.mdx +8 -3
  498. package/docs/cli/drive.mdx +12 -12
  499. package/docs/cli/feedback.mdx +3 -3
  500. package/docs/cli/gate.mdx +7 -2
  501. package/docs/cli/help.mdx +50 -55
  502. package/docs/cli/init.mdx +32 -15
  503. package/docs/cli/license.mdx +12 -8
  504. package/docs/cli/mcp.mdx +4 -4
  505. package/docs/cli/open.mdx +3 -3
  506. package/docs/cli/report.mdx +4 -2
  507. package/docs/cli/restart.mdx +2 -2
  508. package/docs/cli/rollback.mdx +14 -8
  509. package/docs/cli/serve.mdx +4 -4
  510. package/docs/cli/setup.mdx +23 -0
  511. package/docs/cli/status.mdx +13 -4
  512. package/docs/cli/stop.mdx +67 -8
  513. package/docs/cli/try.mdx +62 -0
  514. package/docs/cli/update.mdx +4 -3
  515. package/docs/cli/verify.mdx +17 -6
  516. package/docs/cli/version.mdx +3 -2
  517. package/docs/cli.mdx +110 -41
  518. package/docs/debugging.md +3 -3
  519. package/docs/deploy-checks.md +1 -1
  520. package/docs/desktop.mdx +5 -3
  521. package/docs/enterprise.md +11 -10
  522. package/docs/environment.mdx +5 -2
  523. package/docs/faq.mdx +3 -3
  524. package/docs/first-drive.md +5 -4
  525. package/docs/flows.md +35 -27
  526. package/docs/for-agents.md +4 -5
  527. package/docs/frameworks.mdx +32 -21
  528. package/docs/getting-started.md +66 -64
  529. package/docs/human-control.md +15 -14
  530. package/docs/index.mdx +15 -43
  531. package/docs/install-agentic.mdx +22 -37
  532. package/docs/install-manual.mdx +22 -12
  533. package/docs/instrumentation.mdx +5 -3
  534. package/docs/integration-patterns.md +7 -7
  535. package/docs/license-activation.mdx +1 -1
  536. package/docs/multi-agent-testing.md +24 -6
  537. package/docs/packages/babel-plugin.mdx +4 -2
  538. package/docs/packages/browser.mdx +16 -10
  539. package/docs/packages/core.mdx +12 -5
  540. package/docs/packages/electron.mdx +3 -2
  541. package/docs/packages/engine.mdx +1 -1
  542. package/docs/packages/eslint-plugin.mdx +18 -12
  543. package/docs/packages/next.mdx +8 -7
  544. package/docs/packages/open-verification.mdx +1 -1
  545. package/docs/packages/server.mdx +5 -5
  546. package/docs/packages/tauri.mdx +8 -8
  547. package/docs/packages/test.mdx +4 -10
  548. package/docs/packages/vite-plugin.mdx +11 -4
  549. package/docs/packages.mdx +11 -15
  550. package/docs/platform-integration.md +6 -5
  551. package/docs/predicates.mdx +40 -24
  552. package/docs/quickstart.mdx +58 -29
  553. package/docs/required-checks.md +8 -7
  554. package/docs/reticle-manual.md +15 -5
  555. package/docs/skill-file.mdx +17 -79
  556. package/docs/skill-setup.md +9 -9
  557. package/docs/skill.md +200 -0
  558. package/docs/system-map.md +7 -7
  559. package/docs/telemetry.md +22 -24
  560. package/docs/testing.md +4 -4
  561. package/docs/token-efficiency.md +12 -12
  562. package/docs/tools/act-and-wait.mdx +9 -2
  563. package/docs/tools/act-sequence.mdx +6 -7
  564. package/docs/tools/act.mdx +2 -2
  565. package/docs/tools/assert.mdx +18 -17
  566. package/docs/tools/console.mdx +9 -9
  567. package/docs/tools/inspect.mdx +16 -7
  568. package/docs/tools/look.mdx +24 -0
  569. package/docs/tools/navigate.mdx +5 -3
  570. package/docs/tools/network.mdx +2 -2
  571. package/docs/tools/observe.mdx +6 -6
  572. package/docs/tools/overview.mdx +34 -33
  573. package/docs/tools/query.mdx +5 -5
  574. package/docs/tools/session-and-feedback.mdx +13 -4
  575. package/docs/tools/sessions.mdx +8 -8
  576. package/docs/tools/snapshot.mdx +6 -5
  577. package/docs/tools/state.mdx +9 -9
  578. package/docs/tools/tools-and-run.mdx +26 -24
  579. package/docs/tools/verify.mdx +28 -0
  580. package/docs/tools/wait-for.mdx +8 -4
  581. package/docs/troubleshooting.mdx +21 -6
  582. package/docs/usage.md +51 -45
  583. package/docs/use-cases.mdx +3 -3
  584. package/docs/vs-chrome-devtools-mcp.mdx +9 -6
  585. package/docs/vs-playwright-mcp.mdx +3 -1
  586. package/docs/what-is-recorded.md +7 -5
  587. package/docs/what-is-reticle.mdx +2 -2
  588. package/docs/why-reticle.mdx +1 -1
  589. package/package.json +6 -5
  590. package/dist/command/cli/demo/demo-page.d.ts +0 -78
  591. package/dist/command/cli/demo/demo-page.js +0 -249
  592. package/dist/command/cli/demo/demo-run.d.ts +0 -38
  593. package/dist/command/cli/demo/demo-run.js +0 -138
  594. package/dist/judgement/hunt/hunt-report.d.ts +0 -49
  595. package/dist/judgement/hunt/hunt-report.js +0 -84
  596. package/dist/language/flows/buddy-status.d.ts +0 -25
  597. package/dist/language/flows/buddy-status.js +0 -32
  598. package/dist/language/flows/change/watch-batcher.d.ts +0 -18
  599. package/dist/language/flows/change/watch-batcher.js +0 -19
  600. package/dist/memory/cloud/harness-offer.d.ts +0 -44
  601. package/dist/memory/cloud/harness-offer.js +0 -145
  602. package/dist/surface/tools/feature-capture.d.ts +0 -151
  603. package/dist/surface/tools/feature-capture.js +0 -235
  604. package/dist/surface/tools/tool-hit-rate.d.ts +0 -87
  605. package/dist/surface/tools/tool-hit-rate.js +0 -90
  606. package/dist/telemetry/harness-drive.d.ts +0 -4
  607. package/dist/telemetry/harness-drive.js +0 -35
  608. package/dist/telemetry/identify.d.ts +0 -22
  609. package/dist/telemetry/identify.js +0 -73
  610. package/docs/cli/capsules.mdx +0 -70
  611. package/docs/cli/hunt.mdx +0 -73
  612. package/docs/cli/identify.mdx +0 -76
  613. package/docs/cli/kill.mdx +0 -91
  614. package/docs/cli/watch.mdx +0 -62
@@ -21,12 +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';
24
25
  import { syncRequest } from './cloud-sync.js';
25
26
  import { log } from '../../log.js';
27
+ import { homedir } from 'node:os';
26
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';
27
34
  import { describeSync, runSyncCycle } from './sync-cycle.js';
28
35
  import { diskSink, diskSource, readCloudState } from './sync-disk.js';
29
- 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';
30
39
  /** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
31
40
  const DAEMON_SYNC_INTERVAL_MS = 60_000;
32
41
  /**
@@ -100,18 +109,62 @@ export function startSyncDaemon(deps) {
100
109
  * so too. Once per transition, never per tick — a line every minute is a line people stop reading.
101
110
  */
102
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
+ };
103
146
  /** One root's bundle. The state, source and sink are already per-root; only the caller was not. */
104
147
  const pushRoot = async (root, cloud) => {
105
148
  if (null === cloud.config)
106
149
  return undefined;
107
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);
108
155
  return runSyncCycle({
109
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 } : {}),
110
160
  source: {
111
161
  runs: () => (cloud.policy.runs ? full.runs() : []),
112
162
  flows: () => (cloud.policy.flows ? full.flows() : []),
113
163
  capsules: () => (cloud.policy.capsules ? full.capsules() : []),
114
164
  derived: (kind) => (cloud.policy.memory ? full.derived(kind) : undefined),
165
+ ...(cloud.policy.screenshots && full.screenshot !== undefined
166
+ ? { screenshot: full.screenshot }
167
+ : {}),
115
168
  },
116
169
  sink: diskSink(root),
117
170
  state: readCloudState(root),
@@ -119,6 +172,23 @@ export function startSyncDaemon(deps) {
119
172
  request,
120
173
  });
121
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
+ };
122
192
  /**
123
193
  * Push every other linked root this machine knows about.
124
194
  *
@@ -143,14 +213,19 @@ export function startSyncDaemon(deps) {
143
213
  for (const root of list) {
144
214
  if (root === deps.reticleRoot)
145
215
  continue;
216
+ rootsLinked.set(root, false);
146
217
  try {
147
218
  const cloud = await cloudFor(root);
148
219
  // Linked HERE, by its own link file. An unlinked root resolves to the environment's key,
149
220
  // which is the right answer for the root this daemon stands in and somebody else's project
150
221
  // for every other one: a daemon started with RETICLE_API_KEY pushed every project this
151
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.
152
224
  if (null === cloud.config || null === cloud.projectId)
153
225
  continue;
226
+ rootsLinked.set(root, true);
227
+ if (cloud.policy.flows)
228
+ rootsSendFlows.add(root);
154
229
  const report = await pushRoot(root, cloud);
155
230
  if (report === undefined)
156
231
  continue;
@@ -180,11 +255,18 @@ export function startSyncDaemon(deps) {
180
255
  if (running)
181
256
  return undefined;
182
257
  running = true;
258
+ rootsLinked = new Map();
259
+ rootsSendFlows = new Set();
183
260
  try {
184
261
  const cloud = await deps.cloud();
185
262
  const linked = null !== cloud.config;
186
- 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)) {
187
268
  wasLinked = linked;
269
+ wasProject = cloud.projectId;
188
270
  if (linked)
189
271
  log('reticle_cloud_linked', {
190
272
  projectId: cloud.projectId,
@@ -195,7 +277,7 @@ export function startSyncDaemon(deps) {
195
277
  else
196
278
  log('reticle_cloud_unlinked', {
197
279
  root: deps.reticleRoot,
198
- 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',
199
281
  });
200
282
  }
201
283
  // Every OTHER linked root, first. Their failures are logged and never abort this daemon's own
@@ -253,6 +335,7 @@ export function startSyncDaemon(deps) {
253
335
  return undefined;
254
336
  }
255
337
  finally {
338
+ await refreshUnsynced().catch(() => undefined);
256
339
  running = false;
257
340
  }
258
341
  };
@@ -272,20 +355,30 @@ export function startSyncDaemon(deps) {
272
355
  timer.unref?.();
273
356
  };
274
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));
275
360
  return {
276
- 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,
277
368
  nudge: () => {
278
369
  // A cycle already in flight will not pick this up, so the nudge is still scheduled behind it
279
370
  // rather than dropped — otherwise the run that arrived during a slow cycle waits a full tick.
280
371
  schedule(NUDGE_DELAY_MS);
281
372
  },
282
373
  stop: () => {
374
+ unwatch?.();
283
375
  stopped = true;
284
376
  if (timer !== undefined)
285
377
  clearTimeout(timer);
286
378
  timer = undefined;
287
379
  },
288
380
  flush: async () => {
381
+ unwatch?.();
289
382
  stopped = true;
290
383
  if (timer !== undefined)
291
384
  clearTimeout(timer);
@@ -320,5 +413,22 @@ export function attachCloudSync(deps) {
320
413
  // reporting nothing — indistinguishable, on the dashboard, from nobody having verified anything.
321
414
  otherRoots: () => Promise.resolve(deps.otherRoots()),
322
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),
323
418
  });
324
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
+ }
@@ -12,7 +12,8 @@
12
12
  */
13
13
  import { shareableRun } from '@reticlehq/core/artifacts';
14
14
  import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync, } from 'node:fs';
15
- import { dirname, join } from 'node:path';
15
+ import { basename, dirname, join } from 'node:path';
16
+ import { stepShotDir } from '../project/dir/reticle-dir.js';
16
17
  import { IntentDir, ReticleDir } from '@reticlehq/core';
17
18
  import { DERIVED_RECORDS, } from './sync-cycle.js';
18
19
  import { subjectFor } from '../intent/intent-subject.js';
@@ -102,6 +103,8 @@ const INTENT_INDEX = IntentDir.INDEX_FILE;
102
103
  * Returns undefined when there is no memory at all, so a project with nothing to say sends nothing
103
104
  * rather than an empty envelope the server has to store.
104
105
  */
106
+ /** The longest reason the platform keeps for a statement. */
107
+ const MAX_REASON = 2000;
105
108
  function readIntent(reticleRoot) {
106
109
  const legacy = readJson(join(reticleRoot, ReticleDir.INTENT_FILE));
107
110
  const merged = {
@@ -139,7 +142,16 @@ function readIntent(reticleRoot) {
139
142
  * answers start disagreeing — and the one with the evidence should be the one that decides.
140
143
  * Records that already name a subject keep it: an agent's explicit choice outranks inference.
141
144
  */
142
- 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
+ };
143
155
  const held = record;
144
156
  if ('string' === typeof held.subject && held.subject.length > 0)
145
157
  return [id, record];
@@ -178,6 +190,18 @@ export function diskSource(reticleRoot) {
178
190
  capsules: () => readJsonDir(join(reticleRoot, ReticleDir.CAPSULES_SUBDIR)),
179
191
  // Intent is the only derived record that is no longer one file. See readIntent.
180
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
+ },
181
205
  };
182
206
  }
183
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>;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * The step screenshots of each run the platform has accepted, sent once each.
3
+ *
4
+ * A run goes up first, through the sync; its screenshots follow, one request per image, to
5
+ * `POST /v1/runs/:runId/screenshots` — only once the platform holds the run, because the endpoint
6
+ * answers 404 for a run it does not. What was sent is remembered by `seq:sha256`, like runs by hash,
7
+ * so a quiet cycle sends nothing.
8
+ *
9
+ * An OLDER platform has no such endpoint and answers 404 or 405. That run's screenshots stay on
10
+ * this machine and are not offered again: the alternative is one refused request per image on every
11
+ * cycle, forever. A newer platform picks up the next run's. Anything else that failed (the network,
12
+ * a 5xx) is simply retried next cycle.
13
+ */
14
+ import { createHash } from 'node:crypto';
15
+ import { RunScreenshotSchema, STEP_SCREENSHOT } from '@reticlehq/core';
16
+ const SCREENSHOTS_PATH = (runId) => `/v1/runs/${encodeURIComponent(runId)}/screenshots`;
17
+ const HTTP_CREATED = 201;
18
+ const HTTP_OK = 200;
19
+ /** An older platform: no route (404), or a route that takes no POST (405). */
20
+ const NOT_THERE = new Set([404, 405]);
21
+ /** Refused for good: too large (413), too many for one run (409). Retrying cannot change it. */
22
+ const REFUSED_FOR_GOOD = new Set([409, 413]);
23
+ /** The screenshots a run artifact lists, read defensively: a malformed entry is skipped. */
24
+ function listed(payload) {
25
+ const raw = 'object' === typeof payload && null !== payload
26
+ ? payload['screenshots']
27
+ : undefined;
28
+ if (!Array.isArray(raw))
29
+ return [];
30
+ return raw.flatMap((entry) => {
31
+ const parsed = RunScreenshotSchema.safeParse(entry);
32
+ return parsed.success ? [parsed.data] : [];
33
+ });
34
+ }
35
+ const keyOf = (shot) => `${String(shot.seq)}:${shot.sha256}`;
36
+ /** The upload body for one screenshot — the shape `ScreenshotUploadSchema` reads. */
37
+ function bodyOf(shot, image) {
38
+ return {
39
+ seq: shot.seq,
40
+ file: shot.file,
41
+ ...(shot.step === undefined ? {} : { step: shot.step }),
42
+ tool: shot.tool,
43
+ ...(shot.checkId === undefined ? {} : { checkId: shot.checkId }),
44
+ source: shot.source,
45
+ width: shot.width,
46
+ height: shot.height,
47
+ sha256: shot.sha256,
48
+ jpeg: Buffer.from(image).toString('base64'),
49
+ };
50
+ }
51
+ /** Send what is owed. Never throws; the record is bounded by the runs still on disk. */
52
+ export async function syncScreenshots(deps) {
53
+ const onDisk = new Set(deps.runs.map((run) => run.runId));
54
+ const sent = {};
55
+ for (const [runId, keys] of Object.entries(deps.state.sentScreenshots ?? {}))
56
+ if (onDisk.has(runId) && Array.isArray(keys))
57
+ sent[runId] = keys.filter((k) => 'string' === typeof k);
58
+ const heldLocal = new Set((deps.state.screenshotsHeldLocal ?? []).filter((id) => onDisk.has(id)));
59
+ const finish = (count, error) => ({
60
+ sent: count,
61
+ state: { sentScreenshots: sent, screenshotsHeldLocal: [...heldLocal] },
62
+ ...(error === undefined ? {} : { error }),
63
+ });
64
+ let count = 0;
65
+ try {
66
+ for (const run of deps.runs) {
67
+ if (heldLocal.has(run.runId) || !deps.accepted(run.runId))
68
+ continue;
69
+ const done = new Set(sent[run.runId] ?? []);
70
+ for (const shot of listed(run.payload)) {
71
+ if (done.has(keyOf(shot)))
72
+ continue;
73
+ const image = deps.readImage(run.runId, shot.file);
74
+ // Gone (retention took it) or changed under its name: nothing true is left to send.
75
+ if (image === undefined || createHash('sha256').update(image).digest('hex') !== shot.sha256)
76
+ continue;
77
+ const markDone = () => {
78
+ done.add(keyOf(shot));
79
+ sent[run.runId] = [...done];
80
+ };
81
+ if (image.byteLength > STEP_SCREENSHOT.MAX_BYTES) {
82
+ markDone();
83
+ continue;
84
+ }
85
+ const res = await deps.post(SCREENSHOTS_PATH(run.runId), JSON.stringify(bodyOf(shot, image)));
86
+ if (HTTP_CREATED === res.status || HTTP_OK === res.status) {
87
+ markDone();
88
+ count += 1;
89
+ }
90
+ else if (NOT_THERE.has(res.status)) {
91
+ heldLocal.add(run.runId);
92
+ return finish(count, `screenshots ${String(res.status)}: this platform does not take screenshots yet, so run ${run.runId}'s stay on this machine`);
93
+ }
94
+ else if (REFUSED_FOR_GOOD.has(res.status))
95
+ markDone();
96
+ else
97
+ return finish(count, `screenshots ${String(res.status)}: ${res.text.slice(0, 200)}`);
98
+ }
99
+ }
100
+ }
101
+ catch (error) {
102
+ return finish(count, error instanceof Error ? error.message : String(error));
103
+ }
104
+ return finish(count);
105
+ }
@@ -0,0 +1,47 @@
1
+ export interface UnsyncedRoot {
2
+ root: string;
3
+ /** Runs on disk the platform has not accepted. */
4
+ runs: number;
5
+ /** Flows on disk nothing will send: see `unsentFlowCount`. */
6
+ flows: number;
7
+ /** Whether this folder holds a link. False is the case nothing will ever send. */
8
+ linked: boolean;
9
+ /**
10
+ * The project it belongs to: the linked one, else the id its `.reticle.json` names. A machine
11
+ * reports every folder to each workspace it is linked to; this is how a board keeps only its own.
12
+ */
13
+ projectId?: string;
14
+ }
15
+ /**
16
+ * Run files in `root` the platform never accepted. Ids only — a run file is named by its id — so
17
+ * this costs a directory listing, never a payload read. Never throws.
18
+ */
19
+ export declare function unsentRunCount(root: string): number;
20
+ /**
21
+ * Flows in `root` that will not reach the platform: all of them when nothing sends them (the root is
22
+ * not linked, has no key for its host, or has flow sync switched off), else the ones the platform
23
+ * refused. A flow waiting on a root that does send them is not counted: a save wakes the next cycle.
24
+ * Never throws.
25
+ */
26
+ export declare function unsentFlowCount(root: string, sendsFlows: boolean): number;
27
+ /**
28
+ * Every root here with runs or flows the platform does not have. Roots are deduped; order is kept.
29
+ * `sendsFlows` defaults to `isLinked`: a linked root sends its flows unless flow sync is off.
30
+ */
31
+ export declare function unsyncedRoots(roots: readonly string[], isLinked: (root: string) => Promise<boolean>, sendsFlows?: (root: string) => Promise<boolean>): Promise<UnsyncedRoot[]>;
32
+ /**
33
+ * The buckets under `~/.reticle/unmatched/` — evidence no project claimed, which nothing sends. After
34
+ * the first warning nothing named them again: not `status`, not `connect`, not the dashboard's
35
+ * unsent list. Never throws.
36
+ */
37
+ export declare function unmatchedRoots(home: string): string[];
38
+ /** The project a `.reticle` folder belongs to: where a person runs the command that sends it. */
39
+ export declare function projectFolderOf(root: string): string;
40
+ /** One line a person can act on: where the runs are, and the command that sends them. */
41
+ export declare function describeUnsynced(entry: UnsyncedRoot, from: string): string;
42
+ /**
43
+ * The unsent folders as a command's few lines: every unclaimed bucket in one (they all need the same
44
+ * thing, and `reticle connect` printed ninety near-identical lines of them before "Connected"), the
45
+ * rest one by one up to a handful, then a count and where the full list is.
46
+ */
47
+ export declare function describeUnsyncedBriefly(entries: readonly UnsyncedRoot[], from: string): string[];
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `.reticle` folders whose runs never reached the platform, and why.
3
+ *
4
+ * The daemon skipped every root without a link and said nothing: one repo held 31 runs nobody sent
5
+ * while the log repeated `reticle_cloud_unlinked` about a different folder. Counted here, from run
6
+ * ids on disk against the ids the platform accepted, so `reticle sync`, `doctor`, the HUD and the
7
+ * platform report can each name the folder and the one command that fixes it.
8
+ */
9
+ import { existsSync, readFileSync, readdirSync } from 'node:fs';
10
+ import { basename, dirname, join, relative, sep } from 'node:path';
11
+ import { ReticleDir } from '@reticlehq/core';
12
+ import { DRIVE_RECORD_SUFFIX, UNMATCHED_SUBDIR, isUnmatchedRoot, } from '../project/dir/reticle-dir.js';
13
+ import { diskSource, readCloudState } from './sync-disk.js';
14
+ /** The project's own config, beside its `.reticle` folder. */
15
+ const PROJECT_CONFIG = '.reticle.json';
16
+ /** `projectId` out of a JSON file, or undefined. Never throws. */
17
+ function projectIdIn(path) {
18
+ try {
19
+ const id = JSON.parse(readFileSync(path, 'utf8'))?.['projectId'];
20
+ return 'string' === typeof id && 0 < id.length ? id : undefined;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
26
+ /** The project a `.reticle` folder belongs to, as the platform keys it. */
27
+ function projectOfRoot(root) {
28
+ return (projectIdIn(join(root, ReticleDir.CLOUD_LINK_FILE)) ??
29
+ projectIdIn(join(projectFolderOf(root), PROJECT_CONFIG)));
30
+ }
31
+ const RUN_SUFFIX = '.json';
32
+ /** The folder the command is run from, named when it is the one the person is already in. */
33
+ const HERE = 'this folder';
34
+ /**
35
+ * Run files in `root` the platform never accepted. Ids only — a run file is named by its id — so
36
+ * this costs a directory listing, never a payload read. Never throws.
37
+ */
38
+ export function unsentRunCount(root) {
39
+ try {
40
+ const dir = join(root, ReticleDir.RUNS_SUBDIR);
41
+ if (!existsSync(dir))
42
+ return 0;
43
+ const state = readCloudState(root);
44
+ const sent = new Set([...Object.keys(state.sentRunHashes ?? {}), ...(state.sentRunIds ?? [])]);
45
+ // Made before the folder was linked here: held for `--move-history`, so not waiting for a sync.
46
+ const linkedAt = state.linkedAt;
47
+ const heldBack = (file) => {
48
+ if (linkedAt === undefined)
49
+ return false;
50
+ try {
51
+ const at = JSON.parse(readFileSync(join(dir, file), 'utf8'))
52
+ .createdAt;
53
+ return !('number' === typeof at && at >= linkedAt);
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ };
59
+ return readdirSync(dir)
60
+ .filter((file) => file.endsWith(RUN_SUFFIX) && !file.endsWith(DRIVE_RECORD_SUFFIX))
61
+ .filter((file) => !sent.has(file.slice(0, -RUN_SUFFIX.length)) && !heldBack(file)).length;
62
+ }
63
+ catch {
64
+ return 0;
65
+ }
66
+ }
67
+ /**
68
+ * Flows in `root` that will not reach the platform: all of them when nothing sends them (the root is
69
+ * not linked, has no key for its host, or has flow sync switched off), else the ones the platform
70
+ * refused. A flow waiting on a root that does send them is not counted: a save wakes the next cycle.
71
+ * Never throws.
72
+ */
73
+ export function unsentFlowCount(root, sendsFlows) {
74
+ try {
75
+ if (!sendsFlows)
76
+ return diskSource(root).flows().length;
77
+ return readCloudState(root).refusedSets?.flow?.count ?? 0;
78
+ }
79
+ catch {
80
+ return 0;
81
+ }
82
+ }
83
+ /**
84
+ * Every root here with runs or flows the platform does not have. Roots are deduped; order is kept.
85
+ * `sendsFlows` defaults to `isLinked`: a linked root sends its flows unless flow sync is off.
86
+ */
87
+ export async function unsyncedRoots(roots, isLinked, sendsFlows = isLinked) {
88
+ const out = [];
89
+ for (const root of new Set(roots)) {
90
+ const runs = unsentRunCount(root);
91
+ const linked = await isLinked(root).catch(() => false);
92
+ const flows = unsentFlowCount(root, linked && (await sendsFlows(root).catch(() => false)));
93
+ if (0 === runs && 0 === flows)
94
+ continue;
95
+ const projectId = projectOfRoot(root);
96
+ out.push({ root, runs, flows, linked, ...(projectId === undefined ? {} : { projectId }) });
97
+ }
98
+ return out;
99
+ }
100
+ /**
101
+ * The buckets under `~/.reticle/unmatched/` — evidence no project claimed, which nothing sends. After
102
+ * the first warning nothing named them again: not `status`, not `connect`, not the dashboard's
103
+ * unsent list. Never throws.
104
+ */
105
+ export function unmatchedRoots(home) {
106
+ const dir = join(home, ReticleDir.ROOT, UNMATCHED_SUBDIR);
107
+ try {
108
+ return readdirSync(dir, { withFileTypes: true })
109
+ .filter((entry) => entry.isDirectory())
110
+ .map((entry) => join(dir, entry.name));
111
+ }
112
+ catch {
113
+ return [];
114
+ }
115
+ }
116
+ /** The project a `.reticle` folder belongs to: where a person runs the command that sends it. */
117
+ export function projectFolderOf(root) {
118
+ return ReticleDir.ROOT === basename(root) ? dirname(root) : root;
119
+ }
120
+ /** One line a person can act on: where the runs are, and the command that sends them. */
121
+ export function describeUnsynced(entry, from) {
122
+ // Forward slashes on every platform, as the platform report writes `~/...`: one spelling of a
123
+ // folder whether it is read in a Windows terminal, the HUD or the dashboard.
124
+ const folder = projectFolderOf(entry.root);
125
+ const where = 0 === relative(from, folder).length ? HERE : relative(from, folder).split(sep).join('/');
126
+ const what = [
127
+ ...(0 < entry.runs ? [`${String(entry.runs)} run(s)`] : []),
128
+ ...(0 < entry.flows ? [`${String(entry.flows)} flow(s)`] : []),
129
+ ].join(' and ');
130
+ if (isUnmatchedRoot(entry.root)) {
131
+ return (`${what} in ${entry.root.split(sep).join('/')} are not on your dashboard: no project claimed ` +
132
+ 'them — the checkout that made them shares its project id with another, or has none. They stay ' +
133
+ 'on this machine; serve the app from the checkout that owns the id, or give this one its own, so new work reaches it');
134
+ }
135
+ // `connect`, not `link`: it signs in first when it has to, so it is the one command that works.
136
+ if (!entry.linked)
137
+ return `${what} in ${where} are not on your dashboard: run \`reticle connect\` there`;
138
+ // A linked root counts flows only when flow sync is off or the platform refused them.
139
+ return 0 < entry.flows
140
+ ? `${what} in ${where} are not on your dashboard: \`reticle config --flows on\` there if flow ` +
141
+ 'sync is off; `reticle sync` names any the platform refused'
142
+ : `${what} in ${where} are waiting to be sent: run \`reticle sync\` there`;
143
+ }
144
+ /** Folders named one by one before the rest are counted. */
145
+ const FOLDERS_SHOWN = 5;
146
+ /**
147
+ * The unsent folders as a command's few lines: every unclaimed bucket in one (they all need the same
148
+ * thing, and `reticle connect` printed ninety near-identical lines of them before "Connected"), the
149
+ * rest one by one up to a handful, then a count and where the full list is.
150
+ */
151
+ export function describeUnsyncedBriefly(entries, from) {
152
+ const unclaimed = entries.filter((entry) => isUnmatchedRoot(entry.root));
153
+ const claimed = entries.filter((entry) => !isUnmatchedRoot(entry.root));
154
+ if (entries.length <= FOLDERS_SHOWN)
155
+ return entries.map((entry) => describeUnsynced(entry, from));
156
+ const runs = unclaimed.reduce((sum, entry) => sum + entry.runs, 0);
157
+ const lines = claimed.slice(0, FOLDERS_SHOWN).map((entry) => describeUnsynced(entry, from));
158
+ if (0 < unclaimed.length)
159
+ lines.unshift(`${String(runs)} run(s) in ${String(unclaimed.length)} folders under ~/${ReticleDir.ROOT}/${UNMATCHED_SUBDIR} ` +
160
+ 'are not on your dashboard: no project claimed them, so they stay on this machine');
161
+ const more = claimed.length - FOLDERS_SHOWN;
162
+ if (0 < more || 0 < unclaimed.length)
163
+ lines.push(`${0 < more ? `${String(more)} more folder(s) hold work not on your dashboard. ` : ''}` +
164
+ '`reticle doctor` lists every one');
165
+ return lines;
166
+ }
@@ -9,11 +9,26 @@
9
9
  * ponytail: stat polling (`fs.watchFile`), not `fs.watch`. It works on a file that does not exist
10
10
  * yet — the usual state of `cloud.json` before a link — and costs one stat per file per interval.
11
11
  */
12
+ import type { ImpactSnapshot } from '@reticlehq/core';
12
13
  /** How often each watched file is stat'ed. A second reads as live to somebody switching windows. */
13
14
  export declare const ACCOUNT_WATCH_INTERVAL_MS = 1000;
15
+ /** How often `onTick` re-reads what has no file to watch; the platform config is polled as often. */
16
+ export declare const HUD_REFRESH_MS = 30000;
14
17
  export interface AccountFilesWatch {
15
18
  /** Start watching one file. Watching the same path twice is a no-op. */
16
19
  watch(path: string): void;
17
20
  close(): void;
18
21
  }
19
- export declare function watchAccountFiles(onChange: () => void, intervalMs?: number): AccountFilesWatch;
22
+ export declare function watchAccountFiles(onChange: () => void, intervalMs?: number,
23
+ /** The rest of the platform's side of the HUD, which has no file to watch: run every 30s. */
24
+ onTick?: () => void): AccountFilesWatch;
25
+ /**
26
+ * Push a tab its impact snapshot, or, with `ifChanged`, only when it moved since this last pushed it.
27
+ *
28
+ * The daemon re-reads every tab on a timer so the platform's side of the HUD (sync status, runs on
29
+ * the dashboard, days left) reaches an idle tab without a tool call; resending an unchanged snapshot
30
+ * on every tick would repaint every HUD for nothing.
31
+ */
32
+ export declare function pushIfMoved(tab: {
33
+ pushImpact(read: () => ImpactSnapshot | undefined, immediate: boolean): void;
34
+ }, read: () => ImpactSnapshot | undefined, ifChanged: boolean): void;