failproofai 1.0.10 → 1.0.11-beta.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 (296) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +4 -4
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.meta +8 -0
  12. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  14. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
  19. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  20. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  22. package/.next/standalone/.next/server/app/_not-found.meta +8 -0
  23. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  24. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  26. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
  36. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
  38. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
  40. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  42. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  44. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/audit/page.js +4 -4
  46. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  47. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/index.html +1 -1
  49. package/.next/standalone/.next/server/app/index.meta +8 -0
  50. package/.next/standalone/.next/server/app/index.rsc +14 -14
  51. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  52. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  53. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  54. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  56. package/.next/standalone/.next/server/app/page.js +4 -4
  57. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  61. package/.next/standalone/.next/server/app/policies/page.js +8 -8
  62. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
  67. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  73. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  76. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/server/app/projects/page.js +5 -5
  78. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  79. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  80. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
  81. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  82. package/.next/standalone/.next/server/app/settings/page.js +9 -9
  83. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  84. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  85. package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
  86. package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
  102. package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0el6rg8._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_0ol5pa3._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_04op_ou._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1rqmk27._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{node_modules_posthog-node_dist_entrypoints_index_node_mjs_0dr20uz._.js → node_modules_posthog-node_dist_entrypoints_index_node_mjs_1w7tiq4._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  118. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__1_qv8oh._.js} +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _0lnpwk8._.js} +2 -2
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _15y8myg._.js} +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
  136. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  137. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  138. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  144. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  146. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  147. package/.next/standalone/.next/server/middleware.js +1 -1
  148. package/.next/standalone/.next/server/pages/404.html +1 -1
  149. package/.next/standalone/.next/server/pages/500.html +1 -1
  150. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  152. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0pra9i676pym1.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1-lpi0m-hc1kb.js} +1 -1
  154. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 17yvyyblr_eb5.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 1mylcr99gccyz.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 1wozchtn2etvh.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 23thtelz1a-ur.js} +2 -2
  158. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 2bc17tohgs4bs.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 2xq-ejo8kti18.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/3-_pth_c9n938.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 3q7sndqftqhw9.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  165. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  166. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  167. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  168. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  169. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  175. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  176. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  177. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  178. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  180. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  181. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  182. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  185. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  186. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  187. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  189. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  190. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  191. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  192. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  193. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  194. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  196. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  197. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  198. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  201. package/.next/standalone/node_modules/next/package.json +10 -10
  202. package/.next/standalone/package.json +14 -11
  203. package/.next/standalone/sdk/python/README.md +41 -0
  204. package/.next/standalone/sdk/python/docs/README.md +1 -0
  205. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  206. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  208. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  211. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  212. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  213. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  214. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  215. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  216. package/.next/standalone/sdk/python/uv.lock +258 -147
  217. package/.next/standalone/sdk/typescript/README.md +43 -0
  218. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  219. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  220. package/.next/standalone/sdk/typescript/package.json +2 -0
  221. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  222. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  223. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  224. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  225. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  226. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  227. package/.next/standalone/server.js +1 -1
  228. package/bin/failproofai.mjs +653 -1077
  229. package/dist/cli.mjs +8781 -4050
  230. package/dist/worker.mjs +170 -62
  231. package/hermes-plugin/plugin.yaml +1 -1
  232. package/package.json +14 -11
  233. package/scripts/launch-facts.ts +157 -0
  234. package/scripts/launch-ready.ts +228 -0
  235. package/scripts/launch-screen.ts +202 -0
  236. package/scripts/launch.ts +106 -49
  237. package/scripts/skew-log-filter.ts +38 -0
  238. package/src/audit/cli-login.ts +39 -59
  239. package/src/audit/cli.ts +308 -228
  240. package/src/audit/index.ts +45 -2
  241. package/src/audit/schedule-cli.ts +83 -120
  242. package/src/audit/types.ts +31 -0
  243. package/src/hooks/agent-selection.ts +155 -0
  244. package/src/hooks/backfill-cli.ts +151 -137
  245. package/src/hooks/backfill-request.ts +149 -0
  246. package/src/hooks/cloud-connection.ts +1 -1
  247. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  248. package/src/hooks/cloud-introspect.ts +3 -0
  249. package/src/hooks/collector-config.ts +10 -1
  250. package/src/hooks/config-status.ts +717 -0
  251. package/src/hooks/configure-wizard.ts +967 -988
  252. package/src/hooks/daemon-service.ts +16 -6
  253. package/src/hooks/did-you-mean.ts +185 -0
  254. package/src/hooks/flush-cli.ts +33 -7
  255. package/src/hooks/fp-config.ts +96 -0
  256. package/src/hooks/fp-home.ts +11 -0
  257. package/src/hooks/fp-reset.ts +102 -95
  258. package/src/hooks/handler.ts +4 -0
  259. package/src/hooks/harness-cli.ts +87 -138
  260. package/src/hooks/install-prompt.ts +38 -477
  261. package/src/hooks/integrations.ts +1 -1
  262. package/src/hooks/jev-cli.ts +183 -196
  263. package/src/hooks/manager.ts +554 -358
  264. package/src/hooks/onboarding-attempt.ts +11 -9
  265. package/src/hooks/otel-cli.ts +350 -0
  266. package/src/hooks/otel-output.ts +24 -0
  267. package/src/hooks/otel-writers.ts +215 -0
  268. package/src/hooks/pack-cli.ts +219 -204
  269. package/src/hooks/report-body.ts +50 -0
  270. package/src/hooks/safe-config-write.ts +4 -2
  271. package/src/hooks/session-pause-cli.ts +93 -73
  272. package/src/hooks/tui.ts +849 -389
  273. package/src/hooks/uninstall-cli.ts +136 -149
  274. package/src/hooks/untraced-note.ts +52 -0
  275. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  276. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  282. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  293. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  294. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_buildManifest.js +0 -0
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_clientMiddlewareManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → xtcpqBNBdjQVerPMkI2HI}/_ssgManifest.js +0 -0
@@ -29,13 +29,17 @@
29
29
  * on a real machine: the CLI reported success, and the actual failure sat in the
