dsh-plugin-tool-management 0.5.1 → 0.7.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 +131 -0
- package/README.md +185 -238
- package/README_EN.md +185 -304
- package/cordis.patch.yml +9 -55
- 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 +55 -0
- package/lib/agents-md/preset-id.js +49 -0
- package/lib/agents-md/service.js +180 -57
- package/lib/client.js +4710 -3560
- package/lib/compat/preset-reach.js +425 -0
- package/lib/compat/probe.js +665 -0
- package/lib/history/bridge.js +293 -0
- package/lib/history/workspace.js +498 -52
- package/lib/http-fence.js +94 -0
- package/lib/hub.js +160 -2
- package/lib/index.js +759 -157
- package/lib/mcp/override-blocks.js +195 -0
- package/lib/rules/provider.js +3 -3
- package/lib/rules/service.js +342 -31
- package/lib/scene-prompt-sync.js +112 -0
- package/lib/skills/core.js +123 -35
- package/lib/skills/service.js +9 -1
- package/lib/subagents/service.js +197 -23
- package/lib/subagents/tools.js +8 -2
- package/package.json +6 -3
- package/screenshots.json +10 -9
- package/docs/Changelog.md +0 -517
- package/docs/images/MCP.png +0 -0
- package/docs/images//344/274/232/350/257/235.png +0 -0
- package/docs/images//345/234/272/346/231/257.png +0 -0
- package/docs/images//345/255/220/346/231/272/350/203/275/344/275/223.png +0 -0
- package/docs/images//346/212/200/350/203/275.png +0 -0
- package/docs/images//346/217/220/347/244/272/350/257/215.png +0 -0
- package/docs/images//350/256/260/345/277/206.png +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// 补丁文件(cordis.patch.yml)里的「启停覆盖块」识别与收敛计划。
|
|
2
|
+
//
|
|
3
|
+
// 同一个 loader id 的启停状态有两种写法:insert 行自带的 `disabled:`,以及顶层
|
|
4
|
+
// `- id: xxx` + `disabled:` 的独立覆盖块。补丁按顺序应用、**last-wins**
|
|
5
|
+
// (见 @deepseek-ai/dsh-app-boot 的 applyEntryPatches:`target[key] = value`),
|
|
6
|
+
// 所以覆盖块可以无限追加而每次都不报错 —— 代价是文件越用越长,生效值只能靠
|
|
7
|
+
// "最后一条声明了 disabled 的块"推断。
|
|
8
|
+
//
|
|
9
|
+
// 这里只回答一个问题:要收敛的话,删哪些行区间**且生效值保证不变**。
|
|
10
|
+
//
|
|
11
|
+
// ⚠ 这个模块第一版出过一次事故:调用方把 `parseRows()` 合并覆盖块之后的**生效值**
|
|
12
|
+
// 当基准传进来,而生效值恒等于 decider 的值 —— 于是"decider 的值 == 基准"永远成立,
|
|
13
|
+
// 每个 decider 都被当成 no-op 删掉,6 台 MCP 全部被启用。为了从结构上杜绝这种传错,
|
|
14
|
+
// 现在的接口是:`planOverrideCompaction(content, otherFileContents)` —— 基准只能由
|
|
15
|
+
// 本模块自己从 **insert 行**读(`insertBaseRows`),调用方只能再补"另一份补丁文件的
|
|
16
|
+
// 内容",不能传任何现成的状态值进来。
|
|
17
|
+
/**
|
|
18
|
+
* 顶层块扫描:每个 `- ` 开头的行都是新块的开始,块体延伸到下一个 `- ` 行之前。
|
|
19
|
+
*
|
|
20
|
+
* `end` 剔除块尾的注释/空行 —— 那些行属于"块与块之间",其中就包括下一个块自己的
|
|
21
|
+
* `# dsh-…:disable:<id>` 标记注释。带上它们的话,删一个块会把下一个块的注释一起带走。
|
|
22
|
+
*/
|
|
23
|
+
export function scanBlockRanges(lines) {
|
|
24
|
+
const out = [];
|
|
25
|
+
let current = null;
|
|
26
|
+
const flush = () => {
|
|
27
|
+
if (!current)
|
|
28
|
+
return;
|
|
29
|
+
let end = current.start + current.lines.length;
|
|
30
|
+
while (end > current.start + 1 && (lines[end - 1].trim() === '' || /^\s*#/.test(lines[end - 1])))
|
|
31
|
+
end--;
|
|
32
|
+
out.push({ start: current.start, end, text: current.lines.slice(0, end - current.start).join('\n') });
|
|
33
|
+
current = null;
|
|
34
|
+
};
|
|
35
|
+
for (let i = 0; i < lines.length; i++) {
|
|
36
|
+
if (/^- /.test(lines[i])) {
|
|
37
|
+
flush();
|
|
38
|
+
current = { start: i, lines: [lines[i]] };
|
|
39
|
+
}
|
|
40
|
+
else if (current)
|
|
41
|
+
current.lines.push(lines[i]);
|
|
42
|
+
}
|
|
43
|
+
flush();
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 读一个覆盖块三件事:id、disabled、是不是"纯块"。
|
|
48
|
+
*
|
|
49
|
+
* 只看**顶层/一层缩进**的锚定写法(`- id:`、`name:`、`disabled:`):生成器写出来的
|
|
50
|
+
* 就是这几行,而 config 里的同名键缩进更深 —— 万一手工把 `disabled` 藏进 config,
|
|
51
|
+
* 这里会判成"非纯块"(保留不删),保守方向是对的。
|
|
52
|
+
*
|
|
53
|
+
* 不筛 `name:` —— 补丁是按 **loader id** 生效的,别的模块名写同一个 id 时改的仍是
|
|
54
|
+
* 那个条目;真正决定"能不能删"的是 pure 与 decider 两条规则,不是名字。
|
|
55
|
+
*/
|
|
56
|
+
export function readToggleEntry(lines) {
|
|
57
|
+
let id;
|
|
58
|
+
let disabled;
|
|
59
|
+
let pure = true;
|
|
60
|
+
for (const raw of lines) {
|
|
61
|
+
const line = raw.replace(/\s+$/, '');
|
|
62
|
+
if (!line.trim() || line.trimStart().startsWith('#'))
|
|
63
|
+
continue;
|
|
64
|
+
const head = line.match(/^- id:\s*(\S+)\s*$/);
|
|
65
|
+
if (head) {
|
|
66
|
+
id = head[1];
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (/^\s{2,}name:\s*\S+\s*$/.test(line))
|
|
70
|
+
continue;
|
|
71
|
+
const flag = line.match(/^\s{2,}disabled:\s*(true|false)\s*$/);
|
|
72
|
+
if (flag) {
|
|
73
|
+
disabled = flag[1] === 'true';
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
pure = false;
|
|
77
|
+
}
|
|
78
|
+
return { id, disabled, pure };
|
|
79
|
+
}
|
|
80
|
+
/** 覆盖块自己的标记注释(紧邻上方的 `# …:disable|enable:<id>`),删块时一起删。 */
|
|
81
|
+
export function markerLinesAbove(lines, start) {
|
|
82
|
+
let i = start;
|
|
83
|
+
while (i > 0 && /^# (?:dsh-plugin-tool-management|dsh-mcp-manager):(?:disable|enable):/.test(lines[i - 1]))
|
|
84
|
+
i--;
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
/** 文件里所有顶层启停覆盖块(按出现顺序)。 */
|
|
88
|
+
export function scanOverrideBlocks(content) {
|
|
89
|
+
const lines = content.split(/\r?\n/);
|
|
90
|
+
const out = [];
|
|
91
|
+
for (const block of scanBlockRanges(lines)) {
|
|
92
|
+
if (/^- insert:/.test(lines[block.start]))
|
|
93
|
+
continue;
|
|
94
|
+
const entry = readToggleEntry(lines.slice(block.start, block.end));
|
|
95
|
+
if (!entry.id)
|
|
96
|
+
continue;
|
|
97
|
+
out.push({ ...block, id: entry.id, disabled: entry.disabled, pure: entry.pure });
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* **insert 行自带的** 启停基准(`- insert:` 里每个子条目的 `disabled`,缺失 = 启用)。
|
|
103
|
+
*
|
|
104
|
+
* 这是收敛判定的唯一合法基准来源:绝不能用 `parseRows()` 那种"合并覆盖块之后的生效值",
|
|
105
|
+
* 因为生效值就等于最后一条覆盖块的值,"覆盖块的值 == 基准"会恒成立,decider 会被全删。
|
|
106
|
+
*/
|
|
107
|
+
export function insertBaseRows(content) {
|
|
108
|
+
const lines = content.split(/\r?\n/);
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const block of scanBlockRanges(lines)) {
|
|
111
|
+
if (!/^- insert:/.test(lines[block.start]))
|
|
112
|
+
continue;
|
|
113
|
+
let id;
|
|
114
|
+
let disabled;
|
|
115
|
+
const flush = () => { if (id)
|
|
116
|
+
out.push({ id, disabled }); };
|
|
117
|
+
for (const line of block.text.split('\n')) {
|
|
118
|
+
const child = line.match(/^ {4}- id:\s*(\S+)\s*$/);
|
|
119
|
+
if (child) {
|
|
120
|
+
flush();
|
|
121
|
+
id = child[1];
|
|
122
|
+
disabled = undefined;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!id)
|
|
126
|
+
continue;
|
|
127
|
+
const flag = line.match(/^\s+disabled:\s*(true|false)\s*$/);
|
|
128
|
+
if (flag)
|
|
129
|
+
disabled = flag[1] === 'true';
|
|
130
|
+
}
|
|
131
|
+
flush();
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* 收敛计划:算出"删掉也不改变生效状态"的覆盖块行区间。
|
|
137
|
+
*
|
|
138
|
+
* 为什么除最后一条外都能删:补丁按顺序应用,同一 id 的多个覆盖块 last-wins,真正
|
|
139
|
+
* 决定生效值的只有**最后一个声明了 disabled 的块**(decider)。于是:
|
|
140
|
+
* - decider 之前的纯启停块 → 删(被 decider 盖住);
|
|
141
|
+
* - decider 本身 → 仅当它与 insert 行的 disabled 相同(纯 no-op)**且基准已知**时删;
|
|
142
|
+
* - decider 之后的块 → 留着(它们不声明 disabled,可能改的是别的东西);
|
|
143
|
+
* - 带 config 的非纯块 → 一律留着(可能承载手工覆盖内容)。
|
|
144
|
+
*
|
|
145
|
+
* "基准未知"为什么要单列:覆盖块可以打在**补丁文件之外**定义的条目上(profile 的
|
|
146
|
+
* cordis.yml 原生行,或另一份补丁文件的 insert 行)。此时"删掉这条 disabled: false"
|
|
147
|
+
* 完全可能把它翻回原生状态的 disabled: true —— 所以基准未知时只删被 decider 盖住的
|
|
148
|
+
* 那些块,绝不删 decider 自己。
|
|
149
|
+
*
|
|
150
|
+
* @param content - 补丁文件全文。
|
|
151
|
+
* @param otherFileContents - 同一 id 的 insert 行**可能坐在里面**的其它补丁文件全文
|
|
152
|
+
* (项目级与全局两份互查)。只用于补基准,绝不用来推断本文件要删什么。
|
|
153
|
+
* @returns `ranges` 为要删除的行区间;`ids` 为逐 id 统计。
|
|
154
|
+
*/
|
|
155
|
+
export function planOverrideCompaction(content, otherFileContents = []) {
|
|
156
|
+
const lines = content.split(/\r?\n/);
|
|
157
|
+
const byId = new Map();
|
|
158
|
+
for (const block of scanOverrideBlocks(content)) {
|
|
159
|
+
const list = byId.get(block.id);
|
|
160
|
+
if (list)
|
|
161
|
+
list.push(block);
|
|
162
|
+
else
|
|
163
|
+
byId.set(block.id, [block]);
|
|
164
|
+
}
|
|
165
|
+
const baseOf = new Map();
|
|
166
|
+
for (const row of insertBaseRows(content))
|
|
167
|
+
baseOf.set(row.id, row.disabled === true);
|
|
168
|
+
for (const other of otherFileContents) {
|
|
169
|
+
for (const row of insertBaseRows(other))
|
|
170
|
+
if (!baseOf.has(row.id))
|
|
171
|
+
baseOf.set(row.id, row.disabled === true);
|
|
172
|
+
}
|
|
173
|
+
const ranges = [];
|
|
174
|
+
const ids = {};
|
|
175
|
+
for (const [id, list] of byId) {
|
|
176
|
+
const baseKnown = baseOf.has(id);
|
|
177
|
+
const base = baseOf.get(id) === true;
|
|
178
|
+
let decider = -1;
|
|
179
|
+
for (let i = 0; i < list.length; i++)
|
|
180
|
+
if (list[i].disabled !== undefined)
|
|
181
|
+
decider = i;
|
|
182
|
+
const doomed = [];
|
|
183
|
+
if (decider >= 0) {
|
|
184
|
+
for (let i = 0; i < decider; i++)
|
|
185
|
+
if (list[i].pure)
|
|
186
|
+
doomed.push(list[i]);
|
|
187
|
+
if (baseKnown && list[decider].pure && list[decider].disabled === base)
|
|
188
|
+
doomed.push(list[decider]);
|
|
189
|
+
}
|
|
190
|
+
ids[id] = { total: list.length, dropped: doomed.length };
|
|
191
|
+
for (const block of doomed)
|
|
192
|
+
ranges.push([markerLinesAbove(lines, block.start), block.end]);
|
|
193
|
+
}
|
|
194
|
+
return { ranges, ids };
|
|
195
|
+
}
|
package/lib/rules/provider.js
CHANGED
|
@@ -37,7 +37,7 @@ function systemPromptOf(scope) {
|
|
|
37
37
|
return undefined;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
40
|
-
/** 注册全局 + agent-scope
|
|
40
|
+
/** 注册全局 + agent-scope 的场景记忆段与场景提示词段。 */
|
|
41
41
|
export function createRuleProviderRegistrar(ctx, facade) {
|
|
42
42
|
const registrations = new Map();
|
|
43
43
|
const install = (scope, key) => {
|
|
@@ -57,8 +57,8 @@ export function createRuleProviderRegistrar(ctx, facade) {
|
|
|
57
57
|
registrations.set(key, { scope, dispose: typeof dispose === 'function' ? dispose : () => { } });
|
|
58
58
|
}
|
|
59
59
|
catch (e) {
|
|
60
|
-
// 同层重名注册会抛错:降级为"
|
|
61
|
-
console.error(
|
|
60
|
+
// 同层重名注册会抛错:降级为"该段不注入",不影响其余 scope。
|
|
61
|
+
console.error(`[dsh-plugin-tool-management] ${SECTION_NAME} section registration failed:`, String((e && e.message) || e));
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
64
|
const uninstall = (key) => {
|