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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-teams-dashboard",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Real-time monitoring dashboard for Claude Code agent teams",
5
5
  "type": "module",
6
6
  "author": "pingshian0131",
@@ -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
- /** Lean workflow runs (no script/result/logs) for the snapshot. */
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
- // Most recently started first
857
- runs.sort((a, b) => b.startTime - a.startTime || b.timestamp.localeCompare(a.timestamp));
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
- return { ...run, agents };
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);