Bundle
@dsh-external/dsh-side
DSH plugin: Codex-style side conversations (/side, /btw) in an ephemeral right-workspace panel with Side/Subagents/Goal sections and a ChatGPT-style pinned-notes board (置顶摘要小黑板), plus a best-effort sidebar collapse hotzone.
- Source
- invalidnaaaame
- stars
- 2 stars
- License
- MIT
- Updated
- Updated 16 hours ago
Readme
# dsh-side-workspace
**English** | [中文](README.zh.md)
> A DeepSeek Harness (DSH) plugin built in the spirit of the ChatGPT / Codex trio —
> **side conversations · integrated workspace · pinned notes (置顶摘要小黑板)**
- Repository: `dsh-side-workspace`
- Plugin ID / npm package: `@dsh-external/dsh-side` (unchanged)

> **Disclaimer**: an independent open-source project. Not affiliated with,
> endorsed by, or sponsored by OpenAI or DeepSeek. "ChatGPT" and "Codex" are
> trademarks of their respective owners, referenced only to describe feature
> parity.
Official naming reference (the basis for the repo name):
| This plugin's feature | Official name | Source |
| --- | --- | --- |
| `/side` conversations | Codex **side conversations** | [openai/codex#18190](https://github.com/openai/codex/pull/18190) |
| Integrated right workspace | ChatGPT **Workspace** | OpenAI workspace agent |
| Pinned summary board | ChatGPT **Notes** (pinned message notes) | [Pin a Note to Any Message](https://www.getaiworkspace.com/chatgpt-message-notes) |
---
## Features
### 1. Side conversations `/side` — Codex side conversations
`/side <question>` forks the current conversation's full history into a
**background side conversation** via a boundary context, while the main
conversation stays focused — the two never block each other; `/btw <question>`
is a one-shot side question (read-only). The child is an ordinary top-level
session (`ctx.agents.create`) that inherits the parent's agent preset, model,
and cwd, and goes through the deployment's own approval/sandbox policy — side
output **never flows back** into the main conversation's log.
- Default `ephemeral`: archived on creation, never enters the ordinary session
list; idle children past the TTL (default 60 min) are swept by the host;
running/approval-waiting children never expire; a new `/side` replaces idle
older `/side` children of the same parent.
- Cleanup is a small state machine (active → expiring → removed /
cleanup-failed with bounded backoff retries) — a failed dispose never loses
the management record.
- **Bare `/side` opens an EMPTY side conversation**: no question is injected,
the child waits in a `ready` state, and you type the question directly in
the panel composer; afterwards it returns to the normal run/complete flow.
- **Per-side model and reasoning-effort picker**: expand the "Model" row at
the top of the side detail page — models are grouped by provider; picking a
model reveals that model's reasoning-effort options. It goes through the
host's `session.selectModel`, persists on that child, and applies to its
next step.
### 2. Integrated right workspace — ChatGPT Workspace
The session-header toggle opens the shell's right column (the details column;
the main conversation is squeezed), hosting a grouped list + detail pages:
- **Side**: this session's side conversations (run status, activity line,
failed/cleanup-failed markers); clicking opens the detail page (full
transcript + follow-up composer; `/btw` is read-only).
- **Subagents**: the session's subagent catalog (reference-counted live feed;
the header badge shares the same source).
- **Goal**: the session goal (pause/resume/complete/clear via the goal RPC).
- Any entry opens its detail page **inside the same right column** — the left
sidebar and the main conversation never switch; Escape unwinds step by step
(detail → list → close panel).
- **New-status notifications**: when a side conversation settles (completed /
failed / cleanup-failed), a warning dot appears on the small panel button
above the main conversation (unseen count in the tooltip); opening the
panel marks them read. History already present when you open a session is a
silent baseline — never replayed as notifications.
### 3. Pinned summary board — ChatGPT Notes
A per-session board pinned to the top of the right workspace with its own pin
toggle: a slim chip when collapsed; when expanded it edits a small structured
document — title / objective / one-line status / next steps (checkable) /
decisions and any other non-empty section.
- **User edits win**: in this phase AI never writes to the board; every edit
goes through the **same** CAS domain function the server uses (optimistic
local apply + a serialized PATCH behind it), so the UI and the server can
never disagree about what a patch means.
- **Conflict handling**: a stale-revision response resyncs the board and parks
the edit with a banner + retry; patch ids make retries idempotent, and a
lost-but-applied response is detected by content comparison so nothing is
double-applied.
- Persisted at `$DSH_HOME/dsh-side-boards.json` (atomic write, tolerant of
corrupt files); route `/plugins/dsh-side/board`
(GET/PATCH/DELETE, same-origin, follows the deployment's ordinary access
rules).
- **A record only — never touches /goal automatically**: only the manual
button on the objective section syncs — `goals.create` when no goal exists,
or `goals.edit` on the projection's CAS revision when one does; non-empty
next steps ride along as a compact "下一步" list appended to the objective
text.
### 4. Sidebar collapse hotzone (best-effort)
A thin drag strip just inside the left sidebar's right edge: drag left past
the threshold to fully collapse the sidebar to its 56px rail, drag right from
the rail to expand. Everything delegates to the host's own `toggleSidebar()`;
geometry discovery is structural (never shadows the `sidebar` slot, the host
handle, or internal stores).
---
## Install (DSH web profile)
This is a **bundle-type plugin** (`dsh.bundle.patch` declared): mount it ONLY
through the profile's bundle list — never additionally insert it into the
profile's `cordis.patch.yml` (double-mounting fails loudly). **You must
restart `dsh web` yourself after changes** (this plugin ships no HMR):
```powershell
# 1. add the link dependency to the PROFILE package.json
"@dsh-external/dsh-side": "link:<absolute path to this repo>"
# ...and register "@dsh-external/dsh-side" in the same manifest's
# dsh.profile.bundles list
# 2. install in the profile directory
pnpm install --config.confirmModulesPurge=false
# 3. restart dsh web (stale plugin trees survive browser refresh)
```
## Config
| Key | Default | Description |
| --- | --- | --- |
| `retention` | `ephemeral` | `ephemeral` (archive + TTL expiry) / `persistent` (ordinary durable top-level sessions) |
| `idleTtlMinutes` | `60` | Idle minutes before an ephemeral side is swept (1–1440) |
## Usage
```
/side <question> Start a continuable side conversation (runs in the
background; the right panel reveals it when it settles)
/side Open an EMPTY side conversation and ask directly in the panel
/btw <question> One-shot side question (read-only, no follow-ups)
/side list List this session's side conversations
```
## Diagrams
**UI layout (three-column grid + right workspace + pinned board + notification dot)**

