agent-teams-dashboard 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -2
- package/README.zh-TW.md +40 -6
- package/dist/assets/index-BqtV8ETR.css +1 -0
- package/dist/assets/index-C2fnhLSE.js +51 -0
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/server-dist/server/teamsApi.js +17 -0
- package/server-dist/server/teamsCache.js +104 -18
- package/server-dist/server/teamsWatcher.js +28 -1
- package/dist/assets/index-BwUrrF1R.css +0 -1
- package/dist/assets/index-DEOAkXJf.js +0 -49
package/dist/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Agent Teams Dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-C2fnhLSE.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BqtV8ETR.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
|
@@ -48,6 +48,23 @@ export async function handleTeamsApi(req, res) {
|
|
|
48
48
|
json(res, team);
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
|
+
// GET /api/agents/:agentId/sessions/:sessionId
|
|
52
|
+
const sessionDetailMatch = path.match(/^\/api\/agents\/([^/]+)\/sessions\/([^/]+)$/);
|
|
53
|
+
if (sessionDetailMatch) {
|
|
54
|
+
const agentId = decodeURIComponent(sessionDetailMatch[1]);
|
|
55
|
+
const sessionId = decodeURIComponent(sessionDetailMatch[2]);
|
|
56
|
+
const entries = cache.getSessionEntries(agentId, sessionId);
|
|
57
|
+
json(res, entries);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
// GET /api/agents/:agentId/sessions
|
|
61
|
+
const sessionsMatch = path.match(/^\/api\/agents\/([^/]+)\/sessions$/);
|
|
62
|
+
if (sessionsMatch) {
|
|
63
|
+
const agentId = decodeURIComponent(sessionsMatch[1]);
|
|
64
|
+
const sessions = cache.getAgentSessions(agentId);
|
|
65
|
+
json(res, sessions);
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
51
68
|
// GET /api/agents/:agentId/activity
|
|
52
69
|
const agentMatch = path.match(/^\/api\/agents\/([^/]+)\/activity$/);
|
|
53
70
|
if (agentMatch) {
|
|
@@ -138,24 +138,31 @@ async function refreshAllTasks() {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
// --- Agent JSONL scanning ---
|
|
141
|
+
// Scan both subagent JSONL (agent-*.jsonl) and team session JSONL (UUID.jsonl with teamName)
|
|
141
142
|
export async function scanAgentJsonl() {
|
|
142
143
|
const projectDirs = await safeReaddir(PROJECTS_DIR);
|
|
143
144
|
for (const projDir of projectDirs) {
|
|
144
145
|
const projPath = join(PROJECTS_DIR, projDir);
|
|
145
|
-
const
|
|
146
|
-
for (const
|
|
147
|
-
const
|
|
146
|
+
const entries = await safeReaddir(projPath);
|
|
147
|
+
for (const entry of entries) {
|
|
148
|
+
const entryPath = join(projPath, entry);
|
|
149
|
+
// Team session JSONL: UUID.jsonl files at project root level
|
|
150
|
+
if (entry.endsWith('.jsonl')) {
|
|
151
|
+
await readNewEntries(entryPath, true);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// Subagent JSONL: agent-*.jsonl under session/subagents/
|
|
155
|
+
const subagentsDir = join(entryPath, 'subagents');
|
|
148
156
|
const files = await safeReaddir(subagentsDir);
|
|
149
157
|
for (const file of files) {
|
|
150
158
|
if (!file.startsWith('agent-') || !file.endsWith('.jsonl'))
|
|
151
159
|
continue;
|
|
152
|
-
|
|
153
|
-
await readNewEntries(filePath);
|
|
160
|
+
await readNewEntries(join(subagentsDir, file), false);
|
|
154
161
|
}
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
164
|
}
|
|
158
|
-
async function readNewEntries(filePath) {
|
|
165
|
+
async function readNewEntries(filePath, isSessionFile) {
|
|
159
166
|
const fileStat = await safeFileStat(filePath);
|
|
160
167
|
if (!fileStat)
|
|
161
168
|
return;
|
|
@@ -166,13 +173,48 @@ async function readNewEntries(filePath) {
|
|
|
166
173
|
const raw = await safeReadFile(filePath);
|
|
167
174
|
if (!raw)
|
|
168
175
|
return;
|
|
169
|
-
// Read only from the offset position
|
|
170
176
|
const newContent = raw.slice(currentOffset);
|
|
171
177
|
agentOffsets.set(filePath, fileSize);
|
|
172
178
|
const lines = newContent.split('\n').filter(Boolean);
|
|
173
179
|
for (const line of lines) {
|
|
174
180
|
try {
|
|
175
181
|
const parsed = JSON.parse(line);
|
|
182
|
+
// For session files, only process entries that belong to a team agent
|
|
183
|
+
if (isSessionFile) {
|
|
184
|
+
const teamName = parsed.teamName;
|
|
185
|
+
const agentName = parsed.agentName;
|
|
186
|
+
if (!teamName || !agentName)
|
|
187
|
+
continue;
|
|
188
|
+
// Use team agentId format: name@team
|
|
189
|
+
const fullAgentId = `${agentName}@${teamName}`;
|
|
190
|
+
const entry = {
|
|
191
|
+
agentId: fullAgentId,
|
|
192
|
+
slug: agentName,
|
|
193
|
+
sessionId: parsed.sessionId ?? '',
|
|
194
|
+
type: parsed.type ?? 'assistant',
|
|
195
|
+
message: {
|
|
196
|
+
role: parsed.message?.role ?? '',
|
|
197
|
+
content: Array.isArray(parsed.message?.content)
|
|
198
|
+
? parsed.message.content
|
|
199
|
+
: typeof parsed.message?.content === 'string'
|
|
200
|
+
? [{ type: 'text', text: parsed.message.content }]
|
|
201
|
+
: [],
|
|
202
|
+
model: parsed.message?.model,
|
|
203
|
+
},
|
|
204
|
+
timestamp: parsed.timestamp ?? '',
|
|
205
|
+
};
|
|
206
|
+
let arr = agentEntries.get(fullAgentId);
|
|
207
|
+
if (!arr) {
|
|
208
|
+
arr = [];
|
|
209
|
+
agentEntries.set(fullAgentId, arr);
|
|
210
|
+
}
|
|
211
|
+
arr.push(entry);
|
|
212
|
+
if (arr.length > MAX_ENTRIES_PER_AGENT) {
|
|
213
|
+
arr.splice(0, arr.length - MAX_ENTRIES_PER_AGENT);
|
|
214
|
+
}
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
// Subagent JSONL: use agentId from the file
|
|
176
218
|
const entry = {
|
|
177
219
|
agentId: parsed.agentId ?? '',
|
|
178
220
|
slug: parsed.slug ?? '',
|
|
@@ -191,16 +233,14 @@ async function readNewEntries(filePath) {
|
|
|
191
233
|
};
|
|
192
234
|
if (!entry.agentId)
|
|
193
235
|
continue;
|
|
194
|
-
let
|
|
195
|
-
if (!
|
|
196
|
-
|
|
197
|
-
agentEntries.set(entry.agentId,
|
|
236
|
+
let arr = agentEntries.get(entry.agentId);
|
|
237
|
+
if (!arr) {
|
|
238
|
+
arr = [];
|
|
239
|
+
agentEntries.set(entry.agentId, arr);
|
|
198
240
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const excess = entries.length - MAX_ENTRIES_PER_AGENT;
|
|
203
|
-
entries.splice(0, excess);
|
|
241
|
+
arr.push(entry);
|
|
242
|
+
if (arr.length > MAX_ENTRIES_PER_AGENT) {
|
|
243
|
+
arr.splice(0, arr.length - MAX_ENTRIES_PER_AGENT);
|
|
204
244
|
}
|
|
205
245
|
}
|
|
206
246
|
catch {
|
|
@@ -257,12 +297,20 @@ function buildTeamOverview(teamName) {
|
|
|
257
297
|
export function getSnapshot() {
|
|
258
298
|
const teamOverviews = [];
|
|
259
299
|
const matchedAgentIds = new Set();
|
|
300
|
+
// Map full agentId (name@team) -> resolved entries
|
|
301
|
+
const activity = {};
|
|
260
302
|
for (const teamName of teams.keys()) {
|
|
261
303
|
const overview = buildTeamOverview(teamName);
|
|
262
304
|
teamOverviews.push(overview);
|
|
263
305
|
for (const member of overview.config.members) {
|
|
264
306
|
matchedAgentIds.add(member.agentId);
|
|
265
|
-
|
|
307
|
+
const shortId = member.agentId.split('@')[0];
|
|
308
|
+
matchedAgentIds.add(shortId);
|
|
309
|
+
// Resolve: try full agentId first, then short hash
|
|
310
|
+
const entries = agentEntries.get(member.agentId) ?? agentEntries.get(shortId);
|
|
311
|
+
if (entries && entries.length > 0) {
|
|
312
|
+
activity[member.agentId] = entries;
|
|
313
|
+
}
|
|
266
314
|
}
|
|
267
315
|
}
|
|
268
316
|
// Find unmatched agents
|
|
@@ -275,14 +323,52 @@ export function getSnapshot() {
|
|
|
275
323
|
slug: last.slug,
|
|
276
324
|
sessionId: last.sessionId,
|
|
277
325
|
});
|
|
326
|
+
activity[agentId] = entries;
|
|
278
327
|
}
|
|
279
328
|
}
|
|
280
|
-
return { teams: teamOverviews, unmatchedAgents };
|
|
329
|
+
return { teams: teamOverviews, unmatchedAgents, agentActivity: activity };
|
|
281
330
|
}
|
|
282
331
|
// --- Query ---
|
|
283
332
|
export function getAgentActivity(agentId) {
|
|
284
333
|
return agentEntries.get(agentId) ?? [];
|
|
285
334
|
}
|
|
335
|
+
export function getAgentSessions(agentId) {
|
|
336
|
+
const entries = agentEntries.get(agentId);
|
|
337
|
+
if (!entries || entries.length === 0)
|
|
338
|
+
return [];
|
|
339
|
+
const sessionMap = new Map();
|
|
340
|
+
for (const entry of entries) {
|
|
341
|
+
const sid = entry.sessionId || 'unknown';
|
|
342
|
+
let group = sessionMap.get(sid);
|
|
343
|
+
if (!group) {
|
|
344
|
+
group = { entries: [] };
|
|
345
|
+
sessionMap.set(sid, group);
|
|
346
|
+
}
|
|
347
|
+
group.entries.push(entry);
|
|
348
|
+
}
|
|
349
|
+
const sessions = [];
|
|
350
|
+
for (const [sessionId, group] of sessionMap) {
|
|
351
|
+
const first = group.entries[0];
|
|
352
|
+
const last = group.entries[group.entries.length - 1];
|
|
353
|
+
sessions.push({
|
|
354
|
+
sessionId,
|
|
355
|
+
agentId,
|
|
356
|
+
slug: last.slug,
|
|
357
|
+
entryCount: group.entries.length,
|
|
358
|
+
firstTimestamp: first.timestamp,
|
|
359
|
+
lastTimestamp: last.timestamp,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// Sort by lastTimestamp descending (most recent first)
|
|
363
|
+
sessions.sort((a, b) => (b.lastTimestamp > a.lastTimestamp ? 1 : -1));
|
|
364
|
+
return sessions;
|
|
365
|
+
}
|
|
366
|
+
export function getSessionEntries(agentId, sessionId) {
|
|
367
|
+
const entries = agentEntries.get(agentId);
|
|
368
|
+
if (!entries)
|
|
369
|
+
return [];
|
|
370
|
+
return entries.filter(e => (e.sessionId || 'unknown') === sessionId);
|
|
371
|
+
}
|
|
286
372
|
// --- Full refresh ---
|
|
287
373
|
export async function refreshAll() {
|
|
288
374
|
await refreshTeams();
|
|
@@ -8,10 +8,12 @@ const TASKS_DIR = join(CLAUDE_DIR, 'tasks');
|
|
|
8
8
|
const DEBOUNCE_MS = 200;
|
|
9
9
|
const JSONL_POLL_MS = 2000;
|
|
10
10
|
const DIR_CHECK_MS = 5000;
|
|
11
|
+
const FULL_REFRESH_MS = 5000; // Fallback polling for environments where fs.watch doesn't work (e.g. Docker bind mounts)
|
|
11
12
|
let tasksWatcher = null;
|
|
12
13
|
let teamsWatcher = null;
|
|
13
14
|
let jsonlTimer = null;
|
|
14
15
|
let dirCheckTimer = null;
|
|
16
|
+
let fullRefreshTimer = null;
|
|
15
17
|
// --- Debounce helper ---
|
|
16
18
|
function debounce(fn, ms) {
|
|
17
19
|
let timer = null;
|
|
@@ -95,11 +97,31 @@ function startDirCheckPoller() {
|
|
|
95
97
|
}
|
|
96
98
|
}, DIR_CHECK_MS);
|
|
97
99
|
}
|
|
100
|
+
// --- Full refresh poller (fallback for Docker / environments without fs.watch) ---
|
|
101
|
+
function startFullRefreshPoller() {
|
|
102
|
+
let lastSnapshot = '';
|
|
103
|
+
fullRefreshTimer = setInterval(async () => {
|
|
104
|
+
await cache.refreshTeams();
|
|
105
|
+
for (const team of cache.getSnapshot().teams) {
|
|
106
|
+
await cache.refreshTasks(team.config.name);
|
|
107
|
+
}
|
|
108
|
+
const snapshot = JSON.stringify(cache.getSnapshot());
|
|
109
|
+
if (snapshot !== lastSnapshot) {
|
|
110
|
+
lastSnapshot = snapshot;
|
|
111
|
+
cache.onChange.emit('change');
|
|
112
|
+
}
|
|
113
|
+
}, FULL_REFRESH_MS);
|
|
114
|
+
}
|
|
98
115
|
// --- Agent JSONL poller ---
|
|
99
116
|
function startJsonlPoller() {
|
|
117
|
+
let lastSnapshot = '';
|
|
100
118
|
jsonlTimer = setInterval(async () => {
|
|
101
119
|
await cache.scanAgentJsonl();
|
|
102
|
-
cache.
|
|
120
|
+
const snapshot = JSON.stringify(cache.getSnapshot());
|
|
121
|
+
if (snapshot !== lastSnapshot) {
|
|
122
|
+
lastSnapshot = snapshot;
|
|
123
|
+
cache.onChange.emit('change');
|
|
124
|
+
}
|
|
103
125
|
}, JSONL_POLL_MS);
|
|
104
126
|
}
|
|
105
127
|
// --- Public API ---
|
|
@@ -107,6 +129,7 @@ export function startWatching() {
|
|
|
107
129
|
startTasksWatcher();
|
|
108
130
|
startTeamsWatcher();
|
|
109
131
|
startJsonlPoller();
|
|
132
|
+
startFullRefreshPoller();
|
|
110
133
|
// If either dir doesn't exist yet, poll for them
|
|
111
134
|
if (!tasksWatcher || !teamsWatcher) {
|
|
112
135
|
startDirCheckPoller();
|
|
@@ -125,4 +148,8 @@ export function stopWatching() {
|
|
|
125
148
|
clearInterval(dirCheckTimer);
|
|
126
149
|
dirCheckTimer = null;
|
|
127
150
|
}
|
|
151
|
+
if (fullRefreshTimer) {
|
|
152
|
+
clearInterval(fullRefreshTimer);
|
|
153
|
+
fullRefreshTimer = null;
|
|
154
|
+
}
|
|
128
155
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
:root{--bg-primary: #1a1a2e;--bg-secondary: #16213e;--bg-sidebar: #0f0f1a;--bg-card: #16213e;--bg-hover: #1f2b47;--bg-active: #253350;--border-primary: #2a2a3e;--border-subtle: #222236;--text-primary: #e0e0e0;--text-secondary: #888;--text-muted: #555;--accent-blue: #58a6ff;--accent-green: #00ff88;--accent-yellow: #ffd700;--accent-red: #ff4444;--accent-purple: #bc8cff;--accent-cyan: #00d4ff;--font-mono: "JetBrains Mono", "Fira Code", Consolas, monospace;--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;--radius-sm: 4px;--radius-md: 6px;--radius-lg: 8px;--transition-fast: .15s ease}*,*:before,*:after{margin:0;padding:0;box-sizing:border-box}body{font-family:var(--font-mono);font-size:13px;line-height:1.5;color:var(--text-primary);background:var(--bg-primary);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.app-container{display:flex;height:100vh;overflow:hidden}.sidebar{width:250px;min-width:250px;background:var(--bg-sidebar);border-right:1px solid var(--border-primary);overflow-y:auto;display:flex;flex-direction:column}.sidebar-header{padding:16px;border-bottom:1px solid var(--border-primary)}.sidebar-title{font-size:14px;font-weight:600;color:var(--text-primary);letter-spacing:.02em}.sidebar-nav{flex:1;padding:8px}.main-panel{flex:1;overflow-y:auto;padding:16px}.placeholder{display:flex;align-items:center;justify-content:center;height:100%;color:var(--text-muted);font-size:14px}::-webkit-scrollbar{width:8px;height:8px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border-primary);border-radius:4px}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}*{scrollbar-width:thin;scrollbar-color:var(--border-primary) transparent}.text-primary{color:var(--text-primary)}.text-secondary{color:var(--text-secondary)}.text-muted{color:var(--text-muted)}.text-blue{color:var(--accent-blue)}.text-green{color:var(--accent-green)}.text-yellow{color:var(--accent-yellow)}.text-red{color:var(--accent-red)}.text-purple{color:var(--accent-purple)}.text-cyan{color:var(--accent-cyan)}.bg-card{background:var(--bg-card)}.bg-hover{background:var(--bg-hover)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-md{border-radius:var(--radius-md)}.rounded-lg{border-radius:var(--radius-lg)}.flex{display:flex}.flex-col{flex-direction:column}.flex-1{flex:1}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1{gap:4px}.gap-2{gap:8px}.gap-3{gap:12px}.gap-4{gap:16px}.p-1{padding:4px}.p-2{padding:8px}.p-3{padding:12px}.p-4{padding:16px}.px-2{padding-left:8px;padding-right:8px}.px-3{padding-left:12px;padding-right:12px}.py-1{padding-top:4px;padding-bottom:4px}.py-2{padding-top:8px;padding-bottom:8px}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.font-bold{font-weight:600}.text-sm{font-size:12px}.text-xs{font-size:11px}.border{border:1px solid var(--border-primary)}.border-b{border-bottom:1px solid var(--border-primary)}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;user-select:none}.transition{transition:all var(--transition-fast)}.sidebar-nav-item{display:flex;align-items:center;width:100%;padding:8px 12px;border:none;background:transparent;color:var(--text-secondary);font-family:var(--font-mono);font-size:13px;cursor:pointer;border-radius:var(--radius-sm);transition:all var(--transition-fast);text-align:left}.sidebar-nav-item:hover{background:var(--bg-hover);color:var(--text-primary)}.sidebar-nav-item--active{background:var(--bg-active);color:var(--accent-cyan)}.sidebar-team{margin-bottom:2px}.sidebar-team-header{display:flex;align-items:center;gap:6px;width:100%;padding:8px 12px;border:none;background:transparent;color:var(--text-primary);font-family:var(--font-mono);font-size:13px;cursor:pointer;border-radius:var(--radius-sm);transition:all var(--transition-fast);text-align:left}.sidebar-team-header:hover{background:var(--bg-hover)}.sidebar-team-arrow{font-size:10px;color:var(--text-muted);width:12px}.sidebar-footer{padding:12px 16px;border-top:1px solid var(--border-primary)}.sidebar-stats{display:flex;gap:12px}.agent-item{display:flex;align-items:center;gap:6px;width:100%;padding:6px 12px 6px 28px;border:none;background:transparent;color:var(--text-secondary);font-family:var(--font-mono);font-size:12px;cursor:pointer;border-radius:var(--radius-sm);transition:all var(--transition-fast);text-align:left}.agent-item:hover{background:var(--bg-hover);color:var(--text-primary)}.agent-item--selected{background:var(--bg-active);color:var(--accent-cyan)}.agent-item__dot{font-size:8px;flex-shrink:0}.agent-item__name{flex:1;min-width:0}.agent-item__type{flex-shrink:0}.panel-title{font-size:16px;font-weight:600;color:var(--text-primary);margin-bottom:16px}.empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;text-align:center}.empty-state__icon{font-size:48px}.empty-state__title{font-size:16px;font-weight:600;color:var(--text-secondary)}.empty-state__text{max-width:400px;line-height:1.6}.overview-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px}.overview-card{background:var(--bg-card);border:1px solid var(--border-primary);border-left:3px solid var(--text-muted);border-radius:var(--radius-md);padding:16px;transition:all var(--transition-fast)}.overview-card:hover{border-color:var(--accent-cyan);border-left-color:inherit;background:var(--bg-hover)}.overview-card--active{border-left-color:var(--accent-green)}.overview-card--idle{border-left-color:var(--accent-yellow)}.overview-card--done{border-left-color:var(--accent-cyan)}.overview-card--inactive{border-left-color:var(--text-muted)}.overview-card__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.overview-card__meta{margin-bottom:12px}.overview-card__progress{display:flex;align-items:center;gap:8px;margin-bottom:8px}.progress-bar{flex:1;height:4px;background:var(--border-primary);border-radius:2px;overflow:hidden}.progress-bar__fill{height:100%;background:var(--accent-green);border-radius:2px;transition:width .3s ease}.overview-card__footer{text-align:right}.pulse-dot{display:inline-block;width:8px;height:8px;border-radius:50%;background:var(--accent-green);animation:pulse 2s ease-in-out infinite;flex-shrink:0}@keyframes pulse{0%,to{opacity:1;box-shadow:0 0 #0f86}50%{opacity:.7;box-shadow:0 0 0 6px #0f80}}.task-board__columns{display:flex;gap:12px}.task-board__column{flex:1;min-width:0}.task-board__column-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;margin-bottom:8px;border-bottom:2px solid var(--border-primary);font-weight:600;font-size:12px;color:var(--text-secondary)}.task-card{background:var(--bg-card);border:1px solid var(--border-primary);border-radius:var(--radius-md);padding:12px;margin-bottom:8px;border-left:3px solid var(--text-muted)}.task-card--in-progress{border-left-color:var(--accent-yellow)}.task-card--completed{border-left-color:var(--accent-green)}.task-card--pending{border-left-color:var(--text-muted)}.task-card__subject{font-size:12px;margin-bottom:4px;color:var(--text-primary)}.task-card__desc{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;margin-bottom:8px;line-height:1.4}.task-card__meta{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.task-card__owner{display:inline-block;padding:2px 6px;background:#00d4ff26;color:var(--accent-cyan);border-radius:var(--radius-sm);font-size:11px}.task-card__blocked{color:var(--accent-yellow)}.agent-panel{display:flex;flex-direction:column;height:100%}.agent-panel__header{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border-primary)}.agent-panel__header .panel-title{margin-bottom:0}.status-badge{display:inline-block;padding:2px 8px;border-radius:10px;font-size:11px;font-weight:600}.status-badge--active{background:#00ff8826;color:var(--accent-green)}.status-badge--idle{background:#ffd70026;color:var(--accent-yellow)}.status-badge--done{background:#00d4ff26;color:var(--accent-cyan)}.status-badge--unknown{background:#5555554d;color:var(--text-muted)}.agent-panel__tasks{padding:8px 12px;margin-bottom:12px;background:var(--bg-card);border-radius:var(--radius-md);border:1px solid var(--border-primary)}.agent-panel__task-item{display:flex;align-items:center;gap:8px;padding:4px 0;font-size:12px}.task-status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.task-status-dot--pending{background:var(--text-muted)}.task-status-dot--in_progress{background:var(--accent-yellow)}.task-status-dot--completed{background:var(--accent-green)}.agent-panel__feed{flex:1;overflow-y:auto;padding:4px}.activity-entry{padding:8px 12px;margin-bottom:4px;border-radius:var(--radius-sm);font-size:12px}.activity-entry--user{border-left:3px solid var(--accent-green);background:#00ff880d}.activity-entry--assistant{color:var(--accent-cyan)}.activity-entry--tool-use{background:#ffd7000d}.activity-entry--tool-result{color:var(--text-muted)}.activity-entry__header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px}.activity-entry__role{color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em}.activity-entry__body{word-break:break-word}.activity-entry__code{background:var(--bg-secondary);padding:8px;border-radius:var(--radius-sm);overflow-x:auto;font-size:11px;white-space:pre-wrap;margin-top:4px}.activity-entry__tool-use{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap}.activity-entry__tool-badge{display:inline-block;padding:2px 6px;background:#ffd70026;color:var(--accent-yellow);border-radius:var(--radius-sm);font-size:11px;white-space:nowrap}.activity-entry__tool-input{font-size:11px;word-break:break-all}.activity-entry__tool-result{font-size:11px;line-height:1.4}
|