@reticlehq/server 2.11.0 → 2.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (513) hide show
  1. package/SKILL.md +93 -111
  2. package/dist/bridge/auth-failure-reason.d.ts +6 -2
  3. package/dist/bridge/auth-failure-reason.js +15 -8
  4. package/dist/bridge/bridge.d.ts +18 -14
  5. package/dist/bridge/bridge.js +248 -165
  6. package/dist/bridge/bridge.test-harness.d.ts +2 -0
  7. package/dist/bridge/bridge.test-harness.js +8 -1
  8. package/dist/bridge/pairing-token.d.ts +5 -0
  9. package/dist/bridge/pairing-token.js +28 -0
  10. package/dist/bridge/protocol-skew.d.ts +37 -0
  11. package/dist/bridge/protocol-skew.js +45 -0
  12. package/dist/capsule/capsule-store.d.ts +8 -7
  13. package/dist/capsule/capsule-store.js +1 -1
  14. package/dist/capsule/causal-summary.d.ts +2 -2
  15. package/dist/capsule/causal-summary.js +7 -2
  16. package/dist/chromium-launch-options.d.ts +28 -0
  17. package/dist/chromium-launch-options.js +29 -0
  18. package/dist/cli/cli-doctor.js +39 -1
  19. package/dist/cli/cli-flow-commands.d.ts +3 -1
  20. package/dist/cli/cli-flow-commands.js +34 -4
  21. package/dist/cli/cli-kill.d.ts +2 -1
  22. package/dist/cli/cli-launch.d.ts +11 -1
  23. package/dist/cli/cli-launch.js +59 -10
  24. package/dist/cli/cli-parse.d.ts +17 -1
  25. package/dist/cli/cli-parse.js +238 -12
  26. package/dist/cli/cli-update-commands.js +20 -1
  27. package/dist/cli/cli-verify.d.ts +2 -1
  28. package/dist/cli/cli-verify.js +5 -4
  29. package/dist/cli/cloud-cli.js +428 -253
  30. package/dist/cli/cloud-keystore.d.ts +37 -0
  31. package/dist/cli/cloud-keystore.js +97 -0
  32. package/dist/cli/cloud-kit.d.ts +37 -0
  33. package/dist/cli/cloud-kit.js +121 -0
  34. package/dist/cli/cloud-login.d.ts +17 -0
  35. package/dist/cli/cloud-login.js +183 -0
  36. package/dist/cli/cloud-session.d.ts +48 -0
  37. package/dist/cli/cloud-session.js +53 -0
  38. package/dist/cli/config-discovery.d.ts +2 -1
  39. package/dist/cli/config-discovery.js +1 -0
  40. package/dist/cli/daemon-start-options.d.ts +32 -0
  41. package/dist/cli/daemon-start-options.js +38 -0
  42. package/dist/cli/device-flow.d.ts +76 -0
  43. package/dist/cli/device-flow.js +56 -0
  44. package/dist/cli/doctor-rows.d.ts +12 -1
  45. package/dist/cli/doctor-rows.js +12 -1
  46. package/dist/cli/drive-attach.d.ts +13 -0
  47. package/dist/cli/drive-attach.js +18 -0
  48. package/dist/cli/drive-command.js +7 -3
  49. package/dist/cli/gate-exit.d.ts +21 -0
  50. package/dist/cli/gate-exit.js +21 -0
  51. package/dist/cli/gate-hook-message.d.ts +33 -0
  52. package/dist/cli/gate-hook-message.js +46 -0
  53. package/dist/cli/mcp-command.d.ts +25 -0
  54. package/dist/cli/mcp-command.js +174 -0
  55. package/dist/cli/project-name.d.ts +30 -0
  56. package/dist/cli/project-name.js +44 -0
  57. package/dist/cli/sibling-ports.d.ts +37 -0
  58. package/dist/cli/sibling-ports.js +56 -0
  59. package/dist/cli/status-next-action.d.ts +14 -0
  60. package/dist/cli/status-next-action.js +4 -4
  61. package/dist/cli.d.ts +1 -0
  62. package/dist/cli.js +167 -186
  63. package/dist/cloud/cloud-config.d.ts +24 -2
  64. package/dist/cloud/cloud-config.js +64 -5
  65. package/dist/cloud/cloud-sync.d.ts +16 -5
  66. package/dist/cloud/cloud-sync.js +64 -1
  67. package/dist/cloud/sync-cycle.d.ts +110 -0
  68. package/dist/cloud/sync-cycle.js +270 -0
  69. package/dist/cloud/sync-daemon.d.ts +69 -0
  70. package/dist/cloud/sync-daemon.js +260 -0
  71. package/dist/cloud/sync-disk.d.ts +9 -0
  72. package/dist/cloud/sync-disk.js +191 -0
  73. package/dist/cloud/sync-hash.d.ts +2 -0
  74. package/dist/cloud/sync-hash.js +35 -0
  75. package/dist/crawl/crawl-tools.js +5 -3
  76. package/dist/crawl/crawl.d.ts +12 -13
  77. package/dist/crawl/crawl.js +25 -3
  78. package/dist/daemon/daemon-resolve.d.ts +122 -0
  79. package/dist/daemon/daemon-resolve.js +187 -0
  80. package/dist/daemon/dev-servers.d.ts +15 -0
  81. package/dist/daemon/dev-servers.js +68 -0
  82. package/dist/daemon/free-port.d.ts +29 -0
  83. package/dist/daemon/free-port.js +55 -0
  84. package/dist/daemon/heartbeat.d.ts +0 -4
  85. package/dist/daemon/heartbeat.js +2 -2
  86. package/dist/daemon/wake-decision.d.ts +32 -0
  87. package/dist/daemon/wake-decision.js +38 -0
  88. package/dist/dev/stale-issue-guard.d.ts +0 -2
  89. package/dist/dev/stale-issue-guard.js +1 -1
  90. package/dist/domain/domain-tools.js +2 -2
  91. package/dist/events/contradictions.d.ts +59 -0
  92. package/dist/events/contradictions.js +253 -27
  93. package/dist/events/declared.d.ts +17 -0
  94. package/dist/events/declared.js +43 -1
  95. package/dist/events/echo-mismatch.js +65 -1
  96. package/dist/events/event-filters.d.ts +8 -0
  97. package/dist/events/event-filters.js +19 -2
  98. package/dist/events/observed-in-window.d.ts +12 -0
  99. package/dist/events/observed-in-window.js +17 -0
  100. package/dist/events/predicate-asks.d.ts +13 -0
  101. package/dist/events/predicate-asks.js +22 -0
  102. package/dist/events/predicate-eval.js +117 -27
  103. package/dist/events/predicate-parse.d.ts +0 -6
  104. package/dist/events/predicate-parse.js +43 -3
  105. package/dist/events/predicate-precheck.d.ts +8 -0
  106. package/dist/events/predicate-precheck.js +41 -0
  107. package/dist/events/predicate-route.d.ts +14 -0
  108. package/dist/events/predicate-route.js +36 -8
  109. package/dist/events/predicate-schema.d.ts +44 -0
  110. package/dist/events/predicate-schema.js +126 -23
  111. package/dist/events/predicate.d.ts +20 -0
  112. package/dist/events/predicate.js +77 -6
  113. package/dist/events/split-text-miss.d.ts +30 -0
  114. package/dist/events/split-text-miss.js +44 -0
  115. package/dist/flows/coverage.d.ts +0 -8
  116. package/dist/flows/coverage.js +1 -1
  117. package/dist/flows/decision.d.ts +5 -1
  118. package/dist/flows/decision.js +33 -7
  119. package/dist/flows/flake.d.ts +6 -8
  120. package/dist/flows/flake.js +1 -1
  121. package/dist/flows/flow-intent.d.ts +52 -0
  122. package/dist/flows/flow-intent.js +123 -0
  123. package/dist/flows/flow-memory-consult.d.ts +55 -0
  124. package/dist/flows/flow-memory-consult.js +38 -0
  125. package/dist/flows/flow-replay-run.d.ts +18 -7
  126. package/dist/flows/flow-replay-run.js +263 -23
  127. package/dist/flows/flow-replay.js +11 -2
  128. package/dist/flows/flow-step-runners.d.ts +8 -0
  129. package/dist/flows/flow-step-runners.js +8 -2
  130. package/dist/flows/flow-store-for-session.d.ts +31 -0
  131. package/dist/flows/flow-store-for-session.js +33 -0
  132. package/dist/flows/flow-success.js +15 -3
  133. package/dist/flows/flow-tools.js +87 -33
  134. package/dist/flows/flows.d.ts +14 -1
  135. package/dist/flows/flows.js +502 -24
  136. package/dist/flows/heal-run.js +5 -2
  137. package/dist/flows/predicate-to-expect.d.ts +17 -12
  138. package/dist/flows/predicate-to-expect.js +36 -15
  139. package/dist/flows/recordings.d.ts +13 -1
  140. package/dist/flows/recordings.js +17 -2
  141. package/dist/flows/replay.d.ts +22 -2
  142. package/dist/flows/replay.js +28 -2
  143. package/dist/flows/server-verify.js +16 -2
  144. package/dist/flows/verify-change-tools.js +9 -3
  145. package/dist/honesty/blind-spots.d.ts +51 -15
  146. package/dist/honesty/blind-spots.js +64 -4
  147. package/dist/honesty/feature-capture.d.ts +150 -0
  148. package/dist/honesty/feature-capture.js +233 -0
  149. package/dist/honesty/honesty.d.ts +24 -0
  150. package/dist/honesty/honesty.js +25 -1
  151. package/dist/honesty/instrumentation-gaps.d.ts +39 -6
  152. package/dist/honesty/instrumentation-gaps.js +88 -5
  153. package/dist/honesty/tool-hit-rate.d.ts +86 -0
  154. package/dist/honesty/tool-hit-rate.js +90 -0
  155. package/dist/honesty/undeclared-change.d.ts +51 -0
  156. package/dist/honesty/undeclared-change.js +56 -0
  157. package/dist/honesty/verified.d.ts +16 -0
  158. package/dist/honesty/verified.js +84 -5
  159. package/dist/http-server.d.ts +0 -1
  160. package/dist/http-server.js +13 -1
  161. package/dist/hunt/hunt-report.d.ts +0 -2
  162. package/dist/hunt/hunt-report.js +1 -1
  163. package/dist/impact/impact-recorder.d.ts +31 -8
  164. package/dist/impact/impact-recorder.js +104 -21
  165. package/dist/impact/impact-store.d.ts +13 -4
  166. package/dist/impact/impact-store.js +44 -3
  167. package/dist/index.d.ts +4 -1
  168. package/dist/index.js +113 -7
  169. package/dist/init/agent-rules.d.ts +30 -6
  170. package/dist/init/agent-rules.js +54 -13
  171. package/dist/init/astro-patch.d.ts +16 -0
  172. package/dist/init/astro-patch.js +28 -0
  173. package/dist/init/capabilities.d.ts +4 -3
  174. package/dist/init/capabilities.js +23 -3
  175. package/dist/init/closing-hint.d.ts +36 -0
  176. package/dist/init/closing-hint.js +120 -0
  177. package/dist/init/confirm.d.ts +27 -28
  178. package/dist/init/confirm.js +51 -11
  179. package/dist/init/cra.d.ts +34 -5
  180. package/dist/init/cra.js +96 -25
  181. package/dist/init/csp-check.js +17 -2
  182. package/dist/init/csp-doctor.d.ts +8 -1
  183. package/dist/init/csp-doctor.js +8 -3
  184. package/dist/init/dev-script.d.ts +50 -0
  185. package/dist/init/dev-script.js +66 -0
  186. package/dist/init/format-generated.d.ts +18 -0
  187. package/dist/init/format-generated.js +39 -0
  188. package/dist/init/install-hint.d.ts +9 -0
  189. package/dist/init/install-hint.js +29 -0
  190. package/dist/init/mcp-clients.d.ts +7 -0
  191. package/dist/init/mcp-clients.js +7 -0
  192. package/dist/init/mcp.d.ts +17 -6
  193. package/dist/init/mcp.js +27 -11
  194. package/dist/init/node-io.js +11 -1
  195. package/dist/init/plan-framework.d.ts +0 -5
  196. package/dist/init/plan-framework.js +147 -37
  197. package/dist/init/plan.d.ts +30 -37
  198. package/dist/init/plan.js +50 -60
  199. package/dist/init/preflight.d.ts +28 -0
  200. package/dist/init/preflight.js +38 -0
  201. package/dist/init/refresh-rules.d.ts +39 -0
  202. package/dist/init/refresh-rules.js +51 -0
  203. package/dist/init/run.d.ts +32 -35
  204. package/dist/init/run.js +151 -202
  205. package/dist/init/slash-command.d.ts +1 -1
  206. package/dist/init/snippets.js +90 -31
  207. package/dist/init/vite-config.d.ts +1 -1
  208. package/dist/init/vite-config.js +39 -25
  209. package/dist/init/workspace-apps.d.ts +17 -10
  210. package/dist/init/workspace-apps.js +110 -0
  211. package/dist/input/network-mock-tools.js +3 -6
  212. package/dist/input/real-input.d.ts +2 -0
  213. package/dist/input/real-input.js +28 -7
  214. package/dist/input/scroll-find.d.ts +7 -4
  215. package/dist/input/scroll-find.js +63 -25
  216. package/dist/input/scroll-tools.js +4 -6
  217. package/dist/input/viewport-tools.js +5 -4
  218. package/dist/intent/inline-intent.d.ts +37 -0
  219. package/dist/intent/inline-intent.js +193 -0
  220. package/dist/intent/intent-shard-store.d.ts +72 -0
  221. package/dist/intent/intent-shard-store.js +203 -0
  222. package/dist/intent/intent-shard.d.ts +411 -0
  223. package/dist/intent/intent-shard.js +176 -0
  224. package/dist/intent/intent-store.d.ts +49 -0
  225. package/dist/intent/intent-store.js +133 -0
  226. package/dist/intent/intent-subject.d.ts +45 -0
  227. package/dist/intent/intent-subject.js +177 -0
  228. package/dist/intent/intent-tools.d.ts +2 -0
  229. package/dist/intent/intent-tools.js +143 -0
  230. package/dist/intent/open-intents.d.ts +42 -0
  231. package/dist/intent/open-intents.js +40 -0
  232. package/dist/journal/envelope.d.ts +26 -26
  233. package/dist/journal/journal-recorder.d.ts +23 -0
  234. package/dist/journal/journal-recorder.js +35 -0
  235. package/dist/license/license-env.d.ts +29 -0
  236. package/dist/license/license-env.js +148 -0
  237. package/dist/loopback-agent.d.ts +31 -0
  238. package/dist/loopback-agent.js +57 -0
  239. package/dist/mcp/attach-memory.d.ts +83 -0
  240. package/dist/mcp/attach-memory.js +185 -0
  241. package/dist/mcp/client-identity.d.ts +31 -0
  242. package/dist/mcp/client-identity.js +51 -0
  243. package/dist/mcp/mcp-post-transport.d.ts +26 -0
  244. package/dist/mcp/mcp-post-transport.js +109 -0
  245. package/dist/mcp/mcp-proxy.d.ts +2 -8
  246. package/dist/mcp/mcp-proxy.js +64 -62
  247. package/dist/mcp/mcp.d.ts +2 -0
  248. package/dist/mcp/mcp.js +52 -24
  249. package/dist/mcp/proxy-backoff.d.ts +11 -0
  250. package/dist/mcp/proxy-backoff.js +13 -0
  251. package/dist/mcp/proxy-daemon-probe.d.ts +1 -0
  252. package/dist/mcp/proxy-daemon-probe.js +7 -1
  253. package/dist/mcp/proxy-handshake.d.ts +23 -0
  254. package/dist/mcp/proxy-handshake.js +31 -0
  255. package/dist/mcp/server-instructions.js +45 -3
  256. package/dist/mcp/tool-catalog-cache.d.ts +16 -0
  257. package/dist/mcp/tool-catalog-cache.js +22 -0
  258. package/dist/memory/memory-tools.d.ts +2 -0
  259. package/dist/memory/memory-tools.js +82 -0
  260. package/dist/memory/project-memory.d.ts +34 -0
  261. package/dist/memory/project-memory.js +96 -0
  262. package/dist/oracles/flow-instrument-gaps.d.ts +4 -4
  263. package/dist/oracles/flow-instrument-gaps.js +4 -5
  264. package/dist/oracles/propose-consequences.js +12 -3
  265. package/dist/oracles/self-instrument.d.ts +9 -9
  266. package/dist/oracles/self-instrument.js +19 -13
  267. package/dist/pool/browser-pool.d.ts +75 -5
  268. package/dist/pool/browser-pool.js +144 -5
  269. package/dist/pool/playwright-launcher.js +12 -1
  270. package/dist/project/fs-port.d.ts +3 -0
  271. package/dist/project/fs-port.js +3 -2
  272. package/dist/project/memory-fs.js +6 -1
  273. package/dist/project/project-tools.js +6 -35
  274. package/dist/project/reticle-dir.d.ts +17 -2
  275. package/dist/project/reticle-dir.js +22 -4
  276. package/dist/runs/context-tools.d.ts +2 -0
  277. package/dist/runs/context-tools.js +89 -0
  278. package/dist/runs/run-context.d.ts +40 -0
  279. package/dist/runs/run-context.js +158 -0
  280. package/dist/runs/run-diff.d.ts +2 -1
  281. package/dist/runs/run-store.d.ts +10 -0
  282. package/dist/runs/run-store.js +10 -0
  283. package/dist/runs/verification-sync.d.ts +0 -11
  284. package/dist/runs/verification-sync.js +24 -3
  285. package/dist/runs/verify-server.d.ts +8 -1
  286. package/dist/runs/verify-server.js +14 -2
  287. package/dist/session/command-timeout.d.ts +5 -2
  288. package/dist/session/command-timeout.js +5 -4
  289. package/dist/session/dev-server-probe.d.ts +0 -8
  290. package/dist/session/dev-server-probe.js +1 -1
  291. package/dist/session/last-act.d.ts +7 -1
  292. package/dist/session/last-act.js +2 -2
  293. package/dist/session/lease-visibility.d.ts +67 -0
  294. package/dist/session/lease-visibility.js +76 -0
  295. package/dist/session/live-control-tools.js +1 -42
  296. package/dist/session/no-session-diagnosis.d.ts +33 -0
  297. package/dist/session/no-session-diagnosis.js +82 -24
  298. package/dist/session/no-session-next-action.d.ts +12 -0
  299. package/dist/session/no-session-next-action.js +77 -6
  300. package/dist/session/no-session-watch.d.ts +18 -6
  301. package/dist/session/no-session-watch.js +125 -75
  302. package/dist/session/output-budget.js +4 -1
  303. package/dist/session/pending-commands.d.ts +11 -1
  304. package/dist/session/pending-commands.js +12 -2
  305. package/dist/session/resolve-within.d.ts +34 -0
  306. package/dist/session/resolve-within.js +53 -0
  307. package/dist/session/session-health.d.ts +9 -0
  308. package/dist/session/session-health.js +23 -0
  309. package/dist/session/session-info.d.ts +18 -0
  310. package/dist/session/session-info.js +11 -0
  311. package/dist/session/session-manager.d.ts +14 -0
  312. package/dist/session/session-manager.js +111 -4
  313. package/dist/session/session-mirror.d.ts +16 -0
  314. package/dist/session/session-mirror.js +22 -0
  315. package/dist/session/session-recommendation.d.ts +19 -5
  316. package/dist/session/session-recommendation.js +25 -9
  317. package/dist/session/session-successor.d.ts +37 -0
  318. package/dist/session/session-successor.js +113 -0
  319. package/dist/session/session-tools.js +2 -2
  320. package/dist/session/session.d.ts +53 -3
  321. package/dist/session/session.js +162 -9
  322. package/dist/session/wall-clock.d.ts +12 -0
  323. package/dist/session/wall-clock.js +14 -0
  324. package/dist/setup/agent-configs.d.ts +96 -0
  325. package/dist/setup/agent-configs.js +333 -0
  326. package/dist/setup/agent-io.d.ts +9 -0
  327. package/dist/setup/agent-io.js +16 -0
  328. package/dist/setup/agent-writer.d.ts +51 -0
  329. package/dist/setup/agent-writer.js +115 -0
  330. package/dist/setup/approval-migration.d.ts +47 -0
  331. package/dist/setup/approval-migration.js +68 -0
  332. package/dist/setup/auto-approve.d.ts +85 -0
  333. package/dist/setup/auto-approve.js +150 -0
  334. package/dist/setup/bridge-port.d.ts +15 -0
  335. package/dist/setup/bridge-port.js +24 -0
  336. package/dist/setup/desktop-shape.d.ts +57 -0
  337. package/dist/setup/desktop-shape.js +63 -0
  338. package/dist/setup/dev-server-wait.d.ts +60 -0
  339. package/dist/setup/dev-server-wait.js +65 -0
  340. package/dist/setup/drive-agent.d.ts +71 -0
  341. package/dist/setup/drive-agent.js +171 -0
  342. package/dist/setup/drive-plan.d.ts +68 -0
  343. package/dist/setup/drive-plan.js +130 -0
  344. package/dist/setup/ensure-daemon.d.ts +29 -0
  345. package/dist/setup/ensure-daemon.js +58 -0
  346. package/dist/setup/init-runtime.d.ts +47 -0
  347. package/dist/setup/init-runtime.js +160 -0
  348. package/dist/setup/interrupt.d.ts +25 -0
  349. package/dist/setup/interrupt.js +40 -0
  350. package/dist/setup/license-key.d.ts +39 -0
  351. package/dist/setup/license-key.js +72 -0
  352. package/dist/setup/listeners.d.ts +37 -0
  353. package/dist/setup/listeners.js +85 -0
  354. package/dist/setup/node-effects.d.ts +60 -0
  355. package/dist/setup/node-effects.js +210 -0
  356. package/dist/setup/page-probe.d.ts +39 -0
  357. package/dist/setup/page-probe.js +49 -0
  358. package/dist/setup/relaunch.d.ts +36 -0
  359. package/dist/setup/relaunch.js +57 -0
  360. package/dist/setup/remaining-steps.d.ts +19 -0
  361. package/dist/setup/remaining-steps.js +31 -0
  362. package/dist/setup/run-setup.d.ts +99 -0
  363. package/dist/setup/run-setup.js +187 -0
  364. package/dist/setup/session-pick.d.ts +47 -0
  365. package/dist/setup/session-pick.js +51 -0
  366. package/dist/setup/setup-command.d.ts +45 -0
  367. package/dist/setup/setup-command.js +199 -0
  368. package/dist/setup/setup-options.d.ts +74 -0
  369. package/dist/setup/setup-options.js +67 -0
  370. package/dist/setup/transcripts.d.ts +16 -0
  371. package/dist/setup/transcripts.js +43 -0
  372. package/dist/status-payload.d.ts +14 -1
  373. package/dist/status-payload.js +23 -1
  374. package/dist/telemetry/automation-hint.d.ts +2 -1
  375. package/dist/telemetry/daemon-telemetry.d.ts +8 -1
  376. package/dist/telemetry/daemon-telemetry.js +52 -5
  377. package/dist/telemetry/error-class.js +1 -1
  378. package/dist/telemetry/feedback-context.d.ts +9 -1
  379. package/dist/telemetry/feedback-context.js +60 -29
  380. package/dist/telemetry/feedback-local.d.ts +15 -0
  381. package/dist/telemetry/feedback-local.js +56 -0
  382. package/dist/telemetry/feedback-outbox.d.ts +2 -1
  383. package/dist/telemetry/feedback.d.ts +11 -0
  384. package/dist/telemetry/feedback.js +19 -6
  385. package/dist/telemetry/init-telemetry.d.ts +2 -0
  386. package/dist/telemetry/init-telemetry.js +2 -0
  387. package/dist/telemetry/install-source.d.ts +14 -0
  388. package/dist/telemetry/install-source.js +30 -0
  389. package/dist/telemetry/license-activation.d.ts +7 -1
  390. package/dist/telemetry/license-activation.js +9 -5
  391. package/dist/telemetry/mcp-connection.js +7 -0
  392. package/dist/telemetry/project-profile.d.ts +12 -9
  393. package/dist/telemetry/project-profile.js +12 -7
  394. package/dist/telemetry/proxy-telemetry.d.ts +3 -0
  395. package/dist/telemetry/proxy-telemetry.js +21 -0
  396. package/dist/telemetry/run-telemetry.d.ts +0 -2
  397. package/dist/telemetry/run-telemetry.js +1 -1
  398. package/dist/telemetry/session-metrics.d.ts +12 -0
  399. package/dist/telemetry/session-metrics.js +35 -1
  400. package/dist/telemetry/telemetry.js +17 -2
  401. package/dist/telemetry/tool-refused.d.ts +3 -1
  402. package/dist/telemetry/tool-refused.js +34 -2
  403. package/dist/telemetry/verification-of.js +2 -2
  404. package/dist/tools/act-capsule.d.ts +10 -0
  405. package/dist/tools/act-capsule.js +8 -2
  406. package/dist/tools/act-danger.d.ts +2 -1
  407. package/dist/tools/act-danger.js +11 -3
  408. package/dist/tools/act-observation.d.ts +41 -0
  409. package/dist/tools/act-observation.js +36 -0
  410. package/dist/tools/act-preflight.d.ts +20 -0
  411. package/dist/tools/act-preflight.js +98 -0
  412. package/dist/tools/act-sequence-retry.d.ts +45 -0
  413. package/dist/tools/act-sequence-retry.js +58 -0
  414. package/dist/tools/act-sequence-tool.d.ts +13 -0
  415. package/dist/tools/act-sequence-tool.js +135 -0
  416. package/dist/tools/act-target.d.ts +14 -0
  417. package/dist/tools/act-target.js +47 -0
  418. package/dist/tools/act-tools.d.ts +21 -1
  419. package/dist/tools/act-tools.js +196 -204
  420. package/dist/tools/assert-source.d.ts +22 -0
  421. package/dist/tools/assert-source.js +53 -0
  422. package/dist/tools/assert-verdict.d.ts +20 -3
  423. package/dist/tools/assert-verdict.js +58 -20
  424. package/dist/tools/browser-tools.js +6 -22
  425. package/dist/tools/complete-snapshot.d.ts +48 -0
  426. package/dist/tools/complete-snapshot.js +75 -0
  427. package/dist/tools/coverage-tools.d.ts +10 -1
  428. package/dist/tools/coverage-tools.js +123 -69
  429. package/dist/tools/dynamic-tools.js +53 -13
  430. package/dist/tools/error-recovery.d.ts +25 -14
  431. package/dist/tools/error-recovery.js +78 -9
  432. package/dist/tools/feedback-tools.d.ts +1 -1
  433. package/dist/tools/feedback-tools.js +2 -2
  434. package/dist/tools/invoke-tool.js +121 -11
  435. package/dist/tools/lease-hint.d.ts +10 -0
  436. package/dist/tools/lease-hint.js +53 -4
  437. package/dist/tools/lease-tools.d.ts +35 -5
  438. package/dist/tools/lease-tools.js +191 -13
  439. package/dist/tools/merged-name-redirect.d.ts +13 -0
  440. package/dist/tools/merged-name-redirect.js +22 -0
  441. package/dist/tools/navigate-arrival.d.ts +0 -2
  442. package/dist/tools/navigate-arrival.js +1 -1
  443. package/dist/tools/numeric-bounds.d.ts +100 -0
  444. package/dist/tools/numeric-bounds.js +125 -0
  445. package/dist/tools/observe-tools.js +109 -65
  446. package/dist/tools/read-tools.js +31 -8
  447. package/dist/tools/real-input-attempt.d.ts +32 -0
  448. package/dist/tools/real-input-attempt.js +212 -2
  449. package/dist/tools/reconcile-tools.js +22 -8
  450. package/dist/tools/resolve-target.d.ts +0 -7
  451. package/dist/tools/sdk-marker-probe.d.ts +29 -0
  452. package/dist/tools/sdk-marker-probe.js +57 -0
  453. package/dist/tools/snapshot-delta.d.ts +1 -1
  454. package/dist/tools/tool-kit.d.ts +24 -0
  455. package/dist/tools/tool-kit.js +21 -0
  456. package/dist/tools/tool-names.d.ts +5 -0
  457. package/dist/tools/tool-names.js +5 -0
  458. package/dist/tools/tool-surface.d.ts +85 -6
  459. package/dist/tools/tool-surface.js +122 -2
  460. package/dist/tools/tools-helpers.d.ts +19 -0
  461. package/dist/tools/tools-helpers.js +46 -0
  462. package/dist/tools/tools.d.ts +2 -0
  463. package/dist/tools/tools.js +78 -12
  464. package/dist/tools/unadvertised-help.js +4 -4
  465. package/dist/tools/verify-next-baton.d.ts +53 -0
  466. package/dist/tools/verify-next-baton.js +52 -0
  467. package/dist/update/reticle-deps.d.ts +9 -0
  468. package/dist/update/reticle-deps.js +27 -0
  469. package/dist/update/sdk-sync.d.ts +1 -2
  470. package/dist/update/sdk-sync.js +1 -21
  471. package/dist/update/update-nudge.d.ts +25 -10
  472. package/dist/update/update-nudge.js +32 -3
  473. package/dist/update/updater.d.ts +1 -1
  474. package/dist/version/peer-announce.js +4 -2
  475. package/dist/version/sdk-fix.d.ts +47 -0
  476. package/dist/version/sdk-fix.js +127 -0
  477. package/dist/version/version-nudge.d.ts +14 -0
  478. package/dist/version/version-nudge.js +21 -0
  479. package/dist/version/version-skew.d.ts +38 -3
  480. package/dist/version/version-skew.js +83 -7
  481. package/dist/visual/visual-store.d.ts +6 -6
  482. package/dist/visual/visual-store.js +18 -16
  483. package/dist/visual/visual-tools.js +57 -12
  484. package/docs/README.md +2 -1
  485. package/docs/cli/cloud.mdx +1 -1
  486. package/docs/cli/doctor.mdx +2 -0
  487. package/docs/cli/serve.mdx +4 -2
  488. package/docs/cli/verify.mdx +4 -5
  489. package/docs/cli.mdx +7 -3
  490. package/docs/desktop.mdx +1 -1
  491. package/docs/docs.json +2 -2
  492. package/docs/gates.md +1 -1
  493. package/docs/getting-started.md +19 -3
  494. package/docs/index.mdx +9 -6
  495. package/docs/install-agentic.mdx +38 -12
  496. package/docs/install-manual.mdx +1 -1
  497. package/docs/packages/browser.mdx +2 -2
  498. package/docs/packages/server.mdx +1 -1
  499. package/docs/packages.mdx +8 -6
  500. package/docs/platform-integration.md +17 -0
  501. package/docs/predicates.mdx +8 -0
  502. package/docs/reticle-manual.md +106 -0
  503. package/docs/skill-file.mdx +3 -3
  504. package/docs/telemetry-contract.md +108 -4
  505. package/docs/tools-act-sequence.mdx +16 -0
  506. package/docs/tools-tools-and-run.mdx +2 -4
  507. package/docs/troubleshooting.mdx +9 -0
  508. package/docs/usage.md +4 -3
  509. package/package.json +4 -3
  510. package/dist/init/cursor.d.ts +0 -24
  511. package/dist/init/cursor.js +0 -80
  512. package/dist/session/session-readiness.d.ts +0 -34
  513. package/dist/session/session-readiness.js +0 -36
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Optional cloud sync for saved flows. "Logged in" here means the two cloud env vars are set (written by
3
- * `reticle login` later; settable by hand today): the hosted URL and an API key from the Reticle Cloud
3
+ * `reticle login` later; settable by hand today): the hosted URL and an API key from the Reticle
4
4
  * dashboard. When present, a freshly-saved flow is pushed to `POST /v1/flows` so the team's regression
