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
|
@@ -4,14 +4,8 @@
|
|
|
4
4
|
* Claude stores transcripts at:
|
|
5
5
|
* <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>.jsonl
|
|
6
6
|
*
|
|
7
|
-
* Subagent transcripts (when a session spawned subagents) live alongside
|
|
8
|
-
* shapes — a direct child, and one nested per workflow run:
|
|
7
|
+
* Subagent transcripts (when a session spawned subagents) live alongside:
|
|
9
8
|
* <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/<agentId>.jsonl
|
|
10
|
-
* <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/workflows/<runId>/<agentId>.jsonl
|
|
11
|
-
*
|
|
12
|
-
* Everything below `subagents/` is walked to a bounded depth rather than one
|
|
13
|
-
* level, because the second shape arrived after this module was written and the
|
|
14
|
-
* miss was most of the corpus. See `collectSubagentTranscripts`.
|
|
15
9
|
*
|
|
16
10
|
* The parser for these files lives in `lib/log-entries.ts` (`parseLogContent`,
|
|
17
11
|
* `parseSessionLog`). This module exposes discovery only — the audit pipeline
|
|
@@ -76,99 +70,6 @@ export function listClaudeProjects(): ClaudeProjectFolder[] {
|
|
|
76
70
|
/** Lists every JSONL transcript under one Claude project folder, including
|
|
77
71
|
* subagent transcripts under `<sessionId>/subagents/`. Returns [] on missing
|
|
78
72
|
* or unreadable paths. */
|
|
79
|
-
/**
|
|
80
|
-
* Depth of nesting allowed below `subagents/`.
|
|
81
|
-
*
|
|
82
|
-
* The two shapes that exist today need 0 and 2 (`<agent>.jsonl` and
|
|
83
|
-
* `workflows/<runId>/<agent>.jsonl`). The cap is here so an unexpected layout —
|
|
84
|
-
* or a symlink cycle a future Claude build introduces — costs a bounded walk
|
|
85
|
-
* rather than an audit that never returns, and it is deliberately loose enough
|
|
86
|
-
* that a third shape lands inside it without another release.
|
|
87
|
-
*/
|
|
88
|
-
const MAX_SUBAGENT_DEPTH = 5;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Collect every `.jsonl` beneath a session's `subagents/` directory.
|
|
92
|
-
*
|
|
93
|
-
* This used to read only the DIRECT children of `subagents/`, which was correct
|
|
94
|
-
* for the layout Claude shipped when it was written and silently wrong the day
|
|
95
|
-
* workflow runs started nesting their agents one level further down. On a
|
|
96
|
-
* machine that uses them the miss is most of the corpus: 1,839 transcripts on
|
|
97
|
-
* disk, 1,741 under `subagents/`, and 1,679 of those inside
|
|
98
|
-
* `subagents/workflows/<runId>/` — so the audit was walking 160 files, 8.7% of
|
|
99
|
-
* the evidence, and reporting the result as though it had read everything.
|
|
100
|
-
*
|
|
101
|
-
* ## Session ids come from the relative path, not the basename
|
|
102
|
-
*
|
|
103
|
-
* Every workflow run writes a `journal.jsonl` beside its agents, so basenames
|
|
104
|
-
* are NOT unique below this directory — 1,741 files share 1,613 distinct names
|
|
105
|
-
* on the machine this was found on. A basename id would collide those 128 files
|
|
106
|
-
* onto ~one row each, and `sessionId` is what the cache, the per-session
|
|
107
|
-
* detector state and the example attribution are keyed by, so the collision
|
|
108
|
-
* would silently merge unrelated sessions rather than fail.
|
|
109
|
-
*
|
|
110
|
-
* Nor is the path below `subagents/` unique on its own. A workflow run id is
|
|
111
|
-
* reused when its session is resumed, so `wf_3d609e92-a38/journal.jsonl` exists
|
|
112
|
-
* under two different parent sessions in the same project — found by asserting
|
|
113
|
-
* uniqueness over the real corpus, not by reasoning about it.
|
|
114
|
-
*
|
|
115
|
-
* The id is therefore the PARENT SESSION id followed by the path relative to
|
|
116
|
-
* `subagents/`, minus the extension, joined with `__`:
|
|
117
|
-
* `<parentUuid>__workflows__wf_123__agent-abc`. That is a filesystem path
|
|
118
|
-
* within the project, so it is unique by construction rather than by argument,
|
|
119
|
-
* and it names the session the subagent belongs to. `__` is safe as the joiner
|
|
120
|
-
* because no id Claude generates contains one — verified against 1,741 real
|
|
121
|
-
* files, zero hits. Top-level session ids are untouched.
|
|
122
|
-
*
|
|
123
|
-
* Symlinks are not followed. Nothing in the layout uses them, and following one
|
|
124
|
-
* is how a directory walk turns into an infinite loop.
|
|
125
|
-
*/
|
|
126
|
-
function collectSubagentTranscripts(
|
|
127
|
-
dir: string,
|
|
128
|
-
relPrefix: string,
|
|
129
|
-
project: ClaudeProjectFolder,
|
|
130
|
-
out: ClaudeTranscriptFile[],
|
|
131
|
-
depth: number,
|
|
132
|
-
): void {
|
|
133
|
-
if (depth > MAX_SUBAGENT_DEPTH) return;
|
|
134
|
-
let entries: import("node:fs").Dirent[];
|
|
135
|
-
try {
|
|
136
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
137
|
-
} catch {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
for (const entry of entries) {
|
|
141
|
-
if (entry.isSymbolicLink()) continue;
|
|
142
|
-
if (entry.isDirectory()) {
|
|
143
|
-
collectSubagentTranscripts(
|
|
144
|
-
join(dir, entry.name),
|
|
145
|
-
relPrefix ? `${relPrefix}__${entry.name}` : entry.name,
|
|
146
|
-
project,
|
|
147
|
-
out,
|
|
148
|
-
depth + 1,
|
|
149
|
-
);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
153
|
-
const stem = entry.name.slice(0, -".jsonl".length);
|
|
154
|
-
const transcriptPath = join(dir, entry.name);
|
|
155
|
-
try {
|
|
156
|
-
const s = statSync(transcriptPath);
|
|
157
|
-
out.push({
|
|
158
|
-
projectName: project.name,
|
|
159
|
-
cwd: project.cwd,
|
|
160
|
-
sessionId: relPrefix ? `${relPrefix}__${stem}` : stem,
|
|
161
|
-
transcriptPath,
|
|
162
|
-
mtimeMs: s.mtimeMs,
|
|
163
|
-
sizeBytes: s.size,
|
|
164
|
-
isSubagent: true,
|
|
165
|
-
});
|
|
166
|
-
} catch {
|
|
167
|
-
// unreadable — skip
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
73
|
export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTranscriptFile[] {
|
|
173
74
|
const out: ClaudeTranscriptFile[] = [];
|
|
174
75
|
let entries: import("node:fs").Dirent[];
|
|
@@ -198,9 +99,34 @@ export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTrans
|
|
|
198
99
|
// unreadable — skip
|
|
199
100
|
}
|
|
200
101
|
} else if (entry.isDirectory() && UUID_RE.test(entry.name)) {
|
|
102
|
+
// Subagent transcripts at <sessionId>/subagents/<agentId>.jsonl
|
|
201
103
|
const subDir = join(project.path, entry.name, "subagents");
|
|
202
104
|
if (!existsSync(subDir)) continue;
|
|
203
|
-
|
|
105
|
+
let subEntries: import("node:fs").Dirent[];
|
|
106
|
+
try {
|
|
107
|
+
subEntries = readdirSync(subDir, { withFileTypes: true });
|
|
108
|
+
} catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const sub of subEntries) {
|
|
112
|
+
if (!sub.isFile() || !sub.name.endsWith(".jsonl")) continue;
|
|
113
|
+
const agentId = sub.name.slice(0, -".jsonl".length);
|
|
114
|
+
const transcriptPath = join(subDir, sub.name);
|
|
115
|
+
try {
|
|
116
|
+
const s = statSync(transcriptPath);
|
|
117
|
+
out.push({
|
|
118
|
+
projectName: project.name,
|
|
119
|
+
cwd: project.cwd,
|
|
120
|
+
sessionId: agentId,
|
|
121
|
+
transcriptPath,
|
|
122
|
+
mtimeMs: s.mtimeMs,
|
|
123
|
+
sizeBytes: s.size,
|
|
124
|
+
isSubagent: true,
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
// unreadable — skip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
204
130
|
}
|
|
205
131
|
}
|
|
206
132
|
|
|
@@ -158,21 +158,6 @@ function joinTexts(blocks: CodexContentBlock[] | undefined, wantedType: "input_t
|
|
|
158
158
|
.join("\n");
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/** Text carried by tool outputs. Newer Codex chronicles use an array of
|
|
162
|
-
* `input_text` blocks for `custom_tool_call_output`, while older releases use
|
|
163
|
-
* one string for `function_call_output`. */
|
|
164
|
-
function toolOutputText(output: unknown): string {
|
|
165
|
-
if (typeof output === "string") return output;
|
|
166
|
-
if (Array.isArray(output)) {
|
|
167
|
-
return output
|
|
168
|
-
.filter((block): block is CodexContentBlock => !!block && typeof block === "object")
|
|
169
|
-
.map((block) => typeof block.text === "string" ? block.text : "")
|
|
170
|
-
.filter(Boolean)
|
|
171
|
-
.join("\n");
|
|
172
|
-
}
|
|
173
|
-
return output === undefined ? "" : JSON.stringify(output);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
161
|
/**
|
|
177
162
|
* Parse a Codex JSONL transcript into `LogEntry[]` plus the raw lines.
|
|
178
163
|
* Yields to the event loop every 200 lines so big transcripts don't block.
|
|
@@ -265,11 +250,10 @@ export async function parseCodexLog(
|
|
|
265
250
|
continue;
|
|
266
251
|
}
|
|
267
252
|
|
|
268
|
-
if (subType === "function_call"
|
|
253
|
+
if (subType === "function_call") {
|
|
269
254
|
const callId = payload.call_id as string | undefined;
|
|
270
255
|
const name = (payload.name as string | undefined) ?? "function_call";
|
|
271
|
-
const
|
|
272
|
-
const input = safeJsonParse(encodedInput);
|
|
256
|
+
const input = safeJsonParse(payload.arguments as string | undefined);
|
|
273
257
|
const toolUse: ToolUseBlock = {
|
|
274
258
|
type: "tool_use",
|
|
275
259
|
id: callId ?? `${timestamp}-${name}`,
|
|
@@ -289,7 +273,7 @@ export async function parseCodexLog(
|
|
|
289
273
|
continue;
|
|
290
274
|
}
|
|
291
275
|
|
|
292
|
-
if (subType === "function_call_output"
|
|
276
|
+
if (subType === "function_call_output") {
|
|
293
277
|
const callId = payload.call_id as string | undefined;
|
|
294
278
|
const block = callId ? toolUseById.get(callId) : undefined;
|
|
295
279
|
if (block) {
|
|
@@ -298,7 +282,7 @@ export async function parseCodexLog(
|
|
|
298
282
|
block.result = {
|
|
299
283
|
timestamp,
|
|
300
284
|
timestampFormatted: formatTimestamp(date),
|
|
301
|
-
content:
|
|
285
|
+
content: typeof payload.output === "string" ? (payload.output as string) : JSON.stringify(payload.output),
|
|
302
286
|
durationMs: duration,
|
|
303
287
|
durationFormatted: formatDuration(duration),
|
|
304
288
|
};
|
|
@@ -151,11 +151,6 @@ interface CopilotToolResult {
|
|
|
151
151
|
detailedContent?: string;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
interface CopilotHookToolResult {
|
|
155
|
-
textResultForLlm?: string;
|
|
156
|
-
sessionLog?: string;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
154
|
interface CopilotToolTelemetry {
|
|
160
155
|
metrics?: { commandTimeMs?: number; durationMs?: number };
|
|
161
156
|
properties?: Record<string, unknown>;
|
|
@@ -176,8 +171,6 @@ export async function parseCopilotLog(
|
|
|
176
171
|
// toolCallId → tool_use block, so we can attach tool.execution_complete back.
|
|
177
172
|
const toolUseById = new Map<string, ToolUseBlock>();
|
|
178
173
|
const toolUseStartMs = new Map<string, number>();
|
|
179
|
-
let mostRecentToolUse: ToolUseBlock | undefined;
|
|
180
|
-
let mostRecentToolUseStartMs = 0;
|
|
181
174
|
let cwd: string | undefined;
|
|
182
175
|
let seenSessionStart = false;
|
|
183
176
|
|
|
@@ -280,32 +273,9 @@ export async function parseCopilotLog(
|
|
|
280
273
|
toolUseById.set(callId, toolUse);
|
|
281
274
|
toolUseStartMs.set(callId, date.getTime());
|
|
282
275
|
}
|
|
283
|
-
mostRecentToolUse = toolUse;
|
|
284
|
-
mostRecentToolUseStartMs = date.getTime();
|
|
285
276
|
continue;
|
|
286
277
|
}
|
|
287
278
|
|
|
288
|
-
// Copilot CLI 1.0.39 can omit the command's real stdout from
|
|
289
|
-
// `tool.execution_complete` and expose it only to the PostToolUse hook as
|
|
290
|
-
// `input.toolResult.textResultForLlm`. That is the value the model saw and
|
|
291
|
-
// the value failproofai must audit. Keep it on the active tool block; the
|
|
292
|
-
// later completion record may still contribute timing, but must not replace
|
|
293
|
-
// this output with its shorter status-only summary.
|
|
294
|
-
if (recType === "hook.start" && data.hookType === "postToolUse" && mostRecentToolUse) {
|
|
295
|
-
const input = (data.input as Record<string, unknown> | undefined) ?? {};
|
|
296
|
-
const hookResult = (input.toolResult as CopilotHookToolResult | undefined) ?? {};
|
|
297
|
-
const hookContent = hookResult.textResultForLlm ?? hookResult.sessionLog;
|
|
298
|
-
if (typeof hookContent === "string" && hookContent.length > 0) {
|
|
299
|
-
mostRecentToolUse.result = {
|
|
300
|
-
timestamp,
|
|
301
|
-
timestampFormatted: formatTimestamp(date),
|
|
302
|
-
content: hookContent,
|
|
303
|
-
durationMs: Math.max(0, date.getTime() - mostRecentToolUseStartMs),
|
|
304
|
-
durationFormatted: formatDuration(Math.max(0, date.getTime() - mostRecentToolUseStartMs)),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
279
|
if (recType === "tool.execution_complete") {
|
|
310
280
|
const callId = data.toolCallId as string | undefined;
|
|
311
281
|
const block = callId ? toolUseById.get(callId) : undefined;
|
|
@@ -319,8 +289,7 @@ export async function parseCopilotLog(
|
|
|
319
289
|
typeof reportedMs === "number" && reportedMs >= 0
|
|
320
290
|
? reportedMs
|
|
321
291
|
: Math.max(0, date.getTime() - startMs);
|
|
322
|
-
const
|
|
323
|
-
const content = block.result?.content || completionContent;
|
|
292
|
+
const content = result.detailedContent ?? result.content ?? "";
|
|
324
293
|
block.result = {
|
|
325
294
|
timestamp,
|
|
326
295
|
timestampFormatted: formatTimestamp(date),
|
|
@@ -127,15 +127,7 @@ interface CursorRecord {
|
|
|
127
127
|
/** Cursor 2026-04+ transcript shape: `{role, message: {content: [...]}}`. */
|
|
128
128
|
role?: "user" | "assistant" | "system" | string;
|
|
129
129
|
message?: {
|
|
130
|
-
content?: Array<{
|
|
131
|
-
type?: string;
|
|
132
|
-
text?: string;
|
|
133
|
-
id?: string;
|
|
134
|
-
name?: string;
|
|
135
|
-
input?: Record<string, unknown>;
|
|
136
|
-
tool_use_id?: string;
|
|
137
|
-
content?: unknown;
|
|
138
|
-
}>;
|
|
130
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
139
131
|
};
|
|
140
132
|
}
|
|
141
133
|
|
|
@@ -191,36 +183,11 @@ export async function parseCursorLog(
|
|
|
191
183
|
if (!raw.type && raw.role && raw.message?.content) {
|
|
192
184
|
const synthDate = new Date(SYNTH_T0 + i);
|
|
193
185
|
const synthTs = synthDate.toISOString();
|
|
194
|
-
const
|
|
195
|
-
const textParts = messageContent
|
|
186
|
+
const textParts = raw.message.content
|
|
196
187
|
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
197
188
|
.map((c) => c.text!)
|
|
198
189
|
.join("");
|
|
199
190
|
if (raw.role === "user") {
|
|
200
|
-
// Some Cursor builds emit Claude-style tool_result blocks in the next
|
|
201
|
-
// user record. Pair them before treating any remaining text as a user
|
|
202
|
-
// turn so command output is available to the credential scanner.
|
|
203
|
-
let attachedResult = false;
|
|
204
|
-
for (const block of messageContent) {
|
|
205
|
-
if (block?.type !== "tool_result") continue;
|
|
206
|
-
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
207
|
-
const toolUse = callId ? toolUseById.get(callId) : undefined;
|
|
208
|
-
if (!toolUse) continue;
|
|
209
|
-
const durationMs = Math.max(
|
|
210
|
-
0,
|
|
211
|
-
synthDate.getTime() - (toolUseStartMs.get(callId!) ?? synthDate.getTime()),
|
|
212
|
-
);
|
|
213
|
-
toolUse.result = {
|
|
214
|
-
timestamp: synthTs,
|
|
215
|
-
timestampFormatted: formatTimestamp(synthDate),
|
|
216
|
-
content: typeof block.content === "string"
|
|
217
|
-
? block.content
|
|
218
|
-
: block.content == null ? "" : JSON.stringify(block.content),
|
|
219
|
-
durationMs,
|
|
220
|
-
durationFormatted: formatDuration(durationMs),
|
|
221
|
-
};
|
|
222
|
-
attachedResult = true;
|
|
223
|
-
}
|
|
224
191
|
// Strip the synthesized `<timestamp>...</timestamp>\n<user_query>...\n</user_query>`
|
|
225
192
|
// wrapper Cursor adds for context — keep just the user_query body.
|
|
226
193
|
const queryMatch = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/.exec(textParts);
|
|
@@ -231,37 +198,13 @@ export async function parseCursorLog(
|
|
|
231
198
|
...baseEntry(rawCopy, synthTs, synthDate, source),
|
|
232
199
|
message: { role: "user", content: text },
|
|
233
200
|
} satisfies UserEntry);
|
|
234
|
-
} else if (!attachedResult) {
|
|
235
|
-
entries.push({
|
|
236
|
-
type: "system",
|
|
237
|
-
...baseEntry(rawCopy, synthTs, synthDate, source),
|
|
238
|
-
raw: rawCopy,
|
|
239
|
-
} satisfies GenericEntry);
|
|
240
201
|
}
|
|
241
202
|
continue;
|
|
242
203
|
}
|
|
243
204
|
if (raw.role === "assistant") {
|
|
244
|
-
const blocks: ContentBlock[] =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const contentBlock = messageContent[j];
|
|
248
|
-
if (contentBlock?.type !== "tool_use") continue;
|
|
249
|
-
const name = typeof contentBlock.name === "string" ? contentBlock.name : "tool";
|
|
250
|
-
const id = typeof contentBlock.id === "string" && contentBlock.id
|
|
251
|
-
? contentBlock.id
|
|
252
|
-
: `cursor-${i}-${name}-${j}`;
|
|
253
|
-
const toolUse: ToolUseBlock = {
|
|
254
|
-
type: "tool_use",
|
|
255
|
-
id,
|
|
256
|
-
name,
|
|
257
|
-
input: contentBlock.input && typeof contentBlock.input === "object"
|
|
258
|
-
? contentBlock.input
|
|
259
|
-
: {},
|
|
260
|
-
};
|
|
261
|
-
blocks.push(toolUse);
|
|
262
|
-
toolUseById.set(id, toolUse);
|
|
263
|
-
toolUseStartMs.set(id, synthDate.getTime());
|
|
264
|
-
}
|
|
205
|
+
const blocks: ContentBlock[] = textParts
|
|
206
|
+
? [{ type: "text", text: textParts }]
|
|
207
|
+
: [];
|
|
265
208
|
if (blocks.length === 0) {
|
|
266
209
|
entries.push({
|
|
267
210
|
type: "system",
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Per-CLI dispatcher for the dashboard's Download Logs endpoint.
|
|
3
3
|
*
|
|
4
4
|
* Returns either a real on-disk path (so the route can `createReadStream` and
|
|
5
|
-
* stream the bytes verbatim) or a synthesized
|
|
6
|
-
* transcripts live in SQLite.
|
|
5
|
+
* stream the bytes verbatim) or a synthesized JSONL body (used only by
|
|
6
|
+
* OpenCode, whose transcripts live in SQLite rather than on disk).
|
|
7
7
|
*
|
|
8
8
|
* Per-CLI session loaders / transcript finders already exist in their own
|
|
9
9
|
* files; this module is the thin glue that picks the right one. Imports of
|
|
@@ -34,12 +34,7 @@ export const GOOSE_SESSION_RE = /^\d{8}_\d+$/;
|
|
|
34
34
|
|
|
35
35
|
export type DownloadSource =
|
|
36
36
|
| { kind: "file"; path: string }
|
|
37
|
-
| {
|
|
38
|
-
kind: "synthesized";
|
|
39
|
-
body: string;
|
|
40
|
-
contentType: string;
|
|
41
|
-
extension: string;
|
|
42
|
-
};
|
|
37
|
+
| { kind: "synthesized"; body: string; contentType: string; extension: string };
|
|
43
38
|
|
|
44
39
|
/** Validate a session ID against the per-CLI shape. OpenCode uses `ses_*`
|
|
45
40
|
* prefixes; everyone else is a UUID. */
|
|
@@ -103,12 +98,7 @@ export async function resolveDownloadSource(
|
|
|
103
98
|
const result = await getOpenCodeSessionExport(sessionId);
|
|
104
99
|
if (!result) return null;
|
|
105
100
|
const body = JSON.stringify(result, null, 2) + "\n";
|
|
106
|
-
return {
|
|
107
|
-
kind: "synthesized",
|
|
108
|
-
body,
|
|
109
|
-
contentType: "application/json",
|
|
110
|
-
extension: "json",
|
|
111
|
-
};
|
|
101
|
+
return { kind: "synthesized", body, contentType: "application/json", extension: "json" };
|
|
112
102
|
}
|
|
113
103
|
|
|
114
104
|
if (cli === "hermes") {
|
|
@@ -117,35 +107,13 @@ export async function resolveDownloadSource(
|
|
|
117
107
|
const { getHermesSessionLog } = await import("./hermes-sessions");
|
|
118
108
|
const result = await getHermesSessionLog(sessionId);
|
|
119
109
|
if (!result) return null;
|
|
120
|
-
const body =
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
kind: "synthesized",
|
|
124
|
-
body,
|
|
125
|
-
contentType: "application/x-ndjson",
|
|
126
|
-
extension: "jsonl",
|
|
127
|
-
};
|
|
110
|
+
const body = result.rawLines.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
111
|
+
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
128
112
|
}
|
|
129
113
|
|
|
130
114
|
if (cli === "openclaw") {
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
const { findOpenClawTranscript, listOpenClawAgents, openclawHome } =
|
|
134
|
-
await import("./openclaw-sessions");
|
|
135
|
-
const { readOpenClawSqliteTranscript } = await import("./openclaw-db");
|
|
136
|
-
const sqlite = await readOpenClawSqliteTranscript(
|
|
137
|
-
openclawHome(),
|
|
138
|
-
listOpenClawAgents(),
|
|
139
|
-
sessionId,
|
|
140
|
-
);
|
|
141
|
-
if (sqlite) {
|
|
142
|
-
return {
|
|
143
|
-
kind: "synthesized",
|
|
144
|
-
body: sqlite.eventJsonLines.join("\n") + "\n",
|
|
145
|
-
contentType: "application/x-ndjson",
|
|
146
|
-
extension: "jsonl",
|
|
147
|
-
};
|
|
148
|
-
}
|
|
115
|
+
// OpenClaw writes real JSONL transcripts on disk — stream the file verbatim.
|
|
116
|
+
const { findOpenClawTranscript } = await import("./openclaw-sessions");
|
|
149
117
|
const path = findOpenClawTranscript(sessionId);
|
|
150
118
|
return path ? { kind: "file", path } : null;
|
|
151
119
|
}
|
|
@@ -163,20 +131,13 @@ export async function resolveDownloadSource(
|
|
|
163
131
|
const { getDevinSessionLog } = await import("./devin-sessions");
|
|
164
132
|
const result = await getDevinSessionLog(sessionId);
|
|
165
133
|
if (!result) return null;
|
|
166
|
-
const body =
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
kind: "synthesized",
|
|
170
|
-
body,
|
|
171
|
-
contentType: "application/x-ndjson",
|
|
172
|
-
extension: "jsonl",
|
|
173
|
-
};
|
|
134
|
+
const body = result.rawLines.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
135
|
+
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
174
136
|
}
|
|
175
137
|
|
|
176
138
|
if (cli === "antigravity") {
|
|
177
139
|
// Antigravity (agy) writes real JSONL transcripts on disk — stream verbatim.
|
|
178
|
-
const { findAntigravityTranscript } =
|
|
179
|
-
await import("./antigravity-sessions");
|
|
140
|
+
const { findAntigravityTranscript } = await import("./antigravity-sessions");
|
|
180
141
|
const path = findAntigravityTranscript(sessionId);
|
|
181
142
|
return path ? { kind: "file", path } : null;
|
|
182
143
|
}
|
|
@@ -187,14 +148,8 @@ export async function resolveDownloadSource(
|
|
|
187
148
|
const { getGooseSessionLog } = await import("./goose-sessions");
|
|
188
149
|
const result = await getGooseSessionLog(sessionId);
|
|
189
150
|
if (!result) return null;
|
|
190
|
-
const body =
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
kind: "synthesized",
|
|
194
|
-
body,
|
|
195
|
-
contentType: "application/x-ndjson",
|
|
196
|
-
extension: "jsonl",
|
|
197
|
-
};
|
|
151
|
+
const body = result.rawLines.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
152
|
+
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
198
153
|
}
|
|
199
154
|
|
|
200
155
|
// Exhaustive — but TypeScript can't always see CliId is exhausted across the
|
|
@@ -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 {
|