dsh-plugin-tool-management 0.10.0 → 0.12.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 +100 -1
- package/README.md +67 -50
- package/README_EN.md +61 -38
- package/cordis.patch.yml +10 -1
- package/docs/images/1-EN.png +0 -0
- package/docs/images/1.png +0 -0
- package/docs/images/2-EN.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3-EN.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4-EN.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/images/5-EN.png +0 -0
- package/docs/images/5.png +0 -0
- package/docs/images/6-EN.png +0 -0
- package/docs/images/6.png +0 -0
- package/docs/images/7-EN.png +0 -0
- package/docs/images/7.png +0 -0
- package/docs/images/8-EN.png +0 -0
- package/docs/images/8.png +0 -0
- package/docs/update.md +132 -12
- package/lib/client.js +2826 -547
- package/lib/compat/patch-dialect.js +173 -0
- package/lib/compat/preset-reach.js +1 -10
- package/lib/compat/probe.js +205 -24
- package/lib/compat/runtime-notes.js +25 -0
- package/lib/context-inject.js +83 -9
- package/lib/host-names.js +12 -0
- package/lib/http-fence.js +35 -15
- package/lib/hub.js +28 -2
- package/lib/imports/parsers.js +15 -9
- package/lib/imports/upload.js +43 -4
- package/lib/index.js +927 -3897
- package/lib/mcp/loader-token.js +238 -0
- package/lib/mcp/manager.js +1769 -0
- package/lib/mcp/override-blocks.js +10 -3
- package/lib/mcp/patch-yaml.js +351 -0
- package/lib/mcp/secret-guard.js +145 -0
- package/lib/{rules → memories}/archive-engine.js +1 -1
- package/lib/{rules → memories}/archive.js +1 -1
- package/lib/memories/constants.js +128 -0
- package/lib/memories/index-io.js +330 -0
- package/lib/memories/projection.js +280 -0
- package/lib/memories/service.js +686 -0
- package/lib/memories/snapshot.js +672 -0
- package/lib/ops/candidates.js +64 -0
- package/lib/ops/compat.js +226 -0
- package/lib/ops/ctx.js +9 -0
- package/lib/ops/memory.js +678 -0
- package/lib/ops/prompts.js +107 -0
- package/lib/ops/scene-records.js +460 -0
- package/lib/ops/scene-sync.js +17 -0
- package/lib/ops/sessions.js +603 -0
- package/lib/ops/trash.js +140 -0
- package/lib/paths.js +103 -0
- package/lib/prompts/preset-id.js +49 -0
- package/lib/{agents-md → prompts}/service.js +1 -1
- package/lib/request-gate.js +320 -0
- package/lib/scene-prompt-sync.js +4 -4
- package/lib/scenes/candidates.js +344 -0
- package/lib/{history → sessions}/bridge.js +124 -36
- package/lib/sessions/history.js +323 -0
- package/lib/{history → sessions}/tombstone.js +1 -1
- package/lib/{history → sessions}/workspace.js +151 -50
- package/lib/skills/core.js +74 -39
- package/lib/skills/readonly-discovery.js +4 -1
- package/lib/skills/service.js +98 -13
- package/lib/subagents/service.js +199 -55
- package/lib/tools/deps.js +8 -0
- package/lib/tools/mcp.js +110 -0
- package/lib/tools/memory.js +87 -0
- package/lib/tools/prompt.js +70 -0
- package/lib/tools/skills.js +139 -0
- package/lib/tools/subagent.js +40 -0
- package/package.json +13 -10
- package/lib/agents-md/preset-id.js +0 -49
- package/lib/rules/service.js +0 -3078
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// 插件**自己那条 loader 行**的 `config.token` 读写 —— 唯一口径。
|
|
2
|
+
//
|
|
3
|
+
// 为什么需要它(用户裁定 2026-09-18):访问令牌此前只能靠手改 `cordis.patch.yml` 来设置,
|
|
4
|
+
// 而"想开明文就得会写 YAML"这个门槛把功能卡住了。现在兼容页可以就地设置 / 关闭,本模块
|
|
5
|
+
// 负责那一次**外科手术式的文本改写**。
|
|
6
|
+
//
|
|
7
|
+
// 为什么是行级文本改写而不是 YAML 解析后重序列化:`cordis.patch.yml` 是**人的文件** ——
|
|
8
|
+
// 里面混着手写注释、`!!js` 表达式、以及别的插件/别的 MCP 条目的块。解析再序列化会把注释
|
|
9
|
+
// 全部抹掉、把 `!!js` 引号风格改掉、把不认识的键重排 —— 一次"设置令牌"不该顺带重写整个
|
|
10
|
+
// 文件。(这与 `override-blocks.ts` 选择"只算删除区间"是同一条理由。)
|
|
11
|
+
//
|
|
12
|
+
// 边界:只动**本插件 id 那条 insert 子条目**内的 `config:` / `token:` 两行,别的一概不碰。
|
|
13
|
+
// 找不到那条行时如实回 `found: false`,由调用方决定怎么报错,绝不"找不到就新建一条"。
|
|
14
|
+
//
|
|
15
|
+
// 两个**不改写、只拒绝**的形状(2026-09-19 审计 T-28):
|
|
16
|
+
// - `config:` 写成 flow style(`config: {token: x}`)或缩进不是 6 空格 ⇒ 本模块的
|
|
17
|
+
// `CONFIG_RE` 认不出来,会以为"没有 config"而在 `- id:` 之后**再插一个 `config:`**;
|
|
18
|
+
// 同一映射里出现重复键,官方解析器 `js-yaml` 是 **throw**(不是 last-wins、没有降级开关),
|
|
19
|
+
// 后果是**下一次 DSH 直接启动失败**。所以识别不了形状就拒绝改写,让调用方告诉用户手改。
|
|
20
|
+
// - 同理,写完之前再自检一遍:本条目范围内 `config:` 若多于一个,也拒绝落盘。
|
|
21
|
+
/** 与生成器同一口径的 YAML 标量编码(见 index.ts 的 yq)。 */
|
|
22
|
+
const yq = (value) => JSON.stringify(value);
|
|
23
|
+
/** 本插件 loader 行的 id(与 cordis.patch.yml 里那条 insert 子条目一致)。 */
|
|
24
|
+
export const PLUGIN_LOADER_ID = 'dsh-plugin-tool-management';
|
|
25
|
+
const ROW_ID_RE = /^ {4}- id:\s*(\S+)\s*$/;
|
|
26
|
+
const CONFIG_RE = /^ {6}config:\s*$/;
|
|
27
|
+
/** `config:` 的任何别的写法(flow style `config: {…}`、非空值、别的缩进)—— 一律不改写。 */
|
|
28
|
+
const CONFIG_OTHER_RE = /^\s*config:\s*\S/;
|
|
29
|
+
const keyRe = (key) => new RegExp('^ {8}' + key + ':\\s*(.*)$');
|
|
30
|
+
const TOKEN_RE = keyRe('token');
|
|
31
|
+
const DISABLED_RE = keyRe('tokenDisabled');
|
|
32
|
+
/** 该子条目块内的行:缩进 ≥ 6(键),或空行/注释(块间空隙,归前一块)。 */
|
|
33
|
+
const inRowBlock = (line) => line.trim() === '' || /^\s*#/.test(line) || /^ {6,}\S/.test(line);
|
|
34
|
+
/** config 块内的行:缩进 ≥ 8,或空行/注释。 */
|
|
35
|
+
const inConfigBlock = (line) => line.trim() === '' || /^\s*#/.test(line) || /^ {8,}\S/.test(line);
|
|
36
|
+
const unquote = (raw) => {
|
|
37
|
+
const text = raw.trim();
|
|
38
|
+
if (text.length >= 2 && ((text.startsWith('"') && text.endsWith('"')) || (text.startsWith("'") && text.endsWith("'")))) {
|
|
39
|
+
const inner = text.slice(1, -1);
|
|
40
|
+
if (text.startsWith('"')) {
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return inner;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return inner.replaceAll("''", "'");
|
|
49
|
+
}
|
|
50
|
+
return text;
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* 一个布尔开关的值(`tokenDisabled`)。
|
|
54
|
+
*
|
|
55
|
+
* 容忍**行尾注释**(`tokenDisabled: true # 临时关掉`):YAML 里它是合法的,宿主读到的
|
|
56
|
+
* 也是 `true`;而逐字比较 `=== 'true'` 会得到 false —— 用户以为关掉了,实际开着,
|
|
57
|
+
* 两边都看不出分歧(审计 T-27)。解析不出来(既不是 true 也不是 false)时返回 `null`,
|
|
58
|
+
* 调用方按"没有这条开关"处理 —— 那是**启用门禁**的方向,与宿主对非 `true` 值的读法一致。
|
|
59
|
+
*/
|
|
60
|
+
function parseFlag(raw) {
|
|
61
|
+
const text = String(raw == null ? '' : raw).trim();
|
|
62
|
+
if (text === 'true')
|
|
63
|
+
return true;
|
|
64
|
+
if (text === 'false')
|
|
65
|
+
return false;
|
|
66
|
+
// 只有注释前的那段是明确的 true/false 才认;引号里的 `#` 不参与切分。
|
|
67
|
+
if (!/["']/.test(text)) {
|
|
68
|
+
const head = text.split('#')[0].trim();
|
|
69
|
+
if (head === 'true')
|
|
70
|
+
return true;
|
|
71
|
+
if (head === 'false')
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/** 定位插件 loader 行及其 `config:` 块(找不到行时 `start < 0`)。 */
|
|
77
|
+
function locate(content, pluginId) {
|
|
78
|
+
const lines = content.split(/\r?\n/);
|
|
79
|
+
const start = lines.findIndex((line) => {
|
|
80
|
+
const m = ROW_ID_RE.exec(line);
|
|
81
|
+
return !!m && m[1] === pluginId;
|
|
82
|
+
});
|
|
83
|
+
if (start < 0)
|
|
84
|
+
return { lines, start: -1, end: -1, configAt: -1, configEnd: -1, configUnrecognised: false, configDuplicated: false };
|
|
85
|
+
let end = start + 1;
|
|
86
|
+
while (end < lines.length && inRowBlock(lines[end]))
|
|
87
|
+
end++;
|
|
88
|
+
let configAt = -1;
|
|
89
|
+
let configCount = 0;
|
|
90
|
+
let configUnrecognised = false;
|
|
91
|
+
for (let i = start + 1; i < end; i++) {
|
|
92
|
+
if (CONFIG_RE.test(lines[i])) {
|
|
93
|
+
configCount += 1;
|
|
94
|
+
if (configAt < 0)
|
|
95
|
+
configAt = i;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (CONFIG_OTHER_RE.test(lines[i])) {
|
|
99
|
+
configCount += 1;
|
|
100
|
+
configUnrecognised = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let configEnd = -1;
|
|
104
|
+
if (configAt >= 0) {
|
|
105
|
+
configEnd = configAt + 1;
|
|
106
|
+
while (configEnd < end && inConfigBlock(lines[configEnd]))
|
|
107
|
+
configEnd++;
|
|
108
|
+
}
|
|
109
|
+
return { lines, start, end, configAt, configEnd, configUnrecognised, configDuplicated: configCount > 1 };
|
|
110
|
+
}
|
|
111
|
+
/** 读插件 loader 行里的 `config.token` / `config.tokenDisabled`(没写 / 没这条行都返回空值)。 */
|
|
112
|
+
export function readLoaderToken(content, pluginId = PLUGIN_LOADER_ID) {
|
|
113
|
+
const { lines, start, end, configAt, configEnd } = locate(content, pluginId);
|
|
114
|
+
if (start < 0)
|
|
115
|
+
return { found: false, token: '', disabled: false };
|
|
116
|
+
if (configAt < 0)
|
|
117
|
+
return { found: true, token: '', disabled: false };
|
|
118
|
+
let token = '';
|
|
119
|
+
let disabled = false;
|
|
120
|
+
for (let i = configAt + 1; i < configEnd; i++) {
|
|
121
|
+
const t = TOKEN_RE.exec(lines[i]);
|
|
122
|
+
if (t) {
|
|
123
|
+
token = unquote(t[1]);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const d = DISABLED_RE.exec(lines[i]);
|
|
127
|
+
// `null` = 值既不是 true 也不是 false(宿主同样不当成"已关闭"),按未关闭处理。
|
|
128
|
+
if (d)
|
|
129
|
+
disabled = parseFlag(d[1]) === true;
|
|
130
|
+
}
|
|
131
|
+
return { found: true, token, disabled };
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* 在 `config:` 块里写 / 删一个键 —— **唯一实现**(`token` 与 `tokenDisabled` 共用)。
|
|
135
|
+
*
|
|
136
|
+
* 为什么是行级文本改写而不是 YAML 解析后重序列化:`cordis.patch.yml` 是**人的文件** ——
|
|
137
|
+
* 里面混着手写注释、`!!js` 表达式、以及别的插件/别的 MCP 条目的块。解析再序列化会把注释
|
|
138
|
+
* 全部抹掉、把 `!!js` 引号风格改掉、把不认识的键重排 —— 一次"设置令牌"不该顺带重写整个
|
|
139
|
+
* 文件。(这与 `override-blocks.ts` 选择"只算删除区间"是同一条理由。)
|
|
140
|
+
*
|
|
141
|
+
* 删除时若 `config:` 下面再没有别的键,连 `config:` 一起删 —— 否则留下一个空的 `config:`
|
|
142
|
+
* (YAML 里是 `null`,宿主读到的 config 就是 null,行为与"没有 config"并不完全相同)。
|
|
143
|
+
*
|
|
144
|
+
* @param line - 要写入的整行(含缩进);`null` = 删除该键。
|
|
145
|
+
*/
|
|
146
|
+
function rewriteConfigKey(content, key, line, pluginId) {
|
|
147
|
+
const { lines, start, end, configAt, configEnd, configUnrecognised, configDuplicated } = locate(content, pluginId);
|
|
148
|
+
if (start < 0)
|
|
149
|
+
return { found: false };
|
|
150
|
+
// 写之前先自检:这两种形状一旦落盘就是"DSH 下次起不来"(官方 js-yaml 对重复映射键 throw),
|
|
151
|
+
// 而"设置令牌"是界面上一次点击触发的 —— 宁可拒绝并说明,也不产出一个坏文件。
|
|
152
|
+
if (configDuplicated) {
|
|
153
|
+
return {
|
|
154
|
+
found: true,
|
|
155
|
+
changed: false,
|
|
156
|
+
content,
|
|
157
|
+
refused: '本插件那条 loader 行里出现了多个 `config:` 键(重复映射键会让 DSH 启动失败):请先手工整理 cordis.patch.yml,只保留一个 config 块。',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
if (configUnrecognised) {
|
|
161
|
+
return {
|
|
162
|
+
found: true,
|
|
163
|
+
changed: false,
|
|
164
|
+
content,
|
|
165
|
+
refused: '本插件那条 loader 行里的 `config:` 不是插件认识的写法(flow style 或缩进不是 6 空格),自动改写会插入重复键并导致 DSH 启动失败:请手工把它改回 ` config:` + 缩进 8 空格的键值行,或在配置里手工写 token。',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
const re = keyRe(key);
|
|
169
|
+
const keyAt = (() => {
|
|
170
|
+
if (configAt < 0)
|
|
171
|
+
return -1;
|
|
172
|
+
for (let i = configAt + 1; i < configEnd; i++)
|
|
173
|
+
if (re.test(lines[i]))
|
|
174
|
+
return i;
|
|
175
|
+
return -1;
|
|
176
|
+
})();
|
|
177
|
+
// 写之后的第二道自检:不管走哪条分支,产物里 `config:` 只能有一个。宁可原样退回,
|
|
178
|
+
// 也不让一次界面点击产出一个"DSH 下次起不来"的补丁文件。
|
|
179
|
+
const commit = (nextContent) => {
|
|
180
|
+
if (locate(nextContent, pluginId).configDuplicated) {
|
|
181
|
+
return {
|
|
182
|
+
found: true,
|
|
183
|
+
changed: false,
|
|
184
|
+
content,
|
|
185
|
+
refused: '改写会在本插件那条 loader 行里产生重复的 `config:` 键(会让 DSH 启动失败),已放弃本次修改:请手工整理 cordis.patch.yml。',
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { found: true, changed: true, content: nextContent };
|
|
189
|
+
};
|
|
190
|
+
const next = lines.slice();
|
|
191
|
+
if (line === null) {
|
|
192
|
+
if (keyAt < 0)
|
|
193
|
+
return { found: true, changed: false, content };
|
|
194
|
+
// 这个键之外还有别的键吗?(在原行上判断,避免 splice 之后的下标漂移算错)
|
|
195
|
+
const otherKeys = lines
|
|
196
|
+
.slice(configAt + 1, configEnd)
|
|
197
|
+
.some((row, index) => configAt + 1 + index !== keyAt && /^ {8,}\S/.test(row));
|
|
198
|
+
next.splice(keyAt, 1);
|
|
199
|
+
if (!otherKeys)
|
|
200
|
+
next.splice(configAt, 1);
|
|
201
|
+
return commit(next.join('\n'));
|
|
202
|
+
}
|
|
203
|
+
if (keyAt >= 0) {
|
|
204
|
+
if (lines[keyAt] === line)
|
|
205
|
+
return { found: true, changed: false, content };
|
|
206
|
+
next[keyAt] = line;
|
|
207
|
+
return commit(next.join('\n'));
|
|
208
|
+
}
|
|
209
|
+
if (configAt >= 0) {
|
|
210
|
+
next.splice(configAt + 1, 0, line);
|
|
211
|
+
return commit(next.join('\n'));
|
|
212
|
+
}
|
|
213
|
+
// 没有 config:紧跟 `- id:` 之后插入(映射键的顺序不影响 YAML 语义,位置固定才好断言)。
|
|
214
|
+
next.splice(start + 1, 0, ' config:', line);
|
|
215
|
+
return commit(next.join('\n'));
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* 设置(非空字符串)或移除(`null`)插件 loader 行的 `config.token`。
|
|
219
|
+
*
|
|
220
|
+
* @param content - 补丁文件全文。
|
|
221
|
+
* @param token - 要写入的令牌;`null` = 移除。
|
|
222
|
+
* @returns `found: false` 表示文件里没有本插件那条 loader 行(调用方据此报错);
|
|
223
|
+
* 否则给出新全文与"是否真的改了"。
|
|
224
|
+
*/
|
|
225
|
+
export function applyLoaderToken(content, token, pluginId = PLUGIN_LOADER_ID) {
|
|
226
|
+
return rewriteConfigKey(content, 'token', token === null ? null : ' token: ' + yq(token), pluginId);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* 打开(`true`)或关掉(`false`)令牌功能 —— **不动 `config.token` 本身**。
|
|
230
|
+
*
|
|
231
|
+
* 关掉 = 写一行 `tokenDisabled: true`:令牌留在配置里,随时能开回来,不需要重新输一遍
|
|
232
|
+
* (用户裁定 2026-09-19)。`false` = 删掉那一行。
|
|
233
|
+
*
|
|
234
|
+
* @param disabled - `true` 写开关;`false` / `null` 删掉该键。
|
|
235
|
+
*/
|
|
236
|
+
export function applyLoaderTokenDisabled(content, disabled, pluginId = PLUGIN_LOADER_ID) {
|
|
237
|
+
return rewriteConfigKey(content, 'tokenDisabled', disabled === true ? ' tokenDisabled: true' : null, pluginId);
|
|
238
|
+
}
|