dsh-plugin-tool-management 0.18.0 → 0.19.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.
@@ -16,7 +16,7 @@ import { catalogDepthOf, parsePersona, } from './persona-parse.js';
16
16
  import { serializePersona, validPersonaName } from './persona-write.js';
17
17
  // 拆出四层后 subagents/service.js 的公开面照旧(index.ts、ops/sessions.ts、scenes/candidates.ts、
18
18
  // subagents/tools.ts 与契约测试都按这个路径引)。
19
- export { catalogInjectedAt, catalogDepthOf, DEFAULT_PERSONA_CATALOG_DEPTH, emptyResultNote, neutralizePromptVariables, normalizeReasoningEffort, parsePersona, parsePresetToolRules, renderPersonaPrompt, REASONING_EFFORT_MAX_LENGTH, textOfBlocks, UNLIMITED_PERSONA_CATALOG_DEPTH, } from './persona-parse.js';
19
+ export { catalogInjectedAt, catalogDepthOf, CONTINUABLE_MARK, DEFAULT_PERSONA_CATALOG_DEPTH, emptyResultNote, neutralizePromptVariables, normalizeReasoningEffort, parsePersona, parsePresetToolRules, renderPersonaPrompt, REASONING_EFFORT_MAX_LENGTH, textOfBlocks, UNLIMITED_PERSONA_CATALOG_DEPTH, } from './persona-parse.js';
20
20
  export { presetRulesOf, serializePersona, validPersonaName } from './persona-write.js';
21
21
  export { decideToolFilter } from './tool-filter.js';
22
22
  const message = (e) => String((e && e.message) || e);
@@ -254,7 +254,7 @@ export function createSubagentService(ctx, opts) {
254
254
  ok: true,
255
255
  // catalogDepth 报**生效值**(没写就是默认 1),界面与模型都不必各自知道默认是多少。
256
256
  // 手写 map:新增字段必须**两处都加**(list 与 get),少一处就是界面读不到。
257
- subagents: docs.map((p) => ({ name: p.name, enabled: p.enabled !== false, description: p.description, provider: p.provider ?? null, model: p.model ?? null, reasoningEffort: p.reasoningEffort ?? null, tools: p.tools ?? null, toolsDeny: p.toolsDeny ?? null, toolsByPreset: p.toolsByPreset ?? null, catalogDepth: catalogDepthOf(p), output: p.output ?? null })),
257
+ subagents: docs.map((p) => ({ name: p.name, enabled: p.enabled !== false, description: p.description, provider: p.provider ?? null, model: p.model ?? null, reasoningEffort: p.reasoningEffort ?? null, continuable: p.continuable === true, tools: p.tools ?? null, toolsDeny: p.toolsDeny ?? null, toolsByPreset: p.toolsByPreset ?? null, catalogDepth: catalogDepthOf(p), output: p.output ?? null })),
258
258
  };
259
259
  },
