failproofai 0.0.14-beta.2 → 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]__0s89hoe._.js → [root-of-the-server]__02r5bgf._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__09v9cwb._.js → [root-of-the-server]__04o-kw7._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qeuy9c._.js → [root-of-the-server]__0e8sjqm._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1tkjqka._.js → [root-of-the-server]__0lkzqax._.js} +3 -3
- 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]__03lv-pe._.js → [root-of-the-server]__0u02miy._.js} +3 -3
- 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]__015_i4t._.js → [root-of-the-server]__1zqz4v8._.js} +3 -3
- 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_06xsmte.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/{2llkdafnaejw_.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/{388hgfqye67xa.js → 1qzsbft-jtnf8.js} +1 -1
- 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/{29zkom4s9de2d.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/{1gg-8e9q_m__9.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/log-viewer/entry-row.tsx +8 -3
- package/.next/standalone/app/components/project-list.tsx +229 -42
- package/.next/standalone/app/components/raw-log-viewer.tsx +33 -15
- package/.next/standalone/app/policies/hooks-client.tsx +113 -15
- 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/entry-keys.ts +36 -0
- 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 +9 -7
- package/.next/standalone/pi-extension/index.ts +38 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +623 -308
- package/lib/entry-keys.ts +36 -0
- 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 +9 -7
- 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]__030k0c6._.js +0 -3
- 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]__0twi4fj._.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/_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/0h-6ymo9v86b0.js +0 -6
- package/.next/standalone/.next/static/chunks/0pndjhcxfu-0t.js +0 -1
- package/.next/standalone/.next/static/chunks/1-33f82_l32or.js +0 -1
- package/.next/standalone/.next/static/chunks/1bg_g3g359c_d.js +0 -1
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +0 -2
- package/.next/standalone/.next/static/chunks/396764jgd7-9j.js +0 -1
- package/.next/standalone/.next/static/chunks/3w7g0n0iwwnbs.js +0 -1
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{YKxI22hC-F_nHp7MEKfb1 → QFmU1xD4R9N_HVfwccP6A}/_ssgManifest.js +0 -0
package/lib/openclaw-projects.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* OpenClaw (openclaw gateway) session enumeration — AUDIT-ONLY.
|
|
3
3
|
*
|
|
4
4
|
* Surfaces the on-disk transcripts (agents/<agentId>/sessions/<uuid>.jsonl) as
|
|
5
|
-
* synthetic dashboard "projects" grouped by agentId. The per-agent
|
|
5
|
+
* synthetic dashboard "projects" grouped by (agentId, channel). The per-agent
|
|
6
6
|
* `sessions.json` index maps sessionKey → {sessionId, timestamps}; we read it to
|
|
7
7
|
* recover the sessionKey (which encodes the channel for gateway sessions) and a
|
|
8
8
|
* reliable last-activity time. Verified live against openclaw v2026.7.1.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { readFileSync } from "node:fs";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { runtimeCache } from "./runtime-cache";
|
|
13
|
-
import { listOpenClawTranscripts, openclawHome } from "./openclaw-sessions";
|
|
13
|
+
import { listOpenClawAgents, listOpenClawTranscripts, openclawHome } from "./openclaw-sessions";
|
|
14
14
|
import type { ProjectFolder, SessionFile } from "./projects";
|
|
15
15
|
import { formatDate } from "./format-date";
|
|
16
16
|
|
|
@@ -26,6 +26,8 @@ export interface OpenClawSessionRef {
|
|
|
26
26
|
* gateway-metadata columns. */
|
|
27
27
|
chatId?: string;
|
|
28
28
|
chatType?: string;
|
|
29
|
+
/** Absolute path to the `<uuid>.jsonl` transcript this ref came from. */
|
|
30
|
+
transcriptPath: string;
|
|
29
31
|
mtimeMs: number;
|
|
30
32
|
sizeBytes: number;
|
|
31
33
|
}
|
|
@@ -100,6 +102,7 @@ export async function getOpenClawSessions(): Promise<OpenClawSessionRef[]> {
|
|
|
100
102
|
label: meta?.label,
|
|
101
103
|
chatType: meta?.chatType,
|
|
102
104
|
chatId: meta?.chatId,
|
|
105
|
+
transcriptPath: t.transcriptPath,
|
|
103
106
|
mtimeMs: meta?.lastMs ?? t.mtimeMs,
|
|
104
107
|
sizeBytes: t.sizeBytes,
|
|
105
108
|
});
|
|
@@ -112,28 +115,95 @@ export const getCachedOpenClawSessions = runtimeCache(getOpenClawSessions, 2);
|
|
|
112
115
|
|
|
113
116
|
// ── Dashboard history browser (projects list + project-detail sessions) ──
|
|
114
117
|
|
|
118
|
+
/** Encoded project name for an (agent, channel) pair. */
|
|
119
|
+
export function openClawProjectName(agentId: string, channel: string): string {
|
|
120
|
+
return `openclaw-${agentId}-${channel}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Machine-readable grouping key, shown under the project name. */
|
|
124
|
+
export function openClawProjectPath(agentId: string, channel: string): string {
|
|
125
|
+
return `openclaw:${agentId}:${channel}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface OpenClawNameSplit {
|
|
129
|
+
agentId: string | null;
|
|
130
|
+
channel: string;
|
|
131
|
+
}
|
|
132
|
+
|
|
115
133
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
*
|
|
134
|
+
* Every way `openclaw-<agentId>-<channel>` could split, best guess first.
|
|
135
|
+
*
|
|
136
|
+
* Both an agentId and a channel may contain `-`, so the slug is never split
|
|
137
|
+
* blindly: each candidate is an agent id that actually exists on disk, tried
|
|
138
|
+
* longest-first. That alone is not sufficient — with agents `main` and
|
|
139
|
+
* `main-bot` on disk, `openclaw-main-bot-telegram` could be `main-bot`+`telegram`
|
|
140
|
+
* OR `main`+`bot-telegram`, and length alone picks the wrong one whenever the
|
|
141
|
+
* shorter agent owns it. So callers walk the candidates and take the first that
|
|
142
|
+
* matches real sessions.
|
|
143
|
+
*
|
|
144
|
+
* The last candidate is always the legacy channel-only `openclaw-<channel>`
|
|
145
|
+
* form (`agentId: null`), so links shared before agents became part of the name
|
|
146
|
+
* keep resolving — to every agent on that channel, which is what they meant.
|
|
147
|
+
*/
|
|
148
|
+
export function openClawProjectNameCandidates(name: string): OpenClawNameSplit[] {
|
|
149
|
+
if (!name.startsWith("openclaw-")) return [];
|
|
150
|
+
const rest = name.slice("openclaw-".length);
|
|
151
|
+
if (!rest) return [];
|
|
152
|
+
|
|
153
|
+
const out: OpenClawNameSplit[] = [];
|
|
154
|
+
const agents = listOpenClawAgents().sort((a, b) => b.length - a.length);
|
|
155
|
+
for (const agentId of agents) {
|
|
156
|
+
const prefix = `${agentId}-`;
|
|
157
|
+
if (rest.startsWith(prefix) && rest.length > prefix.length) {
|
|
158
|
+
out.push({ agentId, channel: rest.slice(prefix.length) });
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
out.push({ agentId: null, channel: rest });
|
|
162
|
+
return out;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** The best-guess split for a project name — `null` if it isn't an OpenClaw one. */
|
|
166
|
+
export function parseOpenClawProjectName(name: string): OpenClawNameSplit | null {
|
|
167
|
+
return openClawProjectNameCandidates(name)[0] ?? null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Surface OpenClaw sessions as synthetic "projects" grouped by **(agent,
|
|
172
|
+
* channel)** — gateway sessions have no host repo to group by.
|
|
173
|
+
*
|
|
174
|
+
* Agent is the OUTER axis because it is the stable one: `agentId` is a
|
|
175
|
+
* directory name, whereas `channel` is derived from the LAST channel a session
|
|
176
|
+
* used. Grouping by channel alone (as this did before) also collapsed two
|
|
177
|
+
* different agents that both talk on Telegram into one row with one mixed
|
|
178
|
+
* session list — and disagreed with the audit adapter, which grouped by agent.
|
|
120
179
|
*/
|
|
121
180
|
export async function getOpenClawProjects(): Promise<ProjectFolder[]> {
|
|
122
181
|
const sessions = await getOpenClawSessions();
|
|
123
|
-
const
|
|
182
|
+
const groups = new Map<
|
|
183
|
+
string,
|
|
184
|
+
{ agentId: string; channel: string; latest: number; count: number }
|
|
185
|
+
>();
|
|
124
186
|
for (const s of sessions) {
|
|
125
|
-
|
|
187
|
+
const key = JSON.stringify([s.agentId, s.channel]);
|
|
188
|
+
const prev = groups.get(key);
|
|
189
|
+
if (prev) {
|
|
190
|
+
prev.latest = Math.max(prev.latest, s.mtimeMs);
|
|
191
|
+
prev.count += 1;
|
|
192
|
+
} else {
|
|
193
|
+
groups.set(key, { agentId: s.agentId, channel: s.channel, latest: s.mtimeMs, count: 1 });
|
|
194
|
+
}
|
|
126
195
|
}
|
|
127
196
|
const out: ProjectFolder[] = [];
|
|
128
|
-
for (const
|
|
197
|
+
for (const { agentId, channel, latest, count } of groups.values()) {
|
|
129
198
|
const lastModified = new Date(latest);
|
|
130
199
|
out.push({
|
|
131
|
-
name:
|
|
132
|
-
path:
|
|
200
|
+
name: openClawProjectName(agentId, channel),
|
|
201
|
+
path: openClawProjectPath(agentId, channel),
|
|
133
202
|
isDirectory: true,
|
|
134
203
|
lastModified,
|
|
135
204
|
lastModifiedFormatted: formatDate(lastModified),
|
|
136
205
|
cli: ["openclaw"],
|
|
206
|
+
sessionCount: count,
|
|
137
207
|
});
|
|
138
208
|
}
|
|
139
209
|
out.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
@@ -146,18 +216,41 @@ export interface OpenClawProjectByName {
|
|
|
146
216
|
}
|
|
147
217
|
|
|
148
218
|
/** Resolve the OpenClaw sessions for a synthetic project name
|
|
149
|
-
* (`openclaw-<channel>`), for the project-detail page. Session names
|
|
150
|
-
* human-readable `origin.label` (e.g. "Chetan (@chhhee10) id:…")
|
|
151
|
-
* the raw session key. */
|
|
219
|
+
* (`openclaw-<agentId>-<channel>`), for the project-detail page. Session names
|
|
220
|
+
* use the human-readable `origin.label` (e.g. "Chetan (@chhhee10) id:…")
|
|
221
|
+
* rather than the raw session key. */
|
|
152
222
|
export async function getOpenClawSessionsByEncodedName(
|
|
153
223
|
name: string,
|
|
154
224
|
): Promise<OpenClawProjectByName> {
|
|
155
|
-
|
|
156
|
-
|
|
225
|
+
const candidates = openClawProjectNameCandidates(name);
|
|
226
|
+
if (candidates.length === 0) return { cwd: null, sessions: [] };
|
|
227
|
+
|
|
157
228
|
const sessions = await getOpenClawSessions();
|
|
158
|
-
const
|
|
229
|
+
const matching = (split: OpenClawNameSplit) =>
|
|
230
|
+
sessions.filter(
|
|
231
|
+
(s) => s.channel === split.channel && (split.agentId === null || s.agentId === split.agentId),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Take the first split that actually owns sessions — length alone picks the
|
|
235
|
+
// wrong agent when one id is a hyphen-prefix of another. Falling back to the
|
|
236
|
+
// best guess keeps the page's labelling sensible when nothing matches.
|
|
237
|
+
let chosen = candidates[0];
|
|
238
|
+
let matched = matching(chosen);
|
|
239
|
+
if (matched.length === 0) {
|
|
240
|
+
for (const candidate of candidates.slice(1)) {
|
|
241
|
+
const hits = matching(candidate);
|
|
242
|
+
if (hits.length > 0) {
|
|
243
|
+
chosen = candidate;
|
|
244
|
+
matched = hits;
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const { agentId, channel } = chosen;
|
|
159
250
|
return {
|
|
160
|
-
cwd
|
|
251
|
+
// Legacy channel-only names keep their old cwd label, since they really do
|
|
252
|
+
// span every agent on that channel.
|
|
253
|
+
cwd: agentId === null ? `openclaw:${channel}` : openClawProjectPath(agentId, channel),
|
|
161
254
|
sessions: matched.map((s) => {
|
|
162
255
|
const lastModified = new Date(s.mtimeMs);
|
|
163
256
|
return {
|
package/lib/openclaw-sessions.ts
CHANGED
|
@@ -195,6 +195,26 @@ export interface OpenClawTranscriptFile {
|
|
|
195
195
|
sizeBytes: number;
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Agent ids configured under this OpenClaw home, in name order.
|
|
200
|
+
*
|
|
201
|
+
* An agentId is just a directory name (`main`, `weather-bot`) — OpenClaw
|
|
202
|
+
* validates nothing, so neither do we. Cheap on purpose: one readdir, no
|
|
203
|
+
* statting of transcripts, because the project-name parser calls this to
|
|
204
|
+
* longest-match an agent id out of a URL slug (agent ids may contain `-`, so
|
|
205
|
+
* splitting the slug is not an option).
|
|
206
|
+
*/
|
|
207
|
+
export function listOpenClawAgents(): string[] {
|
|
208
|
+
try {
|
|
209
|
+
return readdirSync(join(openclawHome(), "agents"), { withFileTypes: true })
|
|
210
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith("."))
|
|
211
|
+
.map((d) => d.name)
|
|
212
|
+
.sort();
|
|
213
|
+
} catch {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
198
218
|
/** Enumerate `agents/<agentId>/sessions/<uuid>.jsonl` transcripts, skipping the
|
|
199
219
|
* heavy `.trajectory.jsonl` OTel traces and pointer files. */
|
|
200
220
|
export function listOpenClawTranscripts(): OpenClawTranscriptFile[] {
|
package/lib/paths.ts
CHANGED
|
@@ -29,6 +29,42 @@ export function decodeFolderName(name: string): string {
|
|
|
29
29
|
return name.replace(/-/g, "/");
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Synthetic project path for gateway CLIs with no working directory —
|
|
34
|
+
* `hermes:<profile>:<source>`, `openclaw:<channel>`. Two or more leading letters
|
|
35
|
+
* so a Windows drive letter (`c:/code`) can never match.
|
|
36
|
+
*/
|
|
37
|
+
const SYNTHETIC_PROJECT_PATH = /^[a-z]{2,}:/;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Whether a project `path` is a synthetic gateway path rather than a real
|
|
41
|
+
* filesystem path — `hermes:<profile>:<source>`, `openclaw:<agent>:<channel>`.
|
|
42
|
+
*
|
|
43
|
+
* These are the only projects with a meaningful segment hierarchy, so this is
|
|
44
|
+
* also what decides which rows the projects panel renders as a folder tree.
|
|
45
|
+
*/
|
|
46
|
+
export function isSyntheticProjectPath(path?: string): boolean {
|
|
47
|
+
return !!path && SYNTHETIC_PROJECT_PATH.test(path);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Segments of a synthetic gateway path, outermost first. `[]` for real paths. */
|
|
51
|
+
export function syntheticPathSegments(path?: string): string[] {
|
|
52
|
+
return isSyntheticProjectPath(path) ? path!.split(":") : [];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Label to display for a project card.
|
|
57
|
+
*
|
|
58
|
+
* Filesystem-backed projects decode their folder name. Gateway projects must
|
|
59
|
+
* NOT: `decodeFolderName` turns every `-` into `/`, which splits a Hermes
|
|
60
|
+
* profile named `my-bot` into `my/bot`. Their `path` already carries the
|
|
61
|
+
* segments explicitly, so use it.
|
|
62
|
+
*/
|
|
63
|
+
export function projectDisplayName(name: string, path?: string): string {
|
|
64
|
+
if (isSyntheticProjectPath(path)) return path!.replace(/:/g, "/");
|
|
65
|
+
return decodeFolderName(name);
|
|
66
|
+
}
|
|
67
|
+
|
|
32
68
|
/**
|
|
33
69
|
* Encodes a filesystem path into a Claude-compatible project folder name.
|
|
34
70
|
* Inverse of `decodeFolderName`.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Folder tree for the projects panel.
|
|
3
|
+
*
|
|
4
|
+
* Gateway CLIs (Hermes, OpenClaw) are USER-scoped: their sessions have no
|
|
5
|
+
* working directory, so they can't sit in the cwd-based project model the other
|
|
6
|
+
* CLIs use. What they have instead is a hierarchy — which profile/agent, then
|
|
7
|
+
* which channel — and that hierarchy is already encoded in the synthetic
|
|
8
|
+
* `path` those producers emit (`hermes:work:slack`,
|
|
9
|
+
* `openclaw:weather-bot:telegram`).
|
|
10
|
+
*
|
|
11
|
+
* So this is a pure PRESENTATION transform over the flat `ProjectFolder[]` the
|
|
12
|
+
* server already returns: split those paths into nodes, leave every real
|
|
13
|
+
* filesystem project as a top-level leaf. Nothing about project `name`s
|
|
14
|
+
* changes, which is what keeps existing `/project/<name>` links, bookmarks and
|
|
15
|
+
* stored audit history resolving.
|
|
16
|
+
*
|
|
17
|
+
* Pure and dependency-free on purpose — `import type` only, so the client
|
|
18
|
+
* bundle never pulls in `lib/projects.ts`'s node:fs imports.
|
|
19
|
+
*/
|
|
20
|
+
import type { ProjectCli, ProjectFolder } from "./projects";
|
|
21
|
+
import { syntheticPathSegments } from "./paths";
|
|
22
|
+
|
|
23
|
+
export interface ProjectTreeNode {
|
|
24
|
+
/** Colon-joined segment path (`hermes:work`) — stable across renders, used as
|
|
25
|
+
* the React key and as the collapsed-state key. */
|
|
26
|
+
key: string;
|
|
27
|
+
/** The single segment this row shows (`work`), not the whole path. */
|
|
28
|
+
label: string;
|
|
29
|
+
/** 0 for top-level rows; each level below adds 1 (drives indentation). */
|
|
30
|
+
depth: number;
|
|
31
|
+
children: ProjectTreeNode[];
|
|
32
|
+
/** The real project, on rows that map to one. Leaves always have it; a folder
|
|
33
|
+
* row has it only if a project exists at exactly that path. Rows without one
|
|
34
|
+
* are pure grouping and must not link anywhere. */
|
|
35
|
+
project?: ProjectFolder;
|
|
36
|
+
/** Sessions beneath this row, summed. `undefined` when nothing underneath
|
|
37
|
+
* reported a count — rendered as nothing rather than as `0`, since the two
|
|
38
|
+
* mean very different things. */
|
|
39
|
+
sessionCount?: number;
|
|
40
|
+
/** How many real projects this row covers (1 for a leaf). Lets the caller
|
|
41
|
+
* paginate over top-level rows while still reporting a project range. */
|
|
42
|
+
projectCount: number;
|
|
43
|
+
/** Newest activity anywhere beneath this row. */
|
|
44
|
+
lastModified: Date;
|
|
45
|
+
lastModifiedFormatted?: string;
|
|
46
|
+
/** Union of the CLIs beneath this row, in first-seen order. */
|
|
47
|
+
cli: ProjectCli[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Builder {
|
|
51
|
+
key: string;
|
|
52
|
+
label: string;
|
|
53
|
+
depth: number;
|
|
54
|
+
children: Map<string, Builder>;
|
|
55
|
+
project?: ProjectFolder;
|
|
56
|
+
sessionCount?: number;
|
|
57
|
+
projectCount: number;
|
|
58
|
+
lastModified: Date;
|
|
59
|
+
lastModifiedFormatted?: string;
|
|
60
|
+
cli: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function makeBuilder(key: string, label: string, depth: number): Builder {
|
|
64
|
+
return {
|
|
65
|
+
key,
|
|
66
|
+
label,
|
|
67
|
+
depth,
|
|
68
|
+
children: new Map(),
|
|
69
|
+
projectCount: 0,
|
|
70
|
+
lastModified: new Date(0),
|
|
71
|
+
cli: [],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Fold a project's stats into a node — newest date wins, counts add, CLIs union. */
|
|
76
|
+
function absorb(node: Builder, folder: ProjectFolder): void {
|
|
77
|
+
if (folder.lastModified.getTime() > node.lastModified.getTime()) {
|
|
78
|
+
node.lastModified = folder.lastModified;
|
|
79
|
+
node.lastModifiedFormatted = folder.lastModifiedFormatted;
|
|
80
|
+
}
|
|
81
|
+
if (typeof folder.sessionCount === "number") {
|
|
82
|
+
node.sessionCount = (node.sessionCount ?? 0) + folder.sessionCount;
|
|
83
|
+
}
|
|
84
|
+
node.projectCount += 1;
|
|
85
|
+
for (const c of folder.cli) if (!node.cli.includes(c)) node.cli.push(c);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function finalize(node: Builder): ProjectTreeNode {
|
|
89
|
+
const children = [...node.children.values()]
|
|
90
|
+
.map(finalize)
|
|
91
|
+
.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
92
|
+
return {
|
|
93
|
+
key: node.key,
|
|
94
|
+
label: node.label,
|
|
95
|
+
depth: node.depth,
|
|
96
|
+
children,
|
|
97
|
+
project: node.project,
|
|
98
|
+
sessionCount: node.sessionCount,
|
|
99
|
+
projectCount: node.projectCount,
|
|
100
|
+
lastModified: node.lastModified,
|
|
101
|
+
lastModifiedFormatted: node.lastModifiedFormatted,
|
|
102
|
+
cli: node.cli as ProjectCli[],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Build the render tree for a list of projects (already filtered, sorted and
|
|
108
|
+
* paginated by the caller — this adds no policy of its own).
|
|
109
|
+
*
|
|
110
|
+
* Real filesystem projects become depth-0 leaves in input order-by-recency,
|
|
111
|
+
* exactly as the flat table showed them. Gateway projects nest under one node
|
|
112
|
+
* per path segment.
|
|
113
|
+
*/
|
|
114
|
+
export function buildProjectTree(folders: ProjectFolder[]): ProjectTreeNode[] {
|
|
115
|
+
const roots = new Map<string, Builder>();
|
|
116
|
+
|
|
117
|
+
for (const folder of folders) {
|
|
118
|
+
const segments = syntheticPathSegments(folder.path);
|
|
119
|
+
|
|
120
|
+
if (segments.length === 0) {
|
|
121
|
+
// Real filesystem project — a top-level leaf, keyed by name so two
|
|
122
|
+
// projects with an identical path (or none) can't collide.
|
|
123
|
+
const node = makeBuilder(`project:${folder.name}`, folder.name, 0);
|
|
124
|
+
node.project = folder;
|
|
125
|
+
absorb(node, folder);
|
|
126
|
+
roots.set(node.key, node);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let level = roots;
|
|
131
|
+
let key = "";
|
|
132
|
+
let node: Builder | undefined;
|
|
133
|
+
for (let depth = 0; depth < segments.length; depth++) {
|
|
134
|
+
const label = segments[depth];
|
|
135
|
+
key = depth === 0 ? label : `${key}:${label}`;
|
|
136
|
+
let next = level.get(key);
|
|
137
|
+
if (!next) {
|
|
138
|
+
next = makeBuilder(key, label, depth);
|
|
139
|
+
level.set(key, next);
|
|
140
|
+
}
|
|
141
|
+
// Every ancestor rolls up the project's stats, so a collapsed folder still
|
|
142
|
+
// reports the freshest activity and total sessions underneath it.
|
|
143
|
+
absorb(next, folder);
|
|
144
|
+
node = next;
|
|
145
|
+
level = next.children;
|
|
146
|
+
}
|
|
147
|
+
// The deepest segment IS this project. Guard against two projects claiming
|
|
148
|
+
// the same path (shouldn't happen post-merge, but silently losing one would
|
|
149
|
+
// be worse than keeping the first).
|
|
150
|
+
if (node && !node.project) node.project = folder;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return [...roots.values()]
|
|
154
|
+
.map(finalize)
|
|
155
|
+
.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Flatten the tree to the rows actually rendered, honouring collapsed state.
|
|
160
|
+
*
|
|
161
|
+
* We track COLLAPSED rather than expanded keys deliberately: a profile or agent
|
|
162
|
+
* that appears for the first time should be visible, not hidden behind a click
|
|
163
|
+
* the user doesn't know to make.
|
|
164
|
+
*/
|
|
165
|
+
export function visibleTreeRows(
|
|
166
|
+
nodes: ProjectTreeNode[],
|
|
167
|
+
isCollapsed: (key: string) => boolean,
|
|
168
|
+
): ProjectTreeNode[] {
|
|
169
|
+
const out: ProjectTreeNode[] = [];
|
|
170
|
+
const walk = (list: ProjectTreeNode[]) => {
|
|
171
|
+
for (const node of list) {
|
|
172
|
+
out.push(node);
|
|
173
|
+
if (node.children.length > 0 && !isCollapsed(node.key)) walk(node.children);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
walk(nodes);
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Keys of every node that has children — i.e. every collapsible row. */
|
|
181
|
+
export function collapsibleKeys(nodes: ProjectTreeNode[]): string[] {
|
|
182
|
+
const out: string[] = [];
|
|
183
|
+
const walk = (list: ProjectTreeNode[]) => {
|
|
184
|
+
for (const node of list) {
|
|
185
|
+
if (node.children.length > 0) {
|
|
186
|
+
out.push(node.key);
|
|
187
|
+
walk(node.children);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
walk(nodes);
|
|
192
|
+
return out;
|
|
193
|
+
}
|
package/lib/projects.ts
CHANGED
|
@@ -29,6 +29,20 @@ export interface ProjectFolder {
|
|
|
29
29
|
* the same cwd has transcripts from more than one CLI; rendered as badges.
|
|
30
30
|
*/
|
|
31
31
|
cli: ProjectCli[];
|
|
32
|
+
/**
|
|
33
|
+
* How many sessions this project holds.
|
|
34
|
+
*
|
|
35
|
+
* Only the GATEWAY producers (Hermes, OpenClaw) fill this in, because only
|
|
36
|
+
* they render as a folder tree where a collapsed row has to say how much is
|
|
37
|
+
* underneath it — and for them the count is free, since their grouping loop
|
|
38
|
+
* already visits every session. The cwd-based CLIs leave it undefined and the
|
|
39
|
+
* UI renders nothing, rather than a `0` that would read as "empty".
|
|
40
|
+
*
|
|
41
|
+
* Deliberately NOT filled for Claude: counting there needs an extra readdir
|
|
42
|
+
* per project folder, and a number that exists for some CLIs and not others
|
|
43
|
+
* would sum to a wrong-but-plausible total on a merged row.
|
|
44
|
+
*/
|
|
45
|
+
sessionCount?: number;
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
export interface SessionFile {
|
|
@@ -126,6 +140,14 @@ function mergeProjectFolders(...sources: ProjectFolder[][]): ProjectFolder[] {
|
|
|
126
140
|
cli: mergedCli,
|
|
127
141
|
lastModified: newer.lastModified,
|
|
128
142
|
lastModifiedFormatted: newer.lastModifiedFormatted,
|
|
143
|
+
// Spreading `existing` alone would silently keep only the FIRST source's
|
|
144
|
+
// count for a name several CLIs contribute to, so sum explicitly. Stays
|
|
145
|
+
// undefined unless at least one side reported a count — `0` would claim
|
|
146
|
+
// "no sessions" for producers that simply don't count.
|
|
147
|
+
sessionCount:
|
|
148
|
+
existing.sessionCount === undefined && f.sessionCount === undefined
|
|
149
|
+
? undefined
|
|
150
|
+
: (existing.sessionCount ?? 0) + (f.sessionCount ?? 0),
|
|
129
151
|
});
|
|
130
152
|
}
|
|
131
153
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.15-beta.0",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -71,11 +71,11 @@
|
|
|
71
71
|
"access": "public"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@anthropic-ai/sdk": "^0.
|
|
74
|
+
"@anthropic-ai/sdk": "^0.115.0",
|
|
75
75
|
"@mdx-js/mdx": "^3.1.1",
|
|
76
76
|
"@tailwindcss/postcss": "^4.3.1",
|
|
77
77
|
"@tanstack/react-virtual": "^3.14.3",
|
|
78
|
-
"@testing-library/jest-dom": "^
|
|
78
|
+
"@testing-library/jest-dom": "^7.0.0",
|
|
79
79
|
"@testing-library/react": "^16.3.2",
|
|
80
80
|
"@testing-library/user-event": "^14.6.1",
|
|
81
81
|
"@types/node": "26.1.1",
|
|
@@ -85,9 +85,9 @@
|
|
|
85
85
|
"clsx": "^2.1.1",
|
|
86
86
|
"eslint": "^10.5.0",
|
|
87
87
|
"eslint-config-next": "^16.2.9",
|
|
88
|
-
"jsdom": "^
|
|
88
|
+
"jsdom": "^30.0.0",
|
|
89
89
|
"lucide-react": "^1.18.0",
|
|
90
|
-
"next": "^16.2.
|
|
90
|
+
"next": "^16.2.11",
|
|
91
91
|
"react": "^19.2.4",
|
|
92
92
|
"react-dom": "^19.2.4",
|
|
93
93
|
"tailwind-merge": "^3.4.0",
|
|
@@ -103,9 +103,11 @@
|
|
|
103
103
|
"yaml": "^2.9.0"
|
|
104
104
|
},
|
|
105
105
|
"overrides": {
|
|
106
|
-
"postcss": "8.5.
|
|
106
|
+
"postcss": "8.5.23",
|
|
107
107
|
"eslint-plugin-react-hooks": "7.0.1",
|
|
108
108
|
"vite": "8.0.16",
|
|
109
|
-
"undici": "7.28.0"
|
|
109
|
+
"undici": "7.28.0",
|
|
110
|
+
"brace-expansion": "5.0.8",
|
|
111
|
+
"sharp": "0.35.0"
|
|
110
112
|
}
|
|
111
113
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -347,6 +347,10 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
347
347
|
cwd: resolveCwd(e.cwd),
|
|
348
348
|
hook_event_name: "PreToolUse",
|
|
349
349
|
});
|
|
350
|
+
// `ToolCallEventResult` is `{block?, reason?}` (extensions/types.d.ts:766).
|
|
351
|
+
// agent-loop.ts branches on `beforeResult?.block`, so this is the shape that
|
|
352
|
+
// stops the tool. Do NOT put the user_bash full-replacement shape here — it
|
|
353
|
+
// has no `block` field, so the call would sail straight through.
|
|
350
354
|
if (decision.block) return { block: true, reason: decision.reason };
|
|
351
355
|
return undefined;
|
|
352
356
|
});
|
|
@@ -361,12 +365,38 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
361
365
|
cwd: resolveCwd(e.cwd),
|
|
362
366
|
hook_event_name: "PreToolUse",
|
|
363
367
|
});
|
|
364
|
-
|
|
368
|
+
// `UserBashEventResult` is `{operations?, result?: BashResult}` — there is
|
|
369
|
+
// no `block` field, so the `{block:true}` we used to return matched nothing
|
|
370
|
+
// and the command ran anyway. The supported way to stop it is a FULL
|
|
371
|
+
// REPLACEMENT result: the extension declares it handled execution, so Pi
|
|
372
|
+
// uses this instead of running the command. Verified against the installed
|
|
373
|
+
// pi-coding-agent d.ts (extensions/types.d.ts:772, core/bash-executor.d.ts:15).
|
|
374
|
+
if (decision.block) {
|
|
375
|
+
return {
|
|
376
|
+
result: {
|
|
377
|
+
output: decision.reason ?? "Blocked by failproofai",
|
|
378
|
+
exitCode: 1,
|
|
379
|
+
cancelled: false,
|
|
380
|
+
truncated: false,
|
|
381
|
+
},
|
|
382
|
+
};
|
|
383
|
+
}
|
|
365
384
|
return undefined;
|
|
366
385
|
});
|
|
367
386
|
|
|
368
|
-
// input → UserPromptSubmit.
|
|
369
|
-
//
|
|
387
|
+
// input → UserPromptSubmit.
|
|
388
|
+
//
|
|
389
|
+
// `InputEventResult` is `{action:"continue"|"transform"|"handled"}`
|
|
390
|
+
// (extensions/types.d.ts:629) — there is NO `block` field, so the
|
|
391
|
+
// `{block:true}` we used to return matched neither branch and every prompt
|
|
392
|
+
// was submitted regardless of the verdict. `{action:"handled"}` is the only
|
|
393
|
+
// stop: it tells Pi an extension consumed the input, so nothing is sent to
|
|
394
|
+
// the model.
|
|
395
|
+
//
|
|
396
|
+
// Known UX limitation: `handled` drops the prompt silently — Pi shows the
|
|
397
|
+
// user nothing. We log the reason to stderr so it is at least recoverable
|
|
398
|
+
// from the session output. Pi has no first-class channel for explaining a
|
|
399
|
+
// refusal at this event; if one appears, prefer it over the silent drop.
|
|
370
400
|
pi.on("input", (event: unknown): unknown => {
|
|
371
401
|
const e = event as PiInputEvent;
|
|
372
402
|
const decision = callPolicy("input", {
|
|
@@ -375,8 +405,11 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
375
405
|
cwd: resolveCwd(e.cwd),
|
|
376
406
|
hook_event_name: "UserPromptSubmit",
|
|
377
407
|
});
|
|
378
|
-
if (decision.block)
|
|
379
|
-
|
|
408
|
+
if (decision.block) {
|
|
409
|
+
console.error(`[failproofai] prompt blocked: ${decision.reason ?? "blocked by policy"}`);
|
|
410
|
+
return { action: "handled" };
|
|
411
|
+
}
|
|
412
|
+
return { action: "continue" };
|
|
380
413
|
});
|
|
381
414
|
|
|
382
415
|
// session_start → SessionStart. Observe-only; we still forward so the
|
package/scripts/launch.ts
CHANGED
|
@@ -89,6 +89,13 @@ export function launch(mode: "dev" | "start"): void {
|
|
|
89
89
|
stdio: filterLogs ? ["inherit", "pipe", "pipe"] : "inherit",
|
|
90
90
|
env: {
|
|
91
91
|
...process.env,
|
|
92
|
+
// `.next/standalone/server.js` does `process.chdir(__dirname)` on its
|
|
93
|
+
// first line, so by the time a server action runs, `process.cwd()` is the
|
|
94
|
+
// PACKAGE directory, not the directory the user launched from. Anything
|
|
95
|
+
// resolving project-scope paths from cwd would look inside the installed
|
|
96
|
+
// package and find nothing. Capture the real cwd here, before the child
|
|
97
|
+
// starts, the same way FAILPROOFAI_PACKAGE_ROOT is threaded through.
|
|
98
|
+
FAILPROOFAI_LAUNCH_CWD: process.env.FAILPROOFAI_LAUNCH_CWD ?? process.cwd(),
|
|
92
99
|
...(filterLogs ? { FORCE_COLOR: process.env.FORCE_COLOR ?? "1" } : {}),
|
|
93
100
|
...(loggingLevel ? { FAILPROOFAI_LOG_LEVEL: loggingLevel } : {}),
|
|
94
101
|
...(disableTelemetry ? { FAILPROOFAI_TELEMETRY_DISABLED: "1" } : {}),
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* `logEntriesToEvents` handles the rest.
|
|
9
9
|
*
|
|
10
10
|
* Gateway sessions have no `cwd` (Slack/Telegram runs aren't in a repo), so they
|
|
11
|
-
* group by `source`
|
|
11
|
+
* group by (profile, `source`) instead of working directory — a Hermes profile
|
|
12
|
+
* is a whole separate home dir with its own state.db (lib/hermes-profiles.ts).
|
|
12
13
|
*/
|
|
13
14
|
import { getHermesSessions } from "../../../lib/hermes-projects";
|
|
15
|
+
import { hermesProjectPath } from "../../../lib/hermes-profiles";
|
|
14
16
|
import { getHermesSessionLog } from "../../../lib/hermes-sessions";
|
|
15
17
|
import type { NormalizedToolEvent, TranscriptMetadata } from "../types";
|
|
16
18
|
import type { ListOpts } from "./claude";
|
|
@@ -31,8 +33,9 @@ export async function listHermesTranscriptMetadata(
|
|
|
31
33
|
if (s.messageCount <= 0 && !s.hasMessages) continue; // empty → no events (message_count can lag; trust real messages)
|
|
32
34
|
out.push({
|
|
33
35
|
cli: "hermes",
|
|
34
|
-
// Group by channel; gateway sessions are cwd-less
|
|
35
|
-
|
|
36
|
+
// Group by (profile, channel); gateway sessions are cwd-less, and each
|
|
37
|
+
// profile is a separate Hermes home with its own state.db.
|
|
38
|
+
projectName: s.source ? hermesProjectPath(s.profile, s.source) : `hermes:${s.profile}`,
|
|
36
39
|
sessionId: s.sessionId,
|
|
37
40
|
transcriptPath: `hermes://${s.sessionId}`,
|
|
38
41
|
mtimeMs: s.mtimeMs,
|