agent-teams-dashboard 0.10.0 → 0.10.1
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 +2 -0
- package/README.zh-TW.md +2 -0
- package/package.json +1 -1
- package/server-dist/server/teamsCache.js +107 -9
package/README.md
CHANGED
|
@@ -7,12 +7,14 @@ Real-time monitoring dashboard for [Claude Code](https://claude.ai/code) agent t
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
9
|
- **Dual-Sidebar Navigation** — Three-panel layout: TeamsPanel (collapsible) → AgentsPanel → MainPanel
|
|
10
|
+
- **Three Sidebar Modes** — Toggle the top-left switcher between **Teams**, **Convos**, and **Workflows**
|
|
10
11
|
- **Responsive Design** — Full RWD: mobile (drawer + tab bar), tablet (auto-collapsed sidebar), desktop (three-column)
|
|
11
12
|
- **Team Overview** — Live status cards with status dots, progress bars, and member counts
|
|
12
13
|
- **Agent Sessions** — Per-agent session grouping with expandable timeline
|
|
13
14
|
- **Agent Sort Toggle** — Switch between time-sorted (default) and team-grouped view in Conversations mode
|
|
14
15
|
- **Session Resume** — Hover any session in Conversations mode → click `⎘` to copy `cd <path> && claude --resume <sessionId>` to clipboard
|
|
15
16
|
- **Project Source Display** — Agent panel header shows the source project name; visible when navigating from search results or Conversations mode
|
|
17
|
+
- **Workflows View** — Browse [workflow](https://code.claude.com/docs/en/workflows) runs grouped by project: phase progress, sub-agents (expandable to their conversations), token/tool-call stats, result, and the run's script. Workflow sub-agents also surface in **Convos**, marked with a ⚙ icon.
|
|
16
18
|
- **Kanban Task Board** — Pending / In Progress / Completed columns
|
|
17
19
|
- **Agent Activity Monitor** — Real-time messages and tool usage per agent
|
|
18
20
|
- **WebSocket Streaming** — File system changes pushed to browser instantly
|
package/README.zh-TW.md
CHANGED
|
@@ -7,10 +7,12 @@
|
|
|
7
7
|
## 功能
|
|
8
8
|
|
|
9
9
|
- **雙側邊欄導航** — 三欄式佈局:TeamsPanel(可摺疊)→ AgentsPanel → MainPanel
|
|
10
|
+
- **三種側邊欄模式** — 左上角切換器可在 **Teams**、**Convos**、**Workflows** 之間切換
|
|
10
11
|
- **團隊總覽** — 即時狀態卡片,含 status dots、進度條、成員數
|
|
11
12
|
- **Agent Sessions** — 按 agent 分組的 session 時間軸,可展開檢視
|
|
12
13
|
- **Session Resume** — 在 Conversations 模式 hover 任一 session → 點 `⎘` 複製 `cd <path> && claude --resume <sessionId>` 到剪貼簿
|
|
13
14
|
- **顯示來源 Project** — Agent 面板 header 右側顯示所屬 project 名稱;從搜尋結果或 Conversations 模式進入時自動帶入
|
|
15
|
+
- **Workflows 檢視** — 依 project 分組瀏覽 [workflow](https://code.claude.com/docs/en/workflows) 執行紀錄:phases 進度、sub-agents(可展開看其對話)、token/tool-call 統計、result 與執行腳本。Workflow sub-agent 也會出現在 **Convos**,以 ⚙ 圖示標記。
|
|
14
16
|
- **看板式任務面板** — Pending / In Progress / Completed 三欄式任務追蹤
|
|
15
17
|
- **Agent 活動監控** — 即時顯示每個 agent 的訊息與工具使用紀錄
|
|
16
18
|
- **WebSocket 即時更新** — 檔案系統變更自動推送至瀏覽器
|
package/package.json
CHANGED
|
@@ -23,11 +23,20 @@ const subagentToMember = new Map(); // subagent filePath -> memberAgentId
|
|
|
23
23
|
const removedTeams = new Map(); // teams deleted from disk
|
|
24
24
|
// --- Workflows ---
|
|
25
25
|
// Full workflow run journals (includes script/result/logs), keyed by runId.
|
|
26
|
+
// Only present once a run COMPLETES (Claude Code writes wf_*.json on completion).
|
|
26
27
|
const workflowRuns = new Map();
|
|
27
28
|
const workflowFileMtimes = new Map(); // wf_*.json filePath -> mtimeMs (skip re-parse if unchanged)
|
|
28
29
|
const workflowAgentType = new Map(); // workflow sub-agent jsonl filePath -> agentType (from meta.json)
|
|
29
30
|
// workflow sub-agent agentId ("wf:<runId>:<hash>") -> run association
|
|
30
31
|
const workflowAgentMeta = new Map();
|
|
32
|
+
// Parsed script meta (name/phases) keyed by runId — present from launch, lets us
|
|
33
|
+
// label RUNNING workflows that have no completion journal yet. The script may live
|
|
34
|
+
// under a different project dir than the sub-agents (same session reused across worktrees),
|
|
35
|
+
// so we correlate by runId.
|
|
36
|
+
const workflowScripts = new Map();
|
|
37
|
+
// Where a run's sub-agents live (project/session), keyed by runId — used to anchor a
|
|
38
|
+
// running run to the project where its conversations actually appear.
|
|
39
|
+
const workflowRunLocation = new Map();
|
|
31
40
|
export const onChange = new EventEmitter();
|
|
32
41
|
// --- Helpers ---
|
|
33
42
|
async function safeReaddir(dir) {
|
|
@@ -200,9 +209,12 @@ export async function scanAgentJsonl() {
|
|
|
200
209
|
continue;
|
|
201
210
|
await readNewEntries(join(subagentsDir, file), false, projDir, parentSessionId);
|
|
202
211
|
}
|
|
203
|
-
// Workflow run journals: session/workflows/wf_*.json
|
|
212
|
+
// Workflow run journals: session/workflows/wf_*.json (completed runs only)
|
|
204
213
|
// (scan runs first so sub-agents can resolve their workflowName)
|
|
205
214
|
await scanWorkflowRuns(join(entryPath, 'workflows'), projDir, parentSessionId);
|
|
215
|
+
// Workflow scripts: session/workflows/scripts/<name>-wf_<runId>.js
|
|
216
|
+
// (present from launch — gives name/phases for RUNNING runs without a journal)
|
|
217
|
+
await scanWorkflowScripts(join(entryPath, 'workflows', 'scripts'));
|
|
206
218
|
// Workflow sub-agent conversations: session/subagents/workflows/wf_<runId>/agent-*.jsonl
|
|
207
219
|
await scanWorkflowSubagents(join(subagentsDir, 'workflows'), projDir, parentSessionId);
|
|
208
220
|
}
|
|
@@ -273,6 +285,45 @@ async function scanWorkflowRuns(wfDir, projDir, sessionId) {
|
|
|
273
285
|
}
|
|
274
286
|
}
|
|
275
287
|
}
|
|
288
|
+
/** Best-effort parse of `export const meta = { name, phases:[{title}] }` from a workflow script. */
|
|
289
|
+
function parseWorkflowScriptMeta(src) {
|
|
290
|
+
let workflowName = '';
|
|
291
|
+
const nameMatch = src.match(/name:\s*['"]([^'"]+)['"]/);
|
|
292
|
+
if (nameMatch)
|
|
293
|
+
workflowName = nameMatch[1];
|
|
294
|
+
const phases = [];
|
|
295
|
+
const phasesMatch = src.match(/phases:\s*\[([\s\S]*?)\]/);
|
|
296
|
+
if (phasesMatch) {
|
|
297
|
+
const titleRe = /title:\s*['"]([^'"]+)['"]/g;
|
|
298
|
+
let m;
|
|
299
|
+
while ((m = titleRe.exec(phasesMatch[1])) !== null) {
|
|
300
|
+
phases.push({ title: m[1] });
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return { workflowName, phases };
|
|
304
|
+
}
|
|
305
|
+
/** Scan session/workflows/scripts/*-wf_<runId>.js to learn name/phases of (possibly running) runs. */
|
|
306
|
+
async function scanWorkflowScripts(scriptsDir) {
|
|
307
|
+
const files = await safeReaddir(scriptsDir);
|
|
308
|
+
for (const file of files) {
|
|
309
|
+
if (!file.endsWith('.js'))
|
|
310
|
+
continue;
|
|
311
|
+
const m = file.match(/(wf_[A-Za-z0-9-]+)\.js$/);
|
|
312
|
+
if (!m)
|
|
313
|
+
continue;
|
|
314
|
+
const runId = m[1];
|
|
315
|
+
if (workflowScripts.has(runId))
|
|
316
|
+
continue; // parse once per run
|
|
317
|
+
const filePath = join(scriptsDir, file);
|
|
318
|
+
const st = await safeFileStat(filePath);
|
|
319
|
+
if (!st || Date.now() - st.mtimeMs > MAX_FILE_AGE_MS)
|
|
320
|
+
continue;
|
|
321
|
+
const src = await safeReadFile(filePath);
|
|
322
|
+
if (!src)
|
|
323
|
+
continue;
|
|
324
|
+
workflowScripts.set(runId, parseWorkflowScriptMeta(src));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
276
327
|
/** Scan session/subagents/workflows/wf_<runId>/agent-*.jsonl conversations. */
|
|
277
328
|
async function scanWorkflowSubagents(wfRoot, projDir, parentSessionId) {
|
|
278
329
|
const runDirs = await safeReaddir(wfRoot);
|
|
@@ -281,6 +332,10 @@ async function scanWorkflowSubagents(wfRoot, projDir, parentSessionId) {
|
|
|
281
332
|
continue;
|
|
282
333
|
const runPath = join(wfRoot, runDir);
|
|
283
334
|
const files = await safeReaddir(runPath);
|
|
335
|
+
// Anchor this run to where its sub-agents live (the project shown in Convos).
|
|
336
|
+
if (files.some((f) => f.startsWith('agent-') && f.endsWith('.jsonl'))) {
|
|
337
|
+
workflowRunLocation.set(runDir, { projectDir: projDir, sessionId: parentSessionId });
|
|
338
|
+
}
|
|
284
339
|
for (const file of files) {
|
|
285
340
|
if (!file.startsWith('agent-') || !file.endsWith('.jsonl'))
|
|
286
341
|
continue;
|
|
@@ -846,24 +901,67 @@ function leanWorkflow(run, agents) {
|
|
|
846
901
|
const { script: _s, scriptPath: _sp, result: _r, logs: _l, ...lean } = run;
|
|
847
902
|
return { ...lean, agents };
|
|
848
903
|
}
|
|
849
|
-
/**
|
|
904
|
+
/** Synthesize a `running` WorkflowRun from live data (script meta + sub-agents) — used
|
|
905
|
+
* for runs that have launched but not yet written a completion journal. */
|
|
906
|
+
function buildRunningWorkflow(runId, agents) {
|
|
907
|
+
const script = workflowScripts.get(runId);
|
|
908
|
+
const loc = workflowRunLocation.get(runId);
|
|
909
|
+
if (!loc)
|
|
910
|
+
return null; // no sub-agents anchored → nothing meaningful to show
|
|
911
|
+
const lastTs = agents.reduce((max, a) => (a.lastTimestamp > max ? a.lastTimestamp : max), '');
|
|
912
|
+
const startTime = lastTs ? new Date(lastTs).getTime() : 0;
|
|
913
|
+
return {
|
|
914
|
+
runId,
|
|
915
|
+
workflowName: script?.workflowName || runId,
|
|
916
|
+
summary: '',
|
|
917
|
+
status: 'running',
|
|
918
|
+
startTime: Number.isNaN(startTime) ? 0 : startTime,
|
|
919
|
+
timestamp: lastTs,
|
|
920
|
+
agentCount: agents.length,
|
|
921
|
+
phases: script?.phases ?? [],
|
|
922
|
+
completedPhases: 0,
|
|
923
|
+
projectDir: loc.projectDir,
|
|
924
|
+
projectName: resolveProjectName(loc.projectDir, knownProjectDirs),
|
|
925
|
+
sessionId: loc.sessionId,
|
|
926
|
+
agents,
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
/** Lean workflow runs (no script/result/logs) for the snapshot — completed + running. */
|
|
850
930
|
export function getWorkflows() {
|
|
851
931
|
const byRun = buildWorkflowAgentsByRun();
|
|
852
932
|
const runs = [];
|
|
933
|
+
const seen = new Set();
|
|
934
|
+
// Completed runs (authoritative journal)
|
|
853
935
|
for (const [runId, run] of workflowRuns) {
|
|
936
|
+
seen.add(runId);
|
|
854
937
|
runs.push(leanWorkflow(run, byRun.get(runId) ?? []));
|
|
855
938
|
}
|
|
856
|
-
//
|
|
857
|
-
|
|
939
|
+
// Running runs: have live sub-agents/script but no completion journal yet
|
|
940
|
+
const liveRunIds = new Set([...workflowRunLocation.keys(), ...workflowScripts.keys()]);
|
|
941
|
+
for (const runId of liveRunIds) {
|
|
942
|
+
if (seen.has(runId))
|
|
943
|
+
continue;
|
|
944
|
+
const running = buildRunningWorkflow(runId, byRun.get(runId) ?? []);
|
|
945
|
+
if (running)
|
|
946
|
+
runs.push(running);
|
|
947
|
+
}
|
|
948
|
+
// Running first, then most recent activity first
|
|
949
|
+
runs.sort((a, b) => {
|
|
950
|
+
const ar = a.status === 'running' ? 1 : 0;
|
|
951
|
+
const br = b.status === 'running' ? 1 : 0;
|
|
952
|
+
if (ar !== br)
|
|
953
|
+
return br - ar;
|
|
954
|
+
return b.timestamp.localeCompare(a.timestamp) || b.startTime - a.startTime;
|
|
955
|
+
});
|
|
858
956
|
return runs;
|
|
859
957
|
}
|
|
860
|
-
/** Full workflow run (with script/result/logs) for the detail endpoint. */
|
|
958
|
+
/** Full workflow run (with script/result/logs) for the detail endpoint; falls back to a running run. */
|
|
861
959
|
export function getWorkflowDetail(runId) {
|
|
862
|
-
const run = workflowRuns.get(runId);
|
|
863
|
-
if (!run)
|
|
864
|
-
return null;
|
|
865
960
|
const agents = buildWorkflowAgentsByRun().get(runId) ?? [];
|
|
866
|
-
|
|
961
|
+
const run = workflowRuns.get(runId);
|
|
962
|
+
if (run)
|
|
963
|
+
return { ...run, agents };
|
|
964
|
+
return buildRunningWorkflow(runId, agents);
|
|
867
965
|
}
|
|
868
966
|
export function getAgentSessions(agentId) {
|
|
869
967
|
const entries = agentEntries.get(agentId);
|