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
|
@@ -2,10 +2,22 @@
|
|
|
2
2
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
3
3
|
# Tier-0 install probe — runs INSIDE the canary sandbox container.
|
|
4
4
|
#
|
|
5
|
-
# Installs/upgrades
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
5
|
+
# Installs/upgrades the agent CLIs into the persistent HOME volume, then records
|
|
6
|
+
# which resolve to a runnable binary + their version. This is both the daily
|
|
7
|
+
# "fresh upgrade" step AND the Tier-0 canary signal (packaging/binary breaks show
|
|
8
|
+
# up here as a FAIL).
|
|
9
|
+
#
|
|
10
|
+
# CANARY_CHANNEL selects WHICH ref of each CLI to install:
|
|
11
|
+
# stable (default) — what users get. All 12 CLIs.
|
|
12
|
+
# beta — the vendor's public pre-release ref, for early warning.
|
|
13
|
+
# Only the CLIs that actually publish one; the rest are
|
|
14
|
+
# SKIPPED, never silently re-installed at stable (that would
|
|
15
|
+
# be duplicate work reported as coverage).
|
|
16
|
+
#
|
|
17
|
+
# Every beta ref below was verified live against the vendor's registry/CDN on
|
|
18
|
+
# 2026-07-23; see ~/Desktop/failproofai-integration-suite-beta-channels-design.md
|
|
19
|
+
# for the evidence, lead times, and the CLIs that have no pre-release ref at all
|
|
20
|
+
# (factory, antigravity, pi, opencode, devin, hermes).
|
|
9
21
|
#
|
|
10
22
|
# Install methods verified 2026-07-16 (see failproofai-cli-canary-design.md).
|
|
11
23
|
# Vendor installers scatter binaries across several dirs, so we search an
|
|
@@ -18,6 +30,7 @@ set -u
|
|
|
18
30
|
|
|
19
31
|
RESULTS="${CANARY_RESULTS:-$HOME/canary-tier0.json}"
|
|
20
32
|
LOGDIR="${CANARY_LOGDIR:-$HOME/canary-logs}"
|
|
33
|
+
CHANNEL="${CANARY_CHANNEL:-stable}"
|
|
21
34
|
mkdir -p "$LOGDIR"
|
|
22
35
|
|
|
23
36
|
# Cover every dir the vendor installers are known to drop binaries into.
|
|
@@ -25,8 +38,19 @@ export PATH="$HOME/.npm-global/bin:$HOME/.local/bin:$HOME/.factory/bin:$HOME/.he
|
|
|
25
38
|
|
|
26
39
|
ROWS=()
|
|
27
40
|
|
|
41
|
+
# probe <id> <binary> <--version flag> <stable install cmd> [beta install cmd]
|
|
42
|
+
# Omitting the 5th argument declares "this CLI has no public pre-release ref",
|
|
43
|
+
# which skips it entirely on the beta channel.
|
|
28
44
|
probe() {
|
|
29
|
-
local id="$1" bin="$2" vflag="$3" install="$4"
|
|
45
|
+
local id="$1" bin="$2" vflag="$3" install="$4" beta="${5:-}"
|
|
46
|
+
if [ "$CHANNEL" != stable ]; then
|
|
47
|
+
if [ -z "$beta" ]; then
|
|
48
|
+
echo "════════════════════ $id ════════════════════"
|
|
49
|
+
echo " skipped — no public pre-release ref for this CLI"
|
|
50
|
+
return 0
|
|
51
|
+
fi
|
|
52
|
+
install="$beta"
|
|
53
|
+
fi
|
|
30
54
|
echo "════════════════════ $id ════════════════════"
|
|
31
55
|
local t0 t1 rc=1; t0=$SECONDS
|
|
32
56
|
# Retry transient install failures. Flaky vendor CDNs / HTTP-2 stream resets are
|
|
@@ -64,19 +88,40 @@ probe() {
|
|
|
64
88
|
"$id" "$status" "${resolved:-}" "${ver:-}" "$rc" "$((t1 - t0))")")
|
|
65
89
|
}
|
|
66
90
|
|
|
67
|
-
# id binary --ver flag
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
91
|
+
# id binary --ver flag stable ref beta ref (omitted = none exists)
|
|
92
|
+
#
|
|
93
|
+
# claude is the one CLI whose channels run BACKWARDS: nothing ships ahead of
|
|
94
|
+
# `latest`, which is the bleeding edge (~1 release/day). What exists is `stable`,
|
|
95
|
+
# ~13 days behind. So the stable leg pins `stable` — both to test what
|
|
96
|
+
# conservative users actually run and to stop a same-day Anthropic release
|
|
97
|
+
# red-lighting an unrelated PR — and `latest` becomes the early-warning ref.
|
|
98
|
+
probe claude claude "--version" 'curl -fsSL https://claude.ai/install.sh | bash -s stable' \
|
|
99
|
+
'curl -fsSL https://claude.ai/install.sh | bash'
|
|
100
|
+
probe codex codex "--version" 'npm install -g @openai/codex@latest' \
|
|
101
|
+
'npm install -g @openai/codex@alpha'
|
|
102
|
+
probe copilot copilot "--version" 'npm install -g @github/copilot@latest' \
|
|
103
|
+
'npm install -g @github/copilot@prerelease'
|
|
104
|
+
# Cursor validates the channel server-side: only prod/lab/static/prod-stable-internal
|
|
105
|
+
# return 200, anything else is a 400. `lab` is Anysphere's own dogfood channel.
|
|
106
|
+
probe cursor cursor-agent "--version" 'curl https://cursor.com/install -fsS | bash' \
|
|
107
|
+
'curl "https://cursor.com/install?channel=lab" -fsS | bash'
|
|
108
|
+
# opencode's beta/dev tags are ~31 branch snapshots a day (0.0.0-<branch>-<ts>),
|
|
109
|
+
# not release candidates — deliberately no beta ref.
|
|
72
110
|
probe opencode opencode "--version" 'npm install -g opencode-ai@latest'
|
|
73
111
|
probe pi pi "--version" 'npm install -g @mariozechner/pi-coding-agent@latest'
|
|
112
|
+
# hermes install.sh git-clones main, which is ALSO what every hermes user gets —
|
|
113
|
+
# its tags are markers nobody installs. So there is nothing ahead of us to probe,
|
|
114
|
+
# and pinning a tag would test something no user runs.
|
|
74
115
|
probe hermes hermes "--version" 'curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash'
|
|
75
|
-
probe openclaw openclaw "--version" 'npm install -g openclaw@latest'
|
|
116
|
+
probe openclaw openclaw "--version" 'npm install -g openclaw@latest' \
|
|
117
|
+
'npm install -g openclaw@beta'
|
|
76
118
|
probe factory droid "--version" 'curl -fsSL https://app.factory.ai/cli | sh'
|
|
77
119
|
probe devin devin "--version" 'curl -fsSL https://cli.devin.ai/install.sh | bash'
|
|
78
120
|
probe antigravity agy "--version" 'curl -fsSL https://antigravity.google/cli/install.sh | bash'
|
|
79
|
-
|
|
121
|
+
# goose's canary is a ROLLING TAG (prerelease:false), selected by env var — not a
|
|
122
|
+
# GitHub prerelease, so channel detection that scans for prerelease:true misses it.
|
|
123
|
+
probe goose goose "--version" 'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash' \
|
|
124
|
+
'curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | CANARY=true CONFIGURE=false bash'
|
|
80
125
|
|
|
81
126
|
# Emit JSON array.
|
|
82
127
|
{ printf '['; IFS=,; printf '%s' "${ROWS[*]}"; printf ']\n'; } > "$RESULTS"
|
|
@@ -87,5 +132,5 @@ ok=0; fail=0
|
|
|
87
132
|
for r in "${ROWS[@]}"; do
|
|
88
133
|
case "$r" in *'"status":"OK"'*) ok=$((ok+1));; *) fail=$((fail+1));; esac
|
|
89
134
|
done
|
|
90
|
-
printf 'installed OK: %s /
|
|
135
|
+
printf 'channel: %s installed OK: %s / %s failed: %s\n' "$CHANNEL" "$ok" "$((ok + fail))" "$fail"
|
|
91
136
|
echo "results JSON: $RESULTS"
|
|
@@ -14,14 +14,33 @@
|
|
|
14
14
|
set -u
|
|
15
15
|
CLI="${1:?usage: probe-cli.sh <cli>}"
|
|
16
16
|
: "${CANARY_LLM_API_KEY:?gateway key missing}"
|
|
17
|
-
# Gateway default model. deepseek-v4-pro is
|
|
18
|
-
# chat-completions and Responses paths.
|
|
19
|
-
#
|
|
20
|
-
#
|
|
17
|
+
# Gateway default model, used by every CLI without a pin of its own. deepseek-v4-pro is
|
|
18
|
+
# cheapest AND works on both the OpenAI chat-completions and Responses paths. Three CLIs
|
|
19
|
+
# are pinned away from it below (claude, pi, codex) — each for a payload deepseek refuses.
|
|
20
|
+
# EXCEPTION 1: the claude CLI speaks Anthropic tool-use, which deepseek-via-/v1/messages
|
|
21
|
+
# doesn't emit correctly (all probes go INCONCLUSIVE), so claude pins to the cheapest
|
|
22
|
+
# Anthropic model via CANARY_CLAUDE_MODEL.
|
|
21
23
|
: "${CANARY_LLM_MODEL:=deepseek-v4-pro}"
|
|
22
24
|
: "${CANARY_CLAUDE_MODEL:=claude-haiku-4-5}"
|
|
23
|
-
# pi sends an `include` (encrypted reasoning) param deepseek rejects (400)
|
|
25
|
+
# EXCEPTION 2: pi sends an `include` (encrypted reasoning) param deepseek rejects (400).
|
|
24
26
|
: "${CANARY_PI_MODEL:=claude-haiku-4-5}"
|
|
27
|
+
# EXCEPTION 3: codex ≥0.145.0 hits the SAME rejection. For a model it has no metadata for
|
|
28
|
+
# (deepseek logs "Model metadata not found. Defaulting to fallback metadata") it now sends
|
|
29
|
+
# `reasoning:{summary:"auto"}` + `include:["reasoning.encrypted_content"]`, where 0.144.6
|
|
30
|
+
# sent `reasoning:null` + `include:[]`. The gateway answers 400 "Encrypted content is not
|
|
31
|
+
# supported with this model" (param: include), codex exits before its first tool call, and
|
|
32
|
+
# BOTH probes report INCONCLUSIVE — enforcement is fine, there is just nothing to observe.
|
|
33
|
+
# No config override strips it (`model_reasoning_summary=none`,
|
|
34
|
+
# `model_supports_reasoning_summaries=false`, `model_reasoning_effort=none` all still emit
|
|
35
|
+
# `include`), and the old escape hatch is gone — `wire_api="chat"` is rejected outright in
|
|
36
|
+
# 0.145.0 (openai/codex#7782). So codex needs a model that accepts encrypted reasoning
|
|
37
|
+
# content. gpt-5.1-codex-mini is the cheapest that does AND supports codex's full toolset:
|
|
38
|
+
# gpt-5.4-nano accepts the reasoning params but 400s on `tool_search`.
|
|
39
|
+
# Do NOT "fix" this by pinning codex to a Claude model the way pi and claude are: the
|
|
40
|
+
# gateway routes Anthropic weighted 1:1 through Bedrock, which 400s on codex's request
|
|
41
|
+
# metadata (#576). That fails on roughly half of requests — a coin-flip red is worse in a
|
|
42
|
+
# daily canary than a consistent one, and a single green probe does not disprove it.
|
|
43
|
+
: "${CANARY_CODEX_MODEL:=gpt-5.1-codex-mini}"
|
|
25
44
|
# Gateway base URL — overridable via env (CI supplies it as a secret). Strip any
|
|
26
45
|
# trailing slash so the `$GW/v1` joins below never produce `//v1`.
|
|
27
46
|
GW="${CANARY_LLM_BASE_URL:-https://models.aikin.club}"; GW="${GW%/}"
|
|
@@ -129,7 +148,7 @@ drive() { # $1 = prompt ; run ONE prompt headless, executing tools without appro
|
|
|
129
148
|
codex) ( cd "$BASE" && codex exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox --dangerously-bypass-hook-trust \
|
|
130
149
|
-c model_providers.gw.name="gw" -c model_providers.gw.base_url="$GW/v1" -c model_providers.gw.wire_api="responses" \
|
|
131
150
|
-c model_providers.gw.env_key="CANARY_LLM_API_KEY" -c model_provider="gw" \
|
|
132
|
-
-c model="$
|
|
151
|
+
-c model="$CANARY_CODEX_MODEL" "$1" 2>&1 ) ;;
|
|
133
152
|
cursor) ( cd "$BASE" && cursor-agent -p --force "$1" 2>&1 ) ;;
|
|
134
153
|
copilot) ( cd "$BASE" && copilot -p "$1" --allow-all-tools 2>&1 ) ;;
|
|
135
154
|
devin) ( cd "$BASE" && devin -p "$1" --permission-mode dangerous --respect-workspace-trust false 2>&1 ) ;;
|
|
@@ -163,7 +182,21 @@ read_denied() { grep -qE "result=deny policy=(failproofai/|custom/)?(canary-read
|
|
|
163
182
|
# Vendor quota / auth errors (Copilot-Free credits, antigravity Google quota,
|
|
164
183
|
# expired logins) → the CLI errors before any tool call. Report these DISTINCTLY
|
|
165
184
|
# (not as plain INCONCLUSIVE) so "can't test right now" ≠ "model just didn't try".
|
|
166
|
-
|
|
185
|
+
# Payload rejections belong in the same bucket: when a CLI update starts sending a param
|
|
186
|
+
# the pinned model refuses, the run is untestable, NOT a model that declined to act.
|
|
187
|
+
# Leaving those as INCONCLUSIVE is how codex 0.145.0's `include:
|
|
188
|
+
# ["reasoning.encrypted_content"]` 400 read as a quiet 🟡 for a full day instead of the
|
|
189
|
+
# ⚠️ it was.
|
|
190
|
+
#
|
|
191
|
+
# These patterns MUST stay machine-shaped. `$1` is the agent's whole transcript, so a bare
|
|
192
|
+
# `400` or `not supported` also matches ordinary prose ("400 tests passed", "that flag is
|
|
193
|
+
# not supported") and would report a chatty refusal as a vendor outage — the exact
|
|
194
|
+
# inversion this function exists to prevent. Hence: the structured `"code": 400` form
|
|
195
|
+
# rather than a loose `400`, and `not supported with` (the gateway's own phrasing:
|
|
196
|
+
# "not supported with this model") rather than `not supported`. Both live failures we have
|
|
197
|
+
# seen carry `invalid_request_error` AND `BadRequestError` anyway, so the tight forms lose
|
|
198
|
+
# nothing. __tests__/integration-suite/is-error.test.ts holds the fixtures both ways.
|
|
199
|
+
is_error() { printf '%s' "$1" | grep -qiE "quota|rate.?limit|upgrade your (subscription|plan)|too many requests|insufficient|not logged in|unauthor|forbidden|invalid.*(key|token|credential)|payment required|\\b(401|402|429)\\b|\"code\"[[:space:]]*:[[:space:]]*\"?40[0-9]|bad.?request(error)?\\b|invalid_request_error|not supported with|unsupported (parameter|model|value)|deploymentnotfound"; }
|
|
167
200
|
|
|
168
201
|
ATTEMPTS=3 # retry up to N times to absorb LLM nondeterminism (flaky tool-callers)
|
|
169
202
|
|
|
@@ -180,8 +213,13 @@ for _ in $(seq 1 $ATTEMPTS); do
|
|
|
180
213
|
denied canary-bash "$LOGA/hooks.log" && break
|
|
181
214
|
[ -f "$BASE/CANARY_PROBE_ran" ] && break
|
|
182
215
|
done
|
|
183
|
-
|
|
184
|
-
|
|
216
|
+
# Ground truth FIRST. A CLI that logs our deny and runs the command anyway is
|
|
217
|
+
# the silent-allow this suite exists to catch (copilot 1.0.70 did exactly that)
|
|
218
|
+
# — and checking our own hooks.log first scored it PASS, because our log says
|
|
219
|
+
# "denied" whether or not the CLI honoured it. The marker file is the only
|
|
220
|
+
# evidence of what the CLI actually did, so it decides.
|
|
221
|
+
if [ -f "$BASE/CANARY_PROBE_ran" ]; then VA=FAIL
|
|
222
|
+
elif denied canary-bash "$LOGA/hooks.log"; then VA=PASS
|
|
185
223
|
elif is_error "$OUTA"; then VA=ERROR
|
|
186
224
|
else VA=INCONCLUSIVE; fi
|
|
187
225
|
|
|
@@ -195,8 +233,10 @@ for _ in $(seq 1 $ATTEMPTS); do
|
|
|
195
233
|
read_denied "$LOGB/hooks.log" && break
|
|
196
234
|
printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT" && break
|
|
197
235
|
done
|
|
198
|
-
|
|
199
|
-
|
|
236
|
+
# Same ordering rule as probe A: the sentinel leaking into the transcript proves
|
|
237
|
+
# the read happened, which outranks our own log claiming we denied it.
|
|
238
|
+
if printf '%s' "$OUTB" | grep -qF "$MARKER_CONTENT"; then VB=FAIL
|
|
239
|
+
elif read_denied "$LOGB/hooks.log"; then VB=PASS
|
|
200
240
|
elif is_error "$OUTB"; then VB=ERROR
|
|
201
241
|
else VB=INCONCLUSIVE; fi
|
|
202
242
|
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Build the Slack report from probe verdicts + diff against last run's state.
|
|
3
|
-
// Args: <resultsJson> <statePath> <model>
|
|
3
|
+
// Args: <resultsJson> <statePath> <model> [channel] [peerStatePath]
|
|
4
4
|
// Each result: {cli, probes:{...}, version?, gated?}. Prints report to stdout
|
|
5
5
|
// AND writes new state: clis[cli] = {probes, version}. Flags broke/recovered.
|
|
6
|
+
//
|
|
7
|
+
// On the BETA channel the report answers a different question — not "is
|
|
8
|
+
// enforcement broken?" but "is it about to break?" — so it compares each CLI
|
|
9
|
+
// against the sibling stable leg's last known state (peerStatePath).
|
|
6
10
|
const fs = require("fs");
|
|
7
|
-
const [, , resultsJson, statePath, model] = process.argv;
|
|
11
|
+
const [, , resultsJson, statePath, model, channel = "stable", peerStatePath = "", eligible = ""] = process.argv;
|
|
8
12
|
const results = JSON.parse(resultsJson);
|
|
13
|
+
const isBeta = channel !== "stable";
|
|
9
14
|
|
|
10
15
|
let prev = { clis: {}, lastRun: null };
|
|
11
16
|
try { prev = JSON.parse(fs.readFileSync(statePath, "utf8")); } catch {}
|
|
12
17
|
|
|
18
|
+
let peer = { clis: {} };
|
|
19
|
+
if (peerStatePath) { try { peer = JSON.parse(fs.readFileSync(peerStatePath, "utf8")); } catch {} }
|
|
20
|
+
|
|
13
21
|
const probesOf = (entry) => (entry ? (entry.probes || entry) : null); // compat old flat schema
|
|
14
22
|
const statusOf = (probes) => {
|
|
15
23
|
const v = Object.values(probes || {});
|
|
@@ -23,16 +31,41 @@ const emo = { green: "🟢", yellow: "🟡", red: "🔴", grey: "⚪", error: "
|
|
|
23
31
|
const rank = { green: 0, grey: 1, yellow: 1, error: 1, red: 2 };
|
|
24
32
|
|
|
25
33
|
const now = new Date().toISOString().replace(/\.\d+Z$/, "Z");
|
|
26
|
-
const
|
|
34
|
+
const title = isBeta
|
|
35
|
+
? `🔮 *failproofai CLI pre-release watch* · ${now} · channel=${channel} · model=${model}`
|
|
36
|
+
: `🧪 *failproofai CLI integration tests* · ${now} · model=${model}`;
|
|
37
|
+
const lines = [title];
|
|
27
38
|
let worst = "green";
|
|
28
|
-
const breaks = [], recoveries = [];
|
|
39
|
+
const breaks = [], recoveries = [], incoming = [];
|
|
40
|
+
|
|
41
|
+
// A beta CLI counts as "incoming breakage" only when the SAME CLI is green on the
|
|
42
|
+
// stable leg — otherwise we would report an already-broken CLI twice — and only
|
|
43
|
+
// after two consecutive non-green probes, so a broken alpha that gets reverted
|
|
44
|
+
// before release doesn't burn anyone's attention. Strike counts live in state.
|
|
45
|
+
const strikesOf = (cli) => (prev.clis && prev.clis[cli] && prev.clis[cli].strikes) || 0;
|
|
46
|
+
const newStrikes = {};
|
|
29
47
|
|
|
30
48
|
for (const r of results) {
|
|
31
49
|
const st = statusOf(r.probes);
|
|
32
50
|
const prevEntry = prev.clis && prev.clis[r.cli];
|
|
33
51
|
const prevSt = prevEntry ? statusOf(probesOf(prevEntry)) : null;
|
|
34
52
|
let mark = "";
|
|
35
|
-
if (
|
|
53
|
+
if (isBeta) {
|
|
54
|
+
const peerEntry = peer.clis && peer.clis[r.cli];
|
|
55
|
+
const peerSt = peerEntry ? statusOf(probesOf(peerEntry)) : null;
|
|
56
|
+
const strikes = st === "green" ? 0 : strikesOf(r.cli) + 1;
|
|
57
|
+
newStrikes[r.cli] = strikes;
|
|
58
|
+
if (r.gated) {
|
|
59
|
+
mark = ` 🔵 _gated: pre-release unchanged, last green_`;
|
|
60
|
+
} else if (st !== "green" && peerSt === "green") {
|
|
61
|
+
mark = strikes >= 2
|
|
62
|
+
? ` 🚨 *INCOMING* — green on stable, ${st === "red" ? "FAILING" : "not verifiable"} on ${channel} (${strikes} runs)`
|
|
63
|
+
: ` 👀 _first ${channel} miss; confirming next run_`;
|
|
64
|
+
if (strikes >= 2) incoming.push(r.cli);
|
|
65
|
+
} else if (st !== "green" && peerSt && peerSt !== "green") {
|
|
66
|
+
mark = ` ⏸️ _also ${peerSt} on stable — the stable leg owns this alarm_`;
|
|
67
|
+
}
|
|
68
|
+
} else if (r.gated) {
|
|
36
69
|
mark = ` 🔵 _gated: CLI v${r.version || "?"} + failproofai ${r.fpSha || "?"} unchanged, last green_`;
|
|
37
70
|
} else if (prevSt && prevSt !== "red" && st === "red") {
|
|
38
71
|
mark = " ⚠️ *BROKE*"; breaks.push(r.cli);
|
|
@@ -48,7 +81,19 @@ for (const r of results) {
|
|
|
48
81
|
|
|
49
82
|
const errored = results.filter((r) => statusOf(r.probes) === "error").map((r) => r.cli);
|
|
50
83
|
let hdr;
|
|
51
|
-
if (
|
|
84
|
+
if (isBeta) {
|
|
85
|
+
// Coverage is stated explicitly and always: only some vendors publish a
|
|
86
|
+
// pre-release ref, and "beta: all green" must never read as assurance for the
|
|
87
|
+
// CLIs that were never probed at all. The denominator is how many are ELIGIBLE
|
|
88
|
+
// (passed in by run.sh), not 12 — a targeted run must not imply the CLIs it
|
|
89
|
+
// skipped have no pre-release ref, and hardcoding the eligible list here would
|
|
90
|
+
// be a third copy to drift against.
|
|
91
|
+
const total = Number(eligible) || results.length;
|
|
92
|
+
const cov = `_watching ${results.length}/${total} CLIs that publish a pre-release ref (of 12 total)_`;
|
|
93
|
+
hdr = incoming.length
|
|
94
|
+
? `🚨 *INCOMING BREAKAGE*: ${incoming.join(", ")} — broken on ${channel}, still green on stable. Fix before it ships.\n${cov}`
|
|
95
|
+
: `🔮 no incoming breakage detected on ${channel}\n${cov}`;
|
|
96
|
+
} else if (breaks.length) hdr = `🔴 *ENFORCEMENT BROKEN*: ${breaks.join(", ")} — investigate now`;
|
|
52
97
|
// Any CLI still red dominates the header — a recovery elsewhere must not mask it.
|
|
53
98
|
else if (worst === "red") hdr = "🔴 still broken (unchanged)";
|
|
54
99
|
else if (recoveries.length) hdr = `✅ recovered: ${recoveries.join(", ")}`;
|
|
@@ -66,6 +111,7 @@ for (const r of results) {
|
|
|
66
111
|
probes: r.probes,
|
|
67
112
|
version: r.version || (prevEntry && prevEntry.version) || null,
|
|
68
113
|
fpSha: r.fpSha || (prevEntry && prevEntry.fpSha) || null,
|
|
114
|
+
...(isBeta ? { strikes: newStrikes[r.cli] || 0 } : {}),
|
|
69
115
|
};
|
|
70
116
|
}
|
|
71
117
|
try { fs.writeFileSync(statePath, JSON.stringify(newState, null, 2)); }
|
|
@@ -30,7 +30,29 @@ ENVFILE="${CANARY_ENVFILE:?CANARY_ENVFILE (docker --env-file with gateway creds)
|
|
|
30
30
|
MODEL="${CANARY_LLM_MODEL:-deepseek-v4-pro}"
|
|
31
31
|
GATED="${CANARY_VERSION_GATED-all}"
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
CHANNEL="${CANARY_CHANNEL:-stable}"
|
|
34
|
+
# Sibling leg's state file, for the cross-leg comparison (see report.js). The beta
|
|
35
|
+
# leg needs to know whether a CLI is green on stable: "stable green + beta not
|
|
36
|
+
# green" is the incoming-breakage signal, and it is the ONLY way to distinguish
|
|
37
|
+
# "the vendor is about to break us" from "this CLI is broken for everyone already".
|
|
38
|
+
PEER_STATE="${CANARY_PEER_STATE:-}"
|
|
39
|
+
|
|
40
|
+
# The CLIs with a public pre-release ref — kept in sync with the beta refs in
|
|
41
|
+
# install-clis.sh (__tests__/integration-suite/channel-refs.test.ts asserts the two
|
|
42
|
+
# lists agree, since a silent drift here would look like coverage while probing
|
|
43
|
+
# nothing). Its LENGTH is also the honest denominator for the beta report: a
|
|
44
|
+
# targeted run (`run.sh cursor`) must not imply the CLIs it skipped have no
|
|
45
|
+
# pre-release ref.
|
|
46
|
+
BETA_CLIS=(claude codex copilot cursor goose openclaw)
|
|
47
|
+
|
|
48
|
+
CLIS=("$@")
|
|
49
|
+
if [ ${#CLIS[@]} -eq 0 ]; then
|
|
50
|
+
if [ "$CHANNEL" = stable ]; then
|
|
51
|
+
CLIS=(claude codex copilot cursor factory devin antigravity goose opencode pi hermes openclaw)
|
|
52
|
+
else
|
|
53
|
+
CLIS=("${BETA_CLIS[@]}")
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
34
56
|
|
|
35
57
|
# failproofai main HEAD — the second gate dimension. Prefer the value the workflow
|
|
36
58
|
# computed; fall back to git in the checkout.
|
|
@@ -72,14 +94,33 @@ for cli in "${CLIS[@]}"; do
|
|
|
72
94
|
vj="$(node -e 'const st=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));const p=st.clis[process.argv[2]];process.stdout.write(JSON.stringify({cli:process.argv[2],probes:p.probes||p,version:p.version,fpSha:p.fpSha,gated:true}))' "$STATE" "$cli")"
|
|
73
95
|
else
|
|
74
96
|
echo ">> probing $cli ..." >&2
|
|
75
|
-
|
|
97
|
+
# Spool to a file rather than a shell variable: a probe's output is a full agent
|
|
98
|
+
# transcript from up to 6 CLI invocations, and a stuck or noisy client could make it
|
|
99
|
+
# arbitrarily large. Only the verdict line and a 20-line tail are ever needed.
|
|
100
|
+
out_file="$(mktemp)"
|
|
101
|
+
run_probe "$cli" > "$out_file"
|
|
102
|
+
vj="$(sed -n 's/^VERDICT_JSON //p' "$out_file" | tail -1)"
|
|
103
|
+
# Echo the probe tail whenever the verdict is not a clean pass. Without this the
|
|
104
|
+
# ONLY thing that survived a probe was its VERDICT_JSON line, so a yellow/red run
|
|
105
|
+
# said WHAT broke and never WHY — and re-running told you no more, because the
|
|
106
|
+
# cause (a vendor error message) was discarded both times. Diagnosing codex's
|
|
107
|
+
# 0.145.0 regression needed a full local reproduction purely for want of these
|
|
108
|
+
# lines. Safe in a public log: every credential here is a registered Actions
|
|
109
|
+
# secret, so GitHub masks it on the way out.
|
|
110
|
+
case "$vj" in
|
|
111
|
+
*FAIL*|*INCONCLUSIVE*|*ERROR*|"")
|
|
112
|
+
echo "── $cli probe output (tail) ──" >&2
|
|
113
|
+
tail -20 "$out_file" >&2
|
|
114
|
+
echo "── end $cli ──" >&2 ;;
|
|
115
|
+
esac
|
|
116
|
+
rm -f "$out_file"
|
|
76
117
|
[ -z "$vj" ] && vj="{\"cli\":\"$cli\",\"probes\":{}}"
|
|
77
118
|
vj="$(node -e 'const v=JSON.parse(process.argv[1]);v.version=process.argv[2]||null;v.fpSha=process.argv[3]||null;process.stdout.write(JSON.stringify(v))' "$vj" "$cur_ver" "$FP_SHA")"
|
|
78
119
|
fi
|
|
79
120
|
results="$(node -e 'const a=JSON.parse(process.argv[1]);a.push(JSON.parse(process.argv[2]));process.stdout.write(JSON.stringify(a))' "$results" "$vj")"
|
|
80
121
|
done
|
|
81
122
|
|
|
82
|
-
report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL")"
|
|
123
|
+
report="$(node "$HERE/report.js" "$results" "$STATE" "$MODEL" "$CHANNEL" "$PEER_STATE" "${#BETA_CLIS[@]}")"
|
|
83
124
|
echo "════ report ════" >&2; printf '%s\n' "$report" >&2; echo "════════════════" >&2
|
|
84
125
|
printf '%s\n' "$report" # also to stdout for the workflow log / artifact
|
|
85
126
|
|
|
@@ -94,6 +135,17 @@ else
|
|
|
94
135
|
echo "(no CANARY_SLACK_WEBHOOK set — report not posted)" >&2
|
|
95
136
|
fi
|
|
96
137
|
|
|
138
|
+
# The beta leg is ADVISORY and must never fail the job. It probes vendor
|
|
139
|
+
# pre-release builds, which are broken-by-nature often enough that gating CI on
|
|
140
|
+
# them would train everyone to ignore a red run — and the thing it reports is
|
|
141
|
+
# "this WILL break on release", not "this IS broken", which is not a reason to
|
|
142
|
+
# stop the world. The report is emitted and posted either way, so the signal is
|
|
143
|
+
# never lost; escalation is the report's job, not the exit code's.
|
|
144
|
+
if [ "$CHANNEL" != stable ]; then
|
|
145
|
+
echo "(channel=$CHANNEL — advisory only, not failing the job)" >&2
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
97
149
|
# Fail the job when any probe reported a hard FAIL (broken enforcement) so this
|
|
98
150
|
# scheduled check actually blocks regressions. ERROR (vendor quota/auth) and
|
|
99
151
|
# INCONCLUSIVE (model didn't attempt the tool) are NOT failures — they mean
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes profile discovery — shared by BOTH pillars.
|
|
3
|
+
*
|
|
4
|
+
* A Hermes "profile" is not a column or a flag: it is a whole separate Hermes
|
|
5
|
+
* home directory, each with its own `config.yaml`, `.env`, `SOUL.md`, and
|
|
6
|
+
* `state.db`. The default profile lives at `~/.hermes`; every other profile at
|
|
7
|
+
* `~/.hermes/profiles/<name>/`. Selection is `hermes -p <name>`, a generated
|
|
8
|
+
* `~/.local/bin/<name>` alias that exports `HERMES_HOME`, or a sticky default
|
|
9
|
+
* recorded in `<root>/active_profile`.
|
|
10
|
+
*
|
|
11
|
+
* Upstream's own contributor guide warns that hardcoding `~/.hermes` breaks
|
|
12
|
+
* profiles — which is exactly what both pillars used to do:
|
|
13
|
+
* • audit → read one state.db, so non-default profiles were INVISIBLE;
|
|
14
|
+
* • enforcement→ wrote one config.yaml, so non-default profiles ran UNHOOKED.
|
|
15
|
+
* Both now enumerate through here.
|
|
16
|
+
*
|
|
17
|
+
* Kept deliberately dependency-light (node:fs / node:os / node:path only) so
|
|
18
|
+
* `src/hooks/integrations.ts` can import it without pulling the sql.js reader
|
|
19
|
+
* into the hook hot path.
|
|
20
|
+
*
|
|
21
|
+
* Home override: set `HERMES_HOME` (Hermes's own env var — respected here so a
|
|
22
|
+
* profile-scoped shell and failproofai agree on what "all profiles" means).
|
|
23
|
+
*/
|
|
24
|
+
import { readdirSync } from "node:fs";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
27
|
+
|
|
28
|
+
/** Directory under the Hermes root that holds non-default profiles. */
|
|
29
|
+
const PROFILES_DIR = "profiles";
|
|
30
|
+
|
|
31
|
+
/** Name we give the root home (`~/.hermes`), which Hermes itself leaves unnamed. */
|
|
32
|
+
export const HERMES_DEFAULT_PROFILE = "default";
|
|
33
|
+
|
|
34
|
+
export interface HermesProfile {
|
|
35
|
+
/** Profile name — `"default"` for the root home, else the directory name. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Absolute HERMES_HOME for this profile (holds config.yaml + state.db). */
|
|
38
|
+
home: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Hermes ROOT home — the directory that owns `profiles/`.
|
|
43
|
+
*
|
|
44
|
+
* `HERMES_HOME` may point AT a profile (`<root>/profiles/<name>`), because
|
|
45
|
+
* that's what the per-profile alias wrapper exports. We climb back to `<root>`
|
|
46
|
+
* in that case so discovery still sees every sibling profile — mirroring what
|
|
47
|
+
* upstream does so `profile list` can see them all.
|
|
48
|
+
*/
|
|
49
|
+
export function hermesRoot(): string {
|
|
50
|
+
const env = (process.env.HERMES_HOME || "").trim();
|
|
51
|
+
if (env) {
|
|
52
|
+
const home = resolve(env);
|
|
53
|
+
const parent = dirname(home);
|
|
54
|
+
if (basename(parent) === PROFILES_DIR) return dirname(parent);
|
|
55
|
+
return home;
|
|
56
|
+
}
|
|
57
|
+
return join(homedir(), ".hermes");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Every Hermes profile on disk: the root home first (as `"default"`), then each
|
|
62
|
+
* `<root>/profiles/<name>/` in name order.
|
|
63
|
+
*
|
|
64
|
+
* Fail-open — a missing or unreadable `profiles/` dir just means "default only",
|
|
65
|
+
* which is the single-profile install everyone starts with.
|
|
66
|
+
*
|
|
67
|
+
* A profile directory literally named `default` would collide with the root's
|
|
68
|
+
* reserved name; the root wins and the directory is skipped (dedup by name keeps
|
|
69
|
+
* grouping keys unique and the outcome deterministic).
|
|
70
|
+
*/
|
|
71
|
+
export function listHermesProfiles(): HermesProfile[] {
|
|
72
|
+
const root = hermesRoot();
|
|
73
|
+
const out: HermesProfile[] = [{ name: HERMES_DEFAULT_PROFILE, home: root }];
|
|
74
|
+
const seen = new Set<string>([HERMES_DEFAULT_PROFILE]);
|
|
75
|
+
|
|
76
|
+
let names: string[];
|
|
77
|
+
try {
|
|
78
|
+
names = readdirSync(join(root, PROFILES_DIR), { withFileTypes: true })
|
|
79
|
+
// Symlinked profile dirs are legitimate, and `isDirectory()` is false for them.
|
|
80
|
+
.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."))
|
|
81
|
+
.map((e) => e.name)
|
|
82
|
+
.sort();
|
|
83
|
+
} catch {
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const name of names) {
|
|
88
|
+
if (seen.has(name)) continue;
|
|
89
|
+
seen.add(name);
|
|
90
|
+
out.push({ name, home: join(root, PROFILES_DIR, name) });
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── (profile, source) naming ────────────────────────────────────────────────
|
|
96
|
+
//
|
|
97
|
+
// Gateway sessions have no cwd to group by, so the dashboard and the audit
|
|
98
|
+
// synthesize a project per (profile, source). Lives here — next to the profile
|
|
99
|
+
// list the parser needs — so lib/hermes-sessions.ts and lib/hermes-projects.ts
|
|
100
|
+
// can both use it without importing each other.
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Encoded project name for a (profile, source) pair.
|
|
104
|
+
*
|
|
105
|
+
* `decodeFolderName()` renders every `-` as `/`, so `hermes-work-slack` shows up
|
|
106
|
+
* as `hermes/work/slack` in the projects panel with no UI change.
|
|
107
|
+
*/
|
|
108
|
+
export function hermesProjectName(profile: string, source: string): string {
|
|
109
|
+
return `hermes-${profile}-${source}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Machine-readable grouping key, shown under the project name. */
|
|
113
|
+
export function hermesProjectPath(profile: string, source: string): string {
|
|
114
|
+
return `hermes:${profile}:${source}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface HermesNameSplit {
|
|
118
|
+
profile: string;
|
|
119
|
+
source: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Every way `hermes-<profile>-<source>` could split, best guess first.
|
|
124
|
+
*
|
|
125
|
+
* Profile names may contain `-`, so the slug is never split blindly: each
|
|
126
|
+
* candidate is a profile that actually exists on disk, tried longest-first.
|
|
127
|
+
* Length alone is not decisive though — with profiles `work` and `work-slack`,
|
|
128
|
+
* `hermes-work-slack-dev` could be `work-slack`+`dev` or `work`+`slack-dev` —
|
|
129
|
+
* so callers walk the candidates and take the first that owns real sessions.
|
|
130
|
+
*
|
|
131
|
+
* The last candidate is always the legacy `hermes-<source>` form attributed to
|
|
132
|
+
* the default profile, keeping links made before profile support alive.
|
|
133
|
+
*/
|
|
134
|
+
export function hermesProjectNameCandidates(name: string): HermesNameSplit[] {
|
|
135
|
+
if (!name.startsWith("hermes-")) return [];
|
|
136
|
+
const rest = name.slice("hermes-".length);
|
|
137
|
+
if (!rest) return [];
|
|
138
|
+
|
|
139
|
+
const out: HermesNameSplit[] = [];
|
|
140
|
+
const profiles = listHermesProfiles()
|
|
141
|
+
.map((p) => p.name)
|
|
142
|
+
.sort((a, b) => b.length - a.length);
|
|
143
|
+
for (const profile of profiles) {
|
|
144
|
+
const prefix = `${profile}-`;
|
|
145
|
+
if (rest.startsWith(prefix) && rest.length > prefix.length) {
|
|
146
|
+
out.push({ profile, source: rest.slice(prefix.length) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
out.push({ profile: HERMES_DEFAULT_PROFILE, source: rest });
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The best-guess split for a project name — `null` if it isn't a Hermes one. */
|
|
154
|
+
export function parseHermesProjectName(name: string): HermesNameSplit | null {
|
|
155
|
+
return hermesProjectNameCandidates(name)[0] ?? null;
|
|
156
|
+
}
|