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
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `failproofai uninstall` — the sanctioned way off this machine.
|
|
3
|
+
*
|
|
4
|
+
* It exists because npm cannot do it. npm runs NO uninstall script (this repo
|
|
5
|
+
* proved that empirically and deleted the dead `preuninstall` that assumed
|
|
6
|
+
* otherwise), so `npm rm -g failproofai` deletes the package and leaves behind
|
|
7
|
+
* every durable thing the package installed: hook entries in up to twelve agent
|
|
8
|
+
* CLIs' settings files, and a root-owned systemd unit.
|
|
9
|
+
*
|
|
10
|
+
* That leftovers set is not inert. The hook entries invoke `npx -y failproofai`,
|
|
11
|
+
* which happily re-downloads the package from the registry — so a "removed"
|
|
12
|
+
* failproofai keeps running on every tool call. And on a machine with
|
|
13
|
+
* `[daemon] configured = true`, the surviving unit points at a worker script
|
|
14
|
+
* that npm just deleted, which under fail-closed semantics denies EVERY tool
|
|
15
|
+
* call across every agent CLI, with nothing on screen naming the cause.
|
|
16
|
+
*
|
|
17
|
+
* ORDER IS THE SAFETY PROPERTY HERE, and it is not the obvious one.
|
|
18
|
+
*
|
|
19
|
+
* `daemonConfigured` is cleared FIRST — before hooks, before the service, before
|
|
20
|
+
* anything that can fail or need a password. From that instant the machine can
|
|
21
|
+
* only fail OPEN: policies evaluate in-process, and every later step is
|
|
22
|
+
* best-effort cleanup. Doing it in the intuitive order (tear the service down,
|
|
23
|
+
* then update config) leaves a window where the flag demands a daemon that is
|
|
24
|
+
* already gone, and that window is a total lockout of the user's agent — the
|
|
25
|
+
* exact failure `healDaemonFlag` was written to repair after it bricked a
|
|
26
|
+
* machine during development.
|
|
27
|
+
*
|
|
28
|
+
* Nothing here is silent about failure. A step that cannot complete (no sudo,
|
|
29
|
+
* an unwritable settings file) is reported with the command to finish it by
|
|
30
|
+
* hand, and the exit code says whether anything was left behind — because the
|
|
31
|
+
* one thing worse than a leftover unit is a leftover unit the operator was told
|
|
32
|
+
* did not exist.
|
|
33
|
+
*/
|
|
34
|
+
import { existsSync, rmSync } from "node:fs";
|
|
35
|
+
|
|
36
|
+
import { failproofaiHome } from "./fp-home";
|
|
37
|
+
import { readConfig } from "./fp-config";
|
|
38
|
+
import {
|
|
39
|
+
daemonServiceFilePath,
|
|
40
|
+
daemonServiceStatus,
|
|
41
|
+
daemonStatusCommand,
|
|
42
|
+
isDaemonSupportedPlatform,
|
|
43
|
+
primeElevation,
|
|
44
|
+
setDaemonConfigured,
|
|
45
|
+
uninstallDaemonService,
|
|
46
|
+
} from "./daemon-service";
|
|
47
|
+
import { listInstallableIds, getIntegration } from "./integrations";
|
|
48
|
+
import type { IntegrationType } from "./types";
|
|
49
|
+
import { removeHooks } from "./manager";
|
|
50
|
+
|
|
51
|
+
export interface UninstallOptions {
|
|
52
|
+
/** Also delete ~/.failproofai (config, credentials, state, audit cache, daemon binary). */
|
|
53
|
+
purge?: boolean;
|
|
54
|
+
/** Report what would change and touch nothing. */
|
|
55
|
+
dryRun?: boolean;
|
|
56
|
+
/** Proceed without the interactive confirmation. */
|
|
57
|
+
yes?: boolean;
|
|
58
|
+
/** Project/local scopes are read relative to here. */
|
|
59
|
+
cwd?: string;
|
|
60
|
+
/**
|
|
61
|
+
* Injected so the decision is testable without a TTY. Absent means "no
|
|
62
|
+
* confirmation available", which is a REFUSAL rather than an assumed yes —
|
|
63
|
+
* see the non-interactive branch.
|
|
64
|
+
*/
|
|
65
|
+
confirm?: (lines: string[]) => Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Asked, on a plain uninstall only, whether the daemon service should go too.
|
|
68
|
+
*
|
|
69
|
+
* A plain uninstall removes the hooks and leaves the machine otherwise intact,
|
|
70
|
+
* so the daemon is a genuine choice: someone clearing hooks before reinstalling
|
|
71
|
+
* has no reason to tear down a system service and re-enter their password for
|
|
72
|
+
* it. Absent means "could not ask" and the daemon is KEPT — declining to remove
|
|
73
|
+
* a service is the recoverable half of that decision, and removing one nobody
|
|
74
|
+
* asked about is not.
|
|
75
|
+
*
|
|
76
|
+
* NOT consulted under `--purge`. See `removeDaemon` in the implementation for
|
|
77
|
+
* why purge cannot leave it behind.
|
|
78
|
+
*/
|
|
79
|
+
confirmDaemon?: () => Promise<boolean>;
|
|
80
|
+
/**
|
|
81
|
+
* Ask for the sudo password before removing the service. Injected for tests;
|
|
82
|
+
* defaults to `primeElevation`.
|
|
83
|
+
*/
|
|
84
|
+
elevate?: () => boolean;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface UninstallResult {
|
|
88
|
+
exitCode: number;
|
|
89
|
+
lines: string[];
|
|
90
|
+
/**
|
|
91
|
+
* How many leading entries of `lines` are the plan — the block already handed
|
|
92
|
+
* to `confirm`.
|
|
93
|
+
*
|
|
94
|
+
* Returned rather than inferred so the caller can skip re-printing it without
|
|
95
|
+
* pattern-matching its own output. A caller that guesses (by searching for a
|
|
96
|
+
* blank line, say) silently prints the whole plan twice the day a message
|
|
97
|
+
* above it gains one.
|
|
98
|
+
*/
|
|
99
|
+
planLines: number;
|
|
100
|
+
/**
|
|
101
|
+
* Whether `~/.failproofai` was actually deleted.
|
|
102
|
+
*
|
|
103
|
+
* The caller needs this to know that NOTHING may touch the home afterwards.
|
|
104
|
+
* Telemetry is the trap: `getInstanceId()` lazily writes
|
|
105
|
+
* `state/telemetry-id`, so a routine post-command event re-created the whole
|
|
106
|
+
* directory seconds after the purge reported deleting it — leaving a machine
|
|
107
|
+
* the user had just wiped holding a brand-new tracking identifier, and the
|
|
108
|
+
* command's own "✓ deleted" line a lie. Caught by the container test, which
|
|
109
|
+
* checked the filesystem rather than the output.
|
|
110
|
+
*/
|
|
111
|
+
purged: boolean;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Everything this machine has that uninstalling would remove. */
|
|
115
|
+
interface Leftovers {
|
|
116
|
+
clis: IntegrationType[];
|
|
117
|
+
servicePath: string | null;
|
|
118
|
+
serviceInstalled: boolean;
|
|
119
|
+
daemonConfigured: boolean;
|
|
120
|
+
homeExists: boolean;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function survey(cwd?: string): Leftovers {
|
|
124
|
+
const clis: IntegrationType[] = [];
|
|
125
|
+
// Every INSTALLABLE cli, not every DETECTED one. A CLI can be uninstalled
|
|
126
|
+
// after failproofai wrote hooks into its settings file, and those entries are
|
|
127
|
+
// exactly the orphans this command exists to clear — surveying only what is
|
|
128
|
+
// currently on PATH would walk straight past them.
|
|
129
|
+
for (const id of listInstallableIds()) {
|
|
130
|
+
for (const scope of ["user", "project", "local"] as const) {
|
|
131
|
+
try {
|
|
132
|
+
if (getIntegration(id).hooksInstalledInSettings(scope, cwd)) {
|
|
133
|
+
clis.push(id);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
} catch {
|
|
137
|
+
// An unreadable or malformed settings file is not proof of absence, but
|
|
138
|
+
// it is not proof of presence either, and `removeHooks` will report its
|
|
139
|
+
// own failure against it later with a better message than a guess here.
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let daemonConfigured = false;
|
|
145
|
+
try {
|
|
146
|
+
daemonConfigured = readConfig().daemon.configured;
|
|
147
|
+
} catch {
|
|
148
|
+
/* no config = nothing configured */
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const servicePath = isDaemonSupportedPlatform() ? daemonServiceFilePath() : null;
|
|
152
|
+
return {
|
|
153
|
+
clis,
|
|
154
|
+
servicePath,
|
|
155
|
+
serviceInstalled: !!servicePath && existsSync(servicePath),
|
|
156
|
+
daemonConfigured,
|
|
157
|
+
homeExists: existsSync(failproofaiHome()),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export async function runUninstallCommand(opts: UninstallOptions = {}): Promise<UninstallResult> {
|
|
162
|
+
const found = survey(opts.cwd);
|
|
163
|
+
const lines: string[] = [];
|
|
164
|
+
|
|
165
|
+
const nothingToDo =
|
|
166
|
+
found.clis.length === 0 && !found.serviceInstalled && !found.daemonConfigured &&
|
|
167
|
+
!(opts.purge && found.homeExists);
|
|
168
|
+
if (nothingToDo) {
|
|
169
|
+
return {
|
|
170
|
+
exitCode: 0,
|
|
171
|
+
planLines: 0,
|
|
172
|
+
purged: false,
|
|
173
|
+
lines: [
|
|
174
|
+
"Nothing to uninstall — no hook entries, no daemon service, and nothing configured.",
|
|
175
|
+
...(found.homeExists && !opts.purge
|
|
176
|
+
? [``, `${failproofaiHome()} still holds settings and audit history.`,
|
|
177
|
+
`Remove it with \`failproofai uninstall --purge\`.`]
|
|
178
|
+
: []),
|
|
179
|
+
],
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// The plan is shown before anything happens and is the same text the
|
|
184
|
+
// confirmation asks about, so what is agreed to and what is done cannot drift.
|
|
185
|
+
const plan: string[] = [];
|
|
186
|
+
if (found.clis.length > 0) {
|
|
187
|
+
plan.push(
|
|
188
|
+
` • remove failproofai hook entries from ${found.clis.length} agent CLI${found.clis.length === 1 ? "" : "s"}: ` +
|
|
189
|
+
found.clis.map((id) => getIntegration(id).displayName ?? id).join(", "),
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
if (found.daemonConfigured) {
|
|
193
|
+
plan.push(` • stop requiring the daemon (policies go back to evaluating in-process)`);
|
|
194
|
+
}
|
|
195
|
+
if (found.serviceInstalled) {
|
|
196
|
+
plan.push(
|
|
197
|
+
opts.purge || opts.yes
|
|
198
|
+
? ` • stop, disable and delete the service at ${found.servicePath} — needs sudo`
|
|
199
|
+
: ` • ask whether to remove the service at ${found.servicePath} (kept unless you say so)`,
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
let purged = false;
|
|
203
|
+
if (opts.purge && found.homeExists) {
|
|
204
|
+
plan.push(` • delete ${failproofaiHome()} — settings, credentials, audit history and the daemon binary`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
lines.push("failproofai uninstall will:", ...plan);
|
|
208
|
+
if (!opts.purge && found.homeExists) {
|
|
209
|
+
lines.push(
|
|
210
|
+
``,
|
|
211
|
+
`${failproofaiHome()} is KEPT (settings and audit history survive a reinstall).`,
|
|
212
|
+
`Add --purge to delete it too.`,
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
// Everything pushed so far IS the plan; `confirm` is shown exactly this.
|
|
216
|
+
const planLines = lines.length;
|
|
217
|
+
|
|
218
|
+
if (opts.dryRun) {
|
|
219
|
+
lines.push(``, `--dry-run: nothing was changed.`);
|
|
220
|
+
return { exitCode: 0, lines, planLines, purged: false };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!opts.yes) {
|
|
224
|
+
// No confirmation channel and no --yes is a REFUSAL, never an assumed yes.
|
|
225
|
+
// This runs in scripts and CI, where a prompt that cannot be answered would
|
|
226
|
+
// otherwise read as consent to delete a root-owned service.
|
|
227
|
+
if (!opts.confirm) {
|
|
228
|
+
lines.push(``, `Refusing to uninstall without confirmation. Re-run with --yes.`);
|
|
229
|
+
return { exitCode: 1, lines, planLines, purged: false };
|
|
230
|
+
}
|
|
231
|
+
if (!(await opts.confirm(lines))) {
|
|
232
|
+
return { exitCode: 1, planLines, purged: false, lines: [...lines, ``, `Cancelled — nothing was changed.`] };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
lines.push(``);
|
|
237
|
+
const failures: string[] = [];
|
|
238
|
+
|
|
239
|
+
// STEP 1, and it must stay step 1. See the header: from here the machine can
|
|
240
|
+
// only fail open, so every remaining step is cleanup rather than a step that
|
|
241
|
+
// can lock anybody out by failing halfway.
|
|
242
|
+
if (found.daemonConfigured) {
|
|
243
|
+
try {
|
|
244
|
+
setDaemonConfigured(false);
|
|
245
|
+
lines.push(`✓ policies now evaluate in-process (this machine no longer requires the daemon)`);
|
|
246
|
+
} catch (err) {
|
|
247
|
+
// Uniquely fatal: everything after this assumes the flag is down. Removing
|
|
248
|
+
// the service while it is still up is the lockout this command exists to
|
|
249
|
+
// prevent, so stop rather than press on.
|
|
250
|
+
return {
|
|
251
|
+
exitCode: 2,
|
|
252
|
+
planLines,
|
|
253
|
+
purged: false,
|
|
254
|
+
lines: [
|
|
255
|
+
...lines,
|
|
256
|
+
`✗ could not update ${failproofaiHome()}/config.json: ${err instanceof Error ? err.message : String(err)}`,
|
|
257
|
+
``,
|
|
258
|
+
`Stopped before touching the service. Removing it while this machine still`,
|
|
259
|
+
`requires it would deny every tool call. Fix the file's permissions and re-run.`,
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (found.clis.length > 0) {
|
|
266
|
+
try {
|
|
267
|
+
// Scope "all" and every cli that HAS entries — the same list the plan
|
|
268
|
+
// named. `removeCustomHooks` clears the configured custom-policy paths as
|
|
269
|
+
// well; leaving them behind would point a reinstall at files this command
|
|
270
|
+
// may be about to purge.
|
|
271
|
+
await removeHooks(undefined, "all", opts.cwd, {
|
|
272
|
+
cli: found.clis,
|
|
273
|
+
removeCustomHooks: true,
|
|
274
|
+
source: "uninstall_command",
|
|
275
|
+
});
|
|
276
|
+
lines.push(`✓ removed hook entries from ${found.clis.length} agent CLI${found.clis.length === 1 ? "" : "s"}`);
|
|
277
|
+
} catch (err) {
|
|
278
|
+
failures.push(
|
|
279
|
+
`hook entries: ${err instanceof Error ? err.message : String(err)} ` +
|
|
280
|
+
`(finish with \`failproofai policies --uninstall --scope all\`)`,
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
if (found.serviceInstalled) {
|
|
286
|
+
// PURGE ALWAYS REMOVES IT, and is not asked. `--purge` deletes
|
|
287
|
+
// `~/.failproofai`, which is where the daemon BINARY lives
|
|
288
|
+
// (`bin/failproofaid-<version>`) — so keeping an enabled unit whose ExecStart
|
|
289
|
+
// has just been deleted leaves the machine crash-looping the service at every
|
|
290
|
+
// boot. "Keep the daemon" is not an option purge can offer, because purge has
|
|
291
|
+
// already destroyed what the daemon needs to run.
|
|
292
|
+
//
|
|
293
|
+
// A plain uninstall is the opposite: the home survives, the binary survives,
|
|
294
|
+
// and someone clearing hooks before a reinstall has no reason to tear down a
|
|
295
|
+
// system service and re-type their password. So it asks, and a missing answer
|
|
296
|
+
// means KEEP — declining to remove a service is recoverable, removing one
|
|
297
|
+
// nobody asked about is not.
|
|
298
|
+
//
|
|
299
|
+
// `--yes` also removes it, and that is deliberate rather than incidental: the
|
|
300
|
+
// flag means "yes to the plan", the plan has always included the service, and
|
|
301
|
+
// scripted uninstalls rely on it. Making `--yes` keep the daemon would silently
|
|
302
|
+
// start leaving a service behind on every automated run — a behaviour change
|
|
303
|
+
// nobody asked for, in the direction of leaving more behind.
|
|
304
|
+
//
|
|
305
|
+
// So the only case that keeps it is an INTERACTIVE run where the person said no
|
|
306
|
+
// (or could not be asked).
|
|
307
|
+
const removeDaemon =
|
|
308
|
+
opts.purge || opts.yes ? true : ((await opts.confirmDaemon?.()) ?? false);
|
|
309
|
+
|
|
310
|
+
if (!removeDaemon) {
|
|
311
|
+
lines.push(
|
|
312
|
+
`• kept the daemon service at ${found.servicePath}`,
|
|
313
|
+
` Remove it later with: failproofai uninstall --purge`,
|
|
314
|
+
);
|
|
315
|
+
} else {
|
|
316
|
+
// Ask for the password BEFORE trying, rather than failing on `sudo -n` and
|
|
317
|
+
// printing a unit file to delete by hand. `uninstallDaemonService()` is
|
|
318
|
+
// deliberately non-interactive — the wizard cannot prompt from under a
|
|
319
|
+
// full-screen TUI — but this command is plain line output and has a person
|
|
320
|
+
// in front of it, which is the same reasoning `failproofai update` follows.
|
|
321
|
+
// Best-effort: a refused or absent sudo still falls through to the existing
|
|
322
|
+
// "still there, here are the commands" path below.
|
|
323
|
+
const elevate = opts.elevate ?? primeElevation;
|
|
324
|
+
try {
|
|
325
|
+
elevate();
|
|
326
|
+
} catch {
|
|
327
|
+
// A failed prompt is not a reason to skip the attempt; `sudo -n` inside
|
|
328
|
+
// the removal will simply fail the same way it would have anyway.
|
|
329
|
+
}
|
|
330
|
+
try {
|
|
331
|
+
await uninstallDaemonService();
|
|
332
|
+
// uninstallDaemonService is best-effort by contract — it warns and
|
|
333
|
+
// returns rather than throwing when it cannot elevate — so the unit file
|
|
334
|
+
// is what gets believed here, not the absence of an exception.
|
|
335
|
+
if (found.servicePath && existsSync(found.servicePath)) {
|
|
336
|
+
failures.push(
|
|
337
|
+
`the service at ${found.servicePath} is still there (most often: no sudo). ` +
|
|
338
|
+
`Remove it with the commands below.`,
|
|
339
|
+
);
|
|
340
|
+
} else {
|
|
341
|
+
lines.push(`✓ stopped and removed the daemon service`);
|
|
342
|
+
}
|
|
343
|
+
} catch (err) {
|
|
344
|
+
failures.push(`daemon service: ${err instanceof Error ? err.message : String(err)}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (opts.purge && found.homeExists) {
|
|
350
|
+
// Last, and only after the service is down: the daemon binary and its
|
|
351
|
+
// socket live here, and deleting them out from under a running unit is how
|
|
352
|
+
// a clean uninstall turns into a restart loop.
|
|
353
|
+
try {
|
|
354
|
+
rmSync(failproofaiHome(), { recursive: true, force: true });
|
|
355
|
+
purged = true;
|
|
356
|
+
lines.push(`✓ deleted ${failproofaiHome()}`);
|
|
357
|
+
} catch (err) {
|
|
358
|
+
failures.push(`${failproofaiHome()}: ${err instanceof Error ? err.message : String(err)}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (failures.length > 0) {
|
|
363
|
+
lines.push(``, `Not everything could be removed:`);
|
|
364
|
+
for (const f of failures) lines.push(` ✗ ${f}`);
|
|
365
|
+
if (found.servicePath && existsSync(found.servicePath)) {
|
|
366
|
+
lines.push(
|
|
367
|
+
``,
|
|
368
|
+
`To remove the service by hand:`,
|
|
369
|
+
...manualServiceRemoval(found.servicePath),
|
|
370
|
+
``,
|
|
371
|
+
`(\`${daemonStatusCommand()}\` shows its current state.)`,
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
// Exit 1, not 0: enforcement is off (step 1 succeeded, or there was nothing
|
|
375
|
+
// to clear), but something durable is still installed and the operator has
|
|
376
|
+
// to act. A 0 here is what makes people believe a machine is clean when a
|
|
377
|
+
// root-owned unit is still on it.
|
|
378
|
+
return { exitCode: 1, lines, planLines, purged };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
lines.push(
|
|
382
|
+
``,
|
|
383
|
+
`Done. failproofai no longer enforces anything on this machine.`,
|
|
384
|
+
``,
|
|
385
|
+
`The npm package itself is still installed — npm runs no uninstall script, which`,
|
|
386
|
+
`is why this command exists. Remove it with:`,
|
|
387
|
+
` npm rm -g failproofai`,
|
|
388
|
+
);
|
|
389
|
+
if (!opts.purge && found.homeExists) {
|
|
390
|
+
lines.push(``, `${failproofaiHome()} was kept. Delete it with \`failproofai uninstall --purge\`.`);
|
|
391
|
+
}
|
|
392
|
+
return { exitCode: 0, lines, planLines, purged };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** The exact commands to finish a service removal that could not elevate. */
|
|
396
|
+
function manualServiceRemoval(servicePath: string): string[] {
|
|
397
|
+
if (process.platform === "darwin") {
|
|
398
|
+
return [` sudo launchctl unload -w ${servicePath}`, ` sudo rm -f ${servicePath}`];
|
|
399
|
+
}
|
|
400
|
+
const unit = servicePath.split("/").pop() ?? servicePath;
|
|
401
|
+
return [
|
|
402
|
+
` sudo systemctl disable --now ${unit}`,
|
|
403
|
+
` sudo rm -f ${servicePath}`,
|
|
404
|
+
` sudo systemctl daemon-reload`,
|
|
405
|
+
];
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** What `daemonServiceStatus()` says, for the status line the CLI prints. */
|
|
409
|
+
export function serviceStateLabel(): string {
|
|
410
|
+
const status = daemonServiceStatus();
|
|
411
|
+
if (status === "condition-failed") return "installed but skipped by systemd (a file it needs is missing)";
|
|
412
|
+
// Printing a bare "unknown" invites the reading "something is wrong". It is
|
|
413
|
+
// narrower than that: the service is installed and we could not read its
|
|
414
|
+
// state, because doing so on macOS needs root and no sudo credential was
|
|
415
|
+
// cached. Say which, so the reader knows there is nothing to fix.
|
|
416
|
+
if (status === "unknown") return "installed (state needs sudo to read)";
|
|
417
|
+
return status;
|
|
418
|
+
}
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* correct with zero changes; a worker_threads/process pool is a valid
|
|
20
20
|
* future enhancement if this becomes a real throughput bottleneck.
|
|
21
21
|
*/
|
|
22
|
-
import { createServer, type Server, type Socket } from "node:net";
|
|
22
|
+
import { createConnection, createServer, type Server, type Socket } from "node:net";
|
|
23
23
|
import { existsSync, unlinkSync } from "node:fs";
|
|
24
24
|
import { evaluateHookEvent } from "./handler";
|
|
25
25
|
import type { IntegrationType } from "./types";
|
|
@@ -43,7 +43,15 @@ function isWorkerHookRequest(msg: unknown): msg is WorkerHookRequest {
|
|
|
43
43
|
typeof m.hookEvent === "string" &&
|
|
44
44
|
typeof m.cli === "string" &&
|
|
45
45
|
typeof m.stdin === "string" &&
|
|
46
|
-
|
|
46
|
+
// `null` as well as `undefined`. `cwd` is `Option<String>` on the wire, and
|
|
47
|
+
// the daemon forwards it with `json!({ "cwd": cwd })`, which serialises
|
|
48
|
+
// `None` as an explicit NULL rather than omitting the key. Accepting only
|
|
49
|
+
// `undefined` therefore rejected every request that legitimately carried no
|
|
50
|
+
// cwd — including the setup health probe, which sends none, so
|
|
51
|
+
// `failproofai config` aborted with "its worker process could not be run"
|
|
52
|
+
// against a daemon and worker that were both perfectly healthy. On a
|
|
53
|
+
// daemonConfigured machine the same mismatch denies the tool call.
|
|
54
|
+
(m.cwd === undefined || m.cwd === null || typeof m.cwd === "string")
|
|
47
55
|
);
|
|
48
56
|
}
|
|
49
57
|
|
|
@@ -54,18 +62,70 @@ function encodeFrame(value: unknown): Buffer {
|
|
|
54
62
|
return Buffer.concat([header, body]);
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Last-resort deadline on a single queued task.
|
|
67
|
+
*
|
|
68
|
+
* Deliberately far above `daemon-client.ts`'s 30s `DAEMON_RESPONSE_TIMEOUT_MS`:
|
|
69
|
+
* by the time this fires the client has long since given up and fail-closed, so
|
|
70
|
+
* nothing legitimate is waiting. It exists only to detect a task that will never
|
|
71
|
+
* settle at all.
|
|
72
|
+
*/
|
|
73
|
+
const TASK_DEADLINE_MS = 60_000;
|
|
74
|
+
|
|
57
75
|
/**
|
|
58
76
|
* Serializes every request across every connection through one chain, so
|
|
59
77
|
* `evaluateHookEvent` calls never overlap. `.catch()` on each link keeps the
|
|
60
78
|
* chain alive even if a handler throws unexpectedly — one bad request must
|
|
61
79
|
* never wedge every request queued behind it.
|
|
80
|
+
*
|
|
81
|
+
* `.catch()` covers a task that REJECTS. It does nothing for one that never
|
|
82
|
+
* SETTLES, and that state is reachable: a policy file with a top-level `await`
|
|
83
|
+
* that never resolves hangs its `import()`, so the task neither resolves nor
|
|
84
|
+
* rejects and every hook queued behind it waits forever. Each client then burns
|
|
85
|
+
* its own 30s budget and fail-closed denies — every tool call, every CLI on the
|
|
86
|
+
* machine, until someone restarts the daemon. This is a class the warm worker
|
|
87
|
+
* creates: in the one-shot path the identical file hangs one hook process and
|
|
88
|
+
* the agent CLI's own timeout reaps it.
|
|
89
|
+
*
|
|
90
|
+
* `custom-hooks-loader.ts` bounds that import directly, which is the graceful
|
|
91
|
+
* fix and covers the known cause. This is the backstop for the ones nobody
|
|
92
|
+
* thought of, and it EXITS rather than continuing: the orphaned task is still
|
|
93
|
+
* running and still holds the `globalThis` policy registry this chain exists to
|
|
94
|
+
* serialize access to, so letting the next request proceed would trade a wedge
|
|
95
|
+
* for silent cross-request registry corruption. Exiting hands the supervisor a
|
|
96
|
+
* process it will respawn clean (`worker.rs`'s `ensure_started` respawns a
|
|
97
|
+
* worker whose `try_wait()` reports it gone), which costs one cold start and
|
|
98
|
+
* denies the in-flight requests — the same fail-closed outcome they were headed
|
|
99
|
+
* for anyway, but recovered on the next call instead of never.
|
|
62
100
|
*/
|
|
101
|
+
const WEDGED_EXIT_CODE = 75;
|
|
102
|
+
|
|
63
103
|
let processingChain: Promise<void> = Promise.resolve();
|
|
64
104
|
function enqueue(task: () => Promise<void>): void {
|
|
65
|
-
processingChain = processingChain
|
|
105
|
+
processingChain = processingChain
|
|
106
|
+
.then(
|
|
107
|
+
() =>
|
|
108
|
+
new Promise<void>((settle) => {
|
|
109
|
+
const timer = setTimeout(() => {
|
|
110
|
+
hookLogWarn(
|
|
111
|
+
`worker: a request did not settle within ${TASK_DEADLINE_MS}ms; ` +
|
|
112
|
+
`exiting so the supervisor can respawn a clean worker`,
|
|
113
|
+
);
|
|
114
|
+
process.exit(WEDGED_EXIT_CODE);
|
|
115
|
+
}, TASK_DEADLINE_MS);
|
|
116
|
+
// Never let the deadline itself hold the event loop open.
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
const done = () => {
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
settle();
|
|
121
|
+
};
|
|
122
|
+
void task().then(done, done);
|
|
123
|
+
}),
|
|
124
|
+
)
|
|
125
|
+
.catch(() => {});
|
|
66
126
|
}
|
|
67
127
|
|
|
68
|
-
function handleConnection(socket: Socket): void {
|
|
128
|
+
function handleConnection(socket: Socket, shutdown: () => void): void {
|
|
69
129
|
let recvBuf = Buffer.alloc(0);
|
|
70
130
|
let declaredLen: number | null = null;
|
|
71
131
|
|
|
@@ -103,6 +163,15 @@ function handleConnection(socket: Socket): void {
|
|
|
103
163
|
continue;
|
|
104
164
|
}
|
|
105
165
|
|
|
166
|
+
if (
|
|
167
|
+
message &&
|
|
168
|
+
typeof message === "object" &&
|
|
169
|
+
(message as Record<string, unknown>).type === "shutdown"
|
|
170
|
+
) {
|
|
171
|
+
socket.end(encodeFrame({ type: "shutdownAccepted" }), shutdown);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
106
175
|
if (!isWorkerHookRequest(message)) {
|
|
107
176
|
socket.write(encodeFrame({ type: "error", message: "unrecognized request shape" }));
|
|
108
177
|
continue;
|
|
@@ -113,7 +182,9 @@ function handleConnection(socket: Socket): void {
|
|
|
113
182
|
try {
|
|
114
183
|
const result = await evaluateHookEvent(request.hookEvent, request.cli, request.stdin, {
|
|
115
184
|
awaitTelemetryFlush: false,
|
|
116
|
-
|
|
185
|
+
// Normalised here so no consumer has to know the wire spells
|
|
186
|
+
// "absent" as null.
|
|
187
|
+
fallbackCwd: request.cwd ?? undefined,
|
|
117
188
|
});
|
|
118
189
|
socket.write(
|
|
119
190
|
encodeFrame({
|
|
@@ -139,16 +210,64 @@ function handleConnection(socket: Socket): void {
|
|
|
139
210
|
});
|
|
140
211
|
}
|
|
141
212
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
213
|
+
const STALE_WORKER_SHUTDOWN_TIMEOUT_MS = 2_000;
|
|
214
|
+
|
|
215
|
+
function requestExistingWorkerShutdown(socketPath: string): Promise<"stale" | "stopped"> {
|
|
216
|
+
return new Promise((resolvePromise, reject) => {
|
|
217
|
+
const socket = createConnection({ path: socketPath });
|
|
218
|
+
let recvBuf = Buffer.alloc(0);
|
|
219
|
+
let settled = false;
|
|
220
|
+
const timer = setTimeout(() => finish(new Error("existing worker did not acknowledge shutdown")), STALE_WORKER_SHUTDOWN_TIMEOUT_MS);
|
|
221
|
+
timer.unref?.();
|
|
222
|
+
|
|
223
|
+
const finish = (result: Error | "stale" | "stopped") => {
|
|
224
|
+
if (settled) return;
|
|
225
|
+
settled = true;
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
socket.destroy();
|
|
228
|
+
if (result instanceof Error) reject(result);
|
|
229
|
+
else resolvePromise(result);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
socket.on("connect", () => socket.write(encodeFrame({ type: "shutdown" })));
|
|
233
|
+
socket.on("data", (chunk: Buffer) => {
|
|
234
|
+
recvBuf = Buffer.concat([recvBuf, chunk]);
|
|
235
|
+
if (recvBuf.length < 4) return;
|
|
236
|
+
const len = recvBuf.readUInt32BE(0);
|
|
237
|
+
if (len > MAX_FRAME_LEN || recvBuf.length < 4 + len) return;
|
|
238
|
+
try {
|
|
239
|
+
const response = JSON.parse(recvBuf.subarray(4, 4 + len).toString("utf8")) as { type?: unknown };
|
|
240
|
+
if (response.type === "shutdownAccepted") finish("stopped");
|
|
241
|
+
else finish(new Error("existing socket belongs to a process that does not support shutdown"));
|
|
242
|
+
} catch {
|
|
243
|
+
finish(new Error("existing worker returned a malformed shutdown response"));
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
socket.on("error", (err: NodeJS.ErrnoException) => {
|
|
247
|
+
if (err.code === "ECONNREFUSED" || err.code === "ENOENT") finish("stale");
|
|
248
|
+
else finish(err);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
export function startWorkerServer(socketPath: string, shutdown?: () => void): Server {
|
|
254
|
+
const server = createServer((socket) =>
|
|
255
|
+
handleConnection(socket, shutdown ?? (() => server.close())),
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
const listen = () => {
|
|
259
|
+
if (existsSync(socketPath)) {
|
|
260
|
+
try {
|
|
261
|
+
unlinkSync(socketPath);
|
|
262
|
+
} catch {
|
|
263
|
+
// Racing with the old worker clearing it is harmless; listen surfaces
|
|
264
|
+
// any remaining conflict.
|
|
265
|
+
}
|
|
149
266
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
267
|
+
server.listen(socketPath);
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
if (!existsSync(socketPath)) listen();
|
|
271
|
+
else void requestExistingWorkerShutdown(socketPath).then(listen, (err) => server.emit("error", err));
|
|
153
272
|
return server;
|
|
154
273
|
}
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports=[14747,(e,o,r)=>{o.exports=e.x("path",()=>require("path"))},78500,(e,o,r)=>{o.exports=e.x("node:async_hooks",()=>require("node:async_hooks"))},1457,(e,o,r)=>{o.exports=e.x("node:readline",()=>require("node:readline"))}];
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=%5Bexternals%5D__14odj07._.js.map
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
module.exports=[14747,(e,o,r)=>{o.exports=e.x("path",()=>require("path"))},2157,(e,o,r)=>{o.exports=e.x("node:fs",()=>require("node:fs"))},1457,(e,o,r)=>{o.exports=e.x("node:readline",()=>require("node:readline"))},78500,(e,o,r)=>{o.exports=e.x("node:async_hooks",()=>require("node:async_hooks"))}];
|
|
2
|
-
|
|
3
|
-
//# sourceMappingURL=%5Bexternals%5D__1s61mel._.js.map
|