mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-06 15:39:41 +02:00
chore: bump version to 0.1488
This commit is contained in:
+84
-144
@@ -1145,14 +1145,6 @@ class ClaudemanApp {
|
||||
// Once set, never recalculated. Persisted to localStorage and server.
|
||||
this.subagentParentMap = new Map();
|
||||
|
||||
// Agent Teams tracking
|
||||
this.teams = new Map(); // Map<teamName, TeamConfig>
|
||||
this.teamTasks = new Map(); // Map<teamName, TeamTask[]>
|
||||
this.teammateMap = new Map(); // Map<agentId-prefix, {name, color, teamName}> for quick lookup
|
||||
|
||||
// Teammate tmux pane terminals
|
||||
this.teammatePanesByName = new Map(); // Map<name, { paneTarget, sessionId, color }>
|
||||
this.teammateTerminals = new Map(); // Map<agentId, { terminal, fitAddon, paneTarget, sessionId, resizeObserver }>
|
||||
this.ralphStatePanelCollapsed = true; // Default to collapsed
|
||||
this.ralphClosedSessions = new Set(); // Sessions where user explicitly closed Ralph panel
|
||||
|
||||
@@ -2108,22 +2100,6 @@ class ClaudemanApp {
|
||||
this.closeSessionImagePopups(data.id); // Close image popup windows for this session
|
||||
this.closeSessionSubagentWindows(data.id, true); // Close subagent windows and cleanup activity data
|
||||
|
||||
// Clean up any agent teams associated with this session
|
||||
for (const [teamName, team] of this.teams) {
|
||||
if (team.leadSessionId === data.id) {
|
||||
this.teams.delete(teamName);
|
||||
this.teamTasks.delete(teamName);
|
||||
}
|
||||
}
|
||||
// Clean up teammate pane tracking for this session
|
||||
for (const [name, paneInfo] of this.teammatePanesByName) {
|
||||
if (paneInfo.sessionId === data.id) {
|
||||
this.teammatePanesByName.delete(name);
|
||||
}
|
||||
}
|
||||
this.rebuildTeammateMap();
|
||||
this.renderTeamTasksPanel();
|
||||
|
||||
// Clean up idle timer for this session
|
||||
const idleTimer = this.idleTimers.get(data.id);
|
||||
if (idleTimer) {
|
||||
@@ -2892,81 +2868,6 @@ class ClaudemanApp {
|
||||
}, 5 * 60 * 1000); // 5 minutes
|
||||
});
|
||||
|
||||
// ========== Agent Teams Events ==========
|
||||
|
||||
addListener('team:created', (e) => {
|
||||
const team = JSON.parse(e.data);
|
||||
this.teams.set(team.name, team);
|
||||
this.rebuildTeammateMap();
|
||||
this.renderTeamTasksPanel();
|
||||
this.updateSubagentWindows();
|
||||
});
|
||||
|
||||
addListener('team:updated', (e) => {
|
||||
const team = JSON.parse(e.data);
|
||||
this.teams.set(team.name, team);
|
||||
this.rebuildTeammateMap();
|
||||
this.renderTeamTasksPanel();
|
||||
this.updateSubagentWindows();
|
||||
});
|
||||
|
||||
addListener('team:removed', (e) => {
|
||||
const team = JSON.parse(e.data);
|
||||
this.teams.delete(team.name);
|
||||
this.teamTasks.delete(team.name);
|
||||
this.rebuildTeammateMap();
|
||||
this.renderTeamTasksPanel();
|
||||
this.updateSubagentWindows();
|
||||
});
|
||||
|
||||
addListener('team:task_updated', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
this.teamTasks.set(data.teamName, data.tasks);
|
||||
this.renderTeamTasksPanel();
|
||||
});
|
||||
|
||||
addListener('team:inbox_message', (e) => {
|
||||
// Inbox messages currently just trigger panel updates
|
||||
this.renderTeamTasksPanel();
|
||||
});
|
||||
|
||||
// ========== Teammate Pane Events ==========
|
||||
|
||||
addListener('teammate:pane_available', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
// Only show teammate pane windows if the session has a tab in Claudeman
|
||||
if (!this.sessions.has(data.sessionId)) return;
|
||||
|
||||
this.teammatePanesByName.set(data.teammateName, {
|
||||
paneTarget: data.paneTarget,
|
||||
sessionId: data.sessionId,
|
||||
color: data.color,
|
||||
});
|
||||
// Teammate pane windows are disabled — users interact with teammates
|
||||
// directly through the main tmux split-pane terminal instead.
|
||||
});
|
||||
|
||||
addListener('teammate:terminal', (e) => {
|
||||
const data = JSON.parse(e.data);
|
||||
// Find the terminal instance for this pane
|
||||
for (const [, termData] of this.teammateTerminals) {
|
||||
if (termData.paneTarget === data.paneTarget && termData.sessionId === data.sessionId) {
|
||||
// Decode base64 to Uint8Array to preserve UTF-8 multi-byte sequences
|
||||
const binary = atob(data.data);
|
||||
const bytes = new Uint8Array(binary.length);
|
||||
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
||||
if (termData.terminal) {
|
||||
try { termData.terminal.write(bytes); } catch {}
|
||||
} else if (termData.pendingData) {
|
||||
// Terminal not ready yet — buffer the data
|
||||
termData.pendingData.push(bytes);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ========== Image Detection Events (Screenshots & Generated Images) ==========
|
||||
|
||||
addListener('image:detected', (e) => {
|
||||
@@ -11285,6 +11186,16 @@ class ClaudemanApp {
|
||||
}
|
||||
|
||||
renderSubagentPanel() {
|
||||
// Debounce renders at 150ms to prevent excessive DOM updates from rapid subagent events
|
||||
if (this._subagentPanelRenderTimeout) {
|
||||
clearTimeout(this._subagentPanelRenderTimeout);
|
||||
}
|
||||
this._subagentPanelRenderTimeout = setTimeout(() => {
|
||||
this._renderSubagentPanelImmediate();
|
||||
}, 150);
|
||||
}
|
||||
|
||||
_renderSubagentPanelImmediate() {
|
||||
const list = this.$('subagentList');
|
||||
if (!list) return;
|
||||
|
||||
@@ -11777,6 +11688,17 @@ class ClaudemanApp {
|
||||
// This map stores agentId -> sessionId, where sessionId is the tab's data-id.
|
||||
|
||||
updateConnectionLines() {
|
||||
// Coalesce multiple calls within the same frame into a single rAF
|
||||
if (!this._connectionLinesScheduled) {
|
||||
this._connectionLinesScheduled = true;
|
||||
requestAnimationFrame(() => {
|
||||
this._connectionLinesScheduled = false;
|
||||
this._updateConnectionLinesImmediate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_updateConnectionLinesImmediate() {
|
||||
const svg = document.getElementById('connectionLines');
|
||||
if (!svg) return;
|
||||
|
||||
@@ -12007,13 +11929,6 @@ class ClaudemanApp {
|
||||
// ========== Subagent Floating Windows ==========
|
||||
|
||||
openSubagentWindow(agentId) {
|
||||
// Skip teammate agents — they use tmux split panes in the main terminal,
|
||||
// not floating popup windows. Prevents false-info activity log popups.
|
||||
const agentForTeammateCheck = this.subagents.get(agentId);
|
||||
if (agentForTeammateCheck && this.getTeammateInfo(agentForTeammateCheck)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If window already exists, focus it
|
||||
if (this.subagentWindows.has(agentId)) {
|
||||
const existing = this.subagentWindows.get(agentId);
|
||||
@@ -12581,49 +12496,74 @@ class ClaudemanApp {
|
||||
return;
|
||||
}
|
||||
|
||||
const html = activity.slice(-100).map(a => {
|
||||
const time = new Date(a.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
||||
if (a.type === 'tool') {
|
||||
return `<div class="activity-line">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${this.getToolIcon(a.tool)}</span>
|
||||
<span class="tool-name">${a.tool}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(this.getToolDetail(a.tool, a.input))}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'tool_result') {
|
||||
const icon = a.isError ? '❌' : '📄';
|
||||
const statusClass = a.isError ? ' error' : '';
|
||||
const sizeInfo = a.contentLength > 500 ? ` (${this.formatBytes(a.contentLength)})` : '';
|
||||
const preview = a.preview.length > 60 ? a.preview.substring(0, 60) + '...' : a.preview;
|
||||
return `<div class="activity-line result-line${statusClass}">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${icon}</span>
|
||||
<span class="tool-name">${a.tool || '→'}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(preview)}${sizeInfo}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'progress') {
|
||||
// Check for hook events
|
||||
const isHook = a.hookEvent || a.hookName;
|
||||
const icon = isHook ? '🪝' : (a.progressType === 'query_update' ? '⟳' : '✓');
|
||||
const displayText = isHook ? (a.hookName || a.hookEvent) : (a.query || a.progressType);
|
||||
return `<div class="activity-line progress-line${isHook ? ' hook-line' : ''}">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${icon}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(displayText)}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'message') {
|
||||
const preview = a.text.length > 150 ? a.text.substring(0, 150) + '...' : a.text;
|
||||
return `<div class="message-line">
|
||||
<span class="time">${time}</span> 💬 ${this.escapeHtml(preview)}
|
||||
</div>`;
|
||||
// Incremental rendering: track how many items are already rendered
|
||||
const renderedCount = body.dataset.renderedCount ? parseInt(body.dataset.renderedCount, 10) : 0;
|
||||
const maxItems = 100;
|
||||
const visibleActivity = activity.slice(-maxItems);
|
||||
|
||||
// If activity was trimmed or this is a fresh render, do full rebuild
|
||||
if (renderedCount === 0 || renderedCount > visibleActivity.length || body.children.length === 0 ||
|
||||
(body.children.length === 1 && body.querySelector('.subagent-empty'))) {
|
||||
// Full rebuild
|
||||
const html = visibleActivity.map(a => this._renderActivityItem(a)).join('');
|
||||
body.innerHTML = html;
|
||||
body.dataset.renderedCount = String(visibleActivity.length);
|
||||
} else {
|
||||
// Incremental: only append new items
|
||||
const newItems = visibleActivity.slice(renderedCount);
|
||||
if (newItems.length > 0) {
|
||||
const newHtml = newItems.map(a => this._renderActivityItem(a)).join('');
|
||||
body.insertAdjacentHTML('beforeend', newHtml);
|
||||
body.dataset.renderedCount = String(visibleActivity.length);
|
||||
|
||||
// Trim excess children from the front if over maxItems
|
||||
while (body.children.length > maxItems) {
|
||||
body.removeChild(body.firstChild);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
body.innerHTML = html;
|
||||
body.scrollTop = body.scrollHeight;
|
||||
}
|
||||
|
||||
_renderActivityItem(a) {
|
||||
const time = new Date(a.timestamp).toLocaleTimeString('en-US', { hour12: false });
|
||||
if (a.type === 'tool') {
|
||||
return `<div class="activity-line">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${this.getToolIcon(a.tool)}</span>
|
||||
<span class="tool-name">${a.tool}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(this.getToolDetail(a.tool, a.input))}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'tool_result') {
|
||||
const icon = a.isError ? '❌' : '📄';
|
||||
const statusClass = a.isError ? ' error' : '';
|
||||
const sizeInfo = a.contentLength > 500 ? ` (${this.formatBytes(a.contentLength)})` : '';
|
||||
const preview = a.preview.length > 60 ? a.preview.substring(0, 60) + '...' : a.preview;
|
||||
return `<div class="activity-line result-line${statusClass}">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${icon}</span>
|
||||
<span class="tool-name">${a.tool || '→'}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(preview)}${sizeInfo}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'progress') {
|
||||
const isHook = a.hookEvent || a.hookName;
|
||||
const icon = isHook ? '🪝' : (a.progressType === 'query_update' ? '⟳' : '✓');
|
||||
const displayText = isHook ? (a.hookName || a.hookEvent) : (a.query || a.progressType);
|
||||
return `<div class="activity-line progress-line${isHook ? ' hook-line' : ''}">
|
||||
<span class="time">${time}</span>
|
||||
<span class="tool-icon">${icon}</span>
|
||||
<span class="tool-detail">${this.escapeHtml(displayText)}</span>
|
||||
</div>`;
|
||||
} else if (a.type === 'message') {
|
||||
const preview = a.text.length > 150 ? a.text.substring(0, 150) + '...' : a.text;
|
||||
return `<div class="message-line">
|
||||
<span class="time">${time}</span> 💬 ${this.escapeHtml(preview)}
|
||||
</div>`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Update all open subagent windows
|
||||
updateSubagentWindows() {
|
||||
for (const agentId of this.subagentWindows.keys()) {
|
||||
|
||||
@@ -425,21 +425,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Team Tasks Panel -->
|
||||
<div class="team-tasks-panel" id="teamTasksPanel" style="display: none;">
|
||||
<div class="team-tasks-header">
|
||||
<span class="team-tasks-header-text">Team Tasks</span>
|
||||
<div class="team-tasks-progress">
|
||||
<div class="team-tasks-progress-bar">
|
||||
<div class="team-tasks-progress-fill"></div>
|
||||
</div>
|
||||
<span class="team-tasks-progress-text">0/0</span>
|
||||
</div>
|
||||
<button class="team-tasks-close" onclick="app.hideTeamTasksPanel()" title="Hide">─</button>
|
||||
</div>
|
||||
<div class="team-tasks-list"></div>
|
||||
</div>
|
||||
|
||||
<!-- Session Options Modal (includes Respawn Settings) -->
|
||||
<div class="modal" id="sessionOptionsModal">
|
||||
<div class="modal-backdrop" onclick="app.closeSessionOptions()"></div>
|
||||
|
||||
@@ -5317,20 +5317,6 @@ kbd {
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
/* Teammate terminal pane (xterm.js embedded in subagent window) */
|
||||
.subagent-window-body.teammate-terminal-body {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
.subagent-window-body.teammate-terminal-body .xterm {
|
||||
height: 100%;
|
||||
}
|
||||
/* Larger default size for terminal windows */
|
||||
.subagent-window.has-terminal {
|
||||
width: 550px;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
/* Custom scrollbar for subagent window body */
|
||||
.subagent-window-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
@@ -5499,187 +5485,6 @@ kbd {
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ========== Agent Teams (Teammate Badges + Team Tasks Panel) ========== */
|
||||
|
||||
/* Teammate badge in subagent panel/windows */
|
||||
.teammate-badge {
|
||||
font-size: 10px;
|
||||
padding: 1px 5px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
margin-left: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.teammate-color-blue {
|
||||
background: #3b82f6;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.teammate-color-green {
|
||||
background: #22c55e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.teammate-color-yellow {
|
||||
background: #eab308;
|
||||
color: #1a1a2e;
|
||||
}
|
||||
|
||||
/* Teammate dot icon replacing robot emoji */
|
||||
.teammate-dot {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.teammate-dot.teammate-color-blue { color: #3b82f6; }
|
||||
.teammate-dot.teammate-color-green { color: #22c55e; }
|
||||
.teammate-dot.teammate-color-yellow { color: #eab308; }
|
||||
|
||||
/* Subagent items that are teammates get a subtle left border */
|
||||
.subagent-item.is-teammate {
|
||||
border-left: 2px solid #3b82f6;
|
||||
}
|
||||
.subagent-item.is-teammate .teammate-color-green ~ * { border-left-color: #22c55e; }
|
||||
|
||||
/* Team Tasks Panel */
|
||||
.team-tasks-panel {
|
||||
position: fixed;
|
||||
width: 360px;
|
||||
min-width: 260px;
|
||||
min-height: 120px;
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
background: var(--bg-card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||
z-index: 900;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
resize: both;
|
||||
}
|
||||
|
||||
/* Resize handle indicator */
|
||||
.team-tasks-panel::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
background: linear-gradient(135deg, transparent 50%, var(--border-light) 50%, var(--border-light) 60%, transparent 60%, transparent 70%, var(--border-light) 70%, var(--border-light) 80%, transparent 80%);
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.team-tasks-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: var(--bg-input);
|
||||
border-bottom: 1px solid var(--border);
|
||||
cursor: move;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.team-tasks-close {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.9rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.team-tasks-close:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.team-tasks-header-text {
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.team-tasks-progress {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.team-tasks-progress-bar {
|
||||
width: 60px;
|
||||
height: 6px;
|
||||
background: var(--bg-primary);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.team-tasks-progress-fill {
|
||||
height: 100%;
|
||||
background: var(--success);
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.team-tasks-progress-text {
|
||||
color: var(--text-secondary);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.team-tasks-list {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.team-task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 12px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.team-task-item.completed {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.team-task-item.in-progress .team-task-status {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.team-task-status {
|
||||
flex-shrink: 0;
|
||||
width: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.team-task-subject {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.team-task-owner {
|
||||
font-size: 10px;
|
||||
padding: 1px 5px;
|
||||
border-radius: 8px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.team-task-empty {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
/* ========== Project Insights Panel (Bash File Viewers) ========== */
|
||||
|
||||
.project-insights-panel {
|
||||
|
||||
Reference in New Issue
Block a user