failproofai 1.0.10 → 1.0.11-beta.1

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 (297) 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]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
  117. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
  119. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
  120. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
  122. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
  123. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
  124. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
  130. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.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/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
  137. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
  138. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  141. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  142. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  143. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
  144. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
  145. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  147. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  148. package/.next/standalone/.next/server/middleware.js +1 -1
  149. package/.next/standalone/.next/server/pages/404.html +1 -1
  150. package/.next/standalone/.next/server/pages/500.html +1 -1
  151. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  152. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  153. package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
  154. package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
  155. package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
  158. package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
  159. package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
  160. package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
  161. package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
  164. package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
  165. package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
  166. package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
  167. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  168. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  169. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  170. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
  171. package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
  172. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
  173. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
  174. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
  175. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
  176. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  177. package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
  178. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  179. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  180. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  181. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
  182. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  183. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
  184. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
  185. package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
  186. package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
  187. package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
  188. package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
  189. package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
  190. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  191. package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
  192. package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
  193. package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
  194. package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
  195. package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
  196. package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
  197. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
  198. package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
  199. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  200. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  201. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  202. package/.next/standalone/node_modules/next/package.json +10 -10
  203. package/.next/standalone/package.json +14 -11
  204. package/.next/standalone/sdk/python/README.md +41 -0
  205. package/.next/standalone/sdk/python/docs/README.md +1 -0
  206. package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
  207. package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
  208. package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
  209. package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
  210. package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
  211. package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
  212. package/.next/standalone/sdk/python/pyproject.toml +2 -1
  213. package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
  214. package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
  215. package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
  216. package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
  217. package/.next/standalone/sdk/python/uv.lock +258 -147
  218. package/.next/standalone/sdk/typescript/README.md +43 -0
  219. package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
  220. package/.next/standalone/sdk/typescript/package-lock.json +57 -5
  221. package/.next/standalone/sdk/typescript/package.json +2 -0
  222. package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
  223. package/.next/standalone/sdk/typescript/src/index.ts +2 -0
  224. package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
  225. package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
  226. package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
  227. package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
  228. package/.next/standalone/server.js +1 -1
  229. package/bin/failproofai.mjs +653 -1077
  230. package/dist/cli.mjs +8174 -3930
  231. package/dist/worker.mjs +170 -62
  232. package/hermes-plugin/plugin.yaml +1 -1
  233. package/package.json +14 -11
  234. package/scripts/launch-facts.ts +157 -0
  235. package/scripts/launch-ready.ts +228 -0
  236. package/scripts/launch-screen.ts +202 -0
  237. package/scripts/launch.ts +106 -49
  238. package/scripts/skew-log-filter.ts +42 -0
  239. package/src/audit/cli-login.ts +39 -59
  240. package/src/audit/cli.ts +308 -228
  241. package/src/audit/index.ts +45 -2
  242. package/src/audit/schedule-cli.ts +83 -120
  243. package/src/audit/types.ts +31 -0
  244. package/src/hooks/agent-selection.ts +155 -0
  245. package/src/hooks/backfill-cli.ts +151 -137
  246. package/src/hooks/backfill-request.ts +149 -0
  247. package/src/hooks/cloud-connection.ts +1 -1
  248. package/src/hooks/cloud-enrollment-cli.ts +78 -28
  249. package/src/hooks/cloud-introspect.ts +3 -0
  250. package/src/hooks/collector-config.ts +10 -1
  251. package/src/hooks/config-status.ts +606 -0
  252. package/src/hooks/configure-wizard.ts +915 -989
  253. package/src/hooks/daemon-service.ts +16 -6
  254. package/src/hooks/did-you-mean.ts +185 -0
  255. package/src/hooks/flush-cli.ts +33 -7
  256. package/src/hooks/fp-config.ts +96 -0
  257. package/src/hooks/fp-home.ts +11 -0
  258. package/src/hooks/fp-reset.ts +102 -95
  259. package/src/hooks/handler.ts +4 -0
  260. package/src/hooks/harness-cli.ts +87 -138
  261. package/src/hooks/install-prompt.ts +38 -477
  262. package/src/hooks/integrations.ts +1 -1
  263. package/src/hooks/jev-cli.ts +183 -196
  264. package/src/hooks/manager.ts +554 -358
  265. package/src/hooks/onboarding-attempt.ts +11 -9
  266. package/src/hooks/otel-cli.ts +344 -0
  267. package/src/hooks/otel-output.ts +24 -0
  268. package/src/hooks/otel-writers.ts +225 -0
  269. package/src/hooks/pack-cli.ts +219 -204
  270. package/src/hooks/report-body.ts +50 -0
  271. package/src/hooks/safe-config-write.ts +4 -2
  272. package/src/hooks/session-pause-cli.ts +93 -73
  273. package/src/hooks/tui.ts +849 -389
  274. package/src/hooks/uninstall-cli.ts +136 -149
  275. package/src/hooks/untraced-note.ts +52 -0
  276. package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
  277. package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
  283. package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
  290. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
  291. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
  292. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
  293. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
  294. package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
  295. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
  296. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
  297. /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_ssgManifest.js +0 -0
