failproofai 1.0.4-beta.8 → 1.0.5-beta.0

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 (246) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  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 +2 -2
  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 +2 -2
  8. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  12. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  13. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  16. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
  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 +14 -14
  22. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  34. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  36. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
  37. package/.next/standalone/.next/server/app/audit/page.js +2 -2
  38. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/index.html +1 -1
  41. package/.next/standalone/.next/server/app/index.rsc +14 -14
  42. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  43. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  44. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  45. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/page.js +2 -2
  48. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  52. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  53. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
  58. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  61. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
  64. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  68. package/.next/standalone/.next/server/app/projects/page.js +3 -3
  69. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  70. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  71. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  72. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
  73. package/.next/standalone/.next/server/app/settings/page.js +4 -4
  74. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  75. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/chunks/{[externals]__0hmcwgg._.js → [externals]__0-9ti7z._.js} +1 -1
  77. package/.next/standalone/.next/server/chunks/{[externals]__14b7cc6._.js → [externals]__0u3wrvq._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
  87. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/{_1_eakl4._.js → _1-akg6w._.js} +2 -2
  90. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
  91. package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__187jks6._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__04px77e._.js → [root-of-the-server]__013jr2b._.js} +3 -3
  97. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0i0l1di._.js → [root-of-the-server]__01wy8d-._.js} +3 -3
  98. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0r3c1_0._.js → [root-of-the-server]__02npjtd._.js} +3 -3
  99. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ckpmkk._.js → [root-of-the-server]__0da85px._.js} +3 -3
  100. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__17xnxlx._.js → [root-of-the-server]__0ftmoxc._.js} +3 -3
  101. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0-4ja6q._.js → [root-of-the-server]__0p-5p8u._.js} +3 -3
  104. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0egr5s1._.js → [root-of-the-server]__0s740oi._.js} +3 -3
  105. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fa1jd9._.js → [root-of-the-server]__0x1aj1_._.js} +2 -2
  106. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1kpd1za._.js → [root-of-the-server]__190won8._.js} +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cr3xu._.js → [root-of-the-server]__1p2otjt._.js} +3 -3
  110. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__1sc84bb._.js} +2 -21
  111. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
  113. package/.next/standalone/.next/server/chunks/ssr/{_00bqkhj._.js → _0u3xxgc._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/ssr/_0zs_4uu._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
  116. package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
  118. package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1if9ixi._.js} +2 -21
  119. package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
  120. package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
  124. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
  128. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
  129. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
  131. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  133. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  134. package/.next/standalone/.next/server/pages/404.html +1 -1
  135. package/.next/standalone/.next/server/pages/500.html +1 -1
  136. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  137. package/.next/standalone/.next/server/server-reference-manifest.json +21 -65
  138. package/.next/standalone/.next/static/chunks/0hw6-c206lsma.js +1 -0
  139. package/.next/standalone/.next/static/chunks/{2mizt96igig1y.js → 0m9sx3khjrl1q.js} +1 -1
  140. package/.next/standalone/.next/static/chunks/{1302duobnz07l.js → 0oos8qpc1ugos.js} +1 -1
  141. package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
  142. package/.next/standalone/.next/static/chunks/{2ukucf6g-kbs5.js → 0unrx9g_x0u7j.js} +1 -1
  143. package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
  144. package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
  145. package/.next/standalone/.next/static/chunks/{0vm4wdx3w-2_w.js → 2qjn-leir0x_s.js} +1 -1
  146. package/.next/standalone/.next/static/chunks/{1chm9piluggc-.js → 2xbxkaszi1xnw.js} +1 -1
  147. package/.next/standalone/.next/static/chunks/{2n85g-crgcx5j.js → 33umhy-x204qj.js} +2 -2
  148. package/.next/standalone/.next/static/chunks/3nux7kkrc3eps.js +69 -0
  149. package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
  150. package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
  151. package/.next/standalone/.next/static/chunks/{3a4gth64hwtwf.js → 40tpgs4fjct5p.js} +1 -1
  152. package/.next/standalone/.next/static/chunks/{1_x7pqemw5n_w.js → 40uih96wnkowy.js} +1 -1
  153. package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
  154. package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
  155. package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
  156. package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
  157. package/.next/standalone/app/api/auth/status/route.ts +2 -3
  158. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
  159. package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
  160. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
  161. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
  162. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
  163. package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
  164. package/.next/standalone/app/audit/audit-styles.css +1 -132
  165. package/.next/standalone/app/settings/page.tsx +1 -1
  166. package/.next/standalone/app/settings/settings-client.tsx +45 -50
  167. package/.next/standalone/lib/auth/api-server-client.ts +5 -33
  168. package/.next/standalone/lib/claude-sessions.ts +27 -101
  169. package/.next/standalone/lib/codex-sessions.ts +4 -20
  170. package/.next/standalone/lib/copilot-sessions.ts +1 -32
  171. package/.next/standalone/lib/cursor-sessions.ts +5 -62
  172. package/.next/standalone/lib/download-session.ts +13 -58
  173. package/.next/standalone/lib/openclaw-projects.ts +18 -54
  174. package/.next/standalone/lib/openclaw-sessions.ts +17 -75
  175. package/.next/standalone/lib/sqlite-reader.ts +1 -78
  176. package/.next/standalone/package.json +9 -9
  177. package/.next/standalone/sdk/python/CHANGELOG.md +32 -9
  178. package/.next/standalone/sdk/python/failproofai_sdk/_events.py +20 -0
  179. package/.next/standalone/sdk/python/failproofai_sdk/_redact.py +233 -0
  180. package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +4 -1
  181. package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +5 -5
  182. package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +2 -2
  183. package/.next/standalone/sdk/python/tests/test_redaction.py +140 -0
  184. package/.next/standalone/sdk/python/tests/test_sdk.py +87 -0
  185. package/.next/standalone/sdk/python/tests/test_server_contract.py +60 -0
  186. package/.next/standalone/sdk/python/tests/test_site_docs.py +2 -2
  187. package/.next/standalone/server.js +1 -1
  188. package/bin/failproofai.mjs +1 -1
  189. package/dist/cli.mjs +664 -2121
  190. package/dist/worker.mjs +105 -422
  191. package/lib/auth/api-server-client.ts +5 -33
  192. package/lib/claude-sessions.ts +27 -101
  193. package/lib/codex-sessions.ts +4 -20
  194. package/lib/copilot-sessions.ts +1 -32
  195. package/lib/cursor-sessions.ts +5 -62
  196. package/lib/download-session.ts +13 -58
  197. package/lib/openclaw-projects.ts +18 -54
  198. package/lib/openclaw-sessions.ts +17 -75
  199. package/lib/sqlite-reader.ts +1 -78
  200. package/package.json +9 -9
  201. package/pi-extension/index.ts +5 -50
  202. package/src/audit/cache.ts +0 -6
  203. package/src/audit/cli-adapters/claude.ts +0 -13
  204. package/src/audit/cli-login.ts +1 -4
  205. package/src/audit/cli.ts +7 -157
  206. package/src/audit/harm-report.ts +4 -111
  207. package/src/audit/index.ts +15 -286
  208. package/src/audit/machine-store.ts +9 -8
  209. package/src/audit/redact-example.ts +15 -324
  210. package/src/audit/report-harm.ts +37 -33
  211. package/src/audit/report.ts +3 -6
  212. package/src/audit/schedule-cli.ts +33 -54
  213. package/src/audit/scoring.ts +0 -49
  214. package/src/audit/types.ts +0 -52
  215. package/src/hooks/builtin-policies.ts +2 -67
  216. package/src/hooks/configure-wizard.ts +0 -55
  217. package/src/hooks/fp-config.ts +48 -83
  218. package/src/hooks/fp-home.ts +0 -27
  219. package/src/hooks/integrations.ts +14 -51
  220. package/src/hooks/uninstall-cli.ts +2 -17
  221. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +0 -22
  222. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
  223. package/.next/standalone/.next/server/chunks/[root-of-the-server]__112b4go._.js +0 -24
  224. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +0 -22
  225. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
  226. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n2txhk._.js +0 -3
  227. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +0 -22
  228. package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
  229. package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
  230. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
  231. package/.next/standalone/.next/static/chunks/0igq884ziuv-8.js +0 -1
  232. package/.next/standalone/.next/static/chunks/349jkb79v5g6z.js +0 -1
  233. package/.next/standalone/app/actions/get-leaks.ts +0 -104
  234. package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
  235. package/.next/standalone/lib/openclaw-db.ts +0 -271
  236. package/lib/openclaw-db.ts +0 -271
  237. package/src/audit/desktop-notify.ts +0 -420
  238. package/src/audit/leak-fingerprint.ts +0 -198
  239. package/src/audit/leak-record.ts +0 -248
  240. package/src/audit/leak-scan.ts +0 -268
  241. package/src/audit/leak-store.ts +0 -220
  242. package/src/audit/macos-notifier.ts +0 -298
  243. package/src/hooks/notice.ts +0 -24
  244. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → 4_QeL7mSPYsuiSVJmBOTj}/_buildManifest.js +0 -0
  245. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → 4_QeL7mSPYsuiSVJmBOTj}/_clientMiddlewareManifest.js +0 -0
  246. /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → 4_QeL7mSPYsuiSVJmBOTj}/_ssgManifest.js +0 -0
