dsh-bots 0.2.22 → 0.2.24
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 +160 -8
- package/README.zh-CN.md +157 -0
- package/lib/client.js +61 -28
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -1,12 +1,164 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
1
3
|
# dsh-bots
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
**A multi-bot workbench for DeepSeek Harness (dsh)** —
|
|
6
|
+
bridges the sdk-bots orchestration gateway into the dsh web
|
|
7
|
+
shell: single bots, group chats, transcripts and live events, styled like the official UI.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/dsh-bots)
|
|
10
|
+
[](./LICENSE)
|
|
11
|
+
[](./package.json)
|
|
12
|
+
[](./scripts)
|
|
13
|
+
|
|
14
|
+
English · [简体中文](./README.zh-CN.md)
|
|
15
|
+
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
**dsh-bots** is a formal Cordis plugin (Host +
|
|
21
|
+
Client halves) for the `dsh` web profile. It mounts a workbench into the sidebar where every bot of
|
|
22
|
+
your sdk-bots swarm is one click away — chat with single bots, run group productions with
|
|
23
|
+
@-mentions, watch transcripts stream in over SSE, and manage each bot's workspace sandbox without
|
|
24
|
+
leaving the shell.
|
|
25
|
+
|
|
26
|
+
## ✨ Features
|
|
27
|
+
|
|
28
|
+
**Workbench sidebar**
|
|
29
|
+
|
|
30
|
+
- Two-group collapsible nav 「工作区 | Bots」 with the native sidebar interaction model (hover
|
|
31
|
+
actions, chevron fold, accordion expansion, gateway status dot)
|
|
32
|
+
- 群聊 / 单聊 sections with hover-revealed `+` (create) and `⋯` (more actions) menus
|
|
33
|
+
- Unread badges with a plugin-owned read model (SSE + transcript rebase), composing indicators
|
|
34
|
+
|
|
35
|
+
**Conversations**
|
|
36
|
+
|
|
37
|
+
- Full chat surface: day dividers, stick-to-bottom with jump-to-latest, IME-safe composer,
|
|
38
|
+
same-author run compaction
|
|
39
|
+
- Group chat turn-taking: @-mention directed replies, one-hop delegation, `@全员` broadcast —
|
|
40
|
+
powered by the engine's responder election
|
|
41
|
+
- Markdown rendering, tool-call cards with status dots, thinking blocks
|
|
42
|
+
- **Stop generating**: composer send button morphs into a stop square wired to the gateway's
|
|
43
|
+
`interruptAgent`
|
|
44
|
+
- Local media: inline image previews and one-click file chips for paths mentioned by bots
|
|
45
|
+
|
|
46
|
+
**Management**
|
|
47
|
+
|
|
48
|
+
- **Session settings** (gear in the chat bar): rename, description, and the workspace jail editor
|
|
49
|
+
- **Manage members** for groups (checkbox roster editor, honest 6-member cap feedback)
|
|
50
|
+
- Create bots and groups from the section headers; delete with confirm dialogs
|
|
51
|
+
- Settings page section: gateway health, data dir, SSE state, workspace list, MCP servers & tools
|
|
52
|
+
|
|
53
|
+
**Under the hood**
|
|
54
|
+
|
|
55
|
+
- Host half: a `TypertRemoteService` bridging 27 gateway RPC endpoints + SSE proxy
|
|
56
|
+
- Client half: plain-JS React via Cordis Slots (no build step), zh/en i18n
|
|
57
|
+
- Workspace jail (macOS Seatbelt) editing per bot: root dir + extra writable paths
|
|
58
|
+
|
|
59
|
+
## 📦 Install
|
|
60
|
+
|
|
61
|
+
Requirements:
|
|
62
|
+
|
|
63
|
+
- `dsh` with a web profile (Cordis `^4.0.1` — what the plugin is tested against)
|
|
64
|
+
- An sdk-bots host running its gateway (default discovery:
|
|
65
|
+
`~/.dsh-bots/gateway.json`, falling back to the legacy `~/.sdk-bots/gateway.json`)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# from npm
|
|
69
|
+
dsh plugin --profile web add dsh-bots
|
|
70
|
+
|
|
71
|
+
# or from a packed tarball
|
|
72
|
+
pnpm pack
|
|
73
|
+
dsh plugin --profile web add ./dsh-bots-0.2.22.tgz
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Then restart `dsh web` and refresh the page. The workbench appears as the second group in the
|
|
77
|
+
sidebar; the settings page gains a **Bots** section.
|
|
78
|
+
|
|
79
|
+
> ⚠️ **Half-loading trap**: the plugin's Client half is served fresh on every page load, but the
|
|
80
|
+
> Host half only loads at `dsh web` boot. After upgrading, refresh for UI changes — restart
|
|
81
|
+
> `dsh web` when host RPCs change (otherwise the new UI hits a stale host and new endpoints 404).
|
|
82
|
+
|
|
83
|
+
## ⚙️ Configuration
|
|
84
|
+
|
|
85
|
+
The plugin reads a single option (via `cordis.patch.yml`):
|
|
86
|
+
|
|
87
|
+
| Option | Default | Description |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `dataDir` | `~/.dsh-bots` | Directory holding `gateway.json` and plugin-owned state. Discovery falls back to the legacy `~/.sdk-bots` root until the engine migrates; unread ledger, workspace ops and the media allowlist follow the gateway's actual location. |
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
# cordis.patch.yml (shipped in the package; applied when the plugin is
|
|
93
|
+
# added to a profile — change dataDir here if your gateway lives elsewhere)
|
|
94
|
+
- insert:
|
|
95
|
+
- id: bots
|
|
96
|
+
name: dsh-bots
|
|
97
|
+
config:
|
|
98
|
+
dataDir: '~/.dsh-bots'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## 🚀 Usage
|
|
102
|
+
|
|
103
|
+
| Task | How |
|
|
104
|
+
|---|---|
|
|
105
|
+
| Open a conversation | Click a bot in 单聊 or a group in 群聊 |
|
|
106
|
+
| Direct a group reply | Type `@` and pick a member; `@全员` wakes everyone |
|
|
107
|
+
| Stop a generation | Click the ⏹ square (replaces ▲ send while composing) |
|
|
108
|
+
| Rename / jail a bot | Gear button (top-right of the chat bar) |
|
|
109
|
+
| Add/remove group members | 管理成员 button (top-right, group chats) |
|
|
110
|
+
| Create / refresh | Hover a section header → `+` / `⋯` |
|
|
111
|
+
| Fold a section | Click the section header row |
|
|
112
|
+
| Gateway health & MCP | Settings page → Bots section |
|
|
113
|
+
|
|
114
|
+
**Workspace jail** (singles only): set a workspace root to confine a bot's shell writes via macOS
|
|
115
|
+
Seatbelt (reads stay unrestricted, same-slug bots share a directory, takes effect next turn).
|
|
116
|
+
Empty root = jail disabled. Extra writable paths accept a comma-separated list.
|
|
117
|
+
|
|
118
|
+
**Group member cap**: the engine hard-caps groups at **6 members** and silently truncates larger
|
|
119
|
+
rosters — the member editor surfaces the cap instead of letting a save pretend to work.
|
|
120
|
+
|
|
121
|
+
## 🏗️ Architecture
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
┌────────────────────────── dsh web ──────────────────────────┐
|
|
125
|
+
│ shell ── Slots: sidebar.workspaces (shadow) │
|
|
126
|
+
│ shell.overlay (chat + modals) │
|
|
127
|
+
│ settings.section (Bots card) │
|
|
128
|
+
│ │ botsCall RPC (lossless JSON) │
|
|
129
|
+
│ ┌─────────────────── Host half ───────────────────────┐ │
|
|
130
|
+
│ │ TypertRemoteService · 27 endpoints │ │
|
|
131
|
+
│ │ SSE proxy (3k-entry ring buffer) · unread model │ │
|
|
132
|
+
│ └──────────────────────┬────────────────────────────┘ │
|
|
133
|
+
└─────────────────────────┼──────────────────────────────────┘
|
|
134
|
+
│ HTTP + SSE (loopback token auth)
|
|
135
|
+
┌───────────▼───────────┐
|
|
136
|
+
│ sdk-bots gateway :7331 │ ← single bots · sand groups · MCP
|
|
137
|
+
└────────────────────────┘
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- **Host** (`src/index.ts`, `src/gateway.ts`, `src/sse.ts`, …): gateway discovery + calls, SSE
|
|
141
|
+
ring with reconnect + transcript rebase, unread ledger, media `readImage`/`openFile` with a
|
|
142
|
+
fail-closed realpath allowlist, diagnostics file.
|
|
143
|
+
- **Client** (`src/client.ts`): the whole UI in one factory — sidebar nav, chat surface, six
|
|
144
|
+
modals, settings cards — registered through Cordis Slots with zh/en dictionaries.
|
|
145
|
+
|
|
146
|
+
## 🧪 Development
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pnpm install
|
|
150
|
+
pnpm build # tsc host + client
|
|
151
|
+
pnpm test # vitest (83 tests)
|
|
152
|
+
pnpm test:integration # boots a fixture host, checks all 27 endpoints
|
|
153
|
+
pnpm test:dsh-smoke # plugin loads inside a real dsh profile
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Release loop: build → test → integration → bump → pack → `dsh plugin --profile web add` →
|
|
157
|
+
verify → commit → push → `npm publish --access public`.
|
|
158
|
+
|
|
159
|
+
Project docs: [DESIGN.md](./DESIGN.md) (architecture decisions) ·
|
|
160
|
+
[DEVELOPMENT.md](./DEVELOPMENT.md) (engineering log & pitfalls).
|
|
5
161
|
|
|
6
|
-
|
|
7
|
-
- `sidebar.workspaces` shadow — two-group collapsible nav 「工作区|Bots」.
|
|
8
|
-
- `shell.overlay` — chat surface.
|
|
9
|
-
- `settings.section` — gateway status / guide.
|
|
162
|
+
## 📄 License
|
|
10
163
|
|
|
11
|
-
|
|
12
|
-
`DESIGN.md`.
|
|
164
|
+
[MIT](./LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# dsh-bots
|
|
4
|
+
|
|
5
|
+
**DeepSeek Harness (dsh) 多 Bot 工作台** ——
|
|
6
|
+
把 sdk-bots 编排网关桥接进 dsh web 外壳:单聊、群聊、转录流、实时事件,UI 与官方风格一致。
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/dsh-bots)
|
|
9
|
+
[](./LICENSE)
|
|
10
|
+
[](./package.json)
|
|
11
|
+
[](./scripts)
|
|
12
|
+
|
|
13
|
+
[English](./README.md) · 简体中文
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
**dsh-bots** 是一个正式的 Cordis插件(宿主 + 客户端双半边),服务于 `dsh` web 配置。它在侧边栏
|
|
20
|
+
挂载一个工作台,让 sdk-bots 蜂群里的每个 Bot 都触手可及——和单个 Bot 对话、用 @提及指挥群聊
|
|
21
|
+
剧组、看转录随 SSE 实时滚动、不离开外壳就能管理每个 Bot 的工作区沙盒。
|
|
22
|
+
|
|
23
|
+
## ✨ 功能
|
|
24
|
+
|
|
25
|
+
**工作台侧栏**
|
|
26
|
+
|
|
27
|
+
- 「工作区 | Bots」双分组可折叠导航,完全对齐原生侧栏交互(悬停操作、chevron 折叠、手风琴
|
|
28
|
+
展开、网关状态圆点)
|
|
29
|
+
- 群聊 / 单聊分区,悬停浮现 `+`(新建)与 `⋯`(更多操作)菜单
|
|
30
|
+
- 未读徽章(插件自有已读模型:SSE + 转录重放对账)、生成中指示
|
|
31
|
+
|
|
32
|
+
**对话**
|
|
33
|
+
|
|
34
|
+
- 完整聊天界面:日期分割线、贴底滚动 + 跳到最新、IME 安全输入框、同作者连续发言合并
|
|
35
|
+
- 群聊发言调度:@提及定向回复、一跳接力、`@全员` 全员唤醒——由引擎的 responder 选举驱动
|
|
36
|
+
- Markdown 渲染、带状态圆点的工具调用卡片、思考块
|
|
37
|
+
- **停止生成**:输入框发送键在生成中变形为停止方块,直连网关 `interruptAgent`
|
|
38
|
+
- 本地媒体:Bot 提到的本地路径直接内联图片预览、文件一键打开
|
|
39
|
+
|
|
40
|
+
**管理**
|
|
41
|
+
|
|
42
|
+
- **会话设置**(聊天栏右上角齿轮):改名、简介、工作区隔离编辑器
|
|
43
|
+
- 群聊**管理成员**(checkbox 名单编辑器,6 人上限诚实提示)
|
|
44
|
+
- 分区 header 新建 Bot / 群聊;删除带确认弹窗
|
|
45
|
+
- 设置页 Bots 卡片:网关健康、数据目录、SSE 状态、工作区列表、MCP 服务器与工具
|
|
46
|
+
|
|
47
|
+
**底层**
|
|
48
|
+
|
|
49
|
+
- 宿主半边:`TypertRemoteService` 桥接 27 个网关 RPC 端点 + SSE 代理
|
|
50
|
+
- 客户端半边:纯 JS React 走 Cordis Slots(无构建产物依赖),中英双语
|
|
51
|
+
- 每个 Bot 的工作区监狱(macOS Seatbelt)编辑:根目录 + 额外可写路径
|
|
52
|
+
|
|
53
|
+
## 📦 安装
|
|
54
|
+
|
|
55
|
+
前置要求:
|
|
56
|
+
|
|
57
|
+
- `dsh` 与 web 配置(插件在 Cordis `^4.0.1` 上测试)
|
|
58
|
+
- 一个运行中的 [sdk-bots](https://github.com/dushaobindoudou) 宿主网关(默认发现:
|
|
59
|
+
`~/.dsh-bots/gateway.json`,自动回退旧版 `~/.sdk-bots/gateway.json`)
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# 从 npm 安装
|
|
63
|
+
dsh plugin --profile web add dsh-bots
|
|
64
|
+
|
|
65
|
+
# 或从本地打包安装
|
|
66
|
+
pnpm pack
|
|
67
|
+
dsh plugin --profile web add ./dsh-bots-0.2.22.tgz
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
然后重启 `dsh web` 并刷新页面。工作台出现在侧栏第二个分组;设置页多出 **Bots** 卡片。
|
|
71
|
+
|
|
72
|
+
> ⚠️ **两半边加载陷阱**:插件客户端半边每次页面加载都现读,但宿主半边只在 `dsh web` 启动时
|
|
73
|
+
> 加载一次。升级后 UI 改动刷新即可;宿主 RPC 有变化时必须重启 `dsh web`(否则新 UI 打旧
|
|
74
|
+
> host,新端点 404)。
|
|
75
|
+
|
|
76
|
+
## ⚙️ 配置
|
|
77
|
+
|
|
78
|
+
插件只有一个配置项(随 `cordis.patch.yml` 下发):
|
|
79
|
+
|
|
80
|
+
| 配置 | 默认值 | 说明 |
|
|
81
|
+
|---|---|---|
|
|
82
|
+
| `dataDir` | `~/.dsh-bots` | 存放 `gateway.json` 与插件自有状态。引擎迁移完成前,发现逻辑自动回退旧版 `~/.sdk-bots`;未读账本、工作区操作、媒体白名单都跟随网关真实所在目录。 |
|
|
83
|
+
|
|
84
|
+
```yaml
|
|
85
|
+
# cordis.patch.yml(随包分发;插件装入 profile 时应用——
|
|
86
|
+
# 网关不在默认位置时在这里改 dataDir)
|
|
87
|
+
- insert:
|
|
88
|
+
- id: bots
|
|
89
|
+
name: dsh-bots
|
|
90
|
+
config:
|
|
91
|
+
dataDir: '~/.dsh-bots'
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## 🚀 使用
|
|
95
|
+
|
|
96
|
+
| 任务 | 操作 |
|
|
97
|
+
|---|---|
|
|
98
|
+
| 打开对话 | 点击单聊中的 Bot 或群聊中的群 |
|
|
99
|
+
| 让群里某人回话 | 输入 `@` 选成员;`@全员` 唤醒所有人 |
|
|
100
|
+
| 停止生成 | 点击 ⏹ 方块(生成中替换 ▲ 发送键) |
|
|
101
|
+
| 改名 / 工作区隔离 | 聊天栏右上角齿轮 |
|
|
102
|
+
| 增删群成员 | 群聊聊天栏右上角「管理成员」 |
|
|
103
|
+
| 新建 / 刷新 | 悬停分区 header → `+` / `⋯` |
|
|
104
|
+
| 折叠分区 | 点击分区 header 行 |
|
|
105
|
+
| 网关健康 & MCP | 设置页 → Bots 卡片 |
|
|
106
|
+
|
|
107
|
+
**工作区隔离**(仅单聊 Bot):设置工作区根目录后,Bot 的 Shell 写入被 macOS Seatbelt 限制在
|
|
108
|
+
工作区内(读取不受限,同名 Bot 共享目录,下一轮对话生效)。根目录留空 = 不启用。额外可写路径
|
|
109
|
+
接受逗号分隔列表。
|
|
110
|
+
|
|
111
|
+
**群成员上限**:引擎硬性限制每群 **6 人**,超限名单会被静默截断——成员编辑器会把上限明示出来,
|
|
112
|
+
不让保存假装成功。
|
|
113
|
+
|
|
114
|
+
## 🏗️ 架构
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
┌────────────────────────── dsh web ──────────────────────────┐
|
|
118
|
+
│ shell ── Slots: sidebar.workspaces(影子替换) │
|
|
119
|
+
│ shell.overlay(聊天 + 弹窗) │
|
|
120
|
+
│ settings.section(Bots 卡片) │
|
|
121
|
+
│ │ botsCall RPC(无损 JSON) │
|
|
122
|
+
│ ┌─────────────────── Host半边 ────────────────────────┐ │
|
|
123
|
+
│ │ TypertRemoteService · 27 端点 │ │
|
|
124
|
+
│ │ SSE 代理(3k 环形缓冲)· 未读模型 │ │
|
|
125
|
+
│ └──────────────────────┬────────────────────────────┘ │
|
|
126
|
+
└─────────────────────────┼──────────────────────────────────┘
|
|
127
|
+
│ HTTP + SSE(回环 token 鉴权)
|
|
128
|
+
┌───────────▼───────────┐
|
|
129
|
+
│ sdk-bots 网关 :7331 │ ← 单 Bot · 沙盒群 · MCP
|
|
130
|
+
└────────────────────────┘
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- **Host**(`src/index.ts`、`src/gateway.ts`、`src/sse.ts` 等):网关发现与调用、SSE 环形缓冲
|
|
134
|
+
(重连 + 转录重放对账)、未读账本、媒体 `readImage`/`openFile`(realpath 白名单 fail-closed)、
|
|
135
|
+
诊断文件。
|
|
136
|
+
- **Client**(`src/client.ts`):整个 UI 在一个工厂里——侧栏导航、聊天界面、六个弹窗、设置
|
|
137
|
+
卡片——通过 Cordis Slots 注册,中英双语字典。
|
|
138
|
+
|
|
139
|
+
## 🧪 开发
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
pnpm install
|
|
143
|
+
pnpm build # tsc 宿主 + 客户端
|
|
144
|
+
pnpm test # vitest(83 个测试)
|
|
145
|
+
pnpm test:integration # 拉起夹具宿主,校验全部 27 个端点
|
|
146
|
+
pnpm test:dsh-smoke # 插件在真实 dsh profile 内加载
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
发布流程:build → test → integration → bump → pack → `dsh plugin --profile web add` →
|
|
150
|
+
verify → commit → push → `npm publish --access public`。
|
|
151
|
+
|
|
152
|
+
项目文档:[DESIGN.md](./DESIGN.md)(架构决策)· [DEVELOPMENT.md](./DEVELOPMENT.md)(工程日志
|
|
153
|
+
与踩坑记录)。
|
|
154
|
+
|
|
155
|
+
## 📄 许可
|
|
156
|
+
|
|
157
|
+
[MIT](./LICENSE)
|
package/lib/client.js
CHANGED
|
@@ -336,7 +336,7 @@
|
|
|
336
336
|
.dbs-chatview{position:absolute;top:0;bottom:0;pointer-events:auto;display:flex;flex-direction:column;background:var(--dsw-alias-bg-base);font-family:var(--dsw-font-family,inherit);z-index:2;--dsh-chat-content-width:748px;--dsh-composer-card-max-width:calc(var(--dsh-chat-content-width) + 32px);--dsh-composer-side-clearance:16px;--dsh-composer-dock-inset:8px;--dsh-composer-text-max-height:336px;min-width:0}
|
|
337
337
|
.dbs-mediaRefs{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;align-items:center}
|
|
338
338
|
.dbs-mediaImg{max-width:280px;max-height:210px;border-radius:10px;cursor:zoom-in;display:block;border:1px solid var(--dsw-alias-border-l1,rgba(0,0,0,.08))}
|
|
339
|
-
.dbs-mediaLoading{width:28px;height:20px;display:inline-flex;align-items:center;color:var(--dsw-alias-label-tertiary)}
|
|
339
|
+
.dbs-mediaLoading{width:28px;height:20px;display:inline-flex;align-items:center;color:var(--dsw-alias-label-tertiary);animation:dbsSpin .8s linear infinite}
|
|
340
340
|
.dbs-fileChip{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;background:var(--dsw-alias-interactive-bg-hover);font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);cursor:pointer;max-width:100%;user-select:none}
|
|
341
341
|
.dbs-fileChip:hover{color:var(--dsw-alias-label-primary)}
|
|
342
342
|
.dbs-fileChipName{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -399,9 +399,13 @@
|
|
|
399
399
|
.dbs-modalBody{display:flex;flex-direction:column;gap:10px}
|
|
400
400
|
.dbs-modalMembers{display:flex;flex-direction:column;gap:2px;max-height:220px;overflow-y:auto;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:6px}
|
|
401
401
|
.dbs-modalFooter{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin-top:16px}
|
|
402
|
-
.dbs-
|
|
403
|
-
.dbs-
|
|
404
|
-
.dbs-srow:hover .dbs-
|
|
402
|
+
.dbs-rowMore{display:none;border:none;background:transparent;cursor:pointer;color:var(--dsw-alias-label-tertiary);flex:none;justify-content:center;align-items:center;width:24px;height:24px;border-radius:6px;padding:0}
|
|
403
|
+
.dbs-rowMore:hover,.dbs-rowMore:focus-visible{color:var(--dsw-alias-label-primary);background:transparent}
|
|
404
|
+
.dbs-srow:hover .dbs-rowMore,.dbs-rowMore:focus-visible,.dbs-srow[data-menu="true"] .dbs-rowMore{display:inline-flex}
|
|
405
|
+
.dbs-srow{position:relative}
|
|
406
|
+
.dbs-rowMenu{position:absolute;top:calc(100% - 2px);right:4px;z-index:40}
|
|
407
|
+
/* Same quiet-hover contract for the section-header action buttons. */
|
|
408
|
+
.dbs-secHead .dbs-rowActions button:hover,.dbs-secHead .dbs-rowActions button:focus-visible{background:transparent}
|
|
405
409
|
.dbs-mention{position:absolute;bottom:calc(100% + 6px);left:12px;right:12px;max-height:180px;overflow-y:auto;background:var(--dsw-specific-input-major);border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));border-radius:12px;box-shadow:var(--dsw-shadow-lv2);padding:4px;z-index:3}
|
|
406
410
|
.dbs-mentionRow{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px;cursor:pointer;font-size:13px;line-height:20px;color:var(--dsw-alias-label-primary)}
|
|
407
411
|
.dbs-mentionRow[data-active="true"],.dbs-mentionRow:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -414,11 +418,14 @@
|
|
|
414
418
|
.dbs-jump{position:absolute;right:20px;bottom:12px;display:flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:999px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.12));background:var(--dsw-specific-input-major,var(--dsw-alias-bg-layer-1,#fff));box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-secondary);cursor:pointer;z-index:5;transition:opacity .15s ease,transform .15s ease}
|
|
415
419
|
.dbs-jump:hover{color:var(--dsw-alias-label-primary);transform:translateY(-1px)}
|
|
416
420
|
.dbs-jump[data-show="false"]{opacity:0;pointer-events:none;transform:translateY(4px)}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
.
|
|
420
|
-
.dbs-typing
|
|
421
|
-
|
|
421
|
+
/* Loading states aligned with the native shell: StateDot chase icon where
|
|
422
|
+
available (the real native component), pulse-dot fallback in DeepSeek blue
|
|
423
|
+
at the native 1s cycle; media spinner reuses the native 0.8s linear spin. */
|
|
424
|
+
.dbs-typing{display:inline-flex;align-items:center;gap:6px;padding:6px 2px}
|
|
425
|
+
.dbs-typingText{font-size:12px;line-height:16px;color:var(--dsw-alias-label-tertiary)}
|
|
426
|
+
.dbs-typingDot{width:12px;height:12px;border-radius:999px;background:var(--dsw-static-deepseek-450,var(--dsw-alias-label-tertiary));animation:dbsPulse 1s ease-in-out infinite}
|
|
427
|
+
@keyframes dbsPulse{0%,100%{opacity:.2}50%{opacity:1}}
|
|
428
|
+
@keyframes dbsSpin{to{transform:rotate(360deg)}}
|
|
422
429
|
.dbs-mdRow{display:flex;align-items:flex-start;gap:0;min-width:0;width:100%}
|
|
423
430
|
.dbs-caret{flex:none;display:inline-block;width:3px;height:18px;margin-top:5px;border-radius:2px;background:var(--dsw-alias-label-primary);animation:dbsCaret 1s steps(2) infinite}
|
|
424
431
|
@keyframes dbsCaret{0%,49%{opacity:1}50%,100%{opacity:0}}
|
|
@@ -476,7 +483,7 @@
|
|
|
476
483
|
'chat.stop.noop': '当前没有进行中的生成',
|
|
477
484
|
'media.openFailed': '打开失败,文件可能已移动或被删除',
|
|
478
485
|
'chat.members.manage': '管理成员',
|
|
479
|
-
'chat.members.cap': '已达群成员上限
|
|
486
|
+
'chat.members.cap': '已达群成员上限 {cap} 人——可先在下方调大「成员上限」再添加',
|
|
480
487
|
'chat.settings': '设置',
|
|
481
488
|
'chat.settings.title': '会话设置',
|
|
482
489
|
'chat.settings.name': '名称',
|
|
@@ -489,6 +496,7 @@
|
|
|
489
496
|
'chat.settings.hint': '写入被沙盒限制在工作区(含额外路径)内,读取不受限;同 slug 的 Bot 共享同一目录。',
|
|
490
497
|
'modal.members.title': '管理群成员',
|
|
491
498
|
'modal.members.hint': '勾选的 Bot 为群成员;保存后立即生效(可随时再改)。',
|
|
499
|
+
'modal.members.cap': '成员上限(1–16)',
|
|
492
500
|
'action.delete': '删除',
|
|
493
501
|
'delete.title.bot': '删除 Bot',
|
|
494
502
|
'delete.title.group': '删除群聊',
|
|
@@ -591,7 +599,7 @@
|
|
|
591
599
|
'chat.stop.noop': 'No generation in progress',
|
|
592
600
|
'media.openFailed': 'Open failed — the file may have moved or been deleted',
|
|
593
601
|
'chat.members.manage': 'Manage members',
|
|
594
|
-
'chat.members.cap': 'Group cap is
|
|
602
|
+
'chat.members.cap': 'Group cap is {cap} members — raise the cap below first',
|
|
595
603
|
'chat.settings': 'Settings',
|
|
596
604
|
'chat.settings.title': 'Session settings',
|
|
597
605
|
'chat.settings.name': 'Name',
|
|
@@ -604,6 +612,7 @@
|
|
|
604
612
|
'chat.settings.hint': 'Writes are sandboxed to the workspace (plus extra paths); reads stay unrestricted. Bots sharing a slug share one directory.',
|
|
605
613
|
'modal.members.title': 'Manage group members',
|
|
606
614
|
'modal.members.hint': 'Checked bots are members; changes apply immediately on save (editable again anytime).',
|
|
615
|
+
'modal.members.cap': 'Member cap (1–16)',
|
|
607
616
|
'action.delete': 'Delete',
|
|
608
617
|
'delete.title.bot': 'Delete bot',
|
|
609
618
|
'delete.title.group': 'Delete group chat',
|
|
@@ -810,6 +819,7 @@
|
|
|
810
819
|
manageMembers: null,
|
|
811
820
|
settingsAgentId: null,
|
|
812
821
|
sectionMenu: null,
|
|
822
|
+
rowMenu: null,
|
|
813
823
|
sectionsOpen: null,
|
|
814
824
|
/** Bumped on a language switch so module-scope `t` output re-renders. */
|
|
815
825
|
localeRev: 0,
|
|
@@ -1119,6 +1129,7 @@
|
|
|
1119
1129
|
return e('div', {
|
|
1120
1130
|
key: a.id,
|
|
1121
1131
|
className: 'dbs-srow' + (s.chatAgentId === a.id ? ' dbs-selected' : ''),
|
|
1132
|
+
'data-menu': s.rowMenu === a.id ? 'true' : 'false',
|
|
1122
1133
|
role: 'treeitem',
|
|
1123
1134
|
'aria-selected': s.chatAgentId === a.id,
|
|
1124
1135
|
tabIndex: 0,
|
|
@@ -1134,14 +1145,21 @@
|
|
|
1134
1145
|
? e(StateDot, { state: 'warning', size: 10 })
|
|
1135
1146
|
: unread > 0
|
|
1136
1147
|
? e('span', { className: 'dbs-badge' }, unread > 99 ? '99+' : String(unread))
|
|
1137
|
-
: null,
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1148
|
+
: null,
|
|
1149
|
+
// 更多操作: a single-item menu (删除) replaces the bare trash
|
|
1150
|
+
// button — same affordance as the section headers, room to grow.
|
|
1151
|
+
// The trigger paints NO hover background (user request): color
|
|
1152
|
+
// shift only, so the reveal stays quiet.
|
|
1153
|
+
e('button', {
|
|
1154
|
+
type: 'button', className: 'dbs-rowMore',
|
|
1155
|
+
title: t('list.more'), 'aria-label': t('list.more') + ' ' + a.name,
|
|
1156
|
+
onClick: (ev) => { ev.stopPropagation(); patch({ rowMenu: s.rowMenu === a.id ? null : a.id }); },
|
|
1157
|
+
}, Ico('IconEllipsisOutline16', { size: 14 })), s.rowMenu === a.id
|
|
1158
|
+
? e(React.Fragment, null, e('div', { style: { position: 'fixed', inset: 0, zIndex: 39 }, onClick: () => patch({ rowMenu: null }) }), e('div', { className: 'dbs-secMenu dbs-rowMenu', onClick: (ev) => { ev.stopPropagation(); } }, e('button', {
|
|
1159
|
+
className: 'dbs-secMenuItem', type: 'button',
|
|
1160
|
+
onClick: () => { patch({ rowMenu: null, confirmDelete: { id: a.id, name: a.name, isGroup: a.isGroup === true } }); },
|
|
1161
|
+
}, Ico('IconTrashOutline16', { size: 14 }), t('action.delete'))))
|
|
1162
|
+
: null);
|
|
1145
1163
|
}
|
|
1146
1164
|
/**
|
|
1147
1165
|
* Section header + rows. The header always renders (even when the
|
|
@@ -1803,7 +1821,13 @@
|
|
|
1803
1821
|
? e('div', { className: 'dbs-welcome' }, agent !== null ? e(Avatar, { agent, size: 44 }) : null, e('div', { className: 'dbs-welcomeName' }, agent?.name ?? ''), typeof agent?.description === 'string' && agent.description !== ''
|
|
1804
1822
|
? e('div', { className: 'dbs-welcomeDesc' }, agent.description) : null, e('div', { className: 'dbs-welcomeHint' }, isGroup ? t('chat.empty.group') : t('chat.empty.single', { name: agent?.name ?? '' })))
|
|
1805
1823
|
: thread, composing
|
|
1806
|
-
? e('div', { className: 'dbs-typing', 'aria-label': t('chat.composing'), title: t('chat.composing') },
|
|
1824
|
+
? e('div', { className: 'dbs-typing', 'aria-label': t('chat.composing'), title: t('chat.composing') },
|
|
1825
|
+
// Native ongoing state: the StateDot chase matrix
|
|
1826
|
+
// (DeepSeek-blue cells, 1s cycle) — the exact icon the
|
|
1827
|
+
// shell uses while a run is in flight.
|
|
1828
|
+
StateDot !== null
|
|
1829
|
+
? e(StateDot, { state: 'ongoing', size: 12 })
|
|
1830
|
+
: e('span', { className: 'dbs-typingDot' }), e('span', { className: 'dbs-typingText' }, t('chat.composing')))
|
|
1807
1831
|
: null))), e('button', {
|
|
1808
1832
|
type: 'button', className: 'dbs-jump', 'data-show': showJump ? 'true' : 'false',
|
|
1809
1833
|
'aria-label': t('chat.jump'), title: t('chat.jump'),
|
|
@@ -2191,6 +2215,7 @@
|
|
|
2191
2215
|
const [picked, setPicked] = React.useState(null);
|
|
2192
2216
|
const [err, setErr] = React.useState(null);
|
|
2193
2217
|
const [working, setWorking] = React.useState(false);
|
|
2218
|
+
const [capText, setCapText] = React.useState('8');
|
|
2194
2219
|
// Seed once per opened group; a group vanishing mid-edit (deleted by
|
|
2195
2220
|
// the swarm, say) just renders the modal inert until closed.
|
|
2196
2221
|
React.useEffect(() => {
|
|
@@ -2200,23 +2225,24 @@
|
|
|
2200
2225
|
for (const id of group.memberIds ?? [])
|
|
2201
2226
|
init[id] = true;
|
|
2202
2227
|
setPicked(init);
|
|
2228
|
+
setCapText(String(group.maxMembers ?? 8));
|
|
2203
2229
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2204
2230
|
}, [s.manageMembers]);
|
|
2205
2231
|
if (group === null || group === undefined)
|
|
2206
2232
|
return null;
|
|
2207
2233
|
const singles = s.agents.filter((a) => !a.isGroup && a.isHiddenFromSidebar !== true);
|
|
2208
2234
|
const pickedIds = picked === null ? [] : Object.keys(picked).filter((k) => picked[k]);
|
|
2209
|
-
//
|
|
2210
|
-
// slice(0,
|
|
2211
|
-
// successfully and just drops whoever ranked past
|
|
2212
|
-
// instead of letting a save pretend to work.
|
|
2213
|
-
const MEMBER_CAP =
|
|
2235
|
+
// Per-group cap (group.json maxMembers, default 8, engine hard max 16):
|
|
2236
|
+
// the glue slice(0, maxMembers)-truncates the roster SILENTLY — an
|
|
2237
|
+
// over-cap save writes successfully and just drops whoever ranked past
|
|
2238
|
+
// the cap. Surface it here instead of letting a save pretend to work.
|
|
2239
|
+
const MEMBER_CAP = Math.min(16, Math.max(1, Math.floor(Number(capText)) || 8));
|
|
2214
2240
|
const overCap = pickedIds.length > MEMBER_CAP;
|
|
2215
2241
|
function toggle(id) {
|
|
2216
2242
|
if (picked === null)
|
|
2217
2243
|
return;
|
|
2218
2244
|
if (picked[id] !== true && pickedIds.length >= MEMBER_CAP) {
|
|
2219
|
-
setErr(t('chat.members.cap'));
|
|
2245
|
+
setErr(t('chat.members.cap', { cap: MEMBER_CAP }));
|
|
2220
2246
|
return;
|
|
2221
2247
|
}
|
|
2222
2248
|
setErr(null);
|
|
@@ -2228,7 +2254,7 @@
|
|
|
2228
2254
|
setWorking(true);
|
|
2229
2255
|
setErr(null);
|
|
2230
2256
|
try {
|
|
2231
|
-
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds });
|
|
2257
|
+
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds, maxMembers: MEMBER_CAP });
|
|
2232
2258
|
patch({ manageMembers: null });
|
|
2233
2259
|
await refreshAgents();
|
|
2234
2260
|
return;
|
|
@@ -2251,7 +2277,10 @@
|
|
|
2251
2277
|
disabled: working,
|
|
2252
2278
|
icon: Ico('IconCloseOutline16', { size: 16 }),
|
|
2253
2279
|
onClick: () => patch({ manageMembers: null }),
|
|
2254
|
-
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')),
|
|
2280
|
+
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')), e('div', { className: 'dbs-setrow', style: { padding: '0 2px 8px', gap: 8 } }, e('span', { className: 'dbs-meta' }, t('modal.members.cap')), e(Input, {
|
|
2281
|
+
value: capText, disabled: working, style: { width: 72 },
|
|
2282
|
+
onChange: (ev) => setCapText(ev.target.value),
|
|
2283
|
+
})), picked === null
|
|
2255
2284
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.loading'))
|
|
2256
2285
|
: e('div', { className: 'dbs-modalMembers' }, singles.length === 0
|
|
2257
2286
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.empty'))
|
|
@@ -2436,6 +2465,10 @@
|
|
|
2436
2465
|
if (ev.key !== 'Escape')
|
|
2437
2466
|
return;
|
|
2438
2467
|
// Topmost dialog first: delete confirm, then create, then chat.
|
|
2468
|
+
if (state.rowMenu !== null) {
|
|
2469
|
+
patch({ rowMenu: null });
|
|
2470
|
+
return;
|
|
2471
|
+
}
|
|
2439
2472
|
if (state.confirmDelete !== null) {
|
|
2440
2473
|
patch({ confirmDelete: null });
|
|
2441
2474
|
return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bots",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.24",
|
|
4
4
|
"description": "Multi-bot workbench for DeepSeek Harness: bridges the sdk-bots orchestration gateway (group chats, single bots) into the dsh web shell with official-styled UI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -28,11 +28,15 @@
|
|
|
28
28
|
"test:dsh-smoke": "bash scripts/dsh-smoke.sh"
|
|
29
29
|
},
|
|
30
30
|
"keywords": [
|
|
31
|
+
"bots",
|
|
32
|
+
"cordis",
|
|
31
33
|
"deepseek-harness",
|
|
32
34
|
"dsh-plugin",
|
|
33
|
-
"
|
|
35
|
+
"dsh-plugins",
|
|
36
|
+
"group-chat",
|
|
37
|
+
"mcp",
|
|
34
38
|
"multibot",
|
|
35
|
-
"
|
|
39
|
+
"workbench"
|
|
36
40
|
],
|
|
37
41
|
"license": "MIT",
|
|
38
42
|
"peerDependencies": {
|
|
@@ -53,5 +57,13 @@
|
|
|
53
57
|
"client": {
|
|
54
58
|
"platform": "web"
|
|
55
59
|
}
|
|
60
|
+
},
|
|
61
|
+
"repository": {
|
|
62
|
+
"type": "git",
|
|
63
|
+
"url": "git+ssh://git@github.com/dushaobindoudou/dsh-bots.git"
|
|
64
|
+
},
|
|
65
|
+
"homepage": "https://github.com/dushaobindoudou/dsh-bots#readme",
|
|
66
|
+
"bugs": {
|
|
67
|
+
"url": "https://github.com/dushaobindoudou/dsh-bots/issues"
|
|
56
68
|
}
|
|
57
69
|
}
|