failproofai 1.0.9-beta.0 → 1.0.9-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 (145) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/build-manifest.json +5 -5
  3. package/.next/standalone/.next/prerender-manifest.json +3 -3
  4. package/.next/standalone/.next/required-server-files.json +1 -1
  5. package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +2 -2
  6. package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/standalone/.next/server/app/_global-error.html +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
  11. package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +6 -6
  12. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
  13. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +2 -2
  15. package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
  17. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  19. package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
  20. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  21. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  22. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  24. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  25. package/.next/standalone/.next/server/app/api/audit/status/route.js.nft.json +1 -1
  26. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/download/[project]/[session]/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/audit/page/build-manifest.json +2 -2
  32. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  33. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  34. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  35. package/.next/standalone/.next/server/app/index.html +1 -1
  36. package/.next/standalone/.next/server/app/index.rsc +14 -14
  37. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +13 -13
  38. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  39. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  40. package/.next/standalone/.next/server/app/page/build-manifest.json +2 -2
  41. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  42. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  43. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/policies/page/build-manifest.json +2 -2
  45. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +14 -14
  46. package/.next/standalone/.next/server/app/policies/page.js +1 -1
  47. package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
  48. package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/project/[name]/page/build-manifest.json +2 -2
  50. package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/build-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  56. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  57. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  58. package/.next/standalone/.next/server/app/projects/page/build-manifest.json +2 -2
  59. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  60. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  61. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  62. package/.next/standalone/.next/server/app/settings/page/build-manifest.json +2 -2
  63. package/.next/standalone/.next/server/app/settings/page/server-reference-manifest.json +8 -8
  64. package/.next/standalone/.next/server/app/settings/page.js.nft.json +1 -1
  65. package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
  66. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0o07qi9._.js +1 -1
  67. package/.next/standalone/.next/server/chunks/_09dz7xv._.js +1 -1
  68. package/.next/standalone/.next/server/chunks/_0tovk6q._.js +1 -1
  69. package/.next/standalone/.next/server/chunks/_0trp3yc._.js +1 -1
  70. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_01r25oi._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/node_modules_posthog-node_dist_entrypoints_index_node_mjs_09z9-p7._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__04usis8._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__056wjo4._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0n0xg95._.js +2 -2
  76. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0rwtwpm._.js +2 -2
  77. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__12e7nhs._.js → [root-of-the-server]__1-bm5zf._.js} +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__11mayhe._.js +2 -2
  79. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__15578wp._.js +2 -2
  80. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1m_svbe._.js +2 -2
  81. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0tmz862._.js → [root-of-the-server]__1ox4w55._.js} +2 -2
  82. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1pprgri._.js +2 -2
  83. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1q4p5b8._.js +2 -2
  84. package/.next/standalone/.next/server/chunks/ssr/_042cgl1._.js +1 -1
  85. package/.next/standalone/.next/server/chunks/ssr/_08x1r5t._.js +1 -1
  86. package/.next/standalone/.next/server/chunks/ssr/_0bqoto4._.js +1 -1
  87. package/.next/standalone/.next/server/chunks/ssr/{_1p3iz2d._.js → _0n6zrjv._.js} +2 -2
  88. package/.next/standalone/.next/server/chunks/ssr/_1gb0ifp._.js +2 -2
  89. package/.next/standalone/.next/server/chunks/ssr/{_0n0lja6._.js → _1pa3k5k._.js} +1 -1
  90. package/.next/standalone/.next/server/chunks/ssr/_1zopuov._.js +1 -1
  91. package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_policies_page_actions_1sp2-yo.js +2 -2
  92. package/.next/standalone/.next/server/chunks/ssr/app_actions_get-scheduled-audit_ts_0ei9sni._.js +1 -1
  93. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  94. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  95. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  96. package/.next/standalone/.next/server/chunks/ssr/app_settings_02tf1h4._.js +1 -1
  97. package/.next/standalone/.next/server/chunks/ssr/node_modules_posthog-node_dist_entrypoints_index_node_mjs_11bnuzn._.js +1 -1
  98. package/.next/standalone/.next/server/chunks/ssr/src_hooks_effective-reviewers_ts_1h4wtvo._.js +1 -1
  99. package/.next/standalone/.next/server/middleware-build-manifest.js +5 -5
  100. package/.next/standalone/.next/server/pages/404.html +1 -1
  101. package/.next/standalone/.next/server/pages/500.html +1 -1
  102. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  103. package/.next/standalone/.next/server/server-reference-manifest.json +23 -23
  104. package/.next/standalone/.next/static/chunks/{06jxjnw8bfczc.js → 0542cuk_va8g1.js} +1 -1
  105. package/.next/standalone/.next/static/chunks/{1h0if8ky362as.js → 0h--izcpk__m9.js} +1 -1
  106. package/.next/standalone/.next/static/chunks/{0snylg3y1i47a.js → 1srecm9k-z_i1.js} +1 -1
  107. package/.next/standalone/.next/static/chunks/{03001k9jy-kst.js → 28cmd5molgwa3.js} +1 -1
  108. package/.next/standalone/.next/static/chunks/{06k7gsfnb7jkh.js → 2b59i-n9l-4of.js} +1 -1
  109. package/.next/standalone/.next/static/chunks/{42m0f2hh8vfb1.js → 2k5vhij_w1g82.js} +1 -1
  110. package/.next/standalone/.next/static/chunks/2o1fy-waqn1he.js +6 -0
  111. package/.next/standalone/.next/static/chunks/{3rq7fjj9we0v3.js → 30z-240_lrczt.js} +1 -1
  112. package/.next/standalone/.next/static/chunks/38gpf5tb_rd0m.js +1 -0
  113. package/.next/standalone/.next/static/chunks/3nyf50dub1vuk.js +1 -0
  114. package/.next/standalone/.next/static/chunks/{0qejem4-zt-hy.js → 42-l25bbfo0ec.js} +1 -1
  115. package/.next/standalone/.next/static/chunks/{turbopack-38nzuvp95_2rv.js → turbopack-11dn2pegmg9_9.js} +1 -1
  116. package/.next/standalone/fp-cloud-cli/uv.lock +3 -3
  117. package/.next/standalone/node_modules/@next/env/package.json +1 -1
  118. package/.next/standalone/node_modules/next/dist/build/swc/index.js +1 -1
  119. package/.next/standalone/node_modules/next/dist/compiled/next-server/pages-turbo.runtime.prod.js +1 -1
  120. package/.next/standalone/node_modules/next/dist/lib/patch-incorrect-lockfile.js +3 -3
  121. package/.next/standalone/node_modules/next/dist/server/config.js +1 -1
  122. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-turbopack.js +2 -2
  123. package/.next/standalone/node_modules/next/dist/server/dev/hot-reloader-webpack.js +1 -1
  124. package/.next/standalone/node_modules/next/dist/server/lib/app-info-log.js +1 -1
  125. package/.next/standalone/node_modules/next/dist/server/lib/start-server.js +1 -1
  126. package/.next/standalone/node_modules/next/dist/telemetry/anonymous-meta.js +1 -1
  127. package/.next/standalone/node_modules/next/dist/telemetry/events/swc-load-failure.js +1 -1
  128. package/.next/standalone/node_modules/next/dist/telemetry/events/version.js +2 -2
  129. package/.next/standalone/node_modules/next/package.json +10 -10
  130. package/.next/standalone/package.json +11 -11
  131. package/.next/standalone/sdk/python/uv.lock +18 -18
  132. package/.next/standalone/server.js +1 -1
  133. package/dist/cli.mjs +932 -725
  134. package/dist/worker.mjs +4 -1
  135. package/package.json +11 -11
  136. package/src/hooks/integrations.ts +152 -22
  137. package/src/hooks/jev-activity.ts +5 -0
  138. package/src/hooks/policy-reviewability.ts +4 -2
  139. package/src/hooks/safe-config-write.ts +119 -0
  140. package/.next/standalone/.next/static/chunks/0247hc9ath5t-.js +0 -1
  141. package/.next/standalone/.next/static/chunks/2qsrrrl2maqsb.js +0 -1
  142. package/.next/standalone/.next/static/chunks/3ns-egfh8i8i5.js +0 -6
  143. /package/.next/standalone/.next/static/{z1C0kJVF0NCtWn5R88hW3 → D1BjwOkHNPetPo47DhV4_}/_buildManifest.js +0 -0
  144. /package/.next/standalone/.next/static/{z1C0kJVF0NCtWn5R88hW3 → D1BjwOkHNPetPo47DhV4_}/_clientMiddlewareManifest.js +0 -0
  145. /package/.next/standalone/.next/static/{z1C0kJVF0NCtWn5R88hW3 → D1BjwOkHNPetPo47DhV4_}/_ssgManifest.js +0 -0
