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
package/src/audit/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { batchAll } from "../../lib/concurrency";
|
|
|
11
11
|
import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
|
|
12
12
|
import { readMergedHooksConfig } from "../hooks/hooks-config";
|
|
13
13
|
import { readInstalledPacks } from "../hooks/pack-manifest";
|
|
14
|
-
import { normalizePolicyName } from "../hooks/policy-registry";
|
|
14
|
+
import { getAllPolicies, normalizePolicyName } from "../hooks/policy-registry";
|
|
15
15
|
import { INTEGRATION_TYPES, type IntegrationType } from "../hooks/types";
|
|
16
16
|
import { ADAPTERS } from "./cli-adapters";
|
|
17
17
|
import { AUDIT_DETECTORS } from "./detectors";
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
AUDIT_EXAMPLE_MAX_CHARS,
|
|
23
23
|
AUDIT_MAX_EXAMPLES_PER_NAME,
|
|
24
24
|
type AuditCount,
|
|
25
|
+
type AuditProgress,
|
|
25
26
|
type AuditResult,
|
|
26
27
|
type DetectorSessionState,
|
|
27
28
|
type NormalizedToolEvent,
|
|
@@ -443,6 +444,32 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
443
444
|
allTranscripts.push(...list);
|
|
444
445
|
}
|
|
445
446
|
|
|
447
|
+
// Progress is reported through this and nothing else, so a callback that
|
|
448
|
+
// throws can never reach the scan below — where a throw inside a task would
|
|
449
|
+
// count as a scan error and change the result.
|
|
450
|
+
const report = (progress: AuditProgress): void => {
|
|
451
|
+
try {
|
|
452
|
+
opts.onProgress?.(progress);
|
|
453
|
+
} catch {
|
|
454
|
+
// A progress display must never change what an audit finds.
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
if (opts.onProgress) {
|
|
458
|
+
const perAgent = new Map<IntegrationType, number>();
|
|
459
|
+
for (const meta of allTranscripts) perAgent.set(meta.cli, (perAgent.get(meta.cli) ?? 0) + 1);
|
|
460
|
+
report({
|
|
461
|
+
kind: "discovered",
|
|
462
|
+
agents: clis.map((cli) => ({ cli, transcripts: perAgent.get(cli) ?? 0 })),
|
|
463
|
+
// The builtins `initReplay` registered that can fire on a tool event. The
|
|
464
|
+
// require-*-before-stop policies match only Stop, which a replay never
|
|
465
|
+
// sends, so counting them would claim checks that never run.
|
|
466
|
+
policies: getAllPolicies().filter((p) => {
|
|
467
|
+
const events = p.match.events;
|
|
468
|
+
return !events || events.length === 0 || events.includes("PreToolUse") || events.includes("PostToolUse");
|
|
469
|
+
}).length,
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
|
|
446
473
|
// 2. Scan each transcript (cache-aware), 8 in parallel.
|
|
447
474
|
let skipped = 0;
|
|
448
475
|
let errors = 0;
|
|
@@ -503,7 +530,23 @@ async function runAuditInner(opts: RunAuditOptions, startedAt: number): Promise<
|
|
|
503
530
|
}
|
|
504
531
|
});
|
|
505
532
|
|
|
506
|
-
|
|
533
|
+
// With a listener, each task reports when it finishes, however it finishes:
|
|
534
|
+
// a cache hit, a scan, a failed scan, or a task that threw and is counted as
|
|
535
|
+
// skipped below. Every transcript counts toward its agent's total, so a bar
|
|
536
|
+
// can always reach the end. Without one, the tasks run exactly as they are.
|
|
537
|
+
const run = opts.onProgress
|
|
538
|
+
? tasks.map((task, i) => async (): Promise<TranscriptAuditResult> => {
|
|
539
|
+
let hitsByName: Record<string, number> = {};
|
|
540
|
+
try {
|
|
541
|
+
const done = await task();
|
|
542
|
+
hitsByName = done.hitsByName;
|
|
543
|
+
return done;
|
|
544
|
+
} finally {
|
|
545
|
+
report({ kind: "transcript", cli: allTranscripts[i].cli, hitsByName });
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
: tasks;
|
|
549
|
+
const settled = await batchAll(run, TRANSCRIPT_CONCURRENCY);
|
|
507
550
|
const perTranscript: TranscriptAuditResult[] = [];
|
|
508
551
|
for (const s of settled) {
|
|
509
552
|
if (s.status === "fulfilled") perTranscript.push(s.value);
|
|
@@ -31,20 +31,7 @@ import { readAuditSchedule } from "./audit-schedule";
|
|
|
31
31
|
import { readDashboardCacheMeta } from "./dashboard-cache";
|
|
32
32
|
import { readMachineIdentity } from "./machine-store";
|
|
33
33
|
import { ensureSignedIn, invalidEmail, LoginError } from "./cli-login";
|
|
34
|
-
import {
|
|
35
|
-
ANSI_BOLD,
|
|
36
|
-
ANSI_DIM,
|
|
37
|
-
ANSI_RESET,
|
|
38
|
-
brandAnsi,
|
|
39
|
-
colorsEnabled,
|
|
40
|
-
optsFor,
|
|
41
|
-
outro,
|
|
42
|
-
printBlock,
|
|
43
|
-
rows as kitRows,
|
|
44
|
-
stack,
|
|
45
|
-
step,
|
|
46
|
-
title,
|
|
47
|
-
} from "../hooks/tui";
|
|
34
|
+
import { INDENT, optsFor, paint, printBlock, screenKit, stack, type TTYOut } from "../hooks/tui";
|
|
48
35
|
|
|
49
36
|
/** Mirrors `fp-config`'s own bounds so the error can name them before writing. */
|
|
50
37
|
const MIN_DAYS = 1;
|
|
@@ -53,19 +40,17 @@ const MAX_DAYS = 90;
|
|
|
53
40
|
export class ScheduleCliError extends Error {}
|
|
54
41
|
|
|
55
42
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
* with no structure — the worst of both. `colorsEnabled` is false off a TTY and
|
|
61
|
-
* under `NO_COLOR`, which is exactly when a readout should be plain text.
|
|
43
|
+
* The 2026-10 kit for one stream, read at call time: colour only on a terminal
|
|
44
|
+
* and never under NO_COLOR, so a piped `--status` is plain text. The daemon
|
|
45
|
+
* warning goes to stderr, which can be a terminal when stdout is not, so it is
|
|
46
|
+
* asked about separately.
|
|
62
47
|
*/
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
48
|
+
const kitFor = (stream: TTYOut = process.stdout) => screenKit(optsFor(stream));
|
|
49
|
+
|
|
50
|
+
/** `every 7 days`, `every day`. */
|
|
51
|
+
function everyPhrase(days: number): string {
|
|
52
|
+
return days === 1 ? "every day" : `every ${days} days`;
|
|
53
|
+
}
|
|
69
54
|
|
|
70
55
|
/**
|
|
71
56
|
* Turn scheduled audits on, signing in first if needed.
|
|
@@ -123,64 +108,48 @@ export async function runScheduleOn(
|
|
|
123
108
|
},
|
|
124
109
|
});
|
|
125
110
|
const interval = next.audit.intervalDays;
|
|
111
|
+
const k = kitFor();
|
|
126
112
|
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"you only hear from it when a scan finds something harmful",
|
|
139
|
-
"each report sends: finding counts, redacted example commands, this machine's name",
|
|
113
|
+
// The second line enumerates what leaves the machine, and it is not optional.
|
|
114
|
+
// `reportsConsentedAt`, stamped above, records that a person READ it before
|
|
115
|
+
// anything is sent, so it is part of the consent rather than fine print — the
|
|
116
|
+
// one explanatory line the redesign keeps (decision D6). This is the ONLY
|
|
117
|
+
// opt-in path on the headless boxes the whole feature was built for, and the
|
|
118
|
+
// settings panel argues in its own comment that a checkable list beats a
|
|
119
|
+
// stronger claim. The list is the real payload from `report-harm.ts`: machine
|
|
120
|
+
// id, hostname, platform, the window bounds and the redacted examples.
|
|
121
|
+
const result = [
|
|
122
|
+
k.ok(`Scheduled audits to report to ${user.email} ${everyPhrase(interval)}.`),
|
|
123
|
+
`${INDENT}${paint(optsFor(process.stdout).color).ink3("Each report sends finding counts, redacted example commands and this machine's name.")}`,
|
|
140
124
|
];
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// closes both at once — rather than the frame ending and a loose line
|
|
145
|
-
// appearing underneath.
|
|
146
|
-
step("scheduled audits are on", summary);
|
|
147
|
-
} else {
|
|
148
|
-
// Nothing was asked, so nothing was a flow: a spine here would open a frame
|
|
149
|
-
// that has no beginning.
|
|
150
|
-
process.stdout.write(
|
|
151
|
-
`\n${green("✓")} ${bold("scheduled audits are on")}\n` +
|
|
152
|
-
summary.map((r) => ` ${dim(r)}\n`).join(""),
|
|
153
|
-
);
|
|
154
|
-
}
|
|
125
|
+
// A sign-in just drew this screen's header, so the result continues it;
|
|
126
|
+
// otherwise this is the screen, and it opens with its own.
|
|
127
|
+
process.stdout.write(`\n${(prompted ? result : [k.header("Audit"), "", ...result]).join("\n")}\n`);
|
|
155
128
|
|
|
156
129
|
// The switch is config; whether anything RUNS is the daemon. Saying "on"
|
|
157
130
|
// without checking would be the same "on but silent" state the settings panel
|
|
158
131
|
// exists to make visible.
|
|
159
132
|
warnIfDaemonWontRun();
|
|
160
133
|
|
|
161
|
-
|
|
162
|
-
outro("failproofai audit --status · when the next scan is due");
|
|
163
|
-
} else {
|
|
164
|
-
process.stdout.write(dim(`\n failproofai audit --status when the next scan is due\n\n`));
|
|
165
|
-
}
|
|
134
|
+
process.stdout.write(`\nSee when the next scan runs: ${k.cmd("failproofai audit --status")}\n\n`);
|
|
166
135
|
}
|
|
167
136
|
|
|
168
137
|
export function runScheduleOff(): void {
|
|
169
138
|
const before = readConfig().audit.auto;
|
|
170
139
|
const next = updateConfig({ audit: { auto: false } });
|
|
140
|
+
const k = kitFor();
|
|
141
|
+
const say = (line: string) => printBlock(process.stdout, [k.header("Audit"), "", line]);
|
|
171
142
|
if (next.audit.auto) {
|
|
172
|
-
|
|
143
|
+
say(k.fail("Could not turn scheduled audits off."));
|
|
173
144
|
return;
|
|
174
145
|
}
|
|
175
146
|
if (!before) {
|
|
176
|
-
|
|
147
|
+
say("Scheduled audits were already off.");
|
|
177
148
|
return;
|
|
178
149
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
` ${dim("your session is untouched — sign out from the dashboard for that")}\n\n`,
|
|
183
|
-
);
|
|
150
|
+
// Nothing scans on a timer once this is off, so nothing is sent either. The
|
|
151
|
+
// sign-in is kept, so turning them back on costs no second code.
|
|
152
|
+
say(k.ok("Turned off scheduled audits."));
|
|
184
153
|
}
|
|
185
154
|
|
|
186
155
|
/**
|
|
@@ -200,93 +169,89 @@ export function runScheduleStatus(): void {
|
|
|
200
169
|
const daemon = daemonServiceStatus();
|
|
201
170
|
|
|
202
171
|
const on = config.audit.auto;
|
|
203
|
-
const
|
|
172
|
+
const k = kitFor();
|
|
204
173
|
const detail: Array<[string, string]> = [];
|
|
174
|
+
// One ▲ per distinct problem, the one that stops scans before the one that
|
|
175
|
+
// stops digests.
|
|
176
|
+
const problems: string[] = [];
|
|
205
177
|
|
|
206
178
|
// The state first, because everything below is detail about a machine that is
|
|
207
179
|
// either doing this or not, and reading the detail first answers a question
|
|
208
|
-
// nobody has asked yet. It is a ROW like the rest,
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
detail.push([
|
|
212
|
-
"scheduled audit",
|
|
213
|
-
on
|
|
214
|
-
? `${green("on")}${dim(` every ${config.audit.intervalDays} days`)}`
|
|
215
|
-
: dim("off"),
|
|
216
|
-
]);
|
|
180
|
+
// nobody has asked yet. It is a ROW like the rest, on the one column every
|
|
181
|
+
// row shares.
|
|
182
|
+
const every = everyPhrase(config.audit.intervalDays);
|
|
183
|
+
detail.push(["scans", on ? `${k.on} ${every[0].toUpperCase()}${every.slice(1)}` : `${k.off} Off`]);
|
|
217
184
|
|
|
218
185
|
// A session whose refresh window has closed cannot mint another access token,
|
|
219
186
|
// so it is a destination in name only. Showing the address for one would tell
|
|
220
187
|
// somebody their digests are going somewhere they are not.
|
|
221
188
|
const live = auth && auth.refresh_expires_at * 1000 > Date.now() ? auth : null;
|
|
222
|
-
detail.push(["reports to", live ? live.user.email :
|
|
189
|
+
detail.push(["reports to", live ? live.user.email : "Signed out"]);
|
|
190
|
+
|
|
191
|
+
const daemonRow = describeDaemon(daemon, k);
|
|
192
|
+
detail.push(["daemon", daemonRow.value]);
|
|
193
|
+
if (daemonRow.problem) problems.push(daemonRow.problem);
|
|
194
|
+
|
|
223
195
|
if (on && !live) {
|
|
224
196
|
// The state the reporter surfaces as "signed-out". Named here for the same
|
|
225
197
|
// reason the settings panel names it: the scans keep running, so silence
|
|
226
198
|
// about the digests would look like the feature failing.
|
|
227
|
-
|
|
199
|
+
problems.push(k.caution("Scans continue, but digests are paused until you sign in.", "failproofai audit --schedule"));
|
|
228
200
|
} else if (on && config.audit.reportsConsentedAt === undefined) {
|
|
229
201
|
// Signed in, scheduled, and still not sending: this machine set `audit.auto`
|
|
230
202
|
// when it only meant "scan locally", so nothing has consented to the digest
|
|
231
|
-
// leaving the box. Without this
|
|
203
|
+
// leaving the box. Without this line the status screen would show a healthy
|
|
232
204
|
// schedule and a live address and still mail nothing, with no explanation
|
|
233
205
|
// anywhere the user can see.
|
|
234
|
-
|
|
235
|
-
"",
|
|
236
|
-
pink("scans continue; digests need a fresh opt-in — run `--schedule` to turn them on"),
|
|
237
|
-
]);
|
|
206
|
+
problems.push(k.caution("Scans continue, but digests need a fresh opt-in.", "failproofai audit --schedule"));
|
|
238
207
|
}
|
|
239
208
|
|
|
240
|
-
detail.push(["daemon", describeDaemon(daemon)]);
|
|
241
|
-
|
|
242
209
|
if (sched?.nextDueAtMs != null && on) {
|
|
243
210
|
detail.push(["next scan", untilPhrase(sched.nextDueAtMs)]);
|
|
244
211
|
}
|
|
245
212
|
if (sched?.lastRunAtMs != null) {
|
|
246
213
|
const exit = sched.lastExitCode;
|
|
247
214
|
const failed = exit != null && exit !== 0 && exit !== 75;
|
|
248
|
-
detail.push([
|
|
249
|
-
"last scheduled",
|
|
250
|
-
agoPhrase(sched.lastRunAtMs) + (failed ? ` ${pink(`exit ${exit}`)}` : ""),
|
|
251
|
-
]);
|
|
215
|
+
detail.push(["last scheduled", agoPhrase(sched.lastRunAtMs) + (failed ? `, failed with exit ${exit}` : "")]);
|
|
252
216
|
}
|
|
253
|
-
detail.push([
|
|
254
|
-
"last result",
|
|
255
|
-
meta?.cachedAt ? agoPhrase(Date.parse(meta.cachedAt)) : dim("none yet"),
|
|
256
|
-
]);
|
|
217
|
+
detail.push(["last result", meta?.cachedAt ? agoPhrase(Date.parse(meta.cachedAt)) : "None yet"]);
|
|
257
218
|
if (machine?.last_reported_at) {
|
|
258
219
|
detail.push(["last reported", agoPhrase(Date.parse(machine.last_reported_at))]);
|
|
259
220
|
}
|
|
260
221
|
|
|
261
|
-
printBlock(
|
|
262
|
-
process.stdout,
|
|
263
|
-
stack(
|
|
264
|
-
// The meta carries a fact, not the word "status" — a heading that repeats
|
|
265
|
-
// the command name tells the reader nothing they did not just type.
|
|
266
|
-
title(
|
|
267
|
-
"failproofai audit",
|
|
268
|
-
on ? `scheduled · every ${config.audit.intervalDays} days` : "not scheduled",
|
|
269
|
-
opts,
|
|
270
|
-
),
|
|
271
|
-
kitRows(detail, opts),
|
|
272
|
-
),
|
|
273
|
-
);
|
|
222
|
+
printBlock(process.stdout, stack([k.header("Audit")], [k.head("Schedule"), ...k.kv(detail)], problems));
|
|
274
223
|
}
|
|
275
224
|
|
|
276
|
-
|
|
225
|
+
/**
|
|
226
|
+
* The daemon's row, and the ▲ that goes with it when it needs fixing. The fix
|
|
227
|
+
* is named whether or not scheduling is on: the daemon is what every scheduled
|
|
228
|
+
* scan would run under, and this is the screen people open to ask about it.
|
|
229
|
+
*/
|
|
230
|
+
function describeDaemon(
|
|
231
|
+
status: ReturnType<typeof daemonServiceStatus>,
|
|
232
|
+
k: ReturnType<typeof kitFor>,
|
|
233
|
+
): { value: string; problem?: string } {
|
|
277
234
|
switch (status) {
|
|
278
235
|
case "running":
|
|
279
|
-
return
|
|
236
|
+
return { value: `${k.on} Running` };
|
|
280
237
|
case "stopped":
|
|
281
|
-
return
|
|
238
|
+
return { value: `${k.off} Stopped`, problem: k.caution("The daemon is stopped.", "failproofai config") };
|
|
282
239
|
case "not-installed":
|
|
283
|
-
return
|
|
240
|
+
return {
|
|
241
|
+
value: `${k.off} Not installed`,
|
|
242
|
+
problem: k.caution("The daemon is not installed.", "failproofai config"),
|
|
243
|
+
};
|
|
284
244
|
case "condition-failed":
|
|
285
|
-
return
|
|
245
|
+
return {
|
|
246
|
+
value: `${k.off} Binary missing`,
|
|
247
|
+
problem: k.caution("The daemon is installed but its binary is missing.", "failproofai config"),
|
|
248
|
+
};
|
|
249
|
+
case "unknown":
|
|
250
|
+
// Only macOS without a cached sudo credential reads this, about a daemon
|
|
251
|
+
// that is usually running perfectly — not a fault to fix.
|
|
252
|
+
return { value: "Unknown" };
|
|
286
253
|
default:
|
|
287
|
-
return isDaemonSupportedPlatform()
|
|
288
|
-
? dim(String(status))
|
|
289
|
-
: dim("unavailable on this platform");
|
|
254
|
+
return { value: isDaemonSupportedPlatform() ? String(status) : "Unavailable on this platform" };
|
|
290
255
|
}
|
|
291
256
|
}
|
|
292
257
|
|
|
@@ -302,17 +267,15 @@ function warnIfDaemonWontRun(): void {
|
|
|
302
267
|
// timer yet" in the same breath as confirming their schedule was on. The
|
|
303
268
|
// dashboard already special-cases it; this is the same call.
|
|
304
269
|
if (status === "unknown") return;
|
|
270
|
+
const k = kitFor(process.stderr);
|
|
305
271
|
if (!isDaemonSupportedPlatform()) {
|
|
306
|
-
process.stderr.write(
|
|
307
|
-
`\n ${pink("!")} The background service is not available on this platform,\n` +
|
|
308
|
-
` so nothing will run on the timer here. \`failproofai audit\` still works.\n`,
|
|
309
|
-
);
|
|
272
|
+
process.stderr.write(`\n${k.caution("The daemon is not available on this platform, so nothing will run on the timer.")}\n`);
|
|
310
273
|
return;
|
|
311
274
|
}
|
|
275
|
+
const state =
|
|
276
|
+
status === "not-installed" ? "not installed" : status === "condition-failed" ? "missing its binary" : status.replace(/-/g, " ");
|
|
312
277
|
process.stderr.write(
|
|
313
|
-
`\n
|
|
314
|
-
`so nothing will run on the timer yet.\n` +
|
|
315
|
-
` Run \`failproofai config\` to install or repair it.\n`,
|
|
278
|
+
`\n${k.caution(`The daemon is ${state}, so nothing will run on the timer yet.`, "failproofai config")}\n`,
|
|
316
279
|
);
|
|
317
280
|
}
|
|
318
281
|
|
package/src/audit/types.ts
CHANGED
|
@@ -181,8 +181,39 @@ export interface AuditResult {
|
|
|
181
181
|
enabledBuiltinNames: string[];
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/**
|
|
185
|
+
* What `runAudit()` reports while it runs, for a caller drawing live progress.
|
|
186
|
+
*
|
|
187
|
+
* Two kinds, in this order: ONE `discovered` once every agent's transcripts
|
|
188
|
+
* are listed, then one `transcript` per transcript as each finishes — a cache
|
|
189
|
+
* hit, a scan, a scan that failed and a task that was skipped alike, so the
|
|
190
|
+
* per-agent counts always reach their totals. Transcripts finish in whatever
|
|
191
|
+
* order the scan pool completes them, not in discovery order.
|
|
192
|
+
*/
|
|
193
|
+
export type AuditProgress =
|
|
194
|
+
| {
|
|
195
|
+
kind: "discovered";
|
|
196
|
+
/** Every agent the audit covers, in scan order, with how many transcripts it has to scan (0 included). */
|
|
197
|
+
agents: Array<{ cli: IntegrationType; transcripts: number }>;
|
|
198
|
+
/** How many policies each tool call is replayed against: the builtins that can fire on a tool event. */
|
|
199
|
+
policies: number;
|
|
200
|
+
}
|
|
201
|
+
| {
|
|
202
|
+
kind: "transcript";
|
|
203
|
+
cli: IntegrationType;
|
|
204
|
+
/** This transcript's hits, by policy or detector name. Empty when it found nothing or could not be scanned. */
|
|
205
|
+
hitsByName: Record<string, number>;
|
|
206
|
+
};
|
|
207
|
+
|
|
184
208
|
/** CLI-supplied options for `runAudit()`. Set by `bin/failproofai.mjs`. */
|
|
185
209
|
export interface RunAuditOptions {
|
|
210
|
+
/**
|
|
211
|
+
* Called as the audit progresses. Optional, and only `failproofai audit`'s
|
|
212
|
+
* own screen passes it: the dashboard and the scheduled run do not, and the
|
|
213
|
+
* result is the same either way. A callback that throws is ignored — a
|
|
214
|
+
* progress display must never change what an audit finds.
|
|
215
|
+
*/
|
|
216
|
+
onProgress?: (progress: AuditProgress) => void;
|
|
186
217
|
/** Restrict to one or more CLIs. Default: all 7. */
|
|
187
218
|
clis?: IntegrationType[];
|
|
188
219
|
/** Restrict to sessions whose cwd matches one of these paths. */
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decision D2: a command that installs or removes an agent's hooks keeps the
|
|
3
|
+
* agent selection true.
|
|
4
|
+
*
|
|
5
|
+
* `agents.selected` in `config.json` is one list with three readers: which
|
|
6
|
+
* agents `failproofai config` hooks, which harnesses the daemon collects, and
|
|
7
|
+
* which ones `backfill` re-sends. Only `config` used to write it, so any other
|
|
8
|
+
* command that changed an agent's hooks left the list saying the opposite:
|
|
9
|
+
*
|
|
10
|
+
* - `policies --install --cli goose`, on a machine whose selection leaves Goose
|
|
11
|
+
* out, wired Goose — and the next `failproofai config`, which takes out the
|
|
12
|
+
* hooks of every agent it does not trace, silently removed them again.
|
|
13
|
+
* - `policies --uninstall --cli goose` took the hooks out and left Goose
|
|
14
|
+
* traced, so the daemon kept collecting its sessions and the next `config`
|
|
15
|
+
* put the hooks back.
|
|
16
|
+
*
|
|
17
|
+
* An install traces the agent at ANY scope: whichever file the hooks went into,
|
|
18
|
+
* somebody asked for that agent to be guarded. A removal untraces it only at
|
|
19
|
+
* user scope or every scope. The selection is per machine, and taking one
|
|
20
|
+
* repository's project hooks out says nothing about whether this machine should
|
|
21
|
+
* trace the agent — so that decision is the caller's, which knows the scope.
|
|
22
|
+
*
|
|
23
|
+
* Absent means every agent is traced, so an install has nothing to add. A
|
|
24
|
+
* removal cannot be recorded without a list, so it writes one: the agents on
|
|
25
|
+
* this machine — detected, or still hooked at user scope — but the ones
|
|
26
|
+
* removed, and `seen` as detected now. Not "every agent but the ones removed":
|
|
27
|
+
* `failproofai config` restores a saved list as it stands, so that list would
|
|
28
|
+
* have it hook every agent this build knows, installed or not.
|
|
29
|
+
*
|
|
30
|
+
* An agent traced again starts fresh (`requestFreshStart`), exactly as one
|
|
31
|
+
* re-ticked in `failproofai config` does.
|
|
32
|
+
*
|
|
33
|
+
* Only the commands that ask for it (`syncAgentSelection`) come here.
|
|
34
|
+
* `failproofai config` writes the selection itself, and `failproofai uninstall`
|
|
35
|
+
* leaves it alone.
|
|
36
|
+
*/
|
|
37
|
+
import { existsSync } from "node:fs";
|
|
38
|
+
|
|
39
|
+
import { machineCollectsSessions, requestFreshStart } from "./backfill-request";
|
|
40
|
+
import { readConfig, updateConfig } from "./fp-config";
|
|
41
|
+
import { detectInstalledClis, getIntegration, hermesProfileHealth, settingsPathsFor } from "./integrations";
|
|
42
|
+
import { optsFor, screenKit } from "./tui";
|
|
43
|
+
import { INTEGRATION_TYPES, type IntegrationType } from "./types";
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Whether an agent has failproofai hooks in its USER settings — the seed for an
|
|
47
|
+
* upgrade in `failproofai config`, and what a removal on a machine with no
|
|
48
|
+
* saved list keeps traced. User scope only: project hooks depend on the
|
|
49
|
+
* directory a command happens to run from. A gateway with several profiles
|
|
50
|
+
* counts when ANY profile is hooked; requiring all of them would drop a partly
|
|
51
|
+
* hooked Hermes from the seed, and setup would then unhook every profile it has.
|
|
52
|
+
*
|
|
53
|
+
* Hooked means routed to failproofai, not healthy: a Hermes profile still on the
|
|
54
|
+
* legacy shell hooks sends every tool call here today. Requiring the healthy
|
|
55
|
+
* native plugin left such a gateway unticked, so setup stopped collecting it and
|
|
56
|
+
* never ran the install that moves it onto the plugin.
|
|
57
|
+
*/
|
|
58
|
+
export function hookedAtUserScope(id: IntegrationType, cwd: string): boolean {
|
|
59
|
+
const integration = getIntegration(id);
|
|
60
|
+
try {
|
|
61
|
+
if (integration.hooksInstalledInSettings("user", cwd)) return true;
|
|
62
|
+
if (id === "hermes") {
|
|
63
|
+
return hermesProfileHealth().some(
|
|
64
|
+
(p) => existsSync(p.home) && ((p.pluginInstalled && p.pluginEnabled) || p.legacyShellHookPresent),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
const paths = settingsPathsFor(integration, "user", cwd);
|
|
68
|
+
if (paths.length < 2) return false;
|
|
69
|
+
return paths.some((one) =>
|
|
70
|
+
integration.hooksInstalledInSettings.call({ ...integration, getSettingsPaths: () => [one] }, "user", cwd),
|
|
71
|
+
);
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Bring the selection in line with hooks just installed for, or removed from,
|
|
79
|
+
* `clis`, and return the one line that says so: a ✓ note, a ▲ when the change
|
|
80
|
+
* could not be written, or null when the selection already agreed.
|
|
81
|
+
*
|
|
82
|
+
* The caller prints it after its own output, and only where the scope counts —
|
|
83
|
+
* see the module comment.
|
|
84
|
+
*/
|
|
85
|
+
export function keepAgentSelectionTrue(
|
|
86
|
+
change: "installed" | "removed",
|
|
87
|
+
clis: readonly IntegrationType[],
|
|
88
|
+
): string | null {
|
|
89
|
+
const touched = INTEGRATION_TYPES.filter((id) => clis.includes(id));
|
|
90
|
+
if (touched.length === 0) return null;
|
|
91
|
+
const saved = readConfig().agents;
|
|
92
|
+
|
|
93
|
+
let moved: IntegrationType[];
|
|
94
|
+
let next: { selected: string[]; seen: string[] };
|
|
95
|
+
if (change === "installed") {
|
|
96
|
+
if (!saved) return null;
|
|
97
|
+
moved = touched.filter((id) => !saved.selected.includes(id));
|
|
98
|
+
if (moved.length === 0) return null;
|
|
99
|
+
// INTEGRATION_TYPES order. An id this build does not know is a newer
|
|
100
|
+
// build's agent, so it is kept after them as written, never dropped.
|
|
101
|
+
const inOrder = (ids: readonly string[]): string[] => [
|
|
102
|
+
...INTEGRATION_TYPES.filter((id) => ids.includes(id)),
|
|
103
|
+
...ids.filter((id) => !(INTEGRATION_TYPES as readonly string[]).includes(id)),
|
|
104
|
+
];
|
|
105
|
+
const detected = detectInstalledClis();
|
|
106
|
+
const nowSeen = moved.filter((id) => detected.includes(id) && !saved.seen.includes(id));
|
|
107
|
+
next = {
|
|
108
|
+
selected: inOrder([...saved.selected, ...moved]),
|
|
109
|
+
seen: nowSeen.length > 0 ? inOrder([...saved.seen, ...nowSeen]) : saved.seen,
|
|
110
|
+
};
|
|
111
|
+
} else if (saved) {
|
|
112
|
+
moved = touched.filter((id) => saved.selected.includes(id));
|
|
113
|
+
if (moved.length === 0) return null;
|
|
114
|
+
const gone = new Set<string>(moved);
|
|
115
|
+
// Possibly empty, after every agent's hooks came out: an empty selection
|
|
116
|
+
// traces nothing, which is exactly what was asked for.
|
|
117
|
+
next = { selected: saved.selected.filter((id) => !gone.has(id)), seen: saved.seen };
|
|
118
|
+
} else {
|
|
119
|
+
moved = touched;
|
|
120
|
+
const detected = detectInstalledClis();
|
|
121
|
+
const cwd = process.cwd();
|
|
122
|
+
next = {
|
|
123
|
+
selected: INTEGRATION_TYPES.filter(
|
|
124
|
+
(id) => !touched.includes(id) && (detected.includes(id) || hookedAtUserScope(id, cwd)),
|
|
125
|
+
),
|
|
126
|
+
seen: detected,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const kit = screenKit(optsFor(process.stdout));
|
|
131
|
+
const names = moved.map((id) => getIntegration(id).displayName);
|
|
132
|
+
const who = names.length === 1 ? names[0] : `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
|
|
133
|
+
const one = names.length === 1;
|
|
134
|
+
const setup = kit.cmd("failproofai config");
|
|
135
|
+
// Before the selection that traces them, in the same order setup uses.
|
|
136
|
+
if (change === "installed") requestFreshStart(moved, machineCollectsSessions());
|
|
137
|
+
try {
|
|
138
|
+
updateConfig({ agents: next });
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// The hooks themselves are in place, so the command is not failed for
|
|
141
|
+
// this. Said instead, because it is the exact drift this module exists to
|
|
142
|
+
// stop, and nothing else would mention it.
|
|
143
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
144
|
+
return kit.caution(
|
|
145
|
+
change === "installed"
|
|
146
|
+
? `Couldn't record ${who} as traced (${why}), so ${setup} may remove ${one ? "its" : "their"} hooks again.`
|
|
147
|
+
: `Couldn't record ${who} as no longer traced (${why}), so ${setup} may add ${one ? "its" : "their"} hooks back.`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return kit.ok(
|
|
151
|
+
change === "installed"
|
|
152
|
+
? `${who} ${one ? "is" : "are"} traced again, so ${setup} keeps ${one ? "its" : "their"} hooks.`
|
|
153
|
+
: `${who} ${one ? "is" : "are"} no longer traced, so ${setup} won't add ${one ? "its" : "their"} hooks back.`,
|
|
154
|
+
);
|
|
155
|
+
}
|