dsh-plugin-tool-management 0.9.0 → 0.9.1
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 +275 -245
- package/README.md +51 -51
- package/README_EN.md +51 -51
- 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 -111
- package/lib/client.js +560 -82
- package/lib/context-inject.js +113 -16
- package/lib/index.js +431 -49
- package/lib/rules/archive-engine.js +30 -10
- package/lib/rules/archive.js +7 -1
- package/lib/rules/service.js +48 -4
- package/lib/scene-prompt-sync.js +4 -12
- package/lib/skills/service.js +56 -2
- package/package.json +1 -1
package/lib/context-inject.js
CHANGED
|
@@ -158,20 +158,28 @@ function textOf(message) {
|
|
|
158
158
|
const block = content[0];
|
|
159
159
|
return block && block.type === 'text' && typeof block.text === 'string' ? block.text : undefined;
|
|
160
160
|
}
|
|
161
|
+
/** 本插件自己的五个注入 kind(去重比对只用这一份 —— 绝不能让官方正文影响"发不发"的判断)。 */
|
|
162
|
+
const PLUGIN_KINDS = new Map(INJECT_DOMAIN_KEYS.map((key) => [INJECT_KIND_OF[key], { key, official: false }]));
|
|
161
163
|
/**
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
* → 该域不在 map 里 → 该重发。表面接口整个缺失时退回扫事件流(按"仍可见"处理,
|
|
167
|
-
* 宁可与现状一致,也不制造每步重发)。
|
|
164
|
+
* 官方载体消息的 kind → 本插件域:技能目录(`@deepseek-ai/dsh-tool-skill`)与
|
|
165
|
+
* AGENTS.md(`@deepseek-ai/dsh-agent-instructions`)。仅用于「注入实况」展示 ——
|
|
166
|
+
* 标准类预设下这两个域由官方在送,它同样是"模型看到的内容",体积要算官方那份。
|
|
167
|
+
* 字符串取自官方包,是宿主侧的稳定契约。
|
|
168
168
|
*/
|
|
169
|
-
|
|
169
|
+
const OFFICIAL_KIND_OF = {
|
|
170
|
+
'skill-catalog': 'skills',
|
|
171
|
+
'agent-instructions': 'prompt',
|
|
172
|
+
};
|
|
173
|
+
/** 实况展示用的 kind 表:本插件的五条 + 官方两条。 */
|
|
174
|
+
const LIVE_KINDS = new Map([
|
|
175
|
+
...PLUGIN_KINDS,
|
|
176
|
+
...Object.entries(OFFICIAL_KIND_OF).map(([kind, key]) => [kind, { key, official: true }]),
|
|
177
|
+
]);
|
|
178
|
+
function newestDomainTexts(agent, kinds) {
|
|
170
179
|
const out = new Map();
|
|
171
180
|
const session = agent?.session;
|
|
172
181
|
if (!session || typeof session.seq !== 'number' || typeof session.eventAt !== 'function')
|
|
173
182
|
return out;
|
|
174
|
-
const keyOfKind = new Map(INJECT_DOMAIN_KEYS.map((key) => [INJECT_KIND_OF[key], key]));
|
|
175
183
|
const scan = (seq) => {
|
|
176
184
|
let event;
|
|
177
185
|
try {
|
|
@@ -183,13 +191,13 @@ function newestDomainTexts(agent) {
|
|
|
183
191
|
if (!event || event.type !== 'user/message')
|
|
184
192
|
return false;
|
|
185
193
|
const source = event.data && event.data.source;
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
194
|
+
const mapping = source && typeof source.kind === 'string' ? kinds.get(source.kind) : undefined;
|
|
195
|
+
if (mapping === undefined || out.has(mapping.key))
|
|
188
196
|
return false;
|
|
189
197
|
const text = textOf(event.data);
|
|
190
198
|
if (text === undefined)
|
|
191
199
|
return false;
|
|
192
|
-
out.set(key, text);
|
|
200
|
+
out.set(mapping.key, { text, form: source && typeof source.form === 'string' ? source.form : '', official: mapping.official });
|
|
193
201
|
return out.size === INJECT_DOMAIN_KEYS.length;
|
|
194
202
|
};
|
|
195
203
|
const nodes = session.surface && Array.isArray(session.surface.nodes) ? session.surface.nodes : undefined;
|
|
@@ -211,6 +219,14 @@ function newestDomainTexts(agent) {
|
|
|
211
219
|
break;
|
|
212
220
|
return out;
|
|
213
221
|
}
|
|
222
|
+
const bytesOf = (text) => {
|
|
223
|
+
try {
|
|
224
|
+
return Buffer.byteLength(text, 'utf8');
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
return text.length;
|
|
228
|
+
}
|
|
229
|
+
};
|
|
214
230
|
/**
|
|
215
231
|
* 注册 `agent/pre-step` 注入监听;返回清理函数。
|
|
216
232
|
*
|
|
@@ -224,9 +240,49 @@ export function createContextInjector(deps) {
|
|
|
224
240
|
}
|
|
225
241
|
catch { /* ignore */ }
|
|
226
242
|
};
|
|
243
|
+
// 最近活跃的会话(WeakRef:诊断用,不阻止会话被回收)。每次 pre-step 都刷新 ——
|
|
244
|
+
// 包括"空 turn 提前返回"和"这一步没有内容可发"的分支,页面才能如实说"没投过"。
|
|
245
|
+
let lastAgent = null;
|
|
246
|
+
const delivered = { count: 0, lastAt: null, byDomain: {} };
|
|
247
|
+
// 最近一步里"每个域为什么发/不发"(官方载体 / 开关关 / 空 / 本插件负责)。
|
|
248
|
+
// 只在没有可见注入时用来解释状态 —— 标准类预设下技能与提示词由官方在送,
|
|
249
|
+
// 插件的实况若只说"未投递",读起来像出了问题。
|
|
250
|
+
let lastReasons = {};
|
|
251
|
+
const live = () => {
|
|
252
|
+
let agent;
|
|
253
|
+
try {
|
|
254
|
+
agent = lastAgent ? lastAgent.deref() : undefined;
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
agent = undefined;
|
|
258
|
+
}
|
|
259
|
+
const visible = agent === undefined
|
|
260
|
+
? new Map()
|
|
261
|
+
: newestDomainTexts(agent, LIVE_KINDS);
|
|
262
|
+
const labelOf = new Map(deps.domains().map((domain) => [domain.key, domain.label]));
|
|
263
|
+
const rows = INJECT_DOMAIN_KEYS.map((key) => {
|
|
264
|
+
const entry = visible.get(key);
|
|
265
|
+
const reason = lastReasons[key];
|
|
266
|
+
const state = agent === undefined
|
|
267
|
+
? 'unknown'
|
|
268
|
+
: entry !== undefined
|
|
269
|
+
// 官方载体发的那条也算"模型看到的内容"(只是不是本插件送的);
|
|
270
|
+
// 本插件发过又清空的,报「已清空」。
|
|
271
|
+
? (entry.official ? 'official' : entry.form === 'notice' ? 'cleared' : 'in-context')
|
|
272
|
+
// 没在上下文里:用最近一步的原因解释;`sent`(该发)却没看到 = 压缩后还没补发。
|
|
273
|
+
: reason === 'off' ? 'off' : reason === 'official' ? 'official' : reason === 'empty' ? 'empty' : 'absent';
|
|
274
|
+
const text = (state === 'in-context' || state === 'official') && entry !== undefined ? entry.text : '';
|
|
275
|
+
return { key, label: labelOf.get(key) ?? key, kind: INJECT_KIND_OF[key], state, bytes: bytesOf(text), text };
|
|
276
|
+
});
|
|
277
|
+
return {
|
|
278
|
+
hasAgent: agent !== undefined,
|
|
279
|
+
delivered: { count: delivered.count, lastAt: delivered.lastAt, byDomain: { ...delivered.byDomain } },
|
|
280
|
+
domains: rows,
|
|
281
|
+
};
|
|
282
|
+
};
|
|
227
283
|
const ctx = deps.ctx;
|
|
228
284
|
if (!ctx || typeof ctx.on !== 'function')
|
|
229
|
-
return { dispose: () => { } };
|
|
285
|
+
return { dispose: () => { }, live };
|
|
230
286
|
const stop = ctx.on('agent/pre-step', async (payload, next) => {
|
|
231
287
|
const decision = await next();
|
|
232
288
|
try {
|
|
@@ -235,22 +291,57 @@ export function createContextInjector(deps) {
|
|
|
235
291
|
const agent = payload && payload.agent;
|
|
236
292
|
if (!agent)
|
|
237
293
|
return decision;
|
|
294
|
+
if (typeof agent === 'object') {
|
|
295
|
+
try {
|
|
296
|
+
lastAgent = new WeakRef(agent);
|
|
297
|
+
}
|
|
298
|
+
catch { /* 环境没有 WeakRef → 实况显示"没有会话" */ }
|
|
299
|
+
}
|
|
238
300
|
// 空 turn 不注入(官方 dsh-agent-instructions 同款守卫):step 1 且一条消息都没有时,
|
|
239
301
|
// 这一步本来就该原地结束(宿主随后把 turn 判为 completed)。此时注入会把空 turn
|
|
240
302
|
// 变成一次真实的模型请求 —— 凭空烧一次调用。
|
|
241
303
|
if (Number(payload.step) === 1 && messagesOf(decision).length === 0)
|
|
242
304
|
return decision;
|
|
243
305
|
const domains = deps.domains();
|
|
244
|
-
const
|
|
245
|
-
const
|
|
306
|
+
const settings = deps.settings();
|
|
307
|
+
const facts = await deps.factsFor(agent);
|
|
308
|
+
const sections = selectInjections(domains, settings, facts);
|
|
309
|
+
// 记录每个域这一步"为什么发 / 为什么不发",供「注入实况」解释状态:
|
|
310
|
+
// off = 开关关了;official = 官方载体在送(技能 / 提示词,标准类预设下的常态);
|
|
311
|
+
// empty = 本插件负责但没内容;sent = 本插件负责且有内容(上下文里看不到时说明还没补发)。
|
|
312
|
+
const reasons = {};
|
|
313
|
+
for (const domain of domains) {
|
|
314
|
+
if (settings.domains[domain.key] !== true) {
|
|
315
|
+
reasons[domain.key] = 'off';
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const carrier = CARRIER_FACT_OF[domain.key];
|
|
319
|
+
if (carrier !== undefined && facts?.[carrier] !== false) {
|
|
320
|
+
reasons[domain.key] = 'official';
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
let text = '';
|
|
324
|
+
try {
|
|
325
|
+
text = String(domain.text() ?? '');
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
text = '';
|
|
329
|
+
}
|
|
330
|
+
reasons[domain.key] = text.trim() === '' ? 'empty' : 'sent';
|
|
331
|
+
}
|
|
332
|
+
lastReasons = reasons;
|
|
333
|
+
const visible = newestDomainTexts(agent, PLUGIN_KINDS);
|
|
246
334
|
const additions = [];
|
|
335
|
+
const appended = [];
|
|
247
336
|
const published = new Set();
|
|
248
337
|
for (const section of sections) {
|
|
249
338
|
published.add(section.key);
|
|
250
339
|
const text = renderDomainText(section);
|
|
251
|
-
|
|
340
|
+
const current = visible.get(section.key);
|
|
341
|
+
if (current !== undefined && current.text === text)
|
|
252
342
|
continue;
|
|
253
343
|
additions.push(domainMessage(section, text));
|
|
344
|
+
appended.push(section.key);
|
|
254
345
|
}
|
|
255
346
|
// 曾经注入过、这一轮没有内容的域 → 一条「已清空」;从没注入过的域什么都不用说。
|
|
256
347
|
const labelOf = new Map(domains.map((domain) => [domain.key, domain.label]));
|
|
@@ -261,12 +352,17 @@ export function createContextInjector(deps) {
|
|
|
261
352
|
if (previous === undefined)
|
|
262
353
|
continue;
|
|
263
354
|
const label = labelOf.get(key) ?? key;
|
|
264
|
-
if (previous ===
|
|
355
|
+
if (previous.form === 'notice')
|
|
265
356
|
continue;
|
|
266
357
|
additions.push(clearedMessage(key, label));
|
|
358
|
+
appended.push(key);
|
|
267
359
|
}
|
|
268
360
|
if (additions.length === 0)
|
|
269
361
|
return decision;
|
|
362
|
+
delivered.count += additions.length;
|
|
363
|
+
delivered.lastAt = Date.now();
|
|
364
|
+
for (const key of appended)
|
|
365
|
+
delivered.byDomain[key] = (delivered.byDomain[key] || 0) + 1;
|
|
270
366
|
return { ...decision, messages: [...messagesOf(decision), ...additions] };
|
|
271
367
|
}
|
|
272
368
|
catch (error) {
|
|
@@ -281,6 +377,7 @@ export function createContextInjector(deps) {
|
|
|
281
377
|
stop();
|
|
282
378
|
}
|
|
283
379
|
catch { /* ignore */ } },
|
|
380
|
+
live,
|
|
284
381
|
};
|
|
285
382
|
}
|
|
286
383
|
function messagesOf(decision) {
|