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 CHANGED
@@ -2,4 +2,4 @@
2
2
  - id: bots
3
3
  name: dsh-bots
4
4
  config:
5
- dataDir: '~/.sdk-bots'
5
+ dataDir: '~/.dsh-bots'
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: () => setPicked({ ...picked, [m.id]: !picked[m.id] }),
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 = readDiscovery(dataDir);
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 = readDiscovery(dataDir);
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, readDiscovery, trimAgent, trimEntry } from './gateway.js';
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 = '~/.sdk-bots';
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(expandHome(this.cfg.dataDir), UNREAD_FILE));
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 = readDiscovery(this.cfg.dataDir);
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(expandHome(this.cfg.dataDir));
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(expandHome(this.cfg.dataDir)) };
488
+ return { workspaces: listAgentWorkspaces(effectiveDataDir(this.cfg.dataDir)) };
486
489
  }
487
490
  async workspaceGet(request) {
488
- const config = readAgentWorkspace(expandHome(this.cfg.dataDir), String(request?.agentId ?? ''));
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(expandHome(this.cfg.dataDir), String(request?.agentId ?? ''), {
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(expandHome(this.cfg.dataDir), DIAG_FILE), line + '\n');
522
+ appendFileSync(join(effectiveDataDir(this.cfg.dataDir), DIAG_FILE), line + '\n');
520
523
  return { written: true };
521
524
  }
522
525
  catch {
@@ -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.15",
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",