failproofai 1.0.5 → 1.0.6-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +5 -5
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js +4 -4
  25. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +14 -14
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  40. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  41. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  42. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  43. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  44. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  46. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  47. package/.next/standalone/.next/server/app/policies/page.js +6 -5
  48. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  49. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  54. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  58. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js +2 -2
  59. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  62. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  63. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  64. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  65. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  66. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +4 -4
  67. package/.next/standalone/.next/server/app/settings/page.js +3 -2
  68. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  69. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  70. package/.next/standalone/.next/server/chunks/[externals]__0sds_16._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0zlqo2t._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__112b4go._.js → [root-of-the-server]__1_r2rbg._.js} +4 -4
  74. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1anxb5k._.js +1 -1
  75. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +3 -3
  76. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0-_ki57._.js +4 -0
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__013jr2b._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__01wy8d-._.js +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02npjtd._.js +2 -2
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0576iuy._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0bym1os._.js → [root-of-the-server]__0cg-bgc._.js} +2 -2
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0da85px._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ftmoxc._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0p-5p8u._.js +2 -2
  90. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__00y9ww2._.js → [root-of-the-server]__0u3w0ll._.js} +2 -2
  91. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sz_5na._.js → [root-of-the-server]__1ouobn4._.js} +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1p2otjt._.js +2 -2
  93. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1py795m._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1qapotl._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__15k-dqb._.js → [root-of-the-server]__1x3j9mj._.js} +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/_0-yi74u._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/_06imw3p._.js +5 -0
  98. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/{_0guaevg._.js → _0qlygw6._.js} +2 -2
  100. package/.next/standalone/.next/server/chunks/ssr/_0wso5d3._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/_1-3unkg._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{_1b78kcp._.js → _1_ddpm3._.js} +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/{_14c6q09._.js → _1_qswah._.js} +2 -2
  104. package/.next/standalone/.next/server/chunks/ssr/_1ddpc5g._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/_1es2j7i._.js +3 -3
  106. package/.next/standalone/.next/server/chunks/ssr/_1u8-lu2._.js +1 -1
  107. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  108. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +12 -11
  109. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +2 -2
  113. package/.next/standalone/.next/server/chunks/ssr/app_settings_settings-client_tsx_20lq-mq._.js +1 -1
  114. package/.next/standalone/.next/server/chunks/ssr/node_modules_1hl81y4._.js +3 -0
  115. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +2 -2
  116. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  117. package/.next/standalone/.next/server/pages/404.html +1 -1
  118. package/.next/standalone/.next/server/pages/500.html +1 -1
  119. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  120. package/.next/standalone/.next/server/server-reference-manifest.json +19 -19
  121. package/.next/standalone/.next/static/chunks/{3w46euc60dx39.js → 02vwr5js1x28j.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/06fjx8-0_qufs.js +1 -0
  123. package/.next/standalone/.next/static/chunks/{2zafgs90r_leq.js → 06jxjnw8bfczc.js} +1 -1
  124. package/.next/standalone/.next/static/chunks/0cbvytf8epn34.js +1 -0
  125. package/.next/standalone/.next/static/chunks/0y227_ohdqwvn.js +1 -0
  126. package/.next/standalone/.next/static/chunks/{252xreaecy80h.js → 1p4xauf-zu6zk.js} +1 -1
  127. package/.next/standalone/.next/static/chunks/{021ydrq6rhpsi.js → 1qf2uiipatr80.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/1uc_s_dftgxiq.js +6 -0
  129. package/.next/standalone/.next/static/chunks/1xacnwjkbekn7.js +1 -0
  130. package/.next/standalone/.next/static/chunks/{11s_v03ub6nss.js → 27_bwbg8sv6vj.js} +1 -1
  131. package/.next/standalone/.next/static/chunks/2hg_bp8z_vm2g.js +1 -0
  132. package/.next/standalone/.next/static/chunks/{3bleyy40s99f2.js → 2x776s0x3n_tu.js} +1 -1
  133. package/.next/standalone/.next/static/chunks/{1vb33xa6j9y2q.js → 3ht0xsco46pul.js} +1 -1
  134. package/.next/standalone/.next/static/chunks/{turbopack-3p73obe0xmrxm.js → turbopack-38nzuvp95_2rv.js} +1 -1
  135. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  136. package/.next/standalone/hermes-plugin/README.md +138 -0
  137. package/.next/standalone/hermes-plugin/__init__.py +248 -0
  138. package/.next/standalone/hermes-plugin/client.py +150 -0
  139. package/.next/standalone/hermes-plugin/ledger.py +187 -0
  140. package/.next/standalone/hermes-plugin/plugin.yaml +37 -0
  141. package/.next/standalone/lib/hermes-profiles.ts +67 -19
  142. package/.next/standalone/lib/hermes-sessions.ts +3 -2
  143. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  144. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  145. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo-experimental.runtime.prod.js +1 -1
  146. package/.next/standalone/node_modules/next/dist/compiled/next-server/app-page-turbo.runtime.prod.js +1 -1
  147. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  148. package/.next/standalone/node_modules/next/dist/server/app-render/create-component-styles-and-scripts.js +2 -1
  149. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  150. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  151. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  152. package/.next/standalone/node_modules/next/dist/server/image-optimizer.js +14 -0
  153. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  154. package/.next/standalone/node_modules/next/dist/server/lib/disk-lru-cache.external.js +7 -1
  155. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  156. package/.next/standalone/node_modules/next/dist/server/use-cache/use-cache-wrapper.js +6 -1
  157. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  158. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  159. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  160. package/.next/standalone/node_modules/next/package.json +10 -10
  161. package/.next/standalone/node_modules/react/cjs/react.development.js +92 -46
  162. package/.next/standalone/node_modules/react/cjs/react.production.js +112 -89
  163. package/.next/standalone/node_modules/react/package.json +1 -1
  164. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.browser.production.js +1165 -790
  165. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server-legacy.node.production.js +1171 -791
  166. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.browser.production.js +1294 -862
  167. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.edge.production.js +1305 -868
  168. package/.next/standalone/node_modules/react-dom/cjs/react-dom-server.node.production.js +1315 -896
  169. package/.next/standalone/node_modules/react-dom/cjs/react-dom.production.js +24 -5
  170. package/.next/standalone/node_modules/react-dom/package.json +4 -4
  171. package/.next/standalone/package.json +13 -12
  172. package/.next/standalone/sdk/python/uv.lock +18 -18
  173. package/.next/standalone/server.js +1 -1
  174. package/README.md +29 -13
  175. package/bin/failproofai.mjs +3 -1
  176. package/dist/cli.mjs +655 -360
  177. package/dist/worker.mjs +78 -34
  178. package/hermes-plugin/README.md +138 -0
  179. package/hermes-plugin/__init__.py +248 -0
  180. package/hermes-plugin/client.py +150 -0
  181. package/hermes-plugin/ledger.py +187 -0
  182. package/hermes-plugin/plugin.yaml +37 -0
  183. package/lib/hermes-profiles.ts +67 -19
  184. package/lib/hermes-sessions.ts +3 -2
  185. package/package.json +13 -12
  186. package/scripts/translate-docs/validate-translation.ts +48 -0
  187. package/src/hooks/handler.ts +29 -1
  188. package/src/hooks/integrations.ts +271 -73
  189. package/src/hooks/manager.ts +41 -7
  190. package/src/hooks/types.ts +8 -8
  191. package/src/hooks/worker-server.ts +1 -0
  192. package/.next/standalone/.next/server/chunks/[externals]__157pf8j._.js +0 -3
  193. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0oa1lav._.js +0 -3
  194. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s740oi._.js +0 -6
  195. package/.next/standalone/.next/server/chunks/ssr/node_modules_13n822a._.js +0 -3
  196. package/.next/standalone/.next/static/chunks/0y8_1s45gbqmn.js +0 -6
  197. package/.next/standalone/.next/static/chunks/14b3ezufe5411.js +0 -1
  198. package/.next/standalone/.next/static/chunks/1oskk7pdo8aon.js +0 -1
  199. package/.next/standalone/.next/static/chunks/2xycd6mjg00fb.js +0 -1
  200. package/.next/standalone/.next/static/chunks/3l36d73kj18zw.js +0 -1
  201. package/.next/standalone/.next/static/chunks/40lpt2o2b283e.js +0 -1
  202. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_buildManifest.js +0 -0
  203. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_clientMiddlewareManifest.js +0 -0
  204. /package/.next/standalone/.next/static/{uFpAs_vIXSo6qBzmkXPWD → QCNYz2yCxxdeggg1k0iEq}/_ssgManifest.js +0 -0
package/dist/worker.mjs CHANGED
@@ -3483,7 +3483,7 @@ function parsePackPolicyName(canonicalName) {
3483
3483
  }
3484
3484
 
3485
3485
  // package.json
3486
- var version = "1.0.5";
3486
+ var version = "1.0.6-beta.1";
3487
3487
  var init_package = () => {};
3488
3488
 
3489
3489
  // src/posthog-key.ts
@@ -6388,36 +6388,66 @@ var init_pi_projects = __esm(() => {
6388
6388
  });
6389
6389
 
6390
6390
  // lib/hermes-profiles.ts
6391
- import { readdirSync as readdirSync3 } from "node:fs";
6391
+ import { existsSync as existsSync9, readdirSync as readdirSync3 } from "node:fs";
6392
6392
  import { homedir as homedir10 } from "node:os";
6393
6393
  import { basename as basename2, dirname as dirname3, join as join11, resolve as resolve8 } from "node:path";
6394
6394
  function hermesRoot() {
6395
6395
  const env = (process.env.HERMES_HOME || "").trim();
6396
6396
  if (env) {
6397
- const home = resolve8(env);
6397
+ let home = resolve8(env);
6398
6398
  const parent = dirname3(home);
6399
6399
  if (basename2(parent) === PROFILES_DIR)
6400
- return dirname3(parent);
6400
+ home = dirname3(parent);
6401
+ const base = basename2(home);
6402
+ if (base === STANDARD_HOME || base.startsWith(SIBLING_HOME_PREFIX)) {
6403
+ return join11(dirname3(home), STANDARD_HOME);
6404
+ }
6401
6405
  return home;
6402
6406
  }
6403
- return join11(homedir10(), ".hermes");
6407
+ return join11(homedir10(), STANDARD_HOME);
6408
+ }
6409
+ function uniqueProfileName(preferred, seen) {
6410
+ if (!seen.has(preferred))
6411
+ return preferred;
6412
+ const base = `${preferred}-home`;
6413
+ if (!seen.has(base))
6414
+ return base;
6415
+ let suffix = 2;
6416
+ while (seen.has(`${base}-${suffix}`))
6417
+ suffix += 1;
6418
+ return `${base}-${suffix}`;
6404
6419
  }
6405
6420
  function listHermesProfiles() {
6406
6421
  const root = hermesRoot();
6407
6422
  const out = [{ name: HERMES_DEFAULT_PROFILE, home: root }];
6408
6423
  const seen = new Set([HERMES_DEFAULT_PROFILE]);
6409
- let names;
6424
+ let names = [];
6410
6425
  try {
6411
6426
  names = readdirSync3(join11(root, PROFILES_DIR), { withFileTypes: true }).filter((e) => (e.isDirectory() || e.isSymbolicLink()) && !e.name.startsWith(".")).map((e) => e.name).sort();
6412
- } catch {
6413
- return out;
6414
- }
6427
+ } catch {}
6415
6428
  for (const name of names) {
6416
6429
  if (seen.has(name))
6417
6430
  continue;
6418
6431
  seen.add(name);
6419
6432
  out.push({ name, home: join11(root, PROFILES_DIR, name) });
6420
6433
  }
6434
+ if (basename2(root) !== STANDARD_HOME)
6435
+ return out;
6436
+ let siblings = [];
6437
+ const parent = dirname3(root);
6438
+ try {
6439
+ siblings = readdirSync3(parent, { withFileTypes: true }).filter((entry) => (entry.isDirectory() || entry.isSymbolicLink()) && entry.name.startsWith(SIBLING_HOME_PREFIX) && entry.name.length > SIBLING_HOME_PREFIX.length && existsSync9(join11(parent, entry.name, "config.yaml"))).map((entry) => ({
6440
+ name: entry.name.slice(SIBLING_HOME_PREFIX.length),
6441
+ home: join11(parent, entry.name)
6442
+ })).sort((a, b) => a.name.localeCompare(b.name));
6443
+ } catch {
6444
+ return out;
6445
+ }
6446
+ for (const sibling of siblings) {
6447
+ const name = uniqueProfileName(sibling.name, seen);
6448
+ seen.add(name);
6449
+ out.push({ name, home: sibling.home });
6450
+ }
6421
6451
  return out;
6422
6452
  }
6423
6453
  function hermesProjectName(profile, source) {
@@ -6443,7 +6473,7 @@ function hermesProjectNameCandidates(name) {
6443
6473
  out.push({ profile: HERMES_DEFAULT_PROFILE, source: rest });
6444
6474
  return out;
6445
6475
  }
6446
- var PROFILES_DIR = "profiles", HERMES_DEFAULT_PROFILE = "default";
6476
+ var PROFILES_DIR = "profiles", STANDARD_HOME = ".hermes", SIBLING_HOME_PREFIX = ".hermes-", HERMES_DEFAULT_PROFILE = "default";
6447
6477
  var init_hermes_profiles = () => {};
6448
6478
 
6449
6479
  // lib/format-duration.ts
@@ -6765,7 +6795,7 @@ var init_hermes_projects = __esm(() => {
6765
6795
  });
6766
6796
 
6767
6797
  // lib/openclaw-db.ts
6768
- import { existsSync as existsSync9 } from "node:fs";
6798
+ import { existsSync as existsSync10 } from "node:fs";
6769
6799
  import { join as join13 } from "node:path";
6770
6800
  function str(value) {
6771
6801
  return typeof value === "string" && value.length > 0 ? value : undefined;
@@ -6785,7 +6815,7 @@ function listOpenClawDatabases(home, agentIds) {
6785
6815
  const out = [];
6786
6816
  for (const agentId of agentIds) {
6787
6817
  const dbPath = join13(home, "agents", agentId, "agent", DB_NAME);
6788
- if (existsSync9(dbPath))
6818
+ if (existsSync10(dbPath))
6789
6819
  out.push({ agentId, dbPath });
6790
6820
  }
6791
6821
  return out;
@@ -8962,13 +8992,13 @@ var init_log_entries = __esm(() => {
8962
8992
  });
8963
8993
 
8964
8994
  // lib/codex-sessions.ts
8965
- import { readFileSync as readFileSync8, readdirSync as readdirSync7, existsSync as existsSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, statSync as statSync10 } from "node:fs";
8995
+ import { readFileSync as readFileSync8, readdirSync as readdirSync7, existsSync as existsSync11, writeFileSync as writeFileSync4, mkdirSync as mkdirSync5, statSync as statSync10 } from "node:fs";
8966
8996
  import { readFile as readFile11 } from "node:fs/promises";
8967
8997
  import { dirname as dirname4, join as join24 } from "node:path";
8968
8998
  import { homedir as homedir16 } from "node:os";
8969
8999
  function readCache() {
8970
9000
  try {
8971
- if (!existsSync10(CACHE_PATH))
9001
+ if (!existsSync11(CACHE_PATH))
8972
9002
  return {};
8973
9003
  return JSON.parse(readFileSync8(CACHE_PATH, "utf-8"));
8974
9004
  } catch {
@@ -8996,7 +9026,7 @@ function dirSearch(dir, sessionId) {
8996
9026
  function findCodexTranscript(sessionId) {
8997
9027
  const cache = readCache();
8998
9028
  const cached = cache[sessionId];
8999
- if (cached && existsSync10(cached))
9029
+ if (cached && existsSync11(cached))
9000
9030
  return cached;
9001
9031
  const root = join24(homedir16(), ".codex", "sessions");
9002
9032
  const today = new Date;
@@ -9290,7 +9320,7 @@ var init_resolve_permission_mode = __esm(() => {
9290
9320
  });
9291
9321
 
9292
9322
  // lib/copilot-sessions.ts
9293
- import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync11, statSync as statSync11 } from "node:fs";
9323
+ import { readFileSync as readFileSync10, readdirSync as readdirSync8, existsSync as existsSync12, statSync as statSync11 } from "node:fs";
9294
9324
  import { readFile as readFile12 } from "node:fs/promises";
9295
9325
  import { join as join25, resolve as resolve10, sep as sep2 } from "node:path";
9296
9326
  import { homedir as homedir17 } from "node:os";
@@ -9319,13 +9349,13 @@ function findCopilotTranscript(sessionId) {
9319
9349
  const candidate = resolveSessionFile(sessionId, "events.jsonl");
9320
9350
  if (!candidate)
9321
9351
  return null;
9322
- return existsSync11(candidate) ? candidate : null;
9352
+ return existsSync12(candidate) ? candidate : null;
9323
9353
  }
9324
9354
  function findCopilotWorkspace(sessionId) {
9325
9355
  const candidate = resolveSessionFile(sessionId, "workspace.yaml");
9326
9356
  if (!candidate)
9327
9357
  return null;
9328
- return existsSync11(candidate) ? candidate : null;
9358
+ return existsSync12(candidate) ? candidate : null;
9329
9359
  }
9330
9360
  function readYamlScalar(path, key) {
9331
9361
  try {
@@ -9507,7 +9537,7 @@ var init_copilot_sessions = __esm(() => {
9507
9537
  });
9508
9538
 
9509
9539
  // lib/cursor-sessions.ts
9510
- import { readFileSync as readFileSync11, readdirSync as readdirSync9, existsSync as existsSync12, statSync as statSync12 } from "node:fs";
9540
+ import { readFileSync as readFileSync11, readdirSync as readdirSync9, existsSync as existsSync13, statSync as statSync12 } from "node:fs";
9511
9541
  import { readFile as readFile13 } from "node:fs/promises";
9512
9542
  import { basename as basename3, join as join26, resolve as resolve11, sep as sep3 } from "node:path";
9513
9543
  import { homedir as homedir18 } from "node:os";
@@ -9530,7 +9560,7 @@ function getCursorSessionDir(sessionId) {
9530
9560
  const transcriptRoot = resolve11(projectsRoot, entry.name, NEW_AGENT_TRANSCRIPTS_DIR2);
9531
9561
  if (candidate === transcriptRoot || !candidate.startsWith(`${transcriptRoot}${sep3}`))
9532
9562
  continue;
9533
- if (existsSync12(candidate))
9563
+ if (existsSync13(candidate))
9534
9564
  return candidate;
9535
9565
  }
9536
9566
  for (const sub of LEGACY_SESSION_ROOT_CANDIDATES2) {
@@ -9538,7 +9568,7 @@ function getCursorSessionDir(sessionId) {
9538
9568
  const candidate = resolve11(root, sessionId);
9539
9569
  if (candidate === root || !candidate.startsWith(`${root}${sep3}`))
9540
9570
  continue;
9541
- if (existsSync12(candidate))
9571
+ if (existsSync13(candidate))
9542
9572
  return candidate;
9543
9573
  }
9544
9574
  return null;
@@ -9548,11 +9578,11 @@ function findCursorTranscript(sessionId) {
9548
9578
  if (!dir)
9549
9579
  return null;
9550
9580
  const newCandidate = join26(dir, `${basename3(dir)}.jsonl`);
9551
- if (existsSync12(newCandidate))
9581
+ if (existsSync13(newCandidate))
9552
9582
  return newCandidate;
9553
9583
  for (const name of LEGACY_TRANSCRIPT_FILE_CANDIDATES2) {
9554
9584
  const candidate = join26(dir, name);
9555
- if (existsSync12(candidate))
9585
+ if (existsSync13(candidate))
9556
9586
  return candidate;
9557
9587
  }
9558
9588
  return null;
@@ -9763,7 +9793,7 @@ var init_cursor_sessions = __esm(() => {
9763
9793
  });
9764
9794
 
9765
9795
  // lib/pi-sessions.ts
9766
- import { readFileSync as readFileSync12, readdirSync as readdirSync10, existsSync as existsSync13, statSync as statSync13 } from "node:fs";
9796
+ import { readFileSync as readFileSync12, readdirSync as readdirSync10, existsSync as existsSync14, statSync as statSync13 } from "node:fs";
9767
9797
  import { readFile as readFile14 } from "node:fs/promises";
9768
9798
  import { join as join27, resolve as resolve12, sep as sep4 } from "node:path";
9769
9799
  import { homedir as homedir19 } from "node:os";
@@ -9800,7 +9830,7 @@ function findPiTranscript(sessionId) {
9800
9830
  const candidate = resolve12(cwdPath, f);
9801
9831
  if (!candidate.startsWith(`${cwdPath}${sep4}`))
9802
9832
  continue;
9803
- if (existsSync13(candidate))
9833
+ if (existsSync14(candidate))
9804
9834
  return candidate;
9805
9835
  }
9806
9836
  }
@@ -10160,7 +10190,7 @@ var init_pack_failclosed = __esm(() => {
10160
10190
  var init_atomic_write = () => {};
10161
10191
 
10162
10192
  // src/hooks/session-pause.ts
10163
- import { existsSync as existsSync14, mkdirSync as mkdirSync6, readdirSync as readdirSync11, readFileSync as readFileSync13, rmSync as rmSync2 } from "node:fs";
10193
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readdirSync as readdirSync11, readFileSync as readFileSync13, rmSync as rmSync2 } from "node:fs";
10164
10194
  import { createHash as createHash5 } from "node:crypto";
10165
10195
  import { join as join28, resolve as resolve13 } from "node:path";
10166
10196
  function pauseStateDir() {
@@ -10200,7 +10230,7 @@ function readActivePause(sessionId, now = Date.now()) {
10200
10230
  return null;
10201
10231
  try {
10202
10232
  const path = pauseFilePath(sessionId);
10203
- if (!existsSync14(path))
10233
+ if (!existsSync15(path))
10204
10234
  return null;
10205
10235
  const stored = parseStored(JSON.parse(readFileSync13(path, "utf8")));
10206
10236
  if (!stored)
@@ -10241,7 +10271,7 @@ var init_daemon_service = __esm(() => {
10241
10271
  // src/hooks/fp-reset.ts
10242
10272
  import {
10243
10273
  copyFileSync,
10244
- existsSync as existsSync15,
10274
+ existsSync as existsSync16,
10245
10275
  mkdirSync as mkdirSync7,
10246
10276
  readFileSync as readFileSync14,
10247
10277
  readdirSync as readdirSync12,
@@ -10258,7 +10288,7 @@ function layoutWarningForHook() {
10258
10288
  if (state.kind === "future") {
10259
10289
  return `[failproofai] this directory was written by a newer version (layout ${state.found} vs ${LAYOUT_VERSION}) — policies are NOT being enforced. ` + `Upgrade failproofai.`;
10260
10290
  }
10261
- if (existsSync15(globalPolicyConfigFile()))
10291
+ if (existsSync16(globalPolicyConfigFile()))
10262
10292
  return null;
10263
10293
  return `[failproofai] setup predates this version — global policies are NOT being enforced. ` + `Run \`failproofai config\` to re-create it.`;
10264
10294
  }
@@ -10605,7 +10635,20 @@ async function evaluateHookEvent(eventType, cli = "claude", stdinPayload, opts)
10605
10635
  }
10606
10636
  } catch {}
10607
10637
  }
10608
- return { exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr };
10638
+ return {
10639
+ exitCode: result.exitCode,
10640
+ stdout: result.stdout,
10641
+ stderr: result.stderr,
10642
+ evaluation: {
10643
+ decision: result.decision,
10644
+ policyName: result.policyName,
10645
+ policyNames: result.policyNames ?? (result.policyName ? [result.policyName] : []),
10646
+ reason: result.reason,
10647
+ matchedPolicies,
10648
+ durationMs,
10649
+ toolName: parsed.tool_name ?? null
10650
+ }
10651
+ };
10609
10652
  } finally {
10610
10653
  if (opts?.awaitTelemetryFlush ?? true) {
10611
10654
  await flushHookTelemetry();
@@ -10635,7 +10678,7 @@ var init_handler = __esm(() => {
10635
10678
 
10636
10679
  // src/hooks/worker-server.ts
10637
10680
  import { createConnection, createServer } from "node:net";
10638
- import { existsSync as existsSync16, unlinkSync as unlinkSync2 } from "node:fs";
10681
+ import { existsSync as existsSync17, unlinkSync as unlinkSync2 } from "node:fs";
10639
10682
  function isWorkerHookRequest(msg) {
10640
10683
  if (!msg || typeof msg !== "object")
10641
10684
  return false;
@@ -10709,7 +10752,8 @@ function handleConnection(socket, shutdown) {
10709
10752
  type: "hookResult",
10710
10753
  exitCode: result.exitCode,
10711
10754
  stdout: result.stdout,
10712
- stderr: result.stderr
10755
+ stderr: result.stderr,
10756
+ evaluation: result.evaluation
10713
10757
  }));
10714
10758
  } catch (err) {
10715
10759
  const msg = err instanceof Error ? err.message : String(err);
@@ -10768,14 +10812,14 @@ function requestExistingWorkerShutdown(socketPath) {
10768
10812
  function startWorkerServer(socketPath, shutdown) {
10769
10813
  const server = createServer((socket) => handleConnection(socket, shutdown ?? (() => server.close())));
10770
10814
  const listen = () => {
10771
- if (existsSync16(socketPath)) {
10815
+ if (existsSync17(socketPath)) {
10772
10816
  try {
10773
10817
  unlinkSync2(socketPath);
10774
10818
  } catch {}
10775
10819
  }
10776
10820
  server.listen(socketPath);
10777
10821
  };
10778
- if (!existsSync16(socketPath))
10822
+ if (!existsSync17(socketPath))
10779
10823
  listen();
10780
10824
  else
10781
10825
  requestExistingWorkerShutdown(socketPath).then(listen, (err) => server.emit("error", err));
@@ -0,0 +1,138 @@
1
+ # FailproofAI for Hermes
2
+
3
+ This directory is the native Hermes adapter shipped inside the `failproofai`
4
+ npm package. It keeps policy evaluation in FailproofAI's TypeScript worker and
5
+ translates structured verdicts into Hermes-native hook behavior.
6
+
7
+ Validated contract: Hermes 0.21.3 at
8
+ `4d55ca91656ac5f83e1506679b7f81e0238e5e16`. The plugin uses only documented
9
+ manifest v2 fields, `PluginContext` methods/state, and keyword hook payloads.
10
+
11
+ ## Installation
12
+
13
+ Hermes policy evaluation requires a configured, healthy local `failproofaid`
14
+ daemon. If this machine has not been configured yet, run `failproofai config`
15
+ first. The standalone installer refuses to enable the fail-closed plugin until
16
+ the daemon answers an end-to-end health probe.
17
+
18
+ Then run:
19
+
20
+ ```bash
21
+ failproofai policies --install --cli hermes --scope user
22
+ ```
23
+
24
+ FailproofAI copies this directory to every discovered profile at
25
+ `<HERMES_HOME>/plugins/failproofai/` and adds `failproofai` to
26
+ `plugins.enabled` in that profile's `config.yaml`. Reinstall replaces only a
27
+ directory carrying `.failproofai-managed`; an unrelated plugin with the same
28
+ directory name is never overwritten.
29
+
30
+ Legacy FailproofAI shell hooks are removed during migration. Operator-owned
31
+ hooks and unrelated plugin settings are preserved. No dashboard deployment or
32
+ backtest is part of installation.
33
+
34
+ ## Runtime path
35
+
36
+ ```text
37
+ Hermes pre_tool_call
38
+ -> native Python plugin
39
+ -> owner-only failproofaid Unix socket
40
+ -> warm TypeScript policy worker
41
+ -> structured allow | deny | instruct verdict
42
+ -> Hermes-native return value
43
+ ```
44
+
45
+ There is no cloud request and no new CLI process in the tool-call path.
46
+
47
+ ## Decision behavior
48
+
49
+ - `allow`: return no directive; Hermes runs the tool.
50
+ - `deny`: always return `{"action":"block","message":"..."}`.
51
+ - `instruct`: persist delivery state, block the first attempt with a
52
+ model-visible `FAILPROOF INSTRUCTION`, keep the same API request blocked, then
53
+ allow a later API iteration for the same instruction scope.
54
+
55
+ The default instruction scope is profile + session + task + turn + policy
56
+ fingerprint + canonical tool. The persistent SQLite ledger lives below Hermes'
57
+ profile-scoped plugin data directory. Two distinct instruction interruptions
58
+ are allowed per turn by default; after that, advisory instructions fail open so
59
+ they cannot create an infinite retry loop. A real deny is never bypassed by the
60
+ ledger.
61
+
62
+ ## Configuration
63
+
64
+ Settings live under `plugins.entries.failproofai.settings`:
65
+
66
+ ```yaml
67
+ plugins:
68
+ enabled:
69
+ - failproofai
70
+ entries:
71
+ failproofai:
72
+ settings:
73
+ failure_mode: deny
74
+ connect_timeout_ms: 250
75
+ evaluation_timeout_ms: 12000
76
+ instruction_ttl_seconds: 3600
77
+ max_instruction_rounds: 2
78
+ ```
79
+
80
+ `failure_mode` controls evaluator/protocol failures only. `deny` is the safe
81
+ default. `allow` favors availability when the local daemon cannot return a
82
+ trusted verdict. Ledger failures always allow only the advisory `instruct`
83
+ decision, because otherwise corrupted retry state could block a turn forever.
84
+
85
+ ## Local protocol
86
+
87
+ Requests and responses use the existing length-prefixed failproofaid Unix
88
+ socket protocol, version 1.
89
+
90
+ ```json
91
+ {
92
+ "type": "policyEvaluation",
93
+ "protocolVersion": 1,
94
+ "integration": "hermes",
95
+ "event": "pre_tool_call",
96
+ "payload": {},
97
+ "cwd": "/workspace/project"
98
+ }
99
+ ```
100
+
101
+ ```json
102
+ {
103
+ "type": "policyResult",
104
+ "protocolVersion": 1,
105
+ "decision": "instruct",
106
+ "policyNames": ["custom/approved-write-route"],
107
+ "reason": "Use the approved write route.",
108
+ "matchedPolicies": ["custom/approved-write-route"],
109
+ "durationMs": 3,
110
+ "toolName": "Write"
111
+ }
112
+ ```
113
+
114
+ Requests are limited to 1 MiB and responses to 16 MiB. Invalid JSON, unknown
115
+ message types, version mismatch, timeout, and socket failure are treated as
116
+ untrusted evaluation failures.
117
+
118
+ ## Diagnostics and rollback
119
+
120
+ `failproofai config --status` reports each existing Hermes profile as healthy,
121
+ disabled, incomplete, or duplicated with a legacy shell hook. Hermes-side load
122
+ errors are available through:
123
+
124
+ ```bash
125
+ HERMES_PLUGINS_DEBUG=1 hermes plugins list
126
+ hermes plugins doctor ~/.hermes/plugins/failproofai --ci
127
+ hermes logs --level WARNING
128
+ ```
129
+
130
+ Use a temporary `HERMES_HOME` for development and compatibility tests. Remove
131
+ the integration with:
132
+
133
+ ```bash
134
+ failproofai policies --uninstall --cli hermes --scope user
135
+ ```
136
+
137
+ Uninstall removes the config registration and only the plugin directory marked
138
+ as FailproofAI-managed.
@@ -0,0 +1,248 @@
1
+ """Native Hermes bridge for FailproofAI policy enforcement."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import os
7
+ from pathlib import Path
8
+ from typing import Any, Mapping
9
+
10
+ from .client import PolicyVerdict, evaluate_policy
11
+ from .ledger import InstructionLedger, LedgerError
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ _PROTOCOL_CONTEXT = (
16
+ "When a tool result starts with FAILPROOF INSTRUCTION, treat it as policy guidance. "
17
+ "Reconsider the attempted action before retrying. Do not evade or ignore the instruction; "
18
+ "change tools, arguments, targets, or side effects only when the instruction requires it. "
19
+ "If you cannot follow the instruction, stop and explain why."
20
+ )
21
+
22
+
23
+ def _bounded_int(value: object, default: int, minimum: int, maximum: int) -> int:
24
+ try:
25
+ parsed = int(value)
26
+ except (TypeError, ValueError):
27
+ return default
28
+ return min(max(parsed, minimum), maximum)
29
+
30
+
31
+ def _profile_name() -> str:
32
+ explicit = os.environ.get("HERMES_PROFILE", "").strip()
33
+ if explicit:
34
+ return explicit
35
+ configured_home = os.environ.get("HERMES_HOME", "").strip()
36
+ home = Path(configured_home).expanduser() if configured_home else Path.home() / ".hermes"
37
+ if home.parent.name == "profiles":
38
+ return home.name
39
+ return "default"
40
+
41
+
42
+ def _string(value: object) -> str:
43
+ return value if isinstance(value, str) else ""
44
+
45
+
46
+ class FailproofAIPlugin:
47
+ def __init__(self, ctx: Any) -> None:
48
+ self.ctx = ctx
49
+ self.profile = _profile_name()
50
+ self.failure_mode = str(ctx.get_config("failure_mode", "deny")).strip().lower()
51
+ self.connect_timeout_ms = _bounded_int(
52
+ ctx.get_config("connect_timeout_ms", 250), 250, 25, 5_000
53
+ )
54
+ self.evaluation_timeout_ms = _bounded_int(
55
+ ctx.get_config("evaluation_timeout_ms", 12_000), 12_000, 100, 29_000
56
+ )
57
+ ttl_seconds = _bounded_int(
58
+ ctx.get_config("instruction_ttl_seconds", 3_600), 3_600, 60, 86_400
59
+ )
60
+ max_rounds = _bounded_int(
61
+ ctx.get_config("max_instruction_rounds", 2), 2, 0, 10
62
+ )
63
+ self.ledger = InstructionLedger(
64
+ Path(ctx.state.data_dir) / "instructions.db",
65
+ ttl_seconds=ttl_seconds,
66
+ max_rounds=max_rounds,
67
+ )
68
+
69
+ def _fallback(self, error: Exception) -> dict[str, str] | None:
70
+ logger.error("FailproofAI evaluator unavailable: %s", error)
71
+ if self.failure_mode == "allow":
72
+ return None
73
+ return {
74
+ "action": "block",
75
+ "message": (
76
+ "FailproofAI could not verify this tool call because the local policy "
77
+ "evaluator is unavailable. Check `failproofai status` before retrying."
78
+ ),
79
+ }
80
+
81
+ def _evaluate(self, event: str, payload: Mapping[str, Any]) -> PolicyVerdict:
82
+ cwd = _string(payload.get("cwd")) or os.getcwd()
83
+ return evaluate_policy(
84
+ event=event,
85
+ payload=payload,
86
+ cwd=cwd,
87
+ connect_timeout_ms=self.connect_timeout_ms,
88
+ evaluation_timeout_ms=self.evaluation_timeout_ms,
89
+ )
90
+
91
+ def pre_tool_call(
92
+ self,
93
+ tool_name: str = "",
94
+ args: Mapping[str, Any] | None = None,
95
+ session_id: str = "",
96
+ task_id: str = "",
97
+ tool_call_id: str = "",
98
+ turn_id: str = "",
99
+ api_request_id: str = "",
100
+ **kwargs: Any,
101
+ ) -> dict[str, str] | None:
102
+ payload = {
103
+ "hook_event_name": "pre_tool_call",
104
+ "tool_name": tool_name,
105
+ "tool_input": dict(args or {}),
106
+ "session_id": session_id,
107
+ "cwd": _string(kwargs.get("cwd")) or os.getcwd(),
108
+ "hermes": {
109
+ "profile": self.profile,
110
+ "task_id": task_id,
111
+ "tool_call_id": tool_call_id,
112
+ "turn_id": turn_id,
113
+ "api_request_id": api_request_id,
114
+ },
115
+ }
116
+ try:
117
+ verdict = self._evaluate("pre_tool_call", payload)
118
+ except Exception as exc:
119
+ return self._fallback(exc)
120
+
121
+ if verdict.decision == "allow":
122
+ return None
123
+ if verdict.decision == "deny":
124
+ return {
125
+ "action": "block",
126
+ "message": verdict.reason or "Blocked by FailproofAI policy.",
127
+ }
128
+
129
+ reason = verdict.reason or "Reconsider this tool call before retrying."
130
+ try:
131
+ action = self.ledger.decide(
132
+ profile=self.profile,
133
+ session_id=session_id,
134
+ task_id=task_id,
135
+ turn_id=turn_id,
136
+ api_request_id=api_request_id,
137
+ policy_names=verdict.policy_names,
138
+ reason=reason,
139
+ scope_key=verdict.tool_name or tool_name or "unknown-tool",
140
+ )
141
+ except LedgerError as exc:
142
+ # An advisory instruction must never become an indefinite denial
143
+ # because its local retry state is unavailable.
144
+ logger.error("FailproofAI instruction state unavailable; allowing: %s", exc)
145
+ return None
146
+
147
+ if not action.block:
148
+ if action.status in {"missing-correlation-id", "turn-cap"}:
149
+ logger.warning("FailproofAI instruction allowed in degraded state: %s", action.status)
150
+ return None
151
+
152
+ policies = ", ".join(verdict.policy_names) or "unknown policy"
153
+ return {
154
+ "action": "block",
155
+ "message": (
156
+ f"FAILPROOF INSTRUCTION ({policies})\n\n{reason}\n\n"
157
+ "Reconsider the attempted action, then make a corrected tool call. "
158
+ "Do not route around this instruction with an equivalent ungoverned action."
159
+ ),
160
+ }
161
+
162
+ def _observe(self, event: str, payload: Mapping[str, Any]) -> None:
163
+ try:
164
+ self._evaluate(event, payload)
165
+ except Exception as exc:
166
+ logger.warning("FailproofAI observation failed for %s: %s", event, exc)
167
+
168
+ def post_tool_call(
169
+ self,
170
+ tool_name: str = "",
171
+ args: Mapping[str, Any] | None = None,
172
+ result: Any = None,
173
+ session_id: str = "",
174
+ **kwargs: Any,
175
+ ) -> None:
176
+ self._observe(
177
+ "post_tool_call",
178
+ {
179
+ "hook_event_name": "post_tool_call",
180
+ "tool_name": tool_name,
181
+ "tool_input": dict(args or {}),
182
+ "tool_response": result,
183
+ "session_id": session_id,
184
+ "cwd": _string(kwargs.get("cwd")) or os.getcwd(),
185
+ "hermes": {"profile": self.profile, **kwargs},
186
+ },
187
+ )
188
+
189
+ def on_session_start(self, session_id: str = "", **kwargs: Any) -> None:
190
+ self._observe(
191
+ "on_session_start",
192
+ {
193
+ "hook_event_name": "on_session_start",
194
+ "session_id": session_id,
195
+ "cwd": _string(kwargs.get("cwd")) or os.getcwd(),
196
+ "hermes": {"profile": self.profile, **kwargs},
197
+ },
198
+ )
199
+
200
+ def on_session_end(self, session_id: str = "", **kwargs: Any) -> None:
201
+ self._observe(
202
+ "on_session_end",
203
+ {
204
+ "hook_event_name": "on_session_end",
205
+ "session_id": session_id,
206
+ "cwd": _string(kwargs.get("cwd")) or os.getcwd(),
207
+ "hermes": {"profile": self.profile, **kwargs},
208
+ },
209
+ )
210
+ self._clear_session(session_id)
211
+
212
+ def subagent_stop(self, session_id: str = "", **kwargs: Any) -> None:
213
+ self._observe(
214
+ "subagent_stop",
215
+ {
216
+ "hook_event_name": "subagent_stop",
217
+ "session_id": session_id,
218
+ "cwd": _string(kwargs.get("cwd")) or os.getcwd(),
219
+ "hermes": {"profile": self.profile, **kwargs},
220
+ },
221
+ )
222
+
223
+ def pre_llm_call(self, **kwargs: Any) -> dict[str, str]:
224
+ return {"context": _PROTOCOL_CONTEXT}
225
+
226
+ def on_session_reset(self, session_id: str = "", **kwargs: Any) -> None:
227
+ self._clear_session(session_id)
228
+
229
+ def on_session_finalize(self, session_id: str = "", **kwargs: Any) -> None:
230
+ self._clear_session(session_id)
231
+
232
+ def _clear_session(self, session_id: str) -> None:
233
+ try:
234
+ self.ledger.clear_session(session_id)
235
+ except LedgerError as exc:
236
+ logger.warning("FailproofAI instruction cleanup failed: %s", exc)
237
+
238
+
239
+ def register(ctx: Any) -> None:
240
+ plugin = FailproofAIPlugin(ctx)
241
+ ctx.register_hook("pre_tool_call", plugin.pre_tool_call)
242
+ ctx.register_hook("post_tool_call", plugin.post_tool_call)
243
+ ctx.register_hook("pre_llm_call", plugin.pre_llm_call)
244
+ ctx.register_hook("on_session_start", plugin.on_session_start)
245
+ ctx.register_hook("on_session_end", plugin.on_session_end)
246
+ ctx.register_hook("on_session_reset", plugin.on_session_reset)
247
+ ctx.register_hook("on_session_finalize", plugin.on_session_finalize)
248
+ ctx.register_hook("subagent_stop", plugin.subagent_stop)