dsh-plugin-tool-management 0.6.0 → 0.8.0
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 +179 -0
- package/README.md +117 -221
- package/README_EN.md +116 -220
- 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 +56 -38
- package/lib/agents-md/service.js +84 -5
- package/lib/client.js +1488 -475
- package/lib/compat/preset-reach.js +425 -0
- package/lib/compat/probe.js +8 -0
- package/lib/http-fence.js +21 -0
- package/lib/hub.js +160 -2
- package/lib/index.js +687 -47
- package/lib/mcp/override-blocks.js +195 -0
- package/lib/mcp/state-section.js +90 -0
- package/lib/prompt-sections.js +148 -0
- package/lib/rules/archive-engine.js +99 -29
- package/lib/rules/archive.js +56 -28
- package/lib/rules/service.js +332 -35
- package/lib/skills/core.js +15 -1
- package/lib/skills/service.js +3 -0
- package/lib/subagents/catalog.js +89 -0
- package/lib/subagents/service.js +384 -41
- package/lib/subagents/tools.js +18 -5
- package/package.json +97 -96
- package/screenshots.json +10 -10
- package/docs/Changelog.md +0 -144
- package/lib/rules/provider.js +0 -121
package/lib/skills/core.js
CHANGED
|
@@ -178,6 +178,9 @@ export function customRootsFromState(stateValue) {
|
|
|
178
178
|
label: typeof item.label === "string" && item.label.trim()
|
|
179
179
|
? item.label.trim().slice(0, 64)
|
|
180
180
|
: "自定义目录",
|
|
181
|
+
// 用户加进来的来源(相对「按约定发现」的 dsh / hub / agents / codex / claude):
|
|
182
|
+
// 界面据此决定要不要给「永久删除」——只有自定义来源能真正从插件里删掉记录。
|
|
183
|
+
custom: true,
|
|
181
184
|
mutable: false,
|
|
182
185
|
toggleable: true,
|
|
183
186
|
native: false,
|
|
@@ -280,6 +283,10 @@ export async function removeCustomRoot(key, log) {
|
|
|
280
283
|
delete current.state.sources[clean];
|
|
281
284
|
delete current.state.disabledSkills[clean];
|
|
282
285
|
delete current.state.enabledSkills[clean];
|
|
286
|
+
// 已移除记录一起清掉:否则「永久删除」之后状态文件里还留着一条指向不存在来源的脏键
|
|
287
|
+
// (读取时的 normalize 会丢弃它,但没理由写进去)。
|
|
288
|
+
if (Array.isArray(current.state.removedSources))
|
|
289
|
+
current.state.removedSources = current.state.removedSources.filter((key) => key !== clean);
|
|
283
290
|
await writeManagerState(current.state);
|
|
284
291
|
if (log)
|
|
285
292
|
log("custom-remove", `移除自定义技能目录 ${target.path} (${clean})`);
|
|
@@ -876,7 +883,10 @@ async function writeFileAtomically(path, content) {
|
|
|
876
883
|
const temp = join(dirname(path), `.${basename(path)}.dssm-${randomUUID()}.tmp`);
|
|
877
884
|
try {
|
|
878
885
|
await fs.writeFile(temp, content, "utf8");
|
|
879
|
-
|
|
886
|
+
// Windows 上目标文件会被杀软/索引器短暂占住(EPERM/EACCES/EBUSY)——
|
|
887
|
+
// 场景模式进出时这里写的是技能来源/策略状态,rename 被撞 = 运行时已切、
|
|
888
|
+
// 状态没落盘。与 rules-index.json 同一处理:重试瞬时占用,全失败才抛。
|
|
889
|
+
await renameWithRetry(temp, path);
|
|
880
890
|
}
|
|
881
891
|
catch (error) {
|
|
882
892
|
await fs.rm(temp, { force: true }).catch(() => undefined);
|
|
@@ -2904,6 +2914,8 @@ export async function state(options = {}) {
|
|
|
2904
2914
|
mutable: root.mutable,
|
|
2905
2915
|
deletable: root.deletable === true,
|
|
2906
2916
|
removable: !isDefaultSkillSource(root) && root.scope !== "project",
|
|
2917
|
+
// 自定义来源(用户手加的目录):只有它能「永久删除」——按约定发现的来源删不掉。
|
|
2918
|
+
custom: root.custom === true,
|
|
2907
2919
|
// 默认来源(dsh / hub):界面据此隐藏来源开关、只显示「可管理」标记。
|
|
2908
2920
|
defaultSource: isDefaultSkillSource(root),
|
|
2909
2921
|
toggleable: root.toggleable,
|
|
@@ -2983,6 +2995,8 @@ export async function state(options = {}) {
|
|
|
2983
2995
|
truncated: truncated === true,
|
|
2984
2996
|
// 界面用:能否从管理器「移除」(不再读取)。dsh / hub / 项目级不可移除。
|
|
2985
2997
|
removable: !isDefaultSkillSource(root) && root.scope !== "project",
|
|
2998
|
+
// 自定义来源(用户手加的目录):只有它能「永久删除」——按约定发现的来源删不掉。
|
|
2999
|
+
custom: root.custom === true,
|
|
2986
3000
|
// 默认来源(dsh / hub):界面据此隐藏来源开关(它们不能停用),换成「可管理」标记。
|
|
2987
3001
|
defaultSource: isDefaultSkillSource(root),
|
|
2988
3002
|
removed: false,
|
package/lib/skills/service.js
CHANGED
|
@@ -397,6 +397,9 @@ export function createSkillsService(ctx) {
|
|
|
397
397
|
// 写操作清单:与下方 ops 表同文件同源维护;HTTP 端门禁由 index.ts 从本集合派生,勿在宿主端另抄一份。
|
|
398
398
|
const writeOps = new Set([
|
|
399
399
|
'skill-enable', 'skill-disable', 'skill-source-enable', 'skill-source-disable',
|
|
400
|
+
// 移除/恢复来源会改写本地来源状态(listProviderCandidates 随之变化,模型侧的技能目录
|
|
401
|
+
// 也变),是写操作;漏在这份清单里 = 配了访问令牌也不会被要求带令牌。
|
|
402
|
+
'skill-source-remove', 'skill-source-restore',
|
|
400
403
|
'skill-prefer', 'skill-unprefer',
|
|
401
404
|
'skill-create', 'skill-import', 'skill-upload', 'skill-delete',
|
|
402
405
|
'skill-trash-restore', 'skill-trash-delete', 'skill-custom-add', 'skill-custom-remove',
|
|
@@ -0,0 +1,89 @@
|
|
|
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_run` 并指定下面的人设名。',
|
|
42
|
+
'',
|
|
43
|
+
...lines,
|
|
44
|
+
];
|
|
45
|
+
if (hidden > 0)
|
|
46
|
+
out.push('', `(另有 ${hidden} 个未列出,用 subagent_list 查看全部。)`);
|
|
47
|
+
return out.join('\n');
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 创建 SWR 人设目录。
|
|
51
|
+
*
|
|
52
|
+
* ⚠️ 计算失败(读盘异常等)时**保留上一次的值**,不把段清空 —— 否则一次瞬时 IO 抖动
|
|
53
|
+
* 会让模型突然「忘记」有哪些人设。
|
|
54
|
+
*/
|
|
55
|
+
export function createSubagentCatalog(deps, opts = {}) {
|
|
56
|
+
const maxEntries = opts.maxEntries ?? DEFAULT_CATALOG_MAX_ENTRIES;
|
|
57
|
+
const maxDescription = opts.maxDescription ?? DEFAULT_CATALOG_DESCRIPTION_MAX_LENGTH;
|
|
58
|
+
const ttlMs = opts.ttlMs ?? 1000;
|
|
59
|
+
const now = opts.now ?? (() => Date.now());
|
|
60
|
+
let value = '';
|
|
61
|
+
let loadedAt = Number.NEGATIVE_INFINITY;
|
|
62
|
+
let inflight = null;
|
|
63
|
+
const recompute = async () => {
|
|
64
|
+
try {
|
|
65
|
+
const { allowed } = await filterBySceneBinding(await deps.list(), await deps.sceneLists());
|
|
66
|
+
value = renderSubagentCatalog(allowed, maxEntries, maxDescription);
|
|
67
|
+
}
|
|
68
|
+
catch { /* 保留上一次的值;首次失败则维持 '' */ }
|
|
69
|
+
loadedAt = now();
|
|
70
|
+
};
|
|
71
|
+
const revalidate = () => {
|
|
72
|
+
if (inflight)
|
|
73
|
+
return inflight;
|
|
74
|
+
inflight = recompute().finally(() => { inflight = null; });
|
|
75
|
+
return inflight;
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
text: () => {
|
|
79
|
+
if (now() - loadedAt > ttlMs)
|
|
80
|
+
void revalidate();
|
|
81
|
+
return value;
|
|
82
|
+
},
|
|
83
|
+
refresh: () => {
|
|
84
|
+
loadedAt = Number.NEGATIVE_INFINITY;
|
|
85
|
+
return revalidate();
|
|
86
|
+
},
|
|
87
|
+
warm: () => revalidate(),
|
|
88
|
+
};
|
|
89
|
+
}
|