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
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hermes (hermes-agent) session enumeration — AUDIT-ONLY.
|
|
3
3
|
*
|
|
4
|
-
* Reads the `sessions` table directly from
|
|
5
|
-
* sql.js reader. All gateway users
|
|
6
|
-
* everyone from one place". Because we query the table (not a text
|
|
7
|
-
* list`), we get `source`/`cwd`/`message_count` up front — enough to
|
|
8
|
-
*
|
|
4
|
+
* Reads the `sessions` table directly from every profile's `state.db` via the
|
|
5
|
+
* bundled sql.js reader. All of a profile's gateway users live in its one DB →
|
|
6
|
+
* "audit everyone from one place". Because we query the table (not a text
|
|
7
|
+
* `sessions list`), we get `source`/`cwd`/`message_count` up front — enough to
|
|
8
|
+
* group and to build a real per-transcript cache key.
|
|
9
|
+
*
|
|
10
|
+
* Sessions are grouped by (profile, source): a profile is a whole separate
|
|
11
|
+
* Hermes home (see lib/hermes-profiles.ts), so it is the outer scope, and
|
|
12
|
+
* `source` (slack/telegram/cli/cron) splits the channels within it.
|
|
9
13
|
*/
|
|
10
14
|
import { openSqliteReadonly } from "./sqlite-reader";
|
|
11
|
-
import {
|
|
15
|
+
import { hermesDbPaths, epochToMs } from "./hermes-sessions";
|
|
16
|
+
import {
|
|
17
|
+
HERMES_DEFAULT_PROFILE,
|
|
18
|
+
hermesProjectName,
|
|
19
|
+
hermesProjectPath,
|
|
20
|
+
hermesProjectNameCandidates,
|
|
21
|
+
} from "./hermes-profiles";
|
|
12
22
|
import { runtimeCache } from "./runtime-cache";
|
|
13
23
|
import type { ProjectFolder, SessionFile } from "./projects";
|
|
14
24
|
import { formatDate } from "./format-date";
|
|
15
25
|
|
|
16
26
|
export interface HermesSessionRef {
|
|
17
27
|
sessionId: string;
|
|
28
|
+
/** Which profile's state.db this row came from (`"default"` for `~/.hermes`). */
|
|
29
|
+
profile: string;
|
|
18
30
|
source?: string;
|
|
19
31
|
cwd?: string;
|
|
20
32
|
title?: string;
|
|
@@ -52,11 +64,24 @@ interface SessionRow {
|
|
|
52
64
|
}
|
|
53
65
|
|
|
54
66
|
/**
|
|
55
|
-
* List every Hermes session
|
|
56
|
-
* unreadable
|
|
67
|
+
* List every Hermes session — every user, of every profile. A missing or
|
|
68
|
+
* unreadable profile DB is skipped rather than failing the batch (fail-open:
|
|
69
|
+
* the audit degrades to the profiles it can read instead of dropping Hermes).
|
|
57
70
|
*/
|
|
58
71
|
export async function getHermesSessions(): Promise<HermesSessionRef[]> {
|
|
59
|
-
const
|
|
72
|
+
const out: HermesSessionRef[] = [];
|
|
73
|
+
for (const { profile, dbPath } of hermesDbPaths()) {
|
|
74
|
+
out.push(...(await readSessionsFromDb(dbPath, profile)));
|
|
75
|
+
}
|
|
76
|
+
out.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function readSessionsFromDb(
|
|
81
|
+
dbPath: string,
|
|
82
|
+
profile: string,
|
|
83
|
+
): Promise<HermesSessionRef[]> {
|
|
84
|
+
const db = await openSqliteReadonly(dbPath);
|
|
60
85
|
if (!db) return [];
|
|
61
86
|
try {
|
|
62
87
|
const rows = db.query<SessionRow>(
|
|
@@ -69,6 +94,7 @@ export async function getHermesSessions(): Promise<HermesSessionRef[]> {
|
|
|
69
94
|
);
|
|
70
95
|
return rows.map((r) => ({
|
|
71
96
|
sessionId: r.id,
|
|
97
|
+
profile,
|
|
72
98
|
source: r.source ?? undefined,
|
|
73
99
|
cwd: r.cwd ?? undefined,
|
|
74
100
|
title: r.title ?? undefined,
|
|
@@ -92,29 +118,44 @@ export const getCachedHermesSessions = runtimeCache(getHermesSessions, 2);
|
|
|
92
118
|
// ── Dashboard history browser (projects list + project-detail sessions) ──
|
|
93
119
|
|
|
94
120
|
/**
|
|
95
|
-
* Surface Hermes gateway sessions as synthetic "projects" grouped by
|
|
96
|
-
* (
|
|
97
|
-
* ProjectFolder per
|
|
98
|
-
* `getHermesSessionsByEncodedName`.
|
|
121
|
+
* Surface Hermes gateway sessions as synthetic "projects" grouped by
|
|
122
|
+
* (profile, source) — gateway sessions have no cwd to group by. One
|
|
123
|
+
* ProjectFolder per pair, named by `hermesProjectName`.
|
|
99
124
|
*/
|
|
100
125
|
export async function getHermesProjects(): Promise<ProjectFolder[]> {
|
|
101
126
|
const sessions = await getHermesSessions();
|
|
102
|
-
|
|
127
|
+
// key → { profile, source, latest }; keeps profile/source intact for naming
|
|
128
|
+
// (the key itself is not parseable back when a profile name contains "-").
|
|
129
|
+
const groups = new Map<
|
|
130
|
+
string,
|
|
131
|
+
{ profile: string; source: string; latest: number; count: number }
|
|
132
|
+
>();
|
|
103
133
|
for (const s of sessions) {
|
|
104
134
|
if (s.messageCount <= 0 && !s.hasMessages) continue; // skip empty (message_count can lag; trust real messages)
|
|
105
|
-
const
|
|
106
|
-
|
|
135
|
+
const profile = s.profile || HERMES_DEFAULT_PROFILE;
|
|
136
|
+
const source = s.source ?? "unknown";
|
|
137
|
+
const key = JSON.stringify([profile, source]);
|
|
138
|
+
const prev = groups.get(key);
|
|
139
|
+
if (prev) {
|
|
140
|
+
prev.latest = Math.max(prev.latest, s.mtimeMs);
|
|
141
|
+
// Sessions, not messages — `messageCount` counts messages within one
|
|
142
|
+
// session and is documented as possibly lagging.
|
|
143
|
+
prev.count += 1;
|
|
144
|
+
} else {
|
|
145
|
+
groups.set(key, { profile, source, latest: s.mtimeMs, count: 1 });
|
|
146
|
+
}
|
|
107
147
|
}
|
|
108
148
|
const out: ProjectFolder[] = [];
|
|
109
|
-
for (const
|
|
149
|
+
for (const { profile, source, latest, count } of groups.values()) {
|
|
110
150
|
const lastModified = new Date(latest);
|
|
111
151
|
out.push({
|
|
112
|
-
name:
|
|
113
|
-
path:
|
|
152
|
+
name: hermesProjectName(profile, source),
|
|
153
|
+
path: hermesProjectPath(profile, source),
|
|
114
154
|
isDirectory: true,
|
|
115
155
|
lastModified,
|
|
116
156
|
lastModifiedFormatted: formatDate(lastModified),
|
|
117
157
|
cli: ["hermes"],
|
|
158
|
+
sessionCount: count,
|
|
118
159
|
});
|
|
119
160
|
}
|
|
120
161
|
out.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
@@ -127,18 +168,43 @@ export interface HermesProjectByName {
|
|
|
127
168
|
}
|
|
128
169
|
|
|
129
170
|
/**
|
|
130
|
-
* Resolve the Hermes sessions for a synthetic project name
|
|
131
|
-
* for the project-detail page. Non-Hermes names
|
|
171
|
+
* Resolve the Hermes sessions for a synthetic project name
|
|
172
|
+
* (`hermes-<profile>-<source>`), for the project-detail page. Non-Hermes names
|
|
173
|
+
* resolve to empty.
|
|
132
174
|
*/
|
|
133
175
|
export async function getHermesSessionsByEncodedName(
|
|
134
176
|
name: string,
|
|
135
177
|
): Promise<HermesProjectByName> {
|
|
136
|
-
|
|
137
|
-
|
|
178
|
+
const candidates = hermesProjectNameCandidates(name);
|
|
179
|
+
if (candidates.length === 0) return { cwd: null, sessions: [] };
|
|
180
|
+
|
|
138
181
|
const sessions = await getHermesSessions();
|
|
139
|
-
const
|
|
182
|
+
const matching = (split: { profile: string; source: string }) =>
|
|
183
|
+
sessions.filter(
|
|
184
|
+
(s) =>
|
|
185
|
+
(s.messageCount > 0 || s.hasMessages) &&
|
|
186
|
+
(s.profile || HERMES_DEFAULT_PROFILE) === split.profile &&
|
|
187
|
+
(s.source ?? "unknown") === split.source,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// First split that actually owns sessions — longest-match alone picks the
|
|
191
|
+
// wrong profile when one name is a hyphen-prefix of another. Falling back to
|
|
192
|
+
// the best guess keeps the page labelled sensibly when nothing matches.
|
|
193
|
+
let chosen = candidates[0];
|
|
194
|
+
let matched = matching(chosen);
|
|
195
|
+
if (matched.length === 0) {
|
|
196
|
+
for (const candidate of candidates.slice(1)) {
|
|
197
|
+
const hits = matching(candidate);
|
|
198
|
+
if (hits.length > 0) {
|
|
199
|
+
chosen = candidate;
|
|
200
|
+
matched = hits;
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const { profile, source } = chosen;
|
|
140
206
|
return {
|
|
141
|
-
cwd:
|
|
207
|
+
cwd: hermesProjectPath(profile, source),
|
|
142
208
|
sessions: matched.map((s) => {
|
|
143
209
|
const lastModified = new Date(s.mtimeMs);
|
|
144
210
|
return {
|
|
@@ -12,12 +12,22 @@
|
|
|
12
12
|
* `hermesRowsToLogEntries` pairs them (mirrors `lib/codex-sessions.ts`) and is a
|
|
13
13
|
* PURE function of the message rows, so it is unit-testable without a DB.
|
|
14
14
|
*
|
|
15
|
+
* PROFILES: Hermes profiles are separate home dirs, each with its OWN state.db
|
|
16
|
+
* (`~/.hermes/state.db` for the default, `~/.hermes/profiles/<name>/state.db`
|
|
17
|
+
* otherwise) — see lib/hermes-profiles.ts. So every read here fans out across
|
|
18
|
+
* profiles instead of assuming one DB.
|
|
19
|
+
*
|
|
15
20
|
* DB path override: set `HERMES_DB_PATH` (used by tests and to point at a copied
|
|
16
|
-
* or remote state.db).
|
|
21
|
+
* or remote state.db) — it collapses discovery to that single file. Set
|
|
22
|
+
* `HERMES_HOME` instead to audit a real multi-profile tree.
|
|
17
23
|
*/
|
|
18
|
-
import { homedir } from "node:os";
|
|
19
24
|
import { join } from "node:path";
|
|
20
25
|
import { openSqliteReadonly } from "./sqlite-reader";
|
|
26
|
+
import {
|
|
27
|
+
listHermesProfiles,
|
|
28
|
+
hermesProjectPath,
|
|
29
|
+
HERMES_DEFAULT_PROFILE,
|
|
30
|
+
} from "./hermes-profiles";
|
|
21
31
|
import { runtimeCache } from "./runtime-cache";
|
|
22
32
|
import {
|
|
23
33
|
baseEntry,
|
|
@@ -32,9 +42,26 @@ import {
|
|
|
32
42
|
} from "./log-entries";
|
|
33
43
|
import { formatDuration } from "./format-duration";
|
|
34
44
|
|
|
35
|
-
/**
|
|
36
|
-
export
|
|
37
|
-
|
|
45
|
+
/** One Hermes state.db, tagged with the profile that owns it. */
|
|
46
|
+
export interface HermesDb {
|
|
47
|
+
profile: string;
|
|
48
|
+
dbPath: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Every Hermes state.db to read — one per profile on disk.
|
|
53
|
+
*
|
|
54
|
+
* `HERMES_DB_PATH` still wins and collapses this to a single synthetic
|
|
55
|
+
* `default` profile, so pointing at a copied/remote state.db keeps working
|
|
56
|
+
* exactly as before.
|
|
57
|
+
*/
|
|
58
|
+
export function hermesDbPaths(): HermesDb[] {
|
|
59
|
+
const override = process.env.HERMES_DB_PATH;
|
|
60
|
+
if (override) return [{ profile: HERMES_DEFAULT_PROFILE, dbPath: override }];
|
|
61
|
+
return listHermesProfiles().map((p) => ({
|
|
62
|
+
profile: p.name,
|
|
63
|
+
dbPath: join(p.home, "state.db"),
|
|
64
|
+
}));
|
|
38
65
|
}
|
|
39
66
|
|
|
40
67
|
/** Coerce a Hermes epoch value (seconds or ms) to epoch ms. Hermes stores
|
|
@@ -210,6 +237,8 @@ export interface HermesSessionLogData {
|
|
|
210
237
|
rawLines: Record<string, unknown>[];
|
|
211
238
|
cwd?: string;
|
|
212
239
|
filePath: string; // synthetic — hermes keeps sessions in a DB; we use hermes://<id>
|
|
240
|
+
/** Which profile's state.db the session was found in. */
|
|
241
|
+
profile: string;
|
|
213
242
|
}
|
|
214
243
|
|
|
215
244
|
interface HermesMessageRow {
|
|
@@ -223,14 +252,33 @@ interface HermesMessageRow {
|
|
|
223
252
|
}
|
|
224
253
|
|
|
225
254
|
/**
|
|
226
|
-
* Load one session by ID
|
|
227
|
-
*
|
|
255
|
+
* Load one session by ID, searching every profile's `state.db` and returning the
|
|
256
|
+
* first hit. Returns `null` when no profile has it.
|
|
257
|
+
*
|
|
258
|
+
* Searching beats encoding the profile into the path: session ids are
|
|
259
|
+
* `YYYYMMDD_HHMMSS_<hash>`, so a cross-profile collision is vanishingly
|
|
260
|
+
* unlikely, and `hermes://<id>` therefore stays a stable identifier — no
|
|
261
|
+
* migration for transcript paths already recorded in audit history, and no
|
|
262
|
+
* change to the download route or its id validator. Profile order from
|
|
263
|
+
* `hermesDbPaths()` is deterministic, so the tie-break is too.
|
|
228
264
|
*/
|
|
229
265
|
export async function getHermesSessionLog(
|
|
230
266
|
sessionId: string,
|
|
231
267
|
): Promise<HermesSessionLogData | null> {
|
|
232
268
|
if (!sessionId || !/^[A-Za-z0-9_-]+$/.test(sessionId)) return null;
|
|
233
|
-
const
|
|
269
|
+
for (const { profile, dbPath } of hermesDbPaths()) {
|
|
270
|
+
const found = await loadFromDb(dbPath, profile, sessionId);
|
|
271
|
+
if (found) return found;
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async function loadFromDb(
|
|
277
|
+
dbPath: string,
|
|
278
|
+
profile: string,
|
|
279
|
+
sessionId: string,
|
|
280
|
+
): Promise<HermesSessionLogData | null> {
|
|
281
|
+
const db = await openSqliteReadonly(dbPath);
|
|
234
282
|
if (!db) return null;
|
|
235
283
|
try {
|
|
236
284
|
const sessionRows = db.query<{ source: string | null; cwd: string | null }>(
|
|
@@ -250,14 +298,21 @@ export async function getHermesSessionLog(
|
|
|
250
298
|
msgRows as unknown as Record<string, unknown>[],
|
|
251
299
|
"session",
|
|
252
300
|
);
|
|
253
|
-
// Gateway sessions have no cwd →
|
|
301
|
+
// Gateway sessions have no cwd → fall back to the same (profile, source)
|
|
302
|
+
// key the projects panel and the audit adapter group by, so an event's cwd
|
|
303
|
+
// and its projectName agree.
|
|
254
304
|
const cwd =
|
|
255
|
-
realCwd && realCwd.length > 0
|
|
305
|
+
realCwd && realCwd.length > 0
|
|
306
|
+
? realCwd
|
|
307
|
+
: source
|
|
308
|
+
? hermesProjectPath(profile, source)
|
|
309
|
+
: undefined;
|
|
256
310
|
return {
|
|
257
311
|
entries,
|
|
258
312
|
rawLines: msgRows as unknown as Record<string, unknown>[],
|
|
259
313
|
cwd,
|
|
260
314
|
filePath: `hermes://${sessionId}`,
|
|
315
|
+
profile,
|
|
261
316
|
};
|
|
262
317
|
} catch {
|
|
263
318
|
return null;
|
|
@@ -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 {
|
|
@@ -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[] {
|
|
@@ -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`.
|