failproofai 1.0.0-beta.0 → 1.0.0-beta.10

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 (357) 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 +2 -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]__06hexd0._.js +3 -0
  102. package/.next/standalone/.next/server/chunks/[root-of-the-server]__094p1f1._.js +3 -0
  103. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0_0xu5z._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0ed01_d._.js +3 -0
  105. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0g0vcwt._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0h3lfmp._.js → [root-of-the-server]__0ky_n0r._.js} +2 -2
  107. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__18d-9oi._.js → [root-of-the-server]__0lk_935._.js} +2 -2
  108. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__16le-kd._.js → [root-of-the-server]__0pbp9fe._.js} +2 -2
  109. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0q-v9z2._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__10oa-v9._.js → [root-of-the-server]__0v2fpxe._.js} +2 -2
  111. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0sb_5m8._.js → [root-of-the-server]__131ebum._.js} +3 -3
  112. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0-dh5jy._.js → [root-of-the-server]__1_ps0jw._.js} +2 -2
  113. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__0l4vuqm._.js → [root-of-the-server]__1a7i2q4._.js} +2 -2
  114. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ibjy5x._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1iny24d._.js +3 -0
  116. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tvn4wf._.js +1 -1
  117. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1tyedjt._.js +3 -0
  118. package/.next/standalone/.next/server/chunks/_0lxbzdq._.js +5 -0
  119. package/.next/standalone/.next/server/chunks/_1zuiiy3._.js +5 -0
  120. package/.next/standalone/.next/server/chunks/lib_telemetry_ts_0dctyyw._.js +5 -0
  121. package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_17k9e3w.js +5 -5
  122. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -1
  123. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -1
  124. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  125. package/.next/standalone/.next/server/chunks/ssr/[externals]__1aq139v._.js +3 -0
  126. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04pwh4t._.js +3 -0
  127. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__06bp_fg._.js +5 -0
  128. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__074qmkp._.js +3 -0
  129. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__170799-._.js → [root-of-the-server]__0spkm68._.js} +11 -11
  130. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__10butu6._.js +3 -0
  131. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1a2hcb_._.js +3 -0
  132. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  133. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1apb7k_._.js → [root-of-the-server]__1rdruw4._.js} +2 -2
  134. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1rv-sc9._.js +3 -0
  135. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1tyf0tc._.js +5 -0
  136. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1u-wx42._.js +4 -0
  137. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1v185mo._.js +3 -0
  138. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__01od0m6._.js → [root-of-the-server]__1yw368c._.js} +2 -2
  139. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +3 -0
  140. package/.next/standalone/.next/server/chunks/ssr/_04mekdy._.js +3 -0
  141. package/.next/standalone/.next/server/chunks/ssr/_0r-dsre._.js +3 -0
  142. package/.next/standalone/.next/server/chunks/ssr/_0rjgfln._.js +3 -0
  143. package/.next/standalone/.next/server/chunks/ssr/_0se484m._.js +3 -0
  144. package/.next/standalone/.next/server/chunks/ssr/_0uz7o9y._.js +3 -0
  145. package/.next/standalone/.next/server/chunks/ssr/_11_p9y8._.js +1 -1
  146. package/.next/standalone/.next/server/chunks/ssr/_1e19atl._.js +1 -1
  147. package/.next/standalone/.next/server/chunks/ssr/_1fahg9e._.js +3 -0
  148. package/.next/standalone/.next/server/chunks/ssr/_1gmzkwj._.js +5 -0
  149. package/.next/standalone/.next/server/chunks/ssr/_1w8sola._.js +3 -0
  150. package/.next/standalone/.next/server/chunks/ssr/_1w_6p9g._.js +3 -0
  151. package/.next/standalone/.next/server/chunks/ssr/_1y4qr7u._.js +3 -0
  152. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +2 -2
  153. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  154. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  155. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_0dwo94s.js +4 -0
  156. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -1
  157. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +3 -0
  158. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +5 -0
  159. package/.next/standalone/.next/server/middleware-build-manifest.js +6 -6
  160. package/.next/standalone/.next/server/middleware.js +2 -2
  161. package/.next/standalone/.next/server/pages/404.html +1 -1
  162. package/.next/standalone/.next/server/pages/500.html +1 -1
  163. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  164. package/.next/standalone/.next/server/server-reference-manifest.json +74 -20
  165. package/.next/standalone/.next/static/chunks/00adubtgov21s.js +1 -0
  166. package/.next/standalone/.next/static/chunks/{03fmihek9n986.js → 06zgzyh3wv1ig.js} +1 -1
  167. package/.next/standalone/.next/static/chunks/{1hdhteui932bq.js → 0qmo2t50kpb30.js} +1 -1
  168. package/.next/standalone/.next/static/chunks/{1grd7y94-igz9.js → 0yudogeqddpzr.js} +1 -1
  169. package/.next/standalone/.next/static/chunks/16p68cybetr31.js +1 -0
  170. package/.next/standalone/.next/static/chunks/1bm0q12hgs22l.css +2 -0
  171. package/.next/standalone/.next/static/chunks/{1d6ncspjagm53.js → 1fu90ch80qngv.js} +1 -1
  172. package/.next/standalone/.next/static/chunks/{2vr7aazw6k2-n.js → 1m416lie3qwsj.js} +1 -1
  173. package/.next/standalone/.next/static/chunks/1v1gqvpjk62jm.js +1 -0
  174. package/.next/standalone/.next/static/chunks/1ypqzvoal8j56.js +6 -0
  175. package/.next/standalone/.next/static/chunks/23kev26by1e4f.js +1 -0
  176. package/.next/standalone/.next/static/chunks/{29k6ef49zppxj.js → 2h4sbmp0ad78w.js} +1 -1
  177. package/.next/standalone/.next/static/chunks/2obwg4tterjpa.js +1 -0
  178. package/.next/standalone/.next/static/chunks/2uvhw57mf9lg_.css +2 -0
  179. package/.next/standalone/.next/static/chunks/{29ydr-8q0zis6.js → 30ufy4_e2n78e.js} +22 -22
  180. package/.next/standalone/.next/static/chunks/31coz11ia5vc4.js +1 -0
  181. package/.next/standalone/.next/static/chunks/{1s-_swlc80y43.js → 3p4j0l1o_p-nm.js} +1 -1
  182. package/.next/standalone/.next/static/chunks/{0ip1n8xmgxarv.js → 3uxnxwxcctyj0.js} +1 -1
  183. package/.next/standalone/.next/static/chunks/{turbopack-1lsv92x7klwrn.js → turbopack-3diub4ctvn1n0.js} +1 -1
  184. package/.next/standalone/app/actions/get-active-pauses.ts +16 -0
  185. package/.next/standalone/app/actions/get-hooks-config.ts +2 -1
  186. package/.next/standalone/app/actions/get-scheduled-audit.ts +70 -0
  187. package/.next/standalone/app/actions/update-scheduled-audit.ts +44 -0
  188. package/.next/standalone/app/api/audit/run/route.ts +28 -0
  189. package/.next/standalone/app/api/audit/status/route.ts +13 -1
  190. package/.next/standalone/app/api/auth/status/route.ts +1 -1
  191. package/.next/standalone/app/audit/_components/auth-dialog.css +159 -0
  192. package/.next/standalone/app/audit/_components/auth-dialog.tsx +3 -0
  193. package/.next/standalone/app/audit/audit-styles.css +6 -149
  194. package/.next/standalone/app/components/pause-notices.tsx +111 -0
  195. package/.next/standalone/app/policies/hooks-client.tsx +81 -6
  196. package/.next/standalone/app/settings/page.tsx +31 -0
  197. package/.next/standalone/app/settings/settings-client.tsx +488 -0
  198. package/.next/standalone/components/navbar.tsx +2 -0
  199. package/.next/standalone/lib/auth/api-server-client.ts +2 -3
  200. package/.next/standalone/lib/auth/auth-store.ts +4 -4
  201. package/.next/standalone/lib/codex-sessions.ts +2 -1
  202. package/.next/standalone/lib/dashboard-host.ts +101 -0
  203. package/.next/standalone/lib/install-check.ts +22 -7
  204. package/.next/standalone/lib/pi-sessions.ts +99 -14
  205. package/.next/standalone/lib/telemetry-enabled.ts +54 -0
  206. package/.next/standalone/lib/telemetry-id.ts +52 -1
  207. package/.next/standalone/lib/telemetry.ts +8 -4
  208. package/.next/standalone/node_modules/react/cjs/react.development.js +1 -1
  209. package/.next/standalone/node_modules/react/cjs/react.production.js +1 -1
  210. package/.next/standalone/node_modules/react/package.json +3 -3
  211. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1 -1
  212. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1 -1
  213. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +3 -3
  214. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +3 -3
  215. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +3 -3
  216. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +1 -1
  217. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  218. package/.next/standalone/package.json +17 -10
  219. package/.next/standalone/proxy.ts +89 -1
  220. package/.next/standalone/server.js +1 -1
  221. package/bin/failproofai.mjs +518 -79
  222. package/dist/cli.mjs +11484 -6046
  223. package/dist/worker.mjs +2019 -272
  224. package/lib/auth/api-server-client.ts +2 -3
  225. package/lib/auth/auth-store.ts +4 -4
  226. package/lib/codex-sessions.ts +2 -1
  227. package/lib/dashboard-host.ts +101 -0
  228. package/lib/install-check.ts +22 -7
  229. package/lib/pi-sessions.ts +99 -14
  230. package/lib/telemetry-enabled.ts +54 -0
  231. package/lib/telemetry-id.ts +52 -1
  232. package/lib/telemetry.ts +8 -4
  233. package/package.json +17 -10
  234. package/scripts/build-daemon-packages.mjs +210 -0
  235. package/scripts/daemon-platforms.mjs +42 -0
  236. package/scripts/install-telemetry.mjs +8 -1
  237. package/scripts/launch.ts +25 -3
  238. package/scripts/parse-script-args.ts +17 -1
  239. package/scripts/prune-standalone.mjs +26 -0
  240. package/scripts/publish-aliases.mjs +62 -41
  241. package/scripts/repro-npm-install.sh +155 -0
  242. package/scripts/translate-docs/readme-translator.ts +163 -4
  243. package/scripts/validate-mdx.ts +153 -13
  244. package/src/audit/audit-lock.ts +285 -0
  245. package/src/audit/audit-schedule.ts +111 -0
  246. package/src/audit/cache.ts +18 -4
  247. package/src/audit/cli-adapters/goose.ts +2 -1
  248. package/src/audit/cli-adapters/hermes.ts +28 -10
  249. package/src/audit/cli.ts +191 -35
  250. package/src/audit/dashboard-cache.ts +2 -1
  251. package/src/hooks/backfill-cli.ts +247 -0
  252. package/src/hooks/builtin-policies.ts +390 -2
  253. package/src/hooks/cloud-connection.ts +354 -0
  254. package/src/hooks/cloud-enrollment-cli.ts +325 -0
  255. package/src/hooks/cloud-enrollment.ts +243 -0
  256. package/src/hooks/cloud-introspect.ts +147 -0
  257. package/src/hooks/cloud-managed-policies.ts +169 -0
  258. package/src/hooks/collector-config.ts +234 -0
  259. package/src/hooks/configure-wizard.ts +915 -158
  260. package/src/hooks/custom-hooks-loader.ts +191 -33
  261. package/src/hooks/daemon-client.ts +136 -17
  262. package/src/hooks/daemon-download.ts +192 -30
  263. package/src/hooks/daemon-service.ts +1190 -92
  264. package/src/hooks/first-run-gate.ts +50 -0
  265. package/src/hooks/fp-config.ts +497 -0
  266. package/src/hooks/fp-home.ts +375 -0
  267. package/src/hooks/fp-reset.ts +464 -0
  268. package/src/hooks/handler.ts +185 -6
  269. package/src/hooks/hook-activity-store.ts +85 -20
  270. package/src/hooks/hook-logger.ts +2 -1
  271. package/src/hooks/hook-telemetry.ts +5 -1
  272. package/src/hooks/hooks-config.ts +4 -3
  273. package/src/hooks/loader-utils.ts +171 -8
  274. package/src/hooks/manager.ts +5 -4
  275. package/src/hooks/onboarding-attempt.ts +179 -0
  276. package/src/hooks/onboarding-lock.ts +119 -0
  277. package/src/hooks/policy-types.ts +28 -0
  278. package/src/hooks/session-pause-cli.ts +159 -0
  279. package/src/hooks/session-pause.ts +268 -0
  280. package/src/hooks/setup-state.ts +256 -0
  281. package/src/hooks/tui.ts +119 -4
  282. package/src/hooks/uninstall-cli.ts +345 -0
  283. package/src/hooks/worker-server.ts +65 -3
  284. package/.next/standalone/.next/server/chunks/[externals]__14odj07._.js +0 -3
  285. package/.next/standalone/.next/server/chunks/[externals]__1s61mel._.js +0 -3
  286. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0id_xf7._.js +0 -3
  287. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0longh-._.js +0 -3
  288. package/.next/standalone/.next/server/chunks/[root-of-the-server]__17g9wh7._.js +0 -3
  289. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +0 -3
  290. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +0 -3
  291. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +0 -3
  292. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1rhmvod._.js +0 -3
  293. package/.next/standalone/.next/server/chunks/ssr/[externals]__1_g_b3t._.js +0 -3
  294. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0114ewg._.js +0 -4
  295. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0259h38._.js +0 -3
  296. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +0 -4
  297. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +0 -4
  298. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +0 -4
  299. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +0 -4
  300. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +0 -4
  301. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0uno39i._.js +0 -3
  302. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__14dd6h8._.js +0 -3
  303. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1ath6v_._.js +0 -4
  304. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zj-87g._.js +0 -3
  305. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +0 -4
  306. package/.next/standalone/.next/server/chunks/ssr/_1m_p7pi._.js +0 -3
  307. package/.next/standalone/.next/server/chunks/ssr/node_modules_html-to-image_es_index_1-upb14.js +0 -3
  308. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1ezd2jf._.js +0 -5
  309. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1tnuifj._.js +0 -5
  310. package/.next/standalone/.next/static/chunks/0h7auy7hzjyhw.js +0 -1
  311. package/.next/standalone/.next/static/chunks/0u9hc6oxx-gpj.js +0 -6
  312. package/.next/standalone/.next/static/chunks/0zbxssxh53n-3.js +0 -1
  313. package/.next/standalone/.next/static/chunks/2mazwb1qmur1p.js +0 -1
  314. package/.next/standalone/.next/static/chunks/2pujpnfx4pws6.css +0 -2
  315. package/.next/standalone/.next/static/chunks/33u59vf_8xpd-.js +0 -1
  316. package/.next/standalone/.next/static/chunks/3i27c3hcriawq.css +0 -1
  317. package/.next/standalone/.next/static/chunks/3wrgbih-516uj.js +0 -1
  318. package/.next/standalone/Cargo.lock +0 -428
  319. package/.next/standalone/Cargo.toml +0 -9
  320. package/.next/standalone/crates/PROTOCOL.md +0 -122
  321. package/.next/standalone/crates/failproofaid/Cargo.toml +0 -18
  322. package/.next/standalone/crates/failproofaid/src/lock.rs +0 -81
  323. package/.next/standalone/crates/failproofaid/src/main.rs +0 -83
  324. package/.next/standalone/crates/failproofaid/src/paths.rs +0 -172
  325. package/.next/standalone/crates/failproofaid/src/server.rs +0 -502
  326. package/.next/standalone/crates/failproofaid/src/worker.rs +0 -400
  327. package/.next/standalone/crates/failproofaid/tests/daemon_e2e.rs +0 -241
  328. package/.next/standalone/crates/fpai-ipc/Cargo.toml +0 -16
  329. package/.next/standalone/crates/fpai-ipc/src/envelope.rs +0 -175
  330. package/.next/standalone/crates/fpai-ipc/src/framing.rs +0 -177
  331. package/.next/standalone/crates/fpai-ipc/src/lib.rs +0 -10
  332. package/.next/standalone/crates/fpai-ipc/src/peer.rs +0 -80
  333. package/.next/standalone/docker-hook-sync/Dockerfile +0 -79
  334. package/.next/standalone/docker-hook-sync/entrypoint.sh +0 -109
  335. package/.next/standalone/integration-suite/Dockerfile +0 -47
  336. package/.next/standalone/integration-suite/README.md +0 -129
  337. package/.next/standalone/integration-suite/canary-policies.mjs +0 -45
  338. package/.next/standalone/integration-suite/capture-tokens.sh +0 -48
  339. package/.next/standalone/integration-suite/ci-entrypoint.sh +0 -179
  340. package/.next/standalone/integration-suite/inject-tokens.sh +0 -44
  341. package/.next/standalone/integration-suite/install-clis.sh +0 -136
  342. package/.next/standalone/integration-suite/probe-cli.sh +0 -248
  343. package/.next/standalone/integration-suite/report.js +0 -120
  344. package/.next/standalone/integration-suite/run.sh +0 -159
  345. package/.next/standalone/openclaw-plugin/index.js +0 -223
  346. package/.next/standalone/openclaw-plugin/openclaw.plugin.json +0 -6
  347. package/.next/standalone/openclaw-plugin/package.json +0 -12
  348. package/.next/standalone/osv-scanner.toml +0 -17
  349. package/.next/standalone/pi-extension/index.ts +0 -524
  350. package/.next/standalone/pi-extension/package.json +0 -12
  351. package/.next/standalone/postcss.config.mjs +0 -8
  352. package/.next/standalone/readme-arch-hq.gif +0 -0
  353. package/.next/standalone/rust-toolchain.toml +0 -4
  354. package/src/auth/cli.ts +0 -371
  355. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_buildManifest.js +0 -0
  356. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_clientMiddlewareManifest.js +0 -0
  357. /package/.next/standalone/.next/static/{7Lxtd352rSaHB-pmm6G8M → pBBcTvE6_VueYGbacy1kg}/_ssgManifest.js +0 -0
