failproofai 1.0.7-beta.2 → 1.0.8-beta.0

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 (225) 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 +7 -7
  10. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  11. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  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 +15 -15
  18. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +15 -15
  19. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  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 +5 -5
  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 +4 -4
  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 +15 -15
  36. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +14 -14
  37. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +15 -15
  38. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  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 +6 -7
  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 +2 -2
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  54. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  55. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  56. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  57. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +18 -7
  58. package/.next/standalone/.next/server/app/settings/page.js +9 -7
  59. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  60. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  61. package/.next/standalone/.next/server/chunks/{[externals]__1j-zsg5._.js → [externals]__20kzpkf._.js} +1 -1
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0l3yhx4._.js +2 -2
  63. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1ttrwnd._.js +22 -0
  65. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +20 -20
  66. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/_1ek68ln._.js +9 -9
  69. package/.next/standalone/.next/server/chunks/_1q5i8mb._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/lib_16xa545._.js +3 -0
  71. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  72. package/.next/standalone/.next/server/chunks/src_hooks_1aveq0u._.js +5 -0
  73. package/.next/standalone/.next/server/chunks/src_hooks_1eem5a7._.js +3 -0
  74. package/.next/standalone/.next/server/chunks/src_hooks_custom-hooks-loader_ts_0lnb3n3._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[externals]__0ohnuzs._.js +3 -0
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  78. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0qxnccm._.js → [root-of-the-server]__06pflha._.js} +2 -4
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0soxz2z._.js → [root-of-the-server]__12e7nhs._.js} +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0l44ual._.js → [root-of-the-server]__14o3ek1._.js} +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +4 -0
  85. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__19evfi8._.js +3 -0
  86. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +4 -0
  87. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  92. package/.next/standalone/.next/server/chunks/ssr/{_1v-jvrv._.js → _0o4xkpl._.js} +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/{_1mel6y1._.js → _1-7sqrb._.js} +2 -2
  94. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +13 -13
  97. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  99. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  100. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  101. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  102. package/.next/standalone/.next/server/chunks/ssr/node_modules_next_0aiy-os._.js +1 -1
  103. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0-q0umm._.js +3 -0
  104. package/.next/standalone/.next/server/chunks/ssr/src_hooks_06kzv9d._.js +12 -0
  105. package/.next/standalone/.next/server/chunks/ssr/src_hooks_0g194sy._.js +3 -0
  106. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1cv9_c4._.js +12 -0
  107. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1kx9e0d._.js +3 -0
  108. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +5 -0
  109. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-config_ts_04t589g._.js +1 -1
  110. package/.next/standalone/.next/server/chunks/ssr/src_hooks_fp-home_ts_0je3xkv._.js +1 -1
  111. package/.next/standalone/.next/server/chunks/ssr/src_hooks_pack-cli_ts_0t7me65._.js +1 -1
  112. package/.next/standalone/.next/server/chunks/ssr/src_hooks_semantic_pack-policies_ts_0gh_bu_._.js +3 -0
  113. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  114. package/.next/standalone/.next/server/pages/404.html +1 -1
  115. package/.next/standalone/.next/server/pages/500.html +1 -1
  116. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  117. package/.next/standalone/.next/server/server-reference-manifest.json +33 -22
  118. package/.next/standalone/.next/static/chunks/078gnymqoh3r4.js +1 -0
  119. package/.next/standalone/.next/static/chunks/{3brze37td_wnc.js → 0ahfwmbkpgfw4.js} +1 -1
  120. package/.next/standalone/.next/static/chunks/{043j99m8ykg__.css → 0bhidk90e-07f.css} +1 -1
  121. package/.next/standalone/.next/static/chunks/1bu2-nv59ed6i.js +1 -0
  122. package/.next/standalone/.next/static/chunks/{3ugmd_7dyn0id.js → 1v_tp3hm8wyhe.js} +1 -1
  123. package/.next/standalone/.next/static/chunks/2a405_e1o26ol.js +1 -0
  124. package/.next/standalone/.next/static/chunks/{3yxro_r2_o9ad.js → 2vo7qbdbvnc0o.js} +1 -1
  125. package/.next/standalone/.next/static/chunks/{0fqd7m_u81mi5.js → 2z7i-yg59w4pn.js} +1 -1
  126. package/.next/standalone/.next/static/chunks/36uhh9el_oz8e.js +1 -0
  127. package/.next/standalone/.next/static/chunks/{129ag2bw93bdh.js → 3bgot5v6f6s3c.js} +1 -1
  128. package/.next/standalone/.next/static/chunks/{3otmypm6j_xfo.js → 3o3f1ibfci0p7.js} +2 -2
  129. package/.next/standalone/SECURITY.md +53 -0
  130. package/.next/standalone/app/actions/get-jev-config.ts +194 -14
  131. package/.next/standalone/app/actions/update-jev-config.ts +160 -11
  132. package/.next/standalone/app/policies/hooks-client.tsx +5 -3
  133. package/.next/standalone/app/settings/jev-panel.tsx +169 -12
  134. package/.next/standalone/fp-cloud-cli/CHANGELOG.md +22 -4
  135. package/.next/standalone/fp-cloud-cli/fp_cli/client.py +14 -1
  136. package/.next/standalone/fp-cloud-cli/fp_cli/commands/keys_cmds.py +17 -3
  137. package/.next/standalone/fp-cloud-cli/fp_cli/commands/policies_cmds.py +3 -0
  138. package/.next/standalone/fp-cloud-cli/fp_cli/permissions.py +22 -0
  139. package/.next/standalone/fp-cloud-cli/fp_cli/policy_check.py +169 -0
  140. package/.next/standalone/fp-cloud-cli/skill/references/commands.md +1 -1
  141. package/.next/standalone/fp-cloud-cli/tests/test_keys_queries.py +65 -0
  142. package/.next/standalone/fp-cloud-cli/tests/test_policy_check.py +61 -0
  143. package/.next/standalone/package.json +9 -9
  144. package/.next/standalone/sdk/python/CHANGELOG.md +7 -0
  145. package/.next/standalone/sdk/python/failproofai_sdk/_version.py +1 -1
  146. package/.next/standalone/sdk/typescript/CHANGELOG.md +15 -1
  147. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package-lock.json +10 -30
  148. package/.next/standalone/sdk/typescript/integration/fixtures/ai-4/package.json +3 -0
  149. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package-lock.json +4 -16
  150. package/.next/standalone/sdk/typescript/integration/fixtures/ai-5/package.json +3 -0
  151. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package-lock.json +13 -132
  152. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-0.3/package.json +4 -0
  153. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package-lock.json +4 -142
  154. package/.next/standalone/sdk/typescript/integration/fixtures/langchain-dup-core/package.json +4 -0
  155. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package-lock.json +1396 -1016
  156. package/.next/standalone/sdk/typescript/integration/fixtures/mastra-0/package.json +9 -0
  157. package/.next/standalone/sdk/typescript/scripts/release.mjs +30 -0
  158. package/.next/standalone/server.js +1 -1
  159. package/README.md +1 -0
  160. package/bin/failproofai.mjs +37 -6
  161. package/dist/cli.mjs +2861 -1145
  162. package/dist/worker.mjs +815 -197
  163. package/package.json +9 -9
  164. package/src/hooks/builtin-policies.ts +24 -6
  165. package/src/hooks/cloud-connection.ts +190 -1
  166. package/src/hooks/cloud-enrollment-cli.ts +56 -7
  167. package/src/hooks/cloud-introspect.ts +6 -0
  168. package/src/hooks/configure-wizard.ts +26 -6
  169. package/src/hooks/custom-hooks-loader.ts +58 -8
  170. package/src/hooks/effective-reviewers.ts +172 -8
  171. package/src/hooks/flush-cli.ts +35 -8
  172. package/src/hooks/fp-config.ts +266 -1
  173. package/src/hooks/fp-reset.ts +22 -4
  174. package/src/hooks/handler.ts +44 -7
  175. package/src/hooks/hook-activity-store.ts +13 -5
  176. package/src/hooks/jev-cli.ts +1009 -86
  177. package/src/hooks/jev-cloud-connection.ts +392 -0
  178. package/src/hooks/manager.ts +44 -6
  179. package/src/hooks/pack-cli.ts +175 -37
  180. package/src/hooks/pack-failclosed.ts +3 -0
  181. package/src/hooks/pack-manifest.ts +11 -1
  182. package/src/hooks/pack-store.ts +47 -10
  183. package/src/hooks/policy-authority.ts +73 -20
  184. package/src/hooks/policy-catalog.ts +10 -8
  185. package/src/hooks/policy-evaluator.ts +31 -8
  186. package/src/hooks/policy-registry.ts +3 -2
  187. package/src/hooks/policy-reviewability.ts +41 -12
  188. package/src/hooks/policy-types.ts +5 -3
  189. package/src/hooks/semantic/combine.ts +124 -84
  190. package/src/hooks/semantic/decide.ts +16 -0
  191. package/src/hooks/semantic/envelope.ts +18 -59
  192. package/src/hooks/semantic/evaluator.ts +11 -2
  193. package/src/hooks/semantic/facts.ts +7 -1
  194. package/src/hooks/semantic/intent.ts +8 -8
  195. package/src/hooks/semantic/jev-client.ts +540 -28
  196. package/src/hooks/semantic/jev-config.ts +586 -38
  197. package/src/hooks/semantic/jev-review.ts +26 -11
  198. package/src/hooks/semantic/jev-throttle.ts +8 -2
  199. package/src/hooks/semantic/pack-policies.ts +58 -15
  200. package/src/hooks/semantic/policies.ts +4 -3
  201. package/src/hooks/semantic/redact.ts +28 -1
  202. package/src/hooks/semantic/session-root.ts +116 -0
  203. package/src/hooks/semantic/types.ts +11 -0
  204. package/src/hooks/tui.ts +4 -0
  205. package/src/hooks/worker-server.ts +2 -1
  206. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0cuho4x._.js +0 -3
  207. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1bf34x4._.js +0 -22
  208. package/.next/standalone/.next/server/chunks/src_hooks_01frwmb._.js +0 -5
  209. package/.next/standalone/.next/server/chunks/src_hooks_18qtd42._.js +0 -3
  210. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0s_yomn._.js +0 -4
  211. package/.next/standalone/.next/server/chunks/ssr/_1feuvhb._.js +0 -5
  212. package/.next/standalone/.next/server/chunks/ssr/_1w_5l7t._.js +0 -3
  213. package/.next/standalone/.next/server/chunks/ssr/src_hooks_095a_79._.js +0 -5
  214. package/.next/standalone/.next/server/chunks/ssr/src_hooks_15t8kqj._.js +0 -3
  215. package/.next/standalone/.next/server/chunks/ssr/src_hooks_18k8rl0._.js +0 -12
  216. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1fm2w5z._.js +0 -3
  217. package/.next/standalone/.next/server/chunks/ssr/src_hooks_1j0zy3v._.js +0 -3
  218. package/.next/standalone/.next/server/chunks/ssr/src_hooks_builtin-policies_ts_09j2ndl._.js +0 -3
  219. package/.next/standalone/.next/static/chunks/1qd741hzlmjbo.js +0 -1
  220. package/.next/standalone/.next/static/chunks/2_pltstd8-xgs.js +0 -1
  221. package/.next/standalone/.next/static/chunks/2c8j9l6j_b1ci.js +0 -1
  222. package/.next/standalone/.next/static/chunks/3-k569wzcli8q.js +0 -1
  223. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_buildManifest.js +0 -0
  224. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_clientMiddlewareManifest.js +0 -0
  225. /package/.next/standalone/.next/static/{gbEOjBgZAxF2UIUwZVHNu → O_b5R1axf5cb4NxIXq2kS}/_ssgManifest.js +0 -0
