agent-relay 1.0.8 → 1.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/dist/bridge/config.d.ts +41 -0
- package/dist/bridge/config.d.ts.map +1 -0
- package/dist/bridge/config.js +143 -0
- package/dist/bridge/config.js.map +1 -0
- package/dist/bridge/index.d.ts +10 -0
- package/dist/bridge/index.d.ts.map +1 -0
- package/dist/bridge/index.js +10 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/bridge/multi-project-client.d.ts +99 -0
- package/dist/bridge/multi-project-client.d.ts.map +1 -0
- package/dist/bridge/multi-project-client.js +386 -0
- package/dist/bridge/multi-project-client.js.map +1 -0
- package/dist/bridge/spawner.d.ts +46 -0
- package/dist/bridge/spawner.d.ts.map +1 -0
- package/dist/bridge/spawner.js +223 -0
- package/dist/bridge/spawner.js.map +1 -0
- package/dist/bridge/types.d.ts +55 -0
- package/dist/bridge/types.d.ts.map +1 -0
- package/dist/bridge/types.js +6 -0
- package/dist/bridge/types.js.map +1 -0
- package/dist/bridge/utils.d.ts +30 -0
- package/dist/bridge/utils.d.ts.map +1 -0
- package/dist/bridge/utils.js +54 -0
- package/dist/bridge/utils.js.map +1 -0
- package/dist/cli/index.js +564 -5
- package/dist/cli/index.js.map +1 -1
- package/dist/daemon/agent-registry.d.ts.map +1 -1
- package/dist/daemon/agent-registry.js +6 -1
- package/dist/daemon/agent-registry.js.map +1 -1
- package/dist/daemon/connection.d.ts +22 -0
- package/dist/daemon/connection.d.ts.map +1 -1
- package/dist/daemon/connection.js +59 -13
- package/dist/daemon/connection.js.map +1 -1
- package/dist/daemon/router.d.ts +27 -0
- package/dist/daemon/router.d.ts.map +1 -1
- package/dist/daemon/router.js +108 -3
- package/dist/daemon/router.js.map +1 -1
- package/dist/daemon/server.d.ts +8 -0
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +95 -23
- package/dist/daemon/server.js.map +1 -1
- package/dist/dashboard/metrics.d.ts +105 -0
- package/dist/dashboard/metrics.d.ts.map +1 -0
- package/dist/dashboard/metrics.js +192 -0
- package/dist/dashboard/metrics.js.map +1 -0
- package/dist/dashboard/needs-attention.d.ts +24 -0
- package/dist/dashboard/needs-attention.d.ts.map +1 -0
- package/dist/dashboard/needs-attention.js +78 -0
- package/dist/dashboard/needs-attention.js.map +1 -0
- package/dist/dashboard/public/bridge.html +1272 -0
- package/dist/dashboard/public/index.html +2017 -879
- package/dist/dashboard/public/js/app.js +184 -0
- package/dist/dashboard/public/js/app.js.map +7 -0
- package/dist/dashboard/public/metrics.html +999 -0
- package/dist/dashboard/server.d.ts +13 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +568 -13
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/start.js +1 -1
- package/dist/dashboard/start.js.map +1 -1
- package/dist/dashboard-v2/index.d.ts +10 -0
- package/dist/dashboard-v2/index.d.ts.map +1 -0
- package/dist/dashboard-v2/index.js +54 -0
- package/dist/dashboard-v2/index.js.map +1 -0
- package/dist/dashboard-v2/lib/api.d.ts +95 -0
- package/dist/dashboard-v2/lib/api.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/api.js +270 -0
- package/dist/dashboard-v2/lib/api.js.map +1 -0
- package/dist/dashboard-v2/lib/colors.d.ts +61 -0
- package/dist/dashboard-v2/lib/colors.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/colors.js +198 -0
- package/dist/dashboard-v2/lib/colors.js.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts +74 -0
- package/dist/dashboard-v2/lib/hierarchy.d.ts.map +1 -0
- package/dist/dashboard-v2/lib/hierarchy.js +196 -0
- package/dist/dashboard-v2/lib/hierarchy.js.map +1 -0
- package/dist/dashboard-v2/types/index.d.ts +154 -0
- package/dist/dashboard-v2/types/index.d.ts.map +1 -0
- package/dist/dashboard-v2/types/index.js +6 -0
- package/dist/dashboard-v2/types/index.js.map +1 -0
- package/dist/storage/adapter.d.ts +21 -1
- package/dist/storage/adapter.d.ts.map +1 -1
- package/dist/storage/adapter.js +36 -0
- package/dist/storage/adapter.js.map +1 -1
- package/dist/storage/sqlite-adapter.d.ts +34 -0
- package/dist/storage/sqlite-adapter.d.ts.map +1 -1
- package/dist/storage/sqlite-adapter.js +253 -12
- package/dist/storage/sqlite-adapter.js.map +1 -1
- package/dist/utils/agent-config.d.ts +45 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +118 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/wrapper/client.d.ts +8 -0
- package/dist/wrapper/client.d.ts.map +1 -1
- package/dist/wrapper/client.js +26 -0
- package/dist/wrapper/client.js.map +1 -1
- package/dist/wrapper/parser.d.ts +17 -0
- package/dist/wrapper/parser.d.ts.map +1 -1
- package/dist/wrapper/parser.js +334 -10
- package/dist/wrapper/parser.js.map +1 -1
- package/dist/wrapper/tmux-wrapper.d.ts +37 -2
- package/dist/wrapper/tmux-wrapper.d.ts.map +1 -1
- package/dist/wrapper/tmux-wrapper.js +178 -18
- package/dist/wrapper/tmux-wrapper.js.map +1 -1
- package/docs/AGENTS.md +105 -0
- package/docs/ARCHITECTURE_DECISIONS.md +175 -0
- package/docs/COMPETITIVE_ANALYSIS.md +897 -0
- package/docs/DESIGN_BRIDGE_STAFFING.md +878 -0
- package/docs/MONETIZATION.md +1679 -0
- package/docs/agent-relay-snippet.md +61 -0
- package/docs/dashboard-v2-plan.md +179 -0
- package/package.json +5 -2
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
var i={agents:[],messages:[],currentChannel:"general",currentThread:null,isConnected:!1,ws:null,reconnectAttempts:0,viewMode:"local",fleetData:null},S=[];function Y(t){return S.push(t),()=>{let e=S.indexOf(t);e>-1&&S.splice(e,1)}}function h(){S.forEach(t=>t())}function G(t){i.agents=t,h()}function X(t){i.messages=t,h()}function Z(t){i.currentChannel=t,h()}function k(t){i.isConnected=t,t&&(i.reconnectAttempts=0),h()}function ee(){i.reconnectAttempts++}function te(t){i.ws=t}function ne(){let{messages:t,currentChannel:e}=i;return e==="general"?t:t.filter(n=>n.from===e||n.to===e)}function $(t){i.currentThread=t}function se(t){return i.messages.filter(e=>e.thread===t)}function ae(t){return i.messages.filter(e=>e.thread===t).length}function oe(t){i.viewMode=t,h()}function L(){return i.viewMode}function re(t){i.fleetData=t,h()}function x(){return i.fleetData}var ie=null;function B(){let t=window.location.protocol==="https:"?"wss:":"ws:",e=new WebSocket(`${t}//${window.location.host}/ws`);e.onopen=()=>{k(!0)},e.onclose=()=>{k(!1);let n=Math.min(1e3*Math.pow(2,i.reconnectAttempts),3e4);ee(),setTimeout(B,n)},e.onerror=n=>{console.error("WebSocket error:",n)},e.onmessage=n=>{try{let s=JSON.parse(n.data);Be(s)}catch(s){console.error("Failed to parse message:",s)}},te(e)}function Be(t){console.log("[WS] Received data:",{agentCount:t.agents?.length,messageCount:t.messages?.length,hasFleet:!!t.fleet}),t.agents&&(console.log("[WS] Setting agents:",t.agents.map(e=>e.name)),G(t.agents)),t.messages&&X(t.messages),t.fleet&&(console.log("[WS] Setting fleet data:",{servers:t.fleet.servers?.length,agents:t.fleet.agents?.length}),re(t.fleet)),ie&&ie(t)}async function D(t,e,n){try{let s={to:t,message:e};n&&(s.thread=n);let o=await fetch("/api/send",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(s)}),r=await o.json();return o.ok&&r.success?{success:!0}:{success:!1,error:r.error||"Failed to send message"}}catch{return{success:!1,error:"Network error - could not send message"}}}function M(t){if(!t)return!1;let e=Date.parse(t);return Number.isNaN(e)?!1:Date.now()-e<45e3}function l(t){if(!t)return"";let e=document.createElement("div");return e.textContent=t,e.innerHTML}function H(t){return new Date(t).toLocaleTimeString([],{hour:"numeric",minute:"2-digit"})}function le(t){let e=new Date(t),n=new Date,s=new Date(n);return s.setDate(s.getDate()-1),e.toDateString()===n.toDateString()?"Today":e.toDateString()===s.toDateString()?"Yesterday":e.toLocaleDateString([],{weekday:"long",month:"long",day:"numeric"})}function v(t){let e=["#e01e5a","#2bac76","#e8a427","#1264a3","#7c3aed","#0d9488","#dc2626","#9333ea","#ea580c","#0891b2"],n=0;for(let s=0;s<t.length;s++)n=t.charCodeAt(s)+((n<<5)-n);return e[Math.abs(n)%e.length]}function f(t){return t.substring(0,2).toUpperCase()}function P(t){if(!t)return"";let e=l(t);return e=e.replace(/```(\w+)?\n([\s\S]*?)```/g,(n,s,o)=>`<pre><code>${o.trim()}</code></pre>`),e=e.replace(/```([^`\n]+)```/g,"<pre><code>$1</code></pre>"),e=e.replace(/`([^`]+)`/g,"<code>$1</code>"),e=e.replace(/\*\*([^*]+)\*\*/g,"<strong>$1</strong>"),e=e.replace(/__([^_]+)__/g,"<strong>$1</strong>"),e=e.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g,"<em>$1</em>"),e}var j=[],a,d=-1;function de(){return a={connectionDot:document.getElementById("connection-dot"),channelsList:document.getElementById("channels-list"),agentsList:document.getElementById("agents-list"),messagesList:document.getElementById("messages-list"),currentChannelName:document.getElementById("current-channel-name"),channelTopic:document.getElementById("channel-topic"),onlineCount:document.getElementById("online-count"),messageInput:document.getElementById("message-input"),sendBtn:document.getElementById("send-btn"),boldBtn:document.getElementById("bold-btn"),emojiBtn:document.getElementById("emoji-btn"),searchTrigger:document.getElementById("search-trigger"),commandPaletteOverlay:document.getElementById("command-palette-overlay"),paletteSearch:document.getElementById("palette-search"),paletteResults:document.getElementById("palette-results"),paletteChannelsSection:document.getElementById("palette-channels-section"),paletteAgentsSection:document.getElementById("palette-agents-section"),paletteMessagesSection:document.getElementById("palette-messages-section"),typingIndicator:document.getElementById("typing-indicator"),threadPanelOverlay:document.getElementById("thread-panel-overlay"),threadPanelId:document.getElementById("thread-panel-id"),threadPanelClose:document.getElementById("thread-panel-close"),threadMessages:document.getElementById("thread-messages"),threadMessageInput:document.getElementById("thread-message-input"),threadSendBtn:document.getElementById("thread-send-btn"),mentionAutocomplete:document.getElementById("mention-autocomplete"),mentionAutocompleteList:document.getElementById("mention-autocomplete-list"),spawnBtn:document.getElementById("spawn-btn"),spawnModalOverlay:document.getElementById("spawn-modal-overlay"),spawnModalClose:document.getElementById("spawn-modal-close"),spawnNameInput:document.getElementById("spawn-name-input"),spawnCliInput:document.getElementById("spawn-cli-input"),spawnTaskInput:document.getElementById("spawn-task-input"),spawnSubmitBtn:document.getElementById("spawn-submit-btn"),spawnStatus:document.getElementById("spawn-status"),viewToggle:document.getElementById("view-toggle"),viewToggleLocal:document.querySelector('[data-view="local"]'),viewToggleFleet:document.querySelector('[data-view="fleet"]'),peerCount:document.getElementById("peer-count"),serversSection:document.getElementById("servers-section"),serversList:document.getElementById("servers-list")},a}function O(){return a}function ue(){i.isConnected?a.connectionDot.classList.remove("offline"):a.connectionDot.classList.add("offline")}function b(){console.log("[UI] renderAgents called, agents:",i.agents.length,i.agents.map(n=>n.name));let t=new Set(j.map(n=>n.name)),e=i.agents.map(n=>{let o=M(n.lastSeen||n.lastActive)?"online":"",r=i.currentChannel===n.name,c=n.needsAttention?"needs-attention":"",u=t.has(n.name),E=u?`
|
|
2
|
+
<svg class="spawned-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" title="Spawned from dashboard">
|
|
3
|
+
<polygon points="5 3 19 12 5 21 5 3"/>
|
|
4
|
+
</svg>
|
|
5
|
+
`:"",C=u?`
|
|
6
|
+
<button class="release-btn" title="Release agent" data-release="${l(n.name)}">
|
|
7
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
|
8
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
9
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
10
|
+
</svg>
|
|
11
|
+
</button>
|
|
12
|
+
`:"";return`
|
|
13
|
+
<li class="channel-item ${r?"active":""} ${c}" data-agent="${l(n.name)}" ${u?'title="Spawned from dashboard"':""}>
|
|
14
|
+
<div class="agent-avatar" style="background: ${u?"var(--accent-green)":v(n.name)}">
|
|
15
|
+
${f(n.name)}
|
|
16
|
+
<span class="presence-indicator ${o}"></span>
|
|
17
|
+
</div>
|
|
18
|
+
<span class="channel-name">${l(n.name)}</span>
|
|
19
|
+
${E}
|
|
20
|
+
${n.needsAttention?'<span class="attention-badge">Needs Input</span>':""}
|
|
21
|
+
${C}
|
|
22
|
+
</li>
|
|
23
|
+
`}).join("");a.agentsList.innerHTML=e||'<li class="channel-item" style="color: var(--text-muted); cursor: default;">No agents connected</li>',a.agentsList.querySelectorAll(".channel-item[data-agent]").forEach(n=>{n.addEventListener("click",s=>{if(s.target.closest(".release-btn"))return;let o=n.dataset.agent;o&&g(o)})}),a.agentsList.querySelectorAll(".release-btn[data-release]").forEach(n=>{n.addEventListener("click",async s=>{s.stopPropagation();let o=n.dataset.release;o&&confirm(`Release agent "${o}"? This will terminate the agent.`)&&await Le(o)})}),me()}function q(){let t=ne();if(t.length===0){a.messagesList.innerHTML=`
|
|
24
|
+
<div class="empty-state">
|
|
25
|
+
<svg class="empty-state-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
26
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
27
|
+
</svg>
|
|
28
|
+
<div class="empty-state-title">No messages yet</div>
|
|
29
|
+
<div class="empty-state-text">
|
|
30
|
+
${i.currentChannel==="general"?"Messages between agents will appear here":`Messages with ${i.currentChannel} will appear here`}
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
`;return}let e="",n=null;t.forEach(s=>{let o=new Date(s.timestamp).toDateString();o!==n&&(e+=`
|
|
34
|
+
<div class="date-divider">
|
|
35
|
+
<span class="date-divider-text">${le(s.timestamp)}</span>
|
|
36
|
+
</div>
|
|
37
|
+
`,n=o);let r=s.to==="*",c=v(s.from),u=ae(s.id),E=r?"@everyone":s.project?`<span class="project-badge">${l(s.project)}</span>@${l(s.to)}`:`@${l(s.to)}`;e+=`
|
|
38
|
+
<div class="message ${r?"broadcast":""}" data-id="${l(s.id)}">
|
|
39
|
+
<div class="message-avatar" style="background: ${c}">
|
|
40
|
+
${f(s.from)}
|
|
41
|
+
</div>
|
|
42
|
+
<div class="message-content">
|
|
43
|
+
<div class="message-header">
|
|
44
|
+
<span class="message-sender">@${l(s.from)}</span>
|
|
45
|
+
<span class="message-recipient">
|
|
46
|
+
\u2192 <span class="target">${E}</span>
|
|
47
|
+
</span>
|
|
48
|
+
<span class="message-timestamp">${H(s.timestamp)}</span>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="message-body">${P(s.content)}</div>
|
|
51
|
+
${s.thread?`
|
|
52
|
+
<div class="thread-indicator" data-thread="${l(s.thread)}">
|
|
53
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
54
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
55
|
+
</svg>
|
|
56
|
+
Thread: ${l(s.thread)}
|
|
57
|
+
</div>
|
|
58
|
+
`:""}
|
|
59
|
+
${u>0?`
|
|
60
|
+
<div class="reply-count-badge" data-thread="${l(s.id)}">
|
|
61
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
62
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
63
|
+
</svg>
|
|
64
|
+
${u} ${u===1?"reply":"replies"}
|
|
65
|
+
</div>
|
|
66
|
+
`:""}
|
|
67
|
+
</div>
|
|
68
|
+
<div class="message-actions">
|
|
69
|
+
<button class="message-action-btn" data-action="reply" title="Reply in thread">
|
|
70
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
71
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
72
|
+
</svg>
|
|
73
|
+
</button>
|
|
74
|
+
<button class="message-action-btn" title="Add reaction">
|
|
75
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
76
|
+
<circle cx="12" cy="12" r="10"/>
|
|
77
|
+
<path d="M8 14s1.5 2 4 2 4-2 4-2"/>
|
|
78
|
+
<line x1="9" y1="9" x2="9.01" y2="9"/>
|
|
79
|
+
<line x1="15" y1="9" x2="15.01" y2="9"/>
|
|
80
|
+
</svg>
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
`}),a.messagesList.innerHTML=e,He()}function g(t){Z(t),a.channelsList.querySelectorAll(".channel-item").forEach(n=>{n.classList.toggle("active",n.dataset.channel===t)}),a.agentsList.querySelectorAll(".channel-item").forEach(n=>{n.classList.toggle("active",n.dataset.agent===t)});let e=document.querySelector(".channel-header-name .prefix");if(t==="general")a.currentChannelName.innerHTML="general",a.channelTopic.textContent="All agent communications",e&&(e.textContent="#");else{a.currentChannelName.innerHTML=l(t);let n=i.agents.find(s=>s.name===t);a.channelTopic.textContent=n?.status||"Direct messages",e&&(e.textContent="@")}a.messageInput.placeholder=t==="general"?"@AgentName message... (or @* to broadcast)":`Message ${t}... (@ not required)`,q()}function pe(){let t=i.agents.filter(e=>M(e.lastSeen||e.lastActive)).length;a.onlineCount.textContent=`${t} online`}function me(){let t=i.agents.map(s=>{let o=M(s.lastSeen||s.lastActive);return`
|
|
85
|
+
<div class="palette-item" data-jump-agent="${l(s.name)}">
|
|
86
|
+
<div class="palette-item-icon">
|
|
87
|
+
<div class="agent-avatar" style="background: ${v(s.name)}; width: 20px; height: 20px; font-size: 9px;">
|
|
88
|
+
${f(s.name)}
|
|
89
|
+
<span class="presence-indicator ${o?"online":""}"></span>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
<div class="palette-item-content">
|
|
93
|
+
<div class="palette-item-title">${l(s.name)}</div>
|
|
94
|
+
<div class="palette-item-subtitle">${o?"Online":"Offline"}</div>
|
|
95
|
+
</div>
|
|
96
|
+
</div>
|
|
97
|
+
`}).join(""),e=a.paletteAgentsSection;e.querySelectorAll(".palette-item").forEach(s=>s.remove()),e.insertAdjacentHTML("beforeend",t),e.querySelectorAll(".palette-item[data-jump-agent]").forEach(s=>{s.addEventListener("click",()=>{let o=s.dataset.jumpAgent;o&&(g(o),m())})})}function ge(){a.paletteChannelsSection.querySelectorAll(".palette-item[data-jump-channel]").forEach(t=>{t.addEventListener("click",()=>{let e=t.dataset.jumpChannel;e&&(g(e),m())})})}function V(){a.commandPaletteOverlay.classList.add("visible"),a.paletteSearch.value="",a.paletteSearch.focus(),d=-1,R("")}function ve(){return Array.from(a.paletteResults.querySelectorAll(".palette-item")).filter(e=>e.style.display!=="none")}function ce(){let t=ve();if(t.forEach(e=>e.classList.remove("selected")),d>=0&&d<t.length){let e=t[d];e.classList.add("selected"),e.scrollIntoView({block:"nearest",behavior:"smooth"})}}function fe(t){let e=ve();if(e.length!==0)switch(t.key){case"ArrowDown":t.preventDefault(),d=d<e.length-1?d+1:0,ce();break;case"ArrowUp":t.preventDefault(),d=d>0?d-1:e.length-1,ce();break;case"Enter":t.preventDefault(),d>=0&&d<e.length&&De(e[d]);break}}function De(t){let e=t.dataset.command;if(e){e==="broadcast"?(a.messageInput.value="@* ",a.messageInput.focus()):e==="clear"&&(a.messagesList.innerHTML=""),m();return}let n=t.dataset.jumpChannel;if(n){g(n),m();return}let s=t.dataset.jumpAgent;if(s){g(s),m();return}let o=t.dataset.jumpMessage;if(o){let r=a.messagesList.querySelector(`[data-id="${o}"]`);r&&(r.scrollIntoView({behavior:"smooth",block:"center"}),r.classList.add("highlighted"),setTimeout(()=>r.classList.remove("highlighted"),2e3)),m();return}}function m(){a.commandPaletteOverlay.classList.remove("visible")}function R(t){let e=t.toLowerCase();if(d=-1,document.querySelectorAll(".palette-item[data-command]").forEach(n=>{let o=n.querySelector(".palette-item-title")?.textContent?.toLowerCase()||"";n.style.display=o.includes(e)?"flex":"none"}),document.querySelectorAll(".palette-item[data-jump-channel]").forEach(n=>{let o=n.querySelector(".palette-item-title")?.textContent?.toLowerCase()||"";n.style.display=o.includes(e)?"flex":"none"}),document.querySelectorAll(".palette-item[data-jump-agent]").forEach(n=>{let s=n.dataset.jumpAgent?.toLowerCase()||"";n.style.display=s.includes(e)?"flex":"none"}),e.length>=2){let n=i.messages.filter(s=>s.content.toLowerCase().includes(e)).slice(0,5);if(n.length>0){a.paletteMessagesSection.style.display="block";let s=n.map(r=>`
|
|
98
|
+
<div class="palette-item" data-jump-message="${l(r.id)}">
|
|
99
|
+
<div class="palette-item-icon">
|
|
100
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
101
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
102
|
+
</svg>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="palette-item-content">
|
|
105
|
+
<div class="palette-item-title">${l(r.from)}</div>
|
|
106
|
+
<div class="palette-item-subtitle">${l(r.content.substring(0,60))}${r.content.length>60?"...":""}</div>
|
|
107
|
+
</div>
|
|
108
|
+
</div>
|
|
109
|
+
`).join("");a.paletteMessagesSection.querySelectorAll(".palette-item").forEach(r=>r.remove()),a.paletteMessagesSection.insertAdjacentHTML("beforeend",s)}else a.paletteMessagesSection.style.display="none"}else a.paletteMessagesSection.style.display="none"}function F(t){$(t),a.threadPanelId.textContent=t,a.threadPanelOverlay.classList.add("visible"),a.threadMessageInput.value="",_(t),a.threadMessageInput.focus()}function K(){$(null),a.threadPanelOverlay.classList.remove("visible")}function _(t){let e=se(t);if(e.length===0){a.threadMessages.innerHTML=`
|
|
110
|
+
<div class="thread-empty">
|
|
111
|
+
<p>No messages in this thread yet.</p>
|
|
112
|
+
<p style="font-size: 12px; margin-top: 8px;">Start the conversation below!</p>
|
|
113
|
+
</div>
|
|
114
|
+
`;return}let n=e.map(s=>`
|
|
115
|
+
<div class="thread-message">
|
|
116
|
+
<div class="thread-message-header">
|
|
117
|
+
<div class="thread-message-avatar" style="background: ${v(s.from)}">
|
|
118
|
+
${f(s.from)}
|
|
119
|
+
</div>
|
|
120
|
+
<span class="thread-message-sender">${l(s.from)}</span>
|
|
121
|
+
<span class="thread-message-time">${H(s.timestamp)}</span>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="thread-message-body">${P(s.content)}</div>
|
|
124
|
+
</div>
|
|
125
|
+
`).join("");a.threadMessages.innerHTML=n,a.threadMessages.scrollTop=a.threadMessages.scrollHeight}function He(){a.messagesList.querySelectorAll(".thread-indicator").forEach(t=>{t.style.cursor="pointer",t.addEventListener("click",e=>{e.stopPropagation();let n=t.dataset.thread;n&&F(n)})}),a.messagesList.querySelectorAll(".reply-count-badge").forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let n=t.dataset.thread;n&&F(n)})}),a.messagesList.querySelectorAll('.message-action-btn[data-action="reply"]').forEach(t=>{t.addEventListener("click",e=>{e.stopPropagation();let n=t.closest(".message")?.getAttribute("data-id");n&&F(n)})})}var p=0,A=[];function he(t){let e=t.toLowerCase();A=i.agents.filter(s=>s.name.toLowerCase().includes(e)),p=0;let n="";("*".includes(e)||"everyone".includes(e)||"all".includes(e)||"broadcast".includes(e))&&(n+=`
|
|
126
|
+
<div class="mention-autocomplete-item ${p===0&&A.length===0?"selected":""}" data-mention="*">
|
|
127
|
+
<div class="agent-avatar" style="background: var(--accent-yellow);">*</div>
|
|
128
|
+
<span class="mention-autocomplete-name">@everyone</span>
|
|
129
|
+
<span class="mention-autocomplete-role">Broadcast to all</span>
|
|
130
|
+
</div>
|
|
131
|
+
`),A.forEach((s,o)=>{n+=`
|
|
132
|
+
<div class="mention-autocomplete-item ${o===p?"selected":""}" data-mention="${l(s.name)}">
|
|
133
|
+
<div class="agent-avatar" style="background: ${v(s.name)}">
|
|
134
|
+
${f(s.name)}
|
|
135
|
+
</div>
|
|
136
|
+
<span class="mention-autocomplete-name">@${l(s.name)}</span>
|
|
137
|
+
<span class="mention-autocomplete-role">${l(s.role||"Agent")}</span>
|
|
138
|
+
</div>
|
|
139
|
+
`}),n===""&&(n='<div class="mention-autocomplete-item" style="color: var(--text-muted); cursor: default;">No matching agents</div>'),a.mentionAutocompleteList.innerHTML=n,a.mentionAutocomplete.classList.add("visible"),a.mentionAutocompleteList.querySelectorAll(".mention-autocomplete-item[data-mention]").forEach(s=>{s.addEventListener("click",()=>{let o=s.dataset.mention;o&&W(o)})})}function y(){a.mentionAutocomplete.classList.remove("visible"),A=[],p=0}function ye(){return a.mentionAutocomplete.classList.contains("visible")}function U(t){let e=a.mentionAutocompleteList.querySelectorAll(".mention-autocomplete-item[data-mention]");e.length!==0&&(e[p]?.classList.remove("selected"),t==="down"?p=(p+1)%e.length:p=(p-1+e.length)%e.length,e[p]?.classList.add("selected"),e[p]?.scrollIntoView({block:"nearest"}))}function W(t){let e=a.mentionAutocompleteList.querySelectorAll(".mention-autocomplete-item[data-mention]"),n=t;if(!n&&e.length>0&&(n=e[p]?.dataset.mention),!n){y();return}let s=a.messageInput,o=s.value,r=o.match(/^@\S*/);if(r){let c=`@${n} `;s.value=c+o.substring(r[0].length),s.selectionStart=s.selectionEnd=c.length}y(),s.focus()}function we(){let t=a.messageInput,e=t.value,n=t.selectionStart,s=e.match(/^@(\S*)/);return s&&n<=s[0].length?s[1]:null}function Ee(){a.spawnModalOverlay.classList.add("visible"),a.spawnNameInput.value="",a.spawnCliInput.value="claude",a.spawnTaskInput.value="",a.spawnStatus.textContent="",a.spawnStatus.className="spawn-status",a.spawnNameInput.focus()}function w(){a.spawnModalOverlay.classList.remove("visible")}async function z(){let t=a.spawnNameInput.value.trim(),e=a.spawnCliInput.value.trim()||"claude",n=a.spawnTaskInput.value.trim();if(!t)return a.spawnStatus.textContent="Agent name is required",a.spawnStatus.className="spawn-status error",{success:!1,error:"Agent name is required"};a.spawnSubmitBtn.disabled=!0,a.spawnStatus.textContent="Spawning agent...",a.spawnStatus.className="spawn-status loading";try{let s=await fetch("/api/spawn",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({name:t,cli:e,task:n})}),o=await s.json();if(s.ok&&o.success)return a.spawnStatus.textContent=`Agent "${t}" spawned successfully!`,a.spawnStatus.className="spawn-status success",await T(),setTimeout(()=>{w()},1e3),{success:!0};throw new Error(o.error||"Failed to spawn agent")}catch(s){return a.spawnStatus.textContent=s.message||"Failed to spawn agent",a.spawnStatus.className="spawn-status error",{success:!1,error:s.message}}finally{a.spawnSubmitBtn.disabled=!1}}async function T(){try{let e=await(await fetch("/api/spawned")).json();e.success&&Array.isArray(e.agents)&&(j=e.agents,b())}catch(t){console.error("[UI] Failed to fetch spawned agents:",t)}}async function Le(t){try{let n=await(await fetch(`/api/spawned/${encodeURIComponent(t)}`,{method:"DELETE"})).json();n.success?await T():console.error("[UI] Failed to release agent:",n.error)}catch(e){console.error("[UI] Failed to release agent:",e)}}function Me(){a.viewToggleLocal?.addEventListener("click",()=>{N("local")}),a.viewToggleFleet?.addEventListener("click",()=>{N("fleet")})}function N(t){oe(t),a.viewToggleLocal?.classList.toggle("active",t==="local"),a.viewToggleFleet?.classList.toggle("active",t==="fleet"),a.serversSection&&(a.serversSection.style.display=t==="fleet"?"block":"none"),b(),t==="fleet"&&J()}function be(){let t=x(),e=t&&t.servers.length>0;a.viewToggle&&(a.viewToggle.style.display=e?"flex":"none"),a.peerCount&&t&&(a.peerCount.textContent=String(t.servers.length)),!e&&L()==="fleet"&&N("local")}function J(){let t=x();if(!t||t.servers.length===0){a.serversList&&(a.serversList.innerHTML='<li class="server-item" style="color: var(--text-muted); cursor: default;">No peer servers connected</li>');return}let e=t.servers.map(n=>{let s=n.id===t.localServerId,o=n.connected?"":"offline";return`
|
|
140
|
+
<li class="server-item" data-server="${l(n.id)}">
|
|
141
|
+
<div class="server-icon" style="${s?"background: var(--accent-primary);":""}">
|
|
142
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
143
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/>
|
|
144
|
+
<line x1="8" y1="21" x2="16" y2="21"/>
|
|
145
|
+
<line x1="12" y1="17" x2="12" y2="21"/>
|
|
146
|
+
</svg>
|
|
147
|
+
<span class="status-dot ${o}"></span>
|
|
148
|
+
</div>
|
|
149
|
+
<span class="server-name">${l(n.name)}${s?" (local)":""}</span>
|
|
150
|
+
<span class="agent-count">${n.agentCount}</span>
|
|
151
|
+
</li>
|
|
152
|
+
`}).join("");a.serversList&&(a.serversList.innerHTML=e)}function Se(){let t=L(),e=x();if(t!=="fleet"||!e){b();return}let n=e.agents,s=new Set(j.map(r=>r.name)),o=n.map(r=>{let c=M(r.lastSeen||r.lastActive),u=c?"online":"",E=i.currentChannel===r.name,C=r.needsAttention?"needs-attention":"",I=s.has(r.name),Q=r.isLocal,Ce=`
|
|
153
|
+
<span class="server-badge ${Q?"local":""}">
|
|
154
|
+
<span class="server-dot ${c?"":"offline"}"></span>
|
|
155
|
+
${l(r.serverName||r.server)}
|
|
156
|
+
</span>
|
|
157
|
+
`,Ie=Q?"":`
|
|
158
|
+
<span class="server-indicator" title="${l(r.serverName)}"></span>
|
|
159
|
+
`,ke=I?`
|
|
160
|
+
<svg class="spawned-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" title="Spawned from dashboard">
|
|
161
|
+
<polygon points="5 3 19 12 5 21 5 3"/>
|
|
162
|
+
</svg>
|
|
163
|
+
`:"",$e=I?`
|
|
164
|
+
<button class="release-btn" title="Release agent" data-release="${l(r.name)}">
|
|
165
|
+
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
|
|
166
|
+
<line x1="18" y1="6" x2="6" y2="18"/>
|
|
167
|
+
<line x1="6" y1="6" x2="18" y2="18"/>
|
|
168
|
+
</svg>
|
|
169
|
+
</button>
|
|
170
|
+
`:"";return`
|
|
171
|
+
<li class="channel-item ${E?"active":""} ${C}" data-agent="${l(r.name)}" data-server="${l(r.server)}">
|
|
172
|
+
<div class="agent-avatar" style="background: ${I?"var(--accent-green)":v(r.name)}">
|
|
173
|
+
${f(r.name)}
|
|
174
|
+
<span class="presence-indicator ${u}"></span>
|
|
175
|
+
${Ie}
|
|
176
|
+
</div>
|
|
177
|
+
${Ce}
|
|
178
|
+
<span class="channel-name">${l(r.name)}</span>
|
|
179
|
+
${ke}
|
|
180
|
+
${r.needsAttention?'<span class="attention-badge">Needs Input</span>':""}
|
|
181
|
+
${$e}
|
|
182
|
+
</li>
|
|
183
|
+
`}).join("");a.agentsList.innerHTML=o||'<li class="channel-item" style="color: var(--text-muted); cursor: default;">No agents in fleet</li>',Pe(),me()}function Pe(){a.agentsList.querySelectorAll(".channel-item[data-agent]").forEach(t=>{t.addEventListener("click",e=>{if(e.target.closest(".release-btn"))return;let n=t.dataset.agent;n&&g(n)})}),a.agentsList.querySelectorAll(".release-btn[data-release]").forEach(t=>{t.addEventListener("click",async e=>{e.stopPropagation();let n=t.dataset.release;n&&confirm(`Release agent "${n}"? This will terminate the agent.`)&&await Le(n)})})}function xe(){let t=de();Y(()=>{ue(),L()==="fleet"?(Se(),J()):b(),q(),pe(),be()}),Fe(t),Me(),B(),T()}function Fe(t){t.channelsList.querySelectorAll(".channel-item").forEach(e=>{e.addEventListener("click",()=>{let n=e.dataset.channel;n&&g(n)})}),t.sendBtn.addEventListener("click",Ae),t.messageInput.addEventListener("keydown",e=>{if(ye()){if(e.key==="Tab"||e.key==="Enter"){e.preventDefault(),W();return}if(e.key==="ArrowUp"){e.preventDefault(),U("up");return}if(e.key==="ArrowDown"){e.preventDefault(),U("down");return}if(e.key==="Escape"){e.preventDefault(),y();return}}e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Ae())}),t.messageInput.addEventListener("input",()=>{t.messageInput.style.height="auto",t.messageInput.style.height=Math.min(t.messageInput.scrollHeight,200)+"px";let e=we();e!==null?he(e):y()}),t.messageInput.addEventListener("blur",()=>{setTimeout(()=>{y()},150)}),t.boldBtn.addEventListener("click",()=>{let e=t.messageInput,n=e.selectionStart,s=e.selectionEnd,o=e.value;if(n===s){let r=o.substring(0,n),c=o.substring(s);e.value=r+"**bold**"+c,e.selectionStart=n+2,e.selectionEnd=n+6}else{let r=o.substring(0,n),c=o.substring(n,s),u=o.substring(s);e.value=r+"**"+c+"**"+u,e.selectionStart=n,e.selectionEnd=s+4}e.focus()}),t.emojiBtn.addEventListener("click",()=>{let e=["\u{1F44D}","\u{1F44E}","\u2705","\u274C","\u{1F389}","\u{1F525}","\u{1F4A1}","\u26A0\uFE0F","\u{1F4DD}","\u{1F680}"],n=e[Math.floor(Math.random()*e.length)],s=t.messageInput,o=s.selectionStart,r=s.value;s.value=r.substring(0,o)+n+r.substring(o),s.selectionStart=s.selectionEnd=o+n.length,s.focus()}),t.searchTrigger.addEventListener("click",V),document.addEventListener("keydown",e=>{(e.ctrlKey||e.metaKey)&&e.key==="k"&&(e.preventDefault(),t.commandPaletteOverlay.classList.contains("visible")?m():V()),e.key==="Escape"&&m()}),t.commandPaletteOverlay.addEventListener("click",e=>{e.target===t.commandPaletteOverlay&&m()}),t.paletteSearch.addEventListener("input",e=>{let n=e.target;R(n.value)}),t.paletteSearch.addEventListener("keydown",fe),document.querySelectorAll(".palette-item[data-command]").forEach(e=>{e.addEventListener("click",()=>{let n=e.dataset.command;n==="broadcast"?(t.messageInput.value="@* ",t.messageInput.focus()):n==="clear"&&(t.messagesList.innerHTML=""),m()})}),ge(),t.threadPanelClose.addEventListener("click",K),t.threadSendBtn.addEventListener("click",Te),t.threadMessageInput.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),Te())}),document.addEventListener("keydown",e=>{e.key==="Escape"&&t.threadPanelOverlay.classList.contains("visible")&&K()}),t.spawnBtn.addEventListener("click",Ee),t.spawnModalClose.addEventListener("click",w),document.getElementById("spawn-cancel-btn")?.addEventListener("click",w),t.spawnModalOverlay.addEventListener("click",e=>{e.target===t.spawnModalOverlay&&w()}),document.addEventListener("keydown",e=>{e.key==="Escape"&&t.spawnModalOverlay.classList.contains("visible")&&w()}),t.spawnSubmitBtn.addEventListener("click",z),t.spawnNameInput.addEventListener("keydown",e=>{e.key==="Enter"&&!e.shiftKey&&(e.preventDefault(),z())})}function Ne(t){let n=t.trim().match(/^@(\*|[^\s]+)\s+(.+)$/s);return n?{to:n[1],message:n[2].trim()}:null}async function Ae(){let t=O(),e=t.messageInput.value.trim();if(!e)return;let n,s,o=i.currentChannel!=="general",r=Ne(e);if(r)n=r.to,s=r.message;else if(o)n=i.currentChannel,s=e;else{alert('Message must start with @recipient (e.g., "@Lead hello" or "@* broadcast")');return}t.sendBtn.disabled=!0;let c=await D(n,s);c.success?(t.messageInput.value="",t.messageInput.style.height="auto"):alert(c.error),t.sendBtn.disabled=!1}async function Te(){let t=O(),e=t.threadMessageInput.value.trim(),n=i.currentThread;if(!e||!n)return;t.threadSendBtn.disabled=!0;let s=await D("*",e,n);s.success?(t.threadMessageInput.value="",_(n)):alert(s.error),t.threadSendBtn.disabled=!1}typeof document<"u"&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",xe):xe());export{xe as initApp};
|
|
184
|
+
//# sourceMappingURL=app.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../frontend/state.ts", "../../frontend/websocket.ts", "../../frontend/utils.ts", "../../frontend/components.ts", "../../frontend/app.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Dashboard State Management\n */\n\nimport type { Agent, Message, AppState, ChannelType, ViewMode, FleetData, FleetAgent, PeerServer } from './types.js';\n\n/**\n * Global application state\n */\nexport const state: AppState = {\n agents: [],\n messages: [],\n currentChannel: 'general',\n currentThread: null,\n isConnected: false,\n ws: null,\n reconnectAttempts: 0,\n viewMode: 'local',\n fleetData: null,\n};\n\n/**\n * State update callbacks\n */\ntype StateListener = () => void;\nconst listeners: StateListener[] = [];\n\n/**\n * Subscribe to state changes\n */\nexport function subscribe(listener: StateListener): () => void {\n listeners.push(listener);\n return () => {\n const index = listeners.indexOf(listener);\n if (index > -1) {\n listeners.splice(index, 1);\n }\n };\n}\n\n/**\n * Notify all listeners of state change\n */\nfunction notifyListeners(): void {\n listeners.forEach((listener) => listener());\n}\n\n/**\n * Update agents in state\n */\nexport function setAgents(agents: Agent[]): void {\n state.agents = agents;\n notifyListeners();\n}\n\n/**\n * Update messages in state\n */\nexport function setMessages(messages: Message[]): void {\n state.messages = messages;\n notifyListeners();\n}\n\n/**\n * Set current channel/conversation\n */\nexport function setCurrentChannel(channel: ChannelType): void {\n state.currentChannel = channel;\n notifyListeners();\n}\n\n/**\n * Update connection status\n */\nexport function setConnectionStatus(connected: boolean): void {\n state.isConnected = connected;\n if (connected) {\n state.reconnectAttempts = 0;\n }\n notifyListeners();\n}\n\n/**\n * Increment reconnect attempts\n */\nexport function incrementReconnectAttempts(): void {\n state.reconnectAttempts++;\n}\n\n/**\n * Set WebSocket instance\n */\nexport function setWebSocket(ws: WebSocket | null): void {\n state.ws = ws;\n}\n\n/**\n * Filter messages based on current channel\n */\nexport function getFilteredMessages(): Message[] {\n const { messages, currentChannel } = state;\n\n if (currentChannel === 'general') {\n return messages;\n }\n\n // Filter for specific agent - show messages to/from that agent\n return messages.filter(\n (m) => m.from === currentChannel || m.to === currentChannel\n );\n}\n\n/**\n * Set current thread for thread panel\n */\nexport function setCurrentThread(thread: string | null): void {\n state.currentThread = thread;\n}\n\n/**\n * Get messages for a specific thread\n */\nexport function getThreadMessages(threadId: string): Message[] {\n return state.messages.filter((m) => m.thread === threadId);\n}\n\n/**\n * Get reply count for a thread\n */\nexport function getThreadReplyCount(threadId: string): number {\n return state.messages.filter((m) => m.thread === threadId).length;\n}\n\n// ========================================\n// Fleet View State Management\n// ========================================\n\n/**\n * Set view mode (local or fleet)\n */\nexport function setViewMode(mode: ViewMode): void {\n state.viewMode = mode;\n notifyListeners();\n}\n\n/**\n * Get current view mode\n */\nexport function getViewMode(): ViewMode {\n return state.viewMode;\n}\n\n/**\n * Update fleet data\n */\nexport function setFleetData(data: FleetData | null): void {\n state.fleetData = data;\n notifyListeners();\n}\n\n/**\n * Get fleet data\n */\nexport function getFleetData(): FleetData | null {\n return state.fleetData;\n}\n\n/**\n * Get agents for current view mode\n * Returns local agents or fleet agents depending on view mode\n */\nexport function getAgentsForCurrentView(): (Agent | FleetAgent)[] {\n if (state.viewMode === 'fleet' && state.fleetData) {\n return state.fleetData.agents;\n }\n return state.agents;\n}\n\n/**\n * Check if fleet view is available (has peer connections)\n */\nexport function isFleetAvailable(): boolean {\n return state.fleetData !== null && state.fleetData.servers.length > 0;\n}\n", "/**\n * WebSocket Connection Handler\n */\n\nimport type { DashboardData, FleetData } from './types.js';\nimport {\n state,\n setAgents,\n setMessages,\n setConnectionStatus,\n setWebSocket,\n incrementReconnectAttempts,\n setFleetData,\n} from './state.js';\n\ntype DataHandler = (data: DashboardData) => void;\n\nlet dataHandler: DataHandler | null = null;\n\n/**\n * Set the handler for incoming data\n */\nexport function onData(handler: DataHandler): void {\n dataHandler = handler;\n}\n\n/**\n * Connect to the WebSocket server\n */\nexport function connect(): void {\n const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\n const ws = new WebSocket(`${protocol}//${window.location.host}/ws`);\n\n ws.onopen = (): void => {\n setConnectionStatus(true);\n };\n\n ws.onclose = (): void => {\n setConnectionStatus(false);\n // Reconnect with exponential backoff\n const delay = Math.min(1000 * Math.pow(2, state.reconnectAttempts), 30000);\n incrementReconnectAttempts();\n setTimeout(connect, delay);\n };\n\n ws.onerror = (error): void => {\n console.error('WebSocket error:', error);\n };\n\n ws.onmessage = (event: MessageEvent): void => {\n try {\n const data: DashboardData = JSON.parse(event.data as string);\n handleData(data);\n } catch (e) {\n console.error('Failed to parse message:', e);\n }\n };\n\n setWebSocket(ws);\n}\n\n/**\n * Extended dashboard data with fleet support\n */\ninterface ExtendedDashboardData extends DashboardData {\n fleet?: FleetData;\n}\n\n/**\n * Handle incoming dashboard data\n */\nfunction handleData(data: ExtendedDashboardData): void {\n console.log('[WS] Received data:', { agentCount: data.agents?.length, messageCount: data.messages?.length, hasFleet: !!data.fleet });\n\n if (data.agents) {\n console.log('[WS] Setting agents:', data.agents.map(a => a.name));\n setAgents(data.agents);\n }\n\n if (data.messages) {\n setMessages(data.messages);\n }\n\n // Handle fleet data for multi-server view\n if (data.fleet) {\n console.log('[WS] Setting fleet data:', { servers: data.fleet.servers?.length, agents: data.fleet.agents?.length });\n setFleetData(data.fleet);\n }\n\n if (dataHandler) {\n dataHandler(data);\n }\n}\n\n/**\n * Send a message via the REST API\n */\nexport async function sendMessage(\n to: string,\n message: string,\n thread?: string\n): Promise<{ success: boolean; error?: string }> {\n try {\n const body: { to: string; message: string; thread?: string } = { to, message };\n if (thread) {\n body.thread = thread;\n }\n\n const response = await fetch('/api/send', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n });\n\n const result = await response.json();\n\n if (response.ok && result.success) {\n return { success: true };\n } else {\n return { success: false, error: result.error || 'Failed to send message' };\n }\n } catch (err) {\n return { success: false, error: 'Network error - could not send message' };\n }\n}\n", "/**\n * Dashboard Utility Functions\n */\n\n/** Threshold for considering an agent offline (45 seconds)\n * This is intentionally 1.5x the heartbeat timeout (30s) to prevent flickering\n * when heartbeats are slightly delayed */\nexport const STALE_THRESHOLD_MS = 45000;\n\n/**\n * Check if an agent is online based on last seen timestamp\n */\nexport function isAgentOnline(lastSeen: string | undefined): boolean {\n if (!lastSeen) return false;\n const ts = Date.parse(lastSeen);\n if (Number.isNaN(ts)) return false;\n return Date.now() - ts < STALE_THRESHOLD_MS;\n}\n\n/**\n * Escape HTML to prevent XSS\n */\nexport function escapeHtml(text: string | undefined): string {\n if (!text) return '';\n const div = document.createElement('div');\n div.textContent = text;\n return div.innerHTML;\n}\n\n/**\n * Format timestamp to locale time string\n */\nexport function formatTime(timestamp: string): string {\n const date = new Date(timestamp);\n return date.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });\n}\n\n/**\n * Format timestamp to human-readable date\n */\nexport function formatDate(timestamp: string): string {\n const date = new Date(timestamp);\n const today = new Date();\n const yesterday = new Date(today);\n yesterday.setDate(yesterday.getDate() - 1);\n\n if (date.toDateString() === today.toDateString()) {\n return 'Today';\n } else if (date.toDateString() === yesterday.toDateString()) {\n return 'Yesterday';\n } else {\n return date.toLocaleDateString([], {\n weekday: 'long',\n month: 'long',\n day: 'numeric',\n });\n }\n}\n\n/**\n * Generate a consistent color for an agent based on their name\n */\nexport function getAvatarColor(name: string): string {\n const colors = [\n '#e01e5a',\n '#2bac76',\n '#e8a427',\n '#1264a3',\n '#7c3aed',\n '#0d9488',\n '#dc2626',\n '#9333ea',\n '#ea580c',\n '#0891b2',\n ];\n let hash = 0;\n for (let i = 0; i < name.length; i++) {\n hash = name.charCodeAt(i) + ((hash << 5) - hash);\n }\n return colors[Math.abs(hash) % colors.length];\n}\n\n/**\n * Get initials from a name (first 2 characters, uppercase)\n */\nexport function getInitials(name: string): string {\n return name.substring(0, 2).toUpperCase();\n}\n\n/**\n * Format message body with basic markdown-like formatting\n * Note: CSS uses white-space: pre-wrap to handle newlines naturally\n */\nexport function formatMessageBody(content: string | undefined): string {\n if (!content) return '';\n\n let escaped = escapeHtml(content);\n\n // Simple code block detection (must process before inline code)\n // Handle code blocks with optional language specifier: ```js\\n... ``` or ```...\\n```\n // The language specifier must be followed by a newline to be recognized\n escaped = escaped.replace(/```(\\w+)?\\n([\\s\\S]*?)```/g, (_match, _lang, code) => {\n return `<pre><code>${code.trim()}</code></pre>`;\n });\n\n // Handle inline code blocks without newlines: ```code here```\n escaped = escaped.replace(/```([^`\\n]+)```/g, '<pre><code>$1</code></pre>');\n\n // Inline code: `code`\n escaped = escaped.replace(/`([^`]+)`/g, '<code>$1</code>');\n\n // Bold: **text** or __text__\n escaped = escaped.replace(/\\*\\*([^*]+)\\*\\*/g, '<strong>$1</strong>');\n escaped = escaped.replace(/__([^_]+)__/g, '<strong>$1</strong>');\n\n // Italic: *text* or _text_ (not inside bold)\n escaped = escaped.replace(/(?<!\\*)\\*([^*]+)\\*(?!\\*)/g, '<em>$1</em>');\n\n // Don't convert newlines to <br> - CSS white-space: pre-wrap handles this\n // This was causing double line breaks before\n\n return escaped;\n}\n", "/**\n * Dashboard UI Components\n */\n\nimport type { Agent, Message, DOMElements, ChannelType, SpawnedAgent, FleetAgent, PeerServer, ViewMode } from './types.js';\nimport { state, getFilteredMessages, setCurrentChannel, setCurrentThread, getThreadMessages, getThreadReplyCount, setViewMode, getViewMode, getFleetData, isFleetAvailable, getAgentsForCurrentView } from './state.js';\nimport {\n escapeHtml,\n formatTime,\n formatDate,\n getAvatarColor,\n getInitials,\n formatMessageBody,\n isAgentOnline,\n} from './utils.js';\n\n// Track spawned agents\nlet spawnedAgents: SpawnedAgent[] = [];\n\nlet elements: DOMElements;\nlet paletteSelectedIndex = -1;\n\n/**\n * Initialize DOM element references\n */\nexport function initElements(): DOMElements {\n elements = {\n connectionDot: document.getElementById('connection-dot')!,\n channelsList: document.getElementById('channels-list')!,\n agentsList: document.getElementById('agents-list')!,\n messagesList: document.getElementById('messages-list')!,\n currentChannelName: document.getElementById('current-channel-name')!,\n channelTopic: document.getElementById('channel-topic')!,\n onlineCount: document.getElementById('online-count')!,\n messageInput: document.getElementById('message-input') as HTMLTextAreaElement,\n sendBtn: document.getElementById('send-btn') as HTMLButtonElement,\n boldBtn: document.getElementById('bold-btn') as HTMLButtonElement,\n emojiBtn: document.getElementById('emoji-btn') as HTMLButtonElement,\n searchTrigger: document.getElementById('search-trigger')!,\n commandPaletteOverlay: document.getElementById('command-palette-overlay')!,\n paletteSearch: document.getElementById('palette-search') as HTMLInputElement,\n paletteResults: document.getElementById('palette-results')!,\n paletteChannelsSection: document.getElementById('palette-channels-section')!,\n paletteAgentsSection: document.getElementById('palette-agents-section')!,\n paletteMessagesSection: document.getElementById('palette-messages-section')!,\n typingIndicator: document.getElementById('typing-indicator')!,\n threadPanelOverlay: document.getElementById('thread-panel-overlay')!,\n threadPanelId: document.getElementById('thread-panel-id')!,\n threadPanelClose: document.getElementById('thread-panel-close') as HTMLButtonElement,\n threadMessages: document.getElementById('thread-messages')!,\n threadMessageInput: document.getElementById('thread-message-input') as HTMLTextAreaElement,\n threadSendBtn: document.getElementById('thread-send-btn') as HTMLButtonElement,\n mentionAutocomplete: document.getElementById('mention-autocomplete')!,\n mentionAutocompleteList: document.getElementById('mention-autocomplete-list')!,\n // Spawn modal elements\n spawnBtn: document.getElementById('spawn-btn') as HTMLButtonElement,\n spawnModalOverlay: document.getElementById('spawn-modal-overlay')!,\n spawnModalClose: document.getElementById('spawn-modal-close') as HTMLButtonElement,\n spawnNameInput: document.getElementById('spawn-name-input') as HTMLInputElement,\n spawnCliInput: document.getElementById('spawn-cli-input') as HTMLInputElement,\n spawnTaskInput: document.getElementById('spawn-task-input') as HTMLTextAreaElement,\n spawnSubmitBtn: document.getElementById('spawn-submit-btn') as HTMLButtonElement,\n spawnStatus: document.getElementById('spawn-status')!,\n // Fleet view elements\n viewToggle: document.getElementById('view-toggle')!,\n viewToggleLocal: document.querySelector('[data-view=\"local\"]') as HTMLButtonElement,\n viewToggleFleet: document.querySelector('[data-view=\"fleet\"]') as HTMLButtonElement,\n peerCount: document.getElementById('peer-count')!,\n serversSection: document.getElementById('servers-section')!,\n serversList: document.getElementById('servers-list')!,\n };\n return elements;\n}\n\n/**\n * Get DOM elements\n */\nexport function getElements(): DOMElements {\n return elements;\n}\n\n/**\n * Update connection status indicator\n */\nexport function updateConnectionStatus(): void {\n if (state.isConnected) {\n elements.connectionDot.classList.remove('offline');\n } else {\n elements.connectionDot.classList.add('offline');\n }\n}\n\n/**\n * Render agents list in sidebar\n */\nexport function renderAgents(): void {\n console.log('[UI] renderAgents called, agents:', state.agents.length, state.agents.map(a => a.name));\n\n // Create a set of spawned agent names for quick lookup\n const spawnedNames = new Set(spawnedAgents.map(a => a.name));\n\n const html = state.agents\n .map((agent) => {\n const online = isAgentOnline(agent.lastSeen || agent.lastActive);\n const presenceClass = online ? 'online' : '';\n const isActive = state.currentChannel === agent.name;\n const needsAttentionClass = agent.needsAttention ? 'needs-attention' : '';\n const isSpawned = spawnedNames.has(agent.name);\n\n // Spawned icon SVG (play/launch icon)\n const spawnedIcon = isSpawned ? `\n <svg class=\"spawned-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" title=\"Spawned from dashboard\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n ` : '';\n\n // Release button for spawned agents\n const releaseBtn = isSpawned ? `\n <button class=\"release-btn\" title=\"Release agent\" data-release=\"${escapeHtml(agent.name)}\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n ` : '';\n\n return `\n <li class=\"channel-item ${isActive ? 'active' : ''} ${needsAttentionClass}\" data-agent=\"${escapeHtml(agent.name)}\" ${isSpawned ? 'title=\"Spawned from dashboard\"' : ''}>\n <div class=\"agent-avatar\" style=\"background: ${isSpawned ? 'var(--accent-green)' : getAvatarColor(agent.name)}\">\n ${getInitials(agent.name)}\n <span class=\"presence-indicator ${presenceClass}\"></span>\n </div>\n <span class=\"channel-name\">${escapeHtml(agent.name)}</span>\n ${spawnedIcon}\n ${agent.needsAttention ? '<span class=\"attention-badge\">Needs Input</span>' : ''}\n ${releaseBtn}\n </li>\n `;\n })\n .join('');\n\n elements.agentsList.innerHTML =\n html ||\n '<li class=\"channel-item\" style=\"color: var(--text-muted); cursor: default;\">No agents connected</li>';\n\n // Add click handlers for agent selection\n elements.agentsList.querySelectorAll<HTMLElement>('.channel-item[data-agent]').forEach((item) => {\n item.addEventListener('click', (e) => {\n // Don't select channel if clicking release button\n if ((e.target as HTMLElement).closest('.release-btn')) {\n return;\n }\n const agentName = item.dataset.agent;\n if (agentName) {\n selectChannel(agentName);\n }\n });\n });\n\n // Add release button click handlers\n elements.agentsList.querySelectorAll<HTMLButtonElement>('.release-btn[data-release]').forEach((btn) => {\n btn.addEventListener('click', async (e) => {\n e.stopPropagation();\n const agentName = btn.dataset.release;\n if (agentName && confirm(`Release agent \"${agentName}\"? This will terminate the agent.`)) {\n await releaseAgent(agentName);\n }\n });\n });\n\n // Update command palette agents\n updatePaletteAgents();\n}\n\n/**\n * Render messages list\n */\nexport function renderMessages(): void {\n const filtered = getFilteredMessages();\n\n if (filtered.length === 0) {\n elements.messagesList.innerHTML = `\n <div class=\"empty-state\">\n <svg class=\"empty-state-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n <div class=\"empty-state-title\">No messages yet</div>\n <div class=\"empty-state-text\">\n ${\n state.currentChannel === 'general'\n ? 'Messages between agents will appear here'\n : `Messages with ${state.currentChannel} will appear here`\n }\n </div>\n </div>\n `;\n return;\n }\n\n let html = '';\n let lastDate: string | null = null;\n\n filtered.forEach((msg) => {\n const msgDate = new Date(msg.timestamp).toDateString();\n\n // Add date divider if needed\n if (msgDate !== lastDate) {\n html += `\n <div class=\"date-divider\">\n <span class=\"date-divider-text\">${formatDate(msg.timestamp)}</span>\n </div>\n `;\n lastDate = msgDate;\n }\n\n const isBroadcast = msg.to === '*';\n const avatarColor = getAvatarColor(msg.from);\n const replyCount = getThreadReplyCount(msg.id);\n\n // Format: @From \u2192 @To: message (like Slack)\n // For cross-project messages, show project badge before agent name\n const recipientDisplay = isBroadcast\n ? '@everyone'\n : msg.project\n ? `<span class=\"project-badge\">${escapeHtml(msg.project)}</span>@${escapeHtml(msg.to)}`\n : `@${escapeHtml(msg.to)}`;\n\n html += `\n <div class=\"message ${isBroadcast ? 'broadcast' : ''}\" data-id=\"${escapeHtml(msg.id)}\">\n <div class=\"message-avatar\" style=\"background: ${avatarColor}\">\n ${getInitials(msg.from)}\n </div>\n <div class=\"message-content\">\n <div class=\"message-header\">\n <span class=\"message-sender\">@${escapeHtml(msg.from)}</span>\n <span class=\"message-recipient\">\n \u2192 <span class=\"target\">${recipientDisplay}</span>\n </span>\n <span class=\"message-timestamp\">${formatTime(msg.timestamp)}</span>\n </div>\n <div class=\"message-body\">${formatMessageBody(msg.content)}</div>\n ${\n msg.thread\n ? `\n <div class=\"thread-indicator\" data-thread=\"${escapeHtml(msg.thread)}\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n Thread: ${escapeHtml(msg.thread)}\n </div>\n `\n : ''\n }\n ${\n replyCount > 0\n ? `\n <div class=\"reply-count-badge\" data-thread=\"${escapeHtml(msg.id)}\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n ${replyCount} ${replyCount === 1 ? 'reply' : 'replies'}\n </div>\n `\n : ''\n }\n </div>\n <div class=\"message-actions\">\n <button class=\"message-action-btn\" data-action=\"reply\" title=\"Reply in thread\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n </button>\n <button class=\"message-action-btn\" title=\"Add reaction\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <circle cx=\"12\" cy=\"12\" r=\"10\"/>\n <path d=\"M8 14s1.5 2 4 2 4-2 4-2\"/>\n <line x1=\"9\" y1=\"9\" x2=\"9.01\" y2=\"9\"/>\n <line x1=\"15\" y1=\"9\" x2=\"15.01\" y2=\"9\"/>\n </svg>\n </button>\n </div>\n </div>\n `;\n });\n\n elements.messagesList.innerHTML = html;\n\n // Note: Auto-scroll removed - interferes with manual scrolling through history\n\n // Attach thread click handlers\n attachThreadHandlers();\n}\n\n/**\n * Select a channel and update UI\n */\nexport function selectChannel(channel: ChannelType): void {\n setCurrentChannel(channel);\n\n // Update sidebar active states\n elements.channelsList.querySelectorAll<HTMLElement>('.channel-item').forEach((item) => {\n item.classList.toggle('active', item.dataset.channel === channel);\n });\n elements.agentsList.querySelectorAll<HTMLElement>('.channel-item').forEach((item) => {\n item.classList.toggle('active', item.dataset.agent === channel);\n });\n\n // Update header\n const prefixEl = document.querySelector('.channel-header-name .prefix');\n if (channel === 'general') {\n elements.currentChannelName.innerHTML = 'general';\n elements.channelTopic.textContent = 'All agent communications';\n if (prefixEl) prefixEl.textContent = '#';\n } else {\n elements.currentChannelName.innerHTML = escapeHtml(channel);\n const agent = state.agents.find((a) => a.name === channel);\n elements.channelTopic.textContent = agent?.status || 'Direct messages';\n if (prefixEl) prefixEl.textContent = '@';\n }\n\n // Update composer placeholder - DM mode doesn't require @mention\n elements.messageInput.placeholder =\n channel === 'general'\n ? '@AgentName message... (or @* to broadcast)'\n : `Message ${channel}... (@ not required)`;\n\n // Re-render messages\n renderMessages();\n}\n\n/**\n * Update online count display\n */\nexport function updateOnlineCount(): void {\n const online = state.agents.filter((a) => isAgentOnline(a.lastSeen || a.lastActive)).length;\n elements.onlineCount.textContent = `${online} online`;\n}\n\n/**\n * Update agents in command palette\n */\nexport function updatePaletteAgents(): void {\n const html = state.agents\n .map((agent) => {\n const online = isAgentOnline(agent.lastSeen || agent.lastActive);\n return `\n <div class=\"palette-item\" data-jump-agent=\"${escapeHtml(agent.name)}\">\n <div class=\"palette-item-icon\">\n <div class=\"agent-avatar\" style=\"background: ${getAvatarColor(agent.name)}; width: 20px; height: 20px; font-size: 9px;\">\n ${getInitials(agent.name)}\n <span class=\"presence-indicator ${online ? 'online' : ''}\"></span>\n </div>\n </div>\n <div class=\"palette-item-content\">\n <div class=\"palette-item-title\">${escapeHtml(agent.name)}</div>\n <div class=\"palette-item-subtitle\">${online ? 'Online' : 'Offline'}</div>\n </div>\n </div>\n `;\n })\n .join('');\n\n const section = elements.paletteAgentsSection;\n const items = section.querySelectorAll('.palette-item');\n items.forEach((item) => item.remove());\n section.insertAdjacentHTML('beforeend', html);\n\n // Add click handlers\n section.querySelectorAll<HTMLElement>('.palette-item[data-jump-agent]').forEach((item) => {\n item.addEventListener('click', () => {\n const agentName = item.dataset.jumpAgent;\n if (agentName) {\n selectChannel(agentName);\n closeCommandPalette();\n }\n });\n });\n}\n\n/**\n * Initialize channel click handlers in command palette\n */\nexport function initPaletteChannels(): void {\n elements.paletteChannelsSection\n .querySelectorAll<HTMLElement>('.palette-item[data-jump-channel]')\n .forEach((item) => {\n item.addEventListener('click', () => {\n const channelName = item.dataset.jumpChannel;\n if (channelName) {\n selectChannel(channelName);\n closeCommandPalette();\n }\n });\n });\n}\n\n/**\n * Open command palette\n */\nexport function openCommandPalette(): void {\n elements.commandPaletteOverlay.classList.add('visible');\n elements.paletteSearch.value = '';\n elements.paletteSearch.focus();\n paletteSelectedIndex = -1;\n filterPaletteResults('');\n}\n\n/**\n * Get all visible palette items\n */\nexport function getVisiblePaletteItems(): HTMLElement[] {\n const allItems = Array.from(\n elements.paletteResults.querySelectorAll<HTMLElement>('.palette-item')\n );\n return allItems.filter((item) => item.style.display !== 'none');\n}\n\n/**\n * Update the selected palette item visually\n */\nexport function updatePaletteSelection(): void {\n const items = getVisiblePaletteItems();\n\n // Remove selection from all items\n items.forEach((item) => item.classList.remove('selected'));\n\n // Add selection to current item\n if (paletteSelectedIndex >= 0 && paletteSelectedIndex < items.length) {\n const selectedItem = items[paletteSelectedIndex];\n selectedItem.classList.add('selected');\n\n // Scroll into view if needed\n selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });\n }\n}\n\n/**\n * Handle keyboard navigation in command palette\n */\nexport function handlePaletteKeydown(e: KeyboardEvent): void {\n const items = getVisiblePaletteItems();\n\n if (items.length === 0) return;\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault();\n paletteSelectedIndex = paletteSelectedIndex < items.length - 1\n ? paletteSelectedIndex + 1\n : 0;\n updatePaletteSelection();\n break;\n\n case 'ArrowUp':\n e.preventDefault();\n paletteSelectedIndex = paletteSelectedIndex > 0\n ? paletteSelectedIndex - 1\n : items.length - 1;\n updatePaletteSelection();\n break;\n\n case 'Enter':\n e.preventDefault();\n if (paletteSelectedIndex >= 0 && paletteSelectedIndex < items.length) {\n executePaletteItem(items[paletteSelectedIndex]);\n }\n break;\n }\n}\n\n/**\n * Execute the action for a palette item\n */\nexport function executePaletteItem(item: HTMLElement): void {\n // Check for command\n const command = item.dataset.command;\n if (command) {\n if (command === 'broadcast') {\n // Pre-fill message input with @* for broadcast\n elements.messageInput.value = '@* ';\n elements.messageInput.focus();\n } else if (command === 'clear') {\n elements.messagesList.innerHTML = '';\n }\n closeCommandPalette();\n return;\n }\n\n // Check for channel jump\n const channel = item.dataset.jumpChannel;\n if (channel) {\n selectChannel(channel);\n closeCommandPalette();\n return;\n }\n\n // Check for agent jump\n const agent = item.dataset.jumpAgent;\n if (agent) {\n selectChannel(agent);\n closeCommandPalette();\n return;\n }\n\n // Check for message jump\n const messageId = item.dataset.jumpMessage;\n if (messageId) {\n // Find and scroll to the message\n const messageEl = elements.messagesList.querySelector(`[data-id=\"${messageId}\"]`);\n if (messageEl) {\n messageEl.scrollIntoView({ behavior: 'smooth', block: 'center' });\n messageEl.classList.add('highlighted');\n setTimeout(() => messageEl.classList.remove('highlighted'), 2000);\n }\n closeCommandPalette();\n return;\n }\n}\n\n/**\n * Close command palette\n */\nexport function closeCommandPalette(): void {\n elements.commandPaletteOverlay.classList.remove('visible');\n}\n\n/**\n * Filter command palette results based on query\n */\nexport function filterPaletteResults(query: string): void {\n const q = query.toLowerCase();\n\n // Reset selection when filtering\n paletteSelectedIndex = -1;\n\n // Filter command items\n document.querySelectorAll<HTMLElement>('.palette-item[data-command]').forEach((item) => {\n const titleEl = item.querySelector('.palette-item-title');\n const title = titleEl?.textContent?.toLowerCase() || '';\n item.style.display = title.includes(q) ? 'flex' : 'none';\n });\n\n // Filter channel items\n document.querySelectorAll<HTMLElement>('.palette-item[data-jump-channel]').forEach((item) => {\n const titleEl = item.querySelector('.palette-item-title');\n const title = titleEl?.textContent?.toLowerCase() || '';\n item.style.display = title.includes(q) ? 'flex' : 'none';\n });\n\n // Filter agent items\n document.querySelectorAll<HTMLElement>('.palette-item[data-jump-agent]').forEach((item) => {\n const name = item.dataset.jumpAgent?.toLowerCase() || '';\n item.style.display = name.includes(q) ? 'flex' : 'none';\n });\n\n // Show message search if query is long enough\n if (q.length >= 2) {\n const matches = state.messages.filter((m) => m.content.toLowerCase().includes(q)).slice(0, 5);\n\n if (matches.length > 0) {\n elements.paletteMessagesSection.style.display = 'block';\n const items = matches\n .map(\n (m) => `\n <div class=\"palette-item\" data-jump-message=\"${escapeHtml(m.id)}\">\n <div class=\"palette-item-icon\">\n <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <path d=\"M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z\"/>\n </svg>\n </div>\n <div class=\"palette-item-content\">\n <div class=\"palette-item-title\">${escapeHtml(m.from)}</div>\n <div class=\"palette-item-subtitle\">${escapeHtml(m.content.substring(0, 60))}${m.content.length > 60 ? '...' : ''}</div>\n </div>\n </div>\n `\n )\n .join('');\n\n const existingItems = elements.paletteMessagesSection.querySelectorAll('.palette-item');\n existingItems.forEach((item) => item.remove());\n elements.paletteMessagesSection.insertAdjacentHTML('beforeend', items);\n } else {\n elements.paletteMessagesSection.style.display = 'none';\n }\n } else {\n elements.paletteMessagesSection.style.display = 'none';\n }\n}\n\n/**\n * Open thread panel for a specific thread\n */\nexport function openThreadPanel(threadId: string): void {\n setCurrentThread(threadId);\n elements.threadPanelId.textContent = threadId;\n elements.threadPanelOverlay.classList.add('visible');\n elements.threadMessageInput.value = '';\n renderThreadMessages(threadId);\n elements.threadMessageInput.focus();\n}\n\n/**\n * Close thread panel\n */\nexport function closeThreadPanel(): void {\n setCurrentThread(null);\n elements.threadPanelOverlay.classList.remove('visible');\n}\n\n/**\n * Render messages in thread panel\n */\nexport function renderThreadMessages(threadId: string): void {\n const messages = getThreadMessages(threadId);\n\n if (messages.length === 0) {\n elements.threadMessages.innerHTML = `\n <div class=\"thread-empty\">\n <p>No messages in this thread yet.</p>\n <p style=\"font-size: 12px; margin-top: 8px;\">Start the conversation below!</p>\n </div>\n `;\n return;\n }\n\n const html = messages\n .map((msg) => `\n <div class=\"thread-message\">\n <div class=\"thread-message-header\">\n <div class=\"thread-message-avatar\" style=\"background: ${getAvatarColor(msg.from)}\">\n ${getInitials(msg.from)}\n </div>\n <span class=\"thread-message-sender\">${escapeHtml(msg.from)}</span>\n <span class=\"thread-message-time\">${formatTime(msg.timestamp)}</span>\n </div>\n <div class=\"thread-message-body\">${formatMessageBody(msg.content)}</div>\n </div>\n `)\n .join('');\n\n elements.threadMessages.innerHTML = html;\n\n // Scroll to bottom\n elements.threadMessages.scrollTop = elements.threadMessages.scrollHeight;\n}\n\n/**\n * Attach thread click handlers to messages (call after renderMessages)\n */\nexport function attachThreadHandlers(): void {\n // Thread indicator clicks\n elements.messagesList.querySelectorAll<HTMLElement>('.thread-indicator').forEach((el) => {\n el.style.cursor = 'pointer';\n el.addEventListener('click', (e) => {\n e.stopPropagation();\n const threadId = el.dataset.thread;\n if (threadId) {\n openThreadPanel(threadId);\n }\n });\n });\n\n // Reply count badge clicks\n elements.messagesList.querySelectorAll<HTMLElement>('.reply-count-badge').forEach((el) => {\n el.addEventListener('click', (e) => {\n e.stopPropagation();\n const threadId = el.dataset.thread;\n if (threadId) {\n openThreadPanel(threadId);\n }\n });\n });\n\n // Reply in thread button clicks\n elements.messagesList.querySelectorAll<HTMLElement>('.message-action-btn[data-action=\"reply\"]').forEach((el) => {\n el.addEventListener('click', (e) => {\n e.stopPropagation();\n const messageId = el.closest('.message')?.getAttribute('data-id');\n if (messageId) {\n // Use message ID as thread ID for new threads\n openThreadPanel(messageId);\n }\n });\n });\n}\n\n/**\n * @-Mention Autocomplete State\n */\nlet mentionSelectedIndex = 0;\nlet mentionFilteredAgents: typeof state.agents = [];\n\n/**\n * Show mention autocomplete dropdown with filtered agents\n */\nexport function showMentionAutocomplete(filter: string): void {\n const filterLower = filter.toLowerCase();\n\n // Filter agents by name, include broadcast option\n mentionFilteredAgents = state.agents.filter(agent =>\n agent.name.toLowerCase().includes(filterLower)\n );\n\n // Reset selection\n mentionSelectedIndex = 0;\n\n // Build HTML for agent list\n let html = '';\n\n // Add broadcast option if filter matches\n if ('*'.includes(filterLower) || 'everyone'.includes(filterLower) || 'all'.includes(filterLower) || 'broadcast'.includes(filterLower)) {\n html += `\n <div class=\"mention-autocomplete-item ${mentionSelectedIndex === 0 && mentionFilteredAgents.length === 0 ? 'selected' : ''}\" data-mention=\"*\">\n <div class=\"agent-avatar\" style=\"background: var(--accent-yellow);\">*</div>\n <span class=\"mention-autocomplete-name\">@everyone</span>\n <span class=\"mention-autocomplete-role\">Broadcast to all</span>\n </div>\n `;\n }\n\n // Add agents\n mentionFilteredAgents.forEach((agent, index) => {\n const isSelected = index === mentionSelectedIndex;\n html += `\n <div class=\"mention-autocomplete-item ${isSelected ? 'selected' : ''}\" data-mention=\"${escapeHtml(agent.name)}\">\n <div class=\"agent-avatar\" style=\"background: ${getAvatarColor(agent.name)}\">\n ${getInitials(agent.name)}\n </div>\n <span class=\"mention-autocomplete-name\">@${escapeHtml(agent.name)}</span>\n <span class=\"mention-autocomplete-role\">${escapeHtml(agent.role || 'Agent')}</span>\n </div>\n `;\n });\n\n if (html === '') {\n html = '<div class=\"mention-autocomplete-item\" style=\"color: var(--text-muted); cursor: default;\">No matching agents</div>';\n }\n\n elements.mentionAutocompleteList.innerHTML = html;\n elements.mentionAutocomplete.classList.add('visible');\n\n // Add click handlers to items\n elements.mentionAutocompleteList.querySelectorAll<HTMLElement>('.mention-autocomplete-item[data-mention]').forEach((item) => {\n item.addEventListener('click', () => {\n const mention = item.dataset.mention;\n if (mention) {\n completeMention(mention);\n }\n });\n });\n}\n\n/**\n * Hide mention autocomplete dropdown\n */\nexport function hideMentionAutocomplete(): void {\n elements.mentionAutocomplete.classList.remove('visible');\n mentionFilteredAgents = [];\n mentionSelectedIndex = 0;\n}\n\n/**\n * Check if mention autocomplete is visible\n */\nexport function isMentionAutocompleteVisible(): boolean {\n return elements.mentionAutocomplete.classList.contains('visible');\n}\n\n/**\n * Navigate mention autocomplete selection\n */\nexport function navigateMentionAutocomplete(direction: 'up' | 'down'): void {\n const items = elements.mentionAutocompleteList.querySelectorAll<HTMLElement>('.mention-autocomplete-item[data-mention]');\n if (items.length === 0) return;\n\n // Remove current selection\n items[mentionSelectedIndex]?.classList.remove('selected');\n\n // Update index\n if (direction === 'down') {\n mentionSelectedIndex = (mentionSelectedIndex + 1) % items.length;\n } else {\n mentionSelectedIndex = (mentionSelectedIndex - 1 + items.length) % items.length;\n }\n\n // Add new selection\n items[mentionSelectedIndex]?.classList.add('selected');\n items[mentionSelectedIndex]?.scrollIntoView({ block: 'nearest' });\n}\n\n/**\n * Complete the current mention selection\n */\nexport function completeMention(mention?: string): void {\n const items = elements.mentionAutocompleteList.querySelectorAll<HTMLElement>('.mention-autocomplete-item[data-mention]');\n\n // Use provided mention or get from selected item\n let selectedMention = mention;\n if (!selectedMention && items.length > 0) {\n selectedMention = items[mentionSelectedIndex]?.dataset.mention;\n }\n\n if (!selectedMention) {\n hideMentionAutocomplete();\n return;\n }\n\n // Replace the @... text with the completed mention\n const input = elements.messageInput;\n const value = input.value;\n\n // Find the @ position (should be at start or after whitespace)\n const atMatch = value.match(/^@\\S*/);\n if (atMatch) {\n // Replace the @partial with @CompletedName\n const completedText = `@${selectedMention} `;\n input.value = completedText + value.substring(atMatch[0].length);\n input.selectionStart = input.selectionEnd = completedText.length;\n }\n\n hideMentionAutocomplete();\n input.focus();\n}\n\n/**\n * Get the current @mention being typed (if any)\n */\nexport function getCurrentMentionQuery(): string | null {\n const input = elements.messageInput;\n const value = input.value;\n const cursorPos = input.selectionStart;\n\n // Check if cursor is within an @mention at the start\n const atMatch = value.match(/^@(\\S*)/);\n if (atMatch && cursorPos <= atMatch[0].length) {\n return atMatch[1]; // Return the text after @\n }\n\n return null;\n}\n\n// ========================================\n// Spawn Modal Functions\n// ========================================\n\n/**\n * Open the spawn agent modal\n */\nexport function openSpawnModal(): void {\n elements.spawnModalOverlay.classList.add('visible');\n elements.spawnNameInput.value = '';\n elements.spawnCliInput.value = 'claude';\n elements.spawnTaskInput.value = '';\n elements.spawnStatus.textContent = '';\n elements.spawnStatus.className = 'spawn-status';\n elements.spawnNameInput.focus();\n}\n\n/**\n * Close the spawn agent modal\n */\nexport function closeSpawnModal(): void {\n elements.spawnModalOverlay.classList.remove('visible');\n}\n\n/**\n * Spawn a new agent via the API\n */\nexport async function spawnAgent(): Promise<{ success: boolean; error?: string }> {\n const name = elements.spawnNameInput.value.trim();\n const cli = elements.spawnCliInput.value.trim() || 'claude';\n const task = elements.spawnTaskInput.value.trim();\n\n if (!name) {\n elements.spawnStatus.textContent = 'Agent name is required';\n elements.spawnStatus.className = 'spawn-status error';\n return { success: false, error: 'Agent name is required' };\n }\n\n elements.spawnSubmitBtn.disabled = true;\n elements.spawnStatus.textContent = 'Spawning agent...';\n elements.spawnStatus.className = 'spawn-status loading';\n\n try {\n const response = await fetch('/api/spawn', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ name, cli, task }),\n });\n\n const result = await response.json();\n\n if (response.ok && result.success) {\n elements.spawnStatus.textContent = `Agent \"${name}\" spawned successfully!`;\n elements.spawnStatus.className = 'spawn-status success';\n\n // Refresh spawned agents list\n await fetchSpawnedAgents();\n\n // Close modal after brief delay\n setTimeout(() => {\n closeSpawnModal();\n }, 1000);\n\n return { success: true };\n } else {\n throw new Error(result.error || 'Failed to spawn agent');\n }\n } catch (err: any) {\n elements.spawnStatus.textContent = err.message || 'Failed to spawn agent';\n elements.spawnStatus.className = 'spawn-status error';\n return { success: false, error: err.message };\n } finally {\n elements.spawnSubmitBtn.disabled = false;\n }\n}\n\n/**\n * Fetch list of spawned agents from API\n */\nexport async function fetchSpawnedAgents(): Promise<void> {\n try {\n const response = await fetch('/api/spawned');\n const result = await response.json();\n\n if (result.success && Array.isArray(result.agents)) {\n spawnedAgents = result.agents;\n // Re-render agents to show spawned status\n renderAgents();\n }\n } catch (err) {\n console.error('[UI] Failed to fetch spawned agents:', err);\n }\n}\n\n/**\n * Release a spawned agent\n */\nexport async function releaseAgent(name: string): Promise<void> {\n try {\n const response = await fetch(`/api/spawned/${encodeURIComponent(name)}`, {\n method: 'DELETE',\n });\n\n const result = await response.json();\n\n if (result.success) {\n // Refresh the list\n await fetchSpawnedAgents();\n } else {\n console.error('[UI] Failed to release agent:', result.error);\n }\n } catch (err) {\n console.error('[UI] Failed to release agent:', err);\n }\n}\n\n/**\n * Get spawned agents list\n */\nexport function getSpawnedAgents(): SpawnedAgent[] {\n return spawnedAgents;\n}\n\n// ========================================\n// Fleet View Functions\n// ========================================\n\n/**\n * Initialize fleet view toggle handlers\n */\nexport function initFleetViewToggle(): void {\n elements.viewToggleLocal?.addEventListener('click', () => {\n switchViewMode('local');\n });\n\n elements.viewToggleFleet?.addEventListener('click', () => {\n switchViewMode('fleet');\n });\n}\n\n/**\n * Switch between local and fleet view\n */\nexport function switchViewMode(mode: ViewMode): void {\n setViewMode(mode);\n\n // Update toggle button states\n elements.viewToggleLocal?.classList.toggle('active', mode === 'local');\n elements.viewToggleFleet?.classList.toggle('active', mode === 'fleet');\n\n // Show/hide servers section\n if (elements.serversSection) {\n elements.serversSection.style.display = mode === 'fleet' ? 'block' : 'none';\n }\n\n // Re-render agents list\n renderAgents();\n\n // Re-render servers if in fleet mode\n if (mode === 'fleet') {\n renderServers();\n }\n}\n\n/**\n * Update fleet view visibility based on available peer connections\n */\nexport function updateFleetViewVisibility(): void {\n const fleetData = getFleetData();\n const hasFleet = fleetData && fleetData.servers.length > 0;\n\n // Show toggle only if fleet is available\n if (elements.viewToggle) {\n elements.viewToggle.style.display = hasFleet ? 'flex' : 'none';\n }\n\n // Update peer count\n if (elements.peerCount && fleetData) {\n elements.peerCount.textContent = String(fleetData.servers.length);\n }\n\n // If fleet became unavailable while in fleet mode, switch to local\n if (!hasFleet && getViewMode() === 'fleet') {\n switchViewMode('local');\n }\n}\n\n/**\n * Render peer servers list in sidebar\n */\nexport function renderServers(): void {\n const fleetData = getFleetData();\n\n if (!fleetData || fleetData.servers.length === 0) {\n if (elements.serversList) {\n elements.serversList.innerHTML = '<li class=\"server-item\" style=\"color: var(--text-muted); cursor: default;\">No peer servers connected</li>';\n }\n return;\n }\n\n const html = fleetData.servers.map(server => {\n const isLocal = server.id === fleetData.localServerId;\n const statusClass = server.connected ? '' : 'offline';\n\n return `\n <li class=\"server-item\" data-server=\"${escapeHtml(server.id)}\">\n <div class=\"server-icon\" style=\"${isLocal ? 'background: var(--accent-primary);' : ''}\">\n <svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">\n <rect x=\"2\" y=\"3\" width=\"20\" height=\"14\" rx=\"2\" ry=\"2\"/>\n <line x1=\"8\" y1=\"21\" x2=\"16\" y2=\"21\"/>\n <line x1=\"12\" y1=\"17\" x2=\"12\" y2=\"21\"/>\n </svg>\n <span class=\"status-dot ${statusClass}\"></span>\n </div>\n <span class=\"server-name\">${escapeHtml(server.name)}${isLocal ? ' (local)' : ''}</span>\n <span class=\"agent-count\">${server.agentCount}</span>\n </li>\n `;\n }).join('');\n\n if (elements.serversList) {\n elements.serversList.innerHTML = html;\n }\n}\n\n/**\n * Render agents list with fleet support\n * Updates renderAgents to show server badges in fleet mode\n */\nexport function renderFleetAgents(): void {\n const viewMode = getViewMode();\n const fleetData = getFleetData();\n\n if (viewMode !== 'fleet' || !fleetData) {\n renderAgents();\n return;\n }\n\n // Get fleet agents\n const agents = fleetData.agents;\n const spawnedNames = new Set(spawnedAgents.map(a => a.name));\n\n const html = agents.map((agent: FleetAgent) => {\n const online = isAgentOnline(agent.lastSeen || agent.lastActive);\n const presenceClass = online ? 'online' : '';\n const isActive = state.currentChannel === agent.name;\n const needsAttentionClass = agent.needsAttention ? 'needs-attention' : '';\n const isSpawned = spawnedNames.has(agent.name);\n const isLocal = agent.isLocal;\n\n // Server badge for fleet agents\n const serverBadge = `\n <span class=\"server-badge ${isLocal ? 'local' : ''}\">\n <span class=\"server-dot ${online ? '' : 'offline'}\"></span>\n ${escapeHtml(agent.serverName || agent.server)}\n </span>\n `;\n\n // Server indicator on avatar\n const serverIndicator = !isLocal ? `\n <span class=\"server-indicator\" title=\"${escapeHtml(agent.serverName)}\"></span>\n ` : '';\n\n // Spawned icon SVG\n const spawnedIcon = isSpawned ? `\n <svg class=\"spawned-icon\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" title=\"Spawned from dashboard\">\n <polygon points=\"5 3 19 12 5 21 5 3\"/>\n </svg>\n ` : '';\n\n // Release button for spawned agents\n const releaseBtn = isSpawned ? `\n <button class=\"release-btn\" title=\"Release agent\" data-release=\"${escapeHtml(agent.name)}\">\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"3\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"/>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"/>\n </svg>\n </button>\n ` : '';\n\n return `\n <li class=\"channel-item ${isActive ? 'active' : ''} ${needsAttentionClass}\" data-agent=\"${escapeHtml(agent.name)}\" data-server=\"${escapeHtml(agent.server)}\">\n <div class=\"agent-avatar\" style=\"background: ${isSpawned ? 'var(--accent-green)' : getAvatarColor(agent.name)}\">\n ${getInitials(agent.name)}\n <span class=\"presence-indicator ${presenceClass}\"></span>\n ${serverIndicator}\n </div>\n ${serverBadge}\n <span class=\"channel-name\">${escapeHtml(agent.name)}</span>\n ${spawnedIcon}\n ${agent.needsAttention ? '<span class=\"attention-badge\">Needs Input</span>' : ''}\n ${releaseBtn}\n </li>\n `;\n }).join('');\n\n elements.agentsList.innerHTML = html || '<li class=\"channel-item\" style=\"color: var(--text-muted); cursor: default;\">No agents in fleet</li>';\n\n // Add click handlers\n attachAgentClickHandlers();\n\n // Update command palette agents\n updatePaletteAgents();\n}\n\n/**\n * Attach click handlers to agent list items\n */\nfunction attachAgentClickHandlers(): void {\n elements.agentsList.querySelectorAll<HTMLElement>('.channel-item[data-agent]').forEach((item) => {\n item.addEventListener('click', (e) => {\n if ((e.target as HTMLElement).closest('.release-btn')) {\n return;\n }\n const agentName = item.dataset.agent;\n if (agentName) {\n selectChannel(agentName);\n }\n });\n });\n\n elements.agentsList.querySelectorAll<HTMLButtonElement>('.release-btn[data-release]').forEach((btn) => {\n btn.addEventListener('click', async (e) => {\n e.stopPropagation();\n const agentName = btn.dataset.release;\n if (agentName && confirm(`Release agent \"${agentName}\"? This will terminate the agent.`)) {\n await releaseAgent(agentName);\n }\n });\n });\n}\n\n/**\n * Check if an agent is a fleet agent (has server info)\n */\nfunction isFleetAgent(agent: Agent | FleetAgent): agent is FleetAgent {\n return 'server' in agent && 'serverName' in agent;\n}\n", "/**\n * Dashboard Application Entry Point\n */\n\nimport { subscribe, state, getViewMode } from './state.js';\nimport { connect, sendMessage } from './websocket.js';\nimport {\n initElements,\n getElements,\n updateConnectionStatus,\n renderAgents,\n renderMessages,\n selectChannel,\n updateOnlineCount,\n openCommandPalette,\n closeCommandPalette,\n filterPaletteResults,\n handlePaletteKeydown,\n initPaletteChannels,\n closeThreadPanel,\n renderThreadMessages,\n showMentionAutocomplete,\n hideMentionAutocomplete,\n isMentionAutocompleteVisible,\n navigateMentionAutocomplete,\n completeMention,\n getCurrentMentionQuery,\n openSpawnModal,\n closeSpawnModal,\n spawnAgent,\n fetchSpawnedAgents,\n initFleetViewToggle,\n updateFleetViewVisibility,\n renderFleetAgents,\n renderServers,\n} from './components.js';\n\n/**\n * Initialize the dashboard application\n */\nexport function initApp(): void {\n const elements = initElements();\n\n // Subscribe to state changes\n subscribe(() => {\n updateConnectionStatus();\n // Render agents based on current view mode\n if (getViewMode() === 'fleet') {\n renderFleetAgents();\n renderServers();\n } else {\n renderAgents();\n }\n renderMessages();\n updateOnlineCount();\n // Update fleet toggle visibility based on available peer connections\n updateFleetViewVisibility();\n });\n\n // Set up event listeners\n setupEventListeners(elements);\n\n // Initialize fleet view toggle handlers\n initFleetViewToggle();\n\n // Connect to WebSocket\n connect();\n\n // Fetch initial spawned agents list\n fetchSpawnedAgents();\n}\n\n/**\n * Set up all event listeners\n */\nfunction setupEventListeners(elements: ReturnType<typeof getElements>): void {\n // Channel clicks\n elements.channelsList.querySelectorAll<HTMLElement>('.channel-item').forEach((item) => {\n item.addEventListener('click', () => {\n const channel = item.dataset.channel;\n if (channel) {\n selectChannel(channel);\n }\n });\n });\n\n // Send button\n elements.sendBtn.addEventListener('click', handleSend);\n\n // Keyboard shortcuts for composer\n elements.messageInput.addEventListener('keydown', (e: KeyboardEvent) => {\n // Handle mention autocomplete keys first\n if (isMentionAutocompleteVisible()) {\n if (e.key === 'Tab' || e.key === 'Enter') {\n e.preventDefault();\n completeMention();\n return;\n }\n if (e.key === 'ArrowUp') {\n e.preventDefault();\n navigateMentionAutocomplete('up');\n return;\n }\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n navigateMentionAutocomplete('down');\n return;\n }\n if (e.key === 'Escape') {\n e.preventDefault();\n hideMentionAutocomplete();\n return;\n }\n }\n\n // Enter to send (Slack-style), Shift+Enter for newline\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleSend();\n }\n // Shift+Enter allows default behavior (inserts newline)\n });\n\n // Auto-resize textarea and handle @-mention autocomplete\n elements.messageInput.addEventListener('input', () => {\n elements.messageInput.style.height = 'auto';\n elements.messageInput.style.height =\n Math.min(elements.messageInput.scrollHeight, 200) + 'px';\n\n // Check for @-mention at start of input\n const query = getCurrentMentionQuery();\n if (query !== null) {\n showMentionAutocomplete(query);\n } else {\n hideMentionAutocomplete();\n }\n });\n\n // Hide mention autocomplete when input loses focus (with delay to allow clicks)\n elements.messageInput.addEventListener('blur', () => {\n setTimeout(() => {\n hideMentionAutocomplete();\n }, 150);\n });\n\n // Bold button - wrap selected text with ** or insert **bold**\n elements.boldBtn.addEventListener('click', () => {\n const input = elements.messageInput;\n const start = input.selectionStart;\n const end = input.selectionEnd;\n const text = input.value;\n\n if (start === end) {\n // No selection - insert **bold** placeholder\n const before = text.substring(0, start);\n const after = text.substring(end);\n input.value = before + '**bold**' + after;\n input.selectionStart = start + 2;\n input.selectionEnd = start + 6;\n } else {\n // Wrap selection with **\n const before = text.substring(0, start);\n const selected = text.substring(start, end);\n const after = text.substring(end);\n input.value = before + '**' + selected + '**' + after;\n input.selectionStart = start;\n input.selectionEnd = end + 4;\n }\n input.focus();\n });\n\n // Emoji button - insert common emojis via simple picker\n elements.emojiBtn.addEventListener('click', () => {\n const emojis = ['\uD83D\uDC4D', '\uD83D\uDC4E', '\u2705', '\u274C', '\uD83C\uDF89', '\uD83D\uDD25', '\uD83D\uDCA1', '\u26A0\uFE0F', '\uD83D\uDCDD', '\uD83D\uDE80'];\n const emoji = emojis[Math.floor(Math.random() * emojis.length)];\n const input = elements.messageInput;\n const start = input.selectionStart;\n const text = input.value;\n input.value = text.substring(0, start) + emoji + text.substring(start);\n input.selectionStart = input.selectionEnd = start + emoji.length;\n input.focus();\n });\n\n // Command palette\n elements.searchTrigger.addEventListener('click', openCommandPalette);\n\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if ((e.ctrlKey || e.metaKey) && e.key === 'k') {\n e.preventDefault();\n if (elements.commandPaletteOverlay.classList.contains('visible')) {\n closeCommandPalette();\n } else {\n openCommandPalette();\n }\n }\n\n if (e.key === 'Escape') {\n closeCommandPalette();\n }\n });\n\n elements.commandPaletteOverlay.addEventListener('click', (e: MouseEvent) => {\n if (e.target === elements.commandPaletteOverlay) {\n closeCommandPalette();\n }\n });\n\n elements.paletteSearch.addEventListener('input', (e: Event) => {\n const target = e.target as HTMLInputElement;\n filterPaletteResults(target.value);\n });\n\n elements.paletteSearch.addEventListener('keydown', handlePaletteKeydown);\n\n // Command execution\n document.querySelectorAll<HTMLElement>('.palette-item[data-command]').forEach((item) => {\n item.addEventListener('click', () => {\n const command = item.dataset.command;\n\n if (command === 'broadcast') {\n // Pre-fill message input with @* for broadcast\n elements.messageInput.value = '@* ';\n elements.messageInput.focus();\n } else if (command === 'clear') {\n elements.messagesList.innerHTML = '';\n }\n\n closeCommandPalette();\n });\n });\n\n // Initialize palette channel click handlers\n initPaletteChannels();\n\n // Thread panel close button\n elements.threadPanelClose.addEventListener('click', closeThreadPanel);\n\n // Thread panel send button\n elements.threadSendBtn.addEventListener('click', handleThreadSend);\n\n // Thread message input keyboard shortcuts (Slack-style)\n elements.threadMessageInput.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n handleThreadSend();\n }\n // Shift+Enter allows default behavior (inserts newline)\n });\n\n // Close thread panel on Escape\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape' && elements.threadPanelOverlay.classList.contains('visible')) {\n closeThreadPanel();\n }\n });\n\n // Spawn modal event listeners\n elements.spawnBtn.addEventListener('click', openSpawnModal);\n\n elements.spawnModalClose.addEventListener('click', closeSpawnModal);\n\n // Cancel button in spawn modal\n document.getElementById('spawn-cancel-btn')?.addEventListener('click', closeSpawnModal);\n\n // Close spawn modal on overlay click\n elements.spawnModalOverlay.addEventListener('click', (e: MouseEvent) => {\n if (e.target === elements.spawnModalOverlay) {\n closeSpawnModal();\n }\n });\n\n // Close spawn modal on Escape\n document.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Escape' && elements.spawnModalOverlay.classList.contains('visible')) {\n closeSpawnModal();\n }\n });\n\n // Submit spawn form\n elements.spawnSubmitBtn.addEventListener('click', spawnAgent);\n\n // Enter key in spawn name input triggers submit\n elements.spawnNameInput.addEventListener('keydown', (e: KeyboardEvent) => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n spawnAgent();\n }\n });\n}\n\n/**\n * Parse @mention from message text\n * Formats: \"@AgentName message\" or \"@* message\" for broadcast\n * Returns { to, message } or null if no valid mention found\n */\nfunction parseMention(text: string): { to: string; message: string } | null {\n const trimmed = text.trim();\n\n // Match @mention at the start of the message\n // @* for broadcast, @AgentName for direct message\n const match = trimmed.match(/^@(\\*|[^\\s]+)\\s+(.+)$/s);\n\n if (!match) {\n return null;\n }\n\n return {\n to: match[1],\n message: match[2].trim(),\n };\n}\n\n/**\n * Handle send button click\n */\nasync function handleSend(): Promise<void> {\n const elements = getElements();\n const rawMessage = elements.messageInput.value.trim();\n\n if (!rawMessage) {\n return;\n }\n\n let to: string;\n let message: string;\n\n // Check if we're in a DM (not general channel)\n const isInDM = state.currentChannel !== 'general';\n\n // Parse @mention from the message\n const parsed = parseMention(rawMessage);\n\n if (parsed) {\n // Message has explicit @mention - use it\n to = parsed.to;\n message = parsed.message;\n } else if (isInDM) {\n // In DM context - send to current channel without requiring @\n to = state.currentChannel;\n message = rawMessage;\n } else {\n // In general channel without @mention - require it\n alert('Message must start with @recipient (e.g., \"@Lead hello\" or \"@* broadcast\")');\n return;\n }\n\n elements.sendBtn.disabled = true;\n\n const result = await sendMessage(to, message);\n\n if (result.success) {\n elements.messageInput.value = '';\n elements.messageInput.style.height = 'auto';\n } else {\n alert(result.error);\n }\n\n elements.sendBtn.disabled = false;\n}\n\n/**\n * Handle thread panel send button click\n */\nasync function handleThreadSend(): Promise<void> {\n const elements = getElements();\n const message = elements.threadMessageInput.value.trim();\n const threadId = state.currentThread;\n\n if (!message || !threadId) {\n return;\n }\n\n // For thread replies, send to broadcast or use original recipient\n // For now, send as broadcast with thread ID\n elements.threadSendBtn.disabled = true;\n\n const result = await sendMessage('*', message, threadId);\n\n if (result.success) {\n elements.threadMessageInput.value = '';\n // Re-render thread messages to show the new message\n renderThreadMessages(threadId);\n } else {\n alert(result.error);\n }\n\n elements.threadSendBtn.disabled = false;\n}\n\n// Auto-initialize when DOM is ready\nif (typeof document !== 'undefined') {\n if (document.readyState === 'loading') {\n document.addEventListener('DOMContentLoaded', initApp);\n } else {\n initApp();\n }\n}\n"],
|
|
5
|
+
"mappings": "AASO,IAAMA,EAAkB,CAC7B,OAAQ,CAAC,EACT,SAAU,CAAC,EACX,eAAgB,UAChB,cAAe,KACf,YAAa,GACb,GAAI,KACJ,kBAAmB,EACnB,SAAU,QACV,UAAW,IACb,EAMMC,EAA6B,CAAC,EAK7B,SAASC,EAAUC,EAAqC,CAC7D,OAAAF,EAAU,KAAKE,CAAQ,EAChB,IAAM,CACX,IAAMC,EAAQH,EAAU,QAAQE,CAAQ,EACpCC,EAAQ,IACVH,EAAU,OAAOG,EAAO,CAAC,CAE7B,CACF,CAKA,SAASC,GAAwB,CAC/BJ,EAAU,QAASE,GAAaA,EAAS,CAAC,CAC5C,CAKO,SAASG,EAAUC,EAAuB,CAC/CP,EAAM,OAASO,EACfF,EAAgB,CAClB,CAKO,SAASG,EAAYC,EAA2B,CACrDT,EAAM,SAAWS,EACjBJ,EAAgB,CAClB,CAKO,SAASK,EAAkBC,EAA4B,CAC5DX,EAAM,eAAiBW,EACvBN,EAAgB,CAClB,CAKO,SAASO,EAAoBC,EAA0B,CAC5Db,EAAM,YAAca,EAChBA,IACFb,EAAM,kBAAoB,GAE5BK,EAAgB,CAClB,CAKO,SAASS,IAAmC,CACjDd,EAAM,mBACR,CAKO,SAASe,GAAaC,EAA4B,CACvDhB,EAAM,GAAKgB,CACb,CAKO,SAASC,IAAiC,CAC/C,GAAM,CAAE,SAAAR,EAAU,eAAAS,CAAe,EAAIlB,EAErC,OAAIkB,IAAmB,UACdT,EAIFA,EAAS,OACbU,GAAMA,EAAE,OAASD,GAAkBC,EAAE,KAAOD,CAC/C,CACF,CAKO,SAASE,EAAiBC,EAA6B,CAC5DrB,EAAM,cAAgBqB,CACxB,CAKO,SAASC,GAAkBC,EAA6B,CAC7D,OAAOvB,EAAM,SAAS,OAAQmB,GAAMA,EAAE,SAAWI,CAAQ,CAC3D,CAKO,SAASC,GAAoBD,EAA0B,CAC5D,OAAOvB,EAAM,SAAS,OAAQmB,GAAMA,EAAE,SAAWI,CAAQ,EAAE,MAC7D,CASO,SAASE,GAAYC,EAAsB,CAChD1B,EAAM,SAAW0B,EACjBrB,EAAgB,CAClB,CAKO,SAASsB,GAAwB,CACtC,OAAO3B,EAAM,QACf,CAKO,SAAS4B,GAAaC,EAA8B,CACzD7B,EAAM,UAAY6B,EAClBxB,EAAgB,CAClB,CAKO,SAASyB,GAAiC,CAC/C,OAAO9B,EAAM,SACf,CCpJA,IAAI+B,GAAkC,KAY/B,SAASC,GAAgB,CAC9B,IAAMC,EAAW,OAAO,SAAS,WAAa,SAAW,OAAS,MAC5DC,EAAK,IAAI,UAAU,GAAGD,CAAQ,KAAK,OAAO,SAAS,IAAI,KAAK,EAElEC,EAAG,OAAS,IAAY,CACtBC,EAAoB,EAAI,CAC1B,EAEAD,EAAG,QAAU,IAAY,CACvBC,EAAoB,EAAK,EAEzB,IAAMC,EAAQ,KAAK,IAAI,IAAO,KAAK,IAAI,EAAGC,EAAM,iBAAiB,EAAG,GAAK,EACzEC,GAA2B,EAC3B,WAAWN,EAASI,CAAK,CAC3B,EAEAF,EAAG,QAAWK,GAAgB,CAC5B,QAAQ,MAAM,mBAAoBA,CAAK,CACzC,EAEAL,EAAG,UAAaM,GAA8B,CAC5C,GAAI,CACF,IAAMC,EAAsB,KAAK,MAAMD,EAAM,IAAc,EAC3DE,GAAWD,CAAI,CACjB,OAASE,EAAG,CACV,QAAQ,MAAM,2BAA4BA,CAAC,CAC7C,CACF,EAEAC,GAAaV,CAAE,CACjB,CAYA,SAASQ,GAAWD,EAAmC,CACrD,QAAQ,IAAI,sBAAuB,CAAE,WAAYA,EAAK,QAAQ,OAAQ,aAAcA,EAAK,UAAU,OAAQ,SAAU,CAAC,CAACA,EAAK,KAAM,CAAC,EAE/HA,EAAK,SACP,QAAQ,IAAI,uBAAwBA,EAAK,OAAO,IAAII,GAAKA,EAAE,IAAI,CAAC,EAChEC,EAAUL,EAAK,MAAM,GAGnBA,EAAK,UACPM,EAAYN,EAAK,QAAQ,EAIvBA,EAAK,QACP,QAAQ,IAAI,2BAA4B,CAAE,QAASA,EAAK,MAAM,SAAS,OAAQ,OAAQA,EAAK,MAAM,QAAQ,MAAO,CAAC,EAClHO,GAAaP,EAAK,KAAK,GAGrBQ,IACFA,GAAYR,CAAI,CAEpB,CAKA,eAAsBS,EACpBC,EACAC,EACAC,EAC+C,CAC/C,GAAI,CACF,IAAMC,EAAyD,CAAE,GAAAH,EAAI,QAAAC,CAAQ,EACzEC,IACFC,EAAK,OAASD,GAGhB,IAAME,EAAW,MAAM,MAAM,YAAa,CACxC,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAUD,CAAI,CAC3B,CAAC,EAEKE,EAAS,MAAMD,EAAS,KAAK,EAEnC,OAAIA,EAAS,IAAMC,EAAO,QACjB,CAAE,QAAS,EAAK,EAEhB,CAAE,QAAS,GAAO,MAAOA,EAAO,OAAS,wBAAyB,CAE7E,MAAc,CACZ,MAAO,CAAE,QAAS,GAAO,MAAO,wCAAyC,CAC3E,CACF,CChHO,SAASC,EAAcC,EAAuC,CACnE,GAAI,CAACA,EAAU,MAAO,GACtB,IAAMC,EAAK,KAAK,MAAMD,CAAQ,EAC9B,OAAI,OAAO,MAAMC,CAAE,EAAU,GACtB,KAAK,IAAI,EAAIA,EAAK,IAC3B,CAKO,SAASC,EAAWC,EAAkC,CAC3D,GAAI,CAACA,EAAM,MAAO,GAClB,IAAMC,EAAM,SAAS,cAAc,KAAK,EACxC,OAAAA,EAAI,YAAcD,EACXC,EAAI,SACb,CAKO,SAASC,EAAWC,EAA2B,CAEpD,OADa,IAAI,KAAKA,CAAS,EACnB,mBAAmB,CAAC,EAAG,CAAE,KAAM,UAAW,OAAQ,SAAU,CAAC,CAC3E,CAKO,SAASC,GAAWD,EAA2B,CACpD,IAAME,EAAO,IAAI,KAAKF,CAAS,EACzBG,EAAQ,IAAI,KACZC,EAAY,IAAI,KAAKD,CAAK,EAGhC,OAFAC,EAAU,QAAQA,EAAU,QAAQ,EAAI,CAAC,EAErCF,EAAK,aAAa,IAAMC,EAAM,aAAa,EACtC,QACED,EAAK,aAAa,IAAME,EAAU,aAAa,EACjD,YAEAF,EAAK,mBAAmB,CAAC,EAAG,CACjC,QAAS,OACT,MAAO,OACP,IAAK,SACP,CAAC,CAEL,CAKO,SAASG,EAAeC,EAAsB,CACnD,IAAMC,EAAS,CACb,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,SACF,EACIC,EAAO,EACX,QAASC,EAAI,EAAGA,EAAIH,EAAK,OAAQG,IAC/BD,EAAOF,EAAK,WAAWG,CAAC,IAAMD,GAAQ,GAAKA,GAE7C,OAAOD,EAAO,KAAK,IAAIC,CAAI,EAAID,EAAO,MAAM,CAC9C,CAKO,SAASG,EAAYJ,EAAsB,CAChD,OAAOA,EAAK,UAAU,EAAG,CAAC,EAAE,YAAY,CAC1C,CAMO,SAASK,EAAkBC,EAAqC,CACrE,GAAI,CAACA,EAAS,MAAO,GAErB,IAAIC,EAAUjB,EAAWgB,CAAO,EAKhC,OAAAC,EAAUA,EAAQ,QAAQ,4BAA6B,CAACC,EAAQC,EAAOC,IAC9D,cAAcA,EAAK,KAAK,CAAC,eACjC,EAGDH,EAAUA,EAAQ,QAAQ,mBAAoB,4BAA4B,EAG1EA,EAAUA,EAAQ,QAAQ,aAAc,iBAAiB,EAGzDA,EAAUA,EAAQ,QAAQ,mBAAoB,qBAAqB,EACnEA,EAAUA,EAAQ,QAAQ,eAAgB,qBAAqB,EAG/DA,EAAUA,EAAQ,QAAQ,4BAA6B,aAAa,EAK7DA,CACT,CCzGA,IAAII,EAAgC,CAAC,EAEjCC,EACAC,EAAuB,GAKpB,SAASC,IAA4B,CAC1C,OAAAF,EAAW,CACT,cAAe,SAAS,eAAe,gBAAgB,EACvD,aAAc,SAAS,eAAe,eAAe,EACrD,WAAY,SAAS,eAAe,aAAa,EACjD,aAAc,SAAS,eAAe,eAAe,EACrD,mBAAoB,SAAS,eAAe,sBAAsB,EAClE,aAAc,SAAS,eAAe,eAAe,EACrD,YAAa,SAAS,eAAe,cAAc,EACnD,aAAc,SAAS,eAAe,eAAe,EACrD,QAAS,SAAS,eAAe,UAAU,EAC3C,QAAS,SAAS,eAAe,UAAU,EAC3C,SAAU,SAAS,eAAe,WAAW,EAC7C,cAAe,SAAS,eAAe,gBAAgB,EACvD,sBAAuB,SAAS,eAAe,yBAAyB,EACxE,cAAe,SAAS,eAAe,gBAAgB,EACvD,eAAgB,SAAS,eAAe,iBAAiB,EACzD,uBAAwB,SAAS,eAAe,0BAA0B,EAC1E,qBAAsB,SAAS,eAAe,wBAAwB,EACtE,uBAAwB,SAAS,eAAe,0BAA0B,EAC1E,gBAAiB,SAAS,eAAe,kBAAkB,EAC3D,mBAAoB,SAAS,eAAe,sBAAsB,EAClE,cAAe,SAAS,eAAe,iBAAiB,EACxD,iBAAkB,SAAS,eAAe,oBAAoB,EAC9D,eAAgB,SAAS,eAAe,iBAAiB,EACzD,mBAAoB,SAAS,eAAe,sBAAsB,EAClE,cAAe,SAAS,eAAe,iBAAiB,EACxD,oBAAqB,SAAS,eAAe,sBAAsB,EACnE,wBAAyB,SAAS,eAAe,2BAA2B,EAE5E,SAAU,SAAS,eAAe,WAAW,EAC7C,kBAAmB,SAAS,eAAe,qBAAqB,EAChE,gBAAiB,SAAS,eAAe,mBAAmB,EAC5D,eAAgB,SAAS,eAAe,kBAAkB,EAC1D,cAAe,SAAS,eAAe,iBAAiB,EACxD,eAAgB,SAAS,eAAe,kBAAkB,EAC1D,eAAgB,SAAS,eAAe,kBAAkB,EAC1D,YAAa,SAAS,eAAe,cAAc,EAEnD,WAAY,SAAS,eAAe,aAAa,EACjD,gBAAiB,SAAS,cAAc,qBAAqB,EAC7D,gBAAiB,SAAS,cAAc,qBAAqB,EAC7D,UAAW,SAAS,eAAe,YAAY,EAC/C,eAAgB,SAAS,eAAe,iBAAiB,EACzD,YAAa,SAAS,eAAe,cAAc,CACrD,EACOA,CACT,CAKO,SAASG,GAA2B,CACzC,OAAOH,CACT,CAKO,SAASI,IAA+B,CACzCC,EAAM,YACRL,EAAS,cAAc,UAAU,OAAO,SAAS,EAEjDA,EAAS,cAAc,UAAU,IAAI,SAAS,CAElD,CAKO,SAASM,GAAqB,CACnC,QAAQ,IAAI,oCAAqCD,EAAM,OAAO,OAAQA,EAAM,OAAO,IAAIE,GAAKA,EAAE,IAAI,CAAC,EAGnG,IAAMC,EAAe,IAAI,IAAIT,EAAc,IAAIQ,GAAKA,EAAE,IAAI,CAAC,EAErDE,EAAOJ,EAAM,OAChB,IAAKK,GAAU,CAEd,IAAMC,EADSC,EAAcF,EAAM,UAAYA,EAAM,UAAU,EAChC,SAAW,GACpCG,EAAWR,EAAM,iBAAmBK,EAAM,KAC1CI,EAAsBJ,EAAM,eAAiB,kBAAoB,GACjEK,EAAYP,EAAa,IAAIE,EAAM,IAAI,EAGvCM,EAAcD,EAAY;AAAA;AAAA;AAAA;AAAA,QAI5B,GAGEE,EAAaF,EAAY;AAAA,0EACqCG,EAAWR,EAAM,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMtF,GAEJ,MAAO;AAAA,gCACmBG,EAAW,SAAW,EAAE,IAAIC,CAAmB,iBAAiBI,EAAWR,EAAM,IAAI,CAAC,KAAKK,EAAY,iCAAmC,EAAE;AAAA,uDACrHA,EAAY,sBAAwBI,EAAeT,EAAM,IAAI,CAAC;AAAA,YACzGU,EAAYV,EAAM,IAAI,CAAC;AAAA,4CACSC,CAAa;AAAA;AAAA,qCAEpBO,EAAWR,EAAM,IAAI,CAAC;AAAA,UACjDM,CAAW;AAAA,UACXN,EAAM,eAAiB,mDAAqD,EAAE;AAAA,UAC9EO,CAAU;AAAA;AAAA,KAGhB,CAAC,EACA,KAAK,EAAE,EAEVjB,EAAS,WAAW,UAClBS,GACA,uGAGFT,EAAS,WAAW,iBAA8B,2BAA2B,EAAE,QAASqB,GAAS,CAC/FA,EAAK,iBAAiB,QAAUC,GAAM,CAEpC,GAAKA,EAAE,OAAuB,QAAQ,cAAc,EAClD,OAEF,IAAMC,EAAYF,EAAK,QAAQ,MAC3BE,GACFC,EAAcD,CAAS,CAE3B,CAAC,CACH,CAAC,EAGDvB,EAAS,WAAW,iBAAoC,4BAA4B,EAAE,QAASyB,GAAQ,CACrGA,EAAI,iBAAiB,QAAS,MAAOH,GAAM,CACzCA,EAAE,gBAAgB,EAClB,IAAMC,EAAYE,EAAI,QAAQ,QAC1BF,GAAa,QAAQ,kBAAkBA,CAAS,mCAAmC,GACrF,MAAMG,GAAaH,CAAS,CAEhC,CAAC,CACH,CAAC,EAGDI,GAAoB,CACtB,CAKO,SAASC,GAAuB,CACrC,IAAMC,EAAWC,GAAoB,EAErC,GAAID,EAAS,SAAW,EAAG,CACzB7B,EAAS,aAAa,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,YAQ1BK,EAAM,iBAAmB,UACrB,2CACA,iBAAiBA,EAAM,cAAc,mBAC3C;AAAA;AAAA;AAAA,MAIN,MACF,CAEA,IAAII,EAAO,GACPsB,EAA0B,KAE9BF,EAAS,QAASG,GAAQ,CACxB,IAAMC,EAAU,IAAI,KAAKD,EAAI,SAAS,EAAE,aAAa,EAGjDC,IAAYF,IACdtB,GAAQ;AAAA;AAAA,4CAE8ByB,GAAWF,EAAI,SAAS,CAAC;AAAA;AAAA,QAG/DD,EAAWE,GAGb,IAAME,EAAcH,EAAI,KAAO,IACzBI,EAAcjB,EAAea,EAAI,IAAI,EACrCK,EAAaC,GAAoBN,EAAI,EAAE,EAIvCO,EAAmBJ,EACrB,YACAH,EAAI,QACF,+BAA+Bd,EAAWc,EAAI,OAAO,CAAC,WAAWd,EAAWc,EAAI,EAAE,CAAC,GACnF,IAAId,EAAWc,EAAI,EAAE,CAAC,GAE5BvB,GAAQ;AAAA,4BACgB0B,EAAc,YAAc,EAAE,cAAcjB,EAAWc,EAAI,EAAE,CAAC;AAAA,yDACjCI,CAAW;AAAA,YACxDhB,EAAYY,EAAI,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,4CAIWd,EAAWc,EAAI,IAAI,CAAC;AAAA;AAAA,4CAEzBO,CAAgB;AAAA;AAAA,8CAETC,EAAWR,EAAI,SAAS,CAAC;AAAA;AAAA,sCAEjCS,EAAkBT,EAAI,OAAO,CAAC;AAAA,YAExDA,EAAI,OACA;AAAA,yDACyCd,EAAWc,EAAI,MAAM,CAAC;AAAA;AAAA;AAAA;AAAA,wBAIvDd,EAAWc,EAAI,MAAM,CAAC;AAAA;AAAA,YAG9B,EACN;AAAA,YAEEK,EAAa,EACT;AAAA,0DAC0CnB,EAAWc,EAAI,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA,gBAI5DK,CAAU,IAAIA,IAAe,EAAI,QAAU,SAAS;AAAA;AAAA,YAGpD,EACN;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAmBR,CAAC,EAEDrC,EAAS,aAAa,UAAYS,EAKlCiC,GAAqB,CACvB,CAKO,SAASlB,EAAcmB,EAA4B,CACxDC,EAAkBD,CAAO,EAGzB3C,EAAS,aAAa,iBAA8B,eAAe,EAAE,QAASqB,GAAS,CACrFA,EAAK,UAAU,OAAO,SAAUA,EAAK,QAAQ,UAAYsB,CAAO,CAClE,CAAC,EACD3C,EAAS,WAAW,iBAA8B,eAAe,EAAE,QAASqB,GAAS,CACnFA,EAAK,UAAU,OAAO,SAAUA,EAAK,QAAQ,QAAUsB,CAAO,CAChE,CAAC,EAGD,IAAME,EAAW,SAAS,cAAc,8BAA8B,EACtE,GAAIF,IAAY,UACd3C,EAAS,mBAAmB,UAAY,UACxCA,EAAS,aAAa,YAAc,2BAChC6C,IAAUA,EAAS,YAAc,SAChC,CACL7C,EAAS,mBAAmB,UAAYkB,EAAWyB,CAAO,EAC1D,IAAMjC,EAAQL,EAAM,OAAO,KAAME,GAAMA,EAAE,OAASoC,CAAO,EACzD3C,EAAS,aAAa,YAAcU,GAAO,QAAU,kBACjDmC,IAAUA,EAAS,YAAc,IACvC,CAGA7C,EAAS,aAAa,YACpB2C,IAAY,UACR,6CACA,WAAWA,CAAO,uBAGxBf,EAAe,CACjB,CAKO,SAASkB,IAA0B,CACxC,IAAMC,EAAS1C,EAAM,OAAO,OAAQE,GAAMK,EAAcL,EAAE,UAAYA,EAAE,UAAU,CAAC,EAAE,OACrFP,EAAS,YAAY,YAAc,GAAG+C,CAAM,SAC9C,CAKO,SAASpB,IAA4B,CAC1C,IAAMlB,EAAOJ,EAAM,OAChB,IAAKK,GAAU,CACd,IAAMqC,EAASnC,EAAcF,EAAM,UAAYA,EAAM,UAAU,EAC/D,MAAO;AAAA,mDACsCQ,EAAWR,EAAM,IAAI,CAAC;AAAA;AAAA,yDAEhBS,EAAeT,EAAM,IAAI,CAAC;AAAA,cACrEU,EAAYV,EAAM,IAAI,CAAC;AAAA,8CACSqC,EAAS,SAAW,EAAE;AAAA;AAAA;AAAA;AAAA,4CAIxB7B,EAAWR,EAAM,IAAI,CAAC;AAAA,+CACnBqC,EAAS,SAAW,SAAS;AAAA;AAAA;AAAA,KAIxE,CAAC,EACA,KAAK,EAAE,EAEJC,EAAUhD,EAAS,qBACXgD,EAAQ,iBAAiB,eAAe,EAChD,QAAS3B,GAASA,EAAK,OAAO,CAAC,EACrC2B,EAAQ,mBAAmB,YAAavC,CAAI,EAG5CuC,EAAQ,iBAA8B,gCAAgC,EAAE,QAAS3B,GAAS,CACxFA,EAAK,iBAAiB,QAAS,IAAM,CACnC,IAAME,EAAYF,EAAK,QAAQ,UAC3BE,IACFC,EAAcD,CAAS,EACvB0B,EAAoB,EAExB,CAAC,CACH,CAAC,CACH,CAKO,SAASC,IAA4B,CAC1ClD,EAAS,uBACN,iBAA8B,kCAAkC,EAChE,QAASqB,GAAS,CACjBA,EAAK,iBAAiB,QAAS,IAAM,CACnC,IAAM8B,EAAc9B,EAAK,QAAQ,YAC7B8B,IACF3B,EAAc2B,CAAW,EACzBF,EAAoB,EAExB,CAAC,CACH,CAAC,CACL,CAKO,SAASG,GAA2B,CACzCpD,EAAS,sBAAsB,UAAU,IAAI,SAAS,EACtDA,EAAS,cAAc,MAAQ,GAC/BA,EAAS,cAAc,MAAM,EAC7BC,EAAuB,GACvBoD,EAAqB,EAAE,CACzB,CAKO,SAASC,IAAwC,CAItD,OAHiB,MAAM,KACrBtD,EAAS,eAAe,iBAA8B,eAAe,CACvE,EACgB,OAAQqB,GAASA,EAAK,MAAM,UAAY,MAAM,CAChE,CAKO,SAASkC,IAA+B,CAC7C,IAAMC,EAAQF,GAAuB,EAMrC,GAHAE,EAAM,QAASnC,GAASA,EAAK,UAAU,OAAO,UAAU,CAAC,EAGrDpB,GAAwB,GAAKA,EAAuBuD,EAAM,OAAQ,CACpE,IAAMC,EAAeD,EAAMvD,CAAoB,EAC/CwD,EAAa,UAAU,IAAI,UAAU,EAGrCA,EAAa,eAAe,CAAE,MAAO,UAAW,SAAU,QAAS,CAAC,CACtE,CACF,CAKO,SAASC,GAAqBpC,EAAwB,CAC3D,IAAMkC,EAAQF,GAAuB,EAErC,GAAIE,EAAM,SAAW,EAErB,OAAQlC,EAAE,IAAK,CACb,IAAK,YACHA,EAAE,eAAe,EACjBrB,EAAuBA,EAAuBuD,EAAM,OAAS,EACzDvD,EAAuB,EACvB,EACJsD,GAAuB,EACvB,MAEF,IAAK,UACHjC,EAAE,eAAe,EACjBrB,EAAuBA,EAAuB,EAC1CA,EAAuB,EACvBuD,EAAM,OAAS,EACnBD,GAAuB,EACvB,MAEF,IAAK,QACHjC,EAAE,eAAe,EACbrB,GAAwB,GAAKA,EAAuBuD,EAAM,QAC5DG,GAAmBH,EAAMvD,CAAoB,CAAC,EAEhD,KACJ,CACF,CAKO,SAAS0D,GAAmBtC,EAAyB,CAE1D,IAAMuC,EAAUvC,EAAK,QAAQ,QAC7B,GAAIuC,EAAS,CACPA,IAAY,aAEd5D,EAAS,aAAa,MAAQ,MAC9BA,EAAS,aAAa,MAAM,GACnB4D,IAAY,UACrB5D,EAAS,aAAa,UAAY,IAEpCiD,EAAoB,EACpB,MACF,CAGA,IAAMN,EAAUtB,EAAK,QAAQ,YAC7B,GAAIsB,EAAS,CACXnB,EAAcmB,CAAO,EACrBM,EAAoB,EACpB,MACF,CAGA,IAAMvC,EAAQW,EAAK,QAAQ,UAC3B,GAAIX,EAAO,CACTc,EAAcd,CAAK,EACnBuC,EAAoB,EACpB,MACF,CAGA,IAAMY,EAAYxC,EAAK,QAAQ,YAC/B,GAAIwC,EAAW,CAEb,IAAMC,EAAY9D,EAAS,aAAa,cAAc,aAAa6D,CAAS,IAAI,EAC5EC,IACFA,EAAU,eAAe,CAAE,SAAU,SAAU,MAAO,QAAS,CAAC,EAChEA,EAAU,UAAU,IAAI,aAAa,EACrC,WAAW,IAAMA,EAAU,UAAU,OAAO,aAAa,EAAG,GAAI,GAElEb,EAAoB,EACpB,MACF,CACF,CAKO,SAASA,GAA4B,CAC1CjD,EAAS,sBAAsB,UAAU,OAAO,SAAS,CAC3D,CAKO,SAASqD,EAAqBU,EAAqB,CACxD,IAAMC,EAAID,EAAM,YAAY,EA0B5B,GAvBA9D,EAAuB,GAGvB,SAAS,iBAA8B,6BAA6B,EAAE,QAASoB,GAAS,CAEtF,IAAM4C,EADU5C,EAAK,cAAc,qBAAqB,GACjC,aAAa,YAAY,GAAK,GACrDA,EAAK,MAAM,QAAU4C,EAAM,SAASD,CAAC,EAAI,OAAS,MACpD,CAAC,EAGD,SAAS,iBAA8B,kCAAkC,EAAE,QAAS3C,GAAS,CAE3F,IAAM4C,EADU5C,EAAK,cAAc,qBAAqB,GACjC,aAAa,YAAY,GAAK,GACrDA,EAAK,MAAM,QAAU4C,EAAM,SAASD,CAAC,EAAI,OAAS,MACpD,CAAC,EAGD,SAAS,iBAA8B,gCAAgC,EAAE,QAAS3C,GAAS,CACzF,IAAM6C,EAAO7C,EAAK,QAAQ,WAAW,YAAY,GAAK,GACtDA,EAAK,MAAM,QAAU6C,EAAK,SAASF,CAAC,EAAI,OAAS,MACnD,CAAC,EAGGA,EAAE,QAAU,EAAG,CACjB,IAAMG,EAAU9D,EAAM,SAAS,OAAQ+D,GAAMA,EAAE,QAAQ,YAAY,EAAE,SAASJ,CAAC,CAAC,EAAE,MAAM,EAAG,CAAC,EAE5F,GAAIG,EAAQ,OAAS,EAAG,CACtBnE,EAAS,uBAAuB,MAAM,QAAU,QAChD,IAAMwD,EAAQW,EACX,IACEC,GAAM;AAAA,uDACsClD,EAAWkD,EAAE,EAAE,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,8CAOzBlD,EAAWkD,EAAE,IAAI,CAAC;AAAA,iDACflD,EAAWkD,EAAE,QAAQ,UAAU,EAAG,EAAE,CAAC,CAAC,GAAGA,EAAE,QAAQ,OAAS,GAAK,MAAQ,EAAE;AAAA;AAAA;AAAA,OAIpH,EACC,KAAK,EAAE,EAEYpE,EAAS,uBAAuB,iBAAiB,eAAe,EACxE,QAASqB,GAASA,EAAK,OAAO,CAAC,EAC7CrB,EAAS,uBAAuB,mBAAmB,YAAawD,CAAK,CACvE,MACExD,EAAS,uBAAuB,MAAM,QAAU,MAEpD,MACEA,EAAS,uBAAuB,MAAM,QAAU,MAEpD,CAKO,SAASqE,EAAgBC,EAAwB,CACtDC,EAAiBD,CAAQ,EACzBtE,EAAS,cAAc,YAAcsE,EACrCtE,EAAS,mBAAmB,UAAU,IAAI,SAAS,EACnDA,EAAS,mBAAmB,MAAQ,GACpCwE,EAAqBF,CAAQ,EAC7BtE,EAAS,mBAAmB,MAAM,CACpC,CAKO,SAASyE,GAAyB,CACvCF,EAAiB,IAAI,EACrBvE,EAAS,mBAAmB,UAAU,OAAO,SAAS,CACxD,CAKO,SAASwE,EAAqBF,EAAwB,CAC3D,IAAMI,EAAWC,GAAkBL,CAAQ,EAE3C,GAAII,EAAS,SAAW,EAAG,CACzB1E,EAAS,eAAe,UAAY;AAAA;AAAA;AAAA;AAAA;AAAA,MAMpC,MACF,CAEA,IAAMS,EAAOiE,EACV,IAAK1C,GAAQ;AAAA;AAAA;AAAA,kEAGgDb,EAAea,EAAI,IAAI,CAAC;AAAA,cAC5EZ,EAAYY,EAAI,IAAI,CAAC;AAAA;AAAA,gDAEad,EAAWc,EAAI,IAAI,CAAC;AAAA,8CACtBQ,EAAWR,EAAI,SAAS,CAAC;AAAA;AAAA,2CAE5BS,EAAkBT,EAAI,OAAO,CAAC;AAAA;AAAA,KAEpE,EACA,KAAK,EAAE,EAEVhC,EAAS,eAAe,UAAYS,EAGpCT,EAAS,eAAe,UAAYA,EAAS,eAAe,YAC9D,CAKO,SAAS0C,IAA6B,CAE3C1C,EAAS,aAAa,iBAA8B,mBAAmB,EAAE,QAAS4E,GAAO,CACvFA,EAAG,MAAM,OAAS,UAClBA,EAAG,iBAAiB,QAAU,GAAM,CAClC,EAAE,gBAAgB,EAClB,IAAMN,EAAWM,EAAG,QAAQ,OACxBN,GACFD,EAAgBC,CAAQ,CAE5B,CAAC,CACH,CAAC,EAGDtE,EAAS,aAAa,iBAA8B,oBAAoB,EAAE,QAAS4E,GAAO,CACxFA,EAAG,iBAAiB,QAAU,GAAM,CAClC,EAAE,gBAAgB,EAClB,IAAMN,EAAWM,EAAG,QAAQ,OACxBN,GACFD,EAAgBC,CAAQ,CAE5B,CAAC,CACH,CAAC,EAGDtE,EAAS,aAAa,iBAA8B,0CAA0C,EAAE,QAAS4E,GAAO,CAC9GA,EAAG,iBAAiB,QAAU,GAAM,CAClC,EAAE,gBAAgB,EAClB,IAAMf,EAAYe,EAAG,QAAQ,UAAU,GAAG,aAAa,SAAS,EAC5Df,GAEFQ,EAAgBR,CAAS,CAE7B,CAAC,CACH,CAAC,CACH,CAKA,IAAIgB,EAAuB,EACvBC,EAA6C,CAAC,EAK3C,SAASC,GAAwBC,EAAsB,CAC5D,IAAMC,EAAcD,EAAO,YAAY,EAGvCF,EAAwBzE,EAAM,OAAO,OAAOK,GAC1CA,EAAM,KAAK,YAAY,EAAE,SAASuE,CAAW,CAC/C,EAGAJ,EAAuB,EAGvB,IAAIpE,EAAO,IAGP,IAAI,SAASwE,CAAW,GAAK,WAAW,SAASA,CAAW,GAAK,MAAM,SAASA,CAAW,GAAK,YAAY,SAASA,CAAW,KAClIxE,GAAQ;AAAA,8CACkCoE,IAAyB,GAAKC,EAAsB,SAAW,EAAI,WAAa,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA,OAS9HA,EAAsB,QAAQ,CAACpE,EAAOwE,IAAU,CAE9CzE,GAAQ;AAAA,8CADWyE,IAAUL,EAE0B,WAAa,EAAE,mBAAmB3D,EAAWR,EAAM,IAAI,CAAC;AAAA,uDAC5DS,EAAeT,EAAM,IAAI,CAAC;AAAA,YACrEU,EAAYV,EAAM,IAAI,CAAC;AAAA;AAAA,mDAEgBQ,EAAWR,EAAM,IAAI,CAAC;AAAA,kDACvBQ,EAAWR,EAAM,MAAQ,OAAO,CAAC;AAAA;AAAA,KAGjF,CAAC,EAEGD,IAAS,KACXA,EAAO,sHAGTT,EAAS,wBAAwB,UAAYS,EAC7CT,EAAS,oBAAoB,UAAU,IAAI,SAAS,EAGpDA,EAAS,wBAAwB,iBAA8B,0CAA0C,EAAE,QAASqB,GAAS,CAC3HA,EAAK,iBAAiB,QAAS,IAAM,CACnC,IAAM8D,EAAU9D,EAAK,QAAQ,QACzB8D,GACFC,EAAgBD,CAAO,CAE3B,CAAC,CACH,CAAC,CACH,CAKO,SAASE,GAAgC,CAC9CrF,EAAS,oBAAoB,UAAU,OAAO,SAAS,EACvD8E,EAAwB,CAAC,EACzBD,EAAuB,CACzB,CAKO,SAASS,IAAwC,CACtD,OAAOtF,EAAS,oBAAoB,UAAU,SAAS,SAAS,CAClE,CAKO,SAASuF,EAA4BC,EAAgC,CAC1E,IAAMhC,EAAQxD,EAAS,wBAAwB,iBAA8B,0CAA0C,EACnHwD,EAAM,SAAW,IAGrBA,EAAMqB,CAAoB,GAAG,UAAU,OAAO,UAAU,EAGpDW,IAAc,OAChBX,GAAwBA,EAAuB,GAAKrB,EAAM,OAE1DqB,GAAwBA,EAAuB,EAAIrB,EAAM,QAAUA,EAAM,OAI3EA,EAAMqB,CAAoB,GAAG,UAAU,IAAI,UAAU,EACrDrB,EAAMqB,CAAoB,GAAG,eAAe,CAAE,MAAO,SAAU,CAAC,EAClE,CAKO,SAASO,EAAgBD,EAAwB,CACtD,IAAM3B,EAAQxD,EAAS,wBAAwB,iBAA8B,0CAA0C,EAGnHyF,EAAkBN,EAKtB,GAJI,CAACM,GAAmBjC,EAAM,OAAS,IACrCiC,EAAkBjC,EAAMqB,CAAoB,GAAG,QAAQ,SAGrD,CAACY,EAAiB,CACpBJ,EAAwB,EACxB,MACF,CAGA,IAAMK,EAAQ1F,EAAS,aACjB2F,EAAQD,EAAM,MAGdE,EAAUD,EAAM,MAAM,OAAO,EACnC,GAAIC,EAAS,CAEX,IAAMC,EAAgB,IAAIJ,CAAe,IACzCC,EAAM,MAAQG,EAAgBF,EAAM,UAAUC,EAAQ,CAAC,EAAE,MAAM,EAC/DF,EAAM,eAAiBA,EAAM,aAAeG,EAAc,MAC5D,CAEAR,EAAwB,EACxBK,EAAM,MAAM,CACd,CAKO,SAASI,IAAwC,CACtD,IAAMJ,EAAQ1F,EAAS,aACjB2F,EAAQD,EAAM,MACdK,EAAYL,EAAM,eAGlBE,EAAUD,EAAM,MAAM,SAAS,EACrC,OAAIC,GAAWG,GAAaH,EAAQ,CAAC,EAAE,OAC9BA,EAAQ,CAAC,EAGX,IACT,CASO,SAASI,IAAuB,CACrChG,EAAS,kBAAkB,UAAU,IAAI,SAAS,EAClDA,EAAS,eAAe,MAAQ,GAChCA,EAAS,cAAc,MAAQ,SAC/BA,EAAS,eAAe,MAAQ,GAChCA,EAAS,YAAY,YAAc,GACnCA,EAAS,YAAY,UAAY,eACjCA,EAAS,eAAe,MAAM,CAChC,CAKO,SAASiG,GAAwB,CACtCjG,EAAS,kBAAkB,UAAU,OAAO,SAAS,CACvD,CAKA,eAAsBkG,GAA4D,CAChF,IAAMhC,EAAOlE,EAAS,eAAe,MAAM,KAAK,EAC1CmG,EAAMnG,EAAS,cAAc,MAAM,KAAK,GAAK,SAC7CoG,EAAOpG,EAAS,eAAe,MAAM,KAAK,EAEhD,GAAI,CAACkE,EACH,OAAAlE,EAAS,YAAY,YAAc,yBACnCA,EAAS,YAAY,UAAY,qBAC1B,CAAE,QAAS,GAAO,MAAO,wBAAyB,EAG3DA,EAAS,eAAe,SAAW,GACnCA,EAAS,YAAY,YAAc,oBACnCA,EAAS,YAAY,UAAY,uBAEjC,GAAI,CACF,IAAMqG,EAAW,MAAM,MAAM,aAAc,CACzC,OAAQ,OACR,QAAS,CAAE,eAAgB,kBAAmB,EAC9C,KAAM,KAAK,UAAU,CAAE,KAAAnC,EAAM,IAAAiC,EAAK,KAAAC,CAAK,CAAC,CAC1C,CAAC,EAEKE,EAAS,MAAMD,EAAS,KAAK,EAEnC,GAAIA,EAAS,IAAMC,EAAO,QACxB,OAAAtG,EAAS,YAAY,YAAc,UAAUkE,CAAI,0BACjDlE,EAAS,YAAY,UAAY,uBAGjC,MAAMuG,EAAmB,EAGzB,WAAW,IAAM,CACfN,EAAgB,CAClB,EAAG,GAAI,EAEA,CAAE,QAAS,EAAK,EAEvB,MAAM,IAAI,MAAMK,EAAO,OAAS,uBAAuB,CAE3D,OAASE,EAAU,CACjB,OAAAxG,EAAS,YAAY,YAAcwG,EAAI,SAAW,wBAClDxG,EAAS,YAAY,UAAY,qBAC1B,CAAE,QAAS,GAAO,MAAOwG,EAAI,OAAQ,CAC9C,QAAE,CACAxG,EAAS,eAAe,SAAW,EACrC,CACF,CAKA,eAAsBuG,GAAoC,CACxD,GAAI,CAEF,IAAMD,EAAS,MADE,MAAM,MAAM,cAAc,GACb,KAAK,EAE/BA,EAAO,SAAW,MAAM,QAAQA,EAAO,MAAM,IAC/CvG,EAAgBuG,EAAO,OAEvBhG,EAAa,EAEjB,OAASkG,EAAK,CACZ,QAAQ,MAAM,uCAAwCA,CAAG,CAC3D,CACF,CAKA,eAAsB9E,GAAawC,EAA6B,CAC9D,GAAI,CAKF,IAAMoC,EAAS,MAJE,MAAM,MAAM,gBAAgB,mBAAmBpC,CAAI,CAAC,GAAI,CACvE,OAAQ,QACV,CAAC,GAE6B,KAAK,EAE/BoC,EAAO,QAET,MAAMC,EAAmB,EAEzB,QAAQ,MAAM,gCAAiCD,EAAO,KAAK,CAE/D,OAASE,EAAK,CACZ,QAAQ,MAAM,gCAAiCA,CAAG,CACpD,CACF,CAgBO,SAASC,IAA4B,CAC1CC,EAAS,iBAAiB,iBAAiB,QAAS,IAAM,CACxDC,EAAe,OAAO,CACxB,CAAC,EAEDD,EAAS,iBAAiB,iBAAiB,QAAS,IAAM,CACxDC,EAAe,OAAO,CACxB,CAAC,CACH,CAKO,SAASA,EAAeC,EAAsB,CACnDC,GAAYD,CAAI,EAGhBF,EAAS,iBAAiB,UAAU,OAAO,SAAUE,IAAS,OAAO,EACrEF,EAAS,iBAAiB,UAAU,OAAO,SAAUE,IAAS,OAAO,EAGjEF,EAAS,iBACXA,EAAS,eAAe,MAAM,QAAUE,IAAS,QAAU,QAAU,QAIvEE,EAAa,EAGTF,IAAS,SACXG,EAAc,CAElB,CAKO,SAASC,IAAkC,CAChD,IAAMC,EAAYC,EAAa,EACzBC,EAAWF,GAAaA,EAAU,QAAQ,OAAS,EAGrDP,EAAS,aACXA,EAAS,WAAW,MAAM,QAAUS,EAAW,OAAS,QAItDT,EAAS,WAAaO,IACxBP,EAAS,UAAU,YAAc,OAAOO,EAAU,QAAQ,MAAM,GAI9D,CAACE,GAAYC,EAAY,IAAM,SACjCT,EAAe,OAAO,CAE1B,CAKO,SAASI,GAAsB,CACpC,IAAME,EAAYC,EAAa,EAE/B,GAAI,CAACD,GAAaA,EAAU,QAAQ,SAAW,EAAG,CAC5CP,EAAS,cACXA,EAAS,YAAY,UAAY,6GAEnC,MACF,CAEA,IAAMW,EAAOJ,EAAU,QAAQ,IAAIK,GAAU,CAC3C,IAAMC,EAAUD,EAAO,KAAOL,EAAU,cAClCO,EAAcF,EAAO,UAAY,GAAK,UAE5C,MAAO;AAAA,6CACkCG,EAAWH,EAAO,EAAE,CAAC;AAAA,0CACxBC,EAAU,qCAAuC,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oCAMzDC,CAAW;AAAA;AAAA,oCAEXC,EAAWH,EAAO,IAAI,CAAC,GAAGC,EAAU,WAAa,EAAE;AAAA,oCACnDD,EAAO,UAAU;AAAA;AAAA,KAGnD,CAAC,EAAE,KAAK,EAAE,EAENZ,EAAS,cACXA,EAAS,YAAY,UAAYW,EAErC,CAMO,SAASK,IAA0B,CACxC,IAAMC,EAAWP,EAAY,EACvBH,EAAYC,EAAa,EAE/B,GAAIS,IAAa,SAAW,CAACV,EAAW,CACtCH,EAAa,EACb,MACF,CAGA,IAAMc,EAASX,EAAU,OACnBY,EAAe,IAAI,IAAIC,EAAc,IAAIC,GAAKA,EAAE,IAAI,CAAC,EAErDV,EAAOO,EAAO,IAAKI,GAAsB,CAC7C,IAAMC,EAASC,EAAcF,EAAM,UAAYA,EAAM,UAAU,EACzDG,EAAgBF,EAAS,SAAW,GACpCG,EAAWC,EAAM,iBAAmBL,EAAM,KAC1CM,EAAsBN,EAAM,eAAiB,kBAAoB,GACjEO,EAAYV,EAAa,IAAIG,EAAM,IAAI,EACvCT,EAAUS,EAAM,QAGhBQ,GAAc;AAAA,kCACUjB,EAAU,QAAU,EAAE;AAAA,kCACtBU,EAAS,GAAK,SAAS;AAAA,UAC/CR,EAAWO,EAAM,YAAcA,EAAM,MAAM,CAAC;AAAA;AAAA,MAK5CS,GAAmBlB,EAErB,GAF+B;AAAA,8CACOE,EAAWO,EAAM,UAAU,CAAC;AAAA,MAIhEU,GAAcH,EAAY;AAAA;AAAA;AAAA;AAAA,MAI5B,GAGEI,GAAaJ,EAAY;AAAA,wEACqCd,EAAWO,EAAM,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMtF,GAEJ,MAAO;AAAA,gCACqBI,EAAW,SAAW,EAAE,IAAIE,CAAmB,iBAAiBb,EAAWO,EAAM,IAAI,CAAC,kBAAkBP,EAAWO,EAAM,MAAM,CAAC;AAAA,uDACzGO,EAAY,sBAAwBK,EAAeZ,EAAM,IAAI,CAAC;AAAA,YACzGa,EAAYb,EAAM,IAAI,CAAC;AAAA,4CACSG,CAAa;AAAA,YAC7CM,EAAe;AAAA;AAAA,UAEjBD,EAAW;AAAA,qCACgBf,EAAWO,EAAM,IAAI,CAAC;AAAA,UACjDU,EAAW;AAAA,UACXV,EAAM,eAAiB,mDAAqD,EAAE;AAAA,UAC9EW,EAAU;AAAA;AAAA,KAGlB,CAAC,EAAE,KAAK,EAAE,EAEVjC,EAAS,WAAW,UAAYW,GAAQ,sGAGxCyB,GAAyB,EAGzBC,GAAoB,CACtB,CAKA,SAASD,IAAiC,CACxCpC,EAAS,WAAW,iBAA8B,2BAA2B,EAAE,QAASsC,GAAS,CAC/FA,EAAK,iBAAiB,QAAU,GAAM,CACpC,GAAK,EAAE,OAAuB,QAAQ,cAAc,EAClD,OAEF,IAAMC,EAAYD,EAAK,QAAQ,MAC3BC,GACFC,EAAcD,CAAS,CAE3B,CAAC,CACH,CAAC,EAEDvC,EAAS,WAAW,iBAAoC,4BAA4B,EAAE,QAASyC,GAAQ,CACrGA,EAAI,iBAAiB,QAAS,MAAO,GAAM,CACzC,EAAE,gBAAgB,EAClB,IAAMF,EAAYE,EAAI,QAAQ,QAC1BF,GAAa,QAAQ,kBAAkBA,CAAS,mCAAmC,GACrF,MAAMG,GAAaH,CAAS,CAEhC,CAAC,CACH,CAAC,CACH,CC5mCO,SAASI,IAAgB,CAC9B,IAAMC,EAAWC,GAAa,EAG9BC,EAAU,IAAM,CACdC,GAAuB,EAEnBC,EAAY,IAAM,SACpBC,GAAkB,EAClBC,EAAc,GAEdC,EAAa,EAEfC,EAAe,EACfC,GAAkB,EAElBC,GAA0B,CAC5B,CAAC,EAGDC,GAAoBX,CAAQ,EAG5BY,GAAoB,EAGpBC,EAAQ,EAGRC,EAAmB,CACrB,CAKA,SAASH,GAAoBX,EAAgD,CAE3EA,EAAS,aAAa,iBAA8B,eAAe,EAAE,QAASe,GAAS,CACrFA,EAAK,iBAAiB,QAAS,IAAM,CACnC,IAAMC,EAAUD,EAAK,QAAQ,QACzBC,GACFC,EAAcD,CAAO,CAEzB,CAAC,CACH,CAAC,EAGDhB,EAAS,QAAQ,iBAAiB,QAASkB,EAAU,EAGrDlB,EAAS,aAAa,iBAAiB,UAAY,GAAqB,CAEtE,GAAImB,GAA6B,EAAG,CAClC,GAAI,EAAE,MAAQ,OAAS,EAAE,MAAQ,QAAS,CACxC,EAAE,eAAe,EACjBC,EAAgB,EAChB,MACF,CACA,GAAI,EAAE,MAAQ,UAAW,CACvB,EAAE,eAAe,EACjBC,EAA4B,IAAI,EAChC,MACF,CACA,GAAI,EAAE,MAAQ,YAAa,CACzB,EAAE,eAAe,EACjBA,EAA4B,MAAM,EAClC,MACF,CACA,GAAI,EAAE,MAAQ,SAAU,CACtB,EAAE,eAAe,EACjBC,EAAwB,EACxB,MACF,CACF,CAGI,EAAE,MAAQ,SAAW,CAAC,EAAE,WAC1B,EAAE,eAAe,EACjBJ,GAAW,EAGf,CAAC,EAGDlB,EAAS,aAAa,iBAAiB,QAAS,IAAM,CACpDA,EAAS,aAAa,MAAM,OAAS,OACrCA,EAAS,aAAa,MAAM,OAC1B,KAAK,IAAIA,EAAS,aAAa,aAAc,GAAG,EAAI,KAGtD,IAAMuB,EAAQC,GAAuB,EACjCD,IAAU,KACZE,GAAwBF,CAAK,EAE7BD,EAAwB,CAE5B,CAAC,EAGDtB,EAAS,aAAa,iBAAiB,OAAQ,IAAM,CACnD,WAAW,IAAM,CACfsB,EAAwB,CAC1B,EAAG,GAAG,CACR,CAAC,EAGDtB,EAAS,QAAQ,iBAAiB,QAAS,IAAM,CAC/C,IAAM0B,EAAQ1B,EAAS,aACjB2B,EAAQD,EAAM,eACdE,EAAMF,EAAM,aACZG,EAAOH,EAAM,MAEnB,GAAIC,IAAUC,EAAK,CAEjB,IAAME,EAASD,EAAK,UAAU,EAAGF,CAAK,EAChCI,EAAQF,EAAK,UAAUD,CAAG,EAChCF,EAAM,MAAQI,EAAS,WAAaC,EACpCL,EAAM,eAAiBC,EAAQ,EAC/BD,EAAM,aAAeC,EAAQ,CAC/B,KAAO,CAEL,IAAMG,EAASD,EAAK,UAAU,EAAGF,CAAK,EAChCK,EAAWH,EAAK,UAAUF,EAAOC,CAAG,EACpCG,EAAQF,EAAK,UAAUD,CAAG,EAChCF,EAAM,MAAQI,EAAS,KAAOE,EAAW,KAAOD,EAChDL,EAAM,eAAiBC,EACvBD,EAAM,aAAeE,EAAM,CAC7B,CACAF,EAAM,MAAM,CACd,CAAC,EAGD1B,EAAS,SAAS,iBAAiB,QAAS,IAAM,CAChD,IAAMiC,EAAS,CAAC,YAAM,YAAM,SAAK,SAAK,YAAM,YAAM,YAAM,eAAM,YAAM,WAAI,EAClEC,EAAQD,EAAO,KAAK,MAAM,KAAK,OAAO,EAAIA,EAAO,MAAM,CAAC,EACxDP,EAAQ1B,EAAS,aACjB2B,EAAQD,EAAM,eACdG,EAAOH,EAAM,MACnBA,EAAM,MAAQG,EAAK,UAAU,EAAGF,CAAK,EAAIO,EAAQL,EAAK,UAAUF,CAAK,EACrED,EAAM,eAAiBA,EAAM,aAAeC,EAAQO,EAAM,OAC1DR,EAAM,MAAM,CACd,CAAC,EAGD1B,EAAS,cAAc,iBAAiB,QAASmC,CAAkB,EAEnE,SAAS,iBAAiB,UAAY,GAAqB,EACpD,EAAE,SAAW,EAAE,UAAY,EAAE,MAAQ,MACxC,EAAE,eAAe,EACbnC,EAAS,sBAAsB,UAAU,SAAS,SAAS,EAC7DoC,EAAoB,EAEpBD,EAAmB,GAInB,EAAE,MAAQ,UACZC,EAAoB,CAExB,CAAC,EAEDpC,EAAS,sBAAsB,iBAAiB,QAAU,GAAkB,CACtE,EAAE,SAAWA,EAAS,uBACxBoC,EAAoB,CAExB,CAAC,EAEDpC,EAAS,cAAc,iBAAiB,QAAU,GAAa,CAC7D,IAAMqC,EAAS,EAAE,OACjBC,EAAqBD,EAAO,KAAK,CACnC,CAAC,EAEDrC,EAAS,cAAc,iBAAiB,UAAWuC,EAAoB,EAGvE,SAAS,iBAA8B,6BAA6B,EAAE,QAASxB,GAAS,CACtFA,EAAK,iBAAiB,QAAS,IAAM,CACnC,IAAMyB,EAAUzB,EAAK,QAAQ,QAEzByB,IAAY,aAEdxC,EAAS,aAAa,MAAQ,MAC9BA,EAAS,aAAa,MAAM,GACnBwC,IAAY,UACrBxC,EAAS,aAAa,UAAY,IAGpCoC,EAAoB,CACtB,CAAC,CACH,CAAC,EAGDK,GAAoB,EAGpBzC,EAAS,iBAAiB,iBAAiB,QAAS0C,CAAgB,EAGpE1C,EAAS,cAAc,iBAAiB,QAAS2C,EAAgB,EAGjE3C,EAAS,mBAAmB,iBAAiB,UAAY,GAAqB,CACxE,EAAE,MAAQ,SAAW,CAAC,EAAE,WAC1B,EAAE,eAAe,EACjB2C,GAAiB,EAGrB,CAAC,EAGD,SAAS,iBAAiB,UAAY,GAAqB,CACrD,EAAE,MAAQ,UAAY3C,EAAS,mBAAmB,UAAU,SAAS,SAAS,GAChF0C,EAAiB,CAErB,CAAC,EAGD1C,EAAS,SAAS,iBAAiB,QAAS4C,EAAc,EAE1D5C,EAAS,gBAAgB,iBAAiB,QAAS6C,CAAe,EAGlE,SAAS,eAAe,kBAAkB,GAAG,iBAAiB,QAASA,CAAe,EAGtF7C,EAAS,kBAAkB,iBAAiB,QAAU,GAAkB,CAClE,EAAE,SAAWA,EAAS,mBACxB6C,EAAgB,CAEpB,CAAC,EAGD,SAAS,iBAAiB,UAAY,GAAqB,CACrD,EAAE,MAAQ,UAAY7C,EAAS,kBAAkB,UAAU,SAAS,SAAS,GAC/E6C,EAAgB,CAEpB,CAAC,EAGD7C,EAAS,eAAe,iBAAiB,QAAS8C,CAAU,EAG5D9C,EAAS,eAAe,iBAAiB,UAAY,GAAqB,CACpE,EAAE,MAAQ,SAAW,CAAC,EAAE,WAC1B,EAAE,eAAe,EACjB8C,EAAW,EAEf,CAAC,CACH,CAOA,SAASC,GAAalB,EAAsD,CAK1E,IAAMmB,EAJUnB,EAAK,KAAK,EAIJ,MAAM,wBAAwB,EAEpD,OAAKmB,EAIE,CACL,GAAIA,EAAM,CAAC,EACX,QAASA,EAAM,CAAC,EAAE,KAAK,CACzB,EANS,IAOX,CAKA,eAAe9B,IAA4B,CACzC,IAAMlB,EAAWiD,EAAY,EACvBC,EAAalD,EAAS,aAAa,MAAM,KAAK,EAEpD,GAAI,CAACkD,EACH,OAGF,IAAIC,EACAC,EAGEC,EAASC,EAAM,iBAAmB,UAGlCC,EAASR,GAAaG,CAAU,EAEtC,GAAIK,EAEFJ,EAAKI,EAAO,GACZH,EAAUG,EAAO,gBACRF,EAETF,EAAKG,EAAM,eACXF,EAAUF,MACL,CAEL,MAAM,4EAA4E,EAClF,MACF,CAEAlD,EAAS,QAAQ,SAAW,GAE5B,IAAMwD,EAAS,MAAMC,EAAYN,EAAIC,CAAO,EAExCI,EAAO,SACTxD,EAAS,aAAa,MAAQ,GAC9BA,EAAS,aAAa,MAAM,OAAS,QAErC,MAAMwD,EAAO,KAAK,EAGpBxD,EAAS,QAAQ,SAAW,EAC9B,CAKA,eAAe2C,IAAkC,CAC/C,IAAM3C,EAAWiD,EAAY,EACvBG,EAAUpD,EAAS,mBAAmB,MAAM,KAAK,EACjD0D,EAAWJ,EAAM,cAEvB,GAAI,CAACF,GAAW,CAACM,EACf,OAKF1D,EAAS,cAAc,SAAW,GAElC,IAAMwD,EAAS,MAAMC,EAAY,IAAKL,EAASM,CAAQ,EAEnDF,EAAO,SACTxD,EAAS,mBAAmB,MAAQ,GAEpC2D,EAAqBD,CAAQ,GAE7B,MAAMF,EAAO,KAAK,EAGpBxD,EAAS,cAAc,SAAW,EACpC,CAGI,OAAO,SAAa,MAClB,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoBD,EAAO,EAErDA,GAAQ",
|
|
6
|
+
"names": ["state", "listeners", "subscribe", "listener", "index", "notifyListeners", "setAgents", "agents", "setMessages", "messages", "setCurrentChannel", "channel", "setConnectionStatus", "connected", "incrementReconnectAttempts", "setWebSocket", "ws", "getFilteredMessages", "currentChannel", "m", "setCurrentThread", "thread", "getThreadMessages", "threadId", "getThreadReplyCount", "setViewMode", "mode", "getViewMode", "setFleetData", "data", "getFleetData", "dataHandler", "connect", "protocol", "ws", "setConnectionStatus", "delay", "state", "incrementReconnectAttempts", "error", "event", "data", "handleData", "e", "setWebSocket", "a", "setAgents", "setMessages", "setFleetData", "dataHandler", "sendMessage", "to", "message", "thread", "body", "response", "result", "isAgentOnline", "lastSeen", "ts", "escapeHtml", "text", "div", "formatTime", "timestamp", "formatDate", "date", "today", "yesterday", "getAvatarColor", "name", "colors", "hash", "i", "getInitials", "formatMessageBody", "content", "escaped", "_match", "_lang", "code", "spawnedAgents", "elements", "paletteSelectedIndex", "initElements", "getElements", "updateConnectionStatus", "state", "renderAgents", "a", "spawnedNames", "html", "agent", "presenceClass", "isAgentOnline", "isActive", "needsAttentionClass", "isSpawned", "spawnedIcon", "releaseBtn", "escapeHtml", "getAvatarColor", "getInitials", "item", "e", "agentName", "selectChannel", "btn", "releaseAgent", "updatePaletteAgents", "renderMessages", "filtered", "getFilteredMessages", "lastDate", "msg", "msgDate", "formatDate", "isBroadcast", "avatarColor", "replyCount", "getThreadReplyCount", "recipientDisplay", "formatTime", "formatMessageBody", "attachThreadHandlers", "channel", "setCurrentChannel", "prefixEl", "updateOnlineCount", "online", "section", "closeCommandPalette", "initPaletteChannels", "channelName", "openCommandPalette", "filterPaletteResults", "getVisiblePaletteItems", "updatePaletteSelection", "items", "selectedItem", "handlePaletteKeydown", "executePaletteItem", "command", "messageId", "messageEl", "query", "q", "title", "name", "matches", "m", "openThreadPanel", "threadId", "setCurrentThread", "renderThreadMessages", "closeThreadPanel", "messages", "getThreadMessages", "el", "mentionSelectedIndex", "mentionFilteredAgents", "showMentionAutocomplete", "filter", "filterLower", "index", "mention", "completeMention", "hideMentionAutocomplete", "isMentionAutocompleteVisible", "navigateMentionAutocomplete", "direction", "selectedMention", "input", "value", "atMatch", "completedText", "getCurrentMentionQuery", "cursorPos", "openSpawnModal", "closeSpawnModal", "spawnAgent", "cli", "task", "response", "result", "fetchSpawnedAgents", "err", "initFleetViewToggle", "elements", "switchViewMode", "mode", "setViewMode", "renderAgents", "renderServers", "updateFleetViewVisibility", "fleetData", "getFleetData", "hasFleet", "getViewMode", "html", "server", "isLocal", "statusClass", "escapeHtml", "renderFleetAgents", "viewMode", "agents", "spawnedNames", "spawnedAgents", "a", "agent", "online", "isAgentOnline", "presenceClass", "isActive", "state", "needsAttentionClass", "isSpawned", "serverBadge", "serverIndicator", "spawnedIcon", "releaseBtn", "getAvatarColor", "getInitials", "attachAgentClickHandlers", "updatePaletteAgents", "item", "agentName", "selectChannel", "btn", "releaseAgent", "initApp", "elements", "initElements", "subscribe", "updateConnectionStatus", "getViewMode", "renderFleetAgents", "renderServers", "renderAgents", "renderMessages", "updateOnlineCount", "updateFleetViewVisibility", "setupEventListeners", "initFleetViewToggle", "connect", "fetchSpawnedAgents", "item", "channel", "selectChannel", "handleSend", "isMentionAutocompleteVisible", "completeMention", "navigateMentionAutocomplete", "hideMentionAutocomplete", "query", "getCurrentMentionQuery", "showMentionAutocomplete", "input", "start", "end", "text", "before", "after", "selected", "emojis", "emoji", "openCommandPalette", "closeCommandPalette", "target", "filterPaletteResults", "handlePaletteKeydown", "command", "initPaletteChannels", "closeThreadPanel", "handleThreadSend", "openSpawnModal", "closeSpawnModal", "spawnAgent", "parseMention", "match", "getElements", "rawMessage", "to", "message", "isInDM", "state", "parsed", "result", "sendMessage", "threadId", "renderThreadMessages"]
|
|
7
|
+
}
|