failproofai 1.0.0-beta.2 → 1.0.0-beta.21

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 (381) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
  3. package/.next/standalone/.next/build-manifest.json +6 -6
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/required-server-files.json +1 -1
  6. package/.next/standalone/.next/routes-manifest.json +6 -0
  7. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
  8. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  9. package/.next/standalone/.next/server/app/_global-error/page.js +3 -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 +3 -3
  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 +3 -1
  22. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  23. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  25. package/.next/standalone/.next/server/app/_not-found.rsc +15 -15
  26. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  27. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
  28. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +10 -10
  29. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  30. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  31. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  32. package/.next/standalone/.next/server/app/api/audit/invite/route.js +3 -2
  33. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -3
  35. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  36. package/.next/standalone/.next/server/app/api/audit/status/route.js +1 -1
  37. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  38. package/.next/standalone/.next/server/app/api/auth/login-request/route.js +3 -1
  39. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  40. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js +3 -2
  41. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  42. package/.next/standalone/.next/server/app/api/auth/logout/route.js +3 -2
  43. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/api/auth/reminder/route.js +3 -2
  45. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  46. package/.next/standalone/.next/server/app/api/auth/status/route.js +3 -2
  47. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js +1 -1
  49. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  50. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +3 -3
  51. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  52. package/.next/standalone/.next/server/app/audit/page.js +5 -3
  53. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/index.html +1 -1
  56. package/.next/standalone/.next/server/app/index.rsc +15 -15
  57. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  58. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  59. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  60. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +10 -10
  61. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  62. package/.next/standalone/.next/server/app/page/build-manifest.json +3 -3
  63. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  64. package/.next/standalone/.next/server/app/page.js +3 -1
  65. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  66. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  67. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +3 -3
  68. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +30 -18
  69. package/.next/standalone/.next/server/app/policies/page.js +5 -3
  70. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  71. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  72. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +3 -3
  73. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  74. package/.next/standalone/.next/server/app/project/[name]/page.js +4 -2
  75. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  76. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  77. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +3 -3
  78. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  79. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  80. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +6 -4
  81. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  82. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +3 -3
  84. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  85. package/.next/standalone/.next/server/app/projects/page.js +4 -2
  86. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  87. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  88. package/.next/standalone/.next/server/app/settings/page/app-paths-manifest.json +3 -0
  89. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +18 -0
  90. package/.next/standalone/.next/server/app/settings/page/next-font-manifest.json +6 -0
  91. package/.next/standalone/.next/server/app/settings/page/react-loadable-manifest.json +1 -0
  92. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +53 -0
  93. package/.next/standalone/.next/server/app/settings/page.js +18 -0
  94. package/.next/standalone/.next/server/app/settings/page.js.map +5 -0
  95. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -0
  96. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +3 -0
  97. package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
  98. package/.next/standalone/.next/server/chunks/[externals]__0h9_83k._.js +3 -0
  99. package/.next/standalone/.next/server/chunks/{[externals]__1nl3dvw._.js → [externals]__0r-zamh._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/[externals]__1ifpkx-._.js +3 -0
  101. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__00ni9za._.js} +2 -2
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__06hexd0._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__08y94xv._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__0effm9m._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  109. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0ix8gm7._.js} +2 -2
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ji11a-._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0ojwjxl._.js} +2 -2
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0vb5fl2._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +4 -4
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  118. package/.next/standalone/.next/server/chunks/[root-of-the-server]__20d06rh._.js +3 -0
  119. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +3 -0
  120. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +3 -0
  121. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +3 -0
  122. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  125. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  126. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-wfhrp._.js +3 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  129. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  130. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__0_1tiya._.js} +2 -2
  131. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0t281d4._.js +3 -0
  133. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__17itmx-._.js +5 -0
  134. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1n2vcv4._.js} +2 -2
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tlvv9a._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  139. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0vo2qyt._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0xej-0e._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_10399ge._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_149k9oy._.js +3 -0
  147. package/.next/standalone/.next/server/chunks/ssr/_1_hwp06._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1ctse6u._.js +3 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  150. package/.next/standalone/.next/server/chunks/ssr/_1mnhi_r._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1ror7k9._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/_1sdgprl._.js +5 -0
  153. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  154. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  155. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  157. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  159. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +4 -0
  160. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  161. package/.next/standalone/.next/server/middleware.js +2 -2
  162. package/.next/standalone/.next/server/pages/404.html +1 -1
  163. package/.next/standalone/.next/server/pages/500.html +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  165. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/0km-jn9o5xyqs.css +2 -0
  168. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/{2sbelanzvxvv0.js → 113g8wtal6_c4.js} +22 -22
  170. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  171. package/.next/standalone/.next/static/chunks/18wqn0_3mzylf.js +1 -0
  172. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  173. package/.next/standalone/.next/static/chunks/{0fi645si-uiiq.js → 1xq1m8h14tnlj.js} +1 -1
  174. package/.next/standalone/.next/static/chunks/{3hp8ulqknybil.js → 1y3vwogqb88dx.js} +1 -1
  175. package/.next/standalone/.next/static/chunks/{3vs4ay36tjds_.js → 1zmh-b0ulrqu7.js} +1 -1
  176. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  177. package/.next/standalone/.next/static/chunks/299lhw60e8gr6.js +1 -0
  178. package/.next/standalone/.next/static/chunks/{18rvdw5v72364.js → 2adbpdaxp5ygq.js} +1 -1
  179. package/.next/standalone/.next/static/chunks/{2j94uwkb4s3qr.js → 2kbqcw-6swmav.js} +1 -1
  180. package/.next/standalone/.next/static/chunks/2n9n5m0963pdg.js +6 -0
  181. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  182. package/.next/standalone/.next/static/chunks/{31feojxhjyjpl.js → 30nrrpwymht5m.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  184. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  185. package/.next/standalone/SECURITY.md +8 -2
  186. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  187. package/.next/standalone/app/actions/get-hooks-config.ts +2 -2
  188. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  189. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  190. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  191. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  192. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  193. package/.next/standalone/app/audit/_components/audit-dashboard.tsx +1 -3
  194. package/.next/standalone/app/audit/_components/audit-progress-strip.tsx +1 -1
  195. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  196. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  197. package/.next/standalone/app/audit/_components/come-back-better-section.tsx +1 -1
  198. package/.next/standalone/app/audit/_components/empty-state.tsx +0 -1
  199. package/.next/standalone/app/audit/_components/how-to-improve-section.tsx +1 -1
  200. package/.next/standalone/app/audit/_components/quirks-section.tsx +0 -1
  201. package/.next/standalone/app/audit/_components/report-footer.tsx +0 -2
  202. package/.next/standalone/app/audit/_components/run-progress.tsx +1 -1
  203. package/.next/standalone/app/audit/_components/strengths-section.tsx +0 -1
  204. package/.next/standalone/app/audit/audit-styles.css +6 -149
  205. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  206. package/.next/standalone/app/components/raw-log-viewer.tsx +1 -1
  207. package/.next/standalone/app/components/toast.tsx +1 -1
  208. package/.next/standalone/app/policies/hooks-client.tsx +84 -8
  209. package/.next/standalone/app/settings/page.tsx +31 -0
  210. package/.next/standalone/app/settings/settings-client.tsx +487 -0
  211. package/.next/standalone/components/navbar.tsx +2 -0
  212. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  213. package/.next/standalone/lib/auth/auth-store.ts +4 -5
  214. package/.next/standalone/lib/codex-sessions.ts +2 -1
  215. package/.next/standalone/lib/dashboard-host.ts +101 -0
  216. package/.next/standalone/lib/install-check.ts +22 -7
  217. package/.next/standalone/lib/pi-sessions.ts +99 -14
  218. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  219. package/.next/standalone/lib/telemetry-id.ts +52 -1
  220. package/.next/standalone/lib/telemetry.ts +8 -4
  221. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  222. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  223. package/.next/standalone/node_modules/react/package.json +3 -3
  224. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  225. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  226. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  227. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  228. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  229. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  230. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  231. package/.next/standalone/package.json +23 -12
  232. package/.next/standalone/proxy.ts +111 -1
  233. package/.next/standalone/server.js +1 -1
  234. package/bin/failproofai-worker.mjs +1 -1
  235. package/bin/failproofai.mjs +1041 -81
  236. package/bin/failproofaid-shim.mjs +4 -4
  237. package/dist/cli.mjs +18513 -10474
  238. package/dist/worker.mjs +1223 -299
  239. package/lib/auth/api-server-client.ts +2 -3
  240. package/lib/auth/auth-store.ts +4 -5
  241. package/lib/codex-sessions.ts +2 -1
  242. package/lib/dashboard-host.ts +101 -0
  243. package/lib/install-check.ts +22 -7
  244. package/lib/pi-sessions.ts +99 -14
  245. package/lib/telemetry-enabled.ts +54 -0
  246. package/lib/telemetry-id.ts +52 -1
  247. package/lib/telemetry.ts +8 -4
  248. package/package.json +23 -12
  249. package/scripts/build-daemon-packages.mjs +247 -0
  250. package/scripts/daemon-platforms.mjs +42 -0
  251. package/scripts/dev-hook.mjs +46 -5
  252. package/scripts/install-telemetry.mjs +8 -1
  253. package/scripts/launch.ts +25 -3
  254. package/scripts/parse-script-args.ts +27 -1
  255. package/scripts/prune-standalone.mjs +26 -0
  256. package/scripts/publish-aliases.mjs +62 -41
  257. package/scripts/repro-npm-install.sh +155 -0
  258. package/scripts/translate-docs/cache.ts +1 -1
  259. package/scripts/translate-docs/cli.ts +2 -2
  260. package/scripts/translate-docs/mintlify-nav.ts +1 -1
  261. package/scripts/translate-docs/readme-translator.ts +163 -4
  262. package/scripts/validate-mdx.ts +153 -13
  263. package/src/audit/audit-lock.ts +285 -0
  264. package/src/audit/audit-schedule.ts +111 -0
  265. package/src/audit/cache.ts +18 -5
  266. package/src/audit/cli-adapters/goose.ts +2 -1
  267. package/src/audit/cli-adapters/hermes.ts +28 -10
  268. package/src/audit/cli.ts +191 -35
  269. package/src/audit/dashboard-cache.ts +2 -3
  270. package/src/audit/report.ts +0 -1
  271. package/src/hooks/backfill-cli.ts +247 -0
  272. package/src/hooks/builtin-policies.ts +391 -3
  273. package/src/hooks/cloud-connection.ts +354 -0
  274. package/src/hooks/cloud-enrollment-cli.ts +439 -0
  275. package/src/hooks/cloud-enrollment.ts +284 -0
  276. package/src/hooks/cloud-introspect.ts +147 -0
  277. package/src/hooks/cloud-managed-policies.ts +206 -0
  278. package/src/hooks/collector-config.ts +218 -0
  279. package/src/hooks/configure-wizard.ts +1211 -233
  280. package/src/hooks/custom-hooks-loader.ts +243 -37
  281. package/src/hooks/daemon-client.ts +136 -33
  282. package/src/hooks/daemon-download.ts +264 -31
  283. package/src/hooks/daemon-service.ts +1124 -76
  284. package/src/hooks/first-run-gate.ts +50 -0
  285. package/src/hooks/flush-cli.ts +180 -0
  286. package/src/hooks/fp-config.ts +835 -0
  287. package/src/hooks/fp-home.ts +714 -0
  288. package/src/hooks/fp-reset.ts +1363 -0
  289. package/src/hooks/handler.ts +185 -6
  290. package/src/hooks/harness-cli.ts +386 -0
  291. package/src/hooks/hook-activity-store.ts +171 -24
  292. package/src/hooks/hook-logger.ts +2 -2
  293. package/src/hooks/hook-telemetry.ts +5 -1
  294. package/src/hooks/hooks-config.ts +4 -3
  295. package/src/hooks/integrations.ts +0 -1
  296. package/src/hooks/loader-utils.ts +172 -9
  297. package/src/hooks/manager.ts +41 -5
  298. package/src/hooks/migrations.ts +450 -0
  299. package/src/hooks/onboarding-attempt.ts +195 -0
  300. package/src/hooks/onboarding-lock.ts +118 -0
  301. package/src/hooks/policy-types.ts +28 -0
  302. package/src/hooks/session-pause-cli.ts +159 -0
  303. package/src/hooks/session-pause.ts +267 -0
  304. package/src/hooks/setup-state.ts +256 -0
  305. package/src/hooks/tui.ts +209 -9
  306. package/src/hooks/uninstall-cli.ts +418 -0
  307. package/src/hooks/worker-server.ts +134 -15
  308. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  309. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  310. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  311. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  312. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  313. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  314. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  315. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  316. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  317. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  318. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  319. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  320. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  321. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  322. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0nb0syg._.js +0 -3
  323. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  324. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  325. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  326. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  327. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  328. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1psjybo._.js +0 -3
  329. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  330. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  331. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_0oy_-36.js +0 -3
  332. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  333. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  334. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  335. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  336. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  337. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  338. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  339. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  340. package/.next/standalone/.next/static/chunks/3s7myk_co0gbh.js +0 -1
  341. package/.next/standalone/.next/static/chunks/40k1_v4os4cw6.js +0 -6
  342. package/.next/standalone/Cargo.lock +0 -428
  343. package/.next/standalone/Cargo.toml +0 -9
  344. package/.next/standalone/crates/PROTOCOL.md +0 -122
  345. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  346. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  347. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  348. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  349. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  350. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  351. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  352. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  353. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  354. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  355. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  356. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  357. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  358. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  359. package/.next/standalone/integration-suite/Dockerfile +0 -47
  360. package/.next/standalone/integration-suite/README.md +0 -129
  361. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  362. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  363. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  364. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  365. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  366. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  367. package/.next/standalone/integration-suite/report.js +0 -120
  368. package/.next/standalone/integration-suite/run.sh +0 -159
  369. package/.next/standalone/openclaw-plugin/index.js +0 -223
  370. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  371. package/.next/standalone/openclaw-plugin/package.json +0 -12
  372. package/.next/standalone/osv-scanner.toml +0 -17
  373. package/.next/standalone/pi-extension/index.ts +0 -524
  374. package/.next/standalone/pi-extension/package.json +0 -12
  375. package/.next/standalone/postcss.config.mjs +0 -8
  376. package/.next/standalone/readme-arch-hq.gif +0 -0
  377. package/.next/standalone/rust-toolchain.toml +0 -4
  378. package/src/auth/cli.ts +0 -371
  379. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_buildManifest.js +0 -0
  380. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_clientMiddlewareManifest.js +0 -0
  381. /package/.next/standalone/.next/static/{NWAjs0tHFyzk97gRGG09r → VjEdIL6W5uBsvEzknXqV9}/_ssgManifest.js +0 -0
@@ -11,29 +11,57 @@
11
11
  * and results in an empty hook list for that file. Builtins continue normally.
12
12
  */
13
13
  import { resolve, isAbsolute, basename } from "node:path";
14
+ import { randomUUID } from "crypto";
14
15
  import { existsSync, readdirSync } from "node:fs";
16
+ import { readFile } from "node:fs/promises";
15
17
  import { pathToFileURL } from "node:url";
16
- import { homedir } from "node:os";
18
+ import { createHash } from "node:crypto";
17
19
  import { hookLogWarn, hookLogError, hookLogInfo } from "./hook-logger";
18
- import { getCustomHooks, clearCustomHooks } from "./custom-hooks-registry";
19
- import { findDistIndex, rewriteFileTree, TMP_SUFFIX, cleanupTmpFiles } from "./loader-utils";
20
+ import { customPolicies, getCustomHooks, clearCustomHooks } from "./custom-hooks-registry";
21
+ import {
22
+ findDistIndex,
23
+ rewriteFileTree,
24
+ TMP_SUFFIX,
25
+ cleanupTmpFiles,
26
+ isTmpArtifact,
27
+ sweepStaleTmpArtifacts,
28
+ } from "./loader-utils";
20
29
  import { findProjectConfigDir } from "./hooks-config";
21
30
  import { trackHookEvent } from "./hook-telemetry";
22
31
  import { getInstanceId } from "../../lib/telemetry-id";
23
32
  import type { CustomHook } from "./policy-types";
33
+ import type { CloudManagedPolicyArtifact } from "./cloud-managed-policies";
34
+ import { customPoliciesDir, shimsDir } from "./fp-home";
24
35
 
25
36
  const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
26
37
 
27
38
  /** Regex matching convention policy filenames: *policies.{js,mjs,ts} */
28
39
  const CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
29
40
 
41
+ /** Monotonic suffix for physically distinct temporary module trees. Bun
42
+ * ignores URL query strings when caching imports, so a query-only cache
43
+ * buster silently drops custom policies after the first request in the warm
44
+ * daemon worker. A unique filename is honored by both Bun and Node. */
45
+ let loadSequence = 0;
46
+
47
+ interface CachedPolicyModule {
48
+ fingerprint: string;
49
+ hooks: CustomHook[];
50
+ }
51
+
52
+ /** One retained module instance per source entry. Reusing its registered hook
53
+ * functions prevents an unbounded ESM module-cache leak in the resident worker.
54
+ * A source-tree change replaces this entry and imports one new module. */
55
+ const policyModuleCache = new Map<string, CachedPolicyModule>();
30
56
  /**
31
- * Monotonic counter used to cache-bust each dynamic import. Loading N files
32
- * creates N module instances rather than reusing one; every caller here is
33
- * either a one-shot CLI command or a short-lived hook process, so the
34
- * lifetime cost is bounded by the process, not by uptime.
57
+ * Cap, for the reason `gitBranchCache` states and this cache did not carry
58
+ * over: a warm worker touching many projects over its lifetime must not grow
59
+ * this unboundedly. Keyed by absolute policy-file path and holding cloned hook
60
+ * closures, so the entries are not small. Cleared wholesale rather than evicted
61
+ * one at a time — the next load simply re-imports, which is the same cost the
62
+ * cache exists to avoid paying twice, not a correctness change.
35
63
  */
36
- let loadSequence = 0;
64
+ const POLICY_MODULE_CACHE_MAX_ENTRIES = 500;
37
65
 
38
66
  /** Script extensions we could load, used to spot near-miss filenames. */
39
67
  const LOADABLE_EXT_RE = /\.(js|mjs|ts)$/;
@@ -76,7 +104,11 @@ export function findSkippedPolicyFiles(dir: string): string[] {
76
104
  e.isFile() &&
77
105
  LOADABLE_EXT_RE.test(e.name) &&
78
106
  !CONVENTION_FILE_RE.test(e.name) &&
79
- !e.name.endsWith(".d.ts"),
107
+ !e.name.endsWith(".d.ts") &&
108
+ // Never our own generated files. They are `.mjs` and never match the
109
+ // convention, so each one a killed load left behind was reported to
110
+ // the user as a policy file that would not load.
111
+ !isTmpArtifact(e.name),
80
112
  )
81
113
  .map((e) => e.name)
82
114
  .sort((a, b) => a.localeCompare(b));
@@ -85,13 +117,69 @@ export function findSkippedPolicyFiles(dir: string): string[] {
85
117
  }
86
118
  }
