From 12e538f1a9f4ed8c3808b2bfd59321af9249c703 Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Wed, 15 Apr 2026 19:43:32 +0300 Subject: [PATCH 1/4] feat: add dashboard generation for runTeam execution --- docs/cli.md | 1 + oma-dashboards/code.html | 395 ++++++++++++++++++++++ src/orchestrator/orchestrator.ts | 561 ++++++++++++++++++++++++++++++- task.json | 20 ++ 4 files changed, 976 insertions(+), 1 deletion(-) create mode 100644 oma-dashboards/code.html create mode 100644 task.json diff --git a/docs/cli.md b/docs/cli.md index b9c4d65..9bc0062 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -29,6 +29,7 @@ Set the usual provider API keys in the environment (see [README](../README.md#qu ### `oma run` Runs **`OpenMultiAgent.runTeam(team, goal)`**: coordinator decomposition, task queue, optional synthesis. +Each `runTeam` call also writes a static post-execution DAG dashboard HTML to `oma-dashboards/runTeam-.html` in the current working directory. | Argument | Required | Description | |----------|----------|-------------| diff --git a/oma-dashboards/code.html b/oma-dashboards/code.html new file mode 100644 index 0000000..572892f --- /dev/null +++ b/oma-dashboards/code.html @@ -0,0 +1,395 @@ + + + + + + + + Open Multi Agent + + + + + + + + + + + +
+ +
+
+ + + + + + + + + +
+
+ #NODE_049 + check_circle +
+

Architect: Design API

+

STATUS: DONE (2.3s)

+
+ LLM-01 + STABLE +
+
+ +
+
+ #NODE_050 + sync +
+

Developer: Implement

+

STATUS: RUNNING...

+
+
+
+
+ LLM-02 + ACTIVE_STREAM +
+
+ +
+
+ #NODE_051 + hourglass_empty +
+

Reviewer: + Security

+

STATUS: WAITING

+
+ SEC-V3 +
+
+ +
+
+ #NODE_052 + hourglass_empty +
+

Reviewer: + Quality

+

STATUS: WAITING

+
+ QA-ENGINE +
+
+ +
+
+ #NODE_053 + lock +
+

Deployer: + Prod +

+

STATUS: BLOCKED

+
+
+
+ + +
+ +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/src/orchestrator/orchestrator.ts b/src/orchestrator/orchestrator.ts index df5e4c9..17c2a5f 100644 --- a/src/orchestrator/orchestrator.ts +++ b/src/orchestrator/orchestrator.ts @@ -54,6 +54,8 @@ import type { TokenUsage, } from '../types.js' import type { RunOptions } from '../agent/runner.js' +import { mkdir, writeFile } from 'node:fs/promises' +import { join } from 'node:path' import { Agent } from '../agent/agent.js' import { AgentPool } from '../agent/pool.js' import { emitTrace, generateRunId } from '../utils/trace.js' @@ -205,6 +207,511 @@ function resolveTokenBudget(primary?: number, fallback?: number): number | undef return Math.min(primary, fallback) } +interface DashboardTaskMetrics { + readonly durationMs: number + readonly tokenUsage: TokenUsage + readonly toolCalls: AgentRunResult['toolCalls'] +} + +interface DashboardTaskNode { + readonly id: string + readonly title: string + readonly assignee?: string + readonly status: TaskStatus + readonly dependsOn: readonly string[] + readonly metrics?: DashboardTaskMetrics +} + +function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): string { + const dataJson = JSON.stringify({ + generatedAt: new Date().toISOString(), + goal: _goal, + tasks: _tasks, + }) + + return ` + + + + + Open Multi Agent + + + + + + + + +
+
+
+ +
+
+
+ +
+
+ + + +` +} + +async function writeRunTeamDashboard(goal: string, tasks: DashboardTaskNode[]): Promise { + const directory = join(process.cwd(), 'oma-dashboards') + await mkdir(directory, { recursive: true }) + const stamp = new Date().toISOString().replaceAll(':', '-') + const path = join(directory, `runTeam-${stamp}.html`) + await writeFile(path, buildRunTeamDashboardHtml(goal, tasks), 'utf8') + return path +} + /** * Build a minimal {@link Agent} with its own fresh registry/executor. * Registers all built-in tools so coordinator/worker agents can use them. @@ -405,6 +912,7 @@ interface RunContext { readonly maxTokenBudget?: number budgetExceededTriggered: boolean budgetExceededReason?: string + readonly taskMetrics: Map } /** @@ -510,7 +1018,7 @@ async function executeQueue( ? { onTrace: config.onTrace, runId: ctx.runId ?? '', taskId: task.id, traceAgent: assignee, abortSignal: ctx.abortSignal } : ctx.abortSignal ? { abortSignal: ctx.abortSignal } : undefined - const taskStartMs = config.onTrace ? Date.now() : 0 + const taskStartMs = Date.now() let retryCount = 0 const result = await executeWithRetry( @@ -545,6 +1053,11 @@ async function executeQueue( } ctx.agentResults.set(`${assignee}:${task.id}`, result) + ctx.taskMetrics.set(task.id, { + durationMs: Math.max(0, Date.now() - taskStartMs), + tokenUsage: result.tokenUsage, + toolCalls: result.toolCalls, + }) ctx.cumulativeUsage = addUsage(ctx.cumulativeUsage, result.tokenUsage) const totalTokens = ctx.cumulativeUsage.input_tokens + ctx.cumulativeUsage.output_tokens if ( @@ -702,6 +1215,26 @@ export class OpenMultiAgent { private readonly teams: Map = new Map() private completedTaskCount = 0 + private async emitRunTeamDashboard( + goal: string, + tasks: Task[], + taskMetrics: Map, + ): Promise { + const dashboardTasks: DashboardTaskNode[] = tasks.map((task) => ({ + id: task.id, + title: task.title, + assignee: task.assignee, + status: task.status, + dependsOn: task.dependsOn ?? [], + metrics: taskMetrics.get(task.id), + })) + const htmlPath = await writeRunTeamDashboard(goal, dashboardTasks) + this.config.onProgress?.({ + type: 'message', + data: { kind: 'runTeam_dashboard', path: htmlPath }, + } satisfies OrchestratorEvent) + } + /** * @param config - Optional top-level configuration. * @@ -922,6 +1455,27 @@ export class OpenMultiAgent { const agentResults = new Map() agentResults.set(bestAgent.name, result) + await this.emitRunTeamDashboard( + goal, + [{ + id: 'short-circuit', + title: `Short-circuit: ${bestAgent.name}`, + description: goal, + status: result.success ? 'completed' : 'failed', + assignee: bestAgent.name, + dependsOn: [], + result: result.output, + createdAt: new Date(), + updatedAt: new Date(), + }], + new Map([ + ['short-circuit', { + durationMs: 0, + tokenUsage: result.tokenUsage, + toolCalls: result.toolCalls, + }], + ]), + ) return this.buildTeamRunResult(agentResults) } @@ -977,6 +1531,7 @@ export class OpenMultiAgent { maxTokenBudget, ), }) + await this.emitRunTeamDashboard(goal, [], new Map()) return this.buildTeamRunResult(agentResults) } @@ -987,6 +1542,7 @@ export class OpenMultiAgent { const queue = new TaskQueue() const scheduler = new Scheduler('dependency-first') + const taskMetrics = new Map() if (taskSpecs && taskSpecs.length > 0) { // Map title-based dependsOn references to real task IDs so we can @@ -1026,10 +1582,12 @@ export class OpenMultiAgent { maxTokenBudget, budgetExceededTriggered: false, budgetExceededReason: undefined, + taskMetrics, } await executeQueue(queue, ctx) cumulativeUsage = ctx.cumulativeUsage + await this.emitRunTeamDashboard(goal, queue.list(), taskMetrics) // ------------------------------------------------------------------ // Step 5: Coordinator synthesises final result @@ -1138,6 +1696,7 @@ export class OpenMultiAgent { maxTokenBudget: this.config.maxTokenBudget, budgetExceededTriggered: false, budgetExceededReason: undefined, + taskMetrics: new Map(), } await executeQueue(queue, ctx) diff --git a/task.json b/task.json new file mode 100644 index 0000000..58b58bb --- /dev/null +++ b/task.json @@ -0,0 +1,20 @@ +{ + "orchestrator": { + "defaultModel": "gemini-2.5-flash", + "defaultProvider": "gemini" + }, + "team": { + "name": "pipeline", + "agents": [ + { "name": "summrizer", "model": "gemini-2.5-flash", "systemPrompt": "You are a summarizer. You will be given a text and you will need to summarize it."} + ], + "sharedMemory": true + }, + "tasks": [ + { + "title": "Summarize", + "description": "Summarize the text at /tmp/pipeline-output/text.txt. Write a markdown spec to /tmp/pipeline-output/design-spec.md covering: - TypeScript interfaces for Url and ShortenRequest - The shortening algorithm (hash approach is fine) - API contract: POST /shorten, GET /:code Keep the spec under 30 lines.", + "assignee": "summrizer" + } + ] +} \ No newline at end of file From a33d6021948084969a542b6d6740782d8c166ae8 Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:12:51 +0300 Subject: [PATCH 2/4] feat: remove unused HTML and task configuration files --- oma-dashboards/code.html | 395 --------------------------------------- task.json | 20 -- 2 files changed, 415 deletions(-) delete mode 100644 oma-dashboards/code.html delete mode 100644 task.json diff --git a/oma-dashboards/code.html b/oma-dashboards/code.html deleted file mode 100644 index 572892f..0000000 --- a/oma-dashboards/code.html +++ /dev/null @@ -1,395 +0,0 @@ - - - - - - - - Open Multi Agent - - - - - - - - - - - -
- -
-
- - - - - - - - - -
-
- #NODE_049 - check_circle -
-

Architect: Design API

-

STATUS: DONE (2.3s)

-
- LLM-01 - STABLE -
-
- -
-
- #NODE_050 - sync -
-

Developer: Implement

-

STATUS: RUNNING...

-
-
-
-
- LLM-02 - ACTIVE_STREAM -
-
- -
-
- #NODE_051 - hourglass_empty -
-

Reviewer: - Security

-

STATUS: WAITING

-
- SEC-V3 -
-
- -
-
- #NODE_052 - hourglass_empty -
-

Reviewer: - Quality

-

STATUS: WAITING

-
- QA-ENGINE -
-
- -
-
- #NODE_053 - lock -
-

Deployer: - Prod -

-

STATUS: BLOCKED

-
-
-
- - -
- -
-
- - - - - - - - - - - \ No newline at end of file diff --git a/task.json b/task.json deleted file mode 100644 index 58b58bb..0000000 --- a/task.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "orchestrator": { - "defaultModel": "gemini-2.5-flash", - "defaultProvider": "gemini" - }, - "team": { - "name": "pipeline", - "agents": [ - { "name": "summrizer", "model": "gemini-2.5-flash", "systemPrompt": "You are a summarizer. You will be given a text and you will need to summarize it."} - ], - "sharedMemory": true - }, - "tasks": [ - { - "title": "Summarize", - "description": "Summarize the text at /tmp/pipeline-output/text.txt. Write a markdown spec to /tmp/pipeline-output/design-spec.md covering: - TypeScript interfaces for Url and ShortenRequest - The shortening algorithm (hash approach is fine) - API contract: POST /shorten, GET /:code Keep the spec under 30 lines.", - "assignee": "summrizer" - } - ] -} \ No newline at end of file From 10160bf6a966fe4861310e2781c5570a3343000e Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:29:23 +0300 Subject: [PATCH 3/4] fix: update dashboard metrics with start and end timestamps for tasks --- src/orchestrator/orchestrator.ts | 39 ++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/orchestrator/orchestrator.ts b/src/orchestrator/orchestrator.ts index 17c2a5f..f920234 100644 --- a/src/orchestrator/orchestrator.ts +++ b/src/orchestrator/orchestrator.ts @@ -208,6 +208,8 @@ function resolveTokenBudget(primary?: number, fallback?: number): number | undef } interface DashboardTaskMetrics { + readonly startMs: number + readonly endMs: number readonly durationMs: number readonly tokenUsage: TokenUsage readonly toolCalls: AgentRunResult['toolCalls'] @@ -391,6 +393,10 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): +
+ +

0

+
@@ -412,6 +418,7 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): const selectedAssignee = document.getElementById("selectedAssignee"); const selectedState = document.getElementById("selectedState"); const selectedStart = document.getElementById("selectedStart"); + const selectedToolCalls = document.getElementById("selectedToolCalls"); const selectedEnd = document.getElementById("selectedEnd"); const selectedPromptTokens = document.getElementById("selectedPromptTokens"); const selectedCompletionTokens = document.getElementById("selectedCompletionTokens"); @@ -582,6 +589,7 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): function renderDetails(task) { const metrics = task?.metrics ?? {}; + const statusLabel = (statusStyles[task.status] || statusStyles.pending).chip; const usage = metrics.tokenUsage ?? { input_tokens: 0, output_tokens: 0 }; const inTokens = usage.input_tokens ?? 0; const outTokens = usage.output_tokens ?? 0; @@ -589,9 +597,13 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): const ratio = total > 0 ? Math.round((inTokens / total) * 100) : 0; selectedAssignee.textContent = task?.assignee || "UNASSIGNED"; - selectedState.textContent = "ACTIVE STATE: " + task.status.toUpperCase(); - selectedStart.textContent = payload.generatedAt || "-"; - selectedEnd.textContent = payload.generatedAt || "-"; + + selectedState.textContent = "STATE: " + statusLabel; + selectedStart.textContent = metrics.startMs ? new Date(metrics.startMs).toISOString() : "-"; + selectedEnd.textContent = metrics.endMs ? new Date(metrics.endMs).toISOString() : "-"; + + selectedToolCalls.textContent = (metrics.toolCalls ?? []).length.toString(); + selectedPromptTokens.textContent = inTokens.toLocaleString(); selectedCompletionTokens.textContent = outTokens.toLocaleString(); selectedTokenRatio.style.width = ratio + "%"; @@ -668,9 +680,6 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]): }); renderLiveOutput(taskList); - if (taskList.length > 0) { - renderDetails(taskList[0]); - } } renderDag(tasks); @@ -1035,9 +1044,10 @@ async function executeQueue( }, ) + const taskEndMs = Date.now() + // Emit task trace if (config.onTrace) { - const taskEndMs = Date.now() emitTrace(config.onTrace, { type: 'task', runId: ctx.runId ?? '', @@ -1053,8 +1063,11 @@ async function executeQueue( } ctx.agentResults.set(`${assignee}:${task.id}`, result) + ctx.taskMetrics.set(task.id, { - durationMs: Math.max(0, Date.now() - taskStartMs), + startMs: taskStartMs, + endMs: taskEndMs, + durationMs: Math.max(0, taskEndMs - taskStartMs), tokenUsage: result.tokenUsage, toolCalls: result.toolCalls, }) @@ -1433,7 +1446,11 @@ export class OpenMultiAgent { ? { ...(traceFields ?? {}), ...(abortFields ?? {}) } : undefined + const scStartMs = Date.now() + const result = await agent.run(goal, runOptions) + + const scEndMs = Date.now() if (result.budgetExceeded) { this.config.onProgress?.({ @@ -1455,6 +1472,8 @@ export class OpenMultiAgent { const agentResults = new Map() agentResults.set(bestAgent.name, result) + + await this.emitRunTeamDashboard( goal, [{ @@ -1470,7 +1489,9 @@ export class OpenMultiAgent { }], new Map([ ['short-circuit', { - durationMs: 0, + startMs: scStartMs, + endMs: scEndMs, + durationMs: scEndMs - scStartMs, tokenUsage: result.tokenUsage, toolCalls: result.toolCalls, }], From d3479296e17984a831c020c2455c9b8372aaa929 Mon Sep 17 00:00:00 2001 From: Ibrahim Kazimov <74775400+ibrahimkzmv@users.noreply.github.com> Date: Fri, 17 Apr 2026 12:37:33 +0300 Subject: [PATCH 4/4] fix: remove agent picture --- src/orchestrator/orchestrator.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/orchestrator/orchestrator.ts b/src/orchestrator/orchestrator.ts index f920234..a56bd75 100644 --- a/src/orchestrator/orchestrator.ts +++ b/src/orchestrator/orchestrator.ts @@ -360,7 +360,6 @@ function buildRunTeamDashboardHtml(_goal: string, _tasks: DashboardTaskNode[]):
- Agent Avatar

-

ACTIVE STATE: -