@@ -11,29 +11,58 @@
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
18
  import { homedir } from "node:os";
19
+ import { createHash } from "node:crypto";
17
20
  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";
21
+ import { customPolicies, getCustomHooks, clearCustomHooks } from "./custom-hooks-registry";
22
+ import {
23
+ findDistIndex,
24
+ rewriteFileTree,
25
+ TMP_SUFFIX,
26
+ cleanupTmpFiles,
27
+ isTmpArtifact,
28
+ sweepStaleTmpArtifacts,
29
+ } from "./loader-utils";
20
30
  import { findProjectConfigDir } from "./hooks-config";
21
31
  import { trackHookEvent } from "./hook-telemetry";
22
32
  import { getInstanceId } from "../../lib/telemetry-id";
23
33
  import type { CustomHook } from "./policy-types";
34
+ import type { CloudManagedPolicyArtifact } from "./cloud-managed-policies";
35
+ import { customPoliciesDir, shimsDir } from "./fp-home";
24
36
 
25
37
  const LOADING_KEY = "__FAILPROOFAI_LOADING_HOOKS__";
26
38
 
27
39
  /** Regex matching convention policy filenames: *policies.{js,mjs,ts} */
28
40
  const CONVENTION_FILE_RE = /policies\.(js|mjs|ts)$/;
