class M{state={configPath:"mcp_servers.json",config:{mcpServers:{}},serverStatuses:{},circuitBreakers:[],capabilities:[],resources:[],prompts:[],catalogEvents:[],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,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(o){this.state={...this.state,...o},this.listeners.forEach((e)=>e(this.state))}subscribe(o){return this.listeners.push(o),()=>{this.listeners=this.listeners.filter((e)=>e!==o)}}addEventLog(o,e,n,r){let i=[{time:new Date().toLocaleTimeString(),method:o,target:e,status:n,latency:r},...this.state.eventLogs].slice(0,50);this.setState({eventLogs:i})}}var d=new M;class F{baseUrl;constructor(o=""){this.baseUrl=o}async getConfig(){return(await fetch(`${this.baseUrl}/v1/config`)).json()}async listCapabilities(o){let e={};if(o)e["X-Warmplane-Profile"]=o;return(await fetch(`${this.baseUrl}/v1/capabilities`,{headers:e})).json()}async listResources(o){let e={};if(o)e["X-Warmplane-Profile"]=o;return(await fetch(`${this.baseUrl}/v1/resources`,{headers:e})).json()}async readResource(o,e){let n=performance.now(),r={"Content-Type":"application/json"};if(e)r["X-Warmplane-Profile"]=e;let t=await fetch(`${this.baseUrl}/v1/resources/read`,{method:"POST",headers:r,body:JSON.stringify(o)}),i=performance.now()-n,a=await t.json();return{status:t.status,durationMs:i,data:a}}async listPrompts(o){let e={};if(o)e["X-Warmplane-Profile"]=o;return(await fetch(`${this.baseUrl}/v1/prompts`,{headers:e})).json()}async getPrompt(o,e){let n=performance.now(),r={"Content-Type":"application/json"};if(e)r["X-Warmplane-Profile"]=e;let t=await fetch(`${this.baseUrl}/v1/prompts/get`,{method:"POST",headers:r,body:JSON.stringify(o)}),i=performance.now()-n,a=await t.json();return{status:t.status,durationMs:i,data:a}}async getCatalogEvents(o){let e=o?`?after=${encodeURIComponent(o)}`:"";return(await fetch(`${this.baseUrl}/v1/catalog/events${e}`)).json()}async callCapability(o,e){let n=performance.now(),r={"Content-Type":"application/json"};if(e)r["X-Warmplane-Profile"]=e;let t=await fetch(`${this.baseUrl}/v1/tools/call`,{method:"POST",headers:r,body:JSON.stringify(o)}),i=performance.now()-n,a=await t.json();return{status:t.status,durationMs:i,data:a}}async batchCallCapabilities(o,e){let n=performance.now(),r={"Content-Type":"application/json"};if(e)r["X-Warmplane-Profile"]=e;let t=await fetch(`${this.baseUrl}/v1/tools/batch_call`,{method:"POST",headers:r,body:JSON.stringify({steps:o})}),i=performance.now()-n,a=await t.json();return{status:t.status,durationMs:i,data:a}}async cancelOperation(o){return(await fetch(`${this.baseUrl}/v1/operations/${encodeURIComponent(o)}/cancel`,{method:"POST"})).json()}async completeArgument(o){return(await fetch(`${this.baseUrl}/v1/completion/complete`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)})).json()}async upsertServer(o,e){return(await fetch(`${this.baseUrl}/v1/config/servers`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:o,server:e})})).json()}async deleteServer(o){return(await fetch(`${this.baseUrl}/v1/config/servers/${encodeURIComponent(o)}`,{method:"DELETE"})).json()}async upsertProfile(o,e,n){return(await fetch(`${this.baseUrl}/v1/config/profiles`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:o,servers:e,description:n})})).json()}async deleteProfile(o){return(await fetch(`${this.baseUrl}/v1/config/profiles/${encodeURIComponent(o)}`,{method:"DELETE"})).json()}async getEcosystemSources(){return(await fetch(`${this.baseUrl}/v1/config/ecosystem`)).json()}async importConfig(o,e=!1){return(await fetch(`${this.baseUrl}/v1/config/import`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({source_path:o,overwrite:e})})).json()}async savePolicy(o){let e={allow:o.allow||[],deny:o.deny||[],redactKeys:o.redact_keys||o.redactKeys||[],requireApproval:o.require_approval||o.requireApproval||[],approvalTimeoutSecs:o.approvalTimeoutSecs||o.approval_timeout_secs||300,webhook:o.webhook};return(await fetch(`${this.baseUrl}/v1/config/policy`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(e)})).json()}async listApprovals(){return(await fetch(`${this.baseUrl}/v1/approvals`)).json()}async approveTicket(o,e,n){return(await fetch(`${this.baseUrl}/v1/approvals/${encodeURIComponent(o)}/approve`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operator:e,modified_args:n})})).json()}async rejectTicket(o,e,n){return(await fetch(`${this.baseUrl}/v1/approvals/${encodeURIComponent(o)}/reject`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({operator:e,reason:n})})).json()}async updateAlias(o,e,n){return(await fetch(`${this.baseUrl}/v1/config/alias`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({kind:o,alias:e,target:n})})).json()}async reloadConfig(){return(await fetch(`${this.baseUrl}/v1/config/reload`,{method:"POST"})).json()}async listAuditEvents(o){let e=new URLSearchParams;if(o?.actor_id)e.set("actor_id",o.actor_id);if(o?.server_id&&o.server_id!=="all")e.set("server_id",o.server_id);if(o?.capability_id)e.set("capability_id",o.capability_id);if(o?.event_type&&o.event_type!=="all")e.set("event_type",o.event_type);if(o?.status&&o.status!=="all")e.set("status",o.status);if(o?.trace_id)e.set("trace_id",o.trace_id);if(o?.request_id)e.set("request_id",o.request_id);if(o?.search)e.set("search",o.search);if(o?.limit)e.set("limit",String(o.limit));if(o?.offset!==void 0)e.set("offset",String(o.offset));let n=e.toString();return(await fetch(`${this.baseUrl}/v1/audit/events${n?`?${n}`:""}`)).json()}getAuditExportUrl(o,e="csv"){let n=new URLSearchParams;if(n.set("format",e),o?.actor_id)n.set("actor_id",o.actor_id);if(o?.server_id&&o.server_id!=="all")n.set("server_id",o.server_id);if(o?.capability_id)n.set("capability_id",o.capability_id);if(o?.event_type&&o.event_type!=="all")n.set("event_type",o.event_type);if(o?.status&&o.status!=="all")n.set("status",o.status);if(o?.trace_id)n.set("trace_id",o.trace_id);if(o?.request_id)n.set("request_id",o.request_id);if(o?.search)n.set("search",o.search);return`${this.baseUrl}/v1/audit/export?${n.toString()}`}async verifyAuditChain(){return(await fetch(`${this.baseUrl}/v1/audit/verify`)).json()}async getAuditStats(){return(await fetch(`${this.baseUrl}/v1/audit/stats`)).json()}}var y=new F;function D(){let o=d.getState(),e=o.config.mcpServers||{},n=Object.keys(e),r=n.length,t="";if(n.length===0)t=`
<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 t=n.map((w)=>{let S=e[w],O=S.command?"stdio":"http / sse",z=S.command?`${S.command} ${(S.args||[]).join(" ")}`:S.url,T=o.serverStatuses[w]||{status:"connected",protocol_version:"2026-07-28"},m=T.status==="degraded",N=T.status==="error"||T.status==="disconnected",j=m?"var(--amber-400)":N?"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: ${j}; display: inline-block;"></span>
${k(w)}
</span>
<span class="brand-badge">${O}</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="${k(z||"")}">
${k(z||"")}
</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: ${j};">${k(T.status)}</strong></span>
<span>Protocol: ${T.protocol_version}</span>
</div>
</div>
`}).join("");let i=o.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>
`:o.eventLogs.map((w)=>`
<div class="feed-row" style="grid-template-columns: 80px 100px 1fr 100px 80px;">
<span style="color: var(--text-dim);">${k(w.time)}</span>
<span style="color: var(--cyan-400); font-weight: 600;">${k(w.method)}</span>
<span style="color: var(--text-main); font-family: var(--ff-mono);">${k(w.target)}</span>
<span style="color: var(--green-400);">${k(w.status)}</span>
<span style="color: var(--amber-300); text-align: right;">${k(w.latency)}</span>
</div>
`).join(""),a=o.metrics,s=a.totalCatalogRequests,c=a.totalEtagHits,l=s>0?`${(c/s*100).toFixed(1)}%`:"0.0%",g=s>0?`${c} of ${s} requests served via HTTP 304`:"Waiting for client requests",v=a.totalToolCalls,u=v>0?`${(a.totalToolDurationUs/v/1000).toFixed(1)}ms`:"0.0ms",f=v>0?`${v} tool executions processed`:"Local worker task queues warm",h=Object.keys(o.config.capabilityAliases||{}).length+Object.keys(o.config.resourceAliases||{}).length+Object.keys(o.config.promptAliases||{}).length,_=h>0?`${h*18}B / call`:"0B",A=h>0?`${h} active facade aliases pruning prompt size`:"Configure aliases in Studio to reduce prompt size";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);">${_}</div>
<div class="stat-sub">${A}</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);">${l}</div>
<div class="stat-sub">${g}</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Connected Upstreams</div>
<div class="stat-value" style="color: var(--green-400);">${r} Active</div>
<div class="stat-sub">${r>0?"Persistent worker task channels":"No active upstream servers"}</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Avg Execution Latency</div>
<div class="stat-value">${u}</div>
<div class="stat-sub">${f}</div>
</div>
</div>
<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 (${r}) →</button>
</div>
<div class="bento-grid" style="margin-bottom: 24px;">
${t}
</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">
${i}
</div>
</div>
`}function k(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function K(){let o=d.getState(),e=o.config.mcpServers||{},n=Object.keys(e),r="";if(n.length===0)r=`
<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 ${P(o.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 r=n.map((t)=>{let i=e[t],a=i.command?"stdio":"http / sse",s=i.command?`${i.command} ${(i.args||[]).join(" ")}`:i.url,c=o.serverStatuses[t]||{status:"connected",protocol_version:"2026-07-28"},l=i.env?Object.keys(i.env).map((w)=>`${w}=***`).join(", "):"None",g=(o.circuitBreakers||[]).find((w)=>w.server_id===t),v='<span class="brand-badge" style="color: var(--green-400); border-color: rgba(52, 211, 153, 0.25);">Circuit: CLOSED</span>';if(g){if(g.state==="open")v=`<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 (${g.consecutive_failures} failures)</span>`;else if(g.state==="half_open")v=`<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 (${g.consecutive_successes} probe)</span>`}let u=i.resilience||o.config.resilience,f=u?`FT: ${u.failureThreshold||3} · Cooldown: ${(u.cooldownMs||30000)/1000}s · AutoRestart: ${u.autoRestart!==!1?"ON":"OFF"}`:"Default Resilience",h=c.status==="degraded",_=c.status==="error"||c.status==="disconnected",A=h?"var(--amber-400)":_?"var(--red-400)":"var(--green-400)";return`
<div class="bento-card" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<div>
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 4px;">
<span style="width: 8px; height: 8px; border-radius: 50%; background: ${A}; display: inline-block;"></span>
<span style="font-size: 15px; font-weight: 700; color: var(--text-main);">${P(t)}</span>
<span class="brand-badge">${a}</span>
<span class="brand-badge" style="color: ${A}; border-color: rgba(245, 158, 11, 0.3);">Status: ${P(c.status)}</span>
<span class="brand-badge" style="color: var(--cyan-400); border-color: rgba(34, 211, 238, 0.25);">Protocol: ${c.protocol_version}</span>
${v}
</div>
<div style="font-family: var(--ff-mono); font-size: 12px; color: var(--text-muted); margin-top: 4px;">
${i.command?"Command: ":"URL: "}<code>${P(s||"")}</code>
</div>
<div style="display: flex; gap: 14px; font-family: var(--ff-mono); font-size: 11px; color: var(--text-dim); margin-top: 4px;">
<span>\uD83D\uDEE1️ ${P(f)}</span>
${i.env&&Object.keys(i.env).length>0?`<span>Env: ${P(l)}</span>`:""}
</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.openEditServerModal('${P(t)}')">✏️ Edit</button>
<button class="btn btn-danger" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.deleteServer('${P(t)}')">Remove</button>
</div>
</div>
`}).join("");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>${P(o.configPath)}</code></div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn btn-ghost" onclick="window.app.reloadFromDisk()">⟳ Reload Config</button>
</div>
</div>
${r}
`}function P(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function B(){let o=d.getState(),e=o.playgroundMode||"tools",n=o.capabilities||[],r=o.resources||[],t=o.prompts||[],i=`
<div style="display: flex; gap: 8px; margin-bottom: 12px; align-items: center; justify-content: space-between;">
<div style="display: inline-flex; padding: 3px; background: rgba(0,0,0,0.3); border: 1px solid var(--border); border-radius: var(--radius-sm);">
<button
class="btn ${e==="tools"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px;"
onclick="window.app.setPlaygroundMode('tools')"
>
\uD83D\uDEE0️ Tools (${n.length})
</button>
<button
class="btn ${e==="resources"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px;"
onclick="window.app.setPlaygroundMode('resources')"
>
\uD83D\uDCC4 Resources (${r.length})
</button>
<button
class="btn ${e==="prompts"?"btn-primary":"btn-ghost"}"
style="padding: 4px 12px; font-size: 11.5px; height: 28px;"
onclick="window.app.setPlaygroundMode('prompts')"
>
\uD83D\uDCAC Prompts (${t.length})
</button>
</div>
<div style="font-size: 11.5px; color: var(--text-dim);">
${e==="tools"?"Interactive Tool Caller & Context Distillation":e==="resources"?"Live MCP Resource Inspector & Reader":"Prompt Template Studio & Variable Binder"}
</div>
</div>
`;if(e==="resources")return`
${i}
${to(o)}
`;if(e==="prompts")return`
${i}
${io(o)}
`;return`
${i}
${eo(o)}
${o.isBatchModalOpen?no(o):""}
`}function U(o,e=!1){if(!o||!o.properties)return{};let n=o.properties||{},r=Array.isArray(o.required)?o.required:[],t={};for(let[i,a]of Object.entries(n)){let s=r.includes(i);if(e&&!s)continue;if(a.default!==void 0)t[i]=a.default;else if(Array.isArray(a.enum)&&a.enum.length>0)t[i]=a.enum[0];else if(a.examples&&Array.isArray(a.examples)&&a.examples.length>0)t[i]=a.examples[0];else if(a.example!==void 0)t[i]=a.example;else switch(a.type||"string"){case"string":t[i]=s?`sample_${i}`:"";break;case"number":case"integer":t[i]=0;break;case"boolean":t[i]=!0;break;case"array":t[i]=[];break;case"object":t[i]={};break;default:t[i]=`sample_${i}`}}return t}function eo(o){let e=o.capabilities||[],n=o.selectedCapabilityId||(e.length>0?e[0].id:null),r=e.find((u)=>u.id===n),t=!!o.isExecuting,i="";if(e.length===0)i=`
<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 i=e.map((u)=>{return`
<div class="cap-item ${u.id===n?"active":""}" onclick="window.app.selectCapability('${p(u.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;">${p(u.id)}</span>
<span style="font-size: 10px; color: var(--green-400);">${p(u.mode||"read")}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">server: ${p(u.server||"local")}</div>
</div>
`}).join("");let a=r?.input_schema,s=a?.properties||{},c=Array.isArray(a?.required)?a.required:[],l=Object.entries(s),g="";if(l.length>0)g=`
<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>
${l.map(([u,f])=>{let h=c.includes(u),_=f.type||(f.enum?"enum":"any"),A=h?"rgba(239, 68, 68, 0.15)":"rgba(148, 163, 184, 0.1)",w=h?"var(--red-400)":"var(--text-muted)",S=h?"rgba(239, 68, 68, 0.3)":"var(--border)",O=f.description?` - ${f.description}`:"";return`
<button
type="button"
class="btn"
style="padding: 2px 7px; font-size: 10.5px; font-family: var(--ff-mono); background: ${A}; color: ${w}; border: 1px solid ${S}; border-radius: var(--radius-sm);"
title="Click to insert '${u}' (${_}${O})"
onclick="window.app.insertPlaygroundArgKey('${p(u)}', '${p(_)}', ${p(JSON.stringify(f.default??null))})"
>
+ ${p(u)} <span style="font-size: 9px; opacity: 0.7;">(${_}${h?" *":""})</span>
</button>
`}).join("")}
</div>
`;let v="{}";if(n&&o.playgroundArgs&&o.playgroundArgs[n]!==void 0)v=o.playgroundArgs[n];else{let u=U(a,!1);v=JSON.stringify(u,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 ${e.length} capabilities..." oninput="window.app.filterCapabilities(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-cap-list">
${i}
</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">
${p(r?r.id:"No Capability Selected")}
</div>
<div style="font-size: 11.5px; color: var(--text-dim);" id="pg-selected-desc">
${p(r?r.summary||r.description:"Connect servers to inspect and execute tools")}
</div>
</div>
<div style="display: flex; align-items: center; gap: 10px;">
${t?`
<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()" ${r?"":"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>
${c.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>
${g}
<textarea class="form-textarea" rows="7" id="pg-args-input" oninput="window.app.updatePlaygroundArgs(this.value)">${p(v)}</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 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>
${r&&r.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);">${l.length} field${l.length===1?"":"s"} (${c.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;">${p(JSON.stringify(r.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);">NORMALIZED EXECUTION ENVELOPE</span>
<span id="pg-status-badge" style="font-size: 11px; font-weight: 600; color: ${o.executionResult?o.executionResult.status===200?"var(--green-400)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${o.executionResult?`HTTP ${o.executionResult.status} · ${o.executionResult.durationMs.toFixed(1)}ms`:"READY"}
</span>
</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;">${o.executionResult?p(JSON.stringify(o.executionResult.data,null,2)):"// Response envelope output will be formatted here"}</pre>
</div>
</div>
</div>
</div>
`}function to(o){let e=o.resources||[],n=o.selectedResourceId||(e.length>0?e[0].id:null),r=e.find((a)=>a.id===n),t=o.resourceReadResult,i="";if(e.length===0)i=`
<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 i=e.map((a)=>{let s=a.id===n?"active":"",c=a.uri?a.uri.split(":")[0]:"res";return`
<div class="cap-item ${s}" onclick="window.app.selectResource('${p(a.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;">${p(a.name||a.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(56, 189, 248, 0.15); color: var(--cyan-400);">${p(c)}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${p(a.uri)}</div>
<div style="display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted); margin-top: 4px;">
<span>server: ${p(a.server||"local")}</span>
<span>${p(a.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 ${e.length} resources..." oninput="window.app.filterResources(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-res-list">
${i}
</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);">
${p(r?r.name||r.id:"No Resource Selected")}
</div>
<div style="font-size: 11.5px; color: var(--cyan-400); font-family: var(--ff-mono);">
${p(r?r.uri:"Select a resource from the list to read live content")}
</div>
</div>
<button class="btn btn-primary" onclick="window.app.executeReadResource()" ${r?"":"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;">
${r?`
<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);">${p(r.server)}</strong></div>
<div><span style="color: var(--text-muted);">MIME Type:</span> <strong style="color: var(--text-main);">${p(r.mime_type||"text/plain")}</strong></div>
</div>
${r.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;">
${p(r.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: ${t?t.status===200?"var(--green-400)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${t?`HTTP ${t.status} · ${t.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;">${t?p(JSON.stringify(t.data,null,2)):'// Click "Read Resource Content" to inspect live payload'}</pre>
</div>
</div>
</div>
</div>
`}function io(o){let e=o.prompts||[],n=o.selectedPromptId||(e.length>0?e[0].id:null),r=e.find((s)=>s.id===n),t=o.promptGetResult,i="";if(e.length===0)i=`
<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 i=e.map((s)=>{let c=s.id===n?"active":"",l=s.arguments?s.arguments.length:0;return`
<div class="cap-item ${c}" onclick="window.app.selectPrompt('${p(s.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;">${p(s.name||s.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(168, 85, 247, 0.15); color: var(--purple-400);">${l} args</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">${p(s.description||s.title||"Prompt template")}</div>
<div style="font-size: 10px; color: var(--text-muted); margin-top: 4px;">server: ${p(s.server||"local")}</div>
</div>
`}).join("");let a="";if(r&&r.arguments&&r.arguments.length>0)a=r.arguments.map((s)=>`
<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);">${p(s.name)}</label>
${s.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>
${s.description?`<div style="font-size: 11px; color: var(--text-dim); margin-bottom: 4px;">${p(s.description)}</div>`:""}
<input type="text" class="form-input prompt-arg-input" data-arg-name="${p(s.name)}" placeholder="Enter ${p(s.name)}..." />
</div>
`).join("");else if(r)a=`
<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 ${e.length} prompts..." oninput="window.app.filterPrompts(this.value)">
</div>
<div style="flex: 1; overflow-y: auto; padding: 8px;" id="pg-prompt-list">
${i}
</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);">
${p(r?r.name||r.id:"No Prompt Selected")}
</div>
<div style="font-size: 11.5px; color: var(--text-dim);">
${p(r?r.description||r.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()" ${r?"":"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>
${a}
</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: ${t?t.status===200?"var(--green-400)":"var(--red-400)":"var(--text-dim)"}; font-family: var(--ff-mono);">
${t?`HTTP ${t.status} · ${t.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;">${t?p(JSON.stringify(t.data,null,2)):'// Click "Render Prompt Messages" to view resolved system/user messages'}</pre>
</div>
</div>
</div>
</div>
`}function no(o){let e=o.capabilities||[],n=o.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;">
${n.map((t,i)=>{let a=e.find((f)=>f.id===t.capability_id),s=a?.input_schema,c=s?.properties||{},l=Array.isArray(s?.required)?s.required:[],g=Object.entries(c),v=e.map((f)=>`
<option value="${p(f.id)}" ${f.id===t.capability_id?"selected":""}>
${p(f.id)} (${p(f.server||"local")})
</option>
`).join(""),u="";if(g.length>0)u=`
<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>
${g.map(([f,h])=>{let _=l.includes(f),A=h.type||(h.enum?"enum":"any");return`
<span style="font-size: 9.5px; font-family: var(--ff-mono); padding: 1px 5px; background: ${_?"rgba(239, 68, 68, 0.15)":"rgba(148, 163, 184, 0.1)"}; color: ${_?"var(--red-400)":"var(--text-muted)"}; border: 1px solid ${_?"rgba(239, 68, 68, 0.3)":"var(--border)"}; border-radius: 3px;" title="${p(h.description||"")}">
${p(f)} (${A}${_?" *":""})
</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 ${i+1}</span>
<span style="font-size: 11px; font-family: var(--ff-mono); color: var(--text-dim);">id: ${p(t.id)}</span>
</div>
<button class="btn btn-ghost" style="padding: 2px 8px; font-size: 11px; color: var(--red-400);" onclick="window.app.removeBatchStep(${i})">
✕ 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(${i}, this.value)">
<option value="">-- Select Capability --</option>
${v}
</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" ${t.continue_on_error?"checked":""} onchange="window.app.updateBatchStepContinueOnError(${i}, this.checked)" />
<span>Continue pipeline on step failure</span>
</label>
</div>
</div>
${u}
<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>
${a?`
<button type="button" class="btn btn-ghost" style="padding: 1px 6px; font-size: 9.5px;" onclick="window.app.fillBatchStepSampleArgs(${i})">✨ 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(${i}, '\${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(${i}, '\${steps[0].result.data}')">\${steps[0].result.data}</code>
</div>
</div>
<textarea
id="batch-step-args-${i}"
class="form-textarea"
rows="3"
style="font-size: 11px; font-family: var(--ff-mono);"
oninput="window.app.updateBatchStepArgs(${i}, this.value)"
>${p(t.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);">
${n.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 (${n.length} Steps)
</button>
</div>
</div>
</div>
</div>
`}function p(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function W(o){let e=o.approvals.filter((l)=>l.status==="pending"),n=o.approvals.filter((l)=>l.status!=="pending"),r=o.approvals.filter((l)=>l.status==="approved").length,t=o.approvals.filter((l)=>l.status==="rejected").length,i=o.config.policy?.require_approval||o.config.policy?.requireApproval||[],a=e.length===0?`
<div style="padding: 48px 24px; text-align: center; background: var(--surface-card); border-radius: var(--radius-md); border: 1px dashed var(--border);">
<div style="width: 48px; height: 48px; 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 14px; color: var(--green-400); font-size: 20px; font-weight: 700;">
✓
</div>
<div style="font-size: 15px; font-weight: 600; color: var(--text-main); margin-bottom: 6px;">All Clear — No Pending Approvals</div>
<div style="font-size: 12px; color: var(--text-dim); max-width: 520px; margin: 0 auto; line-height: 1.6;">
Tool invocations intercepted by <code style="color: var(--amber-300); font-family: var(--ff-mono);">require_approval</code> governance rules will suspend execution and appear here for operator inspection, argument modification, and cryptographic gating.
</div>
</div>
`:e.map((l)=>`
<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);">
PENDING APPROVAL
</span>
<span style="font-family: var(--ff-mono); font-size: 12px; color: var(--text-muted);">${$(l.id)}</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);">${$(l.capability_id)}</span>
<span style="font-size: 11px; color: var(--text-dim);">via <span style="color: var(--cyan-400); font-family: var(--ff-mono);">${$(l.server_id)}</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);">${new Date(l.created_at*1000).toLocaleTimeString()}</span></div>
<div style="color: var(--amber-400); margin-top: 2px;">Expires: ${new Date(l.expires_at*1000).toLocaleTimeString()}</div>
</div>
</div>
<!-- Caller Context -->
${l.context||l.request_id?`
<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);">
${l.request_id?`<div><span style="color: var(--text-dim);">Request:</span> <span style="color: var(--text-main);">${$(l.request_id)}</span></div>`:""}
${l.context?.actor_id?`<div><span style="color: var(--text-dim);">Actor:</span> <span style="color: var(--cyan-400);">${$(l.context.actor_id)}</span></div>`:""}
${l.context?.operation_id?`<div><span style="color: var(--text-dim);">Operation:</span> <span style="color: var(--text-main);">${$(l.context.operation_id)}</span></div>`:""}
${l.context?.work_item_id?`<div><span style="color: var(--text-dim);">Work Item:</span> <span style="color: var(--text-main);">${$(l.context.work_item_id)}</span></div>`:""}
</div>
`:""}
<!-- Arguments Editor -->
<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: 6px;">
Parameters (Editable before approval)
</div>
<textarea id="appr-args-${l.id}" class="form-textarea" rows="4" style="color: var(--green-400); font-family: var(--ff-mono); font-size: 11.5px; line-height: 1.4;">${$(JSON.stringify(l.sanitized_args,null,2))}</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="appr-operator-${l.id}" type="text" class="form-input" placeholder="Operator ID" value="security-operator" style="width: 200px; padding: 5px 10px; font-size: 11px;">
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<button class="btn btn-danger" onclick="window.app.promptReject('${$(l.id)}')">
✕ Reject
</button>
<button class="btn btn-primary" onclick="window.app.submitApproval('${$(l.id)}')">
✓ Approve & Execute
</button>
</div>
</div>
</div>
`).join(""),s=i.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. All non-denied capabilities execute immediately.
</div>
`:i.map((l)=>`
<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️ ${$(l)}</span>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 5px;">GATED</span>
</div>
`).join(""),c=n.length===0?`
<div style="padding: 24px; text-align: center; color: var(--text-dim); font-size: 12px;">
No historical operator decisions recorded in this session.
</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;">Approval ID</th>
<th style="padding: 10px 14px;">Operator</th>
<th style="padding: 10px 14px;">Reason / Notes</th>
<th style="padding: 10px 14px; text-align: right;">Timestamp</th>
</tr>
</thead>
<tbody>
${n.map((l)=>`
<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="${l.status==="approved"?"background: rgba(52, 211, 153, 0.12); color: var(--green-400); border-color: rgba(52, 211, 153, 0.3);":l.status==="rejected"?"background: rgba(248, 113, 113, 0.12); color: var(--red-400); border-color: rgba(248, 113, 113, 0.3);":"background: var(--surface-hover); color: var(--text-dim);"}">
${l.status.toUpperCase()}
</span>
</td>
<td style="padding: 10px 14px; font-weight: 600; color: var(--text-main);">${$(l.capability_id)}</td>
<td style="padding: 10px 14px; color: var(--text-dim); font-size: 10.5px;">${$(l.id)}</td>
<td style="padding: 10px 14px; color: var(--text-muted);">${$(l.operator||"system")}</td>
<td style="padding: 10px 14px; color: ${l.reason?"var(--red-400)":"var(--text-dim)"};">${l.reason?`"${$(l.reason)}"`:"—"}</td>
<td style="padding: 10px 14px; text-align: right; color: var(--text-dim);">${new Date(l.created_at*1000).toLocaleTimeString()}</td>
</tr>
`).join("")}
</tbody>
</table>
</div>
`;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: ${e.length>0?"var(--amber-300)":"var(--green-400)"}; border-color: ${e.length>0?"rgba(245, 158, 11, 0.4)":"rgba(52, 211, 153, 0.4)"}; background: ${e.length>0?"rgba(245, 158, 11, 0.1)":"rgba(52, 211, 153, 0.1)"};">
${e.length} PENDING DECISION${e.length===1?"":"S"}
</span>
<span style="font-size: 12px; color: var(--text-dim);">
Inspect, parameter-tweak, and approve or reject sensitive capability executions in real-time.
</span>
</div>
<button class="btn btn-ghost" onclick="window.app.refreshApprovals()" 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 Queue
</button>
</div>
<!-- Top Bento Metrics (Full 12-column span) -->
<div class="bento-grid">
<div class="bento-card col-3">
<div class="stat-label">Pending Decisions</div>
<div class="stat-value" style="color: ${e.length>0?"var(--amber-400)":"var(--text-main)"};">${e.length}</div>
<div class="stat-sub">Suspended in-flight executions</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Approved Executions</div>
<div class="stat-value" style="color: var(--green-400);">${r}</div>
<div class="stat-sub">Operator sanctioned calls</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Rejected Requests</div>
<div class="stat-value" style="color: var(--red-400);">${t}</div>
<div class="stat-sub">Blocked & reported to agent</div>
</div>
<div class="bento-card col-3">
<div class="stat-label">Active Gating Rules</div>
<div class="stat-value" style="color: var(--cyan-400);">${i.length}</div>
<div class="stat-sub">require_approval patterns</div>
</div>
</div>
<!-- Main Content Bento Split (8 cols queue / 4 cols rules) -->
<div class="bento-grid">
<!-- Left Column: Pending 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 Decision (${e.length})</span>
</div>
<div>
${a}
</div>
</div>
<!-- Right Column: Active Rules & Guidelines -->
<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;">
${s}
</div>
<div class="bento-card">
<div class="stat-label" style="margin-bottom: 8px;">Security Notice</div>
<div style="font-size: 11.5px; color: var(--text-dim); line-height: 1.5;">
Warmplane enforces an HMAC-SHA256 signature on external webhook approval dispatches. Intercepted payloads are securely held until sanctioned by an operator.
</div>
</div>
</div>
</div>
<!-- Bottom History (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 Recent Decision History (${n.length})
</div>
</div>
${c}
</div>
`}function $(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function E(){let o=d.getState(),e=o.auditEvents||[],n=o.auditStats||{total_events:0,by_status:{success:0,failed:0,denied:0,intercepted:0}},r=o.auditVerification,t=o.auditFilters,i=o.auditTotal??e.length,a=o.auditSelectedEvent,s=Object.keys(o.config?.mcpServers||{}),c=t.limit||25,l=t.offset||0,g=Math.floor(l/c)+1,v=Math.max(1,Math.ceil(i/c)),u=i===0?0:l+1,f=Math.min(l+c,i),h=y.getAuditExportUrl({actor_id:t.search?void 0:void 0,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},"csv"),_=y.getAuditExportUrl({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},"jsonl"),A=r?r.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 (${r.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 #${r.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>
`,w=s.map((m)=>`<option value="${x(m)}" ${t.serverId===m?"selected":""}>${x(m)}</option>`).join(""),S=`
<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="${x(t.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" ${t.status==="all"?"selected":""}>All Statuses</option>
<option value="success" ${t.status==="success"?"selected":""}>\uD83D\uDFE2 Success</option>
<option value="denied" ${t.status==="denied"?"selected":""}>\uD83D\uDD34 Denied</option>
<option value="intercepted" ${t.status==="intercepted"?"selected":""}>\uD83D\uDFE1 HITL Intercept</option>
<option value="failed" ${t.status==="failed"?"selected":""}>❌ Failed</option>
<option value="cancelled" ${t.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" ${t.eventType==="all"?"selected":""}>All Event Types</option>
<option value="tool_execution" ${t.eventType==="tool_execution"?"selected":""}>Tool Execution</option>
<option value="tool_intercepted_hitl" ${t.eventType==="tool_intercepted_hitl"?"selected":""}>HITL Intercept</option>
<option value="approval_granted" ${t.eventType==="approval_granted"?"selected":""}>Approval Granted</option>
<option value="approval_rejected" ${t.eventType==="approval_rejected"?"selected":""}>Approval Rejected</option>
<option value="approval_expired" ${t.eventType==="approval_expired"?"selected":""}>Approval Expired</option>
<option value="policy_violation" ${t.eventType==="policy_violation"?"selected":""}>Policy Violation</option>
<option value="config_mutation" ${t.eventType==="config_mutation"?"selected":""}>Config Mutation</option>
<option value="sampling_call" ${t.eventType==="sampling_call"?"selected":""}>Sampling Call</option>
<option value="resource_access" ${t.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" ${t.serverId==="all"?"selected":""}>All MCP Servers</option>
${w}
</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>
`,O=`
<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);">${u}–${f}</strong> of <strong style="color: var(--text-main);">${i}</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" ${c===10?"selected":""}>10 / page</option>
<option value="25" ${c===25?"selected":""}>25 / page</option>
<option value="50" ${c===50?"selected":""}>50 / page</option>
<option value="100" ${c===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;"
${g<=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;"
${g<=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 ${g} of ${v}
</span>
<button
class="btn btn-ghost"
style="padding: 4px 8px; font-size: 11px; height: 28px;"
${g>=v?'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;"
${g>=v?'disabled style="opacity: 0.4; cursor: not-allowed;"':""}
onclick="window.app.auditGoToPage(${v})"
title="Last Page"
>
Last ⏭
</button>
</div>
</div>
`,z="";if(e.length===0)z=`
<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 z=e.map((m)=>{let N=new Date(Math.floor(m.timestamp_ns/1e6)).toLocaleString(),j='<span class="badge" style="background: rgba(34, 197, 94, 0.15); color: var(--green-400); font-weight: 600;">SUCCESS</span>';if(m.status==="denied")j='<span class="badge" style="background: rgba(239, 68, 68, 0.15); color: var(--red-400); font-weight: 600;">DENIED</span>';else if(m.status==="intercepted")j='<span class="badge" style="background: rgba(234, 179, 8, 0.15); color: var(--amber-300); font-weight: 600;">HITL INTERCEPT</span>';else if(m.status==="failed")j='<span class="badge" style="background: rgba(239, 68, 68, 0.15); color: var(--red-400); font-weight: 600;">FAILED</span>';else if(m.status==="cancelled")j='<span class="badge" style="background: rgba(148, 163, 184, 0.15); color: var(--text-muted); font-weight: 600;">CANCELLED</span>';let I=m.sanitized_args?JSON.stringify(m.sanitized_args):"-",Z=m.actor_id||m.operator_id||"anonymous",V=m.server_id||"system",H=m.capability_id||m.event_type,oo=m.execution_latency_us?`${(m.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);">${x(m.id)}</span>
${j}
<span style="font-size: 12px; font-weight: 600; color: var(--text-main);">${x(H)}</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);">${x(N)}</div>
<button
class="btn btn-ghost"
style="padding: 2px 8px; font-size: 11px; height: 24px;"
onclick="window.app.selectAuditEvent('${x(m.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);">${x(Z)}</strong></div>
<div><span style="color: var(--text-muted);">Server:</span> <strong style="color: var(--cyan-400);">${x(V)}</strong></div>
<div><span style="color: var(--text-muted);">Trace:</span> <code style="color: var(--cyan-400); font-size: 10.5px;">${x(m.trace_id)}</code></div>
<div><span style="color: var(--text-muted);">Latency:</span> <span style="color: var(--amber-300);">${oo}</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> ${x(I)}
</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> ${x(m.prev_hash.slice(0,16))}...</div>
<div><span style="color: var(--text-dim);">hash:</span> <span style="color: var(--green-400);">${x(m.hash.slice(0,16))}...</span></div>
</div>
</div>
`}).join("");let T="";if(a){let m=new Date(Math.floor(a.timestamp_ns/1e6)).toISOString();T=`
<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 (${x(a.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;">${x(m)}</strong></div>
<div><span style="color: var(--text-muted);">Status:</span> <strong style="color: var(--text-main);">${x(a.status.toUpperCase())}</strong></div>
<div><span style="color: var(--text-muted);">Event Type:</span> <strong style="color: var(--text-main);">${x(a.event_type)}</strong></div>
<div><span style="color: var(--text-muted);">Server:</span> <strong style="color: var(--cyan-400);">${x(a.server_id||"system")}</strong></div>
<div><span style="color: var(--text-muted);">Capability:</span> <strong style="color: var(--text-main);">${x(a.capability_id||"-")}</strong></div>
<div><span style="color: var(--text-muted);">Actor / Operator:</span> <strong style="color: var(--text-main);">${x(a.actor_id||a.operator_id||"anonymous")}</strong></div>
<div><span style="color: var(--text-muted);">Trace ID:</span> <code style="color: var(--cyan-400);">${x(a.trace_id)}</code></div>
<div><span style="color: var(--text-muted);">Request ID:</span> <code style="color: var(--cyan-400);">${x(a.request_id||"-")}</code></div>
<div><span style="color: var(--text-muted);">Client IP:</span> <span style="color: var(--text-main);">${x(a.client_ip||"-")}</span></div>
<div><span style="color: var(--text-muted);">Latency:</span> <span style="color: var(--amber-300);">${a.execution_latency_us?`${(a.execution_latency_us/1000).toFixed(2)} ms`:"-"}</span></div>
</div>
${a.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 (${x(a.error_code||"ERROR")}):</div>
<div style="font-family: var(--ff-mono); font-size: 11px;">${x(a.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;">${x(JSON.stringify(a.sanitized_args||{},null,2))}</pre>
</div>
<!-- Sanitized Response -->
${a.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;">${x(JSON.stringify(a.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;">${x(a.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;">${x(a.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;">
${A}
<a href="${h}" 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="${_}" 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;">${n.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;">${n.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;">${n.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;">${n.by_status.denied}</div>
</div>
</div>
<!-- Search & Filter Toolbar -->
${S}
<!-- 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);">${e.length} events loaded on this page</span>
</div>
<!-- Event Rows -->
<div>
${z}
</div>
<!-- Pagination Footer -->
${i>0?O:""}
<!-- Modal Popup for Event Inspection -->
${T}
`}function x(o){if(!o)return"";return String(o).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")}function G(){let e=d.getState().config.policy||{},n=e.allow||[],r=e.deny||[],t=e.redact_keys||e.redactKeys||[],i=e.require_approval||e.requireApproval||[],a=n.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">No allow list (all non-denied operations permitted)</div>
`:n.map((g,v)=>`
<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);">✔ ${L(g)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('allow', ${v})">✕</button>
</div>
`).join(""),s=r.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">No deny rules configured</div>
`:r.map((g,v)=>`
<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);">✖ ${L(g)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('deny', ${v})">✕</button>
</div>
`).join(""),c=i.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">No human-in-the-loop approval rules configured</div>
`:i.map((g,v)=>`
<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️ ${L(g)}</span>
<button class="btn btn-ghost" style="padding: 2px 6px; font-size: 11px; color: var(--red-400);" onclick="window.app.removePolicyRule('requireApproval', ${v})">✕</button>
</div>
`).join(""),l=t.length===0?`
<div style="color: var(--text-dim); font-size: 12px;">No key redaction patterns configured</div>
`:t.map((g,v)=>`
<span class="brand-badge" style="color: var(--amber-300); padding: 5px 10px; font-size: 11px; display: inline-flex; align-items: center; gap: 6px;">
${L(g)}
<span style="cursor: pointer; color: var(--red-400); font-weight: bold;" onclick="window.app.removePolicyRule('redact', ${v})">✕</span>
</span>
`).join("");return`
<!-- Sub-header -->
<div style="margin-bottom: 16px; font-size: 12px; color: var(--text-dim);">
Wildcard capability access control, human-in-the-loop triggers, and sensitive key masking.
</div>
<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;">
${a}
</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;">
${s}
</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;">
${c}
</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;">
${l}
</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>
<!-- 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 L(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function J(){let o=d.getState(),e=o.config,n=Object.entries(e.capabilityAliases||{}),r=Object.entries(e.resourceAliases||{}),t=Object.entries(e.promptAliases||{}),i="";if(n.length===0&&r.length===0&&t.length===0)i=`
<div style="padding: 24px; text-align: center; color: var(--text-dim);">
No facade aliases configured in ${C(o.configPath)}. Add short names to prune token payload sizes.
</div>
`;else{for(let[a,s]of n)i+=`
<div class="feed-row" style="grid-template-columns: 90px 180px 1fr 80px;">
<span style="color: var(--cyan-400);">Tool</span>
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">${C(a)}</span>
<span style="color: var(--text-dim); font-family: var(--ff-mono);">${C(s)}</span>
<div style="text-align: right;">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('tool', '${C(a)}')">✕</button>
</div>
</div>
`;for(let[a,s]of r)i+=`
<div class="feed-row" style="grid-template-columns: 90px 180px 1fr 80px;">
<span style="color: var(--green-400);">Resource</span>
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">${C(a)}</span>
<span style="color: var(--text-dim); font-family: var(--ff-mono);">${C(s)}</span>
<div style="text-align: right;">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('resource', '${C(a)}')">✕</button>
</div>
</div>
`;for(let[a,s]of t)i+=`
<div class="feed-row" style="grid-template-columns: 90px 180px 1fr 80px;">
<span style="color: var(--amber-300);">Prompt</span>
<span style="font-weight: 700; color: var(--text-main); font-family: var(--ff-mono);">${C(a)}</span>
<span style="color: var(--text-dim); font-family: var(--ff-mono);">${C(s)}</span>
<div style="text-align: right;">
<button class="btn btn-ghost" style="padding: 2px 6px; color: var(--red-400);" onclick="window.app.deleteAlias('prompt', '${C(a)}')">✕</button>
</div>
</div>
`}return`
<!-- Sub-header -->
<div style="margin-bottom: 16px; font-size: 12px; color: var(--text-dim);">
Shorten capability IDs to prune prompt tokens and create stable public interfaces.
</div>
<!-- Quick Add Form -->
<div class="bento-card" style="margin-bottom: 20px; overflow: visible;">
<div class="stat-header" style="margin-bottom: 12px;">
<span class="stat-label">Create New Alias</span>
</div>
<div style="display: grid; grid-template-columns: 140px 1fr 1fr 100px; gap: 10px; align-items: center; position: relative;">
<select class="form-input" id="alias-kind">
<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. db.query)" 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. docker.list_containers)" 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 class="btn btn-primary" onclick="window.app.createAlias()">+ Save</button>
</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: 90px 180px 1fr 80px; 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</span>
<span style="text-align: right;">ACTION</span>
</div>
${i}
</div>
`}function C(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function Q(){let o=d.getState(),e=o.config,n=e.profiles||{},r=Object.entries(n),t=e.mcpServers||{},i=o.activeProfile,a="";if(r.length===0)a=`
<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 a=r.map(([s,c])=>{let l=i===s,g=c.servers.map((u)=>{return`<span class="brand-badge" style="${t[u]?"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);"}">${R(u)}</span>`}).join(" "),v=(o.capabilities||[]).filter((u)=>c.servers.includes(u.server)).length;return`
<div class="bento-card" style="margin-bottom: 14px; border-left: ${l?"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);">${R(s)}</span>
${l?'<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">${c.servers.length} server${c.servers.length===1?"":"s"}</span>
<span class="brand-badge" style="color: var(--text-dim);">${v} capabilities</span>
</div>
<div style="font-size: 12px; color: var(--text-muted); margin-bottom: 10px;">
${R(c.description||"No description provided")}
</div>
<div style="display: flex; flex-wrap: wrap; gap: 6px; align-items: center;">
<span style="font-size: 11px; color: var(--text-dim); font-weight: 600; text-transform: uppercase;">Servers:</span>
${g||'<span style="font-size: 11px; color: var(--text-dim);">None</span>'}
</div>
</div>
<div style="display: flex; gap: 8px; align-items: center;">
${l?`
<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('${R(s)}')">
Activate in UI
</button>
`}
<button class="btn btn-ghost" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.openEditProfileModal('${R(s)}')">
✏️ Edit
</button>
<button class="btn btn-danger" style="padding: 4px 10px; font-size: 11.5px;" onclick="window.app.deleteProfile('${R(s)}')">
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>
${a}
`}function R(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}var q=[{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:["mcp-server-git","--repository","."],argsPlaceholder:"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:["mcp-server-sentry"],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-server"],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:["mcp-server-sqlite","--db-path","./app.db"],argsPlaceholder:"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"],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:"uvx / Key-Value",command:"uvx",defaultArgs:["mcp-server-redis","--url","redis://localhost:6379"],argsPlaceholder:"mcp-server-redis --url 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:"uvx / Cloud Storage",command:"uvx",defaultArgs:["mcp-server-s3","--bucket","my-bucket-name"],argsPlaceholder:"mcp-server-s3 --bucket bucket-name --region us-east-1",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","@modelcontextprotocol/server-notion"],envFields:[{key:"NOTION_API_KEY",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:["mcp-server-jira","--url","https://your-domain.atlassian.net","--email","user@example.com"],argsPlaceholder:"mcp-server-jira --url https://org.atlassian.net --email me@org.com",envFields:[{key:"JIRA_API_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:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-gdrive"],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:"Official / Stdio",command:"npx",defaultArgs:["-y","@modelcontextprotocol/server-kubernetes"],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"],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:"uvx / IaC",command:"uvx",defaultArgs:["mcp-server-terraform"],envFields:[]}];class Y{activeTemplateCategory="all";activeTemplateFilter="";selectedTemplate=null;async init(){let o=window.location.port?`:${window.location.port}`:"",e=document.getElementById("daemon-port-label");if(e)e.textContent=`Daemon ${o}`;await this.refreshData(),this.initSSE(),this.render(),d.subscribe(()=>{this.render()})}auditSearchTimeout=null;async refreshData(){try{let o=d.getState(),e=o.auditFilters,n=o.activeProfile||void 0,[r,t,i,a,s,c,l,g]=await Promise.all([y.getConfig(),y.listCapabilities(n),y.listResources(n),y.listPrompts(n),y.getCatalogEvents(),y.listApprovals(),y.listAuditEvents({server_id:e.serverId!=="all"?e.serverId:void 0,event_type:e.eventType!=="all"?e.eventType:void 0,status:e.status!=="all"?e.status:void 0,search:e.search.trim()?e.search.trim():void 0,limit:e.limit,offset:e.offset}),y.getAuditStats()]);if(r.ok)d.setState({configPath:r.config_path,config:r.config,serverStatuses:r.server_statuses||{},circuitBreakers:r.circuit_breakers||[],metrics:{totalCatalogRequests:r.metrics?.total_catalog_requests||0,totalEtagHits:r.metrics?.total_etag_hits||0,totalToolCalls:r.metrics?.total_tool_calls||0,totalToolDurationUs:r.metrics?.total_tool_duration_us||0}});if(t&&Array.isArray(t.capabilities))d.setState({capabilities:t.capabilities});if(i&&Array.isArray(i.resources))d.setState({resources:i.resources});if(a&&Array.isArray(a.prompts))d.setState({prompts:a.prompts});if(s&&Array.isArray(s.events))d.setState({catalogEvents:s.events});if(c&&Array.isArray(c.approvals))d.setState({approvals:c.approvals});if(l&&Array.isArray(l.events))d.setState({auditEvents:l.events,auditTotal:l.total??l.events.length});if(g&&g.ok)d.setState({auditStats:g})}catch(o){console.error("Failed to fetch daemon state:",o)}}async refreshAuditEvents(){try{let e=d.getState().auditFilters,[n,r]=await Promise.all([y.listAuditEvents({server_id:e.serverId!=="all"?e.serverId:void 0,event_type:e.eventType!=="all"?e.eventType:void 0,status:e.status!=="all"?e.status:void 0,search:e.search.trim()?e.search.trim():void 0,limit:e.limit,offset:e.offset}),y.getAuditStats()]);if(n&&Array.isArray(n.events))d.setState({auditEvents:n.events,auditTotal:n.total??n.events.length});if(r&&r.ok)d.setState({auditStats:r})}catch(o){console.error("Failed to refresh audit events:",o)}}handleAuditSearchInput(o){let n={...d.getState().auditFilters,search:o,offset:0};d.setState({auditFilters:n}),clearTimeout(this.auditSearchTimeout),this.auditSearchTimeout=setTimeout(()=>{this.refreshAuditEvents()},250)}handleAuditStatusFilter(o){let e=d.getState();d.setState({auditFilters:{...e.auditFilters,status:o,offset:0}}),this.refreshAuditEvents()}handleAuditEventTypeFilter(o){let e=d.getState();d.setState({auditFilters:{...e.auditFilters,eventType:o,offset:0}}),this.refreshAuditEvents()}handleAuditServerFilter(o){let e=d.getState();d.setState({auditFilters:{...e.auditFilters,serverId:o,offset:0}}),this.refreshAuditEvents()}handleAuditPageSize(o){let e=parseInt(o,10)||25,n=d.getState();d.setState({auditFilters:{...n.auditFilters,limit:e,offset:0}}),this.refreshAuditEvents()}clearAuditFilters(){let o=d.getState();d.setState({auditFilters:{search:"",status:"all",eventType:"all",serverId:"all",limit:o.auditFilters.limit||25,offset:0}}),this.refreshAuditEvents()}auditPrevPage(){let o=d.getState(),{limit:e,offset:n}=o.auditFilters,r=Math.max(0,n-e);if(r!==n)d.setState({auditFilters:{...o.auditFilters,offset:r}}),this.refreshAuditEvents()}auditNextPage(){let o=d.getState(),{limit:e,offset:n}=o.auditFilters,r=o.auditTotal;if(n+e<r)d.setState({auditFilters:{...o.auditFilters,offset:n+e}}),this.refreshAuditEvents()}auditGoToPage(o){let e=d.getState(),{limit:n}=e.auditFilters,r=Math.max(0,(o-1)*n);d.setState({auditFilters:{...e.auditFilters,offset:r}}),this.refreshAuditEvents()}selectAuditEvent(o){if(!o){d.setState({auditSelectedEvent:null});return}let n=d.getState().auditEvents.find((r)=>r.id===o)||null;d.setState({auditSelectedEvent:n})}async verifyAuditChain(){try{let o=await y.verifyAuditChain();if(o&&o.report)d.setState({auditVerification:o.report})}catch(o){console.error("Failed to verify audit chain:",o)}}async refreshApprovals(){try{let o=await y.listApprovals();if(o&&Array.isArray(o.approvals))d.setState({approvals:o.approvals})}catch(o){console.error("Failed to refresh approvals:",o)}}initSSE(){try{let o=new EventSource("/v1/resources/updates");o.onmessage=(e)=>{d.addEventLog("SSE","/v1/resources/updates","UPDATED","0.1ms"),this.refreshData()}}catch(o){console.warn("SSE connection unavailable")}}switchTab(o){d.setState({activeTab:o}),this.refreshData()}render(){let o=d.getState(),e=document.getElementById("app-main");if(!e)return;let n=o.approvals.filter((a)=>a.status==="pending").length,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((a)=>{if(a.getAttribute("data-tab")===o.activeTab)a.classList.add("active");else a.classList.remove("active")});let t=document.getElementById("top-title"),i={overview:"Overview Cockpit",servers:"Server Hub & Connections",playground:"MCP Capability Playground",approvals:"Human-in-the-Loop Review Queue",audit:"WORM Audit & Compliance Ledger",policy:"Security Governance & Redaction",aliases:"Facade & Alias Studio",profiles:"Server Constellation Profiles"};if(t)t.textContent=i[o.activeTab];switch(this.renderTopProfileSelector(),o.activeTab){case"overview":e.innerHTML=D();break;case"servers":e.innerHTML=K();break;case"playground":e.innerHTML=B();break;case"approvals":e.innerHTML=W(o);break;case"audit":e.innerHTML=E();break;case"policy":e.innerHTML=G();break;case"aliases":e.innerHTML=J();break;case"profiles":e.innerHTML=Q();break}}async submitApproval(o){let e=document.getElementById(`appr-operator-${o}`),n=document.getElementById(`appr-args-${o}`),r=e?.value.trim()||"security-operator",t=void 0;if(n&&n.value.trim())try{t=JSON.parse(n.value.trim())}catch{alert("Invalid JSON in arguments editor");return}let i=await y.approveTicket(o,r,t);if(i.ok)await this.refreshApprovals();else alert(`Approval failed: ${i.error||"Unknown error"}`)}async promptReject(o){let e=prompt("Reason for rejection (will be returned to the calling agent):");if(e===null)return;let r=document.getElementById(`appr-operator-${o}`)?.value.trim()||"security-operator",t=await y.rejectTicket(o,r,e);if(t.ok)await this.refreshApprovals();else alert(`Rejection failed: ${t.error||"Unknown error"}`)}setPlaygroundMode(o){d.setState({playgroundMode:o})}selectCapability(o){d.setState({selectedCapabilityId:o});let e=d.getState().capabilities.find((r)=>r.id===o),n=document.getElementById("pg-args-input");if(e){let r=U(e.input_schema,!1),t=JSON.stringify(r,null,2);if(n)n.value=t;let i={...d.getState().playgroundArgs||{}};i[o]=t,d.getState().playgroundArgs=i}}selectResource(o){d.setState({selectedResourceId:o})}selectPrompt(o){d.setState({selectedPromptId:o})}filterResources(o){let e=o.toLowerCase().trim(),r=(d.getState().resources||[]).filter((i)=>i.id.toLowerCase().includes(e)||i.name&&i.name.toLowerCase().includes(e)||i.uri&&i.uri.toLowerCase().includes(e)||i.server&&i.server.toLowerCase().includes(e)),t=document.getElementById("pg-res-list");if(t)if(r.length===0)t.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No resources match "${b(o)}"
</div>
`;else t.innerHTML=r.map((i)=>{let a=i.id===d.getState().selectedResourceId?"active":"",s=i.uri?i.uri.split(":")[0]:"res";return`
<div class="cap-item ${a}" onclick="window.app.selectResource('${b(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;">${b(i.name||i.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(56, 189, 248, 0.15); color: var(--cyan-400);">${b(s)}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">${b(i.uri)}</div>
<div style="display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted); margin-top: 4px;">
<span>server: ${b(i.server||"local")}</span>
<span>${b(i.mime_type||"text/plain")}</span>
</div>
</div>
`}).join("")}filterPrompts(o){let e=o.toLowerCase().trim(),r=(d.getState().prompts||[]).filter((i)=>i.id.toLowerCase().includes(e)||i.name&&i.name.toLowerCase().includes(e)||i.description&&i.description.toLowerCase().includes(e)||i.server&&i.server.toLowerCase().includes(e)),t=document.getElementById("pg-prompt-list");if(t)if(r.length===0)t.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No prompts match "${b(o)}"
</div>
`;else t.innerHTML=r.map((i)=>{let a=i.id===d.getState().selectedPromptId?"active":"",s=i.arguments?i.arguments.length:0;return`
<div class="cap-item ${a}" onclick="window.app.selectPrompt('${b(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;">${b(i.name||i.id)}</span>
<span class="badge" style="font-size: 9.5px; background: rgba(168, 85, 247, 0.15); color: var(--purple-400);">${s} args</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">${b(i.description||i.title||"Prompt template")}</div>
<div style="font-size: 10px; color: var(--text-muted); margin-top: 4px;">server: ${b(i.server||"local")}</div>
</div>
`}).join("")}updatePlaygroundArgs(o){let e=d.getState(),n=e.selectedCapabilityId||(e.capabilities[0]?e.capabilities[0].id:null);if(!n)return;let r={...e.playgroundArgs||{}};r[n]=o,e.playgroundArgs=r}fillPlaygroundSampleArgs(o=!1){let e=d.getState(),n=e.selectedCapabilityId||(e.capabilities[0]?e.capabilities[0].id:null),r=e.capabilities.find((s)=>s.id===n),t=document.getElementById("pg-args-input");if(!t)return;if(!r||!r.input_schema){if(t.value="{}",n){let s={...e.playgroundArgs||{}};s[n]="{}",e.playgroundArgs=s}return}let i=U(r.input_schema,o),a=JSON.stringify(i,null,2);if(t.value=a,n){let s={...e.playgroundArgs||{}};s[n]=a,e.playgroundArgs=s}}formatPlaygroundArgs(){let o=d.getState(),e=o.selectedCapabilityId||(o.capabilities[0]?o.capabilities[0].id:null),n=document.getElementById("pg-args-input");if(n)try{let r=JSON.parse(n.value||"{}"),t=JSON.stringify(r,null,2);if(n.value=t,e){let i={...o.playgroundArgs||{}};i[e]=t,o.playgroundArgs=i}}catch(r){alert(`Cannot format JSON: ${r.message}`)}}insertPlaygroundArgKey(o,e,n){let r=d.getState(),t=r.selectedCapabilityId||(r.capabilities[0]?r.capabilities[0].id:null),i=document.getElementById("pg-args-input");if(i){let a={};try{a=JSON.parse(i.value||"{}")}catch{a={}}if(a[o]===void 0)if(n!==null&&n!==void 0)a[o]=n;else switch(e){case"string":a[o]=`sample_${o}`;break;case"number":case"integer":a[o]=0;break;case"boolean":a[o]=!0;break;case"array":a[o]=[];break;case"object":a[o]={};break;default:a[o]=`sample_${o}`}let s=JSON.stringify(a,null,2);if(i.value=s,t){let c={...r.playgroundArgs||{}};c[t]=s,r.playgroundArgs=c}}}fillBatchStepSampleArgs(o){let e=d.getState(),n=[...e.batchSteps||[]],r=n[o];if(!r||!r.capability_id)return;let t=e.capabilities.find((c)=>c.id===r.capability_id);if(!t||!t.input_schema)return;let i=t.input_schema.properties||{},a={};for(let[c,l]of Object.entries(i))if(l.default!==void 0)a[c]=l.default;else if(Array.isArray(l.enum)&&l.enum.length>0)a[c]=l.enum[0];else switch(l.type||"string"){case"string":a[c]=`sample_${c}`;break;case"number":case"integer":a[c]=0;break;case"boolean":a[c]=!0;break;case"array":a[c]=[];break;case"object":a[c]={};break;default:a[c]=`sample_${c}`}let s=JSON.stringify(a,null,2);n[o]={...n[o],argsJson:s},d.setState({batchSteps:n})}filterCapabilities(o){let e=o.toLowerCase().trim(),r=d.getState().capabilities.filter((i)=>i.id.toLowerCase().includes(e)||i.summary&&i.summary.toLowerCase().includes(e)||i.server&&i.server.toLowerCase().includes(e)),t=document.getElementById("pg-cap-list");if(t)if(r.length===0)t.innerHTML=`
<div style="padding: 24px 16px; text-align: center; color: var(--text-dim); font-size: 11.5px;">
No capabilities match "${b(o)}"
</div>
`;else t.innerHTML=r.map((i)=>`
<div class="cap-item ${i.id===d.getState().selectedCapabilityId?"active":""}" onclick="window.app.selectCapability('${b(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;">${b(i.id)}</span>
<span style="font-size: 10px; color: var(--green-400);">${b(i.mode||"read")}</span>
</div>
<div style="font-size: 11px; color: var(--text-dim); margin-top: 2px;">server: ${b(i.server||"local")}</div>
</div>
`).join("")}async executePlaygroundTool(){let o=d.getState(),e=o.selectedCapabilityId||(o.capabilities[0]?o.capabilities[0].id:null);if(!e)return;let n=document.getElementById("pg-args-input")?.value||"{}",r=document.getElementById("pg-context-input")?.value||void 0,t=document.getElementById("pg-jsonpath-input")?.value.trim()||void 0,i=document.getElementById("pg-limit-lines-input")?.value.trim()||void 0,a=document.getElementById("pg-truncate-bytes-input")?.value.trim()||void 0,s={};try{s=JSON.parse(n)}catch{alert("Invalid arguments JSON object");return}if(t)s._jsonpath=t;if(i&&!isNaN(Number(i)))s._limit_lines=Number(i);if(a&&!isNaN(Number(a)))s._truncate_bytes=Number(a);let c=`op-${Date.now()}`;d.setState({isExecuting:!0,activeRequestId:c});let l=o.activeProfile||void 0;try{let g=await y.callCapability({capability_id:e,args:s,request_id:c,context:{operation_id:r||c}},l);d.setState({isExecuting:!1,activeRequestId:null,executionResult:{status:g.status,durationMs:g.durationMs,data:g.data}}),d.addEventLog("POST",`/v1/tools/call → ${e}`,g.status===200?"200 OK":`HTTP ${g.status}`,`${g.durationMs.toFixed(1)}ms`),y.getConfig().then((v)=>{if(v.ok&&v.circuit_breakers)d.setState({circuitBreakers:v.circuit_breakers})})}catch(g){d.setState({isExecuting:!1,activeRequestId:null,executionResult:{status:500,durationMs:0,data:{error:g.toString()}}})}}openBatchModal(){let o=d.getState(),e=o.batchSteps;if(!e||e.length===0)e=[{id:"step_1",capability_id:o.selectedCapabilityId||(o.capabilities[0]?o.capabilities[0].id:""),argsJson:"{}",continue_on_error:!1},{id:"step_2",capability_id:"",argsJson:"{}",continue_on_error:!0}],d.setState({batchSteps:e});d.setState({isBatchModalOpen:!0})}closeBatchModal(){d.setState({isBatchModalOpen:!1})}addBatchStep(){let e=[...d.getState().batchSteps||[]],n=e.length+1;e.push({id:`step_${n}`,capability_id:"",argsJson:"{}",continue_on_error:!1}),d.setState({batchSteps:e})}removeBatchStep(o){let n=[...d.getState().batchSteps||[]];if(n.length<=1){alert("Pipeline must contain at least one execution step.");return}n.splice(o,1);let r=n.map((t,i)=>({...t,id:`step_${i+1}`}));d.setState({batchSteps:r})}updateBatchStepCapability(o,e){let r=[...d.getState().batchSteps||[]];if(r[o])r[o]={...r[o],capability_id:e},d.setState({batchSteps:r})}updateBatchStepContinueOnError(o,e){let r=[...d.getState().batchSteps||[]];if(r[o])r[o]={...r[o],continue_on_error:e},d.setState({batchSteps:r})}updateBatchStepArgs(o,e){let n=d.getState(),r=[...n.batchSteps||[]];if(r[o])r[o]={...r[o],argsJson:e},n.batchSteps[o].argsJson=e}appendBatchVariable(o,e){let r=[...d.getState().batchSteps||[]],t=document.getElementById(`batch-step-args-${o}`);if(t){let i=t.value,a=t.selectionStart||i.length,s=t.selectionEnd||i.length,c=i.substring(0,a)+e+i.substring(s);if(t.value=c,r[o])r[o]={...r[o],argsJson:c},d.setState({batchSteps:r})}}async executeBatchPipeline(){let o=d.getState(),e=o.batchSteps||[],n=[];for(let t=0;t<e.length;t++){let i=e[t];if(!i.capability_id){alert(`Please select a capability for Step ${t+1}`);return}let a={};try{a=JSON.parse(i.argsJson||"{}")}catch{alert(`Invalid JSON in Step ${t+1} arguments`);return}n.push({id:i.id||`step_${t+1}`,capability_id:i.capability_id,args:a,continue_on_error:i.continue_on_error})}d.setState({isBatchModalOpen:!1});let r=o.activeProfile||void 0;try{let t=await y.batchCallCapabilities(n,r);d.setState({executionResult:{status:t.status,durationMs:t.durationMs,data:t.data}}),d.addEventLog("POST",`/v1/tools/batch_call (${e.length} steps)`,t.status===200?"200 OK":`HTTP ${t.status}`,`${t.durationMs.toFixed(1)}ms`)}catch(t){d.setState({executionResult:{status:500,durationMs:0,data:{error:t.toString()}}})}}async executeReadResource(){let o=d.getState(),e=o.selectedResourceId||(o.resources[0]?o.resources[0].id:null);if(!e)return;let n=document.getElementById("pg-res-jsonpath-input")?.value.trim()||void 0,r=document.getElementById("pg-res-lines-input")?.value.trim()||void 0,t=document.getElementById("pg-res-bytes-input")?.value.trim()||void 0,i={resource_id:e};if(n)i._jsonpath=n;if(r&&!isNaN(Number(r)))i._limit_lines=Number(r);if(t&&!isNaN(Number(t)))i._truncate_bytes=Number(t);let a=o.activeProfile||void 0;try{let s=await y.readResource({resource_id:e,input_responses:i},a);d.setState({resourceReadResult:{status:s.status,durationMs:s.durationMs,data:s.data}}),d.addEventLog("POST",`/v1/resources/read → ${e}`,s.status===200?"200 OK":`HTTP ${s.status}`,`${s.durationMs.toFixed(1)}ms`)}catch(s){d.setState({resourceReadResult:{status:500,durationMs:0,data:{error:s.toString()}}})}}async executeGetPrompt(){let o=d.getState(),e=o.selectedPromptId||(o.prompts[0]?o.prompts[0].id:null);if(!e)return;let n=document.querySelectorAll(".prompt-arg-input"),r={};n.forEach((i)=>{let a=i,s=a.getAttribute("data-arg-name");if(s&&a.value.trim())r[s]=a.value.trim()});let t=o.activeProfile||void 0;try{let i=await y.getPrompt({prompt_id:e,arguments:r},t);d.setState({promptGetResult:{status:i.status,durationMs:i.durationMs,data:i.data}}),d.addEventLog("POST",`/v1/prompts/get → ${e}`,i.status===200?"200 OK":`HTTP ${i.status}`,`${i.durationMs.toFixed(1)}ms`)}catch(i){d.setState({promptGetResult:{status:500,durationMs:0,data:{error:i.toString()}}})}}toggleBatchPlayground(){let o=document.getElementById("pg-args-input");if(!o)return;let e=[{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}];o.value=JSON.stringify(e,null,2)}async submitPolicyRule(o){let e=o==="allow"?"policy-new-allow":o==="deny"?"policy-new-deny":"policy-new-redact",n=document.getElementById(e);if(!n)return;let r=n.value.trim();if(!r)return;await this.addPolicyRule(o,r),n.value=""}async addPolicyRule(o,e){let n=(e||"").trim();if(!n)return;let t=d.getState().config.policy||{},i=[...t.allow||[]],a=[...t.deny||[]],s=[...t.redact_keys||t.redactKeys||[]];if(o==="allow"&&!i.includes(n))i.push(n);if(o==="deny"&&!a.includes(n))a.push(n);if(o==="redact"&&!s.includes(n))s.push(n);let c=await y.savePolicy({allow:i,deny:a,redact_keys:s,redactKeys:s});if(!c.ok)alert(`Failed to save policy rule: ${c.error||"Unknown error"}`);await this.refreshData()}async removePolicyRule(o,e){let r=d.getState().config.policy||{},t=[...r.allow||[]],i=[...r.deny||[]],a=[...r.redact_keys||r.redactKeys||[]];if(o==="allow")t.splice(e,1);if(o==="deny")i.splice(e,1);if(o==="redact")a.splice(e,1);let s=await y.savePolicy({allow:t,deny:i,redact_keys:a,redactKeys:a});if(!s.ok)alert(`Failed to update policy: ${s.error||"Unknown error"}`);await this.refreshData()}testPolicySandbox(o){let e=document.getElementById("policy-test-verdict");if(!e)return;let n=o.trim();if(!n){e.textContent="ENTER ID",e.style.color="var(--text-dim)";return}let t=d.getState().config.policy||{},i=t.deny||[],a=t.allow||[],s=(c,l)=>{if(c==="*")return!0;if(c.endsWith("*"))return l.startsWith(c.slice(0,-1));return c===l};if(i.some((c)=>s(c,n))){e.textContent="DENIED (Strict Block)",e.style.color="var(--red-400)";return}if(a.length>0&&!a.some((c)=>s(c,n))){e.textContent="DENIED (Not in Allow List)",e.style.color="var(--red-400)";return}e.textContent="ALLOWED",e.style.color="var(--green-400)"}async deleteServer(o){if(!confirm(`Are you sure you want to remove server '${o}' from config?`))return;await y.deleteServer(o),await this.refreshData()}openAddServerModal(){this.closeModals();let o=document.getElementById("modal-srv-title"),e=document.getElementById("modal-srv-template-banner"),n=document.getElementById("modal-srv-name"),r=document.getElementById("modal-srv-transport"),t=document.getElementById("modal-srv-command"),i=document.getElementById("modal-srv-url"),a=document.getElementById("modal-srv-ft"),s=document.getElementById("modal-srv-cd"),c=document.getElementById("modal-srv-autorestart"),l=document.getElementById("modal-srv-maxrestarts");if(o)o.textContent="Add Upstream MCP Server";if(e)e.style.display="flex";if(n)n.value="",n.disabled=!1;if(r)r.value="stdio";if(t)t.value="";if(i)i.value="";let g=document.getElementById("modal-group-cmd"),v=document.getElementById("modal-group-url");if(g)g.style.display="block";if(v)v.style.display="none";if(a)a.value="3";if(s)s.value="30000";if(c)c.value="true";if(l)l.value="5";let u=document.getElementById("modal-add-server");if(u)u.classList.add("active")}openEditServerModal(o){this.closeModals();let e=d.getState(),n=e.config.mcpServers?.[o];if(!n){alert(`Server '${o}' not found in configuration.`);return}let r=document.getElementById("modal-srv-title"),t=document.getElementById("modal-srv-template-banner"),i=document.getElementById("modal-srv-name"),a=document.getElementById("modal-srv-transport"),s=document.getElementById("modal-srv-command"),c=document.getElementById("modal-srv-url"),l=document.getElementById("modal-srv-ft"),g=document.getElementById("modal-srv-cd"),v=document.getElementById("modal-srv-autorestart"),u=document.getElementById("modal-srv-maxrestarts");if(r)r.textContent=`Edit Server '${o}'`;if(t)t.style.display="none";if(i)i.value=o,i.disabled=!0;let f=!!n.command;if(a)a.value=f?"stdio":"http";let h=document.getElementById("modal-group-cmd"),_=document.getElementById("modal-group-url");if(h)h.style.display=f?"block":"none";if(_)_.style.display=f?"none":"block";if(s)s.value=f?`${n.command} ${(n.args||[]).join(" ")}`.trim():"";if(c)c.value=n.url||"";let A=n.resilience||e.config.resilience;if(l)l.value=String(A?.failureThreshold??3);if(g)g.value=String(A?.cooldownMs??30000);if(v)v.value=A?.autoRestart===!1?"false":"true";if(u)u.value=String(A?.maxRestarts??5);let w=document.getElementById("modal-add-server");if(w)w.classList.add("active")}async submitAddServer(){let o=document.getElementById("modal-srv-name"),e=o?.value.trim(),n=document.getElementById("modal-srv-transport")?.value;if(!e){alert("Server name is required");return}if(o&&!o.disabled){if((d.getState().config.mcpServers||{})[e]){if(!confirm(`Server '${e}' already exists in configuration. Do you want to overwrite it?`))return}}let r={};if(n==="stdio"){let g=(document.getElementById("modal-srv-command")?.value.trim()).split(/\s+/).filter(Boolean);if(g.length===0){alert("Command is required");return}r.command=g[0],r.args=g.slice(1)}else{let l=document.getElementById("modal-srv-url")?.value.trim();if(!l){alert("URL is required");return}r.url=l}let t=document.getElementById("modal-srv-ft")?.value.trim(),i=document.getElementById("modal-srv-cd")?.value.trim(),a=document.getElementById("modal-srv-autorestart")?.value,s=document.getElementById("modal-srv-maxrestarts")?.value.trim();if(t||i||a||s)r.resilience={failureThreshold:t?Number(t):3,cooldownMs:i?Number(i):30000,autoRestart:a!=="false",maxRestarts:s?Number(s):5};let c=await y.upsertServer(e,r);if(c.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${c.error}`)}openTemplateCatalog(){this.closeModals();let o=document.getElementById("modal-templates");if(o)o.classList.add("active");this.renderTemplateGrid()}setTemplateCategory(o){this.activeTemplateCategory=o,document.querySelectorAll(".tmpl-cat-btn").forEach((e)=>{if(e.getAttribute("data-category")===o)e.classList.add("active"),e.style.background="var(--surface-elevated)",e.style.color="var(--amber-400)";else e.classList.remove("active"),e.style.background="var(--surface-card)",e.style.color="var(--text-main)"}),this.renderTemplateGrid()}filterTemplates(o){this.activeTemplateFilter=o.toLowerCase().trim(),this.renderTemplateGrid()}renderTemplateGrid(){let o=document.getElementById("tmpl-grid");if(!o)return;let e=q.filter((t)=>{let i=this.activeTemplateCategory==="all"||t.category===this.activeTemplateCategory,a=!this.activeTemplateFilter||t.name.toLowerCase().includes(this.activeTemplateFilter)||t.id.toLowerCase().includes(this.activeTemplateFilter)||t.description.toLowerCase().includes(this.activeTemplateFilter)||t.command.toLowerCase().includes(this.activeTemplateFilter)||t.envFields.some((s)=>s.key.toLowerCase().includes(this.activeTemplateFilter));return i&&a});if(e.length===0){o.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 r=d.getState().config.mcpServers||{};o.innerHTML=e.map((t)=>{let i=!!r[t.id],a=`${t.command} ${t.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;">${b(t.name)}</span>
<span class="brand-badge" style="font-size: 9.5px; padding: 1px 6px; flex-shrink: 0;">${b(t.badge)}</span>
</div>
${i?'<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;">
${b(t.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>${b(a)}</code>
</div>
${t.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;">${t.envFields.map((s)=>b(s.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('${b(t.id)}')">
${i?"Configure Another":"✨ 1-Click Setup"}
</button>
</div>
</div>
`}).join("")}selectTemplate(o){let e=q.find((c)=>c.id===o);if(!e)return;this.selectedTemplate=e,this.closeModals();let n=document.getElementById("modal-configure-template");if(n)n.classList.add("active");let r=document.getElementById("cfg-tmpl-title"),t=document.getElementById("cfg-tmpl-desc"),i=document.getElementById("cfg-tmpl-form");if(r)r.textContent=`Configure ${e.name} Server`;if(t)t.textContent=e.description;let a=d.getState().config.mcpServers||{},s=e.id;if(a[s]){let c=2;while(a[`${e.id}-${c}`])c++;s=`${e.id}-${c}`}if(i){let c="";if(e.envFields.length>0)c=`
<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>
${e.envFields.map((l)=>`
<div class="form-group">
<label class="form-label">${b(l.label)} ${l.required?'<span style="color: var(--red-400);">*</span>':"(Optional)"}</label>
<input type="password" class="form-input tmpl-env-input" data-key="${b(l.key)}" placeholder="${b(l.placeholder||"")}">
${l.description?`<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 3px;">${b(l.description)}</div>`:""}
</div>
`).join("")}
`;i.innerHTML=`
<div class="form-group">
<label class="form-label">Server Identifier (Name)</label>
<input type="text" class="form-input" id="cfg-srv-id" value="${b(s)}">
<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="${b(e.defaultArgs.join(" "))}" placeholder="${b(e.argsPlaceholder||"")}">
<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 3px;">Executable: <code>${b(e.command)}</code></div>
</div>
${c}
<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 o=this.selectedTemplate,e=document.getElementById("cfg-srv-id")?.value.trim(),n=document.getElementById("cfg-srv-args")?.value.trim();if(!e){alert("Server identifier is required");return}if((d.getState().config.mcpServers||{})[e]){if(!confirm(`Server '${e}' already exists. Do you want to overwrite its configuration?`))return}let i=n?n.split(/\s+/).filter(Boolean):[],a={},s=document.querySelectorAll(".tmpl-env-input");for(let h of Array.from(s)){let _=h.getAttribute("data-key"),A=h.value.trim(),w=o.envFields.find((S)=>S.key===_);if(w?.required&&!A){alert(`Required field '${w.label}' is missing.`);return}if(_&&A)a[_]=A}let c={command:o.command,args:i};if(Object.keys(a).length>0)c.env=a;let l=document.getElementById("cfg-srv-ft")?.value.trim(),g=document.getElementById("cfg-srv-cd")?.value.trim(),v=document.getElementById("cfg-srv-autorestart")?.value,u=document.getElementById("cfg-srv-maxrestarts")?.value.trim();if(l||g||v||u)c.resilience={failureThreshold:l?Number(l):3,cooldownMs:g?Number(g):30000,autoRestart:v!=="false",maxRestarts:u?Number(u):5};let f=await y.upsertServer(e,c);if(f.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save server: ${f.error}`)}async openImportModal(){this.closeModals();let o=document.getElementById("modal-import");if(o)o.classList.add("active");let e=document.getElementById("modal-eco-list");if(!e)return;e.innerHTML='<div style="color: var(--text-dim); padding: 12px; text-align: center;">Scanning IDE configs...</div>';try{let n=await y.getEcosystemSources();if(n.sources&&n.sources.length>0)e.innerHTML=n.sources.map((r)=>`
<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="${r.path}" checked>
<div>
<div style="font-weight: 600; color: var(--text-main);">${r.name}</div>
<div style="font-size: 11px; color: var(--text-dim);">${r.server_count} servers (${r.servers.join(", ")})</div>
</div>
</label>
`).join("");else e.innerHTML='<div style="color: var(--text-dim); padding: 12px; text-align: center;">No external MCP configuration files found on this system.</div>'}catch{e.innerHTML='<div style="color: var(--red-400); padding: 12px; text-align: center;">Failed to scan ecosystem sources.</div>'}}async submitImport(){let o=document.querySelectorAll(".eco-checkbox:checked");if(o.length===0){alert("No sources selected");return}for(let e of Array.from(o))await y.importConfig(e.value,!1);this.closeModals(),await this.refreshData()}handleAliasTargetInput(o){let e=document.getElementById("alias-suggestions-dropdown");if(!e)return;let n=(o||"").trim().toLowerCase();if(n.length<2){e.style.display="none";return}let t=d.getState().capabilities.filter((i)=>i.id.toLowerCase().includes(n)||i.summary&&i.summary.toLowerCase().includes(n)||i.description&&i.description.toLowerCase().includes(n)||i.server&&i.server.toLowerCase().includes(n)).slice(0,8);if(t.length===0){e.style.display="none";return}e.innerHTML=t.map((i)=>`
<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('${b(i.id)}')">
<div>
<div style="font-weight: 700; color: var(--text-main);">${b(i.id)}</div>
<div style="font-size: 10.5px; color: var(--text-dim); margin-top: 2px;">${b(i.summary||i.description||"")}</div>
</div>
<span style="font-size: 10px; color: var(--cyan-400);">${b(i.server||"local")}</span>
</div>
`).join(""),e.style.display="block"}selectAliasSuggestion(o){let e=document.getElementById("alias-target");if(e)e.value=o;this.hideAliasDropdown()}hideAliasDropdown(){let o=document.getElementById("alias-suggestions-dropdown");if(o)o.style.display="none"}async createAlias(){let o=document.getElementById("alias-kind")?.value,e=document.getElementById("alias-name")?.value.trim(),n=document.getElementById("alias-target")?.value.trim();if(!e||!n){alert("Please provide both alias name and canonical target");return}await y.updateAlias(o,e,n),await this.refreshData()}async deleteAlias(o,e){await y.updateAlias(o,e,void 0),await this.refreshData()}async reloadFromDisk(){try{let o=await y.reloadConfig();if(o.ok){let e="Hot-reload completed successfully!";if(o.mounted&&o.mounted.length>0)e+=`
Mounted: ${o.mounted.join(", ")}`;if(o.unmounted&&o.unmounted.length>0)e+=`
Unmounted: ${o.unmounted.join(", ")}`;if(o.warnings&&o.warnings.length>0)e+=`
Warnings:
${o.warnings.join(`
`)}`;alert(e)}else alert(`Hot-reload failed: ${o.error||"Unknown error"}`)}catch(o){alert(`Error reaching daemon: ${o.message}`)}await this.refreshData()}renderTopProfileSelector(){let o=document.getElementById("top-profile-selector");if(!o)return;let e=d.getState(),n=e.config.profiles||{},r=Object.keys(n),t=e.activeProfile,i='<option value="">All Servers (Unrestricted)</option>';for(let a of r){let s=t===a?"selected":"";i+=`<option value="${b(a)}" ${s}>Profile: ${b(a)}</option>`}o.innerHTML=i}async setActiveProfile(o){d.setState({activeProfile:o||null}),await this.refreshData()}openAddProfileModal(){let o=document.getElementById("modal-prof-title");if(o)o.textContent="Create Server Constellation Profile";let e=document.getElementById("modal-prof-name"),n=document.getElementById("modal-prof-desc"),r=document.getElementById("modal-prof-mode");if(e)e.value="",e.disabled=!1;if(n)n.value="";if(r)r.value="create";this.renderProfileServerCheckboxes([]);let t=document.getElementById("modal-add-profile");if(t)t.classList.add("active")}openEditProfileModal(o){let n=d.getState().config.profiles?.[o];if(!n)return;let r=document.getElementById("modal-prof-title");if(r)r.textContent=`Edit Profile: ${o}`;let t=document.getElementById("modal-prof-name"),i=document.getElementById("modal-prof-desc"),a=document.getElementById("modal-prof-mode");if(t)t.value=o,t.disabled=!0;if(i)i.value=n.description||"";if(a)a.value="edit";this.renderProfileServerCheckboxes(n.servers||[]);let s=document.getElementById("modal-add-profile");if(s)s.classList.add("active")}renderProfileServerCheckboxes(o){let e=document.getElementById("modal-prof-servers-list");if(!e)return;let n=d.getState(),r=Object.keys(n.config.mcpServers||{});if(r.length===0){e.innerHTML='<div style="font-size: 11.5px; color: var(--text-dim);">No MCP servers configured yet. Add servers first.</div>';return}e.innerHTML=r.map((t)=>{let i=o.includes(t)?"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="${b(t)}" ${i} style="accent-color: var(--amber-400);">
<span style="font-family: var(--ff-mono); font-weight: 600; color: var(--text-main);">${b(t)}</span>
</label>
`}).join("")}async saveProfile(){let o=document.getElementById("modal-prof-name"),e=document.getElementById("modal-prof-desc"),n=o?.value.trim(),r=e?.value.trim();if(!n){alert("Please enter a profile name");return}let t=document.querySelectorAll(".prof-server-checkbox:checked"),i=[];if(t.forEach((a)=>{i.push(a.value)}),i.length===0){alert("Please select at least one server to include in this constellation");return}try{let a=await y.upsertProfile(n,i,r||void 0);if(a.ok)this.closeModals(),await this.refreshData();else alert(`Failed to save profile: ${a.error||"Unknown error"}`)}catch(a){alert(`Error saving profile: ${a.message}`)}}async deleteProfile(o){if(!confirm(`Are you sure you want to delete profile '${o}'?`))return;try{let e=await y.deleteProfile(o);if(e.ok){if(d.getState().activeProfile===o)d.setState({activeProfile:null});await this.refreshData()}else alert(`Failed to delete profile: ${e.error||"Unknown error"}`)}catch(e){alert(`Error deleting profile: ${e.message}`)}}closeModals(){document.querySelectorAll(".modal-backdrop").forEach((o)=>o.classList.remove("active"))}}function b(o){return String(o||"").replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}var X=new Y;window.app=X;window.addEventListener("DOMContentLoaded",()=>X.init());