@reticlehq/server 2.3.0 → 2.5.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 (424) hide show
  1. package/SKILL.md +702 -0
  2. package/dist/bridge/auth-failure-reason.d.ts +19 -0
  3. package/dist/bridge/auth-failure-reason.js +29 -0
  4. package/dist/{bridge-security.d.ts → bridge/bridge-security.d.ts} +1 -1
  5. package/dist/{bridge-security.js → bridge/bridge-security.js} +1 -1
  6. package/dist/{bridge.d.ts → bridge/bridge.d.ts} +1 -1
  7. package/dist/{bridge.js → bridge/bridge.js} +105 -15
  8. package/dist/{bridge.test-harness.d.ts → bridge/bridge.test-harness.d.ts} +6 -11
  9. package/dist/{bridge.test-harness.js → bridge/bridge.test-harness.js} +21 -9
  10. package/dist/{pairing-token.js → bridge/pairing-token.js} +1 -1
  11. package/dist/capsule/capsule-store.d.ts +8 -3
  12. package/dist/capsule/capsule-store.js +7 -2
  13. package/dist/capsule/capsule.js +3 -3
  14. package/dist/capsule/causal-summary.d.ts +8 -0
  15. package/dist/capsule/causal-summary.js +22 -12
  16. package/dist/capsule/divergence.d.ts +9 -4
  17. package/dist/capsule/divergence.js +8 -8
  18. package/dist/capsule/predicate-to-links.js +11 -8
  19. package/dist/{cli-doctor.js → cli/cli-doctor.js} +9 -2
  20. package/dist/{cli-flow-commands.d.ts → cli/cli-flow-commands.d.ts} +2 -2
  21. package/dist/{cli-flow-commands.js → cli/cli-flow-commands.js} +15 -15
  22. package/dist/{cli-launch.d.ts → cli/cli-launch.d.ts} +32 -3
  23. package/dist/cli/cli-launch.js +171 -0
  24. package/dist/cli/cli-parse.d.ts +143 -0
  25. package/dist/{cli-parse.js → cli/cli-parse.js} +146 -65
  26. package/dist/{cli-port.d.ts → cli/cli-port.d.ts} +15 -0
  27. package/dist/{cli-port.js → cli/cli-port.js} +29 -8
  28. package/dist/cli/cli-update-commands.d.ts +4 -0
  29. package/dist/cli/cli-update-commands.js +93 -0
  30. package/dist/{cli-verify.js → cli/cli-verify.js} +14 -14
  31. package/dist/{cloud-cli.js → cli/cloud-cli.js} +29 -24
  32. package/dist/cli.d.ts +2 -2
  33. package/dist/cli.js +182 -93
  34. package/dist/cloud/cloud-config.d.ts +0 -1
  35. package/dist/cloud/cloud-config.js +9 -9
  36. package/dist/cloud/cloud-sync.d.ts +1 -7
  37. package/dist/cloud/cloud-sync.js +12 -12
  38. package/dist/crawl/crawl-empty.d.ts +16 -0
  39. package/dist/crawl/crawl-empty.js +24 -0
  40. package/dist/crawl/crawl-tools.js +4 -1
  41. package/dist/crawl/crawl.js +26 -7
  42. package/dist/daemon/daemon-resilience.d.ts +49 -0
  43. package/dist/daemon/daemon-resilience.js +191 -0
  44. package/dist/daemon/daemon-usefulness.d.ts +61 -0
  45. package/dist/daemon/daemon-usefulness.js +62 -0
  46. package/dist/{daemon.d.ts → daemon/daemon.d.ts} +33 -8
  47. package/dist/{daemon.js → daemon/daemon.js} +80 -48
  48. package/dist/daemon/idle-grace.d.ts +32 -0
  49. package/dist/daemon/idle-grace.js +41 -0
  50. package/dist/{idle-shutdown.d.ts → daemon/idle-shutdown.d.ts} +13 -0
  51. package/dist/{idle-shutdown.js → daemon/idle-shutdown.js} +27 -4
  52. package/dist/domain/domain-model.js +2 -2
  53. package/dist/events/body-failures.js +8 -8
  54. package/dist/events/contradictions.d.ts +1 -1
  55. package/dist/events/contradictions.js +60 -10
  56. package/dist/events/echo-mismatch.js +10 -10
  57. package/dist/events/event-filters.js +6 -6
  58. package/dist/events/json-salvage.d.ts +2 -1
  59. package/dist/events/json-salvage.js +6 -6
  60. package/dist/events/observed-in-window.d.ts +12 -0
  61. package/dist/events/observed-in-window.js +21 -0
  62. package/dist/events/predicate-eval.d.ts +43 -20
  63. package/dist/events/predicate-eval.js +159 -55
  64. package/dist/events/predicate.d.ts +2 -2
  65. package/dist/events/predicate.js +112 -35
  66. package/dist/events/reconcile.js +5 -5
  67. package/dist/events/ring-buffer.js +1 -1
  68. package/dist/events/stale-response.js +6 -6
  69. package/dist/events/testid-near-miss.d.ts +22 -0
  70. package/dist/events/testid-near-miss.js +33 -0
  71. package/dist/events/unit-mismatch.js +8 -8
  72. package/dist/flows/affected-tools.js +19 -4
  73. package/dist/flows/affected.js +1 -1
  74. package/dist/flows/annotate-target.d.ts +1 -0
  75. package/dist/flows/annotate-target.js +20 -0
  76. package/dist/flows/annotate-tools.js +25 -3
  77. package/dist/flows/annotate.js +17 -4
  78. package/dist/flows/assertion-integrity.d.ts +2 -1
  79. package/dist/flows/assertion-tiers-store.d.ts +3 -66
  80. package/dist/flows/assertion-tiers-store.js +2 -2
  81. package/dist/flows/attributed-failure.d.ts +17 -0
  82. package/dist/flows/attributed-failure.js +20 -0
  83. package/dist/flows/buddy-status.d.ts +2 -1
  84. package/dist/flows/buddy-status.js +2 -2
  85. package/dist/flows/coverage.d.ts +3 -2
  86. package/dist/flows/coverage.js +2 -2
  87. package/dist/flows/decision.d.ts +0 -6
  88. package/dist/flows/decision.js +64 -8
  89. package/dist/flows/empty-flow.d.ts +6 -0
  90. package/dist/flows/empty-flow.js +24 -0
  91. package/dist/flows/flake-store.d.ts +0 -1
  92. package/dist/flows/flake-store.js +1 -1
  93. package/dist/flows/flake.d.ts +2 -1
  94. package/dist/flows/flake.js +2 -2
  95. package/dist/flows/flow-classify.js +12 -0
  96. package/dist/flows/flow-replay-run.js +4 -4
  97. package/dist/flows/flow-replay.d.ts +5 -7
  98. package/dist/flows/flow-replay.js +114 -47
  99. package/dist/flows/flow-report.js +1 -1
  100. package/dist/flows/flow-scope.d.ts +1 -6
  101. package/dist/flows/flow-step-runners.d.ts +26 -6
  102. package/dist/flows/flow-step-runners.js +157 -6
  103. package/dist/flows/flow-success.js +20 -15
  104. package/dist/flows/flow-tools.js +67 -13
  105. package/dist/flows/flows.js +15 -11
  106. package/dist/flows/gate.d.ts +4 -3
  107. package/dist/flows/gate.js +1 -1
  108. package/dist/flows/heal-run.d.ts +0 -8
  109. package/dist/flows/heal-run.js +5 -5
  110. package/dist/flows/heal.js +3 -3
  111. package/dist/flows/predicate-to-expect.d.ts +38 -0
  112. package/dist/flows/predicate-to-expect.js +95 -0
  113. package/dist/flows/replay.d.ts +16 -1
  114. package/dist/flows/replay.js +58 -22
  115. package/dist/flows/role-anchor-nearest.d.ts +6 -0
  116. package/dist/flows/role-anchor-nearest.js +45 -0
  117. package/dist/flows/server-verify.d.ts +1 -1
  118. package/dist/flows/server-verify.js +5 -5
  119. package/dist/flows/verify-change-tools.js +32 -10
  120. package/dist/flows/verify-change-verdict.d.ts +15 -0
  121. package/dist/flows/verify-change-verdict.js +25 -0
  122. package/dist/flows/watch-batcher.d.ts +3 -2
  123. package/dist/honesty/already-true.d.ts +2 -0
  124. package/dist/honesty/already-true.js +31 -0
  125. package/dist/honesty/blind-spots.d.ts +16 -3
  126. package/dist/honesty/blind-spots.js +24 -12
  127. package/dist/honesty/honesty.d.ts +3 -2
  128. package/dist/honesty/honesty.js +4 -4
  129. package/dist/honesty/uncaptured-bodies.js +1 -1
  130. package/dist/honesty/unread-outcome.js +2 -2
  131. package/dist/honesty/verified.d.ts +22 -3
  132. package/dist/honesty/verified.js +48 -8
  133. package/dist/http-server.js +14 -7
  134. package/dist/hunt/hunt-report.d.ts +2 -1
  135. package/dist/hunt/hunt-report.js +2 -2
  136. package/dist/index.d.ts +8 -6
  137. package/dist/index.js +32 -25
  138. package/dist/init/agent-rules.d.ts +2 -1
  139. package/dist/init/agent-rules.js +45 -7
  140. package/dist/init/app-choice.d.ts +20 -0
  141. package/dist/init/app-choice.js +25 -0
  142. package/dist/init/astro-host.d.ts +21 -0
  143. package/dist/init/astro-host.js +27 -0
  144. package/dist/init/astro-patch.d.ts +16 -0
  145. package/dist/init/astro-patch.js +240 -0
  146. package/dist/init/capabilities.d.ts +17 -0
  147. package/dist/init/capabilities.js +54 -0
  148. package/dist/init/cra.d.ts +37 -0
  149. package/dist/init/cra.js +90 -0
  150. package/dist/init/cursor.js +39 -4
  151. package/dist/init/desktop-doctor.d.ts +3 -2
  152. package/dist/init/desktop-doctor.js +4 -4
  153. package/dist/init/detect.d.ts +26 -0
  154. package/dist/init/detect.js +78 -5
  155. package/dist/init/mcp-registered.d.ts +12 -0
  156. package/dist/init/mcp-registered.js +14 -0
  157. package/dist/init/mcp.d.ts +27 -2
  158. package/dist/init/mcp.js +40 -4
  159. package/dist/init/next-patch.d.ts +39 -0
  160. package/dist/init/next-patch.js +196 -0
  161. package/dist/init/node-io.js +48 -6
  162. package/dist/init/patch-kind.d.ts +20 -0
  163. package/dist/init/patch-kind.js +10 -0
  164. package/dist/init/plan-framework.d.ts +50 -0
  165. package/dist/init/plan-framework.js +341 -0
  166. package/dist/init/plan.d.ts +119 -6
  167. package/dist/init/plan.js +230 -131
  168. package/dist/init/project-id.d.ts +5 -9
  169. package/dist/init/project-id.js +10 -22
  170. package/dist/init/run.d.ts +22 -0
  171. package/dist/init/run.js +393 -28
  172. package/dist/init/slash-command.d.ts +27 -0
  173. package/dist/init/slash-command.js +61 -0
  174. package/dist/init/snippets.d.ts +41 -3
  175. package/dist/init/snippets.js +185 -10
  176. package/dist/init/vite-config.d.ts +4 -10
  177. package/dist/init/vite-config.js +15 -7
  178. package/dist/init/workspace-apps.d.ts +23 -0
  179. package/dist/init/workspace-apps.js +68 -0
  180. package/dist/input/driven-redaction.d.ts +15 -0
  181. package/dist/input/driven-redaction.js +76 -0
  182. package/dist/input/network-detail.d.ts +9 -3
  183. package/dist/input/network-detail.js +43 -23
  184. package/dist/input/network-mock-tools.js +2 -2
  185. package/dist/input/network-mock.js +4 -4
  186. package/dist/input/real-input.js +8 -3
  187. package/dist/input/scroll-find.js +2 -2
  188. package/dist/input/scroll-tools.d.ts +0 -5
  189. package/dist/input/scroll-tools.js +11 -4
  190. package/dist/input/viewport-tools.js +2 -2
  191. package/dist/journal/ambient-store.d.ts +0 -2
  192. package/dist/journal/ambient-store.js +1 -1
  193. package/dist/journal/ambient.js +1 -1
  194. package/dist/journal/attach-journal.d.ts +2 -1
  195. package/dist/journal/deviation-report.js +6 -6
  196. package/dist/journal/deviation-service.js +1 -1
  197. package/dist/journal/envelope-store.d.ts +0 -2
  198. package/dist/journal/envelope-store.js +1 -1
  199. package/dist/journal/envelope.d.ts +2 -3
  200. package/dist/journal/envelope.js +3 -3
  201. package/dist/journal/journal-query.js +1 -1
  202. package/dist/journal/journal-recorder.d.ts +2 -1
  203. package/dist/journal/journal-recorder.js +2 -2
  204. package/dist/journal/rollups.js +9 -9
  205. package/dist/journal/session-end.d.ts +2 -1
  206. package/dist/journal/session-journal.js +4 -4
  207. package/dist/license/license.d.ts +0 -14
  208. package/dist/license/license.js +8 -8
  209. package/dist/log.d.ts +10 -0
  210. package/dist/log.js +11 -1
  211. package/dist/mcp/mcp-is-error.d.ts +13 -0
  212. package/dist/mcp/mcp-is-error.js +18 -0
  213. package/dist/mcp/mcp-outage.d.ts +31 -0
  214. package/dist/mcp/mcp-outage.js +48 -0
  215. package/dist/mcp/mcp-proxy.d.ts +124 -0
  216. package/dist/mcp/mcp-proxy.js +697 -0
  217. package/dist/{mcp.d.ts → mcp/mcp.d.ts} +13 -10
  218. package/dist/{mcp.js → mcp/mcp.js} +110 -35
  219. package/dist/mcp/proxy-handshake.d.ts +3 -0
  220. package/dist/mcp/proxy-handshake.js +54 -0
  221. package/dist/mcp/proxy-lifecycle.d.ts +64 -0
  222. package/dist/mcp/proxy-lifecycle.js +70 -0
  223. package/dist/mcp/tool-catalog-cache.d.ts +29 -0
  224. package/dist/mcp/tool-catalog-cache.js +55 -0
  225. package/dist/oracles/propose-consequences.d.ts +2 -1
  226. package/dist/oracles/propose-consequences.js +16 -11
  227. package/dist/oracles/self-instrument.d.ts +2 -1
  228. package/dist/oracles/self-instrument.js +1 -1
  229. package/dist/phenomena/phenomena.js +3 -3
  230. package/dist/platform.d.ts +16 -0
  231. package/dist/platform.js +16 -0
  232. package/dist/pool/playwright-launcher.d.ts +15 -2
  233. package/dist/pool/playwright-launcher.js +21 -2
  234. package/dist/project/fs-port.js +1 -1
  235. package/dist/project/project-tools.js +2 -2
  236. package/dist/runs/build-verification-run.js +1 -1
  237. package/dist/runs/repair-prompt.d.ts +0 -6
  238. package/dist/runs/repair-prompt.js +3 -3
  239. package/dist/runs/risk-classify.js +1 -1
  240. package/dist/runs/run-diff.d.ts +0 -2
  241. package/dist/runs/run-diff.js +3 -3
  242. package/dist/runs/run-tools.js +2 -2
  243. package/dist/runs/verification-sync.js +2 -2
  244. package/dist/runs/verify-http.js +1 -1
  245. package/dist/runs/verify-server.js +1 -1
  246. package/dist/session/command-timeout.d.ts +11 -3
  247. package/dist/session/command-timeout.js +2 -2
  248. package/dist/session/control-envelope.d.ts +6 -0
  249. package/dist/session/control-envelope.js +7 -1
  250. package/dist/session/dev-server-probe.d.ts +13 -0
  251. package/dist/session/dev-server-probe.js +52 -0
  252. package/dist/session/last-act.d.ts +17 -3
  253. package/dist/session/last-act.js +16 -4
  254. package/dist/session/live-control-tools.js +7 -2
  255. package/dist/session/live-control.js +1 -1
  256. package/dist/session/looks-like-dev-server.d.ts +14 -0
  257. package/dist/session/looks-like-dev-server.js +25 -0
  258. package/dist/session/no-session-diagnosis.d.ts +38 -0
  259. package/dist/session/no-session-diagnosis.js +70 -0
  260. package/dist/session/no-session-watch.d.ts +19 -0
  261. package/dist/session/no-session-watch.js +61 -0
  262. package/dist/session/observed-state.d.ts +4 -0
  263. package/dist/session/observed-state.js +19 -1
  264. package/dist/session/output-budget.js +1 -1
  265. package/dist/session/session-health.d.ts +8 -1
  266. package/dist/session/session-health.js +8 -7
  267. package/dist/session/session-info.d.ts +26 -7
  268. package/dist/session/session-info.js +42 -1
  269. package/dist/session/session-lease.d.ts +0 -11
  270. package/dist/session/session-lease.js +1 -1
  271. package/dist/session/session-manager.d.ts +3 -0
  272. package/dist/session/session-manager.js +58 -9
  273. package/dist/session/session-readiness.d.ts +1 -1
  274. package/dist/session/session-readiness.js +6 -1
  275. package/dist/session/session-reaper.js +1 -1
  276. package/dist/session/session-reconnect.d.ts +32 -0
  277. package/dist/session/session-reconnect.js +42 -0
  278. package/dist/session/session.d.ts +20 -2
  279. package/dist/session/session.js +76 -26
  280. package/dist/session/state-select.d.ts +1 -1
  281. package/dist/session/state-select.js +1 -1
  282. package/dist/status-payload.d.ts +19 -0
  283. package/dist/status-payload.js +11 -0
  284. package/dist/telemetry/argument-shape.js +10 -1
  285. package/dist/telemetry/browser-mode.d.ts +27 -0
  286. package/dist/telemetry/browser-mode.js +33 -0
  287. package/dist/telemetry/bug-found.d.ts +17 -21
  288. package/dist/telemetry/bug-found.js +120 -8
  289. package/dist/telemetry/cli-telemetry.d.ts +2 -12
  290. package/dist/telemetry/cli-telemetry.js +27 -4
  291. package/dist/telemetry/daemon-telemetry.d.ts +19 -0
  292. package/dist/telemetry/daemon-telemetry.js +12 -2
  293. package/dist/telemetry/dev-repo.js +4 -4
  294. package/dist/telemetry/error-fingerprint.js +21 -2
  295. package/dist/telemetry/feedback-cli.d.ts +1 -1
  296. package/dist/telemetry/feedback-cli.js +4 -4
  297. package/dist/telemetry/feedback-context.d.ts +22 -0
  298. package/dist/telemetry/feedback-context.js +41 -1
  299. package/dist/telemetry/feedback-delivery.d.ts +20 -0
  300. package/dist/telemetry/feedback-delivery.js +30 -0
  301. package/dist/telemetry/feedback-outbox.d.ts +22 -0
  302. package/dist/telemetry/feedback-outbox.js +87 -0
  303. package/dist/telemetry/feedback.d.ts +22 -1
  304. package/dist/telemetry/feedback.js +67 -8
  305. package/dist/telemetry/git-facts.js +1 -1
  306. package/dist/telemetry/session-metrics.d.ts +2 -0
  307. package/dist/telemetry/session-metrics.js +68 -6
  308. package/dist/telemetry/telemetry.d.ts +27 -2
  309. package/dist/telemetry/telemetry.js +119 -29
  310. package/dist/telemetry/verification-of.d.ts +22 -0
  311. package/dist/telemetry/verification-of.js +84 -0
  312. package/dist/tools/act-capsule.js +2 -2
  313. package/dist/tools/act-danger.d.ts +12 -0
  314. package/dist/tools/act-danger.js +20 -3
  315. package/dist/tools/act-helpers.js +4 -4
  316. package/dist/tools/act-tools.d.ts +0 -8
  317. package/dist/tools/act-tools.js +84 -115
  318. package/dist/tools/act-view.js +7 -7
  319. package/dist/tools/alias-args.d.ts +17 -0
  320. package/dist/tools/alias-args.js +25 -0
  321. package/dist/tools/assert-grade.js +9 -9
  322. package/dist/tools/assert-verdict.d.ts +3 -1
  323. package/dist/tools/assert-verdict.js +8 -5
  324. package/dist/tools/browser-tools.js +30 -11
  325. package/dist/tools/contract-tools.js +1 -1
  326. package/dist/tools/coverage-identity.d.ts +34 -0
  327. package/dist/tools/coverage-identity.js +46 -0
  328. package/dist/tools/coverage-tools.js +10 -14
  329. package/dist/tools/dynamic-tools.d.ts +2 -1
  330. package/dist/tools/dynamic-tools.js +90 -18
  331. package/dist/tools/error-recovery.d.ts +20 -2
  332. package/dist/tools/error-recovery.js +179 -4
  333. package/dist/tools/feedback-tools.d.ts +12 -1
  334. package/dist/tools/feedback-tools.js +28 -5
  335. package/dist/tools/invoke-tool.d.ts +0 -6
  336. package/dist/tools/invoke-tool.js +57 -32
  337. package/dist/tools/lease-hint.d.ts +17 -0
  338. package/dist/tools/lease-hint.js +24 -0
  339. package/dist/tools/lease-tools.js +6 -5
  340. package/dist/tools/merge-tools.d.ts +10 -1
  341. package/dist/tools/merge-tools.js +9 -3
  342. package/dist/tools/merged-name-redirect.d.ts +13 -0
  343. package/dist/tools/merged-name-redirect.js +55 -0
  344. package/dist/tools/navigate-result.d.ts +17 -0
  345. package/dist/tools/navigate-result.js +31 -0
  346. package/dist/tools/observe-tools.js +54 -20
  347. package/dist/tools/observed-nothing.d.ts +25 -0
  348. package/dist/tools/observed-nothing.js +33 -0
  349. package/dist/tools/query-paginate.js +2 -2
  350. package/dist/tools/query-shape.d.ts +18 -0
  351. package/dist/tools/query-shape.js +39 -0
  352. package/dist/tools/read-tools.js +85 -20
  353. package/dist/tools/real-input-attempt.d.ts +28 -0
  354. package/dist/tools/real-input-attempt.js +88 -0
  355. package/dist/tools/reconcile-tools.js +7 -7
  356. package/dist/tools/reload-result.d.ts +14 -0
  357. package/dist/tools/reload-result.js +29 -0
  358. package/dist/tools/snapshot-delta.d.ts +4 -5
  359. package/dist/tools/snapshot-delta.js +0 -0
  360. package/dist/tools/tool-kit.d.ts +8 -0
  361. package/dist/tools/tool-surface.d.ts +83 -0
  362. package/dist/tools/tool-surface.js +204 -0
  363. package/dist/tools/tools-helpers.js +6 -6
  364. package/dist/tools/tools.d.ts +15 -0
  365. package/dist/tools/tools.js +53 -17
  366. package/dist/tools/unadvertised-help.d.ts +5 -0
  367. package/dist/tools/unadvertised-help.js +39 -0
  368. package/dist/trace.d.ts +69 -0
  369. package/dist/trace.js +150 -0
  370. package/dist/update/nudge-credit.d.ts +4 -0
  371. package/dist/update/nudge-credit.js +53 -0
  372. package/dist/update/sdk-sync.d.ts +21 -0
  373. package/dist/update/sdk-sync.js +42 -0
  374. package/dist/update/update-checker.js +3 -3
  375. package/dist/update/update-nudge.d.ts +63 -6
  376. package/dist/update/update-nudge.js +128 -15
  377. package/dist/update/updater.d.ts +1 -9
  378. package/dist/update/updater.js +13 -8
  379. package/dist/version/peer-announce.d.ts +4 -0
  380. package/dist/version/peer-announce.js +30 -0
  381. package/dist/{server-version.js → version/server-version.js} +5 -1
  382. package/dist/version/version-nudge.d.ts +28 -0
  383. package/dist/version/version-nudge.js +40 -0
  384. package/dist/version/version-skew.d.ts +66 -0
  385. package/dist/version/version-skew.js +71 -0
  386. package/dist/visual/visual-diff.js +2 -2
  387. package/dist/visual/visual-tools.js +4 -4
  388. package/docs/agent-cheatsheet.md +85 -0
  389. package/docs/architecture.md +113 -0
  390. package/docs/benchmarks.md +255 -0
  391. package/docs/debugging.md +102 -0
  392. package/docs/deploy-checks.md +69 -0
  393. package/docs/desktop-apps.md +256 -0
  394. package/docs/enterprise.md +62 -0
  395. package/docs/fixtures.md +28 -0
  396. package/docs/flows.md +166 -0
  397. package/docs/getting-started.md +469 -0
  398. package/docs/human-control.md +63 -0
  399. package/docs/integration-patterns.md +171 -0
  400. package/docs/local-registry.md +126 -0
  401. package/docs/mint.json +61 -0
  402. package/docs/multi-agent-testing.md +55 -0
  403. package/docs/platform-integration.md +122 -0
  404. package/docs/telemetry-contract.md +214 -0
  405. package/docs/telemetry.md +184 -0
  406. package/docs/testing.md +82 -0
  407. package/docs/token-efficiency.md +80 -0
  408. package/docs/usage.md +1046 -0
  409. package/package.json +8 -5
  410. package/dist/cli-launch.js +0 -112
  411. package/dist/cli-parse.d.ts +0 -140
  412. package/dist/daemon-resilience.d.ts +0 -22
  413. package/dist/daemon-resilience.js +0 -85
  414. package/dist/mcp-proxy.d.ts +0 -35
  415. package/dist/mcp-proxy.js +0 -190
  416. package/dist/tools/profiles.d.ts +0 -37
  417. package/dist/tools/profiles.js +0 -139
  418. /package/dist/{pairing-token.d.ts → bridge/pairing-token.d.ts} +0 -0
  419. /package/dist/{token-auth.d.ts → bridge/token-auth.d.ts} +0 -0
  420. /package/dist/{token-auth.js → bridge/token-auth.js} +0 -0
  421. /package/dist/{cli-doctor.d.ts → cli/cli-doctor.d.ts} +0 -0
  422. /package/dist/{cli-verify.d.ts → cli/cli-verify.d.ts} +0 -0
  423. /package/dist/{cloud-cli.d.ts → cli/cloud-cli.d.ts} +0 -0
  424. /package/dist/{server-version.d.ts → version/server-version.d.ts} +0 -0