30
30
  * journal for twenty minutes while batches parked.
31
31
  */
32
- import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "node:fs";
33
- import { dirname, join } from "node:path";
32
+ import { existsSync } from "node:fs";
34
33
 
35
34
  import { failproofaiHome } from "./fp-home";
36
35
  import { readConfig } from "./fp-config";
37
36
  import { readIngestCredential } from "./collector-config";
38
- import { daemonServiceStatus, isDaemonSupportedPlatform } from "./daemon-service";
37
+ import { daemonServiceStatus, daemonVersionSkew, isDaemonSupportedPlatform } from "./daemon-service";
38
+ import { backfillRequestPath, writeBackfillRequest } from "./backfill-request";
39
+ import { getIntegration } from "./integrations";
40
+ import { INTEGRATION_TYPES, type IntegrationType } from "./types";
41
+ import { screenKit, type RenderOpts } from "./tui";
42
+ import { getAdapter } from "../audit/cli-adapters";
39
43
 
40
44
  /** Default window. `--since` widens it. */
41
45
  export const DEFAULT_BACKFILL_DAYS = 30;
@@ -43,10 +47,21 @@ export const DEFAULT_BACKFILL_DAYS = 30;
43
47
  export interface BackfillOptions {
44
48
  /** Epoch ms. Everything modified at or after this is re-read. */
45
49
  sinceMs?: number;
50
+ /**
51
+ * `--since` as typed (`6m`, `2026-04-13`), repeated in the dry run's "run it
52
+ * for real" command. Without it the window is given as the date it began.
53
+ */
54
+ since?: string;
46
55
  /** Report what would be re-read and write nothing. */
47
56
  dryRun?: boolean;
57
+ /** `--agents`: a subset of the traced agents. Unvalidated ids, as typed. */
58
+ agents?: string[];
59
+ /** How the screen is drawn. */
60
+ render?: RenderOpts;
48
61
  /** Injected for tests. */
49
62
  now?: number;
63
+ /** Injected for tests: sessions an agent has at or after `sinceMs`. */
64
+ countSessions?: (cli: IntegrationType, sinceMs: number) => Promise<number>;
50
65
  }
51
66
 
