dsh-multi-chat 1.0.2 β†’ 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,268 +1,287 @@
1
- # πŸ’¬ dsh-multi-chat β€” Multi-chat, one screen
2
-
3
- **English** | [δΈ­ζ–‡](README.zh.md)
4
-
5
- <p align="center">
6
- <a href="https://www.npmjs.com/package/dsh-multi-chat"><img src="https://img.shields.io/npm/v/dsh-multi-chat" alt="npm version"></a>
7
- <a href="https://www.npmjs.com/package/dsh-multi-chat"><img src="https://img.shields.io/npm/dm/dsh-multi-chat" alt="npm downloads"></a>
8
- <a href="https://github.com/daetz-coder/dsh-multi-chat/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license"></a>
9
- <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/dsh--plugin-community-brightgreen" alt="dsh-plugin"></a>
10
- </p>
11
-
12
- <p align="center">
13
- <a href="https://awesome-dsh-plugin.com/p/daetz-coder/dsh-multi-chat/"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome-dsh-plugin"></a>
14
- <a href="https://dshfind.com/plugins/daetz-coder/dsh-multi-chat"><img src="https://dshfind.com/api/badge/daetz-coder/dsh-multi-chat" alt="dshfind"></a>
15
- <a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/listed%20on-awesome--dsh--plugin-4d6bfe" alt="listed on awesome-dsh-plugin"></a>
16
- </p>
17
-
18
- > **Run N conversations in DeepSeek Harness at once, watch every Agent's live progress side-by-side, and check in from your phone or tablet.** One browser tab goes from "one conversation at a time" to "a panoramic multi-conversation cockpit."
19
-
20
- Install a **multi-window wall** into the official [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) Web UI: a grid that shows N running DSH conversation instances simultaneously (each instance runs its own task), so every Agent's live progress, chat, and output are **visible at a glance** β€” no more hopping between endless tabs and windows.
21
-
22
- ## ✨ What it does
23
-
24
- | Capability | Description |
25
- |------------|-------------|
26
- | πŸ“Ί **Multi-window** | One-click entry from the sidebar; the chat area becomes a window grid showing every task side-by-side, one pane per port |
27
- | πŸ” **Auto-discovery** | Scans a port range to auto-find running DSH instances; manual management is also supported |
28
- | βž• **One-click new window** | Launch a brand-new DSH instance right inside the wall to grow your conversation matrix |
29
- | πŸ“± **Phone access** | The "Phone access" button starts a **built-in authenticated LAN gateway** β€” open the URL on your phone, enter the token, and watch progress |
30
- | πŸ›‘ **Window controls** | Maximize, refresh, open in a new tab, stop an instance, and switch column count (auto/1/2/3/4/6) |
31
-
32
- > **Multi-chat = multi-port.** Start N `dsh web --port <n>` instances (each running one conversation/task), open the wall from any of them, and you see all of them side-by-side.
33
-
34
- ## πŸ“Έ Screenshots
35
-
36
- **πŸ–₯️ Windows Β· Two chats side-by-side** β€” two running DSH instances laid out together, each pane a full official conversation UI with live online status dots and per-window controls (maximize / refresh / new tab / remove):
37
-
38
- ![Windows dual chat: two DSH instances side-by-side](assets/01-windows-dual-chat.png)
39
-
40
- **πŸ“± iPad Β· Two chats on mobile** β€” on the same LAN, open the token-authenticated gateway URL on an iPad to watch two Agents' live progress on one tablet screen:
41
-
42
- ![iPad dual chat: two DSH instances on tablet](assets/02-ipad-dual-chat.png)
43
-
44
- **πŸ–₯️ Windows Β· Three-chat panorama** β€” a 3-column grid of three running instances, all Agents on one screen, upgrading you from "one conversation at a time" to "a panoramic multi-conversation cockpit":
45
-
46
- ![Windows triple chat: 3-column grid of three DSH instances](assets/03-windows-triple-chat.png)
47
-
48
- ## πŸš€ 30-second quick start
49
-
50
- ```bash
51
- # 1. Install β€” ONE command straight from the npm registry (no CLI to download):
52
- dsh plugin --profile web add dsh-multi-chat
53
-
54
- # …or via the plugin's own npx CLI (packs a tarball and installs it):
55
- npx dsh-multi-chat install
56
-
57
- # 2. Start a few instances
58
- npx dsh-multi-chat start --ports 3080,3081,3082
59
-
60
- # 3. (Re)start any instance and click "Multi-window" in the sidebar footer β†’ done πŸŽ‰
61
- ```
62
-
63
- ## Why this approach
64
-
65
- - **No official logic is touched**: the plugin only registers two **additive list slots** (`conversation.view` ring entry, `sidebar.footer.action` sidebar shortcut) and five read-only JSON routes (`/multi/api/ports`, `/multi/api/status`, `/multi/api/stop`, `/multi/api/create`, `/multi/api/link`). No existing slot is replaced, no line is rewritten, and no core session/agent/tool logic is touched.
66
- - **The UI is the official UI**: the wall is a view in the official view ring rendered inside the chat panel (not a popup). Theme, type scale, icons, and controls all use the official `--dsw-*` tokens and official primitives (Button/Input/Menu/StateDot).
67
- - **Recursion guard**: the wall never embeds its own port; embedded pages carry a `?multi-wall=embed` flag and register no wall UI, preventing infinite "wall-in-wall" recursion.
68
- - **Minimal footprint**: one declarative client plugin package β€” it ships its own `dsh.bundle.patch` + `cordis.patch.yml`, so DSH mounts it as a bundle layer automatically.
69
-
70
- ## Directory layout
71
-
72
- ```
73
- dsh-multi-chat/ # single-package structure
74
- lib/ # built artifacts (lib/index.js + lib/client.js + types)
75
- src/ # source (node half + browser half)
76
- bin/dsh-multi-chat.mjs # cross-platform npx CLI (install/start/stop/gateway)
77
- scripts/
78
- install-plugin.ps1 # pack + install into profile (DSH auto-mounts the bundle)
79
- start-multi.ps1 / stop-multi.ps1 # start/stop multiple dsh web instances
80
- gateway.mjs # token-authenticated reverse-proxy gateway (phone/remote)
81
- cordis.patch.yml # DSH bundle layer declaration
82
- harness-src/ # official deepseek-harness source (dev/build reference)
83
- ```
84
-
85
- ## Install & enable (Windows)
86
-
87
- ```powershell
88
- # 1) Pack and install into the web profile. The plugin declares dsh.bundle.patch,
89
- # so DSH auto-mounts its bundle layer β€” no manual patch edit.
90
- .\scripts\install-plugin.ps1
91
-
92
- # 2) Restart dsh web and open any instance
93
- dsh web --port 3084
94
- # Browser: http://127.0.0.1:3084 β€” a "Multi-window" button appears in the sidebar footer
95
- ```
96
-
97
- Or manual:
98
-
99
- ```bash
100
- npm pack # produce a tarball (dsh-multi-chat-1.0.1.tgz)
101
- dsh plugin --profile web add dsh-multi-chat-1.0.1.tgz # DSH adds the package to the bundle layer stack automatically
102
- ```
103
-
104
- Uninstall (one command, nothing manual to clean up β€” restart `dsh web` to unload it):
105
-
106
- ```bash
107
- dsh plugin --profile web remove dsh-multi-chat
108
- ```
109
-
110
- ## Usage
111
-
112
- 1. Start several instances: `.\scripts\start-multi.ps1 -Ports "3080,3081,3082,3084"` (or manual `dsh web --port <n>`).
113
- 2. Open any instance and click the "Multi-window" shortcut in the sidebar footer (or the "Multi-window" tab at the top of the chat area).
114
- 3. Inside the wall view: auto-discovery (own port excluded), column switching (auto/1/2/3/4/6, horizontally filled by default), click title to maximize, ⟳ refresh one, β†— open in a new tab, βœ• remove from view, refresh all, and live online status dots. The layout is persisted to `localStorage`.
115
- 4. To exit the wall, click the **"Exit" button in the toolbar's top-right** to switch back to the chat view in one click.
116
-
117
- ## Phone / remote access (built-in authenticated gateway)
118
-
119
- The official `dsh web` **deliberately forbids `--host 0.0.0.0`** (it would expose remote code execution to the network). This plugin ships a built-in **token-authenticated intranet gateway**: click the "Phone access" button and it **automatically** starts a gateway for the current instance (listening on `0.0.0.0`, reverse-proxying to `127.0.0.1:<this instance's port>`), returning a LAN URL + login token.
120
-
121
- ```text
122
- Click "Phone access" β†’ you get:
123
- Available on your phone on the same network: http://10.105.7.204:9477 token: 2efb23eade16
124
- ```
125
-
126
- Open that URL on your phone and enter the token to reach the full DSH UI. The gateway's security model:
127
-
128
- - HMAC-signed HttpOnly/SameSite session cookie (12h default), `?token=` for script convenience, per-IP rate limiting on failed logins
129
- - All proxied requests rewrite Host/Origin to the loopback target, so the official `/api` browser-trust fence (the DNS-rebinding defense) treats it as a local request β€” no restart / `--trusted-host` needed
130
- - WebSocket upgrades and SSE streams pass through unchanged
131
- - When the intended port hits a Windows excluded range or is already bound, it automatically falls back to an OS-assigned free port
132
-
133
- > A standalone `scripts/gateway.mjs` (with optional TLS) is also available for advanced manual use.
134
-
135
- ## Distribution & install
136
-
137
- `dsh-multi-chat` is published to npm (unscoped public package) and released on GitHub (source zip/tarball per tag). Every channel below ends in the same three things: the package lands as a dependency of the web profile, DSH reconciles it into the bundle layer stack (the package declares `dsh.bundle.patch`, so its own `cordis.patch.yml` is mounted automatically β€” no manual patch edits), and a `dsh web` restart loads the wall.
138
-
139
- ### Install / uninstall cheat-sheet
140
-
141
- | Channel | Install | Uninstall |
142
- |---------|---------|-----------|
143
- | **One-command (registry)** | `dsh plugin --profile web add dsh-multi-chat` | `dsh plugin --profile web remove dsh-multi-chat` |
144
- | **npx (no download)** | `npx dsh-multi-chat install` | `dsh plugin --profile web remove dsh-multi-chat` |
145
- | **Global CLI (npm)** | `npm i -g dsh-multi-chat` then `dsh-multi-chat install` | `dsh plugin --profile web remove dsh-multi-chat` then `npm rm -g dsh-multi-chat` |
146
- | **Tarball (offline)** | `npm pack` β†’ `dsh plugin --profile web add ./dsh-multi-chat-1.0.1.tgz` | `dsh plugin --profile web remove dsh-multi-chat` |
147
- | **Git clone** | `node bin/dsh-multi-chat.mjs install` | `dsh plugin --profile web remove dsh-multi-chat` |
148
-
149
- > `dsh plugin --profile web remove dsh-multi-chat` is the **single uninstall
150
- > command for every channel**: it removes the dependency from the profile, and
151
- > DSH strips it from the `dsh.profile.bundles` layer stack automatically
152
- > (reconciled from the installed dependency set β€” see `dsh plugin --help`).
153
- > Restart `dsh web` afterwards to unload the wall.
154
-
155
- > **pnpm β‰₯ 11 gotcha**: pnpm 11's `minimumReleaseAge` supply-chain guard skips
156
- > freshly published versions (e.g. a plugin published minutes ago) and silently
157
- > installs the newest "aged" release instead. If `dsh plugin --profile web add
158
- > dsh-multi-chat` reports an older version than the latest, add
159
- > `minimumReleaseAge: 0` to the profile's
160
- > `~/.dsh/profiles/web/pnpm-workspace.yaml` and re-run the add.
161
-
162
- The repo also bundles a cross-platform CLI, `dsh-multi-chat` (`bin/dsh-multi-chat.mjs`). Its `install` command probes `$DSH_HOME` (default `~/.dsh`), packs the package into the profile's `plugins/` folder, and runs `dsh plugin --profile web add <tarball>` (same behavior as `install-plugin.ps1`).
163
-
164
- ### Channel 1: npm / npx (recommended, easiest)
165
-
166
- ```bash
167
- # Published on npm β€” one line installs on any machine (node + pnpm required)
168
- npx dsh-multi-chat install
169
-
170
- # Or install the CLI globally, then install the plugin from anywhere
171
- npm i -g dsh-multi-chat
172
- dsh-multi-chat install
173
-
174
- # Or run single commands straight from npx (no plugin install needed)
175
- npx dsh-multi-chat start --remote --token <token> --ports 3080,3081
176
- npx dsh-multi-chat gateway --target 127.0.0.1:3080 --token <token>
177
- ```
178
-
179
- Maintaining / republishing (maintainer): `npm publish` (unscoped public package `dsh-multi-chat`).
180
-
181
- ### Channel 2: GitHub Release
182
-
183
- Download the source zip/tarball from [Releases](https://github.com/daetz-coder/dsh-multi-chat/releases), unpack it, and cd in:
184
-
185
- ```bash
186
- node bin/dsh-multi-chat.mjs install # pack + dsh plugin add (see cheat-sheet)
187
- node bin/dsh-multi-chat.mjs start --ports 3080,3081
188
- ```
189
-
190
- > Tagging a release makes GitHub auto-generate the source zip/tarball assets; you can also attach a `npm pack`-produced `.tgz` as an offline install bundle.
191
-
192
- ### Channel 3: direct git install
193
-
194
- ```bash
195
- git clone https://github.com/daetz-coder/dsh-multi-chat.git
196
- cd dsh-multi-chat
197
-
198
- node bin/dsh-multi-chat.mjs install # pack + dsh plugin add (see cheat-sheet)
199
- node bin/dsh-multi-chat.mjs start --ports 3080,3081
200
- node bin/dsh-multi-chat.mjs gateway --target 127.0.0.1:3080 --token <token>
201
- ```
202
-
203
- > For the one-command-on-this-machine flow, `dsh plugin --profile web add dsh-multi-chat` also works straight from a git clone β€” install the plugin _from_ the repo checkout without packaging it yourself.
204
-
205
- ### Running straight from this repo (development)
206
-
207
- ```bash
208
- node bin/dsh-multi-chat.mjs install
209
- node bin/dsh-multi-chat.mjs start --ports 3080,3081
210
- node bin/dsh-multi-chat.mjs stop
211
- node bin/dsh-multi-chat.mjs gateway --target 127.0.0.1:3080 --token <token>
212
- ```
213
-
214
- ## πŸ” Discovery & ecosystem
215
-
216
- This plugin follows the official [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) client plugin spec:
217
-
218
- - **Be found in the GitHub plugin ecosystem**: adding the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to this repo makes it searchable on the official [`dsh-plugin` topic page](https://github.com/topics/dsh-plugin) (the officially recommended third-party discovery path).
219
- - **Bilingual technical docs**: this repo ships `README.md` (English) and `README.zh.md` (Chinese) at the root, matching the bilingual convention of official `packages/client/*` plugins.
220
- - **Purely additive, no core touching**: registers only the `conversation.view` / `sidebar.footer.action` list slots + `/multi/api/*` read-only routes, changing no official core logic.
221
-
222
- ## Building from source
223
-
224
- The single-package layout uses `tsdown` for bundling and `tsc` for type declarations:
225
-
226
- ```bash
227
- npm install
228
- npm run build # tsc + tsdown β†’ lib/
229
- npm test # vitest (browser half in jsdom + node half)
230
- ```
231
-
232
- ## Testing
233
-
234
- The unit suite (`tests/browser-plugin.client.spec.tsx`, 20 specs) exercises the
235
- browser half (view-ring entry, sidebar shortcut, wall store, recursion guard,
236
- HMR disposal) against a real cordis Context in jsdom, plus the node half
237
- (probe routes, config schema, stop semantics).
238
-
239
- The specs import `@deepseek-ai/*` platform packages whose published versions
240
- lag the snapshot this plugin was written against, so tests resolve them to the
241
- **vendored harness sources** in `harness-src/` instead of npm:
242
-
243
- 1. First install the vendored workspace once (it is a full DSH checkout):
244
- ```bash
245
- cd harness-src && pnpm install && cd ..
246
- ```
247
- 2. `tsconfig.vitest.json` carries the workspace's `tsconfig.base.json` paths
248
- map (rewritten with a `harness-src/` prefix) so every `@deepseek-ai/*`
249
- import resolves to sources β€” never to unbuilt `lib/` outputs. Regenerate it
250
- after updating the vendored checkout:
251
- ```bash
252
- node scripts/sync-vitest-paths.mjs
253
- ```
254
- 3. `vitest.config.ts` feeds that map to `vite-tsconfig-paths` and dedupes
255
- `react`/`react-dom` so the component specs and `@testing-library/react`
256
- share one React instance. Then:
257
-
258
- ```bash
259
- npm test
260
- ```
261
-
262
- > `npm run build`'s `tsc` step resolves `@deepseek-ai/*` types from the
263
- > installed workspace (or from npm-installed versions on a machine with the
264
- > matching snapshot); the committed `lib/` is the reference output.
265
-
266
- ## License
267
-
268
- MIT
1
+ # πŸ’¬ dsh-multi-chat β€” Multi-chat, one screen
2
+
3
+ **English** | [δΈ­ζ–‡](README.zh.md)
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/dsh-multi-chat"><img src="https://img.shields.io/npm/v/dsh-multi-chat" alt="npm version"></a>
7
+ <a href="https://www.npmjs.com/package/dsh-multi-chat"><img src="https://img.shields.io/npm/dm/dsh-multi-chat" alt="npm downloads"></a>
8
+ <a href="https://github.com/daetz-coder/dsh-multi-chat/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="license"></a>
9
+ <a href="https://github.com/topics/dsh-plugin"><img src="https://img.shields.io/badge/dsh--plugin-community-brightgreen" alt="dsh-plugin"></a>
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://awesome-dsh-plugin.com/p/daetz-coder/dsh-multi-chat/"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="awesome-dsh-plugin"></a>
14
+ <a href="https://dshfind.com/plugins/daetz-coder/dsh-multi-chat"><img src="https://dshfind.com/api/badge/daetz-coder/dsh-multi-chat" alt="dshfind"></a>
15
+ <a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://img.shields.io/badge/listed%20on-awesome--dsh--plugin-4d6bfe" alt="listed on awesome-dsh-plugin"></a>
16
+ </p>
17
+
18
+ > **Run N conversations in DeepSeek Harness at once, watch every Agent's live progress side-by-side, and check in from your phone or tablet.** One browser tab goes from "one conversation at a time" to "a panoramic multi-conversation cockpit."
19
+
20
+ Install a **multi-window wall** into the official [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) Web UI: a grid that shows N running DSH conversation instances simultaneously (each instance runs its own task), so every Agent's live progress, chat, and output are **visible at a glance** β€” no more hopping between endless tabs and windows.
21
+
22
+ ## ✨ What it does
23
+
24
+ | Capability | Description |
25
+ |------------|-------------|
26
+ | πŸ“Ί **Multi-window** | One-click entry from the sidebar; the chat area becomes a window grid showing every task side-by-side, one pane per port |
27
+ | πŸ” **Auto-discovery** | Scans a port range to auto-find running DSH instances; manual management is also supported |
28
+ | βž• **One-click new window** | Launch a brand-new DSH instance right inside the wall to grow your conversation matrix |
29
+ | πŸ“± **Phone access** | The "Phone access" button starts a **built-in authenticated LAN gateway** β€” open the URL on your phone, enter the token, and watch progress |
30
+ | πŸ›‘ **Window controls** | Maximize, refresh, open in a new tab, stop an instance, and switch column count (auto/1/2/3/4/6) |
31
+
32
+ > **Multi-chat = multi-port.** Start N `dsh web --port <n>` instances (each running one conversation/task), open the wall from any of them, and you see all of them side-by-side.
33
+
34
+ ## πŸ“Έ Screenshots
35
+
36
+ **πŸ–₯️ Windows Β· Two chats side-by-side** β€” two running DSH instances laid out together, each pane a full official conversation UI with live online status dots and per-window controls (maximize / refresh / new tab / remove):
37
+
38
+ ![Windows dual chat: two DSH instances side-by-side](assets/01-windows-dual-chat.png)
39
+
40
+ **πŸ“± iPad Β· Two chats on mobile** β€” on the same LAN, open the token-authenticated gateway URL on an iPad to watch two Agents' live progress on one tablet screen:
41
+
42
+ ![iPad dual chat: two DSH instances on tablet](assets/02-ipad-dual-chat.png)
43
+
44
+ **πŸ–₯️ Windows Β· Three-chat panorama** β€” a 3-column grid of three running instances, all Agents on one screen, upgrading you from "one conversation at a time" to "a panoramic multi-conversation cockpit":
45
+
46
+ ![Windows triple chat: 3-column grid of three DSH instances](assets/03-windows-triple-chat.png)
47
+
48
+ ## πŸš€ 30-second quick start
49
+
50
+ ```bash
51
+ # 1. Install β€” ONE command straight from the npm registry (no CLI to download):
52
+ dsh plugin --profile web add dsh-multi-chat
53
+
54
+ # …or via the plugin's own npx CLI (packs a tarball and installs it):
55
+ npx dsh-multi-chat install
56
+
57
+ # 2. Start a few instances
58
+ npx dsh-multi-chat start --ports 3080,3081,3082
59
+
60
+ # 3. (Re)start any instance and click "Multi-window" in the sidebar footer β†’ done πŸŽ‰
61
+ ```
62
+
63
+ ## Why this approach
64
+
65
+ - **No official logic is touched**: the plugin only registers two **additive list slots** (`conversation.view` ring entry, `sidebar.footer.action` sidebar shortcut) and five read-only JSON routes (`/multi/api/ports`, `/multi/api/status`, `/multi/api/stop`, `/multi/api/create`, `/multi/api/link`). No existing slot is replaced, no line is rewritten, and no core session/agent/tool logic is touched.
66
+ - **The UI is the official UI**: the wall is a view in the official view ring rendered inside the chat panel (not a popup). Theme, type scale, icons, and controls all use the official `--dsw-*` tokens and official primitives (Button/Input/Menu/StateDot).
67
+ - **Recursion guard**: the wall never embeds its own port; embedded pages carry a `?multi-wall=embed` flag and register no wall UI, preventing infinite "wall-in-wall" recursion.
68
+ - **Minimal footprint**: one declarative client plugin package β€” it ships its own `dsh.bundle.patch` + `cordis.patch.yml`, so DSH mounts it as a bundle layer automatically.
69
+
70
+ ## Directory layout
71
+
72
+ ```
73
+ dsh-multi-chat/ # single-package structure
74
+ lib/ # built artifacts (lib/index.js + lib/client.js + types)
75
+ src/ # source (node half + browser half)
76
+ bin/dsh-multi-chat.mjs # cross-platform npx CLI (install/start/stop/gateway)
77
+ scripts/
78
+ install-plugin.ps1 # pack + install into profile (DSH auto-mounts the bundle)
79
+ start-multi.ps1 / stop-multi.ps1 # start/stop multiple dsh web instances
80
+ gateway.mjs # token-authenticated reverse-proxy gateway (phone/remote)
81
+ cordis.patch.yml # DSH bundle layer declaration
82
+ platform-seeds.json # the DSH module-table contract this plugin builds against
83
+ ```
84
+
85
+ ## Install & enable (Windows)
86
+
87
+ ```powershell
88
+ # 1) Pack and install into the web profile. The plugin declares dsh.bundle.patch,
89
+ # so DSH auto-mounts its bundle layer β€” no manual patch edit.
90
+ .\scripts\install-plugin.ps1
91
+
92
+ # 2) Restart dsh web and open any instance
93
+ dsh web --port 3084
94
+ # Browser: http://127.0.0.1:3084 β€” a "Multi-window" button appears in the sidebar footer
95
+ ```
96
+
97
+ Or manual:
98
+
99
+ ```bash
100
+ npm pack # produce a tarball (dsh-multi-chat-1.0.3.tgz)
101
+ dsh plugin --profile web add dsh-multi-chat-1.0.3.tgz # DSH adds the package to the bundle layer stack automatically
102
+ ```
103
+
104
+ Uninstall (one command, nothing manual to clean up β€” restart `dsh web` to unload it):
105
+
106
+ ```bash
107
+ dsh plugin --profile web remove dsh-multi-chat
108
+ ```
109
+
110
+ ## Usage
111
+
112
+ 1. Start several instances: `.\scripts\start-multi.ps1 -Ports "3080,3081,3082,3084"` (or manual `dsh web --port <n>`).
113
+ 2. Open any instance and click the "Multi-window" shortcut in the sidebar footer (or the "Multi-window" tab at the top of the chat area).
114
+ 3. Inside the wall view: auto-discovery (own port excluded), column switching (auto/1/2/3/4/6, horizontally filled by default), click title to maximize, ⟳ refresh one, β†— open in a new tab, βœ• remove from view, refresh all, and live online status dots. The layout is persisted to `localStorage`.
115
+ 4. To exit the wall, click the **"Exit" button in the toolbar's top-right** to switch back to the chat view in one click.
116
+
117
+ ## Phone / remote access (built-in authenticated gateway)
118
+
119
+ The official `dsh web` **deliberately forbids `--host 0.0.0.0`** (it would expose remote code execution to the network). This plugin ships a built-in **token-authenticated intranet gateway**: click the "Phone access" button and it **automatically** starts a gateway for the current instance (listening on `0.0.0.0`, reverse-proxying to `127.0.0.1:<this instance's port>`), returning a LAN URL + login token.
120
+
121
+ ```text
122
+ Click "Phone access" β†’ you get:
123
+ Available on your phone on the same network: http://10.105.7.204:9477 token: 2efb23eade16
124
+ ```
125
+
126
+ Open that URL on your phone and enter the token to reach the full DSH UI. The gateway's security model:
127
+
128
+ - HMAC-signed HttpOnly/SameSite session cookie (12h default), `?token=` for script convenience, per-IP rate limiting on failed logins
129
+ - All proxied requests rewrite Host/Origin to the loopback target, so the official `/api` browser-trust fence (the DNS-rebinding defense) treats it as a local request β€” no restart / `--trusted-host` needed
130
+ - WebSocket upgrades and SSE streams pass through unchanged
131
+ - When the intended port hits a Windows excluded range or is already bound, it automatically falls back to an OS-assigned free port
132
+
133
+ > A standalone `scripts/gateway.mjs` (with optional TLS) is also available for advanced manual use.
134
+
135
+ ## Distribution & install
136
+
137
+ `dsh-multi-chat` is published to npm (unscoped public package) and released on GitHub (source zip/tarball per tag). Every channel below ends in the same three things: the package lands as a dependency of the web profile, DSH reconciles it into the bundle layer stack (the package declares `dsh.bundle.patch`, so its own `cordis.patch.yml` is mounted automatically β€” no manual patch edits), and a `dsh web` restart loads the wall.
138
+
139
+ ### Install / uninstall cheat-sheet
140
+
141
+ | Channel | Install | Uninstall |
142
+ |---------|---------|-----------|
143
+ | **One-command (registry)** | `dsh plugin --profile web add dsh-multi-chat` | `dsh plugin --profile web remove dsh-multi-chat` |
144
+ | **npx (no download)** | `npx dsh-multi-chat install` | `dsh plugin --profile web remove dsh-multi-chat` |
145
+ | **Global CLI (npm)** | `npm i -g dsh-multi-chat` then `dsh-multi-chat install` | `dsh plugin --profile web remove dsh-multi-chat` then `npm rm -g dsh-multi-chat` |
146
+ | **Tarball (offline)** | `npm pack` β†’ `dsh plugin --profile web add ./dsh-multi-chat-1.0.3.tgz` | `dsh plugin --profile web remove dsh-multi-chat` |
147
+ | **Git clone** | `node bin/dsh-multi-chat.mjs install` | `dsh plugin --profile web remove dsh-multi-chat` |
148
+
149
+ > `dsh plugin --profile web remove dsh-multi-chat` is the **single uninstall
150
+ > command for every channel**: it removes the dependency from the profile, and
151
+ > DSH strips it from the `dsh.profile.bundles` layer stack automatically
152
+ > (reconciled from the installed dependency set β€” see `dsh plugin --help`).
153
+ > Restart `dsh web` afterwards to unload the wall.
154
+
155
+ > **pnpm β‰₯ 11 gotcha**: pnpm 11's `minimumReleaseAge` supply-chain guard skips
156
+ > freshly published versions (default cutoff: 1 day) and silently installs the
157
+ > newest "aged" release instead. If `dsh plugin --profile web add
158
+ > dsh-multi-chat` reports an older version than the latest, add
159
+ > `minimumReleaseAge: 0` to the profile's
160
+ > `~/.dsh/profiles/web/pnpm-workspace.yaml` and re-run the add. The bundled CLI
161
+ > (`npx dsh-multi-chat install`) writes that setting for you automatically.
162
+
163
+ The repo also bundles a cross-platform CLI, `dsh-multi-chat` (`bin/dsh-multi-chat.mjs`). Its `install` command probes `$DSH_HOME` (default `~/.dsh`), packs the package into the profile's `plugins/` folder, and runs `dsh plugin --profile web add <tarball>` (same behavior as `install-plugin.ps1`).
164
+
165
+ ### Channel 1: npm / npx (recommended, easiest)
166
+
167
+ ```bash
168
+ # Published on npm β€” one line installs on any machine (node + pnpm required)
169
+ npx dsh-multi-chat install
170
+
171
+ # Or install the CLI globally, then install the plugin from anywhere
172
+ npm i -g dsh-multi-chat
173
+ dsh-multi-chat install
174
+
175
+ # Or run single commands straight from npx (no plugin install needed)
176
+ npx dsh-multi-chat start --remote --token <token> --ports 3080,3081
177
+ npx dsh-multi-chat gateway --target 127.0.0.1:3080 --token <token>
178
+ ```
179
+
180
+ Maintaining / republishing (maintainer): `npm publish` (unscoped public package `dsh-multi-chat`).
181
+
182
+ ### Channel 2: GitHub Release
183
+
184
+ Download the source zip/tarball from [Releases](https://github.com/daetz-coder/dsh-multi-chat/releases), unpack it, and cd in:
185
+
186
+ ```bash
187
+ node bin/dsh-multi-chat.mjs install # pack + dsh plugin add (see cheat-sheet)
188
+ node bin/dsh-multi-chat.mjs start --ports 3080,3081
189
+ ```
190
+
191
+ > Tagging a release makes GitHub auto-generate the source zip/tarball assets; you can also attach a `npm pack`-produced `.tgz` as an offline install bundle.
192
+
193
+ ### Channel 3: direct git install
194
+
195
+ ```bash
196
+ git clone https://github.com/daetz-coder/dsh-multi-chat.git
197
+ cd dsh-multi-chat
198
+
199
+ node bin/dsh-multi-chat.mjs install # pack + dsh plugin add (see cheat-sheet)
200
+ node bin/dsh-multi-chat.mjs start --ports 3080,3081
201
+ node bin/dsh-multi-chat.mjs gateway --target 127.0.0.1:3080 --token <token>
202
+ ```
203
+
204
+ > For the one-command-on-this-machine flow, `dsh plugin --profile web add dsh-multi-chat` also works straight from a git clone β€” install the plugin _from_ the repo checkout without packaging it yourself.
205
+
206
+ ### Running straight from this repo (development)
207
+
208
+ ```bash
209
+ node bin/dsh-multi-chat.mjs install
210
+ node bin/dsh-multi-chat.mjs start --ports 3080,3081
211
+ node bin/dsh-multi-chat.mjs stop
212
+ node bin/dsh-multi-chat.mjs gateway --target 127.0.0.1:3080 --token <token>
213
+ ```
214
+
215
+ ## πŸ” Discovery & ecosystem
216
+
217
+ This plugin follows the official [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) client plugin spec:
218
+
219
+ - **Be found in the GitHub plugin ecosystem**: adding the [`dsh-plugin`](https://github.com/topics/dsh-plugin) topic to this repo makes it searchable on the official [`dsh-plugin` topic page](https://github.com/topics/dsh-plugin) (the officially recommended third-party discovery path).
220
+ - **Bilingual technical docs**: this repo ships `README.md` (English) and `README.zh.md` (Chinese) at the root, matching the bilingual convention of official `packages/client/*` plugins.
221
+ - **Purely additive, no core touching**: registers only the `conversation.view` / `sidebar.footer.action` list slots + `/multi/api/*` read-only routes, changing no official core logic.
222
+
223
+ ## Building from source
224
+
225
+ The single-package layout uses `tsdown` for bundling and `tsc` for type declarations:
226
+
227
+ ```bash
228
+ npm install
229
+ npm run build # tsc + tsdown β†’ lib/
230
+ npm test # vitest (browser half in jsdom + node half)
231
+ ```
232
+
233
+ ## Testing
234
+
235
+ ```bash
236
+ npm test # builds first, then runs vitest
237
+ ```
238
+
239
+ `npm test` builds before it tests, so the suite always exercises the artifact
240
+ that ships rather than a stale `lib/`.
241
+
242
+ - `tests/browser-plugin.client.spec.tsx` β€” the browser half (view-ring entry,
243
+ sidebar shortcut, wall store, recursion guard, HMR disposal) against a real
244
+ cordis Context in jsdom, plus the node half (probe routes, config schema,
245
+ stop semantics).
246
+ - `tests/client-bundle.spec.ts` β€” loads the **built** `lib/client.js` through
247
+ `tests/dsh-module-loader.ts`, a stand-in for the web shell's module table.
248
+ This is the regression test for the v1.0.3 boot failure described below.
249
+
250
+ ## The platform contract
251
+
252
+ A client plugin bundle is **not** an ES module. The web shell loads it as a
253
+ script whose only top-level effect is `window.__ModuleLoader__.load({ id,
254
+ factory })`, and every `require(spec)` inside the factory resolves against the
255
+ shell's module table. That table has exactly two sources:
256
+
257
+ 1. the platform **seed table** β€” a fixed set of specifiers the shell hardcodes
258
+ into its own Vite bundle, and
259
+ 2. **boot-graph package rows** β€” other plugins' bundles.
260
+
261
+ A plugin cannot conjure another package's row, so requiring anything outside
262
+ the seed table throws `missed the module table`. The boot is fail-closed: the
263
+ whole web UI stops on its "Failed to load plugins" card.
264
+
265
+ That is how v1.0.3 broke. It required `@deepseek-ai/dsh-client-runtime/client`,
266
+ a package upstream deleted, and nothing in the build noticed β€” the types came
267
+ from an unversioned local checkout of the harness monorepo, so the plugin
268
+ compiled happily against a platform that no longer existed.
269
+
270
+ `platform-seeds.json` pins the seed table, and is the single source of truth
271
+ for both `tsdown.config.ts` (which specifiers to leave unresolved) and
272
+ `scripts/check-platform-contract.mjs` (which fails the build if the bundle
273
+ requires anything else):
274
+
275
+ ```bash
276
+ npm run check:platform # runs inside `npm run build`
277
+ npm run check:platform:against -- <path-to-installed-dsh> # diff against a real install
278
+ npm run sync:platform -- <path-to-installed-dsh> # adopt a new release's table
279
+ ```
280
+
281
+ The `@deepseek-ai/*` platform packages are pinned as **exact** devDependencies,
282
+ so the surface this plugin compiles against is versioned and reviewable instead
283
+ of coming from a local checkout.
284
+
285
+ ## License
286
+
287
+ MIT