@@ -1617,14 +1617,24 @@ export async function refreshDaemonToCliVersion(
1617
1617
 
1618
1618
  const result = await install();
1619
1619
  if (!result.installed) {
1620
+ // Root is the usual reason, and the install's own `reason` for it is written
1621
+ // for the wizard and the log: it says to re-run `failproofai config` and
1622
+ // carries a full unit file to paste. From `update` the answer is shorter,
1623
+ // and names this command: get sudo, then run it again. Never "run the CLI
1624
+ // under sudo" — that would configure root's account instead of this one.
1625
+ const needsRoot = /root privileges are required/.test(result.reason ?? "");
1620
1626
  return {
1621
1627
  ok: false,
1622
- lines: [
1623
- `failproofaid ${version} could not be installed: ${result.reason ?? "unknown reason"}`,
1624
- `The previous daemon is untouched. On a machine configured to require it,`,
1625
- `enforcement continues; collection and cloud policy may be stale until this`,
1626
- `succeeds. \`${daemonStatusCommand() ?? "systemctl status"}\` will say more.`,
1627
- ],
1628
+ lines: needsRoot
1629
+ ? [
1630
+ `✕ failproofaid ${version} was not installed: root privileges are required. The previous daemon is untouched.`,
1631
+ " Run `sudo -v` and then `failproofai update` again.",
1632
+ ]
1633
+ : [
1634
+ `✕ failproofaid ${version} could not be installed: ${(result.reason ?? "unknown reason").replace(/\.$/, "")}. ` +
1635
+ "The previous daemon is untouched.",
1636
+ ` See why with \`${daemonStatusCommand() ?? "systemctl status"}\`.`,
1637
+ ],
1628
1638
  };
1629
1639
  }
1630
1640
  // RECORD THE VERSION, or the refresh is invisible to everything that asks.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * "Did you mean …?" for a command or a top-level flag the CLI does not know.
3
+ *
4
+ * Two sources, asked in this order:
5
+ *
6
+ * 1. Words people type that are not commands here, each mapped to the real
7
+ * command that does what they meant: `status` is `config --status`, not
8
+ * `flush`. The nearest-spelling guess used to answer `status` with
9
+ * `flush` and `install` with `uninstall` — the opposite of the request.
10
+ * 2. The nearest real subcommand by spelling, for a typo of one. A word
11
+ * that is a candidate with `un`/`de`/`dis` added or removed is never its
12
+ * match: `unpause` is two edits from `pause` and means the opposite.
13
+ *
14
+ * Both are matched within a distance cut-off, so a typo of a table word still
15
+ * finds it (`statsu` → `config --status`), and a word that is close to nothing
16
+ * gets NO suggestion. A guess at something unrelated sends a person to the
17
+ * wrong command with confidence; saying nothing sends them to `help`.
18
+ *
19
+ * Every suggestion is a whole command, runnable exactly as printed.
20
+ */
21
+
22
+ /** Words that are not commands here, and what to run instead. Lower case. */
23
+ const WORD_INTENTS: Readonly<Record<string, string>> = {
24
+ status: "failproofai config --status",
25
+ doctor: "failproofai config --status",
26
+ whoami: "failproofai config --status",
27
+ pause: "failproofai config --pause",
28
+ resume: "failproofai config --resume",
29
+ // Docker's word for it. Two edits from `pause`, which is the opposite.
30
+ unpause: "failproofai config --resume",
31
+ login: "failproofai config",
32
+ signin: "failproofai config",
33
+ "sign-in": "failproofai config",
34
+ connect: "failproofai config",
35
+ logout: "failproofai config --disconnect",
36
+ disconnect: "failproofai config --disconnect",
37
+ // `setup`, `configure`, `policy` and `pack` already RUN as their command;
38
+ // they are here so a typo of one finds it too.
39
+ setup: "failproofai config",
40
+ configure: "failproofai config",
41
+ policy: "failproofai policies",
42
+ pack: "failproofai policies",
43
+ install: "failproofai policies --install",
44
+ enable: "failproofai policies add",
45
+ disable: "failproofai policies remove",
46
+ add: "failproofai policies add",
47
+ list: "failproofai policies",
48
+ ls: "failproofai policies",
49
+ upgrade: "failproofai update",
50
+ version: "failproofai --version",
51
+ hook: "failproofai help hook",
52
+ hooks: "failproofai help hook",
53
+ dashboard: "failproofai",
54
+ start: "failproofai",
55
+ open: "failproofai",
56
+ // `auth login` was the audit reminder's sign-in until it was removed; that
57
+ // sign-in lives with the audit now.
58
+ auth: "failproofai audit",
59
+ };
60
+
61
+ /** Top-level flags that belong to a command, and that command. */
62
+ const FLAG_INTENTS: Readonly<Record<string, string>> = {
63
+ "--status": "failproofai config --status",
64
+ "--pause": "failproofai config --pause",
65
+ "--resume": "failproofai config --resume",
66
+ "--unpause": "failproofai config --resume",
67
+ "--token": "failproofai config",
68
+ "--connect": "failproofai config",
69
+ "--disconnect": "failproofai config --disconnect",
70
+ "--install": "failproofai policies --install",
71
+ "-i": "failproofai policies --install",
72
+ // Two edits from `--install`: without these the nearest spelling answers a
73
+ // request to take hooks out with the command that puts them in.
74
+ "--uninstall": "failproofai policies --uninstall",
75
+ "-u": "failproofai policies --uninstall",
76
+ "--list": "failproofai policies",
77
+ "--all": "failproofai policies --all",
78
+ };
79
+
80
+ /** The flags that are top-level in their own right, and are typo targets. */
81
+ const TOP_LEVEL_FLAGS = ["--version", "--help"] as const;
82
+
83
+ /**
84
+ * Edit distance with a swap of two neighbouring letters counted as ONE edit
85
+ * (optimal string alignment) — `cofnig` is one slip from `config`, not two.
86
+ */
87
+ function typoDistance(a: string, b: string): number {
88
+ const d: number[][] = Array.from({ length: a.length + 1 }, (_, i) =>
89
+ Array.from({ length: b.length + 1 }, (_, j) => (i === 0 ? j : j === 0 ? i : 0)),
90
+ );
91
+ for (let i = 1; i <= a.length; i++) {
92
+ for (let j = 1; j <= b.length; j++) {
93
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
94
+ d[i][j] = Math.min(d[i - 1][j] + 1, d[i][j - 1] + 1, d[i - 1][j - 1] + cost);
95
+ if (i > 1 && j > 1 && a[i - 1] === b[j - 2] && a[i - 2] === b[j - 1]) {
96
+ d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + 1);
97
+ }
98
+ }
99
+ }
100
+ return d[a.length][b.length];
101
+ }
102
+
103
+ /**
104
+ * How far a typo may be from what it meant. Two letters or fewer must match
105
+ * exactly — almost anything is one edit from `ls`. Up to four letters, one
106
+ * edit; longer, two. `install` is two edits from `uninstall`, which is why the
107
+ * word table is asked first.
108
+ */
109
+ function typoReach(word: string): number {
110
+ if (word.length <= 2) return 0;
111
+ return word.length <= 4 ? 1 : 2;
112
+ }
113
+
114
+ /** Prefixes that turn a word into its opposite: `unpause`, `deinstall`, `disable`. */
115
+ const NEGATING_PREFIXES = ["un", "de", "dis"] as const;
116
+
117
+ /**
118
+ * Whether `a` is `b` with a negating prefix, either way round. Such a pair is
119
+ * a short edit apart and means the opposite, so it is never a typo match.
120
+ */
121
+ function isNegationOf(a: string, b: string): boolean {
122
+ return NEGATING_PREFIXES.some((prefix) => a === prefix + b || b === prefix + a);
123
+ }
124
+
125
+ /**
126
+ * What `word` most likely meant among `candidates` (spelling → command), or
127
+ * null. A word of three letters or more that starts exactly one command's
128
+ * spelling is that command — `back` is `backfill`, though `pack` is nearer by
129
+ * spelling. Otherwise the closest spelling within reach, the earliest one
130
+ * winning a tie. A word that is a candidate with a negating prefix added or
131
+ * taken away is never matched to it.
132
+ */
133
+ function nearestOf(word: string, candidates: ReadonlyArray<readonly [string, string]>): string | null {
134
+ if (word.length >= 3) {
135
+ const prefixed = new Set(candidates.filter(([spelling]) => spelling.startsWith(word)).map(([, to]) => to));
136
+ if (prefixed.size === 1) return [...prefixed][0];
137
+ }
138
+ const reach = typoReach(word);
139
+ let best: { to: string; distance: number } | null = null;
140
+ for (const [spelling, to] of candidates) {
141
+ if (isNegationOf(word, spelling)) continue;
142
+ const distance = typoDistance(word, spelling);
143
+ if (distance <= reach && (best === null || distance < best.distance)) best = { to, distance };
144
+ }
145
+ return best?.to ?? null;
146
+ }
147
+
148
+ /**
149
+ * The command to suggest for an unknown word, or null when nothing is close.
150
+ * `subcommands` is the CLI's own list, passed in so there is one copy of it.
151
+ */
152
+ export function suggestCommand(word: string, subcommands: readonly string[]): string | null {
153
+ const typed = word.trim().toLowerCase();
154
+ if (!typed) return null;
155
+ const intent = WORD_INTENTS[typed];
156
+ if (intent) return intent;
157
+ return nearestOf(typed, [
158
+ ...subcommands.map((name) => [name, `failproofai ${name}`] as const),
159
+ ...Object.entries(WORD_INTENTS),
160
+ ]);
161
+ }
162
+
163
+ /** The one of `names` that `word` is a typo of, or null when none is close. */
164
+ export function nearestName(word: string, names: readonly string[]): string | null {
165
+ const typed = word.trim().toLowerCase();
166
+ if (!typed) return null;
167
+ return nearestOf(typed, names.map((name) => [name, name] as const));
168
+ }
169
+
170
+ /** The command to suggest for an unknown top-level flag, or null when nothing is close. */
171
+ export function suggestFlag(flag: string): string | null {
172
+ const typed = flag.trim().toLowerCase();
173
+ // `-V` and `-H` reach here only because of their case.
174
+ if (typed === "-v") return "failproofai --version";
175
+ if (typed === "-h") return "failproofai --help";
176
+ const intent = FLAG_INTENTS[typed];
177
+ if (intent) return intent;
178
+ // Compared without the dashes, so `-verison` and `--verison` read alike.
179
+ const bare = (spelling: string) => spelling.replace(/^-+/, "");
180
+ if (!bare(typed)) return null;
181
+ return nearestOf(bare(typed), [
182
+ ...TOP_LEVEL_FLAGS.map((name) => [bare(name), `failproofai ${name}`] as const),
183
+ ...Object.entries(FLAG_INTENTS).map(([spelling, to]) => [bare(spelling), to] as const),
184
+ ]);
185
+ }
@@ -19,7 +19,7 @@
19
19
  * before returning success. A CLI that prints "requested" while the daemon is
