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
@@ -25,15 +25,22 @@
25
25
  * `findPageError`), so this net is a strict superset of `mintlify validate`:
26
26
  * it catches both the frontmatter YAML class that fails `mintlify validate`
27
27
  * and the body-MDX class that `mintlify validate` lets through to deploy.
28
+ *
29
+ * It also covers a third class neither tool sees: image references whose target
30
+ * does not exist (`findBrokenAssetRefs`). Those are valid MDX and valid YAML —
31
+ * nothing errors, the reader just gets a broken image — which is how all 14
32
+ * translated READMEs shipped with every logo and the architecture GIF broken.
33
+ * See that function for the mechanism.
28
34
  */
29
- import { readdirSync, statSync, readFileSync } from "node:fs";
30
- import { dirname, join, relative } from "node:path";
35
+ import { readdirSync, statSync, readFileSync, existsSync } from "node:fs";
36
+ import { dirname, join, relative, resolve, posix } from "node:path";
31
37
  import { fileURLToPath } from "node:url";
32
38
  import { compile } from "@mdx-js/mdx";
33
39
  import YAML from "yaml";
34
40
 
35
41
  const __dirname = dirname(fileURLToPath(import.meta.url));
36
- const DOCS_DIR = join(__dirname, "..", "docs");
42
+ const ROOT_DIR = join(__dirname, "..");
43
+ const DOCS_DIR = join(ROOT_DIR, "docs");
37
44
 