29
41
 
42
+ /** Monotonic suffix for physically distinct temporary module trees. Bun
43
+ * ignores URL query strings when caching imports, so a query-only cache
44
+ * buster silently drops custom policies after the first request in the warm
45
+ * daemon worker. A unique filename is honored by both Bun and Node. */
46
+ let loadSequence = 0;
47
+
48
+ interface CachedPolicyModule {
49
+ fingerprint: string;
50
+ hooks: CustomHook[];
51
+ }
52
+
53
+ /** One retained module instance per source entry. Reusing its registered hook
54
+ * functions prevents an unbounded ESM module-cache leak in the resident worker.
55
+ * A source-tree change replaces this entry and imports one new module. */
56
+ const policyModuleCache = new Map<string, CachedPolicyModule>();
30
57
  /**
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.
58
+ * Cap, for the reason `gitBranchCache` states and this cache did not carry
59
+ * over: a warm worker touching many projects over its lifetime must not grow
60
+ * this unboundedly. Keyed by absolute policy-file path and holding cloned hook
61
+ * closures, so the entries are not small. Cleared wholesale rather than evicted
62
+ * one at a time — the next load simply re-imports, which is the same cost the
63
+ * cache exists to avoid paying twice, not a correctness change.
35
64
  */
36
- let loadSequence = 0;
65
+ const POLICY_MODULE_CACHE_MAX_ENTRIES = 500;
37
66
 