52
67
  export interface BackfillResult {
@@ -54,194 +69,193 @@ export interface BackfillResult {
54
69
  lines: string[];
55
70
  }
56
71
 
57
- /**
58
- * `~/.failproofai/state/backfill-request.json` — mirrored from the daemon's
59
- * `paths::backfill_request_path()`. Two processes, one path; the comment there
60
- * says why that is written down twice rather than derived.
61
- */
62
- export function backfillRequestPath(home?: string): string {
63
- return join(failproofaiHome(home), "state", "backfill-request.json");
64
- }
72
+ export { backfillRequestPath };
65
73
 
66
74
  /**
67
- * Where each agent CLI keeps its transcripts, so the command can say what it is
68
- * about to re-read rather than asking for trust.
69
- *
70
- * Deliberately a SURVEY, not the source of truth. The daemon decides what is
71
- * actually collected from its own config; this list only produces the sentence
72
- * a person reads before agreeing. A directory missing here means a quieter
73
- * message, never a missed backfill.
75
+ * Agents whose history lives in a database the collector reads by row, with
76
+ * no time window: a backfill re-sends all of it, whatever `--since` says.
77
+ * Hermes keeps one database per profile, and the daemon does not rewind those
78
+ * at all until it can bound them (D7).
74
79
  */
75
- const SESSION_DIRS: Array<{ cli: string; dir: string }> = [
76
- { cli: "Claude Code", dir: ".claude/projects" },
77
- { cli: "OpenAI Codex", dir: ".codex/sessions" },
78
- { cli: "Cursor", dir: ".cursor/chats" },
79
- { cli: "GitHub Copilot", dir: ".copilot/history-session-state" },
80
- { cli: "OpenCode", dir: ".local/share/opencode/storage" },
81
- { cli: "Factory Droid", dir: ".factory/sessions" },
82
- { cli: "Antigravity", dir: ".gemini/antigravity-cli/brain" },
83
- ];
84
-
85
- interface Survey {
86
- cli: string;
87
- files: number;
88
- newest: number;
89
- }
80
+ const WHOLE_HISTORY: ReadonlySet<IntegrationType> = new Set(["goose", "opencode", "devin"]);
81
+ const NOT_RESENT: ReadonlySet<IntegrationType> = new Set(["hermes"]);
90
82
 
91
- /** What is on disk and inside the window, per CLI. */
92
- function surveySessions(sinceMs: number, homeDir: string): Survey[] {
93
- const out: Survey[] = [];
94
- for (const { cli, dir } of SESSION_DIRS) {
95
- const root = join(homeDir, dir);
96
- if (!existsSync(root)) continue;
97
- let files = 0;
98
- let newest = 0;
99
- const walk = (d: string, depth: number) => {
100
- // Bounded: these trees are date-nested a few levels deep, and an
101
- // unbounded walk on a symlinked home is a way to hang a CLI command.
102
- if (depth > 6) return;
103
- let entries;
104
- try {
105
- entries = readdirSync(d, { withFileTypes: true });
106
- } catch {
107
- return;
108
- }
109
- for (const e of entries) {
110
- const p = join(d, e.name);
111
- if (e.isDirectory()) {
112
- walk(p, depth + 1);
113
- continue;
114
- }
115
- try {
116
- const st = statSync(p);
117
- if (st.mtimeMs >= sinceMs) {
118
- files += 1;
119
- newest = Math.max(newest, st.mtimeMs);
120
- }
121
- } catch {
122
- /* raced away mid-walk */
123
- }
124
- }
125
- };
126
- walk(root, 0);
127
- if (files > 0) out.push({ cli, files, newest });
83
+ /** Sessions an agent has in the window, from the same adapters the audit reads. */
84
+ async function adapterSessionCount(cli: IntegrationType, sinceMs: number): Promise<number> {
85
+ try {
86
+ return (await getAdapter(cli).listTranscripts({ sinceMs })).length;
87
+ } catch {
88
+ // An agent whose store cannot be read has nothing to count; the daemon
89
+ // decides what is actually sent either way.
90
+ return 0;
128
91
  }
129
- return out;
130
92
  }
131
93
 
132
- export function runBackfillCommand(opts: BackfillOptions = {}): BackfillResult {
94
+ const plural = (n: number, one: string, many = `${one}s`): string => `${n.toLocaleString("en-US")} ${n === 1 ? one : many}`;
95
+
96
+ /** "A", "A and B", "A, B and C". */
97
+ function joinNames(names: string[]): string {
98
+ return names.length <= 1 ? (names[0] ?? "") : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
99
+ }
100
+
101
+ export async function runBackfillCommand(opts: BackfillOptions = {}): Promise<BackfillResult> {
102
+ const kit = screenKit(opts.render ?? {});
133
103
  const now = opts.now ?? Date.now();
134
104
  const sinceMs = opts.sinceMs ?? now - DEFAULT_BACKFILL_DAYS * 24 * 60 * 60 * 1000;
105
+ const sinceLabel = new Date(sinceMs).toISOString().slice(0, 10);
135
106
  const home = failproofaiHome();
107
+ const fail = (text: string, ...fix: string[]): BackfillResult => ({ exitCode: 1, lines: [kit.fail(text), ...fix] });
136
108
 
137
109
  if (!existsSync(home)) {
138
- return {
139
- exitCode: 1,
140
- lines: [`No failproofai home at ${home}. Run \`failproofai config\` first.`],
141
- };
110
+ return fail(`There is no failproofai home at ${home}.`, ` Set this machine up first: ${kit.cmd("failproofai config")}`);
142
111
  }
143
112
 
144
113
  // Checked HERE, not left to the daemon. Handing off an impossible request and
145
114
  // reporting success is the failure this command is partly a response to.
146
- const ingest = readIngestCredential();
147
- if (!ingest) {
148
- return {
149
- exitCode: 1,
150
- lines: [
151
- "This machine is not connected, so there is nowhere to send history.",
152
- "Connect first: `failproofai config --token <key>`.",
153
- ],
154
- };
115
+ if (!readIngestCredential()) {
116
+ return fail(
117
+ "This machine isn't connected, so there is nowhere to send history.",
118
+ ` Connect it: ${kit.cmd("failproofai config")}`,
119
+ );
155
120
  }
156
121
 
157
122
  let sessions = false;
158
123
  let hooks = false;
124
+ let selected: IntegrationType[] | null = null;
159
125
  try {
160
126
  const cfg = readConfig();
161
127
  sessions = cfg.collector?.sessions ?? false;
162
128
  hooks = cfg.collector?.hooks ?? false;
129
+ if (cfg.agents) selected = INTEGRATION_TYPES.filter((id) => cfg.agents?.selected.includes(id));
163
130
  } catch {
164
131
  /* an unreadable config is reported as "nothing enabled" below */
165
132
  }
166
133
  if (!sessions && !hooks) {
167
- return {
168
- exitCode: 1,
169
- lines: [
170
- "Collection is switched off, so a backfill would re-read history and send",
171
- 'none of it. Enable it in ~/.failproofai/config.json under "collector".',
172
- ],
173
- };
134
+ return fail(
135
+ "Collection is switched off, so a backfill would send nothing.",
136
+ ` Turn it on under "collector" in ${kit.cmd("~/.failproofai/config.json")}`,
137
+ );
174
138
  }
175
139
 
176
- const found = surveySessions(sinceMs, dirname(home));
177
- const totalFiles = found.reduce((n, f) => n + f.files, 0);
178
- const sinceLabel = new Date(sinceMs).toISOString().slice(0, 10);
140
+ // Traced agents only. With no saved selection every agent is traced, which
141
+ // is how the daemon reads the same file.
142
+ const traced = selected ?? [...INTEGRATION_TYPES];
143
+ let target = traced;
144
+ if (opts.agents && opts.agents.length > 0) {
145
+ const unknown = opts.agents.filter((id) => !(INTEGRATION_TYPES as readonly string[]).includes(id));
146
+ if (unknown.length > 0) {
147
+ return fail(`Not an agent: ${unknown.join(", ")}.`, ` Agents: ${INTEGRATION_TYPES.join(", ")}`);
148
+ }
149
+ const asked = INTEGRATION_TYPES.filter((id) => opts.agents?.includes(id));
150
+ const untraced = asked.filter((id) => !traced.includes(id));
151
+ if (untraced.length > 0) {
152
+ const names = joinNames(untraced.map((id) => getIntegration(id).displayName));
153
+ return fail(
154
+ `${names} ${untraced.length === 1 ? "isn't" : "aren't"} traced, so ${untraced.length === 1 ? "it" : "they"} can't be backfilled.`,
155
+ ` Trace ${untraced.length === 1 ? "it" : "them"} first with ${kit.cmd("failproofai config")} or ${kit.cmd(
156
+ `failproofai config --agents ${[...new Set([...traced, ...untraced])].filter((id) => INTEGRATION_TYPES.includes(id)).join(",")}`,
157
+ )}`,
158
+ );
159
+ }
160
+ // A daemon older than the scoped request reads every request as "rewind
161
+ // everything", so naming agents to it would re-send all of them. Refused on
162
+ // any mismatch: which side is newer cannot be told from here reliably, and
163
+ // `update` brings both to one version either way.
164
+ const skew = daemonVersionSkew();
165
+ if (skew) {
166
+ return fail(
167
+ `failproofaid ${skew.installed} is running, but this CLI ships ${skew.expected}; naming agents needs them to match.`,
168
+ ` Bring them in line: ${kit.cmd("failproofai update")}`,
169
+ );
170
+ }
171
+ target = asked;
172
+ }
179
173
 
180
- const lines: string[] = [
181
- `Re-reading everything modified since ${sinceLabel}, and sending it again.`,
182
- "",
183
- ];
184
- // Named honestly: only what the config actually enables, because that is all
185
- // the daemon will ship. Promising transcripts on a machine with
186
- // `sessions = false` would be a lie the user could only catch by waiting.
187
- lines.push(
188
- ` streams: ${[sessions && "session transcripts", hooks && "hook decisions"]
189
- .filter(Boolean)
190
- .join(" + ")}`,
191
- );
174
+ const count = opts.countSessions ?? adapterSessionCount;
175
+ const rows: Array<[string, string]> = [];
176
+ let total = 0;
177
+ let agentsWithSessions = 0;
192
178
  if (sessions) {
193
- if (found.length === 0) {
194
- lines.push(" sessions: none found on disk in this window");
195
- } else {
196
- for (const f of found) {
197
- lines.push(` ${f.cli}: ${f.files} file${f.files === 1 ? "" : "s"}`);
179
+ for (const id of target) {
180
+ const name = getIntegration(id).displayName;
181
+ if (NOT_RESENT.has(id)) {
182
+ rows.push([name, "not re-sent: its sessions have no time window yet"]);
183
+ continue;
198
184
  }
185
+ const n = await count(id, WHOLE_HISTORY.has(id) ? 0 : sinceMs);
186
+ if (n === 0) continue;
187
+ total += n;
188
+ agentsWithSessions += 1;
189
+ rows.push([name, WHOLE_HISTORY.has(id) ? `all ${plural(n, "session")}, whatever the window` : plural(n, "session")]);
199
190
  }
200
191
  }
192
+ const skipped = INTEGRATION_TYPES.filter((id) => !traced.includes(id)).map((id) => getIntegration(id).displayName);
201
193
 
202
194
  if (opts.dryRun) {
203
- lines.push("", "--dry-run: nothing was requested.");
195
+ const lines = [kit.header("Backfill"), "", kit.head("Would re-send", `since ${sinceLabel}, traced agents only`)];
196
+ if (!sessions) lines.push(`${" "}Hook decisions only: session collection is off.`);
197
+ else if (rows.length === 0) lines.push(" No sessions on disk in this window.");
198
+ else lines.push(...kit.rows(rows, 14));
199
+ if (skipped.length > 0) lines.push(` Not traced, skipped: ${skipped.join(", ")}`);
200
+ // Every option the preview was given, or the command it hands over re-sends
201
+ // a different window: without --since a real run takes the default 30 days.
202
+ const forReal = ["failproofai backfill"];
203
+ if (opts.sinceMs !== undefined) {
204
+ // A date Date.parse reads can hold spaces ("April 13 2026"); quoted, so
205
+ // the line still runs as printed.
206
+ const since = opts.since ?? sinceLabel;
207
+ forReal.push(`--since ${/\s/.test(since) ? `"${since}"` : since}`);
208
+ }
209
+ if (opts.agents) forReal.push(`--agents ${target.join(",")}`);
210
+ lines.push("", `Run it for real with ${kit.cmd(forReal.join(" "))}`);
204
211
  return { exitCode: 0, lines };
205
212
  }
206
213
 
207
214
  try {
208
- const path = backfillRequestPath();
209
- mkdirSync(dirname(path), { recursive: true });
210
- // Whole-file write of a small JSON object: the daemon deletes it before
211
- // acting, so a partially-written file can only ever be read once, and an
212
- // unparseable one is discarded rather than retried.
213
- writeFileSync(path, `${JSON.stringify({ sinceMs, requestedAtMs: now }, null, 2)}\n`, {
214
- mode: 0o600,
215
+ // Merged with anything still pending rather than written over it, and
216
+ // atomically: see backfill-request.ts. Without --agents the request names
217
+ // nobody, which the daemon reads as "every traced agent" at the moment it
218
+ // acts — a selection changed in between is honoured, not a stale copy.
219
+ writeBackfillRequest({
220
+ kind: "user",
221
+ sinceMs,
222
+ requestedAtMs: now,
223
+ ...(opts.agents && opts.agents.length > 0 ? { agents: target } : {}),
215
224
  });
216
225
  } catch (err) {
217
226
  return {
218
227
  exitCode: 2,
219
- lines: [
220
- ...lines,
221
- "",
222
- `Could not write the request: ${err instanceof Error ? err.message : String(err)}`,
223
- ],
228
+ lines: [kit.fail(`Could not write the request: ${err instanceof Error ? err.message : String(err)}`)],
224
229
  };
225
230
  }
226
231
 
227
- lines.push("", "Requested. The daemon picks this up within a few seconds.");
228
-
229
- // A daemon that is not running will act on the request whenever it next
230
- // starts — the request is a file, deliberately, so it survives that. Saying so
231
- // is the difference between "nothing happened" and "nothing happened yet".
232
+ const what = !sessions
233
+ ? `hook decisions since ${sinceLabel}`
234
+ : total === 0
235
+ ? `everything since ${sinceLabel}`
236
+ : `${plural(total, "session")} from ${plural(agentsWithSessions, "traced agent")} since ${sinceLabel}`;
237
+ const lines = [kit.header("Backfill"), "", kit.ok(`Asked the daemon to re-send ${what}.`)];
238
+ // Named honestly: only what the config enables, because that is all the
239
+ // daemon ships.
240
+ if (sessions && !hooks) lines.push(" Session transcripts only: hook activity collection is off.");
241
+ if (!sessions) lines.push(" Session transcripts are not sent: session collection is off.");
242
+ // A daemon that is not running acts on the request when it next starts — the
243
+ // request is a file, deliberately, so it survives that.
244
+ let waiting = false;
232
245
  if (isDaemonSupportedPlatform()) {
233
246
  const status = daemonServiceStatus();
234
247
  if (status !== "running") {
248
+ waiting = true;
235
249
  lines.push(
236
- `failproofaid is ${status}, so nothing moves until it starts. The request is`,
237
- "on disk and will be honoured then.",
250
+ kit.caution(`failproofaid is ${status}, so nothing moves until it starts. The request waits for it.`),
251
+ ` Check it: ${kit.cmd("failproofai config --status")}`,
238
252
  );
239
253
  }
240
254
  }
241
- lines.push(
242
- "",
243
- `Watch it land: ${totalFiles > 0 ? `~${totalFiles} files queued · ` : ""}` +
244
- "`failproofai config --status`",
245
- );
255
+ if (!waiting) {
256
+ // When, not "now": the daemon re-reads first, so `flush --wait` would only
257
+ // empty a queue that is still being refilled.
258
+ lines.push(` They reach the dashboard over the next few minutes. Watch them land: ${kit.cmd("failproofai config --status")}`);
259
+ }
246
260
  return { exitCode: 0, lines };
247
261
  }
@@ -0,0 +1,149 @@
1
+ /**
2
+ * The one writer of `~/.failproofai/state/backfill-request.json`.
3
+ *
4
+ * Two kinds of request go in it: `failproofai backfill` writes a `user`
5
+ * request (re-send what was modified since `sinceMs`), and every path that
6
+ * traces an agent again writes an `added` one through `requestFreshStart`
7
+ * (`failproofai config`, and `policies --install`, `policies add` and the
8
+ * dashboard's agent panel through `keepAgentSelectionTrue`): agents whose old
9
+ * cursors must not be resumed. The file is a single slot, so each writer MERGES with whatever is
10
+ * still pending instead of replacing it — otherwise config's request would
11
+ * quietly cancel a backfill somebody asked for and the daemon has not picked up
12
+ * yet. The daemon's reader is `crates/failproofaid/src/backfill.rs`.
13
+ *
14
+ * Merging keeps the stronger request. A pending `user` request stays `user`:
15
+ * its window is the one somebody chose, and an added agent swept into it is
16
+ * re-read over that window instead of the default seven days. A request with no
17
+ * `agents` reaches every traced agent, so a merge with one has no list either.
18
+ *
19
+ * An `added` request never carries `sinceMs`. A daemon older than `kind` reads
20
+ * every request as a backfill of `sinceMs`, so one written with it would
21
+ * rewind every agent on such a machine; without it, the old daemon drops it.
22
+ */
23
+ import { existsSync, readFileSync } from "node:fs";
24
+ import { join } from "node:path";
25
+
26
+ import { writeJsonAtomically } from "../../lib/atomic-write";
27
+ import { readIngestCredential } from "./collector-config";
28
+ import { readConfig } from "./fp-config";
29
+ import { failproofaiHome } from "./fp-home";
30
+ import { hookLogWarn } from "./hook-logger";
31
+
32
+ /** How long the daemon acts on an `added` request; `ADDED_REQUEST_MAX_AGE` there. */
33
+ export const ADDED_REQUEST_MAX_AGE_MS = 10 * 60 * 1000;
34
+
35
+ export type BackfillRequest =
36
+ | { kind: "user"; sinceMs: number; requestedAtMs: number; agents?: string[] }
37
+ | { kind: "added"; requestedAtMs: number; agents: string[] };
38
+
39
+ /**
40
+ * `~/.failproofai/state/backfill-request.json` — mirrored from the daemon's
41
+ * `paths::backfill_request_path()`. Two processes, one path; the comment there
42
+ * says why that is written down twice rather than derived.
43
+ */
44
+ export function backfillRequestPath(home?: string): string {
45
+ return join(failproofaiHome(home), "state", "backfill-request.json");
46
+ }
47
+
48
+ /** A pending request as the daemon would read it, or null when it would drop it. */
49
+ export function readPendingRequest(raw: unknown, now: number): BackfillRequest | null {
50
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return null;
51
+ const r = raw as Record<string, unknown>;
52
+ let agents: string[] | undefined;
53
+ if (Array.isArray(r.agents)) {
54
+ agents = [...new Set(r.agents.filter((a): a is string => typeof a === "string" && a.trim() !== ""))];
55
+ } else if (r.agents !== undefined && r.agents !== null) {
56
+ return null;
57
+ }
58
+ const requestedAtMs = typeof r.requestedAtMs === "number" ? r.requestedAtMs : undefined;
59
+ const kind = r.kind === undefined || r.kind === null ? "user" : r.kind;
60
+ if (kind === "user") {
61
+ if (typeof r.sinceMs !== "number") return null;
62
+ return { kind: "user", sinceMs: r.sinceMs, requestedAtMs: requestedAtMs ?? now, ...(agents ? { agents } : {}) };
63
+ }
64
+ if (kind === "added") {
65
+ if (requestedAtMs === undefined || !agents) return null;
66
+ if (now - requestedAtMs > ADDED_REQUEST_MAX_AGE_MS) return null;
67
+ return { kind: "added", requestedAtMs, agents };
68
+ }
69
+ return null;
70
+ }
71
+
72
+ /** What `next` becomes when `pending` is still waiting. Pure, for tests. */
73
+ export function mergeBackfillRequests(pending: BackfillRequest | null, next: BackfillRequest): BackfillRequest {
74
+ if (!pending) return next;
75
+ const union = (a?: string[], b?: string[]): string[] | undefined =>
76
+ a === undefined || b === undefined ? undefined : [...new Set([...a, ...b])];
77
+ if (pending.kind === "added" && next.kind === "added") {
78
+ return { kind: "added", requestedAtMs: next.requestedAtMs, agents: [...new Set([...pending.agents, ...next.agents])] };
79
+ }
80
+ const sinces = [pending, next].flatMap((r) => (r.kind === "user" ? [r.sinceMs] : []));
81
+ const agents = union(pending.agents, next.agents);
82
+ return {
83
+ kind: "user",
84
+ sinceMs: Math.min(...sinces),
85
+ requestedAtMs: next.requestedAtMs,
86
+ ...(agents ? { agents } : {}),
87
+ };
88
+ }
89
+
90
+ /** Write `next`, merged with whatever is still pending. Returns what was written. Throws on I/O failure. */
91
+ export function writeBackfillRequest(next: BackfillRequest, home?: string): BackfillRequest {
92
+ const path = backfillRequestPath(home);
93
+ let pending: BackfillRequest | null = null;
94
+ if (existsSync(path)) {
95
+ try {
96
+ pending = readPendingRequest(JSON.parse(readFileSync(path, "utf8")), next.requestedAtMs);
97
+ } catch {
98
+ // Unparseable: the daemon discards it, and so does this.
99
+ pending = null;
100
+ }
101
+ }
102
+ const merged = mergeBackfillRequests(pending, next);
103
+ writeJsonAtomically(path, merged);
104
+ return merged;
105
+ }
106
+
107
+ /**
108
+ * Whether this machine collects session transcripts now: an ingest key on disk
109
+ * and `collector.sessions` on. A machine that is about to connect is the
110
+ * caller's to know — `failproofai config` decides that itself.
111
+ */
112
+ export function machineCollectsSessions(): boolean {
113
+ try {
114
+ return readIngestCredential() !== null && readConfig().collector.sessions === true;
115
+ } catch {
116
+ // Unreadable credentials or config: the daemon cannot collect from them either.
117
+ return false;
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Agents traced again start fresh: their old cursors are forgotten and the
123
+ * collector reads the default window, exactly as for an agent never traced.
124
+ * Without this, an agent unticked while its files kept growing resumes the old
125
+ * offsets once it is traced again, and ships what was written while nobody
126
+ * asked for it — the job the `added` request exists for (backfill.rs).
127
+ *
128
+ * The ONE place that decides it, so every path that re-traces an agent behaves
129
+ * the same. Call it BEFORE writing the selection that traces them: the daemon
130
+ * handles a pending request ahead of its config compare, so one landing a tick
131
+ * later would let the agent resume its stale cursors first.
132
+ *
133
+ * Only when something will be collecting (`collecting`): on an open-source
134
+ * machine the request would wait for a connection that may never come, and the
135
+ * daemon drops one that old anyway. Never throws; a failure is logged, and the
136
+ * caller's own write goes ahead. Returns whether a request was written.
137
+ */
138
+ export function requestFreshStart(agents: readonly string[], collecting: boolean, now = Date.now()): boolean {
139
+ if (agents.length === 0 || !collecting) return false;
140
+ try {
141
+ writeBackfillRequest({ kind: "added", agents: [...agents], requestedAtMs: now });
142
+ return true;
143
+ } catch (err) {
144
+ hookLogWarn(
145
+ `could not write the backfill request for re-added agents: ${err instanceof Error ? err.message : String(err)}`,
146
+ );
147
+ return false;
148
+ }
149
+ }
@@ -414,7 +414,7 @@ writeCloudCredentials(creds);
414
414
  * this machine now, and — the one case worth a second line — why an existing
415
415
  * `jev.json` was not touched.
416
416
  */
417
- function jevLines(outcome: ConnectOutcome): string[] {
417
+ export function jevLines(outcome: ConnectOutcome): string[] {
418
418
  const jev = outcome.jev;
419
419
  if (!jev) return [];
420
420
  if (jev.unconfirmed === "cleared") {