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,343 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Devin CLI (Cognition) session transcript loader + parser.
|
|
3
|
+
*
|
|
4
|
+
* AUDIT-ONLY (Pillar 2). Devin stores every session in one SQLite DB at
|
|
5
|
+
* `~/.local/share/devin/cli/sessions.db`. We read it DIRECTLY via the bundled
|
|
6
|
+
* sql.js driver (lib/sqlite-reader.ts) — the same reusable path Hermes/OpenClaw
|
|
7
|
+
* use. Unlike Hermes (which is cwd-less), each Devin `sessions` row carries a
|
|
8
|
+
* real `working_directory`, so Devin sessions group by project cwd like Claude.
|
|
9
|
+
*
|
|
10
|
+
* Schema (verified live against devin v3000.1.27):
|
|
11
|
+
* sessions(id, working_directory, backend_type, model, agent_mode,
|
|
12
|
+
* created_at INT, last_activity_at INT, title, workspace_dirs, metadata)
|
|
13
|
+
* message_nodes(row_id, session_id, node_id, parent_node_id,
|
|
14
|
+
* chat_message TEXT=JSON, created_at INT, metadata)
|
|
15
|
+
*
|
|
16
|
+
* `chat_message` is OpenAI-style JSON: `{role, content, tool_calls?,
|
|
17
|
+
* tool_call_id?, thinking?, metadata?}`. Assistant tool calls are flat
|
|
18
|
+
* `tool_calls[].{id, name, arguments}` (arguments is already an object, NOT a
|
|
19
|
+
* JSON string as in the OpenAI wire format), and results are separate
|
|
20
|
+
* `role:"tool"` rows keyed by `tool_call_id`. `devinRowsToLogEntries` pairs them
|
|
21
|
+
* (mirrors lib/hermes-sessions.ts) and is a PURE function of the parsed message
|
|
22
|
+
* objects, so it is unit-testable without a DB.
|
|
23
|
+
*
|
|
24
|
+
* Home override: set `DEVIN_HOME` (used by tests / to point at a copied Devin
|
|
25
|
+
* data dir) or `DEVIN_DB_PATH` (points directly at a sessions.db).
|
|
26
|
+
*/
|
|
27
|
+
import { homedir } from "node:os";
|
|
28
|
+
import { join } from "node:path";
|
|
29
|
+
import { openSqliteReadonly } from "./sqlite-reader";
|
|
30
|
+
import { runtimeCache } from "./runtime-cache";
|
|
31
|
+
import {
|
|
32
|
+
baseEntry,
|
|
33
|
+
formatTimestamp,
|
|
34
|
+
type LogEntry,
|
|
35
|
+
type UserEntry,
|
|
36
|
+
type AssistantEntry,
|
|
37
|
+
type GenericEntry,
|
|
38
|
+
type ContentBlock,
|
|
39
|
+
type ToolUseBlock,
|
|
40
|
+
type LogSource,
|
|
41
|
+
} from "./log-entries";
|
|
42
|
+
import { formatDuration } from "./format-duration";
|
|
43
|
+
|
|
44
|
+
/** Devin session IDs are word-slug style (e.g. `estimated-seeker`). */
|
|
45
|
+
export const DEVIN_SESSION_ID_RE = /^[A-Za-z0-9_-]+$/;
|
|
46
|
+
|
|
47
|
+
/** Absolute path to Devin's data home (override with DEVIN_HOME). */
|
|
48
|
+
export function devinHome(): string {
|
|
49
|
+
return process.env.DEVIN_HOME || join(homedir(), ".local", "share", "devin");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Absolute path to Devin's SQLite DB (override with DEVIN_DB_PATH). */
|
|
53
|
+
export function devinDbPath(): string {
|
|
54
|
+
return process.env.DEVIN_DB_PATH || join(devinHome(), "cli", "sessions.db");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Coerce a Devin epoch value (seconds or ms) to epoch ms. Devin stores
|
|
58
|
+
* `created_at`/`last_activity_at` as INTEGER epoch seconds. */
|
|
59
|
+
export function devinEpochToMs(value: unknown): number {
|
|
60
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
61
|
+
if (value > 1e12) return value; // already ms
|
|
62
|
+
if (value > 1e9) return value * 1000; // seconds
|
|
63
|
+
}
|
|
64
|
+
return Date.now();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Parsing helpers ──
|
|
68
|
+
|
|
69
|
+
function safeJsonParse(s: unknown): unknown {
|
|
70
|
+
if (typeof s !== "string" || s.length === 0) return undefined;
|
|
71
|
+
try {
|
|
72
|
+
return JSON.parse(s);
|
|
73
|
+
} catch {
|
|
74
|
+
return undefined;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
79
|
+
return !!v && typeof v === "object" && !Array.isArray(v);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Text from a `content` field that may be a string or an array of
|
|
83
|
+
* `{ type:"text", text }` blocks. */
|
|
84
|
+
function extractText(content: unknown): string {
|
|
85
|
+
if (typeof content === "string") return content;
|
|
86
|
+
if (Array.isArray(content)) {
|
|
87
|
+
return content
|
|
88
|
+
.map((c) => (isPlainObject(c) && typeof c.text === "string" ? (c.text as string) : ""))
|
|
89
|
+
.filter(Boolean)
|
|
90
|
+
.join("\n");
|
|
91
|
+
}
|
|
92
|
+
return "";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function toDate(value: unknown, fallbackMs: number): Date {
|
|
96
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
97
|
+
if (value > 1e12) return new Date(value);
|
|
98
|
+
if (value > 1e9) return new Date(value * 1000);
|
|
99
|
+
}
|
|
100
|
+
if (typeof value === "string") {
|
|
101
|
+
const ms = Date.parse(value);
|
|
102
|
+
if (!Number.isNaN(ms)) return new Date(ms);
|
|
103
|
+
}
|
|
104
|
+
return new Date(fallbackMs);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface NormalizedToolCall {
|
|
108
|
+
id: string;
|
|
109
|
+
name: string;
|
|
110
|
+
input: Record<string, unknown>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Normalize Devin `tool_calls` into `{ id, name, input }[]`. Devin's shape is
|
|
114
|
+
* flat: `{ id, name, arguments }` where `arguments` is already an object. Also
|
|
115
|
+
* tolerates the OpenAI wire shape `{ id|call_id, function:{ name, arguments }}`
|
|
116
|
+
* (arguments as a JSON string) for forward-compat. */
|
|
117
|
+
function normalizeDevinToolCalls(raw: unknown): NormalizedToolCall[] {
|
|
118
|
+
const arr = Array.isArray(raw) ? raw : safeJsonParse(raw);
|
|
119
|
+
if (!Array.isArray(arr)) return [];
|
|
120
|
+
const out: NormalizedToolCall[] = [];
|
|
121
|
+
for (const tc of arr) {
|
|
122
|
+
if (!isPlainObject(tc)) continue;
|
|
123
|
+
const fn = isPlainObject(tc.function) ? tc.function : {};
|
|
124
|
+
const name =
|
|
125
|
+
typeof tc.name === "string"
|
|
126
|
+
? tc.name
|
|
127
|
+
: typeof fn.name === "string"
|
|
128
|
+
? (fn.name as string)
|
|
129
|
+
: "tool";
|
|
130
|
+
const id =
|
|
131
|
+
(typeof tc.id === "string" && tc.id) ||
|
|
132
|
+
(typeof tc.call_id === "string" && tc.call_id) ||
|
|
133
|
+
`${name}-${out.length}`;
|
|
134
|
+
const rawArgs = tc.arguments ?? fn.arguments;
|
|
135
|
+
const parsedArgs = isPlainObject(rawArgs) ? rawArgs : safeJsonParse(rawArgs);
|
|
136
|
+
out.push({ id, name, input: isPlainObject(parsedArgs) ? parsedArgs : {} });
|
|
137
|
+
}
|
|
138
|
+
return out;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Epoch ms for a parsed chat_message: prefer its high-precision
|
|
142
|
+
* `metadata.created_at` ISO string, else the DB row's injected `_created_at`
|
|
143
|
+
* (epoch seconds), else the fallback. */
|
|
144
|
+
function messageDate(m: Record<string, unknown>, fallbackMs: number): Date {
|
|
145
|
+
const meta = isPlainObject(m.metadata) ? m.metadata : undefined;
|
|
146
|
+
if (meta && meta.created_at != null) return toDate(meta.created_at, fallbackMs);
|
|
147
|
+
if (m._created_at != null) return toDate(m._created_at, fallbackMs);
|
|
148
|
+
return new Date(fallbackMs);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Pure parser: parsed chat_message objects → LogEntry[] ──
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Convert parsed `chat_message` objects (in node order) into `LogEntry[]`.
|
|
155
|
+
* Pairs each assistant `tool_calls` entry with its later `role:"tool"` result
|
|
156
|
+
* by `tool_call_id`. Roles other than user/assistant/tool (e.g. `system`)
|
|
157
|
+
* become generic system entries so nothing is dropped. Pure — unit-testable
|
|
158
|
+
* with plain message objects. Each object may carry an injected `_created_at`
|
|
159
|
+
* (the DB row's epoch-seconds timestamp) used when `metadata.created_at` is
|
|
160
|
+
* absent.
|
|
161
|
+
*/
|
|
162
|
+
export function devinRowsToLogEntries(
|
|
163
|
+
rows: Record<string, unknown>[],
|
|
164
|
+
source: LogSource = "session",
|
|
165
|
+
): LogEntry[] {
|
|
166
|
+
const entries: LogEntry[] = [];
|
|
167
|
+
const toolUseById = new Map<string, ToolUseBlock>();
|
|
168
|
+
const toolUseStartMs = new Map<string, number>();
|
|
169
|
+
const baseMs = Date.now();
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < rows.length; i++) {
|
|
172
|
+
const m = rows[i];
|
|
173
|
+
if (!isPlainObject(m)) continue;
|
|
174
|
+
|
|
175
|
+
const role = typeof m.role === "string" ? m.role : "system";
|
|
176
|
+
const date = messageDate(m, baseMs + i);
|
|
177
|
+
const timestamp = date.toISOString();
|
|
178
|
+
const raw: Record<string, unknown> = {
|
|
179
|
+
uuid: m.message_id != null ? String(m.message_id) : `devin-${i}`,
|
|
180
|
+
parentUuid: null,
|
|
181
|
+
};
|
|
182
|
+
const base = baseEntry(raw, timestamp, date, source);
|
|
183
|
+
|
|
184
|
+
if (role === "user") {
|
|
185
|
+
entries.push({
|
|
186
|
+
type: "user",
|
|
187
|
+
...base,
|
|
188
|
+
message: { role: "user", content: extractText(m.content) },
|
|
189
|
+
} satisfies UserEntry);
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (role === "assistant") {
|
|
194
|
+
const blocks: ContentBlock[] = [];
|
|
195
|
+
const text = extractText(m.content);
|
|
196
|
+
if (text) blocks.push({ type: "text", text });
|
|
197
|
+
for (const tc of normalizeDevinToolCalls(m.tool_calls)) {
|
|
198
|
+
const block: ToolUseBlock = { type: "tool_use", id: tc.id, name: tc.name, input: tc.input };
|
|
199
|
+
blocks.push(block);
|
|
200
|
+
toolUseById.set(tc.id, block);
|
|
201
|
+
toolUseStartMs.set(tc.id, date.getTime());
|
|
202
|
+
}
|
|
203
|
+
if (blocks.length === 0) continue; // empty assistant turn
|
|
204
|
+
entries.push({
|
|
205
|
+
type: "assistant",
|
|
206
|
+
...base,
|
|
207
|
+
message: {
|
|
208
|
+
role: "assistant",
|
|
209
|
+
content: blocks,
|
|
210
|
+
model: typeof m.model === "string" ? m.model : undefined,
|
|
211
|
+
},
|
|
212
|
+
} satisfies AssistantEntry);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (role === "tool") {
|
|
217
|
+
const callId = typeof m.tool_call_id === "string" ? m.tool_call_id : undefined;
|
|
218
|
+
const block = callId ? toolUseById.get(callId) : undefined;
|
|
219
|
+
if (block) {
|
|
220
|
+
const startMs = (callId && toolUseStartMs.get(callId)) || date.getTime();
|
|
221
|
+
const durationMs = Math.max(0, date.getTime() - startMs);
|
|
222
|
+
block.result = {
|
|
223
|
+
timestamp,
|
|
224
|
+
timestampFormatted: formatTimestamp(date),
|
|
225
|
+
content: extractText(m.content),
|
|
226
|
+
durationMs,
|
|
227
|
+
durationFormatted: formatDuration(durationMs),
|
|
228
|
+
};
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
// Orphan tool result — fall through to a system entry.
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
entries.push({ type: "system", ...base, raw } satisfies GenericEntry);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
entries.sort((a, b) => a.timestampMs - b.timestampMs);
|
|
238
|
+
return entries;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// ── DB loader ──
|
|
242
|
+
|
|
243
|
+
export interface DevinSessionLogData {
|
|
244
|
+
entries: LogEntry[];
|
|
245
|
+
rawLines: Record<string, unknown>[];
|
|
246
|
+
cwd?: string;
|
|
247
|
+
filePath: string; // synthetic — devin keeps sessions in a DB; we use devin-db://<id>
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
interface DevinMessageNodeRow {
|
|
251
|
+
node_id: number | null;
|
|
252
|
+
parent_node_id: number | null;
|
|
253
|
+
chat_message: string | null;
|
|
254
|
+
created_at: number | null;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Devin stores a session's messages as a FOREST, not a flat list: each turn is a
|
|
259
|
+
* node (`node_id`) linked to its `parent_node_id`, and Devin replays earlier
|
|
260
|
+
* context under fresh roots on later turns — so the table holds many branches
|
|
261
|
+
* that repeat the same messages (verified live: 29 nodes / 11 distinct messages
|
|
262
|
+
* for a 10-message conversation). Reading every node duplicates each message
|
|
263
|
+
* 2-4×. The real conversation is the single path from the NEWEST leaf back to its
|
|
264
|
+
* root. Because Devin appends nodes in order (a child's `node_id` always exceeds
|
|
265
|
+
* its parent's), the newest leaf is simply the max `node_id`; walk its
|
|
266
|
+
* `parent_node_id` chain to the root and reverse to get root→leaf order.
|
|
267
|
+
*/
|
|
268
|
+
export function devinActiveConversationPath(rows: DevinMessageNodeRow[]): DevinMessageNodeRow[] {
|
|
269
|
+
if (rows.length === 0) return rows;
|
|
270
|
+
const byId = new Map<number, DevinMessageNodeRow>();
|
|
271
|
+
for (const r of rows) if (typeof r.node_id === "number") byId.set(r.node_id, r);
|
|
272
|
+
// Newest leaf = highest node_id (a leaf, since children always have a higher id).
|
|
273
|
+
let cur: DevinMessageNodeRow | undefined = rows.reduce((a, b) =>
|
|
274
|
+
(a.node_id ?? -1) > (b.node_id ?? -1) ? a : b,
|
|
275
|
+
);
|
|
276
|
+
const path: DevinMessageNodeRow[] = [];
|
|
277
|
+
const seen = new Set<number>();
|
|
278
|
+
while (cur && typeof cur.node_id === "number" && !seen.has(cur.node_id)) {
|
|
279
|
+
seen.add(cur.node_id);
|
|
280
|
+
path.push(cur);
|
|
281
|
+
cur = cur.parent_node_id != null ? byId.get(cur.parent_node_id) : undefined;
|
|
282
|
+
}
|
|
283
|
+
return path.reverse(); // root → leaf = conversation order
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Parse a session's `message_nodes` rows into the message objects the pure
|
|
287
|
+
* parser consumes, injecting each row's `created_at` as `_created_at`. */
|
|
288
|
+
function parseNodeRows(rows: DevinMessageNodeRow[]): Record<string, unknown>[] {
|
|
289
|
+
const out: Record<string, unknown>[] = [];
|
|
290
|
+
for (const r of rows) {
|
|
291
|
+
const parsed = safeJsonParse(r.chat_message);
|
|
292
|
+
if (!isPlainObject(parsed)) continue;
|
|
293
|
+
if (r.created_at != null && parsed._created_at == null) parsed._created_at = r.created_at;
|
|
294
|
+
out.push(parsed);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Load one session by ID from `sessions.db`. Returns `null` when the DB is
|
|
301
|
+
* unavailable or the session doesn't exist.
|
|
302
|
+
*/
|
|
303
|
+
export async function getDevinSessionLog(
|
|
304
|
+
sessionId: string,
|
|
305
|
+
): Promise<DevinSessionLogData | null> {
|
|
306
|
+
if (!sessionId || !DEVIN_SESSION_ID_RE.test(sessionId)) return null;
|
|
307
|
+
const db = await openSqliteReadonly(devinDbPath());
|
|
308
|
+
if (!db) return null;
|
|
309
|
+
try {
|
|
310
|
+
const sessionRows = db.query<{ working_directory: string | null }>(
|
|
311
|
+
"SELECT working_directory FROM sessions WHERE id = ?",
|
|
312
|
+
[sessionId],
|
|
313
|
+
);
|
|
314
|
+
if (sessionRows.length === 0) return null;
|
|
315
|
+
const realCwd = sessionRows[0].working_directory;
|
|
316
|
+
|
|
317
|
+
const nodeRows = db.query<DevinMessageNodeRow>(
|
|
318
|
+
"SELECT node_id, parent_node_id, chat_message, created_at FROM message_nodes " +
|
|
319
|
+
"WHERE session_id = ? ORDER BY node_id ASC",
|
|
320
|
+
[sessionId],
|
|
321
|
+
);
|
|
322
|
+
// Reconstruct the active conversation path (drop replayed branch duplicates).
|
|
323
|
+
const rawLines = parseNodeRows(devinActiveConversationPath(nodeRows));
|
|
324
|
+
const entries = devinRowsToLogEntries(rawLines, "session");
|
|
325
|
+
const cwd = realCwd && realCwd.length > 0 ? realCwd : undefined;
|
|
326
|
+
return {
|
|
327
|
+
entries,
|
|
328
|
+
rawLines,
|
|
329
|
+
cwd,
|
|
330
|
+
filePath: `devin-db://${sessionId}`,
|
|
331
|
+
};
|
|
332
|
+
} catch {
|
|
333
|
+
return null;
|
|
334
|
+
} finally {
|
|
335
|
+
db.close();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export const getCachedDevinSessionLog = runtimeCache(
|
|
340
|
+
(sessionId: string) => getDevinSessionLog(sessionId),
|
|
341
|
+
2,
|
|
342
|
+
{ maxSize: 50 },
|
|
343
|
+
);
|
|
@@ -20,6 +20,18 @@ export const OPENCODE_SESSION_RE = /^ses_[A-Za-z0-9]+$/;
|
|
|
20
20
|
* its download with `RangeError("Invalid session ID")`. */
|
|
21
21
|
export const HERMES_SESSION_RE = /^[A-Za-z0-9_-]+$/;
|
|
22
22
|
|
|
23
|
+
/** OpenClaw sessions are UUID-named files. Kept in sync with
|
|
24
|
+
* OPENCLAW_SESSION_ID_RE in lib/openclaw-sessions.ts. */
|
|
25
|
+
export const OPENCLAW_SESSION_RE = /^[0-9a-fA-F-]{36}$/;
|
|
26
|
+
|
|
27
|
+
/** Devin session IDs are word-slug style (e.g. `estimated-seeker`). Kept in
|
|
28
|
+
* sync with DEVIN_SESSION_ID_RE in lib/devin-sessions.ts. */
|
|
29
|
+
export const DEVIN_SESSION_RE = /^[A-Za-z0-9_-]+$/;
|
|
30
|
+
|
|
31
|
+
/** Goose session IDs are date-prefixed counters (e.g. `20260714_3`). Kept in
|
|
32
|
+
* sync with GOOSE_SESSION_ID_RE in lib/goose-sessions.ts. */
|
|
33
|
+
export const GOOSE_SESSION_RE = /^\d{8}_\d+$/;
|
|
34
|
+
|
|
23
35
|
export type DownloadSource =
|
|
24
36
|
| { kind: "file"; path: string }
|
|
25
37
|
| { kind: "synthesized"; body: string; contentType: string; extension: string };
|
|
@@ -29,6 +41,9 @@ export type DownloadSource =
|
|
|
29
41
|
export function isValidSessionId(cli: CliId, sessionId: string): boolean {
|
|
30
42
|
if (cli === "opencode") return OPENCODE_SESSION_RE.test(sessionId);
|
|
31
43
|
if (cli === "hermes") return HERMES_SESSION_RE.test(sessionId);
|
|
44
|
+
if (cli === "openclaw") return OPENCLAW_SESSION_RE.test(sessionId);
|
|
45
|
+
if (cli === "devin") return DEVIN_SESSION_RE.test(sessionId);
|
|
46
|
+
if (cli === "goose") return GOOSE_SESSION_RE.test(sessionId);
|
|
32
47
|
return UUID_RE.test(sessionId);
|
|
33
48
|
}
|
|
34
49
|
|
|
@@ -74,11 +89,6 @@ export async function resolveDownloadSource(
|
|
|
74
89
|
const path = findPiTranscript(sessionId);
|
|
75
90
|
return path ? { kind: "file", path } : null;
|
|
76
91
|
}
|
|
77
|
-
if (cli === "gemini") {
|
|
78
|
-
const { findGeminiTranscript } = await import("./gemini-sessions");
|
|
79
|
-
const path = findGeminiTranscript(sessionId);
|
|
80
|
-
return path ? { kind: "file", path } : null;
|
|
81
|
-
}
|
|
82
92
|
if (cli === "opencode") {
|
|
83
93
|
// OpenCode keeps sessions in SQLite (~/.local/share/opencode/opencode.db)
|
|
84
94
|
// across three tables: session / message / part. Export all three so
|
|
@@ -101,6 +111,47 @@ export async function resolveDownloadSource(
|
|
|
101
111
|
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
102
112
|
}
|
|
103
113
|
|
|
114
|
+
if (cli === "openclaw") {
|
|
115
|
+
// OpenClaw writes real JSONL transcripts on disk — stream the file verbatim.
|
|
116
|
+
const { findOpenClawTranscript } = await import("./openclaw-sessions");
|
|
117
|
+
const path = findOpenClawTranscript(sessionId);
|
|
118
|
+
return path ? { kind: "file", path } : null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (cli === "factory") {
|
|
122
|
+
// Factory (droid) writes real JSONL transcripts on disk — stream verbatim.
|
|
123
|
+
const { findFactoryTranscript } = await import("./factory-sessions");
|
|
124
|
+
const path = findFactoryTranscript(sessionId);
|
|
125
|
+
return path ? { kind: "file", path } : null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (cli === "devin") {
|
|
129
|
+
// Devin keeps sessions in SQLite (~/.local/share/devin/cli/sessions.db).
|
|
130
|
+
// Synthesize a JSONL export of the session's raw chat_message rows.
|
|
131
|
+
const { getDevinSessionLog } = await import("./devin-sessions");
|
|
132
|
+
const result = await getDevinSessionLog(sessionId);
|
|
133
|
+
if (!result) return null;
|
|
134
|
+
const body = result.rawLines.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
135
|
+
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (cli === "antigravity") {
|
|
139
|
+
// Antigravity (agy) writes real JSONL transcripts on disk — stream verbatim.
|
|
140
|
+
const { findAntigravityTranscript } = await import("./antigravity-sessions");
|
|
141
|
+
const path = findAntigravityTranscript(sessionId);
|
|
142
|
+
return path ? { kind: "file", path } : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (cli === "goose") {
|
|
146
|
+
// Goose keeps sessions in SQLite (~/.local/share/goose/sessions/sessions.db).
|
|
147
|
+
// Synthesize a JSONL export of the session's raw `messages` rows.
|
|
148
|
+
const { getGooseSessionLog } = await import("./goose-sessions");
|
|
149
|
+
const result = await getGooseSessionLog(sessionId);
|
|
150
|
+
if (!result) return null;
|
|
151
|
+
const body = result.rawLines.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
152
|
+
return { kind: "synthesized", body, contentType: "application/x-ndjson", extension: "jsonl" };
|
|
153
|
+
}
|
|
154
|
+
|
|
104
155
|
// Exhaustive — but TypeScript can't always see CliId is exhausted across the
|
|
105
156
|
// if-chain above, so guard with a runtime fallback.
|
|
106
157
|
const _exhaustive: never = cli;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory (droid) project discovery.
|
|
3
|
+
*
|
|
4
|
+
* droid stores transcripts at `~/.factory/sessions/<encoded-cwd>/<uuid>.jsonl`,
|
|
5
|
+
* using the same Claude-style encoded-cwd folder names (`-home-user-project`)
|
|
6
|
+
* as Claude Code. The encoded folder doubles as the URL slug for
|
|
7
|
+
* `/project/[name]`, so a cwd present in both stores naturally produces the same
|
|
8
|
+
* `name` and merges on the Claude side (see `mergeProjectFolders` in
|
|
9
|
+
* lib/projects.ts).
|
|
10
|
+
*/
|
|
11
|
+
import { decodeFolderName } from "./paths";
|
|
12
|
+
import { listFactoryTranscripts, getFactorySessionLog } from "./factory-sessions";
|
|
13
|
+
import type { ProjectFolder, SessionFile } from "./projects";
|
|
14
|
+
import { runtimeCache } from "./runtime-cache";
|
|
15
|
+
import { formatDate } from "./format-date";
|
|
16
|
+
import { logWarn } from "./logger";
|
|
17
|
+
|
|
18
|
+
/** Returns one ProjectFolder per encoded-cwd folder discovered under
|
|
19
|
+
* ~/.factory/sessions/. */
|
|
20
|
+
export async function getFactoryProjects(): Promise<ProjectFolder[]> {
|
|
21
|
+
let transcripts;
|
|
22
|
+
try {
|
|
23
|
+
transcripts = listFactoryTranscripts();
|
|
24
|
+
} catch (error) {
|
|
25
|
+
logWarn("Failed to scan Factory sessions:", error);
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const byName = new Map<string, { latest: number; cwd: string; name: string }>();
|
|
30
|
+
for (const t of transcripts) {
|
|
31
|
+
const existing = byName.get(t.projectName);
|
|
32
|
+
if (!existing || t.mtimeMs > existing.latest) {
|
|
33
|
+
byName.set(t.projectName, { latest: t.mtimeMs, cwd: t.cwd, name: t.projectName });
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const folders: ProjectFolder[] = [];
|
|
38
|
+
for (const { name, cwd, latest } of byName.values()) {
|
|
39
|
+
const lastModified = new Date(latest);
|
|
40
|
+
folders.push({
|
|
41
|
+
name,
|
|
42
|
+
path: cwd,
|
|
43
|
+
isDirectory: true,
|
|
44
|
+
lastModified,
|
|
45
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
46
|
+
cli: ["factory"],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
folders.sort((a, b) => b.lastModified.getTime() - a.lastModified.getTime());
|
|
50
|
+
return folders;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface FactoryProjectByName {
|
|
54
|
+
/** Canonical cwd recovered from a session's `session_start` line (the folder
|
|
55
|
+
* decode is lossy). Null when no session could be read. */
|
|
56
|
+
cwd: string | null;
|
|
57
|
+
sessions: SessionFile[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Look up Factory sessions for a project URL slug (the encoded-cwd folder name).
|
|
62
|
+
* Because droid names its folders with the same encoding Claude uses, the slug
|
|
63
|
+
* matches the folder directly. The canonical cwd is recovered from the newest
|
|
64
|
+
* session's `session_start` record (folder decode is lossy for cwds with `-`).
|
|
65
|
+
*/
|
|
66
|
+
export async function getFactorySessionsByEncodedName(
|
|
67
|
+
name: string,
|
|
68
|
+
): Promise<FactoryProjectByName> {
|
|
69
|
+
let transcripts;
|
|
70
|
+
try {
|
|
71
|
+
transcripts = listFactoryTranscripts().filter((t) => t.projectName === name);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logWarn("Failed to scan Factory sessions:", error);
|
|
74
|
+
return { cwd: null, sessions: [] };
|
|
75
|
+
}
|
|
76
|
+
if (transcripts.length === 0) return { cwd: null, sessions: [] };
|
|
77
|
+
|
|
78
|
+
const sorted = [...transcripts].sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
79
|
+
|
|
80
|
+
// Recover the canonical cwd from the newest transcript's session_start line.
|
|
81
|
+
let cwd: string | null = null;
|
|
82
|
+
try {
|
|
83
|
+
const log = await getFactorySessionLog(sorted[0].sessionId);
|
|
84
|
+
cwd = log?.cwd ?? null;
|
|
85
|
+
} catch {
|
|
86
|
+
// best-effort — fall back to the lossy decode below
|
|
87
|
+
}
|
|
88
|
+
if (!cwd) cwd = decodeFolderName(name);
|
|
89
|
+
|
|
90
|
+
const sessions: SessionFile[] = sorted.map((t) => {
|
|
91
|
+
const lastModified = new Date(t.mtimeMs);
|
|
92
|
+
return {
|
|
93
|
+
name: t.sessionId,
|
|
94
|
+
path: t.transcriptPath,
|
|
95
|
+
lastModified,
|
|
96
|
+
lastModifiedFormatted: formatDate(lastModified),
|
|
97
|
+
sessionId: t.sessionId,
|
|
98
|
+
cli: "factory" as const,
|
|
99
|
+
};
|
|
100
|
+
});
|
|
101
|
+
return { cwd, sessions };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const getCachedFactoryProjects = runtimeCache(getFactoryProjects, 30);
|
|
105
|
+
export const getCachedFactorySessionsByEncodedName = runtimeCache(
|
|
106
|
+
(name: string) => getFactorySessionsByEncodedName(name),
|
|
107
|
+
30,
|
|
108
|
+
{ maxSize: 50 },
|
|
109
|
+
);
|