failproofai 1.0.0-beta.2 → 1.0.0-beta.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 +6 -6
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +2 -2
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.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/reminder/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 +3 -3
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js +1 -1
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
- package/.next/standalone/.next/server/app/policies/page.js +2 -2
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
- package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-dh5jy._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h3lfmp._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l4vuqm._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0longh-._.js → [root-of-the-server]__0szqzl8._.js} +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10oa-v9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__18d-9oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__1p7s8-d._.js} +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0nb0syg._.js → [root-of-the-server]__0cmdyn_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ath6v_._.js → [root-of-the-server]__0ls0gsr._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0uno39i._.js → [root-of-the-server]__0n_uvv0._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__0rag5dg._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0rtdfof._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1psjybo._.js → [root-of-the-server]__0sdrz-j._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0114ewg._.js → [root-of-the-server]__1j4u3yd._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_0oy_-36.js → node_modules_html-to-image_es_index_1q9xxca.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +32 -20
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 06jakt_xhoolk.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +6 -0
- package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 0ue9ak_xexiqh.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +1 -0
- package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
- package/.next/standalone/.next/static/chunks/2c90h1g6x8z8y.js +1 -0
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 2zb3m1tzq6h98.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 31cqiv_cpgj9m.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 3h5_3u46gu9jx.js} +1 -1
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 43a1ywqsg9bq9.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
- package/.next/standalone/Cargo.lock +1992 -198
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
- package/.next/standalone/app/components/pause-notices.tsx +111 -0
- package/.next/standalone/app/policies/hooks-client.tsx +81 -6
- package/.next/standalone/crates/CLOUD_POLICIES.md +136 -0
- package/.next/standalone/crates/failproofaid/Cargo.toml +7 -0
- package/.next/standalone/crates/failproofaid/src/cloud_client.rs +484 -0
- package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +921 -0
- package/.next/standalone/crates/failproofaid/src/main.rs +452 -3
- package/.next/standalone/crates/failproofaid/src/paths.rs +29 -0
- package/.next/standalone/crates/fpai-collect/Cargo.toml +43 -0
- package/.next/standalone/crates/fpai-collect/src/config.rs +412 -0
- package/.next/standalone/crates/fpai-collect/src/cursor.rs +520 -0
- package/.next/standalone/crates/fpai-collect/src/delivery.rs +472 -0
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +484 -0
- package/.next/standalone/crates/fpai-collect/src/health.rs +369 -0
- package/.next/standalone/crates/fpai-collect/src/lib.rs +34 -0
- package/.next/standalone/crates/fpai-collect/src/redact.rs +516 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +279 -0
- package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +619 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +156 -0
- package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +715 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +158 -0
- package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +512 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +511 -0
- package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +586 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +318 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +579 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +310 -0
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +448 -0
- package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +23 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +176 -0
- package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +500 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +315 -0
- package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +493 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +201 -0
- package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +533 -0
- package/.next/standalone/crates/fpai-collect/src/spool.rs +458 -0
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +387 -0
- package/.next/standalone/crates/fpai-collect/src/supervisor.rs +423 -0
- package/.next/standalone/crates/fpai-collect/src/uploader.rs +600 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +738 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +393 -0
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +975 -0
- package/.next/standalone/crates/fpai-collect/tests/config.rs +286 -0
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1019 -0
- package/.next/standalone/crates/fpai-collect/tests/delivery.rs +281 -0
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1008 -0
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1129 -0
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +621 -0
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +854 -0
- package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +1478 -0
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1191 -0
- package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +290 -0
- package/.next/standalone/crates/fpai-collect/tests/uploader.rs +506 -0
- package/.next/standalone/package.json +15 -9
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +129 -0
- package/dist/cli.mjs +2255 -779
- package/dist/worker.mjs +365 -52
- package/package.json +15 -9
- package/scripts/build-daemon-packages.mjs +210 -0
- package/scripts/daemon-platforms.mjs +42 -0
- package/scripts/prune-standalone.mjs +6 -0
- package/scripts/publish-aliases.mjs +62 -41
- package/src/hooks/builtin-policies.ts +139 -0
- package/src/hooks/cloud-connection.ts +205 -0
- package/src/hooks/cloud-enrollment-cli.ts +227 -0
- package/src/hooks/cloud-enrollment.ts +173 -0
- package/src/hooks/cloud-managed-policies.ts +138 -0
- package/src/hooks/collector-config.ts +226 -0
- package/src/hooks/configure-wizard.ts +221 -5
- package/src/hooks/custom-hooks-loader.ts +69 -25
- package/src/hooks/daemon-client.ts +18 -0
- package/src/hooks/daemon-download.ts +137 -27
- package/src/hooks/daemon-service.ts +23 -8
- package/src/hooks/handler.ts +144 -6
- package/src/hooks/hook-activity-store.ts +50 -0
- package/src/hooks/loader-utils.ts +6 -4
- package/src/hooks/policy-types.ts +35 -0
- package/src/hooks/session-pause-cli.ts +157 -0
- package/src/hooks/session-pause.ts +236 -0
- package/src/hooks/tui.ts +115 -0
- package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
- package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
- package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/31feojxhjyjpl.js +0 -1
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
//! Claude Code session capture — a [`filetail`](crate::filetail) adapter.
|
|
2
|
+
//!
|
|
3
|
+
//! Claude Code writes live-appended JSONL transcripts under
|
|
4
|
+
//! `~/.claude/projects/<slug>/<sessionId>.jsonl`. We open them read-only and
|
|
5
|
+
//! turn each newly-appended line into AgentEye events; Claude Code's own files
|
|
6
|
+
//! are never written, moved or deleted.
|
|
7
|
+
//!
|
|
8
|
+
//! # The layout (verified against 158 transcripts in 14 projects, CC 2.1.220)
|
|
9
|
+
//!
|
|
10
|
+
//! ```text
|
|
11
|
+
//! projects/<slug>/<sessionId>.jsonl main
|
|
12
|
+
//! projects/<slug>/<sessionId>/subagents/agent-<agentId>.jsonl subagent
|
|
13
|
+
//! projects/<slug>/<sessionId>/subagents/workflows/wf_<id>/agent-<agentId>.jsonl
|
|
14
|
+
//! ```
|
|
15
|
+
//!
|
|
16
|
+
//! # Siblings that must not be tailed
|
|
17
|
+
//!
|
|
18
|
+
//! Three live in the same tree and each breaks something different:
|
|
19
|
+
//!
|
|
20
|
+
//! * `<sessionId>.jsonl.tool-calls.json` — rewritten **in place**, so a byte
|
|
21
|
+
//! cursor would re-ship its contents forever. Excluded by requiring `.jsonl`.
|
|
22
|
+
//! * `subagents/**/journal.jsonl` — a different schema (`{type, key, agentId}`,
|
|
23
|
+
//! no timestamps at all). Excluded from [`FORMAT`] by requiring a bare-UUID
|
|
24
|
+
//! stem and from [`SUBAGENT_FORMAT`] by requiring an `agent-` prefix.
|
|
25
|
+
//! * `~/.claude/history.jsonl` — out of scope by construction, since the
|
|
26
|
+
//! configured root is `projects/` rather than the home.
|
|
27
|
+
//!
|
|
28
|
+
//! # `/compact` rewrites the transcript
|
|
29
|
+
//!
|
|
30
|
+
//! Compaction can shrink the file. The engine detects the shrink and re-reads
|
|
31
|
+
//! from zero, which re-ships the surviving prefix; the server dedups it because
|
|
32
|
+
//! every event is a pure function of its line and offset. Separately, the
|
|
33
|
+
//! `system`/`compact_boundary` record it leaves behind is transformed into an
|
|
34
|
+
//! event — see [`transform::transform_line`].
|
|
35
|
+
//!
|
|
36
|
+
//! # Why there are two `Format`s
|
|
37
|
+
//!
|
|
38
|
+
//! [`FORMAT`] claims main transcripts; [`SUBAGENT_FORMAT`] claims subagent ones.
|
|
39
|
+
//! They must stay **disjoint**: a file claimed by both would ship every line
|
|
40
|
+
//! twice, under two different session ids. `is_source_file` is a bare
|
|
41
|
+
//! `fn(&Path) -> bool` with nowhere to hide shared state, so the disjointness is
|
|
42
|
+
//! carried by the `subagents` path component alone — asserted by a test.
|
|
43
|
+
|
|
44
|
+
pub mod transform;
|
|
45
|
+
|
|
46
|
+
use std::path::Path;
|
|
47
|
+
|
|
48
|
+
use crate::cursor::TailState;
|
|
49
|
+
use crate::filetail::{Format, RereadPolicy};
|
|
50
|
+
use serde_json::Value;
|
|
51
|
+
|
|
52
|
+
/// Main transcripts: `<uuid>.jsonl` directly inside a project directory.
|
|
53
|
+
pub const FORMAT: Format = Format {
|
|
54
|
+
kind: "claude",
|
|
55
|
+
is_source_file: is_transcript,
|
|
56
|
+
session_id_from_path,
|
|
57
|
+
agent_id_from_path,
|
|
58
|
+
agent_start: transform::agent_start,
|
|
59
|
+
seed_state,
|
|
60
|
+
agent_end: transform::agent_end,
|
|
61
|
+
transform_line: transform::transform_line,
|
|
62
|
+
reread: RereadPolicy::ByteCursor,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/// Subagent transcripts: `agent-<agentId>.jsonl` under a `subagents/` ancestor.
|
|
66
|
+
///
|
|
67
|
+
/// Only the file-identity half differs from [`FORMAT`]. The line grammar is
|
|
68
|
+
/// byte-for-byte the same — a subagent's `user` and `assistant` records carry
|
|
69
|
+
/// the identical fields, verified across all 122 subagent transcripts on this
|
|
70
|
+
/// machine — so `transform_line`, `seed_state` and `agent_end` are shared
|
|
71
|
+
/// rather than cloned, and any fix to the main path reaches both.
|
|
72
|
+
pub const SUBAGENT_FORMAT: Format = Format {
|
|
73
|
+
kind: "claude-subagent",
|
|
74
|
+
is_source_file: is_subagent_transcript,
|
|
75
|
+
session_id_from_path: subagent_session_id,
|
|
76
|
+
agent_id_from_path: subagent_agent_id,
|
|
77
|
+
agent_start: transform::subagent_start,
|
|
78
|
+
seed_state,
|
|
79
|
+
agent_end: transform::agent_end,
|
|
80
|
+
transform_line: transform::transform_line,
|
|
81
|
+
reread: RereadPolicy::ByteCursor,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const SUBAGENTS_DIR: &str = "subagents";
|
|
85
|
+
|
|
86
|
+
fn is_uuid36(s: &str) -> bool {
|
|
87
|
+
s.len() == 36 && s.chars().all(|c| c.is_ascii_hexdigit() || c == '-')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn under_subagents(path: &Path) -> bool {
|
|
91
|
+
path.ancestors()
|
|
92
|
+
.skip(1)
|
|
93
|
+
.any(|a| a.file_name().is_some_and(|n| n == SUBAGENTS_DIR))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/// A main transcript is `<uuid>.jsonl` not under a `subagents/` ancestor.
|
|
97
|
+
///
|
|
98
|
+
/// The walk is recursive, so this predicate carries the whole filter. The
|
|
99
|
+
/// `.jsonl` requirement excludes the in-place-rewritten `.tool-calls.json`; the
|
|
100
|
+
/// bare-UUID stem excludes `journal.jsonl`; the `subagents` check keeps this
|
|
101
|
+
/// from claiming files a future subagent format will own, which would otherwise
|
|
102
|
+
/// ship every subagent line twice under two session ids.
|
|
103
|
+
fn is_transcript(path: &Path) -> bool {
|
|
104
|
+
let Some(name) = path.file_name().and_then(|n| n.to_str()) else {
|
|
105
|
+
return false;
|
|
106
|
+
};
|
|
107
|
+
let Some(stem) = name.strip_suffix(".jsonl") else {
|
|
108
|
+
return false;
|
|
109
|
+
};
|
|
110
|
+
is_uuid36(stem) && !under_subagents(path)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// The `subagents/` directory itself, for a path beneath it.
|
|
114
|
+
///
|
|
115
|
+
/// Anchored on the literal component, never on a depth count: the workflow
|
|
116
|
+
/// layout inserts two extra levels (`subagents/workflows/wf_<id>/`), so a
|
|
117
|
+
/// depth-based guess is confidently wrong on exactly one of the two shapes and
|
|
118
|
+
/// would file those subagents under a session id that does not exist.
|
|
119
|
+
fn subagents_dir(path: &Path) -> Option<&Path> {
|
|
120
|
+
path.ancestors()
|
|
121
|
+
.skip(1)
|
|
122
|
+
.find(|a| a.file_name().is_some_and(|n| n == SUBAGENTS_DIR))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// The agent id a subagent filename carries: `agent-<agentId>.jsonl`.
|
|
126
|
+
///
|
|
127
|
+
/// The `agent-` prefix is what excludes `journal.jsonl`, the one other `.jsonl`
|
|
128
|
+
/// that lives under `subagents/`. Measured across all 124 files there: 122
|
|
129
|
+
/// match `agent-<17 hex>` and 2 are `journal.jsonl`. The hex shape is
|
|
130
|
+
/// deliberately NOT required — a future id alphabet would then silently stop
|
|
131
|
+
/// being captured, whereas the prefix alone already separates the two.
|
|
132
|
+
fn subagent_id(path: &Path) -> Option<&str> {
|
|
133
|
+
let agent = path
|
|
134
|
+
.file_name()?
|
|
135
|
+
.to_str()?
|
|
136
|
+
.strip_suffix(".jsonl")?
|
|
137
|
+
.strip_prefix("agent-")?;
|
|
138
|
+
(!agent.is_empty()).then_some(agent)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// A subagent transcript is `agent-<agentId>.jsonl` under a `subagents/`
|
|
142
|
+
/// ancestor. Disjoint from [`is_transcript`] by that same ancestor check.
|
|
143
|
+
fn is_subagent_transcript(path: &Path) -> bool {
|
|
144
|
+
subagent_id(path).is_some() && under_subagents(path)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// A subagent becomes a CHILD session keyed `<parentSessionId>:<agentId>`.
|
|
148
|
+
///
|
|
149
|
+
/// The parent id is the directory *containing* `subagents/` — not anything in
|
|
150
|
+
/// the file. Every subagent line does carry a `sessionId`, but it is the
|
|
151
|
+
/// PARENT's, so trusting it would collapse every subagent into its parent's
|
|
152
|
+
/// timeline. Verified on all 122: the in-file `sessionId` equals the containing
|
|
153
|
+
/// directory's name and the in-file `agentId` equals the filename's, so the
|
|
154
|
+
/// path is both sufficient and in agreement.
|
|
155
|
+
fn subagent_session_id(path: &Path) -> Option<String> {
|
|
156
|
+
let agent = subagent_id(path)?;
|
|
157
|
+
let parent = subagents_dir(path)?.parent()?.file_name()?.to_str()?;
|
|
158
|
+
(!parent.is_empty()).then(|| format!("{parent}:{agent}"))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/// `claude-<agentType>` — the native agent name (`Explore`, `general-purpose`,
|
|
162
|
+
/// `Plan`), so the kinds separate in the product instead of collapsing into one.
|
|
163
|
+
///
|
|
164
|
+
/// The type is on NO transcript line as `agentType`; it lives in the
|
|
165
|
+
/// `agent-<id>.meta.json` sidecar. The sidecar is tried first and that ordering
|
|
166
|
+
/// is load-bearing: `agent_id` is frozen onto the cursor at discovery, and a
|
|
167
|
+
/// subagent's `agent_start` resolves on byte 0 (its first line is a real
|
|
168
|
+
/// timestamped user turn), whereas the in-file `attributionAgent` fallback does
|
|
169
|
+
/// not appear until line 3-4 — so a file discovered one line in would freeze the
|
|
170
|
+
/// fallback onto every event of that session. Measured here: the sidecar exists
|
|
171
|
+
/// for 122 of 122, and `attributionAgent` agrees with it in 122 of 122.
|
|
172
|
+
///
|
|
173
|
+
/// `None` when neither is readable, which hands the engine's configured default
|
|
174
|
+
/// ([`SUBAGENT_DEFAULT_AGENT_ID`]) back: an unnameable subagent must still be
|
|
175
|
+
/// captured.
|
|
176
|
+
fn subagent_agent_id(path: &Path, header: &[String]) -> Option<String> {
|
|
177
|
+
let raw = sidecar_agent_type(path).or_else(|| header_agent_type(header))?;
|
|
178
|
+
let part = transform::sanitize_id_part(&raw);
|
|
179
|
+
(!part.is_empty()).then(|| format!("claude-{part}"))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// `agentType` from the sidecar beside a subagent transcript.
|
|
183
|
+
///
|
|
184
|
+
/// A blocking read, deliberately: the file is ~130 bytes and this runs once per
|
|
185
|
+
/// newly discovered transcript, from the same code path that already stats and
|
|
186
|
+
/// reads the transcript's header. Every failure — absent, unreadable,
|
|
187
|
+
/// malformed, no `agentType` — is a `None` that routes to the next fallback.
|
|
188
|
+
fn sidecar_agent_type(path: &Path) -> Option<String> {
|
|
189
|
+
let stem = path.file_name()?.to_str()?.strip_suffix(".jsonl")?;
|
|
190
|
+
let text = std::fs::read_to_string(path.with_file_name(format!("{stem}.meta.json"))).ok()?;
|
|
191
|
+
let v: Value = serde_json::from_str(&text).ok()?;
|
|
192
|
+
let t = v.get("agentType")?.as_str()?;
|
|
193
|
+
(!t.is_empty()).then(|| t.to_string())
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// `attributionAgent` off the header — the same name, written into the
|
|
197
|
+
/// transcript itself. Present on 11,299 assistant lines corpus-wide.
|
|
198
|
+
fn header_agent_type(header: &[String]) -> Option<String> {
|
|
199
|
+
header.iter().find_map(|line| {
|
|
200
|
+
serde_json::from_str::<Value>(line)
|
|
201
|
+
.ok()?
|
|
202
|
+
.get("attributionAgent")?
|
|
203
|
+
.as_str()
|
|
204
|
+
.filter(|s| !s.is_empty())
|
|
205
|
+
.map(str::to_string)
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// The filename stem IS the session id.
|
|
210
|
+
///
|
|
211
|
+
/// Deliberately the only source of it: taking it from the file's header too
|
|
212
|
+
/// would give two answers that could disagree, and the path is what the cursor
|
|
213
|
+
/// and the hook-activity source both key on.
|
|
214
|
+
fn session_id_from_path(path: &Path) -> Option<String> {
|
|
215
|
+
let stem = path.file_name()?.to_str()?.strip_suffix(".jsonl")?;
|
|
216
|
+
is_uuid36(stem).then(|| stem.to_string())
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// `claude-<project>`, from the real `cwd` on the transcript's first lines.
|
|
220
|
+
///
|
|
221
|
+
/// NOT decoded from the directory name. Claude Code encodes the working
|
|
222
|
+
/// directory by replacing every `/` with `-`, and folder names contain `-`
|
|
223
|
+
/// too — so the encoding is not invertible. Measured on this machine, 3 of 16
|
|
224
|
+
/// project directories decode wrongly by splitting on the last `-`
|
|
225
|
+
/// (`openclaw-local`, `agentic-test`, `sensor-app`). The `cwd` field is exact.
|
|
226
|
+
///
|
|
227
|
+
/// The scheme matches what the hook-activity source derives, so hook events and
|
|
228
|
+
/// transcript events for one run land under a single agent rather than two that
|
|
229
|
+
/// look unrelated.
|
|
230
|
+
fn agent_id_from_path(_path: &Path, header: &[String]) -> Option<String> {
|
|
231
|
+
let cwd = header.iter().find_map(|line| {
|
|
232
|
+
serde_json::from_str::<Value>(line)
|
|
233
|
+
.ok()?
|
|
234
|
+
.get("cwd")?
|
|
235
|
+
.as_str()
|
|
236
|
+
.map(str::to_string)
|
|
237
|
+
})?;
|
|
238
|
+
let project = transform::sanitize_id_part(
|
|
239
|
+
cwd.trim_end_matches('/')
|
|
240
|
+
.rsplit('/')
|
|
241
|
+
.find(|p| !p.is_empty())?,
|
|
242
|
+
);
|
|
243
|
+
(!project.is_empty()).then(|| format!("claude-{project}"))
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Prime the model from the header so the session's FIRST prompt is not blank.
|
|
247
|
+
///
|
|
248
|
+
/// A user line carries no model and inherits it from the preceding assistant
|
|
249
|
+
/// line — and the first prompt of a session has no preceding assistant line at
|
|
250
|
+
/// all. The server builds a `model_request` row's summary from the model alone,
|
|
251
|
+
/// so without this the opening row of every session renders empty, which is the
|
|
252
|
+
/// row most likely to be looked at.
|
|
253
|
+
///
|
|
254
|
+
/// See the warning on [`Format::seed_state`]: this is not dedup-safe between a
|
|
255
|
+
/// live tail and a later full re-read. Accepted deliberately here — a blank
|
|
256
|
+
/// opening row is a permanent visible defect, a duplicate is a transient one.
|
|
257
|
+
fn seed_state(header: &[String], state: &mut TailState) {
|
|
258
|
+
for line in header {
|
|
259
|
+
let Ok(v) = serde_json::from_str::<Value>(line) else {
|
|
260
|
+
continue;
|
|
261
|
+
};
|
|
262
|
+
if let Some(model) = v
|
|
263
|
+
.get("message")
|
|
264
|
+
.and_then(|m| m.get("model"))
|
|
265
|
+
.and_then(|m| m.as_str())
|
|
266
|
+
{
|
|
267
|
+
state.last_model = Some(model.to_string());
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/// The agent id used when nothing can be derived.
|
|
274
|
+
pub const DEFAULT_AGENT_ID: &str = "claude";
|
|
275
|
+
|
|
276
|
+
/// The agent id used for a subagent whose type cannot be resolved. Distinct
|
|
277
|
+
/// from [`DEFAULT_AGENT_ID`] so an unnameable subagent is still visibly a
|
|
278
|
+
/// subagent rather than merging into the main-transcript catch-all.
|
|
279
|
+
pub const SUBAGENT_DEFAULT_AGENT_ID: &str = "claude-subagent";
|