dsh-plugin-tool-management 0.7.0 → 0.8.5
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/CHANGELOG.md +75 -0
- package/README.md +19 -5
- package/README_EN.md +19 -5
- package/docs/images/1/345/234/272/346/231/257.png +0 -0
- package/docs/images/1/345/234/272/346/231/257_en.png +0 -0
- package/docs/images/2MCP.png +0 -0
- package/docs/images/2MCP_en.png +0 -0
- package/docs/images/3/346/212/200/350/203/275.png +0 -0
- package/docs/images/3/346/212/200/350/203/275_en.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
- package/docs/images/4/345/255/220/346/231/272/350/203/275/344/275/223_en.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215.png +0 -0
- package/docs/images/5/346/217/220/347/244/272/350/257/215_en.png +0 -0
- package/docs/images/6/350/256/260/345/277/206.png +0 -0
- package/docs/images/6/350/256/260/345/277/206_en.png +0 -0
- package/docs/images/7/344/274/232/350/257/235.png +0 -0
- package/docs/images/7/344/274/232/350/257/235_en.png +0 -0
- package/docs/images/8/345/205/274/345/256/271.png +0 -0
- package/docs/images/8/345/205/274/345/256/271_en.png +0 -0
- package/docs/update.md +31 -0
- package/lib/agents-md/service.js +41 -3
- package/lib/client.js +913 -415
- package/lib/compat/preset-reach.js +1 -1
- package/lib/compat/probe.js +12 -2
- package/lib/index.js +651 -31
- package/lib/mcp/state-section.js +90 -0
- package/lib/prompt-sections.js +148 -0
- package/lib/rules/archive-engine.js +151 -30
- package/lib/rules/archive.js +78 -28
- package/lib/rules/service.js +273 -41
- package/lib/skills/core.js +36 -1
- package/lib/skills/service.js +36 -10
- package/lib/subagents/catalog.js +90 -0
- package/lib/subagents/service.js +187 -20
- package/lib/subagents/tools.js +10 -3
- package/package.json +97 -97
- package/lib/rules/provider.js +0 -121
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { filterBySceneBinding } from './tools.js';
|
|
2
|
+
/**
|
|
3
|
+
* 描述截断长度 —— 照抄官方技能目录的默认值
|
|
4
|
+
* (`dsh-tool-skill` 的 `DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH = 500`)。
|
|
5
|
+
*
|
|
6
|
+
* 照抄的理由:截断风格与模型**已经见过的**技能目录一致,不用让它适应两套习惯;
|
|
7
|
+
* 而且这是官方旋钮的默认值,不是本插件拍脑袋定的数。日后 token 成本成为问题时,
|
|
8
|
+
* 官方对应的旋钮是 `catalogDescriptionMaxLength`。
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH = 500;
|
|
11
|
+
/** 段里最多列几个人设;超出部分只报数量,让模型自己去调 subagent_list。 */
|
|
12
|
+
export const DEFAULT_CATALOG_MAX_ENTRIES = 40;
|
|
13
|
+
/** 无描述时的占位,与 `subagent_list` 工具的输出保持同一口径。 */
|
|
14
|
+
const NO_DESCRIPTION = '(无描述)';
|
|
15
|
+
/**
|
|
16
|
+
* 描述归一化 + 截断 —— 逐字对齐官方 `dsh-tool-skill` 的实现:
|
|
17
|
+
* 先把所有空白折叠成单空格,再按长度截断并补省略号。保证「一人设一行」。
|
|
18
|
+
*/
|
|
19
|
+
export function catalogDescription(value, maxLength) {
|
|
20
|
+
const normalized = String(value ?? '').replaceAll(/\s+/g, ' ').trim();
|
|
21
|
+
if (normalized.length <= maxLength)
|
|
22
|
+
return normalized;
|
|
23
|
+
return `${normalized.slice(0, maxLength - 3)}...`;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 渲染人设目录段(纯函数,便于单独推理)。
|
|
27
|
+
*
|
|
28
|
+
* 返回 `''` 表示不注入 —— `renderPrompt` 会删除空段,所以不用人设的用户零 token 成本。
|
|
29
|
+
* 名字按字典序排序:即使底层目录枚举顺序变化,段文本也保持逐字节稳定(前缀缓存契约)。
|
|
30
|
+
*/
|
|
31
|
+
export function renderSubagentCatalog(allowed, maxEntries = DEFAULT_CATALOG_MAX_ENTRIES, maxDescription = DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH) {
|
|
32
|
+
if (!allowed.length)
|
|
33
|
+
return '';
|
|
34
|
+
const sorted = [...allowed].sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
35
|
+
const shown = sorted.slice(0, Math.max(0, maxEntries));
|
|
36
|
+
const lines = shown.map((p) => '- **' + p.name + '** — ' + (catalogDescription(p.description, maxDescription) || NO_DESCRIPTION));
|
|
37
|
+
const hidden = sorted.length - shown.length;
|
|
38
|
+
const out = [
|
|
39
|
+
'## 子智能体(人设)',
|
|
40
|
+
'',
|
|
41
|
+
'如需要委派独立任务,可从下面的人设中选用合适的(也可先用 `subagent_list` 查看全部人设)。',
|
|
42
|
+
'用 `subagent_run` 委派时,该人设的完整提示词会成为子代理的系统提示词,子代理在独立上下文中执行,只返回最终结果。',
|
|
43
|
+
'',
|
|
44
|
+
...lines,
|
|
45
|
+
];
|
|
46
|
+
if (hidden > 0)
|
|
47
|
+
out.push('', `(另有 ${hidden} 个未列出,用 subagent_list 查看全部。)`);
|
|
48
|
+
return out.join('\n');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 创建 SWR 人设目录。
|
|
52
|
+
*
|
|
53
|
+
* ⚠️ 计算失败(读盘异常等)时**保留上一次的值**,不把段清空 —— 否则一次瞬时 IO 抖动
|
|
54
|
+
* 会让模型突然「忘记」有哪些人设。
|
|
55
|
+
*/
|
|
56
|
+
export function createSubagentCatalog(deps, opts = {}) {
|
|
57
|
+
const maxEntries = opts.maxEntries ?? DEFAULT_CATALOG_MAX_ENTRIES;
|
|
58
|
+
const maxDescription = opts.maxDescription ?? DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH;
|
|
59
|
+
const ttlMs = opts.ttlMs ?? 1000;
|
|
60
|
+
const now = opts.now ?? (() => Date.now());
|
|
61
|
+
let value = '';
|
|
62
|
+
let loadedAt = Number.NEGATIVE_INFINITY;
|
|
63
|
+
let inflight = null;
|
|
64
|
+
const recompute = async () => {
|
|
65
|
+
try {
|
|
66
|
+
const { allowed } = await filterBySceneBinding(await deps.list(), await deps.sceneLists());
|
|
67
|
+
value = renderSubagentCatalog(allowed, maxEntries, maxDescription);
|
|
68
|
+
}
|
|
69
|
+
catch { /* 保留上一次的值;首次失败则维持 '' */ }
|
|
70
|
+
loadedAt = now();
|
|
71
|
+
};
|
|
72
|
+
const revalidate = () => {
|
|
73
|
+
if (inflight)
|
|
74
|
+
return inflight;
|
|
75
|
+
inflight = recompute().finally(() => { inflight = null; });
|
|
76
|
+
return inflight;
|
|
77
|
+
};
|
|
78
|
+
return {
|
|
79
|
+
text: () => {
|
|
80
|
+
if (now() - loadedAt > ttlMs)
|
|
81
|
+
void revalidate();
|
|
82
|
+
return value;
|
|
83
|
+
},
|
|
84
|
+
refresh: () => {
|
|
85
|
+
loadedAt = Number.NEGATIVE_INFINITY;
|
|
86
|
+
return revalidate();
|
|
87
|
+
},
|
|
88
|
+
warm: () => revalidate(),
|
|
89
|
+
};
|
|
90
|
+
}
|
package/lib/subagents/service.js
CHANGED
|
@@ -130,26 +130,96 @@ export function parsePersona(raw, fallbackName) {
|
|
|
130
130
|
const RESULT_MAX = 16 * 1024;
|
|
131
131
|
export function createSubagentService(ctx, opts) {
|
|
132
132
|
const dir = opts?.subagentsDir || defaultPersonasDir();
|
|
133
|
+
const stateDir = opts?.stateDir && opts.stateDir.trim() !== '' ? opts.stateDir : join(resolveDshHome(), 'tool-management');
|
|
134
|
+
const stateFile = join(stateDir, 'agents-index.json');
|
|
133
135
|
const req = createRequire(import.meta.url);
|
|
134
136
|
let cache = null;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
// ── 人设启用集合(子智能体开关)────────────────────────────────────────
|
|
138
|
+
// agents-index.json:{ version: 1, enabled: string[] }(与 rules-index.json 同目录约定)。
|
|
139
|
+
// - 文件缺失/损坏 = **全部启用**(老用户升级零感知,行为与开关上线前一致);
|
|
140
|
+
// - 文件一旦写出即为权威:之后新建/导入/回收站恢复的人设**自动启用**(刚建就想用是常理);
|
|
141
|
+
// - 停用只影响注入与 subagent_* 工具的可见性,人设文件一个字节不动。
|
|
142
|
+
// 缓存约定:undefined = 还没读过盘;null = 文件缺失(全部启用);数组 = 权威集合。
|
|
143
|
+
let enabledCache = undefined;
|
|
144
|
+
async function readEnabled() {
|
|
145
|
+
if (enabledCache !== undefined)
|
|
146
|
+
return enabledCache;
|
|
147
|
+
// 用局部变量过渡:await 之后 TS 对闭包级缓存变量的收窄会失效,直接返回会报 undefined。
|
|
148
|
+
let next;
|
|
141
149
|
try {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
const raw = JSON.parse(await readFile(stateFile, 'utf8'));
|
|
151
|
+
next = Array.isArray(raw && raw.enabled) ? raw.enabled.map((x) => String(x)) : [];
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
next = null;
|
|
155
|
+
}
|
|
156
|
+
enabledCache = next;
|
|
157
|
+
return next;
|
|
158
|
+
}
|
|
159
|
+
async function writeEnabled(list) {
|
|
160
|
+
await mkdir(stateDir, { recursive: true });
|
|
161
|
+
await writeFile(stateFile, JSON.stringify({ version: 1, enabled: list }, null, 2), 'utf8');
|
|
162
|
+
enabledCache = list;
|
|
163
|
+
}
|
|
164
|
+
/** 新建/导入/恢复的人设默认停用(v0.8.5 用户裁定,与技能 / MCP 同口径):
|
|
165
|
+
* 文件缺失(含旧数据)时先把「全部启用」物化成显式全集**并排除新名**——
|
|
166
|
+
* 否则新名会随"文件缺失=全启用"的兼容语义被误判为启用。已有显式集合时新名
|
|
167
|
+
* 不在集合里,天然停用,无需写盘。 */
|
|
168
|
+
async function materializeEnabledExcluding(names) {
|
|
169
|
+
const set = await readEnabled();
|
|
170
|
+
if (set !== null)
|
|
171
|
+
return;
|
|
172
|
+
const docs = await list();
|
|
173
|
+
await writeEnabled(docs.map((d) => d.name).filter((n) => names.indexOf(n) < 0));
|
|
174
|
+
cache = null;
|
|
175
|
+
}
|
|
176
|
+
/** 改名跟随:旧名在集合里就改新名;不在(停用中)保持停用。 */
|
|
177
|
+
async function renamePersonaInEnabled(from, to) {
|
|
178
|
+
const set = await readEnabled();
|
|
179
|
+
if (set === null)
|
|
180
|
+
return;
|
|
181
|
+
const i = set.indexOf(from);
|
|
182
|
+
if (i < 0)
|
|
183
|
+
return;
|
|
184
|
+
set[i] = to;
|
|
185
|
+
await writeEnabled(set);
|
|
186
|
+
}
|
|
187
|
+
/** 删除后从集合摘掉,别留悬空名(列表时无害,但会让集合越攒越脏)。 */
|
|
188
|
+
async function removePersonaFromEnabled(name) {
|
|
189
|
+
const set = await readEnabled();
|
|
190
|
+
if (set === null || set.indexOf(name) < 0)
|
|
191
|
+
return;
|
|
192
|
+
await writeEnabled(set.filter((n) => n !== name));
|
|
193
|
+
}
|
|
194
|
+
/** list() 输出统一附上 enabled:文件缺失 → 全 true;否则按集合。 */
|
|
195
|
+
function withEnabled(docs, set) {
|
|
196
|
+
if (set === null)
|
|
197
|
+
return docs.map((d) => ({ ...d, enabled: true }));
|
|
198
|
+
return docs.map((d) => ({ ...d, enabled: set.indexOf(d.name) >= 0 }));
|
|
199
|
+
}
|
|
200
|
+
async function list() {
|
|
201
|
+
let docs;
|
|
202
|
+
if (cache && Date.now() - cache.at < 1000) {
|
|
203
|
+
docs = cache.value;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
// 旧目录搬家放在首次扫描前(幂等;失败不阻断,旧目录仍会被下面的读取兜底看到)。
|
|
207
|
+
await relocateLegacyPersonas(dir).catch(() => undefined);
|
|
208
|
+
const fresh = [];
|
|
209
|
+
try {
|
|
210
|
+
const entries = await readdir(dir);
|
|
211
|
+
for (const fileName of entries.filter((e) => e.endsWith('.md')).sort()) {
|
|
212
|
+
const raw = await readFile(join(dir, fileName), 'utf8').catch(() => '');
|
|
213
|
+
if (!raw.trim())
|
|
214
|
+
continue;
|
|
215
|
+
fresh.push({ ...parsePersona(raw, fileName.slice(0, -3)), path: join(dir, fileName) });
|
|
216
|
+
}
|
|
148
217
|
}
|
|
218
|
+
catch { /* 目录缺失 = 无人设 */ }
|
|
219
|
+
cache = { at: Date.now(), value: fresh };
|
|
220
|
+
docs = fresh;
|
|
149
221
|
}
|
|
150
|
-
|
|
151
|
-
cache = { at: Date.now(), value: docs };
|
|
152
|
-
return docs;
|
|
222
|
+
return withEnabled(docs, await readEnabled());
|
|
153
223
|
}
|
|
154
224
|
// spawn provider 在场性(设计 §3.2,落地方式见 cordis.patch.yml 的取舍注释):
|
|
155
225
|
// 官方通道探测(ctx.subagents.list())→ 缺失才挂载 → 失败把原因原样带出(不吞、不谎报)。
|
|
@@ -254,7 +324,7 @@ export function createSubagentService(ctx, opts) {
|
|
|
254
324
|
const docs = await list();
|
|
255
325
|
return {
|
|
256
326
|
ok: true,
|
|
257
|
-
subagents: docs.map((p) => ({ name: p.name, description: p.description, provider: p.provider ?? null, model: p.model ?? null, tools: p.tools ?? null, toolsDeny: p.toolsDeny ?? null, toolsByPreset: p.toolsByPreset ?? null })),
|
|
327
|
+
subagents: docs.map((p) => ({ name: p.name, enabled: p.enabled !== false, description: p.description, provider: p.provider ?? null, model: p.model ?? null, tools: p.tools ?? null, toolsDeny: p.toolsDeny ?? null, toolsByPreset: p.toolsByPreset ?? null })),
|
|
258
328
|
};
|
|
259
329
|
},
|
|
260
330
|
'subagent-get': async (args) => {
|
|
@@ -282,9 +352,17 @@ export function createSubagentService(ctx, opts) {
|
|
|
282
352
|
return { ok: false, error: `创建人设目录失败: ${dir}(${message(e)})` };
|
|
283
353
|
}
|
|
284
354
|
await writeFile(target, serializePersona(args), 'utf8');
|
|
355
|
+
// v0.8.5:新建默认不启动——显式集合下新名天然停用;文件缺失时先物化全集并排除新名。
|
|
356
|
+
await materializeEnabledExcluding([name]).catch(() => undefined);
|
|
285
357
|
cache = null;
|
|
286
358
|
return { ok: true, name };
|
|
287
359
|
},
|
|
360
|
+
/**
|
|
361
|
+
* 保存人设,**可选改名**(nextName)。
|
|
362
|
+
* 人设名就是文件名(`agents/<名>.md`),所以改名 = 重命名文件;
|
|
363
|
+
* 目标名已存在直接拒绝,绝不覆盖别人的文件。
|
|
364
|
+
* 返回 `renamedFrom` 供调用方把场景档案里的绑定一起改掉(档案存的是人设名)。
|
|
365
|
+
*/
|
|
288
366
|
'subagent-update': async (args) => {
|
|
289
367
|
const name = String((args && args.name) || '').trim();
|
|
290
368
|
if (!validPersonaName(name))
|
|
@@ -293,9 +371,31 @@ export function createSubagentService(ctx, opts) {
|
|
|
293
371
|
const exists = await readFile(target, 'utf8').then(() => true).catch(() => false);
|
|
294
372
|
if (!exists)
|
|
295
373
|
return { ok: false, error: `人设不存在: ${name}` };
|
|
296
|
-
|
|
374
|
+
let finalName = name;
|
|
375
|
+
let renamedFrom;
|
|
376
|
+
const nextRaw = args && args.nextName !== undefined ? String(args.nextName).trim() : '';
|
|
377
|
+
if (nextRaw !== '' && nextRaw !== name) {
|
|
378
|
+
if (!validPersonaName(nextRaw)) {
|
|
379
|
+
return { ok: false, error: `新人设名不合法: ${nextRaw}(非空、≤64 字符、不含路径分隔符与 < > : " | ? *、不以 . 开头)` };
|
|
380
|
+
}
|
|
381
|
+
const nextTarget = join(dir, nextRaw + '.md');
|
|
382
|
+
const taken = await readFile(nextTarget, 'utf8').then(() => true).catch(() => false);
|
|
383
|
+
if (taken)
|
|
384
|
+
return { ok: false, error: `人设已存在: ${nextRaw}` };
|
|
385
|
+
try {
|
|
386
|
+
await rename(target, nextTarget);
|
|
387
|
+
}
|
|
388
|
+
catch (e) {
|
|
389
|
+
return { ok: false, error: `人设改名失败: ${message(e)}` };
|
|
390
|
+
}
|
|
391
|
+
finalName = nextRaw;
|
|
392
|
+
renamedFrom = name;
|
|
393
|
+
}
|
|
394
|
+
await writeFile(join(dir, finalName + '.md'), serializePersona({ ...args, name: finalName }), 'utf8');
|
|
395
|
+
if (renamedFrom)
|
|
396
|
+
await renamePersonaInEnabled(renamedFrom, finalName).catch(() => undefined);
|
|
297
397
|
cache = null;
|
|
298
|
-
return { ok: true, name };
|
|
398
|
+
return { ok: true, name: finalName, ...(renamedFrom ? { renamedFrom } : {}) };
|
|
299
399
|
},
|
|
300
400
|
/**
|
|
301
401
|
* 删除人设 = **移入回收站**(`hub/trash/agents-trash/<id>/persona.md`)。
|
|
@@ -313,6 +413,7 @@ export function createSubagentService(ctx, opts) {
|
|
|
313
413
|
const moved = await moveToTrash('agents', name, [{ from: target, dest: 'persona.md' }]);
|
|
314
414
|
if (moved.ok === false)
|
|
315
415
|
return { ok: false, error: `移入回收站失败: ${moved.error}` };
|
|
416
|
+
await removePersonaFromEnabled(name).catch(() => undefined);
|
|
316
417
|
cache = null;
|
|
317
418
|
return { ok: true, name, trashId: moved.id };
|
|
318
419
|
},
|
|
@@ -337,6 +438,8 @@ export function createSubagentService(ctx, opts) {
|
|
|
337
438
|
return { ok: false, error: `恢复失败: ${message(e)}` };
|
|
338
439
|
}
|
|
339
440
|
await purgeTrashEntry('agents', id);
|
|
441
|
+
// v0.8.5:回收站恢复默认不启动(与新建/导入同口径)。
|
|
442
|
+
await materializeEnabledExcluding([entry.name]).catch(() => undefined);
|
|
340
443
|
cache = null;
|
|
341
444
|
return { ok: true, name: entry.name };
|
|
342
445
|
},
|
|
@@ -386,12 +489,76 @@ export function createSubagentService(ctx, opts) {
|
|
|
386
489
|
}
|
|
387
490
|
imported.push(target.name);
|
|
388
491
|
}
|
|
389
|
-
if (imported.length)
|
|
492
|
+
if (imported.length) {
|
|
493
|
+
await materializeEnabledExcluding(imported).catch(() => undefined);
|
|
390
494
|
cache = null;
|
|
495
|
+
}
|
|
391
496
|
return { ok: true, imported, skipped };
|
|
392
497
|
},
|
|
498
|
+
/**
|
|
499
|
+
* 子智能体开关(v0.8):停用 = 不注入目录段、subagent_list/run 不可见;文件本体不动。
|
|
500
|
+
* `enabled` 必须显式给布尔值 —— 与 rules-toggle 同一条口径:不按"翻转"推断,
|
|
501
|
+
* 免得参数丢了时界面以为改了、实际什么都没动。
|
|
502
|
+
*/
|
|
503
|
+
'subagent-toggle': async (args) => {
|
|
504
|
+
const name = String((args && args.name) || '').trim();
|
|
505
|
+
if (!validPersonaName(name))
|
|
506
|
+
return { ok: false, error: `人设名不合法: ${name || '(空)'}` };
|
|
507
|
+
if (typeof (args && args.enabled) !== 'boolean') {
|
|
508
|
+
return { ok: false, error: '缺少参数:enabled 必须是布尔值(只按传入值写入,不做"翻转"推断)' };
|
|
509
|
+
}
|
|
510
|
+
const docs = await list();
|
|
511
|
+
if (!docs.some((d) => d.name === name))
|
|
512
|
+
return { ok: false, error: `人设不存在: ${name}` };
|
|
513
|
+
const set = await readEnabled();
|
|
514
|
+
// 文件缺失 = 当前全启用:首次开关时把现状物化成显式集合(否则"部分停用"无处落笔)。
|
|
515
|
+
const base = (set === null ? docs.map((d) => d.name) : set.slice()).filter((n) => docs.some((d) => d.name === n));
|
|
516
|
+
const i = base.indexOf(name);
|
|
517
|
+
if (args.enabled === true) {
|
|
518
|
+
if (i < 0)
|
|
519
|
+
base.push(name);
|
|
520
|
+
}
|
|
521
|
+
else if (i >= 0) {
|
|
522
|
+
base.splice(i, 1);
|
|
523
|
+
}
|
|
524
|
+
await writeEnabled(base);
|
|
525
|
+
cache = null;
|
|
526
|
+
return { ok: true, name, enabled: args.enabled === true };
|
|
527
|
+
},
|
|
528
|
+
};
|
|
529
|
+
const enabledStore = {
|
|
530
|
+
/** 指定名单里当前被停用的(进入模式拍快照用:只记将被启用的行,退出时精确停回)。 */
|
|
531
|
+
async disabledAmong(names) {
|
|
532
|
+
const docs = await list();
|
|
533
|
+
const state = new Map(docs.map((d) => [d.name, d.enabled !== false]));
|
|
534
|
+
return names.filter((n) => state.get(n) === false);
|
|
535
|
+
},
|
|
536
|
+
/** 批量启停;只碰给出的名字,人设已不存在的跳过(别把悬空名写进集合)。 */
|
|
537
|
+
async setEnabled(names, enabled) {
|
|
538
|
+
const docs = await list();
|
|
539
|
+
const set = await readEnabled();
|
|
540
|
+
const base = (set === null ? docs.map((d) => d.name) : set.slice()).filter((n) => docs.some((d) => d.name === n));
|
|
541
|
+
let dirty = false;
|
|
542
|
+
for (const n of names) {
|
|
543
|
+
if (!docs.some((d) => d.name === n))
|
|
544
|
+
continue;
|
|
545
|
+
const i = base.indexOf(n);
|
|
546
|
+
if (enabled && i < 0) {
|
|
547
|
+
base.push(n);
|
|
548
|
+
dirty = true;
|
|
549
|
+
}
|
|
550
|
+
if (!enabled && i >= 0) {
|
|
551
|
+
base.splice(i, 1);
|
|
552
|
+
dirty = true;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
if (dirty) {
|
|
556
|
+
await writeEnabled(base);
|
|
557
|
+
cache = null;
|
|
558
|
+
}
|
|
559
|
+
},
|
|
393
560
|
};
|
|
394
|
-
return { list, runSerial, ops, writeOps: new Set(['subagent-create', 'subagent-update', 'subagent-delete', 'subagent-import', 'subagent-trash-restore', 'subagent-trash-delete']) };
|
|
561
|
+
return { list, runSerial, ops, enabledStore, writeOps: new Set(['subagent-create', 'subagent-update', 'subagent-delete', 'subagent-import', 'subagent-toggle', 'subagent-trash-restore', 'subagent-trash-delete']) };
|
|
395
562
|
}
|
|
396
563
|
function validPersonaName(name) {
|
|
397
564
|
return name.length > 0 && name.length <= 64 && !name.startsWith('.') && !/[\\/<>:"|?*]/.test(name);
|
package/lib/subagents/tools.js
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
export function text(v) {
|
|
2
2
|
return [{ type: 'text', text: v }];
|
|
3
3
|
}
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* 可见性过滤(两道闸,按序):
|
|
6
|
+
* ① 人设开关(全局):停用的绝不进上下文 —— 即便它被场景绑定(场景启动时会自动把
|
|
7
|
+
* 绑定的人设启用,见 archive-engine 的 applySubagentSwitches)。
|
|
8
|
+
* ② 场景绑定校验(设计 §3.3):启用场景的 subagents 并集;空并集 = 全部可用。
|
|
9
|
+
* 之前只有②:全局默认所有子智能体都进上下文,没有开关可言。
|
|
10
|
+
*/
|
|
5
11
|
export async function filterBySceneBinding(docs, enabledSceneLists) {
|
|
12
|
+
const live = docs.filter((d) => d.enabled !== false);
|
|
6
13
|
const bound = new Set(enabledSceneLists.flat());
|
|
7
14
|
if (!bound.size)
|
|
8
|
-
return { allowed:
|
|
9
|
-
const allowed =
|
|
15
|
+
return { allowed: live, reason: null };
|
|
16
|
+
const allowed = live.filter((d) => bound.has(d.name));
|
|
10
17
|
return { allowed, reason: allowed.length ? null : `当前启用场景的子智能体绑定: ${[...bound].join('、')}` };
|
|
11
18
|
}
|
|
12
19
|
export function defineSubagentListTool(subagents) {
|
package/package.json
CHANGED
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "dsh-plugin-tool-management",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "DSH plugin: one settings panel for MCP servers,
|
|
5
|
-
"repository": {
|
|
6
|
-
"type": "git",
|
|
7
|
-
"url": "https://github.com/ouli-1242/dsh-plugin-tool-management"
|
|
8
|
-
},
|
|
9
|
-
"type": "module",
|
|
10
|
-
"main": "lib/index.js",
|
|
11
|
-
"exports": {
|
|
12
|
-
".": "./lib/index.js",
|
|
13
|
-
"./client": "./lib/client.js",
|
|
14
|
-
"./workspace": "./lib/history/workspace.js",
|
|
15
|
-
"./projcache": "./lib/history/projcache.js",
|
|
16
|
-
"./package.json": "./package.json"
|
|
17
|
-
},
|
|
18
|
-
"files": [
|
|
19
|
-
"lib",
|
|
20
|
-
"cordis.patch.yml",
|
|
21
|
-
"README.md",
|
|
22
|
-
"README_EN.md",
|
|
23
|
-
"CHANGELOG.md",
|
|
24
|
-
"LICENSE",
|
|
25
|
-
"screenshots.json",
|
|
26
|
-
"docs"
|
|
27
|
-
],
|
|
28
|
-
"engines": {
|
|
29
|
-
"node": ">=18"
|
|
30
|
-
},
|
|
31
|
-
"publishConfig": {
|
|
32
|
-
"registry": "https://registry.npmjs.org",
|
|
33
|
-
"access": "public"
|
|
34
|
-
},
|
|
35
|
-
"scripts": {
|
|
36
|
-
"build": "tsc -p tsconfig.json && node scripts/sync-client.mjs",
|
|
37
|
-
"build:client": "node scripts/sync-client.mjs",
|
|
38
|
-
"prepublishOnly": "npm run build",
|
|
39
|
-
"lint": "node --check lib/client.js && node --check lib/index.js",
|
|
40
|
-
"check:i18n": "node scripts/check-i18n.mjs src/client.js",
|
|
41
|
-
"check:host": "node scripts/host-deps.mjs && node scripts/doctor.mjs",
|
|
42
|
-
"host-deps": "node scripts/host-deps.mjs --fix",
|
|
43
|
-
"doctor": "node scripts/doctor.mjs",
|
|
44
|
-
"test": "npm run build && npm run check:i18n && node --test test/archive.test.mjs test/import.test.mjs test/approval-policy.test.mjs test/subagent-scene.test.mjs test/subagent-persona.test.mjs test/hub-layout.test.mjs test/skills-state.test.mjs test/skills-delete.test.mjs test/skills-source-remove.test.mjs test/client-exports.test.mjs test/client-render.test.mjs test/compat-probe.test.mjs test/compat-fallback.test.mjs test/preset-reach.test.mjs test/rules-toggle-args.test.mjs"
|
|
45
|
-
},
|
|
46
|
-
"keywords": [
|
|
47
|
-
"dsh",
|
|
48
|
-
"deepseek-harness",
|
|
49
|
-
"skill",
|
|
50
|
-
"skills",
|
|
51
|
-
"mcp",
|
|
52
|
-
"mcp-server",
|
|
53
|
-
"plugin",
|
|
54
|
-
"cordis"
|
|
55
|
-
],
|
|
56
|
-
"dsh": {
|
|
57
|
-
"bundle": {
|
|
58
|
-
"patch": "./cordis.patch.yml"
|
|
59
|
-
},
|
|
60
|
-
"client": {
|
|
61
|
-
"platform": "web",
|
|
62
|
-
"inject": [
|
|
63
|
-
"@deepseek-ai/dsh-client-locale"
|
|
64
|
-
]
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"peerDependencies": {
|
|
68
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
69
|
-
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.2
|
|
70
|
-
"@deepseek-ai/dsh-workspace": ">=0.1.5-rc.2
|
|
71
|
-
"@deepseek-ai/dsh-spill-local": ">=0.1.5-rc.2
|
|
72
|
-
"@deepseek-ai/dsh-session-projection-cache": ">=0.1.5-rc.2
|
|
73
|
-
"@deepseek-ai/dsh-storage-domain": ">=0.1.5-rc.2
|
|
74
|
-
"@deepseek-ai/dsh-subagent-spawn-in-process": ">=0.1.5-rc.2
|
|
75
|
-
"react": "^18.2.0"
|
|
76
|
-
},
|
|
77
|
-
"peerDependenciesMeta": {
|
|
78
|
-
"@deepseek-ai/dsh-subagent-spawn-in-process": {
|
|
79
|
-
"optional": true
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
"devDependencies": {
|
|
83
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
84
|
-
"@deepseek-ai/dsh-session-projection-cache": "^0.1.5-rc.2",
|
|
85
|
-
"@deepseek-ai/dsh-spill-local": "^0.1.5-rc.2",
|
|
86
|
-
"@deepseek-ai/dsh-storage-domain": "^0.1.5-rc.2",
|
|
87
|
-
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
88
|
-
"@deepseek-ai/dsh-user-approval": "^0.1.5-rc.2",
|
|
89
|
-
"@deepseek-ai/dsh-workspace": "^0.1.5-rc.2",
|
|
90
|
-
"@types/node": "^24.0.0",
|
|
91
|
-
"typescript": "^6.0.0"
|
|
92
|
-
},
|
|
93
|
-
"license": "MIT",
|
|
94
|
-
"dependencies": {
|
|
95
|
-
"fflate": "^0.8.3"
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "dsh-plugin-tool-management",
|
|
3
|
+
"version": "0.8.5",
|
|
4
|
+
"description": "DSH plugin: one settings panel for MCP servers, skills, scenes, memories, subagents, AGENTS.md presets and archived sessions — scene memory injects into the system prompt, scene lock freezes all five domains, deleted workspaces re-register with one click. Settings UI + HTTP API + 14 model tools (skill_mcp_manager_*, skill_manager_*, agentsmd_*, rule_manager_*, subagent_*). Install with one command: dsh plugin --profile web add dsh-plugin-tool-management@latest",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/ouli-1242/dsh-plugin-tool-management"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"main": "lib/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./lib/index.js",
|
|
13
|
+
"./client": "./lib/client.js",
|
|
14
|
+
"./workspace": "./lib/history/workspace.js",
|
|
15
|
+
"./projcache": "./lib/history/projcache.js",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"lib",
|
|
20
|
+
"cordis.patch.yml",
|
|
21
|
+
"README.md",
|
|
22
|
+
"README_EN.md",
|
|
23
|
+
"CHANGELOG.md",
|
|
24
|
+
"LICENSE",
|
|
25
|
+
"screenshots.json",
|
|
26
|
+
"docs"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"registry": "https://registry.npmjs.org",
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsc -p tsconfig.json && node scripts/sync-client.mjs",
|
|
37
|
+
"build:client": "node scripts/sync-client.mjs",
|
|
38
|
+
"prepublishOnly": "npm run build",
|
|
39
|
+
"lint": "node --check lib/client.js && node --check lib/index.js",
|
|
40
|
+
"check:i18n": "node scripts/check-i18n.mjs src/client.js",
|
|
41
|
+
"check:host": "node scripts/host-deps.mjs && node scripts/doctor.mjs",
|
|
42
|
+
"host-deps": "node scripts/host-deps.mjs --fix",
|
|
43
|
+
"doctor": "node scripts/doctor.mjs",
|
|
44
|
+
"test": "npm run build && npm run check:i18n && node --test test/archive.test.mjs test/import.test.mjs test/approval-policy.test.mjs test/subagent-scene.test.mjs test/subagent-persona.test.mjs test/hub-layout.test.mjs test/skills-state.test.mjs test/skills-delete.test.mjs test/skills-source-remove.test.mjs test/client-exports.test.mjs test/client-render.test.mjs test/compat-probe.test.mjs test/compat-fallback.test.mjs test/preset-reach.test.mjs test/rules-toggle-args.test.mjs"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"dsh",
|
|
48
|
+
"deepseek-harness",
|
|
49
|
+
"skill",
|
|
50
|
+
"skills",
|
|
51
|
+
"mcp",
|
|
52
|
+
"mcp-server",
|
|
53
|
+
"plugin",
|
|
54
|
+
"cordis"
|
|
55
|
+
],
|
|
56
|
+
"dsh": {
|
|
57
|
+
"bundle": {
|
|
58
|
+
"patch": "./cordis.patch.yml"
|
|
59
|
+
},
|
|
60
|
+
"client": {
|
|
61
|
+
"platform": "web",
|
|
62
|
+
"inject": [
|
|
63
|
+
"@deepseek-ai/dsh-client-locale"
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"peerDependencies": {
|
|
68
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
69
|
+
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.2",
|
|
70
|
+
"@deepseek-ai/dsh-workspace": ">=0.1.5-rc.2",
|
|
71
|
+
"@deepseek-ai/dsh-spill-local": ">=0.1.5-rc.2",
|
|
72
|
+
"@deepseek-ai/dsh-session-projection-cache": ">=0.1.5-rc.2",
|
|
73
|
+
"@deepseek-ai/dsh-storage-domain": ">=0.1.5-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-subagent-spawn-in-process": ">=0.1.5-rc.2",
|
|
75
|
+
"react": "^18.2.0"
|
|
76
|
+
},
|
|
77
|
+
"peerDependenciesMeta": {
|
|
78
|
+
"@deepseek-ai/dsh-subagent-spawn-in-process": {
|
|
79
|
+
"optional": true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"devDependencies": {
|
|
83
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
84
|
+
"@deepseek-ai/dsh-session-projection-cache": "^0.1.5-rc.2",
|
|
85
|
+
"@deepseek-ai/dsh-spill-local": "^0.1.5-rc.2",
|
|
86
|
+
"@deepseek-ai/dsh-storage-domain": "^0.1.5-rc.2",
|
|
87
|
+
"@deepseek-ai/dsh-tools": "^0.1.5-rc.2",
|
|
88
|
+
"@deepseek-ai/dsh-user-approval": "^0.1.5-rc.2",
|
|
89
|
+
"@deepseek-ai/dsh-workspace": "^0.1.5-rc.2",
|
|
90
|
+
"@types/node": "^24.0.0",
|
|
91
|
+
"typescript": "^6.0.0"
|
|
92
|
+
},
|
|
93
|
+
"license": "MIT",
|
|
94
|
+
"dependencies": {
|
|
95
|
+
"fflate": "^0.8.3"
|
|
96
|
+
}
|
|
97
|
+
}
|