20
20
  * stopped has told the user the opposite of what happened.
21
21
  */
22
- import { existsSync, mkdirSync, writeFileSync, readdirSync } from "node:fs";
22
+ import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync } from "node:fs";
23
23
  import { dirname, join } from "node:path";
24
24
  import { customAgentsEventsDir, failedDir, failproofaiHome, spoolDir } from "./fp-home";
25
25
  import { readConfig } from "./fp-config";
@@ -86,15 +86,39 @@ function parkedLine(n: number, home?: string): string {
86
86
 
87
87
  /** Batches awaiting delivery. `.tmp` files are half-written and not counted. */
88
88
  export function pendingBatches(home?: string): number {
89
- let n = 0;
89
+ return spoolBacklog(home).count;
90
+ }
91
+
92
+ /**
93
+ * {@link pendingBatches}, plus how long the oldest of them has waited.
94
+ *
95
+ * The age is what tells a backlog that is draining from one that is stuck: the
96
+ * collector sweeps a batch only once it is two minutes old, so a few young
97
+ * batches are normal, while one that has sat for a quarter of an hour has not
98
+ * been delivered by several passes. `config --status` reads it for that.
99
+ */
100
+ export function spoolBacklog(home?: string, now: number = Date.now()): { count: number; oldestAgeMs?: number } {
101
+ let count = 0;
102
+ let oldest: number | undefined;
90
103
  for (const dir of spoolDirs(home)) {
104
+ let names: string[];
91
105
  try {
92
- n += readdirSync(dir).filter((f) => f.endsWith(".jsonl")).length;
106
+ names = readdirSync(dir).filter((f) => f.endsWith(".jsonl"));
93
107
  } catch {
94
108
  // A directory that vanished mid-scan is one the collector just drained.
109
+ continue;
110
+ }
111
+ count += names.length;
112
+ for (const name of names) {
113
+ try {
114
+ const mtime = statSync(join(dir, name)).mtimeMs;
115
+ if (oldest === undefined || mtime < oldest) oldest = mtime;
116
+ } catch {
117
+ // Delivered between the listing and the stat.
118
+ }
95
119
  }
96
120
  }
97
- return n;
121
+ return oldest === undefined ? { count } : { count, oldestAgeMs: Math.max(0, now - oldest) };
98
122
  }
99
123
 
100
124
  export async function runFlushCommand(opts: FlushOptions = {}): Promise<FlushResult> {
@@ -198,10 +222,12 @@ export async function runFlushCommand(opts: FlushOptions = {}): Promise<FlushRes
198
222
 
199
223
  // A timeout is not necessarily a failure — a large backlog legitimately takes
200
224
  // longer than the budget — so say what is still outstanding rather than
201
- // calling it broken.
225
+ // calling it broken. Marked ▲ on its own line: the report opens on the
226
+ // "Requested delivery." line, and an unmarked failed report puts its ✕ on
227
+ // whichever line comes first.
202
228
  lines.push(
203
- `Still ${pending} batch${pending === 1 ? "" : "es"} spooled after ${timeoutSecs}s.`,
204
- "Delivery continues in the background; re-run with a longer --timeout to keep watching.",
229
+ `▲ Still ${pending} batch${pending === 1 ? "" : "es"} spooled after ${timeoutSecs}s.`,
230
+ " Delivery continues in the background; re-run with a longer --timeout to keep watching.",
205
231
  );
206
232
  return { exitCode: 1, pending, lines };
207
233
  }
@@ -281,6 +281,14 @@ export interface FpConfig {
281
281
  };
282
282
  collector: {
283
283
  sessions: boolean;
284
+ /**
285
+ * Per-agent transcript transport overrides. Orthogonal to `agents.selected`,
286
+ * which gates collection: a traced agent using OTEL keeps its hooks, but
287
+ * must not also send transcripts. Retain the original OTEL key layout so
288
+ * upgrades and recovery snapshots can restore the previous preference.
289
+ */
290
+ agents?: Record<string, { sessions?: boolean }>;
291
+ otlp?: { enabled: boolean; port?: number };
284
292
  hooks: boolean;
285
293
  hooksVerbosity: "all" | "decisions" | "off";
286
294
  redact: "minimal" | "off";
@@ -368,6 +376,23 @@ export interface FpConfig {
368
376
  /** Days between scheduled runs. Wall clock, so it survives suspend. */
369
377
  intervalDays: number;
370
378
  };
379
+ /**
380
+ * Which agents failproofai traces: the remembered answer to `failproofai
381
+ * config`'s "which agents should failproofai trace?".
382
+ *
383
+ * `selected` drives every per-agent thing at once — which CLIs get hooks,
384
+ * which harnesses the daemon's collector reads, and which `backfill` re-sends.
385
+ * `seen` is every agent that was detected at the last setup, which is how a
386
+ * newly installed agent is recognised and offered as "new on this machine".
387
+ * Ids are the integration ids (`claude`, `codex`, …).
388
+ *
389
+ * ABSENT means today's behaviour, everything, and that is what every machine
390
+ * set up before this existed has. So `writeConfig` emits nothing in that case,
391
+ * and the daemon — which reads `agents.selected` from this same file — keeps
392
+ * collecting every harness. The direction matters: an unreadable or missing
393
+ * selection must never quietly switch tracing OFF.
394
+ */
395
+ agents?: { selected: string[]; seen: string[] };
371
396
  }
372
397
 
373
398
  /**
@@ -443,6 +468,45 @@ function readSources(raw: unknown): FpConfig["collector"]["sources"] {
443
468
  return Object.keys(out).length > 0 ? out : undefined;
444
469
  }
445
470
 
471
+ function readAgentSessions(raw: unknown): FpConfig["collector"]["agents"] {
472
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
473
+ const agents = Object.fromEntries(Object.entries(raw).flatMap(([agent, settings]) => {
474
+ if (!settings || typeof settings !== "object" || Array.isArray(settings)) return [];
475
+ const sessions = (settings as Record<string, unknown>).sessions;
476
+ return typeof sessions === "boolean" ? [[agent, { sessions }]] : [];
477
+ }));
478
+ return Object.keys(agents).length ? agents : undefined;
479
+ }
480
+
481
+ function readOtlpSettings(raw: unknown): FpConfig["collector"]["otlp"] {
482
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
483
+ const settings = raw as Record<string, unknown>;
484
+ return {
485
+ enabled: settings.enabled === true,
486
+ ...(typeof settings.port === "number" && Number.isInteger(settings.port) ? { port: settings.port } : {}),
487
+ };
488
+ }
489
+
490
+ /**
491
+ * `agents` → the in-memory shape. Kept as written (strings only, de-duplicated
492
+ * in order): the daemon owns the list of real agent ids and warns about an
493
+ * unknown one, so dropping it here would hide that warning behind a value the
494
+ * CLI had already rewritten — the same reasoning as {@link readSources}.
495
+ *
496
+ * `undefined` unless `selected` is an array, because absent means everything
497
+ * and a garbage value must fail in that direction too.
498
+ */
499
+ function readAgents(raw: unknown): FpConfig["agents"] {
500
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return undefined;
501
+ const ids = (v: unknown): string[] =>
502
+ Array.isArray(v)
503
+ ? [...new Set(v.filter((e): e is string => typeof e === "string" && e.trim() !== ""))]
504
+ : [];
505
+ const table = raw as Record<string, unknown>;
506
+ if (!Array.isArray(table.selected)) return undefined;
507
+ return { selected: ids(table.selected), seen: ids(table.seen) };
508
+ }
509
+
446
510
  /**
447
511
  * {@link readConfig}, plus the raw object it was projected from.
448
512
  *
@@ -532,6 +596,8 @@ export function projectConfig(parsed: Record<string, unknown>): FpConfig {
532
596
  typeof collector.environment === "string" ? collector.environment : "local",
533
597
  machineId: typeof collector.machine_id === "string" ? collector.machine_id : undefined,
534
598
  sources: readSources(collector.sources),
599
+ agents: readAgentSessions(collector.agents),
600
+ otlp: readOtlpSettings(collector.otlp),
535
601
  },
536
602
  // Only an explicit `false` switches it off. Absent, or any other value,
537
603
  // reads as on — the shipped default, and what a config with no
@@ -552,6 +618,7 @@ export function projectConfig(parsed: Record<string, unknown>): FpConfig {
552
618
  ? audit.reports_consented_at
553
619
  : undefined,
554
620
  },
621
+ agents: readAgents(parsed.agents),
555
622
  // Same shape as `audit.auto` above and for the same reason: only an
556
623
  // explicit `true` opts in. Anything else — absent, misspelled, `"yes"` —
557
624
  // reads as off, because the failure direction is a machine that starts
@@ -594,10 +661,15 @@ const OWNED_CONFIG_KEYS: readonly (readonly string[])[] = [
594
661
  ["collector", "environment"],
595
662
  ["collector", "machine_id"],
596
663
  ["collector", "sources", "*", "extra_paths"],
664
+ ["collector", "agents", "*", "sessions"],
665
+ ["collector", "otlp", "enabled"],
666
+ ["collector", "otlp", "port"],
597
667
  ["telemetry", "enabled"],
598
668
  ["audit", "auto"],
599
669
  ["audit", "interval_days"],
600
670
  ["audit", "reports_consented_at"],
671
+ ["agents", "selected"],
672
+ ["agents", "seen"],
601
673
  ];
602
674
 
603
675
  const isPlainObject = (v: unknown): v is Record<string, unknown> =>
@@ -669,6 +741,8 @@ export function writeConfig(config: FpConfig, raw?: Record<string, unknown>): vo
669
741
  // Omitted when unset rather than written null: the Rust side treats an
670
742
  // absent machine_id as "derive one", and an explicit null is not that.
671
743
  ...(c.machineId ? { machine_id: c.machineId } : {}),
744
+ ...(c.agents ? { agents: c.agents } : {}),
745
+ ...(c.otlp ? { otlp: c.otlp } : {}),
672
746
  // Extra capture paths per harness. Emitted ONLY when non-empty, so a
673
747
  // machine that never configured one gets a file byte-identical to what
674
748
  // this function produced before the field existed — the same reasoning as
@@ -708,6 +782,12 @@ export function writeConfig(config: FpConfig, raw?: Record<string, unknown>): vo
708
782
  ? {}
709
783
  : { reports_consented_at: config.audit.reportsConsentedAt }),
710
784
  },
785
+ // Written ONLY when a selection exists — absent means "every agent", and a
786
+ // machine that never chose keeps a file byte-identical to what this
787
+ // function wrote before the field existed.
788
+ ...(config.agents
789
+ ? { agents: { selected: config.agents.selected, seen: config.agents.seen } }
790
+ : {}),
711
791
  };
712
792
  // Start from the previous bytes, strip the keys this build owns — so an
713
793
  // omission above really removes — then lay the projection on top. What is left
@@ -726,6 +806,8 @@ export function updateConfig(patch: {
726
806
  collector?: Partial<FpConfig["collector"]>;
727
807
  telemetry?: Partial<FpConfig["telemetry"]>;
728
808
  audit?: Partial<FpConfig["audit"]>;
809
+ /** Replace the agent selection; `null` removes it (back to "every agent"). */
810
+ agents?: FpConfig["agents"] | null;
729
811
  }): FpConfig {
730
812
  // `readConfigRaw`, not `readConfig` — this is the function EVERY mutation path
731
813
  // goes through (the wizard, `harness add-path`, `healDaemonFlag`, the telemetry
@@ -738,11 +820,25 @@ export function updateConfig(patch: {
738
820
  collector: { ...current.collector, ...patch.collector },
739
821
  telemetry: { ...current.telemetry, ...patch.telemetry },
740
822
  audit: { ...current.audit, ...patch.audit },
823
+ // Carried forward when the patch does not name it. Every other mutation
824
+ // path goes through here — the wizard's own connect step among them — and
825
+ // a selection dropped by an unrelated write would silently turn tracing
826
+ // back on for every agent the user switched off.
827
+ agents: patch.agents === undefined ? current.agents : (patch.agents ?? undefined),
741
828
  };
742
829
  writeConfig(next, raw);
743
830
  return next;
744
831
  }
745
832
 
833
+ /**
834
+ * Whether failproofai traces this agent: hooked, collected and backfilled.
835
+ * True for every agent when no selection has been saved, which is today's
836
+ * behaviour and what every machine set up before the selection existed has.
837
+ */
838
+ export function isAgentTraced(id: string, config: FpConfig = readConfig()): boolean {
839
+ return !config.agents || config.agents.selected.includes(id);
840
+ }
841
+
746
842
  // ── credentials.json ─────────────────────────────────────────────────────────
747
843
 
748
844
  export interface FpCredentials {
@@ -139,6 +139,14 @@ export const versionFile = (home?: string) => atHome(home, "VERSION");
139
139
  /** Non-secret configuration. World-readable by design; never holds a token. */
140
140
  export const configFile = (home?: string) => atHome(home, "config.json");
141
141
 
142
+ export const otelAgentStateDir = (home?: string) => atHome(home, "state", "otel-agents");
143
+ export const otelAgentStateFile = (agent: string) => {
144
+ if (!["claude", "codex", "gemini", "copilot"].includes(agent)) throw new Error("Unknown OTEL agent.");
145
+ return resolve(otelAgentStateDir(), `${agent}.json`);
146
+ };
147
+ export const otlpSpoolDir = (home?: string) => atHome(home, "state", "spool-otlp");
148
+ export const otlpHealthFile = (home?: string) => atHome(home, "state", "otlp-health.json");
149
+
142
150
  /**
143
151
  * Every credential, owner-only.
144
152
  *
@@ -576,6 +584,7 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
576
584
  // the collector preferences, `[audit] auto`, the telemetry opt-out, and
577
585
  // `collector.sources.*.extra_paths` — the entire output of `harness add-path`.
578
586
  { path: configFile, class: "user-typed" },
587
+ { path: otelAgentStateDir, class: "user-typed" },
579
588
  // The builtin enable/disable set and per-policy params. `resetHome` used to
580
589
  // clear this and carry eight named keys back over; it is simply kept now.
581
590
  { path: globalPolicyConfigFile, class: "user-typed" },
@@ -602,6 +611,7 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
602
611
  // these is PERMANENT and not merely slow is `cursorsDir` below: the watermark
603
612
  // has already advanced past them, so nothing will ever read that range again.
604
613
  { path: spoolDir, class: "undelivered" },
614
+ { path: otlpSpoolDir, class: "undelivered" },
605
615
  { path: failedDir, class: "undelivered" },
606
616
  // The SDK spool (`events/` + `failed/`), same argument.
607
617
  { path: customAgentsDir, class: "undelivered" },
@@ -657,6 +667,7 @@ export const HOME_CLASSES: readonly { path: (home?: string) => string; class: Da
657
667
  { path: auditCacheDir, class: "derived" },
658
668
  { path: auditScheduleFile, class: "derived" },
659
669
  { path: collectorHealthFile, class: "derived" },
670
+ { path: otlpHealthFile, class: "derived" },
660
671
  { path: codexSessionPathsFile, class: "derived" },
661
672
  { path: shimsDir, class: "derived" },
662
673
  { path: sessionPauseDir, class: "derived" },