dsh-agentteams-dashboard
Verifieddsh-agentteams-dashboard · v0.2.0 · MIT · Web UI
DeepSeek Harness plugin: manage AgentTeams (agentscope-ai/AgentTeams) from the DSH Web/Desktop GUI
Install
dsh plugin add dsh-agentteams-dashboard Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-agentteams-dashboard
A DeepSeek Harness plugin that manages an AgentTeams deployment from the DSH Web/Desktop GUI.
It contributes three things:
- an AgentTeams panel in the DSH main area (总览 / Worker / 任务 / 聊天 / 团队 / Manager / Human / 知识库 / 审计 / 设置),
agentteams_*tools so the model can read and intervene in the same deployment,- tool cards that render those tool results in the conversation.
The functional reference is agentteams-dashboard, but this plugin is not a port of it and does not depend on it. That dashboard is a Next.js application that proxies the AgentTeams controller; this plugin binds directly to the controller's own REST API, so its contract is the controller's route table rather than another application's proxy layer.
Highlights (0.2.0)
- HELIOS 指挥舱总览:全屏 3D 数字孪生场景打底(three.js,vendor 内置零外部请求)—— 地球核心 + 团队轨道环(每个团队一条倾斜轨道,Worker = 团队卫星星球,Leader 带行星环)+ 双向流星数据流;HUD 面板悬浮其上(模块导航 / 核心遥测 / 集群健康环 / 实时事件流 / 事件密度图)。 拖拽旋转 · 滚轮缩放 · 点击星球锁定查看详情 · 闲置自动巡游 · 支持全屏。
- Worker 星球视图:每个 Worker 是一颗可定制的星球(星名别名 / 色相 / 岩石·气态·冰封 / 大小 / 行星环,存本地实时同步到 3D 场景);点击星球打开运维抽屉(唤醒/休眠/就绪/删除、 工具审批、内置工具开关、知识库浏览器)。
- 聊天富渲染:Markdown(含表格)+ 工具调用卡片(
🔧 **工具名**+ 参数块)+ 思考块解析 + 流式输出光标 + 媒体附件内联/下载 + 文件路径点击下载(任务产物走 artifact 通道)+ 实时审批请求渲染为可点击的审批卡片(批准/拒绝/取消直发/approval命令)。 - 机器可读 @ 提及:发送时自动把
@成员解析为m.mentions.user_ids(完整 Matrix ID)—— 群聊 @ 谁唤醒谁;私聊(Worker/Leader DM)无需 @,每条消息自动投递给对方。 - 知识库浏览器:目录导航 + 文件查看(Markdown 渲染 / HTML 沙箱 / 图片 / 文本)+ 在线编辑保存(If-Match 乐观锁)。
- 历史回填:进入房间自动拉取最近 200 条消息,与长轮询增量按 eventId 去重合并。
- 降级诚实:QwenPaw 运行时不支持的接口(如 0.2.x 的审批级别 API)明确提示而非渲染 必然失败的按钮;嵌入式 vs Kubernetes 模式差异渲染为中性说明。
Where the API contract comes from
Every host route maps one-for-one onto a controller route in
agentteams-controller/internal/server/http.go.
test/controller.test.mjs pins the exact method and path of each helper, so a drift away from the
controller's route table fails the suite instead of failing at runtime.
Two routes are local compositions and exist because the controller has no equivalent:
| Route | Why |
|---|---|
GET /session |
One call answering "is this plugin usable right now": controller reachability, credential source, Matrix login state, polling cadence. |
GET /overview |
Resource counts and cluster status. The reference dashboard composes this itself; the controller exposes no cluster-status endpoint. |
Deliberately not implemented, because they are not controller capabilities:
- the reference dashboard's own
/replay,/api/self-update, and/api/dashboard-build(application-level features); - MinIO/S3 object browsing (needs object-store credentials, a separate product surface);
- the Higress Console surface behind
/docker/*and model-alias binding; - the first-run setup wizard (configuration belongs in
cordis.patch.yml).
Credentials
The AgentTeams controller accepts a bearer token, and an L2 human's Matrix access token is also that bearer token. Handing either to a browser page would expose a long-lived, widely-scoped credential, and the controller is not a CORS peer of the DSH page.
So the plugin keeps every credential in the host process. The panel calls same-origin routes on
/api/agentteams-dashboard/*, the host attaches the credential, and the browser only ever learns
which source was used (env, file, login, or none) — never a value.
Resolution order for the controller token:
- the environment variable named by
controllerTokenEnv; - the file named by
controllerTokenFile, re-read on every request so an operator can rotate the file without restarting DSH; - otherwise requests are sent unauthenticated, which matches a controller running without an authenticator.
Configuration
There are two ways to configure the plugin, and they compose.
From the panel (first run)
Open the AgentTeams panel. On a fresh install it opens its Setup section automatically. Enter the controller URL and either a token or a username and password, save, and the plugin starts working immediately — no restart, no file editing.
Saved values live in $DSH_HOME/dsh-agentteams-dashboard.json (default ~/.dsh/), written with
owner-only permissions where the platform supports it. The document holds credentials, because an
AgentTeams token is itself the credential; treat the file accordingly. Test connection verifies a
token or password with a real request without saving it, so a wrong value fails before it is
stored.
The panel never reads a stored value back. It reports only which fields are set and which source won the precedence, so a browser page can never leak a credential.
From the profile
Deployment defaults belong in the profile's cordis.patch.yml, so a fresh machine is usable without
a manual step. A patch replaces a row's whole config, so restate every key you need:
- id: agentteams-dashboard
name: 'dsh-agentteams-dashboard'
config:
controllerUrl: 'http://127.0.0.1:8090'
controllerTokenEnv: 'AGENTTEAMS_AUTH_TOKEN'
controllerTokenFile: ''
matrixHomeserverUrl: 'http://127.0.0.1:18088'
matrixAccessTokenEnv: 'AGENTTEAMS_MATRIX_TOKEN'
matrixUserId: ''
matrixPassword: ''
requestTimeoutMs: 10000
resourcePollMs: 15000
infraPollMs: 30000
resourcePollMs / infraPollMs drive the resource panels' polling. Chat is
different: messages arrive over a Matrix long-poll /sync (25-second hang,
room-scoped filter, cursor-based), so new messages land near-instantly and idle
rooms cost nothing — borrowed from the chunked-sync idea in
AG-UI's event model rather than
another fixed timer. On error the loop drops its cursor, fully resyncs and backs
off 2s.
The controller listens on AGENTTEAMS_HTTP_ADDR, which defaults to :8090. The embedded AgentTeams
stack serves Element Web and Matrix on :18088; controllerUrl and matrixHomeserverUrl are
different origins and are configured separately.
Precedence
Highest priority first:
- a value saved from the panel;
- the Loader row's
config; - an ambient environment variable (
AGENTTEAMS_AUTH_TOKEN,AGENTTEAMS_CONTROLLER_TOKEN,AGENTTEAMS_MATRIX_TOKEN).
A credential saved in the panel therefore always wins over an ambient variable, so restarting DSH in a shell that happens to export an old token cannot silently change which identity is in use.
Authentication
| Surface | Accepted |
|---|---|
| Controller | bearer token, or HTTP Basic with a username and password |
| Matrix | an access token, or a user id and password (signed in on demand, shared across concurrent requests) |
An L2 human's Matrix access token is also accepted by the controller as a bearer token, so one value can serve both surfaces.
Deployment compatibility: embedded vs Kubernetes
AgentTeams runs in two shapes, and the difference is not cosmetic:
| embedded (Docker single host) | Kubernetes | |
|---|---|---|
| controller | :8090 on the host |
in-cluster Service |
worker-proxy endpoints (status, approval, tools, runtime-config, checkpoints, chats, workspace-files) |
proxied to the worker's app over the shared Docker network | 503 — there is no stable pod DNS to proxy |
So the panel treats a 503 from those endpoints as this deployment mode does not expose it, rendered as a neutral「仅嵌入式部署 / Kubernetes 模式下不可用」notice rather than a red error. Everything else — projects, tasks, chat, audit, teams — works identically in both modes, because they read the controller directly.
The controller URL is the only deployment-specific value: embedded uses http://127.0.0.1:8090,
Kubernetes uses whatever reaches the controller Service.
Interface
The panel is Chinese-labelled throughout. The shell and every section share the HELIOS
orbital-nexus theme: deep-space background (#05080F), fluorescent-cyan signals (#00e6b8) with
amber for warnings, hairline borders with corner brackets instead of rounded cards, a CSS grid with
scanline and sweeping light-beam overlays, and tabular monospace numerals (Rajdhani-style display
face). The overview fills the panel with the 3D scene as its spatial base and floats the HUD panels
over it; every other section renders full-width with the left module rail for navigation.
Long identifiers are aliased, with the full value kept in a title so it stays copyable:
| value | shown as |
|---|---|
@carol:ap-shanghai2.cloudstudio.club |
carol |
!a7f3c9e1d2b4:ap-shanghai2.cloudstudio.club |
!a7f3c9e1d2b4 |
shared/tasks/t1/output.pdf |
output.pdf (the link keeps the full path) |
| long model names / ids | truncated with … |
Response parsing
The controller wraps every list in a named field and returns flat items, which
agentteams-controller/internal/server/types.go declares:
WorkerListResponse{workers,total} TeamListResponse{teams,total}
HumanListResponse{humans,total} ManagerListResponse{managers,total}
AIRouteListResponse{routes,total} audit{events,cursor}
WorkerResponse{ name, runtime, state, phase, model, skills, team, … }
TeamResponse { name, teamName, phase, leaderName, workerNames, readyWorkers, totalWorkers, teamRoomID, … }
HumanResponse { name, phase, displayName, permissionLevel, matrixUserID, … }
There is no metadata/spec/status nesting. The client reads the wrapper field explicitly
(pickList(value, ['workers']), never "the first array") and resolves item fields through a documented
alias list, so a flat model works and a nested one would still parse:
runtime: ['runtime', 'backendRuntime', 'spec.runtime']
room: ['roomID', 'roomId', 'status.roomID', 'status.roomId']
matrixUser:['matrixUserID', 'matrixUserId', 'status.matrixUserID']
test/client-render.test.mjs pins this: it renders every section against the real controller shapes
and asserts the fields come out. A walker that never invokes React components cannot verify this —
h(Rows, props) only creates an element — which is why that test calls components itself.
Compatibility (runtime versions)
DSH ships as 0.x release candidates and the Desktop app auto-updates on a nightly feed, so the
runtime version moves without anyone choosing it. The plugin is verified against both the
0.1.0-rc.6 and 0.2.0-rc.2 generations and declares open peer ranges, so a runtime upgrade does
not trip DSH's compatibility gate:
"peerDependencies": {
"@deepseek-ai/cordis": "^4.0.1 || ^4.0.4",
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6 || ^0.2.0-rc.1",
"@deepseek-ai/dsh-system-prompt": "^0.1.0-rc.6 || ^0.2.0-rc.1"
}
Every contract this plugin depends on was checked against 0.2.0-rc.2 and is unchanged:
| Contract | Status in 0.2.0-rc.2 |
|---|---|
defineTool, ToolDefinition.output, execute |
unchanged |
ctx.webServer.register({ kind: 'prefix', path, handler }) |
unchanged |
ctx.tools.register, ctx.systemPrompt.section({ name, order, text }) |
unchanged (PromptSection only gained optional fields) |
main slot |
still kind: 'keyed', scope: 'root'; conversation is still the only occupied key |
tool.call.toolview slot |
still kind: 'keyed', scope: 'session', dispatched by wire tool name |
dsh.client.platform: 'web', exports["./client"], window.__ModuleLoader__.load |
unchanged |
A future break would be loud rather than silent: a bad client registration surfaces in the Web boot
audit, and a host-side mismatch fails the plugin's fiber, which DSH reports. npm test needs no
install, so it is the fastest way to re-check the plugin after a DSH upgrade.
One trap worth knowing: the unpacked packages under ~/.dsh/profiles/node_modules can lag the
Desktop app's own runtime, which lives inside app.asar. The compatibility gate compares against the
running runtime, so trust the version DSH reports rather than that directory — on this machine it
still reads 0.1.0-rc.6 while the app runs 0.2.0-rc.2.
Why @deepseek-ai/schemastery is a real dependency
The harness intercepts bare @deepseek-ai/dsh-* imports for a plugin, so dsh-tools and
dsh-system-prompt resolve without being installed. It does not intercept
@deepseek-ai/schemastery — the package Config needs in order to be an actual schema. Without it
the plugin fails to load at all, and DSH reports only fiberPhase: failed with no cause.
Two consequences worth remembering, because both cost debugging time:
Configmust be a schema, not an object literal. A plain object is accepted by the loader but reported asstatus: "unsupported", and the plugin never activates. Caught bytest/mount.test.mjs, which asserts the schema node shape.Installed by junction, a non-intercepted package must sit beside the plugin. A junction resolves to its real path, so Node's ancestor lookup never reaches the profile's
node_modules. Install locally:pnpm install --config.node-linker=hoisted # or: npm installThe Desktop app ships its own pnpm at
resources/runtime/primary-runtime/dependencies/pnpm/bin/pnpm.cjswhen the CLI is unavailable.
Install
Published package
dsh plugin --profile web add dsh-agentteams-dashboard
From a checkout
The package ships plain ESM with no build step and no runtime dependencies, so a checkout works as-is:
dsh plugin --profile web add /path/to/agentteam-desktop
Restart the target profile afterwards. A client-bundle change is picked up by a page refresh; a profile, manifest, or host-code change needs a restart.
Installing into the Desktop application
The Electron desktop app owns its desktop profile and carries its runtime inside app.asar, so
its dsh plugin command is not on PATH. Point the profile at the checkout instead:
- add
"dsh-agentteams-dashboard": "link:/path/to/agentteam-desktop"to the profile'sdependencies, and"dsh-agentteams-dashboard"todsh.profile.bundles; - make the package resolvable from the profile directory — a directory junction works without
elevation on Windows, while a symbolic link needs Developer Mode:
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-agentteams-dashboard" -Target "D:\path\to\agentteam-desktop" - restart the application.
Tools
Read: agentteams_status, agentteams_worker_list, agentteams_worker_get,
agentteams_worker_status, agentteams_worker_approval_get, agentteams_worker_tools,
agentteams_worker_skills, agentteams_project_list, agentteams_project_workflow,
agentteams_project_history, agentteams_task_get, agentteams_task_events,
agentteams_spawn_list, agentteams_spawn_messages, agentteams_team_list,
agentteams_team_get, agentteams_human_list, agentteams_human_get,
agentteams_manager_list, agentteams_manager_get, agentteams_mcp_server_list,
agentteams_skill_list, agentteams_audit_list, agentteams_gateway_routes,
agentteams_knowledge_list, agentteams_knowledge_read.
Write: agentteams_worker_create, agentteams_worker_update, agentteams_worker_delete,
agentteams_worker_wake, agentteams_worker_sleep, agentteams_worker_ready,
agentteams_worker_approval_set, agentteams_worker_tool_set, agentteams_team_create,
agentteams_team_delete, agentteams_human_create, agentteams_human_delete,
agentteams_manager_create, agentteams_manager_delete, agentteams_project_create,
agentteams_project_pause, agentteams_project_resume, agentteams_project_replan,
agentteams_project_complete, agentteams_task_cancel, agentteams_knowledge_write.
Every tool returns a JSON envelope in a text block:
{ "ok": true, "value": { } }
{ "ok": false, "error": { "code": "upstream-error", "message": "…", "controllerStatus": 409 } }
Failure semantics
Transport failures are translated; business statuses are not. The controller answers 403/404
to hide whether a resource exists, so rewriting that status would either leak existence or destroy
the caller's ability to distinguish "denied" from "absent".
error.code |
Host status | Meaning |
|---|---|---|
not-configured |
503 | No controller URL, or no credential where one is required. |
upstream-unreachable |
502 | The controller refused the connection. |
upstream-timeout |
502 | The controller exceeded requestTimeoutMs. |
upstream-error |
controller's status | A controller response; upstreamStatus carries its code. |
invalid-input |
400 / 404 / 405 | Rejected locally, before any upstream call. |
internal-error |
500 | A plugin fault. |
Locally enforced checks: replan graphs (duplicate ids, unknown dependencies, cycles), approval
levels, PATCH tool fields, checkpoint and workspace-file sub-resources, non-empty knowledge
writes, and a required cancel reason.
Development
npm test # node:test, no dependencies
npm run typecheck # tsc --checkJs, a local quality gate only
test/loader.mjs substitutes a minimal @deepseek-ai/dsh-tools stub when the real package is not
installed, so the suite runs in a bare checkout. Tests point at
agentteams-controller/internal/server/http.go for the routes they pin; when the controller adds a
route, add the helper and the case together.
The suite is organised by what it can actually prove:
| file | proves |
|---|---|
config.test.mjs |
credential precedence and file rotation; the posture never leaks a value |
settings.test.mjs |
atomic publish, sanitisation, corrupt-document tolerance |
controller.test.mjs |
every helper's method and path against the controller's route table, statuses passed through verbatim |
routes.test.mjs |
the panel API end to end over a real HTTP server, including the token boundary |
plugin.test.mjs, mount.test.mjs |
Cordis contract and that apply mounts with mocked services |
client-bundle.test.mjs |
the module-loader wire format and the three slots it registers |
client-render.test.mjs |
every section renders against the controller's real response shapes, and long ids are aliased with the full value kept in a title |
Mount tests inject an in-memory settings store deliberately: a test that reads the developer's real
~/.dsh can pass locally and assert against ambient state.
Model experience
The plugin registers one system-prompt section in the tool-guidance band (order: 150) with usage
rules, and exposes the tools above. It does not alter the agent loop, the session log, or any
tool's presentation beyond its own agentteams_* cards.
License
MIT