failproofai 0.0.13 → 0.0.14-beta.1
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/.codex/hooks.json +6 -6
- package/.next/standalone/.cursor/hooks.json +6 -6
- package/.next/standalone/.factory/hooks.json +112 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
- package/.next/standalone/.next/server/app/_not-found.segments/_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/build-manifest.json +2 -2
- 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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js +3 -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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -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/build-manifest.json +2 -2
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/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]__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/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
- 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]__09q1168._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__108slqn._.js → [root-of-the-server]__0brjczq._.js} +2 -2
- 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]__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]__07iom4f._.js → [root-of-the-server]__0wp9w6i._.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]__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]__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/_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_087bt9w._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.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_12rjfea.js} +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.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 +5 -5
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
- package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 0d5lypu1zs21r.js} +1 -1
- package/.next/standalone/.next/static/chunks/0emg30_6svycu.js +1 -0
- package/.next/standalone/.next/static/chunks/0o9wpnze1ey8y.js +6 -0
- package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 1359o5785rmve.js} +2 -2
- package/.next/standalone/.next/static/chunks/20tuctw38jbgw.js +1 -0
- package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 2ldhnaweu9ew3.js} +1 -1
- package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 2vav_x0_336ds.js} +1 -1
- package/.next/standalone/.next/static/chunks/3-ogr79xqbw0h.js +1 -0
- package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
- package/.next/standalone/.next/static/chunks/3cg7x0joi2b98.js +1 -0
- package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 3k87p-tijud-4.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 3kmaqcp7rldad.js} +1 -1
- package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
- package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
- 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/components/global-error-listeners.tsx +27 -0
- 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/ci/cli-integration/Dockerfile +47 -0
- package/.next/standalone/ci/cli-integration/README.md +69 -0
- package/.next/standalone/ci/cli-integration/canary-policies.mjs +45 -0
- package/.next/standalone/ci/cli-integration/capture-tokens.sh +48 -0
- package/.next/standalone/ci/cli-integration/inject-tokens.sh +44 -0
- package/.next/standalone/ci/cli-integration/install-clis.sh +91 -0
- package/.next/standalone/ci/cli-integration/probe-cli.sh +208 -0
- package/.next/standalone/ci/cli-integration/report.js +74 -0
- package/.next/standalone/ci/cli-integration/run.sh +107 -0
- package/.next/standalone/components/navbar.tsx +5 -11
- package/.next/standalone/components/reach-developers.tsx +2 -2
- package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
- 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/error-origin.ts +67 -0
- 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/install-check.ts +231 -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 -3
- package/.next/standalone/server.js +1 -1
- package/README.md +94 -62
- package/bin/failproofai.mjs +98 -30
- package/dist/cli.mjs +12499 -9243
- 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/error-origin.ts +67 -0
- 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/install-check.ts +231 -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 -3
- package/scripts/dev-hook.mjs +292 -0
- package/scripts/install-diagnosis.mjs +5 -8
- package/scripts/install-telemetry.mjs +12 -4
- package/scripts/launch.ts +4 -8
- package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
- package/scripts/translate-docs/cli.ts +120 -53
- package/scripts/translate-docs/config.ts +57 -13
- package/scripts/translate-docs/mdx-translator.ts +177 -28
- package/scripts/translate-docs/mintlify-nav.ts +91 -10
- package/scripts/translate-docs/readme-translator.ts +13 -5
- package/scripts/translate-docs/translator.ts +38 -2
- package/scripts/translate-docs/types.ts +3 -0
- package/scripts/validate-mdx.ts +12 -4
- 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 +83 -8
- 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]__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/0wtagd8a5gwq-.js +0 -1
- 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/scripts/postinstall.mjs +0 -243
- package/scripts/preuninstall.mjs +0 -131
- package/src/audit/cli-adapters/gemini.ts +0 -51
- package/src/hooks/first-run-nudge.ts +0 -146
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_clientMiddlewareManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_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
|
+
);
|
package/lib/download-session.ts
CHANGED
|
@@ -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,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Works out whether a browser error actually came from the dashboard.
|
|
3
|
+
*
|
|
4
|
+
* `window`'s "error" and "unhandledrejection" events are page-global: browser
|
|
5
|
+
* extensions inject content scripts into the same page and share the same
|
|
6
|
+
* window, so their failures reach our listeners and get reported as ours. The
|
|
7
|
+
* observed case was MetaMask's "Failed to connect to MetaMask" arriving as a
|
|
8
|
+
* failproofai `unhandled_rejection`. Extensions are user-installed and
|
|
9
|
+
* open-ended, so the noise is unbounded and unrelated to our code.
|
|
10
|
+
*
|
|
11
|
+
* We therefore attribute positively — report an error only when it can be
|
|
12
|
+
* traced to our own origin — rather than maintaining a denylist of extensions
|
|
13
|
+
* we happen to know about. React render errors don't depend on this: the error
|
|
14
|
+
* boundaries (`app/global-error.tsx`, `app/components/error-fallback.tsx`)
|
|
15
|
+
* report `client_error` directly, and React only invokes those for errors thrown
|
|
16
|
+
* inside our own tree.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Any `<vendor>-extension://` URL — `chrome-extension://`, `moz-extension://`,
|
|
21
|
+
* `safari-web-extension://`, `ms-browser-extension://`. Matching the shared
|
|
22
|
+
* suffix rather than an explicit vendor list means a new browser's scheme is
|
|
23
|
+
* filtered the day it ships.
|
|
24
|
+
*/
|
|
25
|
+
const EXTENSION_URL = /\b[\w-]+-extension:\/\//i;
|
|
26
|
+
|
|
27
|
+
export type ErrorOrigin =
|
|
28
|
+
/** Traceable to our own origin — a real dashboard failure. */
|
|
29
|
+
| "app"
|
|
30
|
+
/** Came from a browser extension sharing the page. */
|
|
31
|
+
| "extension"
|
|
32
|
+
/** Not attributable: no stack, or a cross-origin "Script error.". */
|
|
33
|
+
| "unknown";
|
|
34
|
+
|
|
35
|
+
export interface ErrorOriginInput {
|
|
36
|
+
/** `error.stack` / `reason.stack`, when the thrown value was an Error. */
|
|
37
|
+
stack?: string | null;
|
|
38
|
+
/** `ErrorEvent.filename` — the script URL. Absent on rejections. */
|
|
39
|
+
filename?: string | null;
|
|
40
|
+
/** Typically `window.location.origin`. */
|
|
41
|
+
appOrigin: string;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Classify where an error came from, using whatever location information the
|
|
46
|
+
* browser gave us.
|
|
47
|
+
*
|
|
48
|
+
* Extension frames are checked first: an error whose stack passes through an
|
|
49
|
+
* extension is that extension's problem even if one of our frames appears
|
|
50
|
+
* further down.
|
|
51
|
+
*/
|
|
52
|
+
export function classifyErrorOrigin({ stack, filename, appOrigin }: ErrorOriginInput): ErrorOrigin {
|
|
53
|
+
const text = [filename, stack].filter(Boolean).join("\n");
|
|
54
|
+
if (!text.trim()) return "unknown";
|
|
55
|
+
if (EXTENSION_URL.test(text)) return "extension";
|
|
56
|
+
if (appOrigin && text.includes(appOrigin)) return "app";
|
|
57
|
+
return "unknown";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether an error is ours to report. Only positively-attributed errors qualify:
|
|
62
|
+
* `unknown` covers cross-origin "Script error." and stack-less rejections, which
|
|
63
|
+
* are unactionable anyway — there's nothing in them to debug.
|
|
64
|
+
*/
|
|
65
|
+
export function isAppError(input: ErrorOriginInput): boolean {
|
|
66
|
+
return classifyErrorOrigin(input) === "app";
|
|
67
|
+
}
|
|
@@ -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
|
+
);
|