dsh-subagent-hub 0.10.1 → 0.10.3

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/README.md CHANGED
@@ -1,15 +1,15 @@
1
1
  <p align="center">
2
- <img src="https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.1/docs/assets/hero.webp" alt="Subagent Hub — Named roles. Clear routes. Visible models." width="100%">
2
+ <img src="https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.3/docs/assets/hero.webp" alt="Subagent Hub — Named roles. Clear routes. Visible models." width="100%">
3
3
  </p>
4
4
 
5
5
  <p align="center">
6
- <a href="https://github.com/ccsert/dsh-subagent-hub/releases/tag/v0.10.1"><img src="https://img.shields.io/badge/version-0.10.1-66c8ab" alt="Version 0.10.1"></a>
7
- <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
8
- <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/install.md"><img src="https://img.shields.io/badge/DSH_Desktop-0.12.1-7087ad" alt="Tested with DSH Desktop 0.12.1"></a>
9
- <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/CONTRIBUTING.md"><img src="https://img.shields.io/badge/verified-87_unit_%2B_23_runtime-66c8ab" alt="87 unit tests and 23 runtime assertions verified"></a>
6
+ <a href="https://github.com/ccsert/dsh-subagent-hub/releases/tag/v0.10.3"><img src="https://img.shields.io/badge/version-0.10.3-66c8ab" alt="Version 0.10.3"></a>
7
+ <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT license"></a>
8
+ <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/install.md"><img src="https://img.shields.io/badge/DSH_Desktop-0.12.1-7087ad" alt="Tested with DSH Desktop 0.12.1"></a>
9
+ <a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/CONTRIBUTING.md"><img src="https://img.shields.io/badge/verified-102_unit_%2B_31_runtime-66c8ab" alt="102 unit tests and 31 runtime assertions verified"></a>
10
10
  </p>
11
11
 
12
- <p align="center"><a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/README.md">简体中文</a> · <b>English</b></p>
12
+ <p align="center"><a href="https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/README.md">简体中文</a> · <b>English</b></p>
13
13
 
14
14
  # Subagent Hub
15
15
 
@@ -17,7 +17,7 @@
17
17
 
18
18
  Uses official `subagent` / `subagent_fork` dispatch, Agent Presets, and conversation UI. Keep your existing main-agent preset.
19
19
 
