Skip to content

Commit bb63260

Browse files
authored
Merge pull request #23 from arvoreeducacao/joaobarros-/-agent-teams-ui-and-message-delivery
feat: Agent Teams TUI dashboard and message delivery on exit
2 parents 1efee9b + 16c9121 commit bb63260

17 files changed

Lines changed: 3800 additions & 10 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,19 @@ Agent Teams Chat — cross-developer agent communication via Slack threads. Enab
223223
- Search threads by topic or content
224224
- Configurable message format using handlebars-style templates
225225

226+
### [@arvoretech/agent-teams-ui](./packages/agent-teams-ui)
227+
228+
Agent Teams UI — TUI dashboard for monitoring agent teams in real time. Watches `.agent-teams/` and auto-refreshes.
229+
230+
**Features:**
231+
232+
- Team view with per-teammate task counts and current activity
233+
- Kanban board with task selection and detail view
234+
- WhatsApp-style message view with date separators and sender grouping
235+
- Chat view showing agent stdout as conversation, filterable by agent
236+
- Live timer, progress bar, and new-activity indicators per tab
237+
- Auto-refresh via filesystem watching (chokidar)
238+
226239
### [@arvoretech/metabase-mcp](./packages/metabase)
227240

228241
Interact with Metabase BI platform directly from your AI assistant.

packages/agent-teams-lead/src/spawner.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { writeFile, mkdir, readFile, unlink, appendFile } from "node:fs/promises
33
import { existsSync } from "node:fs";
44
import { createRequire } from "node:module";
55
import { join, resolve } from "node:path";
6-
import type { Teammate } from "./types.js";
6+
import type { Teammate, Message } from "./types.js";
77

88
interface SpawnedProcess {
99
teammateId: string;
@@ -135,6 +135,7 @@ export class TeammateSpawner {
135135

136136
proc.on("exit", (code) => {
137137
this.log(teammate.name, `Exited with code ${code}`);
138+
this.deliverPendingMessages(teammate.id, teammate.name);
138139
this.processes.delete(teammate.id);
139140
this.cleanupAgentConfig(configPath);
140141
});
@@ -371,4 +372,43 @@ export class TeammateSpawner {
371372
// noop
372373
}
373374
}
375+
376+
private async deliverPendingMessages(
377+
teammateId: string,
378+
teammateName: string
379+
): Promise<void> {
380+
try {
381+
const messagesPath = join(
382+
this.workspacePath,
383+
".agent-teams",
384+
"messages.json"
385+
);
386+
if (!existsSync(messagesPath)) return;
387+
388+
const raw = await readFile(messagesPath, "utf-8");
389+
const messages = JSON.parse(raw) as Message[];
390+
391+
const pending = messages.filter(
392+
(m) =>
393+
(m.to === teammateId || m.to === "broadcast") &&
394+
!m.read_by.includes(teammateId)
395+
);
396+
397+
if (pending.length === 0) return;
398+
399+
await this.log(
400+
teammateName,
401+
`[pending] ${pending.length} unread message(s) on exit:`
402+
);
403+
404+
for (const msg of pending) {
405+
await this.log(
406+
teammateName,
407+
` [${msg.kind}] from ${msg.from_name}: ${msg.subject}${msg.body}`
408+
);
409+
}
410+
} catch {
411+
// noop
412+
}
413+
}
374414
}

packages/agent-teams-lead/src/store.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,18 @@ export class TeamStore {
281281
return this.artifacts.find((a) => a.id === artifactId);
282282
}
283283

284+
async ackMessages(readerId: string, messageIds: string[]): Promise<void> {
285+
return withFileLock(this.messagesPath(), async () => {
286+
this.messages = await this.readJson<Message[]>(this.messagesPath(), []);
287+
for (const msg of this.messages) {
288+
if (messageIds.includes(msg.id) && !msg.read_by.includes(readerId)) {
289+
msg.read_by.push(readerId);
290+
}
291+
}
292+
await this.writeJson(this.messagesPath(), this.messages);
293+
});
294+
}
295+
284296
getArtifacts(): Artifact[] {
285297
return [...this.artifacts];
286298
}

packages/agent-teams-lead/src/tools.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,15 @@ export class LeadTools {
210210
(inProgress.length === 0 && pending.length === 0) ||
211211
allTeammatesDone
212212
) {
213+
const leadMessages = this.store.getMessages({
214+
to: "lead",
215+
unread_by: "lead",
216+
});
217+
218+
if (leadMessages.length > 0) {
219+
await this.store.ackMessages("lead", leadMessages.map((m) => m.id));
220+
}
221+
213222
return this.ok({
214223
done: true,
215224
reason:
@@ -227,13 +236,30 @@ export class LeadTools {
227236
title: t.title,
228237
notes: t.notes,
229238
})),
239+
pending_messages: leadMessages.map((m) => ({
240+
id: m.id,
241+
from_name: m.from_name,
242+
kind: m.kind,
243+
subject: m.subject,
244+
body: m.body,
245+
created_at: m.created_at,
246+
})),
230247
});
231248
}
232249