38
67
  /** Script extensions we could load, used to spot near-miss filenames. */
39
68
  const LOADABLE_EXT_RE = /\.(js|mjs|ts)$/;
@@ -76,7 +105,11 @@ export function findSkippedPolicyFiles(dir: string): string[] {
76
105
  e.isFile() &&
77
106
  LOADABLE_EXT_RE.test(e.name) &&
78
107
  !CONVENTION_FILE_RE.test(e.name) &&
79
- !e.name.endsWith(".d.ts"),
108
+ !e.name.endsWith(".d.ts") &&
109
+ // Never our own generated files. They are `.mjs` and never match the
110
+ // convention, so each one a killed load left behind was reported to
111
+ // the user as a policy file that would not load.
112
+ !isTmpArtifact(e.name),
80
113
  )
81
114
  .map((e) => e.name)
82
115
  .sort((a, b) => a.localeCompare(b));
@@ -85,13 +118,69 @@ export function findSkippedPolicyFiles(dir: string): string[] {
85
118
  }
86
119
  }
87
120
 
121
+ /**
122
+ * How long a policy module gets to finish evaluating its top level.
123
+ *
124
+ * Matches the 10s `handler.ts` gives a policy's `fn` to run, for the same
125
+ * reason: a policy file is user code, and user code that hangs must cost one
126
+ * skipped policy rather than the process.
127
+ *
128
+ * `FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS` shortens it for tests — a suite proving
129
+ * the deadline fires should not have to sit out the real one.
130
+ */
131
+ function moduleLoadTimeoutMs(): number {
132
+ const override = Number(process.env.FAILPROOFAI_POLICY_LOAD_TIMEOUT_MS);
133
+ return Number.isFinite(override) && override > 0 ? override : 10_000;
134
+ }
135
+
136
+ /**
137
+ * `import()`, but it cannot hang forever.
138
+ *
139
+ * A bare `await import(...)` on a module whose top level contains an `await`
140
+ * that never resolves never settles — it does not reject, so no `catch` and no
141
+ * `finally` anywhere above it ever runs. In the one-shot hook path that costs
142
+ * one hook process, which the agent CLI's own timeout reaps. In the warm daemon
143
+ * worker it holds `worker-server.ts`'s serialization chain forever, and every
144
+ * subsequent hook on the machine queues behind it and fail-closed denies.
145
+ *
146
+ * Losing the race does NOT cancel the import — nothing can. It stops us
147
+ * *waiting* on it, which is the part that matters, and the caller's `catch`
148
+ * below reports the file as failed to load. The abandoned module keeps its
149
+ * half-initialized entry in the ESM cache, but each load writes a uniquely
150
+ * named temporary tree, so a retry imports a different specifier and is never
151
+ * poisoned by it.
152
+ */
153
+ async function importWithDeadline(fileUrl: string): Promise<void> {
154
+ const budget = moduleLoadTimeoutMs();
155
+ let timer: ReturnType<typeof setTimeout> | undefined;
156
+ try {
157
+ await Promise.race([
158
+ import(/* webpackIgnore: true */ fileUrl),
159
+ new Promise<never>((_, reject) => {
160
+ timer = setTimeout(
161
+ () => reject(new Error(`timed out loading module after ${budget}ms`)),
162
+ budget,
163
+ );
164
+ timer.unref?.();
165
+ }),
166
+ ]);
167
+ } finally {
168
+ if (timer) clearTimeout(timer);
169
+ }
170
+ }
171
+
88
172
  /**
89
173
  * Load a single policy file into the globalThis custom hooks registry.
90
174
  * Does NOT clear the registry — caller is responsible for that.
91
175
  */
