test(sdk): defer Team tool parity to issue 7172
This commit is contained in:
1 parent
47c91f7a32
commit
3ff2bd2c32
15 files changed
+30
-210
No files matched your search
@@ -2,8 +2,9 @@
|
||||
* Keyless comparison of actual SDK and Desktop/Web step-admitted tool sets.
|
||||
* Runs in test:expected, hence ci-consumers and PR all checks passed, after the build.
|
||||
* Keep intentional differences in tool-parity.ts; unused exceptions fail.
|
||||
* Standard/PTC compare both the default root and real Team Lead/teammate scopes.
|
||||
* SDK minimal, Creator-only tools, graphical context and optional runtime payloads are excluded.
|
||||
* Team mode is excluded while its known composition mismatch is tracked in #7172.
|
||||
* SDK minimal, Creator-only tools, graphical context and optional runtime payloads
|
||||
* are outside this fixed standard/PTC, top-level-session comparison.
|
||||
*/
|
||||
import { createServer } from 'node:http'
|
||||
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
|
||||
@@ -13,21 +14,15 @@ import { fileURLToPath } from 'node:url'
|
||||
import { execa } from 'execa'
|
||||
import { z } from 'zod'
|
||||
import { DeepSeekHarness } from '@deepseek-ai/dsh-sdk-client'
|
||||
import { initProfile } from '@deepseek-ai/dsh-app-boot'
|
||||
import { PROFILE_TEMPLATES } from '@deepseek-ai/dsh-official-bundles'
|
||||
import { expect, it, type TestContext } from 'vitest'
|
||||
import { clearAmbientProxyEnv } from '../../../../../scripts/test-proxy-environment.ts'
|
||||
import { messagesResponse } from './fixtures/messages-response.ts'
|
||||
import { assertToolParity, toolParityExceptions, type ToolMode } from './tool-parity.ts'
|
||||
import { assertToolParity, type ToolMode } from './tool-parity.ts'
|
||||
|
||||
const repo = fileURLToPath(new URL('../../../../../', import.meta.url))
|
||||
const observer = new URL('./fixtures/tool-parity.mjs', import.meta.url).href
|
||||
const probe = new URL('./fixtures/parity-probe.mjs', import.meta.url).href
|
||||
const memberSchema = z.object({
|
||||
role: z.enum(['lead', 'teammate']), sessionId: z.string(), parentSession: z.string().optional(),
|
||||
names: z.array(z.string()).min(1),
|
||||
})
|
||||
const inventorySchema = z.object({ names: z.array(z.string()).min(1), hmr: z.boolean(), members: z.array(memberSchema) })
|
||||
const inventorySchema = z.object({ names: z.array(z.string()).min(1), hmr: z.boolean() })
|
||||
type Inventory = z.infer<typeof inventorySchema>
|
||||
|
||||
async function fixture(test: TestContext) {
|
||||
@@ -65,28 +60,21 @@ async function fixture(test: TestContext) {
|
||||
})
|
||||
clearAmbientProxyEnv(env)
|
||||
|
||||
async function collect(side: 'sdk' | 'desktop', mode: ToolMode, mutations: object[] = [], team = false): Promise<Inventory> {
|
||||
async function collect(side: 'sdk' | 'desktop', mode: ToolMode, mutations: object[] = []): Promise<Inventory> {
|
||||
const cwd = await mkdtemp(join(root, `${side}-${mode}-`))
|
||||
const home = join(cwd, 'home')
|
||||
if (team) {
|
||||
const profile = side === 'sdk' ? 'sdk' : 'web'
|
||||
initProfile(join(home, 'profiles', profile), [
|
||||
...PROFILE_TEMPLATES[profile]!.bundles, '@deepseek-ai/dsh-experimental-agent-team-profile',
|
||||
])
|
||||
}
|
||||
const output = join(cwd, 'tools.json')
|
||||
const patch = join(cwd, 'observer.patch.yml')
|
||||
await writeFile(patch, JSON.stringify([
|
||||
{ id: 'session-title-llm', disabled: true },
|
||||
{ insert: [{ id: 'tool-parity-observer', name: observer,
|
||||
config: { output, cwd, team, ...(side === 'desktop' ? { preset: mode } : {}) } }] },
|
||||
config: { output, cwd, ...(side === 'desktop' ? { preset: mode } : {}) } }] },
|
||||
...mutations,
|
||||
]))
|
||||
const before = requests.length
|
||||
let inventory: Inventory
|
||||
if (side === 'sdk') {
|
||||
const harness = new DeepSeekHarness({
|
||||
cwd, processCwd: cwd, dshHome: home, env, model: 'deepseek-v4-pro',
|
||||
cwd, processCwd: cwd, dshHome: join(cwd, 'home'), env, model: 'deepseek-v4-pro',
|
||||
patches: [...mode === 'ptc' ? [join(repo, 'apps/cli/config/examples/ptc.yml')] : [], patch],
|
||||
})
|
||||
const abort = (): void => { void harness.close() }
|
||||
@@ -103,7 +91,7 @@ async function fixture(test: TestContext) {
|
||||
} else {
|
||||
const child = execa(process.execPath, [join(repo, 'apps/cli/lib/bin.js'), '--profile', 'web',
|
||||
'--patch', patch, '--host', '127.0.0.1', '--port', '0', '--no-open'], {
|
||||
cwd, env: { ...env, DSH_HOME: home }, extendEnv: false,
|
||||
cwd, env: { ...env, DSH_HOME: join(cwd, 'home') }, extendEnv: false,
|
||||
ipc: true, stdin: 'ignore', reject: false, cancelSignal: test.signal, forceKillAfterDelay: 10_000,
|
||||
})
|
||||
let stdout = ''
|
||||
@@ -138,21 +126,9 @@ async function fixture(test: TestContext) {
|
||||
expect(inventory.hmr).toBe(true)
|
||||
}
|
||||
expect(requests.length).toBeGreaterThan(before)
|
||||
const presented = requests.slice(before).map(request => request.tools.map(tool => tool.name).sort())
|
||||
if (mode === 'ptc') {
|
||||
for (const names of presented) expect(names).toEqual(['run_code'])
|
||||
} else {
|
||||
expect(presented).toContainEqual(inventory.names)
|
||||
for (const member of inventory.members) expect(presented).toContainEqual(member.names)
|
||||
}
|
||||
if (team) {
|
||||
expect(inventory.members.map(member => member.role).sort()).toEqual(['lead', 'teammate'])
|
||||
const lead = inventory.members.find(member => member.role === 'lead')!
|
||||
const teammate = inventory.members.find(member => member.role === 'teammate')!
|
||||
expect(lead.parentSession).toBeUndefined()
|
||||
expect(teammate.parentSession).toBe(lead.sessionId)
|
||||
expect(teammate.sessionId).not.toBe(lead.sessionId)
|
||||
} else expect(inventory.members).toEqual([])
|
||||
const presented = requests[before]!.tools.map(tool => tool.name).sort()
|
||||
if (mode === 'ptc') expect(presented).toEqual(['run_code'])
|
||||
else expect(presented).toEqual(inventory.names)
|
||||
return inventory
|
||||
}
|
||||
return collect
|
||||
@@ -178,34 +154,3 @@ it.for(['standard', 'ptc'] as const)('compares the shipped %s tool sets and dete
|
||||
expect(() => { assertToolParity(mode, sdkExtra.names, desktop.names) }).toThrow('Only in SDK: parity_probe')
|
||||
assertToolParity(mode, sdkExtra.names, desktopExtra.names)
|
||||
})
|
||||
|
||||
it.for(['standard', 'ptc'] as const)('compares real Team Lead and teammate tool sets in %s', async (mode, test) => {
|
||||
const collect = await fixture(test)
|
||||
const sdk = await collect('sdk', mode, [], true)
|
||||
const desktop = await collect('desktop', mode, [], true)
|
||||
const exceptions = toolParityExceptions[mode].filter(exception => exception.tool !== 'list_subagent_models')
|
||||
for (const role of ['lead', 'teammate'] as const) {
|
||||
const sdkMember = sdk.members.find(member => member.role === role)!
|
||||
const desktopMember = desktop.members.find(member => member.role === role)!
|
||||
expect(sdkMember.names).toContain('spawn_teammate')
|
||||
expect(desktopMember.names).toContain('spawn_teammate')
|
||||
for (const names of [sdkMember.names, desktopMember.names]) {
|
||||
for (const retired of ['subagent', 'subagent_fork', 'list_subagent_models']) expect(names).not.toContain(retired)
|
||||
}
|
||||
assertToolParity(mode, sdkMember.names, desktopMember.names, exceptions, `Team ${role}`)
|
||||
}
|
||||
const missing = await collect('sdk', mode, [{ id: 'tool-agent-team', disabled: true }], true)
|
||||
const addition = [{ insert: [{ id: 'parity-probe', name: probe }] }]
|
||||
const desktopExtra = await collect('desktop', mode, addition, true)
|
||||
const sdkExtra = await collect('sdk', mode, addition, true)
|
||||
for (const role of ['lead', 'teammate'] as const) {
|
||||
const names = (inventory: Inventory) => inventory.members.find(member => member.role === role)!.names
|
||||
expect(() => {
|
||||
assertToolParity(mode, names(missing), names(desktop), exceptions, `Team ${role}`)
|
||||
}).toThrow(new RegExp(`Team ${role}[\\s\\S]*Missing from SDK:.*spawn_teammate`))
|
||||
expect(() => {
|
||||
assertToolParity(mode, names(sdk), names(desktopExtra), exceptions, `Team ${role}`)
|
||||
}).toThrow('Missing from SDK: parity_probe')
|
||||
assertToolParity(mode, names(sdkExtra), names(desktopExtra), exceptions, `Team ${role}`)
|
||||
}
|
||||
})
|
||||
@@ -6,46 +6,16 @@ export const inject = ['tools', 'agents', 'loader']
|
||||
|
||||
export function apply(ctx, config) {
|
||||
let observed
|
||||
let writes = Promise.resolve()
|
||||
const members = new Map()
|
||||
ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
|
||||
if (context.toolSet !== undefined) {
|
||||
const names = ctx.tools.definitions(context.scope, context.toolSet).map(tool => tool.name).sort()
|
||||
const membership = config.team ? ctx.get('agentTeams').membership(context.scope) : undefined
|
||||
if (membership !== undefined) members.set(membership.role, {
|
||||
role: membership.role, sessionId: context.scope.id,
|
||||
parentSession: context.scope.session.header.parentSession, names,
|
||||
})
|
||||
observed = {
|
||||
names: membership?.role === 'teammate' ? observed.names : names,
|
||||
names: ctx.tools.definitions(context.scope, context.toolSet).map(tool => tool.name).sort(),
|
||||
hmr: ctx.get('hmr') !== undefined,
|
||||
members: [...members.values()],
|
||||
}
|
||||
const serialized = JSON.stringify(observed)
|
||||
writes = writes.then(() => writeFile(config.output, serialized))
|
||||
await writes
|
||||
await writeFile(config.output, JSON.stringify(observed))
|
||||
}
|
||||
return next()
|
||||
})
|
||||
if (config.team) {
|
||||
const spawned = new Set()
|
||||
// A completed teammate can leave the registry before spawnTeammate's persistence barrier settles.
|
||||
const created = new Map()
|
||||
ctx.on('agent/created', ({ agent }) => { created.set(agent.id, agent) })
|
||||
ctx.on('agent/turn-stopping', async ({ agent, signal }) => {
|
||||
const teams = ctx.get('agentTeams')
|
||||
if (teams.membership(agent).role !== 'lead' || spawned.has(agent)) return
|
||||
spawned.add(agent)
|
||||
const { member } = await teams.spawnTeammate(agent, {
|
||||
name: 'parity-worker', description: 'Inspect teammate tools',
|
||||
prompt: [{ type: 'text', text: 'Inspect teammate tools' }], context: 'fresh', provider: 'spawn', signal,
|
||||
})
|
||||
const child = created.get(member.id)
|
||||
if (child === undefined) throw new Error('Team service created no live teammate')
|
||||
await child.whenIdle()
|
||||
await writes
|
||||
})
|
||||
}
|
||||
if (config.preset === undefined) return
|
||||
const receive = message => {
|
||||
if (message === 'stop-tool-parity') {
|
||||
|
||||
@@ -27,12 +27,10 @@ export const toolParityExceptions: Record<ToolMode, readonly Exception[]> = {
|
||||
* @param sdk - SDK capability names.
|
||||
* @param desktop - Desktop capability names.
|
||||
* @param exceptions - exact one-sided product differences with their rationale.
|
||||
* @param role - compared session role, included in mismatch diagnostics.
|
||||
*/
|
||||
export function assertToolParity(
|
||||
mode: ToolMode, sdk: readonly string[], desktop: readonly string[],
|
||||
exceptions = toolParityExceptions[mode],
|
||||
role = 'main',
|
||||
): void {
|
||||
if (sdk.length === 0 || desktop.length === 0) throw new Error(`Empty tool inventory: ${mode}`)
|
||||
const only = {
|
||||
@@ -46,7 +44,7 @@ export function assertToolParity(
|
||||
}
|
||||
if (only.sdk.size || only.desktop.size || stale.length) {
|
||||
throw new Error([
|
||||
`SDK/Desktop tool mismatch: ${mode} / ${role}`,
|
||||
`SDK/Desktop tool mismatch: ${mode}`,
|
||||
`Missing from SDK: ${[...only.desktop].sort().join(', ') || '(none)'}`,
|
||||
`Only in SDK: ${[...only.sdk].sort().join(', ') || '(none)'}`,
|
||||
`Stale exceptions: ${stale.sort().join(', ') || '(none)'}`,
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
|
||||
# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
|
||||
# After editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-rc.3/team-delegation-tools/guide.md
|
||||
/:
|
||||
en: 0557dcec0f9be0ab
|
||||
zh: 4c6b72d187085feb
|
||||
/team-members-use-teammate-delegation:
|
||||
en: 2c405ca578cd2529
|
||||
zh: 0e80f0dfff779a58
|
||||
/team-members-use-teammate-delegation/change:
|
||||
en: 8d74bec270937271
|
||||
zh: 8d193e0dd7dcad6f
|
||||
/team-members-use-teammate-delegation/migration:
|
||||
en: d91d075ce4be08c3
|
||||
zh: c0acb76729e372e5
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Team members no longer inherit ordinary subagent delegation tools from Web presets or enclosing compositions."
|
||||
---
|
||||
|
||||
# Team members use teammate delegation
|
||||
|
||||
English | [中文](guide.zh.md)
|
||||
|
||||
## Change
|
||||
|
||||
Web standard/PTC presets could expose `subagent`, `subagent_fork`, and `list_subagent_models` after the Team bundle disabled their base rows. Team member scopes now hide these inherited tools and ordinary child controls. Team tools remain available on the Lead and teammates; `interrupt: false` also hides an inherited ordinary `interrupt_agent`. Disabling the Team tools restores the inherited definitions. No profile name or persisted Session format changes.
|
||||
|
||||
## Migration
|
||||
|
||||
1. Update instructions or scripts that call ordinary delegation tools inside a Team. The Lead uses `spawn_teammate` with `context: fresh` or `context: fork`; team messaging uses member targets returned by `list_agents`.
|
||||
2. If ordinary subagent delegation is required, disable the Agent Teams bundle for that profile. SDK profile changes still require a runtime restart.
|
||||
3. Start a new Team session and confirm that `spawn_teammate` is available while `subagent`, `subagent_fork`, and `list_subagent_models` are absent. The same rule applies to teammates.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
kind: upgrade-guide
|
||||
description: "Team 成员不再继承 Web preset 或外层组合中的普通 subagent 委派工具。"
|
||||
---
|
||||
|
||||
# Team 成员使用 teammate 委派
|
||||
|
||||
[English](guide.md) | 中文
|
||||
|
||||
## 变更
|
||||
|
||||
Team bundle 禁用 base 中的工具后,Web standard/PTC preset 仍可能暴露 `subagent`、`subagent_fork` 和 `list_subagent_models`。Team 成员作用域现在会隐藏这些继承的工具以及普通子代理控制工具。Lead 和 teammate 继续拥有 Team 工具;`interrupt: false` 也会隐藏继承的普通 `interrupt_agent`。禁用 Team 工具后恢复继承的定义。profile 名称和持久化 Session 格式均未改变。
|
||||
|
||||
## 迁移
|
||||
|
||||
1. 更新在 Team 内调用普通委派工具的指令或脚本。Lead 使用 `spawn_teammate`,并选择 `context: fresh` 或 `context: fork`;团队消息使用 `list_agents` 返回的成员目标。
|
||||
2. 如果需要普通 subagent 委派,在该 profile 中禁用 Agent Teams bundle。SDK profile 变更仍需重启运行时。
|
||||
3. 启动新的 Team 会话,确认 `spawn_teammate` 可用,而 `subagent`、`subagent_fork` 和 `list_subagent_models` 不再出现。teammate 同样遵循此规则。
|
||||
@@ -36,5 +36,5 @@
|
||||
en: 451497677de7e6b9
|
||||
zh: dcfdc671c38d54d4
|
||||
/deepseek-ai-dsh-sdk-app/known-limitations-and-deferred-work/dev-note:
|
||||
en: cbd70709cfeec31b
|
||||
zh: 806ab8a876dff548
|
||||
en: b9b4e27513ebba78
|
||||
zh: 51b123ac60ff28c6
|
||||
@@ -77,6 +77,6 @@ The static prefix is stable for a fixed profile, provider, model, and tool roste
|
||||
<details>
|
||||
<summary>Working context for maintainers — click to expand</summary>
|
||||
|
||||
The [SDK/Desktop tool comparison](../../../apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts) runs without API credentials in `test:expected`, included in the required PR consumer job. Standard/PTC cover the default root and, with the real Team bundle enabled, both the Lead and a fresh teammate. After building, run `CI=true pnpm run test:expected apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts`. Maintain exact, reasoned differences in the adjacent `tool-parity.ts`; resolved differences invalidate their exceptions. Existing SDK session snapshots and installed-runtime smokes independently check model output and packaging.
|
||||
The [SDK/Desktop tool comparison](../../../apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts) runs without API credentials in `test:expected`, included in the required PR consumer job. It covers standard/PTC root sessions with Team disabled; Team parity is deferred to #7172. After building, run `CI=true pnpm run test:expected apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts`. Maintain exact, reasoned differences in the adjacent `tool-parity.ts`; resolved differences invalidate their exceptions. Existing SDK session snapshots and installed-runtime smokes independently check model output and packaging.
|
||||
|
||||
</details>
|
||||
@@ -77,6 +77,6 @@ profile 继承 base 系统提示词,包括 `You are an AI agent powered by Dee
|
||||
<details>
|
||||
<summary>维护者工作上下文——点击展开</summary>
|
||||
|
||||
[SDK/Desktop 工具比较](../../../apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts) 在 `test:expected` 中无需 API 凭据运行,并纳入 PR 必需的 consumer 作业。standard/PTC 覆盖默认主会话,以及启用真实 Team bundle 后的 Lead 和 fresh teammate。构建后运行 `CI=true pnpm run test:expected apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts`。在相邻的 `tool-parity.ts` 中维护精确、带理由的差异;差异消除后,对应例外会使检查失败。已有 SDK 会话快照和安装后运行时 smoke 分别检查模型输出和打包结果。
|
||||
[SDK/Desktop 工具比较](../../../apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts) 在 `test:expected` 中无需 API 凭据运行,并纳入 PR 必需的 consumer 作业。检查覆盖关闭 Team 的 standard/PTC 主会话;Team 对齐暂由 #7172 跟踪。构建后运行 `CI=true pnpm run test:expected apps/cli/tests/profiles/sdk/desktop-tools.expected.e2e.ts`。在相邻的 `tool-parity.ts` 中维护精确、带理由的差异;差异消除后,对应例外会使检查失败。已有 SDK 会话快照和安装后运行时 smoke 分别检查模型输出和打包结果。
|
||||
|
||||
</details>
|
||||
@@ -9,8 +9,8 @@
|
||||
en: 0b01a243291821bc
|
||||
zh: 0dadd38d709ae9bd
|
||||
/deepseek-ai-dsh-experimental-tool-agent-team/summary:
|
||||
en: 7b7ed5d798166030
|
||||
zh: cd62e2ba6d169eba
|
||||
en: cb426055b46b381e
|
||||
zh: dc29d823d44f0c5e
|
||||
/deepseek-ai-dsh-experimental-tool-agent-team/table-of-contents:
|
||||
en: d152484eb41ac6b4
|
||||
zh: 09388d293f9be9cb
|
||||
@@ -18,8 +18,8 @@
|
||||
en: 5a56781cb7bf4d3b
|
||||
zh: 74df21efd6321afd
|
||||
/deepseek-ai-dsh-experimental-tool-agent-team/use-this-package/when-to-choose-it:
|
||||
en: 9f217af3eb46717e
|
||||
zh: 6aadebba947d5723
|
||||
en: 60ea90ce57cca56f
|
||||
zh: 181e48b7d4dcf755
|
||||
/deepseek-ai-dsh-experimental-tool-agent-team/use-this-package/smallest-working-example:
|
||||
en: 9af650a79711b3b3
|
||||
zh: 2930360011b544e0
|
||||
|
||||
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
|
||||
|
||||
## Summary
|
||||
|
||||
This package lets the model create named teammates, send them messages, inspect availability, wait for progress, interrupt stuck work, and coordinate through a shared task board. Every team member receives the same nine tools and guidance for coordinating in a shared workspace. Team members use teammate delegation instead of inherited ordinary subagent tools. The package is published under its experimental name and provides no stability guarantee.
|
||||
This package lets the model create named teammates, send them messages, inspect availability, wait for progress, interrupt stuck work, and coordinate through a shared task board. Every team member receives the same nine tools and guidance for coordinating in a shared workspace. It replaces legacy subagent controls with the same tool names, so compositions that need both must disable the legacy definitions. The package is published under its experimental name and provides no stability guarantee.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
@@ -29,7 +29,7 @@ Add this package on top of `@deepseek-ai/dsh-experimental-agent-team` when the m
|
||||
|
||||
### When to choose it
|
||||
|
||||
Choose it when the model should create and coordinate teammates by itself rather than a human driving subagent controls. At member activation, Team masks inherited `subagent`, `subagent_fork`, `list_subagent_models`, and ordinary child controls before installing its own tools. This includes tools contributed by a Web preset. With `interrupt: false`, an inherited ordinary `interrupt_agent` remains hidden. Definitions outside member scopes remain unchanged; unloading the Team tools restores their visibility.
|
||||
Choose it when the model should create and coordinate teammates by itself rather than a human driving subagent controls. Avoid it when the legacy global subagent tools with the same names must stay available: the team tools replace them for team members, so a composition that wants both must disable the legacy definitions.
|
||||
|
||||
### Smallest working example
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ kind: "package-reference"
|
||||
|
||||
## 概述
|
||||
|
||||
本包让模型创建具名 teammate、向它们发送消息、查看可用状态、等待进展、中断卡住的工作,并通过共享任务板协调。每个团队成员都会获得相同的九个工具,以及在共享工作区协调的指引。Team 成员使用 teammate 委派,替代继承的普通 subagent 工具。本包以实验性名称公开发布,但不提供稳定性保证。
|
||||
本包让模型创建具名 teammate、向它们发送消息、查看可用状态、等待进展、中断卡住的工作,并通过共享任务板协调。每个团队成员都会获得相同的九个工具,以及在共享工作区协调的指引。它会取代同名的旧版 subagent 控件,因此同时需要两者的组合必须禁用旧定义。本包以实验性名称公开发布,但不提供稳定性保证。
|
||||
|
||||
## 目录
|
||||
|
||||
@@ -29,7 +29,7 @@ kind: "package-reference"
|
||||
|
||||
### 何时选择
|
||||
|
||||
当模型应该自行创建与协调 teammate、而不是由人来操作 subagent 控件时,选择它。成员激活时,Team 会先屏蔽继承的 `subagent`、`subagent_fork`、`list_subagent_models` 和普通子代理控制工具,再安装自己的工具。这也包括 Web preset 提供的工具。设置 `interrupt: false` 时,继承的普通 `interrupt_agent` 仍被隐藏。成员作用域之外的定义保持不变;卸载 Team 工具后恢复其可见性。
|
||||
当模型应该自行创建与协调 teammate、而不是由人来操作 subagent 控件时,选择它。当同名的旧全局 subagent 工具必须继续可用时,请不要选择:团队工具会为团队成员取代它们,因此想同时使用两者的组合必须禁用旧定义。
|
||||
|
||||
### 最小工作示例
|
||||
|
||||
|
||||
@@ -170,10 +170,6 @@ function install(agent: Agent, ctx: Context, config: Required<Config>): () => vo
|
||||
const disposers: Array<() => unknown> = []
|
||||
const register = (disposer: () => unknown): void => { disposers.push(disposer) }
|
||||
try {
|
||||
// Presets can contribute ordinary delegation after the Team bundle disables base rows.
|
||||
const inherited = ['subagent', 'subagent_fork', 'list_subagent_models', 'list_agents', 'send_message', 'interrupt_agent']
|
||||
.filter(name => scoped.tools.get(name, agent) !== undefined)
|
||||
if (inherited.length > 0) register(scoped.tools.restrict({ deny: inherited }))
|
||||
register(scoped.systemPrompt.section({
|
||||
name: 'team:policy',
|
||||
order: scoped.systemPrompt.getSectionOrder('TEAM_POLICY'),
|
||||
|
||||
@@ -680,38 +680,6 @@ describe('dsh-tool-team', () => {
|
||||
expect(JSON.stringify(legacySchema)).toContain('agent_id')
|
||||
})
|
||||
|
||||
it('masks ordinary delegation for Lead and teammate and restores it when Team tools unload', async () => {
|
||||
const { ctx, lead, fiber } = await setup(['hang'], true)
|
||||
await fiber.dispose()
|
||||
const ordinary = ['subagent', 'subagent_fork', 'list_subagent_models']
|
||||
for (const name of ordinary) ctx.tools.register(defineContentToolFixture({
|
||||
name, description: 'ordinary delegation', parameters: {},
|
||||
async execute() { return [{ type: 'text', text: 'ordinary' }] },
|
||||
}))
|
||||
const replacement = await ctx.plugin(toolTeam, { interrupt: false })
|
||||
const spawned = await execute(ctx, lead, 'spawn_teammate', {
|
||||
name: 'masked-worker', description: 'scope masking', prompt: 'wait',
|
||||
})
|
||||
const childId = spawnedChildId(ctx, lead, spawned)
|
||||
const child = await waitRunning(ctx, childId)
|
||||
for (const agent of [lead, child]) {
|
||||
const names = ctx.tools.schemas(agent).map(tool => tool.name)
|
||||
for (const name of [...ordinary, 'interrupt_agent']) {
|
||||
expect(names).not.toContain(name)
|
||||
expect((await execute(ctx, agent, name, {})).isError).toBe(true)
|
||||
}
|
||||
expect(names).toContain('spawn_teammate')
|
||||
}
|
||||
expect(ctx.tools.schemas().map(tool => tool.name)).toEqual(expect.arrayContaining(ordinary))
|
||||
await replacement.dispose()
|
||||
for (const agent of [lead, child]) {
|
||||
expect(ctx.tools.schemas(agent).map(tool => tool.name)).toEqual(expect.arrayContaining([...ordinary, 'interrupt_agent']))
|
||||
expect(text(await execute(ctx, agent, 'subagent', {}))).toBe('ordinary')
|
||||
}
|
||||
ctx.agentTeams.interrupt(lead, 'masked-worker')
|
||||
await waitNoAgent(ctx, childId)
|
||||
})
|
||||
|
||||
it('rolls back partial scoped installation after a same-scope collision', async () => {
|
||||
const { ctx, lead, fiber } = await setup([])
|
||||
await fiber.dispose()
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
# Team members must mask ordinary tools even when an enclosing composition enables them.
|
||||
- id: tool-subagent-control
|
||||
disabled: false
|
||||
disabled: true
|
||||
- id: tool-subagent-report
|
||||
disabled: true
|
||||
- id: tool-subagent
|
||||
disabled: false
|
||||
config:
|
||||
provider: spawn
|
||||
toolName: subagent
|
||||
modelDiscovery: true
|
||||
disabled: true
|
||||
- id: tool-subagent-fork
|
||||
disabled: false
|
||||
disabled: true
|
||||
- insert:
|
||||
- id: agent-team
|
||||
name: '@deepseek-ai/dsh-experimental-agent-team'
|
||||
|
||||
Reference in new issue
Block a user