87
119
 
120
+ /**
121
+ * How long a policy module gets to finish evaluating its top level.
122
+ *
123
+ * Matches the 10s `handler.ts` gives a policy's `fn` to run, for the same
124
+ * reason: a policy file is user code, and user code that hangs must cost one
125
+ * skipped policy rather than the process.
126
+ *
127
+ * `FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS` shortens it for tests — a suite proving
128
+ * the deadline fires should not have to sit out the real one.
129
+ */
130
+ function moduleLoadTimeoutMs(): number {
131
+ const override = Number(process.env.FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS);
132
+ return Number.isFinite(override) && override > 0 ? override : 10_000;
133
+ }
134
+
135
+ /**
136
+ * `import()`, but it cannot hang forever.
137
+ *
138
+ * A bare `await import(...)` on a module whose top level contains an `await`
139
+ * that never resolves never settles — it does not reject, so no `catch` and no
140
+ * `finally` anywhere above it ever runs. In the one-shot hook path that costs
141
+ * one hook process, which the agent CLI's own timeout reaps. In the warm daemon
142
+ * worker it holds `worker-server.ts`'s serialization chain forever, and every
143
+ * subsequent hook on the machine queues behind it and fail-closed denies.
144
+ *
145
+ * Losing the race does NOT cancel the import — nothing can. It stops us
146
+ * *waiting* on it, which is the part that matters, and the caller's `catch`
147
+ * below reports the file as failed to load. The abandoned module keeps its
148
+ * half-initialized entry in the ESM cache, but each load writes a uniquely
149
+ * named temporary tree, so a retry imports a different specifier and is never
150
+ * poisoned by it.
151
+ */
152
+ async function importWithDeadline(fileUrl: string): Promise<void> {
153
+ const budget = moduleLoadTimeoutMs();
154
+ let timer: ReturnType<typeof setTimeout> | undefined;
155
+ try {
156
+ await Promise.race([
157
+ import(/* webpackIgnore: true */ fileUrl),
158
+ new Promise<never>((_, reject) => {
159
+ timer = setTimeout(
160
+ () => reject(new Error(`timed out loading module after ${budget}ms`)),
161
+ budget,
162
+ );
163
+ timer.unref?.();
164
+ }),
165
+ ]);
166
+ } finally {
167
+ if (timer) clearTimeout(timer);
168
+ }
169
+ }
170
+
88
171
  /**
89
172
  * Load a single policy file into the globalThis custom hooks registry.
90
173
  * Does NOT clear the registry — caller is responsible for that.
91
174
  */
