aicodeman 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/web/public/{api-client.eb389d1b.js → api-client.3adebdc2.js} +1 -1
  2. package/dist/web/public/api-client.3adebdc2.js.br +0 -0
  3. package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
  4. package/dist/web/public/app.9dab4ee2.js +26 -0
  5. package/dist/web/public/app.9dab4ee2.js.br +0 -0
  6. package/dist/web/public/app.9dab4ee2.js.gz +0 -0
  7. package/dist/web/public/{constants.7555acc0.js → constants.cd61abbc.js} +4 -2
  8. package/dist/web/public/constants.cd61abbc.js.br +0 -0
  9. package/dist/web/public/constants.cd61abbc.js.gz +0 -0
  10. package/dist/web/public/index.html +17 -11
  11. package/dist/web/public/index.html.br +0 -0
  12. package/dist/web/public/index.html.gz +0 -0
  13. package/dist/web/public/input-cjk.92544c51.js.gz +0 -0
  14. package/dist/web/public/{keyboard-accessory.30cd57bd.js → keyboard-accessory.9fb81db6.js} +1 -1
  15. package/dist/web/public/keyboard-accessory.9fb81db6.js.br +0 -0
  16. package/dist/web/public/keyboard-accessory.9fb81db6.js.gz +0 -0
  17. package/dist/web/public/{mobile-handlers.91ad48b8.js → mobile-handlers.d4830fd3.js} +1 -1
  18. package/dist/web/public/mobile-handlers.d4830fd3.js.br +0 -0
  19. package/dist/web/public/mobile-handlers.d4830fd3.js.gz +0 -0
  20. package/dist/web/public/mobile.42fde818.css.gz +0 -0
  21. package/dist/web/public/{notification-manager.2e817554.js → notification-manager.2d5ea8ec.js} +1 -1
  22. package/dist/web/public/notification-manager.2d5ea8ec.js.br +0 -0
  23. package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
  24. package/dist/web/public/panels-ui.d7f6be08.js +240 -0
  25. package/dist/web/public/panels-ui.d7f6be08.js.br +0 -0
  26. package/dist/web/public/panels-ui.d7f6be08.js.gz +0 -0
  27. package/dist/web/public/ralph-panel.7b014f16.js +69 -0
  28. package/dist/web/public/ralph-panel.7b014f16.js.br +0 -0
  29. package/dist/web/public/ralph-panel.7b014f16.js.gz +0 -0
  30. package/dist/web/public/{ralph-wizard.44a8d04a.js → ralph-wizard.f31ab90e.js} +1 -1
  31. package/dist/web/public/ralph-wizard.f31ab90e.js.br +0 -0
  32. package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
  33. package/dist/web/public/respawn-ui.372c6ea7.js +47 -0
  34. package/dist/web/public/respawn-ui.372c6ea7.js.br +0 -0
  35. package/dist/web/public/respawn-ui.372c6ea7.js.gz +0 -0
  36. package/dist/web/public/session-ui.0a07c3b7.js +16 -0
  37. package/dist/web/public/session-ui.0a07c3b7.js.br +0 -0
  38. package/dist/web/public/session-ui.0a07c3b7.js.gz +0 -0
  39. package/dist/web/public/settings-ui.94c57184.js +55 -0
  40. package/dist/web/public/settings-ui.94c57184.js.br +0 -0
  41. package/dist/web/public/settings-ui.94c57184.js.gz +0 -0
  42. package/dist/web/public/styles.b8ec2f5a.css.gz +0 -0
  43. package/dist/web/public/{subagent-windows.eb8470b0.js → subagent-windows.a366a4ad.js} +2 -2
  44. package/dist/web/public/subagent-windows.a366a4ad.js.br +0 -0
  45. package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
  46. package/dist/web/public/sw.js.gz +0 -0
  47. package/dist/web/public/terminal-ui.e4565c7b.js +3 -0
  48. package/dist/web/public/terminal-ui.e4565c7b.js.br +0 -0
  49. package/dist/web/public/terminal-ui.e4565c7b.js.gz +0 -0
  50. package/dist/web/public/upload.html.gz +0 -0
  51. package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
  52. package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
  53. package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
  54. package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
  55. package/dist/web/public/vendor/xterm.css.gz +0 -0
  56. package/dist/web/public/vendor/xterm.min.js.gz +0 -0
  57. package/dist/web/public/{voice-input.4c22ccd8.js → voice-input.085e9e73.js} +1 -1
  58. package/dist/web/public/voice-input.085e9e73.js.br +0 -0
  59. package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
  60. package/package.json +1 -1
  61. package/dist/web/public/api-client.eb389d1b.js.br +0 -0
  62. package/dist/web/public/api-client.eb389d1b.js.gz +0 -0
  63. package/dist/web/public/app.f3a26c70.js +0 -450
  64. package/dist/web/public/app.f3a26c70.js.br +0 -0
  65. package/dist/web/public/app.f3a26c70.js.gz +0 -0
  66. package/dist/web/public/constants.7555acc0.js.br +0 -0
  67. package/dist/web/public/constants.7555acc0.js.gz +0 -0
  68. package/dist/web/public/keyboard-accessory.30cd57bd.js.br +0 -0
  69. package/dist/web/public/keyboard-accessory.30cd57bd.js.gz +0 -0
  70. package/dist/web/public/mobile-handlers.91ad48b8.js.br +0 -0
  71. package/dist/web/public/mobile-handlers.91ad48b8.js.gz +0 -0
  72. package/dist/web/public/notification-manager.2e817554.js.br +0 -0
  73. package/dist/web/public/notification-manager.2e817554.js.gz +0 -0
  74. package/dist/web/public/ralph-wizard.44a8d04a.js.br +0 -0
  75. package/dist/web/public/ralph-wizard.44a8d04a.js.gz +0 -0
  76. package/dist/web/public/subagent-windows.eb8470b0.js.br +0 -0
  77. package/dist/web/public/subagent-windows.eb8470b0.js.gz +0 -0
  78. package/dist/web/public/voice-input.4c22ccd8.js.br +0 -0
  79. package/dist/web/public/voice-input.4c22ccd8.js.gz +0 -0
