failproofai 1.0.12-beta.0 → 1.0.12-beta.2

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 (214) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +3 -3
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  6. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.rsc +2 -2
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  12. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  15. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  16. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  22. package/.next/standalone/.next/server/app/api/audit/run/route.js +22 -22
  23. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  31. package/.next/standalone/.next/server/app/audit/page.js +1 -1
  32. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/index.html +1 -1
  35. package/.next/standalone/.next/server/app/index.rsc +3 -3
  36. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  37. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +3 -3
  38. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  39. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  40. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  41. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  43. package/.next/standalone/.next/server/app/policies/page.js +4 -4
  44. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  47. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  51. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  54. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  57. package/.next/standalone/.next/server/app/settings/page.js +7 -7
  58. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  59. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  60. package/.next/standalone/.next/server/chunks/{[externals]__0-ha5ydqq0mws._.js → [externals]__0vt9i6fqinjne._.js} +1 -1
  61. package/.next/standalone/.next/server/chunks/{[root-of-the-server]__20xe05hm5u8_1._.js → [root-of-the-server]__0f_4g-vazrvxv._.js} +1 -1
  62. package/.next/standalone/.next/server/chunks/_0hf5q4xzyg40j._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/_0tovk6q0miud6._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/_0trp3ycqv17cl._.js +1 -1
  65. package/.next/standalone/.next/server/chunks/_19011-u44143j._.js +1 -1
  66. package/.next/standalone/.next/server/chunks/_1ek68lnh2daur._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/{_0kc-piux3hady._.js → _1y590mjzpkbos._.js} +2 -2
  68. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4vkpxxu7._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/src_hooks_0_em82s16zfym._.js +5 -0
  70. package/.next/standalone/.next/server/chunks/src_hooks_builtin-policies_ts_1yq5ybgx1v98e._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3h94wnn._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/src_hooks_daemon-client_ts_0il_yu1zc0291._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/src_hooks_fp-home_ts_09kv0bn_4twyh._.js +1 -1
  74. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0ts0fg5yih1c1._.js → [root-of-the-server]__00l2u6j86cb8y._.js} +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__08ch3staq3ejw._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0bigvy9xmv2ka._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0h86bl2qf0k_o._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__1_xbg2kx30wp-._.js → [root-of-the-server]__0naevg4bcoh54._.js} +1 -1
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pia5h3p8re4c._.js +1 -1
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0pwj2h2efe5f-._.js +1 -1
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1-ey9fgesfbc0._.js +1 -1
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15usvs-ms-0hf._.js +1 -1
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__16gb3_92zvk5x._.js +1 -1
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1avi2kypusyb_._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1r7drlqli-ado._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0vcyntk4snf0f._.js → [root-of-the-server]__1vofkb2oq7p7e._.js} +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/_0bseae9u7g98r._.js +1 -1
  88. package/.next/standalone/.next/server/chunks/ssr/_0eoura3_9gjb-._.js +1 -1
  89. package/.next/standalone/.next/server/chunks/ssr/_0mouhaflye1ro._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/{_1gb0ifpkowwub._.js → _0ouu886szh6yj._.js} +10 -9
  91. package/.next/standalone/.next/server/chunks/ssr/_0pedwblqdoh58._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/_1djqs9i27e4kg._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/{_1ve8y151ua7qx._.js → _1fw0_nzkae5ao._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/_20hn4gn87nsjc._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo5_vm5f.js +2 -2
  96. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9snixb5fd-._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47q0xq5w._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_components_project-list_tsx_1ke6anr0vt64h._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x33jqb6._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpcz0uxur._.js +2 -2
  101. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h47jmhhd._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_00jxj8foxqz5t.js → node_modules_html-to-image_es_index_1fv03uigni8s5.js} +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_0janj0i6dqko7._.js +1 -1
  104. package/.next/standalone/.next/server/chunks/ssr/package_json_[json]_cjs_013tntikdft59._.js +1 -1
  105. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9ddabn3_._.js +1 -1
  106. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0agdmptetetdt._.js +5 -0
  107. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0mdwgjisv4fub._.js +5 -0
  108. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4l1tzrm._.js +1 -1
  109. package/.next/standalone/.next/server/chunks/ssr/src_hooks_daemon-client_ts_0k41_i7jg6ndv._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589gp4p60j._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me658zqmh_._.js +1 -1
  112. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  113. package/.next/standalone/.next/server/pages/404.html +1 -1
  114. package/.next/standalone/.next/server/pages/500.html +1 -1
  115. package/.next/standalone/.next/server/preview-props.json +3 -3
  116. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  118. package/.next/standalone/.next/static/chunks/{0pjxnp927y7_j.js → 0ay__-d2h1xb5.js} +1 -1
  119. package/.next/standalone/.next/static/chunks/{2znt1784j9gd9.js → 0tbm3vjm62h-2.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/1c_jf6g5wnslk.js +1 -0
  121. package/.next/standalone/.next/static/chunks/{3tn1bzk094e8e.js → 1my3-5p47t7r-.js} +1 -1
  122. package/.next/standalone/.next/static/chunks/20py8k1gd_i1m.js +1 -0
  123. package/.next/standalone/.next/static/chunks/21hckpmsc4g70.js +6 -0
  124. package/.next/standalone/.next/static/chunks/2hmex93ppj1ke.js +1 -0
  125. package/.next/standalone/.next/static/chunks/2o6d37j425uci.js +1 -0
  126. package/.next/standalone/.next/static/chunks/40oworn_-x12p.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{2qz5piiir-qqm.js → 42xptz88mtq3x.js} +1 -1
  128. package/.next/standalone/app/actions/update-jev-config.ts +5 -3
  129. package/.next/standalone/app/settings/jev-panel.tsx +1 -1
  130. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +32 -0
  131. package/.next/standalone/fp-cloud-cli/README.md +40 -1
  132. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +91 -12
  133. package/.next/standalone/fp-cloud-cli/fp_cli/commands/fleet_cmds.py +78 -16
  134. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +84 -18
  135. package/.next/standalone/fp-cloud-cli/fp_cli/enforcement.py +124 -6
  136. package/.next/standalone/fp-cloud-cli/fp_cli/models.py +89 -6
  137. package/.next/standalone/fp-cloud-cli/fp_cli/output.py +189 -20
  138. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +8 -5
  139. package/.next/standalone/fp-cloud-cli/skill/SKILL.md +2 -2
  140. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +14 -7
  141. package/.next/standalone/fp-cloud-cli/tests/test_cloud_jev.py +527 -0
  142. package/.next/standalone/fp-cloud-cli/tests/test_enforcement_logic.py +46 -0
  143. package/.next/standalone/fp-cloud-cli/tests/test_output.py +23 -0
  144. package/.next/standalone/hermes-plugin/README.md +6 -4
  145. package/.next/standalone/hermes-plugin/__init__.py +27 -0
  146. package/.next/standalone/hermes-plugin/client.py +5 -1
  147. package/.next/standalone/hermes-plugin/plugin.yaml +1 -1
  148. package/.next/standalone/package.json +11 -10
  149. package/.next/standalone/sdk/python/uv.lock +31 -8
  150. package/.next/standalone/server.js +1 -1
  151. package/bin/failproofai.mjs +17 -1
  152. package/dist/cli.mjs +3507 -1250
  153. package/dist/worker.mjs +1779 -169
  154. package/hermes-plugin/README.md +6 -4
  155. package/hermes-plugin/__init__.py +27 -0
  156. package/hermes-plugin/client.py +5 -1
  157. package/hermes-plugin/plugin.yaml +1 -1
  158. package/package.json +11 -10
  159. package/src/audit/open-browser.ts +6 -7
  160. package/src/hooks/agent-roster.ts +180 -0
  161. package/src/hooks/agent-skill.ts +203 -0
  162. package/src/hooks/agent-targets.ts +60 -0
  163. package/src/hooks/cloud-connection.ts +91 -45
  164. package/src/hooks/cloud-enrollment-cli.ts +7 -4
  165. package/src/hooks/cloud-login.ts +6 -3
  166. package/src/hooks/cloud-managed-policies.ts +430 -20
  167. package/src/hooks/cloud-policy-errors.ts +481 -0
  168. package/src/hooks/codex-system-hooks.ts +10 -4
  169. package/src/hooks/config-status.ts +10 -4
  170. package/src/hooks/configure-wizard.ts +54 -26
  171. package/src/hooks/custom-hooks-loader.ts +26 -3
  172. package/src/hooks/daemon-client.ts +9 -4
  173. package/src/hooks/daemon-service.ts +2 -1
  174. package/src/hooks/delivery-health.ts +47 -2
  175. package/src/hooks/effective-reviewers.ts +36 -1
  176. package/src/hooks/fp-config.ts +19 -0
  177. package/src/hooks/fp-home.ts +6 -0
  178. package/src/hooks/handler.ts +255 -19
  179. package/src/hooks/health-probe.ts +15 -0
  180. package/src/hooks/hook-activity-store.ts +7 -1
  181. package/src/hooks/integrations.ts +36 -14
  182. package/src/hooks/jev-activity.ts +117 -1
  183. package/src/hooks/jev-cli.ts +196 -1
  184. package/src/hooks/jev-cloud-connection.ts +92 -0
  185. package/src/hooks/manager.ts +174 -34
  186. package/src/hooks/pack-failclosed.ts +2 -0
  187. package/src/hooks/pack-manifest.ts +8 -1
  188. package/src/hooks/policy-evaluator.ts +22 -1
  189. package/src/hooks/policy-registry.ts +3 -2
  190. package/src/hooks/policy-reviewability.ts +23 -5
  191. package/src/hooks/semantic/cloud-jev-health.ts +227 -0
  192. package/src/hooks/semantic/cloud-jev.ts +868 -0
  193. package/src/hooks/semantic/combine.ts +26 -1
  194. package/src/hooks/semantic/jev-client.ts +19 -2
  195. package/src/hooks/semantic/jev-config.ts +179 -30
  196. package/src/hooks/semantic/jev-review.ts +217 -10
  197. package/src/hooks/semantic/pack-policies.ts +2 -1
  198. package/src/hooks/semantic/types.ts +8 -0
  199. package/src/hooks/session-pause-cli.ts +1 -1
  200. package/src/hooks/tui.ts +13 -4
  201. package/src/hooks/uninstall-cli.ts +12 -1
  202. package/src/hooks/worker-server.ts +5 -1
  203. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0uhs2t1b._.js +0 -5
  204. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7cyqlb2._.js +0 -3
  205. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo55j16x._.js +0 -5
  206. package/.next/standalone/.next/static/chunks/0a-ccsp3xmypz.js +0 -1
  207. package/.next/standalone/.next/static/chunks/13jv-xvabeb62.js +0 -6
  208. package/.next/standalone/.next/static/chunks/2ank2u3apy8m6.js +0 -1
  209. package/.next/standalone/.next/static/chunks/2meqb9kygrncb.js +0 -1
  210. package/.next/standalone/.next/static/chunks/2ohktp_-lkv3x.js +0 -1
  211. package/.next/standalone/.next/static/chunks/2py0eldhwz8ay.js +0 -1
  212. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_buildManifest.js +0 -0
  213. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_clientMiddlewareManifest.js +0 -0
  214. /package/.next/standalone/.next/static/{SNr7o_-G4kNBQTR-iLZMM → LHCtFxVG0mdvd4lsXAOFC}/_ssgManifest.js +0 -0
@@ -7,6 +7,13 @@
7
7
  * `jev.json` never loads the semantic evaluator at all, and its hook path is
8
8
  * exactly what it was before two tiers existed.
9
9
  *
10
+ * Or once FailproofAI Cloud sets this machine's Jev mode to `observe` or
11
+ * `enforce` (CONTRACT C10): then the call goes to FailproofAI Cloud on the
12
+ * Cloud Jev credential (`JevCallContext.cloud`), Cloud decides the checks it
13
+ * deployed here, and `evaluateCloudSemantic` merges that verdict with the
14
+ * machine's own. Cloud's outcomes are filed as their policy's OWN reviewers
15
+ * (`reviewerKey`), so a `both` policy is cleared by its Cloud checks alone.
16
+ *
10
17
  * Everything the request needs comes through the §7 contracts:
11
18
  * `transportForConfig` (T1) for the provider, `throttleTransport` (T5) for the
12
19
  * cache and rate limit (scoped to the config's provider, endpoint and model;
@@ -44,17 +51,29 @@
44
51
  * it distinguishes, which is what `toReview` below does.
45
52
  */
46
53
  import { BUILTIN_POLICIES } from "../builtin-policies";
54
+ import { cloudReviewerName } from "../cloud-managed-policies";
55
+ import type { AgentIdentity } from "../agent-targets";
56
+ import { recordJevBudgetDrops } from "../cloud-policy-errors";
47
57
  import { normalizePolicyName } from "../policy-registry";
48
58
  import { effectiveAuthority, type PolicyAuthority, type RegisteredPolicy } from "../policy-types";
49
59
  import type { JevMode, JevReview, TwoTierReview } from "./combine";
50
60
  import { DEFAULT_THRESHOLDS_V1 } from "./decide";
61
+ import { evaluateCloudSemantic, type CloudSemanticOutcome } from "./cloud-jev";
62
+ import {
63
+ BREAKER_SKIP_REASON,
64
+ classifyCloudJevReview,
65
+ cloudJevGate,
66
+ recordCloudJevResult,
67
+ reportCloudJevHealth,
68
+ } from "./cloud-jev-health";
51
69
  import { DEFAULT_JEV_TIMEOUT_MS, appendVerdictLog, evaluateSemantic, verdictLogRow, type SemanticOutcome } from "./evaluator";
52
70
  import * as intentStore from "./intent";
53
71
  import { JevError, transportForConfig, type JevTransport } from "./jev-client";
54
72
  import { DEFAULT_JEV_MODE, type JevConfig } from "./jev-config";
55
73
  import * as jevThrottle from "./jev-throttle";
56
74
  import { sessionProjectRoot } from "./session-root";
57
- import type { SemanticInput } from "./types";
75
+ import type { PolicyOutcome, SemanticInput } from "./types";
76
+ import { JEV_CHECKS_MAX, JEV_CHECK_PROBES_MAX, JEV_PROBE_ID_RE, round2, type JevCheckRecord } from "../jev-activity";
58
77
 
59
78
  /**
60
79
  * T5's throttle as this file calls it. §7 declares only
@@ -149,6 +168,25 @@ export interface JevCallContext {
149
168
  permissionMode?: string;
150
169
  sessionId?: string;
151
170
  cli: string;
171
+ /**
172
+ * Set when FailproofAI Cloud's Jev mode asks (CONTRACT C10.2): the call goes
173
+ * to Cloud with this machine's id, and the deployment it was made under
174
+ * scopes the answer cache (a new deployment can change Cloud's checks).
175
+ */
176
+ cloud?: {
177
+ machineId: string;
178
+ deployment: number | null;
179
+ mode: "observe" | "enforce";
180
+ agent?: AgentIdentity | null;
181
+ /** Cloud policies whose Jev assignment here is `observe`: recorded, never applied. */
182
+ observedPolicies?: ReadonlyArray<string>;
183
+ };
184
+ /**
185
+ * A session pause is active. It suspends local policy, so no installed
186
+ * pack's check is asked; FailproofAI Cloud's checks are exempt from a pause
187
+ * (as Cloud JS assignments are) and still are. Only meaningful with `cloud`.
188
+ */
189
+ localPaused?: boolean;
152
190
  }
153
191
 
154
192
  export function resolveMode(cfg: JevConfig): JevMode {
@@ -177,6 +215,24 @@ export function resolveTimeout(cfg: JevConfig): number {
177
215
  return Math.min(MAX_JEV_TIMEOUT_MS, Math.max(MIN_JEV_TIMEOUT_MS, t));
178
216
  }
179
217
 
218
+ /**
219
+ * The reviewer an outcome answers for: a FailproofAI Cloud check's is
220
+ * `cloud:<policyId>/<name>` (its outcome's own `origin.cloudPolicyId`, carried
221
+ * pack-shaped as `cloud:<id>`), which is what a Cloud `both` policy registers
222
+ * its `reviewedBy` as — so only that policy's own Cloud outcomes can clear it,
223
+ * and a Cloud outcome never clears an installed pack's policy (CONTRACT C10.5).
224
+ * An installed pack's check is its plain name, as it always was.
225
+ */
226
+ export function reviewerKey(o: Pick<PolicyOutcome, "policy" | "origin">): string {
227
+ const packId = o.origin?.packId;
228
+ return packId !== undefined && packId.startsWith("cloud:") ? cloudReviewerName(packId.slice("cloud:".length), o.policy) : o.policy;
229
+ }
230
+
231
+ /** Whether an outcome came from FailproofAI Cloud (its `cloud:<id>` origin; see `parseCloudReply`). */
232
+ function isCloudOutcome(o: Pick<PolicyOutcome, "origin">): boolean {
233
+ return o.origin?.packId?.startsWith("cloud:") === true;
234
+ }
235
+
180
236
  /** The semantic policy an attributable Jev verdict is filed under. */
181
237
  function jevDecider(outcome: Extract<SemanticOutcome, { status: "ok" }>) {
182
238
  const wanted = outcome.verdict.decision === "deny" ? "deny" : "instruct";
@@ -200,6 +256,66 @@ export function fallbackCode(reason: string): string {
200
256
  return /^[a-z0-9][a-z0-9-]{0,63}$/.test(code) ? code : "error";
201
257
  }
202
258
 
259
+ /** Why an outcome ended where it did (`JevCheckRecord.why`), from its own fields. */
260
+ function checkWhy(o: PolicyOutcome): JevCheckRecord["why"] {
261
+ switch (o.verdict) {
262
+ case "none":
263
+ // Both deciders: under `fire` nothing fired; at or over it only the exemption stops it.
264
+ return o.evidence < DEFAULT_THRESHOLDS_V1.fire ? "not-detected" : "exempt";
265
+ case "overridden":
266
+ return o.intent === "op-requested" ? "op-requested" : o.intent === "task-step" ? "task-step" : "user-asked";
267
+ case "deny":
268
+ return o.escalatedByInjection ? "injection" : "fired";
269
+ default:
270
+ if (o.intent === "downgraded-task-step") return "downgraded";
271
+ return o.targetScanIncomplete ? "scan-incomplete" : "fired";
272
+ }
273
+ }
274
+
275
+ /** Who declared a check, as a registered-name prefix: `cloud/<id>@<v>` or `pack/<id>@<v>`. */
276
+ function checkPolicy(o: PolicyOutcome): string | undefined {
277
+ const packId = o.origin?.packId;
278
+ if (!packId) return undefined;
279
+ const at = o.origin?.packVersion ? `@${o.origin.packVersion}` : "";
280
+ return packId.startsWith("cloud:") ? `cloud/${packId.slice("cloud:".length)}${at}` : `pack/${packId}${at}`;
281
+ }
282
+
283
+ /**
284
+ * Every asked check's answer, for the activity row (`jevChecks`): verdict,
285
+ * why, evidence, exempt and each probe's probability. `answers` holds the
286
+ * machine's own answers and Cloud's for its checks (`<check>.<probe>`).
287
+ */
288
+ export function checkRecords(
289
+ outcomes: ReadonlyArray<PolicyOutcome>,
290
+ answers: Readonly<Record<string, number>>,
291
+ watched: ReadonlyArray<PolicyOutcome> = [],
292
+ ): JevCheckRecord[] {
293
+ const all = [...outcomes.map((o) => ({ o, watched: false })), ...watched.map((o) => ({ o, watched: true }))];
294
+ return all.slice(0, JEV_CHECKS_MAX).map(({ o, watched: isWatched }) => {
295
+ const probes: Record<string, number> = {};
296
+ const prefix = `${o.policy}.`;
297
+ for (const [id, p] of Object.entries(answers)) {
298
+ if (Object.keys(probes).length >= JEV_CHECK_PROBES_MAX) break;
299
+ if (!id.startsWith(prefix)) continue;
300
+ const part = id.slice(prefix.length);
301
+ // `exempt` has its own field; `user_asked` (v0) is a consent question, not a probe.
302
+ if (part === "exempt" || part === "user_asked" || !JEV_PROBE_ID_RE.test(part)) continue;
303
+ probes[part] = round2(p);
304
+ }
305
+ const policy = checkPolicy(o);
306
+ return {
307
+ check: o.policy,
308
+ ...(policy ? { policy } : {}),
309
+ verdict: o.verdict,
310
+ why: checkWhy(o),
311
+ p: round2(o.evidence),
312
+ ...(o.exempt !== null ? { exempt: round2(o.exempt) } : {}),
313
+ ...(Object.keys(probes).length > 0 ? { probes } : {}),
314
+ ...(isWatched ? { watched: true as const } : {}),
315
+ };
316
+ });
317
+ }
318
+
203
319
  /**
204
320
  * @param cached - the answer came from T5's cache, not the provider: it is
205
321
  * applied like any other, but its ~0 ms is not a latency (`latencyMs`
@@ -215,6 +331,16 @@ export function toReview(outcome: SemanticOutcome, cached = false): JevReview {
215
331
  const latencyMs = sent && !cached ? outcome.latencyMs : null;
216
332
  const model = sent ? outcome.model : null;
217
333
  const outcomes = outcome.verdict.outcomes;
334
+ // A FailproofAI Cloud outcome is trusted only for a check the reply's own
335
+ // `asked` names. `parseCloudReply` already refuses a reply that contradicts
336
+ // itself; this keeps the review from relying on that alone, because a Cloud
337
+ // outcome is what clears a `both` policy's regex half. One it cannot vouch
338
+ // for — or a Cloud-origin outcome with no Cloud reply at all — means the
339
+ // picture is not one to clear anything on: the regex decides alone.
340
+ const cloudSummary = "cloud" in outcome ? (outcome as CloudSemanticOutcome & { status: "ok" }).cloud : null;
341
+ const cloudAsked = cloudSummary ? new Set(cloudSummary.asked) : null;
342
+ const unvouched = outcomes.some((o) => isCloudOutcome(o) && !(cloudAsked?.has(o.policy) ?? false));
343
+ if (unvouched) return { kind: "fallback", reason: "malformed", latencyMs, model: null };
218
344
  // `null` when the injection probe was not in the request, which now means
219
345
  // only that no request was made: v1 asks it on every call it sends, with or
220
346
  // without a recorded human message (see `compile.ts`).
@@ -223,20 +349,25 @@ export function toReview(outcome: SemanticOutcome, cached = false): JevReview {
223
349
  // not downgrade from a deny (see "A check that fired without consent keeps
224
350
  // the floor" in `combine.ts`).
225
351
  const warned = (o: (typeof outcomes)[number]) => o.verdict === "instruct" && o.intent !== "downgraded-task-step";
352
+ const cloudAnswers = cloudSummary?.answers ?? {};
353
+ const watched = cloudSummary?.observedOutcomes ?? [];
226
354
  return {
227
355
  kind: "answered",
228
356
  decision: outcome.verdict.decision,
357
+ // What each asked check concluded, for the activity row. `[]` when the
358
+ // request carried no check (globals only, or Cloud's short-circuit).
359
+ checks: sent ? checkRecords(outcomes, { ...outcome.answers, ...cloudAnswers }, watched) : [],
229
360
  reason: outcome.verdict.reason,
230
361
  policyName: jevPolicyName(outcome),
231
362
  ...(jevDecider(outcome)?.origin ? { origin: jevDecider(outcome)?.origin } : {}),
232
363
  // Every selected policy's questions were in the request, and `readAnswers`
233
364
  // refuses a response missing any of them — so selected == asked, and only
234
365
  // when a request was actually made.
235
- asked: sent ? outcomes.map((o) => o.policy) : [],
366
+ asked: sent ? outcomes.map(reviewerKey) : [],
236
367
  // `none` found nothing, `overridden` is what the human asked for, and a
237
368
  // deny the human's task softened to a warning is that task's consent — all
238
369
  // three clear. A `deny`, or a warning nobody consented to, keeps the block.
239
- notDenied: sent ? outcomes.filter((o) => o.verdict !== "deny" && !warned(o)).map((o) => o.policy) : [],
370
+ notDenied: sent ? outcomes.filter((o) => o.verdict !== "deny" && !warned(o)).map(reviewerKey) : [],
240
371
  injectionAsked: injection !== null,
241
372
  injected: injection !== null && injection >= DEFAULT_THRESHOLDS_V1.injection,
242
373
  // A check that CAN deny warned without consent: nothing may be cleared on
@@ -267,6 +398,18 @@ export function toReview(outcome: SemanticOutcome, cached = false): JevReview {
267
398
  requestCut: outcome.requestCut && sent,
268
399
  latencyMs,
269
400
  model,
401
+ // An `observe` Cloud assignment's checks: asked, recorded, never applied.
402
+ ...(sent && cloudSummary?.observedAsked?.length ? { observedAsked: cloudSummary.observedAsked } : {}),
403
+ ...(cloudSummary?.observed?.length
404
+ ? {
405
+ observedCloud: cloudSummary.observed.map((o) => ({
406
+ cloudPolicyId: o.cloudPolicyId,
407
+ cloudVersion: o.cloudVersion,
408
+ decision: o.decision,
409
+ reason: o.reason,
410
+ })),
411
+ }
412
+ : {}),
270
413
  };
271
414
  }
272
415
 
@@ -291,18 +434,37 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
291
434
  if (!settled) controller.abort();
292
435
  },
293
436
  authorityOf,
437
+ ...(call.cloud ? { cloud: true } : {}),
294
438
  });
295
439
 
296
440
  let route: ReturnType<typeof transportForConfig>;
297
441
  let throttled: JevTransport;
298
442
  try {
299
443
  route = transportForConfig(cfg);
300
- throttled = throttle.throttleTransport(route.transport, { scope: throttleScope(cfg, route) });
444
+ // A Cloud answer also depends on what Cloud deployed to this machine, which
445
+ // the request does not show: a new deployment is a new cache scope.
446
+ const scope = call.cloud
447
+ ? `${throttleScope(cfg, route)}\u0000cloud\u0000${call.cloud.machineId}\u0000${call.cloud.deployment ?? "-"}`
448
+ : throttleScope(cfg, route);
449
+ throttled = throttle.throttleTransport(route.transport, { scope });
301
450
  } catch (err) {
302
451
  const reason = err instanceof JevError ? err.code : "config";
303
452
  return handle(Promise.resolve({ kind: "fallback", reason, latencyMs: null, model: null }));
304
453
  }
305
454
 
455
+ // FailproofAI Cloud Jev that keeps failing is skipped for a while, so every
456
+ // tool call does not wait out its timeout (`cloud-jev-health.ts`).
457
+ const cloud = call.cloud;
458
+ const healthScope = cloud ? JSON.stringify([cfg.baseUrl ?? null, cloud.machineId]) : "";
459
+ let probe = false;
460
+ if (cloud) {
461
+ const gate = cloudJevGate(healthScope);
462
+ if (!gate.ask) {
463
+ return handle(Promise.resolve({ kind: "fallback", reason: BREAKER_SKIP_REASON, latencyMs: null, model: null }));
464
+ }
465
+ probe = gate.probe;
466
+ }
467
+
306
468
  let intent: ReturnType<IntentStore["readIntent"]>;
307
469
  try {
308
470
  intent = intentReader.readIntent(call.sessionId);
@@ -326,6 +488,8 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
326
488
 
327
489
  /** Set when the answer came from T5's cache (a hit carries the original `usage` and no real latency). */
328
490
  let cached = false;
491
+ /** A local inert allow or a cached reply cannot prove Cloud is healthy now. */
492
+ let reachedCloud = false;
329
493
  const transport: JevTransport = async (request, signal) => {
330
494
  const response = await throttled(request, signal);
331
495
  cached = servedFromCache(response);
@@ -335,21 +499,52 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
335
499
  const timeoutMs = resolveTimeout(cfg);
336
500
  /** Set once the backstop below gave up on the answer: a late one is logged, never applied. */
337
501
  let abandoned = false;
338
- const answered = evaluateSemantic(input, {
502
+ const options = {
339
503
  transport,
340
504
  cli: call.cli,
341
505
  via: route.via,
342
506
  model: route.model,
343
507
  timeoutMs,
344
- intent: "v1",
508
+ intent: "v1" as const,
345
509
  v1: { thresholds: DEFAULT_THRESHOLDS_V1 },
346
510
  signal: controller.signal,
347
511
  // No `contextTruncated`: nothing reports a store cut out of band, so the
348
512
  // evaluator's own (narrower) guess stands. See `IntentStore` above.
349
- })
513
+ };
514
+ const evaluated: Promise<SemanticOutcome | CloudSemanticOutcome> = cloud
515
+ ? evaluateCloudSemantic(input, {
516
+ ...options,
517
+ machineId: cloud.machineId,
518
+ agent: cloud.agent,
519
+ observedPolicies: cloud.observedPolicies ?? [],
520
+ // Paused: no installed pack's check is sent, only the globals, and
521
+ // Cloud's own checks are selected and decided on Cloud as ever.
522
+ ...(call.localPaused ? { policies: [] } : {}),
523
+ })
524
+ : evaluateSemantic(input, options);
525
+ const answered = evaluated
350
526
  .then((outcome): JevReview => {
351
527
  const hit = cached && outcome.status === "ok";
528
+ reachedCloud = outcome.status === "ok" && !hit && outcome.via !== "none";
352
529
  const review = toReview(outcome, hit);
530
+ // The machine's own pack checks Cloud dropped from this request
531
+ // (CONTRACT C10.5), reported for the deployment it happened under. Only
532
+ // from an answer that arrived in time: one the backstop abandoned is not
533
+ // applied, and neither is anything it said. A group dropped because a
534
+ // Cloud check of the same name was selected (Cloud wins the name, D-S-2)
535
+ // is told apart from a budget drop: that Cloud check is in `asked`, or in
536
+ // `droppedCloud` when the budget then dropped it too.
537
+ if (cloud && !abandoned && outcome.status === "ok" && "cloud" in outcome && outcome.cloud.droppedLocal.length > 0) {
538
+ const cloudNames = new Set([...outcome.cloud.asked, ...outcome.cloud.droppedCloud]);
539
+ recordJevBudgetDrops(
540
+ { machineId: cloud.machineId, deployment: cloud.deployment, jevMode: cloud.mode },
541
+ outcome.cloud.droppedLocal.map((d) => ({
542
+ packId: d.packId ?? "unknown",
543
+ name: d.name,
544
+ ...(cloudNames.has(d.name) ? { clash: true } : {}),
545
+ })),
546
+ );
547
+ }
353
548
  // An aborted request has nothing worth replaying; everything else goes
354
549
  // to the local verdict log (never shipped), so any verdict can be
355
550
  // re-derived offline from its recorded probabilities. A cache hit is
@@ -361,7 +556,8 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
361
556
  eventType: call.eventType,
362
557
  applied: abandoned || review.kind !== "answered" ? "legacy-fallback" : mode === "observe" ? "observe" : "two-tier",
363
558
  });
364
- appendVerdictLog(hit ? { ...row, cached: true } : row);
559
+ const withCloud = outcome.status === "ok" && "cloud" in outcome ? { ...row, cloud: outcome.cloud } : row;
560
+ appendVerdictLog(hit ? { ...withCloud, cached: true } : withCloud);
365
561
  }
366
562
  return review;
367
563
  })
@@ -381,9 +577,20 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
381
577
  resolve({ kind: "fallback", reason: "timeout", latencyMs: timeoutMs + JEV_DEADLINE_GRACE_MS, model: null });
382
578
  }, timeoutMs + JEV_DEADLINE_GRACE_MS);
