dsh-bots 0.2.26 → 0.2.28
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 +37 -19
- 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
|
@@ -289,7 +289,8 @@
|
|
|
289
289
|
row on hover and shrink it on leave — width/height jitter the user read
|
|
290
290
|
as flicker. Fixed 30px row + always-laid-out actions = zero geometry
|
|
291
291
|
change; the same trick the native headerActions uses (max-width/opacity). */
|
|
292
|
-
.dbs-secHead{position:relative;cursor:pointer;border-radius:8px;height:30px}
|
|
292
|
+
.dbs-secHead{position:relative;cursor:pointer;border-radius:8px;height:30px;margin-bottom:4px}
|
|
293
|
+
.dbs-secGroup+.dbs-secGroup{margin-top:10px}
|
|
293
294
|
.dbs-secHead:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
294
295
|
.dbs-secHead .dbs-rowActions{display:inline-flex;visibility:hidden;opacity:0;pointer-events:none;transition:opacity .12s ease-out}
|
|
295
296
|
.dbs-secHead:hover .dbs-rowActions,.dbs-secHead:focus-within .dbs-rowActions,.dbs-secHead[data-menu="true"] .dbs-rowActions{visibility:visible;opacity:1;pointer-events:auto}
|
|
@@ -483,7 +484,7 @@
|
|
|
483
484
|
'chat.stop.noop': '当前没有进行中的生成',
|
|
484
485
|
'media.openFailed': '打开失败,文件可能已移动或被删除',
|
|
485
486
|
'chat.members.manage': '管理成员',
|
|
486
|
-
'chat.members.cap': '已达群成员上限 {cap}
|
|
487
|
+
'chat.members.cap': '已达群成员上限 {cap} 人——可在会话设置里调高',
|
|
487
488
|
'chat.settings': '设置',
|
|
488
489
|
'chat.settings.title': '会话设置',
|
|
489
490
|
'chat.settings.name': '名称',
|
|
@@ -494,9 +495,11 @@
|
|
|
494
495
|
'chat.settings.paths': '额外可写路径(逗号分隔)',
|
|
495
496
|
'chat.settings.pathsPh': '/tmp/xxx, /Users/you/shared',
|
|
496
497
|
'chat.settings.hint': '写入被沙盒限制在工作区(含额外路径)内,读取不受限;同 slug 的 Bot 共享同一目录。',
|
|
498
|
+
'chat.settings.cap': '成员上限(1–16)',
|
|
499
|
+
'chat.settings.capHint': '保存在该群的配置里;调低会立即截断超出部分。',
|
|
500
|
+
'chat.settings.capBad': '成员上限需为 1–16 的整数',
|
|
497
501
|
'modal.members.title': '管理群成员',
|
|
498
502
|
'modal.members.hint': '勾选的 Bot 为群成员;保存后立即生效(可随时再改)。',
|
|
499
|
-
'modal.members.cap': '成员上限(1–16)',
|
|
500
503
|
'action.delete': '删除',
|
|
501
504
|
'delete.title.bot': '删除 Bot',
|
|
502
505
|
'delete.title.group': '删除群聊',
|
|
@@ -599,7 +602,7 @@
|
|
|
599
602
|
'chat.stop.noop': 'No generation in progress',
|
|
600
603
|
'media.openFailed': 'Open failed — the file may have moved or been deleted',
|
|
601
604
|
'chat.members.manage': 'Manage members',
|
|
602
|
-
'chat.members.cap': 'Group cap is {cap} members — raise
|
|
605
|
+
'chat.members.cap': 'Group cap is {cap} members — raise it in session settings',
|
|
603
606
|
'chat.settings': 'Settings',
|
|
604
607
|
'chat.settings.title': 'Session settings',
|
|
605
608
|
'chat.settings.name': 'Name',
|
|
@@ -610,9 +613,11 @@
|
|
|
610
613
|
'chat.settings.paths': 'Extra writable paths (comma-separated)',
|
|
611
614
|
'chat.settings.pathsPh': '/tmp/xxx, /Users/you/shared',
|
|
612
615
|
'chat.settings.hint': 'Writes are sandboxed to the workspace (plus extra paths); reads stay unrestricted. Bots sharing a slug share one directory.',
|
|
616
|
+
'chat.settings.cap': 'Member cap (1–16)',
|
|
617
|
+
'chat.settings.capHint': 'Stored in this group\'s config; lowering it drops members beyond the new cap right away.',
|
|
618
|
+
'chat.settings.capBad': 'The cap must be an integer from 1 to 16',
|
|
613
619
|
'modal.members.title': 'Manage group members',
|
|
614
620
|
'modal.members.hint': 'Checked bots are members; changes apply immediately on save (editable again anytime).',
|
|
615
|
-
'modal.members.cap': 'Member cap (1–16)',
|
|
616
621
|
'action.delete': 'Delete',
|
|
617
622
|
'delete.title.bot': 'Delete bot',
|
|
618
623
|
'delete.title.group': 'Delete group chat',
|
|
@@ -1175,7 +1180,7 @@
|
|
|
1175
1180
|
const toggle = () => {
|
|
1176
1181
|
patch({ sectionMenu: null, sectionsOpen: { ...(s.sectionsOpen ?? {}), [menuKind]: !isOpen } });
|
|
1177
1182
|
};
|
|
1178
|
-
return e('div', { key: label }, e('div', {
|
|
1183
|
+
return e('div', { key: label, className: 'dbs-secGroup' }, e('div', {
|
|
1179
1184
|
className: 'dbs-navBodyErr dbs-secHead', role: 'button', tabIndex: 0, 'aria-expanded': isOpen,
|
|
1180
1185
|
'data-menu': menuOpen ? 'true' : 'false',
|
|
1181
1186
|
// Native sectionHeader outdent: label at x=16 (12 sidebar
|
|
@@ -2223,7 +2228,6 @@
|
|
|
2223
2228
|
const [picked, setPicked] = React.useState(null);
|
|
2224
2229
|
const [err, setErr] = React.useState(null);
|
|
2225
2230
|
const [working, setWorking] = React.useState(false);
|
|
2226
|
-
const [capText, setCapText] = React.useState('8');
|
|
2227
2231
|
// Seed once per opened group; a group vanishing mid-edit (deleted by
|
|
2228
2232
|
// the swarm, say) just renders the modal inert until closed.
|
|
2229
2233
|
React.useEffect(() => {
|
|
@@ -2233,18 +2237,17 @@
|
|
|
2233
2237
|
for (const id of group.memberIds ?? [])
|
|
2234
2238
|
init[id] = true;
|
|
2235
2239
|
setPicked(init);
|
|
2236
|
-
setCapText(String(group.maxMembers ?? 8));
|
|
2237
2240
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2238
2241
|
}, [s.manageMembers]);
|
|
2239
2242
|
if (group === null || group === undefined)
|
|
2240
2243
|
return null;
|
|
2241
2244
|
const singles = s.agents.filter((a) => !a.isGroup && a.isHiddenFromSidebar !== true);
|
|
2242
2245
|
const pickedIds = picked === null ? [] : Object.keys(picked).filter((k) => picked[k]);
|
|
2243
|
-
// Per-group cap (group.json maxMembers, default 8, engine hard max 16
|
|
2244
|
-
// the
|
|
2245
|
-
//
|
|
2246
|
-
//
|
|
2247
|
-
const MEMBER_CAP =
|
|
2246
|
+
// Per-group cap (group.json maxMembers, default 8, engine hard max 16;
|
|
2247
|
+
// the host enriches group rows with the real value). The glue slice(0,
|
|
2248
|
+
// maxMembers)-truncates the roster SILENTLY, so an over-cap pick is
|
|
2249
|
+
// blocked here — the cap itself is edited in session settings.
|
|
2250
|
+
const MEMBER_CAP = group.maxMembers ?? 8;
|
|
2248
2251
|
const overCap = pickedIds.length > MEMBER_CAP;
|
|
2249
2252
|
function toggle(id) {
|
|
2250
2253
|
if (picked === null)
|
|
@@ -2262,7 +2265,7 @@
|
|
|
2262
2265
|
setWorking(true);
|
|
2263
2266
|
setErr(null);
|
|
2264
2267
|
try {
|
|
2265
|
-
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds
|
|
2268
|
+
await botsCall('setGroupMembers', { id: group.id, memberIds: pickedIds });
|
|
2266
2269
|
patch({ manageMembers: null });
|
|
2267
2270
|
await refreshAgents();
|
|
2268
2271
|
return;
|
|
@@ -2285,10 +2288,7 @@
|
|
|
2285
2288
|
disabled: working,
|
|
2286
2289
|
icon: Ico('IconCloseOutline16', { size: 16 }),
|
|
2287
2290
|
onClick: () => patch({ manageMembers: null }),
|
|
2288
|
-
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')),
|
|
2289
|
-
value: capText, disabled: working, style: { width: 72 },
|
|
2290
|
-
onChange: (ev) => setCapText(ev.target.value),
|
|
2291
|
-
})), picked === null
|
|
2291
|
+
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-meta', style: { padding: '0 2px 6px' } }, t('modal.members.hint')), picked === null
|
|
2292
2292
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.loading'))
|
|
2293
2293
|
: e('div', { className: 'dbs-modalMembers' }, singles.length === 0
|
|
2294
2294
|
? e('div', { className: 'dbs-meta', style: { padding: '4px 6px' } }, t('list.empty'))
|
|
@@ -2327,11 +2327,13 @@
|
|
|
2327
2327
|
const [wsLoaded, setWsLoaded] = React.useState(false);
|
|
2328
2328
|
const [working, setWorking] = React.useState(false);
|
|
2329
2329
|
const [err, setErr] = React.useState(null);
|
|
2330
|
+
const [capText, setCapText] = React.useState('8');
|
|
2330
2331
|
React.useEffect(() => {
|
|
2331
2332
|
if (agent === null || agent === undefined)
|
|
2332
2333
|
return;
|
|
2333
2334
|
setName(String(agent.name ?? ''));
|
|
2334
2335
|
setDesc(String(agent.description ?? ''));
|
|
2336
|
+
setCapText(String(agent.maxMembers ?? 8));
|
|
2335
2337
|
if (agent.isGroup === true) {
|
|
2336
2338
|
setWsLoaded(true);
|
|
2337
2339
|
return;
|
|
@@ -2353,13 +2355,24 @@
|
|
|
2353
2355
|
if (agent === null || agent === undefined)
|
|
2354
2356
|
return null;
|
|
2355
2357
|
const valid = name.trim() !== '';
|
|
2358
|
+
// Member cap lives here (session settings), not in the roster editor:
|
|
2359
|
+
// it is this group's own config field. Empty/unchanged = keep.
|
|
2360
|
+
const capNum = Math.floor(Number(capText));
|
|
2361
|
+
const capValid = capText.trim() === '' || (Number.isInteger(capNum) && capNum >= 1 && capNum <= 16);
|
|
2356
2362
|
async function save() {
|
|
2357
2363
|
if (working || valid === false)
|
|
2358
2364
|
return;
|
|
2365
|
+
if (capValid === false) {
|
|
2366
|
+
setErr(t('chat.settings.capBad'));
|
|
2367
|
+
return;
|
|
2368
|
+
}
|
|
2359
2369
|
setWorking(true);
|
|
2360
2370
|
setErr(null);
|
|
2361
2371
|
try {
|
|
2362
2372
|
await botsCall('update', { id: agent.id, profile: { name: name.trim(), description: desc.trim() } });
|
|
2373
|
+
if (agent.isGroup === true && capText.trim() !== '') {
|
|
2374
|
+
await botsCall('setGroupMembers', { id: agent.id, memberIds: agent.memberIds ?? [], maxMembers: capNum });
|
|
2375
|
+
}
|
|
2363
2376
|
if (agent.isGroup !== true && wsLoaded === true) {
|
|
2364
2377
|
const list = paths.split(/[,,]/).map((x) => x.trim()).filter((x) => x !== '');
|
|
2365
2378
|
await botsCall('workspaceSet', {
|
|
@@ -2393,7 +2406,12 @@
|
|
|
2393
2406
|
})), e('div', { className: 'dbs-modalBody' }, e('div', { className: 'dbs-setLabel' }, t('chat.settings.name')), e(Input, {
|
|
2394
2407
|
value: name, autoFocus: true, disabled: working,
|
|
2395
2408
|
onChange: (ev) => setName(ev.target.value),
|
|
2396
|
-
}), isGroup ===
|
|
2409
|
+
}), isGroup === true
|
|
2410
|
+
? e('div', null, e('div', { className: 'dbs-setLabel' }, t('chat.settings.cap')), e(Input, {
|
|
2411
|
+
value: capText, disabled: working, inputMode: 'numeric', style: { width: 96 },
|
|
2412
|
+
onChange: (ev) => setCapText(ev.target.value),
|
|
2413
|
+
}), e('div', { className: 'dbs-meta', style: { marginTop: 6 } }, t('chat.settings.capHint')))
|
|
2414
|
+
: null, isGroup === false
|
|
2397
2415
|
? e('div', null, e('div', { className: 'dbs-setLabel' }, t('chat.settings.desc')), e(Input, {
|
|
2398
2416
|
value: desc, disabled: working,
|
|
2399
2417
|
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.28",
|
|
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",
|