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.
Files changed (310) hide show
  1. package/.next/standalone/.codex/hooks.json +6 -6
  2. package/.next/standalone/.cursor/hooks.json +6 -6
  3. package/.next/standalone/.factory/hooks.json +112 -0
  4. package/.next/standalone/.next/BUILD_ID +1 -1
  5. package/.next/standalone/.next/build-manifest.json +5 -5
  6. package/.next/standalone/.next/prerender-manifest.json +3 -3
  7. package/.next/standalone/.next/required-server-files.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  9. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  10. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  11. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  14. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  15. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  16. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
  17. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  19. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  20. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  21. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  25. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  27. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  28. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  30. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  31. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/audit/run/route.js +3 -3
  33. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  37. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  39. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +2 -1
  40. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  43. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/index.html +1 -1
  46. package/.next/standalone/.next/server/app/index.rsc +15 -15
  47. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  48. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  49. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  50. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  51. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  52. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +8 -8
  58. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  59. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/server/app/project/[name]/page.js +3 -2
  64. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  67. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  68. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  69. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +5 -3
  70. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  73. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/projects/page.js +1 -1
  75. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0--lkk6._.js +3 -0
  78. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0-219ec._.js +3 -0
  79. package/.next/standalone/.next/server/chunks/[root-of-the-server]__04_h-a5._.js +3 -0
  80. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06cuf1y._.js +3 -0
  81. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08w4wmd._.js +3 -0
  82. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g6bbuw._.js +3 -0
  83. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jllheg._.js +3 -0
  84. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0jnjf2t._.js +3 -0
  85. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q0qzx1._.js +3 -0
  87. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q2nbsl._.js +3 -0
  88. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0rv7m0k._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/[root-of-the-server]__16le-kd._.js +3 -0
  90. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d9qen3._.js +3 -0
  92. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ffkmds._.js +3 -0
  93. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1legmza._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1mrihkj._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qb590j._.js +3 -0
  98. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1qxztj-._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1shcqgr._.js +3 -0
  100. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1xuedea._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{_1z062h5._.js → _1vslkoe._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/lib_factory-projects_ts_1eo_bk-._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/lib_openclaw-projects_ts_0gc-p24._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +24 -12
  105. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__00cz9as._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__015_i4t._.js +2 -2
  110. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__030k0c6._.js +3 -0
  111. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__03lv-pe._.js +2 -2
  112. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__042d3cd._.js +3 -0
  113. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__05r_17v._.js +3 -0
  114. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__09q1168._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__108slqn._.js → [root-of-the-server]__0brjczq._.js} +2 -2
  116. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e2l3c1._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hci7t3._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0hdsupo._.js +2 -2
  119. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p84xee._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qeuy9c._.js +2 -2
  121. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s89hoe._.js +2 -2
  122. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__07iom4f._.js → [root-of-the-server]__0wp9w6i._.js} +2 -2
  123. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ynf7tx._.js +2 -2
  124. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__170799-._.js +12 -9
  125. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17ai7sy._.js +4 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d8omgc._.js +1 -1
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1nq7ivq._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tkjqka._.js +2 -2
  129. package/.next/standalone/.next/server/chunks/ssr/_05whahf._.js +1 -1
  130. package/.next/standalone/.next/server/chunks/ssr/_0_9_ytf._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  132. package/.next/standalone/.next/server/chunks/ssr/{_0ul5rf_._.js → _1_ozgif._.js} +2 -2
  133. package/.next/standalone/.next/server/chunks/ssr/_1p0-leb._.js +1 -1
  134. package/.next/standalone/.next/server/chunks/ssr/app_087bt9w._.js +1 -1
  135. package/.next/standalone/.next/server/chunks/ssr/app_1fvisnp._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/app_209u41o._.js +1 -1
  137. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  138. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  139. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  140. package/.next/standalone/.next/server/chunks/ssr/lib_0xkhw_v._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/lib_antigravity-projects_ts_0_6zf11._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/lib_devin-projects_ts_0a64qdy._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/lib_factory-projects_ts_1uyaahq._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/lib_goose-projects_ts_0zkscri._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/lib_openclaw-projects_ts_0qxq3bp._.js +3 -0
  146. 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
  147. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  148. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +1 -1
  149. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +1 -1
  150. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  151. package/.next/standalone/.next/server/pages/404.html +1 -1
  152. package/.next/standalone/.next/server/pages/500.html +1 -1
  153. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  154. package/.next/standalone/.next/server/server-reference-manifest.json +10 -10
  155. package/.next/standalone/.next/static/chunks/{13f1kmjea-0md.js → 02jcotghcxrbu.js} +1 -1
  156. package/.next/standalone/.next/static/chunks/{30ysv-_8jj052.js → 0d5lypu1zs21r.js} +1 -1
  157. package/.next/standalone/.next/static/chunks/0emg30_6svycu.js +1 -0
  158. package/.next/standalone/.next/static/chunks/0o9wpnze1ey8y.js +6 -0
  159. package/.next/standalone/.next/static/chunks/{05apzuand0c22.js → 1359o5785rmve.js} +2 -2
  160. package/.next/standalone/.next/static/chunks/20tuctw38jbgw.js +1 -0
  161. package/.next/standalone/.next/static/chunks/{0vc0eak148vo6.js → 2ldhnaweu9ew3.js} +1 -1
  162. package/.next/standalone/.next/static/chunks/{23c7v2svx90dp.js → 2vav_x0_336ds.js} +1 -1
  163. package/.next/standalone/.next/static/chunks/3-ogr79xqbw0h.js +1 -0
  164. package/.next/standalone/.next/static/chunks/36j05kb2x323i.css +2 -0
  165. package/.next/standalone/.next/static/chunks/3cg7x0joi2b98.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{36wdtkgdf0zrd.js → 3k87p-tijud-4.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{2cejn75gug3j8.js → 3kmaqcp7rldad.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{turbopack-0vavqo2d-tlt3.js → turbopack-10hj8t5pf33tu.js} +1 -1
  169. package/.next/standalone/.opencode/plugins/failproofai.mjs +30 -11
  170. package/.next/standalone/app/api/audit/run/route.ts +2 -0
  171. package/.next/standalone/app/audit/_components/empty-state.tsx +1 -1
  172. package/.next/standalone/app/components/global-error-listeners.tsx +27 -0
  173. package/.next/standalone/app/globals.css +25 -1
  174. package/.next/standalone/app/policies/hooks-client.tsx +2 -4
  175. package/.next/standalone/app/project/[name]/page.tsx +30 -10
  176. package/.next/standalone/app/project/[name]/session/[sessionId]/page.tsx +71 -21
  177. package/.next/standalone/assets/logos/antigravity.svg +1 -0
  178. package/.next/standalone/assets/logos/devin.svg +1 -0
  179. package/.next/standalone/assets/logos/factory-dark.png +0 -0
  180. package/.next/standalone/assets/logos/factory-light.png +0 -0
  181. package/.next/standalone/assets/logos/goose-dark.svg +1 -0
  182. package/.next/standalone/assets/logos/goose-light.svg +1 -0
  183. package/.next/standalone/assets/logos/openclaw.svg +22 -0
  184. package/.next/standalone/ci/cli-integration/Dockerfile +47 -0
  185. package/.next/standalone/ci/cli-integration/README.md +69 -0
  186. package/.next/standalone/ci/cli-integration/canary-policies.mjs +45 -0
  187. package/.next/standalone/ci/cli-integration/capture-tokens.sh +48 -0
  188. package/.next/standalone/ci/cli-integration/inject-tokens.sh +44 -0
  189. package/.next/standalone/ci/cli-integration/install-clis.sh +91 -0
  190. package/.next/standalone/ci/cli-integration/probe-cli.sh +208 -0
  191. package/.next/standalone/ci/cli-integration/report.js +74 -0
  192. package/.next/standalone/ci/cli-integration/run.sh +107 -0
  193. package/.next/standalone/components/navbar.tsx +5 -11
  194. package/.next/standalone/components/reach-developers.tsx +2 -2
  195. package/.next/standalone/docker-hook-sync/Dockerfile +8 -4
  196. package/.next/standalone/lib/antigravity-projects.ts +224 -0
  197. package/.next/standalone/lib/antigravity-sessions.ts +283 -0
  198. package/.next/standalone/lib/cli-registry.ts +26 -6
  199. package/.next/standalone/lib/devin-projects.ts +141 -0
  200. package/.next/standalone/lib/devin-sessions.ts +343 -0
  201. package/.next/standalone/lib/download-session.ts +56 -5
  202. package/.next/standalone/lib/error-origin.ts +67 -0
  203. package/.next/standalone/lib/factory-projects.ts +109 -0
  204. package/.next/standalone/lib/factory-sessions.ts +312 -0
  205. package/.next/standalone/lib/goose-projects.ts +148 -0
  206. package/.next/standalone/lib/goose-sessions.ts +296 -0
  207. package/.next/standalone/lib/install-check.ts +231 -0
  208. package/.next/standalone/lib/openclaw-projects.ts +182 -0
  209. package/.next/standalone/lib/openclaw-sessions.ts +284 -0
  210. package/.next/standalone/lib/projects.ts +33 -9
  211. package/.next/standalone/lib/telemetry-sanitize.ts +43 -0
  212. package/.next/standalone/openclaw-plugin/index.js +223 -0
  213. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +6 -0
  214. package/.next/standalone/openclaw-plugin/package.json +12 -0
  215. package/.next/standalone/package.json +2 -3
  216. package/.next/standalone/server.js +1 -1
  217. package/README.md +94 -62
  218. package/bin/failproofai.mjs +98 -30
  219. package/dist/cli.mjs +12499 -9243
  220. package/lib/antigravity-projects.ts +224 -0
  221. package/lib/antigravity-sessions.ts +283 -0
  222. package/lib/cli-registry.ts +26 -6
  223. package/lib/devin-projects.ts +141 -0
  224. package/lib/devin-sessions.ts +343 -0
  225. package/lib/download-session.ts +56 -5
  226. package/lib/error-origin.ts +67 -0
  227. package/lib/factory-projects.ts +109 -0
  228. package/lib/factory-sessions.ts +312 -0
  229. package/lib/goose-projects.ts +148 -0
  230. package/lib/goose-sessions.ts +296 -0
  231. package/lib/install-check.ts +231 -0
  232. package/lib/openclaw-projects.ts +182 -0
  233. package/lib/openclaw-sessions.ts +284 -0
  234. package/lib/projects.ts +33 -9
  235. package/lib/telemetry-sanitize.ts +43 -0
  236. package/openclaw-plugin/index.js +223 -0
  237. package/openclaw-plugin/openclaw.plugin.json +6 -0
  238. package/openclaw-plugin/package.json +12 -0
  239. package/package.json +2 -3
  240. package/scripts/dev-hook.mjs +292 -0
  241. package/scripts/install-diagnosis.mjs +5 -8
  242. package/scripts/install-telemetry.mjs +12 -4
  243. package/scripts/launch.ts +4 -8
  244. package/scripts/sync-agent-cli-harnesses-prompt.md +7 -10
  245. package/scripts/translate-docs/cli.ts +120 -53
  246. package/scripts/translate-docs/config.ts +57 -13
  247. package/scripts/translate-docs/mdx-translator.ts +177 -28
  248. package/scripts/translate-docs/mintlify-nav.ts +91 -10
  249. package/scripts/translate-docs/readme-translator.ts +13 -5
  250. package/scripts/translate-docs/translator.ts +38 -2
  251. package/scripts/translate-docs/types.ts +3 -0
  252. package/scripts/validate-mdx.ts +12 -4
  253. package/src/audit/cli-adapters/antigravity.ts +87 -0
  254. package/src/audit/cli-adapters/devin.ts +53 -0
  255. package/src/audit/cli-adapters/factory.ts +75 -0
  256. package/src/audit/cli-adapters/goose.ts +53 -0
  257. package/src/audit/cli-adapters/index.ts +30 -6
  258. package/src/audit/cli-adapters/openclaw.ts +56 -0
  259. package/src/audit/cli.ts +83 -8
  260. package/src/audit/report.ts +1 -1
  261. package/src/audit/types.ts +1 -1
  262. package/src/hooks/builtin-policies.ts +5 -5
  263. package/src/hooks/configure-wizard.ts +356 -0
  264. package/src/hooks/handler.ts +283 -218
  265. package/src/hooks/hook-telemetry.ts +43 -3
  266. package/src/hooks/install-prompt.ts +92 -173
  267. package/src/hooks/integrations.ts +739 -138
  268. package/src/hooks/manager.ts +46 -2
  269. package/src/hooks/policy-evaluator.ts +301 -121
  270. package/src/hooks/policy-presets.ts +71 -0
  271. package/src/hooks/resolve-cwd.ts +1 -1
  272. package/src/hooks/resolve-permission-mode.ts +1 -5
  273. package/src/hooks/resolve-transcript-path.ts +25 -7
  274. package/src/hooks/tool-name-canonicalize.ts +36 -3
  275. package/src/hooks/tui.ts +578 -0
  276. package/src/hooks/types.ts +447 -109
  277. package/.next/standalone/.gemini/settings.json +0 -147
  278. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cag8qd._.js +0 -3
  279. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cefjd9._.js +0 -3
  280. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ekj92q._.js +0 -3
  281. package/.next/standalone/.next/server/chunks/[root-of-the-server]__12v-lma._.js +0 -3
  282. package/.next/standalone/.next/server/chunks/[root-of-the-server]__136y39g._.js +0 -3
  283. package/.next/standalone/.next/server/chunks/[root-of-the-server]__13h8pzr._.js +0 -3
  284. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1hgv_75._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04bqvos._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0dqgs96._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0q5rfxg._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qukut7._.js +0 -4
  289. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ql-mch._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/ssr/_11v0g3z._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/ssr/lib_gemini-projects_ts_1c7bgx-._.js +0 -3
  292. package/.next/standalone/.next/static/chunks/0fa7j157kg9_d.js +0 -1
  293. package/.next/standalone/.next/static/chunks/0nrah-qv6smeg.js +0 -6
  294. package/.next/standalone/.next/static/chunks/0wtagd8a5gwq-.js +0 -1
  295. package/.next/standalone/.next/static/chunks/1c8i78p7t1i-5.js +0 -1
  296. package/.next/standalone/.next/static/chunks/25qr0e6ez0ei0.js +0 -1
  297. package/.next/standalone/.next/static/chunks/25s52un3k_wht.css +0 -2
  298. package/.next/standalone/assets/logos/gemini-dark.svg +0 -13
  299. package/.next/standalone/assets/logos/gemini-light.svg +0 -13
  300. package/.next/standalone/lib/gemini-projects.ts +0 -243
  301. package/.next/standalone/lib/gemini-sessions.ts +0 -365
  302. package/lib/gemini-projects.ts +0 -243
  303. package/lib/gemini-sessions.ts +0 -365
  304. package/scripts/postinstall.mjs +0 -243
  305. package/scripts/preuninstall.mjs +0 -131
  306. package/src/audit/cli-adapters/gemini.ts +0 -51
  307. package/src/hooks/first-run-nudge.ts +0 -146
  308. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_buildManifest.js +0 -0
  309. /package/.next/standalone/.next/static/{kmzdjTLVs6LqsfyOaUf69 → LkN-JjmhKGmL-WklH7WVi}/_clientMiddlewareManifest.js +0 -0
  310. /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
+ );
@@ -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
+ );