@agent-link/server 0.1.132 → 0.1.133
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/web/app.js +469 -3
- package/web/modules/connection.js +29 -5
- package/web/modules/markdown.js +1 -0
- package/web/modules/messageHelpers.js +4 -3
- package/web/modules/team.js +342 -0
- package/web/style.css +1278 -133
|
@@ -38,6 +38,10 @@ export function createConnection(deps) {
|
|
|
38
38
|
let filePreview = null;
|
|
39
39
|
function setFilePreview(fp) { filePreview = fp; }
|
|
40
40
|
|
|
41
|
+
// Team module — set after creation to resolve circular dependency
|
|
42
|
+
let team = null;
|
|
43
|
+
function setTeam(t) { team = t; }
|
|
44
|
+
|
|
41
45
|
let ws = null;
|
|
42
46
|
let sessionKey = null;
|
|
43
47
|
let reconnectAttempts = 0;
|
|
@@ -105,7 +109,7 @@ export function createConnection(deps) {
|
|
|
105
109
|
id: ++cache.messageIdCounter, role: 'tool',
|
|
106
110
|
toolId: h.toolId || '', toolName: h.toolName || 'unknown',
|
|
107
111
|
toolInput: h.toolInput || '', hasResult: true,
|
|
108
|
-
expanded: (h.toolName === 'Edit' || h.toolName === 'TodoWrite'),
|
|
112
|
+
expanded: (h.toolName === 'Edit' || h.toolName === 'TodoWrite' || h.toolName === 'Agent'),
|
|
109
113
|
timestamp: h.timestamp ? new Date(h.timestamp) : new Date(),
|
|
110
114
|
});
|
|
111
115
|
}
|
|
@@ -177,7 +181,7 @@ export function createConnection(deps) {
|
|
|
177
181
|
id: ++cache.messageIdCounter, role: 'tool',
|
|
178
182
|
toolId: tool.id, toolName: tool.name || 'unknown',
|
|
179
183
|
toolInput: tool.input ? JSON.stringify(tool.input, null, 2) : '',
|
|
180
|
-
hasResult: false, expanded: (tool.name === 'Edit' || tool.name === 'TodoWrite'),
|
|
184
|
+
hasResult: false, expanded: (tool.name === 'Edit' || tool.name === 'TodoWrite' || tool.name === 'Agent'),
|
|
181
185
|
timestamp: new Date(),
|
|
182
186
|
};
|
|
183
187
|
msgs.push(toolMsg);
|
|
@@ -383,7 +387,7 @@ export function createConnection(deps) {
|
|
|
383
387
|
id: streaming.nextId(), role: 'tool',
|
|
384
388
|
toolId: tool.id, toolName: tool.name || 'unknown',
|
|
385
389
|
toolInput: tool.input ? JSON.stringify(tool.input, null, 2) : '',
|
|
386
|
-
hasResult: false, expanded: (tool.name === 'Edit' || tool.name === 'TodoWrite'), timestamp: new Date(),
|
|
390
|
+
hasResult: false, expanded: (tool.name === 'Edit' || tool.name === 'TodoWrite' || tool.name === 'Agent'), timestamp: new Date(),
|
|
387
391
|
};
|
|
388
392
|
messages.value.push(toolMsg);
|
|
389
393
|
if (tool.id) toolMsgMap.set(tool.id, toolMsg);
|
|
@@ -473,6 +477,16 @@ export function createConnection(deps) {
|
|
|
473
477
|
msg = parsed;
|
|
474
478
|
}
|
|
475
479
|
|
|
480
|
+
// ── Team messages: route before normal conversation routing ──
|
|
481
|
+
if (team && (msg.type?.startsWith('team_') || msg.type === 'teams_list' || (msg.type === 'claude_output' && msg.teamId))) {
|
|
482
|
+
if (msg.type === 'claude_output' && msg.teamId) {
|
|
483
|
+
team.handleTeamAgentOutput(msg);
|
|
484
|
+
} else {
|
|
485
|
+
team.handleTeamMessage(msg);
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
|
|
476
490
|
// ── Multi-session: route messages to background conversations ──
|
|
477
491
|
// Messages with a conversationId that doesn't match the current foreground
|
|
478
492
|
// conversation are routed to their cached background state.
|
|
@@ -510,6 +524,7 @@ export function createConnection(deps) {
|
|
|
510
524
|
wsSend({ type: 'change_workdir', workDir: savedDir });
|
|
511
525
|
}
|
|
512
526
|
sidebar.requestSessionList();
|
|
527
|
+
if (team) team.requestTeamsList();
|
|
513
528
|
startPing();
|
|
514
529
|
wsSend({ type: 'query_active_conversations' });
|
|
515
530
|
} else {
|
|
@@ -553,6 +568,7 @@ export function createConnection(deps) {
|
|
|
553
568
|
sidebar.addToWorkdirHistory(msg.agent.workDir);
|
|
554
569
|
}
|
|
555
570
|
sidebar.requestSessionList();
|
|
571
|
+
if (team) team.requestTeamsList();
|
|
556
572
|
startPing();
|
|
557
573
|
wsSend({ type: 'query_active_conversations' });
|
|
558
574
|
} else if (msg.type === 'active_conversations') {
|
|
@@ -607,6 +623,14 @@ export function createConnection(deps) {
|
|
|
607
623
|
processingConversations.value[convId] = true;
|
|
608
624
|
}
|
|
609
625
|
}
|
|
626
|
+
|
|
627
|
+
// Restore active team state on reconnect
|
|
628
|
+
if (team && msg.activeTeam) {
|
|
629
|
+
team.handleActiveTeamRestore(msg.activeTeam);
|
|
630
|
+
} else if (team && !msg.activeTeam && msg.lastCompletedTeamId) {
|
|
631
|
+
// Team completed before page refresh — auto-load as historical view
|
|
632
|
+
team.viewHistoricalTeam(msg.lastCompletedTeamId);
|
|
633
|
+
}
|
|
610
634
|
} else if (msg.type === 'error') {
|
|
611
635
|
streaming.flushReveal();
|
|
612
636
|
finalizeStreamingMsg(scheduleHighlight);
|
|
@@ -755,7 +779,7 @@ export function createConnection(deps) {
|
|
|
755
779
|
id: streaming.nextId(), role: 'tool',
|
|
756
780
|
toolId: h.toolId || '', toolName: h.toolName || 'unknown',
|
|
757
781
|
toolInput: h.toolInput || '', hasResult: true,
|
|
758
|
-
expanded: (h.toolName === 'Edit' || h.toolName === 'TodoWrite'), timestamp: h.timestamp ? new Date(h.timestamp) : new Date(),
|
|
782
|
+
expanded: (h.toolName === 'Edit' || h.toolName === 'TodoWrite' || h.toolName === 'Agent'), timestamp: h.timestamp ? new Date(h.timestamp) : new Date(),
|
|
759
783
|
});
|
|
760
784
|
}
|
|
761
785
|
}
|
|
@@ -880,5 +904,5 @@ export function createConnection(deps) {
|
|
|
880
904
|
ws.send(JSON.stringify({ type: 'authenticate', password: pwd }));
|
|
881
905
|
}
|
|
882
906
|
|
|
883
|
-
return { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, setFilePreview, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap };
|
|
907
|
+
return { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, setFilePreview, setTeam, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap };
|
|
884
908
|
}
|
package/web/modules/markdown.js
CHANGED
|
@@ -73,6 +73,7 @@ const TOOL_SVG = {
|
|
|
73
73
|
Glob: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 1 1-1.06 1.06l-3.04-3.04zM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7z"/></svg>',
|
|
74
74
|
Grep: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.68 11.74a6 6 0 0 1-7.922-8.982 6 6 0 0 1 8.982 7.922l3.04 3.04a.749.749 0 1 1-1.06 1.06l-3.04-3.04zM11.5 7a4.499 4.499 0 1 0-8.997 0A4.499 4.499 0 0 0 11.5 7z"/></svg>',
|
|
75
75
|
Task: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M1.75 1h12.5c.966 0 1.75.784 1.75 1.75v10.5A1.75 1.75 0 0 1 14.25 15H1.75A1.75 1.75 0 0 1 0 13.25V2.75C0 1.784.784 1 1.75 1zm0 1.5a.25.25 0 0 0-.25.25v10.5c0 .138.112.25.25.25h12.5a.25.25 0 0 0 .25-.25V2.75a.25.25 0 0 0-.25-.25H1.75zM3.5 5h9v1.5h-9V5zm0 3h9v1.5h-9V8zm0 3h5v1.5h-5V11z"/></svg>',
|
|
76
|
+
Agent: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M10.5 5a2.5 2.5 0 1 1-5 0 2.5 2.5 0 0 1 5 0zm.061 3.073a4 4 0 1 0-5.123 0 6.004 6.004 0 0 0-3.431 5.142.75.75 0 0 0 1.498.07 4.5 4.5 0 0 1 8.99 0 .75.75 0 1 0 1.498-.07 6.004 6.004 0 0 0-3.432-5.142z"/></svg>',
|
|
76
77
|
WebFetch: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.7 5.3a.75.75 0 0 0-1.06-1.06l-5.5 5.5a.75.75 0 1 0 1.06 1.06l5.5-5.5zM8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13z"/></svg>',
|
|
77
78
|
WebSearch: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M8 0a8 8 0 1 1 0 16A8 8 0 0 1 8 0zm3.7 5.3a.75.75 0 0 0-1.06-1.06l-5.5 5.5a.75.75 0 1 0 1.06 1.06l5.5-5.5zM8 1.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13z"/></svg>',
|
|
78
79
|
TodoWrite: '<svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M13.78 4.22a.75.75 0 0 1 0 1.06l-7.25 7.25a.75.75 0 0 1-1.06 0L2.22 9.28a.751.751 0 0 1 1.042-1.08L6 10.94l6.72-6.72a.75.75 0 0 1 1.06 0z"/></svg>',
|
|
@@ -69,6 +69,8 @@ export function getToolSummary(msg) {
|
|
|
69
69
|
return `${done}/${obj.todos.length} done`;
|
|
70
70
|
}
|
|
71
71
|
if (name === 'Task' && obj.description) return obj.description;
|
|
72
|
+
if (name === 'Agent' && obj.description) return obj.description;
|
|
73
|
+
if (name === 'Agent' && obj.prompt) return obj.prompt.length > 80 ? obj.prompt.slice(0, 80) + '...' : obj.prompt;
|
|
72
74
|
if (name === 'WebSearch' && obj.query) return obj.query;
|
|
73
75
|
if (name === 'WebFetch' && obj.url) return obj.url.length > 60 ? obj.url.slice(0, 60) + '...' : obj.url;
|
|
74
76
|
} catch {}
|
|
@@ -135,13 +137,12 @@ export function getFormattedToolInput(msg) {
|
|
|
135
137
|
return html;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
|
-
if (name === 'Task') {
|
|
140
|
+
if (name === 'Task' || name === 'Agent') {
|
|
139
141
|
let html = '';
|
|
140
142
|
if (obj.description) html += '<div class="task-field"><span class="tool-input-meta">Description</span> ' + esc(obj.description) + '</div>';
|
|
141
143
|
if (obj.subagent_type) html += '<div class="task-field"><span class="tool-input-meta">Agent</span> <code class="tool-input-cmd">' + esc(obj.subagent_type) + '</code></div>';
|
|
142
144
|
if (obj.prompt) {
|
|
143
|
-
|
|
144
|
-
html += '<div class="task-field"><span class="tool-input-meta">Prompt</span></div><div class="task-prompt">' + esc(short) + '</div>';
|
|
145
|
+
html += '<div class="task-field"><span class="tool-input-meta">Prompt</span></div><div class="task-prompt">' + esc(obj.prompt) + '</div>';
|
|
145
146
|
}
|
|
146
147
|
if (html) return html;
|
|
147
148
|
}
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
// ── Team mode: state management and message routing ───────────────────────
|
|
2
|
+
const { ref, computed } = Vue;
|
|
3
|
+
|
|
4
|
+
const MAX_FEED_ENTRIES = 200;
|
|
5
|
+
|
|
6
|
+
// Color palette (matches agent/src/team.ts AGENT_COLORS)
|
|
7
|
+
const AGENT_COLORS = [
|
|
8
|
+
'#EF4444', '#EAB308', '#3B82F6', '#10B981', '#8B5CF6',
|
|
9
|
+
'#F97316', '#EC4899', '#06B6D4', '#84CC16', '#6366F1',
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates the team mode controller.
|
|
14
|
+
* @param {object} deps
|
|
15
|
+
* @param {Function} deps.wsSend
|
|
16
|
+
* @param {Function} deps.scrollToBottom
|
|
17
|
+
*/
|
|
18
|
+
export function createTeam(deps) {
|
|
19
|
+
const { wsSend, scrollToBottom } = deps;
|
|
20
|
+
|
|
21
|
+
// ── Reactive state ──
|
|
22
|
+
|
|
23
|
+
/** @type {import('vue').Ref<object|null>} Current team state (TeamStateSerialized or null) */
|
|
24
|
+
const teamState = ref(null);
|
|
25
|
+
|
|
26
|
+
/** @type {import('vue').Ref<string>} 'chat' | 'team' — current input mode */
|
|
27
|
+
const teamMode = ref('chat');
|
|
28
|
+
|
|
29
|
+
/** @type {import('vue').Ref<string|null>} Currently viewed agent ID, null = dashboard */
|
|
30
|
+
const activeAgentView = ref(null);
|
|
31
|
+
|
|
32
|
+
/** @type {import('vue').Ref<object|null>} Historical team loaded for read-only viewing */
|
|
33
|
+
const historicalTeam = ref(null);
|
|
34
|
+
|
|
35
|
+
/** @type {import('vue').Ref<Array>} Teams list from server */
|
|
36
|
+
const teamsList = ref([]);
|
|
37
|
+
|
|
38
|
+
/** Per-agent message accumulator: agentId → message[] */
|
|
39
|
+
const agentMessages = ref({});
|
|
40
|
+
|
|
41
|
+
/** Per-agent message ID counter */
|
|
42
|
+
let agentMsgIdCounter = 0;
|
|
43
|
+
|
|
44
|
+
// ── Computed ──
|
|
45
|
+
|
|
46
|
+
const isTeamActive = computed(() => teamState.value !== null && teamState.value.status !== 'completed' && teamState.value.status !== 'failed');
|
|
47
|
+
const isTeamRunning = computed(() => teamState.value !== null && (teamState.value.status === 'running' || teamState.value.status === 'planning' || teamState.value.status === 'summarizing'));
|
|
48
|
+
|
|
49
|
+
/** The team being displayed: active or historical */
|
|
50
|
+
const displayTeam = computed(() => historicalTeam.value || teamState.value);
|
|
51
|
+
|
|
52
|
+
const pendingTasks = computed(() => {
|
|
53
|
+
const t = displayTeam.value;
|
|
54
|
+
if (!t) return [];
|
|
55
|
+
return t.tasks.filter(task => task.status === 'pending');
|
|
56
|
+
});
|
|
57
|
+
const activeTasks = computed(() => {
|
|
58
|
+
const t = displayTeam.value;
|
|
59
|
+
if (!t) return [];
|
|
60
|
+
return t.tasks.filter(task => task.status === 'active');
|
|
61
|
+
});
|
|
62
|
+
const doneTasks = computed(() => {
|
|
63
|
+
const t = displayTeam.value;
|
|
64
|
+
if (!t) return [];
|
|
65
|
+
return t.tasks.filter(task => task.status === 'done');
|
|
66
|
+
});
|
|
67
|
+
const failedTasks = computed(() => {
|
|
68
|
+
const t = displayTeam.value;
|
|
69
|
+
if (!t) return [];
|
|
70
|
+
return t.tasks.filter(task => task.status === 'failed');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// ── Methods ──
|
|
74
|
+
|
|
75
|
+
function launchTeam(instruction, template) {
|
|
76
|
+
wsSend({ type: 'create_team', instruction, template: template || 'custom' });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function dissolveTeam() {
|
|
80
|
+
wsSend({ type: 'dissolve_team' });
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function viewAgent(agentId) {
|
|
84
|
+
activeAgentView.value = agentId;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function viewDashboard() {
|
|
88
|
+
activeAgentView.value = null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function viewHistoricalTeam(teamId) {
|
|
92
|
+
wsSend({ type: 'get_team', teamId });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function requestTeamsList() {
|
|
96
|
+
wsSend({ type: 'list_teams' });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function requestAgentHistory(teamId, agentId) {
|
|
100
|
+
wsSend({ type: 'get_team_agent_history', teamId, agentId });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function getAgentColor(agentId) {
|
|
104
|
+
if (agentId === 'lead') return '#A78BFA'; // purple for lead
|
|
105
|
+
const t = displayTeam.value;
|
|
106
|
+
if (!t || !t.agents) return AGENT_COLORS[0];
|
|
107
|
+
const idx = t.agents.findIndex(a => a.id === agentId);
|
|
108
|
+
return idx >= 0 ? AGENT_COLORS[idx % AGENT_COLORS.length] : AGENT_COLORS[0];
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function findAgent(agentId) {
|
|
112
|
+
if (agentId === 'lead') return { id: 'lead', name: 'Lead', color: '#A78BFA', status: 'working' };
|
|
113
|
+
const t = displayTeam.value;
|
|
114
|
+
if (!t || !t.agents) return null;
|
|
115
|
+
return t.agents.find(a => a.id === agentId) || null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getAgentMessages(agentId) {
|
|
119
|
+
return agentMessages.value[agentId] || [];
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function backToChat() {
|
|
123
|
+
teamMode.value = 'chat';
|
|
124
|
+
historicalTeam.value = null;
|
|
125
|
+
activeAgentView.value = null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function newTeam() {
|
|
129
|
+
historicalTeam.value = null;
|
|
130
|
+
activeAgentView.value = null;
|
|
131
|
+
// If completed team is still in teamState, clear it so create panel shows
|
|
132
|
+
if (teamState.value && (teamState.value.status === 'completed' || teamState.value.status === 'failed')) {
|
|
133
|
+
teamState.value = null;
|
|
134
|
+
}
|
|
135
|
+
requestTeamsList();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── Message routing ──
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Handle incoming team-related messages from the WebSocket.
|
|
142
|
+
* Returns true if the message was consumed (should not be processed further).
|
|
143
|
+
*/
|
|
144
|
+
function handleTeamMessage(msg) {
|
|
145
|
+
switch (msg.type) {
|
|
146
|
+
case 'team_created':
|
|
147
|
+
teamState.value = msg.team;
|
|
148
|
+
teamMode.value = 'team';
|
|
149
|
+
historicalTeam.value = null;
|
|
150
|
+
activeAgentView.value = null;
|
|
151
|
+
agentMessages.value = {};
|
|
152
|
+
agentMsgIdCounter = 0;
|
|
153
|
+
// Initialize lead message list
|
|
154
|
+
agentMessages.value['lead'] = [];
|
|
155
|
+
// Initialize agent message lists
|
|
156
|
+
if (msg.team.agents) {
|
|
157
|
+
for (const agent of msg.team.agents) {
|
|
158
|
+
agentMessages.value[agent.id] = [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return true;
|
|
162
|
+
|
|
163
|
+
case 'team_agent_status': {
|
|
164
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
165
|
+
const agent = msg.agent;
|
|
166
|
+
const existing = teamState.value.agents.find(a => a.id === agent.id);
|
|
167
|
+
if (existing) {
|
|
168
|
+
existing.status = agent.status;
|
|
169
|
+
existing.taskId = agent.taskId;
|
|
170
|
+
} else {
|
|
171
|
+
// New agent joined
|
|
172
|
+
teamState.value.agents.push(agent);
|
|
173
|
+
if (!agentMessages.value[agent.id]) {
|
|
174
|
+
agentMessages.value[agent.id] = [];
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// Update team status to running when first subagent appears
|
|
178
|
+
if (teamState.value.status === 'planning') {
|
|
179
|
+
teamState.value.status = 'running';
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
case 'team_task_update': {
|
|
185
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
186
|
+
const task = msg.task;
|
|
187
|
+
const idx = teamState.value.tasks.findIndex(t => t.id === task.id);
|
|
188
|
+
if (idx >= 0) {
|
|
189
|
+
teamState.value.tasks[idx] = task;
|
|
190
|
+
} else {
|
|
191
|
+
teamState.value.tasks.push(task);
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
case 'team_feed': {
|
|
197
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
198
|
+
teamState.value.feed.push(msg.entry);
|
|
199
|
+
// Cap feed entries
|
|
200
|
+
if (teamState.value.feed.length > MAX_FEED_ENTRIES) {
|
|
201
|
+
teamState.value.feed = teamState.value.feed.slice(-MAX_FEED_ENTRIES);
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
case 'team_completed': {
|
|
207
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
208
|
+
// Update with final state from server
|
|
209
|
+
teamState.value = msg.team;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
case 'team_lead_status': {
|
|
214
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
215
|
+
teamState.value.leadStatus = msg.leadStatus;
|
|
216
|
+
return true;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
case 'teams_list':
|
|
220
|
+
teamsList.value = msg.teams || [];
|
|
221
|
+
return true;
|
|
222
|
+
|
|
223
|
+
case 'team_detail':
|
|
224
|
+
historicalTeam.value = msg.team;
|
|
225
|
+
teamMode.value = 'team';
|
|
226
|
+
activeAgentView.value = null;
|
|
227
|
+
return true;
|
|
228
|
+
|
|
229
|
+
case 'team_agent_history': {
|
|
230
|
+
if (msg.agentId) {
|
|
231
|
+
if (msg.messages && msg.messages.length > 0) {
|
|
232
|
+
// Default expand tool messages in history view
|
|
233
|
+
for (const m of msg.messages) {
|
|
234
|
+
if (m.role === 'tool' && m.expanded === undefined) m.expanded = true;
|
|
235
|
+
}
|
|
236
|
+
agentMessages.value[msg.agentId] = msg.messages;
|
|
237
|
+
} else {
|
|
238
|
+
agentMessages.value[msg.agentId] = [];
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
default:
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Handle claude_output messages tagged with teamId + agentRole.
|
|
251
|
+
* Accumulates per-agent messages for agent detail view.
|
|
252
|
+
* Returns true if consumed.
|
|
253
|
+
*/
|
|
254
|
+
function handleTeamAgentOutput(msg) {
|
|
255
|
+
if (!msg.teamId || !msg.agentRole) return false;
|
|
256
|
+
if (!teamState.value || teamState.value.teamId !== msg.teamId) return false;
|
|
257
|
+
|
|
258
|
+
const agentId = msg.agentRole;
|
|
259
|
+
if (!agentMessages.value[agentId]) {
|
|
260
|
+
agentMessages.value[agentId] = [];
|
|
261
|
+
}
|
|
262
|
+
const msgs = agentMessages.value[agentId];
|
|
263
|
+
const data = msg.data;
|
|
264
|
+
if (!data) return true;
|
|
265
|
+
|
|
266
|
+
if (data.type === 'content_block_delta' && data.delta) {
|
|
267
|
+
// Append text to last assistant message (or create new one)
|
|
268
|
+
const last = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
269
|
+
if (last && last.role === 'assistant' && last.isStreaming) {
|
|
270
|
+
last.content += data.delta;
|
|
271
|
+
} else {
|
|
272
|
+
msgs.push({
|
|
273
|
+
id: ++agentMsgIdCounter, role: 'assistant',
|
|
274
|
+
content: data.delta, isStreaming: true, timestamp: Date.now(),
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
} else if (data.type === 'tool_use' && data.tools) {
|
|
278
|
+
// Finalize streaming message
|
|
279
|
+
const last = msgs.length > 0 ? msgs[msgs.length - 1] : null;
|
|
280
|
+
if (last && last.role === 'assistant' && last.isStreaming) {
|
|
281
|
+
last.isStreaming = false;
|
|
282
|
+
}
|
|
283
|
+
for (const tool of data.tools) {
|
|
284
|
+
msgs.push({
|
|
285
|
+
id: ++agentMsgIdCounter, role: 'tool',
|
|
286
|
+
toolId: tool.id, toolName: tool.name || 'unknown',
|
|
287
|
+
toolInput: tool.input ? JSON.stringify(tool.input, null, 2) : '',
|
|
288
|
+
hasResult: false, expanded: true, timestamp: Date.now(),
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
} else if (data.type === 'user' && data.tool_use_result) {
|
|
292
|
+
const result = data.tool_use_result;
|
|
293
|
+
const results = Array.isArray(result) ? result : [result];
|
|
294
|
+
for (const r of results) {
|
|
295
|
+
const toolMsg = msgs.find(m => m.role === 'tool' && m.toolId === r.tool_use_id);
|
|
296
|
+
if (toolMsg) {
|
|
297
|
+
toolMsg.toolOutput = typeof r.content === 'string'
|
|
298
|
+
? r.content : JSON.stringify(r.content, null, 2);
|
|
299
|
+
toolMsg.hasResult = true;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return true;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Handle active_conversations response that includes activeTeam.
|
|
309
|
+
* Called on initial connect + reconnect to restore team state.
|
|
310
|
+
*/
|
|
311
|
+
function handleActiveTeamRestore(activeTeam) {
|
|
312
|
+
if (!activeTeam) return;
|
|
313
|
+
teamState.value = activeTeam;
|
|
314
|
+
teamMode.value = 'team';
|
|
315
|
+
// Re-initialize agent message lists (messages lost on reconnect)
|
|
316
|
+
if (!agentMessages.value['lead']) {
|
|
317
|
+
agentMessages.value['lead'] = [];
|
|
318
|
+
}
|
|
319
|
+
if (activeTeam.agents) {
|
|
320
|
+
for (const agent of activeTeam.agents) {
|
|
321
|
+
if (!agentMessages.value[agent.id]) {
|
|
322
|
+
agentMessages.value[agent.id] = [];
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return {
|
|
329
|
+
// State
|
|
330
|
+
teamState, teamMode, activeAgentView, historicalTeam, teamsList,
|
|
331
|
+
agentMessages,
|
|
332
|
+
// Computed
|
|
333
|
+
isTeamActive, isTeamRunning, displayTeam,
|
|
334
|
+
pendingTasks, activeTasks, doneTasks, failedTasks,
|
|
335
|
+
// Methods
|
|
336
|
+
launchTeam, dissolveTeam, viewAgent, viewDashboard,
|
|
337
|
+
viewHistoricalTeam, requestTeamsList, requestAgentHistory,
|
|
338
|
+
getAgentColor, findAgent, getAgentMessages, backToChat, newTeam,
|
|
339
|
+
// Message handling
|
|
340
|
+
handleTeamMessage, handleTeamAgentOutput, handleActiveTeamRestore,
|
|
341
|
+
};
|
|
342
|
+
}
|