92
176
  async function loadSingleFile(
93
177
  absPath: string,
94
- opts?: { strict?: boolean; conventionScope?: "project" | "user" },
178
+ opts?: {
179
+ strict?: boolean;
180
+ conventionScope?: "project" | "user";
181
+ /** Cloud-managed policies pass their pinned digest for load-time re-verification. */
182
+ verifyEntrySha?: string;
183
+ },
95
184
  ): Promise<void> {
96
185
  const g = globalThis as Record<string, unknown>;
97
186
  g[LOADING_KEY] = true;
@@ -101,30 +190,48 @@ async function loadSingleFile(
101
190
  const distIndex = await findDistIndex();
102
191
  const distUrl = distIndex ? pathToFileURL(distIndex).href : null;
103
192
 
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);
193
+ const sequence = ++loadSequence;
194
+ // pid + sequence is not unique enough on its own: two containers sharing a
195
+ // mounted HOME, or two pid namespaces, can collide — and on the shim's
196
+ // fallback path a collision means EEXIST, which fails the load OPEN. A
197
+ // random id removes the class. It costs nothing: `fingerprintTemporaryTree`
198
+ // normalises the whole suffix away, so the module cache still hits.
199
+ const tmpSuffix = `${TMP_SUFFIX}.${process.pid}.${sequence}.${randomUUID()}.mjs`;
200
+ tmpFiles = await rewriteFileTree(
201
+ absPath,
202
+ distUrl,
203
+ distIndex,
204
+ tmpSuffix,
205
+ opts?.verifyEntrySha,
206
+ );
207
+
208
+ const fingerprint = await fingerprintTemporaryTree(tmpFiles, tmpSuffix);
209
+ const cached = policyModuleCache.get(absPath);
210
+ if (cached?.fingerprint === fingerprint) {
211
+ for (const hook of cached.hooks) customPolicies.add({ ...hook });
212
+ return;
213
+ }
214
+
215
+ const entryTmp = absPath + tmpSuffix;
216
+ const fileUrl = pathToFileURL(entryTmp).href;
217
+ const hooksBefore = getCustomHooks().length;
218
+ await importWithDeadline(fileUrl);
219
+ if (policyModuleCache.size >= POLICY_MODULE_CACHE_MAX_ENTRIES) policyModuleCache.clear();
220
+ policyModuleCache.set(absPath, {
221
+ fingerprint,
222
+ hooks: getCustomHooks()
223
+ .slice(hooksBefore)
224
+ .map((hook) => ({ ...hook })),
225
+ });
121
226
  } catch (err) {
122
227
  const msg = err instanceof Error ? err.message : String(err);
123
228
  const errorType = /Cannot find module|MODULE_NOT_FOUND|ENOENT/i.test(msg)
124
229
  ? "module_not_found"
125
230
  : /SyntaxError|Unexpected token/i.test(msg)
126
231
  ? "syntax_error"
127
- : "runtime_error";
232
+ : /timed out loading module/i.test(msg)
233
+ ? "load_timeout"
234
+ : "runtime_error";
128
235
  void trackHookEvent(getInstanceId(), "custom_hooks_load_error", {
129
236
  error_type: errorType,
130
237
  is_convention: !!opts?.conventionScope,
@@ -139,6 +246,18 @@ async function loadSingleFile(
139
246
  }
140
247
  }
141
248
 
249
+ async function fingerprintTemporaryTree(tmpFiles: string[], tmpSuffix: string): Promise<string> {
250
+ const hash = createHash("sha256");
251
+ for (const tmpFile of [...tmpFiles].sort()) {
252
+ // The generated filenames change per load. Normalize both the logical path
253
+ // and rewritten import specifiers so identical source graphs hash equally.
254
+ hash.update(tmpFile.replaceAll(tmpSuffix, TMP_SUFFIX));
255
+ const contents = await readFile(tmpFile, "utf8");
256
+ hash.update(contents.replaceAll(tmpSuffix, TMP_SUFFIX));
257
+ }
258
+ return hash.digest("hex");
259
+ }
260
+
142
261
  /**
143
262
  * Load a single explicit custom hooks file (legacy API).
144
263
  * Clears the registry, loads the file, returns registered hooks.
@@ -214,7 +333,11 @@ function warnSkippedPolicyFiles(dir: string, scope: "project" | "user"): void {
214
333
 
215
334
  export async function loadAllCustomHooks(
216
335
  customPoliciesPaths: string | string[] | undefined,
217
- opts?: { sessionCwd?: string; customPoliciesEnabled?: boolean },
336
+ opts?: {
337
+ sessionCwd?: string;
338
+ customPoliciesEnabled?: boolean;
339
+ cloudManagedPolicies?: CloudManagedPolicyArtifact[];
340
+ },
218
341
  ): Promise<LoadAllResult> {
219
342
  clearCustomHooks();
220
343
 
@@ -235,6 +358,9 @@ export async function loadAllCustomHooks(
235
358
  // twice per event. Seeded by the explicit path below and consulted by both
236
359
  // convention passes.
237
360
  const loadedPaths = new Set<string>();
361
+ const cloudManagedByPath = new Map(
362
+ (opts?.cloudManagedPolicies ?? []).map((policy) => [resolve(policy.path), policy]),
363
+ );
238
364
 
239
365
  // 1. Explicit custom policy paths. Accept a string for callers/configs using
240
366
  // the legacy singular form.
@@ -248,9 +374,23 @@ export async function loadAllCustomHooks(
248
374
  if (!loadedPaths.has(absPath)) {
249
375
  loadedPaths.add(absPath);
250
376
  const hooksBefore = getCustomHooks().length;
251
- await loadSingleFile(absPath);
377
+ // A cloud-managed policy re-verifies its pinned digest at load, binding
378
+ // the imported bytes to what desired-state promised.
379
+ await loadSingleFile(absPath, {
380
+ verifyEntrySha: cloudManagedByPath.get(absPath)?.sha256,
381
+ });
252
382
  for (const hook of getCustomHooks().slice(hooksBefore)) {
253
- (hook as CustomHook & { __policyId?: string }).__policyId = customPolicyId(absPath, hook.name);
383
+ const cloudManaged = cloudManagedByPath.get(absPath);
384
+ const tagged = hook as CustomHook & {
385
+ __policyId?: string;
386
+ __cloudManaged?: CloudManagedPolicyArtifact;
387
+ };
388
+ if (cloudManaged) {
389
+ tagged.__cloudManaged = cloudManaged;
390
+ tagged.__policyId = `cloud:${cloudManaged.id}@${cloudManaged.revision}:${hook.name}`;
391
+ } else {
392
+ tagged.__policyId = customPolicyId(absPath, hook.name);
393
+ }
254
394
  }
255
395
  }
256
396
  } else {
@@ -268,7 +408,22 @@ export async function loadAllCustomHooks(
268
408
  // exactly that — and it is discovered here as well. Skip what step 1 loaded,
269
409
  // or the file is imported twice and every hook in it fires twice per event.
270
410
  const projectDir = resolve(projectRoot, ".failproofai", "policies");
271
- if (conventionEnabled) warnSkippedPolicyFiles(projectDir, "project");
411
+ // Clear out generated files a killed load left behind, before anything scans
412
+ // this directory. The temporary tree has to be written beside the sources for
413
+ // a rewritten relative import to resolve, and its name now carries a pid and
414
+ // sequence number — so unlike the old fixed name, each abnormal termination
415
+ // leaks one file permanently rather than leaving one that the next load
416
+ // overwrites. Best-effort and age-gated, so it can never remove a tree
417
+ // another process is still importing.
418
+ if (conventionEnabled) {
419
+ void sweepStaleTmpArtifacts(projectDir);
420
+ warnSkippedPolicyFiles(projectDir, "project");
421
+ }
422
+ // The shim directory leaks the same way and is swept on the same terms. It is
423
+ // ours alone and outside the policy tree, so nothing else would ever reap it;
424
+ // the age gate is what keeps this from removing a shim another process is
425
+ // still importing.
426
+ void sweepStaleTmpArtifacts(shimsDir());
272
427
  const projectFiles = conventionEnabled
273
428
  ? discoverPolicyFiles(projectDir).filter((f) => !loadedPaths.has(f))
274
429
  : [];
@@ -303,8 +458,11 @@ export async function loadAllCustomHooks(
303
458
  // so the second import is a no-op — but Node honours the query and would
304
459
  // double-register. The binary runs under Bun and the tests under Node, so the
305
460
  // 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");
461
+ const userDir = customPoliciesDir();
462
+ if (conventionEnabled && userDir !== projectDir) {
463
+ void sweepStaleTmpArtifacts(userDir);
464
+ warnSkippedPolicyFiles(userDir, "user");
465
+ }
308
466
  const userFiles =
309
467
  conventionEnabled && userDir !== projectDir
310
468
  ? discoverPolicyFiles(userDir).filter((f) => !loadedPaths.has(f))
@@ -17,6 +17,9 @@ import { resolve } from "node:path";
17
17
  import { homedir } from "node:os";
18
18
  import type { IntegrationType } from "./types";
19
19
  import { readHooksConfig } from "./hooks-config";
20
+ import { existsSync } from "node:fs";
21
+ import { daemonSocket as daemonSocketPath } from "./fp-home";
22
+ import { readConfig } from "./fp-config";
20
23
 
21
24
  const PROTOCOL_VERSION = 1;
22
25
 
@@ -66,9 +69,82 @@ export interface DaemonHookResponse {
66
69
  stderr: string;
67
70
  }
68
71
 
72
+ /**
73
+ * Why an attempt failed — and it matters, because the two cases deserve
74
+ * opposite answers.
75
+ *
76
+ * `protocol-mismatch` means a daemon answered and is demonstrably HEALTHY; we
77
+ * simply cannot speak its wire format. The cause is known and benign: the CLI
78
+ * upgraded via npm and the daemon has not been reinstalled yet. Falling back to
79
+ * in-process evaluation there loses no enforcement at all — it runs the
80
+ * identical policy engine, just slower — so denying every tool call over it
81
+ * would take a working machine offline to protect nothing.
82
+ *
83
+ * `unreachable` means nothing answered. That could be a stopped service, a
84
+ * deleted socket, or tampering, and we cannot tell which — so it keeps failing
85
+ * closed, which is the whole point of `daemonConfigured`.
86
+ */
87
+ export type DaemonFailure = "unreachable" | "protocol-mismatch";
88
+
89
+ export type DaemonAttempt =
90
+ | { ok: true; response: DaemonHookResponse }
91
+ | { ok: false; failure: DaemonFailure };
92
+
93
+ /**
94
+ * Whether a daemon is listening, regardless of how it was started.
95
+ *
96
+ * Distinct from `daemonServiceStatus()`, which asks the service manager. A
97
+ * daemon run by hand — during development, or from a container — is invisible
98
+ * to that check but is very much running and pulling policy, so reporting "not
99
+ * installed, so nothing will be pulled" at someone whose machine is actively
100
+ * enforcing is simply false.
101
+ */
102
+ export function daemonSocketPresent(): boolean {
103
+ try {
104
+ return existsSync(socketPath());
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Whether the daemon socket accepts a connection *right now*.
112
+ *
113
+ * Narrower than `daemonSocketPresent()` (a stale socket file outlives the
114
+ * process that made it) and narrower than `attemptDaemonHook`, which reports
115
+ * `"unreachable"` for BOTH a refused connection and a request that was accepted
116
+ * and then never answered. Those two have different causes and different
117
+ * remedies — a socket that is not up yet versus a worker that cannot run — and
118
+ * telling them apart is the whole point of this function.
119
+ *
120
+ * Connect budget only; nothing is sent. Used by the health probe, never on the
121
+ * hook path.
122
+ */
123
+ export async function daemonAcceptsConnections(
124
+ timeoutMs = DAEMON_CONNECT_TIMEOUT_MS,
125
+ ): Promise<boolean> {
126
+ if (process.platform === "win32") return false;
127
+ return new Promise<boolean>((resolve) => {
128
+ let settled = false;
129
+ const done = (v: boolean) => {
130
+ if (settled) return;
131
+ settled = true;
132
+ clearTimeout(timer);
133
+ socket.removeAllListeners();
134
+ socket.destroy();
135
+ resolve(v);
136
+ };
137
+ const socket = createConnection({ path: socketPath() });
138
+ const timer = setTimeout(() => done(false), timeoutMs);
139
+ timer.unref?.();
140
+ socket.on("connect", () => done(true));
141
+ socket.on("error", () => done(false));
142
+ });
143
+ }
144
+
69
145
  function socketPath(): string {
70
146
  if (process.env.FAILPROOFAI_DAEMON_SOCKET) return process.env.FAILPROOFAI_DAEMON_SOCKET;
71
- return resolve(homedir(), ".failproofai", "run", "failproofaid.sock");
147
+ return daemonSocketPath();
72
148
  }
73
149
 
74
150
  /**
@@ -81,8 +157,11 @@ function socketPath(): string {
81
157
  */
82
158
  export function isDaemonConfigured(): boolean {
83
159
  try {
84
- return readHooksConfig().daemonConfigured === true;
160
+ return readConfig().daemon.configured === true;
85
161
  } catch {
162
+ // Unreadable config reads as NOT daemon-configured. The failure direction
163
+ // is deliberate: the alternative is a machine that fails closed on every
164
+ // tool call because a config file got truncated.
86
165
  return false;
87
166
  }
88
167
  }
@@ -102,24 +181,52 @@ function encodeFrame(value: unknown): Buffer {
102
181
  * falls through to whatever its own fallback policy is.
103
182
  */
104
183
  export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookResponse | null> {
184
+ const attempt = await attemptDaemonHook(req);
185
+ return attempt.ok ? attempt.response : null;
186
+ }
187
+
188
+ /**
189
+ * The same attempt, but reporting WHY it failed.
190
+ *
191
+ * `tryDaemonHook` above collapses every failure into `null`, which was fine
192
+ * while the caller had one answer for all of them. It no longer does: a
193
+ * protocol mismatch must fall back rather than deny (see `DaemonFailure`), and
194
+ * a caller cannot make that distinction from `null`.
195
+ */
196
+ export async function attemptDaemonHook(
197
+ req: DaemonHookRequest,
198
+ opts?: {
199
+ /**
200
+ * Override the RESPONSE budget only — the connect budget is never
201
+ * relaxed. Used by the health probe (`probeDaemonEndToEnd`), which runs
202
+ * from an interactive command rather than a hook and must not make a
203
+ * person wait out the full 30s hook budget to be told their daemon is
204
+ * broken. Never set this on the hook path: 30s is matched to the
205
+ * daemon's own read timeout so this side never gives up on a request the
206
+ * daemon is still honestly working on.
207
+ */
208
+ responseTimeoutMs?: number;
209
+ },
210
+ ): Promise<DaemonAttempt> {
105
211
  // Windows never has a daemon in this phase (see the plan's platform
106
212
  // scope) — skip the attempt outright rather than depending on however
107
213
  // Node happens to behave when handed a POSIX socket path on Windows.
108
- if (process.platform === "win32") return null;
214
+ if (process.platform === "win32") return { ok: false, failure: "unreachable" };
215
+ const responseBudget = opts?.responseTimeoutMs ?? DAEMON_RESPONSE_TIMEOUT_MS;
109
216
 
110
- return new Promise<DaemonHookResponse | null>((resolvePromise) => {
217
+ return new Promise<DaemonAttempt>((resolvePromise) => {
111
218
  let settled = false;
112
219
  let timer: ReturnType<typeof setTimeout>;
113
220
  const arm = (ms: number) => {
114
221
  clearTimeout(timer);
115
- timer = setTimeout(() => finish(null), ms);
222
+ timer = setTimeout(() => fail("unreachable"), ms);
116
223
  // Don't let this timer alone keep the process alive if everything else
117
224
  // has already finished — it's always cleared on the success/failure
118
225
  // paths below, this just avoids it being the sole reason a --hook
119
226
  // process lingers if something upstream forgets to await us.
120
227
  timer.unref?.();
121
228
  };
122
- const finish = (result: DaemonHookResponse | null) => {
229
+ const finish = (result: DaemonAttempt) => {
123
230
  if (settled) return;
124
231
  settled = true;
125
232
  clearTimeout(timer);
@@ -127,6 +234,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
127
234
  socket.destroy();
128
235
  resolvePromise(result);
129
236
  };
237
+ const fail = (failure: DaemonFailure) => finish({ ok: false, failure });
130
238
 
131
239
  const socket = createConnection({ path: socketPath() });
132
240
  arm(DAEMON_CONNECT_TIMEOUT_MS);
@@ -137,7 +245,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
137
245
  socket.on("connect", () => {
138
246
  // Connected: the daemon is demonstrably reachable, so the question is
139
247
  // no longer "is it there" but "how long does this evaluation take".
140
- arm(DAEMON_RESPONSE_TIMEOUT_MS);
248
+ arm(responseBudget);
141
249
  socket.write(
142
250
  encodeFrame({
143
251
  type: "hook",
@@ -157,7 +265,7 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
157
265
  if (recvBuf.length < 4) return;
158
266
  declaredLen = recvBuf.readUInt32BE(0);
159
267
  if (declaredLen > MAX_FRAME_LEN) {
160
- finish(null);
268
+ fail("unreachable");
161
269
  return;
162
270
  }
163
271
  recvBuf = recvBuf.subarray(4);
@@ -170,12 +278,16 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
170
278
  try {
171
279
  message = JSON.parse(body.toString("utf8")) as Record<string, unknown>;
172
280
  } catch {
173
- finish(null);
281
+ fail("unreachable");
174
282
  return;
175
283
  }
176
284
 
177
285
  if (message.protocolVersion !== PROTOCOL_VERSION) {
178
- finish(null);
286
+ // Catches BOTH directions. A newer CLI sends v2 and the daemon replies
287
+ // with an error stamped v1; an older CLI sends v1 and a newer daemon
288
+ // replies stamped v2. Either way the versions disagree here, and either
289
+ // way a daemon answered — so this is skew, not absence.
290
+ fail("protocol-mismatch");
179
291
  return;
180
292
  }
181
293
  if (
@@ -184,16 +296,23 @@ export async function tryDaemonHook(req: DaemonHookRequest): Promise<DaemonHookR
184
296
  typeof message.stdout === "string" &&
185
297
  typeof message.stderr === "string"
186
298
  ) {
187
- finish({ exitCode: message.exitCode, stdout: message.stdout, stderr: message.stderr });
299
+ finish({
300
+ ok: true,
301
+ response: {
302
+ exitCode: message.exitCode,
303
+ stdout: message.stdout,
304
+ stderr: message.stderr,
305
+ },
306
+ });
188
307
  return;
189
308
  }
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);
309
+ // Anything else — an explicit `error` message at a MATCHING protocol
310
+ // version, a `pong` (protocol confusion), or a well-formed-but-wrong-shape
311
+ // body — is treated identically to a connection failure: no partial trust.
312
+ fail("unreachable");
194
313
  });
195
314
 
196
- socket.on("error", () => finish(null));
197
- socket.on("close", () => finish(null));
315
+ socket.on("error", () => fail("unreachable"));
316
+ socket.on("close", () => fail("unreachable"));
198
317
  });
199
318
  }