failproofai 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +3 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +3 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +5 -3
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +3 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +5 -3
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +4 -2
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
- package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
- package/.next/standalone/.next/server/app/settings/page.js +18 -0
- package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
- package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
- package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/middleware.js +2 -2
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
- package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
- package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
- package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/SECURITY.md +8 -2
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
- package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
- package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
- package/.next/standalone/app/api/audit/run/route.ts +28 -0
- package/.next/standalone/app/api/audit/status/route.ts +13 -1
- package/.next/standalone/app/api/auth/status/route.ts +1 -1
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
- package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
- package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
- package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
- package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
- package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
- package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
- package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
- package/.next/standalone/app/audit/audit-styles.css +6 -149
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
- package/.next/standalone/app/components/toast.tsx +1 -1
- package/.next/standalone/app/policies/hooks-client.tsx +84 -8
- package/.next/standalone/app/settings/page.tsx +31 -0
- package/.next/standalone/app/settings/settings-client.tsx +487 -0
- package/.next/standalone/components/navbar.tsx +2 -0
- package/.next/standalone/lib/auth/api-server-client.ts +2 -3
- package/.next/standalone/lib/auth/auth-store.ts +4 -5
- package/.next/standalone/lib/codex-sessions.ts +2 -1
- package/.next/standalone/lib/dashboard-host.ts +101 -0
- package/.next/standalone/lib/install-check.ts +22 -7
- package/.next/standalone/lib/pi-sessions.ts +99 -14
- package/.next/standalone/lib/telemetry-enabled.ts +54 -0
- package/.next/standalone/lib/telemetry-id.ts +52 -1
- package/.next/standalone/lib/telemetry.ts +8 -4
- package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
- package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
- package/.next/standalone/node_modules/react/package.json +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
- package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
- package/.next/standalone/node_modules/react-dom/package.json +4 -4
- package/.next/standalone/package.json +23 -12
- package/.next/standalone/proxy.ts +111 -1
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai-worker.mjs +1 -1
- package/bin/failproofai.mjs +1041 -81
- package/bin/failproofaid-shim.mjs +4 -4
- package/dist/cli.mjs +18513 -10474
- package/dist/worker.mjs +1223 -299
- package/lib/auth/api-server-client.ts +2 -3
- package/lib/auth/auth-store.ts +4 -5
- package/lib/codex-sessions.ts +2 -1
- package/lib/dashboard-host.ts +101 -0
- package/lib/install-check.ts +22 -7
- package/lib/pi-sessions.ts +99 -14
- package/lib/telemetry-enabled.ts +54 -0
- package/lib/telemetry-id.ts +52 -1
- package/lib/telemetry.ts +8 -4
- package/package.json +23 -12
- package/scripts/build-daemon-packages.mjs +247 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/dev-hook.mjs +46 -5
- package/scripts/install-telemetry.mjs +8 -1
- package/scripts/launch.ts +25 -3
- package/scripts/parse-script-args.ts +27 -1
- package/scripts/prune-standalone.mjs +26 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/scripts/repro-npm-install.sh +155 -0
- package/scripts/translate-docs/cache.ts +1 -1
- package/scripts/translate-docs/cli.ts +2 -2
- package/scripts/translate-docs/mintlify-nav.ts +1 -1
- package/scripts/translate-docs/readme-translator.ts +163 -4
- package/scripts/validate-mdx.ts +153 -13
- package/src/audit/audit-lock.ts +285 -0
- package/src/audit/audit-schedule.ts +111 -0
- package/src/audit/cache.ts +18 -5
- package/src/audit/cli-adapters/goose.ts +2 -1
- package/src/audit/cli-adapters/hermes.ts +28 -10
- package/src/audit/cli.ts +191 -35
- package/src/audit/dashboard-cache.ts +2 -3
- package/src/audit/report.ts +0 -1
- package/src/hooks/backfill-cli.ts +247 -0
- package/src/hooks/builtin-policies.ts +391 -3
- package/src/hooks/cloud-connection.ts +354 -0
- package/src/hooks/cloud-enrollment-cli.ts +439 -0
- package/src/hooks/cloud-enrollment.ts +284 -0
- package/src/hooks/cloud-introspect.ts +147 -0
- package/src/hooks/cloud-managed-policies.ts +206 -0
- package/src/hooks/collector-config.ts +218 -0
- package/src/hooks/configure-wizard.ts +1211 -233
- package/src/hooks/custom-hooks-loader.ts +243 -37
- package/src/hooks/daemon-client.ts +136 -33
- package/src/hooks/daemon-download.ts +264 -31
- package/src/hooks/daemon-service.ts +1124 -76
- package/src/hooks/first-run-gate.ts +50 -0
- package/src/hooks/flush-cli.ts +180 -0
- package/src/hooks/fp-config.ts +835 -0
- package/src/hooks/fp-home.ts +714 -0
- package/src/hooks/fp-reset.ts +1363 -0
- package/src/hooks/handler.ts +185 -6
- package/src/hooks/harness-cli.ts +386 -0
- package/src/hooks/hook-activity-store.ts +171 -24
- package/src/hooks/hook-logger.ts +2 -2
- package/src/hooks/hook-telemetry.ts +5 -1
- package/src/hooks/hooks-config.ts +4 -3
- package/src/hooks/integrations.ts +0 -1
- package/src/hooks/loader-utils.ts +172 -9
- package/src/hooks/manager.ts +41 -5
- package/src/hooks/migrations.ts +450 -0
- package/src/hooks/onboarding-attempt.ts +195 -0
- package/src/hooks/onboarding-lock.ts +118 -0
- package/src/hooks/policy-types.ts +28 -0
- package/src/hooks/session-pause-cli.ts +159 -0
- package/src/hooks/session-pause.ts +267 -0
- package/src/hooks/setup-state.ts +256 -0
- package/src/hooks/tui.ts +209 -9
- package/src/hooks/uninstall-cli.ts +418 -0
- package/src/hooks/worker-server.ts +134 -15
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
- package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- package/.next/standalone/Cargo.lock +0 -428
- package/.next/standalone/Cargo.toml +0 -9
- package/.next/standalone/crates/PROTOCOL.md +0 -122
- package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
- package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
- package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
- package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
- package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
- package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
- package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
- package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
- package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
- package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
- package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
- package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
- package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
- package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
- package/.next/standalone/integration-suite/Dockerfile +0 -47
- package/.next/standalone/integration-suite/README.md +0 -129
- package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
- package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
- package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
- package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
- package/.next/standalone/integration-suite/install-clis.sh +0 -136
- package/.next/standalone/integration-suite/probe-cli.sh +0 -248
- package/.next/standalone/integration-suite/report.js +0 -120
- package/.next/standalone/integration-suite/run.sh +0 -159
- package/.next/standalone/openclaw-plugin/index.js +0 -223
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
- package/.next/standalone/openclaw-plugin/package.json +0 -12
- package/.next/standalone/osv-scanner.toml +0 -17
- package/.next/standalone/pi-extension/index.ts +0 -524
- package/.next/standalone/pi-extension/package.json +0 -12
- package/.next/standalone/postcss.config.mjs +0 -8
- package/.next/standalone/readme-arch-hq.gif +0 -0
- package/.next/standalone/rust-toolchain.toml +0 -4
- package/src/auth/cli.ts +0 -371
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
package/scripts/validate-mdx.ts
CHANGED
|
@@ -25,15 +25,22 @@
|
|
|
25
25
|
* `findPageError`), so this net is a strict superset of `mintlify validate`:
|
|
26
26
|
* it catches both the frontmatter YAML class that fails `mintlify validate`
|
|
27
27
|
* and the body-MDX class that `mintlify validate` lets through to deploy.
|
|
28
|
+
*
|
|
29
|
+
* It also covers a third class neither tool sees: image references whose target
|
|
30
|
+
* does not exist (`findBrokenAssetRefs`). Those are valid MDX and valid YAML —
|
|
31
|
+
* nothing errors, the reader just gets a broken image — which is how all 14
|
|
32
|
+
* translated READMEs shipped with every logo and the architecture GIF broken.
|
|
33
|
+
* See that function for the mechanism.
|
|
28
34
|
*/
|
|
29
|
-
import { readdirSync, statSync, readFileSync } from "node:fs";
|
|
30
|
-
import { dirname, join, relative } from "node:path";
|
|
35
|
+
import { readdirSync, statSync, readFileSync, existsSync } from "node:fs";
|
|
36
|
+
import { dirname, join, relative, resolve, posix } from "node:path";
|
|
31
37
|
import { fileURLToPath } from "node:url";
|
|
32
38
|
import { compile } from "@mdx-js/mdx";
|
|
33
39
|
import YAML from "yaml";
|
|
34
40
|
|
|
35
41
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
36
|
-
const
|
|
42
|
+
const ROOT_DIR = join(__dirname, "..");
|
|
43
|
+
const DOCS_DIR = join(ROOT_DIR, "docs");
|
|
37
44
|
|
|
38
45
|
export interface MdxParseError {
|
|
39
46
|
message: string;
|
|
@@ -178,34 +185,167 @@ export function collectMdxFiles(dir: string): string[] {
|
|
|
178
185
|
return out;
|
|
179
186
|
}
|
|
180
187
|
|
|
188
|
+
/** File extensions treated as bundled media rather than a link to a page. */
|
|
189
|
+
const ASSET_RE = /\.(?:png|jpe?g|gif|svg|webp|ico|mp4|webm)$/i;
|
|
190
|
+
|
|
191
|
+
export interface BrokenAssetRef {
|
|
192
|
+
/** The reference exactly as written in the page. */
|
|
193
|
+
ref: string;
|
|
194
|
+
/** Repo-relative path the reference resolves to, which does not exist. */
|
|
195
|
+
resolved: string;
|
|
196
|
+
/** 1-based line the reference appears on. */
|
|
197
|
+
line: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Report every local image reference on a page whose target does not exist on
|
|
202
|
+
* disk.
|
|
203
|
+
*
|
|
204
|
+
* Why this is a separate net from the MDX parse above: a broken image path is
|
|
205
|
+
* perfectly valid MDX and perfectly valid YAML, so `mintlify validate` and
|
|
206
|
+
* `findPageError` both pass it — it only surfaces as a missing image in a
|
|
207
|
+
* reader's browser, which nothing in CI was watching. That is exactly how all
|
|
208
|
+
* 14 translated READMEs shipped with every logo and the architecture GIF
|
|
209
|
+
* broken: the translator faithfully copies the root README's repo-root-relative
|
|
210
|
+
* `assets/logos/*.svg` into `docs/i18n/`, two directories deeper, where they
|
|
211
|
+
* resolve to nothing (GitHub 404, Mintlify S3 403). The auto-translation
|
|
212
|
+
* workflow regenerates these pages unattended, so only a deterministic check
|
|
213
|
+
* keeps that class from coming back.
|
|
214
|
+
*
|
|
215
|
+
* Resolution follows the two conventions in this repo:
|
|
216
|
+
* - A leading `/` is Mintlify site-absolute → resolve against `docs/`
|
|
217
|
+
* (`/agenteye/images/x.png` → `docs/agenteye/images/x.png`).
|
|
218
|
+
* - Anything else is relative to the page's own directory, the way GitHub and
|
|
219
|
+
* Mintlify both resolve it.
|
|
220
|
+
*
|
|
221
|
+
* Skipped: absolute URLs and any other scheme (`https:`, `mailto:`, `data:`),
|
|
222
|
+
* protocol-relative `//`, bare anchors, and non-asset references — a link to a
|
|
223
|
+
* `.md`/`.mdx` page or a bare doc slug is nav, already covered by
|
|
224
|
+
* `mintlify validate`, and would false-positive on extensionless Mintlify
|
|
225
|
+
* routes.
|
|
226
|
+
*/
|
|
227
|
+
export function findBrokenAssetRefs(
|
|
228
|
+
file: string,
|
|
229
|
+
source: string,
|
|
230
|
+
): BrokenAssetRef[] {
|
|
231
|
+
const pageDir = dirname(file);
|
|
232
|
+
const out: BrokenAssetRef[] = [];
|
|
233
|
+
const seen = new Set<string>();
|
|
234
|
+
|
|
235
|
+
const lines = source.split("\n");
|
|
236
|
+
for (let i = 0; i < lines.length; i++) {
|
|
237
|
+
const refs = [
|
|
238
|
+
...lines[i].matchAll(/\]\(([^)\s]+)/g),
|
|
239
|
+
...lines[i].matchAll(/(?:src|href)=["']([^"']+)["']/g),
|
|
240
|
+
].map((m) => m[1]);
|
|
241
|
+
|
|
242
|
+
// `srcset` carries a comma-separated candidate list, each optionally
|
|
243
|
+
// followed by a density/width descriptor (`logo.svg 2x`). The README's
|
|
244
|
+
// logo table pairs every <img src> with a dark-mode <source srcset>, so
|
|
245
|
+
// extracting only `src` would pass a table half-broken — and only for
|
|
246
|
+
// dark-theme readers, the half least likely to be caught by eye.
|
|
247
|
+
for (const m of lines[i].matchAll(/srcset=["']([^"']+)["']/g)) {
|
|
248
|
+
for (const candidate of m[1].split(",")) {
|
|
249
|
+
const url = candidate.trim().split(/\s+/)[0];
|
|
250
|
+
if (url) refs.push(url);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
for (const ref of refs) {
|
|
255
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(ref)) continue; // https:, mailto:, data:
|
|
256
|
+
if (ref.startsWith("//") || ref.startsWith("#")) continue;
|
|
257
|
+
// Strip the query/fragment before testing the extension so a versioned
|
|
258
|
+
// `x.png?v=2` is still recognised as an asset.
|
|
259
|
+
const path = ref.split(/[?#]/)[0];
|
|
260
|
+
if (!ASSET_RE.test(path)) continue;
|
|
261
|
+
|
|
262
|
+
const target = path.startsWith("/")
|
|
263
|
+
? join(DOCS_DIR, path.slice(1))
|
|
264
|
+
: resolve(pageDir, path);
|
|
265
|
+
if (existsSync(target)) continue;
|
|
266
|
+
|
|
267
|
+
const key = `${i}:${ref}`;
|
|
268
|
+
if (seen.has(key)) continue;
|
|
269
|
+
seen.add(key);
|
|
270
|
+
out.push({
|
|
271
|
+
ref,
|
|
272
|
+
resolved: relative(ROOT_DIR, target).split(/[\\/]/).join(posix.sep),
|
|
273
|
+
line: i + 1,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
|
|
181
280
|
async function main(): Promise<void> {
|
|
182
281
|
const files = collectMdxFiles(DOCS_DIR).sort();
|
|
183
|
-
const failures: Array<{
|
|
282
|
+
const failures: Array<{
|
|
283
|
+
file: string;
|
|
284
|
+
error: MdxParseError;
|
|
285
|
+
kind: "MDX parse error" | "Broken image";
|
|
286
|
+
}> = [];
|
|
287
|
+
|
|
288
|
+
const collectAssetFailures = (file: string, source: string): void => {
|
|
289
|
+
for (const broken of findBrokenAssetRefs(file, source)) {
|
|
290
|
+
failures.push({
|
|
291
|
+
file: relative(process.cwd(), file),
|
|
292
|
+
kind: "Broken image",
|
|
293
|
+
error: {
|
|
294
|
+
message:
|
|
295
|
+
`Image \`${broken.ref}\` does not exist — it resolves to ` +
|
|
296
|
+
`\`${broken.resolved}\`. Paths are relative to the page's own ` +
|
|
297
|
+
"directory (a leading `/` is relative to `docs/`), so a path " +
|
|
298
|
+
"copied from a file at a different depth has to be re-pointed.",
|
|
299
|
+
line: broken.line,
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
184
304
|
|
|
185
305
|
for (const file of files) {
|
|
186
|
-
const
|
|
187
|
-
|
|
306
|
+
const source = readFileSync(file, "utf-8");
|
|
307
|
+
const error = await findPageError(source);
|
|
308
|
+
if (error)
|
|
309
|
+
failures.push({
|
|
310
|
+
file: relative(process.cwd(), file),
|
|
311
|
+
error,
|
|
312
|
+
kind: "MDX parse error",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// A page can carry a broken image and still parse, so collect these
|
|
316
|
+
// independently rather than only when the parse succeeded.
|
|
317
|
+
collectAssetFailures(file, source);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// The root README is not a docs page (it is GitHub-only, and its HTML
|
|
321
|
+
// comments are illegal MDX, so findPageError would reject it) — but it IS
|
|
322
|
+
// the source every docs/i18n/README.<lang>.md is translated from. A bad
|
|
323
|
+
// image path here propagates into 14 files as an absolute raw URL, which
|
|
324
|
+
// findBrokenAssetRefs deliberately does not follow. Check it at the source.
|
|
325
|
+
const rootReadme = join(ROOT_DIR, "README.md");
|
|
326
|
+
if (existsSync(rootReadme)) {
|
|
327
|
+
collectAssetFailures(rootReadme, readFileSync(rootReadme, "utf-8"));
|
|
188
328
|
}
|
|
189
329
|
|
|
190
330
|
if (failures.length === 0) {
|
|
191
|
-
console.log(
|
|
331
|
+
console.log(
|
|
332
|
+
`✓ ${files.length} MDX page(s) parsed cleanly with no broken images`,
|
|
333
|
+
);
|
|
192
334
|
return;
|
|
193
335
|
}
|
|
194
336
|
|
|
195
|
-
console.error(
|
|
196
|
-
|
|
197
|
-
);
|
|
198
|
-
for (const { file, error } of failures) {
|
|
337
|
+
console.error(`✗ ${failures.length} problem(s) in ${files.length} page(s):\n`);
|
|
338
|
+
for (const { file, error, kind } of failures) {
|
|
199
339
|
const pos = error.line
|
|
200
340
|
? `:${error.line}${error.column ? `:${error.column}` : ""}`
|
|
201
341
|
: "";
|
|
202
|
-
console.error(` ${file}${pos}\n ${error.message}\n`);
|
|
342
|
+
console.error(` ${file}${pos}\n ${kind}: ${error.message}\n`);
|
|
203
343
|
// GitHub Actions inline annotation.
|
|
204
344
|
const loc =
|
|
205
345
|
(error.line ? `,line=${error.line}` : "") +
|
|
206
346
|
(error.column ? `,col=${error.column}` : "");
|
|
207
347
|
console.log(
|
|
208
|
-
`::error file=${encodeAnnotation(file)}${loc}
|
|
348
|
+
`::error file=${encodeAnnotation(file)}${loc}::${kind}: ${encodeAnnotation(error.message)}`,
|
|
209
349
|
);
|
|
210
350
|
}
|
|
211
351
|
process.exitCode = 1;
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One audit at a time on this machine, across processes.
|
|
3
|
+
*
|
|
4
|
+
* Three entry points can start a scan — a scheduled run, an explicit
|
|
5
|
+
* `failproofai audit`, and the dashboard's `/api/audit/run` — and every one of
|
|
6
|
+
* them writes the same sha1-keyed per-transcript cache files and the same
|
|
7
|
+
* single-slot `audit/dashboard.json`. The only lock that existed before this
|
|
8
|
+
* was `app/api/audit/_state.ts`, a module-level singleton inside the Next.js
|
|
9
|
+
* server: invisible to the other two, and by its own admission invisible to a
|
|
10
|
+
* second Next.js worker as well.
|
|
11
|
+
*
|
|
12
|
+
* ## Why a lockfile and not a flock
|
|
13
|
+
*
|
|
14
|
+
* `flock()` releases itself when the holder dies, which is exactly the property
|
|
15
|
+
* we would want — but it needs a held file descriptor, and the interactive path
|
|
16
|
+
* hands off to `launch()` and lives on for as long as the user leaves the
|
|
17
|
+
* dashboard open. A lockfile whose contents name the holder can be released at
|
|
18
|
+
* the end of the SCAN, which is the thing being serialised, and it can be
|
|
19
|
+
* inspected by a human wondering why their audit refused to start. The cost is
|
|
20
|
+
* that a crash leaves the file behind, which the stale rules below cover.
|
|
21
|
+
*
|
|
22
|
+
* ## The two stale rules
|
|
23
|
+
*
|
|
24
|
+
* **A dead pid** is the primary one and the one that fires in practice:
|
|
25
|
+
* Ctrl+C, SIGKILL and a laptop lid are all "the lock is on disk, the process is
|
|
26
|
+
* not". **Age** is the backstop for the cases a pid check cannot see — a
|
|
27
|
+
* recycled pid, or a lock written by a machine that shares this home over a
|
|
28
|
+
* network filesystem.
|
|
29
|
+
*/
|
|
30
|
+
import {
|
|
31
|
+
closeSync,
|
|
32
|
+
linkSync,
|
|
33
|
+
mkdirSync,
|
|
34
|
+
openSync,
|
|
35
|
+
readFileSync,
|
|
36
|
+
unlinkSync,
|
|
37
|
+
writeFileSync,
|
|
38
|
+
writeSync,
|
|
39
|
+
} from "node:fs";
|
|
40
|
+
import { auditLockFile, failproofaiHome, runDir } from "../hooks/fp-home";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* How old a lock has to be before its holder is presumed gone even though the
|
|
44
|
+
* pid still resolves.
|
|
45
|
+
*
|
|
46
|
+
* A full scan measured ~104 seconds over 3,277 transcripts on a development
|
|
47
|
+
* machine with a warm cache. The ceiling has to clear the worst plausible
|
|
48
|
+
* HONEST run by a wide margin, because stealing from a live audit is the one
|
|
49
|
+
* outcome this module exists to prevent — it puts two writers on the same cache
|
|
50
|
+
* files. A cold cache, a much longer history, a spinning disk, or the `nice(19)`
|
|
51
|
+
* scheduled run competing with a busy laptop can each cost an order of
|
|
52
|
+
* magnitude, so: one hour, roughly 35× the measured run. Nothing is lost by
|
|
53
|
+
* being generous here, since the dead-pid rule already collects every crash.
|
|
54
|
+
*/
|
|
55
|
+
export const AUDIT_LOCK_MAX_AGE_MS = 60 * 60_000;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Which entry point holds the lock. Recorded so a refusal can say who it lost
|
|
59
|
+
* to. All four are wired: `cli` and `scheduled` in `src/audit/cli.ts`,
|
|
60
|
+
* `dashboard` in `app/api/audit/run/route.ts`, and `onboarding` in
|
|
61
|
+
* `runPostSetupAudit` — every process that writes the shared cache takes this
|
|
62
|
+
* lock, so no two of them can co-write it.
|
|
63
|
+
*/
|
|
64
|
+
export type AuditLockSource = "cli" | "scheduled" | "dashboard" | "onboarding";
|
|
65
|
+
|
|
66
|
+
export interface AuditLockInfo {
|
|
67
|
+
pid: number;
|
|
68
|
+
/** Wall-clock ms. Monotonic time would reset every process start. */
|
|
69
|
+
startedAt: number;
|
|
70
|
+
source: AuditLockSource;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface AuditLockHandle {
|
|
74
|
+
info: AuditLockInfo;
|
|
75
|
+
/** Idempotent, never throws, and only ever removes a lock this handle owns. */
|
|
76
|
+
release(): void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export type AcquireAuditLockResult =
|
|
80
|
+
| { ok: true; lock: AuditLockHandle }
|
|
81
|
+
/** `heldBy` is null when the holder's lock could not be read at all. */
|
|
82
|
+
| { ok: false; heldBy: AuditLockInfo | null };
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Take the lock, stealing a provably-abandoned one.
|
|
86
|
+
*
|
|
87
|
+
* Never throws: a caller deciding whether to run an audit must not have to
|
|
88
|
+
* handle an exception from the thing that decides.
|
|
89
|
+
*/
|
|
90
|
+
export function acquireAuditLock(source: AuditLockSource, now: number = Date.now()): AcquireAuditLockResult {
|
|
91
|
+
const path = auditLockFile();
|
|
92
|
+
const info: AuditLockInfo = { pid: process.pid, startedAt: now, source };
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
ensureRunDir();
|
|
96
|
+
} catch {
|
|
97
|
+
// We could not even create the directory the lock lives in, so we cannot
|
|
98
|
+
// prove exclusivity. Report contention rather than running: a refused audit
|
|
99
|
+
// is a retry, whereas two unserialised ones corrupt a shared cache.
|
|
100
|
+
return { ok: false, heldBy: null };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
|
|
104
|
+
|
|
105
|
+
const held = readLock(path);
|
|
106
|
+
if (!isStale(held, now)) return { ok: false, heldBy: held };
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
unlinkSync(path);
|
|
110
|
+
} catch {
|
|
111
|
+
// Already gone — another process judged it stale at the same moment. The
|
|
112
|
+
// O_EXCL retry below is what decides between us.
|
|
113
|
+
}
|
|
114
|
+
// Exactly one retry, deliberately. Two processes that both called the lock
|
|
115
|
+
// stale race here and O_EXCL picks one; looping would let the loser keep
|
|
116
|
+
// going until it stole the WINNER's brand-new lock.
|
|
117
|
+
if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
|
|
118
|
+
return { ok: false, heldBy: readLock(path) };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Whoever holds the lock right now, or null if it is free/unreadable. */
|
|
122
|
+
export function readAuditLock(): AuditLockInfo | null {
|
|
123
|
+
return readLock(auditLockFile());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* The holder only when its lock is still ACTIVE — holder alive and not aged
|
|
128
|
+
* out; null when the lock is free, unreadable, or provably abandoned.
|
|
129
|
+
*
|
|
130
|
+
* For status/UI, not for acquiring: a settings page or `/api/audit/status`
|
|
131
|
+
* asking "is a scan running somewhere on this machine right now?" must answer
|
|
132
|
+
* off the same staleness rules `acquireAuditLock` uses, or it would report
|
|
133
|
+
* "running" forever off a crashed run's leftover lockfile — a dead pid or an
|
|
134
|
+
* hour-old file the next real acquire would rightly steal. Read-only: unlike
|
|
135
|
+
* `acquireAuditLock` it never unlinks or steals, so it is safe to call from a
|
|
136
|
+
* request handler that only wants to describe the machine's state.
|
|
137
|
+
*/
|
|
138
|
+
export function readActiveAuditLock(now: number = Date.now()): AuditLockInfo | null {
|
|
139
|
+
const held = readLock(auditLockFile());
|
|
140
|
+
return isStale(held, now) ? null : held;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isStale(held: AuditLockInfo | null, now: number): boolean {
|
|
144
|
+
// EEXIST with nothing parseable inside it: either a truncated write or a file
|
|
145
|
+
// from a version that wrote a different shape. Neither names a holder we
|
|
146
|
+
// could ever wait for.
|
|
147
|
+
if (!held) return true;
|
|
148
|
+
if (!isProcessAlive(held.pid)) return true;
|
|
149
|
+
// A startedAt in the future (clock skew, a shared home across machines) reads
|
|
150
|
+
// as fresh, not as stale — the subtraction goes negative and the comparison
|
|
151
|
+
// fails, which is the direction that does not steal from a live run.
|
|
152
|
+
return now - held.startedAt > AUDIT_LOCK_MAX_AGE_MS;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isProcessAlive(pid: number): boolean {
|
|
156
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
157
|
+
try {
|
|
158
|
+
process.kill(pid, 0);
|
|
159
|
+
return true;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
// EPERM means the process exists and belongs to somebody else — two OS
|
|
162
|
+
// users on one box, or a shared home. Alive is the safe reading of that.
|
|
163
|
+
return (err as NodeJS.ErrnoException).code === "EPERM";
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleFor(path: string, info: AuditLockInfo): AuditLockHandle {
|
|
168
|
+
let released = false;
|
|
169
|
+
const release = (): void => {
|
|
170
|
+
if (released) return;
|
|
171
|
+
released = true;
|
|
172
|
+
process.removeListener("exit", release);
|
|
173
|
+
// Only ever remove OUR lock. After a steal the file on disk can already
|
|
174
|
+
// belong to a third process, and deleting that one would hand a live audit
|
|
175
|
+
// the co-writer this whole module exists to prevent.
|
|
176
|
+
const current = readLock(path);
|
|
177
|
+
if (current && current.pid !== info.pid) return;
|
|
178
|
+
try {
|
|
179
|
+
unlinkSync(path);
|
|
180
|
+
} catch {
|
|
181
|
+
/* already gone */
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
// `process.exit()` does not unwind, so a `finally` cannot be relied on here:
|
|
186
|
+
// `failproofai audit` exits directly on both its failure and its
|
|
187
|
+
// empty-history path. An exit hook covers those, a normal return, and an
|
|
188
|
+
// uncaught throw. It does NOT run when the default SIGINT disposition kills
|
|
189
|
+
// the process — Ctrl+C during a scan is precisely what the dead-pid rule is
|
|
190
|
+
// there for.
|
|
191
|
+
process.once("exit", release);
|
|
192
|
+
return { info, release };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function readLock(path: string): AuditLockInfo | null {
|
|
196
|
+
try {
|
|
197
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<AuditLockInfo>;
|
|
198
|
+
if (typeof parsed?.pid !== "number" || typeof parsed?.startedAt !== "number") return null;
|
|
199
|
+
return {
|
|
200
|
+
pid: parsed.pid,
|
|
201
|
+
startedAt: parsed.startedAt,
|
|
202
|
+
source: (parsed.source ?? "cli") as AuditLockSource,
|
|
203
|
+
};
|
|
204
|
+
} catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* True when this call published the lock; false when somebody else already had
|
|
211
|
+
* it (or the write failed for any other reason, which is equally "not ours").
|
|
212
|
+
*
|
|
213
|
+
* Written to a staging file and `link()`ed into place rather than created with
|
|
214
|
+
* `O_EXCL` and then written. Both are atomic about who WINS, but only this one
|
|
215
|
+
* is atomic about what the winner's file CONTAINS: `open(O_EXCL)` publishes the
|
|
216
|
+
* final name while the file is still empty, and an empty lock is an unreadable
|
|
217
|
+
* lock, which `isStale` — correctly — calls abandoned. A competitor landing in
|
|
218
|
+
* that window steals the lock from a holder that is mid-acquire and both
|
|
219
|
+
* processes then scan at once, which is the two-writers-on-one-cache outcome
|
|
220
|
+
* this whole module exists to prevent. `link()` makes the name appear only once
|
|
221
|
+
* the bytes behind it are already there. (It is also the idiom that survives a
|
|
222
|
+
* home shared over NFS, where `O_EXCL` has historically not been atomic at all
|
|
223
|
+
* — one of the cases the age rule above is a backstop for.)
|
|
224
|
+
*/
|
|
225
|
+
function writeLockExclusive(path: string, info: AuditLockInfo): boolean {
|
|
226
|
+
const body = JSON.stringify(info);
|
|
227
|
+
// Same directory, so `link()` stays within one filesystem; keyed by pid so
|
|
228
|
+
// two processes staging at once cannot overwrite each other's bytes.
|
|
229
|
+
const staging = `${path}.${process.pid}.tmp`;
|
|
230
|
+
try {
|
|
231
|
+
writeFileSync(staging, body, { mode: 0o600 });
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
linkSync(staging, path);
|
|
237
|
+
return true;
|
|
238
|
+
} catch (err) {
|
|
239
|
+
// EEXIST is the answer we asked the kernel for: somebody else holds it.
|
|
240
|
+
// Anything else means link() itself is unavailable — a filesystem with no
|
|
241
|
+
// hard links — and refusing there would mean the audit can never run on
|
|
242
|
+
// that machine at all, so fall back to the create-then-write form.
|
|
243
|
+
if ((err as NodeJS.ErrnoException).code === "EEXIST") return false;
|
|
244
|
+
return createThenWrite(path, body);
|
|
245
|
+
} finally {
|
|
246
|
+
try {
|
|
247
|
+
unlinkSync(staging);
|
|
248
|
+
} catch {
|
|
249
|
+
/* the link (or the failure) is what mattered; a leftover staging file is not */
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** The `O_EXCL` form, kept only as the no-hard-links fallback above. */
|
|
255
|
+
function createThenWrite(path: string, body: string): boolean {
|
|
256
|
+
let fd: number | undefined;
|
|
257
|
+
try {
|
|
258
|
+
fd = openSync(path, "wx", 0o600);
|
|
259
|
+
writeSync(fd, body);
|
|
260
|
+
return true;
|
|
261
|
+
} catch {
|
|
262
|
+
return false;
|
|
263
|
+
} finally {
|
|
264
|
+
if (fd !== undefined) {
|
|
265
|
+
try {
|
|
266
|
+
closeSync(fd);
|
|
267
|
+
} catch {
|
|
268
|
+
/* nothing useful to do with a failed close on a lockfile */
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function ensureRunDir(): void {
|
|
275
|
+
// The home first and at the default mode: `run/` below is created 0700, and
|
|
276
|
+
// letting a recursive mkdir apply that to `~/.failproofai` itself would hide
|
|
277
|
+
// config.toml, which is world-readable by design.
|
|
278
|
+
mkdirSync(failproofaiHome(), { recursive: true });
|
|
279
|
+
// 0700 explicitly, because failproofaid REFUSES TO START when run/ exists
|
|
280
|
+
// with any other mode (`ensure_run_dir` in crates/failproofaid/src/paths.rs).
|
|
281
|
+
// A lock file that created this directory under the ambient umask would
|
|
282
|
+
// therefore stop the daemon coming up — and on a daemon-configured machine a
|
|
283
|
+
// daemon that cannot start denies every tool call across all 12 CLIs.
|
|
284
|
+
mkdirSync(runDir(), { recursive: true, mode: 0o700 });
|
|
285
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The first (and only) TypeScript reader of `~/.failproofai/state/audit-schedule.json`.
|
|
3
|
+
*
|
|
4
|
+
* The **daemon is the sole writer** of that file (`crates/failproofaid/src/
|
|
5
|
+
* audit_lane.rs`); everything on this side only reads it. This module is that
|
|
6
|
+
* read side: `getScheduledAuditAction` calls it to answer "when did the last
|
|
7
|
+
* scheduled scan run, and when is the next one due?" on the /settings page.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this tolerates a file it did not write
|
|
10
|
+
*
|
|
11
|
+
* The daemon and the CLI ship and upgrade independently — a machine can be
|
|
12
|
+
* running a newer `failproofaid` than its `failproofai`, so the schedule file
|
|
13
|
+
* on disk can be a schema *ahead* of this build. A settings page that threw, or
|
|
14
|
+
* blanked its whole scheduled-audit section, because one derived state file was
|
|
15
|
+
* a version in front would be strictly worse than showing slightly less. So we
|
|
16
|
+
* read every field **by name, independent of `schema`**: a future file that
|
|
17
|
+
* still carries `next_due_at_ms` still renders a next-due time. Absent,
|
|
18
|
+
* unreadable, malformed, and non-object all resolve to `null` (the caller shows
|
|
19
|
+
* "no scheduled scan yet"); a recognised-but-unknown-higher schema still yields
|
|
20
|
+
* whatever numeric fields it shares, plus `schemaAhead` so the UI can say "a
|
|
21
|
+
* newer daemon wrote this" rather than silently dropping a field.
|
|
22
|
+
*
|
|
23
|
+
* ## What happened to `next-audit.json` — the one open sub-decision (resolved)
|
|
24
|
+
*
|
|
25
|
+
* `~/.failproofai/next-audit.json` (`lib/auth/auth-store.ts`
|
|
26
|
+
* `readReminder`/`writeReminder`) predates the scheduler and looks like a second
|
|
27
|
+
* source of truth for "the next audit". It is **not**, and after this change
|
|
28
|
+
* there is exactly one answer to "when does the next *scan* run": this file's
|
|
29
|
+
* `next_due_at_ms`. The two are kept **separate with deliberately distinct
|
|
30
|
+
* meanings**, because they answer different questions:
|
|
31
|
+
*
|
|
32
|
+
* - `audit-schedule.json` (here) = when the machine will next **run a scan
|
|
33
|
+
* itself**, headless, via the daemon. This is the scan schedule. It needs
|
|
34
|
+
* no human and no sign-in. `[audit] auto` + `interval_days` in `config.toml`
|
|
35
|
+
* drive it; the daemon derives and writes this file.
|
|
36
|
+
* - `next-audit.json` = an **email nudge to a signed-in human** ("come back
|
|
37
|
+
* and audit again"), set from the /audit "come back better" section,
|
|
38
|
+
* delivered by Failproof Cloud over SES via the api-server. It runs nothing
|
|
39
|
+
* locally; it is re-engagement, not a schedule.
|
|
40
|
+
*
|
|
41
|
+
* Collapsing them would merge "the box scans on a timer" with "email this person
|
|
42
|
+
* a reminder", which are neither the same trigger, the same actor, nor the same
|
|
43
|
+
* effect. So the /settings scheduled-audit section reads and writes **only** the
|
|
44
|
+
* scan schedule (this file + `[audit]`) and never touches `next-audit.json`,
|
|
45
|
+
* leaving the reminder as the /audit page's own concern. That keeps the scan
|
|
46
|
+
* schedule single-writer (the daemon) and single-file, which is the property the
|
|
47
|
+
* two-sources-of-truth worry was really about.
|
|
48
|
+
*/
|
|
49
|
+
import { readFileSync } from "node:fs";
|
|
50
|
+
import { auditScheduleFile } from "../hooks/fp-home";
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The schema the daemon currently writes — mirrors `SCHEMA` in
|
|
54
|
+
* `crates/failproofaid/src/audit_lane.rs`. Only used to *flag* a file that
|
|
55
|
+
* declares a higher one (`schemaAhead`); reads never gate on it, on purpose.
|
|
56
|
+
*/
|
|
57
|
+
export const AUDIT_SCHEDULE_SCHEMA = 1;
|
|
58
|
+
|
|
59
|
+
export interface AuditScheduleView {
|
|
60
|
+
/** The `schema` the file declared, or null if absent/non-numeric. */
|
|
61
|
+
schema: number | null;
|
|
62
|
+
/** Absolute wall-clock ms of the next scheduled scan, or null. */
|
|
63
|
+
nextDueAtMs: number | null;
|
|
64
|
+
/** When a scan was last STARTED (written before the child spawns), or null. */
|
|
65
|
+
lastAttemptAtMs: number | null;
|
|
66
|
+
/** When a scan last finished successfully (never advanced by an exit-75
|
|
67
|
+
* "lock held"), or null. */
|
|
68
|
+
lastRunAtMs: number | null;
|
|
69
|
+
/** The scheduled child's last exit code, or null. */
|
|
70
|
+
lastExitCode: number | null;
|
|
71
|
+
/** The file declares a schema newer than this build knows. Fields are still
|
|
72
|
+
* read best-effort; this only tells the UI to caveat what it shows. */
|
|
73
|
+
schemaAhead: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Read the scheduled-audit state, or null when there is nothing usable to show.
|
|
78
|
+
*
|
|
79
|
+
* Never throws: every failure mode (absent file, EACCES, truncated write,
|
|
80
|
+
* non-JSON, JSON that is not an object) collapses to null, which the caller
|
|
81
|
+
* renders as "no scheduled scan has run yet".
|
|
82
|
+
*/
|
|
83
|
+
export function readAuditSchedule(home?: string): AuditScheduleView | null {
|
|
84
|
+
let raw: string;
|
|
85
|
+
try {
|
|
86
|
+
raw = readFileSync(auditScheduleFile(home), "utf8");
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
let parsed: unknown;
|
|
91
|
+
try {
|
|
92
|
+
parsed = JSON.parse(raw);
|
|
93
|
+
} catch {
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
97
|
+
|
|
98
|
+
const obj = parsed as Record<string, unknown>;
|
|
99
|
+
const num = (v: unknown): number | null =>
|
|
100
|
+
typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
101
|
+
|
|
102
|
+
const schema = num(obj.schema);
|
|
103
|
+
return {
|
|
104
|
+
schema,
|
|
105
|
+
nextDueAtMs: num(obj.next_due_at_ms),
|
|
106
|
+
lastAttemptAtMs: num(obj.last_attempt_at_ms),
|
|
107
|
+
lastRunAtMs: num(obj.last_run_at_ms),
|
|
108
|
+
lastExitCode: num(obj.last_exit_code),
|
|
109
|
+
schemaAhead: schema !== null && schema > AUDIT_SCHEDULE_SCHEMA,
|
|
110
|
+
};
|
|
111
|
+
}
|
package/src/audit/cache.ts
CHANGED
|
@@ -12,10 +12,10 @@
|
|
|
12
12
|
import { createHash } from "node:crypto";
|
|
13
13
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
14
14
|
import { join } from "node:path";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
15
|
import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
|
|
17
16
|
import { AUDIT_DETECTORS } from "./detectors";
|
|
18
17
|
import type { TranscriptAuditResult } from "./types";
|
|
18
|
+
import { auditCacheDir } from "../hooks/fp-home";
|
|
19
19
|
|
|
20
20
|
let cachedEngineVersion: string | null = null;
|
|
21
21
|
let cachedDetectorVersion: string | null = null;
|
|
@@ -44,7 +44,7 @@ function getDetectorVersion(): string {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
function getCachePathFor(transcriptPath: string): string {
|
|
47
|
-
const root =
|
|
47
|
+
const root = auditCacheDir();
|
|
48
48
|
const key = createHash("sha1").update(transcriptPath).digest("hex");
|
|
49
49
|
return join(root, `${key}.json`);
|
|
50
50
|
}
|
|
@@ -73,8 +73,21 @@ export const CACHE_SCHEMA_VERSION = 3;
|
|
|
73
73
|
/** Hard expiry: a cached transcript result is rejected on read once it's
|
|
74
74
|
* this old. Keeps the audit pipeline honest about long-lived results that
|
|
75
75
|
* may no longer reflect current detector intent even if the transcript
|
|
76
|
-
* bytes and policy hashes haven't changed.
|
|
77
|
-
|
|
76
|
+
* bytes and policy hashes haven't changed.
|
|
77
|
+
*
|
|
78
|
+
* 30 days, and it MUST stay clear of the scheduled-audit interval
|
|
79
|
+
* (`DEFAULT_AUDIT_INTERVAL_DAYS`, 7). It was 7 days — exactly equal — which
|
|
80
|
+
* meant a scheduled run at T+7d found every entry cached at T+0 already
|
|
81
|
+
* expired and cold-scanned the entire history: ~104 seconds and megabytes of
|
|
82
|
+
* rewrites, on every single run, for a lane whose whole point is to be cheap.
|
|
83
|
+
* The margin was one scan duration, so any suspend, missed tick, or deferral
|
|
84
|
+
* tipped all of it over at once.
|
|
85
|
+
*
|
|
86
|
+
* Correctness does not rest on this number: `engineVersion` (a hash over every
|
|
87
|
+
* builtin's source) and `detectorVersion` already invalidate the cache the
|
|
88
|
+
* moment detection logic actually changes. The TTL only bounds staleness for
|
|
89
|
+
* changes those two cannot see, so it can be generous. */
|
|
90
|
+
export const CACHE_TTL_MS = 30 * 24 * 60 * 60_000;
|
|
78
91
|
|
|
79
92
|
interface CacheEntry {
|
|
80
93
|
/** Bumped whenever the on-disk shape changes incompatibly. */
|
|
@@ -123,7 +136,7 @@ export function writeCachedTranscriptResult(
|
|
|
123
136
|
if (sizeBytes === 0) return;
|
|
124
137
|
const cachePath = getCachePathFor(transcriptPath);
|
|
125
138
|
try {
|
|
126
|
-
mkdirSync(
|
|
139
|
+
mkdirSync(auditCacheDir(), { recursive: true });
|
|
127
140
|
const entry: CacheEntry = {
|
|
128
141
|
schemaVersion: CACHE_SCHEMA_VERSION,
|
|
129
142
|
cachedAt: Date.now(),
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* lib/goose-sessions.ts parses each session's `messages`), producing the same
|
|
8
8
|
* LogEntry[] shape the other adapters do — so `logEntriesToEvents` handles the
|
|
9
9
|
* rest. Like Devin, each Goose session carries a real `working_dir`, so
|
|
10
|
-
* `audit --project <cwd>` filters work (
|
|
10
|
+
* `audit --project <cwd>` filters work. (Hermes does too, for its `source='cli'`
|
|
11
|
+
* sessions — only its Slack/Telegram gateway runs are genuinely cwd-less.)
|
|
11
12
|
*/
|
|
12
13
|
import { getGooseSessions } from "../../../lib/goose-projects";
|
|
13
14
|
import { getGooseSessionLog } from "../../../lib/goose-sessions";
|