38
45
  export interface MdxParseError {
39
46
  message: string;
@@ -178,34 +185,167 @@ export function collectMdxFiles(dir: string): string[] {
178
185
  return out;
179
186
  }
180
187
 
188
+ /** File extensions treated as bundled media rather than a link to a page. */
189
+ const ASSET_RE = /\.(?:png|jpe?g|gif|svg|webp|ico|mp4|webm)$/i;
190
+
191
+ export interface BrokenAssetRef {
192
+ /** The reference exactly as written in the page. */
193
+ ref: string;
194
+ /** Repo-relative path the reference resolves to, which does not exist. */
195
+ resolved: string;
196
+ /** 1-based line the reference appears on. */
197
+ line: number;
198
+ }
199
+
200
+ /**
201
+ * Report every local image reference on a page whose target does not exist on
202
+ * disk.
203
+ *
204
+ * Why this is a separate net from the MDX parse above: a broken image path is
205
+ * perfectly valid MDX and perfectly valid YAML, so `mintlify validate` and
206
+ * `findPageError` both pass it — it only surfaces as a missing image in a
207
+ * reader's browser, which nothing in CI was watching. That is exactly how all
208
+ * 14 translated READMEs shipped with every logo and the architecture GIF
209
+ * broken: the translator faithfully copies the root README's repo-root-relative
210
+ * `assets/logos/*.svg` into `docs/i18n/`, two directories deeper, where they
211
+ * resolve to nothing (GitHub 404, Mintlify S3 403). The auto-translation
212
+ * workflow regenerates these pages unattended, so only a deterministic check
213
+ * keeps that class from coming back.
214
+ *
215
+ * Resolution follows the two conventions in this repo:
216
+ * - A leading `/` is Mintlify site-absolute → resolve against `docs/`
217
+ * (`/agenteye/images/x.png` → `docs/agenteye/images/x.png`).
218
+ * - Anything else is relative to the page's own directory, the way GitHub and
219
+ * Mintlify both resolve it.
220
+ *
221
+ * Skipped: absolute URLs and any other scheme (`https:`, `mailto:`, `data:`),
222
+ * protocol-relative `//`, bare anchors, and non-asset references — a link to a
223
+ * `.md`/`.mdx` page or a bare doc slug is nav, already covered by
224
+ * `mintlify validate`, and would false-positive on extensionless Mintlify
225
+ * routes.
226
+ */
227
+ export function findBrokenAssetRefs(
228
+ file: string,
229
+ source: string,
230
+ ): BrokenAssetRef[] {
231
+ const pageDir = dirname(file);
232
+ const out: BrokenAssetRef[] = [];
233
+ const seen = new Set<string>();
234
+
235
+ const lines = source.split("\n");
236
+ for (let i = 0; i < lines.length; i++) {
237
+ const refs = [
238
+ ...lines[i].matchAll(/\]\(([^)\s]+)/g),
239
+ ...lines[i].matchAll(/(?:src|href)=["']([^"']+)["']/g),
240
+ ].map((m) => m[1]);
241
+
242
+ // `srcset` carries a comma-separated candidate list, each optionally
243
+ // followed by a density/width descriptor (`logo.svg 2x`). The README's
244
+ // logo table pairs every <img src> with a dark-mode <source srcset>, so
245
+ // extracting only `src` would pass a table half-broken — and only for
246
+ // dark-theme readers, the half least likely to be caught by eye.
247
+ for (const m of lines[i].matchAll(/srcset=["']([^"']+)["']/g)) {
248
+ for (const candidate of m[1].split(",")) {
249
+ const url = candidate.trim().split(/\s+/)[0];
250
+ if (url) refs.push(url);
251
+ }
252
+ }
253
+
254
+ for (const ref of refs) {
255
+ if (/^[a-z][a-z0-9+.-]*:/i.test(ref)) continue; // https:, mailto:, data:
256
+ if (ref.startsWith("//") || ref.startsWith("#")) continue;
257
+ // Strip the query/fragment before testing the extension so a versioned
258
+ // `x.png?v=2` is still recognised as an asset.
259
+ const path = ref.split(/[?#]/)[0];
260
+ if (!ASSET_RE.test(path)) continue;
261
+
262
+ const target = path.startsWith("/")
263
+ ? join(DOCS_DIR, path.slice(1))
264
+ : resolve(pageDir, path);
265
+ if (existsSync(target)) continue;
266
+
267
+ const key = `${i}:${ref}`;
268
+ if (seen.has(key)) continue;
269
+ seen.add(key);
270
+ out.push({
271
+ ref,
272
+ resolved: relative(ROOT_DIR, target).split(/[\\/]/).join(posix.sep),
273
+ line: i + 1,
274
+ });
275
+ }
276
+ }
277
+ return out;
278
+ }
279
+
181
280
  async function main(): Promise<void> {
182
281
  const files = collectMdxFiles(DOCS_DIR).sort();
183
- const failures: Array<{ file: string; error: MdxParseError }> = [];
282
+ const failures: Array<{
283
+ file: string;
284
+ error: MdxParseError;
285
+ kind: "MDX parse error" | "Broken image";
286
+ }> = [];
287
+
288
+ const collectAssetFailures = (file: string, source: string): void => {
289
+ for (const broken of findBrokenAssetRefs(file, source)) {
290
+ failures.push({
291
+ file: relative(process.cwd(), file),
292
+ kind: "Broken image",
293
+ error: {
294
+ message:
295
+ `Image \`${broken.ref}\` does not exist — it resolves to ` +
296
+ `\`${broken.resolved}\`. Paths are relative to the page's own ` +
297
+ "directory (a leading `/` is relative to `docs/`), so a path " +
298
+ "copied from a file at a different depth has to be re-pointed.",
299
+ line: broken.line,
300
+ },
301
+ });
302
+ }
303
+ };
184
304
 
185
305
  for (const file of files) {
186
- const error = await findPageError(readFileSync(file, "utf-8"));
187
- if (error) failures.push({ file: relative(process.cwd(), file), error });
306
+ const source = readFileSync(file, "utf-8");
307
+ const error = await findPageError(source);
308
+ if (error)
309
+ failures.push({
310
+ file: relative(process.cwd(), file),
311
+ error,
312
+ kind: "MDX parse error",
313
+ });
314
+
315
+ // A page can carry a broken image and still parse, so collect these
316
+ // independently rather than only when the parse succeeded.
317
+ collectAssetFailures(file, source);
318
+ }
319
+
320
+ // The root README is not a docs page (it is GitHub-only, and its HTML
321
+ // comments are illegal MDX, so findPageError would reject it) — but it IS
322
+ // the source every docs/i18n/README.<lang>.md is translated from. A bad
323
+ // image path here propagates into 14 files as an absolute raw URL, which
324
+ // findBrokenAssetRefs deliberately does not follow. Check it at the source.
325
+ const rootReadme = join(ROOT_DIR, "README.md");
326
+ if (existsSync(rootReadme)) {
327
+ collectAssetFailures(rootReadme, readFileSync(rootReadme, "utf-8"));
188
328
  }
189
329
 
190
330
  if (failures.length === 0) {
191
- console.log(`✓ ${files.length} MDX page(s) parsed cleanly`);
331
+ console.log(
332
+ `✓ ${files.length} MDX page(s) parsed cleanly with no broken images`,
333
+ );
192
334
  return;
193
335
  }
194
336
 
195
- console.error(
196
- `✗ ${failures.length} of ${files.length} MDX page(s) failed to parse:\n`,
197
- );
198
- for (const { file, error } of failures) {
337
+ console.error(`✗ ${failures.length} problem(s) in ${files.length} page(s):\n`);
338
+ for (const { file, error, kind } of failures) {
199
339
  const pos = error.line
200
340
  ? `:${error.line}${error.column ? `:${error.column}` : ""}`
201
341
  : "";
202
- console.error(` ${file}${pos}\n ${error.message}\n`);
342
+ console.error(` ${file}${pos}\n ${kind}: ${error.message}\n`);
203
343
  // GitHub Actions inline annotation.
204
344
  const loc =
205
345
  (error.line ? `,line=${error.line}` : "") +
206
346
  (error.column ? `,col=${error.column}` : "");
207
347
  console.log(
208
- `::error file=${encodeAnnotation(file)}${loc}::MDX parse error: ${encodeAnnotation(error.message)}`,
348
+ `::error file=${encodeAnnotation(file)}${loc}::${kind}: ${encodeAnnotation(error.message)}`,
209
349
  );
210
350
  }
211
351
  process.exitCode = 1;
@@ -0,0 +1,285 @@
1
+ /**
2
+ * One audit at a time on this machine, across processes.
3
+ *
4
+ * Three entry points can start a scan — a scheduled run, an explicit
5
+ * `failproofai audit`, and the dashboard's `/api/audit/run` — and every one of
6
+ * them writes the same sha1-keyed per-transcript cache files and the same
7
+ * single-slot `audit/dashboard.json`. The only lock that existed before this
8
+ * was `app/api/audit/_state.ts`, a module-level singleton inside the Next.js
9
+ * server: invisible to the other two, and by its own admission invisible to a
10
+ * second Next.js worker as well.
11
+ *
12
+ * ## Why a lockfile and not a flock
13
+ *
14
+ * `flock()` releases itself when the holder dies, which is exactly the property
15
+ * we would want — but it needs a held file descriptor, and the interactive path
16
+ * hands off to `launch()` and lives on for as long as the user leaves the
17
+ * dashboard open. A lockfile whose contents name the holder can be released at
18
+ * the end of the SCAN, which is the thing being serialised, and it can be
19
+ * inspected by a human wondering why their audit refused to start. The cost is
20
+ * that a crash leaves the file behind, which the stale rules below cover.
21
+ *
22
+ * ## The two stale rules
23
+ *
24
+ * **A dead pid** is the primary one and the one that fires in practice:
25
+ * Ctrl+C, SIGKILL and a laptop lid are all "the lock is on disk, the process is
26
+ * not". **Age** is the backstop for the cases a pid check cannot see — a
27
+ * recycled pid, or a lock written by a machine that shares this home over a
28
+ * network filesystem.
29
+ */
30
+ import {
31
+ closeSync,
32
+ linkSync,
33
+ mkdirSync,
34
+ openSync,
35
+ readFileSync,
36
+ unlinkSync,
37
+ writeFileSync,
38
+ writeSync,
39
+ } from "node:fs";
40
+ import { auditLockFile, failproofaiHome, runDir } from "../hooks/fp-home";
41
+
42
+ /**
43
+ * How old a lock has to be before its holder is presumed gone even though the
44
+ * pid still resolves.
45
+ *
46
+ * A full scan measured ~104 seconds over 3,277 transcripts on a development
47
+ * machine with a warm cache. The ceiling has to clear the worst plausible
48
+ * HONEST run by a wide margin, because stealing from a live audit is the one
49
+ * outcome this module exists to prevent — it puts two writers on the same cache
50
+ * files. A cold cache, a much longer history, a spinning disk, or the `nice(19)`
51
+ * scheduled run competing with a busy laptop can each cost an order of
52
+ * magnitude, so: one hour, roughly 35× the measured run. Nothing is lost by
53
+ * being generous here, since the dead-pid rule already collects every crash.
54
+ */
55
+ export const AUDIT_LOCK_MAX_AGE_MS = 60 * 60_000;
56
+
57
+ /**
58
+ * Which entry point holds the lock. Recorded so a refusal can say who it lost
59
+ * to. All four are wired: `cli` and `scheduled` in `src/audit/cli.ts`,
60
+ * `dashboard` in `app/api/audit/run/route.ts`, and `onboarding` in
61
+ * `runPostSetupAudit` — every process that writes the shared cache takes this
62
+ * lock, so no two of them can co-write it.
63
+ */
64
+ export type AuditLockSource = "cli" | "scheduled" | "dashboard" | "onboarding";
65
+
66
+ export interface AuditLockInfo {
67
+ pid: number;
68
+ /** Wall-clock ms. Monotonic time would reset every process start. */
69
+ startedAt: number;
70
+ source: AuditLockSource;
71
+ }
72
+
73
+ export interface AuditLockHandle {
74
+ info: AuditLockInfo;
75
+ /** Idempotent, never throws, and only ever removes a lock this handle owns. */
76
+ release(): void;
77
+ }
78
+
79
+ export type AcquireAuditLockResult =
80
+ | { ok: true; lock: AuditLockHandle }
81
+ /** `heldBy` is null when the holder's lock could not be read at all. */
82
+ | { ok: false; heldBy: AuditLockInfo | null };
83
+
84
+ /**
85
+ * Take the lock, stealing a provably-abandoned one.
86
+ *
87
+ * Never throws: a caller deciding whether to run an audit must not have to
88
+ * handle an exception from the thing that decides.
89
+ */
90
+ export function acquireAuditLock(source: AuditLockSource, now: number = Date.now()): AcquireAuditLockResult {
91
+ const path = auditLockFile();
92
+ const info: AuditLockInfo = { pid: process.pid, startedAt: now, source };
93
+
94
+ try {
95
+ ensureRunDir();
96
+ } catch {
97
+ // We could not even create the directory the lock lives in, so we cannot
98
+ // prove exclusivity. Report contention rather than running: a refused audit
99
+ // is a retry, whereas two unserialised ones corrupt a shared cache.
100
+ return { ok: false, heldBy: null };
101
+ }
102
+
103
+ if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
104
+
105
+ const held = readLock(path);
106
+ if (!isStale(held, now)) return { ok: false, heldBy: held };
107
+
108
+ try {
109
+ unlinkSync(path);
110
+ } catch {
111
+ // Already gone — another process judged it stale at the same moment. The
112
+ // O_EXCL retry below is what decides between us.
113
+ }
114
+ // Exactly one retry, deliberately. Two processes that both called the lock
115
+ // stale race here and O_EXCL picks one; looping would let the loser keep
116
+ // going until it stole the WINNER's brand-new lock.
117
+ if (writeLockExclusive(path, info)) return { ok: true, lock: handleFor(path, info) };
118
+ return { ok: false, heldBy: readLock(path) };
119
+ }
120
+
121
+ /** Whoever holds the lock right now, or null if it is free/unreadable. */
122
+ export function readAuditLock(): AuditLockInfo | null {
123
+ return readLock(auditLockFile());
124
+ }
125
+
126
+ /**
127
+ * The holder only when its lock is still ACTIVE — holder alive and not aged
128
+ * out; null when the lock is free, unreadable, or provably abandoned.
129
+ *
130
+ * For status/UI, not for acquiring: a settings page or `/api/audit/status`
131
+ * asking "is a scan running somewhere on this machine right now?" must answer
132
+ * off the same staleness rules `acquireAuditLock` uses, or it would report
133
+ * "running" forever off a crashed run's leftover lockfile — a dead pid or an
134
+ * hour-old file the next real acquire would rightly steal. Read-only: unlike
135
+ * `acquireAuditLock` it never unlinks or steals, so it is safe to call from a
136
+ * request handler that only wants to describe the machine's state.
137
+ */
138
+ export function readActiveAuditLock(now: number = Date.now()): AuditLockInfo | null {
139
+ const held = readLock(auditLockFile());
140
+ return isStale(held, now) ? null : held;
141
+ }
142
+
143
+ function isStale(held: AuditLockInfo | null, now: number): boolean {
144
+ // EEXIST with nothing parseable inside it: either a truncated write or a file
145
+ // from a version that wrote a different shape. Neither names a holder we
146
+ // could ever wait for.
147
+ if (!held) return true;
148
+ if (!isProcessAlive(held.pid)) return true;
149
+ // A startedAt in the future (clock skew, a shared home across machines) reads
150
+ // as fresh, not as stale — the subtraction goes negative and the comparison
151
+ // fails, which is the direction that does not steal from a live run.
152
+ return now - held.startedAt > AUDIT_LOCK_MAX_AGE_MS;
153
+ }
154
+
155
+ function isProcessAlive(pid: number): boolean {
156
+ if (!Number.isInteger(pid) || pid <= 0) return false;
157
+ try {
158
+ process.kill(pid, 0);
159
+ return true;
160
+ } catch (err) {
161
+ // EPERM means the process exists and belongs to somebody else — two OS
162
+ // users on one box, or a shared home. Alive is the safe reading of that.
163
+ return (err as NodeJS.ErrnoException).code === "EPERM";
164
+ }
165
+ }
166
+
167
+ function handleFor(path: string, info: AuditLockInfo): AuditLockHandle {
168
+ let released = false;
169
+ const release = (): void => {
170
+ if (released) return;
171
+ released = true;
172
+ process.removeListener("exit", release);
173
+ // Only ever remove OUR lock. After a steal the file on disk can already
174
+ // belong to a third process, and deleting that one would hand a live audit
175
+ // the co-writer this whole module exists to prevent.
176
+ const current = readLock(path);
177
+ if (current && current.pid !== info.pid) return;
178
+ try {
179
+ unlinkSync(path);
180
+ } catch {
181
+ /* already gone */
182
+ }
183
+ };
184
+
185
+ // `process.exit()` does not unwind, so a `finally` cannot be relied on here:
186
+ // `failproofai audit` exits directly on both its failure and its
187
+ // empty-history path. An exit hook covers those, a normal return, and an
188
+ // uncaught throw. It does NOT run when the default SIGINT disposition kills
189
+ // the process — Ctrl+C during a scan is precisely what the dead-pid rule is
190
+ // there for.
191
+ process.once("exit", release);
192
+ return { info, release };
193
+ }
194
+
195
+ function readLock(path: string): AuditLockInfo | null {
196
+ try {
197
+ const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<AuditLockInfo>;
198
+ if (typeof parsed?.pid !== "number" || typeof parsed?.startedAt !== "number") return null;
199
+ return {
200
+ pid: parsed.pid,
201
+ startedAt: parsed.startedAt,
202
+ source: (parsed.source ?? "cli") as AuditLockSource,
203
+ };
204
+ } catch {
205
+ return null;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * True when this call published the lock; false when somebody else already had
211
+ * it (or the write failed for any other reason, which is equally "not ours").
212
+ *
213
+ * Written to a staging file and `link()`ed into place rather than created with
214
+ * `O_EXCL` and then written. Both are atomic about who WINS, but only this one
215
+ * is atomic about what the winner's file CONTAINS: `open(O_EXCL)` publishes the
216
+ * final name while the file is still empty, and an empty lock is an unreadable
217
+ * lock, which `isStale` — correctly — calls abandoned. A competitor landing in
218
+ * that window steals the lock from a holder that is mid-acquire and both
219
+ * processes then scan at once, which is the two-writers-on-one-cache outcome
220
+ * this whole module exists to prevent. `link()` makes the name appear only once
221
+ * the bytes behind it are already there. (It is also the idiom that survives a
222
+ * home shared over NFS, where `O_EXCL` has historically not been atomic at all
223
+ * — one of the cases the age rule above is a backstop for.)
224
+ */
225
+ function writeLockExclusive(path: string, info: AuditLockInfo): boolean {
226
+ const body = JSON.stringify(info);
227
+ // Same directory, so `link()` stays within one filesystem; keyed by pid so
228
+ // two processes staging at once cannot overwrite each other's bytes.
229
+ const staging = `${path}.${process.pid}.tmp`;
230
+ try {
231
+ writeFileSync(staging, body, { mode: 0o600 });
232
+ } catch {
233
+ return false;
234
+ }
235
+ try {
236
+ linkSync(staging, path);
237
+ return true;
238
+ } catch (err) {
239
+ // EEXIST is the answer we asked the kernel for: somebody else holds it.
240
+ // Anything else means link() itself is unavailable — a filesystem with no
241
+ // hard links — and refusing there would mean the audit can never run on
242
+ // that machine at all, so fall back to the create-then-write form.
243
+ if ((err as NodeJS.ErrnoException).code === "EEXIST") return false;
244
+ return createThenWrite(path, body);
245
+ } finally {
246
+ try {
247
+ unlinkSync(staging);
248
+ } catch {
249
+ /* the link (or the failure) is what mattered; a leftover staging file is not */
250
+ }
251
+ }
252
+ }
253
+
254
+ /** The `O_EXCL` form, kept only as the no-hard-links fallback above. */
255
+ function createThenWrite(path: string, body: string): boolean {
256
+ let fd: number | undefined;
257
+ try {
258
+ fd = openSync(path, "wx", 0o600);
259
+ writeSync(fd, body);
260
+ return true;
261
+ } catch {
262
+ return false;
263
+ } finally {
264
+ if (fd !== undefined) {
265
+ try {
266
+ closeSync(fd);
267
+ } catch {
268
+ /* nothing useful to do with a failed close on a lockfile */
269
+ }
270
+ }
271
+ }
272
+ }
273
+
274
+ function ensureRunDir(): void {
275
+ // The home first and at the default mode: `run/` below is created 0700, and
276
+ // letting a recursive mkdir apply that to `~/.failproofai` itself would hide
277
+ // config.toml, which is world-readable by design.
278
+ mkdirSync(failproofaiHome(), { recursive: true });
279
+ // 0700 explicitly, because failproofaid REFUSES TO START when run/ exists
280
+ // with any other mode (`ensure_run_dir` in crates/failproofaid/src/paths.rs).
281
+ // A lock file that created this directory under the ambient umask would
282
+ // therefore stop the daemon coming up — and on a daemon-configured machine a
283
+ // daemon that cannot start denies every tool call across all 12 CLIs.
284
+ mkdirSync(runDir(), { recursive: true, mode: 0o700 });
285
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The first (and only) TypeScript reader of `~/.failproofai/state/audit-schedule.json`.
3
+ *
4
+ * The **daemon is the sole writer** of that file (`crates/failproofaid/src/
5
+ * audit_lane.rs`); everything on this side only reads it. This module is that
6
+ * read side: `getScheduledAuditAction` calls it to answer "when did the last
7
+ * scheduled scan run, and when is the next one due?" on the /settings page.
8
+ *
9
+ * ## Why this tolerates a file it did not write
10
+ *
11
+ * The daemon and the CLI ship and upgrade independently — a machine can be
12
+ * running a newer `failproofaid` than its `failproofai`, so the schedule file
13
+ * on disk can be a schema *ahead* of this build. A settings page that threw, or
14
+ * blanked its whole scheduled-audit section, because one derived state file was
15
+ * a version in front would be strictly worse than showing slightly less. So we
16
+ * read every field **by name, independent of `schema`**: a future file that
17
+ * still carries `next_due_at_ms` still renders a next-due time. Absent,
18
+ * unreadable, malformed, and non-object all resolve to `null` (the caller shows
19
+ * "no scheduled scan yet"); a recognised-but-unknown-higher schema still yields
20
+ * whatever numeric fields it shares, plus `schemaAhead` so the UI can say "a
21
+ * newer daemon wrote this" rather than silently dropping a field.
22
+ *
23
+ * ## What happened to `next-audit.json` — the one open sub-decision (resolved)
24
+ *
25
+ * `~/.failproofai/next-audit.json` (`lib/auth/auth-store.ts`
26
+ * `readReminder`/`writeReminder`) predates the scheduler and looks like a second
27
+ * source of truth for "the next audit". It is **not**, and after this change
28
+ * there is exactly one answer to "when does the next *scan* run": this file's
29
+ * `next_due_at_ms`. The two are kept **separate with deliberately distinct
30
+ * meanings**, because they answer different questions:
31
+ *
32
+ * - `audit-schedule.json` (here) = when the machine will next **run a scan
33
+ * itself**, headless, via the daemon. This is the scan schedule. It needs
34
+ * no human and no sign-in. `[audit] auto` + `interval_days` in `config.toml`
35
+ * drive it; the daemon derives and writes this file.
36
+ * - `next-audit.json` = an **email nudge to a signed-in human** ("come back
37
+ * and audit again"), set from the /audit "come back better" section,
38
+ * delivered by Failproof Cloud over SES via the api-server. It runs nothing
39
+ * locally; it is re-engagement, not a schedule.
40
+ *
41
+ * Collapsing them would merge "the box scans on a timer" with "email this person
42
+ * a reminder", which are neither the same trigger, the same actor, nor the same
43
+ * effect. So the /settings scheduled-audit section reads and writes **only** the
44
+ * scan schedule (this file + `[audit]`) and never touches `next-audit.json`,
45
+ * leaving the reminder as the /audit page's own concern. That keeps the scan
46
+ * schedule single-writer (the daemon) and single-file, which is the property the
47
+ * two-sources-of-truth worry was really about.
48
+ */
49
+ import { readFileSync } from "node:fs";
50
+ import { auditScheduleFile } from "../hooks/fp-home";
51
+
52
+ /**
53
+ * The schema the daemon currently writes — mirrors `SCHEMA` in
54
+ * `crates/failproofaid/src/audit_lane.rs`. Only used to *flag* a file that
55
+ * declares a higher one (`schemaAhead`); reads never gate on it, on purpose.
56
+ */
57
+ export const AUDIT_SCHEDULE_SCHEMA = 1;
58
+
59
+ export interface AuditScheduleView {
60
+ /** The `schema` the file declared, or null if absent/non-numeric. */
61
+ schema: number | null;
62
+ /** Absolute wall-clock ms of the next scheduled scan, or null. */
63
+ nextDueAtMs: number | null;
64
+ /** When a scan was last STARTED (written before the child spawns), or null. */
65
+ lastAttemptAtMs: number | null;
66
+ /** When a scan last finished successfully (never advanced by an exit-75
67
+ * "lock held"), or null. */
68
+ lastRunAtMs: number | null;
69
+ /** The scheduled child's last exit code, or null. */
70
+ lastExitCode: number | null;
71
+ /** The file declares a schema newer than this build knows. Fields are still
72
+ * read best-effort; this only tells the UI to caveat what it shows. */
73
+ schemaAhead: boolean;
74
+ }
75
+
76
+ /**
77
+ * Read the scheduled-audit state, or null when there is nothing usable to show.
78
+ *
79
+ * Never throws: every failure mode (absent file, EACCES, truncated write,
80
+ * non-JSON, JSON that is not an object) collapses to null, which the caller
81
+ * renders as "no scheduled scan has run yet".
82
+ */
83
+ export function readAuditSchedule(home?: string): AuditScheduleView | null {
84
+ let raw: string;
85
+ try {
86
+ raw = readFileSync(auditScheduleFile(home), "utf8");
87
+ } catch {
88
+ return null;
89
+ }
90
+ let parsed: unknown;
91
+ try {
92
+ parsed = JSON.parse(raw);
93
+ } catch {
94
+ return null;
95
+ }
96
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
97
+
98
+ const obj = parsed as Record<string, unknown>;
99
+ const num = (v: unknown): number | null =>
100
+ typeof v === "number" && Number.isFinite(v) ? v : null;
101
+
102
+ const schema = num(obj.schema);
103
+ return {
104
+ schema,
105
+ nextDueAtMs: num(obj.next_due_at_ms),
106
+ lastAttemptAtMs: num(obj.last_attempt_at_ms),
107
+ lastRunAtMs: num(obj.last_run_at_ms),
108
+ lastExitCode: num(obj.last_exit_code),
109
+ schemaAhead: schema !== null && schema > AUDIT_SCHEDULE_SCHEMA,
110
+ };
111
+ }
@@ -12,10 +12,10 @@
12
12
  import { createHash } from "node:crypto";
13
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
14
14
  import { join } from "node:path";
15
- import { homedir } from "node:os";
16
15
  import { BUILTIN_POLICIES } from "../hooks/builtin-policies";
17
16
  import { AUDIT_DETECTORS } from "./detectors";
18
17
  import type { TranscriptAuditResult } from "./types";
18
+ import { auditCacheDir } from "../hooks/fp-home";
19
19
 
20
20
  let cachedEngineVersion: string | null = null;
21
21
  let cachedDetectorVersion: string | null = null;
@@ -44,7 +44,7 @@ function getDetectorVersion(): string {
44
44
  }
45
45
 
46
46
  function getCachePathFor(transcriptPath: string): string {
47
- const root = join(homedir(), ".failproofai", "cache", "audit");
47
+ const root = auditCacheDir();
48
48
  const key = createHash("sha1").update(transcriptPath).digest("hex");
49
49
  return join(root, `${key}.json`);
50
50
  }
@@ -73,8 +73,21 @@ export const CACHE_SCHEMA_VERSION = 3;
73
73
  /** Hard expiry: a cached transcript result is rejected on read once it's
74
74
  * this old. Keeps the audit pipeline honest about long-lived results that
75
75
  * may no longer reflect current detector intent even if the transcript
76
- * bytes and policy hashes haven't changed. */
77
- export const CACHE_TTL_MS = 7 * 24 * 60 * 60_000;
76
+ * bytes and policy hashes haven't changed.
77
+ *
78
+ * 30 days, and it MUST stay clear of the scheduled-audit interval
79
+ * (`DEFAULT_AUDIT_INTERVAL_DAYS`, 7). It was 7 days — exactly equal — which
80
+ * meant a scheduled run at T+7d found every entry cached at T+0 already
81
+ * expired and cold-scanned the entire history: ~104 seconds and megabytes of
82
+ * rewrites, on every single run, for a lane whose whole point is to be cheap.
83
+ * The margin was one scan duration, so any suspend, missed tick, or deferral
84
+ * tipped all of it over at once.
85
+ *
86
+ * Correctness does not rest on this number: `engineVersion` (a hash over every
87
+ * builtin's source) and `detectorVersion` already invalidate the cache the
88
+ * moment detection logic actually changes. The TTL only bounds staleness for
89
+ * changes those two cannot see, so it can be generous. */
90
+ export const CACHE_TTL_MS = 30 * 24 * 60 * 60_000;
78
91
 
79
92
  interface CacheEntry {
80
93
  /** Bumped whenever the on-disk shape changes incompatibly. */
@@ -123,7 +136,7 @@ export function writeCachedTranscriptResult(
123
136
  if (sizeBytes === 0) return;
124
137
  const cachePath = getCachePathFor(transcriptPath);
125
138
  try {
126
- mkdirSync(join(homedir(), ".failproofai", "cache", "audit"), { recursive: true });
139
+ mkdirSync(auditCacheDir(), { recursive: true });
127
140
  const entry: CacheEntry = {
128
141
  schemaVersion: CACHE_SCHEMA_VERSION,
129
142
  cachedAt: Date.now(),
@@ -7,7 +7,8 @@
7
7
  * lib/goose-sessions.ts parses each session's `messages`), producing the same
8
8
  * LogEntry[] shape the other adapters do — so `logEntriesToEvents` handles the
9
9
  * rest. Like Devin, each Goose session carries a real `working_dir`, so
10
- * `audit --project <cwd>` filters work (unlike the cwd-less Hermes gateway).
10
+ * `audit --project <cwd>` filters work. (Hermes does too, for its `source='cli'`
11
+ * sessions — only its Slack/Telegram gateway runs are genuinely cwd-less.)
11
12
  */
12
13
  import { getGooseSessions } from "../../../lib/goose-projects";
13
14
  import { getGooseSessionLog } from "../../../lib/goose-sessions";