@@ -1,4 +1,34 @@
1
+ /** Env override for the whole state directory — see ReticleEnv.STATE_DIR. */
2
+ export declare const STATE_DIR_ENV: "RETICLE_STATE_DIR";
3
+ /**
4
+ * Where the daemon keeps pidfiles, the discovery registry and its logs.
5
+ *
6
+ * `~/.reticle` unless overridden. The override exists because a read-only $HOME is not hypothetical
7
+ * — a sandboxed agent or a locked-down Windows profile cannot write there, and until now that meant
8
+ * the daemon could not start at all. Read per call rather than captured at import, so a test (and a
9
+ * wrapper that sets it late) sees the current value.
10
+ */
11
+ export declare function reticleStateHome(): string;
1
12
  export declare function logPath(port: number): string;
13
+ /**
14
+ * How large a daemon log may get before it is rolled over to `<name>.1`.
15
+ *
16
+ * It was unbounded. A real dev machine reached **24MB** on one port, which is not merely untidy: it
17
+ * is the difference between a log somebody opens and a log somebody gives up on, and it is disk that
18
+ * nothing ever reclaims. One previous generation is kept, because the question people bring to this
19
+ * file ("what happened just now?") is answered by the current one and the question they bring next
20
+ * ("and just before that?") is answered by the other.
21
+ */
22
+ export declare const MAX_DAEMON_LOG_BYTES: number;
23
+ /**
24
+ * Roll the log over if it has grown past the cap. Best-effort: a daemon must still start when the
25
+ * log cannot be rotated, because failing to launch over housekeeping is strictly worse than a large
26
+ * file.
27
+ */
28
+ export declare function rotateDaemonLog(path: string, deps: {
29
+ fileSize(p: string): number;
30
+ renameFile(from: string, to: string): void;
31
+ }, max?: number): boolean;
2
32
  export declare function readPid(port: number, home?: string): number | null;
