@nanmicoder/dsh-agent-teams 0.1.23 → 0.1.24
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 +13 -13
- package/README_ZH.md +13 -13
- package/compatibility.json +6 -2
- package/lib/git-artifact-stamp.json +2 -2
- package/lib/harness-compat.js +69 -3
- package/lib/members.js +61 -36
- package/lib/state.js +3 -0
- package/lib/tools.js +12 -20
- package/lib/types/harness-compat.d.ts +22 -1
- package/lib/types/members.d.ts +1 -1
- package/lib/types/types.d.ts +5 -0
- package/package.json +49 -49
- package/pnpm-workspace.yaml +304 -273
- package/release-notes/v0.1.24-rc.1.md +13 -0
- package/release-notes/v0.1.24.md +18 -0
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ Ask in natural language. The plugin provides the team protocol, 14 coordination
|
|
|
36
36
|
|
|
37
37
|
## Releases
|
|
38
38
|
|
|
39
|
-
[v0.1.
|
|
39
|
+
[v0.1.24](./release-notes/v0.1.24.md) adapts member creation, cancellation and recovery to Harness `0.2.1-rc.1`, and preserves member roles and custom tool restrictions across plugin reloads. This stable plugin release uses npm `latest`; the recommended Harness host remains an RC. See the exact [support matrix](./compatibility.json) and [migration evidence](./docs/harness-0.2.1-rc.1/README.md).
|
|
40
40
|
|
|
41
41
|
## Why AgentTeams?
|
|
42
42
|
|
|
@@ -55,33 +55,33 @@ The conversation card and activity panel use Harness's official locale service.
|
|
|
55
55
|
|
|
56
56
|
## Install and choose versions
|
|
57
57
|
|
|
58
|
-
**Recommended pair: DeepSeek Harness `0.2.
|
|
58
|
+
**Recommended pair: DeepSeek Harness `0.2.1-rc.1` + AgentTeams `0.1.24`. The host remains an RC.**
|
|
59
59
|
|
|
60
60
|
| Use case | DeepSeek Harness | AgentTeams plugin |
|
|
61
61
|
| --- | --- | --- |
|
|
62
|
-
| **Recommended** | **`0.2.
|
|
63
|
-
| Previous recommended RC | `0.1.7-rc.2` | `0.1.
|
|
64
|
-
| Legacy RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.
|
|
65
|
-
| Retaining an older RC | `0.1.2-rc.1` | `0.1.
|
|
66
|
-
| Developer Alpha testing | `0.1.2-alpha.5` | `0.1.
|
|
67
|
-
| Retaining an older Alpha | `0.1.2-alpha.2` | `0.1.
|
|
62
|
+
| **Recommended** | **`0.2.1-rc.1`** | **`0.1.24`** |
|
|
63
|
+
| Previous recommended RC | `0.2.0-rc.2`, `0.1.7-rc.2` | `0.1.24` |
|
|
64
|
+
| Legacy RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.24` |
|
|
65
|
+
| Retaining an older RC | `0.1.2-rc.1` | `0.1.24` |
|
|
66
|
+
| Developer Alpha testing | `0.1.2-alpha.5` | `0.1.24` |
|
|
67
|
+
| Retaining an older Alpha | `0.1.2-alpha.2` | `0.1.24` |
|
|
68
68
|
|
|
69
69
|
### Desktop app
|
|
70
70
|
|
|
71
71
|
In the app sidebar, open **Plugins → Add plugin** and enter the npm package spec:
|
|
72
72
|
|
|
73
73
|
```text
|
|
74
|
-
@nanmicoder/dsh-agent-teams@0.1.
|
|
74
|
+
@nanmicoder/dsh-agent-teams@0.1.24
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
Install it, then choose **Enable now**. If the host asks for a restart, restart the desktop app. Use the package spec above for the published build; an npm or GitHub webpage URL is not the same install source. For Git installation, use a commit containing the verified build outputs described below.
|
|
78
78
|
|
|
79
|
-
The desktop app supplies its own Harness core and package manager. Upgrading the global CLI does not upgrade that core, and an ordinary standalone CLI cannot manage its desktop profile. The CLI carrier installed by the desktop app can manage that profile in `0.2.
|
|
79
|
+
The desktop app supplies its own Harness core and package manager. Upgrading the global CLI does not upgrade that core, and an ordinary standalone CLI cannot manage its desktop profile. The CLI carrier installed by the desktop app can manage that profile in `0.2.1-rc.1`. Check the embedded core against the compatibility list. Harness `0.2.1-rc.1` is now the exact published target; its RC status is unchanged. The earlier source-only `0.2.0` prediction is no longer a support declaration. See the [release migration evidence](./docs/harness-0.2.1-rc.1/README.md).
|
|
80
80
|
|
|
81
81
|
### CLI / Web: 1. Install DeepSeek Harness
|
|
82
82
|
|
|
83
83
|
```sh
|
|
84
|
-
npm install --global @deepseek-ai/dsh@0.2.
|
|
84
|
+
npm install --global @deepseek-ai/dsh@0.2.1-rc.1
|
|
85
85
|
dsh --version
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -89,10 +89,10 @@ Skip this if you already run this version. Alpha is opt-in: select an exact Alph
|
|
|
89
89
|
|
|
90
90
|
### CLI / Web: 2. Install the AgentTeams plugin
|
|
91
91
|
|
|
92
|
-
Install or upgrade from npm
|
|
92
|
+
Install or upgrade from npm using the exact stable version:
|
|
93
93
|
|
|
94
94
|
```sh
|
|
95
|
-
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.
|
|
95
|
+
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.24
|
|
96
96
|
```
|
|
97
97
|
|
|
98
98
|
For a CLI-managed installation, replace `web` with your active CLI profile. **Stop and restart that profile's Harness process, then refresh the browser.** Installing the plugin does not upgrade Harness; the host and plugin have independent `latest` channels.
|
package/README_ZH.md
CHANGED
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
|
|
37
37
|
## 版本更新
|
|
38
38
|
|
|
39
|
-
[v0.1.
|
|
39
|
+
[v0.1.24](./release-notes/v0.1.24.md) 适配 Harness `0.2.1-rc.1` 的成员创建、停止和恢复,并在插件热重载后保留成员角色与自定义工具限制。本插件正式版使用 npm `latest` 渠道;推荐的 Harness 宿主仍为 RC。精确范围见 [compatibility.json](./compatibility.json),验收范围见[迁移记录](./docs/harness-0.2.1-rc.1/README.md)。
|
|
40
40
|
|
|
41
41
|
## 为什么需要 AgentTeams?
|
|
42
42
|
|
|
@@ -54,33 +54,33 @@
|
|
|
54
54
|
|
|
55
55
|
## 安装与版本选择
|
|
56
56
|
|
|
57
|
-
**推荐组合:DeepSeek Harness `0.2.
|
|
57
|
+
**推荐组合:DeepSeek Harness `0.2.1-rc.1` + AgentTeams `0.1.24`。宿主仍为 RC 版本。**
|
|
58
58
|
|
|
59
59
|
| 使用场景 | DeepSeek Harness | AgentTeams 插件 |
|
|
60
60
|
| --- | --- | --- |
|
|
61
|
-
| **推荐版本** | **`0.2.
|
|
62
|
-
| 上一推荐 RC | `0.1.7-rc.2` | `0.1.
|
|
63
|
-
| 保留旧 RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.
|
|
64
|
-
| 保留旧 RC | `0.1.2-rc.1` | `0.1.
|
|
65
|
-
| 开发者测试 Alpha | `0.1.2-alpha.5` | `0.1.
|
|
66
|
-
| 保留旧 Alpha | `0.1.2-alpha.2` | `0.1.
|
|
61
|
+
| **推荐版本** | **`0.2.1-rc.1`** | **`0.1.24`** |
|
|
62
|
+
| 上一推荐 RC | `0.2.0-rc.2`, `0.1.7-rc.2` | `0.1.24` |
|
|
63
|
+
| 保留旧 RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.24` |
|
|
64
|
+
| 保留旧 RC | `0.1.2-rc.1` | `0.1.24` |
|
|
65
|
+
| 开发者测试 Alpha | `0.1.2-alpha.5` | `0.1.24` |
|
|
66
|
+
| 保留旧 Alpha | `0.1.2-alpha.2` | `0.1.24` |
|
|
67
67
|
|
|
68
68
|
### 桌面端
|
|
69
69
|
|
|
70
70
|
在应用侧栏打开「插件 → 添加插件」,输入 npm 包名和版本:
|
|
71
71
|
|
|
72
72
|
```text
|
|
73
|
-
@nanmicoder/dsh-agent-teams@0.1.
|
|
73
|
+
@nanmicoder/dsh-agent-teams@0.1.24
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
安装完成后点击「立即启用」;宿主提示需要重启时,重启桌面应用。发布版请使用上面的包名,npm 或 GitHub 网页链接与 npm 包名不是同一种安装来源。从 Git 安装时,请选用包含下方所述已验证构建产物的提交。
|
|
77
77
|
|
|
78
|
-
桌面端自带 Harness 内核与包管理器,升级全局 CLI 不会升级桌面端;普通独立 CLI 不能管理桌面 profile,桌面端自带的 CLI carrier 在此版本可以管理。当前精确目标是已发布的 `0.2.
|
|
78
|
+
桌面端自带 Harness 内核与包管理器,升级全局 CLI 不会升级桌面端;普通独立 CLI 不能管理桌面 profile,桌面端自带的 CLI carrier 在此版本可以管理。当前精确目标是已发布的 `0.2.1-rc.1`,仍属于 RC;此前源码预测的 `0.2.0` 已退出支持声明。详见[迁移验收记录](./docs/harness-0.2.1-rc.1/README.md)。
|
|
79
79
|
|
|
80
80
|
### CLI / Web:1. 安装 DeepSeek Harness
|
|
81
81
|
|
|
82
82
|
```sh
|
|
83
|
-
npm install --global @deepseek-ai/dsh@0.2.
|
|
83
|
+
npm install --global @deepseek-ai/dsh@0.2.1-rc.1
|
|
84
84
|
dsh --version
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -88,10 +88,10 @@ dsh --version
|
|
|
88
88
|
|
|
89
89
|
### CLI / Web:2. 安装 AgentTeams 插件
|
|
90
90
|
|
|
91
|
-
从 npm
|
|
91
|
+
从 npm 安装或升级插件(锁定精确正式版本):
|
|
92
92
|
|
|
93
93
|
```sh
|
|
94
|
-
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.
|
|
94
|
+
dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.24
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
CLI 管理的安装可将 `web` 换成实际使用的 CLI profile。**安装后,停止并重新启动该 profile 的 Harness 进程,再刷新浏览器。** 安装插件不会自动升级宿主;Harness 的 `latest` 与插件的 `latest` 是两个独立渠道。
|
package/compatibility.json
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"recommendedHost": "0.2.
|
|
3
|
+
"recommendedHost": "0.2.1-rc.1",
|
|
4
4
|
"previewTag": "next",
|
|
5
5
|
"supportedHosts": [
|
|
6
6
|
{
|
|
7
|
-
"version": "0.2.
|
|
7
|
+
"version": "0.2.1-rc.1",
|
|
8
8
|
"track": "recommended"
|
|
9
9
|
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.2.0-rc.2",
|
|
12
|
+
"track": "legacy"
|
|
13
|
+
},
|
|
10
14
|
{
|
|
11
15
|
"version": "0.1.7-rc.2",
|
|
12
16
|
"track": "legacy"
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
|
-
"source": "
|
|
4
|
-
"output": "
|
|
3
|
+
"source": "8ae33842b0876c9d64f7625f1c46c3f184396d5c960028ed8d8395869634389c",
|
|
4
|
+
"output": "bb2031aa6d907a929ad81d99ab74f6f2c2136ce1f3a1e9bb68006453bff43908"
|
|
5
5
|
}
|
package/lib/harness-compat.js
CHANGED
|
@@ -13,6 +13,52 @@ const hostPromptQueue = Symbol.for('dsh.subagent.queuePrompt');
|
|
|
13
13
|
// 0.1.5 keeps queueHostSubagentPrompt but replaces its symbol with this
|
|
14
14
|
// delivery-mode-aware implementation (packages/subagent/subagent/src/internal.ts).
|
|
15
15
|
const hostPromptDeliver = Symbol.for('dsh.subagent.deliverPrompt');
|
|
16
|
+
export function usesManagedActivations(runtime) {
|
|
17
|
+
return typeof runtime.startActivation === 'function';
|
|
18
|
+
}
|
|
19
|
+
export async function startMember(runtime, spec) {
|
|
20
|
+
const host = runtime;
|
|
21
|
+
if (typeof host.startActivation === 'function') {
|
|
22
|
+
const { persona: _persona, toolFilter: _filter, ...request } = spec.request;
|
|
23
|
+
const activation = await host.startActivation.call(runtime, { ...spec, request, delivery: 'parent' });
|
|
24
|
+
// Preserve native settlement as the missing-report/error fallback on both
|
|
25
|
+
// fresh and resumed activations. mailbox.ts deduplicates successful reports.
|
|
26
|
+
void activation.result.catch(() => undefined);
|
|
27
|
+
return activation;
|
|
28
|
+
}
|
|
29
|
+
if (typeof host.startContinuable !== 'function')
|
|
30
|
+
return unsupported('missing member creation API');
|
|
31
|
+
return host.startContinuable.call(runtime, spec);
|
|
32
|
+
}
|
|
33
|
+
/** Wrap every available creation API, restoring only this plugin's own methods. */
|
|
34
|
+
export function guardSubagentCreation(ctx, check) {
|
|
35
|
+
const runtime = ctx.subagents;
|
|
36
|
+
ctx.effect(() => {
|
|
37
|
+
const disposers = [];
|
|
38
|
+
for (const key of ['start', 'startContinuable', 'startActivation']) {
|
|
39
|
+
const original = runtime[key];
|
|
40
|
+
if (typeof original !== 'function')
|
|
41
|
+
continue;
|
|
42
|
+
const descriptor = Object.getOwnPropertyDescriptor(runtime, key);
|
|
43
|
+
const guarded = async (...args) => {
|
|
44
|
+
const request = key === 'start' ? args[1] : args[0].request;
|
|
45
|
+
check(request.parent);
|
|
46
|
+
return Reflect.apply(original, ctx.subagents, args);
|
|
47
|
+
};
|
|
48
|
+
Object.defineProperty(runtime, key, { value: guarded, writable: true, configurable: true });
|
|
49
|
+
disposers.push(() => {
|
|
50
|
+
if (Object.getOwnPropertyDescriptor(runtime, key)?.value !== guarded)
|
|
51
|
+
return;
|
|
52
|
+
if (descriptor === undefined)
|
|
53
|
+
Reflect.deleteProperty(runtime, key);
|
|
54
|
+
else
|
|
55
|
+
Object.defineProperty(runtime, key, descriptor);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return () => { for (const dispose of disposers)
|
|
59
|
+
dispose(); };
|
|
60
|
+
}, 'agent-teams: member delegation budget');
|
|
61
|
+
}
|
|
16
62
|
function boundary(runtime) {
|
|
17
63
|
return runtime;
|
|
18
64
|
}
|
|
@@ -101,7 +147,7 @@ export function installContinuableMemberSetup(ctx, setup) {
|
|
|
101
147
|
const installed = new WeakSet();
|
|
102
148
|
const active = new Set();
|
|
103
149
|
ctx.effect(() => {
|
|
104
|
-
const
|
|
150
|
+
const attach = (agent, vetoable) => {
|
|
105
151
|
if (installed.has(agent))
|
|
106
152
|
return;
|
|
107
153
|
// Deliberately synchronous: awaiting here loses the first-request race.
|
|
@@ -139,12 +185,24 @@ export function installContinuableMemberSetup(ctx, setup) {
|
|
|
139
185
|
dispose();
|
|
140
186
|
throw error;
|
|
141
187
|
}
|
|
142
|
-
}
|
|
143
|
-
|
|
188
|
+
};
|
|
189
|
+
const stop = onAgentReady(ctx, attach);
|
|
190
|
+
const cleanup = () => {
|
|
144
191
|
stop();
|
|
145
192
|
for (const dispose of [...active])
|
|
146
193
|
dispose();
|
|
147
194
|
};
|
|
195
|
+
try {
|
|
196
|
+
// Reloads keep Agents alive but dispose the previous plugin's setup.
|
|
197
|
+
// Subscribe first, then hydrate the live registry; attach is idempotent.
|
|
198
|
+
for (const agent of ctx.agents.list())
|
|
199
|
+
attach(agent, false);
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
cleanup();
|
|
203
|
+
throw error;
|
|
204
|
+
}
|
|
205
|
+
return cleanup;
|
|
148
206
|
}, 'agent-teams: member lifecycle compatibility');
|
|
149
207
|
}
|
|
150
208
|
/** Queue a distinct host-authored turn; never substitute model-message steer. */
|
|
@@ -349,3 +407,11 @@ export async function startMemberWithLenientFilter(start, filter, relaxableDeny
|
|
|
349
407
|
}
|
|
350
408
|
}
|
|
351
409
|
}
|
|
410
|
+
/** Targeted recursive cancellation renamed with the unified activation manager. */
|
|
411
|
+
export async function drainMemberChildren(runtime, parent, ids) {
|
|
412
|
+
const host = runtime;
|
|
413
|
+
const drain = host.drainChildren ?? host.drainContinuableChildren;
|
|
414
|
+
if (typeof drain !== 'function')
|
|
415
|
+
return unsupported('missing targeted child drain');
|
|
416
|
+
await drain.call(runtime, parent, ids);
|
|
417
|
+
}
|
package/lib/members.js
CHANGED
|
@@ -16,7 +16,7 @@ import { installModelSelection } from '@deepseek-ai/dsh-agent';
|
|
|
16
16
|
import { foldSubagentDescriptor } from '@deepseek-ai/dsh-subagent';
|
|
17
17
|
import { createUserMessage, LlmError, ReasoningEffortId } from '@deepseek-ai/dsh-llm';
|
|
18
18
|
import { join } from 'node:path';
|
|
19
|
-
import { guardSubagentDelivery, installContinuableMemberSetup, memberToolFilter, queueMemberPrompt, restrictableToolNames, startMemberWithLenientFilter, steerMemberPrompt, sessionOwnEvents } from "./harness-compat.js";
|
|
19
|
+
import { guardSubagentCreation, startMember, usesManagedActivations, guardSubagentDelivery, installContinuableMemberSetup, memberToolFilter, queueMemberPrompt, restrictableToolNames, startMemberWithLenientFilter, steerMemberPrompt, sessionOwnEvents } from "./harness-compat.js";
|
|
20
20
|
import { markMailboxDelivered, appendMailbox, CAPTAIN_KEY, createMessage, readRetiredMemberIds, readTeamSync, readTeam, releaseMailboxDelivery, withTeamLock, writeTeam } from "./state.js";
|
|
21
21
|
import { mailboxPrompt } from "./mailbox.js";
|
|
22
22
|
import { nearestLiveLead } from "./lineage.js";
|
|
@@ -478,6 +478,7 @@ function describeMemberRouteFailure(provider, model, error) {
|
|
|
478
478
|
*/
|
|
479
479
|
export function installMemberSelectionRuntime(ctx, stateDir, onFailureSettled) {
|
|
480
480
|
const pending = new Map();
|
|
481
|
+
const pendingSetup = new Map();
|
|
481
482
|
installContinuableMemberSetup(ctx, (childCtx, child) => {
|
|
482
483
|
const descriptor = foldSubagentDescriptor(sessionOwnEvents(child.session));
|
|
483
484
|
if (descriptor?.mode !== 'continuable' || !descriptor.label.startsWith(MEMBER_LABEL_PREFIX)) {
|
|
@@ -510,11 +511,47 @@ export function installMemberSelectionRuntime(ctx, stateDir, onFailureSettled) {
|
|
|
510
511
|
return { kind: 'reject' };
|
|
511
512
|
return next();
|
|
512
513
|
});
|
|
514
|
+
// New activations no longer persist persona/toolFilter in host descriptors.
|
|
515
|
+
// Install them before the first request, and restore the exact saved values
|
|
516
|
+
// after a cold resume. Historical descriptors remain owned by Harness.
|
|
517
|
+
const savedTeam = pending.has(key) ? undefined : readTeamSync(stateRoot, teamId);
|
|
518
|
+
const savedMember = savedTeam?.captainSessionId === parentSessionId
|
|
519
|
+
? savedTeam.members.find(member => member.id === child.id && member.name === memberName)
|
|
520
|
+
: undefined;
|
|
521
|
+
const setup = pendingSetup.get(key) ?? savedMember?.runtimeSetup;
|
|
522
|
+
if (usesManagedActivations(ctx.subagents) && savedMember !== undefined
|
|
523
|
+
&& setup === undefined && descriptor.toolFilter === undefined) {
|
|
524
|
+
disposeAdmission();
|
|
525
|
+
throw new Error('agent-teams: saved member runtime setup is missing; refusing an unrestricted resume');
|
|
526
|
+
}
|
|
527
|
+
let disposeSetup = () => undefined;
|
|
528
|
+
if (usesManagedActivations(ctx.subagents) && setup !== undefined) {
|
|
529
|
+
try {
|
|
530
|
+
const stopFilter = childCtx.tools.restrict(setup.toolFilter);
|
|
531
|
+
try {
|
|
532
|
+
const stopPersona = childCtx.systemPrompt.section({
|
|
533
|
+
name: 'deployment:persona-prefix',
|
|
534
|
+
order: childCtx.systemPrompt.getSectionOrder('DEPLOYMENT_PERSONA_PREFIX'),
|
|
535
|
+
text: setup.persona,
|
|
536
|
+
});
|
|
537
|
+
disposeSetup = () => { stopPersona(); stopFilter(); };
|
|
538
|
+
}
|
|
539
|
+
catch (error) {
|
|
540
|
+
stopFilter();
|
|
541
|
+
throw error;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
disposeAdmission();
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
const cleanupAdmission = () => { disposeSetup(); disposeAdmission(); };
|
|
513
550
|
let selection = pending.get(key);
|
|
514
551
|
if (selection === undefined) {
|
|
515
552
|
const team = readTeamSync(stateRoot, teamId);
|
|
516
553
|
if (team?.captainSessionId !== parentSessionId)
|
|
517
|
-
return
|
|
554
|
+
return cleanupAdmission;
|
|
518
555
|
const durableMember = team.members.find(member => member.name === memberName);
|
|
519
556
|
selection = selectionFromMember(durableMember);
|
|
520
557
|
if (selection !== undefined && (descriptor.agentProvider !== durableMember?.provider || descriptor.agentModel !== durableMember?.model)) {
|
|
@@ -571,7 +608,7 @@ export function installMemberSelectionRuntime(ctx, stateDir, onFailureSettled) {
|
|
|
571
608
|
});
|
|
572
609
|
// Legacy teams still need failure reporting even without a saved route.
|
|
573
610
|
if (selection === undefined)
|
|
574
|
-
return () => { disposeFailure();
|
|
611
|
+
return () => { disposeFailure(); cleanupAdmission(); };
|
|
575
612
|
const selectionRef = { current: modelSelection(selection), assembled: undefined };
|
|
576
613
|
const disposeSelection = installModelSelection(childCtx, selectionRef);
|
|
577
614
|
const fallback = selection.fallback;
|
|
@@ -601,7 +638,7 @@ export function installMemberSelectionRuntime(ctx, stateDir, onFailureSettled) {
|
|
|
601
638
|
disposeFallback();
|
|
602
639
|
disposeSelection();
|
|
603
640
|
disposeFailure();
|
|
604
|
-
|
|
641
|
+
cleanupAdmission();
|
|
605
642
|
};
|
|
606
643
|
});
|
|
607
644
|
return {
|
|
@@ -611,17 +648,20 @@ export function installMemberSelectionRuntime(ctx, stateDir, onFailureSettled) {
|
|
|
611
648
|
return parent !== undefined && descriptor?.mode === 'continuable'
|
|
612
649
|
&& pending.has(pendingSelectionKey(parent, descriptor.label));
|
|
613
650
|
},
|
|
614
|
-
async withPending(parentSessionId, label, selection, operation) {
|
|
651
|
+
async withPending(parentSessionId, label, selection, operation, setup) {
|
|
615
652
|
const key = pendingSelectionKey(parentSessionId, label);
|
|
616
653
|
if (pending.has(key)) {
|
|
617
654
|
throw new Error(`member model selection is already pending for "${label}"`);
|
|
618
655
|
}
|
|
619
656
|
pending.set(key, selection);
|
|
657
|
+
if (setup !== undefined)
|
|
658
|
+
pendingSetup.set(key, setup);
|
|
620
659
|
try {
|
|
621
660
|
return await operation();
|
|
622
661
|
}
|
|
623
662
|
finally {
|
|
624
663
|
pending.delete(key);
|
|
664
|
+
pendingSetup.delete(key);
|
|
625
665
|
}
|
|
626
666
|
},
|
|
627
667
|
};
|
|
@@ -729,10 +769,11 @@ export async function spawnMember(ctx, config, selections, llmSelection, captain
|
|
|
729
769
|
if (provider.prepareContinuable === undefined) {
|
|
730
770
|
throw new Error(`agent-teams: provider "${config.provider}" does not support continuable members`);
|
|
731
771
|
}
|
|
732
|
-
|
|
772
|
+
const legacyCapabilities = provider.capabilities;
|
|
773
|
+
if (!usesManagedActivations(ctx.subagents) && !legacyCapabilities.persona) {
|
|
733
774
|
throw new Error(`agent-teams: provider "${config.provider}" cannot apply a member persona`);
|
|
734
775
|
}
|
|
735
|
-
if (!
|
|
776
|
+
if (!usesManagedActivations(ctx.subagents) && !legacyCapabilities.toolFilter) {
|
|
736
777
|
throw new Error(`agent-teams: provider "${config.provider}" cannot restrict captain-only tools for members`);
|
|
737
778
|
}
|
|
738
779
|
const label = `${MEMBER_LABEL_PREFIX}${team.id}:${member.name}`;
|
|
@@ -740,11 +781,12 @@ export async function spawnMember(ctx, config, selections, llmSelection, captain
|
|
|
740
781
|
// captain-only deny list plus the depth entries this host registers.
|
|
741
782
|
const requestedFilter = resolvedFilter ?? memberToolFilter(config.maxDepth, restrictableToolNames(captain));
|
|
742
783
|
// The lenient retry may drop names this host reports unknown; keep the exact
|
|
743
|
-
// filter the host accepted
|
|
784
|
+
// filter the host accepted for the durable member setup.
|
|
744
785
|
let acceptedFilter = requestedFilter;
|
|
745
|
-
const start = await
|
|
786
|
+
const start = await startMemberWithLenientFilter(toolFilter => {
|
|
746
787
|
acceptedFilter = toolFilter;
|
|
747
|
-
|
|
788
|
+
const setup = { persona: memberPersona(team, member, stateDir, config.executionPrompt), toolFilter };
|
|
789
|
+
return selections.withPending(captain.id, label, llmSelection, () => startMember(ctx.subagents, {
|
|
748
790
|
provider: config.provider,
|
|
749
791
|
label,
|
|
750
792
|
request: {
|
|
@@ -769,12 +811,15 @@ export async function spawnMember(ctx, config, selections, llmSelection, captain
|
|
|
769
811
|
// the configured member-relative descendant budget instead.
|
|
770
812
|
},
|
|
771
813
|
signal,
|
|
772
|
-
});
|
|
773
|
-
}, requestedFilter, relaxableDeny)
|
|
814
|
+
}), setup);
|
|
815
|
+
}, requestedFilter, relaxableDeny);
|
|
774
816
|
member.id = start.childId;
|
|
817
|
+
if (usesManagedActivations(ctx.subagents)) {
|
|
818
|
+
member.runtimeSetup = { persona: memberPersona(team, member, stateDir, config.executionPrompt), toolFilter: acceptedFilter };
|
|
819
|
+
}
|
|
775
820
|
// Only a policy member carries a persisted filter snapshot; a member without
|
|
776
|
-
// a policy keeps
|
|
777
|
-
//
|
|
821
|
+
// a policy keeps its public record shape. Legacy descriptors or runtimeSetup
|
|
822
|
+
// restore the filter on cold resume, depending on the host generation.
|
|
778
823
|
if (member.toolFilter !== undefined)
|
|
779
824
|
member.toolFilter = { ...acceptedFilter };
|
|
780
825
|
}
|
|
@@ -844,7 +889,6 @@ export function installRetiredMemberGuard(ctx, stateDir) {
|
|
|
844
889
|
}
|
|
845
890
|
/** Bound all descendant creation, including renamed tools and code-runtime calls. */
|
|
846
891
|
export function installMemberDelegationGuard(ctx, stateDir, maxDepth) {
|
|
847
|
-
const runtime = ctx.subagents;
|
|
848
892
|
let active = true;
|
|
849
893
|
const check = (parent) => {
|
|
850
894
|
if (!active)
|
|
@@ -878,27 +922,8 @@ export function installMemberDelegationGuard(ctx, stateDir, maxDepth) {
|
|
|
878
922
|
depth++;
|
|
879
923
|
}
|
|
880
924
|
};
|
|
881
|
-
ctx
|
|
882
|
-
|
|
883
|
-
const continuable = runtime.startContinuable;
|
|
884
|
-
const startDescriptor = Object.getOwnPropertyDescriptor(runtime, 'start');
|
|
885
|
-
const continuableDescriptor = Object.getOwnPropertyDescriptor(runtime, 'startContinuable');
|
|
886
|
-
const guardedStart = async (name, request) => { check(request.parent); return start.call(runtime, name, request); };
|
|
887
|
-
const guardedContinuable = async (spec) => { check(spec.request.parent); return continuable.call(runtime, spec); };
|
|
888
|
-
runtime.start = guardedStart;
|
|
889
|
-
runtime.startContinuable = guardedContinuable;
|
|
890
|
-
return () => {
|
|
891
|
-
active = false;
|
|
892
|
-
for (const [key, fn, descriptor] of [['start', guardedStart, startDescriptor], ['startContinuable', guardedContinuable, continuableDescriptor]]) {
|
|
893
|
-
if (Object.getOwnPropertyDescriptor(runtime, key)?.value !== fn)
|
|
894
|
-
continue;
|
|
895
|
-
if (descriptor === undefined)
|
|
896
|
-
Reflect.deleteProperty(runtime, key);
|
|
897
|
-
else
|
|
898
|
-
Object.defineProperty(runtime, key, descriptor);
|
|
899
|
-
}
|
|
900
|
-
};
|
|
901
|
-
}, 'agent-teams: member delegation budget');
|
|
925
|
+
guardSubagentCreation(ctx, check);
|
|
926
|
+
ctx.effect(() => () => { active = false; }, 'agent-teams: delegation guard lifetime');
|
|
902
927
|
}
|
|
903
928
|
/**
|
|
904
929
|
* Snapshot the real driver activity for durable member ids.
|
package/lib/state.js
CHANGED
|
@@ -665,6 +665,9 @@ function isTeamMember(value) {
|
|
|
665
665
|
// A member without a policy keeps the legacy shape: both fields stay absent.
|
|
666
666
|
&& isOptionalNonBlankString(value['toolPolicy'])
|
|
667
667
|
&& (value['toolFilter'] === undefined || isMemberToolFilter(value['toolFilter']))
|
|
668
|
+
&& (value['runtimeSetup'] === undefined || (isRecord(value['runtimeSetup'])
|
|
669
|
+
&& typeof value['runtimeSetup']['persona'] === 'string'
|
|
670
|
+
&& isMemberToolFilter(value['runtimeSetup']['toolFilter'])))
|
|
668
671
|
&& isFiniteNumber(value['joinedAt'])
|
|
669
672
|
&& (value['status'] === 'idle' || value['status'] === 'working' || value['status'] === 'removed');
|
|
670
673
|
}
|
package/lib/tools.js
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The `agent_teams_*` model-facing tools.
|
|
3
|
-
*
|
|
4
|
-
* The captain (the agent that created the team) orchestrates: members are
|
|
5
|
-
* continuable subagents it spawns and wakes. Members share the same tools and
|
|
6
|
-
* drive their own task state, mirroring the Claude Code AgentTeams flow:
|
|
7
|
-
* create team → add members → create tasks with dependencies → claim/assign →
|
|
8
|
-
* work → report → status → delete.
|
|
9
|
-
* @module dsh-agent-teams/tools
|
|
10
|
-
*/
|
|
1
|
+
import { drainMemberChildren } from "./harness-compat.js";
|
|
11
2
|
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
12
3
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
13
4
|
import { join } from 'node:path';
|
|
@@ -373,7 +364,7 @@ async function stopTeamMemberActivations(ctx, captain, members, signal) {
|
|
|
373
364
|
// Every supported exact host has targeted recursive drain. Unlike interrupt,
|
|
374
365
|
// it closes admission and clears queued work before awaiting descendants.
|
|
375
366
|
signal?.throwIfAborted();
|
|
376
|
-
await ctx.subagents
|
|
367
|
+
await drainMemberChildren(ctx.subagents, captain, memberIds);
|
|
377
368
|
}
|
|
378
369
|
export async function haltTeamWork(input) {
|
|
379
370
|
const halted = await withTeamLock(teamLockKey(input.stateRoot, input.teamId), async () => {
|
|
@@ -480,7 +471,6 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
480
471
|
installMemberDelegationGuard(ctx, config.stateDir, config.memberMaxDepth ?? 0);
|
|
481
472
|
installMailboxAdmission(ctx, config.stateDir);
|
|
482
473
|
const scheduler = installTeamScheduler(ctx, { stateDir: config.stateDir, executionPrompt: config.executionPrompt, dispatch: dispatchMember });
|
|
483
|
-
const memberSelections = installMemberSelectionRuntime(ctx, config.stateDir, (workspace, teamId, memberName) => (scheduler.kickMember(workspace, teamId, memberName)));
|
|
484
474
|
async function dispatchMember(captain, teamId, memberName, text, signal, mode, attemptId) {
|
|
485
475
|
const root = stateRootOf(workspaceOf(captain), config);
|
|
486
476
|
// Record why a member never started. The scheduler treats a failed dispatch as
|
|
@@ -765,14 +755,6 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
765
755
|
captain.cancel({ kind: 'user' }, { keepInbox: true });
|
|
766
756
|
return { teamId: discarded.teamId };
|
|
767
757
|
};
|
|
768
|
-
const runtime = {
|
|
769
|
-
isPendingMember: memberSelections.isPendingMember,
|
|
770
|
-
updateStagedPlan,
|
|
771
|
-
updateStagedPlanBatch,
|
|
772
|
-
approveStagedTeam,
|
|
773
|
-
continueStagedPlanning,
|
|
774
|
-
discardStagedTeam,
|
|
775
|
-
};
|
|
776
758
|
ctx.tools.register(defineTool({
|
|
777
759
|
name: 'agent_teams_create',
|
|
778
760
|
description: 'Create a team. Use approval=required for a two-phase plan: members and tasks remain unspawned/unclaimed until the user reviews the Web plan and explicitly approves it. Optional profiles expand their configured roster; seed profiles also expand template tasks, while captain profiles leave the graph for the Captain to design. approval=automatic preserves the legacy immediate-execution path.',
|
|
@@ -2381,6 +2363,16 @@ export function registerAgentTeamsTools(ctx, config) {
|
|
|
2381
2363
|
return { deleted: true, team_name: team.name };
|
|
2382
2364
|
},
|
|
2383
2365
|
}));
|
|
2366
|
+
// Live-member hydration validates saved filters against registered tools.
|
|
2367
|
+
const memberSelections = installMemberSelectionRuntime(ctx, config.stateDir, (workspace, teamId, memberName) => (scheduler.kickMember(workspace, teamId, memberName)));
|
|
2368
|
+
const runtime = {
|
|
2369
|
+
isPendingMember: memberSelections.isPendingMember,
|
|
2370
|
+
updateStagedPlan,
|
|
2371
|
+
updateStagedPlanBatch,
|
|
2372
|
+
approveStagedTeam,
|
|
2373
|
+
continueStagedPlanning,
|
|
2374
|
+
discardStagedTeam,
|
|
2375
|
+
};
|
|
2384
2376
|
return runtime;
|
|
2385
2377
|
}
|
|
2386
2378
|
async function initializeProfileTeam(input) {
|
|
@@ -8,11 +8,30 @@
|
|
|
8
8
|
* sendMessage instead steers a running Agent and must never carry team jobs.
|
|
9
9
|
*/
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis';
|
|
11
|
-
import type { Agent } from '@deepseek-ai/dsh-agent';
|
|
11
|
+
import type { Agent, AgentOptions } from '@deepseek-ai/dsh-agent';
|
|
12
12
|
import type { ContentBlock, MessageId, MessageSource } from '@deepseek-ai/dsh-llm';
|
|
13
13
|
import type { Session, SessionEvent, SessionId } from '@deepseek-ai/dsh-session';
|
|
14
14
|
import { type MemberToolFilter } from './tool-policies.ts';
|
|
15
15
|
type Setup = (childCtx: Context, child: Agent) => () => void;
|
|
16
|
+
/** Version-independent creation contract; legacy-only options stay at this seam. */
|
|
17
|
+
export interface MemberStartSpec {
|
|
18
|
+
provider: string;
|
|
19
|
+
label: string;
|
|
20
|
+
request: {
|
|
21
|
+
parent: Agent;
|
|
22
|
+
prompt: ContentBlock[];
|
|
23
|
+
agentOptions?: AgentOptions;
|
|
24
|
+
persona?: string;
|
|
25
|
+
toolFilter?: MemberToolFilter;
|
|
26
|
+
};
|
|
27
|
+
signal: AbortSignal;
|
|
28
|
+
}
|
|
29
|
+
export declare function usesManagedActivations(runtime: Context['subagents']): boolean;
|
|
30
|
+
export declare function startMember(runtime: Context['subagents'], spec: MemberStartSpec): Promise<{
|
|
31
|
+
childId: SessionId;
|
|
32
|
+
}>;
|
|
33
|
+
/** Wrap every available creation API, restoring only this plugin's own methods. */
|
|
34
|
+
export declare function guardSubagentCreation(ctx: Context, check: (parent: Agent) => void): void;
|
|
16
35
|
declare module '@deepseek-ai/dsh-llm' {
|
|
17
36
|
interface MessageSourceMap {
|
|
18
37
|
'agent-teams': {
|
|
@@ -121,4 +140,6 @@ export declare function memberToolFilter(maxDepth: number | undefined, knownTool
|
|
|
121
140
|
* @returns the started member.
|
|
122
141
|
*/
|
|
123
142
|
export declare function startMemberWithLenientFilter<T>(start: (filter: MemberToolFilter) => Promise<T>, filter: MemberToolFilter, relaxableDeny?: readonly string[]): Promise<T>;
|
|
143
|
+
/** Targeted recursive cancellation renamed with the unified activation manager. */
|
|
144
|
+
export declare function drainMemberChildren(runtime: Context['subagents'], parent: Agent, ids: readonly SessionId[]): Promise<void>;
|
|
124
145
|
export {};
|
package/lib/types/members.d.ts
CHANGED
|
@@ -108,7 +108,7 @@ export interface MemberSelectionRuntime {
|
|
|
108
108
|
/** Trusted fresh-child admission, before the durable member id is written. */
|
|
109
109
|
isPendingMember(agent: Agent): boolean;
|
|
110
110
|
/** Make one selection visible while Harness materializes the fresh child. */
|
|
111
|
-
withPending<T>(parentSessionId: string, label: string, selection: MemberLlmSelection, operation: () => Promise<T
|
|
111
|
+
withPending<T>(parentSessionId: string, label: string, selection: MemberLlmSelection, operation: () => Promise<T>, setup?: TeamMember['runtimeSetup']): Promise<T>;
|
|
112
112
|
}
|
|
113
113
|
/**
|
|
114
114
|
* Validate a resolved roster against every provider catalog before any child
|
package/lib/types/types.d.ts
CHANGED
|
@@ -252,6 +252,11 @@ export interface TeamMember {
|
|
|
252
252
|
* authority that reapplies the filter to a cold-resumed child.
|
|
253
253
|
*/
|
|
254
254
|
toolFilter?: MemberToolFilter;
|
|
255
|
+
/** Exact child-scoped setup restored on hosts with managed activations. */
|
|
256
|
+
runtimeSetup?: {
|
|
257
|
+
persona: string;
|
|
258
|
+
toolFilter: MemberToolFilter;
|
|
259
|
+
};
|
|
255
260
|
joinedAt: number;
|
|
256
261
|
status: MemberStatus;
|
|
257
262
|
/** Execution admission is closed while a failed/pending handoff is drained. */
|