failproofai 1.0.0-beta.4 → 1.0.0-beta.5

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 (121) 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 +2 -2
  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/_head.segment.rsc +3 -3
  13. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
  14. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  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/_head.segment.rsc +4 -4
  22. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  24. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  25. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  26. package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
  27. package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
  28. package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
  29. package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
  30. package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
  31. package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
  32. package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
  33. package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
  34. package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
  35. package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/index.html +1 -1
  37. package/.next/standalone/.next/server/app/index.rsc +14 -14
  38. package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  39. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
  40. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
  41. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
  42. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  43. package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
  44. package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
  45. package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
  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/server-reference-manifest.json +1 -1
  50. package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
  51. package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
  53. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
  54. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
  55. package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
  56. package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
  57. package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
  58. package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
  59. package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +1 -1
  60. package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
  61. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
  62. package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
  63. package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
  64. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
  65. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sdrz-j._.js → [root-of-the-server]__094nop3._.js} +2 -2
  66. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
  67. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
  68. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +2 -2
  69. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
  70. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
  71. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
  72. package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cmdyn_._.js → [root-of-the-server]__1gak4pd._.js} +2 -2
  73. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +2 -2
  74. package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
  75. package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
  76. package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
  77. package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
  78. package/.next/standalone/.next/server/chunks/ssr/{node_modules_html-to-image_es_index_1q9xxca.js → node_modules_html-to-image_es_index_12c_2cz.js} +1 -1
  79. package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
  80. package/.next/standalone/.next/server/pages/404.html +1 -1
  81. package/.next/standalone/.next/server/pages/500.html +1 -1
  82. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  83. package/.next/standalone/.next/server/server-reference-manifest.json +12 -12
  84. package/.next/standalone/.next/static/chunks/{0ue9ak_xexiqh.js → 0cr85wuc9gc9k.js} +1 -1
  85. package/.next/standalone/.next/static/chunks/{2zb3m1tzq6h98.js → 1bkynffp3q1f7.js} +1 -1
  86. package/.next/standalone/.next/static/chunks/1bw4dfzflelcv.js +1 -0
  87. package/.next/standalone/.next/static/chunks/{3h5_3u46gu9jx.js → 1tp9dex23zs8y.js} +1 -1
  88. package/.next/standalone/.next/static/chunks/{2c90h1g6x8z8y.js → 2cqk4a9s893oy.js} +1 -1
  89. package/.next/standalone/.next/static/chunks/{06jakt_xhoolk.js → 2k285z07qce47.js} +1 -1
  90. package/.next/standalone/.next/static/chunks/{43a1ywqsg9bq9.js → 2s9g1dsde_ts1.js} +1 -1
  91. package/.next/standalone/.next/static/chunks/{31cqiv_cpgj9m.js → 2v1rpzrh2i1wv.js} +1 -1
  92. package/.next/standalone/.next/static/chunks/34sjdw8lv46e3.js +6 -0
  93. package/.next/standalone/Cargo.lock +3 -3
  94. package/.next/standalone/Cargo.toml +1 -1
  95. package/.next/standalone/crates/failproofaid/src/main.rs +118 -11
  96. package/.next/standalone/crates/fpai-collect/src/config.rs +8 -0
  97. package/.next/standalone/crates/fpai-collect/src/filetail.rs +4 -1
  98. package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +5 -1
  99. package/.next/standalone/crates/fpai-collect/src/spool.rs +77 -0
  100. package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +4 -1
  101. package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +1 -0
  102. package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +1 -0
  103. package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +1 -0
  104. package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1 -0
  105. package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1 -0
  106. package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1 -0
  107. package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +1 -0
  108. package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +1 -0
  109. package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1 -0
  110. package/.next/standalone/package.json +5 -5
  111. package/.next/standalone/server.js +1 -1
  112. package/dist/cli.mjs +6 -4
  113. package/dist/worker.mjs +1 -1
  114. package/package.json +5 -5
  115. package/src/hooks/cloud-connection.ts +3 -0
  116. package/src/hooks/collector-config.ts +10 -0
  117. package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +0 -6
  118. package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +0 -1
  119. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → LOuwMmfomO6hQYfMQYs4K}/_buildManifest.js +0 -0
  120. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → LOuwMmfomO6hQYfMQYs4K}/_clientMiddlewareManifest.js +0 -0
  121. /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → LOuwMmfomO6hQYfMQYs4K}/_ssgManifest.js +0 -0
