Bundle
dsh-agent-grid
定员八席 —— 把 DeepSeek Harness 的侧栏换成 2×4 的常驻席位看板:八个 agent 各自的实时动作、状态与待办一眼看全。Not affiliated with DeepSeek.
- Source
- yefengliu1
- stars
- 1 stars
- License
- MIT
- Updated
- Updated 4 days ago
Readme
# Eight Standing Seats · 定员八席
> **其静也专,其动也直** — "Still, it is single-minded; moving, it goes straight." (*I Ching*, Great Treatise I)
>
> A 2×4 standing-seat board that replaces the DeepSeek Harness sidebar: see what all eight agents are doing, and which one is waiting for you, at a glance.
[中文](README.zh.md) | **English**
[](https://github.com/yefengliu1/dsh-agent-grid/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/dsh-agent-grid)
[](#license)
[](https://github.com/topics/dsh-plugin)
[](#compatibility)
---

> A **real screenshot**, not a mock-up: each seat's avatar, status dot and current action (a green dot for the working seat, greyed-out for the two vacant ones). *Dynamic text and working directories on the cards are redacted.*
## What it solves
DSH's sidebar is a **session list** by default. Once you run one agent per session, what you actually want to know is not "which sessions exist" but **"what is each seat doing, and which one is waiting for me"** — a list cannot answer that, it only grows longer.
This plugin swaps it for a fixed 2×4 board:
> **Seat identity is permanent. Sessions are disposable payload.**
Each of the eight seats keeps a permanent identity (persona, accent colour, index); real sessions are just replaceable payload mounted on a seat. Adding, removing or reordering sessions never makes a cell change occupant — that is the precondition for "understandable at a glance".
Each seat has its own name and emblem (the Eight Immortals of Chinese lore: Han Zhongli, Lü Dongbin, Tieguai Li, Zhang Guolao, He Xiangu, Lan Caihe, Han Xiangzi, Cao Guojiu), with a hand-drawn pixel avatar (56×24 logical canvas at integer 2× scale). **The animation is a finite state machine, not a looping GIF** — the figure follows the facts: waiting on you → raises a flag, working → emblem fires, finished-but-unread → settles with an afterglow, otherwise → at rest.
## Three orthogonal states
Deliberately **not collapsed into one field**, because their rates of change and their owners differ completely:
| Dimension | Values | Rate | Owner |
| --- | --- | --- | --- |
| **Persona** | name / emblem / accent | almost never | you |
| **Activity** | working / resting / awaiting answer / awaiting approval | high | the agent |
| **Context** | empty / in use / near limit | medium | the system |
Key consequence: **"resting" ≠ "vacant"**. Resting means "has an identity, is ready, is not working"; vacant means "this slot has never been commissioned". A bot can perfectly well be "resting but 87% context-full" — the card must say so, because you need to clear it before assigning the next task.
## The seven labels
| Label | Fact it states | Source | Figure |
| --- | --- | --- | --- |
| **Vacant** | no session bound yet | no binding | at rest (grey) |
| **Standing by** | seated, session still empty | `blank` | at rest |
| **Working** | currently running | `running` | emblem fires |
| **To review** | a turn ended and **you have not looked** | unread notice | settles + afterglow |
| **Resting** | idle, nothing unread | otherwise | at rest |
| **Awaiting answer** | it asked you a question and is stuck | projection `attention=question` | raises flag |
| **Awaiting approval** | an un-reviewed approval is pending | projection `attention=approval` | raises flag |
Status dots: working **green** · to-review **gold** `#C9A227`  · awaiting **cinnabar** `#B5453C`  · vacant / standing by / resting **grey**
Priority: the two awaiting states outrank everything except vacant; the rest order as working → to-review → standing by → resting.
> **Why not call it "completed"**: `completed` is not a lifecycle state, it is an **unread notice** — the host lights it on the true→false edge of `running`, and it **does not judge success** (a crashed or interrupted turn lights it too).
> Calling it "completed" is not merely imprecise, it can be **simply wrong**. It really only says two things: a turn ended, and you have not looked.
>
> Also: the seat you are currently viewing does **not** light the notice when it finishes — so "to review" always appears on a seat you were *not* watching. That is exactly what this board is for.
## The card shows the process
Not just a status dot. A host-side `seat-activity` projection supplies each seat's latest slice: **tool call → thought (reasoning) → ↳ (tool result) → speech (answer)**, plus how long since the seat last moved.
So when all eight cards are animating, you can tell **what each one is doing** — not eight identical spinners.
> Vertical space is a hard constraint: eight full cards must fit one screen with no scrollbar. Measured at 164px per full card (4 rows + gaps = 697px against ~750px usable), narrow windows drop secondary rows. Better to hide a row than to show a scrollbar.
## The main pane stays stock
The board **does not rewrite the conversation area**. The main pane remains DSH's stock Conversation; the plugin only injects a **seat badge** into the conversation header, so you always know which seat you are talking to.
(An earlier version replaced the main pane with a custom detail page — that threw away the stock conversation. Overturned; it was reinventing the wheel.)
## Escape hatch
**Settings → General → Sidebar**: `[ Seats │ Default ]`
Switching to "Default" works by **unregistering**, not by hiding a component — once our slot registration is `dispose()`d, the built-in `WorkspaceBrowser` returns to the render position automatically. The choice survives page refreshes and host restarts; deployments without a settings service degrade to a local-only switch with an inline notice.
## Install
```bash
dsh plugin --profile web add dsh-agent-grid
```
Then refresh. To go back to the stock sidebar: **Settings → General → Sidebar → Default**.
## Intrusiveness
This plugin **replaces the host's `sidebar.workspaces` slot** (shadowing the built-in implementation with `priority: -1`). It patches no host source, but it does change how the sidebar presents itself — the switch above exists precisely as the way back.
## Compatibility
- Requires **DSH ≥ 0.2.0-rc.2** (`@deepseek-ai/dsh`); verified on 0.2.0-rc.2 — on 0.1.5-rc.2 install `dsh-agent-grid@0.1.3`
- Consumes only host design tokens and hard-codes no colours — so **any DSH theme can drive it**
- Not affiliated with DeepSeek
### Porting notes (0.1.5-rc.2 → 0.2.0-rc.2)
Four ways a DSH client plugin dies **silently** on 0.2.0-rc.2, one cause each. Worth reading before porting any client plugin:
| What you see | Cause | Fix |
| --- | --- | --- |
| The row is quietly disabled; only a stderr line says so | The compatibility gate requires every `@deepseek-ai/dsh*` peer range to satisfy the running DSH version | Widen the peer ranges |
| Host half runs, the UI shows nothing | `dsh-client-modules` matches the row's specifier against the package's own `name`; on a mismatch the client half never registers | Mount by the real package name |
| `duplicate factory registration` + `loaded without registering` | The built `lib/client.js` hard-codes the plugin id inside `__ModuleLoader__.load({ id })` | Rebuild the bundle after any rename |
| `pending (waiting for service: …)` at boot | `settingsScope` was removed; `sessions.open()` was removed too | `ctx.configForms.get(ns)` / `uiWorkspace.openSession(id)` |
## With a theme
Companion theme **[dsh-theme-songgrid](https://github.com/yefengliu1/dsh-theme-songgrid)** (Songgrid · rice paper / night ink) was tuned for it: warm paper against the board's grey-scale cards, Ru celadon carrying the interactive states. The two are fully independent — either works alone.
## License
MIT
Install
dsh plugin --profile web add github:yefengliu1/dsh-agent-grid
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-agent-grid from the hub
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.