5
5
  * suite lives in the cloud — surviving refactors and runnable in CI. When absent, sync is a no-op and
6
6
  * everything stays 100% local (the "no phone-home" default: nothing leaves the machine unless you opt in).
@@ -21,6 +21,17 @@ export interface CloudConfig {
21
21
  }
22
22
  /** Resolve cloud credentials from the environment, or null when not logged in (sync disabled). */
23
23
  export declare function resolveCloudConfig(env: NodeJS.ProcessEnv): CloudConfig | null;
24
+ export declare const CLOUD_VERIFY_TIMEOUT_MS = 120000;
25
+ /**
26
+ * The fetch every cloud call goes through. Adds the abort signal, and turns the abort into a message
27
+ * that says what happened and what to do — a bare `AbortError` reaching a user or an agent is a riddle,
28
+ * and the agent-facing half of this product is judged on whether its errors are actionable.
29
+ */
30
+ export declare function cloudFetch(url: string, init: {
31
+ method: string;
32
+ headers: Record<string, string>;
33
+ body?: string;
34
+ }, timeoutMs?: number): Promise<Response>;
24
35
  export declare const SyncOutcome: {
25
36
  readonly SYNCED: "synced";
26
37
  readonly SKIPPED: "skipped";
@@ -104,21 +115,21 @@ declare const ServerVerificationSchema: z.ZodObject<{
104
115
  }>, "many">;
105
116
  summary: z.ZodString;
106
117
  }, "strip", z.ZodTypeAny, {
118
+ verdict: string | null;
107
119
  flows: {
108
120
  status: string;
109
121
  name: string;
110
122
  }[];
111
- verificationId: string;
112
- verdict: string | null;
113
123
  summary: string;
124
+ verificationId: string;
114
125
  }, {
126
+ verdict: string | null;
115
127
  flows: {
116
128
  status: string;
117
129
  name: string;
118
130
  }[];
119
- verificationId: string;
120
- verdict: string | null;
121
131
  summary: string;
132
+ verificationId: string;
122
133
  }>;
