failproofai 1.0.4-beta.1 → 1.0.4-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +5 -5
- 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.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.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route_client-reference-manifest.js +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/run/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/status/route_client-reference-manifest.js +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-request/route_client-reference-manifest.js +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/login-verify/route_client-reference-manifest.js +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/logout/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route_client-reference-manifest.js +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/api/download/[project]/[session]/route_client-reference-manifest.js +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 +37 -4
- 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 +15 -15
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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.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 +1 -1
- 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 +3 -3
- 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 +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/build-manifest.json +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 +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +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]__1zndo1i._.js → [externals]__0v1hz15._.js} +1 -1
- package/.next/standalone/.next/server/chunks/{[externals]__0_gzk8q._.js → [externals]__1nb206a._.js} +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08dchfu._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.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]__0q0qzx1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adacul._.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]__1p-qi2t._.js +5 -0
- package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
- package/.next/standalone/.next/server/chunks/_0otft92._.js +3 -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/_1ek68ln._.js +14 -21
- package/.next/standalone/.next/server/chunks/_1vslkoe._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +1 -1
- package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/src_hooks_0iu54mz._.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]__00-s7h8._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0wvusu0._.js → [root-of-the-server]__0n_lxhg._.js} +2 -2
- 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]__0p-5p8u._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__16gamwq._.js → [root-of-the-server]__0yl9wb5._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__190won8._.js +3 -0
- 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]__1p2otjt._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1sc84bb._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zl_r0t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_1byck5v._.js → _0guxnh-._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_0l2hi_d._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0oq1dh6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1_ozgif._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +61 -168
- package/.next/standalone/.next/server/chunks/ssr/{_08f0jd2._.js → _1if9ixi._.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
- 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 +1 -69
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_rerun-button_tsx_0blihgw._.js +3 -0
- 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/node_modules_13n822a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
- 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 +54 -21
- package/.next/standalone/.next/static/chunks/04o5zfl7p5lhx.js +1 -0
- package/.next/standalone/.next/static/chunks/{29-iqnp9v_axt.css → 0jpd8dv2vk930.css} +1 -1
- package/.next/standalone/.next/static/chunks/{1po3lirgk2riw.js → 0po8n4tyotjf2.js} +1 -1
- package/.next/standalone/.next/static/chunks/0qmsunv04x4na.css +2 -0
- package/.next/standalone/.next/static/chunks/0qx7gav1c2_vu.js +1 -0
- package/.next/standalone/.next/static/chunks/17tfki9x5rd_7.js +1 -0
- package/.next/standalone/.next/static/chunks/{1_bxotmf9qngs.js → 1i-i0pxg9q_8y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{1_2jsatfuthrw.js → 2bxr2_2h4kaqq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{01hvwwrhvbzyh.js → 2zafgs90r_leq.js} +1 -1
- package/.next/standalone/.next/static/chunks/{00gv0v91mycm-.js → 323cyb3a2s06y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0qn9khv0oo9ex.js → 364cqbpeg1y5i.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2bvwsyu6x__ia.js → 38dqur43qthrl.js} +1 -1
- package/.next/standalone/.next/static/chunks/{3_--3ibia6ba1.js → 3l36d73kj18zw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2k9f4tyv04809.css → 3uyhmm01g64k4.css} +1 -0
- package/.next/standalone/.next/static/chunks/{2bnf_4pgyk1rq.js → 40lpt2o2b283e.js} +1 -1
- package/.next/standalone/.next/static/chunks/42gwu2bsz4ske.js +6 -0
- package/.next/standalone/.next/static/chunks/{turbopack-18qf4jodtft6b.js → turbopack-3s3c-u_u_l0u9.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +51 -27
- package/.next/standalone/app/actions/get-leaks.ts +109 -0
- package/.next/standalone/app/audit/_components/audit-dashboard.tsx +140 -34
- package/.next/standalone/app/audit/_components/audit-poster.tsx +29 -10
- package/.next/standalone/app/audit/_components/come-back-better-section.tsx +6 -1
- package/.next/standalone/app/audit/_components/empty-state.tsx +4 -1
- package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +19 -5
- package/.next/standalone/app/audit/_components/leak-section.tsx +192 -0
- package/.next/standalone/app/audit/_components/share-templates.ts +110 -44
- package/.next/standalone/app/audit/audit-styles.css +132 -1
- package/.next/standalone/app/project/[name]/page.tsx +4 -24
- package/.next/standalone/fp-cloud-cli/CHANGELOG.md +26 -3
- package/.next/standalone/fp-cloud-cli/fp_cli/_click_compat.py +31 -8
- package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +1 -0
- package/.next/standalone/fp-cloud-cli/pyproject.toml +9 -2
- package/.next/standalone/fp-cloud-cli/tests/test_click_compat.py +54 -0
- package/.next/standalone/fp-cloud-cli/uv.lock +9 -12
- package/.next/standalone/lib/auth/api-server-client.ts +28 -0
- package/.next/standalone/lib/claude-sessions.ts +101 -27
- package/.next/standalone/lib/cli-registry.ts +1 -33
- package/.next/standalone/lib/download-session.ts +0 -37
- package/.next/standalone/lib/projects.ts +3 -27
- package/.next/standalone/lib/sqlite-reader.ts +78 -1
- package/.next/standalone/node_modules/@next/env/package.json +1 -1
- package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
- package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
- package/.next/standalone/node_modules/next/dist/experimental/testmode/fetch.js +10 -2
- package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
- package/.next/standalone/node_modules/next/dist/lib/typescript/runTypeScriptCli.js +2 -2
- package/.next/standalone/node_modules/next/dist/lib/verify-typescript-setup.js +3 -1
- package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +2 -2
- package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
- package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
- package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
- package/.next/standalone/node_modules/next/package.json +11 -11
- package/.next/standalone/package.json +15 -14
- package/.next/standalone/sdk/python/CHANGELOG.md +155 -0
- package/.next/standalone/sdk/python/README.md +8 -0
- package/.next/standalone/sdk/python/examples/evaluator_worker.py +121 -0
- package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__init__.py +101 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/__main__.py +49 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/_sandbox_runner.py +63 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/authoring.py +404 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/client.py +299 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/protocol.py +754 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/runtime.py +936 -0
- package/.next/standalone/sdk/python/failproofai_sdk/evaluator/source.py +684 -0
- package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/README.md +28 -0
- package/.next/standalone/sdk/python/tests/fixtures/evaluator_v2/contract.json +252 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_authoring.py +124 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_client.py +253 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_example.py +35 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_http_e2e.py +636 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_main.py +47 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_protocol.py +246 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_review_fixes.py +164 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_runtime.py +1103 -0
- package/.next/standalone/sdk/python/tests/test_evaluator_source.py +430 -0
- package/.next/standalone/sdk/python/tests/test_zero_dependencies.py +17 -0
- package/.next/standalone/sdk/python/uv.lock +18 -18
- package/.next/standalone/server.js +1 -1
- package/README.md +19 -95
- package/bin/failproofai.mjs +8 -25
- package/dist/cli.mjs +20091 -25864
- package/dist/worker.mjs +4881 -6361
- package/lib/auth/api-server-client.ts +28 -0
- package/lib/claude-sessions.ts +101 -27
- package/lib/cli-registry.ts +1 -33
- package/lib/download-session.ts +0 -37
- package/lib/projects.ts +3 -27
- package/lib/sqlite-reader.ts +78 -1
- package/package.json +15 -14
- package/pi-extension/index.ts +61 -8
- package/scripts/changelog-open.py +115 -0
- package/src/audit/cache.ts +6 -0
- package/src/audit/cli-adapters/claude.ts +13 -0
- package/src/audit/cli-adapters/index.ts +0 -24
- package/src/audit/cli-login.ts +4 -1
- package/src/audit/cli.ts +164 -5
- package/src/audit/desktop-notify.ts +420 -0
- package/src/audit/harm-report.ts +101 -0
- package/src/audit/index.ts +284 -15
- package/src/audit/leak-fingerprint.ts +200 -0
- package/src/audit/leak-notice.ts +259 -0
- package/src/audit/leak-record.ts +232 -0
- package/src/audit/leak-scan.ts +268 -0
- package/src/audit/leak-store.ts +265 -0
- package/src/audit/macos-notifier.ts +310 -0
- package/src/audit/redact-example.ts +322 -15
- package/src/audit/report-harm.ts +12 -1
- package/src/audit/report.ts +6 -3
- package/src/audit/schedule-cli.ts +37 -0
- package/src/audit/scoring.ts +49 -0
- package/src/audit/types.ts +47 -0
- package/src/hooks/builtin-policies.ts +71 -8
- package/src/hooks/configure-wizard.ts +20 -0
- package/src/hooks/enforcement-capability.ts +0 -97
- package/src/hooks/fp-config.ts +71 -11
- package/src/hooks/fp-home.ts +27 -0
- package/src/hooks/handler.ts +84 -62
- package/src/hooks/harness-cli.ts +1 -3
- package/src/hooks/integrations.ts +63 -769
- package/src/hooks/normalize-cli-payload.ts +0 -126
- package/src/hooks/notice.ts +191 -0
- package/src/hooks/policy-evaluator.ts +6 -297
- package/src/hooks/resolve-transcript-path.ts +0 -10
- package/src/hooks/tool-name-canonicalize.ts +0 -64
- package/src/hooks/types.ts +2 -533
- package/src/hooks/uninstall-cli.ts +15 -0
- package/.next/standalone/.grok/hooks/failproofai.json +0 -172
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__07hor7h._.js +0 -5
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0elsuli._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0gnj_qa._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ljuzm_._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0w23bpg._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0x5c7sh._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0yik9_w._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1jg6s9m._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1s6qci9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xfm05z._.js +0 -3
- package/.next/standalone/.next/server/chunks/_0tdy_6l._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_grok-projects_ts_1i2epl_._.js +0 -3
- package/.next/standalone/.next/server/chunks/lib_qwen-projects_ts_0wu_a8e._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0_4gcz8._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d--wmy._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qqrlf_._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s11fx1._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1bbk-si._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1c3ybxr._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1izi7zn._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__21bj7uw._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_00z5uuq._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1ylae7o._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_cline-projects_ts_13_hbdd._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_grok-projects_ts_0rc-d0o._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_ori-projects_ts_04tofqo._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_qwen-projects_ts_0w3i9o7._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1ao30b1.js +0 -3
- package/.next/standalone/.next/static/chunks/0hzmxwb2pvk5p.js +0 -6
- package/.next/standalone/.next/static/chunks/0j884dwnwtyrt.js +0 -1
- package/.next/standalone/.next/static/chunks/0mr9dg3bnloa1.js +0 -1
- package/.next/standalone/.next/static/chunks/0u64yey446k4f.css +0 -1
- package/.next/standalone/.next/static/chunks/3gf1jg-5m-u1t.css +0 -2
- package/.next/standalone/.next/static/chunks/3qfoeqhy5l10x.js +0 -1
- package/.next/standalone/.next/static/chunks/3ws7eljkq25wg.js +0 -69
- package/.next/standalone/.next/static/chunks/3zkg2s2vzxc3d.js +0 -1
- package/.next/standalone/.qwen/settings.json +0 -251
- package/.next/standalone/assets/logos/cline.png +0 -0
- package/.next/standalone/assets/logos/grok-dark.svg +0 -1
- package/.next/standalone/assets/logos/grok-light.svg +0 -1
- package/.next/standalone/assets/logos/ori-dark.svg +0 -1
- package/.next/standalone/assets/logos/ori-light.svg +0 -1
- package/.next/standalone/assets/logos/qwen.svg +0 -1
- package/.next/standalone/lib/cline-projects.ts +0 -155
- package/.next/standalone/lib/cline-sessions.ts +0 -268
- package/.next/standalone/lib/grok-projects.ts +0 -123
- package/.next/standalone/lib/grok-sessions.ts +0 -355
- package/.next/standalone/lib/ori-projects.ts +0 -141
- package/.next/standalone/lib/ori-sessions.ts +0 -221
- package/.next/standalone/lib/qwen-projects.ts +0 -106
- package/.next/standalone/lib/qwen-sessions.ts +0 -313
- package/lib/cline-projects.ts +0 -155
- package/lib/cline-sessions.ts +0 -268
- package/lib/grok-projects.ts +0 -123
- package/lib/grok-sessions.ts +0 -355
- package/lib/ori-projects.ts +0 -141
- package/lib/ori-sessions.ts +0 -221
- package/lib/qwen-projects.ts +0 -106
- package/lib/qwen-sessions.ts +0 -313
- package/src/audit/cli-adapters/cline.ts +0 -50
- package/src/audit/cli-adapters/grok.ts +0 -70
- package/src/audit/cli-adapters/ori.ts +0 -55
- package/src/audit/cli-adapters/qwen.ts +0 -71
- package/src/hooks/batch-expand.ts +0 -380
- package/src/hooks/batch-fanout.ts +0 -142
- package/src/hooks/risk-patterns.ts +0 -26
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → P6icI2V2PvVuePJrqjHCr}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → P6icI2V2PvVuePJrqjHCr}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{qJx3mbOQV-5ElrcRb4Sp7 → P6icI2V2PvVuePJrqjHCr}/_ssgManifest.js +0 -0
|
@@ -253,6 +253,34 @@ 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 digest — 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
|
+
}[];
|
|
256
284
|
}
|
|
257
285
|
|
|
258
286
|
export interface AuditReportResult {
|
package/lib/claude-sessions.ts
CHANGED
|
@@ -4,8 +4,14 @@
|
|
|
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
|
|
7
|
+
* Subagent transcripts (when a session spawned subagents) live alongside, in two
|
|
8
|
+
* shapes — a direct child, and one nested per workflow run:
|
|
8
9
|
* <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`.
|
|
9
15
|
*
|
|
10
16
|
* The parser for these files lives in `lib/log-entries.ts` (`parseLogContent`,
|
|
11
17
|
* `parseSessionLog`). This module exposes discovery only — the audit pipeline
|
|
@@ -70,6 +76,99 @@ export function listClaudeProjects(): ClaudeProjectFolder[] {
|
|
|
70
76
|
/** Lists every JSONL transcript under one Claude project folder, including
|
|
71
77
|
* subagent transcripts under `<sessionId>/subagents/`. Returns [] on missing
|
|
72
78
|
* 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
|
+
|
|
73
172
|
export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTranscriptFile[] {
|
|
74
173
|
const out: ClaudeTranscriptFile[] = [];
|
|
75
174
|
let entries: import("node:fs").Dirent[];
|
|
@@ -99,34 +198,9 @@ export function listClaudeTranscripts(project: ClaudeProjectFolder): ClaudeTrans
|
|
|
99
198
|
// unreadable — skip
|
|
100
199
|
}
|
|
101
200
|
} else if (entry.isDirectory() && UUID_RE.test(entry.name)) {
|
|
102
|
-
// Subagent transcripts at <sessionId>/subagents/<agentId>.jsonl
|
|
103
201
|
const subDir = join(project.path, entry.name, "subagents");
|
|
104
202
|
if (!existsSync(subDir)) continue;
|
|
105
|
-
|
|
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
|
-
}
|
|
203
|
+
collectSubagentTranscripts(subDir, entry.name, project, out, 0);
|
|
130
204
|
}
|
|
131
205
|
}
|
|
132
206
|
|
package/lib/cli-registry.ts
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import type { IntegrationType } from "@/src/hooks/types";
|
|
28
28
|
|
|
29
29
|
/** Canonical CLI ids the registry knows about. Mirrors `INTEGRATION_TYPES`. */
|
|
30
|
-
export const KNOWN_CLI_IDS = ["claude", "codex", "copilot", "cursor", "opencode", "pi", "hermes", "openclaw", "factory", "devin", "antigravity", "goose"
|
|
30
|
+
export const KNOWN_CLI_IDS = ["claude", "codex", "copilot", "cursor", "opencode", "pi", "hermes", "openclaw", "factory", "devin", "antigravity", "goose"] as const satisfies readonly IntegrationType[];
|
|
31
31
|
export type CliId = (typeof KNOWN_CLI_IDS)[number];
|
|
32
32
|
|
|
33
33
|
/** Per-CLI metadata consumed by the dashboard. */
|
|
@@ -99,38 +99,6 @@ const CLI_ENTRIES: Record<CliId, CliEntry> = {
|
|
|
99
99
|
label: "Goose",
|
|
100
100
|
badgeClasses: "bg-lime-500/10 text-lime-400 border-lime-500/20",
|
|
101
101
|
},
|
|
102
|
-
grok: {
|
|
103
|
-
id: "grok",
|
|
104
|
-
label: "grok CLI",
|
|
105
|
-
// Neutral zinc, matching the design system's `--color-default`. Deliberately
|
|
106
|
-
// NOT a status hue: green/amber/red carry health meaning in this dashboard
|
|
107
|
-
// and must never be spent on identity. Neutral also happens to suit xAI's
|
|
108
|
-
// monochrome brand, and it is maximally far from qwen's magenta — the two
|
|
109
|
-
// were previously indistinguishable.
|
|
110
|
-
badgeClasses: "bg-zinc-500/10 text-zinc-200 border-zinc-500/20",
|
|
111
|
-
},
|
|
112
|
-
qwen: {
|
|
113
|
-
id: "qwen",
|
|
114
|
-
label: "Qwen Code",
|
|
115
|
-
// Magenta — the opposite pole from grok's neutral, and outside the
|
|
116
|
-
// status set (success/amber/orange/error) so it reads as identity.
|
|
117
|
-
badgeClasses: "bg-fuchsia-500/10 text-fuchsia-400 border-fuchsia-500/20",
|
|
118
|
-
},
|
|
119
|
-
ori: {
|
|
120
|
-
id: "ori",
|
|
121
|
-
label: "Ori",
|
|
122
|
-
// Sky — the badge set must be UNIQUE per CLI (a test asserts it), and
|
|
123
|
-
// violet/teal were already taken. Still outside the status set
|
|
124
|
-
// (success/amber/orange/error) so it reads as identity, not state.
|
|
125
|
-
badgeClasses: "bg-sky-500/10 text-sky-400 border-sky-500/20",
|
|
126
|
-
},
|
|
127
|
-
cline: {
|
|
128
|
-
id: "cline",
|
|
129
|
-
label: "Cline",
|
|
130
|
-
// Stone — the remaining neutral that is not zinc (grok's) and not a status
|
|
131
|
-
// colour.
|
|
132
|
-
badgeClasses: "bg-stone-500/10 text-stone-300 border-stone-500/20",
|
|
133
|
-
},
|
|
134
102
|
};
|
|
135
103
|
|
|
136
104
|
export function getCliEntry(id: string): CliEntry | undefined {
|
package/lib/download-session.ts
CHANGED
|
@@ -125,21 +125,6 @@ export async function resolveDownloadSource(
|
|
|
125
125
|
return path ? { kind: "file", path } : null;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
if (cli === "grok") {
|
|
129
|
-
// grok writes a real chat_history.jsonl per session directory — stream it
|
|
130
|
-
// verbatim (no synthesis, like Factory).
|
|
131
|
-
const { findGrokTranscript } = await import("./grok-sessions");
|
|
132
|
-
const path = findGrokTranscript(sessionId);
|
|
133
|
-
return path ? { kind: "file", path } : null;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (cli === "qwen") {
|
|
137
|
-
// qwen writes a real JSONL transcript per session — stream it verbatim.
|
|
138
|
-
const { findQwenTranscript } = await import("./qwen-sessions");
|
|
139
|
-
const path = findQwenTranscript(sessionId);
|
|
140
|
-
return path ? { kind: "file", path } : null;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
128
|
if (cli === "devin") {
|
|
144
129
|
// Devin keeps sessions in SQLite (~/.local/share/devin/cli/sessions.db).
|
|
145
130
|
// Synthesize a JSONL export of the session's raw chat_message rows.
|
|
@@ -167,28 +152,6 @@ export async function resolveDownloadSource(
|
|
|
167
152
|
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
168
153
|
}
|
|
169
154
|
|
|
170
|
-
if (cli === "ori") {
|
|
171
|
-
// Ori keeps the whole conversation in one SQLite column
|
|
172
|
-
// (ori_agent_loop_history.prompt). Synthesize a JSONL export from the
|
|
173
|
-
// parsed entries — there is no per-session file to stream.
|
|
174
|
-
const { getOriSessionLog } = await import("./ori-sessions");
|
|
175
|
-
const result = await getOriSessionLog(sessionId);
|
|
176
|
-
if (!result) return null;
|
|
177
|
-
const body = result.entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
178
|
-
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
if (cli === "cline") {
|
|
182
|
-
// Cline stores a real per-session messages file, but it is a single JSON
|
|
183
|
-
// document rather than JSONL, so stream the parsed entries as JSONL for
|
|
184
|
-
// consistency with every other synthesized export.
|
|
185
|
-
const { getClineSessionLog } = await import("./cline-sessions");
|
|
186
|
-
const result = await getClineSessionLog(sessionId);
|
|
187
|
-
if (!result) return null;
|
|
188
|
-
const body = result.entries.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
189
|
-
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
190
|
-
}
|
|
191
|
-
|
|
192
155
|
// Exhaustive — but TypeScript can't always see CliId is exhausted across the
|
|
193
156
|
// if-chain above, so guard with a runtime fallback.
|
|
194
157
|
const _exhaustive: never = cli;
|
package/lib/projects.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { formatDate } from "./format-date";
|
|
|
16
16
|
export const UUID_RE = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
|
17
17
|
export const PATH_TRAVERSAL_RE = /(^|[\\/])\.\.($|[\\/])/;
|
|
18
18
|
|
|
19
|
-
export type ProjectCli = "claude" | "codex" | "copilot" | "cursor" | "opencode" | "pi" | "hermes" | "openclaw" | "factory" | "devin" | "antigravity" | "goose"
|
|
19
|
+
export type ProjectCli = "claude" | "codex" | "copilot" | "cursor" | "opencode" | "pi" | "hermes" | "openclaw" | "factory" | "devin" | "antigravity" | "goose";
|
|
20
20
|
|
|
21
21
|
export interface ProjectFolder {
|
|
22
22
|
name: string;
|
|
@@ -171,10 +171,6 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
171
171
|
{ getDevinProjects },
|
|
172
172
|
{ getAntigravityProjects },
|
|
173
173
|
{ getGooseProjects },
|
|
174
|
-
{ getGrokProjects },
|
|
175
|
-
{ getQwenProjects },
|
|
176
|
-
{ getOriProjects },
|
|
177
|
-
{ getClineProjects },
|
|
178
174
|
] = await Promise.all([
|
|
179
175
|
import("./codex-projects"),
|
|
180
176
|
import("./copilot-projects"),
|
|
@@ -187,12 +183,8 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
187
183
|
import("./devin-projects"),
|
|
188
184
|
import("./antigravity-projects"),
|
|
189
185
|
import("./goose-projects"),
|
|
190
|
-
import("./grok-projects"),
|
|
191
|
-
import("./qwen-projects"),
|
|
192
|
-
import("./ori-projects"),
|
|
193
|
-
import("./cline-projects"),
|
|
194
186
|
]);
|
|
195
|
-
const [claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose
|
|
187
|
+
const [claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose] = await Promise.all([
|
|
196
188
|
getClaudeProjectFolders(),
|
|
197
189
|
getCodexProjects().catch((error) => {
|
|
198
190
|
logError("Error reading Codex projects:", error);
|
|
@@ -238,24 +230,8 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
238
230
|
logError("Error reading Goose projects:", error);
|
|
239
231
|
return [] as ProjectFolder[];
|
|
240
232
|
}),
|
|
241
|
-
getGrokProjects().catch((error) => {
|
|
242
|
-
logError("Error reading grok projects:", error);
|
|
243
|
-
return [] as ProjectFolder[];
|
|
244
|
-
}),
|
|
245
|
-
getQwenProjects().catch((error) => {
|
|
246
|
-
logError("Error reading Qwen projects:", error);
|
|
247
|
-
return [] as ProjectFolder[];
|
|
248
|
-
}),
|
|
249
|
-
getOriProjects().catch((error) => {
|
|
250
|
-
logError("Error reading Ori projects:", error);
|
|
251
|
-
return [] as ProjectFolder[];
|
|
252
|
-
}),
|
|
253
|
-
getClineProjects().catch((error) => {
|
|
254
|
-
logError("Error reading Cline projects:", error);
|
|
255
|
-
return [] as ProjectFolder[];
|
|
256
|
-
}),
|
|
257
233
|
]);
|
|
258
|
-
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose
|
|
234
|
+
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose);
|
|
259
235
|
}
|
|
260
236
|
|
|
261
237
|
/**
|
package/lib/sqlite-reader.ts
CHANGED
|
@@ -39,8 +39,42 @@ interface NodeSqliteModule {
|
|
|
39
39
|
DatabaseSync: new (path: string, options?: { readOnly?: boolean }) => NodeSqliteDb;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Swallow Node's "SQLite is an experimental feature" warning, once.
|
|
44
|
+
*
|
|
45
|
+
* It is not information the user can act on — we chose `node:sqlite`
|
|
46
|
+
* deliberately, and the alternative tier is a WASM build that leaks. But it is
|
|
47
|
+
* emitted on first use, which lands it in the MIDDLE of whatever is on screen,
|
|
48
|
+
* and the audit's progress display redraws itself by moving the cursor up a
|
|
49
|
+
* fixed number of lines. Two unexpected lines of stderr push the cursor down
|
|
50
|
+
* two rows, so the next redraw repaints the block two rows lower and strands
|
|
51
|
+
* the top of the previous frame above it. The user sees every stage twice and
|
|
52
|
+
* reasonably concludes the audit ran twice.
|
|
53
|
+
*
|
|
54
|
+
* Filtered by message rather than silenced wholesale: `process.emitWarning` is
|
|
55
|
+
* how deprecations and real problems surface too, and a blanket
|
|
56
|
+
* `NODE_NO_WARNINGS` in a tool that installs into other people's machines would
|
|
57
|
+
* hide those as well.
|
|
58
|
+
*/
|
|
59
|
+
let warningPatched = false;
|
|
60
|
+
function silenceSqliteExperimentalWarning(): void {
|
|
61
|
+
if (warningPatched) return;
|
|
62
|
+
warningPatched = true;
|
|
63
|
+
const original = process.emitWarning.bind(process);
|
|
64
|
+
// The overloads differ across Node versions; the shape we care about is the
|
|
65
|
+
// first argument, and everything else is passed straight through.
|
|
66
|
+
process.emitWarning = ((warning: unknown, ...rest: unknown[]) => {
|
|
67
|
+
const text = typeof warning === "string" ? warning : String((warning as Error)?.message ?? "");
|
|
68
|
+
if (text.includes("SQLite is an experimental feature")) return;
|
|
69
|
+
return (original as (...a: unknown[]) => void)(warning, ...rest);
|
|
70
|
+
}) as typeof process.emitWarning;
|
|
71
|
+
}
|
|
72
|
+
|
|
42
73
|
async function tryNodeSqlite(dbPath: string): Promise<SqliteReader | null> {
|
|
43
74
|
try {
|
|
75
|
+
// Before the import, not after: the warning fires on first use of the
|
|
76
|
+
// module, which is this line.
|
|
77
|
+
silenceSqliteExperimentalWarning();
|
|
44
78
|
// Dynamic import: on Node < 22.5 this rejects and we fall through to sql.js.
|
|
45
79
|
const mod = (await import("node:sqlite")) as unknown as NodeSqliteModule;
|
|
46
80
|
const db = new mod.DatabaseSync(dbPath, { readOnly: true });
|
|
@@ -66,10 +100,53 @@ function loadSqlJs(): Promise<SqlJsStatic> {
|
|
|
66
100
|
return sqlPromise;
|
|
67
101
|
}
|
|
68
102
|
|
|
103
|
+
const SQLITE_MAGIC = "SQLite format 3\0";
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Clear the WAL flag on OUR OWN in-memory copy of the database image.
|
|
107
|
+
*
|
|
108
|
+
* Header bytes 18 and 19 are the file-format write and read versions; `2`
|
|
109
|
+
* means WAL. sql.js never sees the `-wal` sidecar — it is handed the main
|
|
110
|
+
* file's bytes and nothing else, which is exactly the snapshot caveat this
|
|
111
|
+
* module already documents — so the flag buys us nothing, and it costs a great
|
|
112
|
+
* deal: opening a WAL-flagged image makes SQLite build a wal-index
|
|
113
|
+
* shared-memory region that sql.js's MEMFS-backed VFS never reclaims on
|
|
114
|
+
* `close()` (close unlinks only the db file it created).
|
|
115
|
+
*
|
|
116
|
+
* The asm.js build's heap is a FIXED 22,151,168-byte ArrayBuffer with
|
|
117
|
+
* ALLOW_MEMORY_GROWTH off, so `emscripten_resize_heap` is compiled to
|
|
118
|
+
* `abort("OOM")`. Measured: the 128th WAL-mode open exhausts it — the same
|
|
119
|
+
* threshold for a 319 KB database and a 10 MB one, because the leaked region
|
|
120
|
+
* is a fixed per-connection allocation rather than data.
|
|
121
|
+
*
|
|
122
|
+
* And the abort is not confined to that open. `initSqlJs` memoizes one
|
|
123
|
+
* process-wide module, so once it aborts every later `openSqliteReadonly`
|
|
124
|
+
* silently returns null. A full audit opens ~150 databases — devin, goose and
|
|
125
|
+
* opencode each open theirs once per SESSION — so it crossed 128 and then
|
|
126
|
+
* dropped whatever it had not yet read, with no error anywhere. The
|
|
127
|
+
* `Aborted(OOM)` lines on stderr were the only symptom, and they read as
|
|
128
|
+
* harmless because the run still completed.
|
|
129
|
+
*
|
|
130
|
+
* Only under bun and Node < 22.5: tier 1 is `node:sqlite`, which bun does not
|
|
131
|
+
* provide, and `engines.node` allows 20.9. Both fall through to here.
|
|
132
|
+
*/
|
|
133
|
+
function clearWalFlag(bytes: Buffer): Buffer {
|
|
134
|
+
// Too short to carry a header, or not a SQLite image at all — hand it back
|
|
135
|
+
// untouched and let sql.js produce its own error.
|
|
136
|
+
if (bytes.length < 20) return bytes;
|
|
137
|
+
if (bytes.toString("latin1", 0, 16) !== SQLITE_MAGIC) return bytes;
|
|
138
|
+
if (bytes[18] === 1 && bytes[19] === 1) return bytes; // already rollback-journal
|
|
139
|
+
bytes[18] = 1;
|
|
140
|
+
bytes[19] = 1;
|
|
141
|
+
return bytes;
|
|
142
|
+
}
|
|
143
|
+
|
|
69
144
|
async function trySqlJs(dbPath: string): Promise<SqliteReader | null> {
|
|
70
145
|
try {
|
|
71
146
|
const SQL = await loadSqlJs();
|
|
72
|
-
|
|
147
|
+
// `readFileSync` already returns a private copy, so mutating the two header
|
|
148
|
+
// bytes cannot touch the file on disk.
|
|
149
|
+
const db = new SQL.Database(clearWalFlag(readFileSync(dbPath)));
|
|
73
150
|
return {
|
|
74
151
|
query<T = Record<string, unknown>>(sql: string, params?: unknown[]): T[] {
|
|
75
152
|
const stmt = db.prepare(sql);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "1.0.4-beta.
|
|
4
|
-
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into
|
|
3
|
+
"version": "1.0.4-beta.4",
|
|
4
|
+
"description": "Observability and enforcement for AI agent harnesses. 39 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs",
|
|
7
7
|
"failproofaid": "./bin/failproofaid-shim.mjs"
|
|
@@ -80,14 +80,14 @@
|
|
|
80
80
|
"access": "public"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@anthropic-ai/sdk": "^0.
|
|
83
|
+
"@anthropic-ai/sdk": "^0.123.0",
|
|
84
84
|
"@mdx-js/mdx": "^3.1.1",
|
|
85
85
|
"@tailwindcss/postcss": "^4.3.1",
|
|
86
86
|
"@tanstack/react-virtual": "^3.14.10",
|
|
87
87
|
"@testing-library/jest-dom": "^7.0.0",
|
|
88
88
|
"@testing-library/react": "^16.3.2",
|
|
89
89
|
"@testing-library/user-event": "^14.6.5",
|
|
90
|
-
"@types/node": "26.4.
|
|
90
|
+
"@types/node": "26.4.1",
|
|
91
91
|
"@types/react": "19.2.18",
|
|
92
92
|
"@types/react-dom": "^19.2.3",
|
|
93
93
|
"@vitejs/plugin-react": "^6.1.0",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"tailwind-merge": "^3.4.0",
|
|
103
103
|
"tailwindcss": "^4.3.1",
|
|
104
104
|
"typescript": "6.0.3",
|
|
105
|
-
"vitest": "^
|
|
105
|
+
"vitest": "^5.0.0"
|
|
106
106
|
},
|
|
107
107
|
"dependencies": {
|
|
108
108
|
"html-to-image": "^1.11.13",
|
|
@@ -117,18 +117,19 @@
|
|
|
117
117
|
"vite": "8.0.16",
|
|
118
118
|
"undici": "7.29.0",
|
|
119
119
|
"brace-expansion": "5.0.9",
|
|
120
|
-
"sharp": "0.35.
|
|
120
|
+
"sharp": "0.35.4",
|
|
121
|
+
"browserslist": "4.28.8"
|
|
121
122
|
},
|
|
122
123
|
"optionalDependencies": {
|
|
123
|
-
"@failproofai/failproofaid-linux-x64": "1.0.4-beta.
|
|
124
|
-
"@failproofai/failproofaid-linux-arm64": "1.0.4-beta.
|
|
125
|
-
"@failproofai/failproofaid-darwin-x64": "1.0.4-beta.
|
|
126
|
-
"@failproofai/failproofaid-darwin-arm64": "1.0.4-beta.
|
|
124
|
+
"@failproofai/failproofaid-linux-x64": "1.0.4-beta.4",
|
|
125
|
+
"@failproofai/failproofaid-linux-arm64": "1.0.4-beta.4",
|
|
126
|
+
"@failproofai/failproofaid-darwin-x64": "1.0.4-beta.4",
|
|
127
|
+
"@failproofai/failproofaid-darwin-arm64": "1.0.4-beta.4"
|
|
127
128
|
},
|
|
128
129
|
"failproofaidBinaries": {
|
|
129
|
-
"linux-x64": "
|
|
130
|
-
"linux-arm64": "
|
|
131
|
-
"darwin-x64": "
|
|
132
|
-
"darwin-arm64": "
|
|
130
|
+
"linux-x64": "4487790ed9b881da2914b5985a23b5adca1f20e226d71c635d8170064e6b2340",
|
|
131
|
+
"linux-arm64": "c903c5e201198213ccd8295fdab741c7d08d22c8dd5c276e52aa766595bedcc2",
|
|
132
|
+
"darwin-x64": "f0933584abbf0f9ff95cd153f8c6c704e467af1d6ffc8f42f9c426bd44b1142a",
|
|
133
|
+
"darwin-arm64": "387a3acbf13f667a557c8ac632fe67dee350cda078e3266e71edfcfacb9ecae6"
|
|
133
134
|
}
|
|
134
135
|
}
|
package/pi-extension/index.ts
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* spawns extensions with an undefined cwd contract, so paths are resolved
|
|
26
26
|
* relative to this file via `import.meta.url`, NOT process.cwd().
|
|
27
27
|
*/
|
|
28
|
-
import { spawnSync } from "node:child_process";
|
|
28
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
29
29
|
import { resolve, dirname, join } from "node:path";
|
|
30
30
|
import { fileURLToPath } from "node:url";
|
|
31
31
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
@@ -49,6 +49,7 @@ function resolveSpawn(): { cmd: string; args: string[] } {
|
|
|
49
49
|
interface PolicyDecision {
|
|
50
50
|
permission?: "allow" | "deny";
|
|
51
51
|
reason?: string;
|
|
52
|
+
failproofaiNotice?: string;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
/**
|
|
@@ -64,7 +65,45 @@ function debug(msg: string): void {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Forward an event whose VERDICT NOBODY READS, without blocking Pi.
|
|
70
|
+
*
|
|
71
|
+
* `callPolicy` is `spawnSync`, and Pi awaits its handlers serially on the
|
|
72
|
+
* startup path — so a `session_start` forward froze the whole TUI for as long
|
|
73
|
+
* as the subprocess took (measured floor 1.0-1.6s just to boot the binary,
|
|
74
|
+
* ceiling the full 60s timeout) and then threw the answer away. Three call
|
|
75
|
+
* sites are like that by design: `session_start`, `tool_result` and
|
|
76
|
+
* `session_shutdown` forward for the activity feed and for session-id
|
|
77
|
+
* continuity, and Pi cannot act on their return in any case
|
|
78
|
+
* (`ToolResultEventResult` has no `block`; the other two have no Result type
|
|
79
|
+
* at all).
|
|
80
|
+
*
|
|
81
|
+
* So they get a detached `spawn` instead. The event still reaches failproofai,
|
|
82
|
+
* the policies still run and still log, and Pi keeps rendering. Deliberately
|
|
83
|
+
* NOT used for `tool_call`, `user_bash`, `input` or `agent_end`: those four
|
|
84
|
+
* consume the decision, and blocking is what enforcement means.
|
|
85
|
+
*/
|
|
86
|
+
function forwardPolicy(eventName: string, payload: unknown): void {
|
|
87
|
+
const { cmd, args } = resolveSpawn();
|
|
88
|
+
debug(`forwardPolicy (detached) event=${eventName} cmd=${cmd}`);
|
|
89
|
+
try {
|
|
90
|
+
const child = spawn(cmd, [...args, "--hook", eventName, "--cli", "pi"], {
|
|
91
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
92
|
+
detached: false,
|
|
93
|
+
});
|
|
94
|
+
// A child that outlives its parent's interest must not keep the event loop
|
|
95
|
+
// alive, or Pi cannot exit until the subprocess does.
|
|
96
|
+
child.unref();
|
|
97
|
+
// EPIPE if the child died before stdin was consumed — fail-open, silently.
|
|
98
|
+
child.on("error", (err) => debug(`forward error ${err.message}`));
|
|
99
|
+
child.stdin.on("error", () => {});
|
|
100
|
+
child.stdin.end(JSON.stringify(payload));
|
|
101
|
+
} catch (err) {
|
|
102
|
+
debug(`FORWARD EXCEPTION ${err instanceof Error ? err.message : String(err)}`);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function callPolicy(eventName: string, payload: unknown): { block: boolean; reason: string; notice?: string } {
|
|
68
107
|
const { cmd, args } = resolveSpawn();
|
|
69
108
|
debug(`callPolicy event=${eventName} cmd=${cmd}`);
|
|
70
109
|
try {
|
|
@@ -83,8 +122,13 @@ function callPolicy(eventName: string, payload: unknown): { block: boolean; reas
|
|
|
83
122
|
const parsed = JSON.parse(stdout) as PolicyDecision;
|
|
84
123
|
if (parsed.permission === "deny") {
|
|
85
124
|
debug(`DENY reason=${parsed.reason}`);
|
|
86
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
block: true,
|
|
127
|
+
reason: parsed.reason ?? "Blocked by failproofai",
|
|
128
|
+
notice: parsed.failproofaiNotice,
|
|
129
|
+
};
|
|
87
130
|
}
|
|
131
|
+
return { block: false, reason: "", notice: parsed.failproofaiNotice };
|
|
88
132
|
} catch (err) {
|
|
89
133
|
debug(`EXCEPTION ${err instanceof Error ? err.message : String(err)}`);
|
|
90
134
|
// Fail-open: never block tool execution because of an infra failure.
|
|
@@ -332,7 +376,13 @@ interface PiBeforeAgentStartEvent {
|
|
|
332
376
|
}
|
|
333
377
|
|
|
334
378
|
interface PiExtensionApi {
|
|
335
|
-
on(event: string, handler: (event: unknown) => unknown): void;
|
|
379
|
+
on(event: string, handler: (event: unknown, ctx?: PiExtensionContext) => unknown): void;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
interface PiExtensionContext {
|
|
383
|
+
ui?: {
|
|
384
|
+
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
385
|
+
};
|
|
336
386
|
}
|
|
337
387
|
|
|
338
388
|
export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
@@ -417,7 +467,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
417
467
|
// need session_id continuity see the metadata.
|
|
418
468
|
pi.on("session_start", (event: unknown): unknown => {
|
|
419
469
|
const e = event as PiSessionStartEvent;
|
|
420
|
-
|
|
470
|
+
forwardPolicy("session_start", {
|
|
421
471
|
session_id: resolveSessionId(e.sessionId, resolveCwd(e.cwd)),
|
|
422
472
|
cwd: resolveCwd(e.cwd),
|
|
423
473
|
reason: e.reason,
|
|
@@ -435,7 +485,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
435
485
|
pi.on("tool_result", (event: unknown): unknown => {
|
|
436
486
|
const e = event as PiToolResultEvent;
|
|
437
487
|
const canonicalTool = canonicalizeToolName(e.toolName);
|
|
438
|
-
|
|
488
|
+
forwardPolicy("tool_result", {
|
|
439
489
|
tool_name: canonicalTool,
|
|
440
490
|
tool_input: canonicalizeToolInput(canonicalTool, e.input) ?? {},
|
|
441
491
|
tool_response: { content: e.content, isError: e.isError },
|
|
@@ -453,7 +503,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
453
503
|
// The 5 require-*-before-stop builtins thus enforce by gating the NEXT
|
|
454
504
|
// user turn's system prompt rather than by retrying the same loop. If the
|
|
455
505
|
// user kills Pi between turns, the gate is missed — same bound Claude has.
|
|
456
|
-
pi.on("agent_end", (event: unknown): unknown => {
|
|
506
|
+
pi.on("agent_end", (event: unknown, ctx?: PiExtensionContext): unknown => {
|
|
457
507
|
const e = event as PiAgentEndEvent;
|
|
458
508
|
const cwd = resolveCwd(e.cwd);
|
|
459
509
|
const sessionId = resolveSessionId(e.sessionId, cwd);
|
|
@@ -466,6 +516,9 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
466
516
|
pendingStopBlockBySession.set(sessionId, decision.reason);
|
|
467
517
|
debug(`agent_end deny stored for session=${sessionId}`);
|
|
468
518
|
}
|
|
519
|
+
if (decision.notice) {
|
|
520
|
+
try { ctx?.ui?.notify(decision.notice, "warning"); } catch { /* fail-open */ }
|
|
521
|
+
}
|
|
469
522
|
return undefined;
|
|
470
523
|
});
|
|
471
524
|
|
|
@@ -500,7 +553,7 @@ export default function failproofaiBridge(pi: PiExtensionApi) {
|
|
|
500
553
|
pi.on("session_shutdown", (event: unknown): unknown => {
|
|
501
554
|
const e = event as PiSessionShutdownEvent;
|
|
502
555
|
const cwd = resolveCwd(e.cwd);
|
|
503
|
-
|
|
556
|
+
forwardPolicy("session_shutdown", {
|
|
504
557
|
session_id: resolveSessionId(e.sessionId, cwd),
|
|
505
558
|
cwd,
|
|
506
559
|
reason: e.reason,
|