dsh-plugin-tool-management 0.8.0 → 0.9.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 +67 -1
- package/README.md +53 -48
- package/README_EN.md +47 -44
- 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 +39 -0
- package/lib/agents-md/preset-id.js +1 -1
- package/lib/agents-md/service.js +46 -9
- package/lib/approval-policy.js +1 -1
- package/lib/client.js +431 -135
- package/lib/compat/preset-reach.js +155 -93
- package/lib/compat/probe.js +6 -4
- package/lib/context-inject.js +304 -0
- package/lib/hub.js +155 -13
- package/lib/imports/upload.js +34 -14
- package/lib/index.js +870 -226
- package/lib/mcp/state-section.js +46 -15
- package/lib/rules/archive-engine.js +182 -57
- package/lib/rules/archive.js +57 -1
- package/lib/rules/service.js +386 -156
- package/lib/scene-prompt-sync.js +101 -12
- package/lib/skills/catalog.js +125 -0
- package/lib/skills/core.js +38 -4
- package/lib/skills/service.js +36 -10
- package/lib/subagents/catalog.js +15 -5
- package/lib/subagents/service.js +39 -28
- package/lib/subagents/tools.js +18 -13
- package/package.json +14 -9
- package/screenshots.json +8 -8
- 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/lib/prompt-sections.js +0 -148
|
@@ -5,68 +5,90 @@
|
|
|
5
5
|
*
|
|
6
6
|
* WHY THIS EXISTS
|
|
7
7
|
* ---------------
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
8
|
+
* The plugin TELLS the model four things (scene memories, the subagent roster,
|
|
9
|
+
* the MCP server list with the user's notes, and the scene prompt) by injecting
|
|
10
|
+
* a synthetic message at `agent/pre-step` (see `src/context-inject.ts`). A preset
|
|
11
|
+
* can still decide it wants none of that: `@deepseek-ai/dsh-persona` with
|
|
12
|
+
* `complete: true` declares its prompt the only one, and `includeRuntimeContext:
|
|
13
|
+
* false` switches the official runtime-context snapshot off — the shipped
|
|
14
|
+
* `minimal` preset sets both. The plugin then injects nothing by default (the
|
|
15
|
+
* user can force it on the compat page), and no surface may claim otherwise.
|
|
16
|
+
*
|
|
17
|
+
* A second, different question is whether the OFFICIAL carriers are mounted at
|
|
18
|
+
* all: `@deepseek-ai/dsh-agent-instructions` (AGENTS.md) and
|
|
19
|
+
* `@deepseek-ai/dsh-tool-skill` (the skill catalog) are host rows that the Web
|
|
20
|
+
* bundle disables in favour of per-preset rows — a preset that mounts neither
|
|
21
|
+
* gives the model no AGENTS.md text and no skill catalog, whichever persona it
|
|
22
|
+
* declares.
|
|
14
23
|
*
|
|
15
24
|
* WHAT IS MEASURED, AND HOW
|
|
16
25
|
* -------------------------
|
|
17
26
|
* Answers come from each preset's own composition text, read through the
|
|
18
27
|
* roster's public `read(id)` — evidence, not inference, and no mount:
|
|
19
28
|
*
|
|
20
|
-
* - `personaComplete`: does the preset
|
|
21
|
-
*
|
|
29
|
+
* - `personaComplete` / `includeRuntimeContext`: does the preset declare
|
|
30
|
+
* "nothing but my prompt"? (both suppress plugin-injected text)
|
|
22
31
|
* - `agentInstructions`: is `@deepseek-ai/dsh-agent-instructions` mounted?
|
|
23
|
-
* (the row that carries `~/.dsh/AGENTS.md` into the
|
|
32
|
+
* (the row that carries `~/.dsh/AGENTS.md` into the conversation)
|
|
24
33
|
* - `toolSkill`: is `@deepseek-ai/dsh-tool-skill` mounted?
|
|
25
34
|
* (the row that gives the model the skill catalog)
|
|
26
|
-
* - `subagentTool`: is `@deepseek-ai/dsh-tool-subagent` mounted?
|
|
27
|
-
* (the official delegation tool; the shipped `minimal` preset has none)
|
|
28
35
|
* - `mcpClient`: does the composition mount its own MCP client?
|
|
29
|
-
* (the shipped presets mount none, so MCP
|
|
30
|
-
*
|
|
36
|
+
* (the shipped presets mount none, so MCP *tools* come from the host plane
|
|
37
|
+
* and are callable under every preset — see `ReachContext.mcpTools`)
|
|
38
|
+
*
|
|
39
|
+
* TOOLS ARE NOT TEXT — the distinction is the whole point:
|
|
31
40
|
*
|
|
32
|
-
*
|
|
33
|
-
* plane (this plugin's loader row in
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
41
|
+
* - TOOL plane (host, preset-independent): the plugin's own model tools and
|
|
42
|
+
* every `mcp__*` tool register in the HOST plane (this plugin's loader row in
|
|
43
|
+
* the profile patch), so every preset's session resolves them.
|
|
44
|
+
* - TEXT plane (preset-dependent): the runtime snapshot the plugin injects, the
|
|
45
|
+
* AGENTS.md carrier, and the skill catalog. Callable is not the same as
|
|
46
|
+
* known: under `minimal` the model can call every `mcp__*` tool while knowing
|
|
47
|
+
* nothing about the servers behind them — no names, no tool counts, no
|
|
48
|
+
* enablement, and none of the user's notes.
|
|
37
49
|
*
|
|
38
50
|
* Reading never mounts. `list()`/`read(id)` are roster reads, so building the
|
|
39
51
|
* matrix cannot activate a preset early — the same guarantee
|
|
40
52
|
* `compositionInventory()` gives the plugin-listing surfaces.
|
|
41
53
|
*
|
|
42
54
|
* This module is deliberately advisory: it never changes what is injected and
|
|
43
|
-
* never refuses an operation. A preset that
|
|
44
|
-
* exactly what its author asked for; the plugin's job is to say so out loud
|
|
45
|
-
*
|
|
55
|
+
* never refuses an operation. A preset that asks for no extra text is doing
|
|
56
|
+
* exactly what its author asked for; the plugin's job is to say so out loud and
|
|
57
|
+
* point at the switch that overrides it.
|
|
46
58
|
*/
|
|
59
|
+
/** Whether a preset declares "no extra text": persona `complete: true` or runtime context off. */
|
|
60
|
+
export function isSuppressingPreset(facts) {
|
|
61
|
+
return facts.personaComplete === true || facts.includeRuntimeContext === false;
|
|
62
|
+
}
|
|
63
|
+
/** Map composition facts onto the injection decision's facts (parse failure → not suppressing). */
|
|
64
|
+
export function injectionFactsOf(facts) {
|
|
65
|
+
if (facts === undefined)
|
|
66
|
+
return undefined;
|
|
67
|
+
return {
|
|
68
|
+
suppressing: isSuppressingPreset(facts),
|
|
69
|
+
carriesAgentsMd: facts.agentInstructions === 'mounted',
|
|
70
|
+
carriesSkillCatalog: facts.toolSkill === 'mounted',
|
|
71
|
+
};
|
|
72
|
+
}
|
|
47
73
|
/** Module specifiers whose mounting decides reachability. */
|
|
48
74
|
const PERSONA_MODULE = '@deepseek-ai/dsh-persona';
|
|
49
75
|
const AGENT_INSTRUCTIONS_MODULE = '@deepseek-ai/dsh-agent-instructions';
|
|
50
76
|
const TOOL_SKILL_MODULE = '@deepseek-ai/dsh-tool-skill';
|
|
51
|
-
/**
|
|
52
|
-
* The OFFICIAL delegation tool. Absent in the shipped `minimal` preset, present
|
|
53
|
-
* in the other three — the one column whose answer really does differ per
|
|
54
|
-
* preset. (This plugin's own `subagent_list`/`subagent_run` are host-plane and
|
|
55
|
-
* therefore preset-independent; the page's footer says so.)
|
|
56
|
-
*/
|
|
57
|
-
const SUBAGENT_TOOL_MODULE = '@deepseek-ai/dsh-tool-subagent';
|
|
58
77
|
/**
|
|
59
78
|
* An MCP client mounted INSIDE a composition. The shipped presets mount none,
|
|
60
|
-
* so MCP normally
|
|
61
|
-
* layer) and
|
|
62
|
-
* own client only carries
|
|
63
|
-
* tell the two apart.
|
|
79
|
+
* so MCP tools normally come from the host plane (the `$DSH_HOME/cordis.patch.yml`
|
|
80
|
+
* layer) and are callable under every preset; a user-authored preset that mounts
|
|
81
|
+
* its own client only carries those tools under itself, and this scan is how the
|
|
82
|
+
* page can tell the two apart. Tool reachability is not the column's answer,
|
|
83
|
+
* though — the server list and the user's notes are a prompt section, which a
|
|
84
|
+
* complete persona suppresses whichever client serves the tools.
|
|
64
85
|
*/
|
|
65
86
|
const MCP_CLIENT_MODULE = '@deepseek-ai/dsh-mcp-client';
|
|
66
87
|
const NAME_LINE = /^(\s*)name:\s*(['"]?)([^'"\s#]+)\2\s*(?:#.*)?$/;
|
|
67
88
|
const ENTRY_LINE = /^(\s*)-\s/;
|
|
68
89
|
const DISABLED_LINE = /^\s*disabled:\s*(.+?)\s*(?:#.*)?$/;
|
|
69
90
|
const COMPLETE_LINE = /^\s*complete:\s*(true|false)\s*(?:#.*)?$/;
|
|
91
|
+
const RUNTIME_CONTEXT_LINE = /^\s*includeRuntimeContext:\s*(true|false)\s*(?:#.*)?$/;
|
|
70
92
|
/** Leading whitespace width of a line, used as its nesting level. */
|
|
71
93
|
function indentOf(line) {
|
|
72
94
|
const match = /^(\s*)/.exec(line);
|
|
@@ -153,15 +175,18 @@ export function readCompositionFacts(text) {
|
|
|
153
175
|
if (lines.length <= 1 && lines[0] === '') {
|
|
154
176
|
return {
|
|
155
177
|
personaComplete: 'unknown',
|
|
178
|
+
includeRuntimeContext: 'unknown',
|
|
156
179
|
personaMounted: false,
|
|
157
180
|
agentInstructions: 'absent',
|
|
158
181
|
toolSkill: 'absent',
|
|
159
|
-
subagentTool: 'absent',
|
|
160
182
|
mcpClient: 'absent',
|
|
161
183
|
parseFailure: '组合文件为空',
|
|
162
184
|
};
|
|
163
185
|
}
|
|
164
186
|
let personaComplete = false;
|
|
187
|
+
// Schema default is `true`: a mounted persona row that does not state the flag keeps
|
|
188
|
+
// the official runtime context (only `false` suppresses it).
|
|
189
|
+
let includeRuntimeContext = true;
|
|
165
190
|
let personaMounted = false;
|
|
166
191
|
for (let i = 0; i < lines.length; i += 1) {
|
|
167
192
|
const named = NAME_LINE.exec(lines[i]);
|
|
@@ -173,29 +198,30 @@ export function readCompositionFacts(text) {
|
|
|
173
198
|
personaMounted = true;
|
|
174
199
|
for (const line of rowBlockAround(lines, i)) {
|
|
175
200
|
const complete = COMPLETE_LINE.exec(line);
|
|
176
|
-
if (complete !== null)
|
|
201
|
+
if (complete !== null)
|
|
177
202
|
personaComplete = complete[1] === 'true';
|
|
178
|
-
|
|
179
|
-
|
|
203
|
+
const runtime = RUNTIME_CONTEXT_LINE.exec(line);
|
|
204
|
+
if (runtime !== null)
|
|
205
|
+
includeRuntimeContext = runtime[1] === 'true';
|
|
180
206
|
}
|
|
181
207
|
break;
|
|
182
208
|
}
|
|
183
209
|
return {
|
|
184
210
|
personaComplete,
|
|
211
|
+
includeRuntimeContext,
|
|
185
212
|
personaMounted,
|
|
186
213
|
agentInstructions: presenceOf(lines, AGENT_INSTRUCTIONS_MODULE),
|
|
187
214
|
toolSkill: presenceOf(lines, TOOL_SKILL_MODULE),
|
|
188
|
-
subagentTool: presenceOf(lines, SUBAGENT_TOOL_MODULE),
|
|
189
215
|
mcpClient: presenceOf(lines, MCP_CLIENT_MODULE),
|
|
190
216
|
};
|
|
191
217
|
}
|
|
192
218
|
catch (error) {
|
|
193
219
|
return {
|
|
194
220
|
personaComplete: 'unknown',
|
|
221
|
+
includeRuntimeContext: 'unknown',
|
|
195
222
|
personaMounted: false,
|
|
196
223
|
agentInstructions: 'absent',
|
|
197
224
|
toolSkill: 'absent',
|
|
198
|
-
subagentTool: 'absent',
|
|
199
225
|
mcpClient: 'absent',
|
|
200
226
|
parseFailure: String(error?.message ?? error),
|
|
201
227
|
};
|
|
@@ -203,48 +229,61 @@ export function readCompositionFacts(text) {
|
|
|
203
229
|
}
|
|
204
230
|
/** Map parsed facts onto the five user-visible capability columns. */
|
|
205
231
|
export function deriveReach(facts, ctx) {
|
|
206
|
-
const suppressed = facts
|
|
232
|
+
const suppressed = isSuppressingPreset(facts);
|
|
207
233
|
const personaUnknown = facts.personaComplete === 'unknown';
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
234
|
+
const forceUnderSuppressing = ctx?.inject?.underSuppressingPresets === true;
|
|
235
|
+
const domainOff = (key) => ctx?.inject?.domains?.[key] === false;
|
|
236
|
+
/**
|
|
237
|
+
* 本插件自己的文本(场景和记忆 / MCP 服务器与备注 / 技能目录 / 子智能体目录 / 提示词)
|
|
238
|
+
* 走 `agent/pre-step` 注入消息(src/context-inject.ts)—— 不再依赖系统提示词段,所以
|
|
239
|
+
* `persona complete` 压不到它。可达性只看两件事:域开关有没有关、预设压制时有没有开
|
|
240
|
+
* 「仍然注入」。预设信息读不到(`personaUnknown` 且无压制信号)时按可达处理。
|
|
241
|
+
*/
|
|
242
|
+
const pluginText = (key) => {
|
|
243
|
+
if (domainOff(key))
|
|
244
|
+
return 'absent';
|
|
245
|
+
if (suppressed && !forceUnderSuppressing)
|
|
246
|
+
return 'suppressed';
|
|
247
|
+
return 'ok';
|
|
248
|
+
};
|
|
249
|
+
const memory = pluginText('memory');
|
|
250
|
+
const mcp = pluginText('mcp');
|
|
251
|
+
/**
|
|
252
|
+
* 官方两条行承载的能力(提示词 / 技能目录):挂得上就官方送(它也走 pre-step 注入消息,
|
|
253
|
+
* persona `complete` 压不到),挂不上就由本插件的同名域兜底 —— 所以"这一行没挂"不等于
|
|
254
|
+
* "模型看不到",只有兜底域也关掉(或缺省 `跟随预设`)时才是。
|
|
255
|
+
*/
|
|
256
|
+
const officialOrFallback = (domainKey, carrier) => {
|
|
257
|
+
// 官方那条行挂着 = **官方自己送到**,与本插件的域开关无关(开关只管本插件的兜底):
|
|
258
|
+
// 用户实测(2026-09-16)—— 把注入域全关掉后,标准 / ptc / 创造三行的技能与提示词
|
|
259
|
+
// 依然应该是绿的(模型确实拿得到,官方 `dsh-tool-skill` / `dsh-agent-instructions` 送),
|
|
260
|
+
// 只有极简那行是红的(两条行都没挂 + 兜底也关着)。
|
|
261
|
+
if (carrier === 'mounted')
|
|
262
|
+
return 'ok';
|
|
263
|
+
// 组合文本读不出来时既不能断言官方到得了,也不能断言本插件会兜底
|
|
264
|
+
// (注入侧对读不到的预设保守跳过兜底域)→ 如实报"判断不了"。
|
|
265
|
+
if (personaUnknown)
|
|
266
|
+
return 'unknown';
|
|
267
|
+
// 官方行"可能挂着"(有条件启用)同样判断不了:注入侧对本插件兜底域的门槛是"官方明确没挂"
|
|
268
|
+
// (`carrier !== false`),所以这种预设下兜底根本不会发,官方挂不挂只有挂载期才知道。
|
|
269
|
+
if (carrier === 'conditional')
|
|
270
|
+
return 'unknown';
|
|
271
|
+
if (domainOff(domainKey))
|
|
272
|
+
return 'absent';
|
|
273
|
+
if (suppressed && !forceUnderSuppressing)
|
|
274
|
+
return 'suppressed';
|
|
275
|
+
return 'ok';
|
|
276
|
+
};
|
|
277
|
+
const agentsMd = officialOrFallback('prompt', facts.agentInstructions);
|
|
278
|
+
const skillCatalog = officialOrFallback('skills', facts.toolSkill);
|
|
279
|
+
// 子智能体列(用户裁定 2026-09-16 第二版):与记忆 / MCP 两列**同口径** —— 答"人设目录
|
|
280
|
+
// 到不到得了模型"。内容由本插件的注入域送达,跑起来用本插件自己的 `subagent_manager_run`
|
|
281
|
+
// (宿主平面工具,任何预设下都在),所以可达性只看域开关与压制。此前这一列只答"官方
|
|
282
|
+
// `dsh-tool-subagent` 挂没挂"——于是极简下就算开了「极简模式也注入」也永远红着,而那条官方
|
|
283
|
+
// 工具只是另一种委派入口(它认的是通用子代理,不认本插件的人设目录),与人设可达无关。
|
|
284
|
+
const subagent = pluginText('subagents');
|
|
285
|
+
// MCP 列答的是"服务器清单与备注到不到得了":宿主工具数不再参与 —— 清单为空时注入出去
|
|
286
|
+
// 也是空段(没什么可看的),"有几台 server"由插件页面回答,不是这一列的事。
|
|
248
287
|
return { memory, agentsMd, skillCatalog, subagent, mcp };
|
|
249
288
|
}
|
|
250
289
|
/** Narrow the roster off a cordis context without throwing. */
|
|
@@ -277,6 +316,7 @@ async function composeRow(roster, meta, ctx) {
|
|
|
277
316
|
personaMounted: false,
|
|
278
317
|
agentInstructions: 'absent',
|
|
279
318
|
toolSkill: 'absent',
|
|
319
|
+
suppressing: false,
|
|
280
320
|
memory: 'unknown',
|
|
281
321
|
agentsMd: 'unknown',
|
|
282
322
|
skillCatalog: 'unknown',
|
|
@@ -296,6 +336,7 @@ async function composeRow(roster, meta, ctx) {
|
|
|
296
336
|
personaMounted: false,
|
|
297
337
|
agentInstructions: 'absent',
|
|
298
338
|
toolSkill: 'absent',
|
|
339
|
+
suppressing: false,
|
|
299
340
|
memory: 'unknown',
|
|
300
341
|
agentsMd: 'unknown',
|
|
301
342
|
skillCatalog: 'unknown',
|
|
@@ -312,6 +353,7 @@ async function composeRow(roster, meta, ctx) {
|
|
|
312
353
|
personaMounted: facts.personaMounted,
|
|
313
354
|
agentInstructions: facts.agentInstructions,
|
|
314
355
|
toolSkill: facts.toolSkill,
|
|
356
|
+
suppressing: isSuppressingPreset(facts),
|
|
315
357
|
...reach,
|
|
316
358
|
...(facts.parseFailure === undefined ? {} : { reason: `解析组合失败:${facts.parseFailure}` }),
|
|
317
359
|
};
|
|
@@ -363,7 +405,7 @@ export async function assessPresetReach(roster, ctx) {
|
|
|
363
405
|
const suppressed = rows.filter((row) => row.memory === 'suppressed').length;
|
|
364
406
|
const summary = rows.length === 0
|
|
365
407
|
? '未发现任何 Agent 预设'
|
|
366
|
-
: `预设 ${rows.length} 个 ·
|
|
408
|
+
: `预设 ${rows.length} 个 · 压制型预设(本插件默认不注入)${suppressed} 个 · 提示词通道缺失 ${rows.filter((row) => row.agentsMd === 'absent').length} 个 · 技能目录缺失 ${rows.filter((row) => row.skillCatalog === 'absent').length} 个 · 子智能体目录缺失 ${rows.filter((row) => row.subagent === 'absent').length} 个`;
|
|
367
409
|
return {
|
|
368
410
|
rows,
|
|
369
411
|
defaultId: roster.defaultId === undefined ? null : String(roster.defaultId),
|
|
@@ -378,21 +420,41 @@ export async function assessPresetReach(roster, ctx) {
|
|
|
378
420
|
*
|
|
379
421
|
* This is the load-bearing half of the fix: a model that lists memories while
|
|
380
422
|
* running under a suppressing preset would otherwise assume those memories are
|
|
381
|
-
* already in its context.
|
|
382
|
-
*
|
|
423
|
+
* already in its context. The same reasoning covers MCP and the subagent roster:
|
|
424
|
+
* none of them reach the model while the preset keeps the plugin's runtime
|
|
425
|
+
* snapshot out of the conversation, so the notice says which tool to call
|
|
426
|
+
* instead — and where the user can flip that off.
|
|
427
|
+
*
|
|
428
|
+
* Pure and synchronous so a contract test can pin the wording to the facts.
|
|
383
429
|
*
|
|
384
|
-
*
|
|
430
|
+
* 写法纪律(2026-09-16 第三版):
|
|
431
|
+
* - **不点名任何工具**。这条提示挂在 5 个发现型工具上,点名就必然出现循环
|
|
432
|
+
* (`mcp_manager_list` 的结果里写着「用 mcp_manager_list 查询」——
|
|
433
|
+
* 用户实测发现)。按域名过滤只是把循环换成残缺列表;干脆不枚举:**域 → 工具**的
|
|
434
|
+
* 映射属于工具描述(常驻层),提示只负责说清「什么不在你上下文里」。
|
|
435
|
+
* - 全域一句话说完,不逐域展开;不复述工具 schema 的细节。
|
|
436
|
+
* - 第二版把"被 persona complete 压制"当成不可改变的事实;现在本插件改走 pre-step
|
|
437
|
+
* 注入消息(src/context-inject.ts),压制型预设下**默认不注入但可以打开**,
|
|
438
|
+
* 提示里如实给出这个开关的位置。
|
|
439
|
+
*
|
|
440
|
+
* @returns the notice, or `''` when nothing is out of context.
|
|
385
441
|
*/
|
|
386
|
-
export function reachNoticeFor(presetId, facts) {
|
|
442
|
+
export function reachNoticeFor(presetId, facts, inject) {
|
|
387
443
|
const parts = [];
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
444
|
+
const domainOff = (key) => inject?.domains?.[key] === false;
|
|
445
|
+
if (isSuppressingPreset(facts) && inject?.underSuppressingPresets !== true) {
|
|
446
|
+
parts.push(`预设「${presetId}」声明只要它自己的文本(persona complete / 关闭运行时上下文):` +
|
|
447
|
+
'本插件注入的 —— 场景和记忆、MCP、技能、子智能体、提示词 —— 默认不注入,' +
|
|
448
|
+
'需要时用对应的 list / read 工具按需读取;不要假设你已经看到它们。' +
|
|
449
|
+
'(想让它在这类预设下也注入:插件的「兼容」页 → 注入。)');
|
|
393
450
|
}
|
|
394
|
-
|
|
395
|
-
|
|
451
|
+
else {
|
|
452
|
+
if (facts.agentInstructions !== 'mounted' && domainOff('prompt')) {
|
|
453
|
+
parts.push(`提示词(~/.dsh/AGENTS.md)不会自动进入你的上下文:预设「${presetId}」未挂载 @deepseek-ai/dsh-agent-instructions,本插件的注入兜底也关着。`);
|
|
454
|
+
}
|
|
455
|
+
if (facts.toolSkill !== 'mounted' && domainOff('skills')) {
|
|
456
|
+
parts.push('技能目录不在你的上下文里(官方 @deepseek-ai/dsh-tool-skill 行没挂,本插件的注入兜底也关着)。');
|
|
457
|
+
}
|
|
396
458
|
}
|
|
397
459
|
if (parts.length === 0)
|
|
398
460
|
return '';
|
|
@@ -404,7 +466,7 @@ export function reachNoticeFor(presetId, facts) {
|
|
|
404
466
|
* Every failure path answers `''` — a boundary notice must never turn a
|
|
405
467
|
* successful read into an error.
|
|
406
468
|
*/
|
|
407
|
-
export async function reachNoticeForAgent(roster, agentCtx) {
|
|
469
|
+
export async function reachNoticeForAgent(roster, agentCtx, inject) {
|
|
408
470
|
if (roster === undefined || typeof roster.composedPreset !== 'function' || agentCtx === undefined || agentCtx === null)
|
|
409
471
|
return '';
|
|
410
472
|
let presetId = '';
|
|
@@ -417,7 +479,7 @@ export async function reachNoticeForAgent(roster, agentCtx) {
|
|
|
417
479
|
if (presetId === '' || typeof roster.read !== 'function')
|
|
418
480
|
return '';
|
|
419
481
|
try {
|
|
420
|
-
return reachNoticeFor(presetId, readCompositionFacts(String((await roster.read(presetId)) ?? '')));
|
|
482
|
+
return reachNoticeFor(presetId, readCompositionFacts(String((await roster.read(presetId)) ?? '')), inject);
|
|
421
483
|
}
|
|
422
484
|
catch {
|
|
423
485
|
return '';
|
package/lib/compat/probe.js
CHANGED
|
@@ -41,15 +41,17 @@ export const IDENTITY_PACKAGES = [
|
|
|
41
41
|
'@deepseek-ai/dsh-storage-domain',
|
|
42
42
|
'@deepseek-ai/dsh-spill-local',
|
|
43
43
|
];
|
|
44
|
-
/** Peers this plugin was written and verified against
|
|
45
|
-
export const EXPECTED_PEER_RANGE = '>=0.1.5-rc.2
|
|
44
|
+
/** Peers this plugin was written and verified against(只声明最低版本,无上界)。 */
|
|
45
|
+
export const EXPECTED_PEER_RANGE = '>=0.1.5-rc.2';
|
|
46
46
|
/** The release this plugin's adapters were last verified against. */
|
|
47
47
|
export const VERIFIED_HOST_VERSION = '0.1.5-rc.2';
|
|
48
48
|
/**
|
|
49
49
|
* peer range 的下界 —— 界面展示用。
|
|
50
50
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* 刻意不带 semver 上界:官方持续发版,硬上界会在宿主跨 minor 升级时直接挡住
|
|
52
|
+
* 插件安装(pnpm peer 校验失败)。上界改为由**运行时能力探测**兜底 ——
|
|
53
|
+
* 宿主版本高于已验证范围时,`assessHost` 会逐项探测并把缺失能力降级/禁用,
|
|
54
|
+
* 而不是在安装期拒绝整包。展示时只应把「最低要求版本」当事实。
|
|
53
55
|
* 从 EXPECTED_PEER_RANGE 派生,避免两处手写漂移。
|
|
54
56
|
*/
|
|
55
57
|
export const EXPECTED_MIN_HOST_VERSION = EXPECTED_PEER_RANGE.match(/^>=\s*([^\s]+)/)?.[1] ?? VERIFIED_HOST_VERSION;
|