dsh-agentic-tool-catalog 0.3.1 → 0.3.2

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 CHANGED
@@ -1,5 +1,35 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.2 — 2026-11-12
4
+
5
+ Fix: background catalog generation failed 100% on DSH **0.2.2-rc.2** with
6
+ `AGENT_FAILED: Cannot read properties of undefined (reading 'id')`, plus hardening so the next
7
+ host API move degrades gracefully instead of failing silently.
8
+
9
+ - **Cause**: rc.2 flattened the `ctx.subagents.startActivation()` spec
10
+ (`SubagentActivationSpec`). `parent`, `prompt`, `maxDepth`, and `outputSchema` moved from
11
+ `spec.request.*` to the top level; `startActivation` now reads `spec.parent.id` directly.
12
+ 0.3.1 still passed the 0.2.2-rc.1 nested shape, so `spec.parent` was `undefined` and every
13
+ generation threw before a child session was created. This was a host breaking change, not a
14
+ Computer Use plugin issue: an isolated DSH 0.2.2-rc.2 instance with only this plugin reproduced it.
15
+ - **Compatibility adapter**: all version-shape knowledge now lives in `lib/subagent-compat.js`.
16
+ It sends the union of both shapes: request fields at the top level (0.2.2-rc.2+) and a
17
+ **non-enumerable** `request` property (0.2.1-rc.2 … 0.2.2-rc.1). Non-enumerability keeps rc.2's
18
+ external-provider rest-spread from forwarding the legacy nested object, while old versions still
19
+ read `spec.request`. Both host implementations ignore unknown keys, so one call supports both.
20
+ - **Soft gate**: three consecutive shape rejections report `UNSUPPORTED_HOST`, pause automatic
21
+ generation for one hour, and keep the last-known-good catalog with an explicit
22
+ `catalog-generation-paused` warning instead of a stream of `AGENT_FAILED` retries. A plugin
23
+ reload clears the pause and the per-fingerprint backoff, so a fix takes effect immediately.
24
+ - **Compatibility declaration**: `engines.dsh` is now the tested line `>=0.2.1-rc.2 <0.3.0-0`
25
+ instead of an open-ended range. `generation.activationShapes` reports `["flat","nested"]`.
26
+ - **Tests**: `lib/subagent-compat.js` has a dedicated `scripts/verify-subagent-compat.mjs` that
27
+ drives the **real** `@deepseek-ai/dsh-subagent` `0.2.2-rc.1` and `0.2.2-rc.2`
28
+ `SubagentRuntime.startActivation` implementations: both accept the adapter spec, each reads its
29
+ own shape, a flat-only spec fails on rc.1, a nested-only spec fails on rc.2, and both versions
30
+ enforce the `outputSchema` capability. It runs as part of `npm test` (first run installs the two
31
+ pinned host packages into `reference/tmp/subagent-compat/`, gitignored).
32
+
3
33
  ## 0.3.1 — 2026-11-11
4
34
 
5
35
  Documentation-only release: the plugin page was rewritten around the user's questions (what it
package/README.md CHANGED
@@ -98,7 +98,8 @@ dsh plugin --profile web add /path/to/dsh-agentic-tool-catalog
98
98
  dsh plugin add /path/to/dsh-agentic-tool-catalog
99
99
  ```
100
100
 
101
- Published package: `dsh-agentic-tool-catalog` (npm). Requires DSH `0.2.2-rc.1` or later.
101
+ Published package: `dsh-agentic-tool-catalog` (npm). Requires DSH `>=0.2.1-rc.2 <0.3.0`;
102
+ tested with `0.2.2-rc.1` and `0.2.2-rc.2` (both subagent activation shapes are supported).
102
103
 
103
104
  ```bash
104
105
  dsh plugin --profile web add dsh-agentic-tool-catalog
