Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6769b46
Turn the web dashboard into a control center
claude Oct 22, 2026
b93921f
Merge pull request #1 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
38e64e5
Keep session names in any script addressable
claude Oct 22, 2026
2325506
Redesign the web dashboard around a team chat, explorer and preview
claude Oct 22, 2026
dbc6d95
Merge pull request #2 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
0f9ef3d
Show each agent's steps, approve them, switch models and cap usage
claude Oct 22, 2026
09eb4e1
Add a Chinese/English switch, agent avatars and a page background
claude Oct 22, 2026
579392b
Merge pull request #3 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
21ac08b
Keep the dashboard working when an older server serves it
claude Oct 22, 2026
55c4dd4
Let CI be run by hand from the Actions tab
claude Oct 22, 2026
8503852
Show plan limits, let the person interrupt and question agents, strea…
claude Oct 22, 2026
8466d3d
Detect a CLI through its command override
claude Oct 22, 2026
bcffe70
Merge pull request #4 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
58bb2bd
Restart an outdated broker without killing agents; show who has not a…
claude Oct 22, 2026
866016b
Count connected slots before treating the broker as idle
claude Oct 22, 2026
25ca218
Keep finished agents: only delete orphan slots in the background
claude Oct 22, 2026
1547437
Merge pull request #5 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
edefb71
Add "Other" agents on any OpenAI-compatible API (DeepSeek, Qwen, Kimi…)
claude Oct 22, 2026
9c92c74
Say that an Antigravity login does not count for Gemini's quota, and …
claude Oct 22, 2026
b5faf01
API agents: keep writes in the project, register as "api", keep a model
claude Oct 22, 2026
a6e7226
Merge pull request #6 from ZackLiuzeyu/claude/adoring-goodall-vg29z0
ZackLiuzeyu Oct 22, 2026
590c7d8
feat: add daemon mode (-d), antigravity cli support, and proxy integr…
ZackLiuzeyu Oct 22, 2026
99a875e
test: increase waitFor timeout in api-agent test to prevent CI flakes
ZackLiuzeyu Oct 22, 2026
2244a66
fix(ci): skip local proxy injection in CI and set loopback no_proxy
ZackLiuzeyu Oct 22, 2026
5972c0e
feat: detect and display Antigravity tier for Gemini subscription
ZackLiuzeyu Oct 22, 2026
91ccab8
feat(ui): update Codex/ChatGPT avatar icon with official logo
ZackLiuzeyu Oct 22, 2026
781439f
fix(dashboard): enable permanent deletion for released agent cards an…
ZackLiuzeyu Oct 22, 2026
e4e9673
feat(dashboard): auto-save draft to localStorage for new team modal a…
ZackLiuzeyu Oct 22, 2026
1417b6b
refactor(dashboard): prompt to save draft on close and only show rest…
ZackLiuzeyu Oct 22, 2026
32d9358
fix(orchestrator): keep approved agents online in standby and auto-en…
ZackLiuzeyu Oct 22, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ on:
pull_request:
push:
branches: [main]
# Run it by hand from the Actions tab (Run workflow) on any branch
workflow_dispatch:

