dsh-plugin-tool-management 0.20.3 → 0.21.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 +37 -0
- package/README.md +4 -1
- package/README_EN.md +4 -1
- package/docs/update.md +1 -0
- package/lib/client.js +117 -11
- package/lib/compat/capability-specs.js +60 -1
- package/lib/compat/host-contracts.js +72 -0
- package/lib/compat/host-probe.js +59 -0
- package/lib/compat/host-roots.js +1 -1
- package/lib/compat/probe.js +84 -13
- package/lib/context-inject-frame.js +134 -26
- package/lib/context-inject.js +12 -9
- package/lib/http-api.js +3 -2
- package/lib/http-fence.js +26 -1
- package/lib/index.js +5 -2
- package/lib/mcp/manager.js +6 -1
- package/lib/mcp/state-section.js +75 -34
- package/lib/memories/constants.js +39 -7
- package/lib/memories/projection.js +18 -18
- package/lib/memories/service.js +8 -7
- package/lib/memories/snapshot.js +27 -17
- package/lib/ops/compat.js +64 -2
- package/lib/ops/memory.js +2 -2
- package/lib/ops/prompts.js +30 -2
- package/lib/request-gate.js +5 -4
- package/lib/skills/catalog.js +2 -2
- package/lib/subagents/catalog.js +4 -4
- package/lib/subagents/child-preset.js +120 -0
- package/lib/subagents/persona-parse.js +9 -9
- package/lib/subagents/run-provider.js +218 -62
- package/lib/subagents/tool-filter.js +12 -12
- package/lib/subagents/tools.js +29 -22
- package/lib/tools/deps.js +4 -4
- package/lib/tools/mcp.js +34 -34
- package/lib/tools/memory.js +34 -34
- package/lib/tools/prompt.js +5 -5
- package/lib/tools/remove.js +17 -17
- package/lib/tools/scene.js +66 -66
- package/lib/tools/skills.js +21 -21
- package/lib/tools/subagent.js +13 -13
- package/lib/tools/table.js +2 -2
- package/package.json +25 -24
package/lib/tools/scene.js
CHANGED
|
@@ -60,7 +60,7 @@ function toolTableLabel(name) {
|
|
|
60
60
|
const v = typeof name === 'string' ? name.trim() : '';
|
|
61
61
|
if (!v)
|
|
62
62
|
return '';
|
|
63
|
-
return v === FACTORY_TOOL_TABLE_PRESET ? '
|
|
63
|
+
return v === FACTORY_TOOL_TABLE_PRESET ? 'factory default' : v;
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* 一个档案的名单渲染(`_list` 用)。**点名而不是只给数量**:模型要判断"这个场景进入时会不会
|
|
@@ -68,23 +68,23 @@ function toolTableLabel(name) {
|
|
|
68
68
|
*/
|
|
69
69
|
function archiveText(archive) {
|
|
70
70
|
if (!archive || typeof archive !== 'object')
|
|
71
|
-
return '
|
|
71
|
+
return 'not created (entering turns off all MCP / skill / persona domains)';
|
|
72
72
|
const keys = (v) => (v && typeof v === 'object' && !Array.isArray(v) ? Object.keys(v) : []);
|
|
73
73
|
const list = (v) => (Array.isArray(v) ? v.map((x) => String(x)) : []);
|
|
74
74
|
const parts = [
|
|
75
75
|
'MCP ' + toolNameList(keys(archive.mcp)),
|
|
76
|
-
'
|
|
77
|
-
'
|
|
76
|
+
'skills ' + toolNameList(list(archive.skills)),
|
|
77
|
+
'personas ' + toolNameList(list(archive.subagents)),
|
|
78
78
|
];
|
|
79
79
|
const notes = keys(archive.mcpNotes);
|
|
80
80
|
if (notes.length)
|
|
81
|
-
parts.push('
|
|
81
|
+
parts.push('notes ' + toolNameList(notes));
|
|
82
82
|
// 模型工具表方案(0.15.0 起档案里有这一栏):进入时换的是**模型自己能用什么**,不报就是
|
|
83
83
|
// 让它事后发现自己少了工具却说不出原因。只给方案名 —— 关停名单不在档案里,硬算要多读一次
|
|
84
84
|
// 方案清单,而真正需要条数的时机是切换之后(那条回执有)。没绑就不冒这一格(与备注同口径)。
|
|
85
85
|
const tt = toolTableLabel(archive.toolTablePreset);
|
|
86
86
|
if (tt)
|
|
87
|
-
parts.push('
|
|
87
|
+
parts.push('tool-table preset "' + tt + '"');
|
|
88
88
|
// 快捷提示词绑定(0.20.0):进了这个场景会动到 `/` 菜单里哪些条目,不报就只能靠猜。
|
|
89
89
|
// 空表是"进入后全部停用",与"没绑"(这一域不碰)是两句相反的话,不能压成一个 "0 条"。
|
|
90
90
|
//
|
|
@@ -94,7 +94,7 @@ function archiveText(archive) {
|
|
|
94
94
|
// 与 `skills` / `subagents` 两段同款(那两段本来就是 `toolNameList` 印名字)。
|
|
95
95
|
const qp = archive.quickPrompts;
|
|
96
96
|
if (Array.isArray(qp))
|
|
97
|
-
parts.push(qp.length ? '
|
|
97
|
+
parts.push(qp.length ? 'quick prompts ' + toolNameList(qp) : 'quick prompts: all off after entering');
|
|
98
98
|
return parts.join(' | ');
|
|
99
99
|
}
|
|
100
100
|
/**
|
|
@@ -115,9 +115,9 @@ function segmentDiff(key, beforeValue, afterValue) {
|
|
|
115
115
|
if (dropped.length)
|
|
116
116
|
marks.push('-' + toolNameList(dropped));
|
|
117
117
|
if (!marks.length && JSON.stringify(beforeValue ?? null) !== JSON.stringify(afterValue ?? null))
|
|
118
|
-
marks.push('
|
|
118
|
+
marks.push('the checked set changed');
|
|
119
119
|
return {
|
|
120
|
-
text: key + ' ' + b.length + '
|
|
120
|
+
text: key + ' ' + b.length + ' -> ' + a.length + ' items' + (marks.length ? ' (' + marks.join(' ') + ')' : ''),
|
|
121
121
|
dropped,
|
|
122
122
|
};
|
|
123
123
|
}
|
|
@@ -133,11 +133,11 @@ function agentsMdNote(sync) {
|
|
|
133
133
|
if (!sync || typeof sync !== 'object')
|
|
134
134
|
return '';
|
|
135
135
|
if (sync.error)
|
|
136
|
-
return '\nWARN:
|
|
136
|
+
return '\nWARN: the global baseline (AGENTS.md) was not synced: ' + String(sync.error);
|
|
137
137
|
if (sync.applied)
|
|
138
|
-
return '\nAGENTS.md
|
|
138
|
+
return '\nAGENTS.md <- the scene-bound preset "' + String(sync.applied) + '"';
|
|
139
139
|
if (sync.restored === true)
|
|
140
|
-
return '\nAGENTS.md
|
|
140
|
+
return '\nAGENTS.md <- restored the pre-scene baseline';
|
|
141
141
|
return '';
|
|
142
142
|
}
|
|
143
143
|
export function buildSceneTools(deps) {
|
|
@@ -148,15 +148,15 @@ export function buildSceneTools(deps) {
|
|
|
148
148
|
// 这种筛选(未启用的场景照样要能被进入,藏着只会让模型以为它不存在)。
|
|
149
149
|
// 描述与参数说明里**不点名同族工具**(scene_manager_list / _switch / _save 三条都在出厂
|
|
150
150
|
// 默认的隐藏名单里):静态文本没法按 toolVisible 改写,点名一条模型没有的工具只会让它
|
|
151
|
-
// 白跑一趟。要说"去哪看"就用中性说法(the scene listing / the
|
|
151
|
+
// 白跑一趟。要说"去哪看"就用中性说法(the scene listing / the "Current scene" reminder
|
|
152
152
|
// —— 后者是常驻源,只要场景域开着就在)。点名只留在**运行时回执**里、按可见性分叉。
|
|
153
|
-
description: 'List every scene with its archive (what entering it switches on: MCP servers, skills, personas, quick prompts, and the bound model tool-table scheme when there is one), bound prompt preset, memory count and enabled / entered / locked state. The scene currently entered is also shown in the
|
|
153
|
+
description: 'List every scene with its archive (what entering it switches on: MCP servers, skills, personas, quick prompts, and the bound model tool-table scheme when there is one), bound prompt preset, memory count and enabled / entered / locked state. The scene currently entered is also shown in the "Current scene" reminder.',
|
|
154
154
|
parameters: {},
|
|
155
155
|
output: { schema: { type: 'string' }, render: (_a, v) => text(v) },
|
|
156
156
|
async execute() {
|
|
157
157
|
const r = await deps.rulesOps['rules-list']({});
|
|
158
158
|
if (!r || r.ok === false)
|
|
159
|
-
throw new Error((r && r.error) || '
|
|
159
|
+
throw new Error((r && r.error) || 'failed to read scenes');
|
|
160
160
|
// 档案与「已进入哪个场景」要额外读一次(纯读盘,`scene-mode-get`)。
|
|
161
161
|
// 读不到**不阻断**清单:场景行退化成只有名字与启用状态,而不是让整条工具失败 ——
|
|
162
162
|
// 一份少了档案的清单仍然有用,一次失败什么都没有。
|
|
@@ -180,28 +180,28 @@ export function buildSceneTools(deps) {
|
|
|
180
180
|
const scenes = r.scenes || [];
|
|
181
181
|
const lines = scenes.map((s) => {
|
|
182
182
|
const name = String(s.name);
|
|
183
|
-
const state = [s.active === true ? '
|
|
183
|
+
const state = [s.active === true ? 'on' : 'off'];
|
|
184
184
|
// 「启用」与「进入」是两个独立状态轴(见 README「进/出模式」),只报前者的话,
|
|
185
185
|
// 模型会把"启用着"读成"现在就是它" —— 而记忆与提示词只按**进入**的那一个注入。
|
|
186
186
|
if (entered !== null && name === entered)
|
|
187
|
-
state.push('
|
|
187
|
+
state.push('entered');
|
|
188
188
|
if (s.locked === true)
|
|
189
|
-
state.push('
|
|
189
|
+
state.push('locked');
|
|
190
190
|
// 打**场景名**而不是 label:`scene_manager_save` / `_switch` 要的都是名字(单个
|
|
191
191
|
// 路径段),label 只是界面显示名。两者不同时把 label 放进括号,别让模型照抄错那个。
|
|
192
|
-
const title = name + (s.label && String(s.label) !== name ? '
|
|
193
|
-
const bits = ['- ' + title + ' [' + state.join('
|
|
192
|
+
const title = name + (s.label && String(s.label) !== name ? ' (' + String(s.label) + ')' : '');
|
|
193
|
+
const bits = ['- ' + title + ' [' + state.join(', ') + '] · memories: ' + Number(s.count || 0)];
|
|
194
194
|
if (s.description)
|
|
195
|
-
bits.push('
|
|
195
|
+
bits.push('description: ' + String(s.description));
|
|
196
196
|
if (s.prompt)
|
|
197
|
-
bits.push('
|
|
198
|
-
bits.push('
|
|
197
|
+
bits.push('preset: ' + String(s.prompt));
|
|
198
|
+
bits.push('archive: ' + (archives === null ? 'unreadable (does not mean absent)' : archiveText(archives[name])));
|
|
199
199
|
return bits.join(' · ');
|
|
200
200
|
});
|
|
201
|
-
return '
|
|
202
|
-
(r.activeMode === 'all' ? '
|
|
203
|
-
(entered === null ? '
|
|
204
|
-
(lines.join('\n') || '(
|
|
201
|
+
return 'scenes (' + scenes.length + ' · ' +
|
|
202
|
+
(r.activeMode === 'all' ? 'the enabled set is the historical default (all enabled)' : 'the enabled set is narrowed to a single scene') +
|
|
203
|
+
(entered === null ? '; no scene is entered right now' : '; currently entered: "' + entered + '"') + '):\n' +
|
|
204
|
+
(lines.join('\n') || '(no scenes)');
|
|
205
205
|
},
|
|
206
206
|
}));
|
|
207
207
|
register(defineTool({
|
|
@@ -212,13 +212,13 @@ export function buildSceneTools(deps) {
|
|
|
212
212
|
description: 'Enter a scene, or leave the one currently entered. Entering switches on its archive and everything else off, narrows injection to its memories, and applies its bound prompt preset to AGENTS.md. Leaving restores the runtime snapshot taken on entry. Only when the user asks or approves.',
|
|
213
213
|
parameters: {
|
|
214
214
|
action: { type: 'string', required: true, enum: ['enter', 'exit'], description: 'enter = switch to the scene named in `scene`; exit = leave the current one (no scene needed).' },
|
|
215
|
-
scene: { type: 'string', description: 'Scene name (one path segment), required for enter. Use the exact name, not the display label — the scene listing and the
|
|
215
|
+
scene: { type: 'string', description: 'Scene name (one path segment), required for enter. Use the exact name, not the display label — the scene listing and the "Current scene" reminder both print names.' },
|
|
216
216
|
},
|
|
217
217
|
output: { schema: { type: 'string' }, render: (_a, v) => text(v) },
|
|
218
218
|
async execute(args) {
|
|
219
219
|
const action = String((args && args.action) || '');
|
|
220
220
|
if (action !== 'enter' && action !== 'exit') {
|
|
221
|
-
throw new Error('action
|
|
221
|
+
throw new Error('action must be enter / exit (got: ' + (action || '(empty)') + ')');
|
|
222
222
|
}
|
|
223
223
|
// 刻意**不过** `lockedSceneGuard()`:场景**自身**的启停不在冻结清单里(冻结的是五个
|
|
224
224
|
// 域的内容)。锁定中的场景正被进入时不能退出 —— 那条守卫在 `scene-mode-set` 的包装里
|
|
@@ -227,10 +227,10 @@ export function buildSceneTools(deps) {
|
|
|
227
227
|
if (action === 'enter') {
|
|
228
228
|
scene = String((args && args.scene) || '').trim();
|
|
229
229
|
if (scene === '')
|
|
230
|
-
throw new Error('action=enter
|
|
230
|
+
throw new Error('action=enter needs scene: the scene name (copy it from the scene_manager_list line)');
|
|
231
231
|
// 场景名是**单个路径段**(与 scene_manager_save 同一条校验、同一句报错)。
|
|
232
232
|
if (scene.indexOf('/') >= 0 || scene.indexOf('\\') >= 0) {
|
|
233
|
-
throw new Error('
|
|
233
|
+
throw new Error('a scene name is a single path segment (no / or \\): ' + scene);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
236
|
// 先读一次"现在在哪个场景":用来在回执里说清是"从 A 换到 B"还是"本来就在 B"。
|
|
@@ -247,14 +247,14 @@ export function buildSceneTools(deps) {
|
|
|
247
247
|
// `rules-set-active` 写。少调后者,记忆与提示词仍按旧场景注入 —— 而运行时看起来已经切了。
|
|
248
248
|
const mode = await deps.archiveOps['scene-mode-set']({ scene: action === 'enter' ? scene : null });
|
|
249
249
|
if (!mode || mode.ok === false)
|
|
250
|
-
throw new Error((mode && mode.error) || (action === 'enter' ? '
|
|
250
|
+
throw new Error((mode && mode.error) || (action === 'enter' ? 'failed to enter the scene: ' + scene : 'failed to exit the scene'));
|
|
251
251
|
const act = await deps.sceneActivate({ scenes: action === 'enter' ? [scene] : [] });
|
|
252
252
|
if (!act || act.ok === false) {
|
|
253
253
|
// 半成品状态:运行时已经切了,启用集合没跟上。**必须说出来** —— 不说的话模型以为
|
|
254
254
|
// 切完了,而记忆与提示词还是旧场景的。重试一次是安全的(两个 op 都幂等)。
|
|
255
|
-
throw new Error((action === 'enter' ? '
|
|
256
|
-
'
|
|
257
|
-
'
|
|
255
|
+
throw new Error((action === 'enter' ? 'runtime switched to scene "' + scene + '"' : 'runtime left the scene') +
|
|
256
|
+
', but the enabled set was not written (memories and prompts still inject per the old scene): ' + ((act && act.error) || 'unknown error') +
|
|
257
|
+
' — retry scene_manager_switch once (both ops are idempotent)');
|
|
258
258
|
}
|
|
259
259
|
// 回执:说清"从哪到哪 + 实际切了几台 / 几个来源 + 工具表换到哪一份 + 有没有键被丢弃 + AGENTS.md 那一步"。
|
|
260
260
|
// 都是界面会如实报的同一批事实(`memory.result.modeSet` / `modeSwitched` / `archive.stale`),
|
|
@@ -268,10 +268,10 @@ export function buildSceneTools(deps) {
|
|
|
268
268
|
? (mode.applied === null || mode.applied === undefined)
|
|
269
269
|
: !mode.restored;
|
|
270
270
|
const head = noop
|
|
271
|
-
? (action === 'enter' ? 'OK: already in scene
|
|
271
|
+
? (action === 'enter' ? 'OK: already in scene "' + scene + '"' : 'OK: not in any scene (nothing to leave)')
|
|
272
272
|
: action === 'enter'
|
|
273
|
-
? 'OK: ' + (before === null ? 'entered' : '
|
|
274
|
-
: 'OK: left the scene
|
|
273
|
+
? 'OK: ' + (before === null ? 'entered' : 'left "' + before + '", entered ') + 'scene "' + scene + '"'
|
|
274
|
+
: 'OK: left the scene "' + (before || '') + '"';
|
|
275
275
|
const parts = [];
|
|
276
276
|
if (count(sw.mcpServers) || count(sw.skillSources)) {
|
|
277
277
|
parts.push('switched ' + count(sw.mcpServers) + ' MCP server(s) / ' + count(sw.skillSources) + ' skill source(s)');
|
|
@@ -280,16 +280,16 @@ export function buildSceneTools(deps) {
|
|
|
280
280
|
// 原因,用户问"刚才改了什么"时拿回去的就是半截话。引擎只在**名单真的换了**时才回方案名。
|
|
281
281
|
const tt = toolTableLabel(mode && mode.toolTablePreset);
|
|
282
282
|
if (tt) {
|
|
283
|
-
parts.push('
|
|
283
|
+
parts.push('tool table switched to preset "' + tt + '" (' + count(sw.toolTableHidden) + ' hidden)');
|
|
284
284
|
}
|
|
285
285
|
else if (action === 'exit' && mode.restored && mode.restored.toolTable === true) {
|
|
286
|
-
parts.push('
|
|
286
|
+
parts.push('tool table restored from the pre-scene snapshot');
|
|
287
287
|
}
|
|
288
288
|
if (Array.isArray(mode.stale) && mode.stale.length) {
|
|
289
|
-
parts.push('
|
|
289
|
+
parts.push('dropped keys this machine does not have: ' + mode.stale.join(', '));
|
|
290
290
|
}
|
|
291
291
|
return head + (parts.length ? ' · ' + parts.join(' · ') : '') + agentsMdNote(act.agentsMd) +
|
|
292
|
-
(noop ? '\n
|
|
292
|
+
(noop ? '\n(' + (action === 'enter' ? 'already in the target scene' : 'already outside every scene') + ' — runtime unchanged)' : '');
|
|
293
293
|
},
|
|
294
294
|
}));
|
|
295
295
|
register(defineTool({
|
|
@@ -301,7 +301,7 @@ export function buildSceneTools(deps) {
|
|
|
301
301
|
parameters: {
|
|
302
302
|
scene: { type: 'string', required: true, description: 'Scene name (one path segment, ≤64 chars, no / \\ or < > : " | ? *, not `_shared`). Created if absent.' },
|
|
303
303
|
label: { type: 'string', description: 'Display name for this scene. Omit = keep.' },
|
|
304
|
-
description: { type: 'string', description: 'What this scene is for; shown beside the scene name in the
|
|
304
|
+
description: { type: 'string', description: 'What this scene is for; shown beside the scene name in the "Current scene" reminder while the scene is on — write an instruction, not a note. Omit = keep.' },
|
|
305
305
|
// 与下面 `mcp` / `skills` / `subagents` 同一句式("Omit = keep; X = 一个都不"):
|
|
306
306
|
// 界面 2026-10-10 补了「无」这个选项,工具侧的空串语义要跟着说清,否则模型只知道
|
|
307
307
|
// 「省略 = 保持」,不知道还有一条「解绑」的路。
|
|
@@ -344,12 +344,12 @@ export function buildSceneTools(deps) {
|
|
|
344
344
|
throw new Error(blocked);
|
|
345
345
|
const scene = String((args && args.scene) || '').trim();
|
|
346
346
|
if (scene === '')
|
|
347
|
-
throw new Error('
|
|
347
|
+
throw new Error('missing argument: scene (the scene name)');
|
|
348
348
|
// 场景名是**单个路径段**(界面把场景当一级列表展示,场景名就是 `memories/<场景>/` 那级
|
|
349
349
|
// 目录名)。`rules-create-scene` 会做完整校验,但档案那条路不经过它 —— 这里先挡一次,
|
|
350
350
|
// 报错也更贴模型要的东西(它要的是"这个名字不行",不是内部规则)。
|
|
351
351
|
if (scene.indexOf('/') >= 0 || scene.indexOf('\\') >= 0) {
|
|
352
|
-
throw new Error('
|
|
352
|
+
throw new Error('a scene name is a single path segment (no / or \\): ' + scene);
|
|
353
353
|
}
|
|
354
354
|
// ① 场景记录:`rules-create-scene` 本身是**幂等 upsert**(已存在则更新描述/标签/绑定的
|
|
355
355
|
// 预设,不报错),所以不必先查再分建/改 —— 与记忆 save 的"有则改、无则建"同一口径。
|
|
@@ -371,7 +371,7 @@ export function buildSceneTools(deps) {
|
|
|
371
371
|
}
|
|
372
372
|
const created = await deps.rulesOps['rules-create-scene'](payload);
|
|
373
373
|
if (!created || created.ok === false)
|
|
374
|
-
throw new Error((created && created.error) || '
|
|
374
|
+
throw new Error((created && created.error) || 'failed to save the scene');
|
|
375
375
|
// ② 两处「解绑」只能走 `rules-update-scene`:`scene-archive-save` 对载荷里**不带的栏**
|
|
376
376
|
// 一律保留现值(archive-engine 的「不带 ≠ 解绑」),从现值复制出来的合并档案会把
|
|
377
377
|
// 刚解掉的绑定又写回去 —— 所以解绑与设置分成两条路,合并前还要先摘掉刚解掉的键。
|
|
@@ -383,7 +383,7 @@ export function buildSceneTools(deps) {
|
|
|
383
383
|
const setToolTable = args.toolTablePreset !== undefined && String(args.toolTablePreset).trim() !== '';
|
|
384
384
|
const setQuickPrompts = args.quickPrompts !== undefined;
|
|
385
385
|
if (setQuickPrompts && !Array.isArray(args.quickPrompts)) {
|
|
386
|
-
throw new Error('quickPrompts
|
|
386
|
+
throw new Error('quickPrompts must be an array of ids ([] = turn them all off on entry; unbinding is a manual step in the panel, not offered here)');
|
|
387
387
|
}
|
|
388
388
|
const touched = ARCHIVE_SECTIONS.filter((k) => args[k] !== undefined);
|
|
389
389
|
const mustWriteArchive = touched.length > 0 || setToolTable || setQuickPrompts;
|
|
@@ -396,7 +396,7 @@ export function buildSceneTools(deps) {
|
|
|
396
396
|
const cur = await deps.archiveOps['scene-mode-get']({});
|
|
397
397
|
if (!cur || cur.ok === false) {
|
|
398
398
|
if (mustWriteArchive)
|
|
399
|
-
throw new Error((cur && cur.error) || '
|
|
399
|
+
throw new Error((cur && cur.error) || 'failed to read the scene archive');
|
|
400
400
|
}
|
|
401
401
|
else {
|
|
402
402
|
before = (cur.archives || {})[scene] || {};
|
|
@@ -413,7 +413,7 @@ export function buildSceneTools(deps) {
|
|
|
413
413
|
const u = await deps.rulesOps['rules-update-scene'](upd);
|
|
414
414
|
if (!u || u.ok === false) {
|
|
415
415
|
// 主路径已成功、解绑没写上:如实报,别让回执假装绑定已经没了。
|
|
416
|
-
clearWarn = '\nWARN:
|
|
416
|
+
clearWarn = '\nWARN: the unbind was not written (' + ((u && u.error) || 'unknown error') + '); the old binding still applies';
|
|
417
417
|
}
|
|
418
418
|
}
|
|
419
419
|
// ③ 档案 read-modify-write:段(mcp / mcpNotes / skills / subagents)与两个绑定栏的
|
|
@@ -440,32 +440,32 @@ export function buildSceneTools(deps) {
|
|
|
440
440
|
archive.quickPrompts = quickPromptIds(args.quickPrompts);
|
|
441
441
|
const r = await deps.archiveOps['scene-archive-save']({ scene, archive });
|
|
442
442
|
if (!r || r.ok === false)
|
|
443
|
-
throw new Error((r && r.error) || '
|
|
443
|
+
throw new Error((r && r.error) || 'failed to save the scene archive');
|
|
444
444
|
// 回执必须回显**这一次替换挤掉了什么**。整段替换本身是对的(省略的段不填回就会被
|
|
445
445
|
// 静默清掉,所以"模型给什么就是什么"),但"给出去的段盖掉了原来的勾选"是**静默**的
|
|
446
446
|
// —— 不写出来,模型只会看到一句"档案段:mcp",以为只是加了一项。
|
|
447
447
|
const diffs = touched.map((k) => segmentDiff(k, before[k], args[k]));
|
|
448
448
|
if (diffs.length) {
|
|
449
|
-
archiveNote = '
|
|
449
|
+
archiveNote = 'archive segments: ' + diffs.map((d) => d.text).join('; ');
|
|
450
450
|
if (diffs.some((d) => d.dropped.length > 0)) {
|
|
451
|
-
warnTail += '\
|
|
451
|
+
warnTail += '\nnote: these segments are replaced as a whole; entries marked "-" above were dropped. To keep them, include them in this same call.';
|
|
452
452
|
}
|
|
453
453
|
}
|
|
454
454
|
// 当前环境里不存在的键会被 op 丢弃并报告(改名或删除留下的,含工具表方案名与
|
|
455
455
|
// 快捷提示词 id):如实说,否则模型以为勾上了而实际没有。
|
|
456
456
|
if (r.stale && r.stale.length)
|
|
457
|
-
warnTail += '\n
|
|
457
|
+
warnTail += '\n(dropped keys the current environment does not have: ' + r.stale.join(', ') + ')';
|
|
458
458
|
// 存盘成功但应用到运行时失败:`scene-archive-save` 对"正是当前模式的那个场景"会就地
|
|
459
459
|
// 重应用,失败不回滚档案。如实报出去,别假装生效了(与界面同口径)。
|
|
460
460
|
if (r.applyError)
|
|
461
|
-
warnTail += '\n
|
|
461
|
+
warnTail += '\n(saved, but applying it to the runtime failed: ' + r.applyError + ')';
|
|
462
462
|
}
|
|
463
463
|
// 解绑写失败了就别输出「X → 解绑」—— 上面那句 WARN 才是事实;设置方向不受 ② 影响。
|
|
464
464
|
if (args.toolTablePreset !== undefined && !(unbindToolTable && clearWarn !== '')) {
|
|
465
465
|
toolTableNote = beforeKnown
|
|
466
|
-
? '
|
|
467
|
-
(unbindToolTable ? '
|
|
468
|
-
: '
|
|
466
|
+
? 'tool-table preset: ' + (toolTableLabel(before.toolTablePreset) || 'unbound') + ' -> ' +
|
|
467
|
+
(unbindToolTable ? 'unbind' : toolTableLabel(String(args.toolTablePreset).trim()))
|
|
468
|
+
: 'tool-table preset: unbound (the current archive is unreadable, no diff available)';
|
|
469
469
|
}
|
|
470
470
|
if (setQuickPrompts && beforeKnown) {
|
|
471
471
|
const beforeIds = Array.isArray(before.quickPrompts) ? before.quickPrompts.map((x) => String(x)) : [];
|
|
@@ -477,15 +477,15 @@ export function buildSceneTools(deps) {
|
|
|
477
477
|
marks.push('+' + toolNameList(added));
|
|
478
478
|
if (dropped.length)
|
|
479
479
|
marks.push('-' + toolNameList(dropped));
|
|
480
|
-
quickNote = '
|
|
481
|
-
(afterIds.length === 0 ? '
|
|
482
|
-
(marks.length ? '
|
|
480
|
+
quickNote = 'quick prompts: ' + beforeIds.length + ' -> ' + afterIds.length + ' ids' +
|
|
481
|
+
(afterIds.length === 0 ? ' (all off after entering)' : '') +
|
|
482
|
+
(marks.length ? ' (' + marks.join(' ') + ')' : '');
|
|
483
483
|
}
|
|
484
484
|
// 收敛提示:`rules-create-scene` 在"历史默认(全部启用)"状态下会把启用集合收窄成
|
|
485
485
|
// 单选(`collapseActiveForNewScene`)—— 那是一次**会改变运行时**的动作,必须说出来。
|
|
486
486
|
// 界面上同样如实报(`scenes.result.created.collapsed`)。
|
|
487
487
|
const collapsed = created.collapsedActive === true;
|
|
488
|
-
const parts = [touchedRecord.length ? '
|
|
488
|
+
const parts = [touchedRecord.length ? 'scene record updated (' + touchedRecord.join(', ') + ')' : 'scene existence ensured'];
|
|
489
489
|
if (archiveNote)
|
|
490
490
|
parts.push(archiveNote);
|
|
491
491
|
if (toolTableNote)
|
|
@@ -496,13 +496,13 @@ export function buildSceneTools(deps) {
|
|
|
496
496
|
// 部分启用(开 save 不开 switch)时指向一条模型没有的工具,只会让它白跑一趟 ——
|
|
497
497
|
// 与 mcp/skill 的 listHint 同一条纪律。
|
|
498
498
|
const enterHint = deps.toolVisible('scene_manager_switch')
|
|
499
|
-
? '
|
|
500
|
-
: '
|
|
501
|
-
return 'OK: scene
|
|
499
|
+
? 'To take effect, enter it with scene_manager_switch.'
|
|
500
|
+
: 'To take effect, the user has to enter this scene manually (scene_manager_switch is not available in this session).';
|
|
501
|
+
return 'OK: scene "' + scene + '"' + parts.join('; ') + warnTail + clearWarn +
|
|
502
502
|
(collapsed
|
|
503
|
-
? '\
|
|
503
|
+
? '\nnote: every scene was enabled until now (the historical all-enabled default); this call narrowed the enabled set to one scene, and the new one is not it — the user should confirm the enabled state.'
|
|
504
504
|
: '') +
|
|
505
|
-
'\
|
|
505
|
+
'\nSaving does not change the runtime: ' + enterHint;
|
|
506
506
|
},
|
|
507
507
|
}));
|
|
508
508
|
}
|
package/lib/tools/skills.js
CHANGED
|
@@ -16,7 +16,7 @@ export function buildSkillTools(deps) {
|
|
|
16
16
|
// (2026-10-15 注入文本审查):可行动的那半句已经写在 `skill_manager_read` 的描述里
|
|
17
17
|
// ("Not sent when the official `skill` tool is mounted — use that instead."),
|
|
18
18
|
// 这里再预告一遍同一个条件,只是同一件事付两次 token。
|
|
19
|
-
description: 'List skills with enabled state, effective/shadowed status and source file path. The
|
|
19
|
+
description: 'List skills with enabled state, effective/shadowed status and source file path. The "Current skills" reminder carries callable skills and summaries only; use this for entries that are off, for each entry\'s source-file path, and to see which source wins a name collision. Defaults to enabled only; all=true for every entry. A copy marked "shadowed-by:<root>" stays inactive even if enabled; "preferred" marks the copy that wins a name collision.',
|
|
20
20
|
parameters: {
|
|
21
21
|
all: { type: 'boolean', description: 'Include disabled and shadowed entries (default false).' },
|
|
22
22
|
},
|
|
@@ -84,7 +84,7 @@ export function buildSkillTools(deps) {
|
|
|
84
84
|
// 已在上面标出胜出者),再走 `skill-detail` 读那一份 —— 读到的永远是目录列出来的那一份。
|
|
85
85
|
register(defineTool({
|
|
86
86
|
name: 'skill_manager_read',
|
|
87
|
-
description: 'Read one skill\'s full SKILL.md body by name — what the
|
|
87
|
+
description: 'Read one skill\'s full SKILL.md body by name — what the "Current skills" reminder deliberately leaves out (it has names and summaries only). Also lists the other files living in that skill folder (references/, scripts/), which the body often points at. Resolves name collisions the same way, so you get the copy actually in effect; pass root only to inspect a specific (e.g. shadowed) source. Not sent when the official `skill` tool is mounted — use that instead.',
|
|
88
88
|
parameters: {
|
|
89
89
|
name: { type: 'string', required: true, description: 'Skill name, exactly as it appears in the catalog (kebab-case).' },
|
|
90
90
|
root: { type: 'string', description: 'Source root key (dsh/hub/agents/codex/claude, a project key, or a custom one). Default: the winning copy of this name.' },
|
|
@@ -204,35 +204,35 @@ export function buildSkillTools(deps) {
|
|
|
204
204
|
const preferGiven = Boolean(args && args.prefer !== undefined);
|
|
205
205
|
const prefer = args && args.prefer === true;
|
|
206
206
|
if (preferGiven && typeof args.prefer !== 'boolean')
|
|
207
|
-
throw new Error('prefer
|
|
207
|
+
throw new Error('prefer must be true or false');
|
|
208
208
|
if (name && source)
|
|
209
|
-
throw new Error('name
|
|
209
|
+
throw new Error('give either name or source, not both: name is one skill, source is a whole source folder');
|
|
210
210
|
if (preferGiven && source)
|
|
211
|
-
throw new Error('prefer
|
|
211
|
+
throw new Error('prefer only makes sense for a single skill: pass name, not source');
|
|
212
212
|
if (!name && !source)
|
|
213
|
-
throw new Error('
|
|
213
|
+
throw new Error('missing argument: name (one skill) or source (a whole source folder)');
|
|
214
214
|
if (preferGiven && !name)
|
|
215
|
-
throw new Error('
|
|
215
|
+
throw new Error('missing argument: prefer needs name');
|
|
216
216
|
if (preferGiven && String((args && args.root) || '').trim() === '') {
|
|
217
|
-
throw new Error('prefer
|
|
217
|
+
throw new Error('prefer needs root: name the copy that should win this collision (dsh / hub / agents / codex / claude or a project key)');
|
|
218
218
|
}
|
|
219
219
|
// 两个动作一个都没给就拒:`args.enabled ? enable : disable` 会把"忘了给"静默当成"停用"。
|
|
220
220
|
if (args.enabled === undefined && !preferGiven) {
|
|
221
|
-
throw new Error('
|
|
221
|
+
throw new Error('missing argument: enabled must be true or false (pass prefer if you only want to change which copy wins the collision)');
|
|
222
222
|
}
|
|
223
223
|
if (source) {
|
|
224
224
|
// 来源级启停:登记表里 `skill-source-enable` / `skill-source-disable` 都是
|
|
225
225
|
// `{ serviceWrite, frozen, syncsArchive }`,所以守卫已过、档案也要同步
|
|
226
226
|
// —— 同步的入参是 `{ root }`(来源级没有独立字段,档案靠"来源下有没有被勾的技能"反推)。
|
|
227
227
|
if (args.root !== undefined)
|
|
228
|
-
throw new Error('source
|
|
228
|
+
throw new Error('source is already a source-level key; do not pass root with it');
|
|
229
229
|
const op = args.enabled ? 'skill-source-enable' : 'skill-source-disable';
|
|
230
230
|
const r = await deps.skillsOps[op]({ root: source });
|
|
231
231
|
if (!r || r.ok === false)
|
|
232
232
|
throw new Error((r && r.error) || 'skill source toggle failed');
|
|
233
233
|
const syncErr = await deps.syncSwitchToScene(op, { root: source, enabled: args.enabled === true });
|
|
234
234
|
return 'OK: source ' + source + ' now ' + (args.enabled ? 'enabled' : 'disabled') +
|
|
235
|
-
(syncErr ? '\nWARN:
|
|
235
|
+
(syncErr ? '\nWARN: the current scene archive was not synced (' + syncErr + ')' : '');
|
|
236
236
|
}
|
|
237
237
|
// 不给 root 就在全部来源里找(2026-10-15 smoke-test P0-1):`skill_manager_save` 的落点
|
|
238
238
|
// 是 hub(`tool-management/skills/`),而这里的默认值原本是 dsh —— 「建完就启用」这条
|
|
@@ -248,7 +248,7 @@ export function buildSkillTools(deps) {
|
|
|
248
248
|
// 探得到、就是没这个技能:当场把"查过哪些来源"说清楚。否则模型收到 op 那句不带
|
|
249
249
|
// 来源信息的「技能不存在」后,只能猜该传哪个 root(smoke-test P1-4-old 同一处)。
|
|
250
250
|
if (found.ok) {
|
|
251
|
-
throw new Error('
|
|
251
|
+
throw new Error('no such skill: ' + name + (found.roots.length ? ' (searched ' + found.roots.join(', ') + ')' : ''));
|
|
252
252
|
}
|
|
253
253
|
// 探不到(skill-state 失败)就别在这里编结论:退回旧默认,交给 op 报它自己的错。
|
|
254
254
|
return 'dsh';
|
|
@@ -262,7 +262,7 @@ export function buildSkillTools(deps) {
|
|
|
262
262
|
const op = prefer ? 'skill-prefer' : 'skill-unprefer';
|
|
263
263
|
const p = await deps.skillsOps[op]({ name: args.name, root });
|
|
264
264
|
if (!p || p.ok === false)
|
|
265
|
-
throw new Error((p && p.error) || '
|
|
265
|
+
throw new Error((p && p.error) || 'failed to set the collision winner');
|
|
266
266
|
// `skill-prefer` 会在"记下了但不会生效"时回 ok:true + warning(首选指向**停用**的来源,
|
|
267
267
|
// 而同名首选会挡住其他副本 → 两份都不生效)。原样转出去,否则模型以为已经生效。
|
|
268
268
|
out.push((prefer
|
|
@@ -303,7 +303,7 @@ export function buildSkillTools(deps) {
|
|
|
303
303
|
const warn = shadowedBy
|
|
304
304
|
? ' — BUT this copy is shadowed by the same-name skill in "' + shadowedBy + '", so it stays inactive: enable that copy instead, or make it win with prefer=true and root="' + shadowedBy + '"'
|
|
305
305
|
: '';
|
|
306
|
-
const sceneWarn = syncErr ? '\nWARN:
|
|
306
|
+
const sceneWarn = syncErr ? '\nWARN: the current scene archive was not synced (' + syncErr + ')' : '';
|
|
307
307
|
out.push('OK: ' + args.name + ' now ' + (args.enabled ? 'enabled' : 'disabled') + warn + sceneWarn);
|
|
308
308
|
}
|
|
309
309
|
return out.join('\n');
|
|
@@ -339,10 +339,10 @@ export function buildSkillTools(deps) {
|
|
|
339
339
|
throw new Error(blocked);
|
|
340
340
|
const wanted = String((args && args.name) || '').trim();
|
|
341
341
|
if (!wanted)
|
|
342
|
-
throw new Error('
|
|
342
|
+
throw new Error('missing argument: name');
|
|
343
343
|
const kebab = toKebab(wanted);
|
|
344
344
|
if (!kebab)
|
|
345
|
-
throw new Error('
|
|
345
|
+
throw new Error('cannot build a valid kebab-case name (raw name: ' + wanted + ')');
|
|
346
346
|
// 建还是改:先看这个(规范化后的)名字在不在。`skill-create` 对同名是**拒绝**的,
|
|
347
347
|
// 所以这条判定只能在这里做 —— 不能指望 op 层兜,也不能让模型自己先查一次
|
|
348
348
|
// (它手里的清单可能已经过期)。
|
|
@@ -367,7 +367,7 @@ export function buildSkillTools(deps) {
|
|
|
367
367
|
body === '' ? 'body' : '',
|
|
368
368
|
].filter((x) => x !== '');
|
|
369
369
|
if (missing.length)
|
|
370
|
-
throw new Error('
|
|
370
|
+
throw new Error('creating a skill needs ' + missing.join(' and ') + ' (' + kebab + ' does not exist yet)');
|
|
371
371
|
const r = await deps.skillsOps['skill-create']({ name: wanted, description: args.description, body: args.body });
|
|
372
372
|
if (!r || r.ok === false)
|
|
373
373
|
throw new Error((r && r.error) || 'skill create failed');
|
|
@@ -377,8 +377,8 @@ export function buildSkillTools(deps) {
|
|
|
377
377
|
// 里只有这一条不报启用状态)。点名的 `skill_manager_switch` 在出厂默认名单里就是关着的,
|
|
378
378
|
// 关着就改说"用户启用后才能用",别指一条模型没有的工具。
|
|
379
379
|
const enableHint = deps.toolVisible('skill_manager_switch')
|
|
380
|
-
? '
|
|
381
|
-
: '
|
|
380
|
+
? ' (created disabled; use skill_manager_switch to enable it)'
|
|
381
|
+
: ' (created disabled; the user has to enable it first)';
|
|
382
382
|
return 'Created DSH skill ' + (data.name || kebab) + ' at ' + (data.path || '(unknown)') + enableHint;
|
|
383
383
|
}
|
|
384
384
|
// 改的分支:省略 = 保持。两个可改字段一个都没给就拒 —— op 会按"保持原样"重写一次文件
|
|
@@ -389,12 +389,12 @@ export function buildSkillTools(deps) {
|
|
|
389
389
|
if (args.body !== undefined)
|
|
390
390
|
touched.push('body');
|
|
391
391
|
if (!touched.length)
|
|
392
|
-
throw new Error('
|
|
392
|
+
throw new Error('nothing to change: pass description and/or body');
|
|
393
393
|
const r = await deps.skillsOps['skill-update']({ name: wanted, description: args.description, body: args.body });
|
|
394
394
|
if (!r || r.ok === false)
|
|
395
395
|
throw new Error((r && r.error) || 'skill update failed');
|
|
396
396
|
const data = r.data || {};
|
|
397
|
-
return 'Updated DSH skill ' + (data.name || kebab) + ' at ' + (data.path || '(unknown)') + '
|
|
397
|
+
return 'Updated DSH skill ' + (data.name || kebab) + ' at ' + (data.path || '(unknown)') + ' (changed ' + touched.join(', ') + ')';
|
|
398
398
|
},
|
|
399
399
|
}));
|
|
400
400
|
}
|
package/lib/tools/subagent.js
CHANGED
|
@@ -78,7 +78,7 @@ export function buildSubagentTools(deps) {
|
|
|
78
78
|
try {
|
|
79
79
|
register(deps.defineTool({
|
|
80
80
|
name: 'subagent_manager_switch',
|
|
81
|
-
description: 'Enable or disable one persona. Only enabled personas appear in the
|
|
81
|
+
description: 'Enable or disable one persona. Only enabled personas appear in the "Current subagents" catalog and can be given work. Reversible. Only when the user asks or approves.',
|
|
82
82
|
parameters: {
|
|
83
83
|
name: { type: 'string', required: true, description: 'Persona name.' },
|
|
84
84
|
enabled: { type: 'boolean', required: true, description: 'true = enable, false = disable; omission is refused.' },
|
|
@@ -91,12 +91,12 @@ export function buildSubagentTools(deps) {
|
|
|
91
91
|
const name = String((args && args.name) || '').trim();
|
|
92
92
|
const r = await ops()['subagent-toggle']({ name, enabled: args.enabled === true });
|
|
93
93
|
if (!r || r.ok === false)
|
|
94
|
-
throw new Error((r && r.error) || '
|
|
94
|
+
throw new Error((r && r.error) || 'failed to toggle the persona');
|
|
95
95
|
// 场景内改开关要同步进当前场景档案(与 HTTP 层 syncsArchive 同一口径 —— 模型工具
|
|
96
96
|
// 直接调 service op,绕过了那一层包装,所以这里自己叫一声)。
|
|
97
97
|
const syncErr = await deps.syncSwitchToScene('subagent-toggle', { name, enabled: args.enabled === true });
|
|
98
98
|
return 'OK: persona ' + name + ' ' + (args.enabled === true ? 'enabled' : 'disabled') +
|
|
99
|
-
(syncErr ? '\nWARN:
|
|
99
|
+
(syncErr ? '\nWARN: the current scene archive was not synced (' + syncErr + ')' : '');
|
|
100
100
|
},
|
|
101
101
|
}));
|
|
102
102
|
}
|
|
@@ -138,11 +138,11 @@ export function buildSubagentTools(deps) {
|
|
|
138
138
|
// 高级字段的形状校验:填错会在 op 侧**静默**(`serializePersona` 对非数组 `tools`
|
|
139
139
|
// 是"当空处理",等于把用户原有的名单清掉)。在这里先挡一句。
|
|
140
140
|
if (args && args.continuable !== undefined && typeof args.continuable !== 'boolean') {
|
|
141
|
-
throw new Error('continuable
|
|
141
|
+
throw new Error('continuable must be true or false');
|
|
142
142
|
}
|
|
143
143
|
for (const k of ['tools', 'toolsDeny']) {
|
|
144
144
|
if (args && args[k] !== undefined && !Array.isArray(args[k])) {
|
|
145
|
-
throw new Error(k + '
|
|
145
|
+
throw new Error(k + ' must be an array of strings ([] = clear)');
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
const current = await ops()['subagent-get']({ name });
|
|
@@ -156,17 +156,17 @@ export function buildSubagentTools(deps) {
|
|
|
156
156
|
body === '' ? 'body' : '',
|
|
157
157
|
].filter((x) => x !== '');
|
|
158
158
|
if (missing.length)
|
|
159
|
-
throw new Error('
|
|
159
|
+
throw new Error('creating a persona needs ' + missing.join(' and ') + ' (' + name + ' does not exist yet)');
|
|
160
160
|
const r = await ops()['subagent-create']({ name, description: args.description, body: args.body, output: args.output, ...advancedArgs(args) });
|
|
161
161
|
if (!r || r.ok === false)
|
|
162
|
-
throw new Error((r && r.error) || '
|
|
162
|
+
throw new Error((r && r.error) || 'failed to create the persona');
|
|
163
163
|
// 新建的人设默认停用(与 MCP / 技能同口径)。这句必须留:不说的话模型会直接去委派,
|
|
164
164
|
// 然后收到"人设不可用",而它并不知道自己少拨了一个开关。点名的 subagent_manager_switch
|
|
165
165
|
// 在出厂默认名单里就是关着的 —— 关着就改说"用户启用后才能委派",别指一条模型没有的
|
|
166
166
|
// 工具(与 mcp/skill 的 listHint 同一条纪律)。
|
|
167
167
|
const enableHint = deps.toolVisible('subagent_manager_switch')
|
|
168
|
-
? '
|
|
169
|
-
: '
|
|
168
|
+
? ' (created disabled; use subagent_manager_switch to enable it before delegating)'
|
|
169
|
+
: ' (created disabled; the user has to enable it before you can delegate)';
|
|
170
170
|
return 'OK: persona ' + String(r.name || name) + ' created' + enableHint;
|
|
171
171
|
}
|
|
172
172
|
const keep = current.persona || {};
|
|
@@ -179,7 +179,7 @@ export function buildSubagentTools(deps) {
|
|
|
179
179
|
touched.push(k);
|
|
180
180
|
}
|
|
181
181
|
if (!touched.length)
|
|
182
|
-
throw new Error('
|
|
182
|
+
throw new Error('nothing to change: pass at least one of description / body / output / nextName or any advanced field');
|
|
183
183
|
const merged = {
|
|
184
184
|
name,
|
|
185
185
|
nextName: args.nextName,
|
|
@@ -200,9 +200,9 @@ export function buildSubagentTools(deps) {
|
|
|
200
200
|
};
|
|
201
201
|
const r = await ops()['subagent-update'](merged);
|
|
202
202
|
if (!r || r.ok === false)
|
|
203
|
-
throw new Error((r && r.error) || '
|
|
204
|
-
return 'OK: persona ' + String(r.name || name) + ' updated
|
|
205
|
-
(r.renamedFrom ? ' —
|
|
203
|
+
throw new Error((r && r.error) || 'failed to update the persona');
|
|
204
|
+
return 'OK: persona ' + String(r.name || name) + ' updated (changed ' + touched.join(', ') + ')' +
|
|
205
|
+
(r.renamedFrom ? ' — was named "' + r.renamedFrom + '"; the scene-archive binding was renamed too' : '');
|
|
206
206
|
},
|
|
207
207
|
}));
|
|
208
208
|
}
|
package/lib/tools/table.js
CHANGED
|
@@ -35,7 +35,7 @@ import { INJECT_DOMAIN_KEYS, domainOfTool } from '../context-inject.js';
|
|
|
35
35
|
* 判据是用户 2026-09-23 的三轮裁定,同一条:**这些信息或动作常态下由注入段与面板承担,
|
|
36
36
|
* 平时也用不到模型自己伸手**。三组:
|
|
37
37
|
* · **写侧五条** —— 三个 `*_save`(MCP / 技能 / 人设:配置本体在面板上改)+ 提示词两条
|
|
38
|
-
*
|
|
38
|
+
* (这一族只有两条,而当前生效的那份本来就在 `Current prompt` 注入段里)。
|
|
39
39
|
* · **读侧三条** —— 两个 `*_list`(MCP / 技能)+ `memory_manager_read`。会话开头注入的那几段
|
|
40
40
|
* 目录已经写了当前有什么,模型不必每轮再带一份查询工具。
|
|
41
41
|
* · **场景三条 + 开关三条** —— 场景一族(建 / 勾档案 / 进入退出)与 `skill` / `memory` /
|
|
@@ -58,7 +58,7 @@ import { INJECT_DOMAIN_KEYS, domainOfTool } from '../context-inject.js';
|
|
|
58
58
|
* 绑定且深度够的、技能目录只列可调用的),所以"没开的那几台 / 被同名覆盖的那份技能 / 没进
|
|
59
59
|
* 上下文的记忆正文"在会话里查不到;`*_switch` 关掉后模型也不能再替你拨启停。三处配套改动为
|
|
60
60
|
* 的就是不把模型往墙上推:目录被条数上限截断时那句「用 `X_manager_list` 查」自动退回「另有
|
|
61
|
-
* N
|
|
61
|
+
* N more not listed.)」(`listToolVisible`);`mcp_manager_list` / `skill_manager_read` 里"没找到 →
|
|
62
62
|
* 去调 list"那两句先问 `deps.toolVisible`;`memory_manager_list` 的描述不再点名已关的工具。
|
|
63
63
|
*
|
|
64
64
|
* 只在**用户从没记过选择**时生效(见 index.ts 的 `toolTableSettingsFrom`:侧车文件读不出来
|