bothouse-dsh 0.1.58 → 0.1.60
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.en.md +3 -2
- package/README.md +3 -2
- package/collab/dsh-member.mjs +1 -1
- package/collab/hub.mjs +17 -4
- package/collab/page.html +62 -3
- package/collab/plugin.mjs +2 -2
- package/collab/quota-cooling.mjs +4 -4
- package/collab/roster.mjs +3 -2
- package/collab/store.mjs +7 -5
- package/node_modules/bothouse-dsh-channels/client.js +5 -6
- package/package.json +4 -7
package/README.en.md
CHANGED
|
@@ -7,14 +7,14 @@ BotHouse channels inside DeepSeek Harness (DSH): a Discord-style channels page w
|
|
|
7
7
|
## Install (pick one)
|
|
8
8
|
|
|
9
9
|
1. **Easiest**: tell DSH "install the plugin bothouse-dsh".
|
|
10
|
-
2. **From the UI**: DSH sidebar "Plugins" → "Add plugin", enter `bothouse-dsh
|
|
10
|
+
2. **From the UI**: DSH sidebar "Plugins" → "Add plugin", enter `bothouse-dsh`, click "Install"; then switch bothouse on under "Installed plugins" — "Channels" appears in the sidebar right away, no restart.
|
|
11
11
|
3. **From a terminal**:
|
|
12
12
|
|
|
13
13
|
~~~sh
|
|
14
14
|
dsh plugin --profile web add bothouse-dsh
|
|
15
15
|
~~~
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Asked through DSH, "Channels" appears right after the install; installed from the command line while DSH is running, restart DSH once. Then click "Channels" in the sidebar. Team desk, bothouse panel and other computers start off; switch the one you want on under "Plugins → bothouse" and it appears in the sidebar right away. #general already has Whale Girl, running on the model you configured in DSH; nothing else to fill in. @ her to get a reply; tell her "hand this to a workspace: …" and she opens a workspace, does the work and reports back. For more characters or models, use "Add member".
|
|
18
18
|
|
|
19
19
|
If the terminal install says `pnpm was not found`, run `npm i -g pnpm` first.
|
|
20
20
|
|
|
@@ -23,6 +23,7 @@ If the terminal install says `pnpm was not found`, run `npm i -g pnpm` first.
|
|
|
23
23
|
- DSH 0.2.x (from `0.2.1-rc.2`, see `dsh --version`) and Node.js 22.12 or later. Tested on 0.2.1-rc.2 only; no compatibility promise for the DSH plugin API was found and the page is prebuilt against the DSH 0.2 client packages, so the range stops before 0.3.
|
|
24
24
|
- Model members: configure the model and its key in DSH settings first.
|
|
25
25
|
- CLI members: install and sign in to Claude Code, Codex or the Gemini CLI, with the command on PATH.
|
|
26
|
+
The Claude Code and Codex adapters are not installed with the plugin; they download the first time you add such a member (Claude Code about 125 MB).
|
|
26
27
|
- Channel messages are stored in `~/.conest-collab/collab.sqlite3`; set `CONEST_COLLAB_DATA` before starting DSH to use another directory.
|
|
27
28
|
|
|
28
29
|
## Compared with full BotHouse
|
package/README.md
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
## 安装(三选一)
|
|
8
8
|
|
|
9
9
|
1. **最省事**:在 DSH 里对它说「帮我装插件 bothouse-dsh」。
|
|
10
|
-
2. **点界面**:DSH 侧栏「插件」→「添加插件」,填 `bothouse-dsh
|
|
10
|
+
2. **点界面**:DSH 侧栏「插件」→「添加插件」,填 `bothouse-dsh`,点「安装」;装完在「已安装插件」里把 bothouse 的开关打开,左侧马上出现「频道」,不用重启。
|
|
11
11
|
3. **命令行**:
|
|
12
12
|
|
|
13
13
|
~~~sh
|
|
14
14
|
dsh plugin --profile web add bothouse-dsh
|
|
15
15
|
~~~
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
对 DSH 说的装法装完左侧直接出现「频道」;命令行装时 DSH 若正开着,重启一次。然后点左侧「频道」。团队工作台、bothouse 面板、其他电脑默认不在左侧,要用就到「插件 → bothouse」里打开对应那一项,左侧马上出现。#general 里已经有鲸鱼娘,用的是你在 DSH 里配好的那个模型,不用再填任何东西。@鲸鱼娘 就回话;对她说「交给工作区做:……」,她会开工作区干完再交回。想要多角色、多模型,再点「添加成员」。
|
|
18
18
|
|
|
19
19
|
命令行装时提示 `pnpm was not found`:先 `npm i -g pnpm` 再装。
|
|
20
20
|
|
|
@@ -23,6 +23,7 @@ dsh plugin --profile web add bothouse-dsh
|
|
|
23
23
|
- DSH 0.2.x(`0.2.1-rc.2` 起,`dsh --version`)和 Node.js 22.12 以上。只在 0.2.1-rc.2 上实测过;DSH 插件接口有没有兼容承诺没查到,页面又是按 DSH 0.2 的前端包预编译的,所以先只放到 0.2.x,0.3 出来要重新验。
|
|
24
24
|
- 用模型当成员:在 DSH 设置里先配好模型和 Key。
|
|
25
25
|
- 用 CLI 当成员:本机装好并登录 Claude Code、Codex 或 Gemini CLI,命令在 PATH 里。
|
|
26
|
+
Claude Code、Codex 的接入适配器不随插件安装,第一次把它们加成成员时自动下载(Claude Code 约 125 MB),之后不再下。
|
|
26
27
|
- 频道消息存在 `~/.conest-collab/collab.sqlite3`;启动 DSH 前设置 `CONEST_COLLAB_DATA` 可换目录。
|
|
27
28
|
|
|
28
29
|
## 和完整版 BotHouse 的区别
|
package/collab/dsh-member.mjs
CHANGED
|
@@ -597,7 +597,7 @@ export class DshMembers {
|
|
|
597
597
|
const answered = this.approvals.answerByText(msg);
|
|
598
598
|
const hit = mentions(msg.text, this.hub.routableIn(msg.room_id));
|
|
599
599
|
const only = this.hub.replyRoute(msg);
|
|
600
|
-
const leadID = this.hub.
|
|
600
|
+
const leadID = this.hub.defaultReplyIn(msg.room_id)?.id;
|
|
601
601
|
const leadTarget = msg.author_kind === 'user' && leadID !== undefined && target === undefined && only === null && hit.size === 0;
|
|
602
602
|
const visible = new Set(this.hub.routableIn(msg.room_id).map(m => m.id));
|
|
603
603
|
const targets = [...this.config.keys()].filter(id => !this.hub.quota?.cooling(id) && visible.has(id) && id !== msg.author && !answered.has(id) && (!target || target === id)
|
package/collab/hub.mjs
CHANGED
|
@@ -48,9 +48,8 @@ export class Hub {
|
|
|
48
48
|
return member !== undefined && member.kind !== 'human' ? member : undefined;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
/** Channel
|
|
51
|
+
/** Channel primary for delegation; unset channels retain the selected global role. */
|
|
52
52
|
primaryIn(roomId) {
|
|
53
|
-
// #1023: in a private chat the one agent answers every owner message, @ or not.
|
|
54
53
|
const dm = this.store.dmMember?.(roomId);
|
|
55
54
|
if (dm !== undefined) return this.routableIn(roomId).find(m => m.id === dm);
|
|
56
55
|
const id = this.store.leadId(roomId);
|
|
@@ -58,7 +57,18 @@ export class Hub {
|
|
|
58
57
|
return member && this.routableIn(roomId).some(m => m.id === member.id) ? member : undefined;
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
/**
|
|
60
|
+
/** Default replies are channel-local and do not change delegation authority. */
|
|
61
|
+
defaultReplyIn(roomId) {
|
|
62
|
+
// #1023: in a private chat the one agent answers every owner message, @ or not.
|
|
63
|
+
const dm = this.store.dmMember?.(roomId);
|
|
64
|
+
if (dm !== undefined) return this.routableIn(roomId).find(m => m.id === dm);
|
|
65
|
+
const id = this.store.leadId(roomId);
|
|
66
|
+
const members = this.routableIn(roomId);
|
|
67
|
+
if (id === null) return members.length === 1 ? members[0] : undefined;
|
|
68
|
+
return members.find(member => member.id === id);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** UI-only fallback; message routing uses defaultReplyIn(), delegation uses primaryIn(). */
|
|
62
72
|
primaryMember() {
|
|
63
73
|
const fallback = this.fallbackLead === '' ? undefined : this.members.get(this.fallbackLead);
|
|
64
74
|
return this.selectedPrimaryMember() ?? (fallback?.kind !== 'human' ? fallback : undefined);
|
|
@@ -68,6 +78,8 @@ export class Hub {
|
|
|
68
78
|
if (member.avatar?.startsWith('upload:')) member.avatarVersion = Math.random().toString(36).slice(2);
|
|
69
79
|
this.members.set(member.id, member);
|
|
70
80
|
this.emit({ type: 'members', members: this.memberView() });
|
|
81
|
+
// Membership determines an unset channel's effective primary, including its workspaces.
|
|
82
|
+
this.emit({ type: 'rooms', rooms: this.store.roomsView() });
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
removeMember(id) {
|
|
@@ -76,6 +88,7 @@ export class Hub {
|
|
|
76
88
|
for (const room of this.thinking.keys()) this.setThinking(room, id, false);
|
|
77
89
|
for (const room of [...this.queued.keys()]) this.setQueue(room, id, []);
|
|
78
90
|
this.emit({ type: 'members', members: this.memberView() });
|
|
91
|
+
this.emit({ type: 'rooms', rooms: this.store.roomsView() });
|
|
79
92
|
}
|
|
80
93
|
|
|
81
94
|
setOnline(id, online) {
|
|
@@ -283,7 +296,7 @@ export class Hub {
|
|
|
283
296
|
|
|
284
297
|
emit(event) {
|
|
285
298
|
if (event.type === 'rooms') event = { ...event, rooms: event.rooms.map(room => ({ ...room,
|
|
286
|
-
lead_id: this.store.leadId(room.id), effective_lead_id: this.
|
|
299
|
+
lead_id: this.store.leadId(room.id), effective_lead_id: this.defaultReplyIn(room.id)?.id ?? null,
|
|
287
300
|
workspace_path: this.store.workspacePath(room.id, this.workspacePath),
|
|
288
301
|
})) };
|
|
289
302
|
for (const s of this.subscribers) {
|
package/collab/page.html
CHANGED
|
@@ -1026,6 +1026,26 @@ function messageNode(m) {
|
|
|
1026
1026
|
}
|
|
1027
1027
|
card.append(bar);
|
|
1028
1028
|
}
|
|
1029
|
+
// 与原生频道页共用重试接口;后端把已受理的失败卡更新成 retried。
|
|
1030
|
+
if (meta.state === 'failed') {
|
|
1031
|
+
const bar = el('div', 'actions');
|
|
1032
|
+
const retry = el('button', '', '重试');
|
|
1033
|
+
retry.type = 'button';
|
|
1034
|
+
retry.onclick = async () => {
|
|
1035
|
+
retry.disabled = true;
|
|
1036
|
+
retry.textContent = '重试中…';
|
|
1037
|
+
try {
|
|
1038
|
+
await api(`${ROOT}/collab/api/messages/${m.id}/retry`, { method: 'POST' });
|
|
1039
|
+
retry.textContent = '已重试';
|
|
1040
|
+
} catch (err) {
|
|
1041
|
+
retry.disabled = false;
|
|
1042
|
+
retry.textContent = '重试';
|
|
1043
|
+
alert('重试失败:' + err.message);
|
|
1044
|
+
}
|
|
1045
|
+
};
|
|
1046
|
+
bar.append(retry);
|
|
1047
|
+
card.append(bar);
|
|
1048
|
+
}
|
|
1029
1049
|
col.append(card);
|
|
1030
1050
|
} else if (m.text) col.append(renderBody(m.text, 'body'));
|
|
1031
1051
|
if (Array.isArray(m.attachments) && m.attachments.length) col.append(attachmentList(m.attachments));
|
|
@@ -1151,7 +1171,43 @@ function showState(kind, title, detail, retry) {
|
|
|
1151
1171
|
return box;
|
|
1152
1172
|
}
|
|
1153
1173
|
|
|
1174
|
+
// 来源:openRoom 原有每次读取 100 条;向上分页沿用同一页大小。
|
|
1175
|
+
const HISTORY_LIMIT = 100;
|
|
1176
|
+
let historyPage;
|
|
1177
|
+
|
|
1178
|
+
async function loadOlder() {
|
|
1179
|
+
const page = historyPage;
|
|
1180
|
+
if (!page || page.loading || !page.more) return;
|
|
1181
|
+
page.loading = true;
|
|
1182
|
+
const log = $('log');
|
|
1183
|
+
log.querySelector('.history-error')?.remove();
|
|
1184
|
+
try {
|
|
1185
|
+
const { messages } = await api(`${ROOT}/collab/api/rooms/${page.room}/messages?before=${page.before}&limit=${HISTORY_LIMIT}`);
|
|
1186
|
+
if (historyPage !== page) return;
|
|
1187
|
+
const oldHeight = log.scrollHeight;
|
|
1188
|
+
const oldTop = log.scrollTop;
|
|
1189
|
+
messages.forEach(upsert);
|
|
1190
|
+
if (messages.length) page.before = messages[0].id;
|
|
1191
|
+
page.more = messages.length === HISTORY_LIMIT;
|
|
1192
|
+
log.scrollTop = oldTop + log.scrollHeight - oldHeight;
|
|
1193
|
+
} catch (err) {
|
|
1194
|
+
if (historyPage !== page) return;
|
|
1195
|
+
const retry = el('button', 'btn history-error', '历史加载失败,点击重试');
|
|
1196
|
+
retry.type = 'button';
|
|
1197
|
+
retry.title = err.message;
|
|
1198
|
+
retry.onclick = () => loadOlder();
|
|
1199
|
+
log.prepend(retry);
|
|
1200
|
+
} finally {
|
|
1201
|
+
page.loading = false;
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
$('log').addEventListener('scroll', () => {
|
|
1206
|
+
if ($('log').scrollTop === 0) void loadOlder();
|
|
1207
|
+
}, { passive: true });
|
|
1208
|
+
|
|
1154
1209
|
async function openRoom(id) {
|
|
1210
|
+
const page = historyPage = { room: id, before: undefined, more: false, loading: true };
|
|
1155
1211
|
state.room = id; state.synced = 0;
|
|
1156
1212
|
$('log').replaceChildren(showState('loading', '正在加载消息…'));
|
|
1157
1213
|
$('log').setAttribute('aria-busy', 'true');
|
|
@@ -1161,11 +1217,14 @@ async function openRoom(id) {
|
|
|
1161
1217
|
const setting = await api(`${ROOT}/collab/api/rooms/${id}/mode`);
|
|
1162
1218
|
state.mode = setting.dispatch_mode;
|
|
1163
1219
|
renderRooms(); renderMembers();
|
|
1164
|
-
const { messages } = await api(`${ROOT}/collab/api/rooms/${id}/messages?limit
|
|
1165
|
-
if (
|
|
1220
|
+
const { messages } = await api(`${ROOT}/collab/api/rooms/${id}/messages?limit=${HISTORY_LIMIT}`);
|
|
1221
|
+
if (historyPage !== page) return;
|
|
1166
1222
|
const log = $('log');
|
|
1167
1223
|
log.querySelector('.state')?.remove();
|
|
1168
1224
|
messages.forEach(upsert);
|
|
1225
|
+
page.before = messages[0]?.id;
|
|
1226
|
+
page.more = messages.length === HISTORY_LIMIT;
|
|
1227
|
+
page.loading = false;
|
|
1169
1228
|
state.synced = messages.length ? messages[messages.length - 1].id : 0;
|
|
1170
1229
|
if (!log.querySelector('.msg')) log.append(showState('empty', '还没有消息', ''));
|
|
1171
1230
|
$('log').setAttribute('aria-busy', 'false');
|
|
@@ -1173,7 +1232,7 @@ async function openRoom(id) {
|
|
|
1173
1232
|
stuck = true;
|
|
1174
1233
|
$('log').scrollTop = $('log').scrollHeight;
|
|
1175
1234
|
} catch (err) {
|
|
1176
|
-
if (
|
|
1235
|
+
if (historyPage !== page) return;
|
|
1177
1236
|
$('log').replaceChildren(showState('error', '加载失败', err.message, () => openRoom(id)));
|
|
1178
1237
|
$('log').setAttribute('aria-busy', 'false');
|
|
1179
1238
|
}
|
package/collab/plugin.mjs
CHANGED
|
@@ -346,7 +346,7 @@ export async function apply(ctx, rawConfig) {
|
|
|
346
346
|
if (path === '/collab/api/state' && req.method === 'GET') {
|
|
347
347
|
return json(res, 200, { me: OWNER, lead_id: hub.selectedPrimaryMember()?.id ?? null,
|
|
348
348
|
effective_lead_id: hub.primaryMember()?.id ?? null,
|
|
349
|
-
rooms: store.roomsView().map(r => ({ ...r, lead_id: store.leadId(r.id), effective_lead_id: hub.
|
|
349
|
+
rooms: store.roomsView().map(r => ({ ...r, lead_id: store.leadId(r.id), effective_lead_id: hub.defaultReplyIn(r.id)?.id ?? null,
|
|
350
350
|
workspace_path: store.workspacePath(r.id, config.workspacePath) })), members: hub.memberView(),
|
|
351
351
|
thinking: Object.fromEntries([...hub.thinking].map(([k, v]) => [k, [...v]])),
|
|
352
352
|
queued: Object.fromEntries([...hub.queued.keys()].map(r => [r, hub.queueView(r)])),
|
|
@@ -573,7 +573,7 @@ export function roomSettingsRoute(req, res, path, { store, hub, dsh, workspacePa
|
|
|
573
573
|
if (match === null) return undefined;
|
|
574
574
|
const roomId = Number(match[1]);
|
|
575
575
|
if (store.room(roomId) === undefined) return Promise.resolve(json(res, 404, { error: '频道不存在' }));
|
|
576
|
-
const view = () => ({ lead_id: store.leadId(roomId), effective_lead_id: hub.
|
|
576
|
+
const view = () => ({ lead_id: store.leadId(roomId), effective_lead_id: hub.defaultReplyIn(roomId)?.id ?? null,
|
|
577
577
|
workspace_path: store.workspacePath(roomId, workspacePath), max_relay: store.relaySetting(roomId).max_relay });
|
|
578
578
|
if (match[2] === 'renew' && req.method === 'POST') {
|
|
579
579
|
return withBody(req, res, async () => ({ members: await dsh.renewRoom(roomId) }), false);
|
package/collab/quota-cooling.mjs
CHANGED
|
@@ -108,7 +108,7 @@ export class QuotaCooling {
|
|
|
108
108
|
if (!quota.cooling_until && !state.until) continue;
|
|
109
109
|
if (!this.cooling(member.id)) {
|
|
110
110
|
for (const [room, lead] of Object.entries(state.leads ?? {})) {
|
|
111
|
-
if (this.hub.
|
|
111
|
+
if (this.hub.defaultReplyIn(Number(room))?.id === lead.to) {
|
|
112
112
|
this.hub.store.setLead(Number(room), member.id);
|
|
113
113
|
this.notice(Number(room), `${member.id}-${state.until}-restore-${room}`, `${member.name} 额度恢复,主角色已切回。`);
|
|
114
114
|
}
|
|
@@ -124,7 +124,7 @@ export class QuotaCooling {
|
|
|
124
124
|
const key = `${member.id}-${state.until}`;
|
|
125
125
|
const reserved = new Set(Object.values(state.transfers).filter(t => !t.woken).map(t => t.to));
|
|
126
126
|
for (const room of this.hub.store.rooms().filter(r => r.kind !== 'workspace')) {
|
|
127
|
-
if (state.leads[room.id] || this.hub.
|
|
127
|
+
if (state.leads[room.id] || this.hub.defaultReplyIn(room.id)?.id !== member.id) continue;
|
|
128
128
|
const target = this.candidate(room.id, '', member.id, reserved);
|
|
129
129
|
if (!target) continue;
|
|
130
130
|
state.leads[room.id] = { to: target.id, woken: false };
|
|
@@ -135,8 +135,8 @@ export class QuotaCooling {
|
|
|
135
135
|
for (const [room, lead] of Object.entries(state.leads)) {
|
|
136
136
|
if (lead.woken || this.cooling(lead.to)) continue;
|
|
137
137
|
// Replay a crash between persisting intent and changing the selection.
|
|
138
|
-
if (this.hub.
|
|
139
|
-
if (this.hub.
|
|
138
|
+
if (this.hub.defaultReplyIn(Number(room))?.id === member.id) this.hub.store.setLead(Number(room), lead.to);
|
|
139
|
+
if (this.hub.defaultReplyIn(Number(room))?.id !== lead.to) continue;
|
|
140
140
|
const msg = this.notice(Number(room), `${key}-lead-${room}`, `${member.name} 额度冷却,主角色交给 ${this.tasks.nameOf(lead.to)}。请接着推进未完成任务。`);
|
|
141
141
|
if (this.wake(lead.to, msg)) { lead.woken = true; this.save(member.id, state, true); }
|
|
142
142
|
}
|
package/collab/roster.mjs
CHANGED
|
@@ -208,7 +208,7 @@ export class Roster {
|
|
|
208
208
|
this.avatars?.remove(id);
|
|
209
209
|
this.store.removeMember(id);
|
|
210
210
|
// #176: a dead id must not linger as a channel's primary role.
|
|
211
|
-
for (const roomId of this.store.clearLeadOf(id)) this.hub.emit({ type: 'room-settings', room_id: this.store.settingsRoom(roomId), lead_id: null, effective_lead_id: this.hub.
|
|
211
|
+
for (const roomId of this.store.clearLeadOf(id)) this.hub.emit({ type: 'room-settings', room_id: this.store.settingsRoom(roomId), lead_id: null, effective_lead_id: this.hub.defaultReplyIn(roomId)?.id ?? null });
|
|
212
212
|
this.dsh.dropMember(id);
|
|
213
213
|
}
|
|
214
214
|
|
|
@@ -252,7 +252,8 @@ export class Roster {
|
|
|
252
252
|
throw new CollabError('invalid', 'rooms must contain existing chat channel ids');
|
|
253
253
|
}
|
|
254
254
|
const clash = this.hub.memberList().find(m => m.id !== selfId && (!rooms.length || !m.rooms?.length || rooms.some(id => m.rooms.includes(id))) && (m.name.toLowerCase() === name.toLowerCase() || m.id.toLowerCase() === name.toLowerCase()));
|
|
255
|
-
|
|
255
|
+
// #366: 这条会原样显示在成员对话框的错误条里,和同页的成员名校验一样说中文。
|
|
256
|
+
if (clash !== undefined) throw new CollabError('conflict', `名字 ${name} 已被 ${clash.name} 使用`);
|
|
256
257
|
const persona = String(input?.persona ?? '');
|
|
257
258
|
if (persona.length > MAX_PERSONA) throw new CollabError('invalid', `persona exceeds ${MAX_PERSONA} characters`);
|
|
258
259
|
let avatar;
|
package/collab/store.mjs
CHANGED
|
@@ -393,19 +393,21 @@ export class Store {
|
|
|
393
393
|
* workspace is addressed, so a duplicate or a workspace-shaped name is refused with 'conflict'.
|
|
394
394
|
*/
|
|
395
395
|
checkRoomName(name, selfId) {
|
|
396
|
-
|
|
396
|
+
// #366: 这两句会原样显示在客户端改名/新建对话框的错误条里,因此和同页其它校验一样说中文。
|
|
397
|
+
if (new RegExp(`^${WORKSPACE_PREFIX}\\d+$`).test(name)) throw new CollabError('conflict', `${name} 是工作区保留名,换个名字`);
|
|
397
398
|
// A private chat is named after its member and listed with its avatar, so it does not clash.
|
|
398
399
|
const clash = this.rooms().find(r => r.id !== selfId && r.kind === 'chat' && r.dm_member == null && r.name.toLowerCase() === name.toLowerCase());
|
|
399
|
-
if (clash !== undefined) throw new CollabError('conflict',
|
|
400
|
+
if (clash !== undefined) throw new CollabError('conflict', `频道名 ${name} 已被占用`);
|
|
400
401
|
}
|
|
401
402
|
/** Rename the channel without changing any room, message or session identity. */
|
|
402
403
|
renameRoom(id, name) {
|
|
403
404
|
if (typeof name !== 'string' || !name || name.length > 32 || /\s/.test(name)) {
|
|
404
|
-
throw new CollabError('invalid', '
|
|
405
|
+
throw new CollabError('invalid', '频道名 1–32 个字符,且不能有空格');
|
|
405
406
|
}
|
|
406
407
|
const room = this.room(id);
|
|
407
|
-
if (room === undefined) throw new CollabError('room-missing',
|
|
408
|
-
|
|
408
|
+
if (room === undefined) throw new CollabError('room-missing', `频道 ${id} 不存在`);
|
|
409
|
+
// 工作区名是系统分配的(工作区<n>),改名会和一个地址撞名,所以服务端直接拒绝。
|
|
410
|
+
if (room.kind === 'workspace') throw new CollabError('invalid', '工作区名称由系统分配,不能修改');
|
|
409
411
|
this.checkRoomName(name, id);
|
|
410
412
|
return this.db.prepare('UPDATE rooms SET name = ? WHERE id = ? RETURNING id, name, kind, parent_id').get(name, id);
|
|
411
413
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// dsh-client-source-sha256:
|
|
1
|
+
// dsh-client-source-sha256: def898ca7c3b5cbc9806630df8269a00d441300d83397256c948bcbf8313e7f7
|
|
2
2
|
// dsh-style-sha256: f6a6de6373a2ead4c3a965e58c158c16626e49a16b91d2d568b35b19fcfdb95a
|
|
3
3
|
window.__ModuleLoader__.load({
|
|
4
4
|
id: "bothouse-dsh-channels",
|
|
@@ -20541,13 +20541,13 @@ function ChannelSettingsDialog({ room, members, effectiveLead, renameRoom, setDi
|
|
|
20541
20541
|
const [name, setName] = (0, react.useState)(room.name);
|
|
20542
20542
|
const [mode, setMode] = (0, react.useState)(room.dispatch_mode ?? "mixed");
|
|
20543
20543
|
const [relay, setRelay] = (0, react.useState)(String(room.max_relay ?? RELAY_CAP_MIN));
|
|
20544
|
-
const [lead, setLeadDraft] = (0, react.useState)(effectiveLead);
|
|
20544
|
+
const [lead, setLeadDraft] = (0, react.useState)(room.lead_id === void 0 ? effectiveLead : room.lead_id ?? "");
|
|
20545
20545
|
const [directory, setDirectory] = (0, react.useState)(room.workspace_path ?? "");
|
|
20546
20546
|
const [saved, setSaved] = (0, react.useState)({
|
|
20547
20547
|
name: room.name,
|
|
20548
20548
|
mode: room.dispatch_mode ?? "mixed",
|
|
20549
20549
|
relay: String(room.max_relay ?? RELAY_CAP_MIN),
|
|
20550
|
-
lead: effectiveLead,
|
|
20550
|
+
lead: room.lead_id === void 0 ? effectiveLead : room.lead_id ?? "",
|
|
20551
20551
|
directory: room.workspace_path ?? ""
|
|
20552
20552
|
});
|
|
20553
20553
|
const [saving, setSaving] = (0, react.useState)({});
|
|
@@ -20776,7 +20776,6 @@ function ChannelSettingsDialog({ room, members, effectiveLead, renameRoom, setDi
|
|
|
20776
20776
|
},
|
|
20777
20777
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
20778
20778
|
value: "",
|
|
20779
|
-
disabled: true,
|
|
20780
20779
|
children: t("leadNone")
|
|
20781
20780
|
}), members.filter((member) => member.kind !== "human").map((member) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
20782
20781
|
value: member.id,
|
|
@@ -24431,7 +24430,7 @@ const zh = {
|
|
|
24431
24430
|
dispatchTeam: "完全团队 — 交给团队成员协作",
|
|
24432
24431
|
dispatchMixed: "混合 — 按任务选择模型与成员",
|
|
24433
24432
|
lead: "主角色",
|
|
24434
|
-
leadNone: "
|
|
24433
|
+
leadNone: "未选择(单成员自动接话,多成员不接话)",
|
|
24435
24434
|
durSeconds: "{n} 秒",
|
|
24436
24435
|
durMinutes: "{n} 分钟",
|
|
24437
24436
|
durHours: "{h} 小时 {m} 分",
|
|
@@ -24715,7 +24714,7 @@ const en = {
|
|
|
24715
24714
|
taskReviewReason: "Reason for rework (required)",
|
|
24716
24715
|
taskReviewSubmit: "Submit rework",
|
|
24717
24716
|
lead: "Primary role",
|
|
24718
|
-
leadNone: "
|
|
24717
|
+
leadNone: "No selection (one member replies; multiple stay quiet)",
|
|
24719
24718
|
durSeconds: "{n}s",
|
|
24720
24719
|
durMinutes: "{n} min",
|
|
24721
24720
|
durHours: "{h} h {m} min",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bothouse-dsh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.60",
|
|
4
4
|
"description": "BotHouse channels for DeepSeek Harness: Discord-style rooms where DSH models and local CLIs (Claude Code, Codex, Gemini) are members",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./collab/plugin.mjs",
|
|
@@ -51,20 +51,17 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@agentclientprotocol/sdk": "1.4.0",
|
|
54
|
-
"@zed-industries/claude-code-acp": "^0.16.2",
|
|
55
54
|
"bothouse-dsh-channels": "0.1.0",
|
|
56
55
|
"bothouse-dsh-desk": "0.1.0",
|
|
57
56
|
"bothouse-dsh-devices": "0.1.0",
|
|
58
57
|
"bothouse-dsh-panel": "0.1.0",
|
|
59
|
-
"ws": "8.22.1"
|
|
58
|
+
"ws": "8.22.1",
|
|
59
|
+
"zod": "^4.0.0"
|
|
60
60
|
},
|
|
61
61
|
"bundleDependencies": [
|
|
62
62
|
"bothouse-dsh-channels",
|
|
63
63
|
"bothouse-dsh-desk",
|
|
64
64
|
"bothouse-dsh-panel",
|
|
65
65
|
"bothouse-dsh-devices"
|
|
66
|
-
]
|
|
67
|
-
"optionalDependencies": {
|
|
68
|
-
"@agentclientprotocol/codex-acp": "^2.2.2"
|
|
69
|
-
}
|
|
66
|
+
]
|
|
70
67
|
}
|