dsh-bots 0.2.15 → 0.2.17
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/cordis.patch.yml +1 -1
- package/lib/client.js +22 -4
- package/lib/gateway.js +34 -2
- package/lib/index.js +12 -9
- package/lib/types/gateway.d.ts +18 -0
- package/package.json +1 -1
package/cordis.patch.yml
CHANGED
package/lib/client.js
CHANGED
|
@@ -464,6 +464,7 @@
|
|
|
464
464
|
'chat.stop.noop': '当前没有进行中的生成',
|
|
465
465
|
'media.openFailed': '打开失败,文件可能已移动或被删除',
|
|
466
466
|
'chat.members.manage': '管理成员',
|
|
467
|
+
'chat.members.cap': '已达群成员上限 6 人(引擎限制)——请先移除一名成员再添加',
|
|
467
468
|
'chat.settings': '设置',
|
|
468
469
|
'chat.settings.title': '会话设置',
|
|
469
470
|
'chat.settings.name': '名称',
|
|
@@ -576,6 +577,7 @@
|
|
|
576
577
|
'chat.stop.noop': 'No generation in progress',
|
|
577
578
|
'media.openFailed': 'Open failed — the file may have moved or been deleted',
|
|
578
579
|
'chat.members.manage': 'Manage members',
|
|
580
|
+
'chat.members.cap': 'Group cap is 6 members (engine limit) — remove one before adding',
|
|
579
581
|
'chat.settings': 'Settings',
|
|
580
582
|
'chat.settings.title': 'Session settings',
|
|
581
583
|
'chat.settings.name': 'Name',
|
|
@@ -2149,8 +2151,24 @@
|
|
|
2149
2151
|
return null;
|
|
2150
2152
|
const singles = s.agents.filter((a) => !a.isGroup && a.isHiddenFromSidebar !== true);
|
|
2151
2153
|
const pickedIds = picked === null ? [] : Object.keys(picked).filter((k) => picked[k]);
|
|
2154
|
+
// Engine hard cap (sdk-bots agents.ts GROUP_MAX_MEMBERS = 6): the glue
|
|
2155
|
+
// slice(0, 6)-truncates the roster SILENTLY — an over-cap save writes
|
|
2156
|
+
// successfully and just drops whoever ranked past 6. Surface it here
|
|
2157
|
+
// instead of letting a save pretend to work.
|
|
2158
|
+
const MEMBER_CAP = 6;
|
|
2159
|
+
const overCap = pickedIds.length > MEMBER_CAP;
|
|
2160
|
+
function toggle(id) {
|
|
2161
|
+
if (picked === null)
|
|
2162
|
+
return;
|
|
2163
|
+
if (picked[id] !== true && pickedIds.length >= MEMBER_CAP) {
|
|
2164
|
+
setErr(t('chat.members.cap'));
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
setErr(null);
|
|
2168
|
+
setPicked({ ...picked, [id]: !picked[id] });
|
|
2169
|
+
}
|
|
2152
2170
|
async function submit() {
|
|
2153
|
-
if (working || picked === null)
|
|
2171
|
+
if (working || picked === null || overCap)
|
|
2154
2172
|
return;
|
|
2155
2173
|
setWorking(true);
|
|
2156
2174
|
setErr(null);
|
|
@@ -2186,16 +2204,16 @@
|
|
|
2186
2204
|
key: m.id,
|
|
2187
2205
|
className: 'dbs-member' + (picked[m.id] ? ' checked' : ''),
|
|
2188
2206
|
style: { cursor: 'pointer', padding: '4px 6px', borderRadius: 8 },
|
|
2189
|
-
onClick: () =>
|
|
2207
|
+
onClick: () => toggle(m.id),
|
|
2190
2208
|
}, e('input', { type: 'checkbox', checked: Boolean(picked[m.id]), readOnly: true }), e(Avatar, { agent: m, size: 18 }), e('span', { className: 'dbs-title' }, m.name))), singles.length > 0
|
|
2191
|
-
? e('div', { className: 'dbs-meta', style: { padding: '6px 6px 0' } }, t('chat.group', { n: pickedIds.length }))
|
|
2209
|
+
? e('div', { className: 'dbs-meta', style: { padding: '6px 6px 0' } }, t('chat.group', { n: pickedIds.length }) + ' / ' + String(MEMBER_CAP))
|
|
2192
2210
|
: null), err !== null
|
|
2193
2211
|
? e('div', { className: 'dbs-error', onClick: () => { setErr(null); } }, err)
|
|
2194
2212
|
: null), e('div', { className: 'dbs-modalFooter' }, e(Button, {
|
|
2195
2213
|
variant: 'ghost', size: 'sm', disabled: working,
|
|
2196
2214
|
onClick: () => patch({ manageMembers: null }),
|
|
2197
2215
|
}, t('action.cancel')), e(Button, {
|
|
2198
|
-
variant: 'primary', size: 'sm', disabled: picked === null || working,
|
|
2216
|
+
variant: 'primary', size: 'sm', disabled: picked === null || working || overCap,
|
|
2199
2217
|
onClick: () => void submit(),
|
|
2200
2218
|
}, working ? t('action.saving') : t('action.save')))));
|
|
2201
2219
|
}
|
package/lib/gateway.js
CHANGED
|
@@ -13,6 +13,38 @@ import { readFileSync } from 'node:fs';
|
|
|
13
13
|
import { homedir } from 'node:os';
|
|
14
14
|
import { AVATAR_DATA_URL_MAX } from './shared.js';
|
|
15
15
|
const TOKEN_RE = /^[A-Za-z0-9._~+-]+$/;
|
|
16
|
+
/** New default root (0.2.16). The gateway may still live at the legacy root
|
|
17
|
+
* until the engine's SAND_DATA_ROOT migrates — every discovery consumer tries
|
|
18
|
+
* the configured dir first, then the legacy one. */
|
|
19
|
+
export const LEGACY_DATA_DIR = '~/.sdk-bots';
|
|
20
|
+
/**
|
|
21
|
+
* Discovery that follows the gateway to wherever it actually lives: the
|
|
22
|
+
* configured dir when it holds a gateway.json, else the legacy root. Returns
|
|
23
|
+
* null when neither has one (the gateway is simply down).
|
|
24
|
+
*/
|
|
25
|
+
export function readDiscoveryWithFallback(dataDir, legacyDir = LEGACY_DATA_DIR) {
|
|
26
|
+
const direct = readDiscovery(dataDir);
|
|
27
|
+
if (direct !== null)
|
|
28
|
+
return direct;
|
|
29
|
+
if (expandHome(dataDir) !== expandHome(legacyDir))
|
|
30
|
+
return readDiscovery(legacyDir);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The directory where the gateway (and therefore agents/, box-workspace/,
|
|
35
|
+
* swarm/) actually lives right now: the configured dir if it has discovery,
|
|
36
|
+
* else the legacy root, else the configured dir as-is. All state-adjacent
|
|
37
|
+
* consumers (unread ledger, workspace ops, media allowlist, diag) resolve
|
|
38
|
+
* through this so they stay co-located with the real data across migration.
|
|
39
|
+
*/
|
|
40
|
+
export function effectiveDataDir(dataDir, legacyDir = LEGACY_DATA_DIR) {
|
|
41
|
+
if (readDiscovery(dataDir) !== null)
|
|
42
|
+
return expandHome(dataDir);
|
|
43
|
+
if (expandHome(dataDir) !== expandHome(legacyDir) && readDiscovery(legacyDir) !== null) {
|
|
44
|
+
return expandHome(legacyDir);
|
|
45
|
+
}
|
|
46
|
+
return expandHome(dataDir);
|
|
47
|
+
}
|
|
16
48
|
export function expandHome(p) {
|
|
17
49
|
if (p === '~')
|
|
18
50
|
return homedir();
|
|
@@ -54,7 +86,7 @@ export function readDiscovery(dataDir) {
|
|
|
54
86
|
}
|
|
55
87
|
/** Discovery + `/health` probe with pid match validation. */
|
|
56
88
|
export async function discover(dataDir) {
|
|
57
|
-
const d =
|
|
89
|
+
const d = readDiscoveryWithFallback(dataDir);
|
|
58
90
|
if (d === null)
|
|
59
91
|
return { ok: false, reason: 'no-gateway-json' };
|
|
60
92
|
const baseUrl = `http://${d.host}:${d.port}`;
|
|
@@ -87,7 +119,7 @@ export async function discover(dataDir) {
|
|
|
87
119
|
}
|
|
88
120
|
/** POST one `/api/<method>` command; unwraps `{result}` and throws on errors. */
|
|
89
121
|
export async function callGateway(dataDir, method, args = {}) {
|
|
90
|
-
const d =
|
|
122
|
+
const d = readDiscoveryWithFallback(dataDir);
|
|
91
123
|
if (d === null)
|
|
92
124
|
throw new Error('gateway.json not found — sdk-bots host 是否在运行?');
|
|
93
125
|
const baseUrl = `http://${d.host}:${d.port}`;
|
package/lib/index.js
CHANGED
|
@@ -23,7 +23,7 @@ import { dirname, extname, join, resolve, sep } from 'node:path';
|
|
|
23
23
|
import { appendFileSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
24
24
|
import { spawn } from 'node:child_process';
|
|
25
25
|
import { pathToFileURL } from 'node:url';
|
|
26
|
-
import { callGateway, discover, expandHome, nextNonce, normalizeAgents,
|
|
26
|
+
import { callGateway, discover, effectiveDataDir, expandHome, nextNonce, normalizeAgents, readDiscoveryWithFallback, trimAgent, trimEntry } from './gateway.js';
|
|
27
27
|
import { GatewaySseClient, SseRingBuffer } from './sse.js';
|
|
28
28
|
import { UnreadStore } from './unread.js';
|
|
29
29
|
import { listAgentWorkspaces, readAgentWorkspace, setAgentWorkspace } from './workspace.js';
|
|
@@ -34,7 +34,10 @@ export const name = 'dsh-bots';
|
|
|
34
34
|
export const inject = [];
|
|
35
35
|
/** Cordis range this plugin is tested against; only surfaces a warning. */
|
|
36
36
|
export const TESTED_CORDIS_RANGE = '^4.0.1';
|
|
37
|
-
const DEFAULT_DATA_DIR = '~/.
|
|
37
|
+
const DEFAULT_DATA_DIR = '~/.dsh-bots';
|
|
38
|
+
/** Where the gateway lived before the 0.2.16 default rename — discovery and
|
|
39
|
+
* every data-dir consumer fall back to it until the engine migrates. */
|
|
40
|
+
const LEGACY_DATA_DIR = '~/.sdk-bots';
|
|
38
41
|
/** Append-only diagnostics file, read when a shadow takeover misbehaves. */
|
|
39
42
|
const DIAG_FILE = 'dsh-bots-diag.jsonl';
|
|
40
43
|
/** Persisted read markers ("读到哪了") backing the sidebar unread badge. */
|
|
@@ -131,12 +134,12 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
131
134
|
constructor(ctx, config) {
|
|
132
135
|
super(ctx, 'bots');
|
|
133
136
|
this.cfg = { dataDir: config?.dataDir ?? DEFAULT_DATA_DIR };
|
|
134
|
-
this.unread = new UnreadStore(join(
|
|
137
|
+
this.unread = new UnreadStore(join(effectiveDataDir(this.cfg.dataDir), UNREAD_FILE));
|
|
135
138
|
const ring = new SseRingBuffer(3000, (channel, data) => { this.observeTranscript(channel, data); });
|
|
136
139
|
this.sse = new GatewaySseClient({
|
|
137
140
|
ring,
|
|
138
141
|
resolveBase: () => {
|
|
139
|
-
const d =
|
|
142
|
+
const d = readDiscoveryWithFallback(this.cfg.dataDir);
|
|
140
143
|
if (d === null)
|
|
141
144
|
return null;
|
|
142
145
|
return { url: `http://${d.host}:${d.port}`, token: d.token };
|
|
@@ -295,7 +298,7 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
295
298
|
const p = String(raw ?? '').trim();
|
|
296
299
|
if (p === '')
|
|
297
300
|
throw new Error('path is required');
|
|
298
|
-
const root = realpathSync(
|
|
301
|
+
const root = realpathSync(effectiveDataDir(this.cfg.dataDir));
|
|
299
302
|
let resolved = resolve(expandHome(p));
|
|
300
303
|
try {
|
|
301
304
|
resolved = realpathSync(resolved);
|
|
@@ -482,16 +485,16 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
482
485
|
// these methods only read/write the per-agent settings.json contract.
|
|
483
486
|
// ==========================================================
|
|
484
487
|
async workspaceList(request) {
|
|
485
|
-
return { workspaces: listAgentWorkspaces(
|
|
488
|
+
return { workspaces: listAgentWorkspaces(effectiveDataDir(this.cfg.dataDir)) };
|
|
486
489
|
}
|
|
487
490
|
async workspaceGet(request) {
|
|
488
|
-
const config = readAgentWorkspace(
|
|
491
|
+
const config = readAgentWorkspace(effectiveDataDir(this.cfg.dataDir), String(request?.agentId ?? ''));
|
|
489
492
|
if (config === null)
|
|
490
493
|
throw new Error(`workspaceGet: agent 不存在或 id 不合法`);
|
|
491
494
|
return config;
|
|
492
495
|
}
|
|
493
496
|
async workspaceSet(request) {
|
|
494
|
-
return setAgentWorkspace(
|
|
497
|
+
return setAgentWorkspace(effectiveDataDir(this.cfg.dataDir), String(request?.agentId ?? ''), {
|
|
495
498
|
workspaceRoot: request?.workspaceRoot,
|
|
496
499
|
allowPaths: request?.allowPaths,
|
|
497
500
|
});
|
|
@@ -516,7 +519,7 @@ export class BotsRemote extends TypertRemoteService {
|
|
|
516
519
|
stage: request.stage,
|
|
517
520
|
detail: request.detail ?? null,
|
|
518
521
|
});
|
|
519
|
-
appendFileSync(join(
|
|
522
|
+
appendFileSync(join(effectiveDataDir(this.cfg.dataDir), DIAG_FILE), line + '\n');
|
|
520
523
|
return { written: true };
|
|
521
524
|
}
|
|
522
525
|
catch {
|
package/lib/types/gateway.d.ts
CHANGED
|
@@ -15,6 +15,24 @@ export interface Discovery {
|
|
|
15
15
|
token: string | null;
|
|
16
16
|
host: string;
|
|
17
17
|
}
|
|
18
|
+
/** New default root (0.2.16). The gateway may still live at the legacy root
|
|
19
|
+
* until the engine's SAND_DATA_ROOT migrates — every discovery consumer tries
|
|
20
|
+
* the configured dir first, then the legacy one. */
|
|
21
|
+
export declare const LEGACY_DATA_DIR = "~/.sdk-bots";
|
|
22
|
+
/**
|
|
23
|
+
* Discovery that follows the gateway to wherever it actually lives: the
|
|
24
|
+
* configured dir when it holds a gateway.json, else the legacy root. Returns
|
|
25
|
+
* null when neither has one (the gateway is simply down).
|
|
26
|
+
*/
|
|
27
|
+
export declare function readDiscoveryWithFallback(dataDir: string, legacyDir?: string): Discovery | null;
|
|
28
|
+
/**
|
|
29
|
+
* The directory where the gateway (and therefore agents/, box-workspace/,
|
|
30
|
+
* swarm/) actually lives right now: the configured dir if it has discovery,
|
|
31
|
+
* else the legacy root, else the configured dir as-is. All state-adjacent
|
|
32
|
+
* consumers (unread ledger, workspace ops, media allowlist, diag) resolve
|
|
33
|
+
* through this so they stay co-located with the real data across migration.
|
|
34
|
+
*/
|
|
35
|
+
export declare function effectiveDataDir(dataDir: string, legacyDir?: string): string;
|
|
18
36
|
export declare function expandHome(p: string): string;
|
|
19
37
|
/** Read loopback discovery from `<dataDir>/gateway.json`; null when absent/stale. */
|
|
20
38
|
export declare function readDiscovery(dataDir: string): Discovery | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-bots",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.17",
|
|
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",
|