failproofai 1.0.0-beta.2 → 1.0.0-beta.21
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/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- 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 +3 -1
- 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.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- 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 +3 -1
- 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.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- 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 -3
- 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 +3 -1
- 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 +3 -2
- 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 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
- 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 +5 -3
- 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.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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 +3 -1
- 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 +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +5 -3
- 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 +4 -2
- 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 +6 -4
- 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 +4 -2
- 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/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
- package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
- package/.next/standalone/.next/server/app/settings/page.js +18 -0
- package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +2 -2
- 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 +74 -20
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/SECURITY.md +8 -2
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
- package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
- package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
- package/.next/standalone/app/api/audit/run/route.ts +28 -0
- package/.next/standalone/app/api/audit/status/route.ts +13 -1
- package/.next/standalone/app/api/auth/status/route.ts +1 -1
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
- package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
- package/.next/standalone/app/components/toast.tsx +1 -1
- package/.next/standalone/app/policies/hooks-client.tsx +84 -8
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +487 -0
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/lib/auth/api-server-client.ts +2 -3
- package/.next/standalone/lib/auth/auth-store.ts +4 -5
- package/.next/standalone/lib/codex-sessions.ts +2 -1
- package/.next/standalone/lib/dashboard-host.ts +101 -0
- package/.next/standalone/lib/install-check.ts +22 -7
- package/.next/standalone/lib/pi-sessions.ts +99 -14
- package/.next/standalone/lib/telemetry-enabled.ts +54 -0
- package/.next/standalone/lib/telemetry-id.ts +52 -1
- package/.next/standalone/lib/telemetry.ts +8 -4
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +23 -12
- package/.next/standalone/proxy.ts +111 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai-worker.mjs +1 -1
- package/bin/failproofai.mjs +1041 -81
- package/bin/failproofaid-shim.mjs +4 -4
- package/dist/cli.mjs +18513 -10474
- package/dist/worker.mjs +1223 -299
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -5
- package/lib/codex-sessions.ts +2 -1
- package/lib/dashboard-host.ts +101 -0
- package/lib/install-check.ts +22 -7
- package/lib/pi-sessions.ts +99 -14
- package/lib/telemetry-enabled.ts +54 -0
- package/lib/telemetry-id.ts +52 -1
- package/lib/telemetry.ts +8 -4
- package/package.json +23 -12
- package/scripts/build-daemon-packages.mjs +247 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/dev-hook.mjs +46 -5
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +27 -1
- package/scripts/prune-standalone.mjs +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -0
- package/scripts/translate-docs/cache.ts +1 -1
- package/scripts/translate-docs/cli.ts +2 -2
- package/scripts/translate-docs/mintlify-nav.ts +1 -1
- package/scripts/translate-docs/readme-translator.ts +163 -4
- package/scripts/validate-mdx.ts +153 -13
- package/src/audit/audit-lock.ts +285 -0
- package/src/audit/audit-schedule.ts +111 -0
- package/src/audit/cache.ts +18 -5
- package/src/audit/cli-adapters/goose.ts +2 -1
- package/src/audit/cli-adapters/hermes.ts +28 -10
- package/src/audit/cli.ts +191 -35
- package/src/audit/dashboard-cache.ts +2 -3
- package/src/audit/report.ts +0 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +391 -3
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +439 -0
- package/src/hooks/cloud-enrollment.ts +284 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +206 -0
- package/src/hooks/collector-config.ts +218 -0
- package/src/hooks/configure-wizard.ts +1211 -233
- package/src/hooks/custom-hooks-loader.ts +243 -37
- package/src/hooks/daemon-client.ts +136 -33
- package/src/hooks/daemon-download.ts +264 -31
- package/src/hooks/daemon-service.ts +1124 -76
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/flush-cli.ts +180 -0
- package/src/hooks/fp-config.ts +835 -0
- package/src/hooks/fp-home.ts +714 -0
- package/src/hooks/fp-reset.ts +1363 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/harness-cli.ts +386 -0
- package/src/hooks/hook-activity-store.ts +171 -24
- package/src/hooks/hook-logger.ts +2 -2
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/integrations.ts +0 -1
- package/src/hooks/loader-utils.ts +172 -9
- package/src/hooks/manager.ts +41 -5
- package/src/hooks/migrations.ts +450 -0
- package/src/hooks/onboarding-attempt.ts +195 -0
- package/src/hooks/onboarding-lock.ts +118 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +267 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +209 -9
- package/src/hooks/uninstall-cli.ts +418 -0
- package/src/hooks/worker-server.ts +134 -15
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- package/.next/standalone/Cargo.lock +0 -428
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
- package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
- package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
- package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
- package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
- package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
- package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
- package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
- package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
- package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
- package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
- package/.next/standalone/integration-suite/Dockerfile +0 -47
- package/.next/standalone/integration-suite/README.md +0 -129
- package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
- package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
- package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
- package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
- package/.next/standalone/integration-suite/install-clis.sh +0 -136
- package/.next/standalone/integration-suite/probe-cli.sh +0 -248
- package/.next/standalone/integration-suite/report.js +0 -120
- package/.next/standalone/integration-suite/run.sh +0 -159
- package/.next/standalone/openclaw-plugin/index.js +0 -223
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
- package/.next/standalone/openclaw-plugin/package.json +0 -12
- package/.next/standalone/osv-scanner.toml +0 -17
- package/.next/standalone/pi-extension/index.ts +0 -524
- package/.next/standalone/pi-extension/package.json +0 -12
- package/.next/standalone/postcss.config.mjs +0 -8
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/rust-toolchain.toml +0 -4
- package/src/auth/cli.ts +0 -371
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
package/src/hooks/handler.ts
CHANGED
|
@@ -41,6 +41,9 @@ import { resolveTranscriptPath } from "./resolve-transcript-path";
|
|
|
41
41
|
import { getInstanceId } from "../../lib/telemetry-id";
|
|
42
42
|
import { hookLogInfo, hookLogWarn } from "./hook-logger";
|
|
43
43
|
import { readStdinPayload } from "./read-stdin";
|
|
44
|
+
import { readActiveCloudManagedPolicies, type CloudManagedPolicyArtifact } from "./cloud-managed-policies";
|
|
45
|
+
import { readActivePause, type ActivePause } from "./session-pause";
|
|
46
|
+
import { layoutWarningForHook } from "./fp-reset";
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Canonicalize an event name to PascalCase. Codex sends snake_case event names
|
|
@@ -134,6 +137,37 @@ export interface EvaluateHookEventOptions {
|
|
|
134
137
|
fallbackCwd?: string;
|
|
135
138
|
}
|
|
136
139
|
|
|
140
|
+
/**
|
|
141
|
+
* Runs an observe-mode policy under the same timeout and error handling as an
|
|
142
|
+
* enforcing one, so what gets measured is what would actually have happened —
|
|
143
|
+
* including a policy that times out, which in enforce mode is an allow and must
|
|
144
|
+
* be recorded as one rather than as a would-deny.
|
|
145
|
+
*/
|
|
146
|
+
async function runObserved(
|
|
147
|
+
hook: CustomHook,
|
|
148
|
+
ctx: Parameters<CustomHook["fn"]>[0],
|
|
149
|
+
hookName: string,
|
|
150
|
+
eventType: string,
|
|
151
|
+
cli: IntegrationType,
|
|
152
|
+
): Promise<PolicyResult> {
|
|
153
|
+
try {
|
|
154
|
+
return await Promise.race([
|
|
155
|
+
hook.fn(ctx),
|
|
156
|
+
new Promise<PolicyResult>((_, reject) => setTimeout(() => reject(new Error("timeout")), 10_000)),
|
|
157
|
+
]);
|
|
158
|
+
} catch (err) {
|
|
159
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
160
|
+
void trackHookEvent(getInstanceId(), "custom_hook_error", {
|
|
161
|
+
hook_name: hookName,
|
|
162
|
+
error_type: msg === "timeout" ? "timeout" : "exception",
|
|
163
|
+
event_type: eventType,
|
|
164
|
+
cli,
|
|
165
|
+
is_observe_mode: true,
|
|
166
|
+
});
|
|
167
|
+
return { decision: "allow" };
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
137
171
|
/**
|
|
138
172
|
* The core hook-evaluation logic, decoupled from process stdin/stdout so it
|
|
139
173
|
* can be called repeatedly inside a long-lived process (the daemon's warm
|
|
@@ -149,6 +183,13 @@ export async function evaluateHookEvent(
|
|
|
149
183
|
opts?: EvaluateHookEventOptions,
|
|
150
184
|
): Promise<HookEventOutcome> {
|
|
151
185
|
const startTime = performance.now();
|
|
186
|
+
// A home from another layout resolves to no global config, so every builtin
|
|
187
|
+
// silently stops firing — the machine looks protected and is not. Say so on
|
|
188
|
+
// every call rather than deleting anything (a hook is unattended) or denying
|
|
189
|
+
// (a blanket deny takes UserPromptSubmit with it and locks the user out of
|
|
190
|
+
// their agent entirely — demonstrated on a real machine during this work).
|
|
191
|
+
const layoutWarning = layoutWarningForHook();
|
|
192
|
+
if (layoutWarning) hookLogWarn(layoutWarning);
|
|
152
193
|
try {
|
|
153
194
|
let parsed: Record<string, unknown> = {};
|
|
154
195
|
if (stdinPayload) {
|
|
@@ -209,6 +250,20 @@ export async function evaluateHookEvent(
|
|
|
209
250
|
let config: HooksConfig;
|
|
210
251
|
let customHooksList: CustomHook[] = [];
|
|
211
252
|
let conventionHookNames = new Set<string>();
|
|
253
|
+
let activePause: ActivePause | null = null;
|
|
254
|
+
/** Registered policy name → where it came from. See the set() below. */
|
|
255
|
+
const policyAttribution = new Map<
|
|
256
|
+
string,
|
|
257
|
+
{ source: "custom" | "convention" | "cloud"; cloudPolicyId?: string; cloudVersion?: number }
|
|
258
|
+
>();
|
|
259
|
+
let cloudDeployment: number | undefined;
|
|
260
|
+
/** What observe-mode policies WOULD have done, had they been enforcing. */
|
|
261
|
+
const observedResults: Array<{
|
|
262
|
+
policyId: string;
|
|
263
|
+
version: number;
|
|
264
|
+
decision: "deny" | "instruct";
|
|
265
|
+
reason: string | null;
|
|
266
|
+
}> = [];
|
|
212
267
|
|
|
213
268
|
if (opts?.forceDecision) {
|
|
214
269
|
// Fail-closed: no config/custom-hook loading at all — a daemon that
|
|
@@ -228,25 +283,113 @@ export async function evaluateHookEvent(
|
|
|
228
283
|
// Load enabled policies (merge across project/local/global scopes)
|
|
229
284
|
config = readMergedHooksConfig(session.cwd);
|
|
230
285
|
clearPolicies();
|
|
231
|
-
|
|
286
|
+
|
|
287
|
+
// A session pause suspends LOCAL policy only, for a bounded time. Cloud
|
|
288
|
+
// assignments are exempt below for the same reason `disabledCustomPolicies`
|
|
289
|
+
// already exempts them: a locally-issued command that could switch off a
|
|
290
|
+
// centrally assigned policy would make cloud enforcement decorative.
|
|
291
|
+
activePause = readActivePause(session.sessionId);
|
|
292
|
+
registerBuiltinPolicies(activePause ? [] : config.enabledPolicies);
|
|
293
|
+
|
|
294
|
+
// Cloud-managed policies are daemon-reconciled artifacts, but they use
|
|
295
|
+
// the same public JS policy API as local custom policies. Verify and add
|
|
296
|
+
// only the paths referenced by the atomically active deployment.
|
|
297
|
+
//
|
|
298
|
+
// Wrapped, like `readConfigAt` and `readActivePause` above it. This call
|
|
299
|
+
// has fourteen throw sites — a malformed manifest, an unsafe id, an
|
|
300
|
+
// integrity mismatch, an unknown effect — and it sat bare inside a `try`
|
|
301
|
+
// whose only handler is a `finally`, so any of them aborted the entire
|
|
302
|
+
// evaluation. What that cost depended on where the hook ran, and none of
|
|
303
|
+
// the outcomes were the intended one: on a daemon machine the client
|
|
304
|
+
// fail-closed denies every tool call, and off the daemon path the throw
|
|
305
|
+
// reaches `bin/failproofai.mjs`'s outer catch, which exits 2 with nothing
|
|
306
|
+
// on stdout — a deny on Claude and Factory, but a logged warning followed
|
|
307
|
+
// by an ALLOW on Copilot, Cursor, Goose, Pi and Hermes, which read a
|
|
308
|
+
// decision off stdout and ignore the exit code. One corrupt byte was
|
|
309
|
+
// either a permanent machine-wide lockout or silent non-enforcement,
|
|
310
|
+
// depending on the CLI.
|
|
311
|
+
//
|
|
312
|
+
// Failing open here degrades ONE layer: builtins and local custom
|
|
313
|
+
// policies were already registered above and keep enforcing. That is the
|
|
314
|
+
// same trade `2ad735e5` made on the Rust side, and it is strictly better
|
|
315
|
+
// than the status quo, which already failed open on five of the twelve —
|
|
316
|
+
// just accidentally, and without saying so.
|
|
317
|
+
let cloudManagedPolicies: CloudManagedPolicyArtifact[] = [];
|
|
318
|
+
try {
|
|
319
|
+
cloudManagedPolicies = readActiveCloudManagedPolicies();
|
|
320
|
+
} catch (err) {
|
|
321
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
322
|
+
hookLogWarn(
|
|
323
|
+
`[failproofai] cloud-managed policies could NOT be loaded and are not being enforced: ${msg}. ` +
|
|
324
|
+
`Local policies are unaffected. Run \`failproofai config --status\` to check this machine's enrolment.`,
|
|
325
|
+
);
|
|
326
|
+
void trackHookEvent(getInstanceId(), "cloud_managed_load_error", {
|
|
327
|
+
event_type: eventType,
|
|
328
|
+
cli,
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
// Recorded on every row once a machine is managed, whether or not a cloud
|
|
332
|
+
// policy decided this event: "what was deployed here at the time" is a
|
|
333
|
+
// separate question from "what decided", and only the former can tell a
|
|
334
|
+
// rollout that changed nothing from one that never reached the machine.
|
|
335
|
+
cloudDeployment = cloudManagedPolicies[0]?.deployment;
|
|
336
|
+
const configuredCustomPaths = config.customPoliciesPaths ?? config.customPoliciesPath;
|
|
337
|
+
const allExplicitPaths =
|
|
338
|
+
cloudManagedPolicies.length === 0
|
|
339
|
+
? configuredCustomPaths
|
|
340
|
+
: [
|
|
341
|
+
...(typeof configuredCustomPaths === "string" ? [configuredCustomPaths] : configuredCustomPaths ?? []),
|
|
342
|
+
...cloudManagedPolicies.map((policy) => policy.path),
|
|
343
|
+
];
|
|
232
344
|
|
|
233
345
|
// Load and register custom hooks (layer 2, after builtins)
|
|
234
|
-
const loadResult = await loadAllCustomHooks(
|
|
346
|
+
const loadResult = await loadAllCustomHooks(allExplicitPaths, {
|
|
235
347
|
sessionCwd: session.cwd,
|
|
236
348
|
customPoliciesEnabled: config.customPoliciesEnabled,
|
|
349
|
+
...(cloudManagedPolicies.length > 0 ? { cloudManagedPolicies } : {}),
|
|
237
350
|
});
|
|
238
351
|
customHooksList = loadResult.hooks;
|
|
239
352
|
const disabledCustomPolicies = new Set(config.disabledCustomPolicies ?? []);
|
|
240
353
|
conventionHookNames = new Set(loadResult.conventionSources.flatMap((s) => s.hookNames));
|
|
241
354
|
|
|
242
355
|
for (const hook of customHooksList) {
|
|
243
|
-
const
|
|
244
|
-
|
|
356
|
+
const taggedHook = hook as CustomHook & {
|
|
357
|
+
__policyId?: string;
|
|
358
|
+
__cloudManaged?: CloudManagedPolicyArtifact;
|
|
359
|
+
};
|
|
360
|
+
const policyId = taggedHook.__policyId;
|
|
361
|
+
const cloudManaged = taggedHook.__cloudManaged;
|
|
362
|
+
// Local config cannot disable a centrally assigned policy merely by
|
|
363
|
+
// copying its generated ID into disabledCustomPolicies.
|
|
364
|
+
if (!cloudManaged && policyId && disabledCustomPolicies.has(policyId)) continue;
|
|
365
|
+
// Same rule for a session pause — it suspends local policy, never cloud.
|
|
366
|
+
if (!cloudManaged && activePause) continue;
|
|
245
367
|
const hookName = hook.name;
|
|
246
368
|
const conventionScope = (hook as CustomHook & { __conventionScope?: string }).__conventionScope;
|
|
247
369
|
const isConvention = !!conventionScope;
|
|
248
|
-
const prefix =
|
|
370
|
+
const prefix = cloudManaged
|
|
371
|
+
? `cloud/${cloudManaged.id}@${cloudManaged.version}`
|
|
372
|
+
: isConvention
|
|
373
|
+
? `.failproofai-${conventionScope}`
|
|
374
|
+
: "custom";
|
|
375
|
+
// Observe mode: run it for real, record what it decided, then hand back
|
|
376
|
+
// an allow. Evaluating and discarding is the whole point — a rollout is
|
|
377
|
+
// measured against real traffic before it can break anyone's work, and
|
|
378
|
+
// a policy that did not actually run would measure nothing.
|
|
379
|
+
const observeOnly = cloudManaged?.effect === "observe";
|
|
249
380
|
const fn: PolicyFunction = async (ctx): Promise<PolicyResult> => {
|
|
381
|
+
if (observeOnly) {
|
|
382
|
+
const shadow = await runObserved(hook, ctx, hookName, eventType, cli);
|
|
383
|
+
if (shadow.decision !== "allow") {
|
|
384
|
+
observedResults.push({
|
|
385
|
+
policyId: cloudManaged!.id,
|
|
386
|
+
version: cloudManaged!.version,
|
|
387
|
+
decision: shadow.decision,
|
|
388
|
+
reason: shadow.reason ?? null,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
return { decision: "allow" };
|
|
392
|
+
}
|
|
250
393
|
try {
|
|
251
394
|
const result = await Promise.race([
|
|
252
395
|
hook.fn(ctx),
|
|
@@ -268,8 +411,18 @@ export async function evaluateHookEvent(
|
|
|
268
411
|
return { decision: "allow" };
|
|
269
412
|
}
|
|
270
413
|
};
|
|
414
|
+
const registeredName = `${prefix}/${hookName}`;
|
|
415
|
+
// Record where this policy came from as structured data, keyed by the
|
|
416
|
+
// exact name the evaluator will report back. The display name already
|
|
417
|
+
// encodes it ("cloud/org-guard@7/…"), but only as a string — so the one
|
|
418
|
+
// question cloud attribution has to answer, "which rollout produced
|
|
419
|
+
// this decision", could only be answered by re-parsing our own label.
|
|
420
|
+
policyAttribution.set(registeredName, {
|
|
421
|
+
source: cloudManaged ? "cloud" : isConvention ? "convention" : "custom",
|
|
422
|
+
...(cloudManaged ? { cloudPolicyId: cloudManaged.id, cloudVersion: cloudManaged.version } : {}),
|
|
423
|
+
});
|
|
271
424
|
registerPolicy(
|
|
272
|
-
|
|
425
|
+
registeredName,
|
|
273
426
|
hook.description ?? "",
|
|
274
427
|
fn,
|
|
275
428
|
hook.match ?? {},
|
|
@@ -335,6 +488,32 @@ export async function evaluateHookEvent(
|
|
|
335
488
|
cwd: session.cwd,
|
|
336
489
|
permissionMode: session.permissionMode,
|
|
337
490
|
hookEventName: session.hookEventName,
|
|
491
|
+
// Attribution. A builtin is anything registered that is not in the map,
|
|
492
|
+
// so its absence is meaningful rather than missing — but only when a
|
|
493
|
+
// policy actually decided; a plain allow names nobody.
|
|
494
|
+
...(result.policyName
|
|
495
|
+
? (() => {
|
|
496
|
+
const attribution = policyAttribution.get(result.policyName);
|
|
497
|
+
return {
|
|
498
|
+
policySource: attribution?.source ?? ("builtin" as const),
|
|
499
|
+
...(attribution?.cloudPolicyId ? { cloudPolicyId: attribution.cloudPolicyId } : {}),
|
|
500
|
+
...(attribution?.cloudVersion !== undefined
|
|
501
|
+
? { cloudVersion: attribution.cloudVersion }
|
|
502
|
+
: {}),
|
|
503
|
+
};
|
|
504
|
+
})()
|
|
505
|
+
: {}),
|
|
506
|
+
...(cloudDeployment !== undefined ? { cloudDeployment } : {}),
|
|
507
|
+
// The point of observe mode is this record. Without it the rollout is
|
|
508
|
+
// unmeasurable and the row is indistinguishable from one where the policy
|
|
509
|
+
// never matched at all.
|
|
510
|
+
...(observedResults.length > 0 ? { observed: observedResults } : {}),
|
|
511
|
+
// Without these, a row logged during a pause is indistinguishable from
|
|
512
|
+
// one where every policy ran and allowed — the log would assert a clean
|
|
513
|
+
// window over exactly the window that was not enforced.
|
|
514
|
+
...(activePause
|
|
515
|
+
? { pausedBy: activePause.setBy, pauseExpiresAt: activePause.expiresAt }
|
|
516
|
+
: {}),
|
|
338
517
|
};
|
|
339
518
|
try {
|
|
340
519
|
persistHookActivity(activityEntry);
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `failproofai harness` — manage extra capture paths per harness.
|
|
3
|
+
*
|
|
4
|
+
* Each supported agent CLI has one location its own installer put it in, and
|
|
5
|
+
* the collector watches that. This command adds others: a second profile, a
|
|
6
|
+
* bind-mounted team share, a container's home mapped in beside the host's, an
|
|
7
|
+
* agent an operator relocated. Those hold real sessions and nothing collected
|
|
8
|
+
* them.
|
|
9
|
+
*
|
|
10
|
+
* # This file edits config; the daemon decides what the config means
|
|
11
|
+
*
|
|
12
|
+
* Entries are stored as written. The grammar (`label=path`, folder-name
|
|
13
|
+
* fallback), `~` expansion, overlap rejection and the `<label>-<agentId>`
|
|
14
|
+
* namespacing all live in `crates/fpai-collect/src/extra_paths.rs`, and the
|
|
15
|
+
* daemon reports what it rejected at startup. Re-implementing any of that here
|
|
16
|
+
* would give one product two parsers, and the way that fails is the CLI
|
|
17
|
+
* accepting a path the daemon then silently drops — which is indistinguishable
|
|
18
|
+
* from the feature working.
|
|
19
|
+
*
|
|
20
|
+
* The one thing validated here is the HARNESS NAME, because that failure has no
|
|
21
|
+
* other detector: `collector.sources.claud` is valid JSON, parses cleanly,
|
|
22
|
+
* captures nothing, and is mentioned nowhere else a user would look.
|
|
23
|
+
*
|
|
24
|
+
* # No restart, and no sudo
|
|
25
|
+
*
|
|
26
|
+
* This writes one file in the user's own home and nothing else. The daemon's
|
|
27
|
+
* collector manager re-reads `config.json` on an interval and cycles the
|
|
28
|
+
* collector whenever the resolved `CollectorConfig` changes — the same path
|
|
29
|
+
* that already picks up `--connect`, a stream being switched off, and a
|
|
30
|
+
* verbosity change. `extra_paths` rides it for free by living inside
|
|
31
|
+
* `Settings`, which is part of the value being compared. That is load-bearing
|
|
32
|
+
* rather than incidental, and `collector_config_change_cycles_the_collector`
|
|
33
|
+
* in `crates/fpai-collect/src/config.rs` pins it: move `sources` out of
|
|
34
|
+
* `CollectorConfig` and the CLI would keep reporting success for paths the
|
|
35
|
+
* running daemon never picks up.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { homedir } from "node:os";
|
|
39
|
+
import { resolve } from "node:path";
|
|
40
|
+
import { readConfig, updateConfig } from "./fp-config";
|
|
41
|
+
import { configFile } from "./fp-home";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Harness keys `collector.sources.<key>` accepts.
|
|
45
|
+
*
|
|
46
|
+
* MUST stay in step with `HARNESS_KEYS` in `crates/failproofaid/src/main.rs`,
|
|
47
|
+
* which is the side that actually registers the tasks. A name here that the
|
|
48
|
+
* daemon does not know configures a table nothing reads; a name the daemon
|
|
49
|
+
* knows and this list omits is a path the user cannot add. `__tests__` asserts
|
|
50
|
+
* the two lists are identical by reading the Rust source, because nothing else
|
|
51
|
+
* connects them.
|
|
52
|
+
*
|
|
53
|
+
* Twelve keys, thirteen sources: `claude` covers both the main and the subagent
|
|
54
|
+
* transcript formats, which share a root — an extra path holding Claude
|
|
55
|
+
* transcripts holds their subagents too.
|
|
56
|
+
*/
|
|
57
|
+
export const HARNESS_KEYS = [
|
|
58
|
+
"claude",
|
|
59
|
+
"codex",
|
|
60
|
+
"copilot",
|
|
61
|
+
"openclaw",
|
|
62
|
+
"pi",
|
|
63
|
+
"factory",
|
|
64
|
+
"antigravity",
|
|
65
|
+
"cursor",
|
|
66
|
+
"goose",
|
|
67
|
+
"opencode",
|
|
68
|
+
"devin",
|
|
69
|
+
"hermes",
|
|
70
|
+
] as const;
|
|
71
|
+
|
|
72
|
+
export type HarnessKey = (typeof HARNESS_KEYS)[number];
|
|
73
|
+
|
|
74
|
+
export interface HarnessResult {
|
|
75
|
+
lines: string[];
|
|
76
|
+
exitCode: number;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ok = (lines: string[]): HarnessResult => ({ lines, exitCode: 0 });
|
|
80
|
+
const fail = (lines: string[]): HarnessResult => ({ lines, exitCode: 1 });
|
|
81
|
+
|
|
82
|
+
function unknownHarness(name: string): HarnessResult {
|
|
83
|
+
return fail([
|
|
84
|
+
`Unknown harness: ${name}`,
|
|
85
|
+
"",
|
|
86
|
+
`Known harnesses: ${HARNESS_KEYS.join(", ")}`,
|
|
87
|
+
]);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** The path half of an entry, for comparing against what a user typed. */
|
|
91
|
+
function pathOf(entry: string): string {
|
|
92
|
+
const eq = entry.indexOf("=");
|
|
93
|
+
if (eq <= 0) return entry.trim();
|
|
94
|
+
const label = entry.slice(0, eq).trim();
|
|
95
|
+
return label === "" ? entry.trim() : entry.slice(eq + 1).trim();
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** The label half, or `null` when the entry did not name one. */
|
|
99
|
+
function labelOf(entry: string): string | null {
|
|
100
|
+
const eq = entry.indexOf("=");
|
|
101
|
+
if (eq <= 0) return null;
|
|
102
|
+
const label = entry.slice(0, eq).trim();
|
|
103
|
+
return label === "" ? null : label;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function currentPaths(harness: string): string[] {
|
|
107
|
+
return readConfig().collector.sources?.[harness]?.extraPaths ?? [];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function writePaths(harness: string, paths: string[]): void {
|
|
111
|
+
const cfg = readConfig();
|
|
112
|
+
const sources = { ...(cfg.collector.sources ?? {}) };
|
|
113
|
+
if (paths.length > 0) sources[harness] = { extraPaths: paths };
|
|
114
|
+
else delete sources[harness];
|
|
115
|
+
updateConfig({
|
|
116
|
+
collector: {
|
|
117
|
+
...cfg.collector,
|
|
118
|
+
// `undefined` rather than `{}` when the last entry goes, so removing what
|
|
119
|
+
// you added leaves the file as it was rather than carrying an empty table
|
|
120
|
+
// forever.
|
|
121
|
+
sources: Object.keys(sources).length > 0 ? sources : undefined,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* No restart, and deliberately no instruction to perform one.
|
|
128
|
+
*
|
|
129
|
+
* The daemon's collector manager re-reads `config.json` on an interval and
|
|
130
|
+
* cycles the collector whenever the resolved `CollectorConfig` changes — the
|
|
131
|
+
* same path that already picks up `--connect`, a stream being switched off and
|
|
132
|
+
* a verbosity change. `extra_paths` rides that for free because it lives inside
|
|
133
|
+
* `Settings`, which lives inside the value being compared.
|
|
134
|
+
*
|
|
135
|
+
* Verified live: adding a path to a running daemon logged "collector
|
|
136
|
+
* configuration changed; cycling the collector", took the task count from 21 to
|
|
137
|
+
* 23, and the first transcript written under the new path reached the server.
|
|
138
|
+
*
|
|
139
|
+
* This said "run `sudo systemctl restart failproofaid@$USER`" first. That was
|
|
140
|
+
* wrong twice over: the restart is unnecessary, and it asks for root from a
|
|
141
|
+
* command that writes one file in the user's own home and needs none.
|
|
142
|
+
*/
|
|
143
|
+
const TAKES_EFFECT_HINT = [
|
|
144
|
+
"",
|
|
145
|
+
"The daemon picks this up on its own within a few seconds — no restart, no sudo.",
|
|
146
|
+
];
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The daemon's label rule, FOR COMPARISON ONLY.
|
|
150
|
+
*
|
|
151
|
+
* Mirrors `sanitize_label()` in `crates/fpai-collect/src/extra_paths.rs`:
|
|
152
|
+
* lowercase, every non-alphanumeric run collapsed to one `-`, leading and
|
|
153
|
+
* trailing dashes trimmed.
|
|
154
|
+
*
|
|
155
|
+
* Deliberately NOT used to rewrite what is stored. The daemon is the authority on
|
|
156
|
+
* the grammar, and this file's header is explicit that it must not re-implement
|
|
157
|
+
* the parser — "two parsers is how the CLI comes to accept a path the daemon then
|
|
158
|
+
* silently drops". Normalising for a duplicate CHECK is the narrow opposite of
|
|
159
|
+
* that: it makes the CLI's own pre-flight agree with the rule it exists to
|
|
160
|
+
* pre-empt, while the stored string stays exactly what the user typed and the
|
|
161
|
+
* daemon still derives the real label.
|
|
162
|
+
*/
|
|
163
|
+
function comparableLabel(raw: string): string {
|
|
164
|
+
return raw
|
|
165
|
+
.toLowerCase()
|
|
166
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
167
|
+
.replace(/^-+|-+$/g, "");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* The daemon's path rule, for comparison only. Mirrors `clean()`: `~/` expanded,
|
|
172
|
+
* trailing slashes trimmed.
|
|
173
|
+
*/
|
|
174
|
+
function comparablePath(raw: string): string {
|
|
175
|
+
const home = homedir();
|
|
176
|
+
const expanded = raw === "~" ? home : raw.startsWith("~/") ? resolve(home, raw.slice(2)) : raw;
|
|
177
|
+
return expanded.replace(/\/+$/, "");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* What the daemon will call this entry, label given or derived.
|
|
182
|
+
*
|
|
183
|
+
* Mirrors `derive_label()` for the unlabelled case: the folder name with a leading
|
|
184
|
+
* dot stripped, then sanitised. Without this an unlabelled
|
|
185
|
+
* `/mnt/team-a/.claude/projects` and `/mnt/team-b/.claude/projects` both derive
|
|
186
|
+
* "projects" — the CLI's label check was skipped entirely for unlabelled entries,
|
|
187
|
+
* so both were written and the daemon silently dropped the second.
|
|
188
|
+
*/
|
|
189
|
+
function effectiveLabel(entry: string): string {
|
|
190
|
+
const given = labelOf(entry);
|
|
191
|
+
if (given) return comparableLabel(given);
|
|
192
|
+
const path = comparablePath(pathOf(entry));
|
|
193
|
+
const base = path.split("/").filter(Boolean).pop() ?? "";
|
|
194
|
+
return comparableLabel(base.replace(/^\.+/, ""));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function addPath(harness: string, entry: string): HarnessResult {
|
|
198
|
+
if (!(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
|
|
199
|
+
const trimmed = entry.trim();
|
|
200
|
+
if (trimmed === "") return fail(["A path is required."]);
|
|
201
|
+
if (pathOf(trimmed) === "") {
|
|
202
|
+
return fail([`Could not read a path out of ${JSON.stringify(entry)}.`, "", "Use `<path>` or `<label>=<path>`."]);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const existing = currentPaths(harness);
|
|
206
|
+
|
|
207
|
+
// Exact duplicate: succeed rather than error. `add-path` run twice from a
|
|
208
|
+
// provisioning script must not fail the second time.
|
|
209
|
+
if (existing.includes(trimmed)) {
|
|
210
|
+
return ok([`${harness}: ${trimmed} is already configured.`]);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Same path under a different label, or the same label on a different path.
|
|
214
|
+
// Both are rejected HERE rather than left to the daemon, because the daemon
|
|
215
|
+
// resolves them at startup and drops one — so the CLI would report success
|
|
216
|
+
// for a path that is never captured.
|
|
217
|
+
// Compared NORMALISED, not raw. `add-path claude a=/srv/x/` then
|
|
218
|
+
// `add-path claude b=/srv/x` differ as strings and are the same path to the
|
|
219
|
+
// daemon, which resolves them at startup and drops one — so the CLI reported
|
|
220
|
+
// success for a path that is never captured.
|
|
221
|
+
const samePath = existing.find((e) => comparablePath(pathOf(e)) === comparablePath(pathOf(trimmed)));
|
|
222
|
+
if (samePath) {
|
|
223
|
+
return fail([
|
|
224
|
+
`${harness} already captures ${pathOf(trimmed)} as ${JSON.stringify(samePath)}.`,
|
|
225
|
+
"",
|
|
226
|
+
"Remove it first if you meant to relabel it:",
|
|
227
|
+
` failproofai harness remove-path ${harness} ${pathOf(trimmed)}`,
|
|
228
|
+
]);
|
|
229
|
+
}
|
|
230
|
+
const wanted = labelOf(trimmed);
|
|
231
|
+
// The EFFECTIVE label, normalised, and computed for unlabelled entries too.
|
|
232
|
+
// Three inputs slipped past the old exact-string check on the given label:
|
|
233
|
+
// `"Team Share"` vs `team-share` (the daemon lowercases and substitutes), and two
|
|
234
|
+
// unlabelled paths whose folder name derives the same label — for which the check
|
|
235
|
+
// did not run at all. In every case both entries were written, `harness list`
|
|
236
|
+
// showed both, and only one was captured.
|
|
237
|
+
const effective = effectiveLabel(trimmed);
|
|
238
|
+
const sameLabel = effective
|
|
239
|
+
? existing.find((e) => effectiveLabel(e) === effective)
|
|
240
|
+
: undefined;
|
|
241
|
+
if (sameLabel) {
|
|
242
|
+
return fail([
|
|
243
|
+
`${harness} already uses the label ${JSON.stringify(effective)} for ${pathOf(sameLabel)}.`,
|
|
244
|
+
"",
|
|
245
|
+
"Labels namespace agent ids, so two paths cannot share one — and the daemon",
|
|
246
|
+
"lowercases them and collapses punctuation to `-`, so labels that differ only",
|
|
247
|
+
"in case or spacing collide. An unlabelled path takes its folder name.",
|
|
248
|
+
"",
|
|
249
|
+
"Give this one an explicit label:",
|
|
250
|
+
` failproofai harness add-path ${harness} <label>=${pathOf(trimmed)}`,
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
writePaths(harness, [...existing, trimmed]);
|
|
255
|
+
return ok([
|
|
256
|
+
// "configured", NOT "now capturing". The checks above cover what this side can
|
|
257
|
+
// know — the entries already in the file — and there is one rejection it
|
|
258
|
+
// cannot: a path overlapping the harness's own DEFAULT capture root, which the
|
|
259
|
+
// daemon owns and refuses. Claiming capture there was a promise this command
|
|
260
|
+
// is not in a position to make, so it says what it did and where the answer
|
|
261
|
+
// is. Teaching the CLI all thirteen sources' default roots would be the second
|
|
262
|
+
// parser this file exists to avoid.
|
|
263
|
+
`${harness}: configured to also capture ${pathOf(trimmed)}`,
|
|
264
|
+
wanted
|
|
265
|
+
? ` agent ids will be namespaced ${wanted}-*`
|
|
266
|
+
: " a label will be derived from the folder name; `failproofai harness list` shows it",
|
|
267
|
+
` written to ${configFile()}`,
|
|
268
|
+
"",
|
|
269
|
+
" The daemon validates it on the next read and reports what it rejected —",
|
|
270
|
+
" `failproofai harness list` shows what is actually being captured.",
|
|
271
|
+
...TAKES_EFFECT_HINT,
|
|
272
|
+
]);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function removePath(harness: string, target: string): HarnessResult {
|
|
276
|
+
if (!(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
|
|
277
|
+
const existing = currentPaths(harness);
|
|
278
|
+
const t = target.trim();
|
|
279
|
+
// Match on the whole entry, on its path, or on its label — a user removing
|
|
280
|
+
// something is working from what `list` printed, which shows all three.
|
|
281
|
+
const kept = existing.filter((e) => e !== t && pathOf(e) !== t && labelOf(e) !== t);
|
|
282
|
+
if (kept.length === existing.length) {
|
|
283
|
+
return fail([
|
|
284
|
+
`${harness} has no extra path matching ${JSON.stringify(t)}.`,
|
|
285
|
+
"",
|
|
286
|
+
existing.length > 0
|
|
287
|
+
? `Configured: ${existing.join(", ")}`
|
|
288
|
+
: `${harness} has no extra paths configured.`,
|
|
289
|
+
]);
|
|
290
|
+
}
|
|
291
|
+
writePaths(harness, kept);
|
|
292
|
+
return ok([
|
|
293
|
+
`${harness}: no longer capturing ${t}`,
|
|
294
|
+
"",
|
|
295
|
+
"Already-collected sessions from that path are NOT removed — they are on the",
|
|
296
|
+
"server. This only stops new ones.",
|
|
297
|
+
...TAKES_EFFECT_HINT,
|
|
298
|
+
]);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function listPaths(harness?: string): HarnessResult {
|
|
302
|
+
if (harness && !(HARNESS_KEYS as readonly string[]).includes(harness)) return unknownHarness(harness);
|
|
303
|
+
const sources = readConfig().collector.sources ?? {};
|
|
304
|
+
const names: string[] = harness
|
|
305
|
+
? [harness]
|
|
306
|
+
: HARNESS_KEYS.filter((k) => sources[k]?.extraPaths?.length);
|
|
307
|
+
|
|
308
|
+
// Computed BEFORE the empty-state return, not after. A config whose only
|
|
309
|
+
// tables are typos has no known harness configured, so the early return fired
|
|
310
|
+
// and the warning below was unreachable in precisely the case it exists for —
|
|
311
|
+
// the user sees "No extra capture paths configured" while their file plainly
|
|
312
|
+
// contains one, which is the most confusing possible answer.
|
|
313
|
+
const unknown = Object.keys(sources).filter(
|
|
314
|
+
(k) => !(HARNESS_KEYS as readonly string[]).includes(k),
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
if (names.length === 0 && unknown.length === 0) {
|
|
318
|
+
return ok([
|
|
319
|
+
"No extra capture paths configured.",
|
|
320
|
+
"",
|
|
321
|
+
"Every harness is watching only its default location. Add one with:",
|
|
322
|
+
" failproofai harness add-path <harness> [<label>=]<path>",
|
|
323
|
+
"",
|
|
324
|
+
`Harnesses: ${HARNESS_KEYS.join(", ")}`,
|
|
325
|
+
]);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const lines: string[] = [];
|
|
329
|
+
for (const name of names) {
|
|
330
|
+
const entries = sources[name]?.extraPaths ?? [];
|
|
331
|
+
if (entries.length === 0) {
|
|
332
|
+
if (harness) lines.push(`${name}: no extra paths configured (default location only)`);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
lines.push(`${name}:`);
|
|
336
|
+
for (const e of entries) {
|
|
337
|
+
const label = labelOf(e);
|
|
338
|
+
lines.push(
|
|
339
|
+
label
|
|
340
|
+
? ` ${pathOf(e)} → agent ids ${label}-*`
|
|
341
|
+
: ` ${pathOf(e)} → label derived from the folder name`,
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Unknown tables are surfaced here as well as by the daemon: a user who
|
|
346
|
+
// hand-edited config.json runs `list` to check it, and that is the moment the
|
|
347
|
+
// typo is cheapest to find.
|
|
348
|
+
if (unknown.length > 0) {
|
|
349
|
+
if (lines.length === 0) lines.push("No extra capture paths configured for any known harness.");
|
|
350
|
+
lines.push(
|
|
351
|
+
"",
|
|
352
|
+
`⚠ config.json configures ${unknown.length} unknown harness(es): ${unknown.join(", ")}`,
|
|
353
|
+
" Nothing is captured from them.",
|
|
354
|
+
` Known: ${HARNESS_KEYS.join(", ")}`,
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
return ok(lines);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/** Dispatch for `failproofai harness <sub> ...`. */
|
|
361
|
+
export function runHarnessCommand(argv: string[]): HarnessResult {
|
|
362
|
+
const [sub, ...rest] = argv;
|
|
363
|
+
switch (sub) {
|
|
364
|
+
case "add-path":
|
|
365
|
+
if (rest.length < 2) {
|
|
366
|
+
return fail(["Usage: failproofai harness add-path <harness> [<label>=]<path>"]);
|
|
367
|
+
}
|
|
368
|
+
return addPath(rest[0], rest.slice(1).join(" "));
|
|
369
|
+
case "remove-path":
|
|
370
|
+
if (rest.length < 2) {
|
|
371
|
+
return fail(["Usage: failproofai harness remove-path <harness> <path|label>"]);
|
|
372
|
+
}
|
|
373
|
+
return removePath(rest[0], rest.slice(1).join(" "));
|
|
374
|
+
case "list":
|
|
375
|
+
return listPaths(rest[0]);
|
|
376
|
+
default:
|
|
377
|
+
return fail([
|
|
378
|
+
sub ? `Unknown subcommand: ${sub}` : "A subcommand is required.",
|
|
379
|
+
"",
|
|
380
|
+
"Usage:",
|
|
381
|
+
" failproofai harness list [<harness>]",
|
|
382
|
+
" failproofai harness add-path <harness> [<label>=]<path>",
|
|
383
|
+
" failproofai harness remove-path <harness> <path|label>",
|
|
384
|
+
]);
|
|
385
|
+
}
|
|
386
|
+
}
|