260
260
  'subagent-get': async (args) => {
@@ -263,7 +263,7 @@ export function createSubagentService(ctx, opts) {
263
263
  const p = docs.find((d) => d.name === name);
264
264
  if (!p)
265
265
  return { ok: false, error: `人设不存在: ${name}` };
266
- return { ok: true, persona: { name: p.name, description: p.description, provider: p.provider ?? '', model: p.model ?? '', reasoningEffort: p.reasoningEffort ?? '', tools: p.tools ?? [], toolsDeny: p.toolsDeny ?? [], toolsByPreset: p.toolsByPreset ?? {}, catalogDepth: catalogDepthOf(p), output: p.output ?? '', body: p.body } };
266
+ return { ok: true, persona: { name: p.name, description: p.description, provider: p.provider ?? '', model: p.model ?? '', reasoningEffort: p.reasoningEffort ?? '', continuable: p.continuable === true, tools: p.tools ?? [], toolsDeny: p.toolsDeny ?? [], toolsByPreset: p.toolsByPreset ?? {}, catalogDepth: catalogDepthOf(p), output: p.output ?? '', body: p.body } };
267
267
  },
268
268
  'subagent-create': async (args) => {
269
269
  const name = String((args && args.name) || '').trim();
@@ -1,3 +1,8 @@
1
+ // src/subagents/tools.ts —— 模型工具:subagent_manager_list / subagent_manager_run(人设清单 + 官方 ctx.subagents 委派)。
2
+ // 本文件返回的是 **author 侧 tool 定义**(parameters 是 property-spec 形态),装配处必须经
3
+ // defineTool() 编译后再 register(C4:裸 register 会把未编译的参数声明直接发给模型 API)。
4
+ // 参数与 output schema 用 as const 保留字面量类型,defineTool 才能推断出参数表。
5
+ import { CONTINUABLE_MARK } from './service.js';
1
6
  // 深度探针(`subagentDepthOf`)与目录判据(`catalogInjectedAt`)都不再需要:
2
7
  // 2026-09-17 方案 A 之后,这两个工具都不再看会话深度 —— 委派由官方决定(默认能嵌套到 3 层),
3
8
  // `catalogDepth` 只管常驻目录注入到哪些会话。
@@ -34,7 +39,7 @@ export function defineSubagentManagerListTool(subagents) {
34
39
  // 注入到哪些会话,与"能不能委派"无关 —— 委派由官方决定(`dsh-tool-subagent` 默认能嵌套
35
40
  // 到 3 层)。此前按深度过滤等于把可用人设藏起来:目录不注入时模型只能靠这个工具查,
36
41
  // 而工具又不列全,结果是"明明能委派却查不到人设"。
37
- const lines = allowed.map((p) => '- ' + p.name + ' — ' + (p.description || '(无描述)'));
42
+ const lines = allowed.map((p) => '- ' + p.name + ' — ' + (p.description || '(无描述)') + (p.continuable === true ? CONTINUABLE_MARK : ''));
38
43
  let notice = '';
39
44
  try {
40
45
  notice = typeof subagents.noticeFor === 'function' ? await subagents.noticeFor(exec) : '';
@@ -73,7 +78,7 @@ export function defineSubagentManagerRunTool(subagents) {
73
78
  // 最贵的单个参数。收完两处重复后目标 ≈325 tok。判据是"删掉的那句在描述里已经有了吗":
74
79
  // Modes 段留着(它是模型的入口),参数说明只留"这一段独有的信息"。
75
80
  // `inherit` 那句"轮中委派拿不到当前轮"必须留 —— 它是这条参数唯一会让人写错 task 的地方。
76
- description: 'Run a named persona as a subagent: it gets the persona as its own system prompt, works on `task`, and returns only its final output.\n\nModes: by default a fresh child that cannot see this conversation, so `task` must be self-contained. With `inherit: true` it also gets this conversation\'s **finished** turns (like the host\'s `subagent_fork`) — the current turn is never included, so a mid-turn hand-off still needs a self-contained `task`.\n\n`task` = the goal plus the context it needs; leave method and output format to the persona.\n\nUse it when the work matches a persona in the「可委派的子智能体」reminder (a review, an investigation, a piece of writing) and the detail should not sit in your own context. Not for reading a file (Read), finding a definition (Grep/Glob), or touching two or three files.',
81
+ description: 'Run a named persona as a subagent: it gets the persona as its own system prompt, works on `task`, and returns only its final output. A persona marked「允许追问」instead returns an agent id and runs in the background — continue it with `send_message`.\n\nModes: by default a fresh child that cannot see this conversation, so `task` must be self-contained. With `inherit: true` it also gets this conversation\'s **finished** turns (like the host\'s `subagent_fork`) — the current turn is never included, so a mid-turn hand-off still needs a self-contained `task`.\n\n`task` = the goal plus the context it needs; leave method and output format to the persona.\n\nUse it when the work matches a persona in the「可委派的子智能体」reminder (a review, an investigation, a piece of writing) and the detail should not sit in your own context. Not for reading a file (Read), finding a definition (Grep/Glob), or touching two or three files.',
77
82
  parameters: {
78
83
  agent: { type: 'string', required: true, description: 'Persona name.' },
79
84
  task: { type: 'string', required: true, description: 'The task for the subagent. Self-contained by default; with inherit: true only state what is new.' },
@@ -113,6 +118,16 @@ export function defineSubagentManagerRunTool(subagents) {
113
118
  const r = await subagents.runSerial(exec.agent, persona, task, exec.signal, decision === undefined ? undefined : decision.filter, inherit);
114
119
  const prefix = r.stopReason && r.stopReason !== 'completed' ? `[stopReason: ${r.stopReason}]\n` : '';
115
120
  const note = decision && decision.note ? `⚠ ${decision.note}\n\n` : '';
121
+ // 允许追问通道(人设 `continuable: true`):产出是**异步**到达的,这里只拿到子会话 id。
122
+ // 回执必须把"id + 怎么追问 + 怎么中止"三件事写全 —— 少写一句,模型就会把"没拿到结果"
123
+ // 读成"委派失败"而重试一次(那会再起一个后台子代理,白白翻倍)。
124
+ if (r.continuable === true) {
125
+ return note + '已启动允许追问的子代理「' + name + '」(后台运行)\n'
126
+ + 'agent_id: ' + r.runId + '\n\n'
127
+ + '它的产出稍后自己发到这里,届时你会收到一条通知。要追问就 '
128
+ + 'send_message({ agent_id: "' + r.runId + '", message: "..." }),'
129
+ + '要中止就 interrupt_agent({ agent_id: "' + r.runId + '" })。';
130
+ }
116
131
  return note + prefix + r.text;
117
132
  },
118
133
  };
@@ -33,7 +33,15 @@ export function buildSubagentTools(deps) {
33
33
  try {
34
34
  // 工具限制按**当前会话的 Agent 预设**下发:父会话跑在哪个预设,就用那个预设那一行的
35
35
  // 白/黑名单(`decideToolFilter`),名单里已消失的工具名会被丢掉并在结果里如实说明。
36
- register(defineTool(defineSubagentManagerRunTool({ ...deps.subagentService, sceneLists: deps.sceneLists, toolFilterFor: deps.toolFilterFor })));
36
+ // ⚠️ 必须 `Object.create`,**不能**用对象展开 —— 这是 0.15.0 起就存在的实际故障:
37
+ // `deps.subagentService` 是 index.ts 用 `Object.assign(Object.create(真实服务), { ops })`
38
+ // 造的**包装层**,只有 `ops` 落在自有属性上,`list` / `runSerial` 都挂在原型链上。对象展开
39
+ // 只复制**自有可枚举**属性,展开后只剩 `{ ops }` —— 于是 `subagent_manager_run` 每次一进
40
+ // execute 就在 `subagents.list()` 上抛 `subagents.list is not a function`(人设查不到就
41
+ // 谈不上委派;0.19.0 测「允许追问」时才被暴露出来,因为那条通道同样要过这一步)。
42
+ // `Object.create` 保持同一条原型链:`ops` 仍是审计过的那份,`list` / `runSerial` 回真实服务。
43
+ // 与 index.ts 造这个包装层时用的是同一个写法(那边注释写的是「原型链回真实服务,其余读法一字不动」)。
44
+ register(defineTool(defineSubagentManagerRunTool(Object.assign(Object.create(deps.subagentService), { sceneLists: deps.sceneLists, toolFilterFor: deps.toolFilterFor }))));
37
45
  }
38
46
  catch (e) {
39
47
  recordFailure('subagent_manager_run', e);
@@ -147,6 +155,10 @@ export function buildSubagentTools(deps) {
147
155
  // 就是静默清掉它。`reasoningEffort` 是 0.14.0 新增的,最容易在这里被漏掉 ——
148
156
  // 模型只改一句 body,用户配好的思考强度就没了,而且没有任何提示。
149
157
  reasoningEffort: keep.reasoningEffort ?? '',
158
+ // 0.19.0 同理:`continuable` 不在本工具的参数表里(与工具限制、provider/model 同一档 ——
159
+ // 它在面板上配),所以这里**只负责别把它冲掉**。漏了它的后果是:模型改一次描述,
160
+ // 用户设好的"允许追问"就静默变回一次性,而回执照样说成功。
161
+ continuable: keep.continuable === true,
150
162
  tools: keep.tools ?? [],
151
163
  toolsDeny: keep.toolsDeny ?? [],
152
164
  toolsByPreset: keep.toolsByPreset ?? {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-tool-management",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "DSH plugin: one settings panel for MCP servers, skills, scenes, memories, subagent personas and AGENTS.md presets — a scene switches the whole environment, and the model can drive it from chat too.",
5
5
  "repository": {
6
6
  "type": "git",