@@ -0,0 +1,240 @@
1
+ "use strict";Object.assign(CodemanApp.prototype,{_onTaskCreated(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskCompleted(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskFailed(e){this.renderSessionTabs(),e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onTaskUpdated(e){e.sessionId===this.activeSessionId&&this.renderTaskPanel()},_onMuxCreated(e){this.muxSessions.push(e),this.renderMuxSessions()},_onMuxKilled(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions()},_onMuxDied(e){this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e.sessionId),this.renderMuxSessions(),this.showToast("Mux session died: "+this.getShortId(e.sessionId),"warning")},_onMuxStatsUpdated(e){this.muxSessions=e,document.getElementById("monitorPanel").classList.contains("open")&&this.renderMuxSessions()},_onBashToolStart(e){this.handleBashToolStart(e.sessionId,e.tool)},_onBashToolEnd(e){this.handleBashToolEnd(e.sessionId,e.tool)},_onBashToolsUpdate(e){this.handleBashToolsUpdate(e.sessionId,e.tools)},_onSubagentDiscovered(e){if(this.subagents.set(e.agentId,e),this.subagentActivity.set(e.agentId,[]),this.subagentToolResults.delete(e.agentId),this.subagentWindows.has(e.agentId)&&this.forceCloseSubagentWindow(e.agentId),this.renderSubagentPanel(),this.findParentSessionForSubagent(e.agentId),e.status==="active"){const n=this.subagents.get(e.agentId);n?.sessionId&&Array.from(this.sessions.values()).some(o=>o.claudeSessionId===n.sessionId)&&this.openSubagentWindow(e.agentId)}requestAnimationFrame(()=>{this.updateConnectionLines()});const t=this.subagentParentMap.get(e.agentId),s=t?this.sessions.get(t):null;this.notificationManager?.notify({urgency:"info",category:"subagent-spawn",sessionId:t||e.sessionId,sessionName:s?.name||t||e.sessionId,title:"Subagent Spawned",message:e.description||"New background agent started"})},_onSubagentUpdated(e){const t=this.subagents.get(e.agentId);t?(Object.assign(t,e),this.subagents.set(e.agentId,t)):this.subagents.set(e.agentId,e),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&(this.renderSubagentWindowContent(e.agentId),this.updateSubagentWindowHeader(e.agentId))},_onSubagentToolCall(e){const t=this.subagentActivity.get(e.agentId)||[];t.push({type:"tool",...e}),t.length>50&&t.shift(),this.subagentActivity.set(e.agentId,t),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.renderSubagentPanel(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentProgress(e){const t=this.subagentActivity.get(e.agentId)||[];t.push({type:"progress",...e}),t.length>50&&t.shift(),this.subagentActivity.set(e.agentId,t),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentMessage(e){const t=this.subagentActivity.get(e.agentId)||[];t.push({type:"message",...e}),t.length>50&&t.shift(),this.subagentActivity.set(e.agentId,t),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},_onSubagentToolResult(e){this.subagentToolResults.has(e.agentId)||this.subagentToolResults.set(e.agentId,new Map);const t=this.subagentToolResults.get(e.agentId);if(t.set(e.toolUseId,e),t.size>50){const n=t.keys().next().value;t.delete(n)}const s=this.subagentActivity.get(e.agentId)||[];s.push({type:"tool_result",...e}),s.length>50&&s.shift(),this.subagentActivity.set(e.agentId,s),this.activeSubagentId===e.agentId&&this.renderSubagentDetail(),this.subagentWindows.has(e.agentId)&&this.scheduleSubagentWindowRender(e.agentId)},async _onSubagentCompleted(e){const t=this.subagents.get(e.agentId);if(t&&(t.status="completed",this.subagents.set(e.agentId,t)),this.renderSubagentPanel(),this.updateSubagentWindows(),this.subagentWindows.has(e.agentId)){const a=this.subagentWindows.get(e.agentId);a&&!a.minimized&&(await this.closeSubagentWindow(e.agentId),this.saveSubagentWindowStates())}const s=this.subagentParentMap.get(e.agentId),n=s?this.sessions.get(s):null;this.notificationManager?.notify({urgency:"info",category:"subagent-complete",sessionId:s||t?.sessionId||e.sessionId,sessionName:n?.name||s||e.sessionId,title:"Subagent Completed",message:t?.description||e.description||"Background agent finished"}),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&(this.subagentActivity.delete(e.agentId),this.subagentToolResults.delete(e.agentId))},300*1e3),setTimeout(()=>{this.subagents.get(e.agentId)?.status==="completed"&&!this.subagentWindows.has(e.agentId)&&(this.subagents.delete(e.agentId),this.subagentParentMap.delete(e.agentId))},1800*1e3)},_onImageDetected(e){console.log("[Image Detected]",e),this.openImagePopup(e)},async openTokenStats(){try{const t=await(await fetch("/api/token-stats")).json();t.success?(this.renderTokenStats(t),document.getElementById("tokenStatsModal").classList.add("active")):this.showToast("Failed to load token stats","error")}catch(e){console.error("Failed to fetch token stats:",e),this.showToast("Failed to load token stats","error")}},renderTokenStats(e){const{daily:t,totals:s}=e,n=new Date().toISOString().split("T")[0],a=t.find(v=>v.date===n)||{inputTokens:0,outputTokens:0,estimatedCost:0},o=new Date;o.setDate(o.getDate()-7);const r=t.filter(v=>new Date(v.date)>=o),i=r.reduce((v,S)=>v+S.inputTokens,0),l=r.reduce((v,S)=>v+S.outputTokens,0),c=this.estimateCost(i,l),d=s.totalInputTokens,u=s.totalOutputTokens,h=this.estimateCost(d,u),m=document.getElementById("statsSummary");m.innerHTML=`
2
+ <div class="stat-card">
3
+ <span class="stat-card-label">Today</span>
4
+ <span class="stat-card-value">${this.formatTokens(a.inputTokens+a.outputTokens)}</span>
5
+ <span class="stat-card-cost">~$${a.estimatedCost.toFixed(2)}</span>
6
+ </div>
7
+ <div class="stat-card">
8
+ <span class="stat-card-label">7 Days</span>
9
+ <span class="stat-card-value">${this.formatTokens(i+l)}</span>
10
+ <span class="stat-card-cost">~$${c.toFixed(2)}</span>
11
+ </div>
12
+ <div class="stat-card">
13
+ <span class="stat-card-label">Lifetime</span>
14
+ <span class="stat-card-value">${this.formatTokens(d+u)}</span>
15
+ <span class="stat-card-cost">~$${h.toFixed(2)}</span>
16
+ </div>
17
+ `;const g=document.getElementById("statsChart"),f=document.getElementById("statsChartDays"),w=[];for(let v=6;v>=0;v--){const S=new Date;S.setDate(S.getDate()-v);const T=S.toISOString().split("T")[0],y=t.find(D=>D.date===T);w.push({date:T,dayName:S.toLocaleDateString("en-US",{weekday:"short"}),tokens:y?y.inputTokens+y.outputTokens:0,cost:y?y.estimatedCost:0})}const p=Math.max(...w.map(v=>v.tokens),1);g.innerHTML=w.map(v=>{const S=Math.max(v.tokens/p*100,3),T=`${v.dayName}: ${this.formatTokens(v.tokens)} (~$${v.cost.toFixed(2)})`;return`<div class="bar" style="height: ${S}%" data-tooltip="${T}"></div>`}).join(""),f.innerHTML=w.map(v=>`<span>${v.dayName}</span>`).join("");const b=document.getElementById("statsTable"),$=t.slice(0,14);$.length===0?b.innerHTML='<div class="stats-no-data">No usage data recorded yet</div>':b.innerHTML=`
18
+ <div class="stats-table-header">
19
+ <span>Date</span>
20
+ <span>Input</span>
21
+ <span>Output</span>
22
+ <span>Cost</span>
23
+ </div>
24
+ ${$.map(v=>`
25
+ <div class="stats-table-row">
26
+ <span class="cell cell-date">${new Date(v.date+"T00:00:00").toLocaleDateString("en-US",{month:"short",day:"numeric"})}</span>
27
+ <span class="cell">${this.formatTokens(v.inputTokens)}</span>
28
+ <span class="cell">${this.formatTokens(v.outputTokens)}</span>
29
+ <span class="cell cell-cost">$${v.estimatedCost.toFixed(2)}</span>
30
+ </div>
31
+ `).join("")}
32
+ `},closeTokenStats(){const e=document.getElementById("tokenStatsModal");e&&e.classList.remove("active")},async toggleMonitorPanel(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorToggleBtn");e.classList.toggle("open"),e.classList.contains("open")?(await this.loadMuxSessions(),await fetch("/api/mux-sessions/stats/start",{method:"POST"}),this.renderTaskPanel(),t&&(t.innerHTML="&#x25BC;")):(await fetch("/api/mux-sessions/stats/stop",{method:"POST"}),t&&(t.innerHTML="&#x25B2;"))},toggleTaskPanel(){this.toggleMonitorPanel()},toggleMonitorDetach(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupMonitorDrag())},setupMonitorDrag(){const e=document.getElementById("monitorPanel"),t=document.getElementById("monitorPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let g=o+h,f=r+m;const w=e.getBoundingClientRect();g=Math.max(0,Math.min(window.innerWidth-w.width,g)),f=Math.max(0,Math.min(window.innerHeight-w.height,f)),e.style.left=g+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},toggleSubagentsDetach(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsDetachBtn");e.classList.contains("detached")?(e.classList.remove("detached"),e.style.top="",e.style.left="",e.style.width="",e.style.height="",t&&(t.innerHTML="&#x29C9;",t.title="Detach panel")):(e.classList.add("detached"),e.classList.add("open"),t&&(t.innerHTML="&#x229E;",t.title="Attach panel"),this.setupSubagentsDrag())},setupSubagentsDrag(){const e=document.getElementById("subagentsPanel"),t=document.getElementById("subagentsPanelHeader");if(!e||!t)return;let s=!1,n,a,o,r;const i=d=>{if(d.target.closest("button")||!e.classList.contains("detached"))return;s=!0;const u=getEventCoords(d);n=u.clientX,a=u.clientY;const h=e.getBoundingClientRect();o=h.left,r=h.top,document.addEventListener("mousemove",l),document.addEventListener("mouseup",c),document.addEventListener("touchmove",l,{passive:!1}),document.addEventListener("touchend",c),d.preventDefault()},l=d=>{if(!s)return;const u=getEventCoords(d),h=u.clientX-n,m=u.clientY-a;let g=o+h,f=r+m;const w=e.getBoundingClientRect();g=Math.max(0,Math.min(window.innerWidth-w.width,g)),f=Math.max(0,Math.min(window.innerHeight-w.height,f)),e.style.left=g+"px",e.style.top=f+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",l),document.removeEventListener("mouseup",c),document.removeEventListener("touchmove",l),document.removeEventListener("touchend",c)};t.removeEventListener("mousedown",t._dragHandler),t.removeEventListener("touchstart",t._touchDragHandler),t._dragHandler=i,t._touchDragHandler=i,t.addEventListener("mousedown",i),t.addEventListener("touchstart",i,{passive:!1})},renderTaskPanel(){this.renderTaskPanelTimeout&&clearTimeout(this.renderTaskPanelTimeout),this.renderTaskPanelTimeout=setTimeout(()=>{this._renderTaskPanelImmediate()},100)},_renderTaskPanelImmediate(){const e=this.sessions.get(this.activeSessionId),t=document.getElementById("backgroundTasksBody"),s=document.getElementById("taskPanelStats"),n=document.getElementById("backgroundTasksSection");if(!e||!e.taskTree||e.taskTree.length===0){n&&(n.style.display="none"),t.innerHTML="",s.textContent="0 tasks";return}n&&(n.style.display="");const a=e.taskStats||{running:0,completed:0,failed:0,total:0};s.textContent=`${a.running} running, ${a.completed} done`;const o=(l,c)=>{const d=l.status==="running"?"":l.status==="completed"?"&#x2713;":"&#x2717;",u=l.endTime?`${((l.endTime-l.startTime)/1e3).toFixed(1)}s`:`${((Date.now()-l.startTime)/1e3).toFixed(0)}s...`;let h="";if(l.children&&l.children.length>0){h='<div class="task-children">';for(const m of l.children){const g=c.find(f=>f.id===m);g&&(h+=`<div class="task-node">${o(g,c)}</div>`)}h+="</div>"}return`
33
+ <div class="task-item">
34
+ <span class="task-status-icon ${l.status}">${d}</span>
35
+ <div class="task-info">
36
+ <div class="task-description">${escapeHtml(l.description)}</div>
37
+ <div class="task-meta">
38
+ <span class="task-type">${l.subagentType}</span>
39
+ <span>${u}</span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ ${h}
44
+ `},r=this.flattenTaskTree(e.taskTree);let i='<div class="task-tree">';for(const l of e.taskTree)i+=`<div class="task-node">${o(l,r)}</div>`;i+="</div>",t.innerHTML=i},flattenTaskTree(e,t=[]){for(const s of e)t.push(s);return t},toggleSubagentPanel(){this.toggleSubagentsPanel()},updateSubagentBadge(){const e=this.$("subagentCountBadge"),t=Array.from(this.subagents.values()).filter(s=>s.status==="active"||s.status==="idle").length;e&&(e.textContent=t>0?t:"")},renderSubagentPanel(){this._subagentPanelRenderTimeout&&clearTimeout(this._subagentPanelRenderTimeout),this._subagentPanelRenderTimeout=setTimeout(()=>{scheduleBackground(()=>this._renderSubagentPanelImmediate())},150)},_renderSubagentPanelImmediate(){const e=this.$("subagentList");if(!e||(this.updateSubagentBadge(),this.renderMonitorSubagents(),!this.subagentPanelVisible))return;if(this.subagents.size===0){e.innerHTML='<div class="subagent-empty">No background agents detected</div>';return}const t=[],s=Array.from(this.subagents.values()).sort((n,a)=>n.status==="active"&&a.status!=="active"?-1:a.status==="active"&&n.status!=="active"?1:(a.lastActivityAt||0)-(n.lastActivityAt||0));for(const n of s){const a=this.activeSubagentId===n.agentId,o=n.status==="active"?"active":n.status==="idle"?"idle":"completed",r=this.subagentActivity.get(n.agentId)||[],i=r[r.length-1],l=i?.type==="tool"?i.tool:null,c=this.subagentWindows.has(n.agentId),d=n.status==="active"||n.status==="idle",u=n.modelShort?`<span class="subagent-model-badge ${escapeHtml(n.modelShort)}">${escapeHtml(n.modelShort)}</span>`:"",h=this.getTeammateInfo(n),m=h?h.name:n.description||n.agentId.substring(0,7),g=this.getTeammateBadgeHtml(n),f=h?`<span class="subagent-icon teammate-dot teammate-color-${h.color}">\u25CF</span>`:'<span class="subagent-icon">\u{1F916}</span>';t.push(`
45
+ <div class="subagent-item ${o} ${a?"selected":""}${h?" is-teammate":""}"
46
+ onclick="app.selectSubagent('${escapeHtml(n.agentId)}')"
47
+ ondblclick="app.openSubagentWindow('${escapeHtml(n.agentId)}')"
48
+ title="Double-click to open tracking window">
49
+ <div class="subagent-header">
50
+ ${f}
51
+ <span class="subagent-id" title="${escapeHtml(n.description||n.agentId)}">${escapeHtml(m.length>40?m.substring(0,40)+"...":m)}</span>
52
+ ${g}
53
+ ${u}
54
+ <span class="subagent-status ${o}">${n.status}</span>
55
+ ${d?`<button class="subagent-kill-btn" onclick="event.stopPropagation(); app.killSubagent('${escapeHtml(n.agentId)}')" title="Kill agent">&#x2715;</button>`:""}
56
+ <button class="subagent-window-btn" onclick="event.stopPropagation(); app.${c?"closeSubagentWindow":"openSubagentWindow"}('${escapeHtml(n.agentId)}')" title="${c?"Close window":"Open in window"}">
57
+ ${c?"\u2715":"\u29C9"}
58
+ </button>
59
+ </div>
60
+ <div class="subagent-meta">
61
+ <span class="subagent-tools">${n.toolCallCount} tools</span>
62
+ ${l?`<span class="subagent-last-tool">${this.getToolIcon(l)} ${l}</span>`:""}
63
+ </div>
64
+ </div>
65
+ `)}e.innerHTML=t.join("")},selectSubagent(e){this.activeSubagentId=e,this.renderSubagentPanel(),this.renderSubagentDetail()},renderSubagentDetail(){const e=this.$("subagentDetail");if(!e)return;if(!this.activeSubagentId){e.innerHTML='<div class="subagent-empty">Select an agent to view details</div>';return}const t=this.subagents.get(this.activeSubagentId),s=this.subagentActivity.get(this.activeSubagentId)||[];if(!t){e.innerHTML='<div class="subagent-empty">Agent not found</div>';return}const n=s.slice(-30).map(i=>{const l=new Date(i.timestamp).toLocaleTimeString("en-US",{hour12:!1});if(i.type==="tool"){const c=this.getToolDetailExpanded(i.tool,i.input,i.fullInput,i.toolUseId);return`<div class="subagent-activity tool" data-tool-use-id="${i.toolUseId||""}">
66
+ <span class="time">${l}</span>
67
+ <span class="icon">${this.getToolIcon(i.tool)}</span>
68
+ <span class="name">${i.tool}</span>
69
+ <span class="detail">${c.primary}</span>
70
+ ${c.hasMore?`<button class="tool-expand-btn" onclick="app.toggleToolParams('${escapeHtml(i.toolUseId)}')">\u25B6</button>`:""}
71
+ ${c.hasMore?`<div class="tool-params-expanded" id="tool-params-${i.toolUseId}" style="display:none;"><pre>${escapeHtml(JSON.stringify(i.fullInput||i.input,null,2))}</pre></div>`:""}
72
+ </div>`}else if(i.type==="tool_result"){const c=i.isError?"\u274C":"\u{1F4C4}",d=i.isError?"error":"",u=i.contentLength>500?` (${this.formatBytes(i.contentLength)})`:"",h=i.preview.length>80?i.preview.substring(0,80)+"...":i.preview;return`<div class="subagent-activity tool-result ${d}">
73
+ <span class="time">${l}</span>
74
+ <span class="icon">${c}</span>
75
+ <span class="name">${i.tool||"result"}</span>
76
+ <span class="detail">${escapeHtml(h)}${u}</span>
77
+ </div>`}else if(i.type==="progress"){const c=i.hookEvent||i.hookName,d=c?"\u{1FA9D}":i.progressType==="query_update"?"\u27F3":"\u2713",u=c?" hook":"",h=c?i.hookName||i.hookEvent:i.query||i.progressType;return`<div class="subagent-activity progress${u}">
78
+ <span class="time">${l}</span>
79
+ <span class="icon">${d}</span>
80
+ <span class="detail">${h}</span>
81
+ </div>`}else if(i.type==="message"){const c=i.text.length>100?i.text.substring(0,100)+"...":i.text;return`<div class="subagent-activity message">
82
+ <span class="time">${l}</span>
83
+ <span class="icon">\u{1F4AC}</span>
84
+ <span class="detail">${escapeHtml(c)}</span>
85
+ </div>`}return""}).join(""),a=t.description||`Agent ${t.agentId}`,o=t.modelShort?`<span class="subagent-model-badge ${escapeHtml(t.modelShort)}">${escapeHtml(t.modelShort)}</span>`:"",r=t.totalInputTokens||t.totalOutputTokens?`<span>Tokens: ${this.formatTokenCount(t.totalInputTokens||0)}\u2193 ${this.formatTokenCount(t.totalOutputTokens||0)}\u2191</span>`:"";e.innerHTML=`
86
+ <div class="subagent-detail-header">
87
+ <span class="subagent-id" title="${escapeHtml(t.description||t.agentId)}">${escapeHtml(a.length>60?a.substring(0,60)+"...":a)}</span>
88
+ ${o}
89
+ <span class="subagent-status ${t.status}">${t.status}</span>
90
+ <button class="subagent-transcript-btn" onclick="app.viewSubagentTranscript('${escapeHtml(t.agentId)}')">
91
+ View Full Transcript
92
+ </button>
93
+ </div>
94
+ <div class="subagent-detail-stats">
95
+ <span>Tools: ${t.toolCallCount}</span>
96
+ <span>Entries: ${t.entryCount}</span>
97
+ <span>Size: ${(t.fileSize/1024).toFixed(1)}KB</span>
98
+ ${r}
99
+ </div>
100
+ <div class="subagent-activity-log">
101
+ ${n||'<div class="subagent-empty">No activity yet</div>'}
102
+ </div>
103
+ `},toggleToolParams(e){const t=document.getElementById(`tool-params-${e}`);if(!t)return;const s=t.previousElementSibling;t.style.display==="none"?(t.style.display="block",s&&(s.textContent="\u25BC")):(t.style.display="none",s&&(s.textContent="\u25B6"))},formatTokenCount(e){return e>=1e6?(e/1e6).toFixed(1)+"M":e>=1e3?(e/1e3).toFixed(1)+"k":e.toString()},formatBytes(e){return e>=1024*1024?(e/(1024*1024)).toFixed(1)+"MB":e>=1024?(e/1024).toFixed(1)+"KB":e+"B"},getToolIcon(e){return{WebSearch:"\u{1F50D}",WebFetch:"\u{1F310}",Read:"\u{1F4D6}",Write:"\u{1F4DD}",Edit:"\u270F\uFE0F",Bash:"\u{1F4BB}",Glob:"\u{1F4C1}",Grep:"\u{1F50E}",Task:"\u{1F916}"}[e]||"\u{1F527}"},getToolDetail(e,t){if(!t)return"";if(e==="WebSearch"&&t.query)return`"${t.query}"`;if(e==="WebFetch"&&t.url)return t.url;if(e==="Read"&&t.file_path||(e==="Write"||e==="Edit")&&t.file_path)return t.file_path;if(e==="Bash"&&t.command){const s=t.command;return s.length>40?s.substring(0,40)+"...":s}return e==="Glob"&&t.pattern||e==="Grep"&&t.pattern?t.pattern:""},getToolDetailExpanded(e,t,s,n){const a=this.getToolDetail(e,t),o=["query","url","file_path","command","pattern"],l=Object.keys(s||t||{}).filter(c=>!o.includes(c)).length>0||s&&JSON.stringify(s).length>100;return{primary:a,hasMore:l,fullInput:s||t}},async killSubagent(e){try{const s=await(await this._apiDelete(`/api/subagents/${e}`))?.json();if(s?.success){const n=this.subagents.get(e);n&&(n.status="completed",this.subagents.set(e,n)),this.renderSubagentPanel(),this.renderSubagentDetail(),this.updateSubagentWindows(),this.showToast(`Subagent ${e.substring(0,7)} killed`,"success")}else this.showToast(s.error||"Failed to kill subagent","error")}catch(t){console.error("Failed to kill subagent:",t),this.showToast("Failed to kill subagent: "+t.message,"error")}},async viewSubagentTranscript(e){try{const s=await(await fetch(`/api/subagents/${e}/transcript?format=formatted`)).json();if(!s.success){alert("Failed to load transcript");return}const n=s.data.formatted.join(`
104
+ `);window.open("","_blank","width=800,height=600").document.write(`
105
+ <html>
106
+ <head>
107
+ <title>Subagent ${escapeHtml(e)} Transcript</title>
108
+ <style>
109
+ body { background: #1a1a2e; color: #eee; font-family: monospace; padding: 20px; }
110
+ pre { white-space: pre-wrap; word-wrap: break-word; }
111
+ </style>
112
+ </head>
113
+ <body>
114
+ <h2>Subagent ${escapeHtml(e)} Transcript (${s.data.entryCount} entries)</h2>
115
+ <pre>${escapeHtml(n)}</pre>
116
+ </body>
117
+ </html>
118
+ `)}catch(t){console.error("Failed to load transcript:",t),alert("Failed to load transcript: "+t.message)}},findParentSessionForSubagent(e){if(this.subagentParentMap.has(e)){const s=this.subagentParentMap.get(e);if(this.sessions.has(s)){const n=this.subagents.get(e);if(n&&!n.parentSessionId){n.parentSessionId=s;const a=this.sessions.get(s);a&&(n.parentSessionName=this.getSessionName(a)),this.subagents.set(e,n),this.updateSubagentWindowParent(e)}return}this.subagentParentMap.delete(e)}const t=this.subagents.get(e);if(t){if(t.sessionId){for(const[s,n]of this.sessions)if(n.claudeSessionId===t.sessionId){this.setAgentParentSessionId(e,s),this.updateSubagentWindowParent(e),this.updateSubagentWindowVisibility(),this.updateConnectionLines();return}}if(this.activeSessionId&&this.sessions.has(this.activeSessionId)){this.setAgentParentSessionId(e,this.activeSessionId),this.updateSubagentWindowParent(e),this.updateSubagentWindowVisibility(),this.updateConnectionLines();return}if(this.sessions.size>0){const s=this.sessions.keys().next().value;this.setAgentParentSessionId(e,s),this.updateSubagentWindowParent(e),this.updateSubagentWindowVisibility(),this.updateConnectionLines()}}},recheckOrphanSubagents(){let e=!1;for(const[t,s]of this.subagents)if(!this.subagentParentMap.has(t))this.findParentSessionForSubagent(t),this.subagentParentMap.has(t)&&(e=!0);else if(s.sessionId){const n=this.subagentParentMap.get(t),a=this.sessions.get(n);if(a&&a.claudeSessionId!==s.sessionId){for(const[o,r]of this.sessions)if(r.claudeSessionId===s.sessionId){this.subagentParentMap.set(t,o),s.parentSessionId=o,s.parentSessionName=this.getSessionName(r),this.subagents.set(t,s),this.updateSubagentWindowParent(t),e=!0;break}}}e&&(this.saveSubagentParentMap(),this.updateConnectionLines())},updateSubagentParentNames(e){const t=this.sessions.get(e);if(!t)return;const s=this.getSessionName(t);if(this._parentNameCache?.get(e)!==s){this._parentNameCache||(this._parentNameCache=new Map),this._parentNameCache.set(e,s);for(const[a,o]of this.subagentParentMap)if(o===e){const r=this.subagents.get(a);if(r){r.parentSessionName=s,this.subagents.set(a,r);const i=this.subagentWindows.get(a);if(i){const l=i.element.querySelector(".subagent-window-parent .parent-name");l&&(l.textContent=s)}}}}},updateSubagentWindowParent(e){const t=this.subagentWindows.get(e);if(!t)return;const s=this.subagentParentMap.get(e);if(!s)return;const n=this.sessions.get(s),a=n?this.getSessionName(n):"Unknown",o=t.element,r=o.querySelector(".subagent-window-parent");if(r){r.dataset.parentSession=s;const l=r.querySelector(".parent-name");l&&(l.textContent=a,l.onclick=()=>this.selectSession(s));return}const i=o.querySelector(".subagent-window-header");if(i){const l=document.createElement("div");l.className="subagent-window-parent",l.dataset.parentSession=s,l.innerHTML=`
119
+ <span class="parent-label">from</span>
120
+ <span class="parent-name" onclick="app.selectSession('${escapeHtml(s)}')">${escapeHtml(a)}</span>
121
+ `,i.insertAdjacentElement("afterend",l)}},updateSubagentWindowVisibility(){const t=this.loadAppSettingsFromStorage().subagentActiveTabOnly??!0;for(const[s,n]of this.subagentWindows){const a=this.subagentParentMap.get(s),o=this.subagents.get(s),r=a||o?.parentSessionId;let i;t?i=!!!r||r===this.activeSessionId:i=!0,i?(n.minimized||(n.element.style.display="flex",n._lazyTerminal&&this._restoreTeammateTerminalFromLazy(s)),n.hidden=!1):(this._disposeTeammateTerminalForMinimize(s),n.element.style.display="none",n.hidden=!0)}this.updateConnectionLines(),this.relayoutMobileSubagentWindows()},closeSessionSubagentWindows(e,t=!1){const s=[];for(const[n,a]of this.subagentWindows){const o=this.subagents.get(n),r=this.subagentParentMap.get(n);(o?.parentSessionId===e||r===e)&&s.push(n)}for(const n of s)this.forceCloseSubagentWindow(n),t&&(this.subagents.delete(n),this.subagentActivity.delete(n),this.subagentToolResults.delete(n),this.subagentParentMap.delete(n));this.minimizedSubagents.delete(e),this.renderSessionTabs()},forceCloseSubagentWindow(e){const t=this.subagentWindows.get(e);t&&(t.resizeObserver&&t.resizeObserver.disconnect(),t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.touchMove&&(document.removeEventListener("touchmove",t.dragListeners.touchMove),document.removeEventListener("touchend",t.dragListeners.up),document.removeEventListener("touchcancel",t.dragListeners.up)),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.subagentWindows.delete(e));const s=this.teammateTerminals.get(e);if(s){if(s.resizeObserver&&s.resizeObserver.disconnect(),s.terminal)try{s.terminal.dispose()}catch{}this.teammateTerminals.delete(e)}},minimizeSubagentWindow(e){const t=this.subagentWindows.get(e);t&&(this._disposeTeammateTerminalForMinimize(e),t.element.style.display="none",t.minimized=!0,this.updateConnectionLines())},scheduleSubagentWindowRender(e){this.subagentWindows.get(e)?.minimized||(this._subagentWindowRenderTimeouts||(this._subagentWindowRenderTimeouts=new Map),this._subagentWindowRenderTimeouts.has(e)&&clearTimeout(this._subagentWindowRenderTimeouts.get(e)),this._subagentWindowRenderTimeouts.set(e,setTimeout(()=>{this._subagentWindowRenderTimeouts.delete(e),scheduleBackground(()=>this.renderSubagentWindowContent(e))},100)))},renderSubagentWindowContent(e){if(this.teammateTerminals.has(e)||this.subagentWindows.get(e)?._lazyTerminal)return;const s=document.getElementById(`subagent-window-body-${e}`);if(!s)return;const n=this.subagentActivity.get(e)||[];if(n.length===0){s.innerHTML='<div class="subagent-empty">No activity yet</div>';return}const a=s.dataset.renderedCount?parseInt(s.dataset.renderedCount,10):0,o=100,r=n.slice(-o);if(a===0||a>r.length||s.children.length===0||s.children.length===1&&s.querySelector(".subagent-empty")){const i=r.map(l=>this._renderActivityItem(l)).join("");s.innerHTML=i,s.dataset.renderedCount=String(r.length)}else{const i=r.slice(a);if(i.length>0){const l=i.map(c=>this._renderActivityItem(c)).join("");for(s.insertAdjacentHTML("beforeend",l),s.dataset.renderedCount=String(r.length);s.children.length>o;)s.removeChild(s.firstChild)}}s.scrollTop=s.scrollHeight},_renderActivityItem(e){const t=new Date(e.timestamp).toLocaleTimeString("en-US",{hour12:!1});if(e.type==="tool")return`<div class="activity-line">
122
+ <span class="time">${t}</span>
123
+ <span class="tool-icon">${this.getToolIcon(e.tool)}</span>
124
+ <span class="tool-name">${e.tool}</span>
125
+ <span class="tool-detail">${escapeHtml(this.getToolDetail(e.tool,e.input))}</span>
126
+ </div>`;if(e.type==="tool_result"){const s=e.isError?"\u274C":"\u{1F4C4}",n=e.isError?" error":"",a=e.contentLength>500?` (${this.formatBytes(e.contentLength)})`:"",o=e.preview.length>60?e.preview.substring(0,60)+"...":e.preview;return`<div class="activity-line result-line${n}">
127
+ <span class="time">${t}</span>
128
+ <span class="tool-icon">${s}</span>
129
+ <span class="tool-name">${e.tool||"\u2192"}</span>
130
+ <span class="tool-detail">${escapeHtml(o)}${a}</span>
131
+ </div>`}else if(e.type==="progress"){const s=e.hookEvent||e.hookName,n=s?"\u{1FA9D}":e.progressType==="query_update"?"\u27F3":"\u2713",a=s?e.hookName||e.hookEvent:e.query||e.progressType;return`<div class="activity-line progress-line${s?" hook-line":""}">
132
+ <span class="time">${t}</span>
133
+ <span class="tool-icon">${n}</span>
134
+ <span class="tool-detail">${escapeHtml(a)}</span>
135
+ </div>`}else if(e.type==="message"){const s=e.text.length>150?e.text.substring(0,150)+"...":e.text;return`<div class="message-line">
136
+ <span class="time">${t}</span> \u{1F4AC} ${escapeHtml(s)}
137
+ </div>`}return""},updateSubagentWindows(){for(const e of this.subagentWindows.keys())this.renderSubagentWindowContent(e),this.updateSubagentWindowHeader(e)},updateSubagentWindowHeader(e){const t=this.subagents.get(e);if(!t)return;const s=document.getElementById(`subagent-window-${e}`);if(!s)return;const n=s.querySelector(".subagent-window-title .id");if(n){const c=this.getTeammateInfo(t),d=c?c.name:t.description||e.substring(0,7),u=d.length>50?d.substring(0,50)+"...":d;n.textContent=u}let a=s.querySelector(".teammate-badge");const o=this.getTeammateInfo(t);if(o&&!a){const c=s.querySelector(".subagent-window-title");if(c){const d=document.createElement("span");d.className=`teammate-badge teammate-color-${o.color}`,d.title=`Team: ${o.teamName}`,d.textContent=`@${o.name}`;const u=c.querySelector(".status");u&&u.insertAdjacentElement("beforebegin",d)}}const r=s.querySelector(".subagent-window-title");r&&(r.title=t.description||e);let i=s.querySelector(".subagent-window-title .subagent-model-badge");if(t.modelShort){if(!i){i=document.createElement("span"),i.className=`subagent-model-badge ${t.modelShort}`;const c=s.querySelector(".subagent-window-title .status");c&&c.insertAdjacentElement("beforebegin",i)}i.className=`subagent-model-badge ${t.modelShort}`,i.textContent=t.modelShort}const l=s.querySelector(".subagent-window-title .status");l&&(l.className=`status ${t.status}`,l.textContent=t.status)},openAllActiveSubagentWindows(){for(const[e,t]of this.subagents)t.status==="active"&&!this.subagentWindows.has(e)&&this.openSubagentWindow(e)},initTeammateTerminal(e,t,s){const n=s.querySelector(".subagent-window-body");if(!n)return;n.innerHTML="",n.classList.add("teammate-terminal-body"),s.classList.add("has-terminal");const a=t.sessionId,o=[];this.teammateTerminals.set(e,{terminal:null,fitAddon:null,paneTarget:t.paneTarget,sessionId:a,resizeObserver:null,pendingData:o}),requestAnimationFrame(()=>{if(!document.contains(n)){this.teammateTerminals.delete(e);return}const r=new Terminal({theme:{background:"#0d0d0d",foreground:"#e0e0e0",cursor:"#e0e0e0",cursorAccent:"#0d0d0d",selection:"rgba(255, 255, 255, 0.3)",black:"#0d0d0d",red:"#ff6b6b",green:"#51cf66",yellow:"#ffd43b",blue:"#339af0",magenta:"#cc5de8",cyan:"#22b8cf",white:"#e0e0e0",brightBlack:"#495057",brightRed:"#ff8787",brightGreen:"#69db7c",brightYellow:"#ffe066",brightBlue:"#5c7cfa",brightMagenta:"#da77f2",brightCyan:"#66d9e8",brightWhite:"#ffffff"},fontFamily:'"Fira Code", "Cascadia Code", "JetBrains Mono", "SF Mono", Monaco, monospace',fontSize:12,lineHeight:1.2,cursorBlink:!0,cursorStyle:"block",scrollback:5e3,allowTransparency:!0,allowProposedApi:!0}),i=new FitAddon.FitAddon;if(r.loadAddon(i),typeof Unicode11Addon<"u")try{const d=new Unicode11Addon.Unicode11Addon;r.loadAddon(d),r.unicode.activeVersion="11"}catch{}try{r.open(n)}catch(d){console.warn("[TeammateTerminal] Failed to open terminal:",d),this.teammateTerminals.delete(e);return}setTimeout(()=>{try{i.fit()}catch{}fetch(`/api/sessions/${a}/teammate-pane-buffer/${encodeURIComponent(t.paneTarget)}`).then(d=>d.json()).then(d=>{if(d.success&&d.data?.buffer)try{r.write(d.data.buffer)}catch{}}).catch(d=>console.error("[TeammateTerminal] Failed to fetch buffer:",d));for(const d of o)try{r.write(d)}catch{}o.length=0},100),r.onData(d=>{fetch(`/api/sessions/${a}/teammate-pane-input`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({paneTarget:t.paneTarget,input:d})}).catch(u=>console.error("[TeammateTerminal] Failed to send input:",u))});const l=new ResizeObserver(()=>{requestAnimationFrame(()=>{try{i.fit()}catch{}})});l.observe(n);const c=this.teammateTerminals.get(e);c&&(c.terminal=r,c.fitAddon=i,c.resizeObserver=l)})},openTeammateTerminalWindow(e){if(!this.sessions.has(e.sessionId))return;const t=`pane-${e.paneTarget}`;if(this.subagentWindows.has(t)){const y=this.subagentWindows.get(t);y.hidden&&(y.element.style.display="flex",y.hidden=!1),y.element.style.zIndex=++this.subagentWindowZIndex,y.minimized&&this.restoreSubagentWindow(t);return}const s=this.subagentWindows.size,n=550,a=400,o=20,r=window.innerWidth,i=window.innerHeight,l=50,c=120,d=Math.floor((r-l-50)/(n+o))||1,u=Math.floor((i-c-50)/(a+o))||1,h=s%d,m=Math.floor(s/d)%u;let g=l+h*(n+o),f=c+m*(a+o);g=Math.max(10,Math.min(g,r-n-10)),f=Math.max(10,Math.min(f,i-a-10));const w=e.color||"blue",p=document.createElement("div");p.className="subagent-window has-terminal",p.id=`subagent-window-${t}`,p.style.zIndex=++this.subagentWindowZIndex,p.style.left=`${g}px`,p.style.top=`${f}px`,p.style.width=`${n}px`,p.style.height=`${a}px`,p.innerHTML=`
138
+ <div class="subagent-window-header">
139
+ <div class="subagent-window-title" title="Teammate terminal: ${escapeHtml(e.teammateName)} (pane ${e.paneTarget})">
140
+ <span class="icon" style="color: var(--team-color-${w}, #339af0)">\u2B24</span>
141
+ <span class="id">${escapeHtml(e.teammateName)}</span>
142
+ <span class="status running">terminal</span>
143
+ </div>
144
+ <div class="subagent-window-actions">
145
+ <button onclick="app.closeSubagentWindow('${escapeHtml(t)}')" title="Minimize to tab">\u2500</button>
146
+ </div>
147
+ </div>
148
+ <div class="subagent-window-body teammate-terminal-body" id="subagent-window-body-${t}">
149
+ </div>
150
+ `,document.body.appendChild(p);const b=this.makeWindowDraggable(p,p.querySelector(".subagent-window-header"));typeof this.makeWindowResizable=="function"&&this.makeWindowResizable(p);const S=(this.loadAppSettingsFromStorage().subagentActiveTabOnly??!0)&&e.sessionId!==this.activeSessionId;this.subagentWindows.set(t,{element:p,minimized:!1,hidden:S,dragListeners:b,description:`Teammate: ${e.teammateName}`}),this.subagentParentMap.set(t,e.sessionId),S&&(p.style.display="none"),p.addEventListener("mousedown",()=>{p.style.zIndex=++this.subagentWindowZIndex});const T=new ResizeObserver(()=>{this.updateConnectionLines()});if(T.observe(p),this.subagentWindows.get(t).resizeObserver=T,S){const y=this.subagentWindows.get(t);y&&(y._lazyTerminal=!0,y._lazyPaneTarget=e.paneTarget,y._lazySessionId=e.sessionId)}else this.initTeammateTerminal(t,e,p);requestAnimationFrame(()=>{p.style.transition="transform 0.3s ease, opacity 0.3s ease",p.style.transform="scale(1)",p.style.opacity="1"})},rebuildTeammateMap(){this.teammateMap.clear();for(const[e,t]of this.teams)for(const s of t.members)s.agentType!=="team-lead"&&this.teammateMap.set(s.name,{name:s.name,color:s.color||"blue",teamName:e,agentId:s.agentId})},getTeammateInfo(e){if(!e?.description)return null;const t=e.description.match(/<teammate-message\s+teammate_id="?([^">\s]+)/);if(!t)return null;const n=t[1].split("@")[0];return this.teammateMap.get(n)||{name:n,color:"blue",teamName:"unknown"}},getTeammateBadgeHtml(e){const t=this.getTeammateInfo(e);return t?`<span class="teammate-badge teammate-color-${t.color}" title="Team: ${escapeHtml(t.teamName)}">@${escapeHtml(t.name)}</span>`:""},renderTeamTasksPanel(){const e=document.getElementById("teamTasksPanel");if(!e)return;let t=null,s=null;if(this.activeSessionId){for(const[m,g]of this.teams)if(g.leadSessionId===this.activeSessionId){t=g,s=m;break}}if(!t){e.style.display="none";return}const n=e.style.display==="none";if(e.style.display="flex",n&&!this.teamTasksDragListeners){e.style.left=`${Math.max(10,window.innerWidth-360-20)}px`,e.style.top=`${Math.max(10,window.innerHeight-300-70)}px`;const f=e.querySelector(".team-tasks-header");f&&(this.teamTasksDragListeners=this.makeWindowDraggable(e,f))}const a=this.teamTasks.get(s)||[],o=a.filter(m=>m.status==="completed").length,r=a.length,i=r>0?Math.round(o/r*100):0,l=e.querySelector(".team-tasks-header-text");if(l){const m=t.members.filter(g=>g.agentType!=="team-lead").length;l.textContent=`Team Tasks (${m} teammates)`}const c=e.querySelector(".team-tasks-progress-fill");c&&(c.style.width=`${i}%`);const d=e.querySelector(".team-tasks-progress-text");d&&(d.textContent=`${o}/${r}`);const u=e.querySelector(".team-tasks-list");if(!u)return;if(a.length===0){u.innerHTML='<div class="team-task-empty">No tasks yet</div>';return}const h=a.map(m=>{const g=m.status==="completed"?"\u2713":m.status==="in_progress"?"\u25C9":"\u25CB",f=m.status.replace("_","-"),w=m.owner?`<span class="team-task-owner teammate-color-${this.getTeammateColor(m.owner)}">${escapeHtml(m.owner)}</span>`:"";return`<div class="team-task-item ${f}">
151
+ <span class="team-task-status">${g}</span>
152
+ <span class="team-task-subject">${escapeHtml(m.subject)}</span>
153
+ ${w}
154
+ </div>`}).join("");u.innerHTML=h},hideTeamTasksPanel(){const e=document.getElementById("teamTasksPanel");e&&(e.style.display="none"),this.teamTasksDragListeners&&(document.removeEventListener("mousemove",this.teamTasksDragListeners.move),document.removeEventListener("mouseup",this.teamTasksDragListeners.up),this.teamTasksDragListeners.touchMove&&(document.removeEventListener("touchmove",this.teamTasksDragListeners.touchMove),document.removeEventListener("touchend",this.teamTasksDragListeners.up),document.removeEventListener("touchcancel",this.teamTasksDragListeners.up)),this.teamTasksDragListeners.handle&&(this.teamTasksDragListeners.handle.removeEventListener("mousedown",this.teamTasksDragListeners.handleMouseDown),this.teamTasksDragListeners.handle.removeEventListener("touchstart",this.teamTasksDragListeners.handleTouchStart)),this.teamTasksDragListeners=null)},getTeammateColor(e){return this.teammateMap.get(e)?.color||"blue"},normalizeFilePath(e,t){if(!e)return"";let s=e.trim();const n="/home/"+(window.USER||"user");s.startsWith("~/")?s=n+s.slice(1):s==="~"&&(s=n),!s.startsWith("/")&&t&&(s=t+"/"+s);const a=s.split("/"),o=[];for(const r of a)r===""||r==="."||(r===".."?o.length>1&&o.pop():o.push(r));return"/"+o.join("/")},getFilename(e){const t=e.split("/");return t[t.length-1]||""},isShallowRootPath(e){return e.startsWith("/")?e.split("/").filter(s=>s!=="").length===1:!1},isPathInWorkingDir(e,t){if(!t)return!1;const s=this.normalizeFilePath(e,t);return s.startsWith(t+"/")||s===t},pathsAreEquivalent(e,t,s){const n=this.normalizeFilePath(e,s),a=this.normalizeFilePath(t,s);if(n===a)return!0;const o=this.getFilename(n),r=this.getFilename(a);if(o!==r)return!1;const i=this.isShallowRootPath(e),l=this.isShallowRootPath(t),c=this.isPathInWorkingDir(n,s),d=this.isPathInWorkingDir(a,s);return!!(i&&d||l&&c)},pickBetterPath(e,t,s){if(s){const o=this.isPathInWorkingDir(e,s),r=this.isPathInWorkingDir(t,s);if(o&&!r)return e;if(r&&!o)return t}const n=e.startsWith("/"),a=t.startsWith("/");return n&&!a?e:a&&!n?t:e.length!==t.length?e.length>t.length?e:t:!e.includes("~")&&t.includes("~")?e:!t.includes("~")&&e.includes("~")?t:e},deduplicateProjectInsightPaths(e,t){const s=[];for(const o of e)for(const r of o.filePaths)s.push({rawPath:r,toolId:o.id});if(s.length<=1){const o=new Map;for(const r of s)o.set(this.normalizeFilePath(r.rawPath,t),r);return o}s.sort((o,r)=>{const i=this.isPathInWorkingDir(o.rawPath,t),l=this.isPathInWorkingDir(r.rawPath,t);return i&&!l?-1:l&&!i?1:r.rawPath.length-o.rawPath.length});const n=new Map,a=new Set;for(const{rawPath:o,toolId:r}of s){const i=this.normalizeFilePath(o,t);let l=!1;for(const[,c]of n)if(this.pathsAreEquivalent(o,c.rawPath,t)){l=!0;break}!l&&!a.has(i)&&(n.set(i,{rawPath:o,toolId:r}),a.add(i))}return n},handleBashToolStart(e,t){let s=this.projectInsights.get(e)||[];s=s.filter(n=>n.id!==t.id),s.push(t),this.projectInsights.set(e,s),this.renderProjectInsightsPanel()},handleBashToolEnd(e,t){const n=(this.projectInsights.get(e)||[]).find(a=>a.id===t.id);n&&(n.status="completed"),this.renderProjectInsightsPanel(),setTimeout(()=>{const a=this.projectInsights.get(e)||[];this.projectInsights.set(e,a.filter(o=>o.id!==t.id)),this.renderProjectInsightsPanel()},2e3)},handleBashToolsUpdate(e,t){this.projectInsights.set(e,t),this.renderProjectInsightsPanel()},renderProjectInsightsPanel(){const e=this.$("projectInsightsPanel"),t=this.$("projectInsightsList");if(!e||!t)return;if(!(this.loadAppSettingsFromStorage().showProjectInsights??!1)){e.classList.remove("visible"),this.projectInsightsPanelVisible=!1;return}const o=(this.projectInsights.get(this.activeSessionId)||[]).filter(u=>u.status==="running");if(o.length===0){e.classList.remove("visible"),this.projectInsightsPanelVisible=!1;return}e.classList.add("visible"),this.projectInsightsPanelVisible=!0;const i=this.sessions.get(this.activeSessionId)?.workingDir||this.currentSessionWorkingDir,l=this.deduplicateProjectInsightPaths(o,i),c=new Set(Array.from(l.values()).map(u=>u.rawPath)),d=[];for(const u of o){const h=u.filePaths.filter(g=>c.has(g));if(h.length===0)continue;const m=u.command.length>50?u.command.substring(0,50)+"...":u.command;d.push(`
155
+ <div class="project-insight-item" data-tool-id="${u.id}">
156
+ <div class="project-insight-command">
157
+ <span class="icon">\u{1F4BB}</span>
158
+ <span class="cmd" title="${escapeHtml(u.command)}">${escapeHtml(m)}</span>
159
+ <span class="project-insight-status ${u.status}">${u.status}</span>
160
+ ${u.timeout?`<span class="project-insight-timeout">${escapeHtml(u.timeout)}</span>`:""}
161
+ </div>
162
+ <div class="project-insight-paths">
163
+ `);for(const g of h){const f=g.split("/").pop();d.push(`
164
+ <span class="project-insight-filepath"
165
+ onclick="app.openLogViewerWindow('${escapeHtml(g)}', '${escapeHtml(u.sessionId)}')"
166
+ title="${escapeHtml(g)}">${escapeHtml(f)}</span>
167
+ `)}d.push(`
168
+ </div>
169
+ </div>
170
+ `)}t.innerHTML=d.join("")},closeProjectInsightsPanel(){const e=this.$("projectInsightsPanel");e&&(e.classList.remove("visible"),this.projectInsightsPanelVisible=!1)},async loadFileBrowser(e){if(!e)return;const t=this.$("fileBrowserTree"),s=this.$("fileBrowserStatus");if(t){t.innerHTML='<div class="file-browser-loading">Loading files...</div>';try{const n=await fetch(`/api/sessions/${e}/files?depth=5&showHidden=false`);if(!n.ok)throw new Error("Failed to load files");const a=await n.json();if(!a.success)throw new Error(a.error||"Failed to load files");if(this.fileBrowserData=a.data,this.renderFileBrowserTree(),s){const{totalFiles:o,totalDirectories:r,truncated:i}=a.data;s.textContent=`${o} files, ${r} dirs${i?" (truncated)":""}`}}catch(n){console.error("Failed to load file browser:",n),t.innerHTML=`<div class="file-browser-empty">Failed to load files: ${escapeHtml(n.message)}</div>`}}},renderFileBrowserTree(){const e=this.$("fileBrowserTree");if(!e||!this.fileBrowserData)return;const{tree:t}=this.fileBrowserData;if(!t||t.length===0){e.innerHTML='<div class="file-browser-empty">No files found</div>';return}const s=[],n=this.fileBrowserFilter.toLowerCase(),a=(o,r)=>{const i=o.type==="directory",l=this.fileBrowserExpandedDirs.has(o.path),c=!n||o.name.toLowerCase().includes(n);let d=!1;i&&n&&o.children&&(d=this.hasMatchingChild(o,n));const h=!(c||d)&&n?" hidden-by-filter":"",m=i?l?"\u{1F4C2}":"\u{1F4C1}":this.getFileIcon(o.extension),g=i?`<span class="file-tree-expand${l?" expanded":""}">\u25B6</span>`:'<span class="file-tree-expand"></span>',f=!i&&o.size!==void 0?`<span class="file-tree-size">${this.formatFileSize(o.size)}</span>`:"",w=i?"file-tree-name directory":"file-tree-name";if(s.push(`
171
+ <div class="file-tree-item${h}" data-path="${escapeHtml(o.path)}" data-type="${o.type}" data-depth="${r}">
172
+ ${g}
173
+ <span class="file-tree-icon">${m}</span>
174
+ <span class="${w}">${escapeHtml(o.name)}</span>
175
+ ${f}
176
+ </div>
177
+ `),i&&l&&o.children)for(const p of o.children)a(p,r+1)};for(const o of t)a(o,0);e.innerHTML=s.join(""),e.querySelectorAll(".file-tree-item").forEach(o=>{o.addEventListener("click",()=>{const r=o.dataset.path;o.dataset.type==="directory"?this.toggleFileBrowserFolder(r):this.openFilePreview(r)})})},hasMatchingChild(e,t){if(!e.children)return!1;for(const s of e.children)if(s.name.toLowerCase().includes(t)||s.type==="directory"&&this.hasMatchingChild(s,t))return!0;return!1},toggleFileBrowserFolder(e){this.fileBrowserExpandedDirs.has(e)?this.fileBrowserExpandedDirs.delete(e):this.fileBrowserExpandedDirs.add(e),this.renderFileBrowserTree()},filterFileBrowser(e){this.fileBrowserFilter=e,e&&this.expandAllDirectories(this.fileBrowserData?.tree||[]),this.renderFileBrowserTree()},expandAllDirectories(e){for(const t of e)t.type==="directory"&&(this.fileBrowserExpandedDirs.add(t.path),t.children&&this.expandAllDirectories(t.children))},collapseAllDirectories(){this.fileBrowserExpandedDirs.clear()},toggleFileBrowserExpand(){this.fileBrowserAllExpanded=!this.fileBrowserAllExpanded;const e=this.$("fileBrowserExpandBtn");this.fileBrowserAllExpanded?(this.expandAllDirectories(this.fileBrowserData?.tree||[]),e&&(e.innerHTML="\u229F")):(this.collapseAllDirectories(),e&&(e.innerHTML="\u229E")),this.renderFileBrowserTree()},refreshFileBrowser(){if(this.activeSessionId){this.fileBrowserExpandedDirs.clear(),this.fileBrowserFilter="",this.fileBrowserAllExpanded=!1;const e=this.$("fileBrowserSearch");e&&(e.value=""),this.loadFileBrowser(this.activeSessionId)}},closeFileBrowserPanel(){const e=this.$("fileBrowserPanel");if(e&&(e.classList.remove("visible"),e.style.left="",e.style.top="",e.style.bottom="",e.style.right=""),this.fileBrowserDragListeners){const s=this.fileBrowserDragListeners;document.removeEventListener("mousemove",s.move),document.removeEventListener("mouseup",s.up),document.removeEventListener("touchmove",s.touchMove),document.removeEventListener("touchend",s.up),document.removeEventListener("touchcancel",s.up),s.handle&&(s.handle.removeEventListener("mousedown",s.handleMouseDown),s.handle.removeEventListener("touchstart",s.handleTouchStart),s._onFirstDrag&&(s.handle.removeEventListener("mousedown",s._onFirstDrag),s.handle.removeEventListener("touchstart",s._onFirstDrag))),this.fileBrowserDragListeners=null}const t=this.loadAppSettingsFromStorage();t.showFileBrowser=!1,this.saveAppSettingsToStorage(t)},async openFilePreview(e){if(!this.activeSessionId||!e)return;const t=this.$("filePreviewOverlay"),s=this.$("filePreviewTitle"),n=this.$("filePreviewBody"),a=this.$("filePreviewFooter");if(!(!t||!n)){t.classList.add("visible"),s.textContent=e,n.innerHTML='<div class="binary-message">Loading...</div>',a.textContent="";try{const o=await fetch(`/api/sessions/${this.activeSessionId}/file-content?path=${encodeURIComponent(e)}&lines=500`);if(!o.ok)throw new Error("Failed to load file");const r=await o.json();if(!r.success)throw new Error(r.error||"Failed to load file");const i=r.data;if(i.type==="image")n.innerHTML=`<img src="${i.url}" alt="${escapeHtml(e)}">`,a.textContent=`${this.formatFileSize(i.size)} \u2022 ${i.extension}`;else if(i.type==="video")n.innerHTML=`<video src="${i.url}" controls autoplay></video>`,a.textContent=`${this.formatFileSize(i.size)} \u2022 ${i.extension}`;else if(i.type==="binary")n.innerHTML=`<div class="binary-message">Binary file (${this.formatFileSize(i.size)})<br>Cannot preview</div>`,a.textContent=i.extension||"binary";else{this.filePreviewContent=i.content,n.innerHTML=`<pre><code>${escapeHtml(i.content)}</code></pre>`;const l=i.truncated?` (showing 500/${i.totalLines} lines)`:"";a.textContent=`${i.totalLines} lines \u2022 ${this.formatFileSize(i.size)}${l}`}}catch(o){console.error("Failed to preview file:",o),n.innerHTML=`<div class="binary-message">Error: ${escapeHtml(o.message)}</div>`}}},closeFilePreview(){const e=this.$("filePreviewOverlay");e&&e.classList.remove("visible"),this.filePreviewContent=""},copyFilePreviewContent(){this.filePreviewContent&&navigator.clipboard.writeText(this.filePreviewContent).then(()=>{this.showToast("Copied to clipboard","success")}).catch(()=>{this.showToast("Failed to copy","error")})},getFileIcon(e){return e&&{ts:"\u{1F4D8}",tsx:"\u{1F4D8}",js:"\u{1F4D2}",jsx:"\u{1F4D2}",mjs:"\u{1F4D2}",cjs:"\u{1F4D2}",py:"\u{1F40D}",pyx:"\u{1F40D}",pyw:"\u{1F40D}",rs:"\u{1F980}",go:"\u{1F439}",c:"\u2699\uFE0F",cpp:"\u2699\uFE0F",h:"\u2699\uFE0F",hpp:"\u2699\uFE0F",html:"\u{1F310}",htm:"\u{1F310}",css:"\u{1F3A8}",scss:"\u{1F3A8}",sass:"\u{1F3A8}",less:"\u{1F3A8}",json:"\u{1F4CB}",yaml:"\u{1F4CB}",yml:"\u{1F4CB}",xml:"\u{1F4CB}",toml:"\u{1F4CB}",csv:"\u{1F4CB}",md:"\u{1F4DD}",markdown:"\u{1F4DD}",txt:"\u{1F4DD}",rst:"\u{1F4DD}",png:"\u{1F5BC}\uFE0F",jpg:"\u{1F5BC}\uFE0F",jpeg:"\u{1F5BC}\uFE0F",gif:"\u{1F5BC}\uFE0F",svg:"\u{1F5BC}\uFE0F",webp:"\u{1F5BC}\uFE0F",ico:"\u{1F5BC}\uFE0F",bmp:"\u{1F5BC}\uFE0F",mp4:"\u{1F3AC}",webm:"\u{1F3AC}",mov:"\u{1F3AC}",mp3:"\u{1F3B5}",wav:"\u{1F3B5}",ogg:"\u{1F3B5}",sh:"\u{1F4BB}",bash:"\u{1F4BB}",zsh:"\u{1F4BB}",env:"\u{1F510}",gitignore:"\u{1F6AB}",dockerfile:"\u{1F433}",lock:"\u{1F512}"}[e.toLowerCase()]||"\u{1F4C4}"},formatFileSize(e){return e==null?"":e<1024?`${e} B`:e<1024*1024?`${(e/1024).toFixed(1)} KB`:e<1024*1024*1024?`${(e/(1024*1024)).toFixed(1)} MB`:`${(e/(1024*1024*1024)).toFixed(1)} GB`},openLogViewerWindow(e,t){if(t=t||this.activeSessionId,!t)return;const s=`${t}-${e.replace(/[^a-zA-Z0-9]/g,"_")}`;if(this.logViewerWindows.has(s)){const d=this.logViewerWindows.get(s);d.element.style.zIndex=++this.logViewerWindowZIndex;return}const n=this.logViewerWindows.size,a=100+n%5*30,o=100+n%5*30,r=e.split("/").pop(),i=document.createElement("div");i.className="log-viewer-window",i.id=`log-viewer-window-${s}`,i.style.left=`${a}px`,i.style.top=`${o}px`,i.style.zIndex=++this.logViewerWindowZIndex,i.innerHTML=`
178
+ <div class="log-viewer-window-header">
179
+ <div class="log-viewer-window-title" title="${escapeHtml(e)}">
180
+ <span class="icon">\u{1F4C4}</span>
181
+ <span class="filename">${escapeHtml(r)}</span>
182
+ <span class="status streaming">streaming</span>
183
+ </div>
184
+ <div class="log-viewer-window-actions">
185
+ <button onclick="app.closeLogViewerWindow('${escapeHtml(s)}')" title="Close">\xD7</button>
186
+ </div>
187
+ </div>
188
+ <div class="log-viewer-window-body" id="log-viewer-body-${s}">
189
+ <div class="log-info">Connecting to ${escapeHtml(e)}...</div>
190
+ </div>
191
+ `,document.body.appendChild(i);const l=this.makeWindowDraggable(i,i.querySelector(".log-viewer-window-header")),c=new EventSource(`/api/sessions/${t}/tail-file?path=${encodeURIComponent(e)}&lines=50`);c.onmessage=d=>{const u=JSON.parse(d.data),h=document.getElementById(`log-viewer-body-${s}`);if(h)switch(u.type){case"connected":h.innerHTML="";break;case"data":const m=h.scrollTop+h.clientHeight>=h.scrollHeight-10,g=escapeHtml(u.content);h.innerHTML+=g,m&&(h.scrollTop=h.scrollHeight),h.innerHTML.length>5e5&&(h.innerHTML=h.innerHTML.slice(-4e5));break;case"end":this.updateLogViewerStatus(s,"disconnected","ended");break;case"error":h.innerHTML+=`<div class="log-error">${escapeHtml(u.error)}</div>`,this.updateLogViewerStatus(s,"error","error");break}},c.onerror=()=>{this.updateLogViewerStatus(s,"disconnected","connection error")},this.logViewerWindows.set(s,{element:i,eventSource:c,filePath:e,sessionId:t,dragListeners:l})},updateLogViewerStatus(e,t,s){const n=document.querySelector(`#log-viewer-window-${e} .status`);n&&(n.className=`status ${t}`,n.textContent=s)},closeLogViewerWindow(e){const t=this.logViewerWindows.get(e);t&&(t.eventSource&&t.eventSource.close(),t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.logViewerWindows.delete(e))},closeSessionLogViewerWindows(e){const t=[];for(const[s,n]of this.logViewerWindows)n.sessionId===e&&t.push(s);for(const s of t)this.closeLogViewerWindow(s)},openImagePopup(e){const{sessionId:t,filePath:s,relativePath:n,fileName:a,timestamp:o,size:r}=e,i=`${t}-${o}`;if(this.imagePopups.has(i)){const v=this.imagePopups.get(i);v.element.style.zIndex=++this.imagePopupZIndex;return}if(this.imagePopups.size>=20){const v=this.imagePopups.keys().next().value;v&&this.closeImagePopup(v)}const c=this.imagePopups.size,d=(window.innerWidth-600)/2,u=(window.innerHeight-500)/2,h=d+c%5*30,m=u+c%5*30,f=this.sessions.get(t)?.name||t.substring(0,8),w=(r/1024).toFixed(1),p=`/api/sessions/${t}/file-raw?path=${encodeURIComponent(n||a)}`,b=document.createElement("div");b.className="image-popup-window",b.id=`image-popup-${i}`,b.style.left=`${h}px`,b.style.top=`${m}px`,b.style.zIndex=++this.imagePopupZIndex,b.innerHTML=`
192
+ <div class="image-popup-header">
193
+ <div class="image-popup-title" title="${escapeHtml(s)}">
194
+ <span class="icon">\u{1F5BC}\uFE0F</span>
195
+ <span class="filename">${escapeHtml(a)}</span>
196
+ <span class="session-badge">${escapeHtml(f)}</span>
197
+ <span class="size-badge">${w} KB</span>
198
+ </div>
199
+ <div class="image-popup-actions">
200
+ <button onclick="app.openImageInNewTab('${escapeHtml(p)}')" title="Open in new tab">\u2197</button>
201
+ <button onclick="app.closeImagePopup('${escapeHtml(i)}')" title="Close">\xD7</button>
202
+ </div>
203
+ </div>
204
+ <div class="image-popup-body">
205
+ <img src="${p}" alt="${escapeHtml(a)}"
206
+ onerror="this.parentElement.innerHTML='<div class=\\'image-error\\'>Failed to load image</div>'"
207
+ onclick="app.openImageInNewTab('${escapeHtml(p)}')" />
208
+ </div>
209
+ `,document.body.appendChild(b);const $=this.makeWindowDraggable(b,b.querySelector(".image-popup-header"));b.addEventListener("mousedown",()=>{b.style.zIndex=++this.imagePopupZIndex}),this.imagePopups.set(i,{element:b,sessionId:t,filePath:s,dragListeners:$})},closeImagePopup(e){const t=this.imagePopups.get(e);t&&(t.dragListeners&&(document.removeEventListener("mousemove",t.dragListeners.move),document.removeEventListener("mouseup",t.dragListeners.up),t.dragListeners.touchMove&&(document.removeEventListener("touchmove",t.dragListeners.touchMove),document.removeEventListener("touchend",t.dragListeners.up),document.removeEventListener("touchcancel",t.dragListeners.up)),t.dragListeners.handle&&(t.dragListeners.handle.removeEventListener("mousedown",t.dragListeners.handleMouseDown),t.dragListeners.handle.removeEventListener("touchstart",t.dragListeners.handleTouchStart))),t.element.remove(),this.imagePopups.delete(e))},openImageInNewTab(e){window.open(e,"_blank")},closeSessionImagePopups(e){const t=[];for(const[s,n]of this.imagePopups)n.sessionId===e&&t.push(s);for(const s of t)this.closeImagePopup(s)},async loadMuxSessions(){try{const t=await(await fetch("/api/mux-sessions")).json();this.muxSessions=t.sessions||[],this.renderMuxSessions()}catch(e){console.error("Failed to load mux sessions:",e)}},killAllMuxSessions(){const e=this.muxSessions?.length||0;if(e===0){alert("No sessions to kill");return}document.getElementById("killAllCount").textContent=e;const t=document.getElementById("killAllModal");t.classList.add("active"),this.activeFocusTrap=new FocusTrap(t),this.activeFocusTrap.activate()},closeKillAllModal(){document.getElementById("killAllModal").classList.remove("active"),this.activeFocusTrap&&(this.activeFocusTrap.deactivate(),this.activeFocusTrap=null)},async confirmKillAll(e){this.closeKillAllModal();try{if(e){if((await(await fetch("/api/sessions",{method:"DELETE"})).json()).success){this.sessions.clear(),this.muxSessions=[],this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.renderMuxSessions(),this.terminal.clear(),this.terminal.reset(),this.toast("All sessions and tmux killed","success")}}else{this.sessions.clear(),this.activeSessionId=null;try{localStorage.removeItem("codeman-active-session")}catch{}this.renderSessionTabs(),this.terminal.clear(),this.terminal.reset(),this.toast("All tabs removed, tmux still running","info")}}catch(t){console.error("Failed to kill sessions:",t),this.toast("Failed to kill sessions: "+t.message,"error")}},renderMuxSessions(){this.renderMuxSessionsTimeout&&clearTimeout(this.renderMuxSessionsTimeout),this.renderMuxSessionsTimeout=setTimeout(()=>{this._renderMuxSessionsImmediate()},100)},_renderMuxSessionsImmediate(){const e=document.getElementById("muxSessionsBody");if(!this.muxSessions||this.muxSessions.length===0){e.innerHTML='<div class="monitor-empty">No mux sessions</div>';return}let t="";for(const s of this.muxSessions){const n=s.stats||{memoryMB:0,cpuPercent:0,childCount:0},a=this.sessions.get(s.sessionId),o=a?a.status:"unknown",r=a?a.isWorking:!1;let i,l;o==="idle"&&!r?(i="IDLE",l="status-idle"):o==="busy"||r?(i="WORKING",l="status-working"):o==="stopped"?(i="STOPPED",l="status-stopped"):(i=o.toUpperCase(),l="");const c=a&&a.tokens?a.tokens:null,d=a?a.totalCost:0,u=a&&a.cliModel||"",h=u.includes("opus")?"opus":u.includes("sonnet")?"sonnet":u.includes("haiku")?"haiku":"",m=a?a.ralphTodoStats:null;let g="";if(m&&m.total>0){const b=Math.round(m.completed/m.total*100);g=`<span class="process-stat todo-progress">${m.completed}/${m.total} (${b}%)</span>`}let f="";c&&c.total>0&&(f=`<span class="process-stat tokens">${(c.total/1e3).toFixed(1)}k tok</span>`);let w="";d>0&&(w=`<span class="process-stat cost">$${d.toFixed(2)}</span>`);let p="";h&&(p=`<span class="monitor-model-badge ${h}">${h}</span>`),t+=`
210
+ <div class="process-item">
211
+ <span class="monitor-status-badge ${l}">${i}</span>
212
+ <div class="process-info">
213
+ <div class="process-name">${p} ${escapeHtml(s.name||s.muxName)}</div>
214
+ <div class="process-meta">
215
+ ${f}
216
+ ${w}
217
+ ${g}
218
+ <span class="process-stat memory">${n.memoryMB}MB</span>
219
+ <span class="process-stat cpu">${n.cpuPercent}%</span>
220
+ </div>
221
+ </div>
222
+ <div class="process-actions">
223
+ <button class="btn-toolbar btn-sm btn-danger" onclick="app.killMuxSession('${escapeHtml(s.sessionId)}')" title="Kill session">Kill</button>
224
+ </div>
225
+ </div>
226
+ `}e.innerHTML=t},renderMonitorSubagents(){const e=document.getElementById("monitorSubagentsBody"),t=document.getElementById("monitorSubagentStats");if(!e)return;const s=Array.from(this.subagents.values()),n=s.filter(o=>o.status==="active"||o.status==="idle").length;if(t&&(t.textContent=`${s.length} tracked`+(n>0?`, ${n} active`:"")),s.length===0){e.innerHTML='<div class="monitor-empty">No background agents</div>';return}let a="";for(const o of s){const r=o.status==="active"?"active":o.status==="idle"?"idle":"completed",i=o.modelShort?`<span class="model-badge ${o.modelShort}">${o.modelShort}</span>`:"",l=o.description?escapeHtml(o.description.substring(0,40)):o.agentId;a+=`
227
+ <div class="process-item">
228
+ <span class="process-mode ${r}">${o.status}</span>
229
+ <div class="process-info">
230
+ <div class="process-name">${i} ${l}</div>
231
+ <div class="process-meta">
232
+ <span>ID: ${o.agentId}</span>
233
+ <span>${o.toolCallCount||0} tools</span>
234
+ </div>
235
+ </div>
236
+ <div class="process-actions">
237
+ ${o.status!=="completed"?`<button class="btn-toolbar btn-sm btn-danger" onclick="app.killSubagent('${escapeHtml(o.agentId)}')" title="Kill agent">Kill</button>`:""}
238
+ </div>
239
+ </div>
240
+ `}e.innerHTML=a},async killMuxSession(e){if(confirm("Kill this mux session?")){try{await this.closeSession(e,!0)}catch{try{await fetch(`/api/mux-sessions/${e}`,{method:"DELETE"})}catch{}this.showToast("Tmux session killed","success")}this.muxSessions=this.muxSessions.filter(t=>t.sessionId!==e),this.renderMuxSessions()}},async reconcileMuxSessions(){try{const t=await(await fetch("/api/mux-sessions/reconcile",{method:"POST"})).json();t.dead&&t.dead.length>0?(this.showToast(`Found ${t.dead.length} dead mux session(s)`,"warning"),await this.loadMuxSessions()):this.showToast("All mux sessions are alive","success")}catch{this.showToast("Failed to reconcile mux sessions","error")}},toggleNotifications(){this.notificationManager?.toggleDrawer()},toast(e,t="info"){return this.showToast(e,t)},showToast(e,t="info",s={}){const{duration:n=3e3,action:a}=s,o=document.createElement("div");o.className=`toast toast-${t}`;const r=document.createElement("span");if(r.textContent=e,o.appendChild(r),a){const i=document.createElement("button");i.textContent=a.label,i.style.cssText="margin-left:12px;padding:2px 10px;background:rgba(255,255,255,0.15);border:1px solid rgba(255,255,255,0.3);border-radius:3px;color:inherit;cursor:pointer;font-size:12px",i.onclick=l=>{l.stopPropagation(),a.onClick(),o.remove()},o.appendChild(i)}this._toastContainer||(this._toastContainer=document.querySelector(".toast-container"),this._toastContainer||(this._toastContainer=document.createElement("div"),this._toastContainer.className="toast-container",document.body.appendChild(this._toastContainer))),this._toastContainer.appendChild(o),requestAnimationFrame(()=>o.classList.add("show")),setTimeout(()=>{o.classList.remove("show"),setTimeout(()=>o.remove(),200)},n)},startSystemStatsPolling(){this.stopSystemStatsPolling(),this.fetchSystemStats(),this.systemStatsInterval=setInterval(()=>{this.fetchSystemStats()},2e3)},stopSystemStatsPolling(){this.systemStatsInterval&&(clearInterval(this.systemStatsInterval),this.systemStatsInterval=null)},async fetchSystemStats(){const e=document.getElementById("headerSystemStats");if(!(!e||e.style.display==="none"))try{const s=await(await fetch("/api/system/stats")).json();this.updateSystemStatsDisplay(s)}catch{}},updateSystemStatsDisplay(e){const t=this.$("statCpu"),s=this.$("statCpuBar"),n=this.$("statMem"),a=this.$("statMemBar");if(t&&s&&(t.textContent=`${e.cpu}%`,s.style.width=`${Math.min(100,e.cpu)}%`,s.classList.remove("medium","high"),t.classList.remove("high"),e.cpu>80?(s.classList.add("high"),t.classList.add("high")):e.cpu>50&&s.classList.add("medium")),n&&a){const o=(e.memory.usedMB/1024).toFixed(1);n.textContent=`${o}G`,a.style.width=`${Math.min(100,e.memory.percent)}%`,a.classList.remove("medium","high"),n.classList.remove("high"),e.memory.percent>80?(a.classList.add("high"),n.classList.add("high")):e.memory.percent>50&&a.classList.add("medium")}}});
@@ -0,0 +1,69 @@
1
+ "use strict";Object.assign(CodemanApp.prototype,{_onRalphLoopUpdate(t){this.ralphClosedSessions.has(t.sessionId)||this.updateRalphState(t.sessionId,{loop:t.state})},_onRalphTodoUpdate(t){this.ralphClosedSessions.has(t.sessionId)||this.updateRalphState(t.sessionId,{todos:t.todos})},_onRalphCompletionDetected(t){if(this.ralphClosedSessions.has(t.sessionId))return;const e=`${t.sessionId}:${t.phrase}`;if(this._shownCompletions?.has(e))return;this._shownCompletions||(this._shownCompletions=new Set),this._shownCompletions.add(e),setTimeout(()=>this._shownCompletions?.delete(e),3e4);const s=this.ralphStates.get(t.sessionId)||{};s.loop&&(s.loop.active=!1,this.updateRalphState(t.sessionId,s));const a=this.sessions.get(t.sessionId);this.notificationManager?.notify({urgency:"warning",category:"ralph-complete",sessionId:t.sessionId,sessionName:a?.name||this.getShortId(t.sessionId),title:"Loop Complete",message:`Completion: ${t.phrase||"unknown"}`})},_onRalphStatusUpdate(t){this.ralphClosedSessions.has(t.sessionId)||this.updateRalphState(t.sessionId,{statusBlock:t.block})},_onCircuitBreakerUpdate(t){if(!this.ralphClosedSessions.has(t.sessionId)&&(this.updateRalphState(t.sessionId,{circuitBreaker:t.status}),t.status.state==="OPEN")){const e=this.sessions.get(t.sessionId);this.notificationManager?.notify({urgency:"critical",category:"circuit-breaker",sessionId:t.sessionId,sessionName:e?.name||this.getShortId(t.sessionId),title:"Circuit Breaker Open",message:t.status.reason||"Loop stuck - no progress detected"})}},_onExitGateMet(t){const e=this.sessions.get(t.sessionId);this.notificationManager?.notify({urgency:"warning",category:"exit-gate",sessionId:t.sessionId,sessionName:e?.name||this.getShortId(t.sessionId),title:"Exit Gate Met",message:`Loop ready to exit (indicators: ${t.completionIndicators})`})},_onPlanSubagent(t){console.log("[Plan Subagent]",t),this.handlePlanSubagentEvent(t)},_onPlanProgress(t){console.log("[Plan Progress]",t),this._planProgressHandler&&this._planProgressHandler({type:"plan:progress",data:t});const e=document.getElementById("planLoadingTitle"),s=document.getElementById("planLoadingHint");if(e&&t.phase){const a={"parallel-analysis":"Running parallel analysis...",subagent:t.detail||"Subagent working...",synthesis:"Synthesizing results...",verification:"Running verification..."};e.textContent=a[t.phase]||t.phase}s&&t.detail&&(s.textContent=t.detail)},_onPlanStarted(t){console.log("[Plan Started]",t),this.activePlanOrchestratorId=t.orchestratorId,this.planGenerationStopped=!1,this.renderMonitorPlanAgents()},_onPlanCancelled(t){console.log("[Plan Cancelled]",t),this.activePlanOrchestratorId===t.orchestratorId&&(this.activePlanOrchestratorId=null),this.renderMonitorPlanAgents()},_onPlanCompleted(t){console.log("[Plan Completed]",t),this.activePlanOrchestratorId===t.orchestratorId&&(this.activePlanOrchestratorId=null),this.renderMonitorPlanAgents()},updateRalphState(t,e){const a={...this.ralphStates.get(t)||{loop:null,todos:[]},...e};this.ralphStates.set(t,a),t===this.activeSessionId&&this.renderRalphStatePanel()},toggleRalphStatePanel(){const t=this.terminal?.element?.querySelector(".xterm-viewport"),e=t?.scrollTop;this.ralphStatePanelCollapsed=!this.ralphStatePanelCollapsed,this.renderRalphStatePanel(),requestAnimationFrame(()=>{t&&e!==void 0&&(t.scrollTop=e),this.terminal&&this.fitAddon&&this.fitAddon.fit()})},async closeRalphTracker(){this.activeSessionId&&(this.ralphClosedSessions.add(this.activeSessionId),await this._apiPost(`/api/sessions/${this.activeSessionId}/ralph-config`,{enabled:!1}),this.ralphStates.delete(this.activeSessionId),this.renderRalphStatePanel())},toggleRalphMenu(){const t=document.getElementById("ralphDropdown");t&&t.classList.toggle("show")},closeRalphMenu(){const t=document.getElementById("ralphDropdown");t&&t.classList.remove("show")},async resetCircuitBreaker(){if(this.activeSessionId)try{(await(await this._apiPost(`/api/sessions/${this.activeSessionId}/ralph-circuit-breaker/reset`,{}))?.json())?.success&&this.notificationManager?.notify({urgency:"info",category:"circuit-breaker",title:"Reset",message:"Circuit breaker reset to CLOSED"})}catch(t){console.error("Error resetting circuit breaker:",t)}},async showFixPlan(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/fix-plan`)).json();if(!e.success){this.notificationManager?.notify({urgency:"error",category:"fix-plan",title:"Error",message:e.error||"Failed to generate fix plan"});return}this.showFixPlanModal(e.data.content,e.data.todoCount)}catch(t){console.error("Error fetching fix plan:",t)}},showFixPlanModal(t,e){let s=document.getElementById("fixPlanModal");s||(s=document.createElement("div"),s.id="fixPlanModal",s.className="modal",s.innerHTML=`
2
+ <div class="modal-content fix-plan-modal">
3
+ <div class="modal-header">
4
+ <h3>@fix_plan.md</h3>
5
+ <button class="btn-close" onclick="app.closeFixPlanModal()">&times;</button>
6
+ </div>
7
+ <div class="modal-body">
8
+ <textarea id="fixPlanContent" class="fix-plan-textarea" readonly></textarea>
9
+ </div>
10
+ <div class="modal-footer">
11
+ <span class="fix-plan-stats" id="fixPlanStats"></span>
12
+ <button class="btn btn-secondary" onclick="app.copyFixPlan()">Copy</button>
13
+ <button class="btn btn-primary" onclick="app.writeFixPlanToFile()">Write to File</button>
14
+ <button class="btn btn-secondary" onclick="app.closeFixPlanModal()">Close</button>
15
+ </div>
16
+ </div>
17
+ `,document.body.appendChild(s)),document.getElementById("fixPlanContent").value=t,document.getElementById("fixPlanStats").textContent=`${e} tasks`,s.classList.add("show")},closeFixPlanModal(){const t=document.getElementById("fixPlanModal");t&&t.classList.remove("show")},async copyFixPlan(){const t=document.getElementById("fixPlanContent")?.value;t&&(await navigator.clipboard.writeText(t),this.notificationManager?.notify({urgency:"info",category:"fix-plan",title:"Copied",message:"Fix plan copied to clipboard"}))},async writeFixPlanToFile(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/fix-plan/write`,{method:"POST"})).json();e.success?(this.notificationManager?.notify({urgency:"info",category:"fix-plan",title:"Written",message:`@fix_plan.md written to ${e.data.filePath}`}),this.closeFixPlanModal()):this.notificationManager?.notify({urgency:"error",category:"fix-plan",title:"Error",message:e.error||"Failed to write file"})}catch(t){console.error("Error writing fix plan:",t)}},async importFixPlanFromFile(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/fix-plan/read`,{method:"POST"})).json();e.success?(this.notificationManager?.notify({urgency:"info",category:"fix-plan",title:"Imported",message:`Imported ${e.data.importedCount} tasks from @fix_plan.md`}),this.updateRalphState(this.activeSessionId,{todos:e.data.todos})):this.notificationManager?.notify({urgency:"warning",category:"fix-plan",title:"Not Found",message:e.error||"@fix_plan.md not found"})}catch(t){console.error("Error importing fix plan:",t)}},toggleRalphDetach(){const t=this.$("ralphStatePanel"),e=this.$("ralphDetachBtn");t&&(t.classList.contains("detached")?(t.classList.remove("detached"),t.style.top="",t.style.left="",t.style.width="",t.style.height="",e&&(e.innerHTML="&#x29C9;",e.title="Detach panel")):(t.classList.add("detached"),this.ralphStatePanelCollapsed=!1,t.classList.remove("collapsed"),e&&(e.innerHTML="&#x229E;",e.title="Attach panel"),this.setupRalphDrag()),this.renderRalphStatePanel())},setupRalphDrag(){const t=this.$("ralphStatePanel"),e=this.$("ralphSummary");if(!t||!e)return;let s=!1,a,i,r,o;const l=p=>{if(p.target.closest("button")||p.target.closest(".ralph-toggle")||!t.classList.contains("detached"))return;s=!0,a=p.clientX,i=p.clientY;const d=t.getBoundingClientRect();r=d.left,o=d.top,document.addEventListener("mousemove",n),document.addEventListener("mouseup",c),p.preventDefault()},n=p=>{if(!s)return;const d=p.clientX-a,h=p.clientY-i;let u=r+d,m=o+h;const f=t.getBoundingClientRect();u=Math.max(0,Math.min(window.innerWidth-f.width,u)),m=Math.max(0,Math.min(window.innerHeight-f.height,m)),t.style.left=u+"px",t.style.top=m+"px"},c=()=>{s=!1,document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",c)};e.removeEventListener("mousedown",e._ralphDragHandler),e._ralphDragHandler=l,e.addEventListener("mousedown",l)},renderRalphStatePanel(){this.renderRalphStatePanelTimeout&&clearTimeout(this.renderRalphStatePanelTimeout),this.renderRalphStatePanelTimeout=setTimeout(()=>{this._renderRalphStatePanelImmediate()},50)},_renderRalphStatePanelImmediate(){const t=this.$("ralphStatePanel"),e=this.$("ralphToggle");if(!t)return;if(this.ralphClosedSessions.has(this.activeSessionId)){t.style.display="none";return}const s=this.ralphStates.get(this.activeSessionId),a=s?.loop?.enabled===!0,i=s?.loop?.active||s?.loop?.completionPhrase,r=s?.todos?.length>0,o=s?.circuitBreaker&&s.circuitBreaker.state!=="CLOSED",l=s?.statusBlock!==void 0;if(!a&&!i&&!r&&!o&&!l){t.style.display="none";return}t.style.display="";const n=s?.todos||[],c=n.filter(h=>h.status==="completed").length,p=n.length,d=p>0?Math.round(c/p*100):0;this.updateRalphRing(d),this.updateRalphStatus(s?.loop,c,p),this.updateRalphStats(s?.loop,c,p),this.updateCircuitBreakerBadge(s?.circuitBreaker),this.ralphStatePanelCollapsed?(t.classList.add("collapsed"),e&&(e.innerHTML="&#x25BC;")):(t.classList.remove("collapsed"),e&&(e.innerHTML="&#x25B2;"),this.updateRalphExpandedView(s))},updateRalphRing(t){const e=Math.max(0,Math.min(100,Number(t)||0)),s=this.$("ralphRingMiniProgress"),a=this.$("ralphRingMiniText");if(s){const o=100-e;s.style.strokeDashoffset=o}a&&(a.textContent=`${e}%`);const i=this.$("ralphRingProgress"),r=this.$("ralphRingPercent");if(i){const o=264-264*e/100;i.style.strokeDashoffset=o}r&&(r.textContent=`${e}%`)},updateRalphStatus(t,e=0,s=0){const a=this.$("ralphStatusBadge"),i=a?.querySelector(".ralph-status-text");!a||!i||(a.classList.remove("active","completed","tracking"),t?.active?(a.classList.add("active"),i.textContent="Running"):s>0&&e===s?(a.classList.add("completed"),i.textContent="Complete"):t?.enabled||s>0?(a.classList.add("tracking"),i.textContent="Tracking"):i.textContent="Idle")},updateCircuitBreakerBadge(t){let e=this.$("ralphCircuitBreakerBadge");if(!e){const s=this.$("ralphSummary");if(!s)return;if(e=s.querySelector(".ralph-circuit-breaker"),!e){e=document.createElement("div"),e.id="ralphCircuitBreakerBadge",e.className="ralph-circuit-breaker";const a=this.$("ralphStatusBadge");a&&a.nextSibling?a.parentNode.insertBefore(e,a.nextSibling):s.appendChild(e)}}if(!t||t.state==="CLOSED"){e.style.display="none";return}e.style.display="",e.classList.remove("half-open","open"),t.state==="HALF_OPEN"?(e.classList.add("half-open"),e.innerHTML='<span class="cb-icon">\u26A0</span><span class="cb-text">Warning</span>',e.title=t.reason||"Circuit breaker warning"):t.state==="OPEN"&&(e.classList.add("open"),e.innerHTML='<span class="cb-icon">\u{1F6D1}</span><span class="cb-text">Stuck</span>',e.title=t.reason||"Loop appears stuck"),e.onclick=()=>this.resetCircuitBreaker()},updateRalphStats(t,e,s){const a=this.$("ralphStatTime");if(a)if(t?.elapsedHours!==null&&t?.elapsedHours!==void 0)a.textContent=this.formatRalphTime(t.elapsedHours);else if(t?.startedAt){const o=(Date.now()-t.startedAt)/36e5;a.textContent=this.formatRalphTime(o)}else a.textContent="0m";const i=this.$("ralphStatCycles");i&&(t?.maxIterations?i.textContent=`${t.cycleCount||0}/${t.maxIterations}`:i.textContent=String(t?.cycleCount||0));const r=this.$("ralphStatTasks");r&&(r.textContent=`${e}/${s}`)},formatRalphTime(t){if(t<.0167)return"0m";if(t<1)return`${Math.round(t*60)}m`;const e=Math.floor(t),s=Math.round((t-e)*60);return s===0?`${e}h`:`${e}h ${s}m`},updateRalphExpandedView(t){const e=this.$("ralphPhrase");e&&(e.textContent=t?.loop?.completionPhrase||"--");const s=this.$("ralphElapsed");if(s)if(t?.loop?.elapsedHours!==null&&t?.loop?.elapsedHours!==void 0)s.textContent=this.formatRalphTime(t.loop.elapsedHours);else if(t?.loop?.startedAt){const l=(Date.now()-t.loop.startedAt)/36e5;s.textContent=this.formatRalphTime(l)}else s.textContent="0m";const a=this.$("ralphIterations");a&&(t?.loop?.maxIterations?a.textContent=`${t.loop.cycleCount||0} / ${t.loop.maxIterations}`:a.textContent=String(t?.loop?.cycleCount||0));const i=t?.todos||[],r=i.filter(l=>l.status==="completed").length,o=this.$("ralphTasksCount");o&&(o.textContent=`${r}/${i.length}`),t?.loop?.planVersion?this.updatePlanVersionDisplay(t.loop.planVersion,t.loop.planHistoryLength||1):this.updatePlanVersionDisplay(null,0),this.renderRalphTasks(i),this.renderRalphStatusBlock(t?.statusBlock)},renderRalphStatusBlock(t){let e=this.$("ralphStatusBlockDisplay");const s=this.$("ralphExpandedContent");if(!t){e&&e.remove();return}if(!e&&s&&(e=document.createElement("div"),e.id="ralphStatusBlockDisplay",e.className="ralph-status-block",s.insertBefore(e,s.firstChild)),!e)return;const a=t.status==="IN_PROGRESS"?"in-progress":t.status==="COMPLETE"?"complete":t.status==="BLOCKED"?"blocked":"",i=t.testsStatus==="PASSING"?"\u2705":t.testsStatus==="FAILING"?"\u274C":"\u23F8",r=t.workType==="IMPLEMENTATION"?"\u{1F527}":t.workType==="TESTING"?"\u{1F9EA}":t.workType==="DOCUMENTATION"?"\u{1F4DD}":t.workType==="REFACTORING"?"\u267B\uFE0F":"\u{1F4CB}";let o=`
18
+ <div class="ralph-status-block-header">
19
+ <span>RALPH_STATUS</span>
20
+ <span class="ralph-status-block-status ${a}">${escapeHtml(t.status)}</span>
21
+ ${t.exitSignal?'<span style="color: #4caf50;">\u{1F6AA} EXIT</span>':""}
22
+ </div>
23
+ <div class="ralph-status-block-stats">
24
+ <span>${r} ${escapeHtml(t.workType)}</span>
25
+ <span>\u{1F4C1} ${t.filesModified} files</span>
26
+ <span>\u2713 ${escapeHtml(String(t.tasksCompletedThisLoop))} tasks</span>
27
+ <span>${i} Tests: ${escapeHtml(t.testsStatus)}</span>
28
+ </div>
29
+ `;t.recommendation&&(o+=`<div class="ralph-status-block-recommendation">${escapeHtml(t.recommendation)}</div>`),e.innerHTML=o},renderRalphTasks(t){const e=this.$("ralphTasksGrid");if(!e)return;if(t.length===0){(e.children.length!==1||!e.querySelector(".ralph-state-empty"))&&(e.innerHTML='<div class="ralph-state-empty">No tasks detected</div>');return}const s={P0:0,P1:1,P2:2,null:3},a={in_progress:0,pending:1,completed:2},i=[...t].sort((o,l)=>{const n=s[o.priority]??3,c=s[l.priority]??3;return n!==c?n-c:(a[o.status]||1)-(a[l.status]||1)}),r=document.createDocumentFragment();i.forEach((o,l)=>{const n=this.createRalphTaskCard(o,l);r.appendChild(n)}),e.innerHTML="",e.appendChild(r)},createRalphTaskCard(t,e){const s=document.createElement("div"),a=`task-${t.status.replace("_","-")}`,i=t.priority?`task-priority-${t.priority.toLowerCase()}`:"";s.className=`ralph-task-card ${a} ${i}`.trim(),s.dataset.taskId=t.id||e;const r=document.createElement("span");if(r.className="ralph-task-icon",r.textContent=this.getRalphTaskIcon(t.status),s.appendChild(r),t.priority){const n=document.createElement("span");n.className=`ralph-task-priority priority-${t.priority.toLowerCase()}`,n.textContent=t.priority,s.appendChild(n)}const o=document.createElement("span");if(o.className="ralph-task-content",o.textContent=t.content,s.appendChild(o),t.attempts&&t.attempts>0){const n=document.createElement("span");n.className="ralph-task-attempts",t.lastError&&(n.classList.add("has-errors"),n.title=`Last error: ${t.lastError}`),n.textContent=`#${t.attempts}`,s.appendChild(n)}if(t.verificationCriteria){const n=document.createElement("span");n.className="ralph-task-verify-badge",n.title=`Verify: ${t.verificationCriteria}`,n.textContent="\u2713",s.appendChild(n)}if(t.dependencies&&t.dependencies.length>0){const n=document.createElement("span");n.className="ralph-task-deps-indicator",n.title=`Depends on: ${t.dependencies.join(", ")}`,n.textContent=`\u2197${t.dependencies.length}`,s.appendChild(n)}const l=document.createElement("div");if(l.className="ralph-task-actions",t.status!=="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u2713",n.title="Mark complete",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"completed")},l.appendChild(n)}if(t.status==="completed"){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BA",n.title="Reopen",n.onclick=c=>{c.stopPropagation(),this.updateRalphTaskStatus(t.id,"pending")},l.appendChild(n)}if(t.lastError){const n=document.createElement("button");n.className="ralph-task-action-btn",n.textContent="\u21BB",n.title="Retry (clear error)",n.onclick=c=>{c.stopPropagation(),this.retryRalphTask(t.id)},l.appendChild(n)}return s.appendChild(l),s},async updateRalphTaskStatus(t,e){if(this.activeSessionId)try{const s=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({status:e})});if(!s.ok){const a=await s.json();throw new Error(a.error||"Failed to update task")}this.showToast(`Task ${e==="completed"?"completed":"reopened"}`,"success")}catch(s){this.showToast("Failed to update task: "+s.message,"error")}},async retryRalphTask(t){if(this.activeSessionId)try{const e=await fetch(`/api/sessions/${this.activeSessionId}/plan/task/${t}`,{method:"PATCH",headers:{"Content-Type":"application/json"},body:JSON.stringify({attempts:0,lastError:null,status:"pending"})});if(!e.ok){const s=await e.json();throw new Error(s.error||"Failed to retry task")}this.showToast("Task reset for retry","success")}catch(e){this.showToast("Failed to retry task: "+e.message,"error")}},getRalphTaskIcon(t){switch(t){case"completed":return"\u2713";case"in_progress":return"\u25D0";default:return"\u25CB"}},getTodoIcon(t){return this.getRalphTaskIcon(t)},updatePlanVersionDisplay(t,e){const s=this.$("ralphVersionRow"),a=this.$("ralphPlanVersion"),i=this.$("ralphRollbackBtn");s&&(t&&t>0?(s.style.display="",a&&(a.textContent=`v${t}`),i&&(i.style.display=e>1?"":"none")):s.style.display="none")},async showPlanHistory(){if(this.activeSessionId)try{const e=await(await fetch(`/api/sessions/${this.activeSessionId}/plan/history`)).json();if(e.error){this.showToast("Failed to load plan history: "+e.error,"error");return}const s=e.history||[];if(s.length===0){this.showToast("No plan history available","info");return}this.showPlanHistoryModal(s,e.currentVersion)}catch(t){this.showToast("Failed to load plan history: "+t.message,"error")}},showPlanHistoryModal(t,e){const s=document.getElementById("planHistoryModal");s&&s.remove();const a=document.createElement("div");a.id="planHistoryModal",a.className="modal active",a.innerHTML=`
30
+ <div class="modal-backdrop" onclick="app.closePlanHistoryModal()"></div>
31
+ <div class="modal-content modal-sm">
32
+ <div class="modal-header">
33
+ <h3>Plan Version History</h3>
34
+ <button class="modal-close" onclick="app.closePlanHistoryModal()">&times;</button>
35
+ </div>
36
+ <div class="modal-body">
37
+ <p style="font-size: 0.8rem; color: var(--text-muted); margin-bottom: 0.75rem;">
38
+ Current version: <strong>v${e}</strong>
39
+ </p>
40
+ <div class="plan-history-list">
41
+ ${t.map(i=>`
42
+ <div class="plan-history-item ${i.version===e?"current":""}"
43
+ onclick="app.rollbackToPlanVersion(${i.version})">
44
+ <div>
45
+ <span class="plan-history-version">v${i.version}</span>
46
+ <span class="plan-history-tasks">${i.taskCount||0} tasks</span>
47
+ </div>
48
+ <span class="plan-history-time">${this.formatRelativeTime(i.timestamp)}</span>
49
+ </div>
50
+ `).join("")}
51
+ </div>
52
+ </div>
53
+ <div class="modal-footer">
54
+ <button class="btn-toolbar" onclick="app.closePlanHistoryModal()">Close</button>
55
+ </div>
56
+ </div>
57
+ `,document.body.appendChild(a)},closePlanHistoryModal(){const t=document.getElementById("planHistoryModal");t&&t.remove()},async rollbackToPlanVersion(t){if(this.activeSessionId&&confirm(`Rollback to plan version ${t}? Current changes will be preserved in history.`))try{const s=await(await fetch(`/api/sessions/${this.activeSessionId}/plan/rollback/${t}`,{method:"POST"})).json();if(s.error){this.showToast("Failed to rollback: "+s.error,"error");return}this.showToast(`Rolled back to plan v${t}`,"success"),this.closePlanHistoryModal(),this.renderRalphStatePanel()}catch(e){this.showToast("Failed to rollback: "+e.message,"error")}},formatRelativeTime(t){if(!t)return"";const s=Date.now()-t,a=Math.floor(s/6e4),i=Math.floor(s/36e5),r=Math.floor(s/864e5);return a<1?"just now":a<60?`${a}m ago`:i<24?`${i}h ago`:`${r}d ago`},renderMonitorPlanAgents(){const t=document.getElementById("monitorPlanAgentsSection"),e=document.getElementById("monitorPlanAgentsBody"),s=document.getElementById("monitorPlanAgentStats");if(!t||!e)return;const a=Array.from(this.planSubagents?.values()||[]),i=!!this.activePlanOrchestratorId;if(a.length===0&&!i){t.style.display="none";return}t.style.display="";const r=a.filter(n=>n.status==="running").length,o=a.filter(n=>n.status==="completed"||n.status==="failed").length;if(s&&(i?s.textContent=`${r} running, ${o} done`:s.textContent=`${a.length} total`),a.length===0){e.innerHTML=`<div class="monitor-empty">${i?"Plan generation starting...":"No plan agents"}</div>`;return}let l="";for(const n of a){const c=n.status==="running"?"active":n.status==="completed"?"completed":"error",p=n.agentType||n.agentId,d=n.model?'<span class="model-badge opus">opus</span>':"",h=n.detail?escapeHtml(n.detail.substring(0,50)):"",u=n.durationMs?`${(n.durationMs/1e3).toFixed(1)}s`:"",m=n.itemCount?`${n.itemCount} items`:"";l+=`
58
+ <div class="process-item">
59
+ <span class="process-mode ${c}">${n.status||"pending"}</span>
60
+ <div class="process-info">
61
+ <div class="process-name">${d} ${escapeHtml(p)}</div>
62
+ <div class="process-meta">
63
+ ${h?`<span>${h}</span>`:""}
64
+ ${m?`<span>${m}</span>`:""}
65
+ ${u?`<span>${u}</span>`:""}
66
+ </div>
67
+ </div>
68
+ </div>
69
+ `}e.innerHTML=l},async cancelPlanFromMonitor(){if(!this.activePlanOrchestratorId&&this.planSubagents?.size===0){this.showToast("No active plan generation","info");return}if(!confirm("Cancel plan generation and close all plan agent windows?"))return;await this.cancelPlanGeneration(),document.getElementById("ralphWizardModal")?.classList.contains("active")&&this.closeRalphWizard(),this.renderMonitorPlanAgents(),this.showToast("Plan generation cancelled","success")}});
@@ -19,7 +19,7 @@
19
19
  * @dependency app.js (CodemanApp class must be defined)
20
20
  * @dependency keyboard-accessory.js (FocusTrap class for modal focus management)
21
21
  * @dependency constants.js (escapeHtml)
22
- * @loadorder 7 of 9 — loaded after app.js, before api-client.js
22
+ * @loadorder 13 of 15 — loaded after session-ui.js, before api-client.js
23
23
  */
24
24
 
25
25
  // ═══════════════════════════════════════════════════════════════