permissions:
contents: read
Expand Down
11 changes: 10 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ Peer discovery and messaging MCP channel for Claude Code instances.
- `adapters/protocol-text.ts` — Team protocol: short form for MCP `instructions` (Claude Code caps them at 4096 chars, Codex reads 512), full form for orchestrator prompts.
- `orchestrator/claude-driver.ts` — Long-lived `claude -p --input-format stream-json` session; teammate messages are written to stdin and read mid-turn. `system/init` only arrives after the first message; closing stdin ends the session.
- `orchestrator/codex-driver.ts` — `codex app-server` JSON-RPC client (shapes verified against the 0.156.1 schema).
- `orchestrator/api-driver.ts` — Agent type `api` ("Other"): any OpenAI-compatible chat API, configured in `shared/providers.ts` (`~/.multiagents/providers.json`, 0600, keys never sent to the page; slots keep `provider_id`). In-process loop: spawns the team-tools MCP server in driver mode and talks MCP to it, adds local tools with Claude's names (Read/Write/Edit/LS/Glob/Grep/Bash/WebFetch, confined to the project), streams completions (`reasoning_content` → thinking) and emits Claude stream-json events, so it is a `ClaudeSession` and runs on every Claude path (`kind: "claude"` in the driver registry: delivery, approvals by permission mode, activity, live view, token and cost accounting, Q&A, interrupts). Tests use a scripted fake server (`tests/api-agent.test.ts`).
- `orchestrator/delivery.ts` — Hands unacked messages to a driver (`retain` poll) and acks them on confirmed hand-off.
- `orchestrator/supervision.ts` — Driver registry (one driver per slot), non-overlapping loop ticks, release and watchdog verdicts; pure enough to unit-test.
- `orchestrator/runtime.ts` — The orchestrator itself: launched processes and drivers, background supervision loops, and the tool implementations (`ORCHESTRATOR_TOOLS`, `callOrchestratorTool`). Hosted by `orchestrator-server.ts` (MCP stdio) and by `dashboard/server.ts`; each host supervises only the agents it launched.
- `dashboard/control.ts` — Web control API (`POST /api/<action>`). Every mutation names its `session_id` and checks the slot belongs to it; lifecycle actions call `callOrchestratorTool`, record edits go to the broker. Human actions use sender id `human`. A session with `config.human_gate` is not auto-ended while agents sit in `approved`.
- `dashboard/files.ts` — Read-only `GET /api/fs/{list,raw,resolve,check,docx}` over the session's `project_dir`. Every path goes through `resolveInRoot` (realpath, so symlinks cannot escape); HTML/SVG are served with a sandbox CSP. `.docx` is rendered server-side (own zip reader, no dependency). UI: `dashboard/index.html` + `i18n.js` + `app.js` + `styles.css`, plain JS, themes via `html[data-theme]`. `i18n.js` loads first: `tr(english, vars)` looks the English text up in `ZH` (then `RULES_ZH` for server-generated text); static HTML is translated in place at boot. A new UI string needs a `ZH` entry (`tests/web-i18n.test.ts` checks). Avatars and the background are per-browser localStorage (`ma-avatars`, `ma-appearance`).
- `orchestrator/activity.ts` — Turns Claude stream-json, Codex app-server notifications and Gemini stream-json into one activity timeline (broker `/activity/*`), and waits on tool approvals stored in the broker (`/approvals/*`, polled, so any host's dashboard can answer). Per-slot settings live in the slot snapshot (`model`, `effort`, `approval_mode`, `token_limit`); `runtime.ts` reconciles running drivers with them each delivery tick (Claude: `set_model` / `set_permission_mode` control requests; Codex: `driver.settings` for the next turn), interrupts the turn of a paused slot, and enforces session `config.limits` (`limitVerdict`). Claude runs with `--permission-prompt-tool stdio`; its `can_use_tool` control requests go to `onPermissionRequest` (queued until a handler is set, denied after 30s without one).
- `orchestrator/live.ts` — `LiveTracker`: the block an agent is writing right now (Claude `stream_event` deltas from `--include-partial-messages`, Codex `item/*/delta`, Gemini message deltas) plus its phase, published to the broker's in-memory `/activity/live/*` (busy entries not refreshed for 60s expire). The chat polls `/api/live` for the typing indicator.
- `orchestrator/plan-usage.ts` — Subscription windows without a model turn: Claude `get_usage` control request on a throwaway `claude -p` (no MCP, hooks off, cwd tmp), Codex `account/rateLimits/read` on a throwaway app-server, Gemini Code Assist `retrieveUserQuota` with `~/.gemini/oauth_creds.json` (never refreshed here). Cached 2 min; `GET /api/plan-usage`.
- Talking to agents: `human` is a valid message target (agents see `HUMAN_SENDER_LABEL` with the id to reply to). `messages/send` with `interrupt` sets the slot's `interrupt_at`; the host running the driver stops its turn. Session `config.qa_mode` (Q&A mode, `session/qa`): the broker hands agents only `human` / orchestrator messages (`qaFilter`), `runtime.ts` interrupts running turns, runs drivers in ask mode and only lets read-only tools through, and skips nudges, respawns and auto-end.
- Broker updates: the broker is a daemon that keeps old code across updates. `/health` lists `BROKER_FEATURES` (shared/constants.ts — add one with each endpoint or behavior the dashboard needs); the dashboard shows a restart banner when some are missing and restarts an idle one at startup. `restartBroker` leaves agents running: SIGUSR2 (current builds exit without the SIGTERM handler that kills every registered peer) or SIGKILL for older builds (WAL keeps committed data).
- `orchestrator/supervision.ts` — Driver registry (one driver per slot), non-overlapping loop ticks, release, watchdog and dead-slot verdicts; pure enough to unit-test. The background cleanup deletes only orphan slots (`deadSlotVerdict`: connected once, never worked or messaged, session not on hold); finished agents stay listed as offline until a person removes them.
- `orchestrator/monitor.ts` — Slot summaries and token accounting. Claude's `result.usage` is per turn (added as is); Codex's `thread/tokenUsage/updated.total` is cumulative (diffed).
- `shared/types.ts` — Shared TypeScript types for broker API.
- `shared/summarize.ts` — Git-based summary of what an agent is working on (no API calls).
Expand Down
69 changes: 58 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,24 +211,60 @@ Session monitoring stats and enforced limits:
| Messages Per Agent | Monitor | Per agent | Observe |
| Longest Idle | Monitor | Per agent | Observe |

Adjustable from the TUI dashboard (+/- keys) or via the `adjust_guardrail` tool; the web dashboard shows them read-only (its `/api/guardrail` endpoint exists but has no UI yet).
Adjustable from the TUI dashboard (+/- keys), the web dashboard's Stats tab, or the `adjust_guardrail` tool.

## Web Dashboard

```bash
multiagents web [session-id]
```

Real-time web dashboard on `localhost:7900` with live WebSocket updates. Auto-opens in browser when a team is created via the orchestrator. 6-tab interface:
Web control center on `localhost:7900` with live WebSocket updates. Auto-opens in browser when a team is created via the orchestrator, and starts the broker itself when it is not running. Besides watching, you can run a team from the browser. The layout has three panes, and both side panes can be collapsed (`Ctrl+B` / `Ctrl+J`) and resized:

- **Agents**: agent cards with connection status, task state, role, summaries, token usage
- **Messages**: live message feed with type badges and sender names
- **Plan**: task progress with completion bar and assignee labels
- **Knowledge**: shared knowledge entries with categories and provenance
- **Files**: file locks and ownership zones
- **Stats**: session metrics (connected agents, working, tokens) and guardrail bars
- **Left: sessions and an explorer** of the session's project directory (read-only, refreshed while agents work; locked files show a lock).
- **Center: the team chat.** Your messages sit on the right, the agents' on the left with avatar, role and message type (feedback, task complete, approval…). System notices are folded into small centered lines. Send to one agent or the whole team (Enter sends, Shift+Enter starts a new line, IME-safe). File names that agents mention become links once the file is found in the project.
- **Right: file preview** (Markdown, code with line numbers, images, PDF, Word `.docx`, CSV, HTML in a sandbox, audio/video). It follows the open file as agents edit it. The **Team** tab lists every agent with quick actions.

Dark theme, responsive layout, keyboard shortcuts (`1-6` to switch tabs).
Light, dark, or follow the system: pick it from the theme button in the top bar. The language button switches between English and 中文 (Chinese is the default for a Chinese-language browser). Text the agents write stays as they wrote it.

**Making it yours.** Agents show their CLI's icon (Claude, Gemini; Codex gets a neutral terminal glyph because OpenAI's logo isn't redistributable). Click an avatar on an agent card or in its settings to use initials, a color or your own image instead. The **Appearance** button in the top bar sets a background image under the whole page, how strongly it shows, how see-through the panels are, and your own avatar. These choices are kept in the browser (localStorage), not on the server.

**Watching and approving each step.** The **Activity** tab is a live timeline of what every agent does: its thinking, the files it reads, each edit as a diff, every command with its output, and the cost of each turn. Claude and Codex report every step; Gemini reports its tool calls. Each agent has an approval mode (**Settings** on its card, or when you add it):

| Mode | Claude | Codex |
|---|---|---|
| Fully autonomous (default) | `bypassPermissions` | approval policy `never` |
| Auto review — the CLI's reviewer passes safe steps, risky ones come to you | `auto` | `on-request` |
| Auto-approve edits — commands and other tools ask | `acceptEdits` | `untrusted` |
| Ask me every step | `default` | `untrusted` |

A step that needs you shows up as a card with the exact command or diff: **Allow**, **Always allow** (this tool, or this shell program, for the rest of the agent's session), or **Deny** with a note the agent reads. Team-protocol tools (`check_messages`, `send_message`…) never ask. Gemini runs unattended (yolo), so it is always fully autonomous.

**Models, usage and limit locks.** The same Settings dialog switches an agent's model (an alias such as `opus`/`sonnet`, or a full name) and its effort. Claude switches live via `set_model`; Codex switches at its next turn; Gemini and Claude's effort switch at the agent's next start. The **Usage** tab shows tokens (input, output, cache reads) and Claude's reported cost per agent. There you can set limits per agent or for the whole session, in tokens or USD. An agent that reaches a limit is paused and its running turn is interrupted; raise the limit, then resume it.

**Plan limits (5-hour / weekly).** The **Usage** tab starts with each CLI's subscription quota, read the way the CLIs' own `/usage` screens read it and without starting a model turn; the fullest window of each also shows in the top bar. Claude: the claude.ai plan's 5-hour window and weekly windows (all models, Opus, Sonnet, per-model ones), via Claude Code's `get_usage` control request. Codex: the ChatGPT plan's 5-hour (primary) and weekly (secondary) windows, via `account/rateLimits/read`. Gemini has no 5-hour or weekly windows; with a Google login it shows the daily per-model quota (Code Assist `retrieveUserQuota`, using the token Gemini CLI stored, only while it is valid). API-key logins have no plan windows and say so. Answers are cached for two minutes; **Refresh** asks again.

**Other models (any OpenAI-compatible API).** Besides Claude, Codex and Gemini, an agent can run on DeepSeek, Qwen, Kimi, GLM, OpenRouter, a local Ollama, the OpenAI API, or anything else with an OpenAI-compatible chat API. Add the service under **More ▾ → API providers…** (or **Manage providers…** in Add Agent / New Team): a name, the base URL (the part before `/chat/completions`), the API key and a default model — presets fill in the URL of the well-known ones, **Fetch models** reads the model list, an optional price per million tokens turns on cost and cost limits, and extra request fields (JSON) pass provider options such as Qwen's built-in web search. Keys stay in `~/.multiagents/providers.json` (mode 0600) and never reach the page; `$NAME` reads a key from an environment variable. Then pick **Other (OpenAI-compatible API)** as the agent type. Such an agent works like the others: it talks to the team and to you with the same team tools, reads, edits and searches the project's files and runs commands (inside the project directory), and fetches web pages; its thinking streams into the chat, its steps fill the Activity timeline, approval modes, Q&A mode, interrupting, live model switches, token and cost accounting and limit locks all apply. It is driven in-process by the dashboard, so no CLI is installed; plan limits (5-hour / weekly) do not exist for API keys.

**Talking to agents while they work.** Pick one agent under **To** and ask it anything: agents reply to you with `send_message(to_id="human")` and the answer lands in the chat. Tick **Interrupt** to stop what that agent is doing first, so it answers you right away. **Q&A mode** (session toolbar) puts the whole team on hold: every running turn stops, agents are told to wait and only answer you, edits and commands are refused (reading files stays allowed, so they can check before answering), and teammates' messages wait. Ask one agent, then another; **End Q&A** resumes everyone where they stopped, with the messages that waited.

**Seeing an agent think.** While an agent works, the chat shows it under the last message: typing dots, what it is doing (thinking, writing a reply, running `$ bun test`…) and how long, and its thinking as it forms, token by token (Claude with `--include-partial-messages`, Codex reasoning summaries, Gemini replies). **Collapse** keeps it to a few lines.

The tabs in the center hold everything else:

- **Sessions** (sidebar): New Team, empty session, open, pause/resume, relaunch disconnected agents, export JSON, archive, end, delete (typed confirmation)
- **Agents**: message, pause/resume, change role, rename, send back for revision, release, remove; Add Agent; Release all approved / Release all
- **Plan**: add, edit, reassign, change status (pending / in progress / blocked / done), delete
- **Knowledge**: add, edit, delete entries by category
- **Locks**: locks with holder and expiry, force unlock, reserve a file for an agent, assign ownership zones
- **Activity**: the step-by-step timeline and pending approvals (see above)
- **Usage**: plan limits, tokens and cost per agent, limit locks, and guardrail steppers (changes logged as "Adjusted via web dashboard")

**Human gate.** A team started from the browser can wait for you once every agent is approved: instead of ending itself, the session shows *Waiting for human approval* with **Approve & Release** and **Send back for revision** (actionable feedback to a worker, which restarts the review loop). Toggle it per session with the `human gate` chip; MCP's `create_team` takes `human_gate: true`.

Teams and agents launched from the browser are supervised by the dashboard process (the same orchestrator runtime the MCP server runs), so keep `multiagents web` running while they work; stopping it stops them. Agents another orchestrator launched stay under that orchestrator. See [docs/web-control-center.md](docs/web-control-center.md) for the API.

On narrow screens the side panes become drawers. Keyboard shortcuts: `1-6` switch tabs.

## TUI Dashboard

Expand Down Expand Up @@ -278,6 +314,7 @@ multiagents/
│ ├── broker-client.ts HTTP client for broker API
│ ├── broker-url.ts The one place MULTIAGENTS_PORT is resolved
│ ├── constants.ts Ports, intervals, thresholds
│ ├── session-ops.ts Session create/export shared by the CLI and the web dashboard
│ ├── summarize.ts Git-based activity summary (no API calls)
│ └── utils.ts Shared utilities
├── adapters/
Expand All @@ -289,9 +326,14 @@ multiagents/
│ ├── gemini-adapter.ts Gemini adapter (tool-result delivery + file inbox)
│ └── role-practices.ts Role-specific best practices injection
├── orchestrator/
│ ├── orchestrator-server.ts Orchestrator MCP server (team management, driver delivery loop)
│ ├── orchestrator-server.ts Orchestrator MCP server (stdio host of runtime.ts)
│ ├── runtime.ts Orchestrator runtime: tools, agent processes, supervision loops (shared with the web dashboard)
│ ├── activity.ts Activity timeline from each CLI's stream; tool approvals through the broker
│ ├── live.ts What each agent is doing right now (streamed thinking, typing indicator)
│ ├── plan-usage.ts Subscription plan limits of the CLIs (5-hour / weekly / daily)
│ ├── claude-driver.ts ClaudeDriver: long-lived `claude -p --input-format stream-json` session
│ ├── codex-driver.ts CodexDriver: persistent codex app-server via JSON-RPC (steer/interrupt)
│ ├── api-driver.ts ApiDriver: agents on any OpenAI-compatible API (team + local tools, Claude-style events)
│ ├── delivery.ts Hands unacked messages to drivers, acks on confirmed hand-off
│ ├── launcher.ts Agent spawning (drivers, MCP configs, prompts)
│ ├── monitor.ts Process/driver monitoring (event parsing, token tracking)
Expand All @@ -311,7 +353,12 @@ multiagents/
│ └── update.ts `multiagents update`
├── dashboard/
│ ├── server.ts Web dashboard (Bun.serve + WebSocket, localhost:7900)
│ └── index.html Web dashboard UI
│ ├── control.ts Web control API (POST /api/*)
│ ├── files.ts Read-only project files for the explorer and preview (GET /api/fs/*)
│ ├── index.html Web dashboard layout
│ ├── app.js Web dashboard UI logic
│ ├── i18n.js Web dashboard translations (English / 中文)
│ └── styles.css Web dashboard themes and layout
└── scripts/
├── version.ts Version bump + release commit/tag
├── postinstall.ts Configures MCP servers after install
Expand Down
6 changes: 4 additions & 2 deletions adapters/base-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
BROKER_STARTUP_POLL_MS,
BROKER_STARTUP_MAX_ATTEMPTS,
SESSION_FILE,
HUMAN_SENDER,
HUMAN_SENDER_LABEL,
} from "../shared/constants.ts";
import { resolveBrokerPort, brokerUrl } from "../shared/broker-url.ts";
import {
Expand Down Expand Up @@ -77,7 +79,7 @@ const TOOLS = [
},
agent_type: {
type: "string" as const,
enum: ["claude", "codex", "gemini", "custom", "all"],
enum: ["claude", "codex", "gemini", "api", "custom", "all"],
description: "Optional filter by agent type. Defaults to all.",
},
},
Expand Down Expand Up @@ -1752,7 +1754,7 @@ export abstract class BaseAdapter {
const role = msg.from_role ? ` (${msg.from_role})` : "";
const isOrchestrator = msg.from_id === "orchestrator" || msg.from_id === "__orchestrator__";
const idTag = msg.from_display_name && !isOrchestrator ? ` [id ${msg.from_id}]` : "";
const sender = `${name}${role}${idTag}`;
const sender = msg.from_id === HUMAN_SENDER ? HUMAN_SENDER_LABEL : `${name}${role}${idTag}`;

switch (msg.msg_type) {
case "chat":
Expand Down
Loading