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.
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/build-manifest.json +3 -3
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/required-server-files.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error.html +1 -1
- package/.next/standalone/.next/server/app/_global-error.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +7 -7
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/api/audit/invite/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/audit/run/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-request/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/login-verify/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/logout/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/reminder/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/auth/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/audit/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/audit/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/index.html +1 -1
- package/.next/standalone/.next/server/app/index.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +14 -14
- package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +4 -4
- package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +9 -9
- package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/policies/page/server-reference-manifest.json +10 -10
- package/.next/standalone/.next/server/app/policies/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/policies/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/react-loadable-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page/server-reference-manifest.json +2 -2
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/project/[name]/session/[sessionId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/server/app/projects/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0f8nu8d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19120tr._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1fwl2mz._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1myjm-d._.js +1 -1
- package/.next/standalone/.next/server/chunks/package_json_[json]_cjs_1nxcc4v._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__02r5bgf._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0sdrz-j._.js → [root-of-the-server]__094nop3._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0e8sjqm._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0lkzqax._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0ls0gsr._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0qucxyj._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0spkm68._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0u02miy._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/{[root-of-the-server]__0cmdyn_._.js → [root-of-the-server]__1gak4pd._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1j4u3yd._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1zqz4v8._.js +2 -2
- package/.next/standalone/.next/server/chunks/ssr/app_audit__components_audit-dashboard_tsx_0p9ud47._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_global-error_tsx_1kp6l3x._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/app_policies_hooks-client_tsx_19dqvpc._.js +1 -1
- 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
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +12 -12
- package/.next/standalone/.next/static/chunks/{0ue9ak_xexiqh.js → 0cr85wuc9gc9k.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2zb3m1tzq6h98.js → 1bkynffp3q1f7.js} +1 -1
- package/.next/standalone/.next/static/chunks/1bw4dfzflelcv.js +1 -0
- package/.next/standalone/.next/static/chunks/{3h5_3u46gu9jx.js → 1tp9dex23zs8y.js} +1 -1
- package/.next/standalone/.next/static/chunks/{2c90h1g6x8z8y.js → 2cqk4a9s893oy.js} +1 -1
- package/.next/standalone/.next/static/chunks/{06jakt_xhoolk.js → 2k285z07qce47.js} +1 -1
- package/.next/standalone/.next/static/chunks/{43a1ywqsg9bq9.js → 2s9g1dsde_ts1.js} +1 -1
- package/.next/standalone/.next/static/chunks/{31cqiv_cpgj9m.js → 2v1rpzrh2i1wv.js} +1 -1
- package/.next/standalone/.next/static/chunks/34sjdw8lv46e3.js +6 -0
- package/.next/standalone/Cargo.lock +3 -3
- package/.next/standalone/Cargo.toml +1 -1
- package/.next/standalone/crates/failproofaid/src/main.rs +118 -11
- package/.next/standalone/crates/fpai-collect/src/config.rs +8 -0
- package/.next/standalone/crates/fpai-collect/src/filetail.rs +4 -1
- package/.next/standalone/crates/fpai-collect/src/sources/hooks/mod.rs +5 -1
- package/.next/standalone/crates/fpai-collect/src/spool.rs +77 -0
- package/.next/standalone/crates/fpai-collect/src/sqlitepoll.rs +4 -1
- package/.next/standalone/crates/fpai-collect/tests/claude_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/claude_subagents.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/codex_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/copilot_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/goose_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/hermes_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/hooks_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/openclaw_source.rs +1 -0
- package/.next/standalone/crates/fpai-collect/tests/pi_source.rs +1 -0
- package/.next/standalone/package.json +5 -5
- package/.next/standalone/server.js +1 -1
- package/dist/cli.mjs +6 -4
- package/dist/worker.mjs +1 -1
- package/package.json +5 -5
- package/src/hooks/cloud-connection.ts +3 -0
- package/src/hooks/collector-config.ts +10 -0
- package/.next/standalone/.next/static/chunks/0oe2-hp0_2tzs.js +0 -6
- package/.next/standalone/.next/static/chunks/1fw3pxobk_otl.js +0 -1
- /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → LOuwMmfomO6hQYfMQYs4K}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{StV6Lay7Q1cU4ysYCFJmH → LOuwMmfomO6hQYfMQYs4K}/_clientMiddlewareManifest.js +0 -0
- /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.
|
|
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.
|
|
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.
|
|
283
|
+
version = "1.0.0-beta.5"
|
|
284
284
|
dependencies = [
|
|
285
285
|
"libc",
|
|
286
286
|
"proptest",
|
|
@@ -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
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
|
|
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
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
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");
|