dsh-bots 0.2.27 → 0.2.29
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/lib/client.js +43 -22
- package/lib/index.js +17 -1
- package/lib/types/index.d.ts +1 -0
- package/lib/types/shared.d.ts +3 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -320,6 +320,7 @@
|
|
|
320
320
|
.dbs-avatar{width:20px;height:20px;border-radius:6px;flex:none;display:grid;place-items:center;font-size:11px;line-height:1;font-weight:600;color:#fff;overflow:hidden;user-select:none}
|
|
321
321
|
.dbs-avatar.dbs-group{border-radius:999px}
|
|
322
322
|
.dbs-avatar img{width:100%;height:100%;object-fit:cover;display:block}
|
|
323
|
+
.dbs-avatarRound{border-radius:999px}
|
|
323
324
|
.dbs-badge{min-width:16px;height:16px;padding:0 5px;border-radius:999px;background:var(--dsw-alias-state-business-primary,#1a6dff);color:#fff;font-size:11px;line-height:16px;text-align:center;flex:none;font-variant-numeric:tabular-nums}
|
|
324
325
|
.dbs-railBtn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border:none;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);cursor:pointer;padding:0}
|
|
325
326
|
.dbs-railBtn:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
@@ -484,7 +485,7 @@
|
|
|
484
485
|
'chat.stop.noop': '当前没有进行中的生成',
|
|
485
486
|
'media.openFailed': '打开失败,文件可能已移动或被删除',
|
|
486
487
|
'chat.members.manage': '管理成员',
|
|
487
|
-
'chat.members.cap': '已达群成员上限 {cap}
|
|
488
|
+
'chat.members.cap': '已达群成员上限 {cap} 人——可在会话设置里调高',
|
|
488
489
|
'chat.settings': '设置',
|
|
489
490
|
'chat.settings.title': '会话设置',
|
|
490
491
|
'chat.settings.name': '名称',
|
|
@@ -495,9 +496,11 @@
|
|
|
495
496
|
'chat.settings.paths': '额外可写路径(逗号分隔)',
|
|
496
497
|
'chat.settings.pathsPh': '/tmp/xxx, /Users/you/shared',
|
|
497
498
|
'chat.settings.hint': '写入被沙盒限制在工作区(含额外路径)内,读取不受限;同 slug 的 Bot 共享同一目录。',
|
|
499
|
+
'chat.settings.cap': '成员上限(1–16)',
|
|
500
|
+
'chat.settings.capHint': '保存在该群的配置里;调低会立即截断超出部分。',
|
|
501
|
+
'chat.settings.capBad': '成员上限需为 1–16 的整数',
|
|
498
502
|
'modal.members.title': '管理群成员',
|
|
499
503
|
'modal.members.hint': '勾选的 Bot 为群成员;保存后立即生效(可随时再改)。',
|
|
500
|
-
'modal.members.cap': '成员上限(1–16)',
|
|
501
504
|
'action.delete': '删除',
|
|
502
505
|
'delete.title.bot': '删除 Bot',
|
|
503
506
|
'delete.title.group': '删除群聊',
|
|
@@ -600,7 +603,7 @@
|
|
|
600
603
|
'chat.stop.noop': 'No generation in progress',
|
|
601
604
|
'media.openFailed': 'Open failed — the file may have moved or been deleted',
|
|
602
605
|
'chat.members.manage': 'Manage members',
|
|
603
|
-
'chat.members.cap': 'Group cap is {cap} members — raise
|
|
606
|
+
'chat.members.cap': 'Group cap is {cap} members — raise it in session settings',
|
|
604
607
|
'chat.settings': 'Settings',
|
|
605
608
|
'chat.settings.title': 'Session settings',
|
|
606
609
|
'chat.settings.name': 'Name',
|
|
@@ -611,9 +614,11 @@
|
|
|
611
614
|
'chat.settings.paths': 'Extra writable paths (comma-separated)',
|
|
612
615
|
'chat.settings.pathsPh': '/tmp/xxx, /Users/you/shared',
|
|
613
616
|
'chat.settings.hint': 'Writes are sandboxed to the workspace (plus extra paths); reads stay unrestricted. Bots sharing a slug share one directory.',
|
|
617
|
+
'chat.settings.cap': 'Member cap (1–16)',
|
|
618
|
+
'chat.settings.capHint': 'Stored in this group\'s config; lowering it drops members beyond the new cap right away.',
|
|
619
|
+
'chat.settings.capBad': 'The cap must be an integer from 1 to 16',
|
|
614
620
|
'modal.members.title': 'Manage group members',
|
|
615
621
|
'modal.members.hint': 'Checked bots are members; changes apply immediately on save (editable again anytime).',
|
|
616
|
-
'modal.members.cap': 'Member cap (1–16)',
|
|
617
622
|
'action.delete': 'Delete',
|
|
618
623
|
'delete.title.bot': 'Delete bot',
|
|
619
624
|
'delete.title.group': 'Delete group chat',
|
|
@@ -1087,8 +1092,11 @@
|
|
|
1087
1092
|
const a = p.agent;
|
|
1088
1093
|
const size = p.size ?? 20;
|
|
1089
1094
|
const style = { width: size, height: size };
|
|
1095
|
+
// Conversation avatars are round (native chat look); the sidebar keeps
|
|
1096
|
+
// the native squircle via the base class.
|
|
1097
|
+
const shape = p.round === true || a.isGroup === true ? ' dbs-avatarRound' : (a.isGroup ? ' dbs-group' : '');
|
|
1090
1098
|
if (typeof a.avatarDataUrl === 'string' && a.avatarDataUrl !== '') {
|
|
1091
|
-
return e('span', { className: 'dbs-avatar' +
|
|
1099
|
+
return e('span', { className: 'dbs-avatar' + shape, style }, e('img', { src: a.avatarDataUrl, alt: '' }));
|
|
1092
1100
|
}
|
|
1093
1101
|
style.background = typeof a.avatarColor === 'string' && a.avatarColor !== ''
|
|
1094
1102
|
? a.avatarColor
|
|
@@ -1097,7 +1105,7 @@
|
|
|
1097
1105
|
style.fontSize = '13px';
|
|
1098
1106
|
const initial = (a.name ?? '').trim().slice(0, 1) || '·';
|
|
1099
1107
|
return e('span', {
|
|
1100
|
-
className: 'dbs-avatar' +
|
|
1108
|
+
className: 'dbs-avatar' + shape, style, 'aria-hidden': true,
|
|
1101
1109
|
}, initial);
|
|
1102
1110
|
}
|
|
1103
1111
|
/** Chevron matching the shipped project row (fills in if the icon moves). */
|
|
@@ -1553,7 +1561,7 @@
|
|
|
1553
1561
|
};
|
|
1554
1562
|
const displayName = p.isGroup ? en.authorName : p.agent?.name;
|
|
1555
1563
|
const authorColor = 'hsl(' + String(hueOf(avAgent.id)) + ' 55% 45%)';
|
|
1556
|
-
return e('div', { className: 'dbs-botRow' }, e('div', { className: 'dbs-author' }, e(Avatar, { agent: avAgent, size: 22 }), displayName != null && displayName !== ''
|
|
1564
|
+
return e('div', { className: 'dbs-botRow' }, e('div', { className: 'dbs-author' }, e(Avatar, { agent: avAgent, size: 22, round: true }), displayName != null && displayName !== ''
|
|
1557
1565
|
? e('span', { className: 'dbs-authorName', style: { color: authorColor } }, displayName)
|
|
1558
1566
|
: null, e(MsgTime, { entry: en })), e('div', { className: 'dbs-mdRow' }, e(MarkdownText, { text: en.content, streaming: en.isStreaming === true }), en.isStreaming === true ? e('span', { className: 'dbs-caret' }) : null, en.isStreaming === true ? null : e(MediaRefs, { text: en.content, files: true })));
|
|
1559
1567
|
}
|
|
@@ -1813,7 +1821,7 @@
|
|
|
1813
1821
|
variant: 'ghost', size: 'sm', title: t('action.close'), 'aria-label': t('action.close'),
|
|
1814
1822
|
icon: Ico('IconCloseOutline16', { size: 16 }),
|
|
1815
1823
|
onClick: () => patch({ chatAgentId: null }),
|
|
1816
|
-
}), agent !== null ? e(Avatar, { agent, size: 24 }) : null, e('span', { className: 'dbs-chatbarName' }, agent?.name ?? t('chat.loading')), e('span', { className: 'dbs-meta' }, isGroup ? t('chat.group', { n: memberNames.length }) : t('chat.single')), e('span', { style: { flex: 1 } }), isGroup
|
|
1824
|
+
}), agent !== null ? e(Avatar, { agent, size: 24, round: true }) : null, e('span', { className: 'dbs-chatbarName' }, agent?.name ?? t('chat.loading')), e('span', { className: 'dbs-meta' }, isGroup ? t('chat.group', { n: memberNames.length }) : t('chat.single')), e('span', { style: { flex: 1 } }), isGroup
|
|
1817
1825
|
? e(Button, {
|
|
1818
1826
|
variant: 'ghost', size: 'sm', title: t('chat.members.manage'), 'aria-label': t('chat.members.manage'),
|
|
1819
1827
|
onClick: () => patch({ manageMembers: p.agentId }),
|
|
@@ -1827,7 +1835,7 @@
|
|
|
1827
1835
|
: null, e('div', { className: 'dbs-scrollArea' }, e('div', { className: 'dbs-scrollBody', ref: scrollRef, onScroll: onScrollBody }, e('div', { className: 'dbs-scroll' }, e('div', { className: 'dbs-column' }, entries === null
|
|
1828
1836
|
? e('div', { className: 'dbs-skeleton' }, e('div', { className: 'dbs-skelRow', style: { width: '42%' } }), e('div', { className: 'dbs-skelRow', style: { width: '76%' } }), e('div', { className: 'dbs-skelRow', style: { width: '58%' } }))
|
|
1829
1837
|
: list.length === 0
|
|
1830
|
-
? 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 !== ''
|
|
1838
|
+
? e('div', { className: 'dbs-welcome' }, agent !== null ? e(Avatar, { agent, size: 44, round: true }) : null, e('div', { className: 'dbs-welcomeName' }, agent?.name ?? ''), typeof agent?.description === 'string' && agent.description !== ''
|
|
1831
1839
|
? 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 ?? '' })))
|
|
1832
1840
|
: thread, composing
|
|
1833
1841
|
? e('div', { className: 'dbs-typing', 'aria-label': t('chat.composing'), title: t('chat.composing') },
|
|
@@ -2224,7 +2232,6 @@
|
|
|
2224
2232
|
const [picked, setPicked] = React.useState(null);
|
|
2225
2233
|
const [err, setErr] = React.useState(null);
|
|
2226
2234
|
const [working, setWorking] = React.useState(false);
|
|
2227
|
-
const [capText, setCapText] = React.useState('8');
|
|
2228
2235
|
// Seed once per opened group; a group vanishing mid-edit (deleted by
|
|
2229
2236
|
// the swarm, say) just renders the modal inert until closed.
|
|
2230
2237
|
React.useEffect(() => {
|
|
@@ -2234,18 +2241,17 @@
|
|
|
2234
2241
|
for (const id of group.memberIds ?? [])
|
|
2235
2242
|
init[id] = true;
|
|
2236
2243
|
setPicked(init);
|
|
2237
|
-
setCapText(String(group.maxMembers ?? 8));
|
|
2238
2244
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2239
2245
|
}, [s.manageMembers]);
|
|
2240
2246
|
if (group === null || group === undefined)
|
|
2241
2247
|
return null;
|
|
2242
2248
|
const singles = s.agents.filter((a) => !a.isGroup && a.isHiddenFromSidebar !== true);
|
|
2243
2249
|
const pickedIds = picked === null ? [] : Object.keys(picked).filter((k) => picked[k]);
|
|
2244
|
-
// Per-group cap (group.json maxMembers, default 8, engine hard max 16
|
|
2245
|
-
// the
|
|
2246
|
-
//
|
|
2247
|
-
//
|
|
2248
|
-
const MEMBER_CAP =
|
|
2250
|
+
// Per-group cap (group.json maxMembers, default 8, engine hard max 16;
|
|
2251
|
+
// the host enriches group rows with the real value). The glue slice(0,
|
|
2252
|
+
// maxMembers)-truncates the roster SILENTLY, so an over-cap pick is
|
|
2253
|
+
// blocked here — the cap itself is edited in session settings.
|
|
2254
|
+
const MEMBER_CAP = group.maxMembers ?? 8;
|
|
2249
2255
|
const overCap = pickedIds.length > MEMBER_CAP;
|
|
2250
2256
|
function toggle(id) {
|
|
2251
2257
|
if (picked === null)
|
|
@@ -2263,7 +2269,7 @@
|
|
|
2263
2269
|
setWorking(true);
|
|
2264
2270
|
setErr(null);
|
|
2265
2271
|
try {
|
|
2266
|
-
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds
|
|
2272
|
+
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds });
|
|
2267
2273
|
patch({ manageMembers: null });
|
|
2268
2274
|
await refreshAgents();
|
|
2269
2275
|
return;
|
|
@@ -2286,10 +2292,7 @@
|
|
|
2286
2292
|
disabled: working,
|
|
2287
2293
|
icon: Ico('IconCloseOutline16', { size: 16 }),
|
|
2288
2294
|
onClick: () => patch({ manageMembers: null }),
|
|
2289
|
-
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')),
|
|
2290
|
-
value: capText, disabled: working, style: { width: 72 },
|
|
2291
|
-
onChange: (ev) => setCapText(ev.target.value),
|
|
2292
|
-
})), picked === null
|
|
2295
|
+
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')), picked === null
|
|
2293
2296
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.loading'))
|
|
2294
2297
|
: e('div', { className: 'dbs-modalMembers' }, singles.length === 0
|
|
2295
2298
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.empty'))
|
|
@@ -2328,11 +2331,13 @@
|
|
|
2328
2331
|
const [wsLoaded, setWsLoaded] = React.useState(false);
|
|
2329
2332
|
const [working, setWorking] = React.useState(false);
|
|
2330
2333
|
const [err, setErr] = React.useState(null);
|
|
2334
|
+
const [capText, setCapText] = React.useState('8');
|
|
2331
2335
|
React.useEffect(() => {
|
|
2332
2336
|
if (agent === null || agent === undefined)
|
|
2333
2337
|
return;
|
|
2334
2338
|
setName(String(agent.name ?? ''));
|
|
2335
2339
|
setDesc(String(agent.description ?? ''));
|
|
2340
|
+
setCapText(String(agent.maxMembers ?? 8));
|
|
2336
2341
|
if (agent.isGroup === true) {
|
|
2337
2342
|
setWsLoaded(true);
|
|
2338
2343
|
return;
|
|
@@ -2354,13 +2359,24 @@
|
|
|
2354
2359
|
if (agent === null || agent === undefined)
|
|
2355
2360
|
return null;
|
|
2356
2361
|
const valid = name.trim() !== '';
|
|
2362
|
+
// Member cap lives here (session settings), not in the roster editor:
|
|
2363
|
+
// it is this group's own config field. Empty/unchanged = keep.
|
|
2364
|
+
const capNum = Math.floor(Number(capText));
|
|
2365
|
+
const capValid = capText.trim() === '' || (Number.isInteger(capNum) && capNum >= 1 && capNum <= 16);
|
|
2357
2366
|
async function save() {
|
|
2358
2367
|
if (working || valid === false)
|
|
2359
2368
|
return;
|
|
2369
|
+
if (capValid === false) {
|
|
2370
|
+
setErr(t('chat.settings.capBad'));
|
|
2371
|
+
return;
|
|
2372
|
+
}
|
|
2360
2373
|
setWorking(true);
|
|
2361
2374
|
setErr(null);
|
|
2362
2375
|
try {
|
|
2363
2376
|
await botsCall('update', { id: agent.id, profile: { name: name.trim(), description: desc.trim() } });
|
|
2377
|
+
if (agent.isGroup === true && capText.trim() !== '') {
|
|
2378
|
+
await botsCall('setGroupMembers', { id: agent.id, memberIds: agent.memberIds ?? [], maxMembers: capNum });
|
|
2379
|
+
}
|
|
2364
2380
|
if (agent.isGroup !== true && wsLoaded === true) {
|
|
2365
2381
|
const list = paths.split(/[,,]/).map((x) => x.trim()).filter((x) => x !== '');
|
|
2366
2382
|
await botsCall('workspaceSet', {
|
|
@@ -2394,7 +2410,12 @@
|
|
|
2394
2410
|
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-setLabel' }, t('chat.settings.name')), e(Input, {
|
|
2395
2411
|
value: name, autoFocus: true, disabled: working,
|
|
2396
2412
|
onChange: (ev) => setName(ev.target.value),
|
|
2397
|
-
}), isGroup ===
|
|
2413
|
+
}), isGroup === true
|
|
2414
|
+
? e('div', null, e('div', { className: 'dbs-setLabel' }, t('chat.settings.cap')), e(Input, {
|
|
2415
|
+
value: capText, disabled: working, inputMode: 'numeric', style: { width: 96 },
|
|
2416
|
+
onChange: (ev) => setCapText(ev.target.value),
|
|
2417
|
+
}), e('div', { className: 'dbs-meta', style: { marginTop: 6 } }, t('chat.settings.capHint')))
|
|
2418
|
+
: null, isGroup === false
|
|
2398
2419
|
? e('div', null, e('div', { className: 'dbs-setLabel' }, t('chat.settings.desc')), e(Input, {
|
|
2399
2420
|
value: desc, disabled: working,
|
|
2400
2421
|
onChange: (ev) => setDesc(ev.target.value),
|
package/lib/index.js
CHANGED
|
@@ -214,7 +214,22 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
214
214
|
* would make the gateway reject the envelope with "unexpected request".
|
|
215
215
|
*/
|
|
216
216
|
async list(request) {
|
|
217
|
-
|
|
217
|
+
const agents = normalizeAgents(await callGateway(this.cfg.dataDir, 'listAgents', {}));
|
|
218
|
+
// The gateway row omits group.json's maxMembers, so read it here — the
|
|
219
|
+
// settings editor needs the CURRENT cap, not a hardcoded default.
|
|
220
|
+
for (const a of agents) {
|
|
221
|
+
if (a.isGroup !== true)
|
|
222
|
+
continue;
|
|
223
|
+
try {
|
|
224
|
+
const cfg = JSON.parse(readFileSync(join(effectiveDataDir(this.cfg.dataDir), 'agents', a.id, 'group.json'), 'utf-8'));
|
|
225
|
+
const n = typeof cfg.maxMembers === 'number' ? Math.floor(cfg.maxMembers) : Number.NaN;
|
|
226
|
+
a.maxMembers = Number.isFinite(n) ? Math.min(16, Math.max(1, n)) : 8;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
a.maxMembers = 8;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return agents;
|
|
218
233
|
}
|
|
219
234
|
async workspaces(request) {
|
|
220
235
|
const registry = this.ctx.get('workspaceRegistry');
|
|
@@ -285,6 +300,7 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
285
300
|
const updated = await callGateway(this.cfg.dataDir, 'setGroupMembers', {
|
|
286
301
|
id: request?.id,
|
|
287
302
|
memberAgentIds: Array.isArray(request?.memberIds) ? request.memberIds : [],
|
|
303
|
+
...(typeof request?.maxMembers === 'number' ? { maxMembers: request.maxMembers } : {}),
|
|
288
304
|
});
|
|
289
305
|
return trimAgent(updated?.agent ?? updated);
|
|
290
306
|
}
|
package/lib/types/index.d.ts
CHANGED
|
@@ -91,6 +91,7 @@ export declare class BotsRemote extends TypertRemoteService {
|
|
|
91
91
|
setGroupMembers(request: {
|
|
92
92
|
id?: string;
|
|
93
93
|
memberIds?: string[];
|
|
94
|
+
maxMembers?: number;
|
|
94
95
|
} | null): Promise<AgentInfo | null>;
|
|
95
96
|
/**
|
|
96
97
|
* Resolve a bot-written path against the media allowlist: only files inside
|
package/lib/types/shared.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export interface AgentInfo {
|
|
|
20
20
|
title: string;
|
|
21
21
|
isGroup: boolean;
|
|
22
22
|
memberIds: string[];
|
|
23
|
+
/** Per-group roster cap (group.json maxMembers; 1-16, default 8). The
|
|
24
|
+
gateway row omits it, so the host enriches group rows from disk. */
|
|
25
|
+
maxMembers?: number;
|
|
23
26
|
isRunning: boolean;
|
|
24
27
|
isActive: boolean;
|
|
25
28
|
lastMessagePreview: string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bots",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.29",
|
|
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",
|