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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +4 -4
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.meta +8 -0
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +4 -4
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.meta +8 -0
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +2 -2
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +4 -4
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -3
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -3
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +4 -4
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.meta +8 -0
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +4 -4
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +8 -8
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +5 -5
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
- package/.next/standalone/.next/server/app/settings/page.js +9 -9
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0qxhecv._.js → [externals]__0d7i9ea._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[externals]__0sg9-3p._.js → [externals]__0hjaw70._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__1_9ze9b._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1oenfmn._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ga9hs7._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0moxohc._.js +22 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0z2ll-a._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.js +11 -11
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1autwno._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1v25n-6._.js +3 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +2 -2
- package/.next/standalone/.next/server/chunks/_1fz8m31._.js +1 -1
- package/.next/standalone/.next/server/chunks/_1jrv3hg._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1nx8fim._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1ogwigf._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1q5i8mb._.js → _1q27azx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- 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
- 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
- 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
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0oqrl71._.js → [root-of-the-server]__042u-jn._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1-bm5zf._.js → [root-of-the-server]__0502lat._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_gn5iz._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0f765xr._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01bmjsj._.js → [root-of-the-server]__0h38gzz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hoaz1g._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ouobn4._.js → [root-of-the-server]__0mxmwtx._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__19evfi8._.js → [root-of-the-server]__0uescqv._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__13d-wb6._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1484zv2._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1mf3zp6._.js → [root-of-the-server]__19dutr7._.js} +4 -4
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1u6sn69._.js → [root-of-the-server]__1ab4ifb._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fbrk56._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1um4yxn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1wb4yt2._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1y3n3cn._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0yq5_3a._.js → _1ffxwxz._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_042cgl1._.js → _1o95rf0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_1zopuov._.js → _1thr5rf._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{_16pa9ht._.js → _1v1o2ya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -14
- package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0nkkbfv._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +1 -1
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
- package/.next/standalone/.next/static/chunks/08tmc_7n-rba1.js +1 -0
- package/.next/standalone/.next/static/chunks/{3mjfvng11xnoo.js → 09tlrmucn6oq3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{078dofaxvjgpd.js → 0i69k917myh7b.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03h3b-uomi7_d.js → 0u_nhsujkactp.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j92xul6y24_v.js → 0zp51hzx62i-u.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3u1s8dsqiean5.js → 1fz7q0gtnmeod.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3ob2161ap37ul.js → 1j75le8wln-26.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2kj826h1w9r47.js → 1z4km7tw0_50h.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1fqukzzowjflt.js → 239ay_80-xr8z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3nkc2jlgyygda.js → 2c9pc5xco6rnu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3i8rg2fv9nd6e.js → 3n46e45atic-m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0542cuk_va8g1.js → 3sg07wnn969xq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-11dn2pegmg9_9.js → turbopack-1opxka4dtjbc5.js} +1 -1
- package/.next/standalone/app/actions/install-hooks-web.ts +10 -2
- package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/256.js +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/hash.js/sha256/package.json +1 -0
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +17 -17
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +18 -18
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +3 -3
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +13 -13
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/server/base-server.js +1 -2
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +265 -53
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/file-system-cache.js +177 -19
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +36 -17
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/shared-cache-controls.external.js +14 -6
- package/.next/standalone/node_modules/next/dist/server/lib/match-next-data-pathname.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/lib/node-fs-methods.js +14 -0
- package/.next/standalone/node_modules/next/dist/server/lib/route-cache-key.js +77 -0
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/block-cross-site-dev.js +9 -11
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/mcp/get-mcp-middleware.js +4 -2
- package/.next/standalone/node_modules/next/dist/server/next-server.js +7 -3
- package/.next/standalone/node_modules/next/dist/server/response-cache/index.js +32 -16
- package/.next/standalone/node_modules/next/dist/server/route-matcher-providers/app-page-route-matcher-provider.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/route-modules/pages/pages-handler.js +6 -1
- package/.next/standalone/node_modules/next/dist/server/route-modules/route-module.js +9 -2
- package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +30 -29
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/app-paths.js +18 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +14 -11
- package/.next/standalone/sdk/python/README.md +41 -0
- package/.next/standalone/sdk/python/docs/README.md +1 -0
- package/.next/standalone/sdk/python/docs/otel/README.md +36 -0
- package/.next/standalone/sdk/python/docs/otel/examples/agent_trace.py +30 -0
- package/.next/standalone/sdk/python/docs/otel/examples/quickstart.py +22 -0
- package/.next/standalone/sdk/python/failproofai_sdk/__init__.py +8 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/__init__.py +3 -0
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/otel.py +189 -0
- package/.next/standalone/sdk/python/pyproject.toml +2 -1
- package/.next/standalone/sdk/python/tests/test_docs.py +16 -3
- package/.next/standalone/sdk/python/tests/test_integrations.py +1 -1
- package/.next/standalone/sdk/python/tests/test_otel.py +147 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +4 -1
- package/.next/standalone/sdk/python/uv.lock +258 -147
- package/.next/standalone/sdk/typescript/README.md +43 -0
- package/.next/standalone/sdk/typescript/integration/ai.test.ts +9 -2
- package/.next/standalone/sdk/typescript/package-lock.json +57 -5
- package/.next/standalone/sdk/typescript/package.json +2 -0
- package/.next/standalone/sdk/typescript/src/edge/index.ts +9 -0
- package/.next/standalone/sdk/typescript/src/index.ts +2 -0
- package/.next/standalone/sdk/typescript/src/integrations/index.ts +4 -1
- package/.next/standalone/sdk/typescript/src/integrations/otel.ts +204 -0
- package/.next/standalone/sdk/typescript/test/integrations.test.ts +1 -1
- package/.next/standalone/sdk/typescript/test/otel.test.ts +125 -0
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +653 -1077
- package/dist/cli.mjs +8174 -3930
- package/dist/worker.mjs +170 -62
- package/hermes-plugin/plugin.yaml +1 -1
- package/package.json +14 -11
- package/scripts/launch-facts.ts +157 -0
- package/scripts/launch-ready.ts +228 -0
- package/scripts/launch-screen.ts +202 -0
- package/scripts/launch.ts +106 -49
- package/scripts/skew-log-filter.ts +42 -0
- package/src/audit/cli-login.ts +39 -59
- package/src/audit/cli.ts +308 -228
- package/src/audit/index.ts +45 -2
- package/src/audit/schedule-cli.ts +83 -120
- package/src/audit/types.ts +31 -0
- package/src/hooks/agent-selection.ts +155 -0
- package/src/hooks/backfill-cli.ts +151 -137
- package/src/hooks/backfill-request.ts +149 -0
- package/src/hooks/cloud-connection.ts +1 -1
- package/src/hooks/cloud-enrollment-cli.ts +78 -28
- package/src/hooks/cloud-introspect.ts +3 -0
- package/src/hooks/collector-config.ts +10 -1
- package/src/hooks/config-status.ts +606 -0
- package/src/hooks/configure-wizard.ts +915 -989
- package/src/hooks/daemon-service.ts +16 -6
- package/src/hooks/did-you-mean.ts +185 -0
- package/src/hooks/flush-cli.ts +33 -7
- package/src/hooks/fp-config.ts +96 -0
- package/src/hooks/fp-home.ts +11 -0
- package/src/hooks/fp-reset.ts +102 -95
- package/src/hooks/handler.ts +4 -0
- package/src/hooks/harness-cli.ts +87 -138
- package/src/hooks/install-prompt.ts +38 -477
- package/src/hooks/integrations.ts +1 -1
- package/src/hooks/jev-cli.ts +183 -196
- package/src/hooks/manager.ts +554 -358
- package/src/hooks/onboarding-attempt.ts +11 -9
- package/src/hooks/otel-cli.ts +344 -0
- package/src/hooks/otel-output.ts +24 -0
- package/src/hooks/otel-writers.ts +225 -0
- package/src/hooks/pack-cli.ts +219 -204
- package/src/hooks/report-body.ts +50 -0
- package/src/hooks/safe-config-write.ts +4 -2
- package/src/hooks/session-pause-cli.ts +93 -73
- package/src/hooks/tui.ts +849 -389
- package/src/hooks/uninstall-cli.ts +136 -149
- package/src/hooks/untraced-note.ts +52 -0
- package/.next/standalone/.next/server/chunks/[externals]__19_pzeq._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__20kzpkf._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__01eqny5._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__05ja_05._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0igxt32._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +0 -22
- package/.next/standalone/.next/server/chunks/_0bs-v8p._.js +0 -3
- package/.next/standalone/.next/server/chunks/_185gyk9._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0yrsbd_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +0 -6
- package/.next/standalone/.next/static/chunks/1lyg_puttohbc.js +0 -1
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{ECs8__hcNabYV58WfDh-3 → UZKCiP4Q2TeU8zOB-h9Xj}/_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
|
|
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
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
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
|
|
76
|
-
|
|
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
|
-
/**
|
|
92
|
-
function
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
const
|
|
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
|
|
181
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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.
|
|
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
|
-
|
|
209
|
-
|
|
210
|
-
//
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
|
237
|
-
"
|
|
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
|
-
|
|
242
|
-
"",
|
|
243
|
-
|
|
244
|
-
|
|
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") {
|