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.
Files changed (234) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +6 -6
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
  17. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  18. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  20. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  23. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  24. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  25. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  26. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  27. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  28. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/audit/run/route.js +2 -2
  30. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  39. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  40. package/.next/standalone/.next/server/app/audit/page.js +1 -1
  41. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  43. package/.next/standalone/.next/server/app/index.html +1 -1
  44. package/.next/standalone/.next/server/app/index.rsc +15 -15
  45. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  46. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  47. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  48. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  49. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  50. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  55. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  56. package/.next/standalone/.next/server/app/policies/page.js +2 -2
  57. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  60. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
  62. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +1 -1
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  71. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  72. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  73. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  75. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-dh5jy._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0h3lfmp._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l4vuqm._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0longh-._.js → [root-of-the-server]__0szqzl8._.js} +2 -2
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__10oa-v9._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__18d-9oi._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__1p7s8-d._.js} +2 -2
  88. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +4 -4
  89. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  90. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  91. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0nb0syg._.js → [root-of-the-server]__0cmdyn_._.js} +2 -2
  95. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ath6v_._.js → [root-of-the-server]__0ls0gsr._.js} +3 -3
  98. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0uno39i._.js → [root-of-the-server]__0n_uvv0._.js} +2 -2
  99. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__0rag5dg._.js} +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0rtdfof._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1psjybo._.js → [root-of-the-server]__0sdrz-j._.js} +2 -2
  103. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  104. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0114ewg._.js → [root-of-the-server]__1j4u3yd._.js} +3 -3
  106. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
  108. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  112. 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
  113. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  114. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  115. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +32 -20
  121. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 06jakt_xhoolk.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +6 -0
  124. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 0ue9ak_xexiqh.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  127. package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +1 -0
  128. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  129. package/.next/standalone/.next/static/chunks/2c90h1g6x8z8y.js +1 -0
  130. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 2zb3m1tzq6h98.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  132. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 31cqiv_cpgj9m.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 3h5_3u46gu9jx.js} +1 -1
  134. package/.next/standalone/.next/static/chunks/3zdwnnthucs_1.css +2 -0
  135. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 43a1ywqsg9bq9.js} +1 -1
  136. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  137. package/.next/standalone/Cargo.lock +1992 -198
  138. package/.next/standalone/Cargo.toml +1 -1
  139. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  140. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  141. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  142. package/.next/standalone/crates/CLOUD_POLICIES.md +136 -0
  143. package/.next/standalone/crates/failproofaid/Cargo.toml +7 -0
  144. package/.next/standalone/crates/failproofaid/src/cloud_client.rs +484 -0
  145. package/.next/standalone/crates/failproofaid/src/cloud_policies.rs +921 -0
  146. package/.next/standalone/crates/failproofaid/src/main.rs +452 -3
  147. package/.next/standalone/crates/failproofaid/src/paths.rs +29 -0
  148. package/.next/standalone/crates/fpai-collect/Cargo.toml +43 -0
  149. package/.next/standalone/crates/fpai-collect/src/config.rs +412 -0
  150. package/.next/standalone/crates/fpai-collect/src/cursor.rs +520 -0
  151. package/.next/standalone/crates/fpai-collect/src/delivery.rs +472 -0
  152. package/.next/standalone/crates/fpai-collect/src/filetail.rs +484 -0
  153. package/.next/standalone/crates/fpai-collect/src/health.rs +369 -0
  154. package/.next/standalone/crates/fpai-collect/src/lib.rs +34 -0
  155. package/.next/standalone/crates/fpai-collect/src/redact.rs +516 -0
  156. package/.next/standalone/crates/fpai-collect/src/sources/claude/mod.rs +279 -0
  157. package/.next/standalone/crates/fpai-collect/src/sources/claude/transform.rs +619 -0
  158. package/.next/standalone/crates/fpai-collect/src/sources/codex/mod.rs +156 -0
  159. package/.next/standalone/crates/fpai-collect/src/sources/codex/transform.rs +715 -0
  160. package/.next/standalone/crates/fpai-collect/src/sources/copilot/mod.rs +158 -0
  161. package/.next/standalone/crates/fpai-collect/src/sources/copilot/transform.rs +512 -0
  162. package/.next/standalone/crates/fpai-collect/src/sources/goose/mod.rs +511 -0
  163. package/.next/standalone/crates/fpai-collect/src/sources/goose/transform.rs +586 -0
  164. package/.next/standalone/crates/fpai-collect/src/sources/hermes/mod.rs +318 -0
  165. package/.next/standalone/crates/fpai-collect/src/sources/hermes/transform.rs +579 -0
  166. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +310 -0
  167. package/.next/standalone/crates/fpai-collect/src/sources/hooks/transform.rs +448 -0
  168. package/.next/standalone/crates/fpai-collect/src/sources/mod.rs +23 -0
  169. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/mod.rs +176 -0
  170. package/.next/standalone/crates/fpai-collect/src/sources/openclaw/transform.rs +500 -0
  171. package/.next/standalone/crates/fpai-collect/src/sources/opencode/mod.rs +315 -0
  172. package/.next/standalone/crates/fpai-collect/src/sources/opencode/transform.rs +493 -0
  173. package/.next/standalone/crates/fpai-collect/src/sources/pi/mod.rs +201 -0
  174. package/.next/standalone/crates/fpai-collect/src/sources/pi/transform.rs +533 -0
  175. package/.next/standalone/crates/fpai-collect/src/spool.rs +458 -0
  176. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +387 -0
  177. package/.next/standalone/crates/fpai-collect/src/supervisor.rs +423 -0
  178. package/.next/standalone/crates/fpai-collect/src/uploader.rs +600 -0
  179. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +738 -0
  180. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +393 -0
  181. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +975 -0
  182. package/.next/standalone/crates/fpai-collect/tests/config.rs +286 -0
  183. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1019 -0
  184. package/.next/standalone/crates/fpai-collect/tests/delivery.rs +281 -0
  185. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1008 -0
  186. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1129 -0
  187. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +621 -0
  188. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +854 -0
  189. package/.next/standalone/crates/fpai-collect/tests/opencode_source.rs +1478 -0
  190. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1191 -0
  191. package/.next/standalone/crates/fpai-collect/tests/supervisor.rs +290 -0
  192. package/.next/standalone/crates/fpai-collect/tests/uploader.rs +506 -0
  193. package/.next/standalone/package.json +15 -9
  194. package/.next/standalone/server.js +1 -1
  195. package/bin/failproofai.mjs +129 -0
  196. package/dist/cli.mjs +2255 -779
  197. package/dist/worker.mjs +365 -52
  198. package/package.json +15 -9
  199. package/scripts/build-daemon-packages.mjs +210 -0
  200. package/scripts/daemon-platforms.mjs +42 -0
  201. package/scripts/prune-standalone.mjs +6 -0
  202. package/scripts/publish-aliases.mjs +62 -41
  203. package/src/hooks/builtin-policies.ts +139 -0
  204. package/src/hooks/cloud-connection.ts +205 -0
  205. package/src/hooks/cloud-enrollment-cli.ts +227 -0
  206. package/src/hooks/cloud-enrollment.ts +173 -0
  207. package/src/hooks/cloud-managed-policies.ts +138 -0
  208. package/src/hooks/collector-config.ts +226 -0
  209. package/src/hooks/configure-wizard.ts +221 -5
  210. package/src/hooks/custom-hooks-loader.ts +69 -25
  211. package/src/hooks/daemon-client.ts +18 -0
  212. package/src/hooks/daemon-download.ts +137 -27
  213. package/src/hooks/daemon-service.ts +23 -8
  214. package/src/hooks/handler.ts +144 -6
  215. package/src/hooks/hook-activity-store.ts +50 -0
  216. package/src/hooks/loader-utils.ts +6 -4
  217. package/src/hooks/policy-types.ts +35 -0
  218. package/src/hooks/session-pause-cli.ts +157 -0
  219. package/src/hooks/session-pause.ts +236 -0
  220. package/src/hooks/tui.ts +115 -0
  221. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  222. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  223. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  224. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  225. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  226. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  227. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  228. package/.next/standalone/.next/static/chunks/31feojxhjyjpl.js +0 -1
  229. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  230. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  231. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  232. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_buildManifest.js +0 -0
  233. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → StV6Lay7Q1cU4ysYCFJmH}/_clientMiddlewareManifest.js +0 -0
  234. /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";