dsh-subagent-control 0.0.0-stage → 1.0.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 ADDED
@@ -0,0 +1,12 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ First release.
6
+
7
+ - `list_subagents` — list the caller's direct children or its whole descendant tree, with `running` / `inactive` status.
8
+ - `send_subagent_message` — send a follow-up message to a subagent the caller started.
9
+ - `interrupt_subagent` — stop a subagent (or any descendant) the caller started.
10
+ - Registers three new global tool names instead of shadowing the team bundle's `list_agents` / `send_message` / `interrupt_agent`.
11
+ - No runtime or peer dependencies; the module imports nothing.
12
+ - Localized display metadata (zh / en) and an SVG icon.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-subagent-control contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,121 @@
1
- # Temporary Holding Version
1
+ # dsh-subagent-control
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Three tools that let any DSH agent — **including a team member** — list, steer and stop the subagents it started.
4
+
5
+ `list_subagents` · `send_subagent_message` · `interrupt_subagent`
6
+
7
+ ---
8
+
9
+ ## English
10
+
11
+ ### The problem
12
+
13
+ The Harness ships `list_agents`, `send_message` and `interrupt_agent`. When the Agent Team bundle is active it registers tools with **those same three names** in every team member's exact Agent scope, and the more specific registration wins. Inside a teammate scope the team versions take over: they resolve `target` against the team roster only, and interrupting is a Lead-only operation.
14
+
15
+ The result: a teammate can start a subagent with `subagent`, but cannot list it, follow up with it, or stop it — even though `ctx.subagents` (the subagent service) has supported all three for any live agent all along, and the authority rules already allow an ancestor to manage its own descendants.
16
+
17
+ This bundle does not fight over the taken names. It registers three **new** names, globally, so the Lead and every teammate get them at once. Authority is still enforced by the service: a caller can message its own direct children and interrupt its own descendants.
18
+
19
+ ### Tools
20
+
21
+ | Tool | Parameters | Calls |
22
+ | --- | --- | --- |
23
+ | `list_subagents` | `scope?`: `"children"` \| `"descendants"` (default `children`) | `ctx.subagents.listChildren()` / `listDescendants()` |
24
+ | `send_subagent_message` | `subagent_id`, `message` (both required) | `ctx.subagents.sendMessage()` |
25
+ | `interrupt_subagent` | `subagent_id` (required) | `ctx.subagents.interrupt()` with ancestor authority |
26
+
27
+ `list_subagents` returns one line per subagent: `id status "label" depth=`. `status` is `running` or `inactive` — `inactive` means no turn is executing, not that the subagent is gone.
28
+
29
+ ### Behaviour and limits
30
+
31
+ - One-shot subagents are not listed. They cannot accept a follow-up message, so showing them would only invite a call that is guaranteed to fail.
32
+ - A child whose record cannot be read comes back as a diagnostic entry (`corrupt` / `unsupported` / `unavailable`) instead of turning the whole listing into an error.
33
+ - `send_subagent_message` reaches a direct child only. This is the service's rule, not an extra restriction here.
34
+ - `interrupt_subagent` reaches the caller's descendants, so a team Lead can also stop a grandchild.
35
+ - Every failure is a normal error result that names the cause: no calling agent, unknown id, or an id that is not yours.
36
+
37
+ ### Install
38
+
39
+ Through the plugin manager / market, or by asking the agent:
40
+
41
+ ```
42
+ plugin_manager install_bundle target "dsh-subagent-control@1.0.0"
43
+ ```
44
+
45
+ From a checkout, pass the directory path instead of the npm spec. The bundle applies immediately — no restart. Verify the three tool names appear and the plugin row is enabled.
46
+
47
+ ### Compatibility
48
+
49
+ - DSH with the `tools`, `subagents` and `agents` services (0.2.x and later).
50
+ - Node `^22.19.0 || >=24.0.0`.
51
+ - No runtime dependencies, no peer dependencies: the module imports nothing. A plain tool table with explicit JSON Schemas installs the same way from npm or from a linked checkout.
52
+
53
+ ### Known name conflict
54
+
55
+ `dsh-subagent-library` also registers a global `list_subagents` tool. Two global registrations of one name cannot both win — install one of them, or remember that the other may shadow this one.
56
+
57
+ ### Uninstall
58
+
59
+ Remove or disable the bundle in the plugin panel. Nothing else is written: no config, no storage.
60
+
61
+ ### License
62
+
63
+ MIT — see [LICENSE](./LICENSE).
64
+
65
+ ---
66
+
67
+ ## 中文
68
+
69
+ ### 它解决什么问题
70
+
71
+ Harness 自带 `list_agents`、`send_message`、`interrupt_agent`。开启 Agent Team 后,团队插件会把**同名的三个工具**注册进每个团队成员的精确作用域,而更具体的作用域优先。于是在成员作用域里生效的是团队版:`target` 只认队友名,打断只有组长能做。
72
+
73
+ 结果是:团队成员能用 `subagent` 派出子代理,却列不出、追问不了、也打断不了它。而底层的 `ctx.subagents` 服务本来就支持这三件事,权限规则也允许祖先管理自己的后代。
74
+
75
+ 本插件不去抢那三个被占用的名字,而是全局注册三个**新名字**,组员立刻可用。权限仍由服务裁决:调用者可以给自己直接派出的子代理发消息,也可以打断自己名下的所有后代。
76
+
77
+ ### 工具
78
+
79
+ | 工具 | 参数 | 调用 |
80
+ | --- | --- | --- |
81
+ | `list_subagents` | `scope?`:`"children"` 或 `"descendants"`,默认 `children` | `ctx.subagents.listChildren()` / `listDescendants()` |
82
+ | `send_subagent_message` | `subagent_id`、`message`(必填) | `ctx.subagents.sendMessage()` |
83
+ | `interrupt_subagent` | `subagent_id`(必填) | `ctx.subagents.interrupt()`(祖先权限) |
84
+
85
+ `list_subagents` 每个子代理一行:`id status "label" depth=`。`status` 为 `running` 或 `inactive`;`inactive` 表示当前没有在跑,不代表子代理已经消失。
86
+
87
+ ### 行为与边界
88
+
89
+ - 一次性子代理不会被列出:它们无法接收后续消息,列出来只会诱导一次注定失败的调用。
90
+ - 某个子代理记录读不出来时,会作为诊断条目(`corrupt` / `unsupported` / `unavailable`)返回,而不是让整个列表报错。
91
+ - `send_subagent_message` 只能发给直接子代理,这是服务本身的规则,插件没有额外收紧。
92
+ - `interrupt_subagent` 覆盖调用者的所有后代,所以组长也能打断孙代。
93
+ - 任何失败都是普通的错误结果,并说明原因:没有调用者、id 不存在、或者不是你的子代理。
94
+
95
+ ### 安装
96
+
97
+ 在插件面板/市场中安装,或者让智能体执行:
98
+
99
+ ```
100
+ plugin_manager install_bundle target "dsh-subagent-control@1.0.0"
101
+ ```
102
+
103
+ 从本地目录安装时,把 npm 规格换成目录路径。安装即时生效,无需重启;装完确认三个工具名可见、插件行为已启用。
104
+
105
+ ### 兼容性
106
+
107
+ - 具备 `tools`、`subagents`、`agents` 三个服务的 DSH(0.2.x 及以上)。
108
+ - Node `^22.19.0 || >=24.0.0`。
109
+ - 无运行时依赖、无 peer 依赖:模块不 import 任何东西,工具是带显式 JSON Schema 的普通定义对象,npm 安装与软链安装行为一致。
110
+
111
+ ### 已知撞名
112
+
113
+ `dsh-subagent-library` 也注册了全局的 `list_subagents`。同一个名字的两个全局注册不可能同时生效——二选一安装,或记住其中一个可能被另一个覆盖。
114
+
115
+ ### 卸载
116
+
117
+ 在插件面板里移除或停用即可。插件不写任何配置和存储。
118
+
119
+ ### 许可
120
+
121
+ MIT,见 [LICENSE](./LICENSE)。
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: subagent-control
3
+ name: 'dsh-subagent-control'
package/icon.svg ADDED
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Subagent control">
2
+ <rect x="2" y="2" width="60" height="60" rx="14" fill="#1f6feb"/>
3
+ <g fill="none" stroke="#ffffff" stroke-width="3.5" stroke-linecap="round">
4
+ <path d="M32 20v9"/>
5
+ <path d="M32 29H16v8"/>
6
+ <path d="M32 29h16v8"/>
7
+ <path d="M32 29v8"/>
8
+ </g>
9
+ <circle cx="32" cy="15" r="6" fill="#ffffff"/>
10
+ <circle cx="16" cy="43" r="5" fill="#ffffff"/>
11
+ <circle cx="32" cy="43" r="5" fill="#ffffff"/>
12
+ <circle cx="48" cy="43" r="5" fill="#ffffff"/>
13
+ </svg>
package/index.js ADDED
@@ -0,0 +1,214 @@
1
+ /**
2
+ * Subagent control tools.
3
+ *
4
+ * The Harness already registers `list_agents` / `send_message` /
5
+ * `interrupt_agent`, but the Agent Team plugin registers tools with those same
6
+ * three names in each team member's exact Agent scope, so inside a teammate
7
+ * scope the team versions win: they resolve `target` against the team roster
8
+ * only, and `interrupt_agent` is Lead-only. A team member therefore cannot
9
+ * list, steer, or stop the subagents it started, even though
10
+ * `ctx.subagents` already supports all three operations for any live agent.
11
+ *
12
+ * This plugin registers three differently named tools instead of fighting over
13
+ * the taken names. They are registered globally, so the Lead and every team
14
+ * member get them; the subagent service itself enforces that a caller can only
15
+ * reach its own direct children (`sendMessage`) or its own descendants
16
+ * (`interrupt`).
17
+ *
18
+ * This module deliberately imports nothing: the tool definitions below are
19
+ * plain registry-ready objects with explicit JSON Schemas, so the package has
20
+ * no runtime or peer dependencies and installs the same way from npm or from a
21
+ * linked checkout.
22
+ */
23
+
24
+ export const name = 'subagent-control'
25
+
26
+ export const inject = ['tools', 'subagents', 'agents']
27
+
28
+ /** Detail rows returned by the subagent catalog. */
29
+ const CHILD_ITEM = {
30
+ type: 'object',
31
+ additionalProperties: false,
32
+ required: ['kind', 'id', 'label', 'status'],
33
+ properties: {
34
+ kind: { type: 'string', enum: ['child'] },
35
+ id: { type: 'string' },
36
+ label: { type: 'string' },
37
+ status: { type: 'string', enum: ['running', 'inactive'] },
38
+ parent: { type: 'string' },
39
+ depth: { type: 'number' },
40
+ },
41
+ }
42
+
43
+ const DIAGNOSTIC_ITEM = {
44
+ type: 'object',
45
+ additionalProperties: false,
46
+ required: ['kind', 'id', 'reason'],
47
+ properties: {
48
+ kind: { type: 'string', enum: ['diagnostic'] },
49
+ id: { type: 'string' },
50
+ reason: { type: 'string', enum: ['corrupt', 'unsupported', 'unavailable'] },
51
+ parent: { type: 'string' },
52
+ depth: { type: 'number' },
53
+ },
54
+ }
55
+
56
+ /** A tool needs a calling agent; a schedule or an external call has none. */
57
+ function requireAgent(exec, tool) {
58
+ const agent = exec.agent
59
+ if (agent === undefined) {
60
+ throw new Error(`${tool} requires a calling agent (exec.agent was undefined)`)
61
+ }
62
+ return agent
63
+ }
64
+
65
+ /** Report only whether the subagent is working, never whether it is loaded. */
66
+ function statusOf(agents, id) {
67
+ return agents.get(id)?.status === 'running' ? 'running' : 'inactive'
68
+ }
69
+
70
+ /** One catalog row to one model-visible entry; one-shot children are omitted. */
71
+ function project(agents, entry, position) {
72
+ const at = position === undefined ? {} : { parent: position.parentId, depth: position.depth }
73
+ if (entry.kind === 'diagnostic') {
74
+ return { kind: 'diagnostic', id: entry.id, reason: entry.reason, ...at }
75
+ }
76
+ if (entry.mode !== 'continuable') return undefined
77
+ return {
78
+ kind: 'child',
79
+ id: entry.id,
80
+ label: entry.label,
81
+ status: statusOf(agents, entry.id),
82
+ ...at,
83
+ }
84
+ }
85
+
86
+ function renderEntry(entry) {
87
+ if (entry.kind === 'diagnostic') {
88
+ const at = entry.parent === undefined ? '' : ` parent=${entry.parent}`
89
+ return `[${entry.reason}] ${entry.id}${at}`
90
+ }
91
+ const depth = entry.depth === undefined ? '' : ` depth=${entry.depth}`
92
+ return `${entry.id} ${entry.status} ${JSON.stringify(entry.label)}${depth}`
93
+ }
94
+
95
+ export function apply(ctx) {
96
+ /* ---------------------------------------------------------------- list */
97
+
98
+ ctx.tools.register({
99
+ name: 'list_subagents',
100
+ description:
101
+ 'List the subagents you started, with their ids, labels and status. ' +
102
+ 'running means it is working; inactive means it is not working right now, which does not mean it is gone. ' +
103
+ 'You are notified when a subagent finishes, so there is no need to poll. ' +
104
+ 'Use send_subagent_message to continue a conversation with one of them.',
105
+ parameters: {
106
+ type: 'object',
107
+ additionalProperties: false,
108
+ properties: {
109
+ scope: {
110
+ type: 'string',
111
+ enum: ['children', 'descendants'],
112
+ description:
113
+ 'children (default) lists your direct children, which accept send_subagent_message in any status. ' +
114
+ 'descendants lists the whole tree below you with each entry parent and depth; ' +
115
+ 'entries deeper than one level accept only interrupt_subagent.',
116
+ },
117
+ },
118
+ },
119
+ output: {
120
+ schema: { type: 'array', items: { oneOf: [CHILD_ITEM, DIAGNOSTIC_ITEM] } },
121
+ render: (_args, entries) => [
122
+ {
123
+ type: 'text',
124
+ text: entries.length === 0 ? '(no subagents)' : entries.map(renderEntry).join('\n'),
125
+ },
126
+ ],
127
+ },
128
+ async execute(args, exec) {
129
+ const caller = requireAgent(exec, 'list_subagents')
130
+ if ((args.scope ?? 'children') === 'descendants') {
131
+ const rows = await ctx.subagents.listDescendants(caller.id, exec.signal)
132
+ return rows.map((row) => project(ctx.agents, row, row)).filter(Boolean)
133
+ }
134
+ const rows = await ctx.subagents.listChildren(caller.id, exec.signal)
135
+ return rows.map((row) => project(ctx.agents, row)).filter(Boolean)
136
+ },
137
+ })
138
+
139
+ /* ---------------------------------------------------------------- send */
140
+
141
+ ctx.tools.register({
142
+ name: 'send_subagent_message',
143
+ description:
144
+ 'Send a follow-up message to a subagent you started. ' +
145
+ 'A running subagent receives it at its next step; a finished one is resumed with it. ' +
146
+ 'Only a direct child accepts a message; use list_subagents to find its id. ' +
147
+ 'This returns delivery confirmation, not the answer — the answer arrives as a new message.',
148
+ parameters: {
149
+ type: 'object',
150
+ additionalProperties: false,
151
+ required: ['subagent_id', 'message'],
152
+ properties: {
153
+ subagent_id: {
154
+ type: 'string',
155
+ description: 'The id of your direct subagent, from list_subagents.',
156
+ },
157
+ message: { type: 'string', description: 'The message to deliver.' },
158
+ },
159
+ },
160
+ output: {
161
+ schema: {
162
+ type: 'object',
163
+ additionalProperties: false,
164
+ required: ['messageId'],
165
+ properties: { messageId: { type: 'string' } },
166
+ },
167
+ render: (args) => [{ type: 'text', text: `message delivered to subagent ${args.subagent_id}` }],
168
+ },
169
+ async execute(args, exec) {
170
+ const caller = requireAgent(exec, 'send_subagent_message')
171
+ const content = [{ type: 'text', text: args.message }]
172
+ const messageId = await ctx.subagents.sendMessage(caller, args.subagent_id, content, {
173
+ signal: exec.signal,
174
+ })
175
+ return { messageId }
176
+ },
177
+ })
178
+
179
+ /* ----------------------------------------------------------- interrupt */
180
+
181
+ ctx.tools.register({
182
+ name: 'interrupt_subagent',
183
+ description:
184
+ 'Ask a subagent under you to stop its current work. Returns without waiting for it to stop. ' +
185
+ 'A subagent that finished on its own is unaffected. ' +
186
+ 'Subagents it started keep running; interrupt those separately with their own ids.',
187
+ parameters: {
188
+ type: 'object',
189
+ additionalProperties: false,
190
+ required: ['subagent_id'],
191
+ properties: {
192
+ subagent_id: {
193
+ type: 'string',
194
+ description:
195
+ 'The id of a subagent created under you: a direct child or any deeper descendant.',
196
+ },
197
+ },
198
+ },
199
+ output: {
200
+ schema: {
201
+ type: 'object',
202
+ additionalProperties: false,
203
+ required: ['accepted'],
204
+ properties: { accepted: { type: 'boolean' } },
205
+ },
206
+ render: (args) => [{ type: 'text', text: `interrupt requested for subagent ${args.subagent_id}` }],
207
+ },
208
+ execute(args, exec) {
209
+ const caller = requireAgent(exec, 'interrupt_subagent')
210
+ ctx.subagents.interrupt(args.subagent_id, { kind: 'ancestor', agent: caller })
211
+ return Promise.resolve({ accepted: true })
212
+ },
213
+ })
214
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Subagent Control",
4
+ "description": "Lets any agent — including a team member — list, steer and stop the subagents it started."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "子代理管理",
4
+ "description": "让任意智能体(包括团队成员)能列出、追问并中止自己派出的子代理。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "dsh-subagent-control",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Three tools — list_subagents, send_subagent_message, interrupt_subagent — that let any agent, including a team member, list, steer and stop the subagents it started.",
5
+ "license": "MIT",
6
+ "author": {
7
+ "name": "SpaceRelay",
8
+ "url": "https://github.com/SpaceRelay"
9
+ },
10
+ "type": "module",
11
+ "main": "index.js",
12
+ "icon": "./icon.svg",
13
+ "exports": {
14
+ ".": "./index.js",
15
+ "./package.json": "./package.json",
16
+ "./locale/*.json": "./locale/*.json",
17
+ "./icon": "./icon.svg"
18
+ },
19
+ "files": [
20
+ "index.js",
21
+ "cordis.patch.yml",
22
+ "locale/*.json",
23
+ "icon.svg",
24
+ "README.md",
25
+ "LICENSE",
26
+ "CHANGELOG.md"
27
+ ],
28
+ "keywords": [
29
+ "dsh",
30
+ "dsh-plugin",
31
+ "deepseek-harness",
32
+ "cordis-plugin",
33
+ "subagent",
34
+ "agents",
35
+ "agent-team",
36
+ "orchestration"
37
+ ],
38
+ "engines": {
39
+ "node": "^22.19.0 || >=24.0.0"
40
+ },
41
+ "scripts": {
42
+ "test": "node test/smoke.mjs"
43
+ },
44
+ "dsh": {
45
+ "bundle": {
46
+ "patch": "./cordis.patch.yml"
47
+ }
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/SpaceRelay/dsh-subagent-control.git"
52
+ },
53
+ "homepage": "https://github.com/SpaceRelay/dsh-subagent-control#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/SpaceRelay/dsh-subagent-control/issues"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }