failproofai 1.0.0-beta.2 → 1.0.0-beta.3
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.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.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.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.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.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.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/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.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]__0qucxyj._.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]__1psjybo._.js → [root-of-the-server]__10s_z4l._.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]__0nb0syg._.js → [root-of-the-server]__1ky3im9._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- 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_0jfts82.js} +1 -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/{3s7myk_co0gbh.js → 01h6lfjz1_oie.js} +1 -1
- package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 0a7k_x4rp_97z.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 0cvd6bcdwchc_.js} +1 -1
- package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 13-obgukzg_ke.js} +1 -1
- package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
- package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 1h0_yaoq5f3yx.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 29g8ykjk6sx_8.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 2rqm-klet_gbl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 2uvyh8y0naofj.js} +1 -1
- package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
- package/.next/standalone/.next/static/chunks/3p-n_j80zlzi3.js +6 -0
- package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
- 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 +2 -2
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +129 -0
- package/dist/cli.mjs +2195 -770
- package/dist/worker.mjs +365 -52
- package/package.json +2 -2
- 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/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/static/chunks/0zbxssxh53n-3.js +0 -1
- package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
- package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
- package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → pM1veX5Llx4YKEaiFc8Sf}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → pM1veX5Llx4YKEaiFc8Sf}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → pM1veX5Llx4YKEaiFc8Sf}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
//! Codex CLI session capture — a [`filetail`](crate::filetail) adapter.
|
|
2
|
+
//!
|
|
3
|
+
//! Codex writes live-appended JSONL rollouts under
|
|
4
|
+
//! `~/.codex/sessions/<YYYY>/<MM>/<DD>/rollout-<local-ts>-<uuid>.jsonl`, one
|
|
5
|
+
//! file per session, identically from the TUI, `codex exec`, and the IDE
|
|
6
|
+
//! extension. We open them read-only and turn each newly-appended line into
|
|
7
|
+
//! AgentEye events; Codex's own files are never written, moved or deleted.
|
|
8
|
+
//!
|
|
9
|
+
//! # The date tree carries no project, so the agent id comes from inside
|
|
10
|
+
//!
|
|
11
|
+
//! Unlike Claude Code, which files transcripts under a per-project directory,
|
|
12
|
+
//! Codex files them by calendar date — every project on the machine lands in
|
|
13
|
+
//! the same folder. The upstream collector concluded from this that "there is
|
|
14
|
+
//! no agent id to read off the path" and filed every codex session under one
|
|
15
|
+
//! configured id, which collapses every repo a developer touches into a single
|
|
16
|
+
//! agent. The working directory is right there in the `session_meta` line, so
|
|
17
|
+
//! [`agent_id_from_path`] reads it from the header instead — see there for why
|
|
18
|
+
//! the scheme has to match the hook source's exactly.
|
|
19
|
+
//!
|
|
20
|
+
//! # The session id must come from the filename
|
|
21
|
+
//!
|
|
22
|
+
//! `session_meta.payload.session_id` is NOT this file's id on a subagent or
|
|
23
|
+
//! forked rollout: on the one such rollout on this machine it holds the
|
|
24
|
+
//! *parent* thread's id while `payload.id` holds the file's, so keying on
|
|
25
|
+
//! `session_id` would merge a subagent's whole transcript into its parent's
|
|
26
|
+
//! timeline. The filename uuid is unambiguous and is also what the cursor keys
|
|
27
|
+
//! on, so it is the only source of the id here — the parent is carried
|
|
28
|
+
//! separately as `parent_id` on `agent_start`.
|
|
29
|
+
//!
|
|
30
|
+
//! # Files in the tree that must not be tailed
|
|
31
|
+
//!
|
|
32
|
+
//! Codex keeps `history.jsonl` and `session_index.jsonl` at `~/.codex/` rather
|
|
33
|
+
//! than under `sessions/`, so the configured root already excludes them; the
|
|
34
|
+
//! `rollout-` prefix and the trailing-uuid requirement exclude them anyway if a
|
|
35
|
+
//! root is ever widened. Archived rollouts are compressed (`.zst`) and
|
|
36
|
+
//! in-progress writes land on `.tmp` names — neither ends in `.jsonl`, so both
|
|
37
|
+
//! are excluded by construction rather than by an explicit rule that could rot.
|
|
38
|
+
//!
|
|
39
|
+
//! # Scope
|
|
40
|
+
//!
|
|
41
|
+
//! This covers the records that carry the session: start/end, prompts,
|
|
42
|
+
//! assistant turns, tool calls with their results, and per-turn token usage.
|
|
43
|
+
//! Deliberately not emitted, each because it would produce a row with nothing
|
|
44
|
+
//! in it or a duplicate of one already emitted: `reasoning` (encrypted, and its
|
|
45
|
+
//! `summary` is empty in all 1,018 on disk), `compacted` / `world_state`,
|
|
46
|
+
//! `patch_apply_end` (an orphan `call_id`), and the `event_msg` restatements of
|
|
47
|
+
//! the conversation. `turn_aborted` is not emitted either — AgentEye's `error`
|
|
48
|
+
//! type is outside the vocabulary this source shares with the Claude one, and
|
|
49
|
+
//! adding it here alone would make the two sources disagree about what a
|
|
50
|
+
//! session's event stream contains.
|
|
51
|
+
|
|
52
|
+
pub mod transform;
|
|
53
|
+
|
|
54
|
+
use std::path::Path;
|
|
55
|
+
|
|
56
|
+
use crate::filetail::{Format, RereadPolicy, no_seed_state};
|
|
57
|
+
use serde_json::Value;
|
|
58
|
+
|
|
59
|
+
/// Rollout transcripts: `rollout-<local-ts>-<uuid>.jsonl` anywhere under the
|
|
60
|
+
/// configured roots.
|
|
61
|
+
pub const FORMAT: Format = Format {
|
|
62
|
+
kind: "codex",
|
|
63
|
+
is_source_file: is_rollout,
|
|
64
|
+
session_id_from_path,
|
|
65
|
+
agent_id_from_path,
|
|
66
|
+
agent_start: transform::agent_start,
|
|
67
|
+
// Codex writes a `turn_context` carrying the model BEFORE the first human
|
|
68
|
+
// prompt in every one of the 13 rollouts measured, so `transform_line` has
|
|
69
|
+
// the model by the time it matters and nothing needs seeding from the
|
|
70
|
+
// header. That sidesteps the live-tail-vs-re-read hazard documented on
|
|
71
|
+
// `Format::seed_state` entirely rather than accepting it as Claude must.
|
|
72
|
+
seed_state: no_seed_state,
|
|
73
|
+
agent_end: transform::agent_end,
|
|
74
|
+
transform_line: transform::transform_line,
|
|
75
|
+
// Rollouts are strictly appended: a byte offset is sufficient. Verified by
|
|
76
|
+
// re-reading line 1 of a live session — unlike factory/droid, Codex never
|
|
77
|
+
// rewrites it to name the session, because the name is already in the path.
|
|
78
|
+
reread: RereadPolicy::ByteCursor,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/// The agent id used when nothing can be derived.
|
|
82
|
+
pub const DEFAULT_AGENT_ID: &str = "codex";
|
|
83
|
+
|
|
84
|
+
const UUID_LEN: usize = 36;
|
|
85
|
+
|
|
86
|
+
/// `8-4-4-4-12` hex, checked positionally.
|
|
87
|
+
///
|
|
88
|
+
/// Stricter than "hex digits and dashes" on purpose: the filename is
|
|
89
|
+
/// `rollout-<YYYY-MM-DDTHH-MM-SS>-<uuid>`, whose timestamp half is also 36-ish
|
|
90
|
+
/// characters of digits and dashes, so a loose check would happily slice a
|
|
91
|
+
/// session id out of the middle of a date.
|
|
92
|
+
fn is_uuid36(s: &str) -> bool {
|
|
93
|
+
s.len() == UUID_LEN
|
|
94
|
+
&& s.bytes().enumerate().all(|(i, b)| match i {
|
|
95
|
+
8 | 13 | 18 | 23 => b == b'-',
|
|
96
|
+
_ => b.is_ascii_hexdigit(),
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// A rollout is `rollout-*.jsonl` whose stem ends in a uuid.
|
|
101
|
+
///
|
|
102
|
+
/// The walk is recursive, so this predicate carries the whole filter. Requiring
|
|
103
|
+
/// the uuid — rather than just the prefix and extension — is what keeps a
|
|
104
|
+
/// future sibling file in the same tree (`rollout-index.jsonl`, say) from being
|
|
105
|
+
/// tailed as a session under a synthetic id nothing else correlates with.
|
|
106
|
+
fn is_rollout(path: &Path) -> bool {
|
|
107
|
+
session_id_from_path(path).is_some()
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// The uuid embedded in the filename IS the session id.
|
|
111
|
+
///
|
|
112
|
+
/// Taken from the trailing 36 characters rather than by counting dash-separated
|
|
113
|
+
/// groups: the timestamp half of the name contains dashes too, so group
|
|
114
|
+
/// counting only works while the timestamp's own format never changes.
|
|
115
|
+
fn session_id_from_path(path: &Path) -> Option<String> {
|
|
116
|
+
let name = path.file_name()?.to_str()?;
|
|
117
|
+
let stem = name.strip_prefix("rollout-")?.strip_suffix(".jsonl")?;
|
|
118
|
+
let cut = stem.len().checked_sub(UUID_LEN)?;
|
|
119
|
+
// A `-` must separate the timestamp from the uuid, so a name that merely
|
|
120
|
+
// ends in 36 hex-ish characters is not sliced mid-token.
|
|
121
|
+
if cut > 0 && !stem[..cut].ends_with('-') {
|
|
122
|
+
return None;
|
|
123
|
+
}
|
|
124
|
+
let uuid = stem.get(cut..)?;
|
|
125
|
+
is_uuid36(uuid).then(|| uuid.to_string())
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/// `codex-<project>`, from the real `cwd` on the rollout's first lines.
|
|
129
|
+
///
|
|
130
|
+
/// NOT from the path: Codex's tree is `sessions/<YYYY>/<MM>/<DD>/`, which
|
|
131
|
+
/// records the date and nothing about the work. Without reading the header,
|
|
132
|
+
/// every project a developer touches files under one agent id and the product
|
|
133
|
+
/// cannot tell them apart at all.
|
|
134
|
+
///
|
|
135
|
+
/// The scheme matches what the hook-activity source derives from the same
|
|
136
|
+
/// working directory, so a hook event and the rollout events for one run land
|
|
137
|
+
/// under a single agent rather than two that look unrelated.
|
|
138
|
+
fn agent_id_from_path(_path: &Path, header: &[String]) -> Option<String> {
|
|
139
|
+
let cwd = header.iter().find_map(|line| {
|
|
140
|
+
let v: Value = serde_json::from_str(line).ok()?;
|
|
141
|
+
// `session_meta` first, but `turn_context` repeats the same value — so
|
|
142
|
+
// a rollout whose header line 1 is truncated still files correctly.
|
|
143
|
+
match v.get("type")?.as_str()? {
|
|
144
|
+
"session_meta" | "turn_context" => {
|
|
145
|
+
v.get("payload")?.get("cwd")?.as_str().map(str::to_string)
|
|
146
|
+
}
|
|
147
|
+
_ => None,
|
|
148
|
+
}
|
|
149
|
+
})?;
|
|
150
|
+
let project = transform::sanitize_id_part(
|
|
151
|
+
cwd.trim_end_matches('/')
|
|
152
|
+
.rsplit('/')
|
|
153
|
+
.find(|p| !p.is_empty())?,
|
|
154
|
+
);
|
|
155
|
+
(!project.is_empty()).then(|| format!("codex-{project}"))
|
|
156
|
+
}
|