3
33
  export declare function isAlive(pid: number): boolean;
4
34
  export declare function writePid(port: number): void;
@@ -20,13 +50,6 @@ export declare function writeDaemonRegistry(port: number, meta: {
20
50
  projectId?: string;
21
51
  startedAt: number;
22
52
  }): void;
23
- export declare function removeDaemonRegistry(port: number): void;
24
- /**
25
- * Discover the port of a live daemon serving `projectId` by reading the registry entries in ~/.reticle.
26
- * Returns null when none matches (caller falls back to the default port — never guesses a mismatched
27
- * daemon). Stale entries (crashed daemons) are ignored via the pid liveness probe.
28
- */
29
- export declare function discoverDaemonPortForProject(projectId: string | undefined): number | null;
30
53
  export declare function isRunning(port: number): boolean;
31
54
  /**
32
55
  * Find the port of a live reticle daemon by scanning ~/.reticle/daemon-<port>.pid files — so `reticle open`
@@ -52,13 +75,15 @@ export interface SpawnDaemonDeps {
52
75
  readonly home: string;
53
76
  openFile(path: string, flags: string): number;
54
77
  closeFile(fd: number): void;
78
+ /** Size in bytes, or 0 when the file does not exist yet. */
79
+ fileSize(path: string): number;
80
+ renameFile(from: string, to: string): void;
55
81
  spawnChild(command: string, args: readonly string[], options: {
56
82
  detached: boolean;
57
83
  stdio: readonly ('ignore' | number)[];
58
84
  }): SpawnedChild;