233250
await new Promise((resolve) => setTimeout(resolve, pollInterval));
234251
}
235252

236253
const tasks = this.store.getTasks();
254+
const leadMessagesOnTimeout = this.store.getMessages({
255+
to: "lead",
256+
unread_by: "lead",
257+
});
258+
259+
if (leadMessagesOnTimeout.length > 0) {
260+
await this.store.ackMessages("lead", leadMessagesOnTimeout.map((m) => m.id));
261+
}
262+
237263
return this.ok({
238264
done: false,
239265
timed_out: true,
@@ -254,6 +280,14 @@ export class LeadTools {
254280
blocked: tasks
255281
.filter((t) => t.status === "blocked")
256282
.map((t) => ({ id: t.id, title: t.title })),
283+
pending_messages: leadMessagesOnTimeout.map((m) => ({
284+
id: m.id,
285+
from_name: m.from_name,
286+
kind: m.kind,
287+
subject: m.subject,
288+
body: m.body,
289+
created_at: m.created_at,
290+
})),
257291
});
258292
} catch (error) {
259293
return this.errorResult(error);

packages/agent-teams-ui/README.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# @arvoretech/agent-teams-ui
2+
3+
TUI dashboard for monitoring agent teams in real time. Built with [Ink](https://github.com/vadimdemedes/ink) (React for the terminal).
4+
5+
## Usage
6+
7+
```bash
8+
npx @arvoretech/agent-teams-ui [workspace-path]
9+
```
10+
11+
If no path is provided, uses the current directory. The TUI watches `.agent-teams/` for changes and auto-refreshes.
12+
13+
## Tabs
14+
15+
### [1] Team
16+
17+
Teammates with status, task counts `[done/total]`, and current activity with elapsed time.
18+
19+
### [2] Board
20+
21+
Kanban columns: Pending | In Progress | Blocked | Done.
22+
23+
- `j/k` — select task
24+
- `Enter` — open detail view (description, criteria, summary, touched files, notes)
25+
- `Esc` — back to board
26+
27+
### [3] Messages
28+
29+
Inter-agent messages displayed as a group chat with date separators, sender grouping, and kind tags (`info`, `question`, `blocker`, `decision`, `answer`).
30+
31+
### [4] Chat
32+
33+
Agent stdout/stderr rendered as a conversation. Each agent gets a distinct color.
34+
35+
- `f` — cycle filter by agent (or show all)
36+
- `j/k` — scroll up/down
37+
- `g/G` — jump to top/bottom
38+
39+
## Header
40+
41+
- Live elapsed timer since team creation
42+
- Progress bar `[done/total tasks X%]`
43+
- `*` indicator on tabs with new activity
44+
45+
## Keyboard
46+
47+
| Key | Action |
48+
|-----|--------|
49+
| `1-4` | Switch tab |
50+
| `Tab` / `Arrow` | Navigate tabs |
51+
| `r` | Manual refresh |
52+
| `q` / `Ctrl+C` | Quit |
53+
54+
## Development
55+
56+
```bash
57+
pnpm install
58+
pnpm dev # tsx watch mode
59+
pnpm build # tsc
60+
```
61+
62+
## How it works
63+
64+
Reads JSON files from `.agent-teams/` (same files used by `agent-teams-lead` and `agent-teams-teammate`):
65+
66+
- `team.json` — team config and teammates
67+
- `tasks.json` — task list with status
68+
- `messages.json` — inter-agent messages
69+
- `artifacts.json` — published artifacts
70+
- `team.log` — stdout/stderr from teammate processes
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "@arvoretech/agent-teams-ui",
3+
"version": "0.1.0",
4+
"description": "TUI dashboard for agent teams — view teams, tasks, messages, and chats",
5+
"main": "dist/index.js",
6+
"type": "module",
7+
"bin": {
8+
"agent-teams-ui": "./dist/index.js"
9+
},
10+
"scripts": {
11+
"build": "tsc && echo '#!/usr/bin/env node' | cat - dist/index.js > dist/index.js.tmp && mv dist/index.js.tmp dist/index.js",
12+
"dev": "tsx src/index.tsx",
13+
"start": "node dist/index.js"
14+
},
15+
"dependencies": {
16+
"ink": "^6.8.0",
17+
"ink-spinner": "^5.0.0",
18+
"react": "^19.2.4",
19+
"chokidar": "^4.0.0"
20+
},
21+
"devDependencies": {
22+
"@types/node": "^20.10.0",
23+
"@types/react": "^19.2.14",
24+
"tsx": "^4.6.0",
25+
"typescript": "^5.3.0"
26+
},
27+
"author": "Arvore",
28+
"license": "MIT",
29+
"engines": {
30+
"node": ">=20.0.0"
31+
}
32+
}

0 commit comments

Comments
 (0)