@@ -53,6 +53,7 @@ import * as intentStore from "./intent";
53
53
  import { JevError, transportForConfig, type JevTransport } from "./jev-client";
54
54
  import { DEFAULT_JEV_MODE, type JevConfig } from "./jev-config";
55
55
  import * as jevThrottle from "./jev-throttle";
56
+ import { sessionProjectRoot } from "./session-root";
56
57
  import type { SemanticInput } from "./types";
57
58
 
58
59
  /**
@@ -177,12 +178,15 @@ export function resolveTimeout(cfg: JevConfig): number {
177
178
  }
178
179
 
179
180
  /** The semantic policy an attributable Jev verdict is filed under. */
181
+ function jevDecider(outcome: Extract<SemanticOutcome, { status: "ok" }>) {
182
+ const wanted = outcome.verdict.decision === "deny" ? "deny" : "instruct";
183
+ return outcome.verdict.outcomes.find((o) => o.verdict === wanted);
184
+ }
185
+
180
186
  function jevPolicyName(outcome: Extract<SemanticOutcome, { status: "ok" }>): string {
181
- const { verdict } = outcome;
182
- const wanted = verdict.decision === "deny" ? "deny" : "instruct";
183
- const first = verdict.outcomes.find((o) => o.verdict === wanted);
187
+ const first = jevDecider(outcome);
184
188
  if (first) return `semantic/${first.policy}`;
185
- return verdict.beyondTask ? "semantic/beyond-task" : "semantic/jev";
189
+ return outcome.verdict.beyondTask ? "semantic/beyond-task" : "semantic/jev";
186
190
  }
187
191
 