@@ -29,8 +29,12 @@
29
29
  * acts and for anything wrong, mint for the thing that is alive. No third hue,
30
30
  * no new webfont, one hard pixel shadow on the console. That is the budget.
31
31
  *
32
- * Scheduling and system notifications are separate machine settings. Turning
33
- * notifications off must not silently stop the seven-day scan or email alerts.
32
+ * ## One switch, not two
33
+ *
34
+ * Scheduling and mailing are the same decision — the reason to put a scan on a
35
+ * timer is to be told what it found. So there is one toggle, it requires a
36
+ * sign-in, and "signed out with the timer on" is a real state the page names
37
+ * rather than a contradiction it prevents.
34
38
  */
35
39
 
36
40
  import { useCallback, useEffect, useRef, useState, type ReactNode } from "react";
@@ -40,7 +44,6 @@ import {
40
44
  } from "@/app/actions/get-scheduled-audit";
41
45
  import {
42
46
  setAutoAuditAction,
43
- setAuditNotifyAction,
44
47
  setAuditIntervalAction,
45
48
  } from "@/app/actions/update-scheduled-audit";
46
49
  import { triggerRun, RerunError } from "@/app/audit/_components/rerun-button";
@@ -184,9 +187,12 @@ function Toggle({
184
187
  * What the scan does, as three labelled lines. This is the old footer paragraph
185
188
  * restructured — same claims, scannable instead of a wall.
186
189
  *
187
- * "sends" names the exact remote payload. The scheduled scan itself stays
188
- * local, and no report is attempted unless a credential leak exists and the
189
- * machine has a verified email identity.
190
+ * "sends" ENUMERATES rather than saying "only counts and redacted examples".
191
+ * That was very nearly true, and very nearly true is the worse kind: the report
192
+ * carries the machine's name too — its hostname, which routinely carries its
193
+ * owner's. A list a person can check beats a stronger claim they cannot, and
194
+ * this panel is the one place they would come to check. The digest email states
195
+ * the same three, in the same order.
190
196
  */
191
197
  const HOW_IT_WORKS: ReadonlyArray<{ label: string; body: string }> = [
192
198
  {
@@ -196,7 +202,7 @@ const HOW_IT_WORKS: ReadonlyArray<{ label: string; body: string }> = [
196
202
  { label: "runs", body: "entirely on this machine. the transcripts never leave it." },
197
203
  {
198
204
  label: "sends",
199
- body: "only the newest masked credential exposure, its CLI, time, location, and this machine's name — only when email is configured.",
205
+ body: "counts, redacted examples, and this machine's name — and only when a scan finds something harmful.",
200
206
  },
201
207
  ];
202
208
 
@@ -205,7 +211,6 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
205
211
  // about whether scheduled audits are on. See the note in `page.tsx`.
206
212
  const [view, setView] = useState<ScheduledAuditView | null>(initial);
207
213
  const [auto, setAuto] = useState(initial?.auto ?? false);
208
- const [notify, setNotify] = useState(initial?.notify ?? true);
209
214
  const [intervalDays, setIntervalDays] = useState(initial?.intervalDays ?? 7);
210
215
  const [busy, setBusy] = useState(false);
211
216
  const [running, setRunning] = useState(false);
@@ -249,7 +254,6 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
249
254
  setView(next);
250
255
  hasView.current = true;
251
256
  setAuto(next.auto);
252
- setNotify(next.notify);
253
257
  setIntervalDays(next.intervalDays);
254
258
  setNowMs(Date.now());
255
259
  setLoadError(false);
@@ -308,6 +312,26 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
308
312
  setBusy(true);
309
313
  try {
310
314
  const res = await setAutoAuditAction(true);
315
+ if (!res.ok) {
316
+ setAuto(false);
317
+ if (res.reason === "unreachable") {
318
+ // The session is fine and the network is not. Offering a code prompt
319
+ // here would name a failure the user does not have and hand them a
320
+ // flow that cannot succeed either — and abandoning it mid-way is how
321
+ // a working session gets replaced with none at all.
322
+ toast("could not reach the server. check your connection and try again.");
323
+ return;
324
+ }
325
+ // The server rejected the session this page had been showing an address
326
+ // for — expired, or minted against a different api-server. The local
327
+ // file is the only thing that said "signed in", and `whoAmI` has since
328
+ // cleared it, so re-read before opening the dialog: otherwise the page
329
+ // asks for an email while still displaying one.
330
+ await reload();
331
+ setAuthOpen(true);
332
+ toast("that sign-in expired. one more code and it's on.");
333
+ return;
334
+ }
311
335
  setAuto(res.auto);
312
336
  toast("scheduled audits on.");
313
337
  await reload();
@@ -336,23 +360,13 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
336
360
  }
337
361
  return;
338
362
  }
339
- await enable();
340
- }, [auto, enable, reload]);
341
-
342
- const onNotifyToggle = useCallback(async () => {
343
- const requested = !notify;
344
- setBusy(true);
345
- try {
346
- const res = await setAuditNotifyAction(requested);
347
- setNotify(res.notify);
348
- setView((v) => (v ? { ...v, notify: res.notify } : v));
349
- toast(res.notify ? "system notifications on." : "system notifications off.");
350
- } catch {
351
- toast(`could not turn system notifications ${requested ? "on" : "off"}.`);
352
- } finally {
353
- setBusy(false);
363
+ // Turning it on needs somewhere to send the digest.
364
+ if (!signedIn) {
365
+ setAuthOpen(true);
366
+ return;
354
367
  }
355
- }, [notify]);
368
+ await enable();
369
+ }, [auto, enable, reload, signedIn]);
356
370
 
