class ae{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatuses:{},circuitBreakers:[],clients:[],clientFilterCategory:"all",clientSearchQuery:"",secrets:[],clientsCollapsed:!1,capabilities:[],capabilitiesHiddenByPolicy:0,resources:[],resourcesHiddenByPolicy:0,prompts:[],promptsHiddenByPolicy:0,catalogEvents:[],tasks:[],selectedTaskId:null,taskFilterStatus:"all",approvals:[],auditEvents:[],auditTotal:0,auditFilters:{search:"",status:"all",eventType:"all",serverId:"all",limit:25,offset:0},auditSelectedEvent:null,auditStats:null,auditVerification:null,selectedCapabilityId:null,selectedResourceId:null,selectedPromptId:null,playgroundMode:"tools",playgroundArgs:{},isExecuting:!1,playgroundAsyncTask:!1,activeRequestId:null,isBatchModalOpen:!1,batchSteps:[{id:"step_1",capability_id:"",argsJson:"{}",continue_on_error:!1},{id:"step_2",capability_id:"",argsJson:"{}",continue_on_error:!0}],activeTab:"overview",activeProfile:null,eventLogs:[],executionResult:null,resourceReadResult:null,promptGetResult:null,metrics:{totalCatalogRequests:0,totalEtagHits:0,totalToolCalls:0,totalToolDurationUs:0}};listeners=[];getState(){return this.state}setState(e){this.state={...this.state,...e},this.listeners.forEach((t)=>t(this.state))}subscribe(e){return this.listeners.push(e),()=>{this.listeners=this.listeners.filter((t)=>t!==e)}}addEventLog(e,t,a,o){let r=[{time:new Date().toLocaleTimeString(),method:e,target:t,status:a,latency:o},...this.state.eventLogs].slice(0,50);this.setState({eventLogs:r})}}var c=new ae;class ne{baseUrl;constructor(e=""){this.baseUrl=e}async getConfig(){return(await fetch(`${this.baseUrl}/v1/config`)).json()}async listCapabilities(e){let t={};if(e)t["X-Warmplane-Profile"]=e;return(await fetch(`${this.baseUrl}/v1/capabilities`,{headers:t})).json()}async listResources(e){let t={};if(e)t["X-Warmplane-Profile"]=e;return(await fetch(`${this.baseUrl}/v1/resources`,{headers:t})).json()}async readResource(e,t){let a=performance.now(),o={"Content-Type":"application/json"};if(t)o["X-Warmplane-Profile"]=t;let n=await fetch(`${this.baseUrl}/v1/resources/read`,{method:"POST",headers:o,body:JSON.stringify(e)}),r=performance.now()-a,s=await n.json();return{status:n.status,durationMs:r,data:s}}async listPrompts(e){let t={};if(e)t["X-Warmplane-Profile"]=e;return(await fetch(`${this.baseUrl}/v1/prompts`,{headers:t})).json()}async getPrompt(e,t){let a=performance.now(),o={"Content-Type":"application/json"};if(t)o["X-Warmplane-Profile"]=t;let n=await fetch(`${this.baseUrl}/v1/prompts/get`,{method:"POST",headers:o,body:JSON.stringify(e)}),r=performance.now()-a,s=await n.json();return{status:n.status,durationMs:r,data:s}}async getCatalogEvents(e){let t=e?`?after=${encodeURIComponent(e)}`:"";return(await fetch(`${this.baseUrl}/v1/catalog/events${t}`)).json()}async callCapability(e,t){let a=performance.now(),o={"Content-Type":"application/json"};if(t)o["X-Warmplane-Profile"]=t;let n=await fetch(`${this.baseUrl}/v1/tools/call`,{method:"POST",headers:o,body:JSON.stringify(e)}),r=performance.now()-a,s=await n.json();return{status:n.status,durationMs:r,data:s}}async batchCallCapabilities(e,t){let a=performance.now(),o={"Content-Type":"application/json"};if(t)o["X-Warmplane-Profile"]=t;let n=await fetch(`${this.baseUrl}/v1/tools/batch_call`,{method:"POST",headers:o,body:JSON.stringify({steps:e})}),r=performance.now()-a,s=await n.json();return{status:n.status,durationMs:r,data:s}}async cancelOperation(e){return(await fetch(`${this.baseUrl}/v1/operations/${encodeURIComponent(e)}/cancel`,{method:"POST"})).json()}async completeArgument(e){return(await fetch(`${this.baseUrl}/v1/completion/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json()}async upsertServer(e,t){return(await fetch(`${this.baseUrl}/v1/config/servers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,server:t})})).json()}async deleteServer(e){return(await fetch(`${this.baseUrl}/v1/config/servers/${encodeURIComponent(e)}`,{method:"DELETE"})).json()}async restartServer(e){return(await fetch(`${this.baseUrl}/v1/config/servers/${encodeURIComponent(e)}/restart`,{method:"POST"})).json()}async upsertProfile(e,t,a,o){return(await fetch(`${this.baseUrl}/v1/config/profiles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:e,servers:t,description:a,policy:o})})).json()}async deleteProfile(e){return(await fetch(`${this.baseUrl}/v1/config/profiles/${encodeURIComponent(e)}`,{method:"DELETE"})).json()}async getEcosystemSources(){return(await fetch(`${this.baseUrl}/v1/config/ecosystem`)).json()}async importConfig(e,t=!1){return(await fetch(`${this.baseUrl}/v1/config/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_path:e,overwrite:t})})).json()}async savePolicy(e){let t={allow:e.allow||[],deny:e.deny||[],redactKeys:e.redact_keys||e.redactKeys||[],requireApproval:e.require_approval||e.requireApproval||[],approvalTimeoutSecs:e.approvalTimeoutSecs||e.approval_timeout_secs||300,webhook:e.webhook};return(await fetch(`${this.baseUrl}/v1/config/policy`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(t)})).json()}async listTasks(){return(await fetch(`${this.baseUrl}/v1/tasks`)).json()}async getTask(e){return(await fetch(`${this.baseUrl}/v1/tasks/${encodeURIComponent(e)}`)).json()}async updateTask(e,t){return(await fetch(`${this.baseUrl}/v1/tasks/${encodeURIComponent(e)}/update`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({inputResponses:t})})).json()}async cancelTask(e,t){return(await fetch(`${this.baseUrl}/v1/tasks/${encodeURIComponent(e)}/cancel`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({reason:t})})).json()}async listApprovals(){return(await fetch(`${this.baseUrl}/v1/approvals`)).json()}async approveTicket(e,t,a){return(await fetch(`${this.baseUrl}/v1/approvals/${encodeURIComponent(e)}/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operator:t,modified_args:a})})).json()}async rejectTicket(e,t,a){return(await fetch(`${this.baseUrl}/v1/approvals/${encodeURIComponent(e)}/reject`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operator:t,reason:a})})).json()}async updateAlias(e,t,a,o,n,r){return(await fetch(`${this.baseUrl}/v1/config/alias`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:e,alias:t,target:a,summary:o,description:n,passthrough:r})})).json()}async reloadConfig(){return(await fetch(`${this.baseUrl}/v1/config/reload`,{method:"POST"})).json()}async listAuditEvents(e){let t=new URLSearchParams;if(e?.actor_id)t.set("actor_id",e.actor_id);if(e?.server_id&&e.server_id!=="all")t.set("server_id",e.server_id);if(e?.capability_id)t.set("capability_id",e.capability_id);if(e?.event_type&&e.event_type!=="all")t.set("event_type",e.event_type);if(e?.status&&e.status!=="all")t.set("status",e.status);if(e?.trace_id)t.set("trace_id",e.trace_id);if(e?.request_id)t.set("request_id",e.request_id);if(e?.search)t.set("search",e.search);if(e?.limit)t.set("limit",String(e.limit));if(e?.offset!==void 0)t.set("offset",String(e.offset));let a=t.toString();return(await fetch(`${this.baseUrl}/v1/audit/events${a?`?${a}`:""}`)).json()}getAuditExportUrl(e,t="csv"){let a=new URLSearchParams;if(a.set("format",t),e?.actor_id)a.set("actor_id",e.actor_id);if(e?.server_id&&e.server_id!=="all")a.set("server_id",e.server_id);if(e?.capability_id)a.set("capability_id",e.capability_id);if(e?.event_type&&e.event_type!=="all")a.set("event_type",e.event_type);if(e?.status&&e.status!=="all")a.set("status",e.status);if(e?.trace_id)a.set("trace_id",e.trace_id);if(e?.request_id)a.set("request_id",e.request_id);if(e?.search)a.set("search",e.search);return`${this.baseUrl}/v1/audit/export?${a.toString()}`}async verifyAuditChain(){return(await fetch(`${this.baseUrl}/v1/audit/verify`)).json()}async getAuditStats(){return(await fetch(`${this.baseUrl}/v1/audit/stats`)).json()}async getClients(){return(await fetch(`${this.baseUrl}/v1/clients`)).json()}async attachClient(e,t,a="stdio",o){return(await fetch(`${this.baseUrl}/v1/clients/${encodeURIComponent(e)}/attach`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({profile:t||void 0,transport:a,http_url:o||void 0})})).json()}async detachClient(e){return(await fetch(`${this.baseUrl}/v1/clients/${encodeURIComponent(e)}/detach`,{method:"POST",headers:{"Content-Type":"application/json"}})).json()}async testWebhook(e,t){return(await fetch(`${this.baseUrl}/v1/webhooks/test`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({url:e||void 0,format:t||void 0})})).json()}async getSecrets(){return(await fetch(`${this.baseUrl}/v1/secrets`)).json()}async saveSecret(e,t,a){return(await fetch(`${this.baseUrl}/v1/secrets`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({key:e,value:t,service:a})})).json()}async deleteSecret(e){return(await fetch(`${this.baseUrl}/v1/secrets/${encodeURIComponent(e)}`,{method:"DELETE"})).json()}}var h=new ne;function oe(){let e=c.getState(),t=e.config.mcpServers||{},a=Object.keys(t),o=a.length,n="";if(a.length===0)n=`
<div style="grid-column: 1 / -1; padding: 32px; text-align: center; color: var(--text-dim); background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="font-size: 14px; color: var(--text-main); font-weight: 600; margin-bottom: 6px;">No Upstream MCP Servers Connected</div>
<div style="font-size: 12px; margin-bottom: 16px;">Initialize connections by adding a server or syncing existing IDE configurations.</div>
<div style="display: flex; gap: 8px; justify-content: center;">
<button class="btn btn-primary" onclick="window.app.openTemplateCatalog()">✨ Browse Templates</button>
<button class="btn btn-ghost" onclick="window.app.openAddServerModal()">+ Add Custom</button>
<button class="btn btn-ghost" onclick="window.app.openImportModal()">Sync from IDEs</button>
</div>
</div>
`;else n=a.map((S)=>{let O=t[S],W=O.command?"stdio":"http / sse",K=O.command?`${O.command} ${(O.args||[]).join(" ")}`:O.url,F=e.serverStatuses[S]||{status:"connected",protocol_version:"2026-07-28"},G=F.status==="degraded",re=F.status==="error"||F.status==="disconnected",Z=G?"var(--amber-400)":re?"var(--red-400)":"var(--green-400)";return`
<div class="bento-card col-4" style="background: var(--surface); border: 1px solid var(--border);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 6px;">
<span style="width: 8px; height: 8px; border-radius: 50%; background: ${Z}; display: inline-block;"></span>
${M(S)}
</span>
<span class="brand-badge">${W}</span>
</div>
<div style="font-family: var(--ff-mono); font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; margin-bottom: 12px;" title="${M(K||"")}">
${M(K||"")}
</div>
<div style="display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); border-top: 1px solid var(--border); padding-top: 8px;">
<span>Status: <strong style="color: ${Z};">${M(F.status)}</strong></span>
<span>Protocol: ${F.protocol_version}</span>
</div>
</div>
`}).join("");let r=e.eventLogs.length===0?`
<div class="feed-row" style="grid-template-columns: 80px 100px 1fr 100px 80px;">
<span style="color: var(--text-dim);">ready</span>
<span style="color: var(--cyan-400); font-weight: 600;">SSE</span>
<span style="color: var(--text-main);">/v1/resources/updates stream active</span>
<span style="color: var(--green-400);">CONNECTED</span>
<span style="color: var(--amber-300); text-align: right;">0.0ms</span>
</div>
`:e.eventLogs.map((S)=>`
<div class="feed-row" style="grid-template-columns: 80px 100px 1fr 100px 80px;">
<span style="color: var(--text-dim);">${M(S.time)}</span>
<span style="color: var(--cyan-400); font-weight: 600;">${M(S.method)}</span>
<span style="color: var(--text-main); font-family: var(--ff-mono);">${M(S.target)}</span>
<span style="color: var(--green-400);">${M(S.status)}</span>
<span style="color: var(--amber-300); text-align: right;">${M(S.latency)}</span>
</div>
`).join(""),s=e.metrics,i=s.totalCatalogRequests,l=s.totalEtagHits,u=i>0?`${(l/i*100).toFixed(1)}%`:"0.0%",p=i>0?`${l} of ${i} requests served via HTTP 304`:"Waiting for client requests",g=s.totalToolCalls,m=g>0?`${(s.totalToolDurationUs/g/1000).toFixed(1)}ms`:"0.0ms",d=g>0?`${g} tool executions processed`:"Local worker task queues warm",y=Object.keys(e.config.capabilityAliases||{}).length+Object.keys(e.config.resourceAliases||{}).length+Object.keys(e.config.promptAliases||{}).length,b=y>0?`${y*18}B / call`:"0B",v=y>0?`${y} active facade aliases pruning prompt size`:"Configure aliases in Studio to reduce prompt size",x=e.tasks||[],A=x.filter((S)=>S.status==="input_required").length,R=x.filter((S)=>S.status==="working"||S.status==="input_required").length,T=e.clients||[],k=T.filter((S)=>S.is_attached).length,w=T.filter((S)=>S.config_exists&&!S.is_attached).length,z=e.clientsCollapsed,L=k>0?`<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.3); background: rgba(52, 211, 153, 0.1);">⚡ ${k} Connected</span>`:w>0?`<span class="brand-badge" style="color: var(--amber-300); border-color: rgba(251, 191, 36, 0.3); background: rgba(251, 191, 36, 0.1);">○ ${w} Ready to Connect</span>`:'<span class="brand-badge" style="color: var(--text-dim);">No Apps Detected</span>',N=Object.keys(e.config.profiles||{}),D=e.activeProfile,q=e.clientFilterCategory||"all",U=(e.clientSearchQuery||"").toLowerCase().trim(),V=T.filter((S)=>{if(U){let O=S.name.toLowerCase().includes(U),W=S.id.toLowerCase().includes(U),K=S.category.toLowerCase().includes(U);if(!O&&!W&&!K)return!1}if(q==="connected")return S.is_attached;if(q==="ready")return S.config_exists||S.app_installed;if(q==="ides")return S.category.toLowerCase().includes("ide")||S.category.toLowerCase().includes("extension");if(q==="agents")return S.category.toLowerCase().includes("agent")||S.category.toLowerCase().includes("cli")||S.category.toLowerCase().includes("platform");return!0}),te=V.length===0?'<div style="padding: 16px; text-align: center; color: var(--text-dim); font-size: 11.5px; grid-column: 1 / -1;">No AI clients match current filter.</div>':V.map((S)=>{let{is_attached:O,config_exists:W,app_installed:K}=S,F="rgba(255, 255, 255, 0.2)",G="Not Found";if(O){F="var(--green-400)";let J=S.attached_transport==="http"?"HTTP":"stdio";G=S.attached_profile?`Connected · ${J} (${S.attached_profile})`:`Connected · ${J} (All Tools)`}else if(W)F="var(--amber-300)",G="Ready to Attach";else if(K)F="var(--cyan-400)",G="Installed";let re=N.map((J)=>`
<option value="${M(J)}" ${D===J||S.attached_profile===J?"selected":""}>${M(J)}</option>
`).join(""),Z=O?`<button class="btn btn-ghost" style="padding: 2px 7px; font-size: 10px; color: var(--red-400);" onclick="event.stopPropagation(); window.app.detachClient('${M(S.id)}')">Detach</button>`:W||K?`
<div style="display: flex; align-items: center; gap: 4px;" onclick="event.stopPropagation();">
${N.length>0?`
<select id="overview-client-prof-${M(S.id)}" class="form-input" style="font-size: 10px; padding: 1px 4px; height: 22px; width: 85px;" title="Select constellation profile">
<option value="" ${!D?"selected":""}>All Tools</option>
${re}
</select>
`:""}
<button class="btn btn-primary" style="padding: 2px 7px; font-size: 10px;" onclick="window.app.attachClient('${M(S.id)}')">⚡ Connect</button>
</div>
`:"";return`
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 12px; display: flex; align-items: center; justify-content: space-between; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px; overflow: hidden;">
<span style="width: 7px; height: 7px; border-radius: 50%; background: ${F}; flex-shrink: 0;"></span>
<div style="overflow: hidden;">
<div style="font-weight: 600; font-size: 12px; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${M(S.name)}</div>
<div style="font-size: 10px; color: var(--text-dim);">${M(G)}</div>
</div>
</div>
${Z}
</div>
`}).join(""),se=[{id:"all",label:"All"},{id:"ready",label:"Ready / Installed"},{id:"connected",label:"Connected"},{id:"ides",label:"IDEs"},{id:"agents",label:"Agents & CLIs"}].map((S)=>`<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 10.5px; border-radius: 100px; ${q===S.id?"background: var(--amber-400); color: #000; font-weight: 700;":"background: var(--surface); color: var(--text-muted);"}" onclick="event.stopPropagation(); window.app.setClientCategoryFilter('${S.id}')">${S.label}</button>`).join(""),B=`
<div class="bento-card" style="margin-top: 18px; padding: 12px 16px; border-color: rgba(245, 158, 11, 0.25); background: rgba(18, 24, 38, 0.4);">
<div style="display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none;" onclick="window.app.toggleClientsCollapse()">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 13.5px; font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 6px;">
<span>⚡ 1-Click AI Client Integrations</span>
</span>
${L}
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px;" onclick="event.stopPropagation(); window.app.refreshClients()">⟳ Scan</button>
<span style="font-size: 12px; color: var(--text-dim);">${z?"▼ Show":"▲ Hide"}</span>
</div>
</div>
${!z?`
<div style="margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--border-subtle);">
<div style="display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 8px;">
<div style="display: flex; gap: 4px; flex-wrap: wrap;">
${se}
</div>
<input type="text" class="form-input" style="font-size: 10.5px; padding: 2px 8px; width: 140px; height: 22px; border-radius: 100px;" placeholder="\uD83D\uDD0D Search..." value="${M(e.clientSearchQuery||"")}" onclick="event.stopPropagation();" oninput="window.app.setClientSearchQuery(this.value)">
</div>
<div style="display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 8px;">
${te}
</div>
</div>
`:""}
</div>
`;return`
<div class="bento-grid">
<div class="bento-card col-3">
<div class="stat-label">Token Savings Rate</div>
<div class="stat-value" style="color: var(--amber-300);">${b}</div>
<div class="stat-sub">${v}</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">ETag Cache Hit Rate</div>
<div class="stat-value" style="color: var(--cyan-400);">${u}</div>
<div class="stat-sub">${p}</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Tasks & HITL State</div>
<div class="stat-value" style="color: ${A>0?"var(--amber-400)":"var(--green-400)"};">${A>0?`${A} Action Req`:`${R} Active`}</div>
<div class="stat-sub">${A>0?"Awaiting Human-in-the-Loop decision":`${x.length} total registered tasks`}</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Connected Upstreams</div>
<div class="stat-value" style="color: var(--green-400);">${o} Active</div>
<div class="stat-sub">${o>0?"Persistent worker task channels":"No active upstream servers"}</div>
</div>
</div>
${B}
<div style="display: flex; justify-content: space-between; align-items: center; margin: 24px 0 12px;">
<div style="font-size: 15px; font-weight: 700; color: var(--text-main);">Connected Upstream Servers</div>
<button class="btn btn-ghost" onclick="window.app.switchTab('servers')">Manage All (${o}) →</button>
</div>
<div class="bento-grid" style="margin-bottom: 24px;">
${n}
</div>
<div style="font-size: 15px; font-weight: 700; color: var(--text-main); margin-bottom: 12px;">
Live Control Plane Event Stream
</div>
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; font-family: var(--ff-mono); font-size: 11.5px;">
<div style="display: grid; grid-template-columns: 80px 100px 1fr 100px 80px; padding: 8px 14px; background: var(--surface-hover); border-bottom: 1px solid var(--border); color: var(--text-muted); font-weight: 600;">
<span>TIME</span>
<span>METHOD</span>
<span>EVENT / TARGET</span>
<span>STATUS</span>
<span style="text-align: right;">LATENCY</span>
</div>
<div id="overview-event-rows">
${r}
</div>
</div>
`}function M(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}var X=[{id:"github",name:"GitHub",category:"devtools",description:"Explore repositories, issues, pull requests, branches, and commit histories.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-github"],envFields:[{key:"GITHUB_PERSONAL_ACCESS_TOKEN",label:"GitHub Personal Access Token",placeholder:"ghp_...",required:!0,description:"Classic or fine-grained token with repo scope."}]},{id:"git",name:"Git (Local)",category:"devtools",description:"Read local Git repository status, diffs, log histories, and commit changes.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-git","--repository","."],argsPlaceholder:"--with mcp<2 mcp-server-git --repository /path/to/repo",envFields:[]},{id:"filesystem",name:"Filesystem",category:"devtools",description:"Secure, sandboxed access to local files and directories for AI workflows.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-filesystem","."],argsPlaceholder:"-y @modelcontextprotocol/server-filesystem /allowed/dir1 /allowed/dir2",envFields:[]},{id:"memory",name:"Memory Graph",category:"devtools",description:"Persistent knowledge-graph based memory for multi-turn agent learning.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-memory"],envFields:[]},{id:"chrome-devtools",name:"Chrome DevTools",category:"devtools",description:"Inspect live DOM, execute scripts, read console logs, and capture network traces in Chrome.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"sentry",name:"Sentry",category:"devtools",description:"Query production error events, stack traces, and issue frequencies directly from Sentry.",badge:"uvx / Telemetry",command:"uvx",defaultArgs:["--with","mcp<2","--with","httpx","mcp-server-sentry","--auth-token","sntrys_token"],argsPlaceholder:"--with mcp<2 --with httpx mcp-server-sentry --auth-token YOUR_SENTRY_TOKEN",envFields:[{key:"SENTRY_AUTH_TOKEN",label:"Sentry Auth Token",placeholder:"sntrys_...",required:!0}]},{id:"playwright",name:"Playwright Browser",category:"browser",description:"Headless / headed browser automation for scraping, form filling, and UI interaction.",badge:"Popular #1 / npx",command:"npx",defaultArgs:["-y","@executeautomation/playwright-mcp-server"],envFields:[]},{id:"puppeteer",name:"Puppeteer",category:"browser",description:"Official browser automation server for web page scraping and screenshot capture.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-puppeteer"],envFields:[]},{id:"brave-search",name:"Brave Search",category:"browser",description:"Real-time privacy-preserving web search and local point-of-interest query engine.",badge:"Official / Search",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-brave-search"],envFields:[{key:"BRAVE_API_KEY",label:"Brave Search API Key",placeholder:"BSA...",required:!0}]},{id:"tavily",name:"Tavily Search",category:"browser",description:"AI-optimized web search engine structured specifically for LLM context injection.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","tavily-mcp"],envFields:[{key:"TAVILY_API_KEY",label:"Tavily API Key",placeholder:"tvly-...",required:!0}]},{id:"fetch",name:"Fetch / Web Markdown",category:"browser",description:"Download web pages, strip clutter, and convert raw HTML to clean markdown text.",badge:"Official / uvx",command:"uvx",defaultArgs:["mcp-server-fetch"],envFields:[]},{id:"postgres",name:"PostgreSQL",category:"database",description:"Read schemas, inspect tables, and execute SQL queries against PostgreSQL databases.",badge:"Official / Database",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-postgres","postgresql://user:pass@localhost:5432/mydb"],argsPlaceholder:"-y @modelcontextprotocol/server-postgres postgresql://user:pass@localhost:5432/dbname",envFields:[]},{id:"sqlite",name:"SQLite",category:"database",description:"Local embedded SQLite query runner and schema inspector.",badge:"Official / uvx",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-sqlite","--db-path","./app.db"],argsPlaceholder:"--with mcp<2 mcp-server-sqlite --db-path /path/to/database.sqlite",envFields:[]},{id:"supabase",name:"Supabase",category:"database",description:"Query database tables, manage auth policies, and inspect storage in Supabase.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@supabase/mcp-server-supabase@latest"],envFields:[{key:"SUPABASE_ACCESS_TOKEN",label:"Supabase Personal Access Token",placeholder:"sbp_...",required:!0},{key:"SUPABASE_PROJECT_REF",label:"Supabase Project Reference ID",placeholder:"abcdefghijklmnop",required:!1}]},{id:"redis",name:"Redis",category:"database",description:"Inspect cached keys, hash sets, lists, TTLs, and pub/sub channels in Redis.",badge:"Official / Key-Value",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-redis","redis://localhost:6379"],argsPlaceholder:"-y @modelcontextprotocol/server-redis redis://localhost:6379",envFields:[]},{id:"s3",name:"AWS S3 / Cloud Storage",category:"database",description:"Browse S3 buckets, fetch object metadata, and download files from cloud storage.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@geunoh/s3-mcp-server"],argsPlaceholder:"-y @geunoh/s3-mcp-server",envFields:[{key:"AWS_ACCESS_KEY_ID",label:"AWS Access Key ID",placeholder:"AKIA...",required:!0},{key:"AWS_SECRET_ACCESS_KEY",label:"AWS Secret Access Key",placeholder:"...",required:!0},{key:"AWS_REGION",label:"AWS Region",placeholder:"us-east-1",required:!1}]},{id:"linear",name:"Linear",category:"productivity",description:"Search, create, and triage Linear issues, cycles, teams, and project roadmaps.",badge:"Productivity / Stdio",command:"npx",defaultArgs:["-y","mcp-linear"],envFields:[{key:"LINEAR_API_KEY",label:"Linear API Key",placeholder:"lin_api_...",required:!0}]},{id:"slack",name:"Slack",category:"productivity",description:"Read channels, post messages, inspect threads, and search team discussions.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-slack"],envFields:[{key:"SLACK_BOT_TOKEN",label:"Slack Bot User Token",placeholder:"xoxb-...",required:!0},{key:"SLACK_TEAM_ID",label:"Slack Team ID",placeholder:"T01234567",required:!0}]},{id:"notion",name:"Notion",category:"productivity",description:"Search Notion workspace pages, read nested blocks, and query database entries.",badge:"Official / Stdio",command:"npx",defaultArgs:["-y","@notionhq/notion-mcp-server"],envFields:[{key:"NOTION_TOKEN",label:"Notion Internal Integration Token",placeholder:"secret_...",required:!0}]},{id:"jira",name:"Jira / Atlassian",category:"productivity",description:"Manage Jira issues, search JQL, read sprint statuses, and inspect boards.",badge:"uvx / Atlassian",command:"uvx",defaultArgs:["--with","mcp<2","mcp-server-jira","--jira-base-url","https://your-domain.atlassian.net"],argsPlaceholder:"--with mcp<2 mcp-server-jira --jira-base-url https://org.atlassian.net",envFields:[{key:"JIRA_TOKEN",label:"Atlassian API Token",placeholder:"ATATT3...",required:!0}]},{id:"google-drive",name:"Google Drive",category:"productivity",description:"Search, list, and read documents, spreadsheets, and drive files.",badge:"Community / Stdio",command:"npx",defaultArgs:["-y","@piotr-agier/google-drive-mcp"],envFields:[{key:"GOOGLE_APPLICATION_CREDENTIALS",label:"Google Credentials JSON Path",placeholder:"/path/to/credentials.json",required:!0}]},{id:"docker",name:"Docker",category:"cloud",description:"Inspect running containers, tail container logs, list images, and manage compose services.",badge:"uvx / DevOps",command:"uvx",defaultArgs:["mcp-server-docker"],envFields:[]},{id:"kubernetes",name:"Kubernetes (K8s)",category:"cloud",description:"Query cluster pods, services, deployment status, and inspect Kubernetes logs.",badge:"Popular / Stdio",command:"npx",defaultArgs:["-y","@strowk/mcp-k8s"],envFields:[{key:"KUBECONFIG",label:"Kubeconfig File Path (Optional)",placeholder:"~/.kube/config",required:!1}]},{id:"cloudflare",name:"Cloudflare",category:"cloud",description:"Manage Cloudflare Workers, KV namespaces, D1 databases, Vectorize indexes, and DNS.",badge:"Official / Cloudflare",command:"npx",defaultArgs:["-y","@cloudflare/mcp-server-cloudflare","run","dummy_account_id"],argsPlaceholder:"-y @cloudflare/mcp-server-cloudflare run YOUR_ACCOUNT_ID",envFields:[{key:"CLOUDFLARE_API_TOKEN",label:"Cloudflare API Token",placeholder:"...",required:!0},{key:"CLOUDFLARE_ACCOUNT_ID",label:"Cloudflare Account ID",placeholder:"...",required:!0}]},{id:"terraform",name:"Terraform",category:"cloud",description:"Inspect Terraform state files, resource dependency graphs, and plan previews.",badge:"Community / IaC",command:"npx",defaultArgs:["-y","@mseep/terraform-mcp-server"],envFields:[]}];function Q(e,t,a){let o=X.find((r)=>r.id.toLowerCase()===e.toLowerCase());if(o)return o;let n=`${t||""} ${(a||[]).join(" ")}`.toLowerCase();return X.find((r)=>{let s=`${r.command} ${r.defaultArgs.join(" ")}`.toLowerCase();if(n.includes(r.id.toLowerCase()))return!0;if(r.command&&n.includes(r.command.toLowerCase())&&r.defaultArgs.some((i)=>n.includes(i.toLowerCase())))return!0;return!1})}function ie(){let e=c.getState(),t=e.config.mcpServers||{},a=Object.keys(t),o=e.activeProfile,n=o?e.config.profiles?.[o]:void 0,r=!!n,s=n?.servers||[],i="";if(a.length===0)i=`
<div style="padding: 40px; text-align: center; color: var(--text-dim); background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="font-size: 15px; color: var(--text-main); font-weight: 600; margin-bottom: 8px;">No Servers Configured in ${I(e.configPath)}</div>
<p style="font-size: 12px; margin-bottom: 20px; max-width: 480px; margin-left: auto; margin-right: auto;">
Warmplane bridges local tools and remote MCP servers into one unified facade. Add your first server or import existing configs from Claude Desktop or Cursor.
</p>
<div style="display: flex; gap: 8px; justify-content: center;">
<button class="btn btn-primary" onclick="window.app.openTemplateCatalog()">✨ Browse Templates</button>
<button class="btn btn-ghost" onclick="window.app.openAddServerModal()">+ Add Custom</button>
<button class="btn btn-ghost" onclick="window.app.openImportModal()">Sync from IDEs</button>
</div>
</div>
`;else i=a.map((u)=>{let p=t[u],g=p.command?"stdio":"http / sse",m=p.command?`${p.command} ${(p.args||[]).join(" ")}`:p.url,d=e.serverStatuses[u]||{status:"connected",protocol_version:"2026-07-28"},y=!r||s.includes(u),b=Q(u,p.command,p.args),v=Object.keys(p.env||{}),x=(b?.envFields||[]).filter((B)=>B.required&&!v.includes(B.key)),A=p.env?Object.entries(p.env).map(([B,S])=>{if(S.startsWith("keychain://"))return`<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.3);">\uD83D\uDD12 ${I(B)} (Keychain)</span>`;if(S.startsWith("op://"))return`<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.3);">\uD83D\uDD12 ${I(B)} (1Password)</span>`;if(S.startsWith("env://"))return`<span class="brand-badge" style="color: var(--amber-300); border-color: rgba(251, 191, 36, 0.3);">\uD83D\uDD12 ${I(B)} (Env)</span>`;return`<span style="color: var(--text-dim);">${I(B)}=***</span>`}):[];for(let B of x)A.push(`<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);" title="Required environment variable '${I(B.key)}' is missing">⚠️ Missing ${I(B.key)}</span>`);let R=A.length>0?A.join(" "):"None",T=(e.circuitBreakers||[]).find((B)=>B.server_id===u),k='<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.25);">Circuit: CLOSED</span>';if(T){if(T.state==="open")k=`<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);">Circuit: OPEN (${T.consecutive_failures} failures)</span>`;else if(T.state==="half_open")k=`<span class="brand-badge" style="color: var(--amber-300); border-color: rgba(251, 191, 36, 0.4); background: rgba(251, 191, 36, 0.1);">Circuit: HALF-OPEN (${T.consecutive_successes} probe)</span>`}let w=p.resilience||e.config.resilience,z=w?`FT: ${w.failureThreshold||3} · Cooldown: ${(w.cooldownMs||30000)/1000}s · AutoRestart: ${w.autoRestart!==!1?"ON":"OFF"}`:"Default Resilience",L=x.length>0,N=d.status==="degraded"||L,D=d.status==="error"||d.status==="disconnected",q=L?"var(--amber-400)":N?"var(--amber-400)":D?"var(--red-400)":"var(--green-400)",U=L?`Status: ${I(d.status)} (Missing Keys)`:`Status: ${I(d.status)}`,V=(N||D)&&(d.error||L)?`
<div style="background: rgba(239, 68, 68, 0.08); border-left: 3px solid var(--amber-400); border-radius: var(--radius-xs); padding: 8px 12px; margin-top: 8px; display: flex; justify-content: space-between; align-items: center; gap: 8px;">
<div style="font-size: 11px; color: var(--amber-300); font-family: var(--ff-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<span style="font-weight: 700; color: var(--amber-400);">⚠️ Diagnostics:</span> ${I(d.error||`Missing required environment variable(s): ${x.map((B)=>B.key).join(", ")}`)}
</div>
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 10.5px; color: var(--amber-300); border-color: rgba(251, 191, 36, 0.3);" onclick="window.app.openServerDiagnosticsModal('${I(u)}')">Details</button>
</div>
`:"",te=r?y?`<span class="brand-badge" style="color: var(--green-400); border-color: rgba(34, 197, 94, 0.3); background: rgba(34, 197, 94, 0.08); display: inline-flex; align-items: center; gap: 6px;">
✔ IN CONSTELLATION
<button style="background: none; border: none; color: var(--amber-400); font-size: 10px; cursor: pointer; padding: 0 2px; text-decoration: underline;" onclick="window.app.toggleServerInProfile('${I(o)}', '${I(u)}', false)">Exclude</button>
</span>`:`<span class="brand-badge" style="color: var(--amber-400); border-color: rgba(245, 158, 11, 0.35); background: rgba(245, 158, 11, 0.08); display: inline-flex; align-items: center; gap: 6px;">
\uD83D\uDEAB EXCLUDED FROM PROFILE: ${I(o)}
<button style="background: none; border: none; color: var(--green-400); font-size: 10px; cursor: pointer; padding: 0 2px; text-decoration: underline; font-weight: 700;" onclick="window.app.toggleServerInProfile('${I(o)}', '${I(u)}', true)">+ Include</button>
</span>`:"";return`
<div class="bento-card" style="${r&&!y?"margin-bottom: 12px; opacity: 0.65; border: 1px dashed rgba(245, 158, 11, 0.4); background: rgba(0, 0, 0, 0.2);":`margin-bottom: 12px; border-color: ${N?"rgba(251, 191, 36, 0.3)":D?"rgba(248, 113, 113, 0.3)":"var(--border)"};`}">
<div style="display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap;">
<div style="flex: 1; min-width: 260px;">
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px; flex-wrap: wrap;">
<span style="width: 8px; height: 8px; border-radius: 50%; background: ${q}; display: inline-block;"></span>
<span style="font-size: 15px; font-weight: 700; color: var(--text-main);">${I(u)}</span>
<span class="brand-badge">${g}</span>
<span class="brand-badge" style="color: ${q}; border-color: ${L?"rgba(245, 158, 11, 0.5); background: rgba(245, 158, 11, 0.1);":"rgba(245, 158, 11, 0.3);"}">${U}</span>
<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.25);">Protocol: ${d.protocol_version}</span>
${k}
${te}
</div>
<div style="font-family: var(--ff-mono); font-size: 12px; color: var(--text-muted); margin-top: 4px;">
${p.command?"Command: ":"URL: "}<code>${I(m||"")}</code>
</div>
<div style="display: flex; gap: 14px; font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim); margin-top: 4px; align-items: center; flex-wrap: wrap;">
<span>\uD83D\uDEE1️ ${I(z)}</span>
${p.env&&Object.keys(p.env).length>0?`<span>Env: ${R}</span>`:""}
</div>
</div>
<div style="display: flex; gap: 8px; align-items: center; flex-shrink: 0;">
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px; color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.3);" onclick="window.app.restartServer('${I(u)}')">⚡ Restart</button>
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.openServerDiagnosticsModal('${I(u)}')">\uD83D\uDD0D Diagnostics</button>
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.openEditServerModal('${I(u)}')">✏️ Edit</button>
<button class="btn btn-danger" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.deleteServer('${I(u)}')">${r?"Delete Globally":"Remove"}</button>
</div>
</div>
${V}
</div>
`}).join("");let l=r?`
<div class="bento-card" style="margin-bottom: 16px; background: rgba(245, 158, 11, 0.05); border: 1px solid rgba(245, 158, 11, 0.3); display: flex; justify-content: space-between; align-items: center; gap: 14px; flex-wrap: wrap;">
<div style="display: flex; align-items: center; gap: 10px; flex: 1; min-width: 260px;">
<span style="font-size: 18px; flex-shrink: 0;">\uD83C\uDF0C</span>
<div>
<div style="font-size: 13px; font-weight: 700; color: var(--amber-400); display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
<span>Active Profile Constellation: <code style="font-size: 13px; color: var(--text-main);">${I(o)}</code></span>
<span class="brand-badge" style="color: var(--text-main);">${s.length} of ${a.length} servers included</span>
</div>
<div style="font-size: 11.5px; color: var(--text-muted); margin-top: 2px;">
Excluded servers are unavailable to clients connected via this profile. Tools from excluded servers are automatically hidden.
</div>
</div>
</div>
<div style="display: flex; gap: 8px; flex-shrink: 0;">
<button class="btn btn-ghost" style="font-size: 11px; padding: 4px 10px;" onclick="window.app.switchTab('profiles')">Manage Profiles</button>
<button class="btn btn-ghost" style="font-size: 11px; padding: 4px 10px;" onclick="window.app.setActiveProfile(null)">View All Servers</button>
</div>
</div>
`:"";return`
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 18px;">
<div>
<div style="font-size: 16px; font-weight: 700; color: var(--text-main);">Configured MCP Upstream Servers</div>
<div style="font-size: 11px; color: var(--text-dim);">Active configuration file: <code>${I(e.configPath)}</code></div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost" onclick="window.app.reloadFromDisk()">⟳ Reload Config</button>
</div>
</div>
${l}
${i}
${ye()}
`}function ye(){let e=c.getState(),t=e.clients||[],a=Object.keys(e.config.profiles||{}),o=e.clientsCollapsed,n=e.clientFilterCategory||"all",r=(e.clientSearchQuery||"").toLowerCase().trim();if(t.length===0)return"";let s=t.filter((d)=>d.is_attached).length,i=t.filter((d)=>d.config_exists&&!d.is_attached).length,l=t.filter((d)=>d.category.toLowerCase().includes("ide")||d.category.toLowerCase().includes("extension")).length,u=t.filter((d)=>d.category.toLowerCase().includes("agent")||d.category.toLowerCase().includes("cli")||d.category.toLowerCase().includes("platform")).length,p=t.filter((d)=>{if(r){let y=d.name.toLowerCase().includes(r),b=d.id.toLowerCase().includes(r),v=d.category.toLowerCase().includes(r),x=d.config_path.toLowerCase().includes(r);if(!y&&!b&&!v&&!x)return!1}if(n==="connected")return d.is_attached;if(n==="ready")return d.config_exists||d.app_installed;if(n==="ides")return d.category.toLowerCase().includes("ide")||d.category.toLowerCase().includes("extension");if(n==="agents")return d.category.toLowerCase().includes("agent")||d.category.toLowerCase().includes("cli")||d.category.toLowerCase().includes("platform");return!0}),g=[{id:"all",label:`All Ecosystems (${t.length})`},{id:"ready",label:`Ready / Installed (${i+s})`},{id:"connected",label:`⚡ Connected (${s})`},{id:"ides",label:`IDEs & Editors (${l})`},{id:"agents",label:`Agents & CLIs (${u})`}].map((d)=>`
<button class="btn btn-ghost" style="padding: 3px 10px; font-size: 11px; border-radius: 100px; ${n===d.id?"background: var(--amber-400); color: #000; font-weight: 700; border-color: var(--amber-400);":"background: var(--surface); color: var(--text-muted); border-color: var(--border);"}" onclick="window.app.setClientCategoryFilter('${I(d.id)}')">
${I(d.label)}
</button>
`).join(""),m=p.length===0?`<div style="padding: 24px; text-align: center; color: var(--text-dim); font-size: 12px;">No AI clients match the filter "${I(r||n)}".</div>`:p.map((d)=>{let{is_attached:y,config_exists:b,app_installed:v}=d,x='<span class="brand-badge" style="color: var(--text-dim); border-color: rgba(255, 255, 255, 0.1);">Not Found</span>';if(y){let k=d.attached_profile?` · ${d.attached_profile}`:"";x=`<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.3); background: rgba(52, 211, 153, 0.1);">⚡ Connected · ${d.attached_transport==="http"?"HTTP":"stdio"}${I(k)}</span>`}else if(b)x='<span class="brand-badge" style="color: var(--amber-300); border-color: rgba(251, 191, 36, 0.3); background: rgba(251, 191, 36, 0.08);">○ Ready</span>';else if(v)x='<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.25);">○ Installed</span>';let A=e.activeProfile,R=a.map((k)=>`
<option value="${I(k)}" ${A===k||d.attached_profile===k?"selected":""}>Profile: ${I(k)}</option>
`).join(""),T=y?`<button class="btn btn-ghost" style="padding: 3px 10px; font-size: 11px; color: var(--red-400); border-color: rgba(248, 113, 113, 0.3);" onclick="window.app.detachClient('${I(d.id)}')">Disconnect</button>`:`<button class="btn btn-primary" style="padding: 3px 10px; font-size: 11px;" onclick="window.app.attachClient('${I(d.id)}')">⚡ Connect</button>`;return`
<div style="display: grid; grid-template-columns: 200px 130px 1fr 140px 100px; align-items: center; gap: 12px; padding: 8px 12px; background: var(--surface); border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); transition: background 0.15s;" onmouseover="this.style.background='var(--surface-hover)'" onmouseout="this.style.background='var(--surface)'">
<div>
<div style="font-weight: 700; font-size: 13px; color: var(--text-main); display: flex; align-items: center; gap: 6px;">
<span>${I(d.name)}</span>
</div>
<div style="font-size: 10.5px; color: var(--text-dim);">${I(d.category)}</div>
</div>
<div>
${x}
</div>
<div style="font-family: var(--ff-mono); font-size: 10.5px; color: var(--text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${I(d.config_path)}">
${I(d.config_path)}
</div>
<div>
${a.length>0&&!y?`
<select id="client-prof-${I(d.id)}" class="form-input" style="font-size: 10.5px; padding: 2px 6px; height: 26px; width: 100%;">
<option value="">All Tools (Default)</option>
${R}
</select>
`:`<span style="font-size: 11px; color: var(--text-dim);">${d.other_servers_count>0?`${d.other_servers_count} other tools`:"Single facade"}</span>`}
</div>
<div style="text-align: right;">
${T}
</div>
</div>
`}).join("");return`
<div class="bento-card" style="margin-top: 28px; padding: 14px 18px; border-color: rgba(245, 158, 11, 0.2); background: rgba(18, 24, 38, 0.4);">
<div style="display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none;" onclick="window.app.toggleClientsCollapse()">
<div>
<div style="font-size: 14px; font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>⚡ 1-Click AI Client Integrations</span>
<span class="brand-badge" style="color: var(--amber-400); border-color: rgba(245, 158, 11, 0.3);">${s>0?`${s} Connected`:`${t.length} Ecosystems Supported`}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">
Attach Warmplane's unified facade to desktop IDEs, CLI assistants, and autonomous agent platforms without editing JSON/TOML files.
</div>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button class="btn btn-ghost" style="padding: 3px 8px; font-size: 11px;" onclick="event.stopPropagation(); window.app.refreshClients()">⟳ Scan Ecosystems</button>
<span style="font-size: 12px; color: var(--text-dim);">${o?"▼ Show":"▲ Hide"}</span>
</div>
</div>
${!o?`
<div style="margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle);">
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 12px;">
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
${g}
</div>
<div style="position: relative;">
<input type="text" class="form-input" style="font-size: 11px; padding: 4px 10px; width: 180px; height: 26px; border-radius: 100px;" placeholder="\uD83D\uDD0D Search clients..." value="${I(e.clientSearchQuery||"")}" oninput="window.app.setClientSearchQuery(this.value)">
</div>
</div>
<div style="display: flex; flex-direction: column; gap: 6px;">
<div style="display: grid; grid-template-columns: 200px 130px 1fr 140px 100px; gap: 12px; padding: 4px 12px; font-family: var(--ff-mono); font-size: 10px; color: var(--text-dim); font-weight: 700; text-transform: uppercase;">
<span>Application</span>
<span>Status</span>
<span>Configuration Path</span>
<span>Constellation Scope</span>
<span style="text-align: right;">Action</span>
</div>
${m}
</div>
</div>
`:""}
</div>
`}function I(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function le(){let e=c.getState(),t=e.playgroundMode||"tools",a=e.capabilities||[],o=e.resources||[],n=e.prompts||[],r=e.capabilitiesHiddenByPolicy||0,s=e.resourcesHiddenByPolicy||0,i=e.promptsHiddenByPolicy||0,l=e.activeProfile,p=!!(l?e.config.profiles?.[l]:void 0),g=`
<div style="display: flex; gap: 8px; margin-bottom: 12px; align-items: center; justify-content: space-between; flex-wrap: wrap;">
<div style="display: inline-flex; padding: 3px; background: rgba(0,0,0,0.3); border: 1px solid var(--border); border-radius: var(--radius-sm); align-items: center;">
<button
class="btn ${t==="tools"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px; display: inline-flex; align-items: center; gap: 6px;"
onclick="window.app.setPlaygroundMode('tools')"
>
<span>\uD83D\uDEE0️ Tools (${a.length})</span>
${r>0?`<span class="badge" style="background: rgba(245, 158, 11, 0.2); color: var(--amber-300); font-size: 9.5px; padding: 1px 5px;" title="${r} tools hidden by constellation/policy">+${r} hidden</span>`:""}
</button>
<button
class="btn ${t==="resources"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px; display: inline-flex; align-items: center; gap: 6px;"
onclick="window.app.setPlaygroundMode('resources')"
>
<span>\uD83D\uDCC4 Resources (${o.length})</span>
${s>0?`<span class="badge" style="background: rgba(245, 158, 11, 0.2); color: var(--amber-300); font-size: 9.5px; padding: 1px 5px;" title="${s} resources hidden by constellation/policy">+${s} hidden</span>`:""}
</button>
<button
class="btn ${t==="prompts"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px; display: inline-flex; align-items: center; gap: 6px;"
onclick="window.app.setPlaygroundMode('prompts')"
>
<span>\uD83D\uDCAC Prompts (${n.length})</span>
${i>0?`<span class="badge" style="background: rgba(245, 158, 11, 0.2); color: var(--amber-300); font-size: 9.5px; padding: 1px 5px;" title="${i} prompts hidden by constellation/policy">+${i} hidden</span>`:""}
</button>
</div>
<div style="display: flex; align-items: center; gap: 12px;">
${t==="tools"&&r>0?`
<div style="font-size: 11px; color: var(--amber-300); background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.25); padding: 3px 8px; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 6px;">
<span>\uD83D\uDEE1️ ${r} tool${r>1?"s":""} filtered ${p?`(Profile: ${E(l)})`:"by policy"}</span>
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600;">View Policy</a>
${p?`<a href="javascript:void(0)" onclick="window.app.switchTab('servers')" style="color: var(--cyan-400); text-decoration: underline; font-weight: 600; margin-left: 4px;">Server Hub</a>`:""}
</div>
`:t==="resources"&&s>0?`
<div style="font-size: 11px; color: var(--amber-300); background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.25); padding: 3px 8px; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 6px;">
<span>\uD83D\uDEE1️ ${s} resource${s>1?"s":""} filtered ${p?`(Profile: ${E(l)})`:"by policy"}</span>
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600;">View Policy</a>
${p?`<a href="javascript:void(0)" onclick="window.app.switchTab('servers')" style="color: var(--cyan-400); text-decoration: underline; font-weight: 600; margin-left: 4px;">Server Hub</a>`:""}
</div>
`:t==="prompts"&&i>0?`
<div style="font-size: 11px; color: var(--amber-300); background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.25); padding: 3px 8px; border-radius: var(--radius-sm); display: flex; align-items: center; gap: 6px;">
<span>\uD83D\uDEE1️ ${i} prompt${i>1?"s":""} filtered ${p?`(Profile: ${E(l)})`:"by policy"}</span>
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600;">View Policy</a>
${p?`<a href="javascript:void(0)" onclick="window.app.switchTab('servers')" style="color: var(--cyan-400); text-decoration: underline; font-weight: 600; margin-left: 4px;">Server Hub</a>`:""}
</div>
`:`
<div style="font-size: 11.5px; color: var(--text-dim);">
${t==="tools"?"Interactive Tool Caller & Context Distillation":t==="resources"?"Live MCP Resource Inspector & Reader":"Prompt Template Studio & Variable Binder"}
</div>
`}
</div>
</div>
`;if(t==="resources")return`
${g}
${xe(e)}
`;if(t==="prompts")return`
${g}
${he(e)}
`;return`
${g}
${be(e)}
${e.isBatchModalOpen?we(e):""}
`}function ee(e,t=!1){if(!e||!e.properties)return{};let a=e.properties||{},o=Array.isArray(e.required)?e.required:[],n={};for(let[r,s]of Object.entries(a)){let i=o.includes(r);if(t&&!i)continue;if(s.default!==void 0)n[r]=s.default;else if(Array.isArray(s.enum)&&s.enum.length>0)n[r]=s.enum[0];else if(s.examples&&Array.isArray(s.examples)&&s.examples.length>0)n[r]=s.examples[0];else if(s.example!==void 0)n[r]=s.example;else switch(s.type||"string"){case"string":n[r]=i?`sample_${r}`:"";break;case"number":case"integer":n[r]=0;break;case"boolean":n[r]=!0;break;case"array":n[r]=[];break;case"object":n[r]={};break;default:n[r]=`sample_${r}`}}return n}function be(e){let t=e.capabilities||[],a=e.selectedCapabilityId||(t.length>0?t[0].id:null),o=t.find((b)=>b.id===a),n=e.isExecutingCapability,r=e.capabilitiesHiddenByPolicy||0,s=e.activeProfile,i=!!(s&&e.config.profiles?.[s]),l="";if(t.length===0)l=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No tools or capabilities discovered from connected servers.
</div>
`;else l=t.map((b)=>`
<div class="cap-item ${b.id===a?"active":""}" onclick="window.app.selectCapability('${E(b.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${E(b.id)}</span>
<span style="font-size: 10px; color: var(--green-400);">${E(b.mode||"read")}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">server: ${E(b.server||"local")}</div>
</div>
`).join("");let u=o?.input_schema,p=u?.properties||{},g=Array.isArray(u?.required)?u.required:[],m=Object.entries(p),d="";if(m.length>0)d=`
<div style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; align-items: center;">
<span style="font-size: 10px; font-weight: 700; color: var(--text-dim); text-transform: uppercase;">Fields:</span>
${m.map(([b,v])=>{let x=g.includes(b),A=v.type||(v.enum?"enum":"any"),R=x?"rgba(239, 68, 68, 0.15)":"rgba(148, 163, 184, 0.1)",T=x?"var(--red-400)":"var(--text-muted)",k=x?"rgba(239, 68, 68, 0.3)":"var(--border)",w=v.description?` - ${v.description}`:"";return`
<button
type="button"
class="btn"
style="padding: 2px 7px; font-size: 10.5px; font-family: var(--ff-mono); background: ${R}; color: ${T}; border: 1px solid ${k}; border-radius: var(--radius-sm);"
title="Click to insert '${b}' (${A}${w})"
onclick="window.app.insertPlaygroundArgKey('${E(b)}', '${E(A)}', ${E(JSON.stringify(v.default??null))})"
>
+ ${E(b)} <span style="font-size: 9px; opacity: 0.7;">(${A}${x?" *":""})</span>
</button>
`}).join("")}
</div>
`;let y="{}";if(a&&e.playgroundArgs&&e.playgroundArgs[a]!==void 0)y=e.playgroundArgs[a];else{let b=ee(u,!1);y=JSON.stringify(b,null,2)}return`
<div style="display: grid; grid-template-columns: 320px 1fr; gap: 16px; height: calc(100vh - 165px);">
<!-- Left Sidebar: Capabilities Catalog -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 12px; border-bottom: 1px solid var(--border);">
<input type="text" class="form-input" placeholder="Search ${t.length} capabilities..." oninput="window.app.filterCapabilities(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-cap-list">
${l}
</div>
${r>0?`
<div style="padding: 8px 12px; background: rgba(245, 158, 11, 0.08); border-top: 1px solid rgba(245, 158, 11, 0.2); font-size: 11px; color: var(--amber-300); display: flex; justify-content: space-between; align-items: center;">
<span>\uD83D\uDEE1️ ${r} tool${r>1?"s":""} filtered ${i?`(${E(s)})`:""}</span>
<div style="display: flex; gap: 6px;">
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600; font-size: 10.5px;">Policy</a>
${i?`<a href="javascript:void(0)" onclick="window.app.switchTab('servers')" style="color: var(--cyan-400); text-decoration: underline; font-weight: 600; font-size: 10.5px;">Servers</a>`:""}
</div>
</div>
`:""}
</div>
<!-- Right Panel: Capability Execution & Envelope Visualizer -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 15px; font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);" id="pg-selected-title">
${E(o?o.id:"No Capability Selected")}
</div>
<div style="font-size: 11.5px; color: var(--text-dim);" id="pg-selected-desc">
${E(o?o.summary||o.description:"Connect servers to inspect and execute tools")}
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
${n?`
<div style="display: flex; gap: 8px; align-items: center;">
<span class="badge" style="background: rgba(234, 179, 8, 0.15); color: var(--amber-400); font-family: var(--ff-mono); font-size: 11px; padding: 4px 8px; display: inline-flex; align-items: center; gap: 6px;">
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--amber-400); display: inline-block;"></span>
EXECUTING...
</span>
<button class="btn btn-danger" style="background: rgba(239, 68, 68, 0.2); color: var(--red-400); border: 1px solid rgba(239, 68, 68, 0.4); padding: 5px 12px; font-size: 11.5px;" onclick="window.app.cancelActiveOperation()">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" stroke="none"><rect x="4" y="4" width="16" height="16" rx="2"></rect></svg>
Cancel Operation
</button>
</div>
`:`
<button class="btn btn-primary" onclick="window.app.executePlaygroundTool()" ${o?"":"disabled"}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Execute Capability
</button>
`}
</div>
</div>
<div style="flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden;">
<!-- Request Builder -->
<div style="padding: 16px; border-right: 1px solid var(--border); overflow-y: auto;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<label class="form-label" style="margin: 0;">Arguments JSON</label>
<div style="display: flex; gap: 6px;">
<button type="button" class="btn btn-ghost" style="padding: 2px 7px; font-size: 10.5px;" title="Fill sample payload from schema" onclick="window.app.fillPlaygroundSampleArgs(false)">✨ Sample Template</button>
${g.length>0?`
<button type="button" class="btn btn-ghost" style="padding: 2px 7px; font-size: 10.5px;" title="Fill only required schema fields" onclick="window.app.fillPlaygroundSampleArgs(true)">\uD83E\uDDF9 Required Only</button>
`:""}
<button type="button" class="btn btn-ghost" style="padding: 2px 7px; font-size: 10.5px;" title="Format JSON" onclick="window.app.formatPlaygroundArgs()">\uD83D\uDCCB Format</button>
<button type="button" class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px;" onclick="window.app.openBatchModal()">⚡ Pipeline Builder</button>
</div>
</div>
${d}
<textarea class="form-textarea" rows="7" id="pg-args-input" oninput="window.app.updatePlaygroundArgs(this.value)">${E(y)}</textarea>
<div style="margin-top: 12px; padding: 10px; background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div style="font-size: 11px; font-weight: 700; color: var(--cyan-400); margin-bottom: 6px; display: flex; align-items: center; gap: 6px;">
<span>⚡ Context Distillation Filters</span>
</div>
<div class="form-group" style="margin-bottom: 6px;">
<label class="form-label" style="font-size: 10.5px;">JSONPath Filter (e.g. $.items[*].name)</label>
<input type="text" class="form-input" id="pg-jsonpath-input" placeholder="$.result">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<div>
<label class="form-label" style="font-size: 10.5px;">Max Lines</label>
<input type="number" class="form-input" id="pg-limit-lines-input" placeholder="e.g. 50">
</div>
<div>
<label class="form-label" style="font-size: 10.5px;">Max Bytes</label>
<input type="number" class="form-input" id="pg-truncate-bytes-input" placeholder="e.g. 20480">
</div>
</div>
</div>
<div style="margin-top: 10px; display: flex; align-items: center; justify-content: space-between; padding: 8px 12px; background: rgba(0,0,0,0.25); border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div>
<div style="font-size: 11.5px; font-weight: 600; color: var(--amber-300); display: flex; align-items: center; gap: 6px;">
<span>⚡ Async Task Mode (SEP-2663)</span>
</div>
<div style="font-size: 10.5px; color: var(--text-dim);">Execute tool asynchronously returning HTTP 202 Accepted Task</div>
</div>
<label style="position: relative; display: inline-block; width: 36px; height: 20px; margin: 0; cursor: pointer;">
<input type="checkbox" id="pg-async-task-toggle" ${e.playgroundAsyncTask?"checked":""} onchange="window.app.togglePlaygroundAsyncTask(this.checked)" style="opacity: 0; width: 0; height: 0;">
<span style="position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background-color: ${e.playgroundAsyncTask?"var(--amber-400)":"var(--border)"}; transition: .3s; border-radius: 20px;">
<span style="position: absolute; content: ''; height: 14px; width: 14px; left: ${e.playgroundAsyncTask?"19px":"3px"}; bottom: 3px; background-color: white; transition: .3s; border-radius: 50%;"></span>
</span>
</label>
</div>
<div class="form-group" style="margin-top: 10px;">
<label class="form-label">Request Context / Operation ID (Optional)</label>
<input type="text" class="form-input" id="pg-context-input" placeholder="e.g. op-dev-test-1">
</div>
${o&&o.input_schema?`
<div style="margin-top: 14px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<label class="form-label" style="margin: 0;">Input JSON Schema</label>
<span style="font-size: 10px; color: var(--text-dim); font-family: var(--ff-mono);">${m.length} field${m.length===1?"":"s"} (${g.length} required)</span>
</div>
<pre style="background: var(--surface); padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); font-size: 11px; color: var(--text-muted); max-height: 140px; overflow-y: auto;">${E(JSON.stringify(o.input_schema,null,2))}</pre>
</div>
`:""}
</div>
<!-- Response Inspector -->
<div style="padding: 16px; background: var(--bg-app); display: flex; flex-direction: column; overflow: hidden;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 11px; font-weight: 600; color: var(--text-dim);">
${e.executionResult&&(e.executionResult.status===202||e.executionResult.data?.resultType==="task")?"SEP-2663 TASK RESPONSE":"NORMALIZED EXECUTION ENVELOPE"}
</span>
<span id="pg-status-badge" style="font-size: 11px; font-weight: 600; color: ${e.executionResult?e.executionResult.status===200?"var(--green-400)":e.executionResult.status===202?"var(--amber-300)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${e.executionResult?`HTTP ${e.executionResult.status} · ${e.executionResult.durationMs.toFixed(1)}ms`:"READY"}
</span>
</div>
${e.executionResult&&(e.executionResult.status===202||e.executionResult.data?.resultType==="task")?`
<div style="margin-bottom: 12px; padding: 12px 14px; background: rgba(245, 158, 11, 0.1); border: 1px solid rgba(245, 158, 11, 0.35); border-radius: var(--radius-sm); display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="display: flex; align-items: center; gap: 8px;">
<span class="brand-badge" style="background: rgba(245, 158, 11, 0.2); color: var(--amber-300); border-color: rgba(245, 158, 11, 0.5);">
${E(e.executionResult.data?.task?.status||e.executionResult.data?.status||"TASK_CREATED").toUpperCase()}
</span>
<span style="font-family: var(--ff-mono); font-size: 12px; font-weight: 700; color: var(--text-main);">${E(e.executionResult.data?.task?.taskId||e.executionResult.data?.taskId||"")}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 4px;">
Execution suspended for Human-in-the-Loop approval or async resolution.
</div>
</div>
<button class="btn btn-primary" style="padding: 4px 10px; font-size: 11px;" onclick="window.app.switchTab('tasks')">
Go to Tasks & Approvals →
</button>
</div>
`:""}
<pre id="pg-response-json" style="flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; color: var(--amber-300); font-size: 11.5px; overflow-y: auto; margin: 0; white-space: pre-wrap; word-break: break-word;">${e.executionResult?E(JSON.stringify(e.executionResult.data,null,2)):"// Response envelope output will be formatted here"}</pre>
</div>
</div>
</div>
</div>
`}function xe(e){let t=e.resources||[],a=e.resourcesHiddenByPolicy||0,o=e.selectedResourceId||(t.length>0?t[0].id:null),n=t.find((i)=>i.id===o),r=e.resourceReadResult,s="";if(t.length===0)s=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No resources exposed by connected MCP servers.
</div>
`;else s=t.map((i)=>{let l=i.id===o?"active":"",u=i.uri?i.uri.split(":")[0]:"res";return`
<div class="cap-item ${l}" onclick="window.app.selectResource('${E(i.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${E(i.name||i.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(56, 189, 248, 0.15); color: var(--cyan-400);">${E(u)}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${E(i.uri)}</div>
<div style="display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted); margin-top: 4px;">
<span>server: ${E(i.server||"local")}</span>
<span>${E(i.mime_type||"text/plain")}</span>
</div>
</div>
`}).join("");return`
<div style="display: grid; grid-template-columns: 340px 1fr; gap: 16px; height: calc(100vh - 165px);">
<!-- Left Sidebar: Resources Catalog -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 12px; border-bottom: 1px solid var(--border);">
<input type="text" class="form-input" placeholder="Search ${t.length} resources..." oninput="window.app.filterResources(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-res-list">
${s}
</div>
${a>0?`
<div style="padding: 8px 12px; background: rgba(245, 158, 11, 0.08); border-top: 1px solid rgba(245, 158, 11, 0.2); font-size: 11px; color: var(--amber-300); display: flex; justify-content: space-between; align-items: center;">
<span>\uD83D\uDEE1️ ${a} resource${a>1?"s":""} hidden by policy</span>
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600; font-size: 10.5px;">Edit Policy</a>
</div>
`:""}
</div>
<!-- Right Panel: Resource Content Reader & Metadata Inspector -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 15px; font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">
${E(n?n.name||n.id:"No Resource Selected")}
</div>
<div style="font-size: 11.5px; color: var(--cyan-400); font-family: var(--ff-mono);">
${E(n?n.uri:"Select a resource from the list to read live content")}
</div>
</div>
<button class="btn btn-primary" onclick="window.app.executeReadResource()" ${n?"":"disabled"}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>
Read Resource Content
</button>
</div>
<div style="flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden;">
<!-- Request / Distillation Parameters -->
<div style="padding: 16px; border-right: 1px solid var(--border); overflow-y: auto;">
${n?`
<div style="background: rgba(0,0,0,0.25); padding: 12px; border-radius: var(--radius-sm); border: 1px solid var(--border); margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; margin-bottom: 6px;">Resource Metadata</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 11.5px;">
<div><span style="color: var(--text-muted);">Server:</span> <strong style="color: var(--text-main);">${E(n.server)}</strong></div>
<div><span style="color: var(--text-muted);">MIME Type:</span> <strong style="color: var(--text-main);">${E(n.mime_type||"text/plain")}</strong></div>
</div>
${n.description?`
<div style="margin-top: 8px; font-size: 11.5px; color: var(--text-dim); border-top: 1px solid rgba(255,255,255,0.05); padding-top: 6px;">
${E(n.description)}
</div>
`:""}
</div>
`:""}
<div style="padding: 12px; background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div style="font-size: 11px; font-weight: 700; color: var(--cyan-400); margin-bottom: 8px; display: flex; align-items: center; gap: 6px;">
<span>⚡ Context Distillation Options</span>
</div>
<div class="form-group" style="margin-bottom: 8px;">
<label class="form-label" style="font-size: 10.5px;">JSONPath Expression</label>
<input type="text" class="form-input" id="pg-res-jsonpath-input" placeholder="$.items[*].data">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<div>
<label class="form-label" style="font-size: 10.5px;">Max Lines</label>
<input type="number" class="form-input" id="pg-res-lines-input" placeholder="e.g. 100">
</div>
<div>
<label class="form-label" style="font-size: 10.5px;">Max Bytes</label>
<input type="number" class="form-input" id="pg-res-bytes-input" placeholder="e.g. 32768">
</div>
</div>
</div>
</div>
<!-- Content Output Preview -->
<div style="padding: 16px; background: var(--bg-app); display: flex; flex-direction: column; overflow: hidden;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 11px; font-weight: 600; color: var(--text-dim);">RESOURCE CONTENT ENVELOPE</span>
<span style="font-size: 11px; font-weight: 600; color: ${r?r.status===200?"var(--green-400)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${r?`HTTP ${r.status} · ${r.durationMs.toFixed(1)}ms`:"READY"}
</span>
</div>
<pre style="flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; color: var(--cyan-400); font-size: 11.5px; overflow-y: auto; margin: 0; white-space: pre-wrap; word-break: break-word;">${r?E(JSON.stringify(r.data,null,2)):'// Click "Read Resource Content" to inspect live payload'}</pre>
</div>
</div>
</div>
</div>
`}function he(e){let t=e.prompts||[],a=e.promptsHiddenByPolicy||0,o=e.selectedPromptId||(t.length>0?t[0].id:null),n=t.find((l)=>l.id===o),r=e.promptGetResult,s="";if(t.length===0)s=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No prompt templates registered by connected MCP servers.
</div>
`;else s=t.map((l)=>{let u=l.id===o?"active":"",p=l.arguments?l.arguments.length:0;return`
<div class="cap-item ${u}" onclick="window.app.selectPrompt('${E(l.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${E(l.name||l.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(168, 85, 247, 0.15); color: var(--purple-400);">${p} args</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">${E(l.description||l.title||"Prompt template")}</div>
<div style="font-size: 10px; color: var(--text-muted); margin-top: 4px;">server: ${E(l.server||"local")}</div>
</div>
`}).join("");let i="";if(n&&n.arguments&&n.arguments.length>0)i=n.arguments.map((l)=>`
<div class="form-group" style="margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<label class="form-label" style="margin: 0; font-family: var(--ff-mono);">${E(l.name)}</label>
${l.required?'<span class="badge" style="background: rgba(239, 68, 68, 0.15); color: var(--red-400); font-size: 9px;">REQUIRED</span>':'<span style="font-size: 10px; color: var(--text-dim);">optional</span>'}
</div>
${l.description?`<div style="font-size: 11px; color: var(--text-dim); margin-bottom: 4px;">${E(l.description)}</div>`:""}
<input type="text" class="form-input prompt-arg-input" data-arg-name="${E(l.name)}" placeholder="Enter ${E(l.name)}..." />
</div>
`).join("");else if(n)i=`
<div style="padding: 12px; background: rgba(0,0,0,0.2); border-radius: var(--radius-sm); border: 1px solid var(--border); font-size: 11.5px; color: var(--text-dim);">
This prompt template does not require any input arguments.
</div>
`;return`
<div style="display: grid; grid-template-columns: 340px 1fr; gap: 16px; height: calc(100vh - 165px);">
<!-- Left Sidebar: Prompts Catalog -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 12px; border-bottom: 1px solid var(--border);">
<input type="text" class="form-input" placeholder="Search ${t.length} prompts..." oninput="window.app.filterPrompts(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-prompt-list">
${s}
</div>
${a>0?`
<div style="padding: 8px 12px; background: rgba(245, 158, 11, 0.08); border-top: 1px solid rgba(245, 158, 11, 0.2); font-size: 11px; color: var(--amber-300); display: flex; justify-content: space-between; align-items: center;">
<span>\uD83D\uDEE1️ ${a} prompt${a>1?"s":""} hidden by policy</span>
<a href="javascript:void(0)" onclick="window.app.switchTab('policy')" style="color: var(--amber-400); text-decoration: underline; font-weight: 600; font-size: 10.5px;">Edit Policy</a>
</div>
`:""}
</div>
<!-- Right Panel: Prompt Parameter Binder & Message Envelope Preview -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); display: flex; flex-direction: column; overflow: hidden;">
<div style="padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 15px; font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">
${E(n?n.name||n.id:"No Prompt Selected")}
</div>
<div style="font-size: 11.5px; color: var(--text-dim);">
${E(n?n.description||n.title||"Bind variables and render messages":"Select a prompt from the list to test")}
</div>
</div>
<button class="btn btn-primary" onclick="window.app.executeGetPrompt()" ${n?"":"disabled"}>
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Render Prompt Messages
</button>
</div>
<div style="flex: 1; display: grid; grid-template-columns: 1fr 1fr; overflow: hidden;">
<!-- Arguments Form Builder -->
<div style="padding: 16px; border-right: 1px solid var(--border); overflow-y: auto;">
<div style="font-size: 12px; font-weight: 700; color: var(--text-main); margin-bottom: 12px; text-transform: uppercase;">
Template Arguments
</div>
${i}
</div>
<!-- Rendered Messages Output -->
<div style="padding: 16px; background: var(--bg-app); display: flex; flex-direction: column; overflow: hidden;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<span style="font-size: 11px; font-weight: 600; color: var(--text-dim);">RENDERED PROMPT MESSAGES</span>
<span style="font-size: 11px; font-weight: 600; color: ${r?r.status===200?"var(--green-400)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${r?`HTTP ${r.status} · ${r.durationMs.toFixed(1)}ms`:"READY"}
</span>
</div>
<pre style="flex: 1; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; color: #c084fc; font-size: 11.5px; overflow-y: auto; margin: 0; white-space: pre-wrap; word-break: break-word;">${r?E(JSON.stringify(r.data,null,2)):'// Click "Render Prompt Messages" to view resolved system/user messages'}</pre>
</div>
</div>
</div>
</div>
`}function we(e){let t=e.capabilities||[],a=e.batchSteps||[];return`
<div style="position: fixed; inset: 0; background: rgba(0,0,0,0.75); backdrop-filter: blur(4px); display: flex; align-items: center; justify-content: center; z-index: 1000; padding: 24px;" onclick="if(event.target === this) window.app.closeBatchModal()">
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); width: 840px; max-width: 95vw; max-height: 90vh; display: flex; flex-direction: column; overflow: hidden; box-shadow: 0 20px 40px rgba(0,0,0,0.5);">
<div style="padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 16px; font-weight: 700; color: var(--text-main); display: flex; align-items: center; gap: 8px;">
<span>⚡ Visual Multi-Step Batch Pipeline Builder</span>
</div>
<div style="font-size: 11.5px; color: var(--text-dim); margin-top: 2px;">
Execute chained MCP tools with variable reference interpolation and fault tolerance.
</div>
</div>
<button class="btn btn-ghost" style="font-size: 16px; padding: 4px 8px;" onclick="window.app.closeBatchModal()">✕</button>
</div>
<div style="flex: 1; overflow-y: auto; padding: 20px;">
${a.map((n,r)=>{let s=t.find((d)=>d.id===n.capability_id),i=s?.input_schema,l=i?.properties||{},u=Array.isArray(i?.required)?i.required:[],p=Object.entries(l),g=t.map((d)=>`
<option value="${E(d.id)}" ${d.id===n.capability_id?"selected":""}>
${E(d.id)} (${E(d.server||"local")})
</option>
`).join(""),m="";if(p.length>0)m=`
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; margin-bottom: 6px; align-items: center;">
<span style="font-size: 9.5px; font-weight: 700; color: var(--text-dim); text-transform: uppercase;">Parameters:</span>
${p.map(([d,y])=>{let b=u.includes(d),v=y.type||(y.enum?"enum":"any");return`
<span style="font-size: 9.5px; font-family: var(--ff-mono); padding: 1px 5px; background: ${b?"rgba(239, 68, 68, 0.15)":"rgba(148, 163, 184, 0.1)"}; color: ${b?"var(--red-400)":"var(--text-muted)"}; border: 1px solid ${b?"rgba(239, 68, 68, 0.3)":"var(--border)"}; border-radius: 3px;" title="${E(y.description||"")}">
${E(d)} (${v}${b?" *":""})
</span>
`}).join("")}
</div>
`;return`
<div style="background: rgba(0,0,0,0.3); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 14px; margin-bottom: 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div style="display: flex; align-items: center; gap: 8px;">
<span class="badge" style="background: rgba(56, 189, 248, 0.15); color: var(--cyan-400); font-family: var(--ff-mono); font-weight: 700;">STEP ${r+1}</span>
<span style="font-size: 11px; font-family: var(--ff-mono); color: var(--text-dim);">id: ${E(n.id)}</span>
</div>
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px; color: var(--red-400);" onclick="window.app.removeBatchStep(${r})">
✕ Remove
</button>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 6px;">
<div class="form-group" style="margin: 0;">
<label class="form-label" style="font-size: 11px;">Target Capability</label>
<select class="form-input" style="font-size: 11.5px;" onchange="window.app.updateBatchStepCapability(${r}, this.value)">
<option value="">-- Select Capability --</option>
${g}
</select>
</div>
<div style="display: flex; align-items: flex-end; padding-bottom: 6px;">
<label style="display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: var(--text-muted); cursor: pointer;">
<input type="checkbox" ${n.continue_on_error?"checked":""} onchange="window.app.updateBatchStepContinueOnError(${r}, this.checked)" />
<span>Continue pipeline on step failure</span>
</label>
</div>
</div>
${m}
<div class="form-group" style="margin: 0;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<div style="display: flex; align-items: center; gap: 8px;">
<label class="form-label" style="margin: 0; font-size: 11px;">Step Arguments JSON</label>
${s?`
<button type="button" class="btn btn-ghost" style="padding: 1px 6px; font-size: 9.5px;" onclick="window.app.fillBatchStepSampleArgs(${r})">✨ Sample Args</button>
`:""}
</div>
<div style="display: flex; gap: 6px; font-size: 10px; color: var(--cyan-400); font-family: var(--ff-mono);">
<span>Helpers:</span>
<code style="cursor: pointer; background: rgba(0,0,0,0.3); padding: 1px 4px; border-radius: 2px;" onclick="window.app.appendBatchVariable(${r}, '\${steps[0].result.id}')">\${steps[0].result.id}</code>
<code style="cursor: pointer; background: rgba(0,0,0,0.3); padding: 1px 4px; border-radius: 2px;" onclick="window.app.appendBatchVariable(${r}, '\${steps[0].result.data}')">\${steps[0].result.data}</code>
</div>
</div>
<textarea
id="batch-step-args-${r}"
class="form-textarea"
rows="3"
style="font-size: 11px; font-family: var(--ff-mono);"
oninput="window.app.updateBatchStepArgs(${r}, this.value)"
>${E(n.argsJson)}</textarea>
</div>
</div>
`}).join("")}
<div style="display: flex; gap: 10px; margin-top: 14px;">
<button class="btn btn-ghost" style="font-size: 11.5px;" onclick="window.app.addBatchStep()">
+ Add Pipeline Step
</button>
</div>
</div>
<div style="padding: 14px 20px; border-top: 1px solid var(--border); background: var(--bg-app); display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 11.5px; color: var(--text-dim);">
${a.length} sequential execution steps configured
</div>
<div style="display: flex; gap: 10px;">
<button class="btn btn-ghost" onclick="window.app.closeBatchModal()">Cancel</button>
<button class="btn btn-primary" onclick="window.app.executeBatchPipeline()">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polygon points="5 3 19 12 5 21 5 3"></polygon></svg>
Run Batch Pipeline (${a.length} Steps)
</button>
</div>
</div>
</div>
</div>
`}function E(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function de(e){let t=e.tasks||[],a=e.taskFilterStatus||"all",o=t.filter((d)=>d.status==="input_required"),n=t.filter((d)=>d.status==="working"),r=t.filter((d)=>d.status==="completed"),s=t.filter((d)=>d.status==="cancelled"),i=t.filter((d)=>d.status==="failed"),l=a==="all"?t:t.filter((d)=>d.status===a),u=e.config.policy?.require_approval||e.config.policy?.requireApproval||[],p=o.length===0?`
<div style="padding: 36px 24px; text-align: center; background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="width: 44px; height: 44px; border-radius: 50%; background: rgba(52, 211, 153, 0.12); border: 1px solid rgba(52, 211, 153, 0.3); display: flex; align-items: center; justify-content: center; margin: 0 auto 12px; color: var(--green-400); font-size: 18px; font-weight: 700;">
✓
</div>
<div style="font-size: 14.5px; font-weight: 600; color: var(--text-main); margin-bottom: 5px;">No Tasks Awaiting Input or Approval</div>
<div style="font-size: 11.5px; color: var(--text-dim); max-width: 520px; margin: 0 auto; line-height: 1.6;">
Tool calls requiring Human-in-the-Loop approval or returning asynchronous <code style="color: var(--amber-300); font-family: var(--ff-mono);">input_required</code> tasks will suspend here for operator inspection, parameter editing, and response submission.
</div>
</div>
`:o.map((d)=>{let y=d.inputRequests||{},b=Object.keys(y),v=b.length>0,x=Math.floor(Date.now()/1000),A=d.expiresAtEpochSecs?Math.max(0,d.expiresAtEpochSecs-x):d.ttlMs?Math.max(0,Math.floor(d.ttlMs/1000)):d.ttlSeconds||300,R=d.createdAtEpochSecs?new Date(d.createdAtEpochSecs*1000).toLocaleTimeString():d.createdAt?new Date(d.createdAt).toLocaleTimeString():"—";return`
<div class="bento-card" style="border: 1px solid rgba(245, 158, 11, 0.35); background: var(--surface-card); margin-bottom: 14px; padding: 18px;">
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px;">
<div>
<div style="display: flex; align-items: center; gap: 8px;">
<span class="brand-badge" style="background: rgba(245, 158, 11, 0.2); color: var(--amber-300); border-color: rgba(245, 158, 11, 0.5);">
INPUT REQUIRED
</span>
<span style="font-family: var(--ff-mono); font-size: 12px; color: var(--text-muted);">${C(d.taskId)}</span>
</div>
<div style="margin-top: 6px; display: flex; align-items: center; gap: 8px;">
<span style="font-size: 14.5px; font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">
${C(d.capabilityId||"Tool Execution")}
</span>
${d.serverId?`<span style="font-size: 11px; color: var(--text-dim);">via <span style="color: var(--cyan-400); font-family: var(--ff-mono);">${C(d.serverId)}</span></span>`:""}
</div>
</div>
<div style="text-align: right; font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim);">
<div>Created: <span style="color: var(--text-muted);">${R}</span></div>
<div style="color: var(--amber-400); margin-top: 2px;">TTL Remaining: ${A}s</div>
</div>
</div>
<!-- Caller Context -->
${d.context?`
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 12px; font-family: var(--ff-mono); font-size: 11px; display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 12px; color: var(--text-muted);">
${d.context.actor_id?`<div><span style="color: var(--text-dim);">Actor:</span> <span style="color: var(--cyan-400);">${C(d.context.actor_id)}</span></div>`:""}
${d.context.operation_id?`<div><span style="color: var(--text-dim);">Operation:</span> <span style="color: var(--text-main);">${C(d.context.operation_id)}</span></div>`:""}
${d.context.grant_id?`<div><span style="color: var(--text-dim);">Grant:</span> <span style="color: var(--text-main);">${C(d.context.grant_id)}</span></div>`:""}
</div>
`:""}
<!-- Dynamic Input Requests Form -->
<div style="margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 8px;">
${v?"Required Input Responses (MRTR / HITL)":"Input Responses Payload (JSON)"}
</div>
${v?`
<div style="display: flex; flex-direction: column; gap: 10px;">
${b.map((T)=>{let k=y[T]||{},w=typeof k==="string"?k:k.prompt||k.description||k.title||T,z=k.type||"text",L=k.default!==void 0?JSON.stringify(k.default):k.value!==void 0?JSON.stringify(k.value):k.sanitized_args?JSON.stringify(k.sanitized_args,null,2):"";if(z==="approval_review")return`
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<label style="font-size: 11.5px; font-weight: 600; color: var(--amber-300); font-family: var(--ff-mono);">${C(T)}</label>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 5px;">APPROVAL GATED</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-bottom: 6px;">${C(w)}</div>
<div style="margin-bottom: 8px;">
<label style="font-size: 10.5px; color: var(--text-dim); display: block; margin-bottom: 2px;">Decision:</label>
<select id="task-input-${C(d.taskId)}-${C(T)}-decision" class="form-input" style="font-size: 11.5px; font-family: var(--ff-mono); padding: 4px 8px;">
<option value="true" selected>Approve & Execute</option>
<option value="false">Reject Execution</option>
</select>
</div>
<div>
<label style="font-size: 10.5px; color: var(--text-dim); display: block; margin-bottom: 2px;">Parameters (Editable):</label>
<textarea id="task-input-${C(d.taskId)}-${C(T)}" class="form-textarea" rows="3" style="color: var(--green-400); font-family: var(--ff-mono); font-size: 11.5px;">${C(L)}</textarea>
</div>
</div>
`;return`
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px 12px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;">
<label style="font-size: 11.5px; font-weight: 600; color: var(--amber-300); font-family: var(--ff-mono);">${C(T)}</label>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 5px;">${C(z)}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-bottom: 6px;">${C(w)}</div>
${z==="confirmation"||z==="boolean"?`
<select id="task-input-${C(d.taskId)}-${C(T)}" class="form-input" style="font-size: 11.5px; font-family: var(--ff-mono); padding: 4px 8px;">
<option value="true" selected>true (Approve / Confirm)</option>
<option value="false">false (Reject / Deny)</option>
</select>
`:k.sanitized_args||z==="object"||z==="json"?`
<textarea id="task-input-${C(d.taskId)}-${C(T)}" class="form-textarea" rows="3" style="color: var(--green-400); font-family: var(--ff-mono); font-size: 11.5px;">${C(L)}</textarea>
`:`
<input id="task-input-${C(d.taskId)}-${C(T)}" type="text" class="form-input" value="${C(L)}" placeholder="Enter ${C(T)} response..." style="font-size: 11.5px; font-family: var(--ff-mono);">
`}
</div>
`}).join("")}
</div>
`:`
<textarea id="task-raw-input-${C(d.taskId)}" class="form-textarea" rows="3" style="color: var(--green-400); font-family: var(--ff-mono); font-size: 11.5px;">{}</textarea>
`}
</div>
<!-- Action Footer -->
<div style="display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--border); padding-top: 12px;">
<div style="display: flex; align-items: center; gap: 8px;">
<input id="task-operator-${C(d.taskId)}" type="text" class="form-input" placeholder="Operator ID" value="security-operator" style="width: 180px; padding: 5px 10px; font-size: 11px;">
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button class="btn btn-danger" onclick="window.app.promptCancelTask('${C(d.taskId)}')">
✕ Cancel Task
</button>
<button class="btn btn-primary" onclick="window.app.submitTaskInputResponses('${C(d.taskId)}')">
✓ Submit & Resume
</button>
</div>
</div>
</div>
`}).join(""),g=u.length===0?`
<div style="color: var(--text-dim); font-size: 11.5px; line-height: 1.5; padding: 8px 0;">
No explicit <code style="color: var(--amber-400);">require_approval</code> rules active. Gated execution rules convert matching tool calls into tasks in real-time.
</div>
`:u.map((d)=>`
<div style="display: flex; justify-content: space-between; align-items: center; background: var(--surface); padding: 8px 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); margin-bottom: 6px;">
<span style="font-family: var(--ff-mono); font-size: 11.5px; color: var(--amber-300); font-weight: 500;">\uD83D\uDEE1️ ${C(d)}</span>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 5px;">GATED</span>
</div>
`).join(""),m=l.length===0?`
<tr>
<td colspan="6" style="padding: 24px; text-align: center; color: var(--text-dim); font-size: 12px;">
No tasks found matching filter "${C(a)}".
</td>
</tr>
`:l.map((d)=>{let y=d.status==="completed"?"background: rgba(52, 211, 153, 0.12); color: var(--green-400); border-color: rgba(52, 211, 153, 0.3);":d.status==="working"?"background: rgba(56, 189, 248, 0.15); color: var(--cyan-400); border-color: rgba(56, 189, 248, 0.4);":d.status==="input_required"?"background: rgba(245, 158, 11, 0.2); color: var(--amber-300); border-color: rgba(245, 158, 11, 0.5);":d.status==="cancelled"?"background: rgba(148, 163, 184, 0.15); color: var(--text-muted); border-color: rgba(148, 163, 184, 0.3);":"background: rgba(248, 113, 113, 0.12); color: var(--red-400); border-color: rgba(248, 113, 113, 0.3);",b=d.progress!==void 0?Math.round(d.progress*100):d.status==="completed"?100:d.status==="working"?50:0,v=d.createdAtEpochSecs?new Date(d.createdAtEpochSecs*1000).toLocaleTimeString():d.createdAt?new Date(d.createdAt).toLocaleTimeString():"—";return`
<tr style="border-bottom: 1px solid rgba(255,255,255,0.03); transition: background 0.15s;" onmouseover="this.style.background='var(--surface-hover)'" onmouseout="this.style.background='transparent'">
<td style="padding: 10px 14px;">
<span class="brand-badge" style="${y}">
${d.status.toUpperCase()}
</span>
</td>
<td style="padding: 10px 14px; font-family: var(--ff-mono); font-weight: 600; color: var(--text-main); font-size: 11.5px;">
${C(d.capabilityId||"Tool Execution")}
</td>
<td style="padding: 10px 14px; font-family: var(--ff-mono); color: var(--text-dim); font-size: 11px;">
${C(d.taskId)}
</td>
<td style="padding: 10px 14px; width: 140px;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="flex: 1; height: 6px; background: var(--surface-card); border-radius: 3px; overflow: hidden; border: 1px solid var(--border);">
<div style="height: 100%; width: ${b}%; background: ${d.status==="completed"?"var(--green-400)":"var(--amber-400)"}; transition: width 0.3s;"></div>
</div>
<span style="font-size: 10.5px; font-family: var(--ff-mono); color: var(--text-muted);">${b}%</span>
</div>
</td>
<td style="padding: 10px 14px; color: var(--text-dim); font-size: 11px; text-align: right;">
${v}
</td>
<td style="padding: 10px 14px; text-align: right;">
<div style="display: inline-flex; gap: 6px; align-items: center;">
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 10.5px;" onclick="window.app.openTaskInspectorModal('${C(d.taskId)}')">\uD83D\uDD0D Inspect</button>
${d.status==="input_required"||d.status==="working"?`
<button class="btn btn-danger" style="padding: 2px 8px; font-size: 10.5px;" onclick="window.app.promptCancelTask('${C(d.taskId)}')">Cancel</button>
`:""}
</div>
</td>
</tr>
`}).join("");return`
<!-- Sub-header & Actions -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<div style="display: flex; align-items: center; gap: 10px;">
<span class="brand-badge" style="font-size: 11px; padding: 3px 10px; color: ${o.length>0?"var(--amber-300)":"var(--green-400)"}; border-color: ${o.length>0?"rgba(245, 158, 11, 0.4)":"rgba(52, 211, 153, 0.4)"}; background: ${o.length>0?"rgba(245, 158, 11, 0.1)":"rgba(52, 211, 153, 0.1)"};">
${o.length} ACTION REQUIRED
</span>
<span style="font-size: 12px; color: var(--text-dim);">
SEP-2663 Tasks Extension (<code style="color: var(--amber-300); font-family: var(--ff-mono);">io.modelcontextprotocol/tasks</code>) and Unified Human-in-the-Loop execution control.
</span>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost" onclick="window.app.refreshTasks()" style="font-size: 11.5px;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.5 2v6h-6M21.34 15.57a10 10 0 1 1-.57-8.38l5.67-5.67"/></svg>
Refresh Tasks
</button>
</div>
</div>
<!-- Top Bento Metrics (Full 12-column span) -->
<div class="bento-grid">
<div class="bento-card col-3">
<div class="stat-label">Input Required (HITL)</div>
<div class="stat-value" style="color: ${o.length>0?"var(--amber-400)":"var(--text-main)"};">${o.length}</div>
<div class="stat-sub">Awaiting operator decision or response</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Working / In-Flight</div>
<div class="stat-value" style="color: var(--cyan-400);">${n.length}</div>
<div class="stat-sub">Asynchronous active executions</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Completed Tasks</div>
<div class="stat-value" style="color: var(--green-400);">${r.length}</div>
<div class="stat-sub">Finished successfully</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Cancelled / Failed</div>
<div class="stat-value" style="color: ${i.length>0?"var(--red-400)":"var(--text-muted)"};">${s.length+i.length}</div>
<div class="stat-sub">Terminated or errored</div>
</div>
</div>
<!-- Main Content Bento Split (8 cols queue / 4 cols rules) -->
<div class="bento-grid">
<!-- Left Column: Input Required Action Queue -->
<div class="col-8">
<div style="font-size: 11px; font-weight: 700; color: var(--amber-400); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; display: flex; align-items: center; gap: 6px;">
<span>⚡ Awaiting Operator Action (${o.length})</span>
</div>
<div>
${p}
</div>
</div>
<!-- Right Column: Active Governance Rules & Architecture -->
<div class="col-4">
<div style="font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
<span>\uD83D\uDEE1️ Gating Policy Rules</span>
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 10.5px;" onclick="window.app.switchTab('policy')">Edit in Policy →</button>
</div>
<div class="bento-card" style="margin-bottom: 14px;">
${g}
</div>
<div class="bento-card">
<div class="stat-label" style="margin-bottom: 8px;">SEP-2663 Protocol Standard</div>
<div style="font-size: 11.5px; color: var(--text-dim); line-height: 1.5;">
Warmplane exposes compliant <code>task_get</code>, <code>task_update</code>, and <code>task_cancel</code> tools directly on the MCP facade, enabling seamless agent delegation with non-blocking lifecycle management.
</div>
</div>
</div>
</div>
<!-- Bottom History & Task Registry Table (Full 12 columns) -->
<div class="bento-card col-12" style="margin-top: 10px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div style="font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px;">
\uD83D\uDCDC Task Registry (${l.length})
</div>
<div style="display: flex; gap: 6px; align-items: center;">
<span style="font-size: 11px; color: var(--text-dim);">Filter Status:</span>
<select class="form-input" style="padding: 3px 8px; font-size: 11px; width: 140px;" onchange="window.app.filterTasksByStatus(this.value)">
<option value="all" ${a==="all"?"selected":""}>All Statuses</option>
<option value="input_required" ${a==="input_required"?"selected":""}>input_required</option>
<option value="working" ${a==="working"?"selected":""}>working</option>
<option value="completed" ${a==="completed"?"selected":""}>completed</option>
<option value="cancelled" ${a==="cancelled"?"selected":""}>cancelled</option>
<option value="failed" ${a==="failed"?"selected":""}>failed</option>
</select>
</div>
</div>
<div style="overflow-x: auto;">
<table style="width: 100%; border-collapse: collapse; font-family: var(--ff-mono); font-size: 11.5px; text-align: left;">
<thead>
<tr style="border-bottom: 1px solid var(--border); color: var(--text-muted); font-size: 10.5px; text-transform: uppercase;">
<th style="padding: 10px 14px;">Status</th>
<th style="padding: 10px 14px;">Capability / Tool</th>
<th style="padding: 10px 14px;">Task ID</th>
<th style="padding: 10px 14px;">Progress</th>
<th style="padding: 10px 14px; text-align: right;">Created</th>
<th style="padding: 10px 14px; text-align: right;">Actions</th>
</tr>
</thead>
<tbody>
${m}
</tbody>
</table>
</div>
</div>
`}function C(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function ce(){let e=c.getState(),t=e.auditEvents||[],a=e.auditStats||{total_events:0,by_status:{success:0,failed:0,denied:0,intercepted:0}},o=e.auditVerification,n=e.auditFilters,r=e.auditTotal??t.length,s=e.auditSelectedEvent,i=Object.keys(e.config?.mcpServers||{}),l=n.limit||25,u=n.offset||0,p=Math.floor(u/l)+1,g=Math.max(1,Math.ceil(r/l)),m=r===0?0:u+1,d=Math.min(u+l,r),y=h.getAuditExportUrl({actor_id:n.search?void 0:void 0,server_id:n.serverId!=="all"?n.serverId:void 0,event_type:n.eventType!=="all"?n.eventType:void 0,status:n.status!=="all"?n.status:void 0,search:n.search.trim()?n.search.trim():void 0},"csv"),b=h.getAuditExportUrl({server_id:n.serverId!=="all"?n.serverId:void 0,event_type:n.eventType!=="all"?n.eventType:void 0,status:n.status!=="all"?n.status:void 0,search:n.search.trim()?n.search.trim():void 0},"jsonl"),v=o?o.is_valid?`
<div style="display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: rgba(34, 197, 94, 0.1); border: 1px solid rgba(34, 197, 94, 0.3); border-radius: var(--radius-sm); font-size: 11.5px; color: var(--green-400);">
<span>\uD83D\uDEE1️</span>
<span style="font-weight: 600;">Chain Verified: 100% Tamper Free (${o.total_records} events)</span>
</div>
`:`
<div style="display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: var(--radius-sm); font-size: 11.5px; color: var(--red-400);">
<span>⚠️</span>
<span style="font-weight: 600;">TAMPER DETECTED at Record #${o.corrupted_at_index}</span>
</div>
`:`
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.verifyAuditChain()">
\uD83D\uDEE1️ Verify Cryptographic Hash Chain
</button>
`,x=i.map((w)=>`<option value="${_(w)}" ${n.serverId===w?"selected":""}>${_(w)}</option>`).join(""),A=`
<div class="bento-card" style="padding: 14px 16px; margin-bottom: 16px; background: rgba(18, 24, 38, 0.7); border: 1px solid var(--border);">
<div style="display: grid; grid-template-columns: 2fr 1fr 1.2fr 1.2fr auto auto; gap: 10px; align-items: center;">
<!-- Full-text search input -->
<div style="position: relative;">
<input
type="text"
id="audit-search-input"
class="form-input"
style="width: 100%; padding-left: 28px; font-size: 12px; height: 32px;"
placeholder="Search trace, actor, capability, hash, error..."
value="${_(n.search)}"
oninput="window.app.handleAuditSearchInput(this.value)"
/>
<span style="position: absolute; left: 8px; top: 7px; font-size: 12px; color: var(--text-dim);">\uD83D\uDD0D</span>
</div>
<!-- Status Filter -->
<div>
<select
class="form-input"
style="width: 100%; font-size: 12px; height: 32px;"
onchange="window.app.handleAuditStatusFilter(this.value)"
>
<option value="all" ${n.status==="all"?"selected":""}>All Statuses</option>
<option value="success" ${n.status==="success"?"selected":""}>\uD83D\uDFE2 Success</option>
<option value="denied" ${n.status==="denied"?"selected":""}>\uD83D\uDD34 Denied</option>
<option value="intercepted" ${n.status==="intercepted"?"selected":""}>\uD83D\uDFE1 HITL Intercept</option>
<option value="failed" ${n.status==="failed"?"selected":""}>❌ Failed</option>
<option value="cancelled" ${n.status==="cancelled"?"selected":""}>⚪ Cancelled</option>
</select>
</div>
<!-- Event Type Filter -->
<div>
<select
class="form-input"
style="width: 100%; font-size: 12px; height: 32px;"
onchange="window.app.handleAuditEventTypeFilter(this.value)"
>
<option value="all" ${n.eventType==="all"?"selected":""}>All Event Types</option>
<option value="tool_execution" ${n.eventType==="tool_execution"?"selected":""}>Tool Execution</option>
<option value="tool_intercepted_hitl" ${n.eventType==="tool_intercepted_hitl"?"selected":""}>HITL Intercept</option>
<option value="approval_granted" ${n.eventType==="approval_granted"?"selected":""}>Approval Granted</option>
<option value="approval_rejected" ${n.eventType==="approval_rejected"?"selected":""}>Approval Rejected</option>
<option value="approval_expired" ${n.eventType==="approval_expired"?"selected":""}>Approval Expired</option>
<option value="policy_violation" ${n.eventType==="policy_violation"?"selected":""}>Policy Violation</option>
<option value="config_mutation" ${n.eventType==="config_mutation"?"selected":""}>Config Mutation</option>
<option value="sampling_call" ${n.eventType==="sampling_call"?"selected":""}>Sampling Call</option>
<option value="resource_access" ${n.eventType==="resource_access"?"selected":""}>Resource Access</option>
</select>
</div>
<!-- Server Filter -->
<div>
<select
class="form-input"
style="width: 100%; font-size: 12px; height: 32px;"
onchange="window.app.handleAuditServerFilter(this.value)"
>
<option value="all" ${n.serverId==="all"?"selected":""}>All MCP Servers</option>
${x}
</select>
</div>
<!-- Clear Filters Button -->
<div>
<button
class="btn btn-ghost"
style="padding: 6px 12px; font-size: 11.5px; height: 32px;"
onclick="window.app.clearAuditFilters()"
title="Reset all search queries and filters"
>
✕ Reset
</button>
</div>
</div>
</div>
`,R=`
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px 16px; background: rgba(18, 24, 38, 0.5); border-radius: var(--radius-md); border: 1px solid var(--border); margin-top: 16px;">
<div style="font-size: 12px; color: var(--text-dim); display: flex; align-items: center; gap: 8px;">
<span>Showing <strong style="color: var(--text-main);">${m}–${d}</strong> of <strong style="color: var(--text-main);">${r}</strong> events</span>
<span style="color: var(--border);">|</span>
<span>Page Size:</span>
<select
class="form-input"
style="font-size: 11.5px; padding: 2px 24px 2px 8px; height: 28px; width: auto;"
onchange="window.app.handleAuditPageSize(this.value)"
>
<option value="10" ${l===10?"selected":""}>10 / page</option>
<option value="25" ${l===25?"selected":""}>25 / page</option>
<option value="50" ${l===50?"selected":""}>50 / page</option>
<option value="100" ${l===100?"selected":""}>100 / page</option>
</select>
</div>
<div style="display: flex; align-items: center; gap: 6px;">
<button
class="btn btn-ghost"
style="padding: 4px 8px; font-size: 11px; height: 28px;"
${p<=1?'disabled style="opacity: 0.4; cursor: not-allowed;"':""}
onclick="window.app.auditGoToPage(1)"
title="First Page"
>
⏮ First
</button>
<button
class="btn btn-ghost"
style="padding: 4px 8px; font-size: 11px; height: 28px;"
${p<=1?'disabled style="opacity: 0.4; cursor: not-allowed;"':""}
onclick="window.app.auditPrevPage()"
>
◀ Prev
</button>
<span style="font-size: 12px; font-weight: 600; color: var(--text-main); padding: 0 8px;">
Page ${p} of ${g}
</span>
<button
class="btn btn-ghost"
style="padding: 4px 8px; font-size: 11px; height: 28px;"
${p>=g?'disabled style="opacity: 0.4; cursor: not-allowed;"':""}
onclick="window.app.auditNextPage()"
>
Next ▶
</button>
<button
class="btn btn-ghost"
style="padding: 4px 8px; font-size: 11px; height: 28px;"
${p>=g?'disabled style="opacity: 0.4; cursor: not-allowed;"':""}
onclick="window.app.auditGoToPage(${g})"
title="Last Page"
>
Last ⏭
</button>
</div>
</div>
`,T="";if(t.length===0)T=`
<div style="padding: 48px 24px; text-align: center; color: var(--text-dim); background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="font-size: 28px; margin-bottom: 8px;">\uD83D\uDD0D</div>
<div style="font-size: 14px; font-weight: 600; color: var(--text-main); margin-bottom: 4px;">No Matching Audit Events</div>
<div style="font-size: 12px; max-width: 420px; margin: 0 auto;">No audit records match your currently selected filters. Try broadening your search or resetting filters.</div>
<button class="btn btn-ghost" style="margin-top: 14px; font-size: 11.5px;" onclick="window.app.clearAuditFilters()">Reset Filters</button>
</div>
`;else T=t.map((w)=>{let z=new Date(Math.floor(w.timestamp_ns/1e6)).toLocaleString(),L='<span class="badge" style="background: rgba(34, 197, 94, 0.15); color: var(--green-400); font-weight: 600;">SUCCESS</span>';if(w.status==="denied")L='<span class="badge" style="background: rgba(239, 68, 68, 0.15); color: var(--red-400); font-weight: 600;">DENIED</span>';else if(w.status==="intercepted")L='<span class="badge" style="background: rgba(234, 179, 8, 0.15); color: var(--amber-300); font-weight: 600;">HITL INTERCEPT</span>';else if(w.status==="failed")L='<span class="badge" style="background: rgba(239, 68, 68, 0.15); color: var(--red-400); font-weight: 600;">FAILED</span>';else if(w.status==="cancelled")L='<span class="badge" style="background: rgba(148, 163, 184, 0.15); color: var(--text-muted); font-weight: 600;">CANCELLED</span>';let N=w.sanitized_args?JSON.stringify(w.sanitized_args):"-",D=w.actor_id||w.operator_id||"anonymous",q=w.server_id||"system",U=w.capability_id||w.event_type,V=w.execution_latency_us?`${(w.execution_latency_us/1000).toFixed(1)}ms`:"-";return`
<div class="bento-card" style="margin-bottom: 12px; padding: 16px; border: 1px solid var(--border); transition: border-color 0.15s ease;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-family: var(--ff-mono); font-size: 11px; font-weight: 700; color: var(--text-dim);">${_(w.id)}</span>
${L}
<span style="font-size: 12px; font-weight: 600; color: var(--text-main);">${_(U)}</span>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
<div style="font-family: var(--ff-mono); font-size: 11px; color: var(--text-muted);">${_(z)}</div>
<button
class="btn btn-ghost"
style="padding: 2px 8px; font-size: 11px; height: 24px;"
onclick="window.app.selectAuditEvent('${_(w.id)}')"
title="Inspect event details & cryptographic payload"
>
Inspect \uD83D\uDD0D
</button>
</div>
</div>
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; font-size: 11.5px; background: rgba(0,0,0,0.2); padding: 8px 12px; border-radius: var(--radius-sm); margin-bottom: 8px;">
<div><span style="color: var(--text-muted);">Actor:</span> <strong style="color: var(--text-main);">${_(D)}</strong></div>
<div><span style="color: var(--text-muted);">Server:</span> <strong style="color: var(--cyan-400);">${_(q)}</strong></div>
<div><span style="color: var(--text-muted);">Trace:</span> <code style="color: var(--cyan-400); font-size: 10.5px;">${_(w.trace_id)}</code></div>
<div><span style="color: var(--text-muted);">Latency:</span> <span style="color: var(--amber-300);">${V}</span></div>
</div>
<div style="font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim); margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<span style="color: var(--text-muted);">Args:</span> ${_(N)}
</div>
<div style="display: flex; justify-content: space-between; align-items: center; border-top: 1px solid var(--border); padding-top: 6px; font-size: 10.5px; font-family: var(--ff-mono); color: var(--text-muted);">
<div><span style="color: var(--text-dim);">prev_hash:</span> ${_(w.prev_hash.slice(0,16))}...</div>
<div><span style="color: var(--text-dim);">hash:</span> <span style="color: var(--green-400);">${_(w.hash.slice(0,16))}...</span></div>
</div>
</div>
`}).join("");let k="";if(s){let w=new Date(Math.floor(s.timestamp_ns/1e6)).toISOString();k=`
<div style="position: fixed; inset: 0; background: rgba(0,0,0,0.7); backdrop-filter: blur(4px); z-index: 9999; display: flex; align-items: center; justify-content: center; padding: 24px;" onclick="if (event.target === this) window.app.selectAuditEvent(null)">
<div class="bento-card" style="width: 100%; max-width: 720px; max-height: 85vh; display: flex; flex-direction: column; overflow: hidden; background: #0f172a; border: 1px solid var(--border); box-shadow: 0 20px 40px rgba(0,0,0,0.5);">
<!-- Modal Header -->
<div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; border-bottom: 1px solid var(--border);">
<div style="display: flex; align-items: center; gap: 8px;">
<span style="font-size: 16px;">\uD83D\uDD12</span>
<h2 style="font-size: 15px; font-weight: 700; color: var(--text-main); margin: 0;">Audit Event Details (${_(s.id)})</h2>
</div>
<button class="btn btn-ghost" style="padding: 4px 8px; font-size: 14px;" onclick="window.app.selectAuditEvent(null)">✕</button>
</div>
<!-- Modal Body -->
<div style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; font-size: 12px;">
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; background: rgba(0,0,0,0.25); padding: 12px; border-radius: var(--radius-sm);">
<div><span style="color: var(--text-muted);">Timestamp:</span> <strong style="color: var(--text-main); font-family: var(--ff-mono); font-size: 11px;">${_(w)}</strong></div>
<div><span style="color: var(--text-muted);">Status:</span> <strong style="color: var(--text-main);">${_(s.status.toUpperCase())}</strong></div>
<div><span style="color: var(--text-muted);">Event Type:</span> <strong style="color: var(--text-main);">${_(s.event_type)}</strong></div>
<div><span style="color: var(--text-muted);">Server:</span> <strong style="color: var(--cyan-400);">${_(s.server_id||"system")}</strong></div>
<div><span style="color: var(--text-muted);">Capability:</span> <strong style="color: var(--text-main);">${_(s.capability_id||"-")}</strong></div>
<div><span style="color: var(--text-muted);">Actor / Operator:</span> <strong style="color: var(--text-main);">${_(s.actor_id||s.operator_id||"anonymous")}</strong></div>
<div><span style="color: var(--text-muted);">Trace ID:</span> <code style="color: var(--cyan-400);">${_(s.trace_id)}</code></div>
<div><span style="color: var(--text-muted);">Request ID:</span> <code style="color: var(--cyan-400);">${_(s.request_id||"-")}</code></div>
<div><span style="color: var(--text-muted);">Client IP:</span> <span style="color: var(--text-main);">${_(s.client_ip||"-")}</span></div>
<div><span style="color: var(--text-muted);">Latency:</span> <span style="color: var(--amber-300);">${s.execution_latency_us?`${(s.execution_latency_us/1000).toFixed(2)} ms`:"-"}</span></div>
</div>
${s.error_message?`
<div style="background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: var(--radius-sm); padding: 10px 12px; color: var(--red-400);">
<div style="font-weight: 700; margin-bottom: 2px;">Error (${_(s.error_code||"ERROR")}):</div>
<div style="font-family: var(--ff-mono); font-size: 11px;">${_(s.error_message)}</div>
</div>
`:""}
<!-- Sanitized Arguments -->
<div>
<div style="font-weight: 600; color: var(--text-main); margin-bottom: 4px;">Sanitized Arguments</div>
<pre style="background: rgba(0,0,0,0.4); padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); font-family: var(--ff-mono); font-size: 11px; max-height: 140px; overflow: auto; margin: 0; color: #cbd5e1;">${_(JSON.stringify(s.sanitized_args||{},null,2))}</pre>
</div>
<!-- Sanitized Response -->
${s.sanitized_response?`
<div>
<div style="font-weight: 600; color: var(--text-main); margin-bottom: 4px;">Sanitized Response</div>
<pre style="background: rgba(0,0,0,0.4); padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border); font-family: var(--ff-mono); font-size: 11px; max-height: 140px; overflow: auto; margin: 0; color: #cbd5e1;">${_(JSON.stringify(s.sanitized_response,null,2))}</pre>
</div>
`:""}
<!-- Cryptographic Hashes -->
<div style="background: rgba(0,0,0,0.3); padding: 12px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div style="font-weight: 600; color: var(--text-main); margin-bottom: 6px;">Tamper-Evidence Cryptographic Hashes</div>
<div style="margin-bottom: 6px;">
<span style="color: var(--text-muted); font-size: 10.5px;">Previous Chain Hash (prev_hash):</span>
<div style="font-family: var(--ff-mono); font-size: 10.5px; color: var(--text-dim); word-break: break-all;">${_(s.prev_hash)}</div>
</div>
<div>
<span style="color: var(--text-muted); font-size: 10.5px;">Record Hash Signature (hash):</span>
<div style="font-family: var(--ff-mono); font-size: 10.5px; color: var(--green-400); word-break: break-all;">${_(s.hash)}</div>
</div>
</div>
</div>
<!-- Modal Footer -->
<div style="padding: 12px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end;">
<button class="btn btn-primary" style="font-size: 12px;" onclick="window.app.selectAuditEvent(null)">Close</button>
</div>
</div>
</div>
`}return`
<!-- Sub-header & Actions -->
<div style="margin-bottom: 18px; display: flex; justify-content: space-between; align-items: center;">
<div style="font-size: 12px; color: var(--text-dim);">
Cryptographically tamper-evident, append-only execution log for SOC2 & ISO 27001 compliance.
</div>
<div style="display: flex; gap: 8px; align-items: center;">
${v}
<a href="${y}" download class="btn btn-ghost" style="font-size: 11.5px; text-decoration: none;" title="Export current filtered view as CSV">\uD83D\uDCE5 Export CSV</a>
<a href="${b}" download class="btn btn-ghost" style="font-size: 11.5px; text-decoration: none;" title="Export current filtered view as JSONL">\uD83D\uDCE5 Export JSONL</a>
<button class="btn btn-primary" style="font-size: 11.5px;" onclick="window.app.refreshAuditEvents()">\uD83D\uDD04 Refresh</button>
</div>
</div>
<!-- Stats summary cards -->
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 20px;">
<div class="bento-card" style="padding: 14px; text-align: center;">
<div style="font-size: 11px; color: var(--text-muted); text-transform: uppercase; font-weight: 600;">Total Events</div>
<div style="font-size: 22px; font-weight: 800; color: var(--text-main); margin-top: 4px;">${a.total_events}</div>
</div>
<div class="bento-card" style="padding: 14px; text-align: center;">
<div style="font-size: 11px; color: var(--text-muted); text-transform: uppercase; font-weight: 600;">Successful Calls</div>
<div style="font-size: 22px; font-weight: 800; color: var(--green-400); margin-top: 4px;">${a.by_status.success}</div>
</div>
<div class="bento-card" style="padding: 14px; text-align: center;">
<div style="font-size: 11px; color: var(--text-muted); text-transform: uppercase; font-weight: 600;">HITL Intercepts</div>
<div style="font-size: 22px; font-weight: 800; color: var(--amber-300); margin-top: 4px;">${a.by_status.intercepted}</div>
</div>
<div class="bento-card" style="padding: 14px; text-align: center;">
<div style="font-size: 11px; color: var(--text-muted); text-transform: uppercase; font-weight: 600;">Policy Denials</div>
<div style="font-size: 22px; font-weight: 800; color: var(--red-400); margin-top: 4px;">${a.by_status.denied}</div>
</div>
</div>
<!-- Search & Filter Toolbar -->
${A}
<!-- Event Timeline List Header -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h2 style="font-size: 14px; font-weight: 600; color: var(--text-main);">Sequential Audit Ledger (SHA-256 Hash Chained)</h2>
<span style="font-size: 11.5px; color: var(--text-dim);">${t.length} events loaded on this page</span>
</div>
<!-- Event Rows -->
<div>
${T}
</div>
<!-- Pagination Footer -->
${r>0?R:""}
<!-- Modal Popup for Event Inspection -->
${k}
`}function _(e){if(!e)return"";return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function pe(){let e=c.getState(),t=e.activeProfile,a=t?e.config.profiles?.[t]:void 0,o=!!a,n=e.config.policy||{},r=a?.policy,s=o?r||{}:n,i=s.allow||[],l=s.deny||[],u=s.redact_keys||s.redactKeys||[],p=s.require_approval||s.requireApproval||[],g=i.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">${o?"No profile allow list (inherits global rules)":"No allow list (all non-denied operations permitted)"}</div>
`:i.map((k,w)=>`
<div style="display: flex; justify-content: space-between; align-items: center; background: var(--surface); padding: 8px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<span style="font-family: var(--ff-mono); font-size: 12px; color: var(--green-400);">✔ ${j(k)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('allow', ${w})">✕</button>
</div>
`).join(""),m=l.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">${o?"No profile deny rules configured":"No deny rules configured"}</div>
`:l.map((k,w)=>`
<div style="display: flex; justify-content: space-between; align-items: center; background: var(--surface); padding: 8px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<span style="font-family: var(--ff-mono); font-size: 12px; color: var(--red-400);">✖ ${j(k)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('deny', ${w})">✕</button>
</div>
`).join(""),d=p.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">${o?"No profile human-in-the-loop triggers configured":"No human-in-the-loop approval rules configured"}</div>
`:p.map((k,w)=>`
<div style="display: flex; justify-content: space-between; align-items: center; background: var(--surface); padding: 8px 12px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<span style="font-family: var(--ff-mono); font-size: 12px; color: var(--amber-400);">\uD83D\uDEE1️ ${j(k)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('requireApproval', ${w})">✕</button>
</div>
`).join(""),y=u.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">${o?"No profile key redaction patterns configured":"No key redaction patterns configured"}</div>
`:u.map((k,w)=>`
<span class="brand-badge" style="color: var(--amber-300); padding: 5px 10px; font-size: 11px; display: inline-flex; align-items: center; gap: 6px;">
${j(k)}
<span style="cursor: pointer; color: var(--red-400); font-weight: bold;" onclick="window.app.removePolicyRule('redact', ${w})">✕</span>
</span>
`).join(""),b=o?`
<div class="bento-card" style="margin-bottom: 16px; background: rgba(245, 158, 11, 0.05); border: 1px solid rgba(245, 158, 11, 0.3); display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; align-items: center; gap: 10px;">
<span style="font-size: 18px;">\uD83D\uDEE1️</span>
<div>
<div style="font-size: 13px; font-weight: 700; color: var(--amber-400);">
Viewing & Editing Policy for Profile Constellation: <code style="font-size: 13px; color: var(--text-main);">${j(t)}</code>
</div>
<div style="font-size: 11.5px; color: var(--text-muted); margin-top: 2px;">
Rules defined here apply specifically when requests target this profile. Deny and HITL rules are strictly additive with global rules.
</div>
</div>
</div>
<button class="btn btn-ghost" style="font-size: 11px; padding: 4px 10px;" onclick="window.app.setActiveProfile(null)">Switch to Global Policy</button>
</div>
`:`
<div style="margin-bottom: 16px; font-size: 12px; color: var(--text-dim);">
Global security policy rules governing wildcard access control, human-in-the-loop triggers, and sensitive key masking. (Select an active profile in the top bar to edit per-profile rules).
</div>
`,v=Object.keys(e.config.mcpServers||{}),x=a?.servers||[],A=o?v.filter((k)=>!x.includes(k)):[],R=o?`
<div class="bento-card" style="margin-bottom: 16px; border: 1px solid rgba(245, 158, 11, 0.2); background: rgba(0, 0, 0, 0.2);">
<div class="stat-header" style="display: flex; justify-content: space-between; align-items: center;">
<span class="stat-label" style="color: var(--amber-400);">Constellation Server Boundaries (Profile: ${j(t)})</span>
<span style="font-size: 11px; color: var(--text-dim);">${x.length} of ${v.length} servers active</span>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 10px;">
<div style="background: var(--surface); padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div style="font-size: 11px; font-weight: 600; color: var(--green-400); text-transform: uppercase; margin-bottom: 6px;">
✔ Included Servers (${x.length})
</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px;">
${x.length>0?x.map((k)=>`
<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.25); background: rgba(34, 211, 238, 0.05);">
${j(k)}
</span>
`).join(""):'<span style="font-size: 11px; color: var(--text-dim);">No servers included</span>'}
</div>
</div>
<div style="background: var(--surface); padding: 10px; border-radius: var(--radius-sm); border: 1px solid var(--border);">
<div style="font-size: 11px; font-weight: 600; color: var(--amber-400); text-transform: uppercase; margin-bottom: 6px;">
\uD83D\uDEAB Excluded Servers (${A.length}) · Implicitly Denied
</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px; align-items: center;">
${A.length>0?A.map((k)=>`
<span class="brand-badge" style="color: var(--text-muted); border-color: rgba(245, 158, 11, 0.2); background: rgba(245, 158, 11, 0.04); display: inline-flex; align-items: center; gap: 4px;">
${j(k)}
<button style="background: none; border: none; color: var(--amber-400); font-size: 10px; cursor: pointer; padding: 0 2px;" title="Include in profile" onclick="window.app.toggleServerInProfile('${j(t)}', '${j(k)}', true)">+</button>
</span>
`).join(""):'<span style="font-size: 11px; color: var(--text-dim);">All servers included in constellation</span>'}
</div>
</div>
</div>
</div>
`:"",T=o&&A.length>0?`
<div style="border-top: 1px dashed var(--border); padding-top: 8px; margin-top: 8px;">
<div style="font-size: 10.5px; color: var(--text-dim); text-transform: uppercase; font-weight: 600; margin-bottom: 6px;">
Implicit Boundary Denials (${A.length})
</div>
<div style="display: flex; flex-direction: column; gap: 4px;">
${A.map((k)=>`
<div style="display: flex; justify-content: space-between; align-items: center; background: rgba(245, 158, 11, 0.03); padding: 5px 8px; border-radius: var(--radius-xs); border: 1px dashed rgba(245, 158, 11, 0.2);">
<span style="font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim);">✖ ${j(k)}.*</span>
<span style="font-size: 9.5px; color: var(--amber-400); font-family: var(--ff-mono);">server excluded</span>
</div>
`).join("")}
</div>
</div>
`:"";return`
${b}
${R}
<div class="bento-grid">
<!-- Allow Rules -->
<div class="bento-card col-4">
<div class="stat-header">
<span class="stat-label" style="color: var(--green-400);">Allow List Patterns</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin: 12px 0;">
${g}
</div>
<div style="display: flex; gap: 8px; margin-top: 14px;">
<input type="text" class="form-input" id="policy-new-allow" placeholder="e.g. github.*, db.read_*" onkeydown="if(event.key==='Enter') window.app.submitPolicyRule('allow')">
<button class="btn btn-ghost" onclick="window.app.submitPolicyRule('allow')">Add Allow</button>
</div>
</div>
<!-- Deny Rules -->
<div class="bento-card col-4">
<div class="stat-header">
<span class="stat-label" style="color: var(--red-400);">Deny List Patterns (Strict Precedence)</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin: 12px 0;">
${m}
${T}
</div>
<div style="display: flex; gap: 8px; margin-top: 14px;">
<input type="text" class="form-input" id="policy-new-deny" placeholder="e.g. *.drop_*, filesystem.write_*" onkeydown="if(event.key==='Enter') window.app.submitPolicyRule('deny')">
<button class="btn btn-ghost" onclick="window.app.submitPolicyRule('deny')">Add Deny</button>
</div>
</div>
<!-- Require Approval Rules -->
<div class="bento-card col-4">
<div class="stat-header">
<span class="stat-label" style="color: var(--amber-400);">Human-in-the-Loop Triggers</span>
</div>
<div style="display: flex; flex-direction: column; gap: 8px; margin: 12px 0;">
${d}
</div>
<div style="display: flex; gap: 8px; margin-top: 14px;">
<input type="text" class="form-input" id="policy-new-requireApproval" placeholder="e.g. docker.run*, db.write*" onkeydown="if(event.key==='Enter') window.app.submitPolicyRule('requireApproval')">
<button class="btn btn-ghost" onclick="window.app.submitPolicyRule('requireApproval')">Add Approval</button>
</div>
</div>
<!-- Key Redaction -->
<div class="bento-card col-12">
<div class="stat-header">
<span class="stat-label" style="color: var(--amber-300);">Sensitive Key Redaction Patterns</span>
<span style="font-size: 11px; color: var(--text-dim); margin-left: 8px;">Keys automatically masked as <redacted> in logs and envelopes</span>
</div>
<div style="display: flex; gap: 8px; flex-wrap: wrap; margin: 12px 0;">
${y}
</div>
<div style="display: flex; gap: 8px; margin-top: 14px; max-width: 420px;">
<input type="text" class="form-input" id="policy-new-redact" placeholder="e.g. token, api_key, password, secret" onkeydown="if(event.key==='Enter') window.app.submitPolicyRule('redact')">
<button class="btn btn-ghost" onclick="window.app.submitPolicyRule('redact')">Add Key</button>
</div>
</div>
<!-- Webhooks & ChatOps Alerts -->
<div class="bento-card col-12" style="border-color: rgba(59, 130, 246, 0.3);">
<div class="stat-header" style="display: flex; justify-content: space-between; align-items: center;">
<div>
<span class="stat-label" style="color: var(--cyan-400);">⚡ ChatOps & Outbound Webhooks</span>
<span style="font-size: 11px; color: var(--text-dim); margin-left: 8px;">Push actionable HITL approval cards and alerts directly to Slack, Discord, or Teams</span>
</div>
<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.3);">Bidirectional</span>
</div>
<div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 14px;">
<div>
<label class="form-label" style="font-size: 11px;">Target Webhook URL</label>
<input type="text" class="form-input" id="policy-webhook-url" placeholder="https://hooks.slack.com/services/... or Discord webhook URL" value="${j(typeof s.webhook==="object"&&s.webhook?s.webhook.url||"":"")}">
</div>
<div>
<label class="form-label" style="font-size: 11px;">Payload Layout Format</label>
<select class="form-input" id="policy-webhook-format">
<option value="slack" ${typeof s.webhook==="object"&&s.webhook?.format==="slack"?"selected":""}>Slack Block Kit (Interactive)</option>
<option value="discord" ${typeof s.webhook==="object"&&s.webhook?.format==="discord"?"selected":""}>Discord Embed & Actions</option>
<option value="teams" ${typeof s.webhook==="object"&&s.webhook?.format==="teams"?"selected":""}>Microsoft Teams Adaptive Cards</option>
<option value="generic" ${typeof s.webhook==="object"&&s.webhook?.format==="generic"||!s.webhook?"selected":""}>Generic JSON (Standard)</option>
</select>
</div>
<div>
<label class="form-label" style="font-size: 11px;">HMAC Secret (or Env Var)</label>
<input type="text" class="form-input" id="policy-webhook-secret" placeholder="e.g. WARMPLANE_WEBHOOK_SECRET" value="${j(typeof s.webhook==="object"&&s.webhook?s.webhook.secret_env||s.webhook.secretEnv||s.webhook.secret||"":"")}">
</div>
</div>
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border-subtle);">
<div style="display: flex; gap: 8px; align-items: center;">
<button class="btn btn-primary" onclick="window.app.saveWebhookConfig()">Save Webhook Settings</button>
<button class="btn btn-ghost" onclick="window.app.testWebhook()">⚡ Send Test Event</button>
</div>
<div id="policy-webhook-status" style="font-size: 11px; font-family: var(--ff-mono); color: var(--text-dim);">
${typeof s.webhook==="object"&&s.webhook?.url?`Active Target: ${j(s.webhook.url)}`:"No webhook configured"}
</div>
</div>
</div>
<!-- Evaluation Sandbox Tester -->
<div class="bento-card col-12">
<div class="stat-header">
<span class="stat-label">Policy Evaluation Sandbox</span>
<span style="font-size: 11px; color: var(--text-dim); margin-left: 8px;">Live verification of capability access against active rules</span>
</div>
<div style="display: flex; gap: 12px; align-items: center; margin-top: 12px;">
<input type="text" class="form-input" placeholder="Type capability identifier, e.g. github.create_issue or sqlite.drop_table" style="flex: 1;" oninput="window.app.testPolicySandbox(this.value)">
<div id="policy-test-verdict" style="font-weight: 700; font-family: var(--ff-mono); font-size: 12.5px; padding: 7px 16px; border-radius: var(--radius-sm); background: var(--surface); border: 1px solid var(--border); color: var(--green-400);">
ALLOWED
</div>
</div>
</div>
</div>
`}function j(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function ue(){let e=c.getState(),t=e.config,a=Object.entries(t.capabilityAliases||{}),o=Object.entries(t.resourceAliases||{}),n=Object.entries(t.promptAliases||{}),r="";if(a.length===0&&o.length===0&&n.length===0)r=`
<div style="padding: 24px; text-align: center; color: var(--text-dim);">
No facade aliases configured in ${P(e.configPath)}. Add short names or custom descriptions to prune token payload sizes.
</div>
`;else{for(let[s,i]of a){let l=typeof i==="string"?i:i.target,u=typeof i==="object"&&i.summary?i.summary:"",p=typeof i==="object"&&!!i.passthrough,g=p?'<span class="brand-badge" style="color: var(--amber-400); border-color: rgba(245, 158, 11, 0.4); background: rgba(245, 158, 11, 0.12); margin-left: 8px; font-size: 10px; padding: 1px 7px; flex-shrink: 0; white-space: nowrap; display: inline-flex; align-items: center; gap: 3px;">⚡ passthrough</span>':"",m=u?`<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">\uD83D\uDCAC ${P(u)}</div>`:"";r+=`
<div class="feed-row" style="grid-template-columns: 80px 240px 1fr 70px; cursor: pointer; transition: background 0.15s;" onclick="window.app.startEditAlias('tool', '${P(s)}', '${P(l)}', '${P(u)}', ${p})" title="Click to edit alias">
<span style="color: var(--cyan-400);">Tool</span>
<div style="display: flex; align-items: center; min-width: 0;">
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${P(s)}</span>
${g}
</div>
<div>
<span style="color: var(--text-muted); font-family: var(--ff-mono); word-break: break-all;">${P(l)}</span>
${m}
</div>
<div style="text-align: right;" onclick="event.stopPropagation()">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('tool', '${P(s)}')">✕</button>
</div>
</div>
`}for(let[s,i]of o){let l=typeof i==="string"?i:i.target,u=typeof i==="object"&&i.summary?i.summary:"",p=u?`<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">\uD83D\uDCAC ${P(u)}</div>`:"";r+=`
<div class="feed-row" style="grid-template-columns: 80px 240px 1fr 70px; cursor: pointer; transition: background 0.15s;" onclick="window.app.startEditAlias('resource', '${P(s)}', '${P(l)}', '${P(u)}', false)" title="Click to edit alias">
<span style="color: var(--green-400);">Resource</span>
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${P(s)}</span>
<div>
<span style="color: var(--text-muted); font-family: var(--ff-mono); word-break: break-all;">${P(l)}</span>
${p}
</div>
<div style="text-align: right;" onclick="event.stopPropagation()">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('resource', '${P(s)}')">✕</button>
</div>
</div>
`}for(let[s,i]of n){let l=typeof i==="string"?i:i.target,u=typeof i==="object"&&i.summary?i.summary:"",p=u?`<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">\uD83D\uDCAC ${P(u)}</div>`:"";r+=`
<div class="feed-row" style="grid-template-columns: 80px 240px 1fr 70px; cursor: pointer; transition: background 0.15s;" onclick="window.app.startEditAlias('prompt', '${P(s)}', '${P(l)}', '${P(u)}', false)" title="Click to edit alias">
<span style="color: var(--amber-300);">Prompt</span>
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${P(s)}</span>
<div>
<span style="color: var(--text-muted); font-family: var(--ff-mono); word-break: break-all;">${P(l)}</span>
${p}
</div>
<div style="text-align: right;" onclick="event.stopPropagation()">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('prompt', '${P(s)}')">✕</button>
</div>
</div>
`}}return`
<!-- Sub-header -->
<div style="margin-bottom: 16px; font-size: 12px; color: var(--text-dim);">
Shorten capability IDs and supply custom descriptions to prune prompt tokens. Enable <b>Native Passthrough</b> for tools to expose them directly in <code style="color: var(--amber-300);">tools/list</code> with their native schema. Click any alias row to edit.
</div>
<!-- Quick Add / Edit Form -->
<div class="bento-card" style="margin-bottom: 20px; overflow: visible;">
<div class="stat-header" style="margin-bottom: 12px; display: flex; justify-content: space-between; align-items: center;">
<span class="stat-label" id="alias-form-title">Create New Alias</span>
<button id="alias-cancel-btn" class="btn btn-ghost" style="display: none; padding: 2px 8px; font-size: 11px; color: var(--text-dim);" onclick="window.app.resetAliasForm()">Cancel Edit</button>
</div>
<div style="display: grid; grid-template-columns: 140px 1fr 1fr 100px; gap: 10px; align-items: center; position: relative; margin-bottom: 8px;">
<select class="form-input" id="alias-kind" onchange="const cb = document.getElementById('alias-passthrough-container'); if (cb) cb.style.display = this.value === 'tool' ? 'flex' : 'none';">
<option value="tool">Tool / Capability</option>
<option value="resource">Resource</option>
<option value="prompt">Prompt</option>
</select>
<input type="text" class="form-input" id="alias-name" placeholder="Public alias (e.g. search)" onkeydown="if(event.key==='Enter') window.app.createAlias()">
<div style="position: relative; width: 100%;">
<input type="text" class="form-input" id="alias-target" autocomplete="off" placeholder="Target ID (e.g. semble.search)" style="width: 100%;" oninput="window.app.handleAliasTargetInput(this.value)" onkeydown="if(event.key==='Enter') window.app.createAlias()" onfocus="window.app.handleAliasTargetInput(this.value)" onblur="setTimeout(() => window.app.hideAliasDropdown(), 200)">
<div id="alias-suggestions-dropdown" style="display: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; max-height: 240px; overflow-y: auto; background: var(--surface-elevated); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: 0 8px 24px rgba(0,0,0,0.4); z-index: 1000; font-family: var(--ff-mono); font-size: 11.5px;"></div>
</div>
<button id="alias-save-btn" class="btn btn-primary" onclick="window.app.createAlias()">+ Save</button>
</div>
<div style="display: grid; grid-template-columns: 1fr auto; gap: 12px; align-items: center;">
<input type="text" class="form-input" id="alias-summary" placeholder="Optional custom description / prompt instruction (e.g. Fast hybrid code search)" onkeydown="if(event.key==='Enter') window.app.createAlias()" style="font-size: 12px;">
<label id="alias-passthrough-container" style="display: flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text-main); cursor: pointer; user-select: none; white-space: nowrap;">
<input type="checkbox" id="alias-passthrough" style="accent-color: var(--amber-400); cursor: pointer;">
<span>⚡ Native Passthrough Tool</span>
</label>
</div>
</div>
<!-- Aliases Table -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden; font-family: var(--ff-mono); font-size: 12px;">
<div style="display: grid; grid-template-columns: 80px 240px 1fr 70px; padding: 10px 14px; background: var(--surface-hover); border-bottom: 1px solid var(--border); color: var(--text-muted); font-weight: 600;">
<span>TYPE</span>
<span>PUBLIC ALIAS</span>
<span>CANONICAL TARGET & SUMMARY</span>
<span style="text-align: right;">ACTION</span>
</div>
${r}
</div>
`}function P(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function ge(){let e=c.getState(),t=e.config,a=t.profiles||{},o=Object.entries(a),n=t.mcpServers||{},r=e.activeProfile,s="";if(o.length===0)s=`
<div style="padding: 40px; text-align: center; color: var(--text-dim); background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="font-size: 15px; color: var(--text-main); font-weight: 600; margin-bottom: 8px;">No Profiles Configured</div>
<p style="font-size: 12px; margin-bottom: 20px; max-width: 480px; margin-left: auto; margin-right: auto;">
Profiles allow Warmplane to serve multiple task-relevant server constellations (e.g. <code>coding</code>, <code>support</code>, <code>data</code>) from one running daemon process.
</p>
<button class="btn btn-primary" onclick="window.app.openAddProfileModal()">+ Create First Profile</button>
</div>
`;else s=o.map(([i,l])=>{let u=r===i,p=l.servers.map((x)=>`<span class="brand-badge" style="${n[x]?"color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.25); background: rgba(34, 211, 238, 0.05);":"color: var(--red-400); border-color: rgba(248, 113, 113, 0.3); background: rgba(248, 113, 113, 0.05);"}">${Y(x)}</span>`).join(" "),g=(e.capabilities||[]).filter((x)=>l.servers.includes(x.server)).length,m=!!l.policy,d=l.policy?.allow?.length||0,y=l.policy?.deny?.length||0,b=(l.policy?.require_approval||l.policy?.requireApproval||[]).length,v=(l.policy?.redact_keys||l.policy?.redactKeys||[]).length;return`
<div class="bento-card" style="margin-bottom: 14px; border-left: ${u?"3px solid var(--amber-400)":"1px solid var(--border)"};">
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
<span style="font-size: 16px; font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">${Y(i)}</span>
${u?'<span class="brand-badge" style="color: var(--amber-400); border-color: rgba(245, 158, 11, 0.4); background: rgba(245, 158, 11, 0.1);">ACTIVE IN UI</span>':""}
<span class="brand-badge">${l.servers.length} server${l.servers.length===1?"":"s"}</span>
<span class="brand-badge" style="color: var(--text-dim);">${g} capabilities</span>
${m?'<span class="brand-badge" style="color: var(--green-400); border-color: rgba(34, 197, 94, 0.3); background: rgba(34, 197, 94, 0.08);">CUSTOM POLICY</span>':""}
</div>
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 10px;">
${Y(l.description||"No description provided")}
</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: ${m?"8px":"0"};">
<span style="font-size: 11px; color: var(--text-dim); font-weight: 600; text-transform: uppercase;">Servers:</span>
${p||'<span style="font-size: 11px; color: var(--text-dim);">None</span>'}
</div>
${m?`
<div style="display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: 11px;">
<span style="color: var(--text-dim); font-weight: 600; text-transform: uppercase;">Policy Overlay:</span>
${d>0?`<span class="brand-badge" style="color: var(--green-400);">Allow: ${d}</span>`:""}
${y>0?`<span class="brand-badge" style="color: var(--red-400);">Deny: ${y}</span>`:""}
${b>0?`<span class="brand-badge" style="color: var(--amber-400);">HITL: ${b}</span>`:""}
${v>0?`<span class="brand-badge" style="color: var(--text-muted);">Redact: ${v}</span>`:""}
${d===0&&y===0&&b===0&&v===0?'<span style="color: var(--text-dim);">Configured</span>':""}
</div>
`:""}
</div>
<div style="display: flex; gap: 8px; align-items: center;">
${u?`
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.setActiveProfile(null)">
Deselect
</button>
`:`
<button class="btn btn-primary" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.setActiveProfile('${Y(i)}')">
Activate in UI
</button>
`}
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.openEditProfileModal('${Y(i)}')">
✏️ Edit
</button>
<button class="btn btn-danger" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.deleteProfile('${Y(i)}')">
Remove
</button>
</div>
</div>
</div>
`}).join("");return`
<!-- Sub-header & Actions -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<div style="font-size: 12px; color: var(--text-dim);">
Define named subsets of servers for task-specific agent interactions, dynamic per-request switching, and scoped ETag caching.
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-primary" onclick="window.app.openAddProfileModal()">+ Create Profile</button>
</div>
</div>
<!-- Quick Info Box -->
<div class="bento-card" style="margin-bottom: 18px; background: rgba(245, 158, 11, 0.03); border: 1px solid rgba(245, 158, 11, 0.15);">
<div style="display: flex; gap: 12px; align-items: center;">
<span style="font-size: 20px;">\uD83D\uDCA1</span>
<div style="font-size: 12px; color: var(--text-muted); line-height: 1.5;">
HTTP clients can select profiles dynamically using the <code style="color: var(--amber-400);">X-Warmplane-Profile: <name></code> header or <code style="color: var(--amber-400);">?profile=<name></code> query parameter. MCP stdio clients can pass <code style="color: var(--amber-400);">--profile <name></code>.
</div>
</div>
</div>
${s}
`}function Y(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function me(){let e=c.getState(),t=e.secrets||[],a=e.config.mcpServers||{},o=[];for(let[p,g]of Object.entries(a)){let m=Q(p,g.command,g.args);if(m){let d=Object.keys(g.env||{});for(let y of m.envFields)if(y.required&&!d.includes(y.key))o.push({server:p,key:y.key,uri:"(Not Configured)",is_vault:!1,exists:!1,backend:"Required Variable",display:`Required by ${m.name} template (${y.label})`,is_unconfigured_requirement:!0})}}let n=[...t,...o],r=n.length,s=n.filter((p)=>p.is_vault&&p.exists!==!1).length,i=n.filter((p)=>p.exists===!1).length,l=n.filter((p)=>!p.is_vault&&p.exists!==!1).length,u=n.length===0?`
<div style="padding: 32px; text-align: center; color: var(--text-dim);">
No environment variables or secrets configured in active servers.
</div>
`:n.map((p)=>{let g='<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);">Plaintext (Unsecured)</span>',m=p.exists===!1;if(p.is_unconfigured_requirement)g='<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);">⚠️ Not Configured (Required)</span>';else if(m)g=`<span class="brand-badge" style="color: var(--amber-300); border-color: rgba(245, 158, 11, 0.4); background: rgba(245, 158, 11, 0.1);">⚠️ Missing from ${H(p.backend)}</span>`;else if(p.is_vault)g=`<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.3); background: rgba(52, 211, 153, 0.1);">\uD83D\uDD12 ${H(p.backend)}</span>`;return`
<div style="display: grid; grid-template-columns: 140px 180px 1fr 180px auto; padding: 10px 16px; border-bottom: 1px solid var(--border-subtle); align-items: center; font-size: 12px;">
<span style="font-weight: 700; color: var(--text-main);">${H(p.server)}</span>
<span style="font-family: var(--ff-mono); color: ${m?"var(--amber-300)":"var(--amber-300)"};">${H(p.key)}</span>
<span style="font-family: var(--ff-mono); font-size: 11px; color: ${m?"var(--red-400)":"var(--text-muted)"}; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${H(p.display)}</span>
<div>${g}</div>
<div style="display: flex; gap: 6px; justify-content: flex-end;">
${p.is_unconfigured_requirement?`
<button class="btn btn-primary" style="padding: 2px 8px; font-size: 11px;" onclick="window.app.quickVaultEnv('${H(p.server)}', '${H(p.key)}')">➕ Configure in Keychain</button>
`:m?`
<button class="btn btn-primary" style="padding: 2px 8px; font-size: 11px;" onclick="window.app.quickVaultEnv('${H(p.server)}', '${H(p.key)}')">➕ Re-add Key</button>
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px; color: var(--red-400);" onclick="window.app.removeSecretFromConfig('${H(p.server)}', '${H(p.key)}')">Remove from Config</button>
`:!p.is_vault?`
<button class="btn btn-primary" style="padding: 2px 8px; font-size: 11px;" onclick="window.app.quickVaultEnv('${H(p.server)}', '${H(p.key)}')">\uD83D\uDD12 Move to Keychain</button>
`:`
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px; color: var(--red-400);" onclick="window.app.deleteVaultSecret('${H(p.key)}', '${H(p.server)}')">Delete Key</button>
`}
</div>
</div>
`}).join("");return`
<div style="margin-bottom: 16px; font-size: 12px; color: var(--text-dim);">
Manage native OS Keychain credentials (macOS Keychain, Linux Secret Service, 1Password). Secrets are injected directly in-memory at process launch and never saved to disk in plaintext.
</div>
<!-- Stat Header Cards -->
<div class="bento-grid" style="margin-bottom: 20px;">
<div class="bento-card col-4">
<div class="stat-label">Total Required & Configured</div>
<div class="stat-value" style="color: var(--cyan-400);">${r}</div>
<div class="stat-sub">Across all configured MCP servers</div>
</div>
<div class="bento-card col-4">
<div class="stat-label">Secured via Vault / Keychain</div>
<div class="stat-value" style="color: var(--green-400);">${s}</div>
<div class="stat-sub">Zero-disk plaintext exposure</div>
</div>
<div class="bento-card col-4">
<div class="stat-label">Missing or Unsecured</div>
<div class="stat-value" style="color: ${i+l>0?"var(--red-400)":"var(--green-400)"};">${i+l}</div>
<div class="stat-sub">${i>0?`${i} missing required key(s)`:l>0?"Recommend migrating to Keychain":"All credentials protected"}</div>
</div>
</div>
<!-- Action Drawer / Store New Secret -->
<div class="bento-card" style="margin-bottom: 20px; padding: 14px 18px; border-color: rgba(59, 130, 246, 0.3);">
<div style="font-size: 13.5px; font-weight: 700; color: var(--text-main); margin-bottom: 10px;">
\uD83D\uDD11 Store New Secret in OS Keychain
</div>
<div style="display: grid; grid-template-columns: 200px 1fr 140px auto; gap: 10px; align-items: center;">
<input type="text" class="form-input" id="vault-new-key" placeholder="Key identifier, e.g. github_token" style="font-size: 12px;">
<input type="password" class="form-input" id="vault-new-val" placeholder="Secret value (will be written to OS Keychain)" style="font-size: 12px;">
<input type="text" class="form-input" id="vault-new-service" placeholder="Service (warmplane)" value="warmplane" style="font-size: 12px;">
<button class="btn btn-primary" onclick="window.app.saveNewVaultSecret()">Save to Keychain</button>
</div>
</div>
<!-- Secrets Ledger Table -->
<div style="background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); overflow: hidden;">
<div style="display: grid; grid-template-columns: 140px 180px 1fr 180px auto; padding: 8px 16px; background: var(--surface-hover); border-bottom: 1px solid var(--border); color: var(--text-muted); font-size: 11px; font-weight: 600;">
<span>SERVER</span>
<span>VARIABLE KEY</span>
<span>VALUE / URI SCHEME</span>
<span>SECURITY STATUS</span>
<span style="text-align: right;">ACTION</span>
</div>
<div id="secrets-table-rows">
${u}
</div>
</div>
`}function H(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}class ve{activeTemplateCategory="all";activeTemplateFilter="";selectedTemplate=null;pendingClientId=null;async init(){let e=window.location.port?`:${window.location.port}`:"",t=document.getElementById("daemon-port-label");if(t)t.textContent=`Daemon ${e}`;await this.refreshData(),this.initSSE(),this.render(),c.subscribe(()=>{this.render()})}auditSearchTimeout=null;async refreshData(){try{let e=c.getState(),t=e.auditFilters,a=e.activeProfile||void 0,[o,n,r,s,i,l,u,p,g,m,d]=await Promise.all([h.getConfig(),h.listCapabilities(a),h.listResources(a),h.listPrompts(a),h.getCatalogEvents(),h.listApprovals(),h.listTasks(),h.listAuditEvents({server_id:t.serverId!=="all"?t.serverId:void 0,event_type:t.eventType!=="all"?t.eventType:void 0,status:t.status!=="all"?t.status:void 0,search:t.search.trim()?t.search.trim():void 0,limit:t.limit,offset:t.offset}),h.getAuditStats(),h.getClients().catch(()=>({ok:!1,clients:[]})),h.getSecrets().catch(()=>({ok:!1,secrets:[],keychain_service:"warmplane"}))]);if(m&&m.ok&&Array.isArray(m.clients))c.setState({clients:m.clients});if(d&&d.ok&&Array.isArray(d.secrets))c.setState({secrets:d.secrets});if(o.ok)c.setState({configPath:o.config_path,config:o.config,serverStatuses:o.server_statuses||{},circuitBreakers:o.circuit_breakers||[],metrics:{totalCatalogRequests:o.metrics?.total_catalog_requests||0,totalEtagHits:o.metrics?.total_etag_hits||0,totalToolCalls:o.metrics?.total_tool_calls||0,totalToolDurationUs:o.metrics?.total_tool_duration_us||0}});if(n&&Array.isArray(n.capabilities)){let y=c.getState().selectedCapabilityId,v=n.capabilities.some((x)=>x.id===y)?y:n.capabilities.length>0?n.capabilities[0].id:null;c.setState({capabilities:n.capabilities,capabilitiesHiddenByPolicy:n.hidden_by_policy||0,selectedCapabilityId:v})}if(r&&Array.isArray(r.resources)){let y=c.getState().selectedResourceId,v=r.resources.some((x)=>x.uri===y||x.id===y)?y:r.resources.length>0?r.resources[0].uri||r.resources[0].id||null:null;c.setState({resources:r.resources,resourcesHiddenByPolicy:r.hidden_by_policy||0,selectedResourceId:v})}if(s&&Array.isArray(s.prompts)){let y=c.getState().selectedPromptId,v=s.prompts.some((x)=>x.name===y||x.id===y)?y:s.prompts.length>0?s.prompts[0].name||s.prompts[0].id||null:null;c.setState({prompts:s.prompts,promptsHiddenByPolicy:s.hidden_by_policy||0,selectedPromptId:v})}if(i&&Array.isArray(i.events))c.setState({catalogEvents:i.events});if(l&&Array.isArray(l.approvals))c.setState({approvals:l.approvals});if(u&&Array.isArray(u.tasks))c.setState({tasks:u.tasks});if(p&&Array.isArray(p.events))c.setState({auditEvents:p.events,auditTotal:p.total??p.events.length});if(g&&g.ok)c.setState({auditStats:g})}catch(e){console.error("Failed to fetch daemon state:",e)}}async refreshAuditEvents(){try{let t=c.getState().auditFilters,[a,o]=await Promise.all([h.listAuditEvents({server_id:t.serverId!=="all"?t.serverId:void 0,event_type:t.eventType!=="all"?t.eventType:void 0,status:t.status!=="all"?t.status:void 0,search:t.search.trim()?t.search.trim():void 0,limit:t.limit,offset:t.offset}),h.getAuditStats()]);if(a&&Array.isArray(a.events))c.setState({auditEvents:a.events,auditTotal:a.total??a.events.length});if(o&&o.ok)c.setState({auditStats:o})}catch(e){console.error("Failed to refresh audit events:",e)}}handleAuditSearchInput(e){let a={...c.getState().auditFilters,search:e,offset:0};c.setState({auditFilters:a}),clearTimeout(this.auditSearchTimeout),this.auditSearchTimeout=setTimeout(()=>{this.refreshAuditEvents()},250)}handleAuditStatusFilter(e){let t=c.getState();c.setState({auditFilters:{...t.auditFilters,status:e,offset:0}}),this.refreshAuditEvents()}handleAuditEventTypeFilter(e){let t=c.getState();c.setState({auditFilters:{...t.auditFilters,eventType:e,offset:0}}),this.refreshAuditEvents()}handleAuditServerFilter(e){let t=c.getState();c.setState({auditFilters:{...t.auditFilters,serverId:e,offset:0}}),this.refreshAuditEvents()}handleAuditPageSize(e){let t=parseInt(e,10)||25,a=c.getState();c.setState({auditFilters:{...a.auditFilters,limit:t,offset:0}}),this.refreshAuditEvents()}clearAuditFilters(){let e=c.getState();c.setState({auditFilters:{search:"",status:"all",eventType:"all",serverId:"all",limit:e.auditFilters.limit||25,offset:0}}),this.refreshAuditEvents()}auditPrevPage(){let e=c.getState(),{limit:t,offset:a}=e.auditFilters,o=Math.max(0,a-t);if(o!==a)c.setState({auditFilters:{...e.auditFilters,offset:o}}),this.refreshAuditEvents()}auditNextPage(){let e=c.getState(),{limit:t,offset:a}=e.auditFilters,o=e.auditTotal;if(a+t<o)c.setState({auditFilters:{...e.auditFilters,offset:a+t}}),this.refreshAuditEvents()}auditGoToPage(e){let t=c.getState(),{limit:a}=t.auditFilters,o=Math.max(0,(e-1)*a);c.setState({auditFilters:{...t.auditFilters,offset:o}}),this.refreshAuditEvents()}selectAuditEvent(e){if(!e){c.setState({auditSelectedEvent:null});return}let a=c.getState().auditEvents.find((o)=>o.id===e)||null;c.setState({auditSelectedEvent:a})}async verifyAuditChain(){try{let e=await h.verifyAuditChain();if(e&&e.report)c.setState({auditVerification:e.report})}catch(e){console.error("Failed to verify audit chain:",e)}}async refreshApprovals(){try{let e=await h.listApprovals();if(e&&Array.isArray(e.approvals))c.setState({approvals:e.approvals})}catch(e){console.error("Failed to refresh approvals:",e)}}initSSE(){try{let e=new EventSource("/v1/resources/updates");e.onmessage=(t)=>{c.addEventLog("SSE","/v1/resources/updates","UPDATED","0.1ms"),this.refreshData()}}catch(e){console.warn("SSE connection unavailable")}}switchTab(e){c.setState({activeTab:e}),this.refreshData()}render(){let e=c.getState(),t=document.getElementById("app-main");if(!t)return;let a=(e.tasks||[]).filter((l)=>l.status==="input_required").length,o=(e.approvals||[]).filter((l)=>l.status==="pending").length,n=Math.max(a,o),r=document.getElementById("nav-approvals-badge");if(r)r.textContent=n>0?`${n}`:"",r.style.display=n>0?"inline-block":"none";document.querySelectorAll(".nav-item").forEach((l)=>{let u=l.getAttribute("data-tab");if(u===e.activeTab||e.activeTab==="tasks"&&u==="approvals"||e.activeTab==="approvals"&&u==="tasks")l.classList.add("active");else l.classList.remove("active")});let s=document.getElementById("top-title"),i={overview:"Overview Cockpit",servers:"Server Hub & Connections",playground:"MCP Capability Playground",tasks:"SEP-2663 Tasks & HITL Review",approvals:"SEP-2663 Tasks & HITL Review",audit:"WORM Audit & Compliance Ledger",policy:"Security Governance & Redaction",secrets:"Native OS Keychain & Secrets Vault",aliases:"Facade & Alias Studio",profiles:"Server Constellation Profiles"};if(s)s.textContent=i[e.activeTab]||"Control Deck";switch(this.renderTopProfileSelector(),e.activeTab){case"overview":t.innerHTML=oe();break;case"servers":t.innerHTML=ie();break;case"playground":t.innerHTML=le();break;case"tasks":case"approvals":t.innerHTML=de(e);break;case"audit":t.innerHTML=ce();break;case"policy":t.innerHTML=pe();break;case"secrets":t.innerHTML=me();break;case"aliases":t.innerHTML=ue();break;case"profiles":t.innerHTML=ge();break}}toggleClientsCollapse(){let e=c.getState().clientsCollapsed;c.setState({clientsCollapsed:!e}),this.render()}async saveNewVaultSecret(){let e=document.getElementById("vault-new-key"),t=document.getElementById("vault-new-val"),a=document.getElementById("vault-new-service"),o=e?.value.trim(),n=t?.value.trim(),r=a?.value.trim()||"warmplane";if(!o||!n){alert("Key and secret value are required");return}try{let s=await h.saveSecret(o,n,r);if(s.ok){if(alert(`Secret '${o}' saved securely into OS Keychain!
Reference: ${s.uri}`),e)e.value="";if(t)t.value="";await this.refreshData()}else alert(`Failed to save secret: ${s.error}`)}catch(s){alert(`Error saving secret: ${s.message}`)}}async deleteVaultSecret(e,t){let a=t?`Are you sure you want to remove secret '${e}' from OS Keychain and server '${t}'?`:`Are you sure you want to remove secret '${e}' from OS Keychain?`;if(!confirm(a))return;try{let o=await h.deleteSecret(e);if(o.ok||o.error?.includes("not found")){if(t){let s=(c.getState().config.mcpServers||{})[t];if(s&&s.env&&s.env[e]){let i={...s.env};delete i[e];let l={...s,env:i};await h.upsertServer(t,l)}}await this.refreshData()}else alert(`Failed to delete secret: ${o.error}`)}catch(o){alert(`Error deleting secret: ${o.message}`)}}async removeSecretFromConfig(e,t){if(!confirm(`Remove environment variable '${t}' from '${e}' configuration?`))return;try{let n=(c.getState().config.mcpServers||{})[e];if(n&&n.env&&n.env[t]){let r={...n.env};delete r[t];let s={...n,env:r},i=await h.upsertServer(e,s);if(i.ok)await this.refreshData();else alert(`Failed to update server config: ${i.error}`)}}catch(a){alert(`Error removing secret from config: ${a.message}`)}}async quickVaultEnv(e,t){let a=prompt(`Enter secret value to store in OS Keychain for ${e}.${t}:`);if(!a)return;try{let o=await h.saveSecret(t,a,"warmplane");if(!o.ok){alert(`Failed to save to Keychain: ${o.error}`);return}let s=(c.getState().config.mcpServers||{})[e];if(s){let i={...s.env||{},[t]:`keychain://warmplane/${t}`},l={...s,env:i},u=await h.upsertServer(e,l);if(u.ok)await this.refreshData(),alert(`Successfully configured ${e}.${t} in OS Keychain!`);else alert(`Failed to update server config: ${u.error}`)}}catch(o){alert(`Error during migration: ${o.message}`)}}async refreshTasks(){try{let e=await h.listTasks();if(e&&Array.isArray(e.tasks))c.setState({tasks:e.tasks})}catch(e){console.error("Failed to refresh tasks:",e)}}filterTasksByStatus(e){c.setState({taskFilterStatus:e})}togglePlaygroundAsyncTask(e){c.setState({playgroundAsyncTask:e})}async submitTaskInputResponses(e){let a=c.getState().tasks.find((r)=>r.taskId===e)?.inputRequests||{},o=Object.keys(a),n={};if(o.length>0)for(let r of o){let s=a[r];if(s&&s.type==="approval_review"){let i=document.getElementById(`task-input-${e}-${r}-decision`),l=document.getElementById(`task-input-${e}-${r}`),u=i?i.value==="true":!0,p=void 0;if(l&&l.value.trim())try{p=JSON.parse(l.value.trim())}catch{alert("Invalid JSON in parameters editor");return}n[r]={approved:u,modified_args:p,reason:u?void 0:"Operator rejected execution via Tasks review"}}else{let i=document.getElementById(`task-input-${e}-${r}`);if(i){let l=i.value.trim();try{n[r]=JSON.parse(l)}catch{n[r]=l}}}}else{let r=document.getElementById(`task-raw-input-${e}`);if(r&&r.value.trim())try{Object.assign(n,JSON.parse(r.value.trim()))}catch{alert("Invalid JSON in raw input responses");return}}try{let r=await h.updateTask(e,n);if(r.ok)await this.refreshTasks();else alert(`Task update failed: ${r.error?.message||r.error||"Unknown error"}`)}catch(r){alert(`Error updating task: ${r.message}`)}}async promptCancelTask(e){let t=prompt("Reason for cancelling task:");if(t===null)return;try{let a=await h.cancelTask(e,t||void 0);if(a.ok)await this.refreshTasks();else alert(`Task cancellation failed: ${a.error?.message||a.error||"Unknown error"}`)}catch(a){alert(`Error cancelling task: ${a.message}`)}}async openTaskInspectorModal(e){this.closeModals();let t=document.getElementById("modal-task-inspector");if(!t)return;let a=document.getElementById("modal-task-title"),o=document.getElementById("modal-task-body"),n=document.getElementById("modal-task-footer");if(a)a.textContent=`Task Inspector: ${e}`;if(o)o.innerHTML=`
<div style="padding: 30px; text-align: center; color: var(--text-dim); font-family: var(--ff-mono); font-size: 12px;">
⏳ Fetching task execution record...
</div>
`;t.classList.add("active");try{let r=await h.getTask(e);if(!r.ok||!r.task){if(o)o.innerHTML=`
<div style="background: rgba(248, 113, 113, 0.12); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: var(--radius-sm); padding: 16px; color: var(--red-400);">
<div style="font-weight: 700; margin-bottom: 6px;">Failed to load task snapshot</div>
<div style="font-family: var(--ff-mono); font-size: 11.5px;">${f(r.error?.message||"Task not found in runtime registry")}</div>
</div>
`;return}let s=r.task,i=s.progress!==void 0?Math.round(s.progress*100):s.status==="completed"?100:s.status==="working"?50:0,l=Math.floor(Date.now()/1000),u=s.expiresAtEpochSecs?Math.max(0,s.expiresAtEpochSecs-l):s.ttlSeconds||300,p=s.status==="completed"||s.status==="cancelled"||s.status==="failed",g=s.status==="completed"?"var(--green-400)":s.status==="working"?"var(--cyan-400)":s.status==="input_required"?"var(--amber-300)":s.status==="cancelled"?"var(--text-muted)":"var(--red-400)",m=s.status==="completed"?"rgba(52, 211, 153, 0.15)":s.status==="working"?"rgba(56, 189, 248, 0.15)":s.status==="input_required"?"rgba(245, 158, 11, 0.18)":s.status==="cancelled"?"rgba(148, 163, 184, 0.15)":"rgba(248, 113, 113, 0.15)",d=!!s.error,y=s.result!==void 0&&s.result!==null,b=s.inputRequests&&Object.keys(s.inputRequests).length>0;if(o)o.innerHTML=`
<!-- Status Banner & Top Metrics -->
<div style="display: flex; justify-content: space-between; align-items: center; background: var(--surface-card); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px 16px; margin-bottom: 14px;">
<div>
<div style="display: flex; align-items: center; gap: 8px;">
<span class="brand-badge" style="background: ${m}; color: ${g}; border-color: ${g}; font-size: 11px;">
${f(s.status.toUpperCase())}
</span>
<span style="font-family: var(--ff-mono); font-size: 13px; font-weight: 700; color: var(--text-main);">
${f(s.capabilityId||"Tool Execution")}
</span>
${s.serverId?`<span style="font-size: 11px; color: var(--text-dim);">via <code style="color: var(--cyan-400);">${f(s.serverId)}</code></span>`:""}
</div>
<div style="font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim); margin-top: 4px;">
Task ID: <span style="color: var(--text-muted);">${f(s.taskId)}</span>
</div>
</div>
<div style="text-align: right; font-family: var(--ff-mono); font-size: 11px;">
<div style="color: var(--text-dim);">Created: <span style="color: var(--text-muted);">${s.createdAtEpochSecs?new Date(s.createdAtEpochSecs*1000).toLocaleString():s.createdAt?new Date(s.createdAt).toLocaleString():"—"}</span></div>
${!p?`<div style="color: var(--amber-400); margin-top: 2px;">TTL: ${u}s remaining</div>`:""}
</div>
</div>
<!-- Progress Bar -->
<div style="margin-bottom: 16px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; font-family: var(--ff-mono); font-size: 11px;">
<span style="color: var(--text-muted); text-transform: uppercase; font-weight: 600;">Execution Progress</span>
<span style="color: ${g}; font-weight: 700;">${i}% ${s.total?`(step ${Math.round((s.progress||0)*s.total)} of ${s.total})`:""}</span>
</div>
<div style="height: 8px; background: var(--surface-card); border-radius: 4px; overflow: hidden; border: 1px solid var(--border);">
<div style="height: 100%; width: ${i}%; background: ${g}; transition: width 0.3s;"></div>
</div>
</div>
<!-- Caller Context Envelope -->
${s.context?`
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 12px; font-family: var(--ff-mono); font-size: 11px; display: flex; flex-wrap: wrap; gap: 16px; margin-bottom: 14px; color: var(--text-muted);">
${s.context.actor_id?`<div><span style="color: var(--text-dim);">Actor:</span> <span style="color: var(--cyan-400);">${f(s.context.actor_id)}</span></div>`:""}
${s.context.operation_id?`<div><span style="color: var(--text-dim);">Operation:</span> <span style="color: var(--text-main);">${f(s.context.operation_id)}</span></div>`:""}
${s.context.grant_id?`<div><span style="color: var(--text-dim);">Grant:</span> <span style="color: var(--text-main);">${f(s.context.grant_id)}</span></div>`:""}
</div>
`:""}
<!-- Diagnostic / Error Message -->
${d?`
<div style="background: rgba(248, 113, 113, 0.1); border: 1px solid rgba(248, 113, 113, 0.3); border-radius: var(--radius-sm); padding: 12px; margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 700; color: var(--red-400); text-transform: uppercase; margin-bottom: 6px;">
⚠️ Failure / Cancellation Trace
</div>
<pre style="font-family: var(--ff-mono); font-size: 11.5px; color: var(--red-300); white-space: pre-wrap; word-break: break-word; margin: 0;">${f(typeof s.error==="string"?s.error:JSON.stringify(s.error,null,2))}</pre>
</div>
`:""}
<!-- Input Requests (if awaiting input) -->
${b?`
<div style="margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 700; color: var(--amber-400); text-transform: uppercase; margin-bottom: 6px;">
⚡ Pending Input Requests (MRTR / HITL)
</div>
<pre style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; font-family: var(--ff-mono); font-size: 11.5px; color: var(--amber-300); white-space: pre-wrap; word-break: break-word; max-height: 180px; overflow-y: auto; margin: 0;">${f(JSON.stringify(s.inputRequests,null,2))}</pre>
</div>
`:""}
<!-- Result Payload -->
<div style="margin-bottom: 8px;">
<div style="font-size: 11px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; margin-bottom: 6px;">
${y?"\uD83D\uDCE6 Output Result Payload":"State Details"}
</div>
<pre style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; font-family: var(--ff-mono); font-size: 11.5px; color: var(--text-main); white-space: pre-wrap; word-break: break-word; max-height: 220px; overflow-y: auto; margin: 0;">${f(y?JSON.stringify(s.result,null,2):s.status==="working"?"Task execution is currently in-flight in background worker pool.":"No output payload recorded.")}</pre>
</div>
`;if(n)n.innerHTML=`
<div>
${!p?`
<button class="btn btn-danger" style="font-size: 11.5px;" onclick="window.app.promptCancelTask('${f(s.taskId)}'); window.app.closeModals();">
⛔ Cancel Task
</button>
`:`
<span style="font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim);">Task is terminal (${f(s.status)})</span>
`}
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost" style="font-size: 11.5px;" onclick="navigator.clipboard.writeText('${f(s.taskId)}'); alert('Task ID copied to clipboard');">
\uD83D\uDCCB Copy Task ID
</button>
<button class="btn btn-ghost" style="font-size: 11.5px;" onclick="window.app.closeModals()">
Close
</button>
</div>
`}catch(r){if(o)o.innerHTML=`
<div style="color: var(--red-400); font-family: var(--ff-mono); font-size: 11.5px;">
Failed to inspect task: ${f(r.message)}
</div>
`}}async submitApproval(e){let t=document.getElementById(`appr-operator-${e}`),a=document.getElementById(`appr-args-${e}`),o=t?.value.trim()||"security-operator",n=void 0;if(a&&a.value.trim())try{n=JSON.parse(a.value.trim())}catch{alert("Invalid JSON in arguments editor");return}let r=await h.approveTicket(e,o,n);if(r.ok)await this.refreshApprovals(),await this.refreshTasks();else alert(`Approval failed: ${r.error||"Unknown error"}`)}async promptReject(e){let t=prompt("Reason for rejection (will be returned to the calling agent):");if(t===null)return;let o=document.getElementById(`appr-operator-${e}`)?.value.trim()||"security-operator",n=await h.rejectTicket(e,o,t);if(n.ok)await this.refreshApprovals(),await this.refreshTasks();else alert(`Rejection failed: ${n.error||"Unknown error"}`)}setPlaygroundMode(e){c.setState({playgroundMode:e})}selectCapability(e){c.setState({selectedCapabilityId:e});let t=c.getState().capabilities.find((o)=>o.id===e),a=document.getElementById("pg-args-input");if(t){let o=ee(t.input_schema,!1),n=JSON.stringify(o,null,2);if(a)a.value=n;let r={...c.getState().playgroundArgs||{}};r[e]=n,c.getState().playgroundArgs=r}}selectResource(e){c.setState({selectedResourceId:e})}selectPrompt(e){c.setState({selectedPromptId:e})}filterResources(e){let t=e.toLowerCase().trim(),o=(c.getState().resources||[]).filter((r)=>r.id.toLowerCase().includes(t)||r.name&&r.name.toLowerCase().includes(t)||r.uri&&r.uri.toLowerCase().includes(t)||r.server&&r.server.toLowerCase().includes(t)),n=document.getElementById("pg-res-list");if(n)if(o.length===0)n.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No resources match "${f(e)}"
</div>
`;else n.innerHTML=o.map((r)=>{let s=r.id===c.getState().selectedResourceId?"active":"",i=r.uri?r.uri.split(":")[0]:"res";return`
<div class="cap-item ${s}" onclick="window.app.selectResource('${f(r.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${f(r.name||r.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(56, 189, 248, 0.15); color: var(--cyan-400);">${f(i)}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${f(r.uri)}</div>
<div style="display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted); margin-top: 4px;">
<span>server: ${f(r.server||"local")}</span>
<span>${f(r.mime_type||"text/plain")}</span>
</div>
</div>
`}).join("")}filterPrompts(e){let t=e.toLowerCase().trim(),o=(c.getState().prompts||[]).filter((r)=>r.id.toLowerCase().includes(t)||r.name&&r.name.toLowerCase().includes(t)||r.description&&r.description.toLowerCase().includes(t)||r.server&&r.server.toLowerCase().includes(t)),n=document.getElementById("pg-prompt-list");if(n)if(o.length===0)n.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No prompts match "${f(e)}"
</div>
`;else n.innerHTML=o.map((r)=>{let s=r.id===c.getState().selectedPromptId?"active":"",i=r.arguments?r.arguments.length:0;return`
<div class="cap-item ${s}" onclick="window.app.selectPrompt('${f(r.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${f(r.name||r.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(168, 85, 247, 0.15); color: var(--purple-400);">${i} args</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">${f(r.description||r.title||"Prompt template")}</div>
<div style="font-size: 10px; color: var(--text-muted); margin-top: 4px;">server: ${f(r.server||"local")}</div>
</div>
`}).join("")}updatePlaygroundArgs(e){let t=c.getState(),a=t.selectedCapabilityId||(t.capabilities[0]?t.capabilities[0].id:null);if(!a)return;let o={...t.playgroundArgs||{}};o[a]=e,t.playgroundArgs=o}fillPlaygroundSampleArgs(e=!1){let t=c.getState(),a=t.selectedCapabilityId||(t.capabilities[0]?t.capabilities[0].id:null),o=t.capabilities.find((i)=>i.id===a),n=document.getElementById("pg-args-input");if(!n)return;if(!o||!o.input_schema){if(n.value="{}",a){let i={...t.playgroundArgs||{}};i[a]="{}",t.playgroundArgs=i}return}let r=ee(o.input_schema,e),s=JSON.stringify(r,null,2);if(n.value=s,a){let i={...t.playgroundArgs||{}};i[a]=s,t.playgroundArgs=i}}formatPlaygroundArgs(){let e=c.getState(),t=e.selectedCapabilityId||(e.capabilities[0]?e.capabilities[0].id:null),a=document.getElementById("pg-args-input");if(a)try{let o=JSON.parse(a.value||"{}"),n=JSON.stringify(o,null,2);if(a.value=n,t){let r={...e.playgroundArgs||{}};r[t]=n,e.playgroundArgs=r}}catch(o){alert(`Cannot format JSON: ${o.message}`)}}insertPlaygroundArgKey(e,t,a){let o=c.getState(),n=o.selectedCapabilityId||(o.capabilities[0]?o.capabilities[0].id:null),r=document.getElementById("pg-args-input");if(r){let s={};try{s=JSON.parse(r.value||"{}")}catch{s={}}if(s[e]===void 0)if(a!==null&&a!==void 0)s[e]=a;else switch(t){case"string":s[e]=`sample_${e}`;break;case"number":case"integer":s[e]=0;break;case"boolean":s[e]=!0;break;case"array":s[e]=[];break;case"object":s[e]={};break;default:s[e]=`sample_${e}`}let i=JSON.stringify(s,null,2);if(r.value=i,n){let l={...o.playgroundArgs||{}};l[n]=i,o.playgroundArgs=l}}}fillBatchStepSampleArgs(e){let t=c.getState(),a=[...t.batchSteps||[]],o=a[e];if(!o||!o.capability_id)return;let n=t.capabilities.find((l)=>l.id===o.capability_id);if(!n||!n.input_schema)return;let r=n.input_schema.properties||{},s={};for(let[l,u]of Object.entries(r))if(u.default!==void 0)s[l]=u.default;else if(Array.isArray(u.enum)&&u.enum.length>0)s[l]=u.enum[0];else switch(u.type||"string"){case"string":s[l]=`sample_${l}`;break;case"number":case"integer":s[l]=0;break;case"boolean":s[l]=!0;break;case"array":s[l]=[];break;case"object":s[l]={};break;default:s[l]=`sample_${l}`}let i=JSON.stringify(s,null,2);a[e]={...a[e],argsJson:i},c.setState({batchSteps:a})}filterCapabilities(e){let t=e.toLowerCase().trim(),o=c.getState().capabilities.filter((r)=>r.id.toLowerCase().includes(t)||r.summary&&r.summary.toLowerCase().includes(t)||r.server&&r.server.toLowerCase().includes(t)),n=document.getElementById("pg-cap-list");if(n)if(o.length===0)n.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No capabilities match "${f(e)}"
</div>
`;else n.innerHTML=o.map((r)=>`
<div class="cap-item ${r.id===c.getState().selectedCapabilityId?"active":""}" onclick="window.app.selectCapability('${f(r.id)}')">
<div style="display: flex; justify-content: space-between; align-items: center;">
<span style="font-weight: 600; color: var(--text-main); font-family: var(--ff-mono); font-size: 12px;">${f(r.id)}</span>
<span style="font-size: 10px; color: var(--green-400);">${f(r.mode||"read")}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">server: ${f(r.server||"local")}</div>
</div>
`).join("")}async executePlaygroundTool(){let e=c.getState(),t=e.selectedCapabilityId||(e.capabilities[0]?e.capabilities[0].id:null);if(!t)return;let a=document.getElementById("pg-args-input")?.value||"{}",o=document.getElementById("pg-context-input")?.value||void 0,n=document.getElementById("pg-jsonpath-input")?.value.trim()||void 0,r=document.getElementById("pg-limit-lines-input")?.value.trim()||void 0,s=document.getElementById("pg-truncate-bytes-input")?.value.trim()||void 0,i={};try{i=JSON.parse(a)}catch{alert("Invalid arguments JSON object");return}if(n)i._jsonpath=n;if(r&&!isNaN(Number(r)))i._limit_lines=Number(r);if(s&&!isNaN(Number(s)))i._truncate_bytes=Number(s);let l=`op-${Date.now()}`;c.setState({isExecuting:!0,activeRequestId:l});let u=e.activeProfile||void 0,p=e.playgroundAsyncTask||!1;try{let g=await h.callCapability({capability_id:t,args:i,request_id:l,async_task:p?!0:void 0,context:{operation_id:o||l}},u);if(c.setState({isExecuting:!1,activeRequestId:null,executionResult:{status:g.status,durationMs:g.durationMs,data:g.data}}),g.status===202||g.data?.resultType==="task")this.refreshTasks();c.addEventLog("POST",`/v1/tools/call → ${t}`,g.status===200?"200 OK":`HTTP ${g.status}`,`${g.durationMs.toFixed(1)}ms`),h.getConfig().then((m)=>{if(m.ok&&m.circuit_breakers)c.setState({circuitBreakers:m.circuit_breakers})})}catch(g){c.setState({isExecuting:!1,activeRequestId:null,executionResult:{status:500,durationMs:0,data:{error:g.toString()}}})}}async cancelActiveOperation(){let t=c.getState().activeRequestId;if(t)try{await h.cancelOperation(t)}catch(a){console.warn("Failed to send cancel signal:",a)}c.setState({isExecuting:!1,activeRequestId:null,executionResult:{status:499,durationMs:0,data:{ok:!1,error:{code:"CANCELLED",message:"Operation cancelled by operator"}}}})}openBatchModal(){let e=c.getState(),t=e.batchSteps;if(!t||t.length===0)t=[{id:"step_1",capability_id:e.selectedCapabilityId||(e.capabilities[0]?e.capabilities[0].id:""),argsJson:"{}",continue_on_error:!1},{id:"step_2",capability_id:"",argsJson:"{}",continue_on_error:!0}],c.setState({batchSteps:t});c.setState({isBatchModalOpen:!0})}closeBatchModal(){c.setState({isBatchModalOpen:!1})}addBatchStep(){let t=[...c.getState().batchSteps||[]],a=t.length+1;t.push({id:`step_${a}`,capability_id:"",argsJson:"{}",continue_on_error:!1}),c.setState({batchSteps:t})}removeBatchStep(e){let a=[...c.getState().batchSteps||[]];if(a.length<=1){alert("Pipeline must contain at least one execution step.");return}a.splice(e,1);let o=a.map((n,r)=>({...n,id:`step_${r+1}`}));c.setState({batchSteps:o})}updateBatchStepCapability(e,t){let o=[...c.getState().batchSteps||[]];if(o[e])o[e]={...o[e],capability_id:t},c.setState({batchSteps:o})}updateBatchStepContinueOnError(e,t){let o=[...c.getState().batchSteps||[]];if(o[e])o[e]={...o[e],continue_on_error:t},c.setState({batchSteps:o})}updateBatchStepArgs(e,t){let a=c.getState(),o=[...a.batchSteps||[]];if(o[e])o[e]={...o[e],argsJson:t},a.batchSteps[e].argsJson=t}appendBatchVariable(e,t){let o=[...c.getState().batchSteps||[]],n=document.getElementById(`batch-step-args-${e}`);if(n){let r=n.value,s=n.selectionStart||r.length,i=n.selectionEnd||r.length,l=r.substring(0,s)+t+r.substring(i);if(n.value=l,o[e])o[e]={...o[e],argsJson:l},c.setState({batchSteps:o})}}async executeBatchPipeline(){let e=c.getState(),t=e.batchSteps||[],a=[];for(let n=0;n<t.length;n++){let r=t[n];if(!r.capability_id){alert(`Please select a capability for Step ${n+1}`);return}let s={};try{s=JSON.parse(r.argsJson||"{}")}catch{alert(`Invalid JSON in Step ${n+1} arguments`);return}a.push({id:r.id||`step_${n+1}`,capability_id:r.capability_id,args:s,continue_on_error:r.continue_on_error})}c.setState({isBatchModalOpen:!1});let o=e.activeProfile||void 0;try{let n=await h.batchCallCapabilities(a,o);c.setState({executionResult:{status:n.status,durationMs:n.durationMs,data:n.data}}),c.addEventLog("POST",`/v1/tools/batch_call (${t.length} steps)`,n.status===200?"200 OK":`HTTP ${n.status}`,`${n.durationMs.toFixed(1)}ms`)}catch(n){c.setState({executionResult:{status:500,durationMs:0,data:{error:n.toString()}}})}}async executeReadResource(){let e=c.getState(),t=e.selectedResourceId||(e.resources[0]?e.resources[0].id:null);if(!t)return;let a=document.getElementById("pg-res-jsonpath-input")?.value.trim()||void 0,o=document.getElementById("pg-res-lines-input")?.value.trim()||void 0,n=document.getElementById("pg-res-bytes-input")?.value.trim()||void 0,r={resource_id:t};if(a)r._jsonpath=a;if(o&&!isNaN(Number(o)))r._limit_lines=Number(o);if(n&&!isNaN(Number(n)))r._truncate_bytes=Number(n);let s=e.activeProfile||void 0;try{let i=await h.readResource({resource_id:t,input_responses:r},s);c.setState({resourceReadResult:{status:i.status,durationMs:i.durationMs,data:i.data}}),c.addEventLog("POST",`/v1/resources/read → ${t}`,i.status===200?"200 OK":`HTTP ${i.status}`,`${i.durationMs.toFixed(1)}ms`)}catch(i){c.setState({resourceReadResult:{status:500,durationMs:0,data:{error:i.toString()}}})}}async executeGetPrompt(){let e=c.getState(),t=e.selectedPromptId||(e.prompts[0]?e.prompts[0].id:null);if(!t)return;let a=document.querySelectorAll(".prompt-arg-input"),o={};a.forEach((r)=>{let s=r,i=s.getAttribute("data-arg-name");if(i&&s.value.trim())o[i]=s.value.trim()});let n=e.activeProfile||void 0;try{let r=await h.getPrompt({prompt_id:t,arguments:o},n);c.setState({promptGetResult:{status:r.status,durationMs:r.durationMs,data:r.data}}),c.addEventLog("POST",`/v1/prompts/get → ${t}`,r.status===200?"200 OK":`HTTP ${r.status}`,`${r.durationMs.toFixed(1)}ms`)}catch(r){c.setState({promptGetResult:{status:500,durationMs:0,data:{error:r.toString()}}})}}toggleBatchPlayground(){let e=document.getElementById("pg-args-input");if(!e)return;let t=[{id:"step_1",capability_id:"sqlite.read_query",args:{query:"SELECT * FROM users LIMIT 2"}},{id:"step_2",capability_id:"github.issues.search",args:{query:"label:bug"},continue_on_error:!0}];e.value=JSON.stringify(t,null,2)}async submitPolicyRule(e){let t=e==="allow"?"policy-new-allow":e==="deny"?"policy-new-deny":e==="redact"?"policy-new-redact":"policy-new-requireApproval",a=document.getElementById(t);if(!a)return;let o=a.value.trim();if(!o)return;await this.addPolicyRule(e,o),a.value=""}async addPolicyRule(e,t){let a=(t||"").trim();if(!a)return;let o=c.getState(),n=o.activeProfile,r=n?o.config.profiles?.[n]:void 0;if(r&&n){let s=r.policy||{},i=[...s.allow||[]],l=[...s.deny||[]],u=[...s.redact_keys||s.redactKeys||[]],p=[...s.require_approval||s.requireApproval||[]];if(e==="allow"&&!i.includes(a))i.push(a);if(e==="deny"&&!l.includes(a))l.push(a);if(e==="redact"&&!u.includes(a))u.push(a);if(e==="requireApproval"&&!p.includes(a))p.push(a);let g={...s,allow:i,deny:l,redactKeys:u,requireApproval:p},m=await h.upsertProfile(n,r.servers,r.description,g);if(!m.ok)alert(`Failed to save profile policy rule: ${m.error||"Unknown error"}`)}else{let s=o.config.policy||{},i=[...s.allow||[]],l=[...s.deny||[]],u=[...s.redact_keys||s.redactKeys||[]],p=[...s.require_approval||s.requireApproval||[]];if(e==="allow"&&!i.includes(a))i.push(a);if(e==="deny"&&!l.includes(a))l.push(a);if(e==="redact"&&!u.includes(a))u.push(a);if(e==="requireApproval"&&!p.includes(a))p.push(a);let g=await h.savePolicy({...s,allow:i,deny:l,redact_keys:u,redactKeys:u,require_approval:p,requireApproval:p});if(!g.ok)alert(`Failed to save policy rule: ${g.error||"Unknown error"}`)}await this.refreshData()}async removePolicyRule(e,t){let a=c.getState(),o=a.activeProfile,n=o?a.config.profiles?.[o]:void 0;if(n&&o){let r=n.policy||{},s=[...r.allow||[]],i=[...r.deny||[]],l=[...r.redact_keys||r.redactKeys||[]],u=[...r.require_approval||r.requireApproval||[]];if(e==="allow")s.splice(t,1);if(e==="deny")i.splice(t,1);if(e==="redact")l.splice(t,1);if(e==="requireApproval")u.splice(t,1);let p={...r,allow:s,deny:i,redactKeys:l,requireApproval:u},g=await h.upsertProfile(o,n.servers,n.description,p);if(!g.ok)alert(`Failed to update profile policy: ${g.error||"Unknown error"}`)}else{let r=a.config.policy||{},s=[...r.allow||[]],i=[...r.deny||[]],l=[...r.redact_keys||r.redactKeys||[]],u=[...r.require_approval||r.requireApproval||[]];if(e==="allow")s.splice(t,1);if(e==="deny")i.splice(t,1);if(e==="redact")l.splice(t,1);if(e==="requireApproval")u.splice(t,1);let p=await h.savePolicy({...r,allow:s,deny:i,redact_keys:l,redactKeys:l,require_approval:u,requireApproval:u});if(!p.ok)alert(`Failed to update policy: ${p.error||"Unknown error"}`)}await this.refreshData()}async saveWebhookConfig(){let e=document.getElementById("policy-webhook-url"),t=document.getElementById("policy-webhook-format"),a=document.getElementById("policy-webhook-secret"),o=e?e.value.trim():"",n=t?t.value:"generic",r=a?a.value.trim():"",i=c.getState().config.policy||{},l=o?{url:o,format:n,secret:r&&!r.startsWith("WARMPLANE_")&&!r.includes("_")?r:void 0,secret_env:r&&(r.startsWith("WARMPLANE_")||r.includes("_"))?r:void 0,events:["approval.requested","circuit_breaker.tripped","policy.violation"]}:void 0,u=await h.savePolicy({...i,webhook:l});if(u.ok)alert("Webhook settings saved successfully");else alert(`Failed to save webhook settings: ${u.error||"Unknown error"}`);await this.refreshData()}async testWebhook(){let e=document.getElementById("policy-webhook-url"),t=document.getElementById("policy-webhook-format"),a=e?e.value.trim():void 0,o=t?t.value:void 0,n=c.getState(),r=typeof n.config.policy?.webhook==="object"?n.config.policy.webhook?.url:void 0;if(a&&a!==r)await this.saveWebhookConfig();let s=document.getElementById("policy-webhook-status");if(s)s.textContent="Sending test event...",s.style.color="var(--cyan-400)";try{let i=await h.testWebhook(a,o);if(i.ok){if(alert(`Test webhook sent successfully! (${i.message})`),s)s.textContent=`✔ Test sent (HTTP ${i.status_code||200})`,s.style.color="var(--green-400)"}else if(alert(`Test webhook failed: ${i.error||"Unknown error"}`),s)s.textContent=`✖ Failed: ${i.error}`,s.style.color="var(--red-400)"}catch(i){alert(`Error sending test webhook: ${i.message}`)}}testPolicySandbox(e){let t=document.getElementById("policy-test-verdict");if(!t)return;let a=e.trim();if(!a){t.textContent="ENTER ID",t.style.color="var(--text-dim)";return}let o=c.getState(),n=o.activeProfile,r=n?o.config.profiles?.[n]:void 0,s=o.config.policy||{},i=r?.policy,l=s.deny||[],u=i?.deny||[],p=Array.from(new Set([...l,...u])),g=s.require_approval||s.requireApproval||[],m=i?.require_approval||i?.requireApproval||[],d=Array.from(new Set([...g,...m])),y=i&&i.allow&&i.allow.length>0?i.allow:s.allow||[],b=(v,x)=>{if(v==="*")return!0;if(v.endsWith("*"))return x.startsWith(v.slice(0,-1));return v===x};if(p.some((v)=>b(v,a))){t.textContent="DENIED (Strict Block)",t.style.color="var(--red-400)";return}if(y.length>0&&!y.some((v)=>b(v,a))){t.textContent="DENIED (Not in Allow List)",t.style.color="var(--red-400)";return}if(d.some((v)=>b(v,a))){t.textContent="REQUIRE APPROVAL (HITL Gate)",t.style.color="var(--amber-400)";return}t.textContent="ALLOWED",t.style.color="var(--green-400)"}async deleteServer(e){let t=c.getState().activeProfile,a=t?`Permanently delete server '${e}' globally from warmplane configuration? (This will also unbind it from profile '${t}')`:`Are you sure you want to permanently remove server '${e}' from configuration?`;if(!confirm(a))return;try{let o=await h.deleteServer(e);if(o.ok)await this.refreshData();else alert(`Failed to remove server '${e}': ${o.error||"Unknown error"}`)}catch(o){alert(`Error removing server '${e}': ${o.message}`)}}async restartServer(e){try{let t=await h.restartServer(e);if(t.ok)await this.refreshData();else alert(`Failed to restart server '${e}': ${t.error||"Unknown error"}`)}catch(t){alert(`Error restarting server '${e}': ${t.message}`)}}openServerDiagnosticsModal(e){this.closeModals();let t=c.getState(),a=t.config.mcpServers?.[e],o=t.serverStatuses?.[e],n=(t.circuitBreakers||[]).find((l)=>l.server_id===e),r=document.getElementById("modal-server-diagnostics");if(!r)return;let s=document.getElementById("modal-diag-title"),i=document.getElementById("modal-diag-body");if(s)s.textContent=`Live Diagnostics: ${e}`;if(i){let l=o?.status==="degraded",u=l?"var(--amber-400)":o?.status==="connected"?"var(--green-400)":"var(--red-400)",p=o?.error||"No active crash or error reported. Server is healthy.",g=Q(e,a?.command,a?.args),m=a?.env||{},d=Object.keys(m),b=(g?.envFields||[]).filter((x)=>x.required).filter((x)=>!d.includes(x.key)),v="";if(g||d.length>0){let x=(g?.envFields||[]).map((T)=>{let w=m[T.key]!==void 0,z='<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.3);">Configured</span>';if(!w&&T.required)z='<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);">Required / Missing</span>';else if(!w)z='<span class="brand-badge" style="color: var(--text-dim); border-color: var(--border);">Optional / Not Set</span>';return`
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: var(--surface); border-radius: var(--radius-xs); margin-bottom: 6px; font-size: 11.5px;">
<div>
<span style="font-family: var(--ff-mono); font-weight: 700; color: ${!w&&T.required?"var(--amber-300)":"var(--text-main)"};">${f(T.key)}</span>
${T.label?`<span style="font-size: 10.5px; color: var(--text-dim); margin-left: 6px;">(${f(T.label)})</span>`:""}
</div>
<div style="display: flex; align-items: center; gap: 8px;">
${z}
${!w?`
<button class="btn btn-primary" style="padding: 2px 8px; font-size: 10.5px;" onclick="window.app.quickVaultEnv('${f(e)}', '${f(T.key)}')">➕ Configure in Keychain</button>
`:""}
</div>
</div>
`}).join(""),A=(g?.envFields||[]).map((T)=>T.key),R=Object.entries(m).filter(([T])=>!A.includes(T)).map(([T,k])=>`
<div style="display: flex; justify-content: space-between; align-items: center; padding: 6px 10px; background: var(--surface); border-radius: var(--radius-xs); margin-bottom: 6px; font-size: 11.5px;">
<span style="font-family: var(--ff-mono); font-weight: 700; color: var(--text-main);">${f(T)}</span>
<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.3);">Custom Configured</span>
</div>
`).join("");v=`
<div style="background: rgba(0,0,0,0.2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 700; color: ${b.length>0?"var(--amber-400)":"var(--text-main)"}; text-transform: uppercase; margin-bottom: 8px; display: flex; justify-content: space-between; align-items: center;">
<span>\uD83D\uDD11 Environment Variables & Secrets</span>
${b.length>0?`<span style="color: var(--red-400); font-size: 10.5px;">⚠️ ${b.length} required key(s) missing</span>`:""}
</div>
${x}
${R}
</div>
`}i.innerHTML=`
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 16px;">
<span style="width: 10px; height: 10px; border-radius: 50%; background: ${u};"></span>
<span style="font-weight: 700; font-size: 14px; color: var(--text-main);">Current Status: <span style="color: ${u}; text-transform: uppercase;">${f(o?.status||"unknown")}</span></span>
<span class="brand-badge" style="color: var(--cyan-400);">Protocol: ${f(o?.protocol_version||"2026-07-28")}</span>
${b.length>0?'<span class="brand-badge" style="color: var(--red-400); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.1);">⚠️ Missing Required Keys</span>':""}
</div>
<div style="background: rgba(0,0,0,0.3); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 12px; margin-bottom: 14px;">
<div style="font-size: 11px; font-weight: 700; color: var(--amber-400); text-transform: uppercase; margin-bottom: 6px;">
⚠️ Diagnostic Details / Failure Root Cause
</div>
<pre style="font-family: var(--ff-mono); font-size: 11.5px; color: ${l?"var(--red-300)":"var(--text-dim)"}; white-space: pre-wrap; word-break: break-word; margin: 0;">${f(p)}</pre>
</div>
${v}
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 14px;">
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px;">
<div style="font-size: 10.5px; color: var(--text-dim);">Circuit Breaker State</div>
<div style="font-weight: 700; font-size: 13px; color: var(--text-main); margin-top: 2px;">
${n?`${n.state.toUpperCase()} (${n.consecutive_failures} failures)`:"CLOSED (Healthy)"}
</div>
</div>
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px;">
<div style="font-size: 10.5px; color: var(--text-dim);">Process Supervision</div>
<div style="font-weight: 700; font-size: 13px; color: var(--text-main); margin-top: 2px;">
Auto-Restart: ${a?.resilience?.autoRestart!==!1?"ENABLED":"DISABLED"}
</div>
</div>
</div>
<div style="background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 10px; margin-bottom: 16px;">
<div style="font-size: 10.5px; color: var(--text-dim); margin-bottom: 4px;">Configured Execution Target</div>
<code style="font-family: var(--ff-mono); font-size: 11px; color: var(--cyan-400); display: block; word-break: break-all;">
${a?.command?`${f(a.command)} ${f((a.args||[]).join(" "))}`:f(a?.url||"")}
</code>
</div>
<div style="display: flex; justify-content: flex-end; gap: 8px;">
<button class="btn btn-primary" onclick="window.app.restartServer('${f(e)}'); window.app.closeModals();">⚡ Restart & Probe Now</button>
<button class="btn btn-ghost" onclick="window.app.closeModals()">Close</button>
</div>
`}r.classList.add("active")}openAddServerModal(){this.closeModals();let e=document.getElementById("modal-srv-title"),t=document.getElementById("modal-srv-template-banner"),a=document.getElementById("modal-srv-name"),o=document.getElementById("modal-srv-transport"),n=document.getElementById("modal-srv-command"),r=document.getElementById("modal-srv-url"),s=document.getElementById("modal-srv-ft"),i=document.getElementById("modal-srv-cd"),l=document.getElementById("modal-srv-autorestart"),u=document.getElementById("modal-srv-maxrestarts");if(e)e.textContent="Add Upstream MCP Server";if(t)t.style.display="flex";if(a)a.value="",a.disabled=!1;if(o)o.value="stdio";if(n)n.value="";if(r)r.value="";let p=document.getElementById("modal-group-cmd"),g=document.getElementById("modal-group-url");if(p)p.style.display="block";if(g)g.style.display="none";if(s)s.value="3";if(i)i.value="30000";if(l)l.value="true";if(u)u.value="5";let m=document.getElementById("modal-add-server");if(m)m.classList.add("active")}openEditServerModal(e){this.closeModals();let t=c.getState(),a=t.config.mcpServers?.[e];if(!a){alert(`Server '${e}' not found in configuration.`);return}let o=document.getElementById("modal-srv-title"),n=document.getElementById("modal-srv-template-banner"),r=document.getElementById("modal-srv-name"),s=document.getElementById("modal-srv-transport"),i=document.getElementById("modal-srv-command"),l=document.getElementById("modal-srv-url"),u=document.getElementById("modal-srv-ft"),p=document.getElementById("modal-srv-cd"),g=document.getElementById("modal-srv-autorestart"),m=document.getElementById("modal-srv-maxrestarts");if(o)o.textContent=`Edit Server '${e}'`;if(n)n.style.display="none";if(r)r.value=e,r.disabled=!0;let d=!!a.command;if(s)s.value=d?"stdio":"http";let y=document.getElementById("modal-group-cmd"),b=document.getElementById("modal-group-url");if(y)y.style.display=d?"block":"none";if(b)b.style.display=d?"none":"block";if(i)i.value=d?`${a.command} ${(a.args||[]).join(" ")}`.trim():"";if(l)l.value=a.url||"";let v=a.resilience||t.config.resilience;if(u)u.value=String(v?.failureThreshold??3);if(p)p.value=String(v?.cooldownMs??30000);if(g)g.value=v?.autoRestart===!1?"false":"true";if(m)m.value=String(v?.maxRestarts??5);let x=document.getElementById("modal-add-server");if(x)x.classList.add("active")}async submitAddServer(){let e=document.getElementById("modal-srv-name"),t=e?.value.trim(),a=document.getElementById("modal-srv-transport")?.value;if(!t){alert("Server name is required");return}if(e&&!e.disabled){if((c.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists in configuration. Do you want to overwrite it?`))return}}let o={};if(a==="stdio"){let p=(document.getElementById("modal-srv-command")?.value.trim()).split(/\s+/).filter(Boolean);if(p.length===0){alert("Command is required");return}o.command=p[0],o.args=p.slice(1)}else{let u=document.getElementById("modal-srv-url")?.value.trim();if(!u){alert("URL is required");return}o.url=u}let n=document.getElementById("modal-srv-ft")?.value.trim(),r=document.getElementById("modal-srv-cd")?.value.trim(),s=document.getElementById("modal-srv-autorestart")?.value,i=document.getElementById("modal-srv-maxrestarts")?.value.trim();if(n||r||s||i)o.resilience={failureThreshold:n?Number(n):3,cooldownMs:r?Number(r):30000,autoRestart:s!=="false",maxRestarts:i?Number(i):5};let l=await h.upsertServer(t,o);if(l.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${l.error}`)}openTemplateCatalog(){this.closeModals();let e=document.getElementById("modal-templates");if(e)e.classList.add("active");this.renderTemplateGrid()}setTemplateCategory(e){this.activeTemplateCategory=e,document.querySelectorAll(".tmpl-cat-btn").forEach((t)=>{if(t.getAttribute("data-category")===e)t.classList.add("active"),t.style.background="var(--surface-elevated)",t.style.color="var(--amber-400)";else t.classList.remove("active"),t.style.background="var(--surface-card)",t.style.color="var(--text-main)"}),this.renderTemplateGrid()}filterTemplates(e){this.activeTemplateFilter=e.toLowerCase().trim(),this.renderTemplateGrid()}renderTemplateGrid(){let e=document.getElementById("tmpl-grid");if(!e)return;let t=X.filter((n)=>{let r=this.activeTemplateCategory==="all"||n.category===this.activeTemplateCategory,s=!this.activeTemplateFilter||n.name.toLowerCase().includes(this.activeTemplateFilter)||n.id.toLowerCase().includes(this.activeTemplateFilter)||n.description.toLowerCase().includes(this.activeTemplateFilter)||n.command.toLowerCase().includes(this.activeTemplateFilter)||n.envFields.some((i)=>i.key.toLowerCase().includes(this.activeTemplateFilter));return r&&s});if(t.length===0){e.innerHTML=`
<div style="grid-column: span 2; padding: 32px; text-align: center; color: var(--text-dim);">
No matching MCP server templates found.
</div>
`;return}let o=c.getState().config.mcpServers||{};e.innerHTML=t.map((n)=>{let r=!!o[n.id],s=`${n.command} ${n.defaultArgs.join(" ")}`;return`
<div class="bento-card" style="display: flex; flex-direction: column; justify-content: space-between; padding: 14px; background: var(--surface); border: 1px solid var(--border); min-width: 0; transition: transform 0.15s, border-color 0.15s;">
<div>
<div style="display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px;">
<div style="display: flex; align-items: center; gap: 8px; min-width: 0;">
<span style="font-weight: 700; font-size: 13.5px; color: var(--text-main); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${f(n.name)}</span>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 6px; flex-shrink: 0;">${f(n.badge)}</span>
</div>
${r?'<span style="font-size: 10px; color: var(--green-400); font-weight: 600; flex-shrink: 0;">CONNECTED</span>':""}
</div>
<div style="font-size: 11.5px; color: var(--text-muted); line-height: 1.4; margin-bottom: 8px;">
${f(n.description)}
</div>
<div style="font-family: var(--ff-mono); font-size: 10.5px; color: var(--text-dim); background: var(--surface-card); padding: 5px 8px; border-radius: var(--radius-sm); border: 1px solid var(--border-subtle); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<code>${f(s)}</code>
</div>
${n.envFields.length>0?`
<div style="font-size: 10.5px; color: var(--amber-400); margin-top: 6px; display: flex; align-items: center; gap: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">
<span>⚡ Needs:</span>
<code style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${n.envFields.map((i)=>f(i.key)).join(", ")}</code>
</div>
`:""}
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 12px; gap: 6px;">
<button class="btn btn-primary" style="font-size: 11.5px; padding: 4px 10px;" onclick="window.app.selectTemplate('${f(n.id)}')">
${r?"Configure Another":"✨ 1-Click Setup"}
</button>
</div>
</div>
`}).join("")}selectTemplate(e){let t=X.find((l)=>l.id===e);if(!t)return;this.selectedTemplate=t,this.closeModals();let a=document.getElementById("modal-configure-template");if(a)a.classList.add("active");let o=document.getElementById("cfg-tmpl-title"),n=document.getElementById("cfg-tmpl-desc"),r=document.getElementById("cfg-tmpl-form");if(o)o.textContent=`Configure ${t.name} Server`;if(n)n.textContent=t.description;let s=c.getState().config.mcpServers||{},i=t.id;if(s[i]){let l=2;while(s[`${t.id}-${l}`])l++;i=`${t.id}-${l}`}if(r){let l="";if(t.envFields.length>0)l=`
<div style="margin-top: 14px; margin-bottom: 6px; font-weight: 700; font-size: 11px; text-transform: uppercase; color: var(--amber-400); letter-spacing: 0.5px;">
Environment Variables & API Keys
</div>
${t.envFields.map((u)=>`
<div class="form-group">
<label class="form-label">${f(u.label)} ${u.required?'<span style="color: var(--red-400);">*</span>':"(Optional)"}</label>
<input type="password" class="form-input tmpl-env-input" data-key="${f(u.key)}" placeholder="${f(u.placeholder||"")}">
${u.description?`<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 3px;">${f(u.description)}</div>`:""}
</div>
`).join("")}
`;r.innerHTML=`
<div class="form-group">
<label class="form-label">Server Identifier (Name)</label>
<input type="text" class="form-input" id="cfg-srv-id" value="${f(i)}">
<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 3px;">Must be unique across all configured servers.</div>
</div>
<div class="form-group">
<label class="form-label">Command Line Arguments</label>
<input type="text" class="form-input" id="cfg-srv-args" value="${f(t.defaultArgs.join(" "))}" placeholder="${f(t.argsPlaceholder||"")}">
<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 3px;">Executable: <code>${f(t.command)}</code></div>
</div>
${l}
<details style="margin-top: 14px; background: rgba(0,0,0,0.2); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px 12px;">
<summary style="font-size: 11.5px; font-weight: 600; color: var(--amber-400); cursor: pointer;">
\uD83D\uDEE1️ Fault Tolerance & Process Supervision (Optional)
</summary>
<div style="margin-top: 10px; display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">
<div>
<label class="form-label" style="font-size: 10.5px;">Failure Threshold</label>
<input type="number" class="form-input" id="cfg-srv-ft" placeholder="3" value="3">
</div>
<div>
<label class="form-label" style="font-size: 10.5px;">Cooldown (ms)</label>
<input type="number" class="form-input" id="cfg-srv-cd" placeholder="30000" value="30000">
</div>
<div>
<label class="form-label" style="font-size: 10.5px;">Auto-Restart</label>
<select class="form-input" id="cfg-srv-autorestart">
<option value="true">Enabled (Default)</option>
<option value="false">Disabled</option>
</select>
</div>
<div>
<label class="form-label" style="font-size: 10.5px;">Max Restarts</label>
<input type="number" class="form-input" id="cfg-srv-maxrestarts" placeholder="5" value="5">
</div>
</div>
</details>
`}}async submitTemplateServer(){if(!this.selectedTemplate)return;let e=this.selectedTemplate,t=document.getElementById("cfg-srv-id")?.value.trim(),a=document.getElementById("cfg-srv-args")?.value.trim();if(!t){alert("Server identifier is required");return}if((c.getState().config.mcpServers||{})[t]){if(!confirm(`Server '${t}' already exists. Do you want to overwrite its configuration?`))return}let r=a?a.split(/\s+/).filter(Boolean):[],s={},i=document.querySelectorAll(".tmpl-env-input");for(let y of Array.from(i)){let b=y.getAttribute("data-key"),v=y.value.trim(),x=e.envFields.find((A)=>A.key===b);if(x?.required&&!v){alert(`Required field '${x.label}' is missing.`);return}if(b&&v)s[b]=v}let l={command:e.command,args:r};if(Object.keys(s).length>0)l.env=s;let u=document.getElementById("cfg-srv-ft")?.value.trim(),p=document.getElementById("cfg-srv-cd")?.value.trim(),g=document.getElementById("cfg-srv-autorestart")?.value,m=document.getElementById("cfg-srv-maxrestarts")?.value.trim();if(u||p||g||m)l.resilience={failureThreshold:u?Number(u):3,cooldownMs:p?Number(p):30000,autoRestart:g!=="false",maxRestarts:m?Number(m):5};let d=await h.upsertServer(t,l);if(d.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${d.error}`)}async openImportModal(){this.closeModals();let e=document.getElementById("modal-import");if(e)e.classList.add("active");let t=document.getElementById("modal-eco-list");if(!t)return;t.innerHTML='<div style="color: var(--text-dim); padding: 12px; text-align: center;">Scanning IDE configs...</div>';try{let a=await h.getEcosystemSources();if(a.sources&&a.sources.length>0)t.innerHTML=a.sources.map((o)=>`
<label style="display: flex; align-items: center; gap: 10px; background: var(--surface); padding: 10px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border); cursor: pointer;">
<input type="checkbox" class="eco-checkbox" value="${o.path}" checked>
<div>
<div style="font-weight: 600; color: var(--text-main);">${o.name}</div>
<div style="font-size: 11px; color: var(--text-dim);">${o.server_count} servers (${o.servers.join(", ")})</div>
</div>
</label>
`).join("");else t.innerHTML='<div style="color: var(--text-dim); padding: 12px; text-align: center;">No external MCP configuration files found on this system.</div>'}catch{t.innerHTML='<div style="color: var(--red-400); padding: 12px; text-align: center;">Failed to scan ecosystem sources.</div>'}}async submitImport(){let e=document.querySelectorAll(".eco-checkbox:checked");if(e.length===0){alert("No sources selected");return}for(let t of Array.from(e))await h.importConfig(t.value,!1);this.closeModals(),await this.refreshData()}async refreshClients(){try{let e=await h.getClients();if(e.ok&&Array.isArray(e.clients))c.setState({clients:e.clients})}catch(e){console.error("Failed to scan clients:",e)}}setClientCategoryFilter(e){c.setState({clientFilterCategory:e})}setClientSearchQuery(e){c.setState({clientSearchQuery:e})}async attachClient(e,t){let a=t;if(!a){let o=document.getElementById(`client-prof-${e}`)||document.getElementById(`overview-client-prof-${e}`);if(o)a=o.value||void 0;else a=c.getState().activeProfile||void 0}this.openClientAttachModal(e,a)}openClientAttachModal(e,t){this.closeModals(),this.pendingClientId=e;let a=c.getState().config,o=a.mcpHttpServer,n=o?`http://${o.bind==="0.0.0.0"||o.bind==="::"?"127.0.0.1":o.bind||"127.0.0.1"}:${o.port||9191}/mcp`:"",r=document.getElementById("modal-client-title"),s=document.getElementById("modal-client-transport"),i=document.getElementById("modal-client-url"),l=document.getElementById("modal-client-profile"),u=document.getElementById("modal-client-profile-wrap");if(r)r.textContent=`Connect ${e}`;if(s)s.value="stdio";if(i)i.value=n;if(l){let p=Object.keys(a.profiles||{});if(l.innerHTML=`<option value="">All Tools (Default)</option>${p.map((g)=>`<option value="${f(g)}">Profile: ${f(g)}</option>`).join("")}`,l.value=t||"",u)u.style.display=p.length>0?"block":"none"}this.updateClientTransportForm(),document.getElementById("modal-client-attach")?.classList.add("active")}updateClientTransportForm(){let t=(document.getElementById("modal-client-transport")?.value||"stdio")==="http",a=document.getElementById("modal-client-http-group"),o=document.getElementById("modal-client-http-status"),n=!!c.getState().config.mcpHttpServer;if(a)a.style.display=t?"block":"none";if(o)o.textContent=n?"Uses the daemon Streamable HTTP endpoint. The daemon must already be running.":"HTTP is unavailable until mcpHttpServer is configured and the daemon is restarted.",o.style.color=n?"var(--text-dim)":"var(--amber-300)"}async submitClientAttach(){let e=this.pendingClientId;if(!e)return;let t=document.getElementById("modal-client-transport")?.value||"stdio",a=document.getElementById("modal-client-profile")?.value||void 0,o=document.getElementById("modal-client-url")?.value.trim()||void 0;if(t==="http"&&!c.getState().config.mcpHttpServer&&!o){alert("Configure mcpHttpServer and restart the daemon, or provide an explicit HTTP endpoint.");return}if(t==="http"&&!o){alert("Enter an HTTP endpoint URL.");return}let n=await h.attachClient(e,a,t,o);if(!n.ok)alert(`Failed to attach client: ${n.error||n.message||"Unknown error"}`);else this.pendingClientId=null,this.closeModals(),await this.refreshData()}async detachClient(e){if(!confirm("Disconnect Warmplane from this client?"))return;let t=await h.detachClient(e);if(!t.ok)alert(`Failed to detach client: ${t.error||t.message||"Unknown error"}`);else await this.refreshData()}handleAliasTargetInput(e){let t=document.getElementById("alias-suggestions-dropdown");if(!t)return;let a=(e||"").trim().toLowerCase();if(a.length<2){t.style.display="none";return}let n=c.getState().capabilities.filter((r)=>r.id.toLowerCase().includes(a)||r.summary&&r.summary.toLowerCase().includes(a)||r.description&&r.description.toLowerCase().includes(a)||r.server&&r.server.toLowerCase().includes(a)).slice(0,8);if(n.length===0){t.style.display="none";return}t.innerHTML=n.map((r)=>`
<div style="padding: 8px 12px; cursor: pointer; border-bottom: 1px solid var(--border-subtle); display: flex; justify-content: space-between; align-items: center; transition: background 0.1s;"
onmouseover="this.style.background='var(--surface-hover)'"
onmouseout="this.style.background='transparent'"
onmousedown="window.app.selectAliasSuggestion('${f(r.id)}')">
<div>
<div style="font-weight: 700; color: var(--text-main);">${f(r.id)}</div>
<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 2px;">${f(r.summary||r.description||"")}</div>
</div>
<span style="font-size: 10px; color: var(--cyan-400);">${f(r.server||"local")}</span>
</div>
`).join(""),t.style.display="block"}selectAliasSuggestion(e){let t=document.getElementById("alias-target");if(t)t.value=e;this.hideAliasDropdown()}hideAliasDropdown(){let e=document.getElementById("alias-suggestions-dropdown");if(e)e.style.display="none"}startEditAlias(e,t,a,o="",n=!1){let r=document.getElementById("alias-kind"),s=document.getElementById("alias-name"),i=document.getElementById("alias-target"),l=document.getElementById("alias-summary"),u=document.getElementById("alias-passthrough"),p=document.getElementById("alias-passthrough-container"),g=document.getElementById("alias-form-title"),m=document.getElementById("alias-save-btn"),d=document.getElementById("alias-cancel-btn");if(r)r.value=e;if(s)s.value=t;if(i)i.value=a;if(l)l.value=o;if(u)u.checked=n;if(p)p.style.display=e==="tool"?"flex":"none";if(g)g.innerHTML=`✏️ EDIT ALIAS: <span style="color: var(--amber-400); font-family: var(--ff-mono);">${t}</span>`;if(m)m.textContent="✓ Update";if(d)d.style.display="inline-block";s?.focus(),s?.scrollIntoView({behavior:"smooth",block:"nearest"})}resetAliasForm(){let e=document.getElementById("alias-kind"),t=document.getElementById("alias-name"),a=document.getElementById("alias-target"),o=document.getElementById("alias-summary"),n=document.getElementById("alias-passthrough"),r=document.getElementById("alias-passthrough-container"),s=document.getElementById("alias-form-title"),i=document.getElementById("alias-save-btn"),l=document.getElementById("alias-cancel-btn");if(e)e.value="tool";if(t)t.value="";if(a)a.value="";if(o)o.value="";if(n)n.checked=!1;if(r)r.style.display="flex";if(s)s.textContent="Create New Alias";if(i)i.textContent="+ Save";if(l)l.style.display="none"}async createAlias(){let e=document.getElementById("alias-kind")?.value,t=document.getElementById("alias-name")?.value.trim(),a=document.getElementById("alias-target")?.value.trim(),o=document.getElementById("alias-summary")?.value.trim()||void 0,n=document.getElementById("alias-passthrough")?.checked||!1;if(!t||!a){alert("Please provide both alias name and canonical target");return}if(e==="tool"&&n&&!/^[a-zA-Z0-9_-]{1,64}$/.test(t)){let r=t.replace(/[^a-zA-Z0-9_-]/g,"_").substring(0,64);if(!confirm(`MCP tool names must match ^[a-zA-Z0-9_-]{1,64}$.
'${t}' will be exported to MCP clients as '${r}'.
Do you want to proceed?`))return}await h.updateAlias(e,t,a,o,void 0,n),this.resetAliasForm(),await this.refreshData()}async deleteAlias(e,t){await h.updateAlias(e,t,void 0),this.resetAliasForm(),await this.refreshData()}async reloadFromDisk(){try{let e=await h.reloadConfig();if(e.ok){let t="Hot-reload completed successfully!";if(e.mounted&&e.mounted.length>0)t+=`
Mounted: ${e.mounted.join(", ")}`;if(e.unmounted&&e.unmounted.length>0)t+=`
Unmounted: ${e.unmounted.join(", ")}`;if(e.warnings&&e.warnings.length>0)t+=`
Warnings:
${e.warnings.join(`
`)}`;alert(t)}else alert(`Hot-reload failed: ${e.error||"Unknown error"}`)}catch(e){alert(`Error reaching daemon: ${e.message}`)}await this.refreshData()}renderTopProfileSelector(){let e=document.getElementById("top-profile-selector");if(!e)return;let t=c.getState(),a=t.config.profiles||{},o=Object.keys(a),n=t.activeProfile,r='<option value="">All Servers (Unrestricted)</option>';for(let s of o){let i=n===s?"selected":"";r+=`<option value="${f(s)}" ${i}>Profile: ${f(s)}</option>`}e.innerHTML=r}async setActiveProfile(e){c.setState({activeProfile:e||null}),await this.refreshData()}openAddProfileModal(){let e=document.getElementById("modal-prof-title");if(e)e.textContent="Create Server Constellation Profile";let t=document.getElementById("modal-prof-name"),a=document.getElementById("modal-prof-desc"),o=document.getElementById("modal-prof-mode");if(t)t.value="",t.disabled=!1;if(a)a.value="";if(o)o.value="create";let n=document.getElementById("modal-prof-allow"),r=document.getElementById("modal-prof-deny"),s=document.getElementById("modal-prof-hitl"),i=document.getElementById("modal-prof-redact");if(n)n.value="";if(r)r.value="";if(s)s.value="";if(i)i.value="";this.renderProfileServerCheckboxes([]);let l=document.getElementById("modal-add-profile");if(l)l.classList.add("active")}openEditProfileModal(e){let a=c.getState().config.profiles?.[e];if(!a)return;let o=document.getElementById("modal-prof-title");if(o)o.textContent=`Edit Profile: ${e}`;let n=document.getElementById("modal-prof-name"),r=document.getElementById("modal-prof-desc"),s=document.getElementById("modal-prof-mode");if(n)n.value=e,n.disabled=!0;if(r)r.value=a.description||"";if(s)s.value="edit";let i=document.getElementById("modal-prof-allow"),l=document.getElementById("modal-prof-deny"),u=document.getElementById("modal-prof-hitl"),p=document.getElementById("modal-prof-redact"),g=a.policy;if(i)i.value=(g?.allow||[]).join(", ");if(l)l.value=(g?.deny||[]).join(", ");if(u)u.value=(g?.require_approval||g?.requireApproval||[]).join(", ");if(p)p.value=(g?.redact_keys||g?.redactKeys||[]).join(", ");this.renderProfileServerCheckboxes(a.servers||[]);let m=document.getElementById("modal-add-profile");if(m)m.classList.add("active")}renderProfileServerCheckboxes(e){let t=document.getElementById("modal-prof-servers-list");if(!t)return;let a=c.getState(),o=Object.keys(a.config.mcpServers||{});if(o.length===0){t.innerHTML='<div style="font-size: 11.5px; color: var(--text-dim);">No MCP servers configured yet. Add servers first.</div>';return}t.innerHTML=o.map((n)=>{let r=e.includes(n)?"checked":"";return`
<label style="display: flex; align-items: center; gap: 8px; font-size: 12px; cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); transition: background 0.15s;" onmouseover="this.style.background='var(--surface-hover)'" onmouseout="this.style.background='transparent'">
<input type="checkbox" class="prof-server-checkbox" value="${f(n)}" ${r} style="accent-color: var(--amber-400);">
<span style="font-family: var(--ff-mono); font-weight: 600; color: var(--text-main);">${f(n)}</span>
</label>
`}).join("")}async saveProfile(){let e=document.getElementById("modal-prof-name"),t=document.getElementById("modal-prof-desc"),a=e?.value.trim(),o=t?.value.trim();if(!a){alert("Please enter a profile name");return}let n=document.querySelectorAll(".prof-server-checkbox:checked"),r=[];if(n.forEach((v)=>{r.push(v.value)}),r.length===0){alert("Please select at least one server to include in this constellation");return}let s=(v)=>{if(!v)return[];return v.split(",").map((x)=>x.trim()).filter((x)=>x.length>0)},i=document.getElementById("modal-prof-allow"),l=document.getElementById("modal-prof-deny"),u=document.getElementById("modal-prof-hitl"),p=document.getElementById("modal-prof-redact"),g=s(i?.value),m=s(l?.value),d=s(u?.value),y=s(p?.value),b=void 0;if(g.length>0||m.length>0||d.length>0||y.length>0)b={allow:g,deny:m,requireApproval:d,redactKeys:y};try{let v=await h.upsertProfile(a,r,o||void 0,b);if(v.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save profile: ${v.error||"Unknown error"}`)}catch(v){alert(`Error saving profile: ${v.message}`)}}async deleteProfile(e){if(!confirm(`Are you sure you want to delete profile '${e}'?`))return;try{let t=await h.deleteProfile(e);if(t.ok){if(c.getState().activeProfile===e)c.setState({activeProfile:null});await this.refreshData()}else alert(`Failed to delete profile: ${t.error||"Unknown error"}`)}catch(t){alert(`Error deleting profile: ${t.message}`)}}async toggleServerInProfile(e,t,a){let n=c.getState().config.profiles?.[e];if(!n)return;let r=[...n.servers||[]];if(a){if(!r.includes(t))r.push(t)}else if(r=r.filter((s)=>s!==t),r.length===0){alert("A profile must contain at least one server. To remove the profile, delete it in the Profiles tab.");return}try{let s=await h.upsertProfile(e,r,n.description,n.policy);if(s.ok)await this.refreshData();else alert(`Failed to update profile constellation: ${s.error||"Unknown error"}`)}catch(s){alert(`Error updating profile constellation: ${s.message}`)}}closeModals(){document.querySelectorAll(".modal-backdrop").forEach((e)=>e.classList.remove("active"))}}function f(e){return String(e||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}var fe=new ve;window.app=fe;window.addEventListener("DOMContentLoaded",()=>fe.init());