@@ -0,0 +1,6 @@
1
+ (globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,64245,e=>{"use strict";var t=e.i(43476),s=e.i(71645),r=e.i(74080),n=e.i(22016),o=e.i(89664),l=e.i(16327),i=e.i(56420);let a=(0,i.default)("code",[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]]);var c=e.i(8734),d=e.i(66794),m=e.i(82954),u=e.i(16306),x=e.i(84026),p=e.i(54959);let h=(0,i.default)("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]]);var b=e.i(63676),f=e.i(73520),g=e.i(95187);let v=(0,g.createServerReference)("40f8dd16a345f3403a65fd84fdd8f760f1fa0f2002",g.callServer,void 0,g.findSourceMapURL,"getHookActivityAction");var j=e.i(74545);let y=(0,g.createServerReference)("00d23e1d3b6851bff2ecef2df7e165e53e05dfff86",g.callServer,void 0,g.findSourceMapURL,"getActivePausesAction");function N({pauses:e,now:r}){let[n,o]=(0,s.useState)(()=>Date.now());(0,s.useEffect)(()=>{let e=setInterval(()=>o(Date.now()),3e4);return()=>clearInterval(e)},[]);let l=r??n,i=e.filter(e=>e.expiresAt>l);if(0===i.length)return null;let a=i.reduce((e,t)=>e.expiresAt<t.expiresAt?e:t);return(0,t.jsxs)("div",{role:"status",className:"mb-4 flex items-start gap-2.5 rounded border border-amber-500/40 bg-amber-500/[0.07] px-3 py-2.5",children:[(0,t.jsx)(u.ShieldAlert,{className:"h-4 w-4 shrink-0 text-amber-500 mt-0.5","aria-hidden":"true"}),(0,t.jsxs)("div",{className:"text-xs leading-relaxed",children:[(0,t.jsxs)("p",{className:"font-medium text-foreground",children:["Enforcement is paused for ",i.length," session",1===i.length?"":"s"," —"," ",function(e){if(e<=0)return"expiring now";if(e<6e4)return"under a minute";let t=Math.round(e/6e4);if(t<60)return`${t}m`;let s=Math.floor(t/60),r=t%60;return 0===r?`${s}h`:`${s}h${r}m`}(a.expiresAt-l)," left",i.length>1?" on the next to expire":"","."]}),(0,t.jsxs)("p",{className:"text-muted-foreground mt-0.5",children:["Builtin, custom and convention policies are suspended for"," ",1===i.length?"that session":"those sessions","; cloud-managed policies keep enforcing. It lifts on its own — or run"," ",(0,t.jsx)("span",{className:"font-mono text-foreground",children:"failproofai config --resume"}),"."]})]})]})}function k(){return(0,t.jsx)("span",{title:"Enforcement was paused for this session — local policies did not run",className:"ml-1.5 inline-flex items-center rounded px-1.5 py-0.5 text-[0.6rem] font-medium uppercase tracking-wide border border-amber-500/40 bg-amber-500/10 text-amber-600 dark:text-amber-500",children:"paused"})}function S({item:e}){if(!e.pausedBy)return null;let s="number"==typeof e.pauseExpiresAt?new Date(e.pauseExpiresAt).toLocaleTimeString():null;return(0,t.jsxs)("div",{className:"sm:col-span-2 lg:col-span-3 mt-1 flex items-start gap-2 rounded border border-amber-500/30 bg-amber-500/5 px-2.5 py-2",children:[(0,t.jsx)(h,{className:"h-3.5 w-3.5 shrink-0 text-amber-500/80 mt-0.5","aria-hidden":"true"}),(0,t.jsxs)("p",{className:"text-[0.7rem] leading-relaxed text-muted-foreground",children:[(0,t.jsx)("span",{className:"font-medium text-foreground",children:"Not enforced — paused."})," Enforcement was suspended for this session","cli"===e.pausedBy?" from the CLI":` by ${e.pausedBy}`,s?`, lifting at ${s}`:"",". Builtin, custom and convention policies did not run for this event; cloud-managed policies did."]})]})}let w=(0,g.createServerReference)("004e029097bf9033db01186aaddfceb556f748f5d6",g.callServer,void 0,g.findSourceMapURL,"getHooksConfigAction"),P=(0,g.createServerReference)("6059e521bdcd258161024e3bc737d9317f65fd8d26",g.callServer,void 0,g.findSourceMapURL,"toggleCustomPolicyAction"),C=(0,g.createServerReference)("60578e42212b3ec2483835c99f822bd139a42a0ca5",g.callServer,void 0,g.findSourceMapURL,"togglePolicyAction"),_=(0,g.createServerReference)("603bb3919f286f61674be307bd77ffbf42b77fe336",g.callServer,void 0,g.findSourceMapURL,"installHooksWebAction"),T=(0,g.createServerReference)("604b1dcfbb039d78501d33484d92b9bde97c4477e9",g.callServer,void 0,g.findSourceMapURL,"removeHooksWebAction"),U=(0,g.createServerReference)("605d33384fd494333cbe683cc348c19bdcf1137d46",g.callServer,void 0,g.findSourceMapURL,"updatePolicyParamsAction");var I=e.i(85881),R=e.i(12344),E=e.i(80060),$=e.i(2711),A=e.i(24578);let L={claude:{PreToolUse:"block",UserPromptSubmit:"block",PermissionRequest:"block",Stop:"block",SubagentStop:"block",PreCompact:"block",PostToolBatch:"block",ConfigChange:"block",UserPromptExpansion:"block",TaskCreated:"block",TaskCompleted:"block",TeammateIdle:"block",Elicitation:"block",ElicitationResult:"block",PostToolUse:"observe",PostToolUseFailure:"observe",SessionStart:"observe",Setup:"observe",SubagentStart:"observe",SessionEnd:"observe",StopFailure:"observe",PermissionDenied:"observe",Notification:"observe",InstructionsLoaded:"observe",CwdChanged:"observe",FileChanged:"observe",PostCompact:"observe",WorktreeRemove:"observe"},codex:{PreToolUse:"block",PermissionRequest:"block",UserPromptSubmit:"block",Stop:"block",SubagentStop:"block",PostToolUse:"observe",SessionStart:"observe",SubagentStart:"observe",PreCompact:"observe",PostCompact:"observe"},copilot:{PreToolUse:"block",Stop:"block",SubagentStop:"block",UserPromptSubmit:"block",PermissionRequest:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe",PostToolUseFailure:"observe",PreCompact:"observe",Notification:"observe"},cursor:{PreToolUse:"block",Stop:"block",UserPromptSubmit:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe"},opencode:{PreToolUse:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe",UserPromptSubmit:"observe",PermissionRequest:"observe"},pi:{PreToolUse:"block",UserPromptSubmit:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe",Stop:"observe"},hermes:{PreToolUse:"block",PostToolUse:"observe",SubagentStop:"observe",SessionStart:"observe",SessionEnd:"observe"},openclaw:{PreToolUse:"block",UserPromptSubmit:"block",Stop:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe",SubagentStop:"observe",PreCompact:"observe"},factory:{PreToolUse:"block",UserPromptSubmit:"block",Stop:"block",PreCompact:"block",PostToolUse:"observe",SubagentStop:"observe",SessionStart:"observe",SessionEnd:"observe",Notification:"observe"},devin:{PreToolUse:"block",UserPromptSubmit:"block",Stop:"block",PermissionRequest:"block",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe"},antigravity:{PreToolUse:"block",Stop:"block",PostToolUse:"observe",UserPromptSubmit:"observe"},goose:{PreToolUse:"block",UserPromptSubmit:"observe",PostToolUse:"observe",SessionStart:"observe",SessionEnd:"observe"}};var F=e.i(34713),B=e.i(67881);function D({sessionId:e,transcriptPath:s,integration:r,cwd:o}){if(!e)return(0,t.jsx)("span",{className:"text-muted-foreground",children:"\\u2014"});let l=e?e.slice(0,8):"—",i="codex"===r||(s?.includes("/.codex/")??!1),a="copilot"===r||(s?.includes("/.copilot/session-state/")??!1),c="cursor"===r||(s?.includes("/.cursor/")??!1),d="opencode"===r||(s?.includes("/.local/share/opencode/")??!1)||(s?.includes("/.opencode/")??!1),m="pi"===r||(s?.includes("/.pi/")??!1);if(i||a||c||d||m){let s,r=o?(s=/^([A-Za-z]):[\\/](.*)$/.exec(o))?s[1]+"--"+s[2].replace(/[\\/]/g,"-"):o.replace(/[\\/]/g,"-"):i?"codex":a?"copilot":c?"cursor":d?"opencode":"pi";return(0,t.jsx)(n.default,{href:`/project/${encodeURIComponent(r)}/session/${encodeURIComponent(e)}`,className:"text-primary hover:underline font-mono",onClick:e=>e.stopPropagation(),children:l})}let u=s?function(e){let t=e.replace(/\\/g,"/").split("/").filter(Boolean);if(t.length<2)return null;let s=t[t.length-2];return!s||s.startsWith(".")?null:s}(s):null;return u?(0,t.jsx)(n.default,{href:`/project/${encodeURIComponent(u)}/session/${encodeURIComponent(e)}`,className:"text-primary hover:underline font-mono",onClick:e=>e.stopPropagation(),children:l}):(0,t.jsx)("span",{className:"text-muted-foreground font-mono",children:l})}function z({decision:e}){return"deny"===e?(0,t.jsxs)("span",{className:"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[0.65rem] font-semibold tracking-wide uppercase bg-red-500/10 text-red-400 border border-red-500/20",children:[(0,t.jsx)(p.ShieldX,{className:"h-3 w-3"}),"Deny"]}):"instruct"===e?(0,t.jsxs)("span",{className:"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[0.65rem] font-semibold tracking-wide uppercase bg-amber-500/10 text-amber-400 border border-amber-500/20",children:[(0,t.jsx)(u.ShieldAlert,{className:"h-3 w-3"}),"Instruct"]}):(0,t.jsxs)("span",{className:"inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[0.65rem] font-semibold tracking-wide uppercase bg-emerald-500/10 text-emerald-400 border border-emerald-500/20",children:[(0,t.jsx)(x.ShieldCheck,{className:"h-3 w-3"}),"Allow"]})}function M({eventType:e}){return(0,t.jsx)("span",{className:"inline-flex items-center rounded px-1.5 py-0.5 text-[0.6rem] font-medium bg-muted text-muted-foreground border border-border/50",children:e})}function O({integration:e}){if(!e)return null;let s=(0,A.getCliLabel)(e),r=(0,A.getCliBadgeClasses)(e);return(0,t.jsx)("span",{className:`inline-flex items-center rounded px-1.5 py-0.5 text-[0.6rem] font-medium border ${r}`,title:`Agent CLI: ${s}`,children:s})}function W({mode:e}){let s="default"===e;return(0,t.jsx)("span",{title:e,className:`inline-block max-w-full truncate rounded px-1.5 py-0.5 align-middle text-[0.6rem] font-medium border ${s?"bg-sky-500/10 text-sky-400 border-sky-500/20":"bg-amber-500/10 text-amber-400 border-amber-500/20"}`,children:e})}function H({ms:e}){return(0,t.jsx)("span",{className:`font-mono text-[0.7rem] ${e>500?"text-red-400":e>100?"text-amber-400":"text-muted-foreground"}`,children:e<1e3?`${e}ms`:`${(e/1e3).toFixed(1)}s`})}function q({text:e,field:r}){let[n,l]=(0,s.useState)(!1),{capture:i}=(0,R.usePostHog)(),a=async t=>{t.stopPropagation(),i("activity_copy_clicked",{field:r??"unknown"});try{await navigator.clipboard.writeText(e),l(!0),setTimeout(()=>l(!1),1500)}catch{let t=document.createElement("textarea");t.value=e,t.style.position="fixed",t.style.opacity="0",document.body.appendChild(t),t.select(),document.execCommand("copy"),document.body.removeChild(t),l(!0),setTimeout(()=>l(!1),1500)}};return(0,t.jsx)("button",{onClick:a,className:"inline-flex items-center justify-center h-5 w-5 rounded hover:bg-muted/50 text-muted-foreground hover:text-foreground transition-colors",title:"Copy",children:n?(0,t.jsx)(o.Check,{className:"h-3 w-3 text-emerald-400"}):(0,t.jsx)(c.Copy,{className:"h-3 w-3"})})}function X({value:e,onChange:s}){return(0,t.jsx)("div",{className:"inline-flex items-center rounded-md border border-border bg-muted/30 p-0.5",children:[{label:"All",value:""},{label:"Allow",value:"allow"},{label:"Instruct",value:"instruct"},{label:"Deny",value:"deny"}].map(r=>(0,t.jsx)("button",{onClick:()=>s(r.value),className:`px-2.5 py-1 text-[0.65rem] font-medium rounded transition-all ${e===r.value?"deny"===r.value?"bg-red-500/15 text-red-400 shadow-sm":"allow"===r.value?"bg-emerald-500/15 text-emerald-400 shadow-sm":"instruct"===r.value?"bg-amber-500/15 text-amber-400 shadow-sm":"bg-background text-foreground shadow-sm":"text-muted-foreground hover:text-foreground"}`,children:r.label},r.value))})}function K({stats:e}){let s=e.totalEvents>0?e.denyCount/e.totalEvents*100:0,r=s.toFixed(s>=10?0:1),n=0===e.denyCount?"":s>=5?" is-deny":s>=2?" is-warn":"";return(0,t.jsxs)("div",{className:"stat-bar",role:"group","aria-label":"Activity stats",children:[(0,t.jsxs)("div",{className:"stat-cell",children:[(0,t.jsx)("span",{className:"eyebrow",children:"total events"}),(0,t.jsx)("span",{className:"value",children:e.totalEvents.toLocaleString()}),(0,t.jsx)("span",{className:"meta",children:"across all installed agents"})]}),(0,t.jsxs)("div",{className:"stat-cell",children:[(0,t.jsx)("span",{className:"eyebrow",children:"deny rate"}),(0,t.jsxs)("span",{className:`value${n}`,children:[r,"%"]}),(0,t.jsxs)("span",{className:"meta",children:[e.denyCount.toLocaleString()," of ",e.totalEvents.toLocaleString()," denied"]})]}),(0,t.jsxs)("div",{className:"stat-cell",children:[(0,t.jsx)("span",{className:"eyebrow",children:"top policy"}),(0,t.jsx)("span",{className:"value",style:{fontSize:16,fontWeight:500,color:e.topPolicy?"var(--accent-pink)":"var(--dim)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:e.topPolicy??void 0,children:e.topPolicy??"—"}),(0,t.jsx)("span",{className:"meta",children:"most-evaluated policy in scope"})]})]})}function J({item:e}){if("observe"!==function(e,t){if(!e||!t)return;let s=L[e];if(s)return s[t]}(e.integration,e.eventType))return null;let s=e.matchedPolicies??[];if(0===s.length)return null;let r=e.integration?(0,A.getCliLabel)(e.integration):"This CLI",n=s.map(e=>e.replace(/^(custom|\.failproofai-(project|user))\//,"")),o=1===n.length?n[0]:`${n.length} policies`,l="deny"===e.decision;return(0,t.jsxs)("div",{className:"sm:col-span-2 lg:col-span-3 mt-1 flex items-start gap-2 rounded border border-amber-500/30 bg-amber-500/5 px-2.5 py-2",children:[(0,t.jsx)(h,{className:"h-3.5 w-3.5 shrink-0 text-amber-500/80 mt-0.5","aria-hidden":"true"}),(0,t.jsxs)("p",{className:"text-[0.7rem] leading-relaxed text-muted-foreground",children:[(0,t.jsx)("span",{className:"font-medium text-foreground",children:l?"Not enforced.":"Ran, but cannot enforce here."})," ",(0,t.jsx)("span",{className:"font-mono",children:o})," ran on"," ",(0,t.jsx)("span",{className:"font-mono",children:e.eventType}),", but ",r," discards hook verdicts for this event",l?" — the action went ahead anyway.":", so a policy cannot block here."," ","Move the check to an event ",r," enforces on, such as"," ",(0,t.jsx)("span",{className:"font-mono",children:"PreToolUse"}),"."]})]})}function G({item:e}){return(0,t.jsx)("tr",{children:(0,t.jsx)("td",{colSpan:11,className:"px-0 py-0",children:(0,t.jsxs)("div",{className:"activity-detail animate-expand text-xs",children:[(0,t.jsxs)("span",{className:"activity-detail-eyebrow",children:[(0,t.jsx)("span",{"aria-hidden":"true",children:"▾"}),"event detail"]}),(0,t.jsxs)("div",{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-2",children:[(0,t.jsx)(S,{item:e}),(0,t.jsx)(J,{item:e}),e.policySource&&(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Decided by: "}),(0,t.jsx)("span",{className:"font-mono text-foreground",children:"cloud"===e.policySource&&e.cloudPolicyId?`cloud \xb7 ${e.cloudPolicyId} rev ${e.cloudRevision}`:e.policySource})]}),void 0!==e.cloudGeneration&&(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Cloud generation: "}),(0,t.jsx)("span",{className:"font-mono text-foreground",children:e.cloudGeneration})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Session ID: "}),(0,t.jsx)("span",{className:"font-mono text-foreground",children:e.sessionId??"—"}),e.sessionId&&(0,t.jsx)(q,{text:e.sessionId,field:"session_id"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"CWD: "}),(0,t.jsx)("span",{className:"font-mono text-foreground",children:e.cwd??"—"}),e.cwd&&(0,t.jsx)(q,{text:e.cwd,field:"cwd"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Transcript: "}),(0,t.jsx)("span",{className:"font-mono text-foreground break-all",children:e.transcriptPath??"—"}),e.transcriptPath?.startsWith("opencode-db://")&&(0,t.jsx)("span",{className:"text-muted-foreground ml-1",children:"(stored in opencode DB)"})]})]}),e.policyNames&&e.policyNames.length>1&&(0,t.jsxs)("div",{style:{marginTop:8},children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Policies: "}),(0,t.jsx)("span",{className:"font-mono text-foreground",children:e.policyNames.join(", ")})]}),e.reason&&(0,t.jsxs)("div",{style:{marginTop:8},children:[(0,t.jsx)("span",{className:"text-muted-foreground",children:"Full reason: "}),(0,t.jsx)("span",{className:"text-foreground",children:e.reason})]})]})})})}function V({hooksInstalled:e,onSwitchTab:r}){let{intervalSec:n}=(0,I.useAutoRefresh)(),{capture:o}=(0,R.usePostHog)(),i=(0,E.useUrlParams)(),a=(0,s.useRef)(!1),[c,d]=(0,s.useState)(()=>(0,$.paramToPage)(i.get("page"))),[u,x]=(0,s.useState)(null),[p,h]=(0,s.useState)([]),[b,g]=(0,s.useState)(null),[S,w]=(0,s.useState)(()=>{let e=i.get("decision");return"allow"===e||"deny"===e||"instruct"===e?e:""}),[P,C]=(0,s.useState)(()=>i.get("event")??""),[_,T]=(0,s.useState)(()=>i.get("policy")??""),[U,L]=(0,s.useState)(()=>i.get("session")??""),[B,q]=(0,s.useState)(()=>{let e=i.get("cli");return(0,A.isKnownCli)(e)?e:""}),[J,Z]=(0,s.useState)(()=>{let e=i.get("source");return"builtin"===e||"custom"===e||"convention"===e||"cloud"===e?e:""}),Q=(0,s.useRef)(null),Y=(0,s.useRef)(!0),ee=(0,s.useRef)({filterDecision:S,filterEventType:P,filterPolicy:_,filterSessionId:U,filterCli:B,filterSource:J});ee.current={filterDecision:S,filterEventType:P,filterPolicy:_,filterSessionId:U,filterCli:B,filterSource:J},(0,s.useEffect)(()=>{if(!a.current){a.current=!0;return}i.setAll({decision:S||void 0,event:P||void 0,policy:_||void 0,session:U||void 0,cli:B||void 0,source:J||void 0,page:(0,$.pageToParam)(c)})},[S,P,_,U,B,J,c]);let et=""!==S||""!==P||""!==_||""!==U||""!==B||""!==J,es=(0,s.useCallback)(async e=>{try{let t,{filterDecision:s,filterEventType:r,filterPolicy:n,filterSessionId:o,filterCli:l,filterSource:i}=ee.current;t=""!==s||""!==r||""!==n||""!==o||""!==l||""!==i?await (0,j.searchHookActivityAction)({decision:s||void 0,eventType:r||void 0,policyName:n||void 0,sessionId:o||void 0,integration:l||void 0,source:i||void 0},e):await v(e),x(t)}catch{}},[]);(0,s.useEffect)(()=>{es(c);let e=setInterval(()=>es(c),n>0?1e3*n:5e3);return()=>clearInterval(e)},[c,es,n]),(0,s.useEffect)(()=>{let e=!1,t=()=>{y().then(t=>{e||h(t)}).catch(()=>{})};t();let s=setInterval(t,n>0?1e3*n:5e3);return()=>{e=!0,clearInterval(s)}},[n]),(0,s.useEffect)(()=>(Q.current&&clearTimeout(Q.current),Q.current=setTimeout(()=>{if(d(1),g(null),es(1),Y.current){Y.current=!1;return}o("activity_filter_changed",{active_filter_count:+(""!==S)+ +(""!==P)+ +(""!==_)+ +(""!==U)+ +(""!==B),has_decision:""!==S,has_event_type:""!==P,has_policy:""!==_,has_session:""!==U,has_cli:""!==B,decision:S||null,event_type:P||null,cli:B||null})},300),()=>{Q.current&&clearTimeout(Q.current)}),[S,P,_,U,B]);let er=u?.entries??[],en=u?.totalPages??1;return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(N,{pauses:p}),u?.stats&&u.stats.totalEvents>0&&(0,t.jsx)("div",{style:{marginBottom:18},children:(0,t.jsx)(K,{stats:u.stats})}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"filter-bar",children:[(0,t.jsxs)("div",{className:"filter-group",children:[(0,t.jsx)("span",{className:"filter-label",children:"decision"}),(0,t.jsx)(X,{value:S,onChange:w})]}),(0,t.jsxs)("div",{className:"filter-group",children:[(0,t.jsx)("span",{className:"filter-label",children:"event"}),(0,t.jsxs)("select",{value:P,onChange:e=>C(e.target.value),className:"filter-input",children:[(0,t.jsx)("option",{value:"",children:"all events"}),(0,t.jsx)("option",{value:"PreToolUse",children:"PreToolUse"}),(0,t.jsx)("option",{value:"PostToolUse",children:"PostToolUse"}),(0,t.jsx)("option",{value:"SessionStart",children:"SessionStart"}),(0,t.jsx)("option",{value:"SessionEnd",children:"SessionEnd"}),(0,t.jsx)("option",{value:"UserPromptSubmit",children:"UserPromptSubmit"}),(0,t.jsx)("option",{value:"PermissionRequest",children:"PermissionRequest"})]})]}),(0,t.jsxs)("div",{className:"filter-group",children:[(0,t.jsx)("span",{className:"filter-label",children:"source"}),(0,t.jsxs)("select",{value:J,onChange:e=>{let t=e.target.value;Z("builtin"===t||"custom"===t||"convention"===t||"cloud"===t?t:"")},className:"filter-input","aria-label":"Filter by policy source",children:[(0,t.jsx)("option",{value:"",children:"all sources"}),(0,t.jsx)("option",{value:"builtin",children:"builtin"}),(0,t.jsx)("option",{value:"custom",children:"custom"}),(0,t.jsx)("option",{value:"convention",children:"convention"}),(0,t.jsx)("option",{value:"cloud",children:"cloud"})]})]}),(0,t.jsxs)("div",{className:"filter-group",children:[(0,t.jsx)("span",{className:"filter-label",children:"cli"}),(0,t.jsxs)("select",{value:B,onChange:e=>{let t=e.target.value;q(""===t||(0,A.isKnownCli)(t)?t:"")},className:"filter-input","aria-label":"Filter by CLI",children:[(0,t.jsx)("option",{value:"",children:"all clis"}),A.KNOWN_CLI_IDS.map(e=>(0,t.jsx)("option",{value:e,children:(0,A.getCliLabel)(e)},e))]})]}),(0,t.jsxs)("div",{className:"filter-group filter-group--grow",children:[(0,t.jsx)("span",{className:"filter-label",children:"policy"}),(0,t.jsx)("input",{type:"text",value:_,onChange:e=>T(e.target.value),placeholder:"filter by policy…",className:"filter-input"})]}),(0,t.jsxs)("div",{className:"filter-group filter-group--grow",children:[(0,t.jsx)("span",{className:"filter-label",children:"session"}),(0,t.jsx)("input",{type:"text",value:U,onChange:e=>L(e.target.value),placeholder:"filter by session…",className:"filter-input"})]}),et&&(0,t.jsx)("button",{type:"button",className:"filter-clear",onClick:()=>{w(""),C(""),q(""),T(""),L(""),Z("")},"aria-label":"Clear all filters",children:"[ clear ]"})]}),0===er.length?(0,t.jsxs)("div",{className:"flex flex-col items-center justify-center py-16 px-4 text-center",children:[(0,t.jsx)(m.Shield,{className:"h-12 w-12 text-muted-foreground/30"}),!1===e?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("p",{className:"text-sm text-muted-foreground mt-4 font-medium",children:"Policies are not installed"}),(0,t.jsxs)("p",{className:"text-xs text-muted-foreground mt-1 max-w-sm",children:["Go to the"," ",(0,t.jsx)("button",{className:"underline hover:text-foreground transition-colors",onClick:()=>r?.("policies"),children:"Policies tab"})," ","and click ",(0,t.jsx)("span",{className:"font-mono bg-muted px-1 rounded",children:"Install"})," to enable policy monitoring."]})]}):(0,t.jsx)("p",{className:"text-sm text-muted-foreground mt-4",children:et?"No matching activity.":"Waiting for hook events…"})]}):(0,t.jsx)("div",{className:"overflow-x-auto activity-table-wrap",children:(0,t.jsxs)("table",{className:"w-full text-xs activity-table",style:{tableLayout:"fixed"},children:[(0,t.jsxs)("colgroup",{children:[(0,t.jsx)("col",{style:{width:32}}),(0,t.jsx)("col",{style:{width:"7%"}}),(0,t.jsx)("col",{style:{width:"9%"}}),(0,t.jsx)("col",{style:{width:"12%"}}),(0,t.jsx)("col",{style:{width:"11%"}}),(0,t.jsx)("col",{style:{width:"9%"}}),(0,t.jsx)("col",{style:{width:"13%"}}),(0,t.jsx)("col",{style:{width:"12%"}}),(0,t.jsx)("col",{style:{width:"11%"}}),(0,t.jsx)("col",{style:{width:"8%"}}),(0,t.jsx)("col",{style:{width:"8%"}})]}),(0,t.jsx)("thead",{className:"activity-thead",children:(0,t.jsxs)("tr",{children:[(0,t.jsx)("th",{className:"px-4 py-3"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Time"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Decision"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Event"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"CLI"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Tool"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Policy"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Reason"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Mode"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Duration"}),(0,t.jsx)("th",{className:"px-3 py-3 activity-th",children:"Session"})]})}),(0,t.jsx)("tbody",{className:"divide-y divide-border/30",children:er.map((e,r)=>{let n="deny"===e.decision,i=b===r,a="instruct"===e.decision;return(0,t.jsxs)(s.default.Fragment,{children:[(0,t.jsxs)("tr",{onClick:()=>{g(e=>{let t=e===r?null:r,s=er[r];return o("activity_row_toggled",{expanded:null!==t,decision:s?.decision??null,policy_name:s?.policyName??null,event_type:s?.eventType??null}),t})},className:`activity-data-row cursor-pointer transition-colors ${n?"bg-red-500/[0.03] hover:bg-red-500/[0.07] border-l-2 border-l-red-500/40":a?"bg-amber-500/[0.03] hover:bg-amber-500/[0.07] border-l-2 border-l-amber-500/40":r%2==0?"hover:bg-muted/30":"bg-muted/[0.04] hover:bg-muted/30"} ${i?"bg-muted/20":""}`,children:[(0,t.jsx)("td",{className:"px-4 py-2",children:(0,t.jsx)(l.ChevronDown,{className:`h-3.5 w-3.5 text-muted-foreground transition-transform duration-150 ${i?"rotate-0":"-rotate-90"}`})}),(0,t.jsx)("td",{className:"px-3 py-2 text-muted-foreground whitespace-nowrap",title:new Date(e.timestamp).toLocaleString(void 0,{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"}),children:(0,F.formatRelativeTime)(e.timestamp)}),(0,t.jsxs)("td",{className:"px-3 py-2",children:[(0,t.jsx)(z,{decision:e.decision}),e.pausedBy&&(0,t.jsx)(k,{})]}),(0,t.jsx)("td",{className:"px-3 py-2",children:(0,t.jsx)(M,{eventType:e.eventType})}),(0,t.jsx)("td",{className:"px-3 py-2",children:(0,t.jsx)(O,{integration:e.integration})}),(0,t.jsx)("td",{className:"px-3 py-2 font-mono text-foreground",children:e.toolName??"—"}),(0,t.jsx)("td",{className:"px-3 py-2 font-mono text-foreground",children:e.policyNames&&e.policyNames.length>1?(0,t.jsxs)("span",{title:e.policyNames.join(", "),children:[e.policyNames[0],(0,t.jsxs)("span",{className:"text-muted-foreground text-[0.6rem]",children:[" +",e.policyNames.length-1]})]}):e.policyName??"—"}),(0,t.jsx)("td",{className:"px-3 py-2 text-muted-foreground truncate",title:e.reason??"",children:e.reason??"—"}),(0,t.jsx)("td",{className:"px-3 py-2",children:e.permissionMode?(0,t.jsx)(W,{mode:e.permissionMode}):"—"}),(0,t.jsx)("td",{className:"px-3 py-2",children:(0,t.jsx)(H,{ms:e.durationMs})}),(0,t.jsx)("td",{className:"px-3 py-2",title:e.sessionId??"",children:(0,t.jsx)(D,{sessionId:e.sessionId,transcriptPath:e.transcriptPath,integration:e.integration,cwd:e.cwd})})]}),i&&(0,t.jsx)(G,{item:e})]},`${e.timestamp}-${r}`)})})]})}),(0,t.jsx)(f.default,{currentPage:c,totalPages:en,onPageChange:e=>{o("activity_pagination_changed",{from_page:c,to_page:e,total_pages:en}),d(e),g(null)}})]})]})}function Z({enabled:e,onChange:s,disabled:r}){return(0,t.jsx)("button",{onClick:s,disabled:r,className:`relative inline-flex h-4 w-7 shrink-0 items-center rounded-full transition-colors disabled:opacity-40 ${e?"bg-emerald-500":"bg-muted-foreground/30"}`,"aria-label":e?"Disable policy":"Enable policy",children:(0,t.jsx)("span",{className:`inline-block h-3 w-3 rounded-full bg-white shadow transition-transform ${e?"translate-x-3.5":"translate-x-0.5"}`})})}function Q({policy:e,onClose:r,onSave:n}){let o=e.params??{},l=e.currentParams??{},[i,a]=(0,s.useState)(()=>{let e={};for(let[t,s]of Object.entries(o)){let r=t in l?l[t]:s.default;e[t]=r}return e});(0,s.useEffect)(()=>{let e=e=>{"Escape"===e.key&&r()};return document.addEventListener("keydown",e),()=>document.removeEventListener("keydown",e)},[r]);let c=(e,t)=>{a(s=>({...s,[e]:t}))};return(0,t.jsx)("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/50",onClick:e=>{e.target===e.currentTarget&&r()},children:(0,t.jsxs)("div",{className:"bg-card border border-border rounded-lg shadow-xl w-full max-w-lg mx-4 max-h-[80vh] flex flex-col",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between px-4 py-3 border-b border-border",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("p",{className:"text-sm font-mono font-semibold text-foreground",children:e.name}),(0,t.jsx)("p",{className:"text-xs text-muted-foreground mt-0.5",children:e.description})]}),(0,t.jsx)("button",{onClick:r,className:"text-muted-foreground hover:text-foreground transition-colors",children:(0,t.jsx)(b.X,{className:"h-4 w-4"})})]}),(0,t.jsx)("div",{className:"overflow-y-auto flex-1 px-4 py-4 space-y-4",children:Object.entries(o).map(([e,s])=>(0,t.jsxs)("div",{children:[(0,t.jsx)("label",{className:"block text-xs font-semibold text-foreground mb-1 font-mono",children:e}),(0,t.jsx)("p",{className:"text-[0.65rem] text-muted-foreground mb-1.5",children:s.description}),((e,s)=>{let r=i[e];if("boolean"===s.type)return(0,t.jsxs)("label",{className:"flex items-center gap-2 cursor-pointer",children:[(0,t.jsx)("input",{type:"checkbox",checked:!!r,onChange:t=>c(e,t.target.checked),className:"rounded border-border"}),(0,t.jsx)("span",{className:"text-xs text-muted-foreground",children:s.description})]});if("number"===s.type)return(0,t.jsx)("input",{type:"number",value:"number"==typeof r?r:"",onChange:t=>c(e,Number(t.target.value)),className:"w-full h-7 rounded border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-primary/40"});if("string[]"===s.type){let s=Array.isArray(r)?r.join("\n"):"";return(0,t.jsx)("textarea",{value:s,onChange:t=>c(e,t.target.value.split("\n").filter(e=>""!==e.trim())),rows:4,placeholder:"One entry per line",className:"w-full rounded border border-border bg-background px-2 py-1.5 text-xs text-foreground font-mono focus:outline-none focus:ring-2 focus:ring-primary/40 resize-y"})}if("pattern[]"===s.type){let s=Array.isArray(r)?r:[];return(0,t.jsxs)("div",{className:"space-y-1.5",children:[s.map((r,n)=>(0,t.jsxs)("div",{className:"flex gap-1.5 items-center",children:[(0,t.jsx)("input",{type:"text",value:r.regex,onChange:t=>{let r=[...s];r[n]={...r[n],regex:t.target.value},c(e,r)},placeholder:"regex",className:"flex-1 h-6 rounded border border-border bg-background px-2 text-xs font-mono focus:outline-none focus:ring-2 focus:ring-primary/40"}),(0,t.jsx)("input",{type:"text",value:r.label,onChange:t=>{let r=[...s];r[n]={...r[n],label:t.target.value},c(e,r)},placeholder:"label",className:"flex-1 h-6 rounded border border-border bg-background px-2 text-xs focus:outline-none focus:ring-2 focus:ring-primary/40"}),(0,t.jsx)("button",{onClick:()=>c(e,s.filter((e,t)=>t!==n)),className:"text-muted-foreground/50 hover:text-destructive transition-colors",children:(0,t.jsx)(b.X,{className:"h-3.5 w-3.5"})})]},n)),(0,t.jsx)("button",{onClick:()=>c(e,[...s,{regex:"",label:""}]),className:"text-xs text-primary hover:underline",children:"+ Add pattern"})]})}return(0,t.jsx)("input",{type:"text",value:"string"==typeof r?r:"",onChange:t=>c(e,t.target.value),className:"w-full h-7 rounded border border-border bg-background px-2 text-xs text-foreground focus:outline-none focus:ring-2 focus:ring-primary/40"})})(e,s)]},e))}),(0,t.jsxs)("div",{className:"flex items-center justify-end gap-2 px-4 py-3 border-t border-border",children:[(0,t.jsx)(B.Button,{variant:"outline",size:"sm",onClick:r,className:"h-7 text-xs",children:"Cancel"}),(0,t.jsx)(B.Button,{size:"sm",onClick:()=>n(i),className:"h-7 text-xs",children:"Save"})]})]})})}function Y({message:e,onDismiss:s,onInstall:n,isPending:o}){let{capture:l}=(0,R.usePostHog)();return(0,r.createPortal)((0,t.jsxs)("div",{className:"fixed top-4 right-4 z-[9999] w-full max-w-sm",style:{animation:"slideInFromRight 0.25s ease-out"},children:[(0,t.jsx)("style",{children:`
2
+ @keyframes slideInFromRight {
3
+ from { transform: translateX(calc(100% + 1rem)); opacity: 0; }
4
+ to { transform: translateX(0); opacity: 1; }
5
+ }
6
+ `}),(0,t.jsxs)("div",{className:"rounded-lg border border-red-500/40 bg-red-950/95 shadow-2xl shadow-red-900/40 overflow-hidden backdrop-blur-sm",children:[(0,t.jsx)("div",{className:"h-0.5 w-full bg-gradient-to-r from-red-600 via-red-400 to-red-600"}),(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsxs)("div",{className:"flex items-start gap-3",children:[(0,t.jsx)("div",{className:"shrink-0 mt-0.5 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 ring-2 ring-red-500/30",children:(0,t.jsx)(b.X,{className:"h-3 w-3 text-white",strokeWidth:3})}),(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsx)("p",{className:"text-[0.65rem] font-bold uppercase tracking-widest text-red-400 mb-0.5",children:"Error"}),(0,t.jsx)("p",{className:"text-sm text-red-100 leading-snug",children:e})]}),(0,t.jsx)("button",{onClick:s,className:"shrink-0 -mt-0.5 -mr-0.5 rounded p-0.5 text-red-400/60 transition-colors hover:bg-red-500/20 hover:text-red-200","aria-label":"Dismiss",children:(0,t.jsx)(b.X,{className:"h-4 w-4"})})]}),(0,t.jsxs)("div",{className:"mt-3 flex gap-2",children:[(0,t.jsx)(B.Button,{size:"sm",onClick:()=>{l("hooks_install_from_error_clicked",{error_message:e.slice(0,200)}),s(),n()},disabled:o,className:"h-7 flex-1 border-0 bg-red-500 px-3 text-xs font-semibold text-white hover:bg-red-400 disabled:opacity-50",children:"Install hooks"}),(0,t.jsx)(B.Button,{variant:"ghost",size:"sm",onClick:s,className:"h-7 px-3 text-xs text-red-400 hover:bg-red-500/10 hover:text-red-200",children:"Dismiss"})]})]})]})]}),document.body)}function ee({onHooksInstallChange:e}){let[r,n]=(0,s.useState)(null),[o,l]=(0,s.useTransition)(),[i,c]=(0,s.useState)(null),[u,x]=(0,s.useState)(null),[p,h]=(0,s.useState)(null),[b,f]=(0,s.useState)(()=>new Set),g=(0,s.useRef)(!1),{capture:v}=(0,R.usePostHog)(),j=(0,s.useCallback)((e,t)=>{v("action_error_displayed",{error_type:e,error_message:t.slice(0,200)}),c(t)},[v]),y=(0,s.useCallback)(async()=>{try{let t=await w();n(t),e?.(t.clis.some(e=>e.installed))}catch{}},[e]);(0,s.useEffect)(()=>{y()},[y]),(0,s.useEffect)(()=>{r&&(g.current?f(new Set(r.clis.filter(e=>e.installed).map(e=>e.id))):(g.current=!0,f(new Set(r.clis.filter(e=>e.installed||e.detected).map(e=>e.id)))))},[r?.clis]);let N=(0,s.useMemo)(()=>new Set((r?.clis??[]).filter(e=>e.installed).map(e=>e.id)),[r?.clis]),k=(0,s.useMemo)(()=>{let e=[],t=[];for(let s of r?.clis??[]){let r=b.has(s.id);r&&!N.has(s.id)&&e.push(s.id),!r&&N.has(s.id)&&t.push(s.id)}return{toInstall:e,toRemove:t}},[r?.clis,b,N]),S=k.toInstall.length>0||k.toRemove.length>0,I=(e,t)=>{r&&(n(s=>s?{...s,customPolicies:s.customPolicies?.map(s=>s.id===e?{...s,enabled:!t}:s),conventionPolicies:s.conventionPolicies.map(s=>({...s,policies:s.policies.map(s=>s.id===e?{...s,enabled:!t}:s)}))}:s),l(async()=>{try{await P(e,!t)}catch{j("custom_policy_toggle","Failed to save custom policy change."),y()}}))},E=()=>{let{toInstall:e,toRemove:t}=k;(0!==e.length||0!==t.length)&&(v("cli_install_remove_submitted",{to_install:e,to_remove:t,count_install:e.length,count_remove:t.length}),l(async()=>{try{c(null),e.length>0&&await _("user",e),t.length>0&&await T("user",t)}catch(e){j("cli_apply",e instanceof Error?e.message:"Failed to apply changes.")}finally{await y()}}))};if(!r)return(0,t.jsx)("div",{className:"flex items-center justify-center py-16",children:(0,t.jsxs)("span",{className:"text-sm text-muted-foreground",children:["Loading","…"]})});let $=r.clis.some(e=>e.installed),L=N.size,F=b.size,D=Array.from(new Set(r.policies.map(e=>e.category)));return(0,t.jsxs)(t.Fragment,{children:[p&&(0,t.jsx)(Q,{policy:p,onClose:()=>{v("policy_config_modal_closed",{policy_name:p.name,action:"cancel"}),h(null)},onSave:e=>{if(!p)return;let t=p.name;h(null),l(async()=>{try{c(null),await U(t,e),await y()}catch(e){j("param_update",e instanceof Error?e.message:"Failed to save configuration.")}})}}),(0,t.jsxs)("div",{className:"bg-card border border-border rounded-lg overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between gap-4 px-5 py-3.5 border-b border-border/60 bg-muted/10",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3 min-w-0 flex-wrap",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:`h-2 w-2 rounded-full transition-shadow ${$?"bg-emerald-400 shadow-[0_0_8px_rgba(52,211,153,0.6)]":"bg-muted-foreground/40"}`,"aria-hidden":!0}),(0,t.jsx)("span",{className:"text-[0.65rem] uppercase tracking-[0.2em] font-mono text-foreground/90",children:"Integrations"})]}),(0,t.jsx)("span",{className:"text-[0.65rem] font-mono text-muted-foreground/40",children:"·"}),(0,t.jsxs)("div",{className:"text-[0.65rem] uppercase tracking-[0.2em] font-mono text-muted-foreground",children:[(0,t.jsx)("span",{className:"text-foreground tabular-nums",children:L.toString().padStart(2,"0")}),(0,t.jsxs)("span",{className:"opacity-50",children:[" / 0",r.clis.length," active"]})]}),S&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("span",{className:"text-[0.65rem] font-mono text-muted-foreground/40",children:"·"}),(0,t.jsxs)("span",{className:"inline-flex items-center gap-1.5 text-[0.65rem] uppercase tracking-[0.2em] font-mono text-pink-400",children:[(0,t.jsx)("span",{className:"h-1 w-1 rounded-full bg-pink-400 animate-pulse"}),k.toInstall.length+k.toRemove.length," pending"]})]})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2 shrink-0",children:[(0,t.jsx)(B.Button,{variant:"outline",size:"sm",onClick:()=>{let e=Array.from(N).filter(e=>b.has(e));0!==e.length&&(v("cli_reinstall_submitted",{cli_ids:e,count:e.length}),l(async()=>{try{c(null),await _("user",e)}catch(e){j("cli_reinstall",e instanceof Error?e.message:"Failed to reinstall.")}finally{await y()}}))},disabled:o||0===F,className:"text-xs h-7 px-3 font-mono",children:"Reinstall"}),(0,t.jsx)(B.Button,{variant:"default",size:"sm",onClick:E,disabled:o||!S,className:"text-xs h-7 px-3 font-mono",children:"Apply changes"})]})]}),(0,t.jsx)("div",{className:"divide-y divide-border/30 border-b border-border/60",children:r.clis.map((e,s)=>{let n=b.has(e.id),l=N.has(e.id),i=n!==l,a=(0,A.getCliBadgeClasses)(e.id),c=a.split(" ").find(e=>e.startsWith("text-"))??"text-foreground";return(0,t.jsxs)("label",{className:`group relative flex items-center gap-3 pl-6 pr-5 py-3 cursor-pointer transition-colors hover:bg-muted/15 ${i?"bg-pink-500/[0.04]":""}`,children:[(0,t.jsx)("div",{className:`absolute left-0 top-0 bottom-0 w-[3px] ${c} bg-current transition-opacity ${n?"opacity-100":"opacity-25 group-hover:opacity-60"}`,"aria-hidden":!0}),(0,t.jsx)("div",{className:"font-mono text-[0.65rem] text-muted-foreground/40 tabular-nums w-6 select-none",children:(s+1).toString().padStart(2,"0")}),(0,t.jsxs)("div",{className:"relative shrink-0",children:[(0,t.jsx)("input",{type:"checkbox",checked:n,onChange:()=>{var t;return t=e.id,void f(e=>{let s=new Set(e),n=!s.has(t);n?s.add(t):s.delete(t);let o=r?.clis.find(e=>e.id===t);return v("cli_selection_toggled",{cli_id:t,checked:n,current_state:o?.installed?"installed":o?.detected?"detected":"inactive"}),s})},disabled:o,className:"sr-only peer","aria-label":`Toggle ${e.label}`}),(0,t.jsx)("div",{className:`h-[18px] w-[18px] rounded-sm border transition-all peer-focus-visible:ring-2 peer-focus-visible:ring-primary/40 ${n?"bg-primary border-primary":"bg-background/50 border-border group-hover:border-foreground/40"}`,children:n&&(0,t.jsx)("svg",{viewBox:"0 0 18 18",className:"h-full w-full text-primary-foreground",fill:"none","aria-hidden":!0,children:(0,t.jsx)("path",{d:"M4 9.5L7.5 12.5L14 6",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round"})})})]}),(0,t.jsx)("div",{className:"min-w-[150px] shrink-0",children:(0,t.jsx)("div",{className:"text-sm font-medium text-foreground",children:e.label})}),(0,t.jsxs)("div",{className:"flex items-center gap-1.5 shrink-0",children:[l?(0,t.jsxs)("span",{className:`inline-flex items-center gap-1.5 px-2 py-0.5 rounded-sm text-[0.6rem] uppercase tracking-[0.15em] font-mono border ${a}`,children:[(0,t.jsx)("span",{className:"h-1 w-1 rounded-full bg-current shadow-[0_0_4px_currentColor]"}),"Active"]}):e.detected?(0,t.jsxs)("span",{className:"inline-flex items-center gap-1.5 px-2 py-0.5 rounded-sm text-[0.6rem] uppercase tracking-[0.15em] font-mono border border-border/60 text-muted-foreground/80 bg-muted/20",children:[(0,t.jsx)("span",{className:"h-1 w-1 rounded-full bg-current opacity-60"}),"Detected"]}):(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-0.5 rounded-sm text-[0.6rem] uppercase tracking-[0.15em] font-mono border border-border/30 text-muted-foreground/40",children:"Inactive"}),i&&(0,t.jsx)("span",{className:"inline-flex items-center px-2 py-0.5 rounded-sm text-[0.6rem] uppercase tracking-[0.15em] font-mono border border-pink-500/40 text-pink-400 bg-pink-500/10",children:n?"+ install":"− remove"})]}),(0,t.jsx)("div",{className:"ml-auto hidden lg:block text-[0.7rem] font-mono text-muted-foreground/50 truncate max-w-[420px] group-hover:text-muted-foreground/80 transition-colors",title:e.settingsPath,children:e.settingsPath})]},e.id)})}),(0,t.jsxs)("div",{className:"flex items-center gap-2 px-4 py-2 border-b border-border/40 bg-muted/5",children:[(0,t.jsxs)("span",{className:"text-xs text-muted-foreground",children:[(0,t.jsx)("span",{className:"font-semibold text-foreground",children:r.enabledPolicies.length})," / ",r.policies.length+(r.customPolicies?.length??0)+(r.conventionPolicies?.reduce((e,t)=>e+t.policies.length,0)??0)," ","policies enabled"]}),$&&(0,t.jsxs)("span",{className:"text-[0.65rem] text-muted-foreground/60",children:["· shared across ",L," active CLI",1===L?"":"s"]})]}),i&&(0,t.jsx)("div",{className:"px-4 py-2 bg-red-500/10 border-b border-red-500/20 text-xs text-red-400",children:i}),u&&(0,t.jsx)(Y,{message:u,onDismiss:()=>x(null),onInstall:E,isPending:o}),D.map(e=>{let s=r.policies.filter(t=>t.category===e),i=s.filter(e=>e.enabled).length;return(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between px-4 py-2.5 bg-muted/20 border-b border-border/50",children:[(0,t.jsx)("span",{className:"text-[0.7rem] font-semibold uppercase tracking-wider text-muted-foreground",children:e}),(0,t.jsxs)("span",{className:"text-[0.7rem] text-muted-foreground",children:[i," / ",s.length," enabled"]})]}),s.map(e=>(0,t.jsxs)("div",{className:"flex items-start gap-3 px-4 py-3 border-b border-border/20 hover:bg-muted/20 transition-colors",children:[(0,t.jsx)("div",{className:"mt-0.5 shrink-0",children:(0,t.jsx)(Z,{enabled:e.enabled,onChange:()=>{var t,s;return t=e.name,s=e.enabled,void(!r||(!r.clis.some(e=>e.installed)?x("Policies are not installed. Install policies to continue."):(x(null),n(e=>e?{...e,policies:e.policies.map(e=>e.name===t?{...e,enabled:!s}:e),enabledPolicies:s?e.enabledPolicies.filter(e=>e!==t):[...e.enabledPolicies,t]}:e),l(async()=>{try{await C(t,!s)}catch{j("policy_toggle","Failed to save policy change."),y()}}))))},disabled:o})}),(0,t.jsxs)("div",{className:"flex items-center gap-1.5 min-w-0 w-56 shrink-0 mt-0.5",children:[(0,t.jsx)("span",{className:"text-xs font-mono text-foreground truncate",children:e.name}),e.beta&&(0,t.jsx)("span",{className:"shrink-0 text-[0.6rem] px-1 py-0.5 rounded border bg-violet-500/10 text-violet-400 border-violet-500/20",children:"beta"})]}),(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,t.jsx)("span",{className:"text-xs text-muted-foreground leading-relaxed",children:e.description}),e.eventScope&&(0,t.jsx)("span",{className:"block text-[0.65rem] text-muted-foreground/40 font-mono mt-0.5 hidden lg:block",children:e.eventScope}),e.params&&Object.keys(e.params).length>0&&(0,t.jsx)("div",{className:"flex flex-wrap gap-1 mt-1.5",children:Object.entries(e.params).map(([s,r])=>{let n=e.currentParams?.[s]??r.default,o=JSON.stringify(n)!==JSON.stringify(r.default);return(0,t.jsxs)("span",{className:`inline-flex items-center gap-1 font-mono text-[0.6rem] px-1.5 py-0.5 rounded border ${o?"bg-primary/10 text-primary/70 border-primary/20":"bg-muted/40 text-muted-foreground/55 border-border/40"}`,children:[(0,t.jsxs)("span",{className:"opacity-70",children:[s,":"]}),(0,t.jsx)("span",{children:function(e,t){if("string[]"===e||"pattern[]"===e){let s=Array.isArray(t)?t:[];return 0===s.length?"none":"pattern[]"===e?`${s.length} pattern${1!==s.length?"s":""}`:1===s.length?String(s[0]).slice(0,24):`${s.length} values`}return"boolean"===e?t?"on":"off":null==t||""===t?"—":String(t).slice(0,32)}(r.type,n)})]},s)})})]}),e.params&&Object.keys(e.params).length>0&&(0,t.jsx)("button",{className:"shrink-0 mt-0.5 text-muted-foreground hover:text-primary transition-colors",onClick:()=>{let t=Object.entries(e.params??{}).filter(([t,s])=>JSON.stringify(e.currentParams?.[t]??s.default)!==JSON.stringify(s.default)).length;v("policy_config_modal_opened",{policy_name:e.name,param_count:Object.keys(e.params??{}).length,has_customized_params:t>0,customized_count:t}),h(e)},title:"Edit parameters",children:(0,t.jsx)(d.Settings,{className:"h-3.5 w-3.5"})})]},e.name))]},e)}),(r.customPoliciesPaths?.length||r.customPoliciesPath)&&(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between px-4 py-2.5 bg-muted/20 border-b border-border/50",children:[(0,t.jsx)("span",{className:"text-[0.7rem] font-semibold uppercase tracking-wider text-muted-foreground",children:"Custom Policies"}),(0,t.jsxs)("span",{className:"text-[0.7rem] text-muted-foreground",children:[r.customPolicies?.length??0," detected"]})]}),(r.customPoliciesPaths??[r.customPoliciesPath]).map(e=>(0,t.jsxs)("div",{className:"flex items-center gap-3 px-4 py-3 border-b border-border/20",children:[(0,t.jsx)(a,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),(0,t.jsx)("span",{className:"text-xs font-mono text-muted-foreground truncate",children:e})]},e)),(0,t.jsxs)("div",{className:"flex items-start gap-2 px-4 py-2.5 border-b border-border/20 bg-muted/10",children:[(0,t.jsx)(m.Shield,{className:"h-3.5 w-3.5 text-muted-foreground/60 shrink-0 mt-0.5"}),(0,t.jsx)("p",{className:"text-[0.7rem] text-muted-foreground/70 leading-relaxed",children:"Re-run the install command to add, remove, or reorder files."})]}),r.customPolicies?.map(e=>(0,t.jsxs)("div",{className:"flex items-start gap-3 px-4 py-3 border-b border-border/20 hover:bg-muted/20 transition-colors",children:[(0,t.jsx)("div",{className:"mt-0.5 shrink-0",children:(0,t.jsx)(Z,{enabled:e.enabled,onChange:()=>I(e.id,e.enabled),disabled:o})}),(0,t.jsx)("div",{className:"flex items-center gap-1.5 min-w-0 w-56 shrink-0 mt-0.5",children:(0,t.jsx)("span",{className:"text-xs font-mono text-foreground truncate",children:e.name})}),(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[e.description&&(0,t.jsx)("span",{className:"text-xs text-muted-foreground leading-relaxed",children:e.description}),e.eventScope&&(0,t.jsx)("span",{className:"block text-[0.65rem] text-muted-foreground/40 font-mono mt-0.5 hidden lg:block",children:e.eventScope})]})]},e.id))]}),r.conventionPolicies?.map(e=>(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between px-4 py-2.5 bg-muted/20 border-b border-border/50",children:[(0,t.jsxs)("span",{className:"text-[0.7rem] font-semibold uppercase tracking-wider text-muted-foreground",children:["Convention Policies — ","project"===e.scope?"Project":"User"]}),(0,t.jsxs)("span",{className:"text-[0.7rem] text-muted-foreground",children:[e.policies.length," hook",1===e.policies.length?"":"s"]})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3 px-4 py-3 border-b border-border/20",children:[(0,t.jsx)(a,{className:"h-3.5 w-3.5 text-muted-foreground shrink-0"}),(0,t.jsx)("span",{className:"text-xs font-mono text-muted-foreground truncate",children:e.path})]}),0===e.policies.length?(0,t.jsxs)("div",{className:"flex items-start gap-2 px-4 py-2.5 border-b border-border/20 bg-muted/10",children:[(0,t.jsx)(m.Shield,{className:"h-3.5 w-3.5 text-muted-foreground/60 shrink-0 mt-0.5"}),(0,t.jsxs)("p",{className:"text-[0.7rem] text-muted-foreground/70 leading-relaxed",children:["No policies could be read from this file. It is still loaded at runtime — run ",(0,t.jsx)("span",{className:"font-mono",children:"failproofai policies"})," to see what it registers."]})]}):e.policies.map(s=>(0,t.jsxs)("div",{className:"flex items-start gap-3 px-4 py-3 border-b border-border/20 hover:bg-muted/20 transition-colors",children:[(0,t.jsx)("div",{className:"mt-0.5 shrink-0",children:(0,t.jsx)(Z,{enabled:s.enabled,onChange:()=>I(s.id,s.enabled),disabled:o})}),(0,t.jsx)("div",{className:"flex items-center gap-1.5 min-w-0 w-56 shrink-0 mt-0.5",children:(0,t.jsx)("span",{className:"text-xs font-mono text-foreground truncate",children:s.name})}),(0,t.jsxs)("div",{className:"flex-1 min-w-0",children:[s.description&&(0,t.jsx)("span",{className:"text-xs text-muted-foreground leading-relaxed",children:s.description}),s.eventScope&&(0,t.jsx)("span",{className:"block text-[0.65rem] text-muted-foreground/40 font-mono mt-0.5 hidden lg:block",children:s.eventScope})]})]},`${e.path}:${s.name}`))]},e.path))]})]})}function et({activeTab:e,onChange:s}){return(0,t.jsx)("div",{className:"tabs",style:{padding:0,marginBottom:24},children:[{id:"activity",label:"Activity"},{id:"policies",label:"Configure"}].map(r=>(0,t.jsx)("button",{type:"button",onClick:()=>s(r.id),className:`tab${e===r.id?" is-active":""}`,children:r.label.toLowerCase()},r.id))})}e.s(["default",0,function({initialTab:e="activity"}){let r=(0,E.useUrlParams)(),{capture:n}=(0,R.usePostHog)(),[o,l]=(0,s.useState)(e),[i,a]=(0,s.useState)(void 0),[c,d]=(0,s.useState)(null),[m,u]=(0,s.useState)([]);(0,s.useEffect)(()=>{w().then(e=>{a(e.clis.some(e=>e.installed)),d({enabled:e.enabledPolicies.length,total:e.policies.length+(e.customPolicies?.length??0)+(e.conventionPolicies?.reduce((e,t)=>e+t.policies.length,0)??0)}),u(e.clis.filter(e=>e.installed).map(e=>e.label))}).catch(()=>a(void 0))},[]);let x=0===m.length?"Policy evaluations":m.length<=3?`Policy evaluations across ${m.join(", ")}`:`Policy evaluations across ${m.length} agents`,p=e=>{e!==o&&n("policies_tab_switched",{tab:e,from_tab:o}),l(e),r.setAll({tab:"activity"===e?void 0:e})};return(0,t.jsx)("main",{className:"report",children:(0,t.jsxs)("section",{className:"section","data-screen-label":"policies",children:[(0,t.jsx)("h2",{style:{fontFamily:"var(--font-mono)",fontSize:24,fontWeight:600,letterSpacing:"-0.01em",color:"var(--ink)",margin:"0 0 16px"},children:"activity"===o?"Policies":"Configure Policies"}),"activity"===o?(0,t.jsxs)("div",{style:{display:"flex",alignItems:"flex-start",justifyContent:"space-between",gap:32,flexWrap:"wrap",margin:"0 0 24px"},children:[(0,t.jsxs)("div",{style:{fontFamily:"var(--font-mono)",fontSize:14,color:"var(--ink-2)",lineHeight:1.7,flex:"1 1 480px",minWidth:0},children:[x,c&&(0,t.jsxs)("span",{style:{color:"var(--dim)"},children:[" · ","enabled policies"," ",(0,t.jsxs)("span",{style:{color:"var(--ink)",fontVariantNumeric:"tabular-nums"},children:[c.enabled,"/",c.total]})]})]}),(0,t.jsxs)("div",{style:{display:"flex",flexDirection:"column",alignItems:"flex-end",gap:6,flex:"0 0 auto"},children:[(0,t.jsx)("span",{style:{fontFamily:"var(--font-mono)",fontSize:10,letterSpacing:"0.22em",textTransform:"uppercase",color:"var(--dim)"},children:"want to change which policies fire?"}),(0,t.jsx)("button",{type:"button",className:"btn btn-primary",onClick:()=>p("policies"),children:"[ configure policies → ]"})]})]}):(0,t.jsx)("p",{style:{fontFamily:"var(--font-mono)",fontSize:14,color:"var(--ink-2)",lineHeight:1.7,maxWidth:720,margin:"0 0 24px"},children:"switch policies on or off across your installed agent CLIs."}),(0,t.jsx)(et,{activeTab:o,onChange:p}),"activity"===o?(0,t.jsx)(V,{hooksInstalled:i,onSwitchTab:p}):(0,t.jsx)(ee,{onHooksInstallChange:a})]})})}],64245)}]);
@@ -205,7 +205,7 @@ dependencies = [
205
205
 
206
206
  [[package]]
207
207
  name = "failproofaid"
208
- version = "1.0.0-beta.4"
208
+ version = "1.0.0-beta.5"
209
209
  dependencies = [
210
210
  "fpai-collect",
211
211
  "fpai-ipc",
@@ -265,7 +265,7 @@ dependencies = [
265
265
 
266
266
  [[package]]
267
267
  name = "fpai-collect"
268
- version = "1.0.0-beta.4"
268
+ version = "1.0.0-beta.5"
269
269
  dependencies = [
270
270
  "notify",
271
271
  "reqwest",
@@ -280,7 +280,7 @@ dependencies = [
280
280
 
281
281
  [[package]]
282
282
  name = "fpai-ipc"
283
- version = "1.0.0-beta.4"
283
+ version = "1.0.0-beta.5"
284
284
  dependencies = [
285
285
  "libc",
286
286
  "proptest",
@@ -3,7 +3,7 @@ resolver = "3"
3
3
  members = ["crates/*"]
4
4
 
5
5
  [workspace.package]
6
- version = "1.0.0-beta.4"
6
+ version = "1.0.0-beta.5"
7
7
  edition = "2024"
8
8
  license-file = "LICENSE"
9
9
  repository = "https://github.com/FailproofAI/failproofai"
@@ -93,23 +93,26 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
93
93
  // machine (see fpai_collect::supervisor). It observes the same `shutdown`
94
94
  // flag the server and the cloud monitor do, so one SIGTERM stops all three.
95
95
  //
96
- // INERT until ingest is configured: `collector_tasks()` returns an empty
97
- // list, `spawn_supervised` then declines to start a thread or a runtime at
98
- // all, and the daemon behaves byte-for-byte as it did before this existed.
99
- let collector = fpai_collect::spawn_supervised(collector_tasks(), shutdown.clone());
96
+ // INERT until ingest is configured. But the config is not necessarily
97
+ // ready at startup: `failproofai config` installs the daemon and THEN
98
+ // connects, so the very first daemon start usually sees no ingest yet. A
99
+ // manager thread waits for the config to become enabled and starts the
100
+ // collector once it is — the collector's analogue of the cloud lane
101
+ // re-resolving enrolment per tick, so enabling collection takes effect
102
+ // within one interval with no restart and no root (see
103
+ // `spawn_collector_manager`). The manager owns the collector's lifecycle,
104
+ // including draining it on shutdown.
105
+ let collector_mgr = spawn_collector_manager(shutdown.clone());
100
106
 
101
107
  let srv = server::Server::bind(&socket_path, worker)?;
102
108
  eprintln!("[failproofaid] listening on {}", socket_path.display());
103
109
 
104
110
  let run_result = srv.run_until(shutdown);
105
111
 
106
- // Drain both background workers before returning. The collector gets a
107
- // bounded budget rather than an unbounded join: process exit must not wait
108
- // on a task that has wedged. Done before `?` so a server error still gets
109
- // the collector a chance to flush instead of dropping buffered events.
110
- if let Some(collector) = collector {
111
- collector.join_with_flush(fpai_collect::DEFAULT_FLUSH_BUDGET);
112
- }
112
+ // Join the manager, which drains the collector within its flush budget
113
+ // before returning. Done before `?` so a server error still gives the
114
+ // collector a chance to flush instead of dropping buffered events.
115
+ let _ = collector_mgr.join();
113
116
  let _ = cloud_monitor.join();
114
117
 
115
118
  run_result?;
@@ -128,6 +131,92 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
128
131
  /// serving the socket, because the CLI fails closed and a daemon that refused
129
132
  /// to boot over a malformed `ingest.json` would deny every tool call on the
130
133
  /// machine. The error is loud so it is fixable, not silent.
134
+ /// Own the collector's lifecycle on a dedicated thread.
135
+ ///
136
+ /// The collector config is not necessarily complete when the daemon starts:
137
+ /// `failproofai config` installs the service, so the daemon comes up, and only
138
+ /// THEN runs the connect step that writes `ingest.json` and the collector
139
+ /// block. Resolving the config once at startup therefore left a freshly-set-up
140
+ /// machine shipping nothing until the next manual restart — the exact confusion
141
+ /// a user hit while testing.
142
+ ///
143
+ /// This mirrors what the cloud-policy lane already does: it re-resolves its
144
+ /// config every tick precisely so `--connect` needs no root (restarting a
145
+ /// system unit does). The collector follows the same rule — it waits for the
146
+ /// config to become enabled, then starts once. Enabling collection thus takes
147
+ /// effect within one poll interval, no restart, no sudo.
148
+ ///
149
+ /// It starts the collector once and does not tear it back down on a later
150
+ /// `--disconnect` — that remains a restart, matching today's behaviour and
151
+ /// avoiding a second start against the set-once health registry. The gap this
152
+ /// closes is the common one: enabled *after* startup never taking effect.
153
+ fn spawn_collector_manager(daemon_shutdown: Arc<AtomicBool>) -> std::thread::JoinHandle<()> {
154
+ let interval = collector_config_poll_interval();
155
+ std::thread::Builder::new()
156
+ .name("fpai-collect-mgr".to_string())
157
+ .spawn(move || {
158
+ // Wait until collection is enabled. A cheap config read each tick,
159
+ // not the full task build, so an incomplete config waits quietly
160
+ // rather than logging on every interval.
161
+ loop {
162
+ if daemon_shutdown.load(Ordering::Relaxed) {
163
+ return;
164
+ }
165
+ if collector_is_enabled() {
166
+ break;
167
+ }
168
+ let deadline = std::time::Instant::now() + interval;
169
+ while std::time::Instant::now() < deadline {
170
+ if daemon_shutdown.load(Ordering::Relaxed) {
171
+ return;
172
+ }
173
+ std::thread::sleep(Duration::from_millis(200));
174
+ }
175
+ }
176
+
177
+ // Enabled — build and start. `collector_tasks()` logs "collector
178
+ // enabled" once; `spawn_supervised` returns None only if the config
179
+ // flipped back to disabled between the check and the build, or the
180
+ // runtime failed to start.
181
+ let Some(collector) =
182
+ fpai_collect::spawn_supervised(collector_tasks(), daemon_shutdown.clone())
183
+ else {
184
+ return;
185
+ };
186
+
187
+ while !daemon_shutdown.load(Ordering::Relaxed) {
188
+ std::thread::sleep(Duration::from_millis(200));
189
+ }
190
+ collector.join_with_flush(fpai_collect::DEFAULT_FLUSH_BUDGET);
191
+ })
192
+ .expect("failed to spawn the collector manager thread")
193
+ }
194
+
195
+ /// Cheap "should the collector be running?" check — reads the two small config
196
+ /// files. Any error resolves to `false`; the full `collector_tasks()` build
197
+ /// logs the reason when it acts on an enabled config.
198
+ fn collector_is_enabled() -> bool {
199
+ let Ok(home) = paths::failproofai_home() else {
200
+ return false;
201
+ };
202
+ fpai_collect::config::load(&home)
203
+ .map(|cfg| cfg.is_enabled())
204
+ .unwrap_or(false)
205
+ }
206
+
207
+ /// How often to re-check whether the collector config has become enabled.
208
+ /// Short by default — it is two small JSON reads — so enabling collection after
209
+ /// a fresh setup is prompt. `FAILPROOFAI_COLLECTOR_CONFIG_POLL_MS` overrides it.
210
+ fn collector_config_poll_interval() -> Duration {
211
+ const DEFAULT_MS: u64 = 5_000;
212
+ const MINIMUM_MS: u64 = 500;
213
+ let ms = std::env::var("FAILPROOFAI_COLLECTOR_CONFIG_POLL_MS")
214
+ .ok()
215
+ .and_then(|v| v.parse::<u64>().ok())
216
+ .unwrap_or(DEFAULT_MS);
217
+ Duration::from_millis(ms.max(MINIMUM_MS))
218
+ }
219
+
131
220
  fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
132
221
  let home = match paths::failproofai_home() {
133
222
  Ok(home) => home,
@@ -211,6 +300,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
211
300
  let spool_dir = cfg.own_spool_dir.clone();
212
301
  let verbosity = cfg.settings.hooks_verbosity;
213
302
  let environment = cfg.settings.environment.clone();
303
+ let machine_id = cfg.settings.machine_id.clone();
214
304
  tasks.push(fpai_collect::TaskSpec::new("hook-activity", move |sd| {
215
305
  fpai_collect::sources::hooks::run(
216
306
  store_dir.clone(),
@@ -218,6 +308,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
218
308
  spool_dir.clone(),
219
309
  verbosity,
220
310
  environment.clone(),
311
+ machine_id.clone(),
221
312
  sd,
222
313
  )
223
314
  }));
@@ -232,6 +323,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
232
323
  // uses, so adding one is a row here plus its own module.
233
324
  let spool = cfg.own_spool_dir.clone();
234
325
  let env = cfg.settings.environment.clone();
326
+ let machine = cfg.settings.machine_id.clone();
235
327
  let cursors = home.join("cursors");
236
328
 
237
329
  use fpai_collect::sources::{claude, codex, copilot, openclaw, pi};
@@ -244,6 +336,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
244
336
  &spool,
245
337
  &cursors,
246
338
  &env,
339
+ machine.as_deref(),
247
340
  );
248
341
  // Subagent transcripts live under the SAME root, claimed by a second
249
342
  // format. A separate source, not a second predicate: `is_source_file`
@@ -259,6 +352,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
259
352
  &spool,
260
353
  &cursors,
261
354
  &env,
355
+ machine.as_deref(),
262
356
  );
263
357
  file_source(
264
358
  &mut tasks,
@@ -269,6 +363,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
269
363
  &spool,
270
364
  &cursors,
271
365
  &env,
366
+ machine.as_deref(),
272
367
  );
273
368
  file_source(
274
369
  &mut tasks,
@@ -279,6 +374,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
279
374
  &spool,
280
375
  &cursors,
281
376
  &env,
377
+ machine.as_deref(),
282
378
  );
283
379
  file_source(
284
380
  &mut tasks,
@@ -289,6 +385,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
289
385
  &spool,
290
386
  &cursors,
291
387
  &env,
388
+ machine.as_deref(),
292
389
  );
293
390
  file_source(
294
391
  &mut tasks,
@@ -299,6 +396,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
299
396
  &spool,
300
397
  &cursors,
301
398
  &env,
399
+ machine.as_deref(),
302
400
  );
303
401
 
304
402
  use fpai_collect::sources::{goose, hermes, opencode};
@@ -311,6 +409,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
311
409
  &spool,
312
410
  cursors.join("goose"),
313
411
  &env,
412
+ machine.as_deref(),
314
413
  );
315
414
  sqlite_source(
316
415
  &mut tasks,
@@ -321,6 +420,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
321
420
  &spool,
322
421
  cursors.join("opencode"),
323
422
  &env,
423
+ machine.as_deref(),
324
424
  );
325
425
 
326
426
  // Hermes profiles are SEPARATE databases, and the SQLite poller keys its
@@ -338,6 +438,7 @@ fn collector_tasks() -> Vec<fpai_collect::TaskSpec> {
338
438
  &spool,
339
439
  state,
340
440
  &env,
441
+ machine.as_deref(),
341
442
  );
342
443
  }
343
444
  }
@@ -389,6 +490,7 @@ fn file_source(
389
490
  spool_dir: &std::path::Path,
390
491
  cursor_root: &std::path::Path,
391
492
  environment: &str,
493
+ machine_id: Option<&str>,
392
494
  ) {
393
495
  let spool_dir = spool_dir.to_path_buf();
394
496
  // One cursor store per source, never shared: the store writes its whole map
@@ -396,6 +498,7 @@ fn file_source(
396
498
  // loser would re-read from zero after every restart.
397
499
  let state_dir = cursor_root.join(name);
398
500
  let environment = environment.to_string();
501
+ let machine_id = machine_id.map(str::to_string);
399
502
  tasks.push(fpai_collect::TaskSpec::new(name, move |sd| {
400
503
  fpai_collect::filetail::run(
401
504
  fpai_collect::filetail::Spec {
@@ -407,6 +510,7 @@ fn file_source(
407
510
  params: fpai_collect::filetail::Params {
408
511
  agent_id: default_agent_id.to_string(),
409
512
  environment: environment.clone(),
513
+ machine_id: machine_id.clone(),
410
514
  end_idle_mins: 10,
411
515
  max_read_bytes: 32 * 1024 * 1024,
412
516
  max_batch_bytes: fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
@@ -432,9 +536,11 @@ fn sqlite_source(
432
536
  spool_dir: &std::path::Path,
433
537
  state_dir: std::path::PathBuf,
434
538
  environment: &str,
539
+ machine_id: Option<&str>,
435
540
  ) {
436
541
  let spool_dir = spool_dir.to_path_buf();
437
542
  let environment = environment.to_string();
543
+ let machine_id = machine_id.map(str::to_string);
438
544
  tasks.push(fpai_collect::TaskSpec::new(name, move |sd| {
439
545
  fpai_collect::sqlitepoll::run(
440
546
  fpai_collect::sqlitepoll::Spec {
@@ -446,6 +552,7 @@ fn sqlite_source(
446
552
  params: fpai_collect::sqlitepoll::Params {
447
553
  agent_id: default_agent_id.to_string(),
448
554
  environment: environment.clone(),
555
+ machine_id: machine_id.clone(),
449
556
  max_rows_per_poll: 2000,
450
557
  max_batch_bytes: fpai_collect::spool::DEFAULT_MAX_BATCH_BYTES,
451
558
  max_drain_passes: 20,
@@ -117,6 +117,13 @@ pub struct Settings {
117
117
  /// here would silently drop every event this machine produced.
118
118
  #[serde(default = "default_environment")]
119
119
  pub environment: String,
120
+ /// The id of the machine this daemon runs on, written by
121
+ /// `failproofai config --connect --machine-id <id>`. Stamped on every
122
+ /// collected event so the server groups by machine rather than by
123
+ /// `agent_id` (a per-project identity). Absent on config written before
124
+ /// this field existed; such events carry no machine.
125
+ #[serde(default, rename = "machineId")]
126
+ pub machine_id: Option<String>,
120
127
  }
121
128
 
122
129
  fn default_true() -> bool {
@@ -135,6 +142,7 @@ impl Default for Settings {
135
142
  hooks_verbosity: HooksVerbosity::default(),
136
143
  redact: Redact::default(),
137
144
  environment: default_environment(),
145
+ machine_id: None,
138
146
  }
139
147
  }
140
148
  }
@@ -130,6 +130,8 @@ pub fn no_seed_state(_lines: &[String], _state: &mut TailState) {}
130
130
  pub struct Params {
131
131
  pub agent_id: String,
132
132
  pub environment: String,
133
+ /// Machine this daemon runs on, stamped on every event. See `SpoolWriter`.
134
+ pub machine_id: Option<String>,
133
135
  /// Minutes a file must be idle at EOF before `agent_end` is emitted.
134
136
  pub end_idle_mins: u64,
135
137
  pub max_read_bytes: u64,
@@ -248,7 +250,8 @@ async fn process_file(
248
250
  spec.params.max_batch_bytes,
249
251
  spec.format.kind,
250
252
  &ctx.session_id,
251
- );
253
+ )
254
+ .with_machine_id(spec.params.machine_id.clone());
252
255
  let mut emitted = 0u64;
253
256
 
254
257
  // A session with no start event never appears in the product, so retry it
@@ -81,6 +81,7 @@ pub async fn run(
81
81
  spool_dir: PathBuf,
82
82
  verbosity: HooksVerbosity,
83
83
  environment: String,
84
+ machine_id: Option<String>,
84
85
  sd: Shutdown,
85
86
  ) -> Result<(), TaskError> {
86
87
  if verbosity == HooksVerbosity::Off {
@@ -108,6 +109,7 @@ pub async fn run(
108
109
  &mut cursors,
109
110
  verbosity,
110
111
  &environment,
112
+ machine_id.as_deref(),
111
113
  )
112
114
  .await
113
115
  {
@@ -138,6 +140,7 @@ async fn poll_once(
138
140
  cursors: &mut CursorStore,
139
141
  verbosity: HooksVerbosity,
140
142
  environment: &str,
143
+ machine_id: Option<&str>,
141
144
  ) -> Result<u64, TaskError> {
142
145
  let files = list_pages(store_dir).await;
143
146
  if files.is_empty() {
@@ -149,7 +152,8 @@ async fn poll_once(
149
152
  DEFAULT_MAX_BATCH_BYTES,
150
153
  "hooks",
151
154
  "activity",
152
- );
155
+ )
156
+ .with_machine_id(machine_id.map(str::to_string));
153
157
  // Allow rows are rolled up across the whole pass, so a bucket spanning two
154
158
  // files still emits once.
155
159
  let mut buckets: BTreeMap<transform::BucketKey, AllowBucket> = BTreeMap::new();
@@ -56,6 +56,14 @@ pub struct SpoolWriter {
56
56
  buf_bytes: u64,
57
57
  redact: Redact,
58
58
  redacted: u64,
59
+ /// The machine this daemon runs on, stamped on every event so the server
60
+ /// can tell one machine from another. Without it, events carry only
61
+ /// `agent_id` — a per-project/per-harness identity — and the fleet views
62
+ /// mistake every project for a separate machine. `None` on a machine that
63
+ /// was never enrolled with a machine id (older config); such events simply
64
+ /// carry no machine and are excluded from machine-level counts rather than
65
+ /// guessed into one.
66
+ machine_id: Option<String>,
59
67
  }
60
68
 
61
69
  impl SpoolWriter {
@@ -84,6 +92,7 @@ impl SpoolWriter {
84
92
  // which is the safe direction for the mistake to fall.
85
93
  redact: Redact::Minimal,
86
94
  redacted: 0,
95
+ machine_id: None,
87
96
  }
88
97
  }
89
98
 
@@ -95,6 +104,13 @@ impl SpoolWriter {
95
104
  self
96
105
  }
97
106
 
107
+ /// Set the machine id stamped on every event. An empty string is treated as
108
+ /// absent, so a misconfigured blank id never becomes a machine of its own.
109
+ pub fn with_machine_id(mut self, id: Option<String>) -> Self {
110
+ self.machine_id = id.filter(|s| !s.is_empty());
111
+ self
112
+ }
113
+
98
114
  /// How many string leaves have been scrubbed. Reported without ever
99
115
  /// logging what was removed.
100
116
  pub fn redacted_count(&self) -> u64 {
@@ -117,6 +133,17 @@ impl SpoolWriter {
117
133
  // disk.
118
134
  self.redacted += crate::redact::scrub_value(&mut event, self.redact) as u64;
119
135
 
136
+ // Stamp the machine id here, at the one point every event passes
137
+ // through — a source cannot forget it and a new source inherits it. Set
138
+ // only when absent, so an event that already names its machine (a
139
+ // re-shipped batch, say) is never overwritten.
140
+ if let Some(id) = &self.machine_id
141
+ && let Some(obj) = event.as_object_mut()
142
+ {
143
+ obj.entry("machine_id")
144
+ .or_insert_with(|| Value::String(id.clone()));
145
+ }
146
+
120
147
  let mut line = serialize(&event)?;
121
148
 
122
149
  if line.len() as u64 > self.max_batch_bytes {
@@ -430,6 +457,56 @@ mod tests {
430
457
  std::fs::remove_dir_all(&dir).ok();
431
458
  }
432
459
 
460
+ #[tokio::test]
461
+ async fn machine_id_is_stamped_on_every_event_when_set() {
462
+ // Without this the server can only group by agent_id — a per-project
463
+ // identity — so one machine's many projects each read as a machine.
464
+ let dir = tmpdir("machine-id");
465
+ let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
466
+ .with_machine_id(Some("beta-test".into()));
467
+ w.push(json!({"type": "agent_start", "agent_id": "claude-foo"}))
468
+ .await
469
+ .unwrap();
470
+ w.flush().await.unwrap();
471
+ let name = batches(&dir).remove(0);
472
+ let body = std::fs::read_to_string(dir.join(&name)).unwrap();
473
+ assert!(body.contains(r#""machine_id":"beta-test""#), "got {body}");
474
+ std::fs::remove_dir_all(&dir).ok();
475
+ }
476
+
477
+ #[tokio::test]
478
+ async fn an_absent_machine_id_stamps_nothing() {
479
+ // A machine that was never given an id must not invent one — the server
480
+ // excludes machine-less events rather than bucket them.
481
+ let dir = tmpdir("no-machine-id");
482
+ let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s");
483
+ w.push(json!({"type": "agent_start"})).await.unwrap();
484
+ w.flush().await.unwrap();
485
+ let name = batches(&dir).remove(0);
486
+ assert!(
487
+ !std::fs::read_to_string(dir.join(&name))
488
+ .unwrap()
489
+ .contains("machine_id")
490
+ );
491
+ std::fs::remove_dir_all(&dir).ok();
492
+ }
493
+
494
+ #[tokio::test]
495
+ async fn an_empty_machine_id_is_treated_as_absent() {
496
+ let dir = tmpdir("blank-machine-id");
497
+ let mut w = SpoolWriter::new(dir.clone(), DEFAULT_MAX_BATCH_BYTES, "claude", "s")
498
+ .with_machine_id(Some(String::new()));
499
+ w.push(json!({"type": "agent_start"})).await.unwrap();
500
+ w.flush().await.unwrap();
501
+ let name = batches(&dir).remove(0);
502
+ assert!(
503
+ !std::fs::read_to_string(dir.join(&name))
504
+ .unwrap()
505
+ .contains("machine_id")
506
+ );
507
+ std::fs::remove_dir_all(&dir).ok();
508
+ }
509
+
433
510
  #[tokio::test]
434
511
  async fn redaction_can_be_turned_off() {
435
512
  let dir = tmpdir("redact-off");