59
85
  pidAlive(pid: number): boolean;
60
86
  }
61
- export declare function defaultSpawnDaemonDeps(): SpawnDaemonDeps;
62
87
  /**
63
88
  * Spawn the reticle daemon as a detached background process, redirecting output to the log file.
64
89
  * Writes the PID file from the parent before returning so callers can call isRunning
@@ -1,19 +1,61 @@
1
1
  import { join } from 'node:path';
2
2
  import { homedir } from 'node:os';
3
- import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync, openSync, closeSync, readdirSync, } from 'node:fs';
3
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, unlinkSync, openSync, closeSync, readdirSync, statSync, renameSync, } from 'node:fs';
4
4
  import { spawn } from 'node:child_process';
5
- import { daemonRegistryFileName, daemonRegistryPort, DaemonRegistryEntrySchema, pickDaemonPort, } from '@reticlehq/core';
6
- const RETICLE_HOME = join(homedir(), '.reticle');
7
- function pidPath(port, home = RETICLE_HOME) {
5
+ import { daemonRegistryFileName, ReticleEnv } from '@reticlehq/core';
6
+ import { log } from '../log.js';
7
+ /** Env override for the whole state directory — see ReticleEnv.STATE_DIR. */
8
+ export const STATE_DIR_ENV = ReticleEnv.STATE_DIR;
9
+ /**
10
+ * Where the daemon keeps pidfiles, the discovery registry and its logs.
11
+ *
12
+ * `~/.reticle` unless overridden. The override exists because a read-only $HOME is not hypothetical
13
+ * — a sandboxed agent or a locked-down Windows profile cannot write there, and until now that meant
14
+ * the daemon could not start at all. Read per call rather than captured at import, so a test (and a
15
+ * wrapper that sets it late) sees the current value.
16
+ */
17
+ export function reticleStateHome() {
18
+ const override = process.env[STATE_DIR_ENV];
19
+ if (override !== undefined && override.length > 0)
20
+ return override;
21
+ return join(homedir(), '.reticle');
22
+ }
23
+ function pidPath(port, home = reticleStateHome()) {
8
24
  return join(home, `daemon-${port}.pid`);
9
25
  }