188
192
  /**
@@ -215,24 +219,30 @@ export function toReview(outcome: SemanticOutcome, cached = false): JevReview {
215
219
  // only that no request was made: v1 asks it on every call it sends, with or
216
220
  // without a recorded human message (see `compile.ts`).
217
221
  const injection = sent ? outcome.verdict.injectionSuspected : null;
222
+ // Fired, and no consent softened it: an `instruct` the human's own task did
223
+ // not downgrade from a deny (see "A check that fired without consent keeps
224
+ // the floor" in `combine.ts`).
225
+ const warned = (o: (typeof outcomes)[number]) => o.verdict === "instruct" && o.intent !== "downgraded-task-step";
218
226
  return {
219
227
  kind: "answered",
220
228
  decision: outcome.verdict.decision,
221
229
  reason: outcome.verdict.reason,
222
230
  policyName: jevPolicyName(outcome),
231
+ ...(jevDecider(outcome)?.origin ? { origin: jevDecider(outcome)?.origin } : {}),
223
232
  // Every selected policy's questions were in the request, and `readAnswers`
224
233
  // refuses a response missing any of them — so selected == asked, and only
225
234
  // when a request was actually made.
226
235
  asked: sent ? outcomes.map((o) => o.policy) : [],
227
- // Every answer but `deny`: `none` and `overridden` found nothing to stop,
228
- // and `instruct` looked at the same concern the regex policy names and
229
- // judged it a warning. `combine.ts` clears a reviewable verdict on all
230
- // three, and Jev's own instruct then carries the warning through the
231
- // most-severe merge — see "A warning-level answer clears the deny, and
232
- // leaves the warning" there. A `deny` is what keeps the block.
233
- notDenied: sent ? outcomes.filter((o) => o.verdict !== "deny").map((o) => o.policy) : [],
236
+ // `none` found nothing, `overridden` is what the human asked for, and a
237
+ // deny the human's task softened to a warning is that task's consent — all
238
+ // 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) : [],
234
240
  injectionAsked: injection !== null,
235
241
  injected: injection !== null && injection >= DEFAULT_THRESHOLDS_V1.injection,
242
+ // A check that CAN deny warned without consent: nothing may be cleared on
243
+ // this call (see "A check that fired without consent keeps the floor" in
244
+ // `combine.ts`).
245
+ unclearableWarned: outcomes.some((o) => o.mode === "deny" && warned(o)),
236
246
  // Something did not fit — a human turn, the agent's message, or what T4's
237
247
  // store had already capped before the envelope saw it (it caps to fit the
238
248
  // envelope, so the envelope cannot see that cut; only the evaluator's
@@ -300,11 +310,15 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
300
310
  intent = { userSaid: [], agentLastMessage: null };
301
311
  }
302
312
 
313
+ // Never throws; null only without a cwd, and then there is nothing to pin.
314
+ const projectRoot = sessionProjectRoot(call.sessionId, call.cwd);
315
+
303
316
  const input: SemanticInput = {
304
317
  eventType: call.eventType,
305
318
  toolName: call.toolName,
306
319
  toolInput: toolInputRecord(call.toolInput),
307
320
  ...(call.cwd ? { cwd: call.cwd } : {}),
321
+ ...(projectRoot ? { projectRoot } : {}),
308
322
  ...(call.permissionMode ? { permissionMode: call.permissionMode } : {}),
309
323
  userSaid: intent.userSaid,
310
324
  agentLastMessage: intent.agentLastMessage,
@@ -323,6 +337,7 @@ export function startJevReview(cfg: JevConfig, call: JevCallContext): TwoTierRev
323
337
  let abandoned = false;
324
338
  const answered = evaluateSemantic(input, {
325
339
  transport,
340
+ cli: call.cli,
326
341
  via: route.via,
327
342
  model: route.model,
328
343
  timeoutMs,
@@ -37,8 +37,14 @@
37
37
  * the only evaluator on a configured machine and lives for hours, so the
38
38
  * process is the cache and the bucket is the process's upstream budget: a
39
39
  * caller may build a new wrapper per hook event and every wrapper shares both.
40
- * The worker also serializes `evaluateHookEvent`, so there is never more than
41
- * one Jev call in flight and no in-flight de-duplication is needed.
40
+ * The worker serializes the registry part of `evaluateHookEvent` but hands the
41
+ * queue back while a two-tier review waits on Jev (`worker-server.ts`
42
+ * `release`), so several Jev calls can be in flight at once. The cache fills
43
+ * only when an answer arrives: identical requests in flight together each
44
+ * miss, each take a token and each go upstream (and are billed).
45
+ * ponytail: no in-flight de-duplication; add a single-flight map keyed by the
46
+ * digest if duplicate spend shows up (the joiner's abort must not cancel the
47
+ * leader, and a leader's own abort must not fail the joiner).
42
48
  *
43
49
  * **No on-disk cache**, deliberately. It would only help the one-shot
44
50
  * in-process path, which on a configured machine does not exist (CLAUDE.md,
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ## The replacement rule
5
5
  *
6
- * A pack that declares at least ONE `semantic` entry replaces
6
+ * A FailproofAI pack that declares at least ONE `semantic` entry replaces
7
7
  * `SEMANTIC_POLICIES` wholesale. It mirrors the rule already in force for the
8
8
  * regex builtins — installing a pack stops the compiled builtins registering —
9
9
  * and it exists for the same reason: one source of truth, so a name collision
@@ -11,6 +11,12 @@
11
11
  * arise, and `reviewedBy: ["destructive-deletion"]` in a pack manifest cannot
12
12
  * silently mean the builtin's question set instead of the pack's.
13
13
  *
14
+ * Anyone else's checks are ADDED to the compiled-in set instead. Their names
15
+ * cannot collide with it (a third party's claim to a builtin name is void:
16
+ * `isReservedClaim`),
17
+ * and replacing it would drop deny-mode checks like `credential-exfiltration`
18
+ * that add denies the regex tier does not have — weaker, not noisier.
19
+ *
14
20
  * Two packs that both declare entries CONCATENATE; a name declared by two packs
15
21
  * keeps the first and drops the later one, because the answer map is keyed by
16
22
  * policy name and the second would overwrite the first's questions in the
@@ -33,6 +39,7 @@
33
39
  * regex policies cover — a machine locked out over a typo in the half of the
34
40
  * system whose job is to let more real work through.
35
41
  */
42
+ import { contestedSemanticNames, isFirstPartyPack, isReservedClaim, jevPacks, replacesBuiltinChecks } from "../effective-reviewers";
36
43
  import { hookLogWarn } from "../hook-logger";