**Architecture & data flow (browser/host halves + shell services + lifecycle)**

> The hero screenshot lives at `assets/screenshot-workspace.png`; to replace
> it, capture a new full-window shot with one running `/side` and the board
> expanded, then overwrite that file.
## Architecture
```
src/
index.ts node half: create/archive/cleanup state machine + TTL
sweep + web routes (/plugins/dsh-side/list, /last,
/board) + command wiring
side.ts fork cut (host fork RPC contract), model inheritance,
message shapes
prompts.ts boundary prompt / persona / mode line (self-written
equivalent of the /side contract)
registry.ts parent → child registry + cleanup lifecycle + pure
TTL/retry decisions
board.ts pure board domain: sections, items, CAS patch engine,
locked-item protection, size caps
board-persistence.ts BoardStore: one atomic JSON file under DSH_HOME,
per-board save chains, patch idempotency
client/ browser half: right workspace (Side/Subagents/Goal +
board), transcript layer (paged history readers with
seed cut, streaming merge, FIFO cache), catalog
refcount, action gates, session-fenced pollers,
sidebar collapse hotzone
tests/ 165 unit tests (domain / persistence / client stores /
registry / transcript / …)
```
## Development
```powershell
pnpm install --config.confirmModulesPurge=false # see the link notes below
pnpm check # typecheck (node + client) && vitest && build
```
`pnpm build` emits `lib/index.js` (ESM node half) + `lib/index.d.ts` +
`lib/client.js` (CJS browser half wrapped in `window.__ModuleLoader__.load`;
react/cordis/dsh-client-* externalized, lucide imported per-icon, bundle
≈195kB / gzip 43kB).
**Dependency notes**: devDependencies are pinned to the official
`@deepseek-ai/*` packages **as published on npm** (`0.1.1-rc.2` line — the same
build the installed DSH ships; cordis follows its own 4.0.1 line and
schemastery 3.18.1). Never substitute the unrelated bare `cordis`/`schemastery`
npm packages. `pnpm-lock.yaml` is not committed (regenerate locally).
> Note: the npm registry may be slow from some networks; if tarball downloads
> fail, simply re-run `pnpm install` (each attempt makes progress).
## Known limitations (honest notes)
- **Single details track**: the shell has one right column — the board and the
workspace share it vertically (no "double squeeze"); a second column would
require host support.
- **No public width setter**: the shell's internal layout store now ships
`setSidebar`/`setDetails` (clamped 264–420 / 300–520), but `ctx.layout`
still exposes only `toggleSidebar/openDetails/closeDetails` — the collapse
hotzone stays toggle-based until those setters reach the public face.
- The side registry is **process-local**: after a `dsh web` restart sides no
longer appear in the panel (that is the default semantics — see the
retention contract below).
## Roadmap
- Image input for side conversations (`sessions.prompt` accepts inline image
parts; the composer UI is pending);
- Optionally inject board content into the side boundary context (let a side
work against the objective/next steps);
- AI proposals for the board (the `assistant-proposal` data model and locked
items are ready; the proposal entry point is pending);
- True free-drag sidebar (the host's internal layout store already has
`setSidebar`; waiting for exposure on `ctx.layout`);
- Notification extension: subagent/goal phase changes into the same unseen
queue;
- Persistent mode: attach children to the parent's workspace (gap found in
the fork spike, see `docs/fork-spike.md`).
## Retention contract (read before changing)
**Default is `ephemeral` (ChatGPT-style temporary side threads):** archived on
creation (never in the left session list), registry is process-local (forgotten
on restart), idle past TTL is swept by the host (running children are never
swept), and cleanup failures stay visible with bounded retries. Set
`retention: persistent` for the pre-0.2 durable top-level-session behavior.
## License
MIT. Third-party notices in
[licenses/THIRD-PARTY-NOTICES.md](licenses/THIRD-PARTY-NOTICES.md).
Install
dsh plugin --profile web add github:invalidnaaaame/dsh-side-workspace
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-external-dsh-side from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.