failproofai 0.0.14 → 0.0.15-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +2 -2
- 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 +2 -2
- 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 +2 -2
- 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 +1 -1
- 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 +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js +1 -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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +1 -1
- 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 +2 -2
- 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 +2 -2
- 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 +8 -8
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- 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 -4
- 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 +4 -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 +3 -3
- 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/chunks/[root-of-the-server]__0-219ec._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0hv08mx._.js → [root-of-the-server]__0-dh5jy._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0obaznk._.js → [root-of-the-server]__0h3lfmp._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0xvsnvt._.js → [root-of-the-server]__0l4vuqm._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0sb_5m8._.js +4 -4
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +6 -4
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1hkdkmz._.js → [root-of-the-server]__10oa-v9._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__1i9rknc._.js → [root-of-the-server]__18d-9oi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1is3glr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +6 -6
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0hdsupo._.js → [root-of-the-server]__0114ewg._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01od0m6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1s2b6h0._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lpvwqm._.js +21 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ynf7tx._.js → [root-of-the-server]__0qucxyj._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +8 -8
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cz9as._.js → [root-of-the-server]__1apb7k_._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1i21qhv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_077y5np._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit_loading_tsx_1j1kc6j._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +3 -0
- 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/lib_0xkhw_v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_hermes-projects_ts_05b8-it._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0gkzjaa.js → node_modules_html-to-image_es_index_0w82veb.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- 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 +10 -10
- package/.next/standalone/.next/static/chunks/0hn30_j22wpp_.js +6 -0
- package/.next/standalone/.next/static/chunks/{3s8aaig91937v.js → 1_20f96f1253b.js} +1 -1
- package/.next/standalone/.next/static/chunks/1hdhteui932bq.js +1 -0
- package/.next/standalone/.next/static/chunks/{33u59vf_8xpd-.js → 1j2xm011_aoae.js} +1 -1
- package/.next/standalone/.next/static/chunks/1qzsbft-jtnf8.js +1 -0
- package/.next/standalone/.next/static/chunks/{3-ogr79xqbw0h.js → 2mazwb1qmur1p.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +2 -0
- package/.next/standalone/.next/static/chunks/2xbkqlma_-61k.js +1 -0
- package/.next/standalone/.next/static/chunks/{29o4j32z643_a.js → 2y7acsw0--8xf.js} +1 -1
- package/.next/standalone/.next/static/chunks/3060a-0fayz96.js +1 -0
- package/.next/standalone/.next/static/chunks/312ihvhxeuoi_.js +1 -0
- package/.next/standalone/.next/static/chunks/31c084gsop1bb.js +1 -0
- package/.next/standalone/.next/static/chunks/{1ua5armwfph8o.js → 31fmsp1soxi07.js} +1 -1
- package/.next/standalone/.next/static/chunks/{02jcotghcxrbu.js → 3kmwfieo15mr2.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2uh_4k4m296z5.js → 3q9y67-fobgpc.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-10hj8t5pf33tu.js → turbopack-1lsv92x7klwrn.js} +1 -1
- package/.next/standalone/app/actions/get-hooks-config.ts +85 -0
- package/.next/standalone/app/components/project-list.tsx +229 -42
- package/.next/standalone/app/policies/hooks-client.tsx +112 -14
- package/.next/standalone/app/project/[name]/page.tsx +12 -2
- package/.next/standalone/integration-suite/README.md +43 -1
- package/.next/standalone/integration-suite/ci-entrypoint.sh +21 -6
- package/.next/standalone/integration-suite/install-clis.sh +58 -13
- package/.next/standalone/integration-suite/probe-cli.sh +51 -11
- package/.next/standalone/integration-suite/report.js +52 -6
- package/.next/standalone/integration-suite/run.sh +55 -3
- package/.next/standalone/lib/hermes-profiles.ts +156 -0
- package/.next/standalone/lib/hermes-projects.ts +91 -25
- package/.next/standalone/lib/hermes-sessions.ts +65 -10
- package/.next/standalone/lib/openclaw-projects.ts +111 -18
- package/.next/standalone/lib/openclaw-sessions.ts +20 -0
- package/.next/standalone/lib/paths.ts +36 -0
- package/.next/standalone/lib/project-tree.ts +193 -0
- package/.next/standalone/lib/projects.ts +22 -0
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/next-config-ts/transpile-config.js +3 -114
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/cli/next-test.js +2 -1
- package/.next/standalone/node_modules/next/dist/compiled/http-proxy/index.js +5 -5
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +11 -10
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +11 -10
- package/.next/standalone/node_modules/next/dist/compiled/next-server/app-route-turbo.runtime.prod.js +2 -2
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +9 -9
- package/.next/standalone/node_modules/next/dist/lib/install-dependencies.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/lib/typescript/loadTsConfig.js +181 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/missingDependencyError.js +1 -1
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeCheckCli.js +53 -0
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +256 -0
- package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +97 -56
- package/.next/standalone/node_modules/next/dist/server/app-render/action-handler.js +129 -34
- package/.next/standalone/node_modules/next/dist/server/app-render/manifests-singleton.js +52 -4
- package/.next/standalone/node_modules/next/dist/server/config-schema.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/config-shared.js +1 -2
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +8 -27
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/incremental-cache/index.js +90 -53
- package/.next/standalone/node_modules/next/dist/server/lib/patch-fetch.js +15 -2
- package/.next/standalone/node_modules/next/dist/server/lib/router-utils/proxy-request.js +4 -0
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/web/spec-extension/unstable-cache.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/errors/canary-only-config-error.js +1 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/router/utils/prepare-destination.js +2 -1
- package/.next/standalone/node_modules/next/dist/shared/lib/server-reference-info.js +12 -0
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +10 -10
- package/.next/standalone/package.json +7 -6
- package/.next/standalone/pi-extension/index.ts +38 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +623 -308
- package/lib/hermes-profiles.ts +156 -0
- package/lib/hermes-projects.ts +91 -25
- package/lib/hermes-sessions.ts +65 -10
- package/lib/openclaw-projects.ts +111 -18
- package/lib/openclaw-sessions.ts +20 -0
- package/lib/paths.ts +36 -0
- package/lib/project-tree.ts +193 -0
- package/lib/projects.ts +22 -0
- package/package.json +7 -6
- package/pi-extension/index.ts +38 -5
- package/scripts/launch.ts +7 -0
- package/src/audit/cli-adapters/hermes.ts +6 -3
- package/src/audit/cli-adapters/openclaw.ts +17 -13
- package/src/hooks/configure-wizard.ts +24 -2
- package/src/hooks/custom-hooks-loader.ts +58 -4
- package/src/hooks/enforcement-capability.ts +266 -0
- package/src/hooks/handler.ts +12 -1
- package/src/hooks/hook-activity-store.ts +14 -0
- package/src/hooks/hooks-config.ts +59 -1
- package/src/hooks/integrations.ts +123 -19
- package/src/hooks/manager.ts +93 -27
- package/src/hooks/policy-evaluator.ts +59 -7
- package/src/hooks/policy-types.ts +26 -0
- package/src/hooks/types.ts +50 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0zf8xlq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1fax1sl._.js +0 -19
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1kje4fm._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +0 -3
- package/.next/standalone/.next/static/chunks/06z_ex1ts3n4s.js +0 -1
- package/.next/standalone/.next/static/chunks/0o0ut0ugd0_-s.js +0 -1
- package/.next/standalone/.next/static/chunks/0xl97x-874k-u.js +0 -1
- package/.next/standalone/.next/static/chunks/19d7hqx8udopz.js +0 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
- package/.next/standalone/.next/static/chunks/370sj2lgkw5ey.js +0 -1
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
- package/.next/standalone/.next/static/chunks/42oeva1-w0z2d.js +0 -6
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{WQRKgSfFekLcC_pvyzp4S → QFmU1xD4R9N_HVfwccP6A}/_ssgManifest.js +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
import { useState, useMemo, useEffect, useRef } from "react";
|
|
8
8
|
import { ProjectFolder } from "@/lib/projects";
|
|
9
9
|
import { CliBadge } from "./cli-badge";
|
|
10
|
-
import {
|
|
10
|
+
import { projectDisplayName } from "@/lib/paths";
|
|
11
11
|
import { formatDate } from "@/lib/format-date";
|
|
12
12
|
import {
|
|
13
13
|
FILTER_PRESETS,
|
|
@@ -24,7 +24,13 @@ import {
|
|
|
24
24
|
pageToParam, paramToPage,
|
|
25
25
|
} from "@/lib/url-filter-serializers";
|
|
26
26
|
import { KNOWN_CLI_IDS, getCliLabel, isKnownCli, type CliId } from "@/lib/cli-registry";
|
|
27
|
-
import {
|
|
27
|
+
import {
|
|
28
|
+
buildProjectTree,
|
|
29
|
+
visibleTreeRows,
|
|
30
|
+
collapsibleKeys,
|
|
31
|
+
type ProjectTreeNode,
|
|
32
|
+
} from "@/lib/project-tree";
|
|
33
|
+
import { ChevronDown, ChevronRight, Folder, Search, X } from "lucide-react";
|
|
28
34
|
import Link from "next/link";
|
|
29
35
|
import PaginationControls from "./pagination-controls";
|
|
30
36
|
import DatePickerInput from "./date-picker-input";
|
|
@@ -44,6 +50,126 @@ function normalizeKeywordForSearch(keyword: string): string {
|
|
|
44
50
|
return keyword.trim().toLowerCase().replace(/\//g, "-");
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
/** Where collapsed folder rows are remembered between visits. */
|
|
54
|
+
const COLLAPSED_STORAGE_KEY = "failproofai.projects.collapsed";
|
|
55
|
+
|
|
56
|
+
function readCollapsed(): string[] {
|
|
57
|
+
try {
|
|
58
|
+
const raw = window.localStorage.getItem(COLLAPSED_STORAGE_KEY);
|
|
59
|
+
const parsed = raw ? JSON.parse(raw) : null;
|
|
60
|
+
return Array.isArray(parsed) ? parsed.filter((k) => typeof k === "string") : [];
|
|
61
|
+
} catch {
|
|
62
|
+
// Private mode / disabled storage / corrupt value — everything stays expanded.
|
|
63
|
+
return [];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function persistCollapsed(keys: Set<string>): void {
|
|
68
|
+
try {
|
|
69
|
+
window.localStorage.setItem(COLLAPSED_STORAGE_KEY, JSON.stringify([...keys]));
|
|
70
|
+
} catch {
|
|
71
|
+
// Storage unavailable — collapsing still works for this session.
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* One row of the projects table — a leaf project, or a folder grouping the
|
|
77
|
+
* gateway CLIs' (profile/agent, channel) hierarchy.
|
|
78
|
+
*
|
|
79
|
+
* Both shapes render as a `<tr>` in the SAME table so the Path and Last
|
|
80
|
+
* Modified columns stay aligned down the page; depth becomes left padding on
|
|
81
|
+
* the name cell rather than a nested table.
|
|
82
|
+
*/
|
|
83
|
+
function ProjectRow({
|
|
84
|
+
node,
|
|
85
|
+
collapsed,
|
|
86
|
+
interactive,
|
|
87
|
+
onToggle,
|
|
88
|
+
}: {
|
|
89
|
+
node: ProjectTreeNode;
|
|
90
|
+
collapsed: boolean;
|
|
91
|
+
/** False while a search force-expands the tree — see the folder branch below. */
|
|
92
|
+
interactive: boolean;
|
|
93
|
+
onToggle: (key: string) => void;
|
|
94
|
+
}) {
|
|
95
|
+
const isFolder = node.children.length > 0;
|
|
96
|
+
const project = node.project;
|
|
97
|
+
const Chevron = collapsed ? ChevronRight : ChevronDown;
|
|
98
|
+
|
|
99
|
+
// Depth is indentation, capped so a deep gateway path can't push the name off
|
|
100
|
+
// a narrow screen.
|
|
101
|
+
//
|
|
102
|
+
// Applied to the icon cell as well as the name, not just the name: the icon
|
|
103
|
+
// lives in its own <td>, so indenting only the name left every child's folder
|
|
104
|
+
// icon sitting at the same x as its parent's. The row then read as a flat
|
|
105
|
+
// list with ragged text rather than a hierarchy. Shifting both by the same
|
|
106
|
+
// amount moves icon and label together, so a child visibly starts to the
|
|
107
|
+
// right of its parent's icon.
|
|
108
|
+
const indent = { paddingLeft: `${Math.min(node.depth, 4) * 1.25}rem` };
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<tr className="border-b border-border hover:bg-muted/50 transition-colors">
|
|
112
|
+
<td className="px-4 py-3">
|
|
113
|
+
<div style={indent}>
|
|
114
|
+
<Folder className={`w-5 h-5 ${isFolder ? "text-muted-foreground" : "text-primary"}`} />
|
|
115
|
+
</div>
|
|
116
|
+
</td>
|
|
117
|
+
<td className="px-4 py-3 max-w-md">
|
|
118
|
+
<div className="flex flex-wrap items-center gap-2" style={indent}>
|
|
119
|
+
{isFolder ? (
|
|
120
|
+
// While a search is active the tree is force-expanded, so a toggle
|
|
121
|
+
// could only flip hidden state: it would look interactive, do
|
|
122
|
+
// nothing on screen, and silently invert what gets remembered once
|
|
123
|
+
// the search is cleared. Render it inert instead.
|
|
124
|
+
interactive ? (
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={() => onToggle(node.key)}
|
|
128
|
+
aria-expanded={!collapsed}
|
|
129
|
+
aria-label={`${collapsed ? "Expand" : "Collapse"} ${node.label}`}
|
|
130
|
+
className="flex items-center gap-1 font-semibold text-foreground hover:text-primary transition-colors break-words break-all text-left"
|
|
131
|
+
>
|
|
132
|
+
<Chevron className="w-4 h-4 shrink-0" />
|
|
133
|
+
{node.label}
|
|
134
|
+
</button>
|
|
135
|
+
) : (
|
|
136
|
+
<span className="flex items-center gap-1 font-semibold text-foreground break-words break-all">
|
|
137
|
+
<Chevron className="w-4 h-4 shrink-0" />
|
|
138
|
+
{node.label}
|
|
139
|
+
</span>
|
|
140
|
+
)
|
|
141
|
+
) : project ? (
|
|
142
|
+
<Link
|
|
143
|
+
href={`/project/${encodeURIComponent(project.name)}`}
|
|
144
|
+
className="font-semibold text-foreground hover:text-primary transition-colors break-words break-all inline-block max-w-full"
|
|
145
|
+
>
|
|
146
|
+
{/* Leaves of a gateway tree show only their own segment; a
|
|
147
|
+
filesystem project keeps its full decoded path. */}
|
|
148
|
+
{node.depth > 0 ? node.label : projectDisplayName(project.name, project.path)}
|
|
149
|
+
</Link>
|
|
150
|
+
) : (
|
|
151
|
+
<span className="font-semibold text-foreground">{node.label}</span>
|
|
152
|
+
)}
|
|
153
|
+
{node.cli.map((c) => (
|
|
154
|
+
<CliBadge key={c} cli={c} />
|
|
155
|
+
))}
|
|
156
|
+
{isFolder && node.sessionCount !== undefined && (
|
|
157
|
+
<span className="text-xs text-muted-foreground">
|
|
158
|
+
{node.sessionCount} session{node.sessionCount === 1 ? "" : "s"}
|
|
159
|
+
</span>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
</td>
|
|
163
|
+
<td className="px-4 py-3 text-sm text-muted-foreground hidden md:table-cell truncate max-w-md">
|
|
164
|
+
{project ? project.path : ""}
|
|
165
|
+
</td>
|
|
166
|
+
<td className="px-4 py-3 text-sm text-muted-foreground">
|
|
167
|
+
<DateDisplay date={node.lastModified} formatted={node.lastModifiedFormatted} />
|
|
168
|
+
</td>
|
|
169
|
+
</tr>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
47
173
|
export default function ProjectList({ folders }: ProjectListProps) {
|
|
48
174
|
const url = useUrlParams();
|
|
49
175
|
const mountedRef = useRef(false);
|
|
@@ -127,13 +253,88 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
127
253
|
return filtered.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
128
254
|
}, [normalizedFolders, filterPreset, dateRange, keywords, filterCli]);
|
|
129
255
|
|
|
130
|
-
|
|
256
|
+
// ── Folder tree ──
|
|
257
|
+
//
|
|
258
|
+
// Built from ALL filtered projects, never from a page slice: a gateway root's
|
|
259
|
+
// channels are individual projects interleaved by recency, so slicing first
|
|
260
|
+
// would split one profile across two pages — rendering the root twice, each
|
|
261
|
+
// time rolling up only the sessions that happened to land on that page, with
|
|
262
|
+
// the true total shown nowhere. Grouping is pure presentation; project names,
|
|
263
|
+
// and therefore every /project/<name> link, are untouched.
|
|
264
|
+
const tree = useMemo(() => buildProjectTree(filteredFolders), [filteredFolders]);
|
|
265
|
+
|
|
266
|
+
// Pagination walks TOP-LEVEL rows. With no gateway CLIs every root is exactly
|
|
267
|
+
// one project, so the page size and the reported range are identical to the
|
|
268
|
+
// flat table's; a gateway CLI simply folds its channels into one entry.
|
|
269
|
+
const totalPages = Math.max(1, Math.ceil(tree.length / ITEMS_PER_PAGE));
|
|
131
270
|
useEffect(() => {
|
|
132
271
|
if (currentPage > totalPages) setCurrentPage(totalPages);
|
|
133
272
|
}, [currentPage, totalPages, setCurrentPage]);
|
|
134
|
-
const
|
|
135
|
-
const
|
|
136
|
-
const
|
|
273
|
+
const rootStart = (currentPage - 1) * ITEMS_PER_PAGE;
|
|
274
|
+
const rootEnd = Math.min(rootStart + ITEMS_PER_PAGE, tree.length);
|
|
275
|
+
const pageRoots = useMemo(() => tree.slice(rootStart, rootEnd), [tree, rootStart, rootEnd]);
|
|
276
|
+
|
|
277
|
+
// The summary counts PROJECTS, so derive its range from the projects the
|
|
278
|
+
// displayed roots actually cover rather than from the row indices.
|
|
279
|
+
const projectsBefore = tree
|
|
280
|
+
.slice(0, rootStart)
|
|
281
|
+
.reduce((sum, node) => sum + node.projectCount, 0);
|
|
282
|
+
const projectsOnPage = pageRoots.reduce((sum, node) => sum + node.projectCount, 0);
|
|
283
|
+
const startIndex = projectsBefore;
|
|
284
|
+
const endIndex = projectsBefore + projectsOnPage;
|
|
285
|
+
|
|
286
|
+
// Collapsed (not expanded) is what we persist: a profile or agent appearing
|
|
287
|
+
// for the first time should be visible, not hidden behind a click nobody
|
|
288
|
+
// knows to make. Loaded in an effect, never during render — reading
|
|
289
|
+
// localStorage while rendering would desync the server-rendered HTML.
|
|
290
|
+
const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());
|
|
291
|
+
useEffect(() => {
|
|
292
|
+
setCollapsed(new Set(readCollapsed()));
|
|
293
|
+
}, []);
|
|
294
|
+
|
|
295
|
+
const toggleCollapsed = (key: string) => {
|
|
296
|
+
setCollapsed((prev) => {
|
|
297
|
+
const next = new Set(prev);
|
|
298
|
+
if (next.has(key)) next.delete(key);
|
|
299
|
+
else next.add(key);
|
|
300
|
+
persistCollapsed(next);
|
|
301
|
+
return next;
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// A search that matched something must not leave the match hidden inside a
|
|
306
|
+
// collapsed folder, so an active search force-expands everything.
|
|
307
|
+
const searching = keywords.length > 0;
|
|
308
|
+
const visibleRows = useMemo(
|
|
309
|
+
() => visibleTreeRows(pageRoots, (key) => !searching && collapsed.has(key)),
|
|
310
|
+
[pageRoots, collapsed, searching],
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
// Keys the bulk toggle acts on: everything collapsible in the CURRENT view
|
|
314
|
+
// (this page, this filter). It must never be used to REPLACE the stored set —
|
|
315
|
+
// see toggleAll.
|
|
316
|
+
const pageCollapsibleKeys = useMemo(() => collapsibleKeys(pageRoots), [pageRoots]);
|
|
317
|
+
// The label, though, follows what is on SCREEN. Judging it by the whole page's
|
|
318
|
+
// keys would keep saying "Collapse all" after a root was collapsed, because
|
|
319
|
+
// its hidden descendants are still expanded — a button that then does nothing
|
|
320
|
+
// visible.
|
|
321
|
+
const visibleCollapsibleKeys = visibleRows.filter((n) => n.children.length > 0).map((n) => n.key);
|
|
322
|
+
const anyExpanded = visibleCollapsibleKeys.some((k) => !collapsed.has(k));
|
|
323
|
+
|
|
324
|
+
const toggleAll = () => {
|
|
325
|
+
setCollapsed((prev) => {
|
|
326
|
+
// MERGE, never replace: the stored set spans every page and filter, so
|
|
327
|
+
// assigning this view's keys wholesale would silently discard collapses
|
|
328
|
+
// the user made elsewhere.
|
|
329
|
+
const next = new Set(prev);
|
|
330
|
+
for (const key of pageCollapsibleKeys) {
|
|
331
|
+
if (anyExpanded) next.add(key);
|
|
332
|
+
else next.delete(key);
|
|
333
|
+
}
|
|
334
|
+
persistCollapsed(next);
|
|
335
|
+
return next;
|
|
336
|
+
});
|
|
337
|
+
};
|
|
137
338
|
|
|
138
339
|
return (
|
|
139
340
|
<div className="space-y-4">
|
|
@@ -266,11 +467,20 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
266
467
|
</div>
|
|
267
468
|
|
|
268
469
|
{/* Results Count */}
|
|
269
|
-
<div className="text-sm text-muted-foreground">
|
|
470
|
+
<div className="flex flex-wrap items-center gap-x-3 gap-y-1 text-sm text-muted-foreground">
|
|
471
|
+
{pageCollapsibleKeys.length > 0 && !searching && (
|
|
472
|
+
<button
|
|
473
|
+
type="button"
|
|
474
|
+
onClick={toggleAll}
|
|
475
|
+
className="text-xs text-muted-foreground hover:text-foreground underline underline-offset-2 transition-colors"
|
|
476
|
+
>
|
|
477
|
+
{anyExpanded ? "Collapse all" : "Expand all"}
|
|
478
|
+
</button>
|
|
479
|
+
)}
|
|
270
480
|
{filteredFolders.length === 0 ? (
|
|
271
|
-
|
|
481
|
+
<span>No projects found</span>
|
|
272
482
|
) : (
|
|
273
|
-
|
|
483
|
+
<span>
|
|
274
484
|
Showing {startIndex + 1}-{endIndex} of {filteredFolders.length} projects
|
|
275
485
|
{filteredFolders.length !== normalizedFolders.length && (
|
|
276
486
|
<span className="ml-1">
|
|
@@ -282,7 +492,7 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
282
492
|
with {keywords.length} keyword{keywords.length !== 1 ? "s" : ""}
|
|
283
493
|
</span>
|
|
284
494
|
)}
|
|
285
|
-
|
|
495
|
+
</span>
|
|
286
496
|
)}
|
|
287
497
|
</div>
|
|
288
498
|
</div>
|
|
@@ -309,44 +519,21 @@ export default function ProjectList({ folders }: ProjectListProps) {
|
|
|
309
519
|
</tr>
|
|
310
520
|
</thead>
|
|
311
521
|
<tbody>
|
|
312
|
-
{
|
|
522
|
+
{visibleRows.length === 0 ? (
|
|
313
523
|
<tr>
|
|
314
524
|
<td colSpan={4} className="px-4 py-8 text-center text-muted-foreground">
|
|
315
525
|
No projects found matching the selected filter.
|
|
316
526
|
</td>
|
|
317
527
|
</tr>
|
|
318
528
|
) : (
|
|
319
|
-
|
|
320
|
-
<
|
|
321
|
-
key={
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
<td className="px-4 py-3 max-w-md">
|
|
328
|
-
<div className="flex flex-wrap items-center gap-2">
|
|
329
|
-
<Link
|
|
330
|
-
href={`/project/${encodeURIComponent(folder.name)}`}
|
|
331
|
-
className="font-semibold text-foreground hover:text-primary transition-colors break-words break-all inline-block max-w-full"
|
|
332
|
-
>
|
|
333
|
-
{decodeFolderName(folder.name)}
|
|
334
|
-
</Link>
|
|
335
|
-
{folder.cli.map((c) => (
|
|
336
|
-
<CliBadge key={c} cli={c} />
|
|
337
|
-
))}
|
|
338
|
-
</div>
|
|
339
|
-
</td>
|
|
340
|
-
<td className="px-4 py-3 text-sm text-muted-foreground hidden md:table-cell truncate max-w-md">
|
|
341
|
-
{folder.path}
|
|
342
|
-
</td>
|
|
343
|
-
<td className="px-4 py-3 text-sm text-muted-foreground">
|
|
344
|
-
<DateDisplay
|
|
345
|
-
date={folder.lastModified}
|
|
346
|
-
formatted={folder.lastModifiedFormatted}
|
|
347
|
-
/>
|
|
348
|
-
</td>
|
|
349
|
-
</tr>
|
|
529
|
+
visibleRows.map((node) => (
|
|
530
|
+
<ProjectRow
|
|
531
|
+
key={node.key}
|
|
532
|
+
node={node}
|
|
533
|
+
collapsed={!searching && collapsed.has(node.key)}
|
|
534
|
+
interactive={!searching}
|
|
535
|
+
onToggle={toggleCollapsed}
|
|
536
|
+
/>
|
|
350
537
|
))
|
|
351
538
|
)}
|
|
352
539
|
</tbody>
|
|
@@ -3,18 +3,7 @@
|
|
|
3
3
|
import React, { useState, useEffect, useCallback, useMemo, useRef, useTransition } from "react";
|
|
4
4
|
import { createPortal } from "react-dom";
|
|
5
5
|
import Link from "next/link";
|
|
6
|
-
import {
|
|
7
|
-
ShieldCheck,
|
|
8
|
-
ShieldX,
|
|
9
|
-
ShieldAlert,
|
|
10
|
-
Shield,
|
|
11
|
-
ChevronDown,
|
|
12
|
-
Copy,
|
|
13
|
-
Check,
|
|
14
|
-
Settings,
|
|
15
|
-
Code,
|
|
16
|
-
X,
|
|
17
|
-
} from "lucide-react";
|
|
6
|
+
import { Check, ChevronDown, Code, Copy, Settings, Shield, ShieldAlert, ShieldCheck, ShieldX, TriangleAlert, X } from "lucide-react";
|
|
18
7
|
import PaginationControls from "@/app/components/pagination-controls";
|
|
19
8
|
import { getHookActivityAction, searchHookActivityAction } from "@/app/actions/get-hook-activity";
|
|
20
9
|
import type { HookActivityPayload } from "@/app/actions/get-hook-activity";
|
|
@@ -29,6 +18,7 @@ import { usePostHog } from "@/contexts/PostHogContext";
|
|
|
29
18
|
import { useUrlParams } from "@/lib/use-url-params";
|
|
30
19
|
import { pageToParam, paramToPage } from "@/lib/url-filter-serializers";
|
|
31
20
|
import { getCliLabel, getCliBadgeClasses, KNOWN_CLI_IDS, isKnownCli, type CliId } from "@/lib/cli-registry";
|
|
21
|
+
import { enforcementFor } from "@/src/hooks/enforcement-capability";
|
|
32
22
|
import { formatRelativeTime } from "@/lib/format-duration";
|
|
33
23
|
import { Button } from "@/components/ui/button";
|
|
34
24
|
|
|
@@ -330,6 +320,58 @@ function StatsBar({ stats }: { stats: HookActivityPayload["stats"] }) {
|
|
|
330
320
|
|
|
331
321
|
// -- Expandable Detail Panel --
|
|
332
322
|
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* "Your policy ran, but this event cannot block on this CLI."
|
|
326
|
+
*
|
|
327
|
+
* Renders ONLY when the (cli, event) pair is a verified `observe`. A pair we
|
|
328
|
+
* have not traced returns undefined from `enforcementFor` and this renders
|
|
329
|
+
* nothing — a hedge ("capability unverified") would still be a claim, and
|
|
330
|
+
* unverified claims are the failure this whole matrix exists to prevent.
|
|
331
|
+
*
|
|
332
|
+
* The deny case matters more than the allow case: the row already says
|
|
333
|
+
* "denied", the activity store recorded a deny, and telemetry counted one — so
|
|
334
|
+
* without this note the UI actively reports enforcement that did not happen.
|
|
335
|
+
*/
|
|
336
|
+
function EnforcementNote({
|
|
337
|
+
item,
|
|
338
|
+
}: {
|
|
339
|
+
item: HookActivityPayload["entries"][number];
|
|
340
|
+
}) {
|
|
341
|
+
const capability = enforcementFor(
|
|
342
|
+
item.integration as IntegrationType | undefined,
|
|
343
|
+
item.eventType,
|
|
344
|
+
);
|
|
345
|
+
if (capability !== "observe") return null;
|
|
346
|
+
|
|
347
|
+
const ran = item.matchedPolicies ?? [];
|
|
348
|
+
// Rows written before matchedPolicies existed have no list; naming no policy
|
|
349
|
+
// is better than implying none ran.
|
|
350
|
+
if (ran.length === 0) return null;
|
|
351
|
+
|
|
352
|
+
const cli = item.integration ? getCliLabel(item.integration as IntegrationType) : "This CLI";
|
|
353
|
+
const names = ran.map((n) => n.replace(/^(custom|\.failproofai-(project|user))\//, ""));
|
|
354
|
+
const label = names.length === 1 ? names[0] : `${names.length} policies`;
|
|
355
|
+
const denied = item.decision === "deny";
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<div className="sm:col-span-2 lg:col-span-3 mt-1 flex items-start gap-2 rounded border border-amber-500/30 bg-amber-500/5 px-2.5 py-2">
|
|
359
|
+
<TriangleAlert className="h-3.5 w-3.5 shrink-0 text-amber-500/80 mt-0.5" aria-hidden="true" />
|
|
360
|
+
<p className="text-[0.7rem] leading-relaxed text-muted-foreground">
|
|
361
|
+
<span className="font-medium text-foreground">
|
|
362
|
+
{denied ? "Not enforced." : "Ran, but cannot enforce here."}
|
|
363
|
+
</span>{" "}
|
|
364
|
+
<span className="font-mono">{label}</span> ran on{" "}
|
|
365
|
+
<span className="font-mono">{item.eventType}</span>, but {cli} discards hook
|
|
366
|
+
verdicts for this event
|
|
367
|
+
{denied ? " — the action went ahead anyway." : ", so a policy cannot block here."}{" "}
|
|
368
|
+
Move the check to an event {cli} enforces on, such as{" "}
|
|
369
|
+
<span className="font-mono">PreToolUse</span>.
|
|
370
|
+
</p>
|
|
371
|
+
</div>
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
333
375
|
function DetailPanel({
|
|
334
376
|
item,
|
|
335
377
|
}: {
|
|
@@ -344,6 +386,7 @@ function DetailPanel({
|
|
|
344
386
|
event detail
|
|
345
387
|
</span>
|
|
346
388
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-2">
|
|
389
|
+
<EnforcementNote item={item} />
|
|
347
390
|
<div>
|
|
348
391
|
<span className="text-muted-foreground">Session ID: </span>
|
|
349
392
|
<span className="font-mono text-foreground">
|
|
@@ -1427,7 +1470,7 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1427
1470
|
<span className="text-xs text-muted-foreground">
|
|
1428
1471
|
<span className="font-semibold text-foreground">{config.enabledPolicies.length}</span>
|
|
1429
1472
|
{" / "}
|
|
1430
|
-
{config.policies.length + (config.customPolicies?.length ?? 0)}{" "}
|
|
1473
|
+
{config.policies.length + (config.customPolicies?.length ?? 0) + (config.conventionPolicies?.reduce((n, e) => n + e.policies.length, 0) ?? 0)}{" "}
|
|
1431
1474
|
policies enabled
|
|
1432
1475
|
</span>
|
|
1433
1476
|
{installed && (
|
|
@@ -1596,6 +1639,58 @@ function PoliciesTab({ onHooksInstallChange }: { onHooksInstallChange?: (install
|
|
|
1596
1639
|
))}
|
|
1597
1640
|
</div>
|
|
1598
1641
|
)}
|
|
1642
|
+
|
|
1643
|
+
{/* Convention policies — discovered from .failproofai/policies/, never
|
|
1644
|
+
from config, so they are grouped by the file that declares them. */}
|
|
1645
|
+
{config.conventionPolicies?.map((entry) => (
|
|
1646
|
+
<div key={entry.path}>
|
|
1647
|
+
<div className="flex items-center justify-between px-4 py-2.5 bg-muted/20 border-b border-border/50">
|
|
1648
|
+
<span className="text-[0.7rem] font-semibold uppercase tracking-wider text-muted-foreground">
|
|
1649
|
+
Convention Policies — {entry.scope === "project" ? "Project" : "User"}
|
|
1650
|
+
</span>
|
|
1651
|
+
<span className="text-[0.7rem] text-muted-foreground">
|
|
1652
|
+
{entry.policies.length} hook{entry.policies.length === 1 ? "" : "s"}
|
|
1653
|
+
</span>
|
|
1654
|
+
</div>
|
|
1655
|
+
<div className="flex items-center gap-3 px-4 py-3 border-b border-border/20">
|
|
1656
|
+
<Code className="h-3.5 w-3.5 text-muted-foreground shrink-0" />
|
|
1657
|
+
<span className="text-xs font-mono text-muted-foreground truncate">{entry.path}</span>
|
|
1658
|
+
</div>
|
|
1659
|
+
{entry.policies.length === 0 ? (
|
|
1660
|
+
<div className="flex items-start gap-2 px-4 py-2.5 border-b border-border/20 bg-muted/10">
|
|
1661
|
+
<Shield className="h-3.5 w-3.5 text-muted-foreground/60 shrink-0 mt-0.5" />
|
|
1662
|
+
<p className="text-[0.7rem] text-muted-foreground/70 leading-relaxed">
|
|
1663
|
+
No policies could be read from this file. It is still loaded at runtime —
|
|
1664
|
+
run <span className="font-mono">failproofai policies</span> to see what it registers.
|
|
1665
|
+
</p>
|
|
1666
|
+
</div>
|
|
1667
|
+
) : (
|
|
1668
|
+
entry.policies.map((policy) => (
|
|
1669
|
+
<div
|
|
1670
|
+
key={`${entry.path}:${policy.name}`}
|
|
1671
|
+
className="flex items-start gap-3 px-4 py-3 border-b border-border/20 hover:bg-muted/20 transition-colors"
|
|
1672
|
+
>
|
|
1673
|
+
<div className="h-4 w-7 shrink-0 mt-0.5" />
|
|
1674
|
+
<div className="flex items-center gap-1.5 min-w-0 w-56 shrink-0 mt-0.5">
|
|
1675
|
+
<span className="text-xs font-mono text-foreground truncate">{policy.name}</span>
|
|
1676
|
+
</div>
|
|
1677
|
+
<div className="flex-1 min-w-0">
|
|
1678
|
+
{policy.description && (
|
|
1679
|
+
<span className="text-xs text-muted-foreground leading-relaxed">
|
|
1680
|
+
{policy.description}
|
|
1681
|
+
</span>
|
|
1682
|
+
)}
|
|
1683
|
+
{policy.eventScope && (
|
|
1684
|
+
<span className="block text-[0.65rem] text-muted-foreground/40 font-mono mt-0.5 hidden lg:block">
|
|
1685
|
+
{policy.eventScope}
|
|
1686
|
+
</span>
|
|
1687
|
+
)}
|
|
1688
|
+
</div>
|
|
1689
|
+
</div>
|
|
1690
|
+
))
|
|
1691
|
+
)}
|
|
1692
|
+
</div>
|
|
1693
|
+
))}
|
|
1599
1694
|
</div>
|
|
1600
1695
|
</>
|
|
1601
1696
|
);
|
|
@@ -1646,7 +1741,10 @@ export default function HooksClient({ initialTab = "activity" }: { initialTab?:
|
|
|
1646
1741
|
setHooksInstalled(cfg.clis.some((c) => c.installed));
|
|
1647
1742
|
setPolicyCounts({
|
|
1648
1743
|
enabled: cfg.enabledPolicies.length,
|
|
1649
|
-
total:
|
|
1744
|
+
total:
|
|
1745
|
+
cfg.policies.length +
|
|
1746
|
+
(cfg.customPolicies?.length ?? 0) +
|
|
1747
|
+
(cfg.conventionPolicies?.reduce((n, e) => n + e.policies.length, 0) ?? 0),
|
|
1650
1748
|
});
|
|
1651
1749
|
setInstalledCliLabels(cfg.clis.filter((c) => c.installed).map((c) => c.label));
|
|
1652
1750
|
})
|
|
@@ -13,7 +13,7 @@ import { getCachedDevinSessionsByEncodedName } from "@/lib/devin-projects";
|
|
|
13
13
|
import { getCachedAntigravitySessionsByEncodedName } from "@/lib/antigravity-projects";
|
|
14
14
|
import { getCachedGooseSessionsByEncodedName } from "@/lib/goose-projects";
|
|
15
15
|
import { logWarn } from "@/lib/logger";
|
|
16
|
-
import { decodeFolderName } from "@/lib/paths";
|
|
16
|
+
import { decodeFolderName, projectDisplayName, isSyntheticProjectPath } from "@/lib/paths";
|
|
17
17
|
import { notFound } from "next/navigation";
|
|
18
18
|
import { existsSync } from "fs";
|
|
19
19
|
import { stat } from "fs/promises";
|
|
@@ -150,7 +150,17 @@ export default async function ProjectPage({ params }: ProjectPageProps) {
|
|
|
150
150
|
back to projects
|
|
151
151
|
</Link>
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
{/* Gateway projects carry a synthetic `hermes:profile:source` root; show
|
|
154
|
+
the same slash form the projects panel does, so the page you land on
|
|
155
|
+
is titled what you clicked. Everything else keeps the LITERAL cwd
|
|
156
|
+
recovered above — passing it through projectDisplayName would discard
|
|
157
|
+
it and re-derive a lossy name from the slug, turning a real
|
|
158
|
+
`/home/u/my-app` into `/home/u/my/app`. */}
|
|
159
|
+
<h1 className="project-page-title">
|
|
160
|
+
{isSyntheticProjectPath(canonicalRoot)
|
|
161
|
+
? projectDisplayName(name, canonicalRoot)
|
|
162
|
+
: canonicalRoot}
|
|
163
|
+
</h1>
|
|
154
164
|
|
|
155
165
|
<div className="project-page-meta">
|
|
156
166
|
<span className="label">path</span>
|
|
@@ -45,6 +45,48 @@ failproofai's HEAD changed since its last green run — unchanged ⇒ can't have
|
|
|
45
45
|
drifted ⇒ skip, protecting LLM/vendor quota. `FAIL`/`INCONCLUSIVE`/`ERROR` always
|
|
46
46
|
re-probe until they recover. Dispatch with **force** to probe everything.
|
|
47
47
|
|
|
48
|
+
## Two channels: `stable` and `beta`
|
|
49
|
+
|
|
50
|
+
The workflow runs as a **matrix over `CANARY_CHANNEL`**, in two concurrent legs
|
|
51
|
+
answering different questions:
|
|
52
|
+
|
|
53
|
+
| Leg | Installs | Question | On failure |
|
|
54
|
+
|-----|----------|----------|------------|
|
|
55
|
+
| `stable` | what users get, all 12 CLIs | *is enforcement broken now?* | **fails the job** + Slack |
|
|
56
|
+
| `beta` | each vendor's public pre-release ref, 6 CLIs | *is it about to break?* | **advisory only** — never fails |
|
|
57
|
+
|
|
58
|
+
Only six vendors publish something usable ahead of their release, so the beta leg
|
|
59
|
+
skips the rest rather than re-installing a stable build and reporting it as
|
|
60
|
+
pre-release coverage:
|
|
61
|
+
|
|
62
|
+
| CLI | Pre-release ref | Typical lead |
|
|
63
|
+
|-----|-----------------|--------------|
|
|
64
|
+
| codex | npm `alpha` | up to ~12 d (minor bumps only — patches ship blind) |
|
|
65
|
+
| copilot | npm `prerelease` | 0.6–5.8 d |
|
|
66
|
+
| openclaw | npm `beta` | 0.3–11.4 d |
|
|
67
|
+
| cursor | `install?channel=lab` | 2–4 d |
|
|
68
|
+
| goose | `CANARY=true` (rolling tag) | ~1 release cycle |
|
|
69
|
+
| claude | `latest` — **inverted**, see below | ~13 d |
|
|
70
|
+
|
|
71
|
+
**claude runs backwards.** Nothing ships ahead of `latest`, which *is* the
|
|
72
|
+
bleeding edge (~1 release/day); what exists is `stable`, ~13 days behind. So the
|
|
73
|
+
stable leg pins `bash -s stable` — testing what conservative users run, and
|
|
74
|
+
stopping a same-day Anthropic release from red-lighting an unrelated PR — and
|
|
75
|
+
`latest` becomes the early-warning ref. **hermes** is inverted too but has no fix:
|
|
76
|
+
its installer git-clones `main`, which is also what every hermes user gets, so
|
|
77
|
+
there is nothing ahead of us to probe.
|
|
78
|
+
|
|
79
|
+
**Escalation is a cross-leg comparison, not a beta failure.** A CLI red on both
|
|
80
|
+
legs is already broken and belongs to the stable leg's alarm. The early warning is
|
|
81
|
+
`stable green + beta not-green`, held for **two consecutive runs** so a broken
|
|
82
|
+
alpha that gets reverted before release doesn't burn attention. Note the signal is
|
|
83
|
+
usually `INCONCLUSIVE`/`ERROR`, not `FAIL` — a vendor payload change stops the
|
|
84
|
+
model before it ever calls a tool — so a FAIL-only rule would miss it entirely.
|
|
85
|
+
|
|
86
|
+
Each leg has its own Docker volume and Actions cache key: a pre-release install
|
|
87
|
+
overwrites the stable binary in a shared `$HOME`, and a beta result must never be
|
|
88
|
+
able to overwrite the stable leg's gating record.
|
|
89
|
+
|
|
48
90
|
## Auth & secrets (the `cli-integration` Environment)
|
|
49
91
|
|
|
50
92
|
> The GitHub **Environment** is still named `cli-integration` — it's configured in
|
|
@@ -77,7 +119,7 @@ re-login on the capture machine and refresh the secret.
|
|
|
77
119
|
```
|
|
78
120
|
ci-entrypoint.sh CI front door: build -> sandbox -> install -> probe -> cleanup
|
|
79
121
|
Dockerfile non-root sandbox base image
|
|
80
|
-
install-clis.sh install/upgrade
|
|
122
|
+
install-clis.sh install/upgrade the CLIs for $CANARY_CHANNEL (with retries)
|
|
81
123
|
inject-tokens.sh write captured OAuth creds into the fresh volume
|
|
82
124
|
probe-cli.sh live enforcement probe for ONE CLI (the oracle)
|
|
83
125
|
canary-policies.mjs benign-marker custom policies the probe trips
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
# CANARY_LLM_MODEL (default deepseek-v4-pro)
|
|
28
28
|
# CANARY_CLAUDE_MODEL (default claude-haiku-4-5)
|
|
29
29
|
# CANARY_PI_MODEL (default claude-haiku-4-5)
|
|
30
|
+
# CANARY_CODEX_MODEL (default gpt-5.1-codex-mini)
|
|
30
31
|
# COPILOT_GITHUB_TOKEN copilot PAT
|
|
31
32
|
# CURSOR_TOKEN_TGZ_B64 } base64 gzip-tars of each OAuth credential tree,
|
|
32
33
|
# DEVIN_TOKEN_TGZ_B64 } rooted at $HOME (see capture-tokens.sh)
|
|
@@ -43,11 +44,20 @@ set -u
|
|
|
43
44
|
HERE="$(cd "$(dirname "$0")" && pwd)"
|
|
44
45
|
REPO="${GITHUB_WORKSPACE:-$(dirname "$HERE")}"
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
# Each channel gets its OWN volume: installing a pre-release into the same $HOME
|
|
48
|
+
# overwrites the stable binary (npm -g, and the vendor installers all rewrite
|
|
49
|
+
# their own shims), so the two refs cannot coexist in one volume.
|
|
50
|
+
CHANNEL="${CANARY_CHANNEL:-stable}"
|
|
51
|
+
SUFFIX=""; [ "$CHANNEL" != stable ] && SUFFIX="-$CHANNEL"
|
|
52
|
+
VOL="${CANARY_VOL:-integration-suite$SUFFIX}"
|
|
47
53
|
IMAGE="${CANARY_IMAGE:-failproofai-integration-suite:base}"
|
|
48
|
-
ENVFILE="${CANARY_ENVFILE:-$REPO/canary.env}"
|
|
49
|
-
TOKENS_DIR="${CANARY_TOKENS_DIR:-$REPO/tokens}"
|
|
50
|
-
STATE="${CANARY_STATE:-$REPO/integration-suite-state.json}"
|
|
54
|
+
ENVFILE="${CANARY_ENVFILE:-$REPO/canary$SUFFIX.env}"
|
|
55
|
+
TOKENS_DIR="${CANARY_TOKENS_DIR:-$REPO/tokens$SUFFIX}"
|
|
56
|
+
STATE="${CANARY_STATE:-$REPO/integration-suite-state$SUFFIX.json}"
|
|
57
|
+
# The stable leg's state, so the beta leg can tell "about to break" from
|
|
58
|
+
# "already broken" (see report.js). Unset on the stable leg.
|
|
59
|
+
PEER_STATE="${CANARY_PEER_STATE:-}"
|
|
60
|
+
[ "$CHANNEL" != stable ] && [ -z "$PEER_STATE" ] && PEER_STATE="$REPO/integration-suite-state.json"
|
|
51
61
|
|
|
52
62
|
step() { echo "── $* ──" >&2; }
|
|
53
63
|
|
|
@@ -123,8 +133,8 @@ docker volume rm "$VOL" -f >/dev/null 2>&1 || true
|
|
|
123
133
|
docker volume create "$VOL" >/dev/null || { echo "✗ volume create failed" >&2; exit 1; }
|
|
124
134
|
|
|
125
135
|
# ── 4. install CLIs @latest, then inject tokens ─────────────────────────────
|
|
126
|
-
step "installing
|
|
127
|
-
docker run --rm -v "$VOL:/home/canary" -v "$HERE:/opt/canary:ro" \
|
|
136
|
+
step "installing CLIs (channel=$CHANNEL) into the fresh volume"
|
|
137
|
+
docker run --rm -e CANARY_CHANNEL="$CHANNEL" -v "$VOL:/home/canary" -v "$HERE:/opt/canary:ro" \
|
|
128
138
|
"$IMAGE" bash /opt/canary/install-clis.sh \
|
|
129
139
|
|| { echo "✗ CLI install failed" >&2; exit 1; }
|
|
130
140
|
|
|
@@ -145,6 +155,9 @@ step "assembling gateway env-file"
|
|
|
145
155
|
echo "CANARY_LLM_MODEL=${CANARY_LLM_MODEL:-deepseek-v4-pro}"
|
|
146
156
|
echo "CANARY_CLAUDE_MODEL=${CANARY_CLAUDE_MODEL:-claude-haiku-4-5}"
|
|
147
157
|
echo "CANARY_PI_MODEL=${CANARY_PI_MODEL:-claude-haiku-4-5}"
|
|
158
|
+
# Without this line probe-cli.sh's CANARY_CODEX_MODEL override was dead plumbing:
|
|
159
|
+
# the var is read inside the container, and only what is written here crosses into it.
|
|
160
|
+
echo "CANARY_CODEX_MODEL=${CANARY_CODEX_MODEL:-gpt-5.1-codex-mini}"
|
|
148
161
|
echo "COPILOT_GITHUB_TOKEN=${COPILOT_GITHUB_TOKEN:-}"
|
|
149
162
|
} > "$ENVFILE"
|
|
150
163
|
)
|
|
@@ -161,4 +174,6 @@ CANARY_VOL="$VOL" \
|
|
|
161
174
|
CANARY_IMAGE="$IMAGE" \
|
|
162
175
|
CANARY_STATE="$STATE" \
|
|
163
176
|
CANARY_ENVFILE="$ENVFILE" \
|
|
177
|
+
CANARY_CHANNEL="$CHANNEL" \
|
|
178
|
+
CANARY_PEER_STATE="$PEER_STATE" \
|
|
164
179
|
bash "$HERE/run.sh" ${CANARY_CLIS:-}
|