failproofai 1.0.4-beta.8 → 1.0.4
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 +5 -5
- package/.next/standalone/.next/prerender-manifest.json +4 -4
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- 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 +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- 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 +2 -2
- 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 +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +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 +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +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.nft.json +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/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 +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
- 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 +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- 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 +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- 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 +2 -2
- 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 +2 -2
- 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 +5 -5
- 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 +2 -2
- 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/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
- package/.next/standalone/.next/server/app/settings/page.js +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0hmcwgg._.js → [externals]__0-9ti7z._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__14b7cc6._.js → [externals]__0u3wrvq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1_eakl4._.js → _1-akg6w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
- package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__187jks6._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fa1jd9._.js → [root-of-the-server]__09flfsx._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +6 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1kpd1za._.js → [root-of-the-server]__190won8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__1sc84bb._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/_02yoeej._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{_00bqkhj._.js → _04rbnaq._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
- package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1if9ixi._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
- 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 +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.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_13n822a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- 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 +21 -65
- package/.next/standalone/.next/static/chunks/{1302duobnz07l.js → 0-tyrpwuc3lla.js} +1 -1
- package/.next/standalone/.next/static/chunks/0ca7octhod0g2.js +69 -0
- package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
- package/.next/standalone/.next/static/chunks/{2n85g-crgcx5j.js → 1j6o9min_6qa9.js} +1 -1
- package/.next/standalone/.next/static/chunks/27cl8h-c2w7cd.js +1 -0
- package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
- package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
- package/.next/standalone/.next/static/chunks/{1chm9piluggc-.js → 2lwipmv5etc8m.js} +1 -1
- package/.next/standalone/.next/static/chunks/2pd16tn5qmx13.js +1 -0
- package/.next/standalone/.next/static/chunks/{2ukucf6g-kbs5.js → 2u8vh73tpw28_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_x7pqemw5n_w.js → 31u94yukw9fz-.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2mizt96igig1y.js → 3p9_8x5ezce-9.js} +1 -1
- package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
- package/.next/standalone/.next/static/chunks/{0vm4wdx3w-2_w.js → 3tjji451hrgc0.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
- package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
- package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
- package/.next/standalone/app/api/auth/status/route.ts +2 -3
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
- package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
- package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
- package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
- package/.next/standalone/app/audit/audit-styles.css +1 -132
- package/.next/standalone/app/settings/page.tsx +1 -1
- package/.next/standalone/app/settings/settings-client.tsx +45 -50
- package/.next/standalone/lib/auth/api-server-client.ts +5 -33
- package/.next/standalone/lib/claude-sessions.ts +27 -101
- package/.next/standalone/lib/codex-sessions.ts +4 -20
- package/.next/standalone/lib/copilot-sessions.ts +1 -32
- package/.next/standalone/lib/cursor-sessions.ts +5 -62
- package/.next/standalone/lib/download-session.ts +13 -58
- package/.next/standalone/lib/openclaw-projects.ts +18 -54
- package/.next/standalone/lib/openclaw-sessions.ts +17 -75
- package/.next/standalone/lib/sqlite-reader.ts +1 -78
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +1 -1
- package/dist/cli.mjs +664 -2121
- package/dist/worker.mjs +105 -422
- package/lib/auth/api-server-client.ts +5 -33
- package/lib/claude-sessions.ts +27 -101
- package/lib/codex-sessions.ts +4 -20
- package/lib/copilot-sessions.ts +1 -32
- package/lib/cursor-sessions.ts +5 -62
- package/lib/download-session.ts +13 -58
- package/lib/openclaw-projects.ts +18 -54
- package/lib/openclaw-sessions.ts +17 -75
- package/lib/sqlite-reader.ts +1 -78
- package/package.json +9 -9
- package/pi-extension/index.ts +5 -50
- package/src/audit/cache.ts +0 -6
- package/src/audit/cli-adapters/claude.ts +0 -13
- package/src/audit/cli-login.ts +1 -4
- package/src/audit/cli.ts +7 -157
- package/src/audit/harm-report.ts +4 -111
- package/src/audit/index.ts +15 -286
- package/src/audit/machine-store.ts +9 -8
- package/src/audit/redact-example.ts +15 -324
- package/src/audit/report-harm.ts +37 -33
- package/src/audit/report.ts +3 -6
- package/src/audit/schedule-cli.ts +33 -54
- package/src/audit/scoring.ts +0 -49
- package/src/audit/types.ts +0 -52
- package/src/hooks/builtin-policies.ts +2 -67
- package/src/hooks/configure-wizard.ts +0 -55
- package/src/hooks/fp-config.ts +48 -83
- package/src/hooks/fp-home.ts +0 -27
- package/src/hooks/integrations.ts +14 -51
- package/src/hooks/uninstall-cli.ts +2 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +0 -22
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__112b4go._.js +0 -24
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +0 -22
- 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]__0-4ja6q._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cr3xu._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04px77e._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0egr5s1._.js +0 -6
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0i0l1di._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n2txhk._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0r3c1_0._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17xnxlx._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ckpmkk._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
- package/.next/standalone/.next/static/chunks/0igq884ziuv-8.js +0 -1
- package/.next/standalone/.next/static/chunks/349jkb79v5g6z.js +0 -1
- package/.next/standalone/.next/static/chunks/3a4gth64hwtwf.js +0 -1
- package/.next/standalone/app/actions/get-leaks.ts +0 -104
- package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
- package/.next/standalone/lib/openclaw-db.ts +0 -271
- package/lib/openclaw-db.ts +0 -271
- package/src/audit/desktop-notify.ts +0 -420
- package/src/audit/leak-fingerprint.ts +0 -198
- package/src/audit/leak-record.ts +0 -248
- package/src/audit/leak-scan.ts +0 -268
- package/src/audit/leak-store.ts +0 -220
- package/src/audit/macos-notifier.ts +0 -298
- package/src/hooks/notice.ts +0 -24
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → dg8VQ7Ejlo0Qsh_x92oIt}/_ssgManifest.js +0 -0
package/lib/openclaw-projects.ts
CHANGED
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw (openclaw gateway) session enumeration — AUDIT-ONLY.
|
|
3
3
|
*
|
|
4
|
-
* Surfaces
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Surfaces the on-disk transcripts (agents/<agentId>/sessions/<uuid>.jsonl) as
|
|
5
|
+
* synthetic dashboard "projects" grouped by (agentId, channel). The per-agent
|
|
6
|
+
* `sessions.json` index maps sessionKey → {sessionId, timestamps}; we read it to
|
|
7
|
+
* recover the sessionKey (which encodes the channel for gateway sessions) and a
|
|
8
|
+
* reliable last-activity time. Verified live against openclaw v2026.7.1.
|
|
7
9
|
*/
|
|
8
10
|
import { readFileSync } from "node:fs";
|
|
9
11
|
import { join } from "node:path";
|
|
10
12
|
import { runtimeCache } from "./runtime-cache";
|
|
11
|
-
import {
|
|
12
|
-
listOpenClawAgents,
|
|
13
|
-
listOpenClawTranscripts,
|
|
14
|
-
openclawHome,
|
|
15
|
-
} from "./openclaw-sessions";
|
|
13
|
+
import { listOpenClawAgents, listOpenClawTranscripts, openclawHome } from "./openclaw-sessions";
|
|
16
14
|
import type { ProjectFolder, SessionFile } from "./projects";
|
|
17
15
|
import { formatDate } from "./format-date";
|
|
18
|
-
import { listOpenClawSqliteSessions } from "./openclaw-db";
|
|
19
16
|
|
|
20
17
|
export interface OpenClawSessionRef {
|
|
21
18
|
sessionId: string;
|
|
@@ -54,13 +51,7 @@ function str(v: unknown): string | undefined {
|
|
|
54
51
|
* than in the key — verified live against v2026.7.1. */
|
|
55
52
|
function readSessionsIndex(agentId: string): Map<string, SessionIndexMeta> {
|
|
56
53
|
const out = new Map<string, SessionIndexMeta>();
|
|
57
|
-
const indexPath = join(
|
|
58
|
-
openclawHome(),
|
|
59
|
-
"agents",
|
|
60
|
-
agentId,
|
|
61
|
-
"sessions",
|
|
62
|
-
"sessions.json",
|
|
63
|
-
);
|
|
54
|
+
const indexPath = join(openclawHome(), "agents", agentId, "sessions", "sessions.json");
|
|
64
55
|
let raw: unknown;
|
|
65
56
|
try {
|
|
66
57
|
raw = JSON.parse(readFileSync(indexPath, "utf-8"));
|
|
@@ -73,9 +64,7 @@ function readSessionsIndex(agentId: string): Map<string, SessionIndexMeta> {
|
|
|
73
64
|
const e = v as Record<string, unknown>;
|
|
74
65
|
const sessionId = str(e.sessionId);
|
|
75
66
|
if (!sessionId) continue;
|
|
76
|
-
const origin = (
|
|
77
|
-
e.origin && typeof e.origin === "object" ? e.origin : {}
|
|
78
|
-
) as Record<string, unknown>;
|
|
67
|
+
const origin = (e.origin && typeof e.origin === "object" ? e.origin : {}) as Record<string, unknown>;
|
|
79
68
|
const lastMs =
|
|
80
69
|
typeof e.lastInteractionAt === "number"
|
|
81
70
|
? e.lastInteractionAt
|
|
@@ -84,8 +73,7 @@ function readSessionsIndex(agentId: string): Map<string, SessionIndexMeta> {
|
|
|
84
73
|
: undefined;
|
|
85
74
|
out.set(sessionId, {
|
|
86
75
|
lastMs,
|
|
87
|
-
channel:
|
|
88
|
-
str(e.lastChannel) ?? str(origin.provider) ?? str(origin.surface),
|
|
76
|
+
channel: str(e.lastChannel) ?? str(origin.provider) ?? str(origin.surface),
|
|
89
77
|
chatType: str(e.chatType) ?? str(origin.chatType),
|
|
90
78
|
label: str(origin.label),
|
|
91
79
|
chatId: str(e.lastTo) ?? str(origin.from),
|
|
@@ -98,7 +86,7 @@ function readSessionsIndex(agentId: string): Map<string, SessionIndexMeta> {
|
|
|
98
86
|
export async function getOpenClawSessions(): Promise<OpenClawSessionRef[]> {
|
|
99
87
|
const transcripts = listOpenClawTranscripts();
|
|
100
88
|
const indexByAgent = new Map<string, Map<string, SessionIndexMeta>>();
|
|
101
|
-
const refs =
|
|
89
|
+
const refs: OpenClawSessionRef[] = [];
|
|
102
90
|
for (const t of transcripts) {
|
|
103
91
|
let idx = indexByAgent.get(t.agentId);
|
|
104
92
|
if (!idx) {
|
|
@@ -106,7 +94,7 @@ export async function getOpenClawSessions(): Promise<OpenClawSessionRef[]> {
|
|
|
106
94
|
indexByAgent.set(t.agentId, idx);
|
|
107
95
|
}
|
|
108
96
|
const meta = idx.get(t.sessionId);
|
|
109
|
-
refs.
|
|
97
|
+
refs.push({
|
|
110
98
|
sessionId: t.sessionId,
|
|
111
99
|
agentId: t.agentId,
|
|
112
100
|
// Gateway sessions group by channel; CLI/local runs have none.
|
|
@@ -119,18 +107,8 @@ export async function getOpenClawSessions(): Promise<OpenClawSessionRef[]> {
|
|
|
119
107
|
sizeBytes: t.sizeBytes,
|
|
120
108
|
});
|
|
121
109
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
// replaces an archived JSONL copy of the same agent/session.
|
|
125
|
-
const sqliteSessions = await listOpenClawSqliteSessions(
|
|
126
|
-
openclawHome(),
|
|
127
|
-
listOpenClawAgents(),
|
|
128
|
-
);
|
|
129
|
-
for (const s of sqliteSessions) {
|
|
130
|
-
refs.set(`${s.agentId}\0${s.sessionId}`, s);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return [...refs.values()].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
110
|
+
refs.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
111
|
+
return refs;
|
|
134
112
|
}
|
|
135
113
|
|
|
136
114
|
export const getCachedOpenClawSessions = runtimeCache(getOpenClawSessions, 2);
|
|
@@ -167,9 +145,7 @@ export interface OpenClawNameSplit {
|
|
|
167
145
|
* form (`agentId: null`), so links shared before agents became part of the name
|
|
168
146
|
* keep resolving — to every agent on that channel, which is what they meant.
|
|
169
147
|
*/
|
|
170
|
-
export function openClawProjectNameCandidates(
|
|
171
|
-
name: string,
|
|
172
|
-
): OpenClawNameSplit[] {
|
|
148
|
+
export function openClawProjectNameCandidates(name: string): OpenClawNameSplit[] {
|
|
173
149
|
if (!name.startsWith("openclaw-")) return [];
|
|
174
150
|
const rest = name.slice("openclaw-".length);
|
|
175
151
|
if (!rest) return [];
|
|
@@ -187,9 +163,7 @@ export function openClawProjectNameCandidates(
|
|
|
187
163
|
}
|
|
188
164
|
|
|
189
165
|
/** The best-guess split for a project name — `null` if it isn't an OpenClaw one. */
|
|
190
|
-
export function parseOpenClawProjectName(
|
|
191
|
-
name: string,
|
|
192
|
-
): OpenClawNameSplit | null {
|
|
166
|
+
export function parseOpenClawProjectName(name: string): OpenClawNameSplit | null {
|
|
193
167
|
return openClawProjectNameCandidates(name)[0] ?? null;
|
|
194
168
|
}
|
|
195
169
|
|
|
@@ -216,12 +190,7 @@ export async function getOpenClawProjects(): Promise<ProjectFolder[]> {
|
|
|
216
190
|
prev.latest = Math.max(prev.latest, s.mtimeMs);
|
|
217
191
|
prev.count += 1;
|
|
218
192
|
} else {
|
|
219
|
-
groups.set(key, {
|
|
220
|
-
agentId: s.agentId,
|
|
221
|
-
channel: s.channel,
|
|
222
|
-
latest: s.mtimeMs,
|
|
223
|
-
count: 1,
|
|
224
|
-
});
|
|
193
|
+
groups.set(key, { agentId: s.agentId, channel: s.channel, latest: s.mtimeMs, count: 1 });
|
|
225
194
|
}
|
|
226
195
|
}
|
|
227
196
|
const out: ProjectFolder[] = [];
|
|
@@ -259,9 +228,7 @@ export async function getOpenClawSessionsByEncodedName(
|
|
|
259
228
|
const sessions = await getOpenClawSessions();
|
|
260
229
|
const matching = (split: OpenClawNameSplit) =>
|
|
261
230
|
sessions.filter(
|
|
262
|
-
(s) =>
|
|
263
|
-
s.channel === split.channel &&
|
|
264
|
-
(split.agentId === null || s.agentId === split.agentId),
|
|
231
|
+
(s) => s.channel === split.channel && (split.agentId === null || s.agentId === split.agentId),
|
|
265
232
|
);
|
|
266
233
|
|
|
267
234
|
// Take the first split that actually owns sessions — length alone picks the
|
|
@@ -283,10 +250,7 @@ export async function getOpenClawSessionsByEncodedName(
|
|
|
283
250
|
return {
|
|
284
251
|
// Legacy channel-only names keep their old cwd label, since they really do
|
|
285
252
|
// span every agent on that channel.
|
|
286
|
-
cwd:
|
|
287
|
-
agentId === null
|
|
288
|
-
? `openclaw:${channel}`
|
|
289
|
-
: openClawProjectPath(agentId, channel),
|
|
253
|
+
cwd: agentId === null ? `openclaw:${channel}` : openClawProjectPath(agentId, channel),
|
|
290
254
|
sessions: matched.map((s) => {
|
|
291
255
|
const lastModified = new Date(s.mtimeMs);
|
|
292
256
|
return {
|
package/lib/openclaw-sessions.ts
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* OpenClaw (openclaw gateway) session transcript loader + parser.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* AUDIT-ONLY (Pillar 2). OpenClaw writes one JSONL transcript per session at
|
|
5
|
+
* `~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl` (sessionId is a
|
|
6
|
+
* UUID), alongside a much larger `<sessionId>.trajectory.jsonl` OTel trace we
|
|
7
|
+
* IGNORE, and a `sessions.json` index keyed by sessionKey. Verified live
|
|
8
|
+
* against openclaw v2026.7.1.
|
|
7
9
|
*
|
|
8
10
|
* The transcript is type-discriminated JSONL:
|
|
9
11
|
* {type:"session", cwd, …} — header (carries cwd)
|
|
@@ -16,8 +18,8 @@
|
|
|
16
18
|
* `toolResult` by `toolCallId` (mirrors lib/hermes-sessions.ts) and is PURE, so
|
|
17
19
|
* it is unit-testable with plain line objects.
|
|
18
20
|
*
|
|
19
|
-
* Home override:
|
|
20
|
-
*
|
|
21
|
+
* Home override: set `OPENCLAW_HOME` (used by tests / to point at a copied
|
|
22
|
+
* gateway config dir).
|
|
21
23
|
*/
|
|
22
24
|
import { readFile } from "node:fs/promises";
|
|
23
25
|
import { readdirSync, statSync } from "node:fs";
|
|
@@ -37,18 +39,13 @@ import {
|
|
|
37
39
|
type LogSource,
|
|
38
40
|
} from "./log-entries";
|
|
39
41
|
import { formatDuration } from "./format-duration";
|
|
40
|
-
import { readOpenClawSqliteTranscript } from "./openclaw-db";
|
|
41
42
|
|
|
42
43
|
/** OpenClaw sessions are stored under UUID filenames. */
|
|
43
44
|
export const OPENCLAW_SESSION_ID_RE = /^[0-9a-fA-F-]{36}$/;
|
|
44
45
|
|
|
45
|
-
/** Absolute path to OpenClaw's
|
|
46
|
+
/** Absolute path to OpenClaw's config home (override with OPENCLAW_HOME). */
|
|
46
47
|
export function openclawHome(): string {
|
|
47
|
-
return (
|
|
48
|
-
process.env.OPENCLAW_STATE_DIR ||
|
|
49
|
-
process.env.OPENCLAW_HOME ||
|
|
50
|
-
join(homedir(), ".openclaw")
|
|
51
|
-
);
|
|
48
|
+
return process.env.OPENCLAW_HOME || join(homedir(), ".openclaw");
|
|
52
49
|
}
|
|
53
50
|
|
|
54
51
|
// ── Parsing helpers ──
|
|
@@ -63,11 +60,7 @@ function extractText(content: unknown): string {
|
|
|
63
60
|
if (typeof content === "string") return content;
|
|
64
61
|
if (Array.isArray(content)) {
|
|
65
62
|
return content
|
|
66
|
-
.map((c) =>
|
|
67
|
-
isPlainObject(c) && typeof c.text === "string"
|
|
68
|
-
? (c.text as string)
|
|
69
|
-
: "",
|
|
70
|
-
)
|
|
63
|
+
.map((c) => (isPlainObject(c) && typeof c.text === "string" ? (c.text as string) : ""))
|
|
71
64
|
.filter(Boolean)
|
|
72
65
|
.join("\n");
|
|
73
66
|
}
|
|
@@ -141,10 +134,7 @@ export function openclawLinesToLogEntries(
|
|
|
141
134
|
if (b.type === "text" && typeof b.text === "string") {
|
|
142
135
|
blocks.push({ type: "text", text: b.text });
|
|
143
136
|
} else if (b.type === "toolCall") {
|
|
144
|
-
const id =
|
|
145
|
-
typeof b.id === "string"
|
|
146
|
-
? b.id
|
|
147
|
-
: `${String(b.name ?? "tool")}-${blocks.length}`;
|
|
137
|
+
const id = typeof b.id === "string" ? b.id : `${String(b.name ?? "tool")}-${blocks.length}`;
|
|
148
138
|
const name = typeof b.name === "string" ? b.name : "tool";
|
|
149
139
|
const input = isPlainObject(b.arguments) ? b.arguments : {};
|
|
150
140
|
const block: ToolUseBlock = { type: "tool_use", id, name, input };
|
|
@@ -161,23 +151,17 @@ export function openclawLinesToLogEntries(
|
|
|
161
151
|
entries.push({
|
|
162
152
|
type: "assistant",
|
|
163
153
|
...base,
|
|
164
|
-
message: {
|
|
165
|
-
role: "assistant",
|
|
166
|
-
content: blocks,
|
|
167
|
-
model: typeof m.model === "string" ? m.model : undefined,
|
|
168
|
-
},
|
|
154
|
+
message: { role: "assistant", content: blocks, model: typeof m.model === "string" ? m.model : undefined },
|
|
169
155
|
} satisfies AssistantEntry);
|
|
170
156
|
continue;
|
|
171
157
|
}
|
|
172
158
|
|
|
173
159
|
if (role === "toolResult") {
|
|
174
|
-
const callId =
|
|
175
|
-
typeof m.toolCallId === "string" ? m.toolCallId : undefined;
|
|
160
|
+
const callId = typeof m.toolCallId === "string" ? m.toolCallId : undefined;
|
|
176
161
|
const block = callId ? toolUseById.get(callId) : undefined;
|
|
177
162
|
if (block) {
|
|
178
163
|
const details = isPlainObject(m.details) ? m.details : undefined;
|
|
179
|
-
const startMs =
|
|
180
|
-
(callId && toolUseStartMs.get(callId)) || date.getTime();
|
|
164
|
+
const startMs = (callId && toolUseStartMs.get(callId)) || date.getTime();
|
|
181
165
|
const durationMs =
|
|
182
166
|
details && typeof details.durationMs === "number"
|
|
183
167
|
? details.durationMs
|
|
@@ -254,20 +238,13 @@ export function listOpenClawTranscripts(): OpenClawTranscriptFile[] {
|
|
|
254
238
|
}
|
|
255
239
|
for (const file of files) {
|
|
256
240
|
// Only `<uuid>.jsonl` — not `<uuid>.trajectory.jsonl` / `.trajectory-path.json`.
|
|
257
|
-
if (!file.endsWith(".jsonl") || file.endsWith(".trajectory.jsonl"))
|
|
258
|
-
continue;
|
|
241
|
+
if (!file.endsWith(".jsonl") || file.endsWith(".trajectory.jsonl")) continue;
|
|
259
242
|
const sessionId = file.slice(0, -".jsonl".length);
|
|
260
243
|
if (!OPENCLAW_SESSION_ID_RE.test(sessionId)) continue;
|
|
261
244
|
const transcriptPath = join(sessionsDir, file);
|
|
262
245
|
try {
|
|
263
246
|
const st = statSync(transcriptPath);
|
|
264
|
-
out.push({
|
|
265
|
-
agentId,
|
|
266
|
-
sessionId,
|
|
267
|
-
transcriptPath,
|
|
268
|
-
mtimeMs: st.mtimeMs,
|
|
269
|
-
sizeBytes: st.size,
|
|
270
|
-
});
|
|
247
|
+
out.push({ agentId, sessionId, transcriptPath, mtimeMs: st.mtimeMs, sizeBytes: st.size });
|
|
271
248
|
} catch {
|
|
272
249
|
// skip unreadable
|
|
273
250
|
}
|
|
@@ -299,36 +276,6 @@ export interface OpenClawSessionLogData {
|
|
|
299
276
|
export async function getOpenClawSessionLog(
|
|
300
277
|
sessionId: string,
|
|
301
278
|
): Promise<OpenClawSessionLogData | null> {
|
|
302
|
-
if (!OPENCLAW_SESSION_ID_RE.test(sessionId)) return null;
|
|
303
|
-
|
|
304
|
-
// New OpenClaw versions keep live transcripts in SQLite. Prefer that copy
|
|
305
|
-
// when an archived JSONL with the same UUID also exists.
|
|
306
|
-
const sqlite = await readOpenClawSqliteTranscript(
|
|
307
|
-
openclawHome(),
|
|
308
|
-
listOpenClawAgents(),
|
|
309
|
-
sessionId,
|
|
310
|
-
);
|
|
311
|
-
if (sqlite) {
|
|
312
|
-
const entries = openclawLinesToLogEntries(sqlite.rawLines, "session");
|
|
313
|
-
let cwd: string | undefined;
|
|
314
|
-
for (const line of sqlite.rawLines) {
|
|
315
|
-
if (
|
|
316
|
-
line.type === "session" &&
|
|
317
|
-
typeof line.cwd === "string" &&
|
|
318
|
-
line.cwd.length > 0
|
|
319
|
-
) {
|
|
320
|
-
cwd = line.cwd;
|
|
321
|
-
break;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
return {
|
|
325
|
-
entries,
|
|
326
|
-
rawLines: sqlite.rawLines,
|
|
327
|
-
cwd,
|
|
328
|
-
filePath: sqlite.filePath,
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
|
|
332
279
|
const filePath = findOpenClawTranscript(sessionId);
|
|
333
280
|
if (!filePath) return null;
|
|
334
281
|
let content: string;
|
|
@@ -342,12 +289,7 @@ export async function getOpenClawSessionLog(
|
|
|
342
289
|
// cwd lives on the `type:"session"` header line.
|
|
343
290
|
let cwd: string | undefined;
|
|
344
291
|
for (const line of rawLines) {
|
|
345
|
-
if (
|
|
346
|
-
isPlainObject(line) &&
|
|
347
|
-
line.type === "session" &&
|
|
348
|
-
typeof line.cwd === "string" &&
|
|
349
|
-
line.cwd.length > 0
|
|
350
|
-
) {
|
|
292
|
+
if (isPlainObject(line) && line.type === "session" && typeof line.cwd === "string" && line.cwd.length > 0) {
|
|
351
293
|
cwd = line.cwd;
|
|
352
294
|
break;
|
|
353
295
|
}
|
package/lib/sqlite-reader.ts
CHANGED
|
@@ -39,42 +39,8 @@ interface NodeSqliteModule {
|
|
|
39
39
|
DatabaseSync: new (path: string, options?: { readOnly?: boolean }) => NodeSqliteDb;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Swallow Node's "SQLite is an experimental feature" warning, once.
|
|
44
|
-
*
|
|
45
|
-
* It is not information the user can act on — we chose `node:sqlite`
|
|
46
|
-
* deliberately, and the alternative tier is a WASM build that leaks. But it is
|
|
47
|
-
* emitted on first use, which lands it in the MIDDLE of whatever is on screen,
|
|
48
|
-
* and the audit's progress display redraws itself by moving the cursor up a
|
|
49
|
-
* fixed number of lines. Two unexpected lines of stderr push the cursor down
|
|
50
|
-
* two rows, so the next redraw repaints the block two rows lower and strands
|
|
51
|
-
* the top of the previous frame above it. The user sees every stage twice and
|
|
52
|
-
* reasonably concludes the audit ran twice.
|
|
53
|
-
*
|
|
54
|
-
* Filtered by message rather than silenced wholesale: `process.emitWarning` is
|
|
55
|
-
* how deprecations and real problems surface too, and a blanket
|
|
56
|
-
* `NODE_NO_WARNINGS` in a tool that installs into other people's machines would
|
|
57
|
-
* hide those as well.
|
|
58
|
-
*/
|
|
59
|
-
let warningPatched = false;
|
|
60
|
-
function silenceSqliteExperimentalWarning(): void {
|
|
61
|
-
if (warningPatched) return;
|
|
62
|
-
warningPatched = true;
|
|
63
|
-
const original = process.emitWarning.bind(process);
|
|
64
|
-
// The overloads differ across Node versions; the shape we care about is the
|
|
65
|
-
// first argument, and everything else is passed straight through.
|
|
66
|
-
process.emitWarning = ((warning: unknown, ...rest: unknown[]) => {
|
|
67
|
-
const text = typeof warning === "string" ? warning : String((warning as Error)?.message ?? "");
|
|
68
|
-
if (text.includes("SQLite is an experimental feature")) return;
|
|
69
|
-
return (original as (...a: unknown[]) => void)(warning, ...rest);
|
|
70
|
-
}) as typeof process.emitWarning;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
42
|
async function tryNodeSqlite(dbPath: string): Promise<SqliteReader | null> {
|
|
74
43
|
try {
|
|
75
|
-
// Before the import, not after: the warning fires on first use of the
|
|
76
|
-
// module, which is this line.
|
|
77
|
-
silenceSqliteExperimentalWarning();
|
|
78
44
|
// Dynamic import: on Node < 22.5 this rejects and we fall through to sql.js.
|
|
79
45
|
const mod = (await import("node:sqlite")) as unknown as NodeSqliteModule;
|
|
80
46
|
const db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -100,53 +66,10 @@ function loadSqlJs(): Promise<SqlJsStatic> {
|
|
|
100
66
|
return sqlPromise;
|
|
101
67
|
}
|
|
102
68
|
|
|
103
|
-
const SQLITE_MAGIC = "SQLite format 3\0";
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Clear the WAL flag on OUR OWN in-memory copy of the database image.
|
|
107
|
-
*
|
|
108
|
-
* Header bytes 18 and 19 are the file-format write and read versions; `2`
|
|
109
|
-
* means WAL. sql.js never sees the `-wal` sidecar — it is handed the main
|
|
110
|
-
* file's bytes and nothing else, which is exactly the snapshot caveat this
|
|
111
|
-
* module already documents — so the flag buys us nothing, and it costs a great
|
|
112
|
-
* deal: opening a WAL-flagged image makes SQLite build a wal-index
|
|
113
|
-
* shared-memory region that sql.js's MEMFS-backed VFS never reclaims on
|
|
114
|
-
* `close()` (close unlinks only the db file it created).
|
|
115
|
-
*
|
|
116
|
-
* The asm.js build's heap is a FIXED 22,151,168-byte ArrayBuffer with
|
|
117
|
-
* ALLOW_MEMORY_GROWTH off, so `emscripten_resize_heap` is compiled to
|
|
118
|
-
* `abort("OOM")`. Measured: the 128th WAL-mode open exhausts it — the same
|
|
119
|
-
* threshold for a 319 KB database and a 10 MB one, because the leaked region
|
|
120
|
-
* is a fixed per-connection allocation rather than data.
|
|
121
|
-
*
|
|
122
|
-
* And the abort is not confined to that open. `initSqlJs` memoizes one
|
|
123
|
-
* process-wide module, so once it aborts every later `openSqliteReadonly`
|
|
124
|
-
* silently returns null. A full audit opens ~150 databases — devin, goose and
|
|
125
|
-
* opencode each open theirs once per SESSION — so it crossed 128 and then
|
|
126
|
-
* dropped whatever it had not yet read, with no error anywhere. The
|
|
127
|
-
* `Aborted(OOM)` lines on stderr were the only symptom, and they read as
|
|
128
|
-
* harmless because the run still completed.
|
|
129
|
-
*
|
|
130
|
-
* Only under bun and Node < 22.5: tier 1 is `node:sqlite`, which bun does not
|
|
131
|
-
* provide, and `engines.node` allows 20.9. Both fall through to here.
|
|
132
|
-
*/
|
|
133
|
-
function clearWalFlag(bytes: Buffer): Buffer {
|
|
134
|
-
// Too short to carry a header, or not a SQLite image at all — hand it back
|
|
135
|
-
// untouched and let sql.js produce its own error.
|
|
136
|
-
if (bytes.length < 20) return bytes;
|
|
137
|
-
if (bytes.toString("latin1", 0, 16) !== SQLITE_MAGIC) return bytes;
|
|
138
|
-
if (bytes[18] === 1 && bytes[19] === 1) return bytes; // already rollback-journal
|
|
139
|
-
bytes[18] = 1;
|
|
140
|
-
bytes[19] = 1;
|
|
141
|
-
return bytes;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
69
|
async function trySqlJs(dbPath: string): Promise<SqliteReader | null> {
|
|
145
70
|
try {
|
|
146
71
|
const SQL = await loadSqlJs();
|
|
147
|
-
|
|
148
|
-
// bytes cannot touch the file on disk.
|
|
149
|
-
const db = new SQL.Database(clearWalFlag(readFileSync(dbPath)));
|
|
72
|
+
const db = new SQL.Database(readFileSync(dbPath));
|
|
150
73
|
return {
|
|
151
74
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
152
75
|
const stmt = db.prepare(sql);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.4
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
@@ -121,15 +121,15 @@
|
|
|
121
121
|
"browserslist": "4.28.8"
|
|
122
122
|
},
|
|
123
123
|
"optionalDependencies": {
|
|
124
|
-
"@failproofai/failproofaid-linux-x64": "1.0.4
|
|
125
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.4
|
|
126
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.4
|
|
127
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.4
|
|
124
|
+
"@failproofai/failproofaid-linux-x64": "1.0.4",
|
|
125
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.4",
|
|
126
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.4",
|
|
127
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.4"
|
|
128
128
|
},
|
|
129
129
|
"failproofaidBinaries": {
|
|
130
|
-
"linux-x64": "
|
|
131
|
-
"linux-arm64": "
|
|
132
|
-
"darwin-x64": "
|
|
133
|
-
"darwin-arm64": "
|
|
130
|
+
"linux-x64": "cca1d41ff951784ba084bf2d72cfad0fe8d988f8226e0e7a4538b1fb8d76a5bd",
|
|
131
|
+
"linux-arm64": "55e9c1e2864cbf6057fd0c27e4bc573ca8fba2cd35d480a3db3a55d398e7bec0",
|
|
132
|
+
"darwin-x64": "2a46ec77d2cac14a036f35e832f92f16895239814a5f10f02121f3c313e70574",
|
|
133
|
+
"darwin-arm64": "a0e7ccc6fe24e1017592637192ba8a26c9d05ca839c98bc72a983094c863b39b"
|
|
134
134
|
}
|
|
135
135
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* spawns extensions with an undefined cwd contract, so paths are resolved
|
|
26
26
|
* relative to this file via `import.meta.url`, NOT process.cwd().
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
28
|
+
import { spawnSync } from "node:child_process";
|
|
29
29
|
import { resolve, dirname, join } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
@@ -64,47 +64,6 @@ function debug(msg: string): void {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
* Forward an event whose VERDICT NOBODY READS, without blocking Pi.
|
|
69
|
-
*
|
|
70
|
-
* `callPolicy` is `spawnSync`, and Pi awaits its handlers serially on the
|
|
71
|
-
* startup path — so a `session_start` forward froze the whole TUI for as long
|
|
72
|
-
* as the subprocess took (measured floor 1.0-1.6s just to boot the binary,
|
|
73
|
-
* ceiling the full 60s timeout) and then threw the answer away. Three call
|
|
74
|
-
* sites are like that by design: `session_start`, `tool_result` and
|
|
75
|
-
* `session_shutdown` forward for the activity feed and for session-id
|
|
76
|
-
* continuity, and Pi cannot act on their return in any case
|
|
77
|
-
* (`ToolResultEventResult` has no `block`; the other two have no Result type
|
|
78
|
-
* at all).
|
|
79
|
-
*
|
|
80
|
-
* So they get a detached `spawn` instead. The event still reaches failproofai,
|
|
81
|
-
* the policies still run and still log, and Pi keeps rendering. Deliberately
|
|
82
|
-
* NOT used for `tool_call`, `user_bash`, `input` or `agent_end`: those four
|
|
83
|
-
* consume the decision, and blocking is what enforcement means.
|
|
84
|
-
*/
|
|
85
|
-
function forwardPolicy(
|
|
86
|
-
eventName: string,
|
|
87
|
-
payload: unknown,
|
|
88
|
-
): void {
|
|
89
|
-
const { cmd, args } = resolveSpawn();
|
|
90
|
-
debug(`forwardPolicy (detached) event=${eventName} cmd=${cmd}`);
|
|
91
|
-
try {
|
|
92
|
-
const child = spawn(cmd, [...args, "--hook", eventName, "--cli", "pi"], {
|
|
93
|
-
stdio: ["pipe", "ignore", "ignore"],
|
|
94
|
-
detached: false,
|
|
95
|
-
});
|
|
96
|
-
// A child that outlives its parent's interest must not keep the event loop
|
|
97
|
-
// alive, or Pi cannot exit until the subprocess does.
|
|
98
|
-
child.unref();
|
|
99
|
-
// EPIPE if the child died before stdin was consumed — fail-open, silently.
|
|
100
|
-
child.on("error", (err) => debug(`forward error ${err.message}`));
|
|
101
|
-
child.stdin?.on("error", () => {});
|
|
102
|
-
child.stdin?.end(JSON.stringify(payload));
|
|
103
|
-
} catch (err) {
|
|
104
|
-
debug(`FORWARD EXCEPTION ${err instanceof Error ? err.message : String(err)}`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
67
|
function callPolicy(eventName: string, payload: unknown): { block: boolean; reason: string } {
|
|
109
68
|
const { cmd, args } = resolveSpawn();
|
|
110
69
|
debug(`callPolicy event=${eventName} cmd=${cmd}`);
|
|
@@ -124,12 +83,8 @@ function callPolicy(eventName: string, payload: unknown): { block: boolean; reas
|
|
|
124
83
|
const parsed = JSON.parse(stdout) as PolicyDecision;
|
|
125
84
|
if (parsed.permission === "deny") {
|
|
126
85
|
debug(`DENY reason=${parsed.reason}`);
|
|
127
|
-
return {
|
|
128
|
-
block: true,
|
|
129
|
-
reason: parsed.reason ?? "Blocked by failproofai",
|
|
130
|
-
};
|
|
86
|
+
return { block: true, reason: parsed.reason ?? "Blocked by failproofai" };
|
|
131
87
|
}
|
|
132
|
-
return { block: false, reason: "" };
|
|
133
88
|
} catch (err) {
|
|
134
89
|
debug(`EXCEPTION ${err instanceof Error ? err.message : String(err)}`);
|
|
135
90
|
// Fail-open: never block tool execution because of an infra failure.
|
|
@@ -462,7 +417,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
462
417
|
// need session_id continuity see the metadata.
|
|
463
418
|
pi.on("session_start", (event: unknown): unknown => {
|
|
464
419
|
const e = event as PiSessionStartEvent;
|
|
465
|
-
|
|
420
|
+
callPolicy("session_start", {
|
|
466
421
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
467
422
|
cwd: resolveCwd(e.cwd),
|
|
468
423
|
reason: e.reason,
|
|
@@ -480,7 +435,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
480
435
|
pi.on("tool_result", (event: unknown): unknown => {
|
|
481
436
|
const e = event as PiToolResultEvent;
|
|
482
437
|
const canonicalTool = canonicalizeToolName(e.toolName);
|
|
483
|
-
|
|
438
|
+
callPolicy("tool_result", {
|
|
484
439
|
tool_name: canonicalTool,
|
|
485
440
|
tool_input: canonicalizeToolInput(canonicalTool, e.input) ?? {},
|
|
486
441
|
tool_response: { content: e.content, isError: e.isError },
|
|
@@ -545,7 +500,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
545
500
|
pi.on("session_shutdown", (event: unknown): unknown => {
|
|
546
501
|
const e = event as PiSessionShutdownEvent;
|
|
547
502
|
const cwd = resolveCwd(e.cwd);
|
|
548
|
-
|
|
503
|
+
callPolicy("session_shutdown", {
|
|
549
504
|
session_id: resolveSessionId(e.sessionId, cwd),
|
|
550
505
|
cwd,
|
|
551
506
|
reason: e.reason,
|
package/src/audit/cache.ts
CHANGED
|
@@ -14,7 +14,6 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, openSync
|
|
|
14
14
|
import { join } from "node:path";
|
|
15
15
|
import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
|
|
16
16
|
import { AUDIT_DETECTORS } from "./detectors";
|
|
17
|
-
import { LEAK_SCAN_VERSION } from "./leak-scan";
|
|
18
17
|
import type { TranscriptAuditResult, DetectorSessionState } from "./types";
|
|
19
18
|
import { auditCacheDir } from "../hooks/fp-home";
|
|
20
19
|
|
|
@@ -163,9 +162,6 @@ interface CacheEntry {
|
|
|
163
162
|
sizeBytes: number;
|
|
164
163
|
engineVersion: string;
|
|
165
164
|
detectorVersion: string;
|
|
166
|
-
/** Separate because leak detection is neither a builtin policy nor one of
|
|
167
|
-
* the behavioural AUDIT_DETECTORS hashed above. */
|
|
168
|
-
leakScanVersion: number;
|
|
169
165
|
result: TranscriptAuditResult;
|
|
170
166
|
/**
|
|
171
167
|
* How far into the file the recorded result actually accounts for, at a LINE
|
|
@@ -273,7 +269,6 @@ export function readCachedTranscript(
|
|
|
273
269
|
if (entry.schemaVersion !== CACHE_SCHEMA_VERSION) return null;
|
|
274
270
|
if (entry.engineVersion !== getEngineVersion()) return null;
|
|
275
271
|
if (entry.detectorVersion !== getDetectorVersion()) return null;
|
|
276
|
-
if (entry.leakScanVersion !== LEAK_SCAN_VERSION) return null;
|
|
277
272
|
// Number.isFinite (not typeof) so a malformed JSON `Infinity` /
|
|
278
273
|
// `NaN` is rejected — those would otherwise bypass the TTL check
|
|
279
274
|
// and pin a stale entry as valid forever.
|
|
@@ -337,7 +332,6 @@ export function writeCachedTranscriptResult(
|
|
|
337
332
|
sizeBytes,
|
|
338
333
|
engineVersion: getEngineVersion(),
|
|
339
334
|
detectorVersion: getDetectorVersion(),
|
|
340
|
-
leakScanVersion: LEAK_SCAN_VERSION,
|
|
341
335
|
result,
|
|
342
336
|
};
|
|
343
337
|
if (resume && resume.bytesScanned > 0) {
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
* parses each via lib/log-entries.ts.
|
|
7
7
|
*/
|
|
8
8
|
import { readFile, open, type FileHandle } from "node:fs/promises";
|
|
9
|
-
import { readFileSync } from "node:fs";
|
|
10
9
|
import {
|
|
11
10
|
listClaudeProjects,
|
|
12
11
|
listClaudeTranscripts,
|
|
@@ -41,17 +40,6 @@ export async function listClaudeTranscriptMetadata(
|
|
|
41
40
|
}
|
|
42
41
|
for (const t of transcripts) {
|
|
43
42
|
if (t.mtimeMs < sinceMs) continue;
|
|
44
|
-
let sessionDescription: string | undefined;
|
|
45
|
-
if (t.isSubagent) {
|
|
46
|
-
try {
|
|
47
|
-
const meta = JSON.parse(
|
|
48
|
-
readFileSync(t.transcriptPath.replace(/\.jsonl$/, ".meta.json"), "utf8"),
|
|
49
|
-
) as { description?: unknown };
|
|
50
|
-
if (typeof meta.description === "string") sessionDescription = meta.description;
|
|
51
|
-
} catch {
|
|
52
|
-
// Sidecars are optional (journal transcripts do not have one).
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
43
|
out.push({
|
|
56
44
|
cli: "claude",
|
|
57
45
|
projectName: project.name,
|
|
@@ -59,7 +47,6 @@ export async function listClaudeTranscriptMetadata(
|
|
|
59
47
|
transcriptPath: t.transcriptPath,
|
|
60
48
|
mtimeMs: t.mtimeMs,
|
|
61
49
|
sizeBytes: t.sizeBytes,
|
|
62
|
-
sessionDescription,
|
|
63
50
|
});
|
|
64
51
|
}
|
|
65
52
|
}
|
package/src/audit/cli-login.ts
CHANGED
|
@@ -173,10 +173,7 @@ export async function ensureSignedIn(preset?: string): Promise<EnsureSignedIn> {
|
|
|
173
173
|
throw new LoginError(
|
|
174
174
|
"Signing in needs an interactive terminal, and this one is not.\n" +
|
|
175
175
|
"Run `failproofai audit --schedule` from a shell you are sitting at,\n" +
|
|
176
|
-
"or sign in from the dashboard — both write the same session file
|
|
177
|
-
"\n" +
|
|
178
|
-
"`--email` does not avoid this: it fills in the address so you are not\n" +
|
|
179
|
-
"asked for it, but a one-time code is still sent there for you to paste.",
|
|
176
|
+
"or sign in from the dashboard — both write the same session file.",
|
|
180
177
|
);
|
|
181
178
|
}
|
|
182
179
|
|