357
371
  const commitInterval = useCallback(
358
372
  async (raw: number) => {
@@ -389,7 +403,7 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
389
403
  setBusy(true);
390
404
  try {
391
405
  await fetch("/api/auth/logout", { method: "POST" });
392
- toast("signed out. scans and desktop notifications continue; email alerts pause.");
406
+ toast("signed out. scans continue; digests pause.");
393
407
  await reload();
394
408
  } catch {
395
409
  toast("could not sign out.");
@@ -520,9 +534,7 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
520
534
  </span>
521
535
  <span className="set-dim">
522
536
  {auto
523
- ? signedIn
524
- ? "scheduled scans are active; the latest masked leak is emailed."
525
- : "scheduled scans are active. add an email below for masked alerts."
537
+ ? "you'll get an email only when a scan finds something."
526
538
  : "off. nothing runs and nothing is sent."}
527
539
  </span>
528
540
  </div>
@@ -554,23 +566,6 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
554
566
  </span>
555
567
  </div>
556
568
 
557
- <div className="set-row set-row-main">
558
- <Toggle
559
- enabled={notify}
560
- disabled={busy || view === null}
561
- onChange={() => void onNotifyToggle()}
562
- label={notify ? "turn off system notifications" : "turn on system notifications"}
563
- />
564
- <div className="set-row-copy">
565
- <span className="set-strong">system notifications.</span>
566
- <span className="set-dim">
567
- {notify
568
- ? "on when a scheduled scan finds credentials."
569
- : "off. scheduled scans and email alerts continue."}
570
- </span>
571
- </div>
572
- </div>
573
-
574
569
  <div className="set-rule" />
575
570
 
576
571
  <div className="set-row set-row-identity">
@@ -598,7 +593,7 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
598
593
  // from it at all.
599
594
  <>
600
595
  <span className="set-warn">
601
- signed out — scheduled scans continue; email alerts are paused.
596
+ signed out — scans continue, digests are paused.
602
597
  </span>
603
598
  <button
604
599
  type="button"
@@ -606,13 +601,13 @@ export default function SettingsClient({ initial }: { initial: ScheduledAuditVie
606
601
  disabled={busy}
607
602
  onClick={() => setAuthOpen(true)}
608
603
  >
609
- add email alerts
604
+ sign in to resume
610
605
  </button>
611
606
  </>
612
607
  ) : (
613
608
  <span className="set-dim">
614
- scans and system notifications work without an account.
615
- add email later if you want alerts in your inbox.
609
+ turning this on asks for an email, so there is somewhere to
610
+ send the report.
616
611
  </span>
617
612
  )}
618
613
  </div>
@@ -253,49 +253,21 @@ export interface AuditReportBody {
253
253
  last_seen?: string;
254
254
  examples: string[];
255
255
  }[];
256
- /**
257
- * Credentials this machine found in its own transcripts.
258
- *
259
- * Optional on the wire, so a CLI newer than the api-server sends a field the
260
- * server ignores rather than failing the whole report — the harmful counts
261
- * are the older half of the contract and must keep arriving either way.
262
- *
263
- * Every field here is a mask, a label, a length or a location. There is no
264
- * shape of this object that carries a secret, because the record it is built
265
- * from never stores one: `recordLeaks` fingerprints at detection time and
266
- * keeps only the masked rendering.
267
- */
268
- leaks?: {
269
- id: string;
270
- display: string;
271
- label: string;
272
- length: number;
273
- attributed: boolean;
274
- name: string | null;
275
- confidence: string;
276
- first_seen: string;
277
- last_seen: string;
278
- occurrences: number;
279
- cli: string;
280
- project: string;
281
- mechanism: string;
282
- direction: "input" | "result";
283
- }[];
284
256
  }
285
257
 
286
258
  export interface AuditReportResult {
287
259
  report_id: string;
288
260
  /** Whether this report produced an email. */
289
261
  emailed: boolean;
290
- /** `no_leak`, `cooldown`, `send_failed`, or null when mail went out. */
262
+ /** `below_threshold`, `cooldown`, `send_failed`, or null when mail went out. */
291
263
  reason: string | null;
292
264
  /**
293
265
  * Where the next window starts, per the SERVER.
294
266
  *
295
267
  * Persisted verbatim rather than computed locally. The server anchors it on
296
- * the last DELIVERED alert, so a report held by the cooldown — or one whose
268
+ * the last DELIVERED digest, so a report held by the cooldown — or one whose
297
269
  * send failed — correctly leaves the watermark where it was, and its findings
298
- * turn up in the next alert instead of falling into a gap. A machine that
270
+ * turn up in the next digest instead of falling into a gap. A machine that
299
271
  * lost `machine.json` also resyncs here rather than re-reporting from the
300
272
  * beginning of time.
301
273
  */
@@ -303,11 +275,11 @@ export interface AuditReportResult {
303
275
  }
304
276
 
305
277
  /**
306
- * Submit one scheduled scan's harmful findings and newest masked credential.
278
+ * Submit one scheduled scan's harmful findings.
307
279
  *
308
280
  * Called only by the audit child, and only on `--scheduled`. The destination
309
281
  * address is never sent: the api-server takes it from the access-token claims,
310
- * so a report cannot name where its email alert goes.
282
+ * so a report cannot name where its digest goes.
311
283
  */
312
284
  export async function submitAuditReport(
313
285
  accessToken: string,
@@ -4,14 +4,8 @@
4
4
  * Claude stores transcripts at:
5
5
  * <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>.jsonl
6
6
  *
7
- * Subagent transcripts (when a session spawned subagents) live alongside, in two
8
- * shapes — a direct child, and one nested per workflow run:
7
+ * Subagent transcripts (when a session spawned subagents) live alongside:
9
8
  * <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/<agentId>.jsonl
10
- * <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/workflows/<runId>/<agentId>.jsonl
11
- *
12
- * Everything below `subagents/` is walked to a bounded depth rather than one
13
- * level, because the second shape arrived after this module was written and the
14
- * miss was most of the corpus. See `collectSubagentTranscripts`.
15
9
  *
16
10
  * The parser for these files lives in `lib/log-entries.ts` (`parseLogContent`,
17
11
  * `parseSessionLog`). This module exposes discovery only — the audit pipeline
@@ -76,99 +70,6 @@ export function listClaudeProjects(): ClaudeProjectFolder[] {
76
70
  /** Lists every JSONL transcript under one Claude project folder, including
77
71
  * subagent transcripts under `<sessionId>/subagents/`. Returns [] on missing
78
72
  * or unreadable paths. */
79
- /**
80
- * Depth of nesting allowed below `subagents/`.
81
- *
82
- * The two shapes that exist today need 0 and 2 (`<agent>.jsonl` and
83
- * `workflows/<runId>/<agent>.jsonl`). The cap is here so an unexpected layout —
84
- * or a symlink cycle a future Claude build introduces — costs a bounded walk
85
- * rather than an audit that never returns, and it is deliberately loose enough
86
- * that a third shape lands inside it without another release.
87
- */
88
- const MAX_SUBAGENT_DEPTH = 5;
89
-
90
- /**
91
- * Collect every `.jsonl` beneath a session's `subagents/` directory.
92
- *
93
- * This used to read only the DIRECT children of `subagents/`, which was correct
94
- * for the layout Claude shipped when it was written and silently wrong the day
95
- * workflow runs started nesting their agents one level further down. On a
96
- * machine that uses them the miss is most of the corpus: 1,839 transcripts on
97
- * disk, 1,741 under `subagents/`, and 1,679 of those inside
98
- * `subagents/workflows/<runId>/` — so the audit was walking 160 files, 8.7% of
99
- * the evidence, and reporting the result as though it had read everything.
100
- *
101
- * ## Session ids come from the relative path, not the basename
102
- *
103
- * Every workflow run writes a `journal.jsonl` beside its agents, so basenames
104
- * are NOT unique below this directory — 1,741 files share 1,613 distinct names
105
- * on the machine this was found on. A basename id would collide those 128 files
106
- * onto ~one row each, and `sessionId` is what the cache, the per-session
107
- * detector state and the example attribution are keyed by, so the collision
108
- * would silently merge unrelated sessions rather than fail.
109
- *
110
- * Nor is the path below `subagents/` unique on its own. A workflow run id is
111
- * reused when its session is resumed, so `wf_3d609e92-a38/journal.jsonl` exists
112
- * under two different parent sessions in the same project — found by asserting
113
- * uniqueness over the real corpus, not by reasoning about it.
114
- *
115
- * The id is therefore the PARENT SESSION id followed by the path relative to
116
- * `subagents/`, minus the extension, joined with `__`:
117
- * `<parentUuid>__workflows__wf_123__agent-abc`. That is a filesystem path
118
- * within the project, so it is unique by construction rather than by argument,
119
- * and it names the session the subagent belongs to. `__` is safe as the joiner
120
- * because no id Claude generates contains one — verified against 1,741 real
121
- * files, zero hits. Top-level session ids are untouched.
122
- *
123
- * Symlinks are not followed. Nothing in the layout uses them, and following one
124
- * is how a directory walk turns into an infinite loop.
125
- */
126
- function collectSubagentTranscripts(
127
- dir: string,
128
- relPrefix: string,
129
- project: ClaudeProjectFolder,
130
- out: ClaudeTranscriptFile[],
131
- depth: number,
132
- ): void {
133
- if (depth > MAX_SUBAGENT_DEPTH) return;
134
- let entries: import("node:fs").Dirent[];
135
- try {
136
- entries = readdirSync(dir, { withFileTypes: true });
137
- } catch {
138
- return;
139
- }
140
- for (const entry of entries) {
141
- if (entry.isSymbolicLink()) continue;
142
- if (entry.isDirectory()) {
143
- collectSubagentTranscripts(
144
- join(dir, entry.name),
145
- relPrefix ? `${relPrefix}__${entry.name}` : entry.name,
146
- project,
147
- out,
148
- depth + 1,
149
- );
150
- continue;
151
- }
152
- if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
153
- const stem = entry.name.slice(0, -".jsonl".length);
154
- const transcriptPath = join(dir, entry.name);
155
- try {
156
- const s = statSync(transcriptPath);
157
- out.push({
158
- projectName: project.name,
159
- cwd: project.cwd,
160
- sessionId: relPrefix ? `${relPrefix}__${stem}` : stem,
161
- transcriptPath,
162
- mtimeMs: s.mtimeMs,
163
- sizeBytes: s.size,
164
- isSubagent: true,
165
- });
166
- } catch {
167
- // unreadable — skip
168
- }
169
- }
170
- }
171
-
172
73
  export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTranscriptFile[] {
173
74
  const out: ClaudeTranscriptFile[] = [];
174
75
  let entries: import("node:fs").Dirent[];
@@ -198,9 +99,34 @@ export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTrans
198
99
  // unreadable — skip
199
100
  }
200
101
  } else if (entry.isDirectory() && UUID_RE.test(entry.name)) {
102
+ // Subagent transcripts at <sessionId>/subagents/<agentId>.jsonl
201
103
  const subDir = join(project.path, entry.name, "subagents");
202
104
  if (!existsSync(subDir)) continue;
203
- collectSubagentTranscripts(subDir, entry.name, project, out, 0);
105
+ let subEntries: import("node:fs").Dirent[];
106
+ try {
107
+ subEntries = readdirSync(subDir, { withFileTypes: true });
108
+ } catch {
109
+ continue;
110
+ }
111
+ for (const sub of subEntries) {
112
+ if (!sub.isFile() || !sub.name.endsWith(".jsonl")) continue;
113
+ const agentId = sub.name.slice(0, -".jsonl".length);
114
+ const transcriptPath = join(subDir, sub.name);
115
+ try {
116
+ const s = statSync(transcriptPath);
117
+ out.push({
118
+ projectName: project.name,
119
+ cwd: project.cwd,
120
+ sessionId: agentId,
121
+ transcriptPath,
122
+ mtimeMs: s.mtimeMs,
123
+ sizeBytes: s.size,
124
+ isSubagent: true,
125
+ });
126
+ } catch {
127
+ // unreadable — skip
128
+ }
129
+ }
204
130
  }
205
131
  }
206
132
 
@@ -158,21 +158,6 @@ function joinTexts(blocks: CodexContentBlock[] | undefined, wantedType: "input_t
158
158
  .join("\n");
159
159
  }
160
160
 
161
- /** Text carried by tool outputs. Newer Codex chronicles use an array of
162
- * `input_text` blocks for `custom_tool_call_output`, while older releases use
163
- * one string for `function_call_output`. */
164
- function toolOutputText(output: unknown): string {
165
- if (typeof output === "string") return output;
166
- if (Array.isArray(output)) {
167
- return output
168
- .filter((block): block is CodexContentBlock => !!block && typeof block === "object")
169
- .map((block) => typeof block.text === "string" ? block.text : "")
170
- .filter(Boolean)
171
- .join("\n");
172
- }
173
- return output === undefined ? "" : JSON.stringify(output);
174
- }
175
-
176
161
  /**
177
162
  * Parse a Codex JSONL transcript into `LogEntry[]` plus the raw lines.
178
163
  * Yields to the event loop every 200 lines so big transcripts don't block.
@@ -265,11 +250,10 @@ export async function parseCodexLog(
265
250
  continue;
266
251
  }
267
252
 
268
- if (subType === "function_call" || subType === "custom_tool_call") {
253
+ if (subType === "function_call") {
269
254
  const callId = payload.call_id as string | undefined;
270
255
  const name = (payload.name as string | undefined) ?? "function_call";
271
- const encodedInput = (payload.arguments ?? payload.input) as string | undefined;
272
- const input = safeJsonParse(encodedInput);
256
+ const input = safeJsonParse(payload.arguments as string | undefined);
273
257
  const toolUse: ToolUseBlock = {
274
258
  type: "tool_use",
275
259
  id: callId ?? `${timestamp}-${name}`,
@@ -289,7 +273,7 @@ export async function parseCodexLog(
289
273
  continue;
290
274
  }
291
275
 
292
- if (subType === "function_call_output" || subType === "custom_tool_call_output") {
276
+ if (subType === "function_call_output") {
293
277
  const callId = payload.call_id as string | undefined;
294
278
  const block = callId ? toolUseById.get(callId) : undefined;
295
279
  if (block) {
@@ -298,7 +282,7 @@ export async function parseCodexLog(
298
282
  block.result = {
299
283
  timestamp,
300
284
  timestampFormatted: formatTimestamp(date),
301
- content: toolOutputText(payload.output),
285
+ content: typeof payload.output === "string" ? (payload.output as string) : JSON.stringify(payload.output),
302
286
  durationMs: duration,
303
287
  durationFormatted: formatDuration(duration),
304
288
  };
@@ -151,11 +151,6 @@ interface CopilotToolResult {
151
151
  detailedContent?: string;
152
152
  }
153
153
 
154
- interface CopilotHookToolResult {
155
- textResultForLlm?: string;
156
- sessionLog?: string;
157
- }
158
-
159
154
  interface CopilotToolTelemetry {
160
155
  metrics?: { commandTimeMs?: number; durationMs?: number };
161
156
  properties?: Record<string, unknown>;
@@ -176,8 +171,6 @@ export async function parseCopilotLog(
176
171
  // toolCallId → tool_use block, so we can attach tool.execution_complete back.
177
172
  const toolUseById = new Map<string, ToolUseBlock>();
178
173
  const toolUseStartMs = new Map<string, number>();
179
- let mostRecentToolUse: ToolUseBlock | undefined;
180
- let mostRecentToolUseStartMs = 0;
181
174
  let cwd: string | undefined;
182
175
  let seenSessionStart = false;
183
176
 
@@ -280,32 +273,9 @@ export async function parseCopilotLog(
280
273
  toolUseById.set(callId, toolUse);
281
274
  toolUseStartMs.set(callId, date.getTime());
282
275
  }
283
- mostRecentToolUse = toolUse;
284
- mostRecentToolUseStartMs = date.getTime();
285
276
  continue;
286
277
  }
287
278
 
288
- // Copilot CLI 1.0.39 can omit the command's real stdout from
289
- // `tool.execution_complete` and expose it only to the PostToolUse hook as
290
- // `input.toolResult.textResultForLlm`. That is the value the model saw and
291
- // the value failproofai must audit. Keep it on the active tool block; the
292
- // later completion record may still contribute timing, but must not replace
293
- // this output with its shorter status-only summary.
294
- if (recType === "hook.start" && data.hookType === "postToolUse" && mostRecentToolUse) {
295
- const input = (data.input as Record<string, unknown> | undefined) ?? {};
296
- const hookResult = (input.toolResult as CopilotHookToolResult | undefined) ?? {};
297
- const hookContent = hookResult.textResultForLlm ?? hookResult.sessionLog;
298
- if (typeof hookContent === "string" && hookContent.length > 0) {
299
- mostRecentToolUse.result = {
300
- timestamp,
301
- timestampFormatted: formatTimestamp(date),
302
- content: hookContent,
303
- durationMs: Math.max(0, date.getTime() - mostRecentToolUseStartMs),
304
- durationFormatted: formatDuration(Math.max(0, date.getTime() - mostRecentToolUseStartMs)),
305
- };
306
- }
307
- }
308
-
309
279
  if (recType === "tool.execution_complete") {
310
280
  const callId = data.toolCallId as string | undefined;
311
281
  const block = callId ? toolUseById.get(callId) : undefined;
@@ -319,8 +289,7 @@ export async function parseCopilotLog(
319
289
  typeof reportedMs === "number" && reportedMs >= 0
320
290
  ? reportedMs
321
291
  : Math.max(0, date.getTime() - startMs);
322
- const completionContent = result.detailedContent ?? result.content ?? "";
323
- const content = block.result?.content || completionContent;
292
+ const content = result.detailedContent ?? result.content ?? "";
324
293
  block.result = {
325
294
  timestamp,
326
295
  timestampFormatted: formatTimestamp(date),
@@ -127,15 +127,7 @@ interface CursorRecord {
127
127
  /** Cursor 2026-04+ transcript shape: `{role, message: {content: [...]}}`. */
128
128
  role?: "user" | "assistant" | "system" | string;
129
129
  message?: {
130
- content?: Array<{
131
- type?: string;
132
- text?: string;
133
- id?: string;
134
- name?: string;
135
- input?: Record<string, unknown>;
136
- tool_use_id?: string;
137
- content?: unknown;
138
- }>;
130
+ content?: Array<{ type?: string; text?: string }>;
139
131
  };
140
132
  }
141
133
 
@@ -191,36 +183,11 @@ export async function parseCursorLog(
191
183
  if (!raw.type && raw.role && raw.message?.content) {
192
184
  const synthDate = new Date(SYNTH_T0 + i);
193
185
  const synthTs = synthDate.toISOString();
194
- const messageContent = raw.message.content;
195
- const textParts = messageContent
186
+ const textParts = raw.message.content
196
187
  .filter((c) => c?.type === "text" && typeof c.text === "string")
197
188
  .map((c) => c.text!)
198
189
  .join("");
199
190
  if (raw.role === "user") {
200
- // Some Cursor builds emit Claude-style tool_result blocks in the next
201
- // user record. Pair them before treating any remaining text as a user
202
- // turn so command output is available to the credential scanner.
203
- let attachedResult = false;
204
- for (const block of messageContent) {
205
- if (block?.type !== "tool_result") continue;
206
- const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
207
- const toolUse = callId ? toolUseById.get(callId) : undefined;
208
- if (!toolUse) continue;
209
- const durationMs = Math.max(
210
- 0,
211
- synthDate.getTime() - (toolUseStartMs.get(callId!) ?? synthDate.getTime()),
212
- );
213
- toolUse.result = {
214
- timestamp: synthTs,
215
- timestampFormatted: formatTimestamp(synthDate),
216
- content: typeof block.content === "string"
217
- ? block.content
218
- : block.content == null ? "" : JSON.stringify(block.content),
219
- durationMs,
220
- durationFormatted: formatDuration(durationMs),
221
- };
222
- attachedResult = true;
223
- }
224
191
  // Strip the synthesized `<timestamp>...</timestamp>\n<user_query>...\n</user_query>`
225
192
  // wrapper Cursor adds for context — keep just the user_query body.
226
193
  const queryMatch = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/.exec(textParts);
@@ -231,37 +198,13 @@ export async function parseCursorLog(
231
198
  ...baseEntry(rawCopy, synthTs, synthDate, source),
232
199
  message: { role: "user", content: text },
233
200
  } satisfies UserEntry);
234
- } else if (!attachedResult) {
235
- entries.push({
236
- type: "system",
237
- ...baseEntry(rawCopy, synthTs, synthDate, source),
238
- raw: rawCopy,
239
- } satisfies GenericEntry);
240
201
  }
241
202
  continue;
242
203
  }
243
204
  if (raw.role === "assistant") {
244
- const blocks: ContentBlock[] = [];
245
- if (textParts) blocks.push({ type: "text", text: textParts });
246
- for (let j = 0; j < messageContent.length; j++) {
247
- const contentBlock = messageContent[j];
248
- if (contentBlock?.type !== "tool_use") continue;
249
- const name = typeof contentBlock.name === "string" ? contentBlock.name : "tool";
250
- const id = typeof contentBlock.id === "string" && contentBlock.id
251
- ? contentBlock.id
252
- : `cursor-${i}-${name}-${j}`;
253
- const toolUse: ToolUseBlock = {
254
- type: "tool_use",
255
- id,
256
- name,
257
- input: contentBlock.input && typeof contentBlock.input === "object"
258
- ? contentBlock.input
259
- : {},
260
- };
261
- blocks.push(toolUse);
262
- toolUseById.set(id, toolUse);
263
- toolUseStartMs.set(id, synthDate.getTime());
264
- }
205
+ const blocks: ContentBlock[] = textParts
206
+ ? [{ type: "text", text: textParts }]
207
+ : [];
265
208
  if (blocks.length === 0) {
266
209
  entries.push({
267
210
  type: "system",