383
579
  });
580
+ const raced = Promise.race([answered, expired]).finally(() => {
581
+ if (deadline !== undefined) clearTimeout(deadline);
582
+ });
583
+ if (!cloud) return handle(raced);
384
584
  return handle(
385
- Promise.race([answered, expired]).finally(() => {
386
- if (deadline !== undefined) clearTimeout(deadline);
585
+ raced.then((review) => {
586
+ recordCloudJevResult(
587
+ healthScope,
588
+ classifyCloudJevReview(review, { reachedCloud }),
589
+ review.kind === "fallback" ? review.reason : null,
590
+ probe,
591
+ );
592
+ reportCloudJevHealth();
593
+ return review;
387
594
  }),
388
595
  );
389
596
  }
@@ -142,7 +142,8 @@ export interface ResolvedSemanticPolicies {
142
142
  }
143
143
 
144
144
  /** Turn one validated manifest entry into a policy the compiler can use. */
145
- function toSemanticPolicy(entry: SemanticManifestEntry, pack: { id: string; version?: string }): SemanticPolicy {
145
+ /** Exported for the FailproofAI Cloud Jev parity replay (`__tests__/hooks/cloud-jev-parity.test.ts`). */
146
+ export function toSemanticPolicy(entry: SemanticManifestEntry, pack: { id: string; version?: string }): SemanticPolicy {
146
147
  // An unknown precondition name DROPS the policy (the caller catches this),
147
148
  // rather than compiling it with no gate at all. Ungating would be the wider
148
149
  // direction, not the weaker one, but it is not what the author asked for: a
@@ -21,6 +21,12 @@ export interface JevRequest {
21
21
  model: string;
22
22
  state: Record<string, unknown>;
23
23
  questions: Record<string, NoulQuestion>;
24
+ /**
25
+ * FailproofAI Cloud only: the tool-call metadata Cloud decides this
26
+ * machine's deployed Jev checks on (CONTRACT C10.3, `cloud-jev.ts`). Cloud
27
+ * strips it before anything reaches TypeSafe.
28
+ */
29
+ cloud?: Record<string, unknown>;
24
30
  }
25
31
 
26
32
  export interface JevNoulAnswer {
@@ -34,6 +40,8 @@ export interface JevResponse {
34
40
  usage?: { input_tokens?: number; output_tokens?: number };
35
41
  /** Set when the provider did not report which Jev version answered (Cloudflare's unversioned alias). */
36
42
  modelUnverified?: boolean;
43
+ /** FailproofAI Cloud only: its verdict for this machine's Cloud checks, unvalidated (`parseCloudReply`). */
44
+ cloud?: unknown;
37
45
  }
38
46
 
39
47
  /** A yes/no probe. A policy fires only when every one of its probes holds. */
@@ -195,7 +195,7 @@ export function runPauseCommand(opts: PauseCommandOptions): PauseCommandResult {
195
195
  ...(capped
196
196
  ? [`${INDENT}Capped at ${pauseDurationWords(PAUSE_CEILING_MS)} from when this pause began.`]
197
197
  : []),
198
- `${INDENT}Cloud-managed policies keep enforcing.`,
198
+ `${INDENT}Cloud-managed policies, and FailproofAI Cloud's Jev checks, keep enforcing.`,
199
199
  `${INDENT}Resume early: ${kit.cmd(resume)}`,
200
200
  ],
201
201
  affected: 1,
package/src/hooks/tui.ts CHANGED
@@ -1481,6 +1481,15 @@ export function promptBrowserLogin<T>(opts: BrowserLoginOptions<T>): Promise<{ v
1481
1481
  above.push(...lines);
1482
1482
  };
1483
1483
 
1484
+ /** Fold what was printed above into the region, so the next repaint erases
1485
+ * it too — the heading goes with it and returns to the region. */
1486
+ const forgetAbove = (): void => {
1487
+ const cols = stdout.columns || 80;
1488
+ for (const line of above) region.lastCount += Math.max(1, Math.ceil(visibleWidth(line) / cols));
1489
+ above.length = 0;
1490
+ headingShown = false;
1491
+ };
1492
+
1484
1493
  const clearPoll = (): void => {
1485
1494
  if (pollTimer) clearTimeout(pollTimer);
1486
1495
  pollTimer = null;
@@ -1500,10 +1509,7 @@ export function promptBrowserLogin<T>(opts: BrowserLoginOptions<T>): Promise<{ v
1500
1509
  stdin.pause();
1501
1510
  process.removeListener("exit", restoreTerminal);
1502
1511
  // Leaving for the menu takes the printed link with it.
1503
- if (answer === BACK) {
1504
- const cols = stdout.columns || 80;
1505
- for (const line of above) region.lastCount += Math.max(1, Math.ceil(visibleWidth(line) / cols));
1506
- }
1512
+ if (answer === BACK) forgetAbove();
1507
1513
  const lines =
1508
1514
  answer === null
1509
1515
  ? [c.ink3("cancelled")]
@@ -1564,6 +1570,9 @@ export function promptBrowserLogin<T>(opts: BrowserLoginOptions<T>): Promise<{ v
1564
1570
  clearPoll();
1565
1571
  cautionLine = "";
1566
1572
  phase = "starting";
1573
+ // A retry: the expired or refused link is dead, so it leaves the screen
1574
+ // rather than sit above the new one, where it could still be opened.
1575
+ forgetAbove();
1567
1576
  draw();
1568
1577
  let started: BrowserLoginStart;
1569
1578
  try {
@@ -47,6 +47,7 @@ import {
47
47
  import { listInstallableIds, getIntegration } from "./integrations";
48
48
  import type { IntegrationType } from "./types";
49
49
  import { removeHooks } from "./manager";
50
+ import { installedAgentSkillDirs, removeAgentSkill } from "./agent-skill";
50
51
  import { CODEX_MANAGED_BEGIN, CODEX_MANAGED_END, codexSystemConfigPath, codexSystemHooksPresent } from "./codex-system-hooks";
51
52
  import { optsFor, screenKit, type RenderOpts } from "./tui";
52
53
 
@@ -110,6 +111,8 @@ export interface UninstallResult {
110
111
  /** Everything this machine has that uninstalling would remove. */
111
112
  interface Leftovers {
112
113
  clis: IntegrationType[];
114
+ /** The failproofai skill folders `config` wrote into agents. */
115
+ skillDirs: string[];
113
116
  /** failproofai's block in Codex's system config, which only root can remove. */
114
117
  codexSystemPath: string | null;
115
118
  servicePath: string | null;
@@ -149,6 +152,7 @@ function survey(cwd?: string): Leftovers {
149
152
  const servicePath = isDaemonSupportedPlatform() ? daemonServiceFilePath() : null;
150
153
  return {
151
154
  clis,
155
+ skillDirs: installedAgentSkillDirs(),
152
156
  codexSystemPath: codexSystemHooksPresent() ? codexSystemConfigPath() : null,
153
157
  servicePath,
154
158
  serviceInstalled: !!servicePath && existsSync(servicePath),
@@ -166,7 +170,7 @@ export async function runUninstallCommand(opts: UninstallOptions = {}): Promise<
166
170
  const lines: string[] = [];
167
171
 
168
172
  const nothingToDo =
169
- found.clis.length === 0 && !found.serviceInstalled && !found.daemonConfigured &&
173
+ found.clis.length === 0 && found.skillDirs.length === 0 && !found.serviceInstalled && !found.daemonConfigured &&
170
174
  !(opts.purge && found.homeExists);
171
175
  if (nothingToDo) {
172
176
  return {
@@ -191,6 +195,7 @@ export async function runUninstallCommand(opts: UninstallOptions = {}): Promise<
191
195
  const purging = Boolean(opts.purge && found.homeExists);
192
196
  const removals: string[] = [];
193
197
  if (found.clis.length > 0) removals.push(`failproofai's hooks from ${many(found.clis.length, "agent")}`);
198
+ if (found.skillDirs.length > 0) removals.push(`the failproofai skill from ${many(found.skillDirs.length, "agent folder")}`);
194
199
  if (found.serviceInstalled) removals.push("the failproofaid service (needs sudo)");
195
200
  if (purging) removals.push(home);
196
201
  const releasesDaemon = found.daemonConfigured && !found.serviceInstalled;
@@ -321,6 +326,12 @@ export async function runUninstallCommand(opts: UninstallOptions = {}): Promise<
321
326
  }
322
327
  }
323
328
 
329
+ if (found.skillDirs.length > 0) {
330
+ const left = found.skillDirs.length - removeAgentSkill().length;
331
+ if (left === 0) lines.push(kit.ok(`Removed the failproofai skill from ${many(found.skillDirs.length, "agent folder")}.`));
332
+ else failures.push(`Could not remove the failproofai skill from ${many(left, "agent folder")}:\n${installedAgentSkillDirs().join("\n")}`);
333
+ }
334
+
324
335
  if (found.serviceInstalled) {
325
336
  // Every run that gets here agreed to the whole plan, and the plan names the
326
337
  // service: `--yes` is yes to the plan, an interactive yes is the same answer
@@ -36,6 +36,7 @@ interface WorkerHookRequest {
36
36
  cli: IntegrationType;
37
37
  stdin: string;
38
38
  cwd?: string;
39
+ agentSettingsPath?: string;
39
40
  }
40
41
 
41
42
  function isWorkerHookRequest(msg: unknown): msg is WorkerHookRequest {
@@ -54,7 +55,9 @@ function isWorkerHookRequest(msg: unknown): msg is WorkerHookRequest {
54
55
  // `failproofai config` aborted with "its worker process could not be run"
55
56
  // against a daemon and worker that were both perfectly healthy. On a
56
57
  // daemonConfigured machine the same mismatch denies the tool call.
57
- (m.cwd === undefined || m.cwd === null || typeof m.cwd === "string")
58
+ (m.cwd === undefined || m.cwd === null || typeof m.cwd === "string") &&
59
+ (m.agentSettingsPath === undefined || m.agentSettingsPath === null ||
60
+ typeof m.agentSettingsPath === "string")
58
61
  );
59
62
  }
60
63
 
@@ -278,6 +281,7 @@ function handleConnection(socket: Socket, shutdown: () => void): void {
278
281
  // Normalised here so no consumer has to know the wire spells
279
282
  // "absent" as null.
280
283
  fallbackCwd: request.cwd ?? undefined,
284
+ agentSettingsPath: request.agentSettingsPath ?? undefined,
281
285
  releaseRegistry: release,
282
286
  });
283
287
  deliver(
@@ -1,5 +0,0 @@
1
- (()=>{"use strict";module.exports=[16774,99891,98901,52306,e=>{let r;var t=e.i(66680),i=e.i(2157),o=e.i(50227),n=e.i(24214),a=e.i(79857);e.i(97348);let s=new Set(["destructive-deletion","production-infra-change","git-history-rewrite","push-to-protected-branch","commit-on-protected-branch","secret-exposure","credential-exfiltration","remote-code-execution","privilege-escalation","database-destruction","read-outside-workspace","agent-config-tampering","system-modification","env-secrets-dump","external-destructive-action","external-data-egress"]),c=new Set,l=e=>"string"==typeof e&&e.length>0;function f(e){let r={};("hard"===e.authority||"reviewable"===e.authority)&&(r.authority=e.authority);let t=e.reviewedBy;return Array.isArray(t)&&t.length>0&&t.every(l)&&(r.reviewedBy=[...t]),r}e.s(["_",0,c,"g",0,s,"i",0,f,"d",0,function(e,r=c,t){let i=e?.authority==="reviewable";if(!e||"hard"==(!0!==e.alwaysOn&&"reviewable"===e.authority&&Array.isArray(e.reviewedBy)&&e.reviewedBy.filter(e=>"string"==typeof e&&e.length>0).length>0?"reviewable":"hard")){var o;return i?{authority:"hard",downgraded:!0===(o=e).alwaysOn?"it is alwaysOn, and the self-protection guard is never reviewable":Array.isArray(o.reviewedBy)&&0!==o.reviewedBy.length?"reviewedBy is not a list of semantic policy names":"reviewedBy does not name any semantic policy"}:{authority:"hard"}}let n=e.reviewedBy;if(!n.every(l))return{authority:"hard",downgraded:"reviewedBy is not a list of semantic policy names"};let a=n.filter(e=>!r.has(e));return 0!==r.size||a.some(e=>t?.has(e))?a.length>0?{authority:"hard",downgraded:`reviewedBy names ${a.map(e=>(function(e,r,t){let i=JSON.stringify(e),o=t?.get(e);if(o)return`${i}, which packs ${o.join(" and ")} declare differently, so it is asked for neither`;if(r===s)return`${i}, which is not a semantic policy in this build`;let n=[...r].sort(),a=n.length>6?`${n.slice(0,6).join(", ")} and ${n.length-6} more`:n.join(", ");return`${i}, which is not among the Jev checks it is judged against (${a})`})(e,r,t)).join("; ")}`}:{authority:"reviewable",reviewedBy:[...new Set(n)]}:{authority:"hard"}}],99891);let d=/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/,p=/^(?:0|[1-9]\d*)$/;function h(e){if("string"!=typeof e)return null;let r=d.exec(e);return r?{major:Number(r[1]),minor:Number(r[2]),patch:Number(r[3]),prerelease:void 0===r[4]?[]:r[4].split(".").map(e=>p.test(e)?Number(e):e)}:null}let u=(e,r)=>e<r?-1:+(e>r),y=["always","protected_branch","in_git_repo","has_paths","paths_outside_project","system_or_root_paths"];function m(e){return"string"==typeof e&&y.includes(e)}e.s(["B",0,y,"M",0,m],98901);let g=[1],w=/^[a-f0-9]{64}$/,$=/^[A-Za-z0-9._-]{1,64}\/[A-Za-z0-9._-]{1,64}$/,v=/^[A-Za-z0-9][A-Za-z0-9._+-]{0,63}$/,b=/^[0-9a-f]{7,40}$/,A=/^[A-Za-z0-9._-]{1,128}$/,O=/^[a-z][a-z0-9_]{0,31}$/,S=new Set(["exempt","user_asked"]),k=new Set(["shell","write","read","network","other"]),E=new Set(["string","number","boolean","string[]","pattern[]"]);function j(e,r,t,i){if("string"!=typeof t||0===t.length)throw Error(`${e} is missing ${r}`);if(t.length>i)throw Error(`${e} has a ${r} of ${t.length} characters, over the ${i}-character cap`);return t}function P(e,r,t){let i;if(!r||"object"!=typeof r||Array.isArray(r))throw Error(`${e} is not an object`);if(null!==t)i=t;else{if("string"!=typeof r.id||!O.test(r.id))throw Error(`${e} has an unsafe id ${JSON.stringify(r.id)}`);if(S.has(r.id))throw Error(`${e} uses the reserved probe id ${JSON.stringify(r.id)}`);i=r.id}let o=j(e,"instructions",r.instructions,600);if(void 0===r.criteria)return{id:i,instructions:o};if(!r.criteria||"object"!=typeof r.criteria||Array.isArray(r.criteria))throw Error(`${e} has criteria that is not an object`);let n=r.criteria;return{id:i,instructions:o,criteria:{true:j(e,"criteria.true",n.true,300),false:j(e,"criteria.false",n.false,300)}}}function _(){let e,r=process.env.FAILPROOFAI_PACK_DIR?(0,o.resolve)(process.env.FAILPROOFAI_PACK_DIR,"installed.json"):(0,a.v)();if(!(0,i.existsSync)(r))return{packs:[],errors:[]};try{e=JSON.parse((0,i.readFileSync)(r,"utf8"))}catch(e){return{packs:[],errors:[{id:null,reason:`unreadable pack manifest: ${N(e)}`}]}}if(!e||"object"!=typeof e)return{packs:[],errors:[{id:null,reason:"pack manifest is not an object"}]};let s=e;if(!g.includes(s.schemaVersion))return{packs:[],errors:[{id:null,reason:`unsupported pack manifest schema ${String(s.schemaVersion)} (supported: ${g.join(", ")})`}]};if(!Array.isArray(s.packs))return{packs:[],errors:[{id:null,reason:"pack manifest packs is not an array"}]};let c=process.env.FAILPROOFAI_PACK_DIR??(0,a.n)(),l=[],d=[],p=[],O=new Set;for(let e of s.packs){let r=e&&"object"==typeof e&&"string"==typeof e.id?e.id:null,a=[];try{let r=function(e,r,a){if(!r||"object"!=typeof r)throw Error("pack entry is not an object");let s=function(e){if("string"!=typeof e.id||!$.test(e.id))throw Error(`unsafe pack id ${JSON.stringify(e.id)}`);if("string"!=typeof e.version||!v.test(e.version))throw Error(`invalid version for pack ${e.id}`);if(void 0!==e.effect&&"enforce"!==e.effect&&"observe"!==e.effect)throw Error(`unknown effect ${JSON.stringify(e.effect)} for pack ${e.id}`);let r="string"==typeof e.commit&&b.test(e.commit.trim().toLowerCase())?e.commit.trim().toLowerCase():void 0;return{id:e.id,version:e.version,effect:e.effect??"enforce",...r?{commit:r}:{}}}(r);if("string"!=typeof r.source||0===r.source.length)throw Error(`pack ${r.id} has no source`);if("string"!=typeof r.sha256||!w.test(r.sha256))throw Error(`invalid SHA-256 for pack ${r.id}`);let c=s.effect,l=function(e,r,t=n.version){if(void 0===r)return{kind:"satisfied"};let i=function(e,r){let t=h(e),i=h(r);return t&&i?u(t.major,i.major)||u(t.minor,i.minor)||u(t.patch,i.patch)||function(e,r){if(0===e.length&&0===r.length)return 0;if(0===e.length)return 1;if(0===r.length)return -1;for(let t=0;t<Math.min(e.length,r.length);t++){let i=e[t],o=r[t];if(i===o)continue;let n="number"==typeof i,a="number"==typeof o;if(n!==a)return n?-1:1;if(n&&a)return u(i,o);return i<o?-1:1}return u(e.length,r.length)}(t.prerelease,i.prerelease):null}(t,r);return null===i?{kind:"unreadable",reason:`pack ${e} declares minCliVersion ${JSON.stringify(r)}, which is not a version this CLI can compare against its own (${t}) — the requirement was ignored`}:i<0?{kind:"too-old",reason:`pack ${e} needs failproofai ${String(r)} or newer and this is ${t} — run \`npm i -g "failproofai@>=${String(r)}" && failproofai update\``}:{kind:"satisfied",declared:r}}(s.id,r.minCliVersion);if("too-old"===l.kind)throw Error(l.reason);"unreadable"===l.kind&&a.push(l.reason);let d=function(e,r){if(!r||(0,o.isAbsolute)(r))throw Error(`unsafe managed policy path ${JSON.stringify(r)}`);let t=(0,o.resolve)(e,r),n=(0,o.relative)(e,t);if(n.startsWith("..")||(0,o.isAbsolute)(n))throw Error(`managed policy path escapes its root: ${JSON.stringify(r)}`);let a=(0,i.realpathSync)(e),s=(0,i.realpathSync)(t),c=(0,o.relative)(a,s);if(c.startsWith("..")||(0,o.isAbsolute)(c))throw Error(`managed policy symlink escapes its root: ${JSON.stringify(r)}`);return s}(e,r.entry),p=(0,t.createHash)("sha256").update((0,i.readFileSync)(d)).digest("hex");if(p!==r.sha256)throw Error(`pack ${r.id} failed integrity verification: expected ${r.sha256}, got ${p}`);if(!Array.isArray(r.policies))throw Error(`pack ${r.id} policies is not an array`);let g=r.policies.map((e,t)=>(function(e,r,t,i){let o=`${e} policy #${t}`;if(!r||"object"!=typeof r)throw Error(`${o} is not an object`);let n=r.name;if("string"!=typeof n||!A.test(n))throw Error(`${o} has an unsafe name ${JSON.stringify(n)}`);if("alwaysOn"in r)throw Error(`${o} declares alwaysOn, which packs may not set`);if("string"!=typeof r.description||0===r.description.length)throw Error(`${o} is missing a description`);if("string"!=typeof r.category||0===r.category.length)throw Error(`${o} is missing a category`);if("boolean"!=typeof r.defaultEnabled)throw Error(`${o} is missing defaultEnabled`);if(!r.match||"object"!=typeof r.match)throw Error(`${o} is missing a match`);let a=r.match;for(let e of["events","toolNames"]){let r=a[e];if(void 0!==r&&(!Array.isArray(r)||r.some(e=>"string"!=typeof e||0===e.length)))throw Error(`${o} has a match.${e} that is not a list of names`)}let s=f(r),c=["authority","reviewedBy"].filter(e=>e in r&&!(e in s));if("params"in r){let e=function(e){if(!e||"object"!=typeof e||Array.isArray(e))return"params is not an object";for(let[r,t]of Object.entries(e)){if(0===r.length)return"params has an empty parameter name";if(!t||"object"!=typeof t||Array.isArray(t))return`params.${r} is not an object`;if("string"!=typeof t.type||!E.has(t.type))return`params.${r} has type ${JSON.stringify(t.type)}, which is not one of ${[...E].join(", ")}`;if("string"!=typeof t.description||0===t.description.length)return`params.${r} is missing a description`;if(!("default"in t))return`params.${r} is missing a default`}}(r.params);e&&(i?.push(`${o} declares a params schema that was dropped: ${e}`),c.push("params"))}return c.length>0?Object.fromEntries(Object.entries(r).filter(([e])=>!c.includes(e))):r})(r.id,e,t,a)),O=new Set;for(let e of g){if(O.has(e.name))throw Error(`pack ${r.id} declares ${e.name} twice`);O.add(e.name)}let S=null;if(void 0!==r.enabled){if(!Array.isArray(r.enabled)||r.enabled.some(e=>"string"!=typeof e))throw Error(`pack ${r.id} enabled is not an array of names`);S=r.enabled.filter(e=>O.has(e))}let _=null;if(void 0!==r.clis){if(!Array.isArray(r.clis)||r.clis.some(e=>"string"!=typeof e))throw Error(`pack ${r.id} clis is not an array of agent names`);_=r.clis}return{clis:_,id:s.id,version:s.version,source:r.source,path:d,sha256:r.sha256,effect:c,policies:g,semantic:function(e,r,t){if(void 0===r)return[];if(!Array.isArray(r))return t.push(`pack ${e} semantic is not an array, so none of it was loaded`),[];let i=[],o=new Set;for(let[n,a]of r.entries()){if(i.length>=24){t.push(`pack ${e} declares more than 24 semantic policies; the ${r.length-24} past that cap were dropped`);break}try{let r=function(e,r,t){let i=`${e} semantic policy #${t}`;if(!r||"object"!=typeof r||Array.isArray(r))throw Error(`${i} is not an object`);let o=r.name;if("string"!=typeof o||!A.test(o))throw Error(`${i} has an unsafe name ${JSON.stringify(o)}`);if("alwaysOn"in r)throw Error(`${i} declares alwaysOn, which packs may not set`);let n=j(`${e} semantic policy ${o}`,"title",r.title,120),a=`${e} semantic policy ${o}`;if(!Array.isArray(r.appliesTo)||0===r.appliesTo.length)throw Error(`${a} has no appliesTo tool classes`);let s=r.appliesTo.find(e=>"string"!=typeof e||!k.has(e));if(void 0!==s)throw Error(`${a} applies to ${JSON.stringify(s)}, which is not a tool class (${[...k].join(", ")})`);if("deny"!==r.mode&&"instruct"!==r.mode)throw Error(`${a} has mode ${JSON.stringify(r.mode)}, which must be "deny" or "instruct"`);if("boolean"!=typeof r.userCanOverride)throw Error(`${a} is missing userCanOverride, which has no default`);if(!Array.isArray(r.probes)||0===r.probes.length)throw Error(`${a} declares no probes`);if(r.probes.length>6)throw Error(`${a} declares ${r.probes.length} probes, over the cap of 6`);let c=r.probes.map((e,r)=>P(`${a} probe #${r}`,e,null)),l=new Set;for(let e of c){if(l.has(e.id))throw Error(`${a} declares probe ${e.id} twice`);l.add(e.id)}let f=void 0===r.exempt?void 0:P(`${a} exempt`,r.exempt,"exempt");if(void 0!==r.precondition&&!m(r.precondition))throw Error(`${a} names precondition ${JSON.stringify(r.precondition)}, which this build does not have (${y.join(", ")})`);let d=j(a,"guidance",r.guidance,600);return{name:o,title:n,appliesTo:r.appliesTo,mode:r.mode,userCanOverride:r.userCanOverride,probes:c,...f?{exempt:f}:{},...void 0!==r.precondition?{precondition:r.precondition}:{},guidance:d}}(e,a,n);if(o.has(r.name))throw Error(`pack ${e} declares semantic policy ${r.name} twice`);o.add(r.name),i.push(r)}catch(e){t.push(N(e))}}return i}(s.id,r.semantic,a),..."satisfied"===l.kind&&l.declared?{minCliVersion:l.declared}:{},enabled:S}}(c,e,a);if(O.has(r.id))throw Error(`duplicate pack id ${r.id}`);O.add(r.id),l.push(r),p.push(...a)}catch(i){let t=e&&("observe"===e.effect||"enforce"===e.effect)?e.effect:"enforce";d.push({id:r,reason:N(i),effect:t,...Array.isArray(e?.policies)?{declared:function(e){let r=[];for(let t of e)t&&"object"==typeof t&&"string"==typeof t.name&&t.match&&"object"==typeof t.match&&r.push(t);return r}(e.policies)}:{},...Array.isArray(e?.policies)&&0===e.policies.length?{semanticOnly:!0}:{},...Array.isArray(e?.clis)&&e.clis.every(e=>"string"==typeof e)?{clis:e.clis}:{}})}}return{packs:l,errors:d,...p.length>0?{warnings:p}:{}}}function N(e){return e instanceof Error?e.message:String(e)}e.s(["f",0,_],52306);let F=null;function I(e){let r=new Map,t=new Map;for(let i of e)for(let e of i.semantic??[]){if(J(i,e.name))continue;let o=function e(r){if(Array.isArray(r))return`[${r.map(e).join(",")}]`;if(null!==r&&"object"==typeof r){let t=Object.entries(r).filter(([,e])=>void 0!==e).sort(([e],[r])=>e<r?-1:+(e>r));return`{${t.map(([r,t])=>`${JSON.stringify(r)}:${e(t)}`).join(",")}}`}return JSON.stringify(r)??"null"}(e),n=r.get(e.name);if(void 0===n){r.set(e.name,{form:o,ids:[i.id]});continue}n.ids.push(i.id),n.form!==o&&t.set(e.name,n.ids)}return t}function J(e,r){return s.has(r)&&!/^github:FailproofAI\//i.test(e.source??"")}e.s(["j",0,function(){if(F)return F;let e=c;try{var t,i,o;let n,a,s=(t=_().packs,i=r,t.filter(e=>"observe"!==e.effect&&!(i&&Array.isArray(e.clis)&&e.clis.length>0&&!e.clis.includes(i))));o=s,n=I(o),a=o.flatMap(e=>(e.semantic??[]).filter(r=>!J(e,r.name)).map(e=>e.name)).filter(e=>!n.has(e)),e=0===a.length?c:new Set(a),I(s)}catch{}return F=e,e},"Z",0,function(e){F=null,r=e}],16774)},97348,e=>{var r=e.i(2157),t=e.i(50227),i=e.i(79857);let o={info:0,warn:1,error:2},n=(0,i.A)(),a=!1,s="warn",c=!1,l=n;function f(e){return!function(){if(a)return;a=!0;let e=(process.env.FAILPROOFAI_LOG_LEVEL??"").toLowerCase();("info"===e||"warn"===e||"error"===e)&&(s=e);let r=(process.env.FAILPROOFAI_HOOK_LOG_FILE??"").trim();r&&(c=!0,"1"!==r&&"true"!==r&&(l=r))}(),o[e]>=o[s]}function d(e,r){process.stderr.write(`[failproofai:hook] ${e} ${r}
2
- `)}function p(e,i){if(c)try{(0,r.existsSync)(l)||(0,r.mkdirSync)(l,{recursive:!0});let o=(0,t.join)(l,"hooks.log");try{if((0,r.statSync)(o).size>=524288){let e=`hooks-${Date.now()}.log`;(0,r.renameSync)(o,(0,t.join)(l,e))}}catch{}let n=new Date().toISOString(),a=`[${n}] ${e} ${i}
3
- `;(0,r.appendFileSync)(o,a,"utf-8")}catch{}}e.s(["F",0,function(e){f("error")&&(d("ERROR",e),p("ERROR",e))},"Q",0,function(e){f("info")&&(d("INFO",e),p("INFO",e))},"g",0,function(e){f("warn")&&(d("WARN",e),p("WARN",e))}])},73136,49335,e=>{var r=e.i(2157),t=e.i(50227),i=e.i(60526),o=e.i(97348),n=e.i(79857);function a(e){if(!(0,r.existsSync)(e))return{};try{let t=(0,r.readFileSync)(e,"utf8");return JSON.parse(t)}catch(r){return(0,o.g)(`failed to parse config at ${e}: ${r instanceof Error?r.message:String(r)}`),{}}}e.s(["f",0,function(e){let o=function(e){let o=(0,i.homedir)(),n=(0,t.resolve)(e);for(;n!==o;){let e=(0,t.resolve)(n,".failproofai");try{if((0,r.statSync)(e).isDirectory())return n}catch{}let i=(0,t.dirname)(n);if(i===n)break;n=i}return(0,t.resolve)(e)}(e??process.cwd()),s=(0,t.resolve)(o,".failproofai","policies-config.json"),c=(0,t.resolve)(o,".failproofai","policies-config.local.json"),l=(0,n.O)(),f=a(s),d=a(c),p=a(l),h=new Set([...f.enabledPolicies??[],...d.enabledPolicies??[],...p.enabledPolicies??[]]),u={};for(let e of[f,d,p])if(e.policyParams)for(let[r,t]of Object.entries(e.policyParams))r in u||(u[r]=t);let y=[f,d,p].map(e=>void 0!==e.customPoliciesPaths?e.customPoliciesPaths:void 0!==e.customPoliciesPath?[e.customPoliciesPath]:void 0).find(e=>void 0!==e),m=new Set([...f.disabledCustomPolicies??[],...d.disabledCustomPolicies??[],...p.disabledCustomPolicies??[]]),g=f.customPoliciesEnabled??d.customPoliciesEnabled??p.customPoliciesEnabled,w=f.llm??d.llm??p.llm;return{enabledPolicies:[...h],...Object.keys(u).length>0?{policyParams:u}:{},...void 0!==y?{customPoliciesPaths:y}:{},...m.size?{disabledCustomPolicies:[...m]}:{},...void 0!==g?{customPoliciesEnabled:g}:{},...void 0!==w?{llm:w}:{}}}],73136);var s=e.i(24214),c=e.i(28067),l=e.i(17694);let f=c._,d=new Set;async function p(e,r,t){let i=JSON.stringify({api_key:process.env.FAILPROOFAI_POSTHOG_KEY??f,event:r,distinct_id:e,properties:{...t,$lib:"failproofai-hooks",failproofai_version:s.version,product:c.W}});try{await fetch(process.env.FAILPROOFAI_POSTHOG_HOST?`${process.env.FAILPROOFAI_POSTHOG_HOST}/capture/`:"https://us.i.posthog.com/capture/",{method:"POST",headers:{"Content-Type":"application/json"},body:i,signal:AbortSignal.timeout(5e3)})}catch{}}async function h(e,r,t){if(!(0,l.f)())return;let i=p(e,r,t);d.add(i),i.finally(()=>d.delete(i)),await i}e.s(["f",0,h],49335)}]})();
4
-
5
- //# sourceMappingURL=src_hooks_1aveq0uhs2t1b._.js.map