123
134
  export type ServerVerification = z.infer<typeof ServerVerificationSchema>;
124
135
  /**
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Optional cloud sync for saved flows. "Logged in" here means the two cloud env vars are set (written by
3
- * `reticle login` later; settable by hand today): the hosted URL and an API key from the Reticle Cloud
3
+ * `reticle login` later; settable by hand today): the hosted URL and an API key from the Reticle
4
4
  * dashboard. When present, a freshly-saved flow is pushed to `POST /v1/flows` so the team's regression
5
5
  * suite lives in the cloud — surviving refactors and runnable in CI. When absent, sync is a no-op and
6
6
  * everything stays 100% local (the "no phone-home" default: nothing leaves the machine unless you opt in).
@@ -30,6 +30,69 @@ export function resolveCloudConfig(env) {
30
30
  return null;
31
31
  return { url: url.replace(/\/+$/, ''), apiKey };
32
32
  }
33
+ /**
34
+ * Budget for a cloud call. Node's `fetch` has NO default timeout, so a connection that opens and then
35
+ * stalls never settles — and the caller `await`s it forever. In `reticle_flow_verify` that is an MCP
36
+ * tool call that never returns, and in `reticle cloud …` a terminal that prints nothing.
37
+ *
38
+ * One shared budget covers every call here: they are all small JSON request/response pairs against the
39
+ * same API, so there is nothing to tune per site. The ONE exception is the hosted verification submit,
40
+ * which blocks while a real browser runs the suite server-side — a normal response there is minutes,
41
+ * not seconds, so it gets its own (still bounded) budget rather than dragging the common one up.
42
+ */
43
+ const CLOUD_FETCH_TIMEOUT_MS = 30_000;
44
+ export const CLOUD_VERIFY_TIMEOUT_MS = 120_000;
45
+ /** Names Node gives an abort: `AbortSignal.timeout` raises TimeoutError, an explicit abort AbortError. */
46
+ const TIMEOUT_ERROR_NAMES = new Set(['TimeoutError', 'AbortError']);
47
+ /**
48
+ * A header value `fetch` can actually send. Anything above U+00FF is not a valid ByteString.
49
+ *
50
+ * The one that happens in practice is an ellipsis. A dashboard masks a key as `rk_live_…` so it is
51
+ * never shown in full, somebody copies what they can SEE into `.env`, and the request dies deep
52
+ * inside undici with "Cannot convert argument to a ByteString because the character at index 15 has
53
+ * a value of 8230" — a byte offset into a header the user never wrote, naming no variable, no file
54
+ * and no fix.
55
+ */
56
+ const UNSENDABLE_HEADER_CHAR = /[^ -ÿ]/;
57
+ /** Which knob holds the credential behind a header, so the error can name the thing to go and edit. */
58
+ const CREDENTIAL_SOURCE = {
59
+ authorization: `${CloudEnv.KEY} (or the session from \`reticle login\`)`,
60
+ };
61
+ /**
62
+ * Refuse a request whose headers cannot be encoded, before it is sent.
63
+ *
64
+ * Before dialling on purpose: a credential that cannot be encoded cannot succeed, so the round trip
65
+ * buys only a slower and less specific failure.
66
+ */
67
+ function assertSendableHeaders(headers) {
68
+ for (const [name, value] of Object.entries(headers)) {
69
+ if (!UNSENDABLE_HEADER_CHAR.test(value))
70
+ continue;
71
+ const source = CREDENTIAL_SOURCE[name];
72
+ throw new Error(`the ${name} header carries a character that cannot be sent, which means the value is truncated — ` +
73
+ `a credential displayed masked as \`rk_live_…\` keeps that ellipsis when it is copied. ` +
74
+ `Paste the full value${source === undefined ? '' : ` into ${source}`}.`);
75
+ }
76
+ }
77
+ /**
78
+ * The fetch every cloud call goes through. Adds the abort signal, and turns the abort into a message
79
+ * that says what happened and what to do — a bare `AbortError` reaching a user or an agent is a riddle,
80
+ * and the agent-facing half of this product is judged on whether its errors are actionable.
81
+ */
82
+ export async function cloudFetch(url, init, timeoutMs = CLOUD_FETCH_TIMEOUT_MS) {
83
+ assertSendableHeaders(init.headers);
84
+ try {
85
+ return await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
86
+ }
87
+ catch (err) {
88
+ if (err instanceof Error && TIMEOUT_ERROR_NAMES.has(err.name)) {
89
+ throw new Error(`Reticle request timed out after ${Math.round(timeoutMs / 1000)}s: ${init.method} ${url}. ` +
90
+ `The server accepted the connection but never answered. Check the network and ${CloudEnv.URL}, then retry — ` +
91
+ `verification works locally without cloud.`);
92
+ }
93
+ throw err;
94
+ }
95
+ }
33
96
  export const SyncOutcome = {
34
97
  SYNCED: 'synced',
35
98
  SKIPPED: 'skipped',
@@ -0,0 +1,110 @@
1
+ /**
2
+ * The derived records that ride along with runs, and the file each one lives in.
3
+ *
4
+ * A list rather than three hand-written blocks so adding a fourth is one line and cannot be
5
+ * half-done — the bundle, the hashing and the reporting all walk this.
6
+ */
7
+ export declare const DERIVED_RECORDS: readonly [{
8
+ readonly kind: "impact";
9
+ readonly file: "impact.json";
10
+ }, {
11
+ readonly kind: "flake";
12
+ readonly file: "flake.json";
13
+ }, {
14
+ readonly kind: "intent";
15
+ readonly file: "intent.json";
16
+ }];
17
+ export type DerivedKind = (typeof DERIVED_RECORDS)[number]['kind'];
18
+ /** What the machine reads from disk. Injected so a cycle is testable with no filesystem at all. */
19
+ export interface SyncSource {
20
+ /** Every run artifact currently on disk. */
21
+ runs: () => ReadonlyArray<{
22
+ runId: string;
23
+ payload: unknown;
24
+ }>;
25
+ /** Every saved flow. Small, upserted by name, so they ride along whenever anything else does. */
26
+ flows: () => readonly unknown[];
27
+ /** One derived record, or undefined when the file is absent. */
28
+ derived: (kind: DerivedKind) => unknown;
29
+ }
30
+ /** What the machine writes back. Separated from reads so a dry run is a source with no sink. */
31
+ export interface SyncSink {
32
+ /** Persist the triage decisions pulled from the dashboard. */
33
+ writeIssues: (issues: PulledIssues) => void;
34
+ /** Persist the cursor and the bookkeeping. */
35
+ writeState: (state: CloudSyncState) => void;
36
+ }
37
+ /** A decision a human made on the dashboard, as the machine stores it. */
38
+ export interface PulledIssue {
39
+ status: string;
40
+ flowName: string | null;
41
+ title: string;
42
+ at: number;
43
+ }
44
+ export interface PulledIssues {
45
+ /** Keyed by the server's fingerprint — stable across runs, which is the point of it. */
46
+ triage: Record<string, PulledIssue>;
47
+ }
48
+ /** `.reticle/cloud-state.json`. This machine's side of the conversation, never git-checked. */
49
+ export interface CloudSyncState {
50
+ /** Opaque. Handed back to the server verbatim; the machine must never parse or invent one. */
51
+ cursor?: string;
52
+ lastPushAt?: number;
53
+ lastPullAt?: number;
54
+ /** The last failure, kept so `reticle sync` can say why it is behind instead of just "0 sent". */
55
+ lastError?: string;
56
+ }
57
+ export interface SyncReport {
58
+ ok: boolean;
59
+ /** Runs the server accepted this cycle. */
60
+ runsSent: number;
61
+ /** Runs it refused, with the reason, so a bad artifact is visible rather than silently stuck. */
62
+ runsRejected: Array<{
63
+ index: number;
64
+ reason: string;
65
+ }>;
66
+ flowsSent: number;
67
+ /** Which derived records had actually moved. Empty on a quiet cycle, which is the normal case. */
68
+ derivedSent: DerivedKind[];
69
+ /** Decisions collected from the dashboard. */
70
+ pulled: number;
71
+ /** True when the pull page was full — call again now rather than waiting for the next tick. */
72
+ morePending: boolean;
73
+ /**
74
+ * The repo holds NO artifacts at all — no runs, no flows, no derived records.
75
+ *
76
+ * Distinct from "everything here is already pushed", which is the healthy steady state and looks
77
+ * identical from the outside. An empty repo usually means the app announces no projectId, so its
78
+ * runs are pooling into a different root and this binding will never report anything. Tracked so
79
+ * the summary can tell a user which of the two they are looking at.
80
+ */
81
+ localIsEmpty?: boolean;
82
+ /** Set when the cycle could not complete. The local record is untouched either way. */
83
+ error?: string;
84
+ }
85
+ export interface SyncDeps {
86
+ config: {
87
+ url: string;
88
+ apiKey: string;
89
+ };
90
+ source: SyncSource;
91
+ sink: SyncSink;
92
+ state: CloudSyncState;
93
+ now: () => number;
94
+ /** Injected so a cycle can be driven against a scripted server with no network. */
95
+ request: (url: string, init: {
96
+ method: string;
97
+ headers: Record<string, string>;
98
+ body?: string;
99
+ }) => Promise<{
100
+ status: number;
101
+ text: string;
102
+ }>;
103
+ }
104
+ /**
105
+ * Run one cycle. Never throws: the local record is already safe on disk, and a sync that can take
106
+ * down the thing it is backing up is worse than no sync.
107
+ */
108
+ export declare function runSyncCycle(deps: SyncDeps): Promise<SyncReport>;
109
+ /** One line a human can read, for the CLI and the daemon log. */
110
+ export declare function describeSync(report: SyncReport): string;
@@ -0,0 +1,270 @@
1
+ /**
2
+ * One full conversation with the dashboard: ask, send the difference, collect what came back.
3
+ *
4
+ * ── WHY IT LOOKS LIKE THIS ─────────────────────────────────────────────────────────────────────
5
+ * `.reticle/` is the source of truth and always will be. Reticle has to work with no account and no
6
+ * network; a verification that blocks on an HTTP round trip is one somebody switches off. So the
7
+ * dashboard is a REPLICA, and this is the replication protocol:
8
+ *
9
+ * 1. ASK GET /v1/sync/status → run ids it already holds, a content hash per derived record
10
+ * 2. SEND POST /v1/sync → only unseen runs, only records whose hash moved
11
+ * 3. COLLECT GET /v1/sync/pull → decisions a human made there, since our cursor
12
+ * 4. APPLY write them to .reticle/issues.json so the HUD and the next run can see them
13
+ *
14
+ * ASK-THEN-SEND, not send-everything. A three-hour session rewrites impact.json on every tool call;
15
+ * uploading it each time would re-send the same megabytes dozens of times, and a sync that costs
16
+ * that much is one people turn off — which is the real failure, because a dashboard nobody syncs to
17
+ * is a dashboard nobody opens. When nothing has moved, a cycle sends nothing at all, and that is
18
+ * what makes running it on a timer affordable.
19
+ *
20
+ * HASHES, NOT TIMESTAMPS. Two machines comparing their own clocks is a guess: they drift, and a
21
+ * laptop that was asleep will cheerfully decide it is current. A content hash is an answer.
22
+ *
23
+ * BOTH DIRECTIONS, ALWAYS. The pull runs even when there is nothing to push — a quiet machine is
24
+ * precisely the one whose dashboard somebody has been triaging on, so skipping the pull when the
25
+ * push is empty would starve the direction that matters most.
26
+ *
27
+ * NOTHING IS DELETED LOCALLY ON SUCCESS. A lost response costs one redundant upload rather than a
28
+ * hole in the record, and the server dedupes by run id anyway.
29
+ *
30
+ * Every dependency is injected — the clock, the fetch, the reads and writes — because this is the
31
+ * one piece of Reticle that talks to a network AND to a disk, and it must be provable without either.
32
+ */
33
+ import { ReticleDir } from '@reticlehq/core';
34
+ import { hashPayload } from './sync-hash.js';
35
+ /** The server's own doors. Kept beside the code that calls them, like the other cloud paths. */
36
+ const SYNC_PATH = '/v1/sync';
37
+ const SYNC_STATUS_PATH = '/v1/sync/status';
38
+ const SYNC_PULL_PATH = '/v1/sync/pull';
39
+ /**
40
+ * The derived records that ride along with runs, and the file each one lives in.
41
+ *
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
+ */
45
+ export const DERIVED_RECORDS = [
46
+ { kind: 'impact', file: ReticleDir.IMPACT_FILE },
47
+ { kind: 'flake', file: ReticleDir.FLAKE_FILE },
48
+ { kind: 'intent', file: ReticleDir.INTENT_FILE },
49
+ ];
50
+ const asJson = (text) => {
51
+ try {
52
+ return JSON.parse(text);
53
+ }
54
+ catch {
55
+ return undefined;
56
+ }
57
+ };
58
+ const isRecord = (v) => 'object' === typeof v && null !== v;
59
+ /**
60
+ * Run one cycle. Never throws: the local record is already safe on disk, and a sync that can take
61
+ * down the thing it is backing up is worse than no sync.
62
+ */
63
+ export async function runSyncCycle(deps) {
64
+ const empty = {
65
+ ok: false,
66
+ runsSent: 0,
67
+ runsRejected: [],
68
+ flowsSent: 0,
69
+ derivedSent: [],
70
+ pulled: 0,
71
+ morePending: false,
72
+ };
73
+ const call = async (path, init) => {
74
+ const res = await deps.request(`${deps.config.url}${path}`, {
75
+ method: init?.method ?? 'GET',
76
+ headers: {
77
+ 'content-type': 'application/json',
78
+ authorization: `Bearer ${deps.config.apiKey}`,
79
+ },
80
+ ...(init?.body === undefined ? {} : { body: init.body }),
81
+ });
82
+ return { status: res.status, json: asJson(res.text), text: res.text };
83
+ };
84
+ const nextState = { ...deps.state };
85
+ try {
86
+ // 1. ASK.
87
+ const status = await call(SYNC_STATUS_PATH);
88
+ if (200 !== status.status) {
89
+ const error = `status ${String(status.status)}: ${status.text.slice(0, 200)}`;
90
+ deps.sink.writeState({ ...nextState, lastError: error });
91
+ return { ...empty, error };
92
+ }
93
+ const held = isRecord(status.json) ? status.json : {};
94
+ /*
95
+ * Validated, not trusted. `new Set(someString)` builds a set of CHARACTERS, so a `knownRunIds`
96
+ * that arrived as a string made `known.has('a')` true and silently skipped any run whose id was
97
+ * one of those letters — the client deciding, on the server's malformed word, not to upload
98
+ * something the server does not have. Silent data loss is the one failure this protocol must
99
+ * not have, and an unreadable answer has to mean "it knows nothing", never "it knows this".
100
+ */
101
+ const known = new Set(Array.isArray(held.knownRunIds)
102
+ ? held.knownRunIds.filter((id) => 'string' === typeof id)
103
+ : []);
104
+ // Same rule for the hashes: only a string can equal a hash we computed, so anything else reads
105
+ // as "unknown" and the record is sent once. Re-sending costs a request; skipping costs the data.
106
+ const hashes = isRecord(held.stateHashes) ? held.stateHashes : {};
107
+ // 2. SEND — only what the server does not already have.
108
+ const allRuns = deps.source.runs();
109
+ const unsent = allRuns.filter((r) => !known.has(r.runId));
110
+ const derivedSent = [];
111
+ const bundle = {};
112
+ if (unsent.length > 0)
113
+ bundle['runs'] = unsent.map((r) => r.payload);
114
+ for (const { kind } of DERIVED_RECORDS) {
115
+ const payload = deps.source.derived(kind);
116
+ if (payload === undefined)
117
+ continue;
118
+ // The one comparison the whole protocol rests on. Same hash, do not send it.
119
+ if (hashPayload(payload) === hashes[kind])
120
+ continue;
121
+ bundle[kind] = payload;
122
+ derivedSent.push(kind);
123
+ }
124
+ // Flows are small and upserted by name, so they ride along whenever anything else does rather
125
+ // than earning a round trip of their own.
126
+ const flows = bundle['runs'] === undefined && 0 === derivedSent.length ? [] : deps.source.flows();
127
+ if (flows.length > 0)
128
+ bundle['flows'] = flows;
129
+ let runsSent = 0;
130
+ let flowsSent = 0;
131
+ let runsRejected = [];
132
+ if (Object.keys(bundle).length > 0) {
133
+ const pushed = await call(SYNC_PATH, { method: 'POST', body: JSON.stringify(bundle) });
134
+ if (200 !== pushed.status) {
135
+ const error = `sync ${String(pushed.status)}: ${pushed.text.slice(0, 200)}`;
136
+ deps.sink.writeState({ ...nextState, lastError: error });
137
+ return { ...empty, error };
138
+ }
139
+ const body = isRecord(pushed.json) ? pushed.json : {};
140
+ const runs = isRecord(body['runs']) ? body['runs'] : {};
141
+ const flowsPart = isRecord(body['flows']) ? body['flows'] : {};
142
+ runsSent = 'number' === typeof runs['accepted'] ? runs['accepted'] : 0;
143
+ flowsSent = 'number' === typeof flowsPart['accepted'] ? flowsPart['accepted'] : 0;
144
+ runsRejected = Array.isArray(runs['rejected'])
145
+ ? runs['rejected']
146
+ : [];
147
+ nextState.lastPushAt = deps.now();
148
+ }
149
+ // 3. COLLECT — always, even when there was nothing to send.
150
+ const query = nextState.cursor === undefined ? '' : `?since=${encodeURIComponent(nextState.cursor)}`;
151
+ const pull = await call(`${SYNC_PULL_PATH}${query}`);
152
+ if (200 !== pull.status) {
153
+ // The push already landed; report it rather than throwing the whole cycle away.
154
+ const error = `pull ${String(pull.status)}: ${pull.text.slice(0, 200)}`;
155
+ deps.sink.writeState({ ...nextState, lastError: error });
156
+ return { ...empty, ok: false, runsSent, flowsSent, derivedSent, runsRejected, error };
157
+ }
158
+ const pulled = isRecord(pull.json) ? pull.json : {};
159
+ const decisions = pulled.triage ?? [];
160
+ // 4. APPLY.
161
+ if (decisions.length > 0) {
162
+ const triage = {};
163
+ for (const d of decisions) {
164
+ triage[d.fingerprint] = {
165
+ status: d.status,
166
+ flowName: d.flowName ?? null,
167
+ title: d.title ?? '',
168
+ at: d.at,
169
+ };
170
+ }
171
+ deps.sink.writeIssues({ triage });
172
+ }
173
+ // The cursor is written even on an empty page: it is the server's, and it never goes backwards.
174
+ if ('string' === typeof pulled.cursor)
175
+ nextState.cursor = pulled.cursor;
176
+ nextState.lastPullAt = deps.now();
177
+ delete nextState.lastError;
178
+ deps.sink.writeState(nextState);
179
+ return {
180
+ ok: true,
181
+ runsSent,
182
+ runsRejected,
183
+ flowsSent,
184
+ derivedSent,
185
+ pulled: decisions.length,
186
+ morePending: true === pulled.more,
187
+ /*
188
+ * Nothing on disk at all, as opposed to nothing NEW. Computed from what the source offered
189
+ * before any cursor filtering, because a repo whose runs were all already pushed is healthy
190
+ * and a repo that has never recorded one is usually misconfigured.
191
+ */
192
+ localIsEmpty: 0 === allRuns.length &&
193
+ 0 === deps.source.flows().length &&
194
+ DERIVED_RECORDS.every(({ kind }) => deps.source.derived(kind) === undefined),
195
+ };
196
+ }
197
+ catch (error) {
198
+ // A network that is down is not an error condition for a local-first tool; it is Tuesday.
199
+ const message = describeTransportError(error, deps.config.url);
200
+ deps.sink.writeState({ ...nextState, lastError: message });
201
+ return { ...empty, error: message };
202
+ }
203
+ }
204
+ /**
205
+ * Say WHICH host failed and WHY.
206
+ *
207
+ * Node's fetch rejects with the bare string "fetch failed" and hides the real reason — a refused
208
+ * connection, a bad hostname, an expired certificate — one level down in `cause`. A machine that
209
+ * has quietly stopped syncing shows that string in `reticle whoami`, and on its own it is
210
+ * indistinguishable from every other network problem, so the person reading it learns nothing and
211
+ * checks nothing. Naming the origin and the underlying code turns it into one thing to look at.
212
+ */
213
+ function describeTransportError(error, url) {
214
+ if (!(error instanceof Error))
215
+ return String(error);
216
+ const cause = error.cause;
217
+ if (!(cause instanceof Error))
218
+ return `${error.message} (${url})`;
219
+ const code = cause.code;
220
+ // The code and the message are each sometimes empty — an aggregate DNS failure carries a code and
221
+ // no text, a TLS failure the reverse. Joined only when both are there, so the line never trails
222
+ // off into a dangling separator.
223
+ const parts = ['string' === typeof code && code.length > 0 ? code : '', cause.message].filter((part) => part.length > 0);
224
+ const detail = parts.join(': ');
225
+ return 0 === detail.length
226
+ ? `${error.message} (${url})`
227
+ : `${error.message} — ${detail} (${url})`;
228
+ }
229
+ /** One line a human can read, for the CLI and the daemon log. */
230
+ export function describeSync(report) {
231
+ if (report.error !== undefined)
232
+ return `sync failed — ${report.error}`;
233
+ const sent = [];
234
+ if (report.runsSent > 0)
235
+ sent.push(`${String(report.runsSent)} run(s)`);
236
+ if (report.flowsSent > 0)
237
+ sent.push(`${String(report.flowsSent)} flow(s)`);
238
+ if (report.derivedSent.length > 0)
239
+ sent.push(report.derivedSent.join(', '));
240
+ /*
241
+ * "Nothing to send" is a statement about the QUEUE, and it is false the moment the queue was full
242
+ * and the server threw it away. Built from what was accepted, the sentence used to read "nothing
243
+ * to send, 3 rejected" — something was very much sent, and the reader is told both that it was
244
+ * not and nothing about why.
245
+ */
246
+ const push = sent.length > 0
247
+ ? `sent ${sent.join(' + ')}`
248
+ : report.runsRejected.length > 0
249
+ ? 'nothing accepted'
250
+ : true === report.localIsEmpty
251
+ ? // Not the same statement as "nothing to send", which describes a repo that is simply up
252
+ // to date. This one has never recorded anything, which for a LINKED repo usually means
253
+ // the app announces no projectId and its runs are landing under a different root.
254
+ 'nothing recorded here yet — if this app has been driven, it is reporting somewhere else'
255
+ : 'nothing to send';
256
+ const pull = 0 === report.pulled
257
+ ? ''
258
+ : `, pulled ${String(report.pulled)} decision(s)${report.morePending ? ' (more waiting)' : ''}`;
259
+ /*
260
+ * One reason, not a count. A rejection count tells somebody they have a problem and nothing about
261
+ * which problem — and these arrive from a BACKGROUND daemon, so the summary line is often the only
262
+ * place anybody ever sees it. Rejections in one cycle almost always share a cause (a version skew
263
+ * refuses every payload the same way), so the first reason plus the count is the whole story
264
+ * without printing a line per run; `reticle sync` still lists them all.
265
+ */
266
+ const bad = 0 === report.runsRejected.length
267
+ ? ''
268
+ : `, ${String(report.runsRejected.length)} rejected — ${report.runsRejected[0]?.reason ?? 'no reason given'}`;
269
+ return `${push}${pull}${bad}`;
270
+ }
@@ -0,0 +1,69 @@
1
+ import { type SyncReport } from './sync-cycle.js';
2
+ import type { ProjectCloud } from './cloud-config.js';
3
+ /** How often the daemon cycles. See the note above on why this is a constant and not a curve. */
4
+ export declare const DAEMON_SYNC_INTERVAL_MS = 60000;
5
+ /**
6
+ * The cadence while something is actually happening.
7
+ *
8
+ * A fixed minute is the wrong shape for both states it has to cover. During a drive the ledger
9
+ * changes on every tool call and a minute of lag is what makes a dashboard feel dead; idle, a minute
10
+ * is already more often than nothing has changed deserves.
11
+ *
12
+ * So the interval follows the work: a cycle that MOVED something earns the fast rate, a cycle that
13
+ * moved nothing backs off to the slow one. No session plumbing and no new configuration — activity
14
+ * is inferred from what the last cycle actually sent, which is the only honest evidence available.
15
+ *
16
+ * Deliberately not per-tool-call. A push in the tool path would put the network in the agent's inner
17
+ * loop, and verification working with the network down is a promise this product makes.
18
+ */
19
+ export declare const DAEMON_SYNC_ACTIVE_INTERVAL_MS = 5000;
20
+ export interface SyncDaemonDeps {
21
+ reticleRoot: string;
22
+ /** Resolved per tick, not once: a repo linked while the daemon is alive starts syncing itself. */
23
+ cloud: () => Promise<ProjectCloud>;
24
+ /**
25
+ * Every OTHER `.reticle` root on this machine that might be linked.
26
+ *
27
+ * One daemon serves many projects — that is what `artifactRootFor` exists for — and this loop
28
+ * pushed exactly one of them: whichever directory the daemon happened to be started in. Every
29
+ * other linked repo went silent, and silent is indistinguishable from "nobody verified anything",
30
+ * which is the worst possible failure for a dashboard somebody is deciding budget on.
31
+ *
32
+ * Optional: a caller that does not supply it gets exactly the old single-root behaviour.
33
+ */
34
+ otherRoots?: () => Promise<readonly string[]>;
35
+ /** Resolve the link for a root that is not this daemon's own. Required to use `otherRoots`. */
36
+ cloudFor?: (root: string) => Promise<ProjectCloud>;
37
+ now?: () => number;
38
+ intervalMs?: number;
39
+ /** Injected for the test; the real one is `fetch`. */
40
+ request?: (url: string, init: {
41
+ method: string;
42
+ headers: Record<string, string>;
43
+ body?: string;
44
+ }) => Promise<{
45
+ status: number;
46
+ text: string;
47
+ }>;
48
+ }
49
+ export interface SyncDaemon {
50
+ /** Run one cycle now, whatever the timer is doing. Returns undefined when not linked. */
51
+ syncNow: () => Promise<SyncReport | undefined>;
52
+ /**
53
+ * Something worth shipping just landed — cycle soon rather than at the next tick.
54
+ *
55
+ * A verification RUN is the artifact people wait on, and making them wait up to a full interval
56
+ * for it is the difference between a dashboard that reflects the work and one that lags it. The
57
+ * counters keep the timer: they are a rolling aggregate, and a write per tool call to move a
58
+ * number nobody is watching that second is exactly the cost the interval exists to avoid.
59
+ *
60
+ * Coalescing, never additive: many nudges in a burst collapse into one cycle.
61
+ */
62
+ nudge: () => void;
63
+ stop: () => void;
64
+ }
65
+ /**
66
+ * Start the loop. Safe to call for an UNLINKED project: it resolves the link on every tick and does
67
+ * nothing until one appears, which is what lets `reticle link` take effect without a restart.
68
+ */
69
+ export declare function startSyncDaemon(deps: SyncDaemonDeps): SyncDaemon;