20
- [Install](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/install.md) · [Download a release](https://github.com/ccsert/dsh-subagent-hub/releases/latest) · [Configuration example](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/examples/subagent-hub.yml) · [Changelog](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/CHANGELOG.md) · [Contribute](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/CONTRIBUTING.md)
20
+ [Install](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/install.md) · [Download a release](https://github.com/ccsert/dsh-subagent-hub/releases/latest) · [Configuration example](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/examples/subagent-hub.yml) · [Changelog](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/CHANGELOG.md) · [Contribute](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/CONTRIBUTING.md)
21
21
 
22
22
  ## Features
23
23
 
@@ -25,7 +25,7 @@ Uses official `subagent` / `subagent_fork` dispatch, Agent Presets, and conversa
25
25
  |---|---|
26
26
  | **Named roles** | Register official `hub-agent-<id>` presets and dispatch with `agent_preset` |
27
27
  | **Model selection catalog** | Provide configured routes, capabilities, purposes, and role instructions to the main agent |
28
- | **Route guard** | Reject explicit or inherited routes outside a named role's allowed `routes` before dispatch |
28
+ | **Route guard** | Check the global child routes before subagent, fork, and Team creation; named roles can narrow that range further |
29
29
  | **Per-session concurrency** | Limit child agents in each parent session, alongside DSH's global capacity |
30
30
  | **Native settings form** | Edit models, roles, tags, and management options in **Settings → Subagent Hub** |
31
31
  | **Request model visibility** | Read the latest provider/model from the child header; expand for full IDs and recorded reasoning effort |
@@ -34,29 +34,29 @@ Uses official `subagent` / `subagent_fork` dispatch, Agent Presets, and conversa
34
34
 
35
35
  **Models and named agents**
36
36
 
37
- ![Actual configuration component with demo models and named agents](https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.1/docs/assets/settings.webp)
37
+ ![Actual configuration component with demo models and named agents](https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.3/docs/assets/settings.webp)
38
38
 
39
39
  **The latest child request**
40
40
 
41
- ![Child header with full provider, model ID and reasoning effort](https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.1/docs/assets/model-badge.webp)
41
+ ![Child header with full provider, model ID and reasoning effort](https://raw.githubusercontent.com/ccsert/dsh-subagent-hub/v0.10.3/docs/assets/model-badge.webp)
42
42
 
43
- These are the actual plugin components rendered with demo data. They contain no user profile, private conversation, or credentials. See [screenshot provenance](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/screenshots.md) for full-size and narrow views. The Hero was generated with [dsh-image-gen](https://github.com/shanliuling/dsh-image-gen).
43
+ These are the actual plugin components rendered with demo data. They contain no user profile, private conversation, or credentials. See [screenshot provenance](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/screenshots.md) for full-size and narrow views. The Hero was generated with [dsh-image-gen](https://github.com/shanliuling/dsh-image-gen).
44
44
 
45
45
  ## Quick start
46
46
 
47
47
  Tested with **DSH Desktop 0.12.1 / `@deepseek-ai/dsh` 0.2.1-rc.2**. Configure a working model provider in DSH first.
48
48
 
49
- 1. Download `dsh-subagent-hub-0.10.1.tgz` from [Releases](https://github.com/ccsert/dsh-subagent-hub/releases/latest).
49
+ 1. Download `dsh-subagent-hub-0.10.3.tgz` from [Releases](https://github.com/ccsert/dsh-subagent-hub/releases/latest).
50
50
  2. Install it from the target profile directory:
51
51
 
52
52
  ```bash
53
- pnpm add /absolute/path/to/dsh-subagent-hub-0.10.1.tgz
53
+ pnpm add /absolute/path/to/dsh-subagent-hub-0.10.3.tgz
54
54
  ```
55
55
 
56
56
  3. **Append** `"dsh-subagent-hub"` to `dsh.profile.bundles` in that profile's package manifest. Preserve existing bundles and restart DSH to load the plugin code.
57
57
  4. Open **Settings → Subagent Hub**, select models already available in your DSH, and add a role. Enable **Manage the official delegation settings** if you want Hub to synchronize the official allowlists. Save, then start a new conversation.
58
58
 
59
- Git checkout installation, profile locations, upgrades, and removal are covered in the [installation guide](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/install.md). GitHub release archives are the current distribution channel; npm registry publishing is not enabled yet.
59
+ Git checkout installation, profile locations, upgrades, and removal are covered in the [installation guide](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/install.md). The package is published as [dsh-subagent-hub](https://www.npmjs.com/package/dsh-subagent-hub); fixed GitHub release archives are also available. The Team/inheritance guard requires 0.10.2 or newer.
60
60
 
61
61
  ## Minimal configuration
62
62
 
@@ -85,11 +85,14 @@ The main agent selects a role based on the task and catalog. When the official t
85
85
 
86
86
  ## Behavior to keep in mind
87
87
 
88
+ - **Team context selects history.** In the supported runtime, fresh and fork both inherit the Lead model and have no model-selection input. For a specific child model, use a `subagent` tool whose actual schema exposes model selection. See [Team model routing](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/team-model-routing.md).
89
+ - **Global routes also guard inheritance.** A nonempty `routes` list bounds all three creation tools; out-of-list or unconfirmed models are refused. An empty list retains native inheritance. Existing members and later request rewrites are outside this creation check.
88
90
  - **Tags guide decisions.** Tags such as `fast`, `reasoning`, or `free` are maintained by you. Use a role's `routes` for a hard route boundary. Tags do not verify pricing or implement automatic fallback.
89
91
  - **The guard runs before dispatch.** Model rewrites performed by other plugins during a later child request are outside this guard. Check those plugins when combining route policies.
90
92
  - **Fork follows official capabilities.** The target fork provider does not support an independent `agent_preset`; named roles use `subagent`.
91
93
  - **Managed settings are opt-in.** `manageOfficialSettings` defaults to `false`. Enabling it synchronizes official allowlists. Disabling it stops syncing and retains settings already written.
92
94
  - **Existing conversations keep their tool schema.** Saved configuration is live; use a new conversation for newly added roles or tool parameters. Plugin code upgrades require a DSH restart.
95
+ - **Desktop model badges need 0.10.3 or newer.** This fixes the required native list-slot registration field. A component demo is distinct from a reload of the current Desktop window; see the [mounting fix record](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/model-badge-acceptance.md).
93
96
  - **The model badge shows request history.** `lastUsed` identifies the latest request; a different `next` selection is shown separately. A ready empty history says “No request yet”; unavailable data says “Unconfirmed.” A request record does not establish success or billing, and forked history may include inherited requests.
94
97
 
95
98
  ## Development (repository checkout)
@@ -102,8 +105,8 @@ bash test.sh # real DSH integration in a temporary DSH_HOME
102
105
  npm run pack:check # inspect the distributable archive
103
106
  ```
104
107
 
105
- This release is verified by **87 unit tests and 23 real-runtime assertions**. Integration uses scripted model streams and makes no external model calls. CI runs unit, documentation, and package checks. See the [settings acceptance record](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/ui-acceptance.md) and [model badge acceptance record](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/model-badge-acceptance.md) for scope and limitations.
108
+ This release is verified by **87 unit tests and 23 real-runtime assertions**. Integration uses scripted model streams and makes no external model calls. CI runs unit, documentation, and package checks. See the [settings acceptance record](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/ui-acceptance.md) and [model badge acceptance record](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/model-badge-acceptance.md) for scope and limitations. [Team routing verification](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/team-model-routing.md) covers the new creation checks.
106
109
 
107
- Issues and PRs are welcome. Read [CONTRIBUTING](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/CONTRIBUTING.md), the [design notes](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/design.md), and the [release guide](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/docs/releasing.md). The previous v0.3.0 architecture remains available at its tag.
110
+ Issues and PRs are welcome. Read [CONTRIBUTING](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/CONTRIBUTING.md), the [design notes](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/design.md), and the [release guide](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/docs/releasing.md). The previous v0.3.0 architecture remains available at its tag.
108
111
 
109
- [MIT License](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.1/LICENSE) · Maintained by [ccsert](https://github.com/ccsert)
112
+ [MIT License](https://github.com/ccsert/dsh-subagent-hub/blob/v0.10.3/LICENSE) · Maintained by [ccsert](https://github.com/ccsert)
package/lib/client.js CHANGED
@@ -1719,7 +1719,7 @@ window.__ModuleLoader__.load({
1719
1719
  function registerModelBadge(ctx, t) {
1720
1720
  ctx.inject(['sessions'], scope => scope.slots.inject(MODEL_BADGE_SLOT, () => scope.slots.register({
1721
1721
  name: MODEL_BADGE_SLOT,
1722
- key: 'subagent-hub-model',
1722
+ id: 'subagent-hub-model',
1723
1723
  order: -10,
1724
1724
  locale: NS,
1725
1725
  inject: sessionId => modelBadgeShares(scope.sessions, sessionId),
package/lib/guard.js CHANGED
@@ -57,12 +57,47 @@ export function concurrencyVerdict({ toolName, delegationTools, limit, running }
57
57
  return `${GUARD_PREFIX} 这个会话已有 ${running} 个子 agent 正在运行(上限 ${limit})。等其中一个结束后再派发,或者自己把剩下的工作做完。`
58
58
  }
59
59
 
60
+ export const CREATION_TOOLS = ['subagent', 'subagent_fork', 'spawn_teammate']
61
+
62
+ export function modelSelectionAvailable(parameters) {
63
+ const properties = parameters?.properties ?? {}
64
+ return Object.hasOwn(properties, 'provider') && Object.hasOwn(properties, 'model')
65
+ }
66
+
67
+ /**
68
+ * Check the actual route of every supported creation tool against the configured
69
+ * child allowlist. Tool capabilities decide whether model arguments are effective;
70
+ * extra arguments on a model-less fork/Team tool cannot disguise inheritance.
71
+ */
72
+ export function creationRouteVerdict({ toolName, arguments: args, parent, catalog, parameters, configured = catalog.routes.size > 0 }) {
73
+ if (!CREATION_TOOLS.includes(toolName) || !configured) return undefined
74
+ const allowed = [...catalog.routes.values()]
75
+ if (allowed.length === 0) return `${GUARD_PREFIX} ${toolName} cannot verify its child model: the configured child route list has no valid routes. Fix the Hub route configuration before delegating.`
76
+ const canSelectModel = modelSelectionAvailable(parameters)
77
+ if (canSelectModel) {
78
+ const provider = typeof args?.provider === 'string' && args.provider !== ''
79
+ const model = typeof args?.model === 'string' && args.model !== ''
80
+ if (provider !== model) return undefined // the native tool reports its pairing error
81
+ }
82
+ const route = effectiveRoute(canSelectModel ? args ?? {} : {}, parent)
83
+ const labels = allowed.map(routeLabel).join(', ')
84
+ if (route !== undefined && allowed.some(allowed => allowed.provider === route.provider && allowed.model === route.model)) return undefined
85
+ const actual = route === undefined ? 'an unconfirmed model' : `${route.provider}/${route.model}`
86
+ const mode = toolName === 'spawn_teammate' ? ` (${args?.context === 'fork' ? 'fork' : 'fresh'})` : ''
87
+ const alternative = canSelectModel
88
+ ? 'Choose provider and model from the allowed routes.'
89
+ : toolName === 'spawn_teammate'
90
+ ? 'This spawn_teammate schema has no provider/model selector. Both fresh and fork inherit the Lead model; changing context does not choose a different model. Use a delegation tool only if its actual schema exposes model selection, or select an allowed Lead model before using Teams.'
91
+ : `This ${toolName} schema has no provider/model selector and inherits the parent model. Use a delegation tool only if its actual schema exposes model selection, or select an allowed parent model first.`
92
+ return `${GUARD_PREFIX} ${toolName}${mode} would use ${actual}, outside the configured child routes: ${labels}. No child was started. ${alternative}`
93
+ }
94
+
60
95
  /**
61
96
  * Whether one official delegation call must be rejected.
62
97
  * @param {{ arguments?: GuardArguments, parent?: any, catalog: GuardCatalog }} input
63
98
  * @returns {string | undefined} a model-readable rejection reason, or undefined to allow.
64
99
  */
65
- export function guardVerdict({ arguments: args, parent, catalog }) {
100
+ export function guardVerdict({ arguments: args, parent, catalog, canSelectModel = true }) {
66
101
  const presetId = args?.agent_preset
67
102
  if (typeof presetId !== 'string') return undefined
68
103
  const agent = catalog.byPreset.get(presetId)
@@ -87,7 +122,10 @@ export function guardVerdict({ arguments: args, parent, catalog }) {
87
122
  const labels = allowed.map(routeLabel).join(', ')
88
123
  const explicit = allowed.map(route => route.id).join(', ')
89
124
  if (requested.source === 'inherited parent') {
90
- return `${GUARD_PREFIX} named agent "${agent.id}" is limited to routes ${labels}, but the child would inherit the parent route ${requested.provider}/${requested.model}. Pass \`provider\` and \`model\` together, choosing one of: ${labels} (route ids: ${explicit}).`
125
+ const guidance = canSelectModel
126
+ ? `Pass \`provider\` and \`model\` together, choosing one of: ${labels} (route ids: ${explicit}).`
127
+ : 'This tool has no provider/model selector. Use a tool whose actual schema supports model selection, or an allowed parent model; do not pass unsupported model arguments.'
128
+ return `${GUARD_PREFIX} named agent "${agent.id}" is limited to routes ${labels}, but the child would inherit the parent route ${requested.provider}/${requested.model}. ${guidance}`
91
129
  }
92
130
  return `${GUARD_PREFIX} named agent "${agent.id}" is limited to routes ${labels}; requested ${requested.provider}/${requested.model} is not allowed. Choose \`provider\` and \`model\` from: ${labels} (route ids: ${explicit}).`
93
131
  }
package/lib/index.js CHANGED
@@ -17,7 +17,7 @@ import { Service } from '@deepseek-ai/cordis'
17
17
  import z from '@deepseek-ai/schemastery'
18
18
  import { AGENT_PRESET_PREFIX, buildCatalog } from './catalog.js'
19
19
  import { deriveAgentPreset } from './presets.js'
20
- import { GUARD_PREFIX, concurrencyVerdict, guardVerdict } from './guard.js'
20
+ import { CREATION_TOOLS, GUARD_PREFIX, concurrencyVerdict, creationRouteVerdict, guardVerdict, modelSelectionAvailable } from './guard.js'
21
21
  import { promptSection } from './prompt.js'
22
22
  import { MODEL_SETTINGS_ID, PRESET_PLUGIN, modelSettingsFor, officialSettingsPlan, patchPresetRows, sameAllowedModels } from './official-settings.js'
23
23
 
@@ -287,22 +287,34 @@ export default class SubagentHub extends Service {
287
287
  exposedParameters(scope) {
288
288
  const tools = this.ctx.get('tools')
289
289
  if (tools === undefined) return undefined
290
+ const independent = []
290
291
  for (const name of ['subagent', 'subagent_fork']) {
291
292
  const tool = tools.get(name, scope)
292
293
  if (tool === undefined) continue
293
294
  const properties = tool.parameters?.properties ?? {}
294
- return {
295
+ independent.push({
296
+ name,
295
297
  agentPreset: Object.hasOwn(properties, 'agent_preset'),
296
298
  model: Object.hasOwn(properties, 'provider') && Object.hasOwn(properties, 'model'),
297
- }
299
+ })
300
+ }
301
+ const teamTool = tools.get('spawn_teammate', scope)
302
+ if (independent.length === 0 && teamTool === undefined) return undefined
303
+ const properties = teamTool?.parameters?.properties ?? {}
304
+ return {
305
+ agentPreset: independent[0]?.agentPreset ?? false,
306
+ model: independent[0]?.model ?? false,
307
+ independent,
308
+ team: teamTool === undefined ? undefined : {
309
+ model: Object.hasOwn(properties, 'provider') && Object.hasOwn(properties, 'model'),
310
+ },
298
311
  }
299
- return undefined
300
312
  }
301
313
 
302
314
  sectionText(scope) {
303
315
  const params = this.exposedParameters(scope)
304
316
  if (params === undefined) return ''
305
- return promptSection({ catalog: this.catalog, params, limit: read(this.config.maxConcurrent) ?? 0 })
317
+ return promptSection({ catalog: this.catalog, params, limit: read(this.config.maxConcurrent) ?? 0, limitTools: read(this.config.delegationTools) ?? [] })
306
318
  }
307
319
 
308
320
  /** Child agents of one Session that are still running, from the start/end ledger. */
@@ -348,9 +360,30 @@ export default class SubagentHub extends Service {
348
360
  running: this.runningChildren(exec.agent),
349
361
  })
350
362
  if (busy !== undefined) return Promise.resolve({ kind: 'deny', reason: busy })
351
- const reason = guardVerdict({ arguments: exec.arguments, parent: exec.agent, catalog: this.catalog })
352
- if (reason !== undefined) return Promise.resolve({ kind: 'deny', reason })
363
+ let args = exec.arguments
364
+ let canSelectModel = true
365
+ if (CREATION_TOOLS.includes(exec.name)) {
366
+ const tool = this.ctx.get('tools')?.get(exec.name, exec.agent)
367
+ const reason = creationRouteVerdict({
368
+ toolName: exec.name,
369
+ arguments: args,
370
+ parent: exec.agent,
371
+ catalog: this.catalog,
372
+ parameters: tool?.parameters,
373
+ configured: (read(this.config.routes) ?? []).length > 0,
374
+ })
375
+ if (reason !== undefined) return Promise.resolve({ kind: 'deny', reason })
376
+ canSelectModel = modelSelectionAvailable(tool?.parameters)
377
+ if (!canSelectModel) args = { ...args, provider: undefined, model: undefined }
378
+ }
379
+ if (exec.name !== 'spawn_teammate') {
380
+ const reason = guardVerdict({ arguments: args, parent: exec.agent, catalog: this.catalog, canSelectModel })
381
+ if (reason !== undefined) return Promise.resolve({ kind: 'deny', reason })
382
+ }
353
383
  } catch (error) {
384
+ if (CREATION_TOOLS.includes(exec.name)) {
385
+ return Promise.resolve({ kind: 'deny', reason: `${GUARD_PREFIX} ${exec.name} could not verify its model against the Hub child routes. No child was started; check the route configuration and tool availability.` })
386
+ }
354
387
  this.ctx.logger?.warn?.(`${PACKAGE}: route guard skipped one call: ${String(error?.message ?? error)}`)
355
388
  }
356
389
  return next()
package/lib/prompt.js CHANGED
@@ -7,7 +7,8 @@ import { allowedModels, routeLabel, routeSummary } from './router.js'
7
7
  * @typedef {import('./router.js').Route} Route
8
8
  * @typedef {import('./guard.js').GuardAgent & { description: string, needs?: string[] }} PromptAgent
9
9
  * @typedef {{ routes: Map<string, Route>, agents: PromptAgent[], tags: string[], problems: string[] }} PromptCatalog
10
- * @typedef {{ agentPreset: boolean, model: boolean }} PromptParameters
10
+ * @typedef {{ name: string, agentPreset: boolean, model: boolean }} DelegationInputs
11
+ * @typedef {{ agentPreset: boolean, model: boolean, independent?: DelegationInputs[], team?: { model: boolean } }} PromptParameters
11
12
  */
12
13
 
13
14
  /**
@@ -16,11 +17,25 @@ import { allowedModels, routeLabel, routeSummary } from './router.js'
16
17
  * @param {{ catalog: PromptCatalog, params: PromptParameters, limit?: number }} input
17
18
  * @returns {string} section markdown, or '' when there is nothing to say.
18
19
  */
19
- export function promptSection({ catalog, params, limit = 0 }) {
20
+ export function promptSection({ catalog, params, limit = 0, limitTools = ['subagent', 'subagent_fork'] }) {
20
21
  const { agents, routes, tags, problems } = catalog
21
22
  if (agents.length === 0 && routes.size === 0) return ''
22
23
  const lines = ['## Choosing a subagent']
23
- lines.push(guidance(params))
24
+ if (params.team !== undefined && params.independent?.length === 0) {
25
+ lines.push('This session exposes Agent Teams without an independent subagent creation tool. The named-agent catalog is informational here; use only the inputs present on the visible tool.')
26
+ } else {
27
+ lines.push(guidance(params))
28
+ }
29
+ if (params.team !== undefined) {
30
+ if (params.team.model) {
31
+ lines.push('`spawn_teammate` exposes model selection in this composition; choose provider and model from the configured child routes.')
32
+ } else {
33
+ lines.push('`spawn_teammate` has no `provider`, `model`, or `agent_preset` input in this runtime. Both `context=fresh` and `context=fork` inherit the Lead model; context selects history, not a different model. The Hub rejects a teammate whose inherited model is outside the configured child routes.')
34
+ const alternatives = (params.independent ?? []).filter(tool => tool.model).map(tool => `\`${tool.name}\``)
35
+ if (alternatives.length > 0) lines.push(`For an explicit child model requirement, use ${alternatives.join(' or ')} with the provider/model inputs its schema exposes.`)
36
+ else lines.push('No visible independent delegation tool exposes model selection. If the inherited model cannot satisfy the requested model requirement, explain this limitation before delegating; do not start a different model and apologize afterwards.')
37
+ }
38
+ }
24
39
  if (agents.length > 0) {
25
40
  lines.push('', 'Named agents:')
26
41
  for (const agent of agents) {
@@ -39,11 +54,17 @@ export function promptSection({ catalog, params, limit = 0 }) {
39
54
  lines.push(`- \`${routeLabel(route)}\`${tagsPart}${notePart}`)
40
55
  }
41
56
  if (tags.length > 0) lines.push(`Capability tags: ${tags.map(tag => `\`${tag}\``).join(', ')}.`)
57
+ lines.push('The configured child route list applies to `subagent`, `subagent_fork`, and `spawn_teammate`, including inherited models. If the parent model is not listed, use a tool that actually exposes provider/model to select an allowed child route.')
42
58
  const restricted = agents.some(agent => (agent.routes?.length ?? 0) > 0)
43
59
  if (restricted) lines.push('When a named agent lists routes, the child `provider`/`model` must be one of that agent\'s routes; any other route is rejected before the child starts.')
44
60
  }
45
61
  if (limit > 0) {
46
- lines.push('', `At most ${limit} child agents may run at the same time in one Session; a delegation beyond that is refused, so wait for one to finish instead of queueing more.`)
62
+ if (params.team !== undefined) {
63
+ const covered = (params.independent ?? []).filter(tool => limitTools.includes(tool.name)).map(tool => `\`${tool.name}\``)
64
+ if (covered.length > 0) lines.push('', `The Hub per-session concurrency cap is ${limit}, enforced on ${covered.join(', ')}. Team creation follows the native Team capacity setting.`)
65
+ } else {
66
+ lines.push('', `At most ${limit} child agents may run at the same time in one Session; a delegation beyond that is refused, so wait for one to finish instead of queueing more.`)
67
+ }
47
68
  }
48
69
  if (problems.length > 0) lines.push('', `Ignored configuration problems: ${problems.join('; ')}`)
49
70
  return lines.join('\n')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-subagent-hub",
3
- "version": "0.10.1",
3
+ "version": "0.10.3",
4
4
  "description": "Named roles, model route guards, native configuration, and request-model visibility for official DeepSeek Harness subagents.",
5
5
  "license": "MIT",
6
6
  "type": "module",