@nanmicoder/dsh-agent-teams 0.1.23 → 0.1.24-rc.1

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
@@ -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.23](./release-notes/v0.1.23.md) fixes team identity, member model routing, quality and repair gates, and cold fork recovery, and adds owner-configured member tool policies. This plugin release uses npm `latest`; the recommended host remains Harness `0.2.0-rc.2` (its `next` channel). See the exact [support matrix](./compatibility.json).
39
+ [v0.1.24-rc.1](./release-notes/v0.1.24-rc.1.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. Release channel: npm `next` (prerelease). Check [npm versions](https://www.npmjs.com/package/@nanmicoder/dsh-agent-teams?activeTab=versions) for publication status before installing. 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.0-rc.2` + AgentTeams `0.1.23`. The host remains an RC.**
58
+ **Recommended pair: DeepSeek Harness `0.2.1-rc.1` + AgentTeams `0.1.24-rc.1`. The host remains an RC.**
59
59
 
60
60
  | Use case | DeepSeek Harness | AgentTeams plugin |
61
61
  | --- | --- | --- |
62
- | **Recommended** | **`0.2.0-rc.2`** | **`0.1.23`** |
63
- | Previous recommended RC | `0.1.7-rc.2` | `0.1.23` |
64
- | Legacy RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.23` |
65
- | Retaining an older RC | `0.1.2-rc.1` | `0.1.23` |
66
- | Developer Alpha testing | `0.1.2-alpha.5` | `0.1.23` |
67
- | Retaining an older Alpha | `0.1.2-alpha.2` | `0.1.23` |
62
+ | **Recommended** | **`0.2.1-rc.1`** | **`0.1.24-rc.1`** |
63
+ | Previous recommended RC | `0.2.0-rc.2`, `0.1.7-rc.2` | `0.1.24-rc.1` |
64
+ | Legacy RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.24-rc.1` |
65
+ | Retaining an older RC | `0.1.2-rc.1` | `0.1.24-rc.1` |
66
+ | Developer Alpha testing | `0.1.2-alpha.5` | `0.1.24-rc.1` |
67
+ | Retaining an older Alpha | `0.1.2-alpha.2` | `0.1.24-rc.1` |
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.23
74
+ @nanmicoder/dsh-agent-teams@0.1.24-rc.1
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.0-rc.2`. Check the embedded core against the compatibility list. Harness `0.2.0-rc.2` 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.0-rc.2/README.md).
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.0-rc.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 (pinned to this `latest` release):
92
+ After this version is published, install or upgrade from npm using the exact prerelease version:
93
93
 
94
94
  ```sh
95
- dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.23
95
+ dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.24-rc.1
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.23](./release-notes/v0.1.23.md) 修复团队标识、成员模型路由、质量与修复门禁及 fork 冷恢复,并新增由工作区所有者配置的成员工具策略。本插件正式版使用 npm `latest` 渠道;推荐宿主仍为 Harness `0.2.0-rc.2`(宿主的 `next` 渠道)。精确支持范围见 [compatibility.json](./compatibility.json)。
39
+ [v0.1.24-rc.1](./release-notes/v0.1.24-rc.1.md) 适配 Harness `0.2.1-rc.1` 的成员创建、停止和恢复,并在插件热重载后保留成员角色与自定义工具限制。发布渠道为 npm `next`(预发布);安装前请在 [npm 版本列表](https://www.npmjs.com/package/@nanmicoder/dsh-agent-teams?activeTab=versions) 确认发布状态。精确范围见 [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.0-rc.2` + AgentTeams `0.1.23`。宿主仍为 RC 版本。**
57
+ **推荐组合:DeepSeek Harness `0.2.1-rc.1` + AgentTeams `0.1.24-rc.1`。宿主仍为 RC 版本。**
58
58
 
59
59
  | 使用场景 | DeepSeek Harness | AgentTeams 插件 |
60
60
  | --- | --- | --- |
61
- | **推荐版本** | **`0.2.0-rc.2`** | **`0.1.23`** |
62
- | 上一推荐 RC | `0.1.7-rc.2` | `0.1.23` |
63
- | 保留旧 RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.23` |
64
- | 保留旧 RC | `0.1.2-rc.1` | `0.1.23` |
65
- | 开发者测试 Alpha | `0.1.2-alpha.5` | `0.1.23` |
66
- | 保留旧 Alpha | `0.1.2-alpha.2` | `0.1.23` |
61
+ | **推荐版本** | **`0.2.1-rc.1`** | **`0.1.24-rc.1`** |
62
+ | 上一推荐 RC | `0.2.0-rc.2`, `0.1.7-rc.2` | `0.1.24-rc.1` |
63
+ | 保留旧 RC | `0.1.5-rc.1`, `0.1.5-rc.2`, `0.1.5-rc.3` | `0.1.24-rc.1` |
64
+ | 保留旧 RC | `0.1.2-rc.1` | `0.1.24-rc.1` |
65
+ | 开发者测试 Alpha | `0.1.2-alpha.5` | `0.1.24-rc.1` |
66
+ | 保留旧 Alpha | `0.1.2-alpha.2` | `0.1.24-rc.1` |
67
67
 
68
68
  ### 桌面端
69
69
 
70
70
  在应用侧栏打开「插件 → 添加插件」,输入 npm 包名和版本:
71
71
 
72
72
  ```text
73
- @nanmicoder/dsh-agent-teams@0.1.23
73
+ @nanmicoder/dsh-agent-teams@0.1.24-rc.1
74
74
  ```
75
75
 
76
76
  安装完成后点击「立即启用」;宿主提示需要重启时,重启桌面应用。发布版请使用上面的包名,npm 或 GitHub 网页链接与 npm 包名不是同一种安装来源。从 Git 安装时,请选用包含下方所述已验证构建产物的提交。
77
77
 
78
- 桌面端自带 Harness 内核与包管理器,升级全局 CLI 不会升级桌面端;普通独立 CLI 不能管理桌面 profile,桌面端自带的 CLI carrier 在此版本可以管理。当前精确目标是已发布的 `0.2.0-rc.2`,仍属于 RC;此前源码预测的 `0.2.0` 已退出支持声明。详见[迁移验收记录](./docs/harness-0.2.0-rc.2/README.md)。
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.0-rc.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 安装或升级插件(命令锁定本次 `latest` 正式版):
91
+ 此版本发布后,从 npm 安装或升级插件(锁定精确预发布版本):
92
92
 
93
93
  ```sh
94
- dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.23
94
+ dsh plugin --profile web add --save-exact @nanmicoder/dsh-agent-teams@0.1.24-rc.1
95
95
  ```
96
96
 
97
97
  CLI 管理的安装可将 `web` 换成实际使用的 CLI profile。**安装后,停止并重新启动该 profile 的 Harness 进程,再刷新浏览器。** 安装插件不会自动升级宿主;Harness 的 `latest` 与插件的 `latest` 是两个独立渠道。
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "recommendedHost": "0.2.0-rc.2",
3
+ "recommendedHost": "0.2.1-rc.1",
4
4
  "previewTag": "next",
5
5
  "supportedHosts": [
6
6
  {
7
- "version": "0.2.0-rc.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": "0ed6e725cf32de96d7927305ca0b630626441504cce32a3c7641c5fb6d9a0896",
4
- "output": "83a84431b6c1d130d8920286abfe4888c09a22b0f3ebf454edf8ed2a456086bf"
3
+ "source": "983fb392dfa483a3095fd1e7ad67bfd5786d467c77244865d22e7ca7b046e2ad",
4
+ "output": "bb2031aa6d907a929ad81d99ab74f6f2c2136ce1f3a1e9bb68006453bff43908"
5
5
  }
@@ -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 stop = onAgentReady(ctx, (agent, vetoable) => {
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
- return () => {
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 disposeAdmission;
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(); disposeAdmission(); };
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
- disposeAdmission();
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
- if (!provider.capabilities.persona) {
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 (!provider.capabilities.toolFilter) {
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 so team.json and the child descriptor agree.
784
+ // filter the host accepted for the durable member setup.
744
785
  let acceptedFilter = requestedFilter;
745
- const start = await selections.withPending(captain.id, label, llmSelection, () => (startMemberWithLenientFilter(toolFilter => {
786
+ const start = await startMemberWithLenientFilter(toolFilter => {
746
787
  acceptedFilter = toolFilter;
747
- return ctx.subagents.startContinuable({
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 the previous record shape and behavior. The descriptor, not
777
- // this snapshot, is what reapplies the filter on a cold resume.
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.effect(() => {
882
- const start = runtime.start;
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.drainContinuableChildren(captain, memberIds);
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 {};
@@ -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>): 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
@@ -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. */