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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- 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 +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js +2 -2
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js +2 -2
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +4 -37
- package/.next/standalone/.next/server/app/audit/page.js +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
- package/.next/standalone/.next/server/app/policies/page.js +4 -4
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +4 -4
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -5
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +3 -3
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -15
- package/.next/standalone/.next/server/app/settings/page.js +4 -4
- package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0hmcwgg._.js → [externals]__0-9ti7z._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__14b7cc6._.js → [externals]__0u3wrvq._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1le9jqc._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
- package/.next/standalone/.next/server/chunks/{_1_eakl4._.js → _1-akg6w._.js} +2 -2
- package/.next/standalone/.next/server/chunks/_1ek68ln._.js +10 -10
- package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__187jks6._.js → [root-of-the-server]__00-s7h8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__04px77e._.js → [root-of-the-server]__013jr2b._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0i0l1di._.js → [root-of-the-server]__01wy8d-._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0r3c1_0._.js → [root-of-the-server]__02npjtd._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1ckpmkk._.js → [root-of-the-server]__0da85px._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__17xnxlx._.js → [root-of-the-server]__0ftmoxc._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0kokc8z._.js → [root-of-the-server]__0n_lxhg._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0-4ja6q._.js → [root-of-the-server]__0p-5p8u._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0egr5s1._.js → [root-of-the-server]__0s740oi._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1fa1jd9._.js → [root-of-the-server]__0x1aj1_._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1kpd1za._.js → [root-of-the-server]__190won8._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ctpynv._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00cr3xu._.js → [root-of-the-server]__1p2otjt._.js} +3 -3
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__1sc84bb._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0s-77v5._.js → _0h_douw._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_00bqkhj._.js → _0u3xxgc._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0zs_4uu._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +14 -51
- package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1if9ixi._.js} +2 -21
- package/.next/standalone/.next/server/chunks/ssr/{_07t3jol._.js → _1u8-lu2._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +69 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +21 -65
- package/.next/standalone/.next/static/chunks/0hw6-c206lsma.js +1 -0
- package/.next/standalone/.next/static/chunks/{2mizt96igig1y.js → 0m9sx3khjrl1q.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1302duobnz07l.js → 0oos8qpc1ugos.js} +1 -1
- package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +1 -0
- package/.next/standalone/.next/static/chunks/{2ukucf6g-kbs5.js → 0unrx9g_x0u7j.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0jpd8dv2vk930.css → 29-iqnp9v_axt.css} +1 -1
- package/.next/standalone/.next/static/chunks/{3uyhmm01g64k4.css → 2k9f4tyv04809.css} +0 -1
- package/.next/standalone/.next/static/chunks/{0vm4wdx3w-2_w.js → 2qjn-leir0x_s.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1chm9piluggc-.js → 2xbxkaszi1xnw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2n85g-crgcx5j.js → 33umhy-x204qj.js} +2 -2
- package/.next/standalone/.next/static/chunks/3nux7kkrc3eps.js +69 -0
- package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +1 -0
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +1 -0
- package/.next/standalone/.next/static/chunks/{3a4gth64hwtwf.js → 40tpgs4fjct5p.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_x7pqemw5n_w.js → 40uih96wnkowy.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-3s3c-u_u_l0u9.js → turbopack-3p73obe0xmrxm.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +27 -51
- package/.next/standalone/app/actions/get-scheduled-audit.ts +0 -5
- package/.next/standalone/app/actions/update-scheduled-audit.ts +57 -18
- package/.next/standalone/app/api/auth/status/route.ts +2 -3
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +34 -140
- package/.next/standalone/app/audit/_components/audit-poster.tsx +10 -29
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -6
- package/.next/standalone/app/audit/_components/empty-state.tsx +1 -4
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +5 -19
- package/.next/standalone/app/audit/_components/share-templates.ts +44 -110
- package/.next/standalone/app/audit/audit-styles.css +1 -132
- package/.next/standalone/app/settings/page.tsx +1 -1
- package/.next/standalone/app/settings/settings-client.tsx +45 -50
- package/.next/standalone/lib/auth/api-server-client.ts +5 -33
- package/.next/standalone/lib/claude-sessions.ts +27 -101
- package/.next/standalone/lib/codex-sessions.ts +4 -20
- package/.next/standalone/lib/copilot-sessions.ts +1 -32
- package/.next/standalone/lib/cursor-sessions.ts +5 -62
- package/.next/standalone/lib/download-session.ts +13 -58
- package/.next/standalone/lib/openclaw-projects.ts +18 -54
- package/.next/standalone/lib/openclaw-sessions.ts +17 -75
- package/.next/standalone/lib/sqlite-reader.ts +1 -78
- package/.next/standalone/package.json +9 -9
- package/.next/standalone/sdk/python/CHANGELOG.md +32 -9
- package/.next/standalone/sdk/python/failproofai_sdk/_events.py +20 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_redact.py +233 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_writer.py +4 -1
- package/.next/standalone/sdk/python/failproofai_sdk/integrations/llama_index.py +5 -5
- package/.next/standalone/sdk/python/tests/integrations/test_llama_index.py +2 -2
- package/.next/standalone/sdk/python/tests/test_redaction.py +140 -0
- package/.next/standalone/sdk/python/tests/test_sdk.py +87 -0
- package/.next/standalone/sdk/python/tests/test_server_contract.py +60 -0
- package/.next/standalone/sdk/python/tests/test_site_docs.py +2 -2
- package/.next/standalone/server.js +1 -1
- package/bin/failproofai.mjs +1 -1
- package/dist/cli.mjs +664 -2121
- package/dist/worker.mjs +105 -422
- package/lib/auth/api-server-client.ts +5 -33
- package/lib/claude-sessions.ts +27 -101
- package/lib/codex-sessions.ts +4 -20
- package/lib/copilot-sessions.ts +1 -32
- package/lib/cursor-sessions.ts +5 -62
- package/lib/download-session.ts +13 -58
- package/lib/openclaw-projects.ts +18 -54
- package/lib/openclaw-sessions.ts +17 -75
- package/lib/sqlite-reader.ts +1 -78
- package/package.json +9 -9
- package/pi-extension/index.ts +5 -50
- package/src/audit/cache.ts +0 -6
- package/src/audit/cli-adapters/claude.ts +0 -13
- package/src/audit/cli-login.ts +1 -4
- package/src/audit/cli.ts +7 -157
- package/src/audit/harm-report.ts +4 -111
- package/src/audit/index.ts +15 -286
- package/src/audit/machine-store.ts +9 -8
- package/src/audit/redact-example.ts +15 -324
- package/src/audit/report-harm.ts +37 -33
- package/src/audit/report.ts +3 -6
- package/src/audit/schedule-cli.ts +33 -54
- package/src/audit/scoring.ts +0 -49
- package/src/audit/types.ts +0 -52
- package/src/hooks/builtin-policies.ts +2 -67
- package/src/hooks/configure-wizard.ts +0 -55
- package/src/hooks/fp-config.ts +48 -83
- package/src/hooks/fp-home.ts +0 -27
- package/src/hooks/integrations.ts +14 -51
- package/src/hooks/uninstall-cli.ts +2 -17
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0faqz9x._.js +0 -22
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zyc_nm._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__112b4go._.js +0 -24
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1l71g3y._.js +0 -22
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n2txhk._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1kaotix._.js +0 -22
- package/.next/standalone/.next/server/chunks/ssr/_04ng_6x._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +0 -3
- package/.next/standalone/.next/static/chunks/0igq884ziuv-8.js +0 -1
- package/.next/standalone/.next/static/chunks/349jkb79v5g6z.js +0 -1
- package/.next/standalone/app/actions/get-leaks.ts +0 -104
- package/.next/standalone/app/audit/_components/leak-section.tsx +0 -192
- package/.next/standalone/lib/openclaw-db.ts +0 -271
- package/lib/openclaw-db.ts +0 -271
- package/src/audit/desktop-notify.ts +0 -420
- package/src/audit/leak-fingerprint.ts +0 -198
- package/src/audit/leak-record.ts +0 -248
- package/src/audit/leak-scan.ts +0 -268
- package/src/audit/leak-store.ts +0 -220
- package/src/audit/macos-notifier.ts +0 -298
- package/src/hooks/notice.ts +0 -24
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → 4_QeL7mSPYsuiSVJmBOTj}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{DNhaAH8iPGH0tIuxee5_2 → 4_QeL7mSPYsuiSVJmBOTj}/_clientMiddlewareManifest.js +0 -0
- /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
|
-
*
|
|
33
|
-
*
|
|
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"
|
|
188
|
-
*
|
|
189
|
-
* machine
|
|
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: "
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
?
|
|
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 —
|
|
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
|
-
|
|
604
|
+
sign in to resume
|
|
610
605
|
</button>
|
|
611
606
|
</>
|
|
612
607
|
) : (
|
|
613
608
|
<span className="set-dim">
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
/** `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
8
|
-
* shapes — a direct child, and one nested per workflow run:
|
|
7
|
+
* Subagent transcripts (when a session spawned subagents) live alongside:
|
|
9
8
|
* <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/<agentId>.jsonl
|
|
10
|
-
* <CLAUDE_PROJECTS_PATH>/<encoded-cwd>/<sessionId>/subagents/workflows/<runId>/<agentId>.jsonl
|
|
11
|
-
*
|
|
12
|
-
* Everything below `subagents/` is walked to a bounded depth rather than one
|
|
13
|
-
* level, because the second shape arrived after this module was written and the
|
|
14
|
-
* miss was most of the corpus. See `collectSubagentTranscripts`.
|
|
15
9
|
*
|
|
16
10
|
* The parser for these files lives in `lib/log-entries.ts` (`parseLogContent`,
|
|
17
11
|
* `parseSessionLog`). This module exposes discovery only — the audit pipeline
|
|
@@ -76,99 +70,6 @@ export function listClaudeProjects(): ClaudeProjectFolder[] {
|
|
|
76
70
|
/** Lists every JSONL transcript under one Claude project folder, including
|
|
77
71
|
* subagent transcripts under `<sessionId>/subagents/`. Returns [] on missing
|
|
78
72
|
* or unreadable paths. */
|
|
79
|
-
/**
|
|
80
|
-
* Depth of nesting allowed below `subagents/`.
|
|
81
|
-
*
|
|
82
|
-
* The two shapes that exist today need 0 and 2 (`<agent>.jsonl` and
|
|
83
|
-
* `workflows/<runId>/<agent>.jsonl`). The cap is here so an unexpected layout —
|
|
84
|
-
* or a symlink cycle a future Claude build introduces — costs a bounded walk
|
|
85
|
-
* rather than an audit that never returns, and it is deliberately loose enough
|
|
86
|
-
* that a third shape lands inside it without another release.
|
|
87
|
-
*/
|
|
88
|
-
const MAX_SUBAGENT_DEPTH = 5;
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Collect every `.jsonl` beneath a session's `subagents/` directory.
|
|
92
|
-
*
|
|
93
|
-
* This used to read only the DIRECT children of `subagents/`, which was correct
|
|
94
|
-
* for the layout Claude shipped when it was written and silently wrong the day
|
|
95
|
-
* workflow runs started nesting their agents one level further down. On a
|
|
96
|
-
* machine that uses them the miss is most of the corpus: 1,839 transcripts on
|
|
97
|
-
* disk, 1,741 under `subagents/`, and 1,679 of those inside
|
|
98
|
-
* `subagents/workflows/<runId>/` — so the audit was walking 160 files, 8.7% of
|
|
99
|
-
* the evidence, and reporting the result as though it had read everything.
|
|
100
|
-
*
|
|
101
|
-
* ## Session ids come from the relative path, not the basename
|
|
102
|
-
*
|
|
103
|
-
* Every workflow run writes a `journal.jsonl` beside its agents, so basenames
|
|
104
|
-
* are NOT unique below this directory — 1,741 files share 1,613 distinct names
|
|
105
|
-
* on the machine this was found on. A basename id would collide those 128 files
|
|
106
|
-
* onto ~one row each, and `sessionId` is what the cache, the per-session
|
|
107
|
-
* detector state and the example attribution are keyed by, so the collision
|
|
108
|
-
* would silently merge unrelated sessions rather than fail.
|
|
109
|
-
*
|
|
110
|
-
* Nor is the path below `subagents/` unique on its own. A workflow run id is
|
|
111
|
-
* reused when its session is resumed, so `wf_3d609e92-a38/journal.jsonl` exists
|
|
112
|
-
* under two different parent sessions in the same project — found by asserting
|
|
113
|
-
* uniqueness over the real corpus, not by reasoning about it.
|
|
114
|
-
*
|
|
115
|
-
* The id is therefore the PARENT SESSION id followed by the path relative to
|
|
116
|
-
* `subagents/`, minus the extension, joined with `__`:
|
|
117
|
-
* `<parentUuid>__workflows__wf_123__agent-abc`. That is a filesystem path
|
|
118
|
-
* within the project, so it is unique by construction rather than by argument,
|
|
119
|
-
* and it names the session the subagent belongs to. `__` is safe as the joiner
|
|
120
|
-
* because no id Claude generates contains one — verified against 1,741 real
|
|
121
|
-
* files, zero hits. Top-level session ids are untouched.
|
|
122
|
-
*
|
|
123
|
-
* Symlinks are not followed. Nothing in the layout uses them, and following one
|
|
124
|
-
* is how a directory walk turns into an infinite loop.
|
|
125
|
-
*/
|
|
126
|
-
function collectSubagentTranscripts(
|
|
127
|
-
dir: string,
|
|
128
|
-
relPrefix: string,
|
|
129
|
-
project: ClaudeProjectFolder,
|
|
130
|
-
out: ClaudeTranscriptFile[],
|
|
131
|
-
depth: number,
|
|
132
|
-
): void {
|
|
133
|
-
if (depth > MAX_SUBAGENT_DEPTH) return;
|
|
134
|
-
let entries: import("node:fs").Dirent[];
|
|
135
|
-
try {
|
|
136
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
137
|
-
} catch {
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
for (const entry of entries) {
|
|
141
|
-
if (entry.isSymbolicLink()) continue;
|
|
142
|
-
if (entry.isDirectory()) {
|
|
143
|
-
collectSubagentTranscripts(
|
|
144
|
-
join(dir, entry.name),
|
|
145
|
-
relPrefix ? `${relPrefix}__${entry.name}` : entry.name,
|
|
146
|
-
project,
|
|
147
|
-
out,
|
|
148
|
-
depth + 1,
|
|
149
|
-
);
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
if (!entry.isFile() || !entry.name.endsWith(".jsonl")) continue;
|
|
153
|
-
const stem = entry.name.slice(0, -".jsonl".length);
|
|
154
|
-
const transcriptPath = join(dir, entry.name);
|
|
155
|
-
try {
|
|
156
|
-
const s = statSync(transcriptPath);
|
|
157
|
-
out.push({
|
|
158
|
-
projectName: project.name,
|
|
159
|
-
cwd: project.cwd,
|
|
160
|
-
sessionId: relPrefix ? `${relPrefix}__${stem}` : stem,
|
|
161
|
-
transcriptPath,
|
|
162
|
-
mtimeMs: s.mtimeMs,
|
|
163
|
-
sizeBytes: s.size,
|
|
164
|
-
isSubagent: true,
|
|
165
|
-
});
|
|
166
|
-
} catch {
|
|
167
|
-
// unreadable — skip
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
73
|
export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTranscriptFile[] {
|
|
173
74
|
const out: ClaudeTranscriptFile[] = [];
|
|
174
75
|
let entries: import("node:fs").Dirent[];
|
|
@@ -198,9 +99,34 @@ export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTrans
|
|
|
198
99
|
// unreadable — skip
|
|
199
100
|
}
|
|
200
101
|
} else if (entry.isDirectory() && UUID_RE.test(entry.name)) {
|
|
102
|
+
// Subagent transcripts at <sessionId>/subagents/<agentId>.jsonl
|
|
201
103
|
const subDir = join(project.path, entry.name, "subagents");
|
|
202
104
|
if (!existsSync(subDir)) continue;
|
|
203
|
-
|
|
105
|
+
let subEntries: import("node:fs").Dirent[];
|
|
106
|
+
try {
|
|
107
|
+
subEntries = readdirSync(subDir, { withFileTypes: true });
|
|
108
|
+
} catch {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
for (const sub of subEntries) {
|
|
112
|
+
if (!sub.isFile() || !sub.name.endsWith(".jsonl")) continue;
|
|
113
|
+
const agentId = sub.name.slice(0, -".jsonl".length);
|
|
114
|
+
const transcriptPath = join(subDir, sub.name);
|
|
115
|
+
try {
|
|
116
|
+
const s = statSync(transcriptPath);
|
|
117
|
+
out.push({
|
|
118
|
+
projectName: project.name,
|
|
119
|
+
cwd: project.cwd,
|
|
120
|
+
sessionId: agentId,
|
|
121
|
+
transcriptPath,
|
|
122
|
+
mtimeMs: s.mtimeMs,
|
|
123
|
+
sizeBytes: s.size,
|
|
124
|
+
isSubagent: true,
|
|
125
|
+
});
|
|
126
|
+
} catch {
|
|
127
|
+
// unreadable — skip
|
|
128
|
+
}
|
|
129
|
+
}
|
|
204
130
|
}
|
|
205
131
|
}
|
|
206
132
|
|
|
@@ -158,21 +158,6 @@ function joinTexts(blocks: CodexContentBlock[] | undefined, wantedType: "input_t
|
|
|
158
158
|
.join("\n");
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
/** Text carried by tool outputs. Newer Codex chronicles use an array of
|
|
162
|
-
* `input_text` blocks for `custom_tool_call_output`, while older releases use
|
|
163
|
-
* one string for `function_call_output`. */
|
|
164
|
-
function toolOutputText(output: unknown): string {
|
|
165
|
-
if (typeof output === "string") return output;
|
|
166
|
-
if (Array.isArray(output)) {
|
|
167
|
-
return output
|
|
168
|
-
.filter((block): block is CodexContentBlock => !!block && typeof block === "object")
|
|
169
|
-
.map((block) => typeof block.text === "string" ? block.text : "")
|
|
170
|
-
.filter(Boolean)
|
|
171
|
-
.join("\n");
|
|
172
|
-
}
|
|
173
|
-
return output === undefined ? "" : JSON.stringify(output);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
161
|
/**
|
|
177
162
|
* Parse a Codex JSONL transcript into `LogEntry[]` plus the raw lines.
|
|
178
163
|
* Yields to the event loop every 200 lines so big transcripts don't block.
|
|
@@ -265,11 +250,10 @@ export async function parseCodexLog(
|
|
|
265
250
|
continue;
|
|
266
251
|
}
|
|
267
252
|
|
|
268
|
-
if (subType === "function_call"
|
|
253
|
+
if (subType === "function_call") {
|
|
269
254
|
const callId = payload.call_id as string | undefined;
|
|
270
255
|
const name = (payload.name as string | undefined) ?? "function_call";
|
|
271
|
-
const
|
|
272
|
-
const input = safeJsonParse(encodedInput);
|
|
256
|
+
const input = safeJsonParse(payload.arguments as string | undefined);
|
|
273
257
|
const toolUse: ToolUseBlock = {
|
|
274
258
|
type: "tool_use",
|
|
275
259
|
id: callId ?? `${timestamp}-${name}`,
|
|
@@ -289,7 +273,7 @@ export async function parseCodexLog(
|
|
|
289
273
|
continue;
|
|
290
274
|
}
|
|
291
275
|
|
|
292
|
-
if (subType === "function_call_output"
|
|
276
|
+
if (subType === "function_call_output") {
|
|
293
277
|
const callId = payload.call_id as string | undefined;
|
|
294
278
|
const block = callId ? toolUseById.get(callId) : undefined;
|
|
295
279
|
if (block) {
|
|
@@ -298,7 +282,7 @@ export async function parseCodexLog(
|
|
|
298
282
|
block.result = {
|
|
299
283
|
timestamp,
|
|
300
284
|
timestampFormatted: formatTimestamp(date),
|
|
301
|
-
content:
|
|
285
|
+
content: typeof payload.output === "string" ? (payload.output as string) : JSON.stringify(payload.output),
|
|
302
286
|
durationMs: duration,
|
|
303
287
|
durationFormatted: formatDuration(duration),
|
|
304
288
|
};
|
|
@@ -151,11 +151,6 @@ interface CopilotToolResult {
|
|
|
151
151
|
detailedContent?: string;
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
interface CopilotHookToolResult {
|
|
155
|
-
textResultForLlm?: string;
|
|
156
|
-
sessionLog?: string;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
154
|
interface CopilotToolTelemetry {
|
|
160
155
|
metrics?: { commandTimeMs?: number; durationMs?: number };
|
|
161
156
|
properties?: Record<string, unknown>;
|
|
@@ -176,8 +171,6 @@ export async function parseCopilotLog(
|
|
|
176
171
|
// toolCallId → tool_use block, so we can attach tool.execution_complete back.
|
|
177
172
|
const toolUseById = new Map<string, ToolUseBlock>();
|
|
178
173
|
const toolUseStartMs = new Map<string, number>();
|
|
179
|
-
let mostRecentToolUse: ToolUseBlock | undefined;
|
|
180
|
-
let mostRecentToolUseStartMs = 0;
|
|
181
174
|
let cwd: string | undefined;
|
|
182
175
|
let seenSessionStart = false;
|
|
183
176
|
|
|
@@ -280,32 +273,9 @@ export async function parseCopilotLog(
|
|
|
280
273
|
toolUseById.set(callId, toolUse);
|
|
281
274
|
toolUseStartMs.set(callId, date.getTime());
|
|
282
275
|
}
|
|
283
|
-
mostRecentToolUse = toolUse;
|
|
284
|
-
mostRecentToolUseStartMs = date.getTime();
|
|
285
276
|
continue;
|
|
286
277
|
}
|
|
287
278
|
|
|
288
|
-
// Copilot CLI 1.0.39 can omit the command's real stdout from
|
|
289
|
-
// `tool.execution_complete` and expose it only to the PostToolUse hook as
|
|
290
|
-
// `input.toolResult.textResultForLlm`. That is the value the model saw and
|
|
291
|
-
// the value failproofai must audit. Keep it on the active tool block; the
|
|
292
|
-
// later completion record may still contribute timing, but must not replace
|
|
293
|
-
// this output with its shorter status-only summary.
|
|
294
|
-
if (recType === "hook.start" && data.hookType === "postToolUse" && mostRecentToolUse) {
|
|
295
|
-
const input = (data.input as Record<string, unknown> | undefined) ?? {};
|
|
296
|
-
const hookResult = (input.toolResult as CopilotHookToolResult | undefined) ?? {};
|
|
297
|
-
const hookContent = hookResult.textResultForLlm ?? hookResult.sessionLog;
|
|
298
|
-
if (typeof hookContent === "string" && hookContent.length > 0) {
|
|
299
|
-
mostRecentToolUse.result = {
|
|
300
|
-
timestamp,
|
|
301
|
-
timestampFormatted: formatTimestamp(date),
|
|
302
|
-
content: hookContent,
|
|
303
|
-
durationMs: Math.max(0, date.getTime() - mostRecentToolUseStartMs),
|
|
304
|
-
durationFormatted: formatDuration(Math.max(0, date.getTime() - mostRecentToolUseStartMs)),
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
279
|
if (recType === "tool.execution_complete") {
|
|
310
280
|
const callId = data.toolCallId as string | undefined;
|
|
311
281
|
const block = callId ? toolUseById.get(callId) : undefined;
|
|
@@ -319,8 +289,7 @@ export async function parseCopilotLog(
|
|
|
319
289
|
typeof reportedMs === "number" && reportedMs >= 0
|
|
320
290
|
? reportedMs
|
|
321
291
|
: Math.max(0, date.getTime() - startMs);
|
|
322
|
-
const
|
|
323
|
-
const content = block.result?.content || completionContent;
|
|
292
|
+
const content = result.detailedContent ?? result.content ?? "";
|
|
324
293
|
block.result = {
|
|
325
294
|
timestamp,
|
|
326
295
|
timestampFormatted: formatTimestamp(date),
|
|
@@ -127,15 +127,7 @@ interface CursorRecord {
|
|
|
127
127
|
/** Cursor 2026-04+ transcript shape: `{role, message: {content: [...]}}`. */
|
|
128
128
|
role?: "user" | "assistant" | "system" | string;
|
|
129
129
|
message?: {
|
|
130
|
-
content?: Array<{
|
|
131
|
-
type?: string;
|
|
132
|
-
text?: string;
|
|
133
|
-
id?: string;
|
|
134
|
-
name?: string;
|
|
135
|
-
input?: Record<string, unknown>;
|
|
136
|
-
tool_use_id?: string;
|
|
137
|
-
content?: unknown;
|
|
138
|
-
}>;
|
|
130
|
+
content?: Array<{ type?: string; text?: string }>;
|
|
139
131
|
};
|
|
140
132
|
}
|
|
141
133
|
|
|
@@ -191,36 +183,11 @@ export async function parseCursorLog(
|
|
|
191
183
|
if (!raw.type && raw.role && raw.message?.content) {
|
|
192
184
|
const synthDate = new Date(SYNTH_T0 + i);
|
|
193
185
|
const synthTs = synthDate.toISOString();
|
|
194
|
-
const
|
|
195
|
-
const textParts = messageContent
|
|
186
|
+
const textParts = raw.message.content
|
|
196
187
|
.filter((c) => c?.type === "text" && typeof c.text === "string")
|
|
197
188
|
.map((c) => c.text!)
|
|
198
189
|
.join("");
|
|
199
190
|
if (raw.role === "user") {
|
|
200
|
-
// Some Cursor builds emit Claude-style tool_result blocks in the next
|
|
201
|
-
// user record. Pair them before treating any remaining text as a user
|
|
202
|
-
// turn so command output is available to the credential scanner.
|
|
203
|
-
let attachedResult = false;
|
|
204
|
-
for (const block of messageContent) {
|
|
205
|
-
if (block?.type !== "tool_result") continue;
|
|
206
|
-
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
207
|
-
const toolUse = callId ? toolUseById.get(callId) : undefined;
|
|
208
|
-
if (!toolUse) continue;
|
|
209
|
-
const durationMs = Math.max(
|
|
210
|
-
0,
|
|
211
|
-
synthDate.getTime() - (toolUseStartMs.get(callId!) ?? synthDate.getTime()),
|
|
212
|
-
);
|
|
213
|
-
toolUse.result = {
|
|
214
|
-
timestamp: synthTs,
|
|
215
|
-
timestampFormatted: formatTimestamp(synthDate),
|
|
216
|
-
content: typeof block.content === "string"
|
|
217
|
-
? block.content
|
|
218
|
-
: block.content == null ? "" : JSON.stringify(block.content),
|
|
219
|
-
durationMs,
|
|
220
|
-
durationFormatted: formatDuration(durationMs),
|
|
221
|
-
};
|
|
222
|
-
attachedResult = true;
|
|
223
|
-
}
|
|
224
191
|
// Strip the synthesized `<timestamp>...</timestamp>\n<user_query>...\n</user_query>`
|
|
225
192
|
// wrapper Cursor adds for context — keep just the user_query body.
|
|
226
193
|
const queryMatch = /<user_query>\s*([\s\S]*?)\s*<\/user_query>/.exec(textParts);
|
|
@@ -231,37 +198,13 @@ export async function parseCursorLog(
|
|
|
231
198
|
...baseEntry(rawCopy, synthTs, synthDate, source),
|
|
232
199
|
message: { role: "user", content: text },
|
|
233
200
|
} satisfies UserEntry);
|
|
234
|
-
} else if (!attachedResult) {
|
|
235
|
-
entries.push({
|
|
236
|
-
type: "system",
|
|
237
|
-
...baseEntry(rawCopy, synthTs, synthDate, source),
|
|
238
|
-
raw: rawCopy,
|
|
239
|
-
} satisfies GenericEntry);
|
|
240
201
|
}
|
|
241
202
|
continue;
|
|
242
203
|
}
|
|
243
204
|
if (raw.role === "assistant") {
|
|
244
|
-
const blocks: ContentBlock[] =
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
const contentBlock = messageContent[j];
|
|
248
|
-
if (contentBlock?.type !== "tool_use") continue;
|
|
249
|
-
const name = typeof contentBlock.name === "string" ? contentBlock.name : "tool";
|
|
250
|
-
const id = typeof contentBlock.id === "string" && contentBlock.id
|
|
251
|
-
? contentBlock.id
|
|
252
|
-
: `cursor-${i}-${name}-${j}`;
|
|
253
|
-
const toolUse: ToolUseBlock = {
|
|
254
|
-
type: "tool_use",
|
|
255
|
-
id,
|
|
256
|
-
name,
|
|
257
|
-
input: contentBlock.input && typeof contentBlock.input === "object"
|
|
258
|
-
? contentBlock.input
|
|
259
|
-
: {},
|
|
260
|
-
};
|
|
261
|
-
blocks.push(toolUse);
|
|
262
|
-
toolUseById.set(id, toolUse);
|
|
263
|
-
toolUseStartMs.set(id, synthDate.getTime());
|
|
264
|
-
}
|
|
205
|
+
const blocks: ContentBlock[] = textParts
|
|
206
|
+
? [{ type: "text", text: textParts }]
|
|
207
|
+
: [];
|
|
265
208
|
if (blocks.length === 0) {
|
|
266
209
|
entries.push({
|
|
267
210
|
type: "system",
|