failproofai 0.0.13 → 0.0.14-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/.factory/hooks.json +112 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -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/server-reference-manifest.json +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 +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/page/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/server-reference-manifest.json +8 -8
- 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/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
- 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/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 -3
- 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/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/chunks/[root-of-the-server]__0--lkk6._.js +3 -0
- 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]__06cuf1y._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jllheg._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0xuaoik._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +3 -0
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +3 -0
- package/.next/standalone/.next/server/chunks/{_1z062h5._.js → _1vslkoe._.js} +2 -2
- package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +3 -0
- package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +24 -12
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07iom4f._.js → [root-of-the-server]__07qfsfa._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ge8e7w._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h12me5._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11rtg6s._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +12 -9
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1dky4g0._.js +1 -1
- 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]__1tkjqka._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__212nf49._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_0ul5rf_._.js → _1_ozgif._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
- 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/lib_0xkhw_v._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_antigravity-projects_ts_0_6zf11._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_devin-projects_ts_0a64qdy._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_factory-projects_ts_1uyaahq._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_goose-projects_ts_0zkscri._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_13p7zob.js → node_modules_html-to-image_es_index_0g5r64e.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0-uvagv.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_03c7gi5.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_09p-8om.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0v-kfiu.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0xrgzyz.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1806lsc.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1j6dd-e.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_1sa65r-.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- 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 +10 -10
- package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 003uz088wbmz7.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 0405htbxay9vd.js} +1 -1
- package/.next/standalone/.next/static/chunks/0iinhn4417nk1.js +1 -0
- package/.next/standalone/.next/static/chunks/1374x9bv_s_vg.js +6 -0
- package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 17sdztgbcc0fi.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 2m_tbl39fcedn.js} +1 -1
- package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 2o3ql0n5owqsw.js} +1 -1
- package/.next/standalone/.next/static/chunks/{0wtagd8a5gwq-.js → 2ufuwhv03yvtb.js} +1 -1
- package/.next/standalone/.next/static/chunks/35-90m9py64d4.js +1 -0
- package/.next/standalone/.next/static/chunks/3r--yyjkp9_7b.css +2 -0
- package/.next/standalone/.next/static/chunks/43weu8f59tlix.js +1 -0
- package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 454dyizqik6t9.js} +2 -2
- package/.next/standalone/app/api/audit/run/route.ts +2 -0
- package/.next/standalone/app/audit/_components/empty-state.tsx +1 -1
- package/.next/standalone/app/globals.css +25 -1
- package/.next/standalone/app/policies/hooks-client.tsx +2 -4
- package/.next/standalone/app/project/[name]/page.tsx +30 -10
- package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +71 -21
- package/.next/standalone/assets/logos/antigravity.svg +1 -0
- package/.next/standalone/assets/logos/devin.svg +1 -0
- package/.next/standalone/assets/logos/factory-dark.png +0 -0
- package/.next/standalone/assets/logos/factory-light.png +0 -0
- package/.next/standalone/assets/logos/goose-dark.svg +1 -0
- package/.next/standalone/assets/logos/goose-light.svg +1 -0
- package/.next/standalone/assets/logos/openclaw.svg +22 -0
- package/.next/standalone/components/navbar.tsx +5 -11
- package/.next/standalone/components/reach-developers.tsx +1 -1
- package/.next/standalone/lib/antigravity-projects.ts +224 -0
- package/.next/standalone/lib/antigravity-sessions.ts +283 -0
- package/.next/standalone/lib/cli-registry.ts +26 -6
- package/.next/standalone/lib/devin-projects.ts +141 -0
- package/.next/standalone/lib/devin-sessions.ts +343 -0
- package/.next/standalone/lib/download-session.ts +56 -5
- package/.next/standalone/lib/factory-projects.ts +109 -0
- package/.next/standalone/lib/factory-sessions.ts +312 -0
- package/.next/standalone/lib/goose-projects.ts +148 -0
- package/.next/standalone/lib/goose-sessions.ts +296 -0
- package/.next/standalone/lib/openclaw-projects.ts +182 -0
- package/.next/standalone/lib/openclaw-sessions.ts +284 -0
- package/.next/standalone/lib/projects.ts +33 -9
- package/.next/standalone/lib/telemetry-sanitize.ts +43 -0
- package/.next/standalone/openclaw-plugin/index.js +223 -0
- package/.next/standalone/openclaw-plugin/openclaw.plugin.json +6 -0
- package/.next/standalone/openclaw-plugin/package.json +12 -0
- package/.next/standalone/package.json +2 -1
- package/.next/standalone/server.js +1 -1
- package/README.md +105 -57
- package/bin/failproofai.mjs +87 -29
- package/dist/cli.mjs +12803 -9809
- package/lib/antigravity-projects.ts +224 -0
- package/lib/antigravity-sessions.ts +283 -0
- package/lib/cli-registry.ts +26 -6
- package/lib/devin-projects.ts +141 -0
- package/lib/devin-sessions.ts +343 -0
- package/lib/download-session.ts +56 -5
- package/lib/factory-projects.ts +109 -0
- package/lib/factory-sessions.ts +312 -0
- package/lib/goose-projects.ts +148 -0
- package/lib/goose-sessions.ts +296 -0
- package/lib/openclaw-projects.ts +182 -0
- package/lib/openclaw-sessions.ts +284 -0
- package/lib/projects.ts +33 -9
- package/lib/telemetry-sanitize.ts +43 -0
- package/openclaw-plugin/index.js +223 -0
- package/openclaw-plugin/openclaw.plugin.json +6 -0
- package/openclaw-plugin/package.json +12 -0
- package/package.json +2 -1
- package/scripts/launch.ts +4 -8
- package/scripts/postinstall.mjs +3 -4
- package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
- package/scripts/translate-docs/cli.ts +73 -52
- package/scripts/translate-docs/config.ts +57 -13
- package/scripts/translate-docs/mdx-translator.ts +51 -31
- package/scripts/translate-docs/mintlify-nav.ts +91 -10
- package/scripts/translate-docs/types.ts +3 -0
- package/src/audit/cli-adapters/antigravity.ts +87 -0
- package/src/audit/cli-adapters/devin.ts +53 -0
- package/src/audit/cli-adapters/factory.ts +75 -0
- package/src/audit/cli-adapters/goose.ts +53 -0
- package/src/audit/cli-adapters/index.ts +30 -6
- package/src/audit/cli-adapters/openclaw.ts +56 -0
- package/src/audit/cli.ts +44 -1
- package/src/audit/report.ts +1 -1
- package/src/audit/types.ts +1 -1
- package/src/hooks/builtin-policies.ts +5 -5
- package/src/hooks/configure-wizard.ts +356 -0
- package/src/hooks/handler.ts +283 -218
- package/src/hooks/hook-telemetry.ts +43 -3
- package/src/hooks/install-prompt.ts +92 -173
- package/src/hooks/integrations.ts +739 -138
- package/src/hooks/manager.ts +46 -2
- package/src/hooks/policy-evaluator.ts +301 -121
- package/src/hooks/policy-presets.ts +71 -0
- package/src/hooks/resolve-cwd.ts +1 -1
- package/src/hooks/resolve-permission-mode.ts +1 -5
- package/src/hooks/resolve-transcript-path.ts +25 -7
- package/src/hooks/tool-name-canonicalize.ts +36 -3
- package/src/hooks/tui.ts +578 -0
- package/src/hooks/types.ts +447 -109
- package/.next/standalone/.gemini/settings.json +0 -147
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cag8qd._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +0 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hgv_75._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +0 -4
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__108slqn._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_1c7bgx-._.js +0 -3
- package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +0 -1
- package/.next/standalone/.next/static/chunks/0nrah-qv6smeg.js +0 -6
- package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +0 -1
- package/.next/standalone/.next/static/chunks/25qr0e6ez0ei0.js +0 -1
- package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +0 -2
- package/.next/standalone/assets/logos/gemini-dark.svg +0 -13
- package/.next/standalone/assets/logos/gemini-light.svg +0 -13
- package/.next/standalone/lib/gemini-projects.ts +0 -243
- package/.next/standalone/lib/gemini-sessions.ts +0 -365
- package/lib/gemini-projects.ts +0 -243
- package/lib/gemini-sessions.ts +0 -365
- package/src/audit/cli-adapters/gemini.ts +0 -51
- package/src/hooks/first-run-nudge.ts +0 -146
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → MNJWFb2jLRrxRKGFNK6Hy}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenClaw (openclaw gateway) session transcript loader + parser.
|
|
3
|
+
*
|
|
4
|
+
* AUDIT-ONLY (Pillar 2). OpenClaw writes one JSONL transcript per session at
|
|
5
|
+
* `~/.openclaw/agents/<agentId>/sessions/<sessionId>.jsonl` (sessionId is a
|
|
6
|
+
* UUID), alongside a much larger `<sessionId>.trajectory.jsonl` OTel trace we
|
|
7
|
+
* IGNORE, and a `sessions.json` index keyed by sessionKey. Verified live
|
|
8
|
+
* against openclaw v2026.7.1.
|
|
9
|
+
*
|
|
10
|
+
* The transcript is type-discriminated JSONL:
|
|
11
|
+
* {type:"session", cwd, …} — header (carries cwd)
|
|
12
|
+
* {type:"model_change" | "thinking_level_change" | "custom", …} — metadata
|
|
13
|
+
* {type:"message", timestamp, message:{role, content, …}} — the turns, where
|
|
14
|
+
* role ∈ "user" (content string) | "assistant" (content = list of
|
|
15
|
+
* {type:"text",text} / {type:"toolCall", id, name, arguments}) |
|
|
16
|
+
* "toolResult" ({toolCallId, toolName, content, details, isError}).
|
|
17
|
+
* `openclawLinesToLogEntries` pairs each assistant `toolCall` with its later
|
|
18
|
+
* `toolResult` by `toolCallId` (mirrors lib/hermes-sessions.ts) and is PURE, so
|
|
19
|
+
* it is unit-testable with plain line objects.
|
|
20
|
+
*
|
|
21
|
+
* Home override: set `OPENCLAW_HOME` (used by tests / to point at a copied
|
|
22
|
+
* gateway config dir).
|
|
23
|
+
*/
|
|
24
|
+
import { readFile } from "node:fs/promises";
|
|
25
|
+
import { readdirSync, statSync } from "node:fs";
|
|
26
|
+
import { homedir } from "node:os";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { runtimeCache } from "./runtime-cache";
|
|
29
|
+
import {
|
|
30
|
+
baseEntry,
|
|
31
|
+
formatTimestamp,
|
|
32
|
+
parseRawLines,
|
|
33
|
+
type LogEntry,
|
|
34
|
+
type UserEntry,
|
|
35
|
+
type AssistantEntry,
|
|
36
|
+
type GenericEntry,
|
|
37
|
+
type ContentBlock,
|
|
38
|
+
type ToolUseBlock,
|
|
39
|
+
type LogSource,
|
|
40
|
+
} from "./log-entries";
|
|
41
|
+
import { formatDuration } from "./format-duration";
|
|
42
|
+
|
|
43
|
+
/** OpenClaw sessions are stored under UUID filenames. */
|
|
44
|
+
export const OPENCLAW_SESSION_ID_RE = /^[0-9a-fA-F-]{36}$/;
|
|
45
|
+
|
|
46
|
+
/** Absolute path to OpenClaw's config home (override with OPENCLAW_HOME). */
|
|
47
|
+
export function openclawHome(): string {
|
|
48
|
+
return process.env.OPENCLAW_HOME || join(homedir(), ".openclaw");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Parsing helpers ──
|
|
52
|
+
|
|
53
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
54
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Text from a `content` field that may be a string or an array of
|
|
58
|
+
* `{ type:"text", text }` blocks. */
|
|
59
|
+
function extractText(content: unknown): string {
|
|
60
|
+
if (typeof content === "string") return content;
|
|
61
|
+
if (Array.isArray(content)) {
|
|
62
|
+
return content
|
|
63
|
+
.map((c) => (isPlainObject(c) && typeof c.text === "string" ? (c.text as string) : ""))
|
|
64
|
+
.filter(Boolean)
|
|
65
|
+
.join("\n");
|
|
66
|
+
}
|
|
67
|
+
return "";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function toDate(value: unknown, fallbackMs: number): Date {
|
|
71
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
72
|
+
if (value > 1e12) return new Date(value);
|
|
73
|
+
if (value > 1e9) return new Date(value * 1000);
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
const ms = Date.parse(value);
|
|
77
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
78
|
+
}
|
|
79
|
+
return new Date(fallbackMs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Pure parser: transcript lines → LogEntry[] ──
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Convert OpenClaw transcript JSONL lines (parsed objects, in file order) into
|
|
86
|
+
* `LogEntry[]`. Only `type:"message"` lines carry turns; `session` /
|
|
87
|
+
* `model_change` / `thinking_level_change` / `custom` lines are metadata and
|
|
88
|
+
* are skipped (they'd otherwise clutter the audit with empty system entries).
|
|
89
|
+
* Assistant `toolCall` blocks are paired with their later `toolResult` message
|
|
90
|
+
* by `toolCallId`. Pure — unit-testable with plain line objects.
|
|
91
|
+
*/
|
|
92
|
+
export function openclawLinesToLogEntries(
|
|
93
|
+
lines: Record<string, unknown>[],
|
|
94
|
+
source: LogSource = "session",
|
|
95
|
+
): LogEntry[] {
|
|
96
|
+
const entries: LogEntry[] = [];
|
|
97
|
+
const toolUseById = new Map<string, ToolUseBlock>();
|
|
98
|
+
const toolUseStartMs = new Map<string, number>();
|
|
99
|
+
const baseMs = Date.now();
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < lines.length; i++) {
|
|
102
|
+
const line = lines[i];
|
|
103
|
+
if (!isPlainObject(line)) continue;
|
|
104
|
+
if (line.type !== "message") continue; // skip session/model_change/custom/…
|
|
105
|
+
|
|
106
|
+
const m = isPlainObject(line.message) ? line.message : undefined;
|
|
107
|
+
if (!m) continue;
|
|
108
|
+
const role = typeof m.role === "string" ? m.role : "system";
|
|
109
|
+
|
|
110
|
+
// Outer line `timestamp` is an ISO string; message.timestamp is epoch ms.
|
|
111
|
+
const date = toDate(line.timestamp ?? m.timestamp, baseMs + i);
|
|
112
|
+
const timestamp = date.toISOString();
|
|
113
|
+
const raw: Record<string, unknown> = {
|
|
114
|
+
uuid: line.id != null ? String(line.id) : `openclaw-${i}`,
|
|
115
|
+
parentUuid: line.parentId != null ? String(line.parentId) : null,
|
|
116
|
+
};
|
|
117
|
+
const base = baseEntry(raw, timestamp, date, source);
|
|
118
|
+
|
|
119
|
+
if (role === "user") {
|
|
120
|
+
entries.push({
|
|
121
|
+
type: "user",
|
|
122
|
+
...base,
|
|
123
|
+
message: { role: "user", content: extractText(m.content) },
|
|
124
|
+
} satisfies UserEntry);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (role === "assistant") {
|
|
129
|
+
const blocks: ContentBlock[] = [];
|
|
130
|
+
const content = m.content;
|
|
131
|
+
if (Array.isArray(content)) {
|
|
132
|
+
for (const b of content) {
|
|
133
|
+
if (!isPlainObject(b)) continue;
|
|
134
|
+
if (b.type === "text" && typeof b.text === "string") {
|
|
135
|
+
blocks.push({ type: "text", text: b.text });
|
|
136
|
+
} else if (b.type === "toolCall") {
|
|
137
|
+
const id = typeof b.id === "string" ? b.id : `${String(b.name ?? "tool")}-${blocks.length}`;
|
|
138
|
+
const name = typeof b.name === "string" ? b.name : "tool";
|
|
139
|
+
const input = isPlainObject(b.arguments) ? b.arguments : {};
|
|
140
|
+
const block: ToolUseBlock = { type: "tool_use", id, name, input };
|
|
141
|
+
blocks.push(block);
|
|
142
|
+
toolUseById.set(id, block);
|
|
143
|
+
toolUseStartMs.set(id, date.getTime());
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
const text = extractText(content);
|
|
148
|
+
if (text) blocks.push({ type: "text", text });
|
|
149
|
+
}
|
|
150
|
+
if (blocks.length === 0) continue; // empty / failed assistant turn
|
|
151
|
+
entries.push({
|
|
152
|
+
type: "assistant",
|
|
153
|
+
...base,
|
|
154
|
+
message: { role: "assistant", content: blocks, model: typeof m.model === "string" ? m.model : undefined },
|
|
155
|
+
} satisfies AssistantEntry);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (role === "toolResult") {
|
|
160
|
+
const callId = typeof m.toolCallId === "string" ? m.toolCallId : undefined;
|
|
161
|
+
const block = callId ? toolUseById.get(callId) : undefined;
|
|
162
|
+
if (block) {
|
|
163
|
+
const details = isPlainObject(m.details) ? m.details : undefined;
|
|
164
|
+
const startMs = (callId && toolUseStartMs.get(callId)) || date.getTime();
|
|
165
|
+
const durationMs =
|
|
166
|
+
details && typeof details.durationMs === "number"
|
|
167
|
+
? details.durationMs
|
|
168
|
+
: Math.max(0, date.getTime() - startMs);
|
|
169
|
+
block.result = {
|
|
170
|
+
timestamp,
|
|
171
|
+
timestampFormatted: formatTimestamp(date),
|
|
172
|
+
content: extractText(m.content),
|
|
173
|
+
durationMs,
|
|
174
|
+
durationFormatted: formatDuration(durationMs),
|
|
175
|
+
};
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Orphan tool result — fall through to a system entry.
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
entries.push({ type: "system", ...base, raw } satisfies GenericEntry);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
entries.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
185
|
+
return entries;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ── Discovery + file loader ──
|
|
189
|
+
|
|
190
|
+
export interface OpenClawTranscriptFile {
|
|
191
|
+
agentId: string;
|
|
192
|
+
sessionId: string;
|
|
193
|
+
transcriptPath: string;
|
|
194
|
+
mtimeMs: number;
|
|
195
|
+
sizeBytes: number;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Enumerate `agents/<agentId>/sessions/<uuid>.jsonl` transcripts, skipping the
|
|
199
|
+
* heavy `.trajectory.jsonl` OTel traces and pointer files. */
|
|
200
|
+
export function listOpenClawTranscripts(): OpenClawTranscriptFile[] {
|
|
201
|
+
const agentsDir = join(openclawHome(), "agents");
|
|
202
|
+
const out: OpenClawTranscriptFile[] = [];
|
|
203
|
+
let agentIds: string[];
|
|
204
|
+
try {
|
|
205
|
+
agentIds = readdirSync(agentsDir, { withFileTypes: true })
|
|
206
|
+
.filter((d) => d.isDirectory())
|
|
207
|
+
.map((d) => d.name);
|
|
208
|
+
} catch {
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
for (const agentId of agentIds) {
|
|
212
|
+
const sessionsDir = join(agentsDir, agentId, "sessions");
|
|
213
|
+
let files: string[];
|
|
214
|
+
try {
|
|
215
|
+
files = readdirSync(sessionsDir);
|
|
216
|
+
} catch {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
for (const file of files) {
|
|
220
|
+
// Only `<uuid>.jsonl` — not `<uuid>.trajectory.jsonl` / `.trajectory-path.json`.
|
|
221
|
+
if (!file.endsWith(".jsonl") || file.endsWith(".trajectory.jsonl")) continue;
|
|
222
|
+
const sessionId = file.slice(0, -".jsonl".length);
|
|
223
|
+
if (!OPENCLAW_SESSION_ID_RE.test(sessionId)) continue;
|
|
224
|
+
const transcriptPath = join(sessionsDir, file);
|
|
225
|
+
try {
|
|
226
|
+
const st = statSync(transcriptPath);
|
|
227
|
+
out.push({ agentId, sessionId, transcriptPath, mtimeMs: st.mtimeMs, sizeBytes: st.size });
|
|
228
|
+
} catch {
|
|
229
|
+
// skip unreadable
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Resolve a session UUID to its on-disk transcript path (host-side). Guards
|
|
237
|
+
* against traversal by requiring a UUID filename. Shared by the audit adapter
|
|
238
|
+
* and download-session. */
|
|
239
|
+
export function findOpenClawTranscript(sessionId: string): string | null {
|
|
240
|
+
if (!OPENCLAW_SESSION_ID_RE.test(sessionId)) return null;
|
|
241
|
+
for (const t of listOpenClawTranscripts()) {
|
|
242
|
+
if (t.sessionId === sessionId) return t.transcriptPath;
|
|
243
|
+
}
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export interface OpenClawSessionLogData {
|
|
248
|
+
entries: LogEntry[];
|
|
249
|
+
rawLines: Record<string, unknown>[];
|
|
250
|
+
cwd?: string;
|
|
251
|
+
filePath: string;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Load and parse one session transcript by UUID. Returns `null` when the file
|
|
255
|
+
* is missing/unreadable or the id fails validation. */
|
|
256
|
+
export async function getOpenClawSessionLog(
|
|
257
|
+
sessionId: string,
|
|
258
|
+
): Promise<OpenClawSessionLogData | null> {
|
|
259
|
+
const filePath = findOpenClawTranscript(sessionId);
|
|
260
|
+
if (!filePath) return null;
|
|
261
|
+
let content: string;
|
|
262
|
+
try {
|
|
263
|
+
content = await readFile(filePath, "utf-8");
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
const rawLines = parseRawLines(content, "session");
|
|
268
|
+
const entries = openclawLinesToLogEntries(rawLines, "session");
|
|
269
|
+
// cwd lives on the `type:"session"` header line.
|
|
270
|
+
let cwd: string | undefined;
|
|
271
|
+
for (const line of rawLines) {
|
|
272
|
+
if (isPlainObject(line) && line.type === "session" && typeof line.cwd === "string" && line.cwd.length > 0) {
|
|
273
|
+
cwd = line.cwd;
|
|
274
|
+
break;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return { entries, rawLines, cwd, filePath };
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export const getCachedOpenClawSessionLog = runtimeCache(
|
|
281
|
+
(sessionId: string) => getOpenClawSessionLog(sessionId),
|
|
282
|
+
2,
|
|
283
|
+
{ maxSize: 50 },
|
|
284
|
+
);
|
|
@@ -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" | "
|
|
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;
|
|
@@ -143,18 +143,26 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
143
143
|
{ getCursorProjects },
|
|
144
144
|
{ getOpenCodeProjects },
|
|
145
145
|
{ getPiProjects },
|
|
146
|
-
{ getGeminiProjects },
|
|
147
146
|
{ getHermesProjects },
|
|
147
|
+
{ getOpenClawProjects },
|
|
148
|
+
{ getFactoryProjects },
|
|
149
|
+
{ getDevinProjects },
|
|
150
|
+
{ getAntigravityProjects },
|
|
151
|
+
{ getGooseProjects },
|
|
148
152
|
] = await Promise.all([
|
|
149
153
|
import("./codex-projects"),
|
|
150
154
|
import("./copilot-projects"),
|
|
151
155
|
import("./cursor-projects"),
|
|
152
156
|
import("./opencode-projects"),
|
|
153
157
|
import("./pi-projects"),
|
|
154
|
-
import("./gemini-projects"),
|
|
155
158
|
import("./hermes-projects"),
|
|
159
|
+
import("./openclaw-projects"),
|
|
160
|
+
import("./factory-projects"),
|
|
161
|
+
import("./devin-projects"),
|
|
162
|
+
import("./antigravity-projects"),
|
|
163
|
+
import("./goose-projects"),
|
|
156
164
|
]);
|
|
157
|
-
const [claude, codex, copilot, cursor, opencode, pi,
|
|
165
|
+
const [claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose] = await Promise.all([
|
|
158
166
|
getClaudeProjectFolders(),
|
|
159
167
|
getCodexProjects().catch((error) => {
|
|
160
168
|
logError("Error reading Codex projects:", error);
|
|
@@ -176,16 +184,32 @@ export async function getProjectFolders(): Promise<ProjectFolder[]> {
|
|
|
176
184
|
logError("Error reading Pi projects:", error);
|
|
177
185
|
return [] as ProjectFolder[];
|
|
178
186
|
}),
|
|
179
|
-
getGeminiProjects().catch((error) => {
|
|
180
|
-
logError("Error reading Gemini projects:", error);
|
|
181
|
-
return [] as ProjectFolder[];
|
|
182
|
-
}),
|
|
183
187
|
getHermesProjects().catch((error) => {
|
|
184
188
|
logError("Error reading Hermes projects:", error);
|
|
185
189
|
return [] as ProjectFolder[];
|
|
186
190
|
}),
|
|
191
|
+
getOpenClawProjects().catch((error) => {
|
|
192
|
+
logError("Error reading OpenClaw projects:", error);
|
|
193
|
+
return [] as ProjectFolder[];
|
|
194
|
+
}),
|
|
195
|
+
getFactoryProjects().catch((error) => {
|
|
196
|
+
logError("Error reading Factory projects:", error);
|
|
197
|
+
return [] as ProjectFolder[];
|
|
198
|
+
}),
|
|
199
|
+
getDevinProjects().catch((error) => {
|
|
200
|
+
logError("Error reading Devin projects:", error);
|
|
201
|
+
return [] as ProjectFolder[];
|
|
202
|
+
}),
|
|
203
|
+
getAntigravityProjects().catch((error) => {
|
|
204
|
+
logError("Error reading Antigravity projects:", error);
|
|
205
|
+
return [] as ProjectFolder[];
|
|
206
|
+
}),
|
|
207
|
+
getGooseProjects().catch((error) => {
|
|
208
|
+
logError("Error reading Goose projects:", error);
|
|
209
|
+
return [] as ProjectFolder[];
|
|
210
|
+
}),
|
|
187
211
|
]);
|
|
188
|
-
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi,
|
|
212
|
+
return mergeProjectFolders(claude, codex, copilot, cursor, opencode, pi, hermes, openclaw, factory, devin, antigravity, goose);
|
|
189
213
|
}
|
|
190
214
|
|
|
191
215
|
/**
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
|
|
3
|
+
/** Max length of a telemetry error message; longer strings are truncated. */
|
|
4
|
+
const MAX_ERROR_MESSAGE_LEN = 300;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Turn an error into a telemetry-safe message string for the `error_message`
|
|
8
|
+
* property on failure events (e.g. `cli_audit_failed`, `audit_run_failed`).
|
|
9
|
+
*
|
|
10
|
+
* Two protections keep this consistent with failproofai's telemetry philosophy
|
|
11
|
+
* (no raw local paths / PII on the wire):
|
|
12
|
+
* 1. The user's home directory is collapsed to `~`, so an error that embeds a
|
|
13
|
+
* path like `/home/alice/.codex/…` is reported as `~/.codex/…`.
|
|
14
|
+
* 2. The message is truncated to a bounded length (with an ellipsis marker).
|
|
15
|
+
*
|
|
16
|
+
* Never throws — falls back to `"unknown"` if the error can't be stringified.
|
|
17
|
+
*/
|
|
18
|
+
export function sanitizeErrorMessage(err: unknown): string {
|
|
19
|
+
let msg: string;
|
|
20
|
+
try {
|
|
21
|
+
msg = err instanceof Error ? err.message : String(err);
|
|
22
|
+
} catch {
|
|
23
|
+
return "unknown";
|
|
24
|
+
}
|
|
25
|
+
if (!msg) return "";
|
|
26
|
+
|
|
27
|
+
let home = "";
|
|
28
|
+
try {
|
|
29
|
+
home = os.homedir();
|
|
30
|
+
} catch {
|
|
31
|
+
/* homedir unavailable — skip path stripping */
|
|
32
|
+
}
|
|
33
|
+
// Only strip a real, non-root home to avoid mangling messages when homedir is
|
|
34
|
+
// "/" or empty.
|
|
35
|
+
if (home && home.length > 1) {
|
|
36
|
+
msg = msg.split(home).join("~");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (msg.length > MAX_ERROR_MESSAGE_LEN) {
|
|
40
|
+
msg = msg.slice(0, MAX_ERROR_MESSAGE_LEN) + "…";
|
|
41
|
+
}
|
|
42
|
+
return msg;
|
|
43
|
+
}
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* failproofai policy bridge for OpenClaw (openclaw gateway).
|
|
3
|
+
*
|
|
4
|
+
* Loaded in-process by the gateway (registered via `plugins.load.paths` +
|
|
5
|
+
* `plugins.entries.failproofai` in ~/.openclaw/openclaw.json). It subscribes to
|
|
6
|
+
* OpenClaw's typed PLUGIN hooks — the only surface that can block; OpenClaw's
|
|
7
|
+
* file-based "internal hooks" are observation-only — and forwards each to the
|
|
8
|
+
* failproofai binary as `failproofai --hook <event> --cli openclaw`. failproofai
|
|
9
|
+
* prints a flat `{permission, reason}` verdict on stdout; this shim maps it to
|
|
10
|
+
* each hook's native return shape.
|
|
11
|
+
*
|
|
12
|
+
* Marker comment for failproofai's installer detection (do not remove):
|
|
13
|
+
* __failproofai_hook__: true
|
|
14
|
+
*
|
|
15
|
+
* Design notes (verified live against openclaw v2026.7.1):
|
|
16
|
+
* • ASYNC spawn, never spawnSync — the gateway is a long-running multi-channel
|
|
17
|
+
* process; a synchronous spawn on every hook would stall every channel.
|
|
18
|
+
* 30s guard timer; fail-open on any error (never block on infra failure).
|
|
19
|
+
* • Raw forwarding — we send the raw tool name / params and a Claude-shaped
|
|
20
|
+
* stdin (params→tool_input, toolName→tool_name, transcriptPath→
|
|
21
|
+
* transcript_path, stopHookActive→stop_hook_active, sessionKey→session_id).
|
|
22
|
+
* The binary canonicalizes via OPENCLAW_* maps (single source of truth); the
|
|
23
|
+
* shim ships NO inline maps (unlike the OpenCode/Pi shims).
|
|
24
|
+
* • No top-level `await` — OpenClaw's plugin loader rejects it; all async work
|
|
25
|
+
* happens inside register()'s handlers.
|
|
26
|
+
*
|
|
27
|
+
* Binary resolution mirrors pi-extension/index.ts: prefer the bundled
|
|
28
|
+
* dist/cli.mjs (node-compatible), fall back to bin/failproofai.mjs with bun for
|
|
29
|
+
* dev; honor FAILPROOFAI_BINARY_OVERRIDE. Paths resolve relative to this file
|
|
30
|
+
* via import.meta.url (the gateway spawns plugins with an undefined cwd).
|
|
31
|
+
*/
|
|
32
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
33
|
+
import { spawn } from "node:child_process";
|
|
34
|
+
import { existsSync } from "node:fs";
|
|
35
|
+
import { resolve, dirname } from "node:path";
|
|
36
|
+
import { fileURLToPath } from "node:url";
|
|
37
|
+
|
|
38
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
39
|
+
const DIST_BIN = resolve(HERE, "..", "dist", "cli.mjs");
|
|
40
|
+
const SRC_BIN = resolve(HERE, "..", "bin", "failproofai.mjs");
|
|
41
|
+
|
|
42
|
+
function resolveSpawn() {
|
|
43
|
+
if (process.env.FAILPROOFAI_BINARY_OVERRIDE) {
|
|
44
|
+
return { cmd: "node", args: [process.env.FAILPROOFAI_BINARY_OVERRIDE] };
|
|
45
|
+
}
|
|
46
|
+
if (existsSync(DIST_BIN)) {
|
|
47
|
+
return { cmd: "node", args: [DIST_BIN] };
|
|
48
|
+
}
|
|
49
|
+
return { cmd: "bun", args: [SRC_BIN] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function debug(msg) {
|
|
53
|
+
if (process.env.FAILPROOFAI_OPENCLAW_DEBUG === "1") {
|
|
54
|
+
process.stderr.write(`[failproofai-openclaw-shim] ${msg}\n`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Spawn `failproofai --hook <rawEvent> --cli openclaw`, write the JSON payload
|
|
60
|
+
* to stdin, and resolve the parsed flat `{permission, reason}` verdict. Async +
|
|
61
|
+
* fail-open: any spawn/parse error or timeout resolves to `{permission:"allow"}`
|
|
62
|
+
* so a failproofai fault never blocks the gateway.
|
|
63
|
+
*/
|
|
64
|
+
function callPolicy(rawEvent, payload) {
|
|
65
|
+
return new Promise((done) => {
|
|
66
|
+
const { cmd, args } = resolveSpawn();
|
|
67
|
+
let child;
|
|
68
|
+
try {
|
|
69
|
+
child = spawn(cmd, [...args, "--hook", rawEvent, "--cli", "openclaw"], {
|
|
70
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
debug(`spawn threw: ${err && err.message ? err.message : String(err)}`);
|
|
74
|
+
done({ permission: "allow" });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
let out = "";
|
|
78
|
+
let settled = false;
|
|
79
|
+
const finish = (verdict) => {
|
|
80
|
+
if (settled) return;
|
|
81
|
+
settled = true;
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
done(verdict);
|
|
84
|
+
};
|
|
85
|
+
const timer = setTimeout(() => {
|
|
86
|
+
debug(`timeout on ${rawEvent}; killing child`);
|
|
87
|
+
try { child.kill(); } catch { /* ignore */ }
|
|
88
|
+
finish({ permission: "allow" });
|
|
89
|
+
}, 30_000);
|
|
90
|
+
child.stdout.on("data", (d) => { out += d; });
|
|
91
|
+
child.on("error", (err) => {
|
|
92
|
+
debug(`child error: ${err && err.message ? err.message : String(err)}`);
|
|
93
|
+
finish({ permission: "allow" });
|
|
94
|
+
});
|
|
95
|
+
child.on("close", () => {
|
|
96
|
+
const stdout = out.trim();
|
|
97
|
+
if (!stdout) { finish({ permission: "allow" }); return; }
|
|
98
|
+
try {
|
|
99
|
+
finish(JSON.parse(stdout));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
debug(`parse error: ${err && err.message ? err.message : String(err)}`);
|
|
102
|
+
finish({ permission: "allow" });
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
try {
|
|
106
|
+
child.stdin.end(JSON.stringify(payload));
|
|
107
|
+
} catch (err) {
|
|
108
|
+
debug(`stdin write failed: ${err && err.message ? err.message : String(err)}`);
|
|
109
|
+
finish({ permission: "allow" });
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Claude-shaped stdin base. Extra keys are ignored by the binary's payload
|
|
115
|
+
* parser; they're forwarded for future use / activity attribution. */
|
|
116
|
+
function baseMeta(payload, ctx) {
|
|
117
|
+
const p = payload || {};
|
|
118
|
+
const c = ctx || {};
|
|
119
|
+
return {
|
|
120
|
+
session_id: c.sessionId ?? p.sessionId ?? c.sessionKey ?? p.sessionKey,
|
|
121
|
+
cwd: p.cwd ?? c.workspaceDir ?? process.cwd(),
|
|
122
|
+
transcript_path: p.transcriptPath,
|
|
123
|
+
stop_hook_active: p.stopHookActive === true,
|
|
124
|
+
openclaw: {
|
|
125
|
+
agentId: c.agentId,
|
|
126
|
+
sessionKey: c.sessionKey ?? p.sessionKey,
|
|
127
|
+
runId: c.runId ?? p.runId,
|
|
128
|
+
provider: p.provider,
|
|
129
|
+
model: p.model,
|
|
130
|
+
},
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export default definePluginEntry({
|
|
135
|
+
id: "failproofai",
|
|
136
|
+
name: "failproofai",
|
|
137
|
+
description: "Real-time policy enforcement for OpenClaw by failproofai",
|
|
138
|
+
register(api) {
|
|
139
|
+
// before_tool_call → PreToolUse. Full deny: return {block:true, blockReason}.
|
|
140
|
+
api.on(
|
|
141
|
+
"before_tool_call",
|
|
142
|
+
async (payload, ctx) => {
|
|
143
|
+
const p = payload || {};
|
|
144
|
+
const verdict = await callPolicy("before_tool_call", {
|
|
145
|
+
...baseMeta(payload, ctx),
|
|
146
|
+
tool_name: p.toolName,
|
|
147
|
+
tool_input: p.params,
|
|
148
|
+
hook_event_name: "before_tool_call",
|
|
149
|
+
});
|
|
150
|
+
if (verdict.permission === "deny") {
|
|
151
|
+
return { block: true, blockReason: verdict.reason || "Blocked by failproofai" };
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
},
|
|
155
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// before_agent_run → UserPromptSubmit. Deny: return {outcome:"block", reason}.
|
|
159
|
+
api.on(
|
|
160
|
+
"before_agent_run",
|
|
161
|
+
async (payload, ctx) => {
|
|
162
|
+
const p = payload || {};
|
|
163
|
+
const verdict = await callPolicy("before_agent_run", {
|
|
164
|
+
...baseMeta(payload, ctx),
|
|
165
|
+
prompt: p.prompt,
|
|
166
|
+
hook_event_name: "before_agent_run",
|
|
167
|
+
});
|
|
168
|
+
if (verdict.permission === "deny") {
|
|
169
|
+
return { outcome: "block", reason: verdict.reason || "Blocked by failproofai" };
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
},
|
|
173
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// before_agent_finalize → Stop. The real turn-end gate: a deny becomes a
|
|
177
|
+
// revise so the agent runs another pass. The evaluator supplies the
|
|
178
|
+
// MANDATORY ACTION wording; the payload's stop_hook_active guards the
|
|
179
|
+
// require-*-before-stop builtins against infinite loops.
|
|
180
|
+
api.on(
|
|
181
|
+
"before_agent_finalize",
|
|
182
|
+
async (payload, ctx) => {
|
|
183
|
+
const verdict = await callPolicy("before_agent_finalize", {
|
|
184
|
+
...baseMeta(payload, ctx),
|
|
185
|
+
hook_event_name: "before_agent_finalize",
|
|
186
|
+
});
|
|
187
|
+
if (verdict.permission === "deny") {
|
|
188
|
+
return { action: "revise", reason: verdict.reason || "Action required by failproofai" };
|
|
189
|
+
}
|
|
190
|
+
return undefined;
|
|
191
|
+
},
|
|
192
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Observation hooks — await so activity ordering is stable, ignore verdict.
|
|
196
|
+
// after_tool_call → PostToolUse; session_start/end → SessionStart/End;
|
|
197
|
+
// subagent_ended → SubagentStop (obs only); before_compaction → PreCompact.
|
|
198
|
+
for (const ev of [
|
|
199
|
+
"after_tool_call",
|
|
200
|
+
"session_start",
|
|
201
|
+
"session_end",
|
|
202
|
+
"subagent_ended",
|
|
203
|
+
"before_compaction",
|
|
204
|
+
]) {
|
|
205
|
+
api.on(
|
|
206
|
+
ev,
|
|
207
|
+
async (payload, ctx) => {
|
|
208
|
+
const p = payload || {};
|
|
209
|
+
await callPolicy(ev, {
|
|
210
|
+
...baseMeta(payload, ctx),
|
|
211
|
+
tool_name: p.toolName,
|
|
212
|
+
tool_input: p.params,
|
|
213
|
+
tool_response: p.result,
|
|
214
|
+
reason: p.reason,
|
|
215
|
+
hook_event_name: ev,
|
|
216
|
+
});
|
|
217
|
+
return undefined;
|
|
218
|
+
},
|
|
219
|
+
{ priority: 100, timeoutMs: 60_000 },
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@failproofai/openclaw-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "failproofai policy bridge for OpenClaw (openclaw gateway)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.js",
|
|
7
|
+
"private": true,
|
|
8
|
+
"keywords": [
|
|
9
|
+
"openclaw-plugin",
|
|
10
|
+
"failproofai"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "failproofai",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14-beta.0",
|
|
4
4
|
"description": "The easiest way to manage policies that keep your AI agents reliable, on-task, and running autonomously — for Claude Code & the Agents SDK",
|
|
5
5
|
"bin": {
|
|
6
6
|
"failproofai": "./dist/cli.mjs"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"scripts/",
|
|
12
12
|
"lib/",
|
|
13
13
|
"pi-extension/",
|
|
14
|
+
"openclaw-plugin/",
|
|
14
15
|
".next/standalone/",
|
|
15
16
|
"dist/",
|
|
16
17
|
"README.md"
|