@@ -131,6 +132,11 @@ official ContextMeter: a segmented bar plus three rows (always available / loade
131
132
  freshness word, and one meta line (groups · storage · updated). There is no directory dump, no configuration, and
132
133
  no long explanatory copy. It uses official slots/theme tokens and follows the light/dark theme.
133
134
 
135
+ ## Development & release
136
+
137
+ See [`docs/development.md`](docs/development.md) for the layered validation flow (link dev profile →
138
+ npm artifact → staging composition → `next` dogfood → `latest`).
139
+
134
140
  ## Verification
135
141
 
136
142
  ```bash
package/README.zh.md CHANGED
@@ -85,7 +85,8 @@ dsh plugin --profile web add /path/to/dsh-agentic-tool-catalog
85
85
  dsh plugin add /path/to/dsh-agentic-tool-catalog
86
86
  ```
87
87
 
88
- 发布包:`dsh-agentic-tool-catalog`(npm);要求 DSH `0.2.2-rc.1` 及以上。
88
+ 发布包:`dsh-agentic-tool-catalog`(npm);要求 DSH `>=0.2.1-rc.2 <0.3.0`,
89
+ 已在 `0.2.2-rc.1` 与 `0.2.2-rc.2` 上验证(两种子代理激活 spec 形状都支持)。
89
90
 
90
91
  ## 性能(A/B:用 vs 不用)
91
92
 
package/docs/benchmark.md CHANGED
@@ -50,5 +50,5 @@ Notes:
50
50
  `workspace-calendar-count` is excluded from the main table because it depends on MCP availability.
51
51
  - First-request token figures come from real session usage (`inputTokens + cacheReadTokens`); schema chars are a
52
52
  payload-size metric, **not** a chars/4 token estimate.
53
- - Full methodology and raw results: `benchmark/discovery/data-card.md` and `benchmark/discovery/results/` in the
53
+ - Full methodology and raw results: `benchmark/data-card.md` and `benchmark/results/` in the
54
54
  source repository.
package/docs/data-card.md CHANGED
@@ -86,7 +86,7 @@
86
86
  本卡使用的是工具面一致的 10–4x 批次;
87
87
  - 首请求 tokens 来自会话日志真实 usage(非 chars/4 估算);schema 字符是载荷指标。成本/墙钟受模型与网络影响,应看多轮重复;
88
88
  - 复现:`node benchmark/discovery/card.mjs --baseline baseline-resident --treatment agent-catalog --tasks host-ops-session-list,docs-workers-limit,terminal-echo,goal-set,docs-d1-storage --markdown`
89
- 原始结果在 [`results/`](results/),方法说明 [`benchmark/discovery/README.md`](README.md)。
89
+ 原始结果在 [`results/`](../benchmark/results/),方法说明 [`benchmark/README.md`](../benchmark/README.md)。
90
90
 
91
91
  ## 7. 和竞品怎么比
92
92
 
package/docs/detail.md CHANGED
@@ -24,7 +24,7 @@ plugin. The catalog saves context, not by making tasks cheaper to run.
24
24
 
25
25
  ## Good to know
26
26
 
27
- - Works with DSH `>=0.2.1-rc.2` or `>=0.2.2-rc.1`.
27
+ - Works with DSH `>=0.2.1-rc.2 <0.3.0`; tested on `0.2.2-rc.1` and `0.2.2-rc.2`.
28
28
  - v1 covers **tools**; native skills keep their normal behavior.
29
29
  - The catalog is derived data and contains no messages, prompts, tool results or credentials.
30
30
  - It does **not** regenerate on a timer: only when the set of available tools changes. One-time
package/docs/detail.zh.md CHANGED
@@ -22,7 +22,7 @@ DSH 默认会把每个可见工具的完整 schema 放进每一次请求。这
22
22
 
23
23
  ## 你需要知道
24
24
 
25
- - 适用于 DSH `>=0.2.1-rc.2` 或 `>=0.2.2-rc.1`。
25
+ - 适用于 DSH `>=0.2.1-rc.2 <0.3.0`;已在 `0.2.2-rc.1` 与 `0.2.2-rc.2` 上验证。
26
26
  - v1 覆盖 **tools**;DSH 原生 skills 行为不变。
27
27
  - 目录是派生数据,不含消息、提示词、工具结果或凭据。
28
28
  - **不会定时重新生成**:只有可用工具集发生变化时才重建。生成结果会被缓存,工具集稳定的情况下
package/lib/generation.js CHANGED
@@ -14,6 +14,12 @@
14
14
  * @module @local/dsh-agentic-tool-catalog/generation
15
15
  */
16
16
  import { normalizeGroupId, summarizeGroups } from './catalog.js'
17
+ import { ACTIVATION_SHAPES, isActivationShapeError, startSubagentActivation } from './subagent-compat.js'
18
+
19
+ /** 连续多少次"宿主不认 activation spec"后暂停自动生成(避免 9/9 静默刷失败)。 */
20
+ export const SHAPE_FAILURE_PAUSE_THRESHOLD = 3
21
+ /** 形状不兼容暂停时长:1 小时;期间继续用 last-known-good 目录。 */
22
+ export const SHAPE_FAILURE_PAUSE_MS = 60 * 60 * 1000
17
23
 
18
24
  export const AGENT_GROUP_DEFAULTS = {
19
25
  provider: 'spawn',
@@ -228,6 +234,8 @@ export function createAgentGrouper(ctx, config, logger, sharedStats = undefined)
228
234
  delete stats.fallbacks
229
235
  const provider = config?.catalogProvider ?? AGENT_GROUP_DEFAULTS.provider
230
236
  const timeoutMs = config?.catalogTimeoutMs ?? AGENT_GROUP_DEFAULTS.timeoutMs
237
+ /** 连续"形状不兼容"失败计数;跨 generator 实例不保留(插件重载即重试)。 */
238
+ let shapeFailures = 0
231
239
 
232
240
  const service = () => {
233
241
  const subagents = ctx.get?.('subagents')
@@ -238,6 +246,12 @@ export function createAgentGrouper(ctx, config, logger, sharedStats = undefined)
238
246
 
239
247
  /** 生成一次分组;失败返回 error(不抛)。 */
240
248
  async function generate({ agent, tools, previousGroups }) {
249
+ // 形状不兼容暂停:到期自动解除;期间不计数、不重试,继续用 last-known-good。
250
+ if (stats.paused !== undefined) {
251
+ if (Date.now() < stats.paused.retryAt) return { error: { code: stats.paused.code, message: stats.paused.message } }
252
+ delete stats.paused
253
+ shapeFailures = 0
254
+ }
241
255
  const subagents = service()
242
256
  if (subagents === undefined) {
243
257
  // 观测缺口修复:provider 不可用也计入 failures / lastError(否则状态里看不到真实失败原因)。
@@ -256,19 +270,22 @@ export function createAgentGrouper(ctx, config, logger, sharedStats = undefined)
256
270
  const attemptTimer = setTimeout(() => attemptController.abort(), timeoutMs)
257
271
  let activation
258
272
  try {
259
- activation = await subagents.startActivation({
273
+ // 版本形状差异只在 subagent-compat.js 里处理:顶层字段服务 0.2.2-rc.2+,
274
+ // 不可枚举的 request 兼容 0.2.2-rc.1 及更早。
275
+ activation = await startSubagentActivation(subagents, {
260
276
  provider,
261
277
  label: 'agentic-tool-catalog generation',
262
- request: {
263
- parent: agent,
264
- prompt: [{ type: 'text', text: buildAgentGroupPrompt(tools, { feedback, previousGroups: useIncremental ? previousGroups : undefined }) }],
265
- maxDepth: 1,
266
- outputSchema: AGENT_GROUP_SCHEMA,
267
- },
278
+ parent: agent,
279
+ prompt: [{ type: 'text', text: buildAgentGroupPrompt(tools, { feedback, previousGroups: useIncremental ? previousGroups : undefined }) }],
280
+ maxDepth: 1,
281
+ outputSchema: AGENT_GROUP_SCHEMA,
268
282
  signal: attemptController.signal,
269
283
  delivery: 'caller',
270
284
  })
271
285
  const result = await activation.result
286
+ // 宿主接受了 spec 并返回结果:形状回退计数清零(即使下面校验失败,也与形状无关)。
287
+ shapeFailures = 0
288
+ delete stats.paused
272
289
  const structured = result?.structured
273
290
  const validated = validateAgentGroups(tools, structured)
274
291
  if (validated.error === undefined) {
@@ -287,8 +304,27 @@ export function createAgentGrouper(ctx, config, logger, sharedStats = undefined)
287
304
  return { error: validated.error }
288
305
  } catch (error) {
289
306
  const message = String(error?.message ?? error).slice(0, 160)
290
- stats.lastError = { code: attemptController.signal.aborted ? 'TIMEOUT' : 'AGENT_FAILED', message, at: Date.now() }
307
+ const aborted = attemptController.signal.aborted
308
+ const shapeFailure = !aborted && isActivationShapeError(error)
309
+ if (shapeFailure) shapeFailures += 1
310
+ else if (!aborted) shapeFailures = 0
311
+ stats.lastError = {
312
+ code: aborted ? 'TIMEOUT' : shapeFailure ? 'UNSUPPORTED_HOST' : 'AGENT_FAILED',
313
+ message,
314
+ at: Date.now(),
315
+ }
291
316
  logger?.warn?.(`dsh-agentic-tool-catalog generation: ${message}`)
317
+ if (shapeFailure && shapeFailures >= SHAPE_FAILURE_PAUSE_THRESHOLD && stats.paused === undefined) {
318
+ // 软闸门:不为"宿主形状不认识"反复刷失败;保留 last-known-good,显式标 paused。
319
+ stats.paused = {
320
+ code: 'UNSUPPORTED_HOST',
321
+ message: `host rejected the ${ACTIVATION_SHAPES.join('+')} subagent activation spec (${message}); `
322
+ + 'automatic catalog generation paused for 1h; the last successful grouping keeps serving',
323
+ at: Date.now(),
324
+ retryAt: Date.now() + SHAPE_FAILURE_PAUSE_MS,
325
+ }
326
+ logger?.warn?.(`dsh-agentic-tool-catalog: ${stats.paused.message}`)
327
+ }
292
328
  stats.failures += 1
293
329
  return { error: stats.lastError }
294
330
  } finally {
@@ -302,5 +338,5 @@ export function createAgentGrouper(ctx, config, logger, sharedStats = undefined)
302
338
  return { error: stats.lastError ?? { code: 'UNKNOWN', message: 'generation failed' } }
303
339
  }
304
340
 
305
- return { generate, stats, provider, enabled: () => service() !== undefined }
341
+ return { generate, stats, provider, activationShapes: ACTIVATION_SHAPES, enabled: () => service() !== undefined }
306
342
  }
package/lib/index.js CHANGED
@@ -117,6 +117,9 @@ export function apply(ctx, config) {
117
117
  const instance = Symbol('instance')
118
118
  const shared = sharedState()
119
119
  const instanceId = ++shared.instanceSeq
120
+ // 新实例 = 新代码:不继承上一版留下的退避/暂停,否则热修后还要等 10 分钟才重试。
121
+ shared.failures.clear()
122
+ delete shared.agentStats.paused
120
123
  const registry = restrictionRegistry()
121
124
  const logger = ctx.logger
122
125
  const bootAt = Date.now() // 区分“进程启动后新建会话”(预热)与“恢复会话”(按需)
@@ -339,6 +342,16 @@ export function apply(ctx, config) {
339
342
  state.needsEnrichment = true
340
343
  return
341
344
  }
345
+ // 软闸门:宿主连续拒绝 activation spec 后暂停自动生成,继续用 last-known-good;
346
+ // 到期由 generate() 自动解除,插件重装也会清掉(见 apply 顶部)。
347
+ const paused = shared.agentStats.paused
348
+ if (paused !== undefined && Date.now() < paused.retryAt) {
349
+ state.needsEnrichment = true
350
+ if (!state.warnings.some((entry) => entry.kind === 'catalog-generation-paused')) {
351
+ state.warnings = [...state.warnings, { kind: 'catalog-generation-paused', message: paused.message }]
352
+ }
353
+ return
354
+ }
342
355
  const failedAt = shared.failures.get(cacheKey)
343
356
  if (failedAt !== undefined && Date.now() - failedAt < 600000) return // 10 分钟退避
344
357
 
@@ -516,6 +529,7 @@ export function apply(ctx, config) {
516
529
  generation: {
517
530
  provider: agentGrouper.provider,
518
531
  enabled: agentGrouper.enabled(),
532
+ activationShapes: agentGrouper.activationShapes,
519
533
  state: active ? 'running' : 'idle',
520
534
  ...shared.agentStats,
521
535
  instanceId,
@@ -0,0 +1,87 @@
1
+ /**
2
+ * subagent-compat.js —— 唯一知道宿主 `ctx.subagents.startActivation()` 入参形状的地方。
3
+ *
4
+ * 背景(2026-11-12 实测):
5
+ * - DSH 0.2.1-rc.2 … 0.2.2-rc.1:请求字段装在 `spec.request` 里
6
+ * (`{ provider, label, request: { parent, prompt, maxDepth, outputSchema, ... }, signal, delivery }`);
7
+ * - DSH 0.2.2-rc.2+:同一组字段被拍平到 spec 顶层,`startActivation` 直接读 `spec.parent.id`。
8
+ *
9
+ * 兼容策略:同时发送两种形状。
10
+ * - 顶层字段服务 0.2.2-rc.2+;
11
+ * - 不可枚举的 `request` 属性服务旧版(旧版只做属性读取 `spec.request`,仍可访问);
12
+ * - 不可枚举很重要:rc.2 的外部 provider 路径会把 spec 的剩余字段展开进 provider 请求,
13
+ * 可枚举的 `request` 会泄漏过去;不可枚举属性不会被展开。
14
+ * - 两个版本的实现都只读自己认识的字段、不校验未知字段,因此并集是安全的。
15
+ *
16
+ * 这里是**唯一**允许出现版本形状差异的文件;其余代码只调用 `startSubagentActivation()`。
17
+ * 契约由 `scripts/verify-subagent-compat.mjs` 用真实的
18
+ * `@deepseek-ai/dsh-subagent@0.2.2-rc.1` / `@0.2.2-rc.2` 实现验证。
19
+ *
20
+ * @module @local/dsh-agentic-tool-catalog/subagent-compat
21
+ */
22
+
23
+ /** 适配器同时支持的激活 spec 形状(用于状态观测;不代表宿主版本)。 */
24
+ export const ACTIVATION_SHAPES = Object.freeze(['flat', 'nested'])
25
+
26
+ /** 属于 `SubagentStartRequest`(随版本在顶层 / request 之间搬家的字段)。 */
27
+ export const ACTIVATION_REQUEST_FIELDS = Object.freeze([
28
+ 'cwd',
29
+ 'agentPreset',
30
+ 'prompt',
31
+ 'parent',
32
+ 'agentOptions',
33
+ 'outputSchema',
34
+ 'maxDepth',
35
+ ])
36
+
37
+ /**
38
+ * 把扁平请求构造成两种宿主都能读的 activation spec。
39
+ * @param spec - 扁平请求(含 provider / label / signal / delivery 与 request 字段)。
40
+ * @returns 可直接传给 `ctx.subagents.startActivation()` 的 spec。
41
+ */
42
+ export function buildSubagentActivationSpec(spec) {
43
+ const request = {}
44
+ for (const key of ACTIVATION_REQUEST_FIELDS) {
45
+ if (spec[key] !== undefined) request[key] = spec[key]
46
+ }
47
+ const activationSpec = {
48
+ provider: spec.provider,
49
+ label: spec.label,
50
+ ...(spec.name === undefined ? {} : { name: spec.name }),
51
+ ...(spec.childId === undefined ? {} : { childId: spec.childId }),
52
+ // 0.2.2-rc.2+:字段在顶层。
53
+ ...request,
54
+ signal: spec.signal,
55
+ delivery: spec.delivery,
56
+ }
57
+ // 0.2.1-rc.2 … 0.2.2-rc.1:字段在 request 里。不可枚举,避免 rc.2 的外部 provider 展开时泄漏。
58
+ Object.defineProperty(activationSpec, 'request', {
59
+ value: request,
60
+ enumerable: false,
61
+ configurable: true,
62
+ writable: false,
63
+ })
64
+ return activationSpec
65
+ }
66
+
67
+ /**
68
+ * 启动一个子代理激活,自动适配宿主的 spec 形状。
69
+ * @param subagents - `ctx.get('subagents')` 得到的服务。
70
+ * @param spec - 扁平请求(见 {@link ACTIVATION_REQUEST_FIELDS})。
71
+ * @returns `startActivation()` 的原样结果。
72
+ */
73
+ export function startSubagentActivation(subagents, spec) {
74
+ return subagents.startActivation(buildSubagentActivationSpec(spec))
75
+ }
76
+
77
+ /**
78
+ * 判断一个失败是否像"宿主不认识 activation spec 形状"(而非业务/模型失败)。
79
+ * 仅用于观测与软闸门:形状错误发生在创建子代理之前,无副作用。
80
+ * @param error - 捕获到的错误。
81
+ * @returns 是否匹配各版本读取缺失字段时的 TypeError。
82
+ */
83
+ export function isActivationShapeError(error) {
84
+ if (!(error instanceof TypeError)) return false
85
+ const message = String(error.message ?? '')
86
+ return /Cannot read properties of undefined \(reading '(?:id|parent|prompt|request|maxDepth|outputSchema|cwd|agentPreset|agentOptions)'\)/.test(message)
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-agentic-tool-catalog",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "Agentic Tool Catalog for DSH: an agent builds and maintains a tool catalog; schemas stay out of context and tools load on demand.",
5
5
  "icon": "./icon.svg",
6
6
  "type": "module",
@@ -80,11 +80,12 @@
80
80
  },
81
81
  "engines": {
82
82
  "node": ">=22.19.0",
83
- "dsh": ">=0.2.1-rc.2 || >=0.2.2-rc.1"
83
+ "dsh": ">=0.2.1-rc.2 <0.3.0-0"
84
84
  },
85
85
  "scripts": {
86
- "test": "node scripts/verify-agentic-tool-catalog.mjs && node scripts/verify-agentic-tool-catalog-schema.mjs",
86
+ "test": "node scripts/verify-agentic-tool-catalog.mjs && node scripts/verify-agentic-tool-catalog-schema.mjs && node scripts/verify-subagent-compat.mjs",
87
87
  "test:schema": "node scripts/verify-agentic-tool-catalog-schema.mjs",
88
+ "test:compat": "node scripts/verify-subagent-compat.mjs",
88
89
  "pack:check": "npm pack --dry-run",
89
90
  "test:presentation": "node scripts/verify-presentation.mjs"
90
91
  }