37
44
  import {
38
45
  packSemantic,
@@ -88,9 +95,9 @@ const GLOBAL_QUESTION_CHARS = Math.max(
88
95
  * "somebody installed a big pack", and the flag that was supposed to be
89
96
  * unreachable from ordinary work becomes routine.
90
97
  *
91
- * The real sixteen use 19,787 characters at their worst, so this is not a
92
- * constraint on the set we ship. It is the ceiling on what a stranger's pack may
93
- * ask for.
98
+ * The real sixteen use `BUILTIN_QUESTION_CHARS` of it, so this is not a
99
+ * constraint on the set we ship. A stranger's pack is added to them, so what it
100
+ * may ask for is what they leave.
94
101
  */
95
102
  export const MAX_PACK_QUESTION_CHARS = MAX_REQUEST_CHARS - MAX_STATE_CHARS - GLOBAL_QUESTION_CHARS;
96
103
 
@@ -116,16 +123,19 @@ export function questionChars(entry: SemanticManifestEntry): number {
116
123
  return JSON.stringify(questions).length;
117
124
  }
118
125
 
126
+ /** What the compiled-in set spends of that budget when third-party checks join it. */
127
+ export const BUILTIN_QUESTION_CHARS = SEMANTIC_POLICIES.reduce((n, p) => n + questionChars(p as SemanticManifestEntry), 0);
128
+
119
129
  export interface ResolvedSemanticPolicies {
120
130
  policies: ReadonlyArray<SemanticPolicy>;
121
- /** True when a pack supplied the set, so the compiled-in one is not in play. */
131
+ /** True when a pack supplied any of the set. */
122
132
  fromPack: boolean;
123
133
  /** One line per dropped policy. Diagnostics; nothing here changes a verdict. */
124
134
  errors: string[];
125
135
  }
126
136
 
127
137
  /** Turn one validated manifest entry into a policy the compiler can use. */
128
- function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
138
+ function toSemanticPolicy(entry: SemanticManifestEntry, pack: { id: string; version?: string }): SemanticPolicy {
129
139
  // An unknown precondition name DROPS the policy (the caller catches this),
130
140
  // rather than compiling it with no gate at all. Ungating would be the wider
131
141
  // direction, not the weaker one, but it is not what the author asked for: a
@@ -151,6 +161,7 @@ function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
151
161
  // to the identical policy.
152
162
  ...(precondition ? { precondition } : {}),
153
163
  guidance: entry.guidance,
164
+ origin: { packId: pack.id, ...(pack.version ? { packVersion: pack.version } : {}) },
154
165
  };
155
166
  }
156
167
 
@@ -163,18 +174,50 @@ function toSemanticPolicy(entry: SemanticManifestEntry): SemanticPolicy {
163
174
  * a filesystem.
164
175
  */
165
176
  export function semanticPoliciesFromPacks(
166
- packs: ReadonlyArray<Pick<ResolvedPack, "id" | "semantic">>,
177
+ packs: ReadonlyArray<Pick<ResolvedPack, "id" | "semantic"> & { source?: string; version?: string }>,
167
178
  ): ResolvedSemanticPolicies {
168
179
  const declared = packs.filter((p) => packSemantic(p).length > 0);
169
180
  if (declared.length === 0) return { policies: SEMANTIC_POLICIES, fromPack: false, errors: [] };
170
181
 
171
- const policies: SemanticPolicy[] = [];
182
+ // FailproofAI's own checks replace the compiled-in set; anyone else's join it
183
+ // (`replacesBuiltinChecks`, which the reviewer set applies too). First-party
184
+ // packs spend the budget first, so install order cannot starve them.
185
+ const replace = replacesBuiltinChecks(declared);
186
+ const ordered = [...declared.filter((p) => isFirstPartyPack(p)), ...declared.filter((p) => !isFirstPartyPack(p))];
187
+ const policies: SemanticPolicy[] = replace ? [] : [...SEMANTIC_POLICIES];
172
188
  const errors: string[] = [];
173
- const seen = new Set<string>();
174
- let spent = 0;
175
- for (const pack of declared) {
189
+ const seen = new Set<string>(policies.map((p) => p.name));
190
+ // A name two packs declare DIFFERENTLY is asked for nobody. Keeping the first
191
+ // was the escalation: the question that decides another pack's policies came
192
+ // from whichever pack was listed first, so installing a permissive
193
+ // `production-infra-change` beside a real one cleared every policy reviewable
194
+ // by that name. Computed by `effectiveReviewerNames`'s own function, because
195
+ // the name it refuses in the reviewer set and the question refused here have to
196
+ // be the same name — a check in the set with nobody's question, or a question
197
+ // nobody may name, are both worse than neither.
198
+ const contested = contestedSemanticNames(declared);
199
+ let spent = replace ? 0 : BUILTIN_QUESTION_CHARS;
200
+ for (const pack of ordered) {
176
201
  for (const entry of packSemantic(pack)) {
202
+ if (isReservedClaim(pack, entry.name)) {
203
+ errors.push(
204
+ `pack ${pack.id} declares semantic policy ${entry.name}, a name reserved for FailproofAI's own Jev checks, ` +
205
+ `so that pack's version of it is never asked`,
206
+ );
207
+ continue;
208
+ }
209
+ const claimants = contested.get(entry.name);
210
+ if (claimants) {
211
+ // Once per name: every claimant reaches here with the same message.
212
+ const message =
213
+ `packs ${claimants.join(" and ")} declare different semantic policies named ${entry.name}, so it is asked ` +
214
+ `for neither of them and no policy can be cleared by that name`;
215
+ if (!errors.includes(message)) errors.push(message);
216
+ continue;
217
+ }
177
218
  if (seen.has(entry.name)) {
219
+ // Same name, same declaration — a fork or a re-publish of one pack, where
220
+ // the question is identical either way. Kept once, said once.
178
221
  errors.push(`pack ${pack.id} declares semantic policy ${entry.name}, which another pack already declared`);
179
222
  continue;
180
223
  }
@@ -188,7 +231,7 @@ export function semanticPoliciesFromPacks(
188
231
  continue;
189
232
  }
190
233
  try {
191
- policies.push(toSemanticPolicy(entry));
234
+ policies.push(toSemanticPolicy(entry, pack));
192
235
  } catch (err) {
193
236
  errors.push(`pack ${pack.id} semantic policy ${entry.name}: ${err instanceof Error ? err.message : String(err)}`);
194
237
  continue;
@@ -203,7 +246,7 @@ export function semanticPoliciesFromPacks(
203
246
  // the safe one: what a pack's regex half names in `reviewedBy` will not match
204
247
  // it, so those policies stay hard rather than being cleared by questions
205
248
  // nobody validated.
206
- if (policies.length === 0) return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
249
+ if (policies.length === (replace ? 0 : SEMANTIC_POLICIES.length)) return { policies: SEMANTIC_POLICIES, fromPack: false, errors };
207
250
  return { policies, fromPack: true, errors };
208
251
  }
209
252
 
@@ -224,7 +267,7 @@ const warned = new Set<string>();
224
267
  * fails in the safe direction: the set a pack's `reviewedBy` names is not the
225
268
  * builtin set, so nothing gets cleared by a policy nobody could read.
226
269
  */
227
- export function resolveSemanticPolicies(): ReadonlyArray<SemanticPolicy> {
270
+ export function resolveSemanticPolicies(cli?: string): ReadonlyArray<SemanticPolicy> {
228
271
  let packs: ReadonlyArray<ResolvedPack> = [];
229
272
  let manifestErrors: string[] = [];
230
273
  try {
@@ -234,7 +277,7 @@ export function resolveSemanticPolicies(): ReadonlyArray<SemanticPolicy> {
234
277
  } catch {
235
278
  return SEMANTIC_POLICIES;
236
279
  }
237
- const resolved = semanticPoliciesFromPacks(packs);
280
+ const resolved = semanticPoliciesFromPacks(jevPacks(packs, cli));
238
281
  // Once per process per message, like `warnAuthority`: this runs on every gate
239
282
  // event, and in the warm worker that is every tool call of every session.
240
283
  for (const message of [...manifestErrors, ...resolved.errors]) {
@@ -53,8 +53,9 @@ const OUTSIDE_PROJECT: ReadonlySet<PathFact["relation"]> = new Set<PathFact["rel
53
53
  * question has to be asked. Two tests, because the two tiers measure "the
54
54
  * project" from different roots:
55
55
  *
56
- * - `relation` is computed against the enclosing git repository
57
- * (`facts.projectRoot`). Anything but `inside_project` / `project_root` is
56
+ * - `relation` is computed against `facts.projectRoot`: the git root of the
57
+ * directory the session started in, pinned by `session-root.ts` so a later
58
+ * `cd` cannot move it. Anything but `inside_project` / `project_root` is
58
59
  * outside it.
59
60
  * - the partner compares against `$CLAUDE_PROJECT_DIR` or, when that is unset,
60
61
  * the session's LIVE cwd, which drifts below the git root as the agent `cd`s.
@@ -260,7 +261,7 @@ export const SEMANTIC_POLICIES: ReadonlyArray<SemanticPolicy> = [
260
261
  },
261
262
  },
262
263
  ],
263
- guidance: "This sends secrets or private files to a remote destination. It is blocked regardless of instructions.",
264
+ guidance: "This sends secrets or private files to a remote destination, which the user cannot approve. Do not send them.",
264
265
  },
265
266
  {
266
267
  name: "remote-code-execution",
@@ -2017,6 +2017,25 @@ function literalValue(
2017
2017
 
2018
2018
  /** Long runs of token characters: candidates for the high-entropy rule. Greedy, so a match is a whole run. */
2019
2019
  const LONG_TOKEN_RE = /[A-Za-z0-9_-]{32,}/g;
2020
+ /**
2021
+ * The same, in standard base64: `+` and `/` split an AWS secret access key
2022
+ * (40 characters, ~72% hold one) into runs under 32, so `LONG_TOKEN_RE` never
2023
+ * saw it and the key went out beside its redacted `AKIA` id.
2024
+ */
2025
+ const LONG_B64_TOKEN_RE = /[A-Za-z0-9+/_-]{32,}={0,2}/g;
2026
+
2027
+ /**
2028
+ * A standard-base64 run that looks generated. A path or URL is a run of `/`
2029
+ * too, so any `+`/`/`-separated segment that is a word rejects it — the
2030
+ * word-built run then falls through to `LONG_TOKEN_RE`, which still takes a
2031
+ * random token sitting inside it.
2032
+ */
2033
+ function looksRandomB64(t: string): boolean {
2034
+ if (!/[+/]/.test(t)) return false;
2035
+ const bare = t.replace(/=+$/, "");
2036
+ if (bare.split(/[+/]/).some((seg) => /^[A-Z]?[a-z]{3,}$/.test(seg) || /^[A-Z]{4,}$/.test(seg))) return false;
2037
+ return looksRandomToken(bare.replace(/[+/]/g, ""));
2038
+ }
2020
2039
 
2021
2040
  // ── Secret names ─────────────────────────────────────────────────────────────
2022
2041
 
@@ -2801,7 +2820,15 @@ export function redactSecretsDetailed(text: string, opts: RedactOptions = {}): R
2801
2820
 
2802
2821
  out = redactNamedSecrets(out, c);
2803
2822
 
2804
- // 8. Anything left that looks generated.
2823
+ // 8. Anything left that looks generated: standard base64 first, whole, so
2824
+ // no piece of it is left for the narrower pass to miss.
2825
+ out = replaceCounting(
2826
+ out,
2827
+ LONG_B64_TOKEN_RE,
2828
+ (m, _g, offset, whole) => (looksRandomB64(m) && !insideDigest(whole, offset) ? marker("high-entropy token") : null),
2829
+ c,
2830
+ "skip",
2831
+ );
2805
2832
  out = replaceCounting(
2806
2833
  out,
2807
2834
  LONG_TOKEN_RE,
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The project a session started in, pinned the first time the session is seen.
3
+ *
4
+ * The hook payload's `cwd` is the agent's LIVE shell directory. Claude Code
5
+ * keeps a `cd` across calls, so a session started in `~/work` that once ran
6
+ * `cd ~/work/api` reports `~/work/api` from then on — and `facts.projectRoot`,
7
+ * derived from that, shrank with it. Every file elsewhere in `~/work` then read
8
+ * as `outside_project_in_home`, and `read-outside-workspace` fired on the
9
+ * project the human opened.
10
+ *
11
+ * Letting the root FOLLOW the `cd` instead would be a bypass: `cd ~/.ssh` in
12
+ * one call and `cat id_rsa` in the next would make `~/.ssh` the project. So the
13
+ * root is fixed at the session's first reviewed call and never moves after.
14
+ * A `cd` can change how a relative path resolves (that is still the live cwd's
15
+ * job, in `extractPaths`) but never what counts as inside the project.
16
+ *
17
+ * Failure is never stricter or looser than before this file existed: no
18
+ * session id, an unreadable store, or a store someone else can write to all
19
+ * fall back to the live cwd's root — exactly the old behaviour. A session whose
20
+ * first reviewed call came after a `cd` pins that later directory; same again.
21
+ */
22
+ import { mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
23
+ import { isAbsolute, resolve } from "node:path";
24
+ import { semanticDir } from "../fp-home";
25
+ import { findProjectRoot } from "./facts";
26
+ import { looseIntentStoreDir } from "./intent";
27
+
28
+ const SESSION_ID_RE = /^[A-Za-z0-9._-]{1,128}$/;
29
+ const ROOT_FILE_RE = /^[A-Za-z0-9._-]{1,128}\.json$/;
30
+ /** Sessions outlive the intent store's six hours; a week covers any real one. */
31
+ export const SESSION_ROOT_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
32
+ const DIR_WRITABLE_BY_OTHERS = 0o022;
33
+
34
+ const rootsDir = (): string => resolve(semanticDir(), "roots");
35
+ const rootFile = (sessionId: string): string => resolve(rootsDir(), `${sessionId}.json`);
36
+
37
+ /** A stored root is only believed when it is a plain absolute path, and never `/`. */
38
+ function validRoot(v: unknown): v is string {
39
+ return typeof v === "string" && isAbsolute(v) && resolve(v) === v && v !== "/";
40
+ }
41
+
42
+ /**
43
+ * Whether the store may be trusted. A directory anyone else can write to lets
44
+ * them swap in a root of their choosing — a wider root is fewer paths outside
45
+ * it — so a loose one is ignored, after the same tightening the intent store
46
+ * applies (`looseIntentStoreDir`).
47
+ */
48
+ function storeIsOurs(): boolean {
49
+ if (process.platform === "win32") return true;
50
+ if (looseIntentStoreDir() !== null) return false;
51
+ try {
52
+ const mode = statSync(rootsDir()).mode & 0o777;
53
+ return (mode & DIR_WRITABLE_BY_OTHERS) === 0;
54
+ } catch {
55
+ return true; // Not there yet: nothing to read out of it.
56
+ }
57
+ }
58
+
59
+ function readPinned(sessionId: string): string | null {
60
+ try {
61
+ const parsed = JSON.parse(readFileSync(rootFile(sessionId), "utf8")) as { root?: unknown };
62
+ return validRoot(parsed?.root) ? parsed.root : null;
63
+ } catch {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ /** Delete root files older than {@link SESSION_ROOT_MAX_AGE_MS}. Best effort. */
69
+ export function pruneSessionRoots(now: number = Date.now()): number {
70
+ let removed = 0;
71
+ try {
72
+ const dir = rootsDir();
73
+ for (const name of readdirSync(dir)) {
74
+ if (!ROOT_FILE_RE.test(name)) continue;
75
+ const path = resolve(dir, name);
76
+ try {
77
+ const st = statSync(path);
78
+ if (st.isFile() && now - st.mtimeMs > SESSION_ROOT_MAX_AGE_MS) {
79
+ unlinkSync(path);
80
+ removed++;
81
+ }
82
+ } catch {
83
+ // Raced with another writer, or already gone.
84
+ }
85
+ }
86
+ } catch {
87
+ // No directory yet.
88
+ }
89
+ return removed;
90
+ }
91
+
92
+ /**
93
+ * The project root to judge this call's paths against: the one pinned for the
94
+ * session, or — on its first reviewed call — the live cwd's root, pinned now.
95
+ * Null only when there is no cwd at all. Never throws.
96
+ */
97
+ export function sessionProjectRoot(sessionId: string | undefined, cwd: string | undefined, now: number = Date.now()): string | null {
98
+ if (!cwd) return null;
99
+ const live = findProjectRoot(cwd);
100
+ if (!sessionId || !SESSION_ID_RE.test(sessionId)) return live;
101
+ try {
102
+ if (!storeIsOurs()) return live;
103
+ const pinned = readPinned(sessionId);
104
+ if (pinned !== null) return pinned;
105
+ if (!validRoot(live)) return live;
106
+ mkdirSync(rootsDir(), { recursive: true, mode: 0o700 });
107
+ // `wx`: the first writer wins. Two calls racing on a new session both
108
+ // computed a root from the same moment; whichever lands is the pin, and
109
+ // the loser's EEXIST just means it answers with the live root this once.
110
+ writeFileSync(rootFile(sessionId), JSON.stringify({ root: live, at: now }), { mode: 0o600, flag: "wx" });
111
+ pruneSessionRoots(now);
112
+ } catch {
113
+ // Unwritable store: the old behaviour, for this call.
114
+ }
115
+ return live;
116
+ }
@@ -68,6 +68,8 @@ export interface SemanticPolicy {
68
68
  precondition?: (facts: Facts) => boolean;
69
69
  /** Shown to the agent when the policy fires. */
70
70
  guidance: string;
71
+ /** The pack that declared it; absent for the compiled-in set. Attribution only. */
72
+ origin?: { packId: string; packVersion?: string };
71
73
  }
72
74
 
73
75
  export interface PathFact {
@@ -94,6 +96,11 @@ export interface SemanticInput {
94
96
  toolName: string;
95
97
  toolInput: Record<string, unknown>;
96
98
  cwd?: string;
99
+ /**
100
+ * The project root pinned for the session (`session-root.ts`), so a `cd`
101
+ * cannot move what counts as inside the project. Absent: derived from `cwd`.
102
+ */
103
+ projectRoot?: string;
97
104
  permissionMode?: string;
98
105
  /** What the human typed recently, oldest first. Trusted channel only. */
99
106
  userSaid: string[];
@@ -121,6 +128,10 @@ export type IntentMode = "v0" | "v1";
121
128
  export interface PolicyOutcome {
122
129
  policy: string;
123
130
  mode: "deny" | "instruct";
131
+ /** The policy's own `userCanOverride`: false is a check no consent can clear. */
132
+ userCanOverride?: boolean;
133
+ /** The pack that declared the check, when one did (`SemanticPolicy.origin`). */
134
+ origin?: { packId: string; packVersion?: string };
124
135
  /** min over probe probabilities — one weak probe spoils the evidence. */
125
136
  evidence: number;
126
137
  exempt: number | null;
package/src/hooks/tui.ts CHANGED
@@ -96,6 +96,10 @@ export interface MultiSelectOptions<T> {
96
96
 
97
97
  const ESC = "\x1B";
98
98
 
99
+ /** How every "the key was on argv" warning opens, whichever command took it (`jev setup --token`, `config --token`). */
100
+ export const TOKEN_ON_ARGV =
101
+ "--token was on the command line: your shell history has it, and while this command ran any process of yours could read it from the process list.";
102
+
99
103
  // ── glyphs ────────────────────────────────────────────────────────────────
100
104
  // Exported so the other branded prompts (install-prompt.ts) share the exact
101
105
  // same set instead of hand-syncing copies.
@@ -77,7 +77,8 @@ const TASK_DEADLINE_MS = 60_000;
77
77
 
78
78
  /**
79
79
  * Serializes every request across every connection through one chain, so
80
- * `evaluateHookEvent` calls never overlap. `.catch()` on each link keeps the
80
+ * `evaluateHookEvent` calls never overlap while reading the registry (see
81
+ * `release` below). `.catch()` on each link keeps the
81
82
  * chain alive even if a handler throws unexpectedly — one bad request must
82
83
  * never wedge every request queued behind it.
83
84
  *
@@ -1,3 +0,0 @@
1
- module.exports=[24868,(e,t,r)=>{t.exports=e.x("fs/promises",()=>require("fs/promises"))},12714,(e,t,r)=>{t.exports=e.x("node:fs/promises",()=>require("node:fs/promises"))},46786,(e,t,r)=>{t.exports=e.x("os",()=>require("os"))},14747,(e,t,r)=>{t.exports=e.x("path",()=>require("path"))},71809,e=>{"use strict";async function t(e,t){let r=Array(e.length),i=0;async function n(){for(;i<e.length;){let t=i++;try{r[t]={status:"fulfilled",value:await e[t]()}}catch(e){r[t]={status:"rejected",reason:e}}}}let a=Array.from({length:Math.min(t,e.length)},()=>n());return await Promise.all(a),r}e.s(["batchAll",0,t])},28557,e=>{"use strict";e.s(["formatDate",0,function(e){return new Intl.DateTimeFormat("en-US",{month:"short",day:"numeric",year:"numeric",hour:"numeric",minute:"2-digit",hour12:!0}).format(e)}])},54214,67405,e=>{"use strict";var t=e.i(24868),r=e.i(14747),i=e.i(81580),n=e.i(56936);async function a(e,i,n,a){let o=`agent-${a}.jsonl`;for(let a of[(0,r.join)(e,i,o),(0,r.join)(e,i,n,o),(0,r.join)(e,i,n,"subagents",o)])if(!(0,r.relative)(e,a).startsWith(".."))try{return await (0,t.access)(a),a}catch(e){if("ENOENT"===e.code)continue;break}return null}var o=e.i(83534),s=e.i(71809),l=e.i(28557);function u(e){if(e<1e3)return`${e}ms`;let t=Math.round(e/100);if(t<600)return`${(t/10).toFixed(1)}s`;let r=Math.round(e/1e3);if(r<3600){let e=Math.floor(r/60);return`${e}m ${r%60}s`}let i=Math.round(e/6e4),n=Math.floor(i/60);return`${n}h ${i%60}m`}function c(e){let t=(0,l.formatDate)(e),r=e.getMilliseconds().toString().padStart(3,"0");return`${t}.${r}`}function f(e,t,r,i){return{_source:i,uuid:e.uuid||"",parentUuid:e.parentUuid??null,timestamp:t,timestampMs:r.getTime(),timestampFormatted:c(r)}}async function m(e,t){let r=e.split("\n").filter(e=>""!==e.trim()),i=new Map,n=[],a=[],o=new Set,s=!1;for(let e=0;e<r.length;e++){let l;e>0&&e%200==0&&await new Promise(e=>setImmediate(e));let u=r[e];try{l=JSON.parse(u)}catch{continue}let c={...l,_source:t};a.push(c);let m=l.type,d=l.timestamp;if(!d)continue;let p=new Date(d),g=p.getTime();if("user"===m){let e=l.message;if(Array.isArray(e?.content)){let t=e.content;if(t.some(e=>"tool_result"===e.type)){let e=l.toolUseResult,r="string"==typeof e?.agentId?e.agentId:void 0;for(let e of(r&&/^[a-f0-9]+$/.test(r)&&o.add(r),t)){if("tool_result"!==e.type)continue;let t=e.tool_use_id;if(!t)continue;let{text:n,images:a}=function(e){let t=e.content;if("string"==typeof t)return{text:t};if(Array.isArray(t)){let e=t.filter(e=>"text"===e.type).map(e=>e.text),r=t.filter(e=>"image"===e.type).map(e=>{let t=e.source;return{base64:t.data,mediaType:t.media_type}}).filter(e=>e.base64&&e.mediaType);return{text:e.length>0?e.join("\n"):void 0,images:r.length>0?r:void 0}}return{}}(e);i.set(t,{timestamp:d,timestampMs:g,content:n,images:a,agentId:r})}continue}}let r="string"==typeof e?.content?e.content:"";n.push({type:"user",...f(l,d,p,t),message:{role:"user",content:r}});continue}if("assistant"===m){let e=l.message,r=[];if(Array.isArray(e?.content)&&(r=e.content.filter(e=>["text","tool_use","thinking"].includes(e.type)).map(e=>{if("text"===e.type)return{type:"text",text:e.text};if("tool_use"===e.type){let t=e.input;return{type:"tool_use",id:e.id,name:e.name,input:e.input??{},..."Task"===e.name&&t?{subagentType:t.subagent_type,subagentDescription:t.description}:{}}}return{type:"thinking",thinking:e.thinking,signature:e.signature}})),0===r.length)continue;n.push({type:"assistant",...f(l,d,p,t),message:{role:"assistant",content:r,model:e?.model}});continue}if("file-history-snapshot"===m||"progress"===m||"system"===m){n.push({type:m,...f(l,d,p,t),raw:{...l}});continue}if("queue-operation"===m){let e=s?"Session Resumed":"Session Started";s=!0,n.push({type:"queue-operation",...f(l,d,p,t),label:e});continue}}let l=0;for(let e of n)if("assistant"===e.type)for(let t of(++l%200==0&&await new Promise(e=>setImmediate(e)),e.message.content)){if("tool_use"!==t.type)continue;let r=i.get(t.id);if(!r)continue;let n=new Date(r.timestamp),a=Math.max(0,r.timestampMs-e.timestampMs);t.result={timestamp:r.timestamp,timestampFormatted:c(n),content:r.content,images:r.images,durationMs:a,durationFormatted:u(a)},r.agentId&&(t.subagentId=r.agentId)}return n.length>500&&await new Promise(e=>setImmediate(e)),n.sort((e,t)=>e.timestampMs-t.timestampMs),{entries:n,rawLines:a,subagentIds:Array.from(o)}}async function d(e,t="session"){return(await m(e,t)).entries}async function p(e,o){let l=(0,n.resolveProjectPath)(e),u=(0,i.getClaudeProjectsPath)(),c=(0,r.join)(l,`${o}.jsonl`),f=await (0,t.readFile)(c,"utf-8"),{entries:d,rawLines:p,subagentIds:g}=await m(f,"session");if(0===g.length)return{entries:d,rawLines:p,subagentIds:[]};let h=await (0,s.batchAll)(g.map(r=>async()=>{let i=`agent-${r}`,n=await a(u,e,o,r);if(!n)return null;let s=await (0,t.readFile)(n,"utf-8"),{entries:l,rawLines:c}=await m(s,i);return{entries:l,rawLines:c}}),4),y=[...d],w=[...p];for(let e of h)"fulfilled"===e.status&&e.value&&(y.push(...e.value.entries),w.push(...e.value.rawLines));return y.length>500&&await new Promise(e=>setImmediate(e)),y.sort((e,t)=>e.timestampMs-t.timestampMs),{entries:y,rawLines:w,subagentIds:g}}e.s(["formatDuration",0,u],67405),(0,o.runtimeCache)((e,t)=>p(e,t),60,{maxSize:50}),e.s(["baseEntry",0,f,"formatTimestamp",0,c,"parseLogContent",0,d,"parseRawLines",0,function(e,t){return e.split("\n").filter(e=>""!==e.trim()).flatMap(e=>{try{let r=JSON.parse(e);return void 0!==t&&(r._source=t),[r]}catch{return[]}})}],54214)},40911,e=>{"use strict";let t={info:0,warn:1,error:2};function r(e){return t[e]>=t.warn}function i(e){return`[failproofai${new Date().toISOString()}] ${e}`}e.s(["logActivity",0,function(e,t,n){if(!r("info"))return;let a=[`user=${e}`,`action=${t}`];n&&a.push(n),console.log(i("ACTIVITY"),a.join(" "))},"logError",0,function(e,t){void 0!==t?console.error(i("ERROR"),e,t):console.error(i("ERROR"),e)},"logWarn",0,function(e,t){r("warn")&&(void 0!==t?console.warn(i("WARN"),e,t):console.warn(i("WARN"),e))}])},81580,e=>{"use strict";var t=e.i(46786),r=e.i(14747);e.s(["decodeFolderName",0,function(e){return/^[A-Za-z]--/.test(e)?e[0]+":/"+e.slice(3).replace(/-/g,"/"):e.replace(/-/g,"/")},"encodeFolderName",0,function(e){let t=/^([A-Za-z]):[\\/](.*)$/.exec(e);return t?t[1]+"--"+t[2].replace(/[\\/]/g,"-"):e.replace(/[\\/]/g,"-")},"getClaudeProjectsPath",0,function(){let e=process.env.CLAUDE_PROJECTS_PATH;return e||(0,r.join)((0,t.homedir)(),".claude","projects")}])},56936,e=>{"use strict";var t=e.i(12714),r=e.i(50227),i=e.i(81580),n=e.i(83534),a=e.i(71809),o=e.i(40911),s=e.i(28557);let l=/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;async function u(e,r){try{return(await (0,t.stat)(e)).mtime}catch(e){return(0,o.logWarn)(`Failed to stat ${r}:`,e),new Date(0)}}async function c(e){try{if(!(await (0,t.stat)(e)).isDirectory())return null;return await (0,t.readdir)(e,{withFileTypes:!0})}catch{return null}}async function f(){try{let e=(0,i.getClaudeProjectsPath)(),t=await c(e);if(!t)return[];let n=(await (0,a.batchAll)(t.filter(e=>e.isDirectory()).map(t=>async()=>{let i=(0,r.join)(e,t.name),n=await u(i,t.name);return{name:t.name,path:i,isDirectory:!0,lastModified:n,lastModifiedFormatted:(0,s.formatDate)(n),cli:["claude"]}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return n.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),n}catch(e){return(0,o.logError)("Error reading Claude project folders:",e),[]}}async function m(){let[{getCodexProjects:t},{getCopilotProjects:r},{getCursorProjects:i},{getOpenCodeProjects:n},{getPiProjects:a},{getHermesProjects:s},{getOpenClawProjects:l},{getFactoryProjects:u},{getDevinProjects:c},{getAntigravityProjects:m},{getGooseProjects:d}]=await Promise.all([e.A(37352),e.A(5807),e.A(97452),e.A(82978),e.A(45093),e.A(67347),e.A(38978),e.A(62998),e.A(52400),e.A(91301),e.A(31854)]),[p,g,h,y,w,E,j,M,A,v,x,P]=await Promise.all([f(),t().catch(e=>((0,o.logError)("Error reading Codex projects:",e),[])),r().catch(e=>((0,o.logError)("Error reading Copilot projects:",e),[])),i().catch(e=>((0,o.logError)("Error reading Cursor projects:",e),[])),n().catch(e=>((0,o.logError)("Error reading OpenCode projects:",e),[])),a().catch(e=>((0,o.logError)("Error reading Pi projects:",e),[])),s().catch(e=>((0,o.logError)("Error reading Hermes projects:",e),[])),l().catch(e=>((0,o.logError)("Error reading OpenClaw projects:",e),[])),u().catch(e=>((0,o.logError)("Error reading Factory projects:",e),[])),c().catch(e=>((0,o.logError)("Error reading Devin projects:",e),[])),m().catch(e=>((0,o.logError)("Error reading Antigravity projects:",e),[])),d().catch(e=>((0,o.logError)("Error reading Goose projects:",e),[]))]);return function(...e){let t=new Map;for(let r of e)for(let e of r){let r=t.get(e.name);if(!r){t.set(e.name,{...e,cli:[...e.cli]});continue}let i=[...r.cli];for(let t of e.cli)i.includes(t)||i.push(t);let n=e.lastModified.getTime()>r.lastModified.getTime()?e:r;t.set(e.name,{...r,cli:i,lastModified:n.lastModified,lastModifiedFormatted:n.lastModifiedFormatted,sessionCount:void 0===r.sessionCount&&void 0===e.sessionCount?void 0:(r.sessionCount??0)+(e.sessionCount??0)})}let r=Array.from(t.values());return r.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),r}(p,g,h,y,w,E,j,M,A,v,x,P)}function d(e){if(!e)throw RangeError("Empty project name");if(/^[/\\]/.test(e))throw RangeError("Absolute project name");let t=(0,r.resolve)((0,i.getClaudeProjectsPath)()),n=(0,r.resolve)((0,r.join)(t,e));if(!n.startsWith(t+r.sep))throw RangeError("Project path escapes root");return n}function p(e){let t=e.match(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i);return t?t[0]:void 0}async function g(e){try{let t=await c(e);if(!t)return[];let i=t.filter(e=>e.isFile()&&e.name.endsWith(".jsonl")&&p(e.name)),n=(await (0,a.batchAll)(i.map(t=>async()=>{let i=(0,r.join)(e,t.name),n=await u(i,t.name);return{name:t.name,path:i,lastModified:n,lastModifiedFormatted:(0,s.formatDate)(n),sessionId:p(t.name),cli:"claude"}}),16)).filter(e=>"fulfilled"===e.status).map(e=>e.value);return n.sort((e,t)=>t.lastModified.getTime()-e.lastModified.getTime()),n}catch(e){return(0,o.logError)("Error reading session files:",e),[]}}(0,n.runtimeCache)(m,30),(0,n.runtimeCache)(e=>g(e),30),e.s(["UUID_RE",0,l,"resolveProjectPath",0,d,"resolveSessionFilePath",0,function(e,t){if(!l.test(t))throw RangeError("Invalid session ID");let i=d(e);return(0,r.join)(i,`${t}.jsonl`)}])},83534,e=>{"use strict";e.s(["runtimeCache",0,function(e,t,r){let i=new Map,n=new Map,a=r?.maxSize;return async(...r)=>{let o=JSON.stringify(r),s=i.get(o);if(s&&Date.now()<s.expiry)return s.data;let l=n.get(o);if(l)return l;let u=e(...r).then(e=>{if(n.delete(o),a&&i.size>=a){let e=i.keys().next().value;i.delete(e)}return i.set(o,{data:e,expiry:Date.now()+1e3*t}),e},e=>{throw n.delete(o),e});return n.set(o,u),u}}])}];
2
-
3
- //# sourceMappingURL=%5Broot-of-the-server%5D__0cuho4x._.js.map