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
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable per-entry identity for the log viewer.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately standalone and dependency-free: `lib/log-entries.ts` reaches
|
|
5
|
+
* fs/promises through its project-resolution imports, so a *value* import of
|
|
6
|
+
* it from a client component drags node:fs into the browser bundle and the
|
|
7
|
+
* session page 500s. Client code may only import types from there.
|
|
8
|
+
*/
|
|
9
|
+
import type { LogEntry } from "./log-entries";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Assigns every entry a unique, render-stable identity.
|
|
13
|
+
*
|
|
14
|
+
* Not every CLI writes a per-record `uuid` — Codex, Copilot, Cursor and Pi
|
|
15
|
+
* transcripts have none, so `baseEntry` leaves it "". Keying rows off
|
|
16
|
+
* `uuid || timestamp` therefore collapses to the timestamp, which those CLIs
|
|
17
|
+
* reuse freely (one 771-record Codex session: 93 timestamps shared by 2-5
|
|
18
|
+
* records each). Duplicate React keys break reconciliation, and in the
|
|
19
|
+
* virtualized log list that strands orphaned DOM nodes stacked on top of live
|
|
20
|
+
* rows — they keep their old transform and never recover (#292 follow-up).
|
|
21
|
+
*
|
|
22
|
+
* Collisions are disambiguated by occurrence order, so keys stay stable for as
|
|
23
|
+
* long as the parsed entry list is — which is what React and the virtualizer's
|
|
24
|
+
* measurement cache both require.
|
|
25
|
+
*/
|
|
26
|
+
export function buildEntryKeys(entries: LogEntry[]): Map<LogEntry, string> {
|
|
27
|
+
const keys = new Map<LogEntry, string>();
|
|
28
|
+
const seen = new Map<string, number>();
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
const base = entry.uuid || `${entry._source}:${entry.timestamp}`;
|
|
31
|
+
const n = seen.get(base) ?? 0;
|
|
32
|
+
seen.set(base, n + 1);
|
|
33
|
+
keys.set(entry, n === 0 ? base : `${base}#${n}`);
|
|
34
|
+
}
|
|
35
|
+
return keys;
|
|
36
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hermes profile discovery — shared by BOTH pillars.
|
|
3
|
+
*
|
|
4
|
+
* A Hermes "profile" is not a column or a flag: it is a whole separate Hermes
|
|
5
|
+
* home directory, each with its own `config.yaml`, `.env`, `SOUL.md`, and
|
|
6
|
+
* `state.db`. The default profile lives at `~/.hermes`; every other profile at
|
|
7
|
+
* `~/.hermes/profiles/<name>/`. Selection is `hermes -p <name>`, a generated
|
|
8
|
+
* `~/.local/bin/<name>` alias that exports `HERMES_HOME`, or a sticky default
|
|
9
|
+
* recorded in `<root>/active_profile`.
|
|
10
|
+
*
|
|
11
|
+
* Upstream's own contributor guide warns that hardcoding `~/.hermes` breaks
|
|
12
|
+
* profiles — which is exactly what both pillars used to do:
|
|
13
|
+
* • audit → read one state.db, so non-default profiles were INVISIBLE;
|
|
14
|
+
* • enforcement→ wrote one config.yaml, so non-default profiles ran UNHOOKED.
|
|
15
|
+
* Both now enumerate through here.
|
|
16
|
+
*
|
|
17
|
+
* Kept deliberately dependency-light (node:fs / node:os / node:path only) so
|
|
18
|
+
* `src/hooks/integrations.ts` can import it without pulling the sql.js reader
|
|
19
|
+
* into the hook hot path.
|
|
20
|
+
*
|
|
21
|
+
* Home override: set `HERMES_HOME` (Hermes's own env var — respected here so a
|
|
22
|
+
* profile-scoped shell and failproofai agree on what "all profiles" means).
|
|
23
|
+
*/
|
|
24
|
+
import { readdirSync } from "node:fs";
|
|
25
|
+
import { homedir } from "node:os";
|
|
26
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
27
|
+
|
|
28
|
+
/** Directory under the Hermes root that holds non-default profiles. */
|
|
29
|
+
const PROFILES_DIR = "profiles";
|
|
30
|
+
|
|
31
|
+
/** Name we give the root home (`~/.hermes`), which Hermes itself leaves unnamed. */
|
|
32
|
+
export const HERMES_DEFAULT_PROFILE = "default";
|
|
33
|
+
|
|
34
|
+
export interface HermesProfile {
|
|
35
|
+
/** Profile name — `"default"` for the root home, else the directory name. */
|
|
36
|
+
name: string;
|
|
37
|
+
/** Absolute HERMES_HOME for this profile (holds config.yaml + state.db). */
|
|
38
|
+
home: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The Hermes ROOT home — the directory that owns `profiles/`.
|
|
43
|
+
*
|
|
44
|
+
* `HERMES_HOME` may point AT a profile (`<root>/profiles/<name>`), because
|
|
45
|
+
* that's what the per-profile alias wrapper exports. We climb back to `<root>`
|
|
46
|
+
* in that case so discovery still sees every sibling profile — mirroring what
|
|
47
|
+
* upstream does so `profile list` can see them all.
|
|
48
|
+
*/
|
|
49
|
+
export function hermesRoot(): string {
|
|
50
|
+
const env = (process.env.HERMES_HOME || "").trim();
|
|
51
|
+
if (env) {
|
|
52
|
+
const home = resolve(env);
|
|
53
|
+
const parent = dirname(home);
|
|
54
|
+
if (basename(parent) === PROFILES_DIR) return dirname(parent);
|
|
55
|
+
return home;
|
|
56
|
+
}
|
|
57
|
+
return join(homedir(), ".hermes");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Every Hermes profile on disk: the root home first (as `"default"`), then each
|
|
62
|
+
* `<root>/profiles/<name>/` in name order.
|
|
63
|
+
*
|
|
64
|
+
* Fail-open — a missing or unreadable `profiles/` dir just means "default only",
|
|
65
|
+
* which is the single-profile install everyone starts with.
|
|
66
|
+
*
|
|
67
|
+
* A profile directory literally named `default` would collide with the root's
|
|
68
|
+
* reserved name; the root wins and the directory is skipped (dedup by name keeps
|
|
69
|
+
* grouping keys unique and the outcome deterministic).
|
|
70
|
+
*/
|
|
71
|
+
export function listHermesProfiles(): HermesProfile[] {
|
|
72
|
+
const root = hermesRoot();
|
|
73
|
+
const out: HermesProfile[] = [{ name: HERMES_DEFAULT_PROFILE, home: root }];
|
|
74
|
+
const seen = new Set<string>([HERMES_DEFAULT_PROFILE]);
|
|
75
|
+
|
|
76
|
+
let names: string[];
|
|
77
|
+
try {
|
|
78
|
+
names = readdirSync(join(root, PROFILES_DIR), { withFileTypes: true })
|
|
79
|
+
// Symlinked profile dirs are legitimate, and `isDirectory()` is false for them.
|
|
80
|
+
.filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith("."))
|
|
81
|
+
.map((e) => e.name)
|
|
82
|
+
.sort();
|
|
83
|
+
} catch {
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const name of names) {
|
|
88
|
+
if (seen.has(name)) continue;
|
|
89
|
+
seen.add(name);
|
|
90
|
+
out.push({ name, home: join(root, PROFILES_DIR, name) });
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── (profile, source) naming ────────────────────────────────────────────────
|
|
96
|
+
//
|
|
97
|
+
// Gateway sessions have no cwd to group by, so the dashboard and the audit
|
|
98
|
+
// synthesize a project per (profile, source). Lives here — next to the profile
|
|
99
|
+
// list the parser needs — so lib/hermes-sessions.ts and lib/hermes-projects.ts
|
|
100
|
+
// can both use it without importing each other.
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Encoded project name for a (profile, source) pair.
|
|
104
|
+
*
|
|
105
|
+
* `decodeFolderName()` renders every `-` as `/`, so `hermes-work-slack` shows up
|
|
106
|
+
* as `hermes/work/slack` in the projects panel with no UI change.
|
|
107
|
+
*/
|
|
108
|
+
export function hermesProjectName(profile: string, source: string): string {
|
|
109
|
+
return `hermes-${profile}-${source}`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Machine-readable grouping key, shown under the project name. */
|
|
113
|
+
export function hermesProjectPath(profile: string, source: string): string {
|
|
114
|
+
return `hermes:${profile}:${source}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface HermesNameSplit {
|
|
118
|
+
profile: string;
|
|
119
|
+
source: string;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Every way `hermes-<profile>-<source>` could split, best guess first.
|
|
124
|
+
*
|
|
125
|
+
* Profile names may contain `-`, so the slug is never split blindly: each
|
|
126
|
+
* candidate is a profile that actually exists on disk, tried longest-first.
|
|
127
|
+
* Length alone is not decisive though — with profiles `work` and `work-slack`,
|
|
128
|
+
* `hermes-work-slack-dev` could be `work-slack`+`dev` or `work`+`slack-dev` —
|
|
129
|
+
* so callers walk the candidates and take the first that owns real sessions.
|
|
130
|
+
*
|
|
131
|
+
* The last candidate is always the legacy `hermes-<source>` form attributed to
|
|
132
|
+
* the default profile, keeping links made before profile support alive.
|
|
133
|
+
*/
|
|
134
|
+
export function hermesProjectNameCandidates(name: string): HermesNameSplit[] {
|
|
135
|
+
if (!name.startsWith("hermes-")) return [];
|
|
136
|
+
const rest = name.slice("hermes-".length);
|
|
137
|
+
if (!rest) return [];
|
|
138
|
+
|
|
139
|
+
const out: HermesNameSplit[] = [];
|
|
140
|
+
const profiles = listHermesProfiles()
|
|
141
|
+
.map((p) => p.name)
|
|
142
|
+
.sort((a, b) => b.length - a.length);
|
|
143
|
+
for (const profile of profiles) {
|
|
144
|
+
const prefix = `${profile}-`;
|
|
145
|
+
if (rest.startsWith(prefix) && rest.length > prefix.length) {
|
|
146
|
+
out.push({ profile, source: rest.slice(prefix.length) });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
out.push({ profile: HERMES_DEFAULT_PROFILE, source: rest });
|
|
150
|
+
return out;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** The best-guess split for a project name — `null` if it isn't a Hermes one. */
|
|
154
|
+
export function parseHermesProjectName(name: string): HermesNameSplit | null {
|
|
155
|
+
return hermesProjectNameCandidates(name)[0] ?? null;
|
|
156
|
+
}
|
package/lib/hermes-projects.ts
CHANGED
|
@@ -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 {
|
package/lib/hermes-sessions.ts
CHANGED
|
@@ -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;
|