package/dist/worker.mjs CHANGED
@@ -1162,7 +1162,7 @@ function instruct(reason) {
1162
1162
  }
1163
1163
 
1164
1164
  // package.json
1165
- var version = "1.0.9-beta.0";
1165
+ var version = "1.0.9-beta.2";
1166
1166
  var init_package = () => {};
1167
1167
 
1168
1168
  // src/hooks/policy-types.ts
@@ -4172,8 +4172,11 @@ function sanitizeJevActivity(entry) {
4172
4172
  drop("evaluator");
4173
4173
  if (!(typeof out.jevDecision === "string" && DECISIONS.has(out.jevDecision)))
4174
4174
  drop("jevDecision");
4175
+ const unknownMode = out.jevMode !== undefined && !(typeof out.jevMode === "string" && MODES.has(out.jevMode));
4175
4176
  if (!(typeof out.jevMode === "string" && MODES.has(out.jevMode)))
4176
4177
  drop("jevMode");
4178
+ if (unknownMode)
4179
+ drop("jevCleared");
4177
4180
  if (Array.isArray(out.jevCleared)) {
4178
4181
  const names = [
4179
4182
  ...new Set(out.jevCleared.filter(isJevPolicyName))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "failproofai",
3
- "version": "1.0.9-beta.0",
3
+ "version": "1.0.9-beta.2",
4
4
  "description": "Observability and enforcement for AI agent harnesses. 40 built-in policies hooked into 12 of them — Claude Code, Codex, Cursor, Hermes, OpenClaw and more — blocking the tool call before it runs. Local dashboard included, no account needed.",
5
5
  "bin": {
6
6
  "failproofai": "./dist/cli.mjs",
@@ -81,14 +81,14 @@
81
81
  "access": "public"
82
82
  },
83
83
  "devDependencies": {
84
- "@anthropic-ai/sdk": "^0.127.0",
84
+ "@anthropic-ai/sdk": "^0.128.0",
85
85
  "@mdx-js/mdx": "^3.1.1",
86
86
  "@tailwindcss/postcss": "^4.3.1",
87
87
  "@tanstack/react-virtual": "^3.14.10",
88
88
  "@testing-library/jest-dom": "^7.0.0",
89
89
  "@testing-library/react": "^16.3.2",
90
90
  "@testing-library/user-event": "^14.6.5",
91
- "@types/node": "26.6.1",
91
+ "@types/node": "26.6.2",
92
92
  "@types/react": "19.3.0",
93
93
  "@types/react-dom": "^19.2.3",
94
94
  "@vitejs/plugin-react": "^6.1.0",
@@ -122,15 +122,15 @@
122
122
  "browserslist": "4.28.8"
123
123
  },
124
124
  "optionalDependencies": {
125
- "@failproofai/failproofaid-linux-x64": "1.0.9-beta.0",
126
- "@failproofai/failproofaid-linux-arm64": "1.0.9-beta.0",
127
- "@failproofai/failproofaid-darwin-x64": "1.0.9-beta.0",
128
- "@failproofai/failproofaid-darwin-arm64": "1.0.9-beta.0"
125
+ "@failproofai/failproofaid-linux-x64": "1.0.9-beta.2",
126
+ "@failproofai/failproofaid-linux-arm64": "1.0.9-beta.2",
127
+ "@failproofai/failproofaid-darwin-x64": "1.0.9-beta.2",
128
+ "@failproofai/failproofaid-darwin-arm64": "1.0.9-beta.2"
129
129
  },
130
130
  "failproofaidBinaries": {
131
- "linux-x64": "044b6619007227b8aa1fc1095b8c61a17bd90e11da53183ac1f5adecdcae6839",
132
- "linux-arm64": "479450d7d15c52a59bd1a8cf4c7086be9210e8d31ad4f98d99b123f571e944bd",
133
- "darwin-x64": "11562912ff726195bfb966ed24a774ffea38d01acc3edc1f9bfa3ea9c298977f",
134
- "darwin-arm64": "5bbaee1ec89d2d8032b7f16e502fb96891fbc6aa2ee6c861f43a64a0a60e61ba"
131
+ "linux-x64": "cd0a7124ae94ae78753cda6ffad985ba30be80443d8bc93d617ce507ba04e211",
132
+ "linux-arm64": "925446186bbfa827275356a9dc80b7d5351a6dcce9d926731419bbd91c1f98ba",
133
+ "darwin-x64": "cd35a6549f402b6c04848772074edb110a54a242fe7488792e59f8e8a00af475",
134
+ "darwin-arm64": "587b045accada4947a556e1bc1d748780a846c2ef95cf8dafb973a23fdbcb173"
135
135
  }
136
136
  }
@@ -25,6 +25,7 @@ import { resolve, dirname, basename } from "node:path";
25
25
  import { fileURLToPath } from "node:url";
26
26
  import { homedir } from "node:os";
27
27
  import { parseDocument, type Document } from "yaml";
28
+ import { writeConfigFileAtomic } from "./safe-config-write";
28
29
  import { listHermesProfiles, hermesRoot } from "../../lib/hermes-profiles";
29
30
  import { listOpenClawProfiles } from "../../lib/openclaw-profiles";
30
31
  import {
@@ -65,32 +66,82 @@ import {
65
66
 
66
67
  // ── Generic helpers ─────────────────────────────────────────────────────────
67
68
 
69
+ /**
70
+ * The error every config reader throws for a file it cannot read faithfully.
71
+ *
72
+ * These files belong to the user's agent. A config that exists but does not
73
+ * parse is never treated as empty: writing our entries into an "empty" document
74
+ * would replace every other setting in it, and the agent would come back without
75
+ * its model, keys or channels. Refusing leaves the file exactly as the user has
76
+ * it and says which file and why.
77
+ */
78
+ export class UnreadableAgentConfigError extends Error {
79
+ constructor(readonly path: string, reason: string) {
80
+ super(
81
+ `Refusing to modify ${path}: ${reason}. ` +
82
+ `failproofai never rewrites a config it could not parse — fix or move the file, then retry.`,
83
+ );
84
+ this.name = "UnreadableAgentConfigError";
85
+ }
86
+ }
87
+
68
88
  function readJsonFile(path: string): Record<string, unknown> {
69
89
  if (!existsSync(path)) return {};
70
- const raw = readFileSync(path, "utf8");
71
- return JSON.parse(raw) as Record<string, unknown>;
90
+ let raw: string;
91
+ try {
92
+ raw = readFileSync(path, "utf8");
93
+ } catch (err) {
94
+ throw new UnreadableAgentConfigError(path, `it could not be read (${err instanceof Error ? err.message : String(err)})`);
95
+ }
96
+ if (raw.trim() === "") return {};
97
+ let parsed: unknown;
98
+ try {
99
+ parsed = JSON.parse(raw);
100
+ } catch (err) {
101
+ throw new UnreadableAgentConfigError(path, `it is not valid JSON (${err instanceof Error ? err.message : String(err)})`);
102
+ }
103
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
104
+ throw new UnreadableAgentConfigError(path, "its top level is not a JSON object");
105
+ }
106
+ return parsed as Record<string, unknown>;
72
107
  }
73
108
 
74
109
  function writeJsonFile(path: string, data: Record<string, unknown>): void {
75
- mkdirSync(dirname(path), { recursive: true });
76
- writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf8");
110
+ writeConfigFileAtomic(path, JSON.stringify(data, null, 2) + "\n");
77
111
  }
78
112
 
79
113
  /** Read a YAML file as a `Document` so writes round-trip the user's other keys +
80
- * comments (a plain parse→stringify would strip comments). Empty / missing /
81
- * corrupt → an empty Document. Used by the Hermes integration, whose config
82
- * lives in `~/.hermes/config.yaml`. */
114
+ * comments (a plain parse→stringify would strip comments). Missing or empty →
115
+ * an empty Document; unreadable or unparseable → {@link UnreadableAgentConfigError},
116
+ * never an empty Document that the next write would put in the user's place.
117
+ * Used by the Hermes integration, whose config lives in `~/.hermes/config.yaml`. */
83
118
  function readYamlDoc(path: string): Document {
119
+ if (!existsSync(path)) return parseDocument("");
120
+ let text: string;
84
121
  try {
85
- return existsSync(path) ? parseDocument(readFileSync(path, "utf8")) : parseDocument("");
122
+ text = readFileSync(path, "utf8");
123
+ } catch (err) {
124
+ throw new UnreadableAgentConfigError(path, `it could not be read (${err instanceof Error ? err.message : String(err)})`);
125
+ }
126
+ const doc = parseDocument(text);
127
+ if (doc.errors.length > 0) {
128
+ const first = doc.errors[0]?.message.split("\n")[0] ?? "syntax error";
129
+ throw new UnreadableAgentConfigError(path, `it does not parse as YAML (${first})`);
130
+ }
131
+ return doc;
132
+ }
133
+
134
+ /** {@link readYamlDoc} for read-only callers (status, health): an unreadable file is `null`. */
135
+ function inspectYamlDoc(path: string): Document | null {
136
+ try {
137
+ return readYamlDoc(path);
86
138
  } catch {
87
- return parseDocument("");
139
+ return null;
88
140
  }
89
141
  }
90
142
 
91
143
  function writeYamlDoc(path: string, doc: Document): void {
92
- mkdirSync(dirname(path), { recursive: true });
93
- writeFileSync(path, doc.toString(), "utf8");
144
+ writeConfigFileAtomic(path, doc.toString());
94
145
  }
95
146
 
96
147
  function isMarkedHook(hook: unknown): boolean {
@@ -1127,7 +1178,7 @@ export const opencode: Integration = {
1127
1178
  // (a) Write the shim file. mkdirSync is recursive so the plugins/ dir
1128
1179
  // is created on first install.
1129
1180
  mkdirSync(dirname(pluginPath), { recursive: true });
1130
- writeFileSync(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope), "utf8");
1181
+ writeConfigFileAtomic(pluginPath, buildOpenCodePluginShim(binaryPath, effectiveScope));
1131
1182
 
1132
1183
  // (b) Merge our entry into the plugin array idempotently. Replace any
1133
1184
  // existing failproofai-marked entry; otherwise append.
@@ -1422,7 +1473,30 @@ const HERMES_PLUGIN_SOURCE_DIR = "hermes-plugin";
1422
1473
  function getHermesPluginSourcePath(): string {
1423
1474
  const fromEnv = process.env.FAILPROOFAI_PACKAGE_ROOT;
1424
1475
  if (fromEnv) return resolve(fromEnv, HERMES_PLUGIN_SOURCE_DIR);
1425
- return resolve(fileURLToPath(import.meta.url), "..", "..", "..", HERMES_PLUGIN_SOURCE_DIR);
1476
+ return findHermesPluginSourcePath(dirname(fileURLToPath(import.meta.url)));
1477
+ }
1478
+
1479
+ /**
1480
+ * The package's `hermes-plugin/`, found by walking up from `startDir`.
1481
+ *
1482
+ * The CLI entry point sets `FAILPROOFAI_PACKAGE_ROOT`, so this is only the
1483
+ * fallback — but a fixed number of parents is right for exactly one layout:
1484
+ * three from `src/hooks/` is the package root, three from the bundled
1485
+ * `dist/cli.mjs` is the package's PARENT. Walking up to the first directory
1486
+ * that really holds the plugin's manifest works from source, from `dist/`, and
1487
+ * from the dashboard's standalone build alike. When none is found the old
1488
+ * answer is returned, so the install still fails loudly on a missing asset.
1489
+ */
1490
+ export function findHermesPluginSourcePath(startDir: string): string {
1491
+ let dir = resolve(startDir);
1492
+ for (let i = 0; i < 8; i++) {
1493
+ const candidate = resolve(dir, HERMES_PLUGIN_SOURCE_DIR);
1494
+ if (existsSync(resolve(candidate, "plugin.yaml"))) return candidate;
1495
+ const parent = dirname(dir);
1496
+ if (parent === dir) break;
1497
+ dir = parent;
1498
+ }
1499
+ return resolve(startDir, "..", "..", HERMES_PLUGIN_SOURCE_DIR);
1426
1500
  }
1427
1501
 
1428
1502
  export function hermesPluginPathForSettings(settingsPath: string): string {
@@ -1599,11 +1673,16 @@ export function installHermesPlugin(
1599
1673
  if (linked && (existing.kind === "absent" || existing.kind === "link")) {
1600
1674
  try {
1601
1675
  renameSync(temporary, destination);
1676
+ return "linked";
1602
1677
  } catch (err) {
1603
- rmSync(temporary, { force: true });
1604
- throw err;
1678
+ // Windows cannot always rename a junction over another junction; the
1679
+ // backup swap below moves the old link aside first, so try that before
1680
+ // giving up. With nothing there to move aside, the rename is the error.
1681
+ if (existing.kind !== "link") {
1682
+ rmSync(temporary, { force: true });
1683
+ throw err;
1684
+ }
1605
1685
  }
1606
- return "linked";
1607
1686
  }
1608
1687
 
1609
1688
  const backup = `${destination}.backup-${suffix}`;
@@ -1669,26 +1748,44 @@ function removeLegacyHermesHooks(doc: Document): number {
1669
1748
  }
1670
1749
 
1671
1750
  function hermesConfigState(settingsPath: string): {
1751
+ /** Hermes will load it: listed in `plugins.enabled` AND not in `plugins.disabled`. */
1672
1752
  pluginEnabled: boolean;
1753
+ /** Listed in `plugins.enabled`, whatever `plugins.disabled` says. */
1754
+ pluginListed: boolean;
1755
+ /** Listed in `plugins.disabled`, which Hermes checks first and which wins. */
1756
+ pluginDisabled: boolean;
1673
1757
  legacyShellHookPresent: boolean;
1758
+ unreadable?: boolean;
1674
1759
  } {
1675
1760
  if (!existsSync(settingsPath)) {
1676
- return { pluginEnabled: false, legacyShellHookPresent: false };
1761
+ return { pluginEnabled: false, pluginListed: false, pluginDisabled: false, legacyShellHookPresent: false };
1762
+ }
1763
+ const doc = inspectYamlDoc(settingsPath);
1764
+ if (!doc) {
1765
+ return { pluginEnabled: false, pluginListed: false, pluginDisabled: false, legacyShellHookPresent: false, unreadable: true };
1677
1766
  }
1678
- const doc = readYamlDoc(settingsPath);
1679
1767
  const js = (doc.toJS() ?? {}) as {
1680
1768
  hooks?: Record<string, HermesHookEntry[]>;
1681
1769
  plugins?: HermesPluginsConfig;
1682
1770
  };
1683
1771
  const enabled = js.plugins?.enabled;
1772
+ const disabled = js.plugins?.disabled;
1684
1773
  const legacyShellHookPresent =
1685
1774
  !!js.hooks &&
1686
1775
  typeof js.hooks === "object" &&
1687
1776
  Object.values(js.hooks).some(
1688
1777
  (entries) => Array.isArray(entries) && entries.some((entry) => isMarkedHook(entry)),
1689
1778
  );
1779
+ // Hermes gates `plugins.disabled` before `plugins.enabled`
1780
+ // (hermes_cli/plugins_discovery.py `gate_manifest`), so a plugin in both
1781
+ // lists never loads. Treating that as enabled reported a switched-off plugin
1782
+ // healthy and let `update` skip it as "already current".
1783
+ const pluginListed = Array.isArray(enabled) && enabled.includes(HERMES_PLUGIN_ID);
1784
+ const pluginDisabled = Array.isArray(disabled) && disabled.includes(HERMES_PLUGIN_ID);
1690
1785
  return {
1691
- pluginEnabled: Array.isArray(enabled) && enabled.includes(HERMES_PLUGIN_ID),
1786
+ pluginEnabled: pluginListed && !pluginDisabled,
1787
+ pluginListed,
1788
+ pluginDisabled,
1692
1789
  legacyShellHookPresent,
1693
1790
  };
1694
1791
  }
@@ -1763,7 +1860,30 @@ export async function migrateHermesProfiles(opts: {
1763
1860
  const base = { name: profile.name, home: profile.home };
1764
1861
  const legacy = config.legacyShellHookPresent;
1765
1862
 
1766
- if (!legacy && !ours && !config.pluginEnabled) {
1863
+ // A config.yaml that does not parse is never edited. Whether it is ours to
1864
+ // report is read from the raw text: it cannot be parsed to find out.
1865
+ if (config.unreadable) {
1866
+ let raw = "";
1867
+ try {
1868
+ raw = readFileSync(settingsPath, "utf8");
1869
+ } catch {
1870
+ raw = "";
1871
+ }
1872
+ const mentionsUs = ours || raw.includes(FAILPROOFAI_HOOK_MARKER) || /failproofai/.test(raw);
1873
+ results.push(
1874
+ mentionsUs
1875
+ ? {
1876
+ ...base,
1877
+ status: "failed",
1878
+ detail: `${settingsPath} does not parse as YAML; left untouched`,
1879
+ legacyShellHooksRemain: /--hook\s+\S+\s+--cli\s+hermes|__failproofai_hook__/.test(raw),
1880
+ }
1881
+ : { ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false },
1882
+ );
1883
+ continue;
1884
+ }
1885
+
1886
+ if (!legacy && !ours && !config.pluginListed && !config.pluginDisabled) {
1767
1887
  results.push({ ...base, status: "untouched", detail: "no failproofai integration", legacyShellHooksRemain: false });
1768
1888
  continue;
1769
1889
  }
@@ -1844,12 +1964,16 @@ export interface HermesProfileHealth {
1844
1964
  /** How the plugin is installed: a link into the package, a copy, or neither. */
1845
1965
  pluginMode: "link" | "copy" | null;
1846
1966
  pluginEnabled: boolean;
1967
+ /** failproofai is listed in `plugins.disabled`, so Hermes will not load it. */
1968
+ pluginDisabled: boolean;
1847
1969
  legacyShellHookPresent: boolean;
1848
1970
  /**
1849
1971
  * Still enforced only by legacy config.yaml shell hooks. Hermes cron jobs run
1850
1972
  * with none of those, so their tool calls go unchecked.
1851
1973
  */
1852
1974
  cronUnchecked: boolean;
1975
+ /** config.yaml exists but does not parse; failproofai will not touch it. */
1976
+ configUnreadable: boolean;
1853
1977
  healthy: boolean;
1854
1978
  }
1855
1979
 
@@ -1868,9 +1992,11 @@ export function hermesProfileHealth(): HermesProfileHealth[] {
1868
1992
  pluginInstalled,
1869
1993
  pluginMode: plugin.kind === "link" || plugin.kind === "copy" ? plugin.kind : null,
1870
1994
  pluginEnabled: config.pluginEnabled,
1995
+ pluginDisabled: config.pluginDisabled,
1871
1996
  legacyShellHookPresent: config.legacyShellHookPresent,
1872
1997
  cronUnchecked: config.legacyShellHookPresent && !pluginWorks,
1873
- healthy: pluginWorks && !config.legacyShellHookPresent,
1998
+ configUnreadable: config.unreadable === true,
1999
+ healthy: pluginWorks && !config.legacyShellHookPresent && config.unreadable !== true,
1874
2000
  };
1875
2001
  });
1876
2002
  }
@@ -1881,6 +2007,9 @@ export function hermesProfileStatusRows(): Array<[string, string]> {
1881
2007
  .filter((profile) => existsSync(profile.home))
1882
2008
  .map((profile) => {
1883
2009
  const label = "hermes/" + profile.name;
2010
+ if (profile.configUnreadable) {
2011
+ return [label, `UNHEALTHY — ${profile.settingsPath} does not parse; failproofai leaves it untouched until it is fixed`];
2012
+ }
1884
2013
  if (profile.cronUnchecked) {
1885
2014
  // Not a cosmetic leftover: the shell-hook integration does not reach
1886
2015
  // cron jobs at all, so this profile has an unenforced path.
@@ -1891,7 +2020,8 @@ export function hermesProfileStatusRows(): Array<[string, string]> {
1891
2020
  }
1892
2021
  const problems: string[] = [];
1893
2022
  if (!profile.pluginInstalled) problems.push("plugin files missing or incomplete");
1894
- if (!profile.pluginEnabled) problems.push("plugin not enabled");
2023
+ if (profile.pluginDisabled) problems.push("plugin disabled (listed in plugins.disabled)");
2024
+ else if (!profile.pluginEnabled) problems.push("plugin not enabled");
1895
2025
  if (profile.legacyShellHookPresent) problems.push("legacy shell hook also present");
1896
2026
  if (problems.length > 0) return [label, "UNHEALTHY — " + problems.join("; ")];
1897
2027
  return [
@@ -236,7 +236,12 @@ export function sanitizeJevActivity<T extends JevActivityFields>(entry: T): T {
236
236
 
237
237
  if (!(typeof out.evaluator === "string" && EVALUATORS.has(out.evaluator))) drop("evaluator");
238
238
  if (!(typeof out.jevDecision === "string" && DECISIONS.has(out.jevDecision))) drop("jevDecision");
239
+ // A mode this build does not know (a row written before the mode was named
240
+ // `observe`) cannot say whether its clears were enforced, so they are not
241
+ // counted as anything: dropping only the mode would file them as enforced.
242
+ const unknownMode = out.jevMode !== undefined && !(typeof out.jevMode === "string" && MODES.has(out.jevMode));
239
243
  if (!(typeof out.jevMode === "string" && MODES.has(out.jevMode))) drop("jevMode");
244
+ if (unknownMode) drop("jevCleared");
240
245
 
241
246
  if (Array.isArray(out.jevCleared)) {
242
247
  const names = [
@@ -61,7 +61,7 @@ import { resolve } from "node:path";
61
61
  import { discoverPolicyFiles } from "./custom-hooks-loader";
62
62
  import { customPoliciesDir } from "./fp-home";
63
63
  import { configuredCustomPolicyPaths, findProjectConfigDir, readMergedHooksConfig } from "./hooks-config";
64
- import { hasInstalledPacks, readInstalledPacks } from "./pack-manifest";
64
+ import { hasInstalledRegexPacks, readInstalledPacks } from "./pack-manifest";
65
65
  import { resolvePolicyAuthority } from "./policy-authority";
66
66
  import { POLICY_CATALOG } from "./policy-catalog";
67
67
  import { normalizePolicyName } from "./policy-registry";
@@ -167,7 +167,9 @@ export function surveyReviewableCoverage(cwd?: string): ReviewableCoverage {
167
167
  */
168
168
  let reviewers: ReadonlySet<string> | undefined;
169
169
  try {
170
- packsInstalled = hasInstalledPacks();
170
+ // Only a pack with regex policies retires the shim, exactly as handler.ts
171
+ // decides it: a Jev-only pack (FailproofAI/jev-policies) replaces none.
172
+ packsInstalled = hasInstalledRegexPacks();
171
173
  const packs = readInstalledPacks().packs;
172
174
  for (const pack of packs) {
173
175
  const selected = pack.enabled;
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Crash-safe writes for configuration files that belong to someone else.
3
+ *
4
+ * Every agent integration edits a file the user's agent reads on startup:
5
+ * `~/.claude/settings.json`, `~/.hermes/config.yaml`, `~/.openclaw/openclaw.json`
6
+ * and the rest. A plain `writeFileSync` truncates that file first and fills it
7
+ * second, so a crash, a full disk or a killed process in between leaves the agent
8
+ * with a half-written config — for Hermes that is a gateway that will not start.
9
+ * And a later install then refuses to touch the file because it no longer parses,
10
+ * so nothing repairs it.
11
+ *
12
+ * So the new content never goes near the live file until it is complete and on
13
+ * disk:
14
+ *
15
+ * 1. write it to a uniquely named temporary file in the SAME directory (a
16
+ * rename is only atomic within one filesystem),
17
+ * 2. fsync that file,
18
+ * 3. copy the current file to `<name>.failproofai-backup` — the last version
19
+ * before failproofai changed it, for a person to restore by hand,
20
+ * 4. rename the temporary file over the live one (atomic on POSIX, and a
21
+ * replacing move on Windows), then fsync the directory so the rename
22
+ * itself survives a power cut.
23
+ *
24
+ * Any failure before step 4 leaves the live file exactly as it was and removes
25
+ * the temporary file. The file keeps its permission bits (Hermes keeps
26
+ * `config.yaml` at 0600), and a config that is a symlink — a dotfiles checkout —
27
+ * is written through to its target rather than replaced by a regular file.
28
+ */
29
+ import {
30
+ chmodSync,
31
+ closeSync,
32
+ copyFileSync,
33
+ existsSync,
34
+ fsyncSync,
35
+ lstatSync,
36
+ mkdirSync,
37
+ openSync,
38
+ realpathSync,
39
+ renameSync,
40
+ rmSync,
41
+ statSync,
42
+ writeSync,
43
+ } from "node:fs";
44
+ import { randomBytes } from "node:crypto";
45
+ import { basename, dirname, join } from "node:path";
46
+
47
+ /** Where the copy of the previous version is kept, beside the file itself. */
48
+ export function configBackupPath(path: string): string {
49
+ return `${resolveWriteTarget(path)}.failproofai-backup`;
50
+ }
51
+
52
+ /** Hooks for tests to simulate an interruption at the one step that matters. */
53
+ export interface AtomicWriteDeps {
54
+ rename?: (from: string, to: string) => void;
55
+ }
56
+
57
+ /** A symlinked config is written through to what it points at. */
58
+ function resolveWriteTarget(path: string): string {
59
+ try {
60
+ if (lstatSync(path).isSymbolicLink()) return realpathSync(path);
61
+ } catch {
62
+ // Absent or unreadable: write to the path as given.
63
+ }
64
+ return path;
65
+ }
66
+
67
+ export function writeConfigFileAtomic(path: string, content: string, deps: AtomicWriteDeps = {}): void {
68
+ const target = resolveWriteTarget(path);
69
+ const dir = dirname(target);
70
+ mkdirSync(dir, { recursive: true });
71
+
72
+ let mode: number | undefined;
73
+ try {
74
+ mode = statSync(target).mode & 0o777;
75
+ } catch {
76
+ mode = undefined; // a new file: the process umask decides, as writeFileSync would
77
+ }
78
+
79
+ const temporary = join(dir, `.${basename(target)}.failproofai-${process.pid}-${randomBytes(4).toString("hex")}.tmp`);
80
+ let fd: number | undefined;
81
+ try {
82
+ fd = openSync(temporary, "wx", mode ?? 0o666);
83
+ const bytes = Buffer.from(content, "utf8");
84
+ let written = 0;
85
+ while (written < bytes.length) written += writeSync(fd, bytes, written, bytes.length - written);
86
+ fsyncSync(fd);
87
+ closeSync(fd);
88
+ fd = undefined;
89
+ // `open` applies the umask; the live file's own bits are what should survive.
90
+ if (mode !== undefined) chmodSync(temporary, mode);
91
+
92
+ if (existsSync(target)) copyFileSync(target, `${target}.failproofai-backup`);
93
+
94
+ (deps.rename ?? renameSync)(temporary, target);
95
+ } catch (err) {
96
+ if (fd !== undefined) {
97
+ try {
98
+ closeSync(fd);
99
+ } catch {
100
+ // already closed or never opened
101
+ }
102
+ }
103
+ rmSync(temporary, { force: true });
104
+ throw err;
105
+ }
106
+
107
+ // Make the rename itself durable. Not supported on every platform (Windows
108
+ // cannot open a directory); the file content is already safe either way.
109
+ try {
110
+ const dirFd = openSync(dir, "r");
111
+ try {
112
+ fsyncSync(dirFd);
113
+ } finally {
114
+ closeSync(dirFd);
115
+ }
116
+ } catch {
117
+ // best effort
118
+ }
119
+ }
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,34578,e=>{"use strict";var t=e.i(95187);let a=(0,t.createServerReference)("60b2a3a075bc71a6052007070cfceb60df6ec81cc5",t.callServer,void 0,t.findSourceMapURL,"searchHookActivityAction");e.s(["searchHookActivityAction",0,a])},73520,e=>{"use strict";var t=e.i(43476),a=e.i(24071),r=e.i(67927);e.s(["default",0,function({currentPage:e,totalPages:l,onPageChange:o}){if(l<=1)return null;let s=e=>`px-3 py-2 text-sm rounded-md transition-colors flex items-center gap-1 bg-muted text-muted-foreground ${e?"opacity-50 cursor-not-allowed":"hover:bg-muted/80"}`;return(0,t.jsxs)("div",{className:"flex items-center justify-center gap-2 py-4",children:[(0,t.jsxs)("button",{onClick:()=>o(e-1),disabled:1===e,className:s(1===e),"aria-label":"Previous page",children:[(0,t.jsx)(a.ChevronLeft,{className:"w-4 h-4"}),(0,t.jsx)("span",{className:"hidden sm:inline",children:"Previous"})]}),(0,t.jsx)("div",{className:"flex items-center gap-1",children:(()=>{if(l<=7)return Array.from({length:l},(e,t)=>t+1);let t=[1],a=Math.max(2,e-1),r=Math.min(l-1,e+1);e<=3&&(r=Math.min(5,l-1)),e>=l-2&&(a=Math.max(2,l-4)),a>2&&t.push("ellipsis-start");for(let e=a;e<=r;e++)t.push(e);return r<l-1&&t.push("ellipsis-end"),t.push(l),t})().map(a=>"string"==typeof a?(0,t.jsx)("span",{className:"px-2 text-muted-foreground",children:"..."},a):(0,t.jsx)("button",{onClick:()=>o(a),className:`min-w-[2.5rem] px-3 py-2 text-sm rounded-md transition-colors ${e===a?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground hover:bg-muted/80"}`,"aria-label":`Page ${a}`,"aria-current":e===a?"page":void 0,children:a},a))}),(0,t.jsxs)("button",{onClick:()=>o(e+1),disabled:e===l,className:s(e===l),"aria-label":"Next page",children:[(0,t.jsx)("span",{className:"hidden sm:inline",children:"Next"}),(0,t.jsx)(r.ChevronRight,{className:"w-4 h-4"})]})]})}])},24578,e=>{"use strict";let t={claude:{id:"claude",label:"Claude Code",badgeClasses:"bg-orange-500/10 text-orange-400 border-orange-500/20"},codex:{id:"codex",label:"OpenAI Codex",badgeClasses:"bg-purple-500/10 text-purple-400 border-purple-500/20"},copilot:{id:"copilot",label:"GitHub Copilot",badgeClasses:"bg-blue-500/10 text-blue-400 border-blue-500/20"},cursor:{id:"cursor",label:"Cursor Agent",badgeClasses:"bg-emerald-500/10 text-emerald-400 border-emerald-500/20"},opencode:{id:"opencode",label:"OpenCode",badgeClasses:"bg-amber-500/10 text-amber-400 border-amber-500/20"},pi:{id:"pi",label:"Pi",badgeClasses:"bg-pink-500/10 text-pink-400 border-pink-500/20"},hermes:{id:"hermes",label:"Hermes",badgeClasses:"bg-indigo-500/10 text-indigo-400 border-indigo-500/20"},openclaw:{id:"openclaw",label:"OpenClaw",badgeClasses:"bg-teal-500/10 text-teal-400 border-teal-500/20"},factory:{id:"factory",label:"Factory Droid",badgeClasses:"bg-rose-500/10 text-rose-400 border-rose-500/20"},devin:{id:"devin",label:"Devin CLI",badgeClasses:"bg-violet-500/10 text-violet-400 border-violet-500/20"},antigravity:{id:"antigravity",label:"Antigravity CLI",badgeClasses:"bg-cyan-500/10 text-cyan-400 border-cyan-500/20"},goose:{id:"goose",label:"Goose",badgeClasses:"bg-lime-500/10 text-lime-400 border-lime-500/20"}};e.s(["KNOWN_CLI_IDS",0,["claude","codex","copilot","cursor","opencode","pi","hermes","openclaw","factory","devin","antigravity","goose"],"getCliBadgeClasses",0,function(e){return t[e]?.badgeClasses??t.claude.badgeClasses},"getCliLabel",0,function(e){return t[e]?.label??e},"isKnownCli",0,function(e){return"string"==typeof e&&Object.prototype.hasOwnProperty.call(t,e)}])},34713,e=>{"use strict";e.s(["formatDuration",0,function(e){if(e<1e3)return`${e}ms`;let t=Math.round(e/100);if(t<600)return`${(t/10).toFixed(1)}s`;let a=Math.round(e/1e3);if(a<3600){let e=Math.floor(a/60);return`${e}m ${a%60}s`}let r=Math.round(e/6e4),l=Math.floor(r/60);return`${l}h ${r%60}m`},"formatRelativeTime",0,function(e){let t=Date.now()-e;return t<6e4?`${Math.max(1,Math.floor(t/1e3))}s ago`:t<36e5?`${Math.floor(t/6e4)}m ago`:t<864e5?`${Math.floor(t/36e5)}h ago`:`${Math.floor(t/864e5)}d ago`}])},80060,2711,e=>{"use strict";var t=e.i(18566),a=e.i(71645);function r(e){let t=e.getFullYear(),a=String(e.getMonth()+1).padStart(2,"0"),r=String(e.getDate()).padStart(2,"0");return`${t}-${a}-${r}`}function l(e){var t;if(!e||(t=e,!/^\d{4}-\d{2}-\d{2}$/.test(t)||isNaN(Date.parse(t))))return null;let[a,r,l]=e.split("-").map(Number);return new Date(a,r-1,l,12,0,0)}e.s(["useUrlParams",0,function(){let e=(0,t.useSearchParams)(),r=(0,t.useRouter)(),l=(0,t.usePathname)(),o=(0,a.useRef)(null),s=(0,a.useCallback)(t=>e.get(t),[e]),i=(0,a.useCallback)(()=>new URLSearchParams(e.toString()),[e]),n=(0,a.useCallback)(t=>{o.current&&clearTimeout(o.current),o.current=setTimeout(()=>{let a=new URLSearchParams(e.toString());for(let[e,r]of Object.entries(t))void 0===r||""===r?a.delete(e):a.set(e,r);let o=a.toString(),s=o?`${l}?${o}`:l;r.replace(s,{scroll:!1})},150)},[e,l,r]);return(0,a.useEffect)(()=>()=>{o.current&&clearTimeout(o.current)},[]),{get:s,getAll:i,setAll:n}}],80060),e.s(["dateRangeToParams",0,function(e){let t={};return e.from&&(t.from=r(e.from)),e.to&&(t.to=r(e.to)),t},"keywordsToParam",0,function(e){if(0!==e.length)return e.map(e=>encodeURIComponent(e)).join(",")},"pageToParam",0,function(e){return e<=1?void 0:String(e)},"paramToKeywords",0,function(e){return e?e.split(",").map(e=>decodeURIComponent(e.trim())).filter(e=>e.length>0):[]},"paramToPage",0,function(e){if(!e)return 1;let t=parseInt(e,10);return Number.isFinite(t)&&t>=1?t:1},"paramToPreset",0,function(e){return e&&["all","last-hour","today","last-7-days","last-30-days","custom"].includes(e)?e:"all"},"paramsToDateRange",0,function(e,t){return{from:l(e),to:l(t)}},"presetToParam",0,function(e){return"all"===e?void 0:e}],2711)},89664,e=>{"use strict";var t=e.i(56420);let a={name:"check",size:24,node:[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]]};a.node;let r=(0,t.default)(a);e.s(["Check",0,r],89664)},24071,e=>{"use strict";var t=e.i(56420);let a={name:"chevron-left",size:24,node:[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]};a.node;let r=(0,t.default)(a);e.s(["ChevronLeft",0,r],24071)},67927,e=>{"use strict";var t=e.i(56420);let a={name:"chevron-right",size:24,node:[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]};a.node;let r=(0,t.default)(a);e.s(["ChevronRight",0,r],67927)},8734,e=>{"use strict";var t=e.i(56420);let a={name:"copy",size:24,node:[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]]};a.node;let r=(0,t.default)(a);e.s(["Copy",0,r],8734)},16306,e=>{"use strict";var t=e.i(56420);let a={name:"shield-alert",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]};a.node;let r=(0,t.default)(a);e.s(["ShieldAlert",0,r],16306)},84026,29407,e=>{"use strict";var t=e.i(56420);let a={name:"shield-check",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]};a.node;let r=(0,t.default)(a);e.s(["ShieldCheck",0,r],84026);let l={name:"shield-x",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m14.5 9.5-5 5",key:"17q4r4"}],["path",{d:"m9.5 9.5 5 5",key:"18nt4w"}]],aliases:["shield-close"]};l.node;let o=(0,t.default)(l);e.s(["ShieldX",0,o],29407)},82954,e=>{"use strict";var t=e.i(56420);let a={name:"shield",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]]};a.node;let r=(0,t.default)(a);e.s(["Shield",0,r],82954)},63676,e=>{"use strict";var t=e.i(56420);let a={name:"x",size:24,node:[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]]};a.node;let r=(0,t.default)(a);e.s(["X",0,r],63676)}]);
@@ -1 +0,0 @@
1
- (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,34578,e=>{"use strict";var t=e.i(95187);let a=(0,t.createServerReference)("60b2a3a075bc71a6052007070cfceb60df6ec81cc5",t.callServer,void 0,t.findSourceMapURL,"searchHookActivityAction");e.s(["searchHookActivityAction",0,a])},73520,e=>{"use strict";var t=e.i(43476),a=e.i(24071),r=e.i(67927);e.s(["default",0,function({currentPage:e,totalPages:s,onPageChange:i}){if(s<=1)return null;let l=e=>`px-3 py-2 text-sm rounded-md transition-colors flex items-center gap-1 bg-muted text-muted-foreground ${e?"opacity-50 cursor-not-allowed":"hover:bg-muted/80"}`;return(0,t.jsxs)("div",{className:"flex items-center justify-center gap-2 py-4",children:[(0,t.jsxs)("button",{onClick:()=>i(e-1),disabled:1===e,className:l(1===e),"aria-label":"Previous page",children:[(0,t.jsx)(a.ChevronLeft,{className:"w-4 h-4"}),(0,t.jsx)("span",{className:"hidden sm:inline",children:"Previous"})]}),(0,t.jsx)("div",{className:"flex items-center gap-1",children:(()=>{if(s<=7)return Array.from({length:s},(e,t)=>t+1);let t=[1],a=Math.max(2,e-1),r=Math.min(s-1,e+1);e<=3&&(r=Math.min(5,s-1)),e>=s-2&&(a=Math.max(2,s-4)),a>2&&t.push("ellipsis-start");for(let e=a;e<=r;e++)t.push(e);return r<s-1&&t.push("ellipsis-end"),t.push(s),t})().map(a=>"string"==typeof a?(0,t.jsx)("span",{className:"px-2 text-muted-foreground",children:"..."},a):(0,t.jsx)("button",{onClick:()=>i(a),className:`min-w-[2.5rem] px-3 py-2 text-sm rounded-md transition-colors ${e===a?"bg-primary text-primary-foreground":"bg-muted text-muted-foreground hover:bg-muted/80"}`,"aria-label":`Page ${a}`,"aria-current":e===a?"page":void 0,children:a},a))}),(0,t.jsxs)("button",{onClick:()=>i(e+1),disabled:e===s,className:l(e===s),"aria-label":"Next page",children:[(0,t.jsx)("span",{className:"hidden sm:inline",children:"Next"}),(0,t.jsx)(r.ChevronRight,{className:"w-4 h-4"})]})]})}])},34713,e=>{"use strict";e.s(["formatDuration",0,function(e){if(e<1e3)return`${e}ms`;let t=Math.round(e/100);if(t<600)return`${(t/10).toFixed(1)}s`;let a=Math.round(e/1e3);if(a<3600){let e=Math.floor(a/60);return`${e}m ${a%60}s`}let r=Math.round(e/6e4),s=Math.floor(r/60);return`${s}h ${r%60}m`},"formatRelativeTime",0,function(e){let t=Date.now()-e;return t<6e4?`${Math.max(1,Math.floor(t/1e3))}s ago`:t<36e5?`${Math.floor(t/6e4)}m ago`:t<864e5?`${Math.floor(t/36e5)}h ago`:`${Math.floor(t/864e5)}d ago`}])},24071,e=>{"use strict";var t=e.i(56420);let a={name:"chevron-left",size:24,node:[["path",{d:"m15 18-6-6 6-6",key:"1wnfg3"}]]};a.node;let r=(0,t.default)(a);e.s(["ChevronLeft",0,r],24071)},67927,e=>{"use strict";var t=e.i(56420);let a={name:"chevron-right",size:24,node:[["path",{d:"m9 18 6-6-6-6",key:"mthhwq"}]]};a.node;let r=(0,t.default)(a);e.s(["ChevronRight",0,r],67927)},16306,e=>{"use strict";var t=e.i(56420);let a={name:"shield-alert",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"M12 8v4",key:"1got3b"}],["path",{d:"M12 16h.01",key:"1drbdi"}]]};a.node;let r=(0,t.default)(a);e.s(["ShieldAlert",0,r],16306)},84026,29407,e=>{"use strict";var t=e.i(56420);let a={name:"shield-check",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m9 12 2 2 4-4",key:"dzmm74"}]]};a.node;let r=(0,t.default)(a);e.s(["ShieldCheck",0,r],84026);let s={name:"shield-x",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}],["path",{d:"m14.5 9.5-5 5",key:"17q4r4"}],["path",{d:"m9.5 9.5 5 5",key:"18nt4w"}]],aliases:["shield-close"]};s.node;let i=(0,t.default)(s);e.s(["ShieldX",0,i],29407)},82954,e=>{"use strict";var t=e.i(56420);let a={name:"shield",size:24,node:[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]]};a.node;let r=(0,t.default)(a);e.s(["Shield",0,r],82954)}]);