failproofai 1.0.0-beta.0 → 1.0.0-beta.10
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 +2 -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]__06hexd0._.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]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.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]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.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]__1tyedjt._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -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]__04pwh4t._.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]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
- 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]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
- 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]__1tyf0tc._.js +5 -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/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1yw368c._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1y4qr7u._.js +3 -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 +5 -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/00adubtgov21s.js +1 -0
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
- package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
- package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
- 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/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/policies/hooks-client.tsx +81 -6
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +488 -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 -4
- 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 +17 -10
- package/.next/standalone/proxy.ts +89 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +518 -79
- package/dist/cli.mjs +11484 -6046
- package/dist/worker.mjs +2019 -272
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -4
- 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 +17 -10
- package/scripts/build-daemon-packages.mjs +210 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +17 -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/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 -4
- 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 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +390 -2
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +325 -0
- package/src/hooks/cloud-enrollment.ts +243 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +169 -0
- package/src/hooks/collector-config.ts +234 -0
- package/src/hooks/configure-wizard.ts +915 -158
- package/src/hooks/custom-hooks-loader.ts +191 -33
- package/src/hooks/daemon-client.ts +136 -17
- package/src/hooks/daemon-download.ts +192 -30
- package/src/hooks/daemon-service.ts +1190 -92
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/fp-config.ts +497 -0
- package/src/hooks/fp-home.ts +375 -0
- package/src/hooks/fp-reset.ts +464 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/hook-activity-store.ts +85 -20
- package/src/hooks/hook-logger.ts +2 -1
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/loader-utils.ts +171 -8
- package/src/hooks/manager.ts +5 -4
- package/src/hooks/onboarding-attempt.ts +179 -0
- package/src/hooks/onboarding-lock.ts +119 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +268 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +119 -4
- package/src/hooks/uninstall-cli.ts +345 -0
- package/src/hooks/worker-server.ts +65 -3
- 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]__0259h38._.js +0 -3
- 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]__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]__1zj-87g._.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_1-upb14.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/0u9hc6oxx-gpj.js +0 -6
- 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/3wrgbih-516uj.js +0 -1
- 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/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
|
@@ -11,29 +11,58 @@
|
|
|
11
11
|
* and results in an empty hook list for that file. Builtins continue normally.
|
|
12
12
|
*/
|
|
13
13
|
import { resolve, isAbsolute, basename } from "node:path";
|
|
14
|
+
import { randomUUID } from "crypto";
|
|
14
15
|
import { existsSync, readdirSync } from "node:fs";
|
|
16
|
+
import { readFile } from "node:fs/promises";
|
|
15
17
|
import { pathToFileURL } from "node:url";
|
|
16
18
|
import { homedir } from "node:os";
|
|
19
|
+
import { createHash } from "node:crypto";
|
|
17
20
|
import { hookLogWarn, hookLogError, hookLogInfo } from "./hook-logger";
|
|
18
|
-
import { getCustomHooks, clearCustomHooks } from "./custom-hooks-registry";
|
|
19
|
-
import {
|
|
21
|
+
import { customPolicies, getCustomHooks, clearCustomHooks } from "./custom-hooks-registry";
|
|
22
|
+
import {
|
|
23
|
+
findDistIndex,
|
|
24
|
+
rewriteFileTree,
|
|
25
|
+
TMP_SUFFIX,
|
|
26
|
+
cleanupTmpFiles,
|
|
27
|
+
isTmpArtifact,
|
|
28
|
+
sweepStaleTmpArtifacts,
|
|
29
|
+
} from "./loader-utils";
|
|
20
30
|
import { findProjectConfigDir } from "./hooks-config";
|
|
21
31
|
import { trackHookEvent } from "./hook-telemetry";
|
|
22
32
|
import { getInstanceId } from "../../lib/telemetry-id";
|
|
23
33
|
import type { CustomHook } from "./policy-types";
|
|
34
|
+
import type { CloudManagedPolicyArtifact } from "./cloud-managed-policies";
|
|
35
|
+
import { customPoliciesDir, shimsDir } from "./fp-home";
|
|
24
36
|
|
|
25
37
|
const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
|
|
26
38
|
|
|
27
39
|
/** Regex matching convention policy filenames: *policies.{js,mjs,ts} */
|
|
28
40
|
const CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
|
|
29
41
|
|
|
42
|
+
/** Monotonic suffix for physically distinct temporary module trees. Bun
|
|
43
|
+
* ignores URL query strings when caching imports, so a query-only cache
|
|
44
|
+
* buster silently drops custom policies after the first request in the warm
|
|
45
|
+
* daemon worker. A unique filename is honored by both Bun and Node. */
|
|
46
|
+
let loadSequence = 0;
|
|
47
|
+
|
|
48
|
+
interface CachedPolicyModule {
|
|
49
|
+
fingerprint: string;
|
|
50
|
+
hooks: CustomHook[];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** One retained module instance per source entry. Reusing its registered hook
|
|
54
|
+
* functions prevents an unbounded ESM module-cache leak in the resident worker.
|
|
55
|
+
* A source-tree change replaces this entry and imports one new module. */
|
|
56
|
+
const policyModuleCache = new Map<string, CachedPolicyModule>();
|
|
30
57
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
58
|
+
* Cap, for the reason `gitBranchCache` states and this cache did not carry
|
|
59
|
+
* over: a warm worker touching many projects over its lifetime must not grow
|
|
60
|
+
* this unboundedly. Keyed by absolute policy-file path and holding cloned hook
|
|
61
|
+
* closures, so the entries are not small. Cleared wholesale rather than evicted
|
|
62
|
+
* one at a time — the next load simply re-imports, which is the same cost the
|
|
63
|
+
* cache exists to avoid paying twice, not a correctness change.
|
|
35
64
|
*/
|
|
36
|
-
|
|
65
|
+
const POLICY_MODULE_CACHE_MAX_ENTRIES = 500;
|
|
37
66
|
|
|
38
67
|
/** Script extensions we could load, used to spot near-miss filenames. */
|
|
39
68
|
const LOADABLE_EXT_RE = /\.(js|mjs|ts)$/;
|
|
@@ -76,7 +105,11 @@ export function findSkippedPolicyFiles(dir: string): string[] {
|
|
|
76
105
|
e.isFile() &&
|
|
77
106
|
LOADABLE_EXT_RE.test(e.name) &&
|
|
78
107
|
!CONVENTION_FILE_RE.test(e.name) &&
|
|
79
|
-
!e.name.endsWith(".d.ts")
|
|
108
|
+
!e.name.endsWith(".d.ts") &&
|
|
109
|
+
// Never our own generated files. They are `.mjs` and never match the
|
|
110
|
+
// convention, so each one a killed load left behind was reported to
|
|
111
|
+
// the user as a policy file that would not load.
|
|
112
|
+
!isTmpArtifact(e.name),
|
|
80
113
|
)
|
|
81
114
|
.map((e) => e.name)
|
|
82
115
|
.sort((a, b) => a.localeCompare(b));
|
|
@@ -85,13 +118,69 @@ export function findSkippedPolicyFiles(dir: string): string[] {
|
|
|
85
118
|
}
|
|
86
119
|
}
|
|
87
120
|
|
|
121
|
+
/**
|
|
122
|
+
* How long a policy module gets to finish evaluating its top level.
|
|
123
|
+
*
|
|
124
|
+
* Matches the 10s `handler.ts` gives a policy's `fn` to run, for the same
|
|
125
|
+
* reason: a policy file is user code, and user code that hangs must cost one
|
|
126
|
+
* skipped policy rather than the process.
|
|
127
|
+
*
|
|
128
|
+
* `FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS` shortens it for tests — a suite proving
|
|
129
|
+
* the deadline fires should not have to sit out the real one.
|
|
130
|
+
*/
|
|
131
|
+
function moduleLoadTimeoutMs(): number {
|
|
132
|
+
const override = Number(process.env.FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS);
|
|
133
|
+
return Number.isFinite(override) && override > 0 ? override : 10_000;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* `import()`, but it cannot hang forever.
|
|
138
|
+
*
|
|
139
|
+
* A bare `await import(...)` on a module whose top level contains an `await`
|
|
140
|
+
* that never resolves never settles — it does not reject, so no `catch` and no
|
|
141
|
+
* `finally` anywhere above it ever runs. In the one-shot hook path that costs
|
|
142
|
+
* one hook process, which the agent CLI's own timeout reaps. In the warm daemon
|
|
143
|
+
* worker it holds `worker-server.ts`'s serialization chain forever, and every
|
|
144
|
+
* subsequent hook on the machine queues behind it and fail-closed denies.
|
|
145
|
+
*
|
|
146
|
+
* Losing the race does NOT cancel the import — nothing can. It stops us
|
|
147
|
+
* *waiting* on it, which is the part that matters, and the caller's `catch`
|
|
148
|
+
* below reports the file as failed to load. The abandoned module keeps its
|
|
149
|
+
* half-initialized entry in the ESM cache, but each load writes a uniquely
|
|
150
|
+
* named temporary tree, so a retry imports a different specifier and is never
|
|
151
|
+
* poisoned by it.
|
|
152
|
+
*/
|
|
153
|
+
async function importWithDeadline(fileUrl: string): Promise<void> {
|
|
154
|
+
const budget = moduleLoadTimeoutMs();
|
|
155
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
156
|
+
try {
|
|
157
|
+
await Promise.race([
|
|
158
|
+
import(/* webpackIgnore: true */ fileUrl),
|
|
159
|
+
new Promise<never>((_, reject) => {
|
|
160
|
+
timer = setTimeout(
|
|
161
|
+
() => reject(new Error(`timed out loading module after ${budget}ms`)),
|
|
162
|
+
budget,
|
|
163
|
+
);
|
|
164
|
+
timer.unref?.();
|
|
165
|
+
}),
|
|
166
|
+
]);
|
|
167
|
+
} finally {
|
|
168
|
+
if (timer) clearTimeout(timer);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
88
172
|
/**
|
|
89
173
|
* Load a single policy file into the globalThis custom hooks registry.
|
|
90
174
|
* Does NOT clear the registry — caller is responsible for that.
|
|
91
175
|
*/
|
|
92
176
|
async function loadSingleFile(
|
|
93
177
|
absPath: string,
|
|
94
|
-
opts?: {
|
|
178
|
+
opts?: {
|
|
179
|
+
strict?: boolean;
|
|
180
|
+
conventionScope?: "project" | "user";
|
|
181
|
+
/** Cloud-managed policies pass their pinned digest for load-time re-verification. */
|
|
182
|
+
verifyEntrySha?: string;
|
|
183
|
+
},
|
|
95
184
|
): Promise<void> {
|
|
96
185
|
const g = globalThis as Record<string, unknown>;
|
|
97
186
|
g[LOADING_KEY] = true;
|
|
@@ -101,30 +190,48 @@ async function loadSingleFile(
|
|
|
101
190
|
const distIndex = await findDistIndex();
|
|
102
191
|
const distUrl = distIndex ? pathToFileURL(distIndex).href : null;
|
|
103
192
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const
|
|
120
|
-
|
|
193
|
+
const sequence = ++loadSequence;
|
|
194
|
+
// pid + sequence is not unique enough on its own: two containers sharing a
|
|
195
|
+
// mounted HOME, or two pid namespaces, can collide — and on the shim's
|
|
196
|
+
// fallback path a collision means EEXIST, which fails the load OPEN. A
|
|
197
|
+
// random id removes the class. It costs nothing: `fingerprintTemporaryTree`
|
|
198
|
+
// normalises the whole suffix away, so the module cache still hits.
|
|
199
|
+
const tmpSuffix = `${TMP_SUFFIX}.${process.pid}.${sequence}.${randomUUID()}.mjs`;
|
|
200
|
+
tmpFiles = await rewriteFileTree(
|
|
201
|
+
absPath,
|
|
202
|
+
distUrl,
|
|
203
|
+
distIndex,
|
|
204
|
+
tmpSuffix,
|
|
205
|
+
opts?.verifyEntrySha,
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
const fingerprint = await fingerprintTemporaryTree(tmpFiles, tmpSuffix);
|
|
209
|
+
const cached = policyModuleCache.get(absPath);
|
|
210
|
+
if (cached?.fingerprint === fingerprint) {
|
|
211
|
+
for (const hook of cached.hooks) customPolicies.add({ ...hook });
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const entryTmp = absPath + tmpSuffix;
|
|
216
|
+
const fileUrl = pathToFileURL(entryTmp).href;
|
|
217
|
+
const hooksBefore = getCustomHooks().length;
|
|
218
|
+
await importWithDeadline(fileUrl);
|
|
219
|
+
if (policyModuleCache.size >= POLICY_MODULE_CACHE_MAX_ENTRIES) policyModuleCache.clear();
|
|
220
|
+
policyModuleCache.set(absPath, {
|
|
221
|
+
fingerprint,
|
|
222
|
+
hooks: getCustomHooks()
|
|
223
|
+
.slice(hooksBefore)
|
|
224
|
+
.map((hook) => ({ ...hook })),
|
|
225
|
+
});
|
|
121
226
|
} catch (err) {
|
|
122
227
|
const msg = err instanceof Error ? err.message : String(err);
|
|
123
228
|
const errorType = /Cannot find module|MODULE_NOT_FOUND|ENOENT/i.test(msg)
|
|
124
229
|
? "module_not_found"
|
|
125
230
|
: /SyntaxError|Unexpected token/i.test(msg)
|
|
126
231
|
? "syntax_error"
|
|
127
|
-
:
|
|
232
|
+
: /timed out loading module/i.test(msg)
|
|
233
|
+
? "load_timeout"
|
|
234
|
+
: "runtime_error";
|
|
128
235
|
void trackHookEvent(getInstanceId(), "custom_hooks_load_error", {
|
|
129
236
|
error_type: errorType,
|
|
130
237
|
is_convention: !!opts?.conventionScope,
|
|
@@ -139,6 +246,18 @@ async function loadSingleFile(
|
|
|
139
246
|
}
|
|
140
247
|
}
|
|
141
248
|
|
|
249
|
+
async function fingerprintTemporaryTree(tmpFiles: string[], tmpSuffix: string): Promise<string> {
|
|
250
|
+
const hash = createHash("sha256");
|
|
251
|
+
for (const tmpFile of [...tmpFiles].sort()) {
|
|
252
|
+
// The generated filenames change per load. Normalize both the logical path
|
|
253
|
+
// and rewritten import specifiers so identical source graphs hash equally.
|
|
254
|
+
hash.update(tmpFile.replaceAll(tmpSuffix, TMP_SUFFIX));
|
|
255
|
+
const contents = await readFile(tmpFile, "utf8");
|
|
256
|
+
hash.update(contents.replaceAll(tmpSuffix, TMP_SUFFIX));
|
|
257
|
+
}
|
|
258
|
+
return hash.digest("hex");
|
|
259
|
+
}
|
|
260
|
+
|
|
142
261
|
/**
|
|
143
262
|
* Load a single explicit custom hooks file (legacy API).
|
|
144
263
|
* Clears the registry, loads the file, returns registered hooks.
|
|
@@ -214,7 +333,11 @@ function warnSkippedPolicyFiles(dir: string, scope: "project" | "user"): void {
|
|
|
214
333
|
|
|
215
334
|
export async function loadAllCustomHooks(
|
|
216
335
|
customPoliciesPaths: string | string[] | undefined,
|
|
217
|
-
opts?: {
|
|
336
|
+
opts?: {
|
|
337
|
+
sessionCwd?: string;
|
|
338
|
+
customPoliciesEnabled?: boolean;
|
|
339
|
+
cloudManagedPolicies?: CloudManagedPolicyArtifact[];
|
|
340
|
+
},
|
|
218
341
|
): Promise<LoadAllResult> {
|
|
219
342
|
clearCustomHooks();
|
|
220
343
|
|
|
@@ -235,6 +358,9 @@ export async function loadAllCustomHooks(
|
|
|
235
358
|
// twice per event. Seeded by the explicit path below and consulted by both
|
|
236
359
|
// convention passes.
|
|
237
360
|
const loadedPaths = new Set<string>();
|
|
361
|
+
const cloudManagedByPath = new Map(
|
|
362
|
+
(opts?.cloudManagedPolicies ?? []).map((policy) => [resolve(policy.path), policy]),
|
|
363
|
+
);
|
|
238
364
|
|
|
239
365
|
// 1. Explicit custom policy paths. Accept a string for callers/configs using
|
|
240
366
|
// the legacy singular form.
|
|
@@ -248,9 +374,23 @@ export async function loadAllCustomHooks(
|
|
|
248
374
|
if (!loadedPaths.has(absPath)) {
|
|
249
375
|
loadedPaths.add(absPath);
|
|
250
376
|
const hooksBefore = getCustomHooks().length;
|
|
251
|
-
|
|
377
|
+
// A cloud-managed policy re-verifies its pinned digest at load, binding
|
|
378
|
+
// the imported bytes to what desired-state promised.
|
|
379
|
+
await loadSingleFile(absPath, {
|
|
380
|
+
verifyEntrySha: cloudManagedByPath.get(absPath)?.sha256,
|
|
381
|
+
});
|
|
252
382
|
for (const hook of getCustomHooks().slice(hooksBefore)) {
|
|
253
|
-
|
|
383
|
+
const cloudManaged = cloudManagedByPath.get(absPath);
|
|
384
|
+
const tagged = hook as CustomHook & {
|
|
385
|
+
__policyId?: string;
|
|
386
|
+
__cloudManaged?: CloudManagedPolicyArtifact;
|
|
387
|
+
};
|
|
388
|
+
if (cloudManaged) {
|
|
389
|
+
tagged.__cloudManaged = cloudManaged;
|
|
390
|
+
tagged.__policyId = `cloud:${cloudManaged.id}@${cloudManaged.revision}:${hook.name}`;
|
|
391
|
+
} else {
|
|
392
|
+
tagged.__policyId = customPolicyId(absPath, hook.name);
|
|
393
|
+
}
|
|
254
394
|
}
|
|
255
395
|
}
|
|
256
396
|
} else {
|
|
@@ -268,7 +408,22 @@ export async function loadAllCustomHooks(
|
|
|
268
408
|
// exactly that — and it is discovered here as well. Skip what step 1 loaded,
|
|
269
409
|
// or the file is imported twice and every hook in it fires twice per event.
|
|
270
410
|
const projectDir = resolve(projectRoot, ".failproofai", "policies");
|
|
271
|
-
|
|
411
|
+
// Clear out generated files a killed load left behind, before anything scans
|
|
412
|
+
// this directory. The temporary tree has to be written beside the sources for
|
|
413
|
+
// a rewritten relative import to resolve, and its name now carries a pid and
|
|
414
|
+
// sequence number — so unlike the old fixed name, each abnormal termination
|
|
415
|
+
// leaks one file permanently rather than leaving one that the next load
|
|
416
|
+
// overwrites. Best-effort and age-gated, so it can never remove a tree
|
|
417
|
+
// another process is still importing.
|
|
418
|
+
if (conventionEnabled) {
|
|
419
|
+
void sweepStaleTmpArtifacts(projectDir);
|
|
420
|
+
warnSkippedPolicyFiles(projectDir, "project");
|
|
421
|
+
}
|
|
422
|
+
// The shim directory leaks the same way and is swept on the same terms. It is
|
|
423
|
+
// ours alone and outside the policy tree, so nothing else would ever reap it;
|
|
424
|
+
// the age gate is what keeps this from removing a shim another process is
|
|
425
|
+
// still importing.
|
|
426
|
+
void sweepStaleTmpArtifacts(shimsDir());
|
|
272
427
|
const projectFiles = conventionEnabled
|
|
273
428
|
? discoverPolicyFiles(projectDir).filter((f) => !loadedPaths.has(f))
|
|
274
429
|
: [];
|
|
@@ -303,8 +458,11 @@ export async function loadAllCustomHooks(
|
|
|
303
458
|
// so the second import is a no-op — but Node honours the query and would
|
|
304
459
|
// double-register. The binary runs under Bun and the tests under Node, so the
|
|
305
460
|
// bug is invisible from both sides. Do not rely on that; dedupe the paths.
|
|
306
|
-
const userDir =
|
|
307
|
-
if (conventionEnabled && userDir !== projectDir)
|
|
461
|
+
const userDir = customPoliciesDir();
|
|
462
|
+
if (conventionEnabled && userDir !== projectDir) {
|
|
463
|
+
void sweepStaleTmpArtifacts(userDir);
|
|
464
|
+
warnSkippedPolicyFiles(userDir, "user");
|
|
465
|
+
}
|
|
308
466
|
const userFiles =
|
|
309
467
|
conventionEnabled && userDir !== projectDir
|
|
310
468
|
? discoverPolicyFiles(userDir).filter((f) => !loadedPaths.has(f))
|
|
@@ -17,6 +17,9 @@ import { resolve } from "node:path";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import type { IntegrationType } from "./types";
|
|
19
19
|
import { readHooksConfig } from "./hooks-config";
|
|
20
|
+
import { existsSync } from "node:fs";
|
|
21
|
+
import { daemonSocket as daemonSocketPath } from "./fp-home";
|
|
22
|
+
import { readConfig } from "./fp-config";
|
|
20
23
|
|
|
21
24
|
const PROTOCOL_VERSION = 1;
|
|
22
25
|
|
|
@@ -66,9 +69,82 @@ export interface DaemonHookResponse {
|
|
|
66
69
|
stderr: string;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Why an attempt failed — and it matters, because the two cases deserve
|
|
74
|
+
* opposite answers.
|
|
75
|
+
*
|
|
76
|
+
* `protocol-mismatch` means a daemon answered and is demonstrably HEALTHY; we
|
|
77
|
+
* simply cannot speak its wire format. The cause is known and benign: the CLI
|
|
78
|
+
* upgraded via npm and the daemon has not been reinstalled yet. Falling back to
|
|
79
|
+
* in-process evaluation there loses no enforcement at all — it runs the
|
|
80
|
+
* identical policy engine, just slower — so denying every tool call over it
|
|
81
|
+
* would take a working machine offline to protect nothing.
|
|
82
|
+
*
|
|
83
|
+
* `unreachable` means nothing answered. That could be a stopped service, a
|
|
84
|
+
* deleted socket, or tampering, and we cannot tell which — so it keeps failing
|
|
85
|
+
* closed, which is the whole point of `daemonConfigured`.
|
|
86
|
+
*/
|
|
87
|
+
export type DaemonFailure = "unreachable" | "protocol-mismatch";
|
|
88
|
+
|
|
89
|
+
export type DaemonAttempt =
|
|
90
|
+
| { ok: true; response: DaemonHookResponse }
|
|
91
|
+
| { ok: false; failure: DaemonFailure };
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Whether a daemon is listening, regardless of how it was started.
|
|
95
|
+
*
|
|
96
|
+
* Distinct from `daemonServiceStatus()`, which asks the service manager. A
|
|
97
|
+
* daemon run by hand — during development, or from a container — is invisible
|
|
98
|
+
* to that check but is very much running and pulling policy, so reporting "not
|
|
99
|
+
* installed, so nothing will be pulled" at someone whose machine is actively
|
|
100
|
+
* enforcing is simply false.
|
|
101
|
+
*/
|
|
102
|
+
export function daemonSocketPresent(): boolean {
|
|
103
|
+
try {
|
|
104
|
+
return existsSync(socketPath());
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Whether the daemon socket accepts a connection *right now*.
|
|
112
|
+
*
|
|
113
|
+
* Narrower than `daemonSocketPresent()` (a stale socket file outlives the
|
|
114
|
+
* process that made it) and narrower than `attemptDaemonHook`, which reports
|
|
115
|
+
* `"unreachable"` for BOTH a refused connection and a request that was accepted
|
|
116
|
+
* and then never answered. Those two have different causes and different
|
|
117
|
+
* remedies — a socket that is not up yet versus a worker that cannot run — and
|
|
118
|
+
* telling them apart is the whole point of this function.
|
|
119
|
+
*
|
|
120
|
+
* Connect budget only; nothing is sent. Used by the health probe, never on the
|
|
121
|
+
* hook path.
|
|
122
|
+
*/
|
|
123
|
+
export async function daemonAcceptsConnections(
|
|
124
|
+
timeoutMs = DAEMON_CONNECT_TIMEOUT_MS,
|
|
125
|
+
): Promise<boolean> {
|
|
126
|
+
if (process.platform === "win32") return false;
|
|
127
|
+
return new Promise<boolean>((resolve) => {
|
|
128
|
+
let settled = false;
|
|
129
|
+
const done = (v: boolean) => {
|
|
130
|
+
if (settled) return;
|
|
131
|
+
settled = true;
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
socket.removeAllListeners();
|
|
134
|
+
socket.destroy();
|
|
135
|
+
resolve(v);
|
|
136
|
+
};
|
|
137
|
+
const socket = createConnection({ path: socketPath() });
|
|
138
|
+
const timer = setTimeout(() => done(false), timeoutMs);
|
|
139
|
+
timer.unref?.();
|
|
140
|
+
socket.on("connect", () => done(true));
|
|
141
|
+
socket.on("error", () => done(false));
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
69
145
|
function socketPath(): string {
|
|
70
146
|
if (process.env.FAILPROOFAI_DAEMON_SOCKET) return process.env.FAILPROOFAI_DAEMON_SOCKET;
|
|
71
|
-
return
|
|
147
|
+
return daemonSocketPath();
|
|
72
148
|
}
|
|
73
149
|
|
|
74
150
|
/**
|
|
@@ -81,8 +157,11 @@ function socketPath(): string {
|
|
|
81
157
|
*/
|
|
82
158
|
export function isDaemonConfigured(): boolean {
|
|
83
159
|
try {
|
|
84
|
-
return
|
|
160
|
+
return readConfig().daemon.configured === true;
|
|
85
161
|
} catch {
|
|
162
|
+
// Unreadable config reads as NOT daemon-configured. The failure direction
|
|
163
|
+
// is deliberate: the alternative is a machine that fails closed on every
|
|
164
|
+
// tool call because a config file got truncated.
|
|
86
165
|
return false;
|
|
87
166
|
}
|
|
88
167
|
}
|
|
@@ -102,24 +181,52 @@ function encodeFrame(value: unknown): Buffer {
|
|
|
102
181
|
* falls through to whatever its own fallback policy is.
|
|
103
182
|
*/
|
|
104
183
|
export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookResponse | null> {
|
|
184
|
+
const attempt = await attemptDaemonHook(req);
|
|
185
|
+
return attempt.ok ? attempt.response : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The same attempt, but reporting WHY it failed.
|
|
190
|
+
*
|
|
191
|
+
* `tryDaemonHook` above collapses every failure into `null`, which was fine
|
|
192
|
+
* while the caller had one answer for all of them. It no longer does: a
|
|
193
|
+
* protocol mismatch must fall back rather than deny (see `DaemonFailure`), and
|
|
194
|
+
* a caller cannot make that distinction from `null`.
|
|
195
|
+
*/
|
|
196
|
+
export async function attemptDaemonHook(
|
|
197
|
+
req: DaemonHookRequest,
|
|
198
|
+
opts?: {
|
|
199
|
+
/**
|
|
200
|
+
* Override the RESPONSE budget only — the connect budget is never
|
|
201
|
+
* relaxed. Used by the health probe (`probeDaemonEndToEnd`), which runs
|
|
202
|
+
* from an interactive command rather than a hook and must not make a
|
|
203
|
+
* person wait out the full 30s hook budget to be told their daemon is
|
|
204
|
+
* broken. Never set this on the hook path: 30s is matched to the
|
|
205
|
+
* daemon's own read timeout so this side never gives up on a request the
|
|
206
|
+
* daemon is still honestly working on.
|
|
207
|
+
*/
|
|
208
|
+
responseTimeoutMs?: number;
|
|
209
|
+
},
|
|
210
|
+
): Promise<DaemonAttempt> {
|
|
105
211
|
// Windows never has a daemon in this phase (see the plan's platform
|
|
106
212
|
// scope) — skip the attempt outright rather than depending on however
|
|
107
213
|
// Node happens to behave when handed a POSIX socket path on Windows.
|
|
108
|
-
if (process.platform === "win32") return
|
|
214
|
+
if (process.platform === "win32") return { ok: false, failure: "unreachable" };
|
|
215
|
+
const responseBudget = opts?.responseTimeoutMs ?? DAEMON_RESPONSE_TIMEOUT_MS;
|
|
109
216
|
|
|
110
|
-
return new Promise<
|
|
217
|
+
return new Promise<DaemonAttempt>((resolvePromise) => {
|
|
111
218
|
let settled = false;
|
|
112
219
|
let timer: ReturnType<typeof setTimeout>;
|
|
113
220
|
const arm = (ms: number) => {
|
|
114
221
|
clearTimeout(timer);
|
|
115
|
-
timer = setTimeout(() =>
|
|
222
|
+
timer = setTimeout(() => fail("unreachable"), ms);
|
|
116
223
|
// Don't let this timer alone keep the process alive if everything else
|
|
117
224
|
// has already finished — it's always cleared on the success/failure
|
|
118
225
|
// paths below, this just avoids it being the sole reason a --hook
|
|
119
226
|
// process lingers if something upstream forgets to await us.
|
|
120
227
|
timer.unref?.();
|
|
121
228
|
};
|
|
122
|
-
const finish = (result:
|
|
229
|
+
const finish = (result: DaemonAttempt) => {
|
|
123
230
|
if (settled) return;
|
|
124
231
|
settled = true;
|
|
125
232
|
clearTimeout(timer);
|
|
@@ -127,6 +234,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
|
|
|
127
234
|
socket.destroy();
|
|
128
235
|
resolvePromise(result);
|
|
129
236
|
};
|
|
237
|
+
const fail = (failure: DaemonFailure) => finish({ ok: false, failure });
|
|
130
238
|
|
|
131
239
|
const socket = createConnection({ path: socketPath() });
|
|
132
240
|
arm(DAEMON_CONNECT_TIMEOUT_MS);
|
|
@@ -137,7 +245,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
|
|
|
137
245
|
socket.on("connect", () => {
|
|
138
246
|
// Connected: the daemon is demonstrably reachable, so the question is
|
|
139
247
|
// no longer "is it there" but "how long does this evaluation take".
|
|
140
|
-
arm(
|
|
248
|
+
arm(responseBudget);
|
|
141
249
|
socket.write(
|
|
142
250
|
encodeFrame({
|
|
143
251
|
type: "hook",
|
|
@@ -157,7 +265,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
|
|
|
157
265
|
if (recvBuf.length < 4) return;
|
|
158
266
|
declaredLen = recvBuf.readUInt32BE(0);
|
|
159
267
|
if (declaredLen > MAX_FRAME_LEN) {
|
|
160
|
-
|
|
268
|
+
fail("unreachable");
|
|
161
269
|
return;
|
|
162
270
|
}
|
|
163
271
|
recvBuf = recvBuf.subarray(4);
|
|
@@ -170,12 +278,16 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
|
|
|
170
278
|
try {
|
|
171
279
|
message = JSON.parse(body.toString("utf8")) as Record<string, unknown>;
|
|
172
280
|
} catch {
|
|
173
|
-
|
|
281
|
+
fail("unreachable");
|
|
174
282
|
return;
|
|
175
283
|
}
|
|
176
284
|
|
|
177
285
|
if (message.protocolVersion !== PROTOCOL_VERSION) {
|
|
178
|
-
|
|
286
|
+
// Catches BOTH directions. A newer CLI sends v2 and the daemon replies
|
|
287
|
+
// with an error stamped v1; an older CLI sends v1 and a newer daemon
|
|
288
|
+
// replies stamped v2. Either way the versions disagree here, and either
|
|
289
|
+
// way a daemon answered — so this is skew, not absence.
|
|
290
|
+
fail("protocol-mismatch");
|
|
179
291
|
return;
|
|
180
292
|
}
|
|
181
293
|
if (
|
|
@@ -184,16 +296,23 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
|
|
|
184
296
|
typeof message.stdout === "string" &&
|
|
185
297
|
typeof message.stderr === "string"
|
|
186
298
|
) {
|
|
187
|
-
finish({
|
|
299
|
+
finish({
|
|
300
|
+
ok: true,
|
|
301
|
+
response: {
|
|
302
|
+
exitCode: message.exitCode,
|
|
303
|
+
stdout: message.stdout,
|
|
304
|
+
stderr: message.stderr,
|
|
305
|
+
},
|
|
306
|
+
});
|
|
188
307
|
return;
|
|
189
308
|
}
|
|
190
|
-
// Anything else — an explicit `error` message
|
|
191
|
-
// confusion), or a well-formed-but-wrong-shape
|
|
192
|
-
// identically to a connection failure: no partial trust.
|
|
193
|
-
|
|
309
|
+
// Anything else — an explicit `error` message at a MATCHING protocol
|
|
310
|
+
// version, a `pong` (protocol confusion), or a well-formed-but-wrong-shape
|
|
311
|
+
// body — is treated identically to a connection failure: no partial trust.
|
|
312
|
+
fail("unreachable");
|
|
194
313
|
});
|
|
195
314
|
|
|
196
|
-
socket.on("error", () =>
|
|
197
|
-
socket.on("close", () =>
|
|
315
|
+
socket.on("error", () => fail("unreachable"));
|
|
316
|
+
socket.on("close", () => fail("unreachable"));
|
|
198
317
|
});
|
|
199
318
|
}
|