10
26
  function registryPath(port) {
11
- return join(RETICLE_HOME, daemonRegistryFileName(port));
27
+ return join(reticleStateHome(), daemonRegistryFileName(port));
12
28
  }
13
29
  export function logPath(port) {
14
- return join(RETICLE_HOME, `daemon-${port}.log`);
30
+ return join(reticleStateHome(), `daemon-${port}.log`);
15
31
  }
16
- export function readPid(port, home = RETICLE_HOME) {
32
+ /**
33
+ * How large a daemon log may get before it is rolled over to `<name>.1`.
34
+ *
35
+ * It was unbounded. A real dev machine reached **24MB** on one port, which is not merely untidy: it
36
+ * is the difference between a log somebody opens and a log somebody gives up on, and it is disk that
37
+ * nothing ever reclaims. One previous generation is kept, because the question people bring to this
38
+ * file ("what happened just now?") is answered by the current one and the question they bring next
39
+ * ("and just before that?") is answered by the other.
40
+ */
41
+ export const MAX_DAEMON_LOG_BYTES = 8 * 1024 * 1024;
42
+ /**
43
+ * Roll the log over if it has grown past the cap. Best-effort: a daemon must still start when the
44
+ * log cannot be rotated, because failing to launch over housekeeping is strictly worse than a large
45
+ * file.
46
+ */
47
+ export function rotateDaemonLog(path, deps, max = MAX_DAEMON_LOG_BYTES) {
48
+ try {
49
+ if (max >= deps.fileSize(path))
50
+ return false;
51
+ deps.renameFile(path, `${path}.1`);
52
+ return true;
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
58
+ export function readPid(port, home = reticleStateHome()) {
17
59
  const path = pidPath(port, home);
18
60
  if (!existsSync(path))
19
61
  return null;
@@ -30,7 +72,7 @@ export function isAlive(pid) {
30
72
  }
31
73
  }
32
74
  export function writePid(port) {
33
- mkdirSync(RETICLE_HOME, { recursive: true });
75
+ mkdirSync(reticleStateHome(), { recursive: true });
34
76
  writeFileSync(pidPath(port), String(process.pid), 'utf8');
35
77
  }
36
78
  /**
@@ -39,7 +81,7 @@ export function writePid(port) {
39
81
  * the orphan-race guard: a losing childB (EADDRINUSE) must not delete the winning childA's live pidfile.
40
82
  */
41
83
  export function shouldRemovePid(owner, expectedPid, alive) {
42
- return owner === null || owner === expectedPid || !alive;
84
+ return null === owner || owner === expectedPid || !alive;
43
85
  }
44
86
  export function removePid(port, expectedPid = process.pid) {
45
87
  const path = pidPath(port);
@@ -66,14 +108,14 @@ export function writeDaemonRegistry(port, meta) {
66
108
  ...(meta.projectId !== undefined ? { projectId: meta.projectId } : {}),
67
109
  };
68
110
  try {
69
- mkdirSync(RETICLE_HOME, { recursive: true });
111
+ mkdirSync(reticleStateHome(), { recursive: true });
70
112
  writeFileSync(registryPath(port), JSON.stringify(entry), 'utf8');
71
113
  }
72
114
  catch {
73
115
  // discovery is a convenience — never block startup on it
74
116
  }
75
117
  }
76
- export function removeDaemonRegistry(port) {
118
+ function removeDaemonRegistry(port) {
77
119
  const path = registryPath(port);
78
120
  try {
79
121
  if (existsSync(path))
@@ -83,34 +125,6 @@ export function removeDaemonRegistry(port) {
83
125
  // racing another cleaner — already gone
84
126
  }
85
127
  }
86
- /**
87
- * Discover the port of a live daemon serving `projectId` by reading the registry entries in ~/.reticle.
88
- * Returns null when none matches (caller falls back to the default port — never guesses a mismatched
89
- * daemon). Stale entries (crashed daemons) are ignored via the pid liveness probe.
90
- */
91
- export function discoverDaemonPortForProject(projectId) {
92
- const entries = [];
93
- let files;
94
- try {
95
- files = readdirSync(RETICLE_HOME);
96
- }
97
- catch {
98
- return null; // no ~/.reticle yet
99
- }
100
- for (const file of files) {
101
- if (daemonRegistryPort(file) === null)
102
- continue;
103
- try {
104
- const parsed = DaemonRegistryEntrySchema.safeParse(JSON.parse(readFileSync(join(RETICLE_HOME, file), 'utf8')));
105
- if (parsed.success)
106
- entries.push(parsed.data);
107
- }
108
- catch {
109
- // unreadable/corrupt entry — skip it
110
- }
111
- }
112
- return pickDaemonPort(entries, projectId, isAlive);
113
- }
114
128
  export function isRunning(port) {
115
129
  const pid = readPid(port);
116
130
  return pid !== null && isAlive(pid);
@@ -124,12 +138,12 @@ export function discoverDaemonPort() {
124
138
  reclaimStaleDaemons(); // sweep crashed daemons' stale pidfiles before scanning for live ones
125
139
  let found = null;
126
140
  try {
127
- for (const file of readdirSync(RETICLE_HOME)) {
141
+ for (const file of readdirSync(reticleStateHome())) {
128
142
  const m = /^daemon-(\d+)\.pid$/.exec(file);
129
- if (m === null)
143
+ if (null === m)
130
144
  continue;
131
145
  const port = Number(m[1]);
132
- if (isRunning(port) && (found === null || port < found))
146
+ if (isRunning(port) && (null === found || port < found))
133
147
  found = port;
134
148
  }
135
149
  }
@@ -144,7 +158,7 @@ export function discoverDaemonPort() {
144
158
  * Returns the ports reclaimed. `home` and `pidAlive` are injectable for testing (default to the real
145
159
  * ~/.reticle and the process.kill(pid,0) liveness probe).
146
160
  */
147
- export function reclaimStaleDaemons(home = RETICLE_HOME, pidAlive = isAlive) {
161
+ export function reclaimStaleDaemons(home = reticleStateHome(), pidAlive = isAlive) {
148
162
  const reclaimed = [];
149
163
  let files;
150
164
  try {
@@ -155,7 +169,7 @@ export function reclaimStaleDaemons(home = RETICLE_HOME, pidAlive = isAlive) {
155
169
  }
156
170
  for (const file of files) {
157
171
  const match = /^daemon-(\d+)\.pid$/.exec(file);
158
- if (match === null)
172
+ if (null === match)
159
173
  continue;
160
174
  const path = join(home, file);
161
175
  let pid = null;
@@ -167,7 +181,7 @@ export function reclaimStaleDaemons(home = RETICLE_HOME, pidAlive = isAlive) {
167
181
  catch {
168
182
  pid = null; // unreadable pidfile counts as stale
169
183
  }
170
- if (pid === null || !pidAlive(pid)) {
184
+ if (null === pid || !pidAlive(pid)) {
171
185
  try {
172
186
  unlinkSync(path);
173
187
  removeDaemonRegistry(Number(match[1])); // drop the sidecar discovery entry too
@@ -180,11 +194,13 @@ export function reclaimStaleDaemons(home = RETICLE_HOME, pidAlive = isAlive) {
180
194
  }
181
195
  return reclaimed;
182
196
  }
183
- export function defaultSpawnDaemonDeps() {
197
+ function defaultSpawnDaemonDeps() {
184
198
  return {
185
- home: RETICLE_HOME,
199
+ home: reticleStateHome(),
186
200
  openFile: openSync,
187
201
  closeFile: closeSync,
202
+ fileSize: (path) => (existsSync(path) ? statSync(path).size : 0),
203
+ renameFile: renameSync,
188
204
  spawnChild: (command, args, options) => spawn(command, [...args], { detached: options.detached, stdio: [...options.stdio] }),
189
205
  pidAlive: isAlive,
190
206
  };
@@ -198,7 +214,20 @@ export function defaultSpawnDaemonDeps() {
198
214
  * pattern as reclaimStaleDaemons(home, pidAlive).
199
215
  */
200
216
  export function spawnDaemon(nodeExec, scriptPath, args, port, deps = defaultSpawnDaemonDeps()) {
201
- mkdirSync(deps.home, { recursive: true });
217
+ try {
218
+ mkdirSync(deps.home, { recursive: true });
219
+ }
220
+ catch (err) {
221
+ // The log-file open below has always been guarded; this was not, so the ONE failure a sandboxed
222
+ // agent actually hits — a read-only $HOME — escaped as a raw EACCES naming nothing. Refuse
223
+ // loudly, and name the way out.
224
+ log('reticle_daemon_state_unwritable', {
225
+ home: deps.home,
226
+ error: err instanceof Error ? err.message : String(err),
227
+ fix: `Reticle could not create its state directory. Set ${STATE_DIR_ENV} to a writable path and retry.`,
228
+ });
229
+ return false;
230
+ }
202
231
  const pidFilePath = join(deps.home, `daemon-${port}.pid`);
203
232
  const logFilePath = join(deps.home, `daemon-${port}.log`);
204
233
  // O_EXCL spawn-lock: only the FIRST racer to create the pidfile spawns. A concurrent second gets
@@ -220,6 +249,9 @@ export function spawnDaemon(nodeExec, scriptPath, args, port, deps = defaultSpaw
220
249
  return false; // lost a concurrent reclaim race
221
250
  }
222
251
  }
252
+ // Roll the log BEFORE opening the append handle, or the daemon writes into the renamed file
253
+ // through an fd that no longer matches any name anyone can find.
254
+ rotateDaemonLog(logFilePath, deps);
223
255
  let logFd;
224
256
  try {
225
257
  logFd = deps.openFile(logFilePath, 'a');
@@ -0,0 +1,32 @@
1
+ /**
2
+ * How long an idle daemon is given before it shuts itself down.
3
+ *
4
+ * It used to be one number for every case, and at 5 minutes that number killed live runs: 187
5
+ * `reticle_daemon_idle_shutdown` lines on one user's machine, with their editor's Reticle
6
+ * disconnecting each time; and in the fixtures gate it fired during long dependency installs, so apps
7
+ * that booted afterwards hit ERR_CONNECTION_REFUSED and were scored as install failures when nothing
8
+ * was wrong with their install.
9
+ *
10
+ * Reverting is not an option. Daemons used to sit idle a median of 28 minutes at a 0.04% duty cycle
11
+ * because "an agent is attached" alone kept one alive for a whole editor session, and the widened
12
+ * rule is what fixed that.
13
+ *
14
+ * The distinction is TIME, not state. Mid-install an attached daemon is state-identical to one in an
15
+ * empty directory — nothing has been asked of either. What differs is how long it is reasonable to
16
+ * wait: quiet with a client attached means the human is thinking or the install is slow; the same
17
+ * quiet with nobody attached means the daemon is unwanted. Both still exit.
18
+ */
19
+ /**
20
+ * An attached daemon waits this many times the base grace. Six is chosen against the two measurements
21
+ * that matter: the default base is 5 minutes, so an attached daemon survives 30 — comfortably past
22
+ * the slow installs that were killing runs, and comfortably short of the 28-minute median idle the
23
+ * shorter rule was introduced to end.
24
+ */
25
+ export declare const ATTACHED_GRACE_MULTIPLIER = 6;
26
+ /**
27
+ * @param overrideMs an explicit attached grace (RETICLE_IDLE_ATTACHED_MS). Derived from the base when
28
+ * absent — but the derived value is arithmetic on a number the caller also controls, and a test that
29
+ * has to reason about both is a test that breaks for the wrong reason. Configuring it directly is
30
+ * what lets the e2e spec assert the BEHAVIOUR (an attached daemon exits) without racing a product.
31
+ */
32
+ export declare function idleGraceMs(baseMs: number, agentAttached: boolean, overrideMs?: number): number;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * How long an idle daemon is given before it shuts itself down.
3
+ *
4
+ * It used to be one number for every case, and at 5 minutes that number killed live runs: 187
5
+ * `reticle_daemon_idle_shutdown` lines on one user's machine, with their editor's Reticle
6
+ * disconnecting each time; and in the fixtures gate it fired during long dependency installs, so apps
7
+ * that booted afterwards hit ERR_CONNECTION_REFUSED and were scored as install failures when nothing
8
+ * was wrong with their install.
9
+ *
10
+ * Reverting is not an option. Daemons used to sit idle a median of 28 minutes at a 0.04% duty cycle
11
+ * because "an agent is attached" alone kept one alive for a whole editor session, and the widened
12
+ * rule is what fixed that.
13
+ *
14
+ * The distinction is TIME, not state. Mid-install an attached daemon is state-identical to one in an
15
+ * empty directory — nothing has been asked of either. What differs is how long it is reasonable to
16
+ * wait: quiet with a client attached means the human is thinking or the install is slow; the same
17
+ * quiet with nobody attached means the daemon is unwanted. Both still exit.
18
+ */
19
+ /**
20
+ * An attached daemon waits this many times the base grace. Six is chosen against the two measurements
21
+ * that matter: the default base is 5 minutes, so an attached daemon survives 30 — comfortably past
22
+ * the slow installs that were killing runs, and comfortably short of the 28-minute median idle the
23
+ * shorter rule was introduced to end.
24
+ */
25
+ export const ATTACHED_GRACE_MULTIPLIER = 6;
26
+ /**
27
+ * @param overrideMs an explicit attached grace (RETICLE_IDLE_ATTACHED_MS). Derived from the base when
28
+ * absent — but the derived value is arithmetic on a number the caller also controls, and a test that
29
+ * has to reason about both is a test that breaks for the wrong reason. Configuring it directly is
30
+ * what lets the e2e spec assert the BEHAVIOUR (an attached daemon exits) without racing a product.
31
+ */
32
+ export function idleGraceMs(baseMs, agentAttached, overrideMs) {
33
+ // A non-positive base means the watcher is disabled; multiplying must not resurrect it.
34
+ if (baseMs <= 0)
35
+ return baseMs;
36
+ if (!agentAttached)
37
+ return baseMs;
38
+ return overrideMs !== undefined && overrideMs > 0
39
+ ? overrideMs
40
+ : baseMs * ATTACHED_GRACE_MULTIPLIER;
41
+ }
@@ -5,6 +5,14 @@ export interface IdleShutdownOptions {
5
5
  onShutdown: () => void;
6
6
  /** Continuous-idle window before shutdown. `0` (or negative) disables the watcher entirely. */
7
7
  graceMs: number;
8
+ /**
9
+ * Is an agent's MCP client attached right now? An attached daemon gets a much longer grace — quiet
10
+ * with a client present means a slow install or a thinking human, not an unwanted daemon. It still
11
+ * exits. See idle-grace.
12
+ */
13
+ agentAttached?: () => boolean;
14
+ /** Explicit attached grace (RETICLE_IDLE_ATTACHED_MS); derived from graceMs when absent. */
15
+ attachedGraceMs?: number;
8
16
  /** How often to re-check idleness. */
9
17
  checkIntervalMs?: number;
10
18
  /** Injected clock — never read wall-clock time in logic (repo rule). */
@@ -30,4 +38,9 @@ export declare class IdleShutdown {
30
38
  * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of milliseconds,
31
39
  * `0` to disable. Anything missing/invalid falls back to the default. Pure.
32
40
  */
41
+ /**
42
+ * The idle re-check cadence, from the environment. Same shape as the grace resolver: a bad value
43
+ * falls back rather than throwing, and 0/negative is not a legal cadence (it would spin).
44
+ */
45
+ export declare function resolveIdleCheckMs(raw: string | undefined): number;
33
46
  export declare function resolveIdleShutdownMs(raw: string | undefined): number;
@@ -1,5 +1,6 @@
1
1
  import { SESSION_LIFECYCLE } from '@reticlehq/core';
2
- import { log } from './log.js';
2
+ import { idleGraceMs } from './idle-grace.js';
3
+ import { log } from '../log.js';
3
4
  /**
4
5
  * Self-terminates an idle daemon so Reticle never lingers eating a user's resources (the daemon process,
5
6
  * any headless Chromium the pool launched, and the bound port) after the editor closes. The `reticle mcp`
@@ -15,12 +16,16 @@ export class IdleShutdown {
15
16
  #isIdle;
16
17
  #onShutdown;
17
18
  #graceMs;
19
+ #agentAttached;
20
+ #attachedGraceMs;
18
21
  #checkMs;
19
22
  #clock;
20
23
  constructor(opts) {
21
24
  this.#isIdle = opts.isIdle;
22
25
  this.#onShutdown = opts.onShutdown;
23
26
  this.#graceMs = opts.graceMs;
27
+ this.#agentAttached = opts.agentAttached;
28
+ this.#attachedGraceMs = opts.attachedGraceMs;
24
29
  this.#checkMs = opts.checkIntervalMs ?? SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
25
30
  this.#clock = opts.clock ?? (() => Date.now());
26
31
  }
@@ -34,9 +39,15 @@ export class IdleShutdown {
34
39
  }
35
40
  const now = this.#clock();
36
41
  this.#idleSince ??= now;
37
- if (now - this.#idleSince >= this.#graceMs) {
42
+ // Read per CHECK, not at construction: a client attaches and detaches during a daemon's life, and
43
+ // the question "how long should this quiet be tolerated" is only answerable now. See idle-grace.
44
+ const grace = idleGraceMs(this.#graceMs, this.#agentAttached?.() ?? false, this.#attachedGraceMs);
45
+ if (now - this.#idleSince >= grace) {
38
46
  this.#fired = true;
39
- log('reticle_daemon_idle_shutdown', { idleMs: now - this.#idleSince });
47
+ log('reticle_daemon_idle_shutdown', {
48
+ idleMs: now - this.#idleSince,
49
+ attached: this.#agentAttached?.() ?? false,
50
+ });
40
51
  this.#onShutdown();
41
52
  }
42
53
  }
@@ -57,8 +68,20 @@ export class IdleShutdown {
57
68
  * Resolve the idle-shutdown grace from `RETICLE_IDLE_SHUTDOWN_MS`: a non-negative integer of milliseconds,
58
69
  * `0` to disable. Anything missing/invalid falls back to the default. Pure.
59
70
  */
71
+ /**
72
+ * The idle re-check cadence, from the environment. Same shape as the grace resolver: a bad value
73
+ * falls back rather than throwing, and 0/negative is not a legal cadence (it would spin).
74
+ */
75
+ export function resolveIdleCheckMs(raw) {
76
+ if (raw === undefined || '' === raw.trim())
77
+ return SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
78
+ const n = Number(raw);
79
+ if (!Number.isFinite(n) || n <= 0)
80
+ return SESSION_LIFECYCLE.DAEMON_IDLE_CHECK_MS;
81
+ return Math.floor(n);
82
+ }
60
83
  export function resolveIdleShutdownMs(raw) {
61
- if (raw === undefined || raw.trim() === '')
84
+ if (raw === undefined || '' === raw.trim())
62
85
  return SESSION_LIFECYCLE.DAEMON_IDLE_SHUTDOWN_MS;
63
86
  const n = Number(raw);
64
87
  if (!Number.isFinite(n) || n < 0)
@@ -107,11 +107,11 @@ export function buildDomainModel(flows, contract, runs = []) {
107
107
  };
108
108
  }
109
109
  function buildSummary(flowCount, coverage, gaps, topRisk) {
110
- if (flowCount === 0) {
110
+ if (0 === flowCount) {
111
111
  return 'No saved flows yet — record the critical journeys (reticle_record{action:"start"}) so the agent learns the app.';
112
112
  }
113
113
  const parts = [
114
- `${String(flowCount)} flow${flowCount === 1 ? '' : 's'}: ${String(coverage.asserted)} asserted, ${String(coverage.presenceOnly)} presence-only, ${String(coverage.assertionFree)} assertion-free`,
114
+ `${String(flowCount)} flow${1 === flowCount ? '' : 's'}: ${String(coverage.asserted)} asserted, ${String(coverage.presenceOnly)} presence-only, ${String(coverage.assertionFree)} assertion-free`,
115
115
  ];
116
116
  if (topRisk !== undefined) {
117
117
  parts.push(`test first: ${topRisk.name} (${topRisk.reason})`);
@@ -25,19 +25,19 @@ const ERROR_KEYS = ['error', 'errors', 'errorMessage', 'failureReason'];
25
25
  /** Arrays worth walking for per-item outcomes. Bulk APIs converge on a handful of names. */
26
26
  const ITEM_KEYS = ['results', 'items', 'data', 'records', 'responses'];
27
27
  function isRecord(value) {
28
- return typeof value === 'object' && value !== null && !Array.isArray(value);
28
+ return 'object' === typeof value && value !== null && !Array.isArray(value);
29
29
  }
30
30
  /** Whether one object reports a failure of its own. */
31
31
  function reportsFailure(item) {
32
32
  if (!isRecord(item))
33
33
  return false;
34
34
  for (const flag of FAILURE_FLAGS) {
35
- if (item[flag] === false)
35
+ if (false === item[flag])
36
36
  return true;
37
37
  }
38
38
  for (const key of ERROR_KEYS) {
39
39
  const value = item[key];
40
- if (value === undefined || value === null)
40
+ if (value === undefined || null === value)
41
41
  continue;
42
42
  if (Array.isArray(value)) {
43
43
  if (value.length > 0)
@@ -45,7 +45,7 @@ function reportsFailure(item) {
45
45
  continue;
46
46
  }
47
47
  // An empty string or an explicit null is an API saying "no error", not an error.
48
- if (typeof value === 'string' && value.length === 0)
48
+ if ('string' === typeof value && 0 === value.length)
49
49
  continue;
50
50
  return true;
51
51
  }
@@ -55,7 +55,7 @@ function reportsFailure(item) {
55
55
  function itemOutcomes(body) {
56
56
  for (const key of ITEM_KEYS) {
57
57
  const list = body[key];
58
- if (!Array.isArray(list) || list.length === 0)
58
+ if (!Array.isArray(list) || 0 === list.length)
59
59
  continue;
60
60
  const failed = list.filter(reportsFailure).length;
61
61
  return { failed, total: list.length };
@@ -63,7 +63,7 @@ function itemOutcomes(body) {
63
63
  return { failed: 0, total: 0 };
64
64
  }
65
65
  function parse(raw) {
66
- if (typeof raw !== 'string' || raw.length === 0)
66
+ if (typeof raw !== 'string' || 0 === raw.length)
67
67
  return undefined;
68
68
  try {
69
69
  const parsed = JSON.parse(raw);
@@ -87,8 +87,8 @@ export function findBodyFailures(events) {
87
87
  const body = parse(event.data['responseBody']);
88
88
  if (body === undefined)
89
89
  continue;
90
- const url = typeof event.data['url'] === 'string' ? event.data['url'] : '';
91
- const method = typeof event.data['method'] === 'string' ? event.data['method'] : '';
90
+ const url = 'string' === typeof event.data['url'] ? event.data['url'] : '';
91
+ const method = 'string' === typeof event.data['method'] ? event.data['method'] : '';
92
92
  const where = `${method} ${url} → ${String(status)}`;
93
93
  const { failed, total } = itemOutcomes(body);
94
94
  if (failed > 0) {
@@ -50,4 +50,4 @@ export interface ContradictionOptions {
50
50
  */
51
51
  mutatedWithin?: number | undefined;
52
52
  }
53
- export declare function findContradictions(events: readonly ReticleEvent[], options?: ContradictionOptions): Contradiction[];
53
+ export declare function findContradictions(allEvents: readonly ReticleEvent[], options?: ContradictionOptions): Contradiction[];