92
175
  async function loadSingleFile(
93
176
  absPath: string,
94
- opts?: { strict?: boolean; conventionScope?: "project" | "user" },
177
+ opts?: {
178
+ strict?: boolean;
179
+ conventionScope?: "project" | "user";
180
+ /** Cloud-managed policies pass their pinned digest for load-time re-verification. */
181
+ verifyEntrySha?: string;
182
+ },
95
183
  ): Promise<void> {
96
184
  const g = globalThis as Record<string, unknown>;
97
185
  g[LOADING_KEY] = true;
@@ -101,30 +189,48 @@ async function loadSingleFile(
101
189
  const distIndex = await findDistIndex();
102
190
  const distUrl = distIndex ? pathToFileURL(distIndex).href : null;
103
191
 
104
- tmpFiles = await rewriteFileTree(absPath, distUrl, distIndex);
105
-
106
- const entryTmp = absPath + TMP_SUFFIX;
107
- // Cache-bust the specifier. NOTE this works under Node but NOT under Bun,
108
- // which caches by resolved path and ignores the query and the shipped
109
- // binary runs under Bun. It is kept because it makes repeat loads behave
110
- // the same on both runtimes where it can, but nothing may DEPEND on a
111
- // repeat load re-executing; callers dedupe paths instead (see above).
112
- // The temp path is deterministic, so a second
113
- // load of the same file inside one process hit the ESM module cache, the
114
- // module body never re-ran, no `customPolicies.add` fired, and the caller
115
- // saw zero hooks — indistinguishable from a broken policy file. That is
116
- // exactly what `failproofai policies` rendered as "failed to load" for
117
- // every file when it walked a shared project/user directory twice.
118
- // A distinct query string makes it a distinct module to the loader.
119
- const fileUrl = `${pathToFileURL(entryTmp).href}?v=${++loadSequence}`;
120
- await import(/* webpackIgnore: true */ fileUrl);
192
+ const sequence = ++loadSequence;
193
+ // pid + sequence is not unique enough on its own: two containers sharing a
194
+ // mounted HOME, or two pid namespaces, can collide — and on the shim's
195
+ // fallback path a collision means EEXIST, which fails the load OPEN. A
196
+ // random id removes the class. It costs nothing: `fingerprintTemporaryTree`
197
+ // normalises the whole suffix away, so the module cache still hits.
198
+ const tmpSuffix = `${TMP_SUFFIX}.${process.pid}.${sequence}.${randomUUID()}.mjs`;
199
+ tmpFiles = await rewriteFileTree(
200
+ absPath,
201
+ distUrl,
202
+ distIndex,
203
+ tmpSuffix,
204
+ opts?.verifyEntrySha,
205
+ );
206
+
207
+ const fingerprint = await fingerprintTemporaryTree(tmpFiles, tmpSuffix);
208
+ const cached = policyModuleCache.get(absPath);
209
+ if (cached?.fingerprint === fingerprint) {
210
+ for (const hook of cached.hooks) customPolicies.add({ ...hook });
211
+ return;
212
+ }
213
+
214
+ const entryTmp = absPath + tmpSuffix;
215
+ const fileUrl = pathToFileURL(entryTmp).href;
216
+ const hooksBefore = getCustomHooks().length;
217
+ await importWithDeadline(fileUrl);
218
+ if (policyModuleCache.size >= POLICY_MODULE_CACHE_MAX_ENTRIES) policyModuleCache.clear();
219
+ policyModuleCache.set(absPath, {
220
+ fingerprint,
221
+ hooks: getCustomHooks()
222
+ .slice(hooksBefore)
223
+ .map((hook) => ({ ...hook })),
224
+ });
121
225
  } catch (err) {
122
226
  const msg = err instanceof Error ? err.message : String(err);
123
227
  const errorType = /Cannot find module|MODULE_NOT_FOUND|ENOENT/i.test(msg)
124
228
  ? "module_not_found"
125
229
  : /SyntaxError|Unexpected token/i.test(msg)
126
230
  ? "syntax_error"
127
- : "runtime_error";
231
+ : /timed out loading module/i.test(msg)
232
+ ? "load_timeout"
233
+ : "runtime_error";
128
234
  void trackHookEvent(getInstanceId(), "custom_hooks_load_error", {
129
235
  error_type: errorType,
130
236
  is_convention: !!opts?.conventionScope,
@@ -139,6 +245,18 @@ async function loadSingleFile(
139
245
  }
140
246
  }
141
247
 
248
+ async function fingerprintTemporaryTree(tmpFiles: string[], tmpSuffix: string): Promise<string> {
249
+ const hash = createHash("sha256");
250
+ for (const tmpFile of [...tmpFiles].sort()) {
251
+ // The generated filenames change per load. Normalize both the logical path
252
+ // and rewritten import specifiers so identical source graphs hash equally.
253
+ hash.update(tmpFile.replaceAll(tmpSuffix, TMP_SUFFIX));
254
+ const contents = await readFile(tmpFile, "utf8");
255
+ hash.update(contents.replaceAll(tmpSuffix, TMP_SUFFIX));
256
+ }
257
+ return hash.digest("hex");
258
+ }
259
+
142
260
  /**
143
261
  * Load a single explicit custom hooks file (legacy API).
144
262
  * Clears the registry, loads the file, returns registered hooks.
@@ -214,7 +332,11 @@ function warnSkippedPolicyFiles(dir: string, scope: "project" | "user"): void {
214
332
 
215
333
  export async function loadAllCustomHooks(
216
334
  customPoliciesPaths: string | string[] | undefined,
217
- opts?: { sessionCwd?: string; customPoliciesEnabled?: boolean },
335
+ opts?: {
336
+ sessionCwd?: string;
337
+ customPoliciesEnabled?: boolean;
338
+ cloudManagedPolicies?: CloudManagedPolicyArtifact[];
339
+ },
218
340
  ): Promise<LoadAllResult> {
219
341
  clearCustomHooks();
220
342
 
@@ -236,6 +358,43 @@ export async function loadAllCustomHooks(
236
358
  // convention passes.
237
359
  const loadedPaths = new Set<string>();
238
360
 
361
+ // Cloud policies keyed by the artifact they resolve to — and artifacts are
362
+ // CONTENT-ADDRESSED, so two policies whose source is byte-identical share one
363
+ // path. Building this map with a plain `new Map(entries)` let the last one win
364
+ // silently, and since `loadedPaths` then loads that file exactly once, the
365
+ // other policy vanished: its id never appeared in `failproofai policies` or in
366
+ // the decision log, and — the part that matters — ITS EFFECT DECIDED NOTHING.
367
+ // An `enforce` policy sharing bytes with an `observe` one was downgraded to
368
+ // observation, which is a silent enforcement gap, and the reverse turned a
369
+ // measurement-only rollout into live denials.
370
+ //
371
+ // It could not happen before the flattening: `deployments/<n>/<id>.mjs` gave
372
+ // every policy its own path even when the bytes matched.
373
+ //
374
+ // One module instance is right — `customPolicies.add` is an unconditional
375
+ // push, so importing the file twice would register every hook twice and run
376
+ // the policy twice per event. So the collision is resolved rather than
377
+ // avoided, and it resolves toward ENFORCEMENT: over-enforcing is visible to
378
+ // whoever hits it, while under-enforcing is exactly the silent failure this
379
+ // codebase exists to remove. It is also announced, so an operator can give the
380
+ // two policies distinguishable source instead of living with the merge.
381
+ const cloudManagedByPath = new Map<string, CloudManagedPolicyArtifact>();
382
+ for (const policy of opts?.cloudManagedPolicies ?? []) {
383
+ const key = resolve(policy.path);
384
+ const existing = cloudManagedByPath.get(key);
385
+ if (!existing) {
386
+ cloudManagedByPath.set(key, policy);
387
+ continue;
388
+ }
389
+ hookLogWarn(
390
+ `cloud-managed policies ${existing.id} and ${policy.id} have identical source, so they share ` +
391
+ `one artifact and load as one policy; enforcing it if either asks to enforce`,
392
+ );
393
+ if (existing.effect !== "enforce" && policy.effect === "enforce") {
394
+ cloudManagedByPath.set(key, policy);
395
+ }
396
+ }
397
+
239
398
  // 1. Explicit custom policy paths. Accept a string for callers/configs using
240
399
  // the legacy singular form.
241
400
  for (const customPoliciesPath of typeof customPoliciesPaths === "string"
@@ -248,9 +407,23 @@ export async function loadAllCustomHooks(
248
407
  if (!loadedPaths.has(absPath)) {
249
408
  loadedPaths.add(absPath);
250
409
  const hooksBefore = getCustomHooks().length;
251
- await loadSingleFile(absPath);
410
+ // A cloud-managed policy re-verifies its pinned digest at load, binding
411
+ // the imported bytes to what desired-state promised.
412
+ await loadSingleFile(absPath, {
413
+ verifyEntrySha: cloudManagedByPath.get(absPath)?.sha256,
414
+ });
252
415
  for (const hook of getCustomHooks().slice(hooksBefore)) {
253
- (hook as CustomHook & { __policyId?: string }).__policyId = customPolicyId(absPath, hook.name);
416
+ const cloudManaged = cloudManagedByPath.get(absPath);
417
+ const tagged = hook as CustomHook & {
418
+ __policyId?: string;
419
+ __cloudManaged?: CloudManagedPolicyArtifact;
420
+ };
421
+ if (cloudManaged) {
422
+ tagged.__cloudManaged = cloudManaged;
423
+ tagged.__policyId = `cloud:${cloudManaged.id}@${cloudManaged.version}:${hook.name}`;
424
+ } else {
425
+ tagged.__policyId = customPolicyId(absPath, hook.name);
426
+ }
254
427
  }
255
428
  }
256
429
  } else {
@@ -268,21 +441,51 @@ export async function loadAllCustomHooks(
268
441
  // exactly that — and it is discovered here as well. Skip what step 1 loaded,
269
442
  // or the file is imported twice and every hook in it fires twice per event.
270
443
  const projectDir = resolve(projectRoot, ".failproofai", "policies");
271
- if (conventionEnabled) warnSkippedPolicyFiles(projectDir, "project");
444
+ // Clear out generated files a killed load left behind, before anything scans
445
+ // this directory. The temporary tree has to be written beside the sources for
446
+ // a rewritten relative import to resolve, and its name now carries a pid and
447
+ // sequence number — so unlike the old fixed name, each abnormal termination
448
+ // leaks one file permanently rather than leaving one that the next load
449
+ // overwrites. Best-effort and age-gated, so it can never remove a tree
450
+ // another process is still importing.
451
+ // WHICH SCOPE this directory is depends on whether it IS the user directory.
452
+ // Run an agent CLI with the cwd set to $HOME and `<cwd>/.failproofai/policies`
453
+ // resolves to `~/.failproofai/policies` — the user scope itself. Layout 3 made
454
+ // that reachable by collapsing `customPoliciesDir()` onto `policiesDir()`;
455
+ // layout 2 nested the user directory one level deeper, so the two could never
456
+ // be the same path.
457
+ //
458
+ // Tagging it `project` there would make a policy's id depend on where the
459
+ // agent happened to be launched from — `convention:project:x-policies.mjs:foo`
460
+ // from $HOME and `convention:user:x-policies.mjs:foo` from anywhere else — and
461
+ // a disable is RECORDED AGAINST THAT ID. So a user who switched a policy off
462
+ // would find it firing again whenever they worked out of their home
463
+ // directory, with nothing to explain it. The files are the user's global
464
+ // policies; that the cwd also points at them is incidental.
465
+ const projectScope: "project" | "user" = projectDir === customPoliciesDir() ? "user" : "project";
466
+ if (conventionEnabled) {
467
+ void sweepStaleTmpArtifacts(projectDir);
468
+ warnSkippedPolicyFiles(projectDir, projectScope);
469
+ }
470
+ // The shim directory leaks the same way and is swept on the same terms. It is
471
+ // ours alone and outside the policy tree, so nothing else would ever reap it;
472
+ // the age gate is what keeps this from removing a shim another process is
473
+ // still importing.
474
+ void sweepStaleTmpArtifacts(shimsDir());
272
475
  const projectFiles = conventionEnabled
273
476
  ? discoverPolicyFiles(projectDir).filter((f) => !loadedPaths.has(f))
274
477
  : [];
275
478
  for (const file of projectFiles) {
276
479
  loadedPaths.add(file);
277
480
  const hooksBefore = getCustomHooks().length;
278
- await loadSingleFile(file, { conventionScope: "project" });
481
+ await loadSingleFile(file, { conventionScope: projectScope });
279
482
  const newHooks = getCustomHooks().slice(hooksBefore);
280
483
  for (const hook of newHooks) {
281
- (hook as CustomHook & { __policyId?: string }).__policyId = conventionPolicyId("project", basename(file), hook.name);
484
+ (hook as CustomHook & { __policyId?: string }).__policyId = conventionPolicyId(projectScope, basename(file), hook.name);
282
485
  }
283
486
  if (newHooks.length > 0) {
284
487
  conventionSources.push({
285
- scope: "project",
488
+ scope: projectScope,
286
489
  file: basename(file),
287
490
  hookNames: newHooks.map((h) => h.name),
288
491
  });
@@ -303,8 +506,11 @@ export async function loadAllCustomHooks(
303
506
  // so the second import is a no-op — but Node honours the query and would
304
507
  // double-register. The binary runs under Bun and the tests under Node, so the
305
508
  // bug is invisible from both sides. Do not rely on that; dedupe the paths.
306
- const userDir = resolve(homedir(), ".failproofai", "policies");
307
- if (conventionEnabled && userDir !== projectDir) warnSkippedPolicyFiles(userDir, "user");
509
+ const userDir = customPoliciesDir();
510
+ if (conventionEnabled && userDir !== projectDir) {
511
+ void sweepStaleTmpArtifacts(userDir);
512
+ warnSkippedPolicyFiles(userDir, "user");
513
+ }
308
514
  const userFiles =
309
515
  conventionEnabled && userDir !== projectDir
310
516
  ? discoverPolicyFiles(userDir).filter((f) => !loadedPaths.has(f))
@@ -6,17 +6,15 @@
6
6
  * request, a 4-byte big-endian u32 length prefix followed by that many
7
7
  * bytes of UTF-8 JSON, camelCase fields, `"type"` as the tag.
8
8
  *
9
- * This module makes NO decision about what to do when the daemon can't be
10
- * reached `tryDaemonHook` just returns `null` on any failure. The caller
11
- * (`bin/failproofai.mjs`) decides: fall back to full in-process evaluation
12
- * on a machine that was never daemon-configured, or fail closed on one that
13
- * was. See `isDaemonConfigured`.
9
+ * This module reports whether a daemon request succeeded, was unreachable, or
10
+ * used an incompatible protocol. The caller (`bin/failproofai.mjs`) owns the
11
+ * fail-closed response for daemon-configured machines.
14
12
  */
15
13
  import { createConnection } from "node:net";
16
- import { resolve } from "node:path";
17
- import { homedir } from "node:os";
18
14
  import type { IntegrationType } from "./types";
19
- import { readHooksConfig } from "./hooks-config";
15
+ import { existsSync } from "node:fs";
16
+ import { daemonSocket as daemonSocketPath } from "./fp-home";
17
+ import { readConfig } from "./fp-config";
20
18
 
21
19
  const PROTOCOL_VERSION = 1;
22
20
 
@@ -66,9 +64,90 @@ export interface DaemonHookResponse {
66
64
  stderr: string;
67
65
  }
68
66
 
67
+ /**
68
+ * Why an attempt failed. BOTH cases deny; they differ only in what the user is
69
+ * told to do about it.
70
+ *
71
+ * `protocol-mismatch` means a daemon answered, so it is alive — we simply
72
+ * cannot speak its wire format. The cause is known and benign: the CLI upgraded
73
+ * via npm and the daemon has not been reinstalled yet, so the remedy is
74
+ * `failproofai config`, and naming it is the difference between a one-command
75
+ * fix and a support ticket.
76
+ *
77
+ * `unreachable` means nothing answered. A stopped service, a deleted socket and
78
+ * deliberate tampering are indistinguishable from here.
79
+ *
80
+ * This comment used to say a mismatch should FALL BACK to in-process
81
+ * evaluation, "because denying every tool call over it would take a working
82
+ * machine offline to protect nothing". `2926252` deliberately reversed that,
83
+ * and `bin/failproofai.mjs` now routes both failures to the same forced deny:
84
+ * on a daemon-configured machine the daemon is the ONLY evaluator, and a second
85
+ * policy engine reachable by breaking the first is not a guarantee. The
86
+ * distinction survives in the MESSAGE and nowhere else. Do not restore the
87
+ * fallback without revisiting that decision — the comment outliving the code is
88
+ * exactly how it would come back by accident.
89
+ */
90
+ export type DaemonFailure = "unreachable" | "protocol-mismatch";
91
+
92
+ export type DaemonAttempt =
93
+ | { ok: true; response: DaemonHookResponse }
94
+ | { ok: false; failure: DaemonFailure };
95
+
96
+ /**
97
+ * Whether a daemon is listening, regardless of how it was started.
98
+ *
99
+ * Distinct from `daemonServiceStatus()`, which asks the service manager. A
100
+ * daemon run by hand — during development, or from a container — is invisible
101
+ * to that check but is very much running and pulling policy, so reporting "not
102
+ * installed, so nothing will be pulled" at someone whose machine is actively
103
+ * enforcing is simply false.
104
+ */
105
+ export function daemonSocketPresent(): boolean {
106
+ try {
107
+ return existsSync(socketPath());
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Whether the daemon socket accepts a connection *right now*.
115
+ *
116
+ * Narrower than `daemonSocketPresent()` (a stale socket file outlives the
117
+ * process that made it) and narrower than `attemptDaemonHook`, which reports
118
+ * `"unreachable"` for BOTH a refused connection and a request that was accepted
119
+ * and then never answered. Those two have different causes and different
120
+ * remedies — a socket that is not up yet versus a worker that cannot run — and
121
+ * telling them apart is the whole point of this function.
122
+ *
123
+ * Connect budget only; nothing is sent. Used by the health probe, never on the
124
+ * hook path.
125
+ */
126
+ export async function daemonAcceptsConnections(
127
+ timeoutMs = DAEMON_CONNECT_TIMEOUT_MS,
128
+ ): Promise<boolean> {
129
+ if (process.platform === "win32") return false;
130
+ return new Promise<boolean>((resolve) => {
131
+ let settled = false;
132
+ const done = (v: boolean) => {
133
+ if (settled) return;
134
+ settled = true;
135
+ clearTimeout(timer);
136
+ socket.removeAllListeners();
137
+ socket.destroy();
138
+ resolve(v);
139
+ };
140
+ const socket = createConnection({ path: socketPath() });
141
+ const timer = setTimeout(() => done(false), timeoutMs);
142
+ timer.unref?.();
143
+ socket.on("connect", () => done(true));
144
+ socket.on("error", () => done(false));
145
+ });
146
+ }
147
+
69
148
  function socketPath(): string {
70
149
  if (process.env.FAILPROOFAI_DAEMON_SOCKET) return process.env.FAILPROOFAI_DAEMON_SOCKET;
71
- return resolve(homedir(), ".failproofai", "run", "failproofaid.sock");
150
+ return daemonSocketPath();
72
151
  }
73
152
 
74
153
  /**
@@ -81,8 +160,11 @@ function socketPath(): string {
81
160
  */
82
161
  export function isDaemonConfigured(): boolean {
83
162
  try {
84
- return readHooksConfig().daemonConfigured === true;
163
+ return readConfig().daemon.configured === true;
85
164
  } catch {
165
+ // Unreadable config reads as NOT daemon-configured. The failure direction
166
+ // is deliberate: the alternative is a machine that fails closed on every
167
+ // tool call because a config file got truncated.
86
168
  return false;
87
169
  }
88
170
  }
@@ -94,32 +176,41 @@ function encodeFrame(value: unknown): Buffer {
94
176
  return Buffer.concat([header, body]);
95
177
  }
96
178
 
97
- /**
98
- * Attempts one hook evaluation via the daemon. Returns `null` on **any**
99
- * failure — no socket, connection refused, timeout, malformed response,
100
- * protocol-version mismatch, or an explicit `error` message from the
101
- * daemon. The caller never needs to distinguish failure modes; it just
102
- * falls through to whatever its own fallback policy is.
103
- */
104
- export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookResponse | null> {
179
+ /** Attempts a daemon evaluation while preserving the failure category. */
180
+ export async function attemptDaemonHook(
181
+ req: DaemonHookRequest,
182
+ opts?: {
183
+ /**
184
+ * Override the RESPONSE budget only the connect budget is never
185
+ * relaxed. Used by the health probe (`probeDaemonEndToEnd`), which runs
186
+ * from an interactive command rather than a hook and must not make a
187
+ * person wait out the full 30s hook budget to be told their daemon is
188
+ * broken. Never set this on the hook path: 30s is matched to the
189
+ * daemon's own read timeout so this side never gives up on a request the
190
+ * daemon is still honestly working on.
191
+ */
192
+ responseTimeoutMs?: number;
193
+ },
194
+ ): Promise<DaemonAttempt> {
105
195
  // Windows never has a daemon in this phase (see the plan's platform
106
196
  // scope) — skip the attempt outright rather than depending on however
107
197
  // Node happens to behave when handed a POSIX socket path on Windows.
108
- if (process.platform === "win32") return null;
198
+ if (process.platform === "win32") return { ok: false, failure: "unreachable" };
199
+ const responseBudget = opts?.responseTimeoutMs ?? DAEMON_RESPONSE_TIMEOUT_MS;
109
200
 
110
- return new Promise<DaemonHookResponse | null>((resolvePromise) => {
201
+ return new Promise<DaemonAttempt>((resolvePromise) => {
111
202
  let settled = false;
112
203
  let timer: ReturnType<typeof setTimeout>;
113
204
  const arm = (ms: number) => {
114
205
  clearTimeout(timer);
115
- timer = setTimeout(() => finish(null), ms);
206
+ timer = setTimeout(() => fail("unreachable"), ms);
116
207
  // Don't let this timer alone keep the process alive if everything else
117
208
  // has already finished — it's always cleared on the success/failure
118
209
  // paths below, this just avoids it being the sole reason a --hook
119
210
  // process lingers if something upstream forgets to await us.
120
211
  timer.unref?.();
121
212
  };
122
- const finish = (result: DaemonHookResponse | null) => {
213
+ const finish = (result: DaemonAttempt) => {
123
214
  if (settled) return;
124
215
  settled = true;
125
216
  clearTimeout(timer);
@@ -127,6 +218,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
127
218
  socket.destroy();
128
219
  resolvePromise(result);
129
220
  };
221
+ const fail = (failure: DaemonFailure) => finish({ ok: false, failure });
130
222
 
131
223
  const socket = createConnection({ path: socketPath() });
132
224
  arm(DAEMON_CONNECT_TIMEOUT_MS);
@@ -137,7 +229,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
137
229
  socket.on("connect", () => {
138
230
  // Connected: the daemon is demonstrably reachable, so the question is
139
231
  // no longer "is it there" but "how long does this evaluation take".
140
- arm(DAEMON_RESPONSE_TIMEOUT_MS);
232
+ arm(responseBudget);
141
233
  socket.write(
142
234
  encodeFrame({
143
235
  type: "hook",
@@ -157,7 +249,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
157
249
  if (recvBuf.length < 4) return;
158
250
  declaredLen = recvBuf.readUInt32BE(0);
159
251
  if (declaredLen > MAX_FRAME_LEN) {
160
- finish(null);
252
+ fail("unreachable");
161
253
  return;
162
254
  }
163
255
  recvBuf = recvBuf.subarray(4);
@@ -170,12 +262,16 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
170
262
  try {
171
263
  message = JSON.parse(body.toString("utf8")) as Record<string, unknown>;
172
264
  } catch {
173
- finish(null);
265
+ fail("unreachable");
174
266
  return;
175
267
  }
176
268
 
177
269
  if (message.protocolVersion !== PROTOCOL_VERSION) {
178
- finish(null);
270
+ // Catches BOTH directions. A newer CLI sends v2 and the daemon replies
271
+ // with an error stamped v1; an older CLI sends v1 and a newer daemon
272
+ // replies stamped v2. Either way the versions disagree here, and either
273
+ // way a daemon answered — so this is skew, not absence.
274
+ fail("protocol-mismatch");
179
275
  return;
180
276
  }
181
277
  if (
@@ -184,16 +280,23 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
184
280
  typeof message.stdout === "string" &&
185
281
  typeof message.stderr === "string"
186
282
  ) {
187
- finish({ exitCode: message.exitCode, stdout: message.stdout, stderr: message.stderr });
283
+ finish({
284
+ ok: true,
285
+ response: {
286
+ exitCode: message.exitCode,
287
+ stdout: message.stdout,
288
+ stderr: message.stderr,
289
+ },
290
+ });
188
291
  return;
189
292
  }
190
- // Anything else — an explicit `error` message, a `pong` (protocol
191
- // confusion), or a well-formed-but-wrong-shape body — is treated
192
- // identically to a connection failure: no partial trust.
193
- finish(null);
293
+ // Anything else — an explicit `error` message at a MATCHING protocol
294
+ // version, a `pong` (protocol confusion), or a well-formed-but-wrong-shape
295
+ // body — is treated identically to a connection failure: no partial trust.
296
+ fail("unreachable");
194
297
  });
195
298
 
196
- socket.on("error", () => finish(null));
197
- socket.on("close", () => finish(null));
299
+ socket.on("error", () => fail("unreachable"));
300
+ socket.on("close", () => fail("unreachable"));
198
301
  });
199
302
  }