From 999f30aae9add5235324e8b3f78446a0e2fe41d3 Mon Sep 17 00:00:00 2001 From: Dudu-0223 Date: Fri, 30 Oct 2026 11:55:46 +0800 Subject: [PATCH 1/2] fix(sdk): identify subagent ownership errors by name and code --- packages/sdk/server/README.i18n.yaml | 4 ++-- packages/sdk/server/README.md | 2 +- packages/sdk/server/README.zh.md | 2 +- packages/sdk/server/src/server.ts | 11 +++++++++-- packages/sdk/server/tests/approval.spec.ts | 22 ++++++++++++++++++++++ 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/packages/sdk/server/README.i18n.yaml b/packages/sdk/server/README.i18n.yaml index e08184697e..fcc20f3d98 100644 --- a/packages/sdk/server/README.i18n.yaml +++ b/packages/sdk/server/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package: - en: 364c47155e5687be - zh: 7322c098fc8f23c5 + en: 1a8598a9ca7ec995 + zh: dbbd0dd3aecb7d24 /deepseek-ai-dsh-sdk-jsonrpc-server/use-this-package/wiring: en: 47a33e9397e36e15 zh: e6d173820f9ea6e5 diff --git a/packages/sdk/server/README.md b/packages/sdk/server/README.md index 7ecc0232a4..b2fe1e211a 100644 --- a/packages/sdk/server/README.md +++ b/packages/sdk/server/README.md @@ -27,7 +27,7 @@ English | [中文](README.zh.md) Mount this plugin when a runtime must serve SDK clients: add it to a `cordis.yml` that composes the agent service, boot the runtime, and clients connect over stdio. The common path is explicit — the plugin needs the `agents` service; every other capability comes from the surrounding tree. -Set `initialize.approvalRequests` to `true` to send internal approvals as server-to-client `approval/request` calls. The bridge handles SDK-owned Agents and their live in-process descendants, resolving descendants through `subagents.rootOf()` and matching the exact SDK-owned root instance. Persisted `parentSession` lineage does not establish approval ownership. Unrelated Agents and clients that omit the flag continue through the composed answerer chain. The approval service still applies `never` policy before dispatch and records the ask/decision audit pair. The bridge accepts only `allowed-once`, `rejected`, or `cancelled`; an invalid response, client error, or transport loss yields the service's fail-closed `unavailable` outcome. Request cancellation abandons the pending wire waiter; shutdown abandons all such waiters before draining Agents. Late replies cannot grant an abandoned operation. See the [protocol](../protocol/README.md) for the payload and the [Python example](../../../python/sdk/README.md#client-approvals) for client handling. +Set `initialize.approvalRequests` to `true` to send internal approvals as server-to-client `approval/request` calls. The bridge handles SDK-owned Agents and their live in-process descendants, resolving descendants through `subagents.rootOf()` and matching the exact SDK-owned root instance. Persisted `parentSession` lineage does not establish approval ownership. A root lookup error with `name: SubagentError` and `code: UNAUTHORIZED` delegates to the next answerer regardless of its constructor; other lookup failures yield `unavailable` without delegation. Unrelated Agents and clients that omit the flag continue through the composed answerer chain. The approval service still applies `never` policy before dispatch and records the ask/decision audit pair. The bridge accepts only `allowed-once`, `rejected`, or `cancelled`; an invalid response, client error, or transport loss yields the service's fail-closed `unavailable` outcome. Request cancellation abandons the pending wire waiter; shutdown abandons all such waiters before draining Agents. Late replies cannot grant an abandoned operation. See the [protocol](../protocol/README.md) for the payload and the [Python example](../../../python/sdk/README.md#client-approvals) for client handling. `session/resume` accepts a persisted `{ sessionId }` after initialization and returns `{}` once its Agent is ready. It loads history through `agents.resume()`, repairs an interrupted final turn, and preserves the stored origin and effective directory while recording the initialized model route as the Session selection. An omitted effort preserves an explicit saved effort only for that same route. It sends no new input. Subagent sessions reject before loading or repairing history; resume their root session instead. Missing, unreadable, incompatible, or occupied sessions reject without creating a replacement. Prompts for an identity currently being restored wait for that restoration; shutdown drains pending restoration and releases its Agent. Historical events are retained in the log rather than streamed again as new notifications. A model selection recorded during resume emits a new `session.event` notification before the resume response. diff --git a/packages/sdk/server/README.zh.md b/packages/sdk/server/README.zh.md index b27805dae8..ac9bc98399 100644 --- a/packages/sdk/server/README.zh.md +++ b/packages/sdk/server/README.zh.md @@ -27,7 +27,7 @@ kind: "package-reference" 当运行时必须服务 SDK 客户端时挂载本插件:把它加入组合了 agent 服务的 `cordis.yml`,启动运行时,客户端即可通过 stdio 连接。常用路径是显式的——插件需要 `agents` 服务;其余每个能力都来自外围插件树。 -将 `initialize.approvalRequests` 设为 `true`,即可把内部审批作为服务端到客户端的 `approval/request` 请求发送。桥接处理 SDK 拥有的 Agent 及其仍在运行的进程内后代,通过 `subagents.rootOf()` 解析后代的根,并与 SDK 拥有的确切根实例匹配,持久化的 `parentSession` 历史关系不决定审批归属;其他 Agent,以及未提供该标志的客户端,继续使用已组合的应答链。审批服务仍在分发前执行 `never` 策略,并记录询问与决定的审计事件。桥接仅接受 `allowed-once`、`rejected` 或 `cancelled`;无效响应、客户端错误或传输断开会产生审批服务的拒绝放行结果 `unavailable`。请求取消时释放待处理的协议等待项;shutdown 在等待 Agent 结束前释放所有此类等待项。迟到响应不能授权已放弃的操作。载荷见[协议](../protocol/README.zh.md),客户端处理见 [Python 示例](../../../python/sdk/README.zh.md#client-approvals)。 +将 `initialize.approvalRequests` 设为 `true`,即可把内部审批作为服务端到客户端的 `approval/request` 请求发送。桥接处理 SDK 拥有的 Agent 及其仍在运行的进程内后代,通过 `subagents.rootOf()` 解析后代的根,并与 SDK 拥有的确切根实例匹配,持久化的 `parentSession` 历史关系不决定审批归属。根查找错误的 `name: SubagentError` 与 `code: UNAUTHORIZED` 同时匹配时,无论其构造器是什么,均委派给下一个应答方;其他查找失败返回 `unavailable`,不继续委派。其他 Agent,以及未提供该标志的客户端,继续使用已组合的应答链。审批服务仍在分发前执行 `never` 策略,并记录询问与决定的审计事件。桥接仅接受 `allowed-once`、`rejected` 或 `cancelled`;无效响应、客户端错误或传输断开会产生审批服务的拒绝放行结果 `unavailable`。请求取消时释放待处理的协议等待项;shutdown 在等待 Agent 结束前释放所有此类等待项。迟到响应不能授权已放弃的操作。载荷见[协议](../protocol/README.zh.md),客户端处理见 [Python 示例](../../../python/sdk/README.zh.md#client-approvals)。 `session/resume` 在初始化后接受持久化的 `{ sessionId }`,待其 Agent 就绪后返回 `{}`。它通过 `agents.resume()` 加载历史、修复最后一个被中断的轮次,并保留存储的起始目录和当前有效目录,同时将初始化时选定的模型路由记录为 Session 选择。省略推理强度时,仅在路由相同的情况下保留已保存的显式强度。它不发送新输入。子代理会话在加载或修复历史前被拒绝;应恢复其根会话。会话缺失、不可读、不兼容或被占用时,请求失败且不创建替代会话。对正在恢复的标识发送提示词时,会等待恢复完成;shutdown 会等待进行中的恢复并释放其 Agent。历史事件保留在日志中,不会作为新通知重新发送。恢复期间记录的模型选择会在恢复响应前发出新的 `session.event` 通知。 diff --git a/packages/sdk/server/src/server.ts b/packages/sdk/server/src/server.ts index 06c28d220c..bccfa6634e 100644 --- a/packages/sdk/server/src/server.ts +++ b/packages/sdk/server/src/server.ts @@ -21,7 +21,7 @@ import type {} from '@deepseek-ai/dsh-working-directory' import type {} from '@deepseek-ai/dsh-user-approval' import type {} from '@deepseek-ai/dsh-schedule' -import { SubagentError, type SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent' +import type { SubagentRunEndInfo } from '@deepseek-ai/dsh-subagent' import * as LlmDeepSeek from '@deepseek-ai/dsh-llm-deepseek-api-key' import type { ApprovalRequestParams, @@ -382,6 +382,11 @@ export class HarnessSdkJsonRpcServer { } } + /** + * Resolves live SDK ownership; root lookup failures other than SubagentError/UNAUTHORIZED propagate. + * @param agent - Agent whose requests need routing. + * @returns Whether this server owns the Agent or its live subagent tree root. + */ private ownsAgent(agent: Agent): boolean { const agents = this.ctx.agents if (agents.get(agent.id) !== agent) return false @@ -392,7 +397,9 @@ export class HarnessSdkJsonRpcServer { try { root = subagents.rootOf(agent) } catch (error: unknown) { - if (error instanceof SubagentError && error.code === 'UNAUTHORIZED') return false + if (typeof error === 'object' && error !== null + && 'name' in error && error.name === 'SubagentError' + && 'code' in error && error.code === 'UNAUTHORIZED') return false throw error } return this.sessions.get(root.session.id)?.handle.agent === root diff --git a/packages/sdk/server/tests/approval.spec.ts b/packages/sdk/server/tests/approval.spec.ts index 9272bc4265..e3436587c5 100644 --- a/packages/sdk/server/tests/approval.spec.ts +++ b/packages/sdk/server/tests/approval.spec.ts @@ -237,8 +237,30 @@ describe('SDK approval bridge', () => { expect(clientDecision).not.toHaveBeenCalled() }) + it.each([ + { name: 'SubagentError', code: 'UNAUTHORIZED' }, + Object.assign(new Error('not a managed subagent'), { name: 'SubagentError', code: 'UNAUTHORIZED' }), + ])('delegates approval for an unauthorized ownership lookup by error fields: %s', async (error) => { + const h = await setup(true, true) + const foreign = await h.ctx.agents.create({ sessionId: SessionId('foreign') }) + onTestFinished(() => foreign.dispose()) + foreign.agent.session.append('turn/start', { turn: 1 }) + vi.spyOn(h.ctx.subagents, 'rootOf').mockImplementationOnce(() => { throw error }) + const clientDecision = vi.fn(async () => ({ outcome: 'allowed-once' })) + h.client.onRequest(clientDecision) + const fallback = vi.fn(() => Promise.resolve('rejected' as const)) + h.ctx.on('approval/request', fallback) + await expect(h.ctx.approval.request({ agent: foreign.agent, toolName: 'bash' })).resolves.toBe('rejected') + expect(fallback).toHaveBeenCalledOnce() + expect(clientDecision).not.toHaveBeenCalled() + }) + it.each([ new Error('root lookup failed'), new SubagentError('root lookup failed', 'NOT_RESUMABLE'), + null, undefined, 'root lookup failed', {}, + { name: 'SubagentError' }, { code: 'UNAUTHORIZED' }, + { name: 'OtherError', code: 'UNAUTHORIZED' }, + { name: 'SubagentError', code: 'NOT_RESUMABLE' }, ])('does not pass a failed ownership lookup to another approval answerer: %s', async (error) => { const h = await setup(true, true) const foreign = await h.ctx.agents.create({ sessionId: SessionId('foreign') }) From 0eb1bb52e2b28f8878d1ed4e170aa906222820f8 Mon Sep 17 00:00:00 2001 From: Dudu-0223 Date: Fri, 30 Oct 2026 14:25:27 +0800 Subject: [PATCH 2/2] refactor(team): keep session UI and task tools keyed by IDs --- .../2026-10-20-session-team-mode.i18n.yaml | 19 + .../2026-10-20-session-team-mode.md | 29 + .../2026-10-20-session-team-mode.zh.md | 29 + .../feature/2026-08-05-agent-teams.i18n.yaml | 20 +- .../feature/2026-08-05-agent-teams.md | 12 +- .../feature/2026-08-05-agent-teams.zh.md | 12 +- ...6-09-15-shipped-optional-bundles.i18n.yaml | 4 +- .../2026-09-15-shipped-optional-bundles.md | 1 - .../2026-09-15-shipped-optional-bundles.zh.md | 1 - ...-10-29-team-task-only-projection.i18n.yaml | 19 + .../2026-10-29-team-task-only-projection.md | 29 + ...2026-10-29-team-task-only-projection.zh.md | 29 + AGENTS.md | 1 + apps/cli/composition.md | 6 + apps/cli/package.json | 5 +- apps/cli/tests/agent-team-headless.e2e.ts | 16 +- .../tests/optional-bundle-transitions.e2e.ts | 6 +- .../profiles/headless/team-snapshot.patch.yml | 8 - .../headless/tests/fixtures/team-llm.mjs | 5 +- .../profiles/web/tests/default-web-process.ts | 2 +- .../web-default-isolation.expected.e2e.ts | 7 +- apps/cli/tests/web-agent-presets.e2e.ts | 31 +- apps/web/package.json | 2 +- apps/web/tests/agent-team-panel.e2e.ts | 127 +++-- apps/web/tests/agent-team-panel.overlay.yml | 12 - apps/web/tests/default-model.e2e.ts | 3 +- .../tests/default-product-isolation.e2e.ts | 2 +- .../plugin-config/official.expected.md | 5 - .../expected/plugin-manager/icons.expected.md | 4 +- .../plugin-manager/live-enabled.expected.md | 5 - .../plugin-manager/manager.expected.md | 5 - .../plugin-manager/refresh.expected.md | 4 +- .../migration-required-child.expected.md | 4 +- .../navigation-only.expected.md | 10 + apps/web/tests/minimal-preset.snapshot.ts | 5 +- apps/web/tests/plugin-manager.e2e.ts | 95 +--- apps/web/tests/shipped-composition.e2e.ts | 10 +- .../agent-team-panel/resize.expected.md | 31 +- .../agent-team-panel/task.expected.md | 19 +- apps/web/tests/subagent-conversation.e2e.ts | 77 ++- apps/web/tests/subagent-interrupt-ui.e2e.ts | 4 +- apps/web/tests/workflow-run.e2e.ts | 2 +- docs/capability-seams.md | 8 +- docs/capability-seams.zh.md | 8 +- docs/config-catalog.md | 51 +- docs/config-catalog.zh.md | 51 +- docs/event-producer-consumer.md | 4 +- docs/event-producer-consumer.zh.md | 4 +- docs/module-graph.md | 35 +- docs/module-graph.zh.md | 35 +- docs/persistence-catalog.i18n.yaml | 96 ++-- docs/persistence-catalog.md | 102 +++- docs/persistence-catalog.zh.md | 102 +++- .../2026-10-19-session-team-mode.i18n.yaml | 28 + .../2026-10-19-session-team-mode.md | 48 ++ .../2026-10-19-session-team-mode.schema.json | 15 + .../2026-10-19-session-team-mode.zh.md | 48 ++ docs/persistence-schema.json | 29 +- docs/subsystems/agent-team.i18n.yaml | 12 +- docs/subsystems/agent-team.md | 32 +- docs/subsystems/agent-team.zh.md | 32 +- docs/tool-catalog.i18n.yaml | 34 +- docs/tool-catalog.md | 14 +- docs/tool-catalog.zh.md | 14 +- .../team-direct-inbox/guide.i18n.yaml | 4 +- .../v0.2.1-alpha.1/team-direct-inbox/guide.md | 2 +- .../team-direct-inbox/guide.zh.md | 2 +- .../session-team-mode/guide.i18n.yaml | 16 + .../v0.2.1-rc.2/session-team-mode/guide.md | 22 + .../v0.2.1-rc.2/session-team-mode/guide.zh.md | 22 + .../team-mode-menu/guide.i18n.yaml | 16 + .../v0.2.1-rc.3/team-mode-menu/guide.md | 18 + .../v0.2.1-rc.3/team-mode-menu/guide.zh.md | 18 + .../team-projection/guide.i18n.yaml | 16 + .../v0.2.1-rc.3/team-projection/guide.md | 20 + .../v0.2.1-rc.3/team-projection/guide.zh.md | 20 + packages/README.i18n.yaml | 4 +- packages/README.md | 1 + packages/README.zh.md | 1 + packages/api/remotes/README.i18n.yaml | 4 +- packages/api/remotes/README.md | 2 +- packages/api/remotes/README.zh.md | 2 +- packages/api/remotes/package.json | 1 + packages/api/remotes/src/client/index.ts | 4 +- packages/api/remotes/tsconfig.client.json | 1 + packages/boot/app-boot/README.i18n.yaml | 4 +- packages/boot/app-boot/README.md | 2 + packages/boot/app-boot/README.zh.md | 2 + packages/boot/app-boot/tests/profile.spec.ts | 4 +- packages/bundle/base/README.md | 2 + packages/bundle/base/README.zh.md | 2 + packages/bundle/base/cordis.patch.yml | 8 + packages/bundle/base/package.json | 44 +- packages/bundle/web-app/README.i18n.yaml | 4 +- packages/bundle/web-app/README.md | 2 + packages/bundle/web-app/README.zh.md | 2 + packages/bundle/web-app/cordis.patch.yml | 3 + packages/bundle/web-app/package.json | 14 +- packages/client/README.i18n.yaml | 4 +- packages/client/README.md | 1 + packages/client/README.zh.md | 1 + packages/client/locale/src/locales/de.ts | 2 + packages/client/locale/src/locales/en.ts | 2 + packages/client/locale/src/locales/es.ts | 2 + packages/client/locale/src/locales/fr.ts | 2 + packages/client/locale/src/locales/ja.ts | 2 + packages/client/locale/src/locales/pt.ts | 2 + packages/client/locale/src/locales/ru.ts | 2 + packages/client/locale/src/locales/zh.ts | 2 + .../client/ui-agent-team/README.i18n.yaml | 40 ++ packages/client/ui-agent-team/README.md | 94 ++++ packages/client/ui-agent-team/README.zh.md | 94 ++++ .../ui-agent-team}/locale/de.json | 0 .../ui-agent-team}/locale/en.json | 0 .../ui-agent-team}/locale/es.json | 0 .../ui-agent-team}/locale/fr.json | 0 .../ui-agent-team}/locale/ja.json | 0 .../ui-agent-team}/locale/pt.json | 0 .../ui-agent-team}/locale/ru.json | 0 .../ui-agent-team}/locale/zh.json | 0 .../ui-agent-team}/package.json | 9 +- .../src/client/TeamBoard.module.css} | 101 +--- .../ui-agent-team/src/client/TeamBoard.tsx | 117 ++++ .../src/client/TeamModeToggle.module.css | 11 + .../src/client/TeamModeToggle.tsx | 52 ++ .../ui-agent-team}/src/client/index.ts | 6 +- .../ui-agent-team}/src/client/locales.ts | 16 + .../ui-agent-team}/src/client/mount.ts | 29 +- .../ui-agent-team}/src/css-modules.d.ts | 0 .../ui-agent-team}/src/index.ts | 0 .../tests/browser-plugin.client.spec.ts | 92 +++ .../tests/team-board.client.spec.tsx | 286 ++++++++++ .../tests/team-mode.client.spec.tsx | 63 +++ packages/client/ui-agent-team/tsconfig.json | 23 + .../ui-agent-team}/tsdown.config.ts | 2 +- .../ui-commands/src/client/presentation.ts | 3 +- .../ui-conversation/src/client/locales.ts | 42 +- .../ui-model-selection/README.i18n.yaml | 4 +- packages/client/ui-model-selection/README.md | 2 + .../client/ui-model-selection/README.zh.md | 2 + .../src/client/ModelSelect.tsx | 14 +- .../ui-model-selection/src/client/index.ts | 1 + .../ui-model-selection/src/client/slots.ts | 11 + .../tests/model-select.client.spec.tsx | 1 + .../tests/components.client.spec.tsx | 28 +- packages/client/ui-subagent/README.i18n.yaml | 16 +- packages/client/ui-subagent/README.md | 14 +- packages/client/ui-subagent/README.zh.md | 14 +- packages/client/ui-subagent/package.json | 3 +- .../client/SubagentHeaderLineage.module.css | 56 +- .../src/client/SubagentHeaderLineage.tsx | 179 ++++-- .../client/ui-subagent/src/client/index.ts | 14 +- .../client/ui-subagent/src/client/locales.ts | 16 + .../client/ui-subagent/src/client/slots.ts | 13 + .../tests/browser-plugin.client.spec.ts | 32 +- .../tests/conversation-ui.client.spec.tsx | 197 +++++-- packages/client/ui-subagent/tsconfig.json | 3 + packages/client/ui-tool/README.i18n.yaml | 8 +- packages/client/ui-tool/README.md | 4 +- packages/client/ui-tool/README.zh.md | 4 +- .../tool/components/ToolDetails.module.css | 44 ++ .../client/tool/components/ToolDetails.tsx | 29 +- .../tool/models/control-details-model.ts | 23 +- .../client/tool/models/detail-model-shared.ts | 2 +- .../src/client/tool/toolviews/details-row.tsx | 5 +- .../tests/details-models.client.spec.ts | 16 +- .../ui-tool/tests/details-row.client.spec.tsx | 41 +- .../core/session/src/known-event-types.ts | 1 + packages/experimental/README.i18n.yaml | 4 +- packages/experimental/README.md | 4 - packages/experimental/README.zh.md | 4 - .../agent-team-profile/README.i18n.yaml | 52 -- .../experimental/agent-team-profile/README.md | 117 ---- .../agent-team-profile/README.zh.md | 117 ---- .../agent-team-profile/cordis.patch.yml | 15 - .../agent-team-profile/docs/DETAIL.md | 16 - .../agent-team-profile/docs/DETAIL.zh.md | 16 - .../agent-team-profile/docs/preview.svg | 1 - .../agent-team-profile/docs/preview.zh.svg | 1 - .../experimental/agent-team-profile/icon.svg | 12 - .../agent-team-profile/locale/de.json | 7 - .../agent-team-profile/locale/en.json | 7 - .../agent-team-profile/locale/es.json | 7 - .../agent-team-profile/locale/fr.json | 7 - .../agent-team-profile/locale/ja.json | 7 - .../agent-team-profile/locale/pt.json | 7 - .../agent-team-profile/locale/ru.json | 7 - .../agent-team-profile/locale/zh.json | 7 - .../agent-team-profile/package.json | 54 -- .../agent-team-profile/src/index.ts | 8 - .../agent-team-profile/tests/profile.spec.ts | 54 -- .../agent-team-profile/tsconfig.json | 21 - .../experimental/agent-team/README.i18n.yaml | 100 ---- .../experimental/agent-team/src/projection.ts | 366 ------------ .../client-ui-agent-team/README.i18n.yaml | 40 -- .../client-ui-agent-team/README.md | 96 ---- .../client-ui-agent-team/README.zh.md | 96 ---- .../src/client/TeamAction.tsx | 265 --------- .../src/client/team-root.ts | 29 - .../tests/browser-plugin.client.spec.ts | 57 -- .../tests/team-action.client.spec.tsx | 526 ------------------ .../client-ui-agent-team/tsconfig.json | 20 - .../experimental/client-ui-stats/package.json | 1 + .../tests/stats-fold.client.spec.ts | 8 +- .../liveshare/src/endpoint-classification.ts | 1 + .../tests/viewer-access-policy.spec.ts | 2 +- .../tool-agent-team/README.i18n.yaml | 103 ---- .../experimental/tool-agent-team/src/index.ts | 250 --------- .../src/client/slot-catalog.ts | 123 +++- .../extensions/tool-cordis/src/api-catalog.ts | 13 + .../references/packages.md | 11 +- packages/subagent/subagent/src/lifecycle.ts | 8 +- .../tests/wait-agent.spec.ts | 15 + packages/team/README.i18n.yaml | 25 + packages/team/README.md | 39 ++ packages/team/README.zh.md | 39 ++ packages/team/agent-team/README.i18n.yaml | 97 ++++ .../agent-team/README.md | 46 +- .../agent-team/README.zh.md | 50 +- .../agent-team/locale/de.json | 0 .../agent-team/locale/en.json | 0 .../agent-team/locale/es.json | 0 .../agent-team/locale/fr.json | 0 .../agent-team/locale/ja.json | 0 .../agent-team/locale/pt.json | 0 .../agent-team/locale/ru.json | 0 .../agent-team/locale/zh.json | 0 .../agent-team/package.json | 27 +- .../agent-team/src/client.ts | 0 .../agent-team/src/error.ts | 0 .../agent-team/src/index.ts | 42 +- .../agent-team/src/journal.ts | 0 packages/team/agent-team/src/projection.ts | 212 +++++++ .../agent-team/src/task-board.ts | 14 +- .../agent-team/src/task-graph.ts | 0 .../agent-team/src/task-view.ts | 0 .../agent-team/src/types.ts | 5 +- .../agent-team/src/validation.ts | 0 .../agent-team/tests/built-lib.e2e.ts | 20 +- .../agent-team/tests/enable-team-fixture.ts | 17 + .../tests/historical-records-fixture.ts | 33 ++ .../agent-team/tests/persistence.spec.ts | 67 ++- .../tests/projection-events.spec.ts | 405 ++++++-------- .../agent-team/tests/session-mode-fixture.ts | 31 ++ .../agent-team/tests/team.spec.ts | 97 +++- .../agent-team/tests/test-session-query.ts | 0 .../agent-team/tsconfig.json | 1 + .../agent-team/tsdown.config.ts | 0 .../team/tool-agent-team/README.i18n.yaml | 103 ++++ .../tool-agent-team/README.md | 29 +- .../tool-agent-team/README.zh.md | 31 +- .../tool-agent-team/locale/de.json | 0 .../tool-agent-team/locale/en.json | 0 .../tool-agent-team/locale/es.json | 0 .../tool-agent-team/locale/fr.json | 0 .../tool-agent-team/locale/ja.json | 0 .../tool-agent-team/locale/pt.json | 0 .../tool-agent-team/locale/ru.json | 0 .../tool-agent-team/locale/zh.json | 0 .../tool-agent-team/package.json | 8 +- packages/team/tool-agent-team/src/index.ts | 230 ++++++++ .../tool-agent-team/tests/tool-team.spec.ts | 301 +++++++++- .../tool-agent-team/tsconfig.json | 0 packages/util/official-bundles/src/bundles.ts | 2 +- .../official-bundles/src/plugin-manager.ts | 2 - pnpm-lock.yaml | 419 +++++++------- python/sdk-runtime/package.json | 30 +- .../tests/expected/team-mode-projection.json | 48 ++ .../tests/test_cot_translation_projection.py | 20 +- scripts/check-workspace-constraints.spec.ts | 8 +- scripts/client-bundle-purity.spec.ts | 4 +- scripts/gen-doc-graphs.ts | 4 +- scripts/gen-tool-catalog.ts | 11 +- scripts/no-unknown-casts.baseline.json | 10 +- scripts/npm-baseline-packages.spec.ts | 2 +- scripts/release/families.spec.ts | 4 - scripts/run-gates.spec.ts | 2 +- scripts/run-gates.ts | 2 +- scripts/type-equiv.manifest.json | 12 +- .../verify-package-readme-model-experience.ts | 2 +- snapshots/sdk/sdk.snapshot.ts | 1 + .../sdk/team-session-mode/cordis.snapshot.yml | 15 + snapshots/sdk/team-session-mode/cordis.yml | 35 ++ .../sdk/team-session-mode/session.v4.jsonl | 42 ++ snapshots/sdk/team-session-mode/snapshot.yml | 10 + .../system-prompt.expected.md | 45 ++ .../tool-schemas.expected.json | 244 ++++++++ .../team-direct-inbox/session.v4.jsonl | 1 + .../runtime.cordis.yml | 3 + .../team-historical-records/session.v4.jsonl | 45 ++ .../team-historical-records/snapshot.yml | 9 + .../team-session-mode/cordis.snapshot.yml | 49 ++ .../session/team-session-mode/cordis.yml | 3 + .../team-session-mode/session.v4.jsonl | 39 ++ .../session/team-session-mode/snapshot.yml | 10 + .../system-prompt.expected.md | 54 ++ .../tool-schemas.expected.json | 244 ++++++++ .../session/team-targets/cordis.snapshot.yml | 6 +- snapshots/session/team-targets/cordis.yml | 6 +- .../session/team-targets/session.v4.jsonl | 7 +- .../session/team-task-inbox/session.v4.jsonl | 1 + .../team-tree-messages/session.v4.jsonl | 13 +- .../creation-recommendations/session.v4.jsonl | 2 +- .../branchless.expected.md | 2 +- .../subagent-conversation/nested.expected.md | 2 +- .../subagent-conversation/tree.expected.md | 14 +- .../ui-expanded.expected.md | 2 +- .../web/subagent-conversation/ui.expected.md | 2 +- .../image-queue.expected.md | 2 +- .../offline-composer.expected.md | 2 +- snapshots/web/tool-details/ui.expected.md | 23 +- tsconfig.base.json | 10 +- tsconfig.client.json | 2 +- tsconfig.host.json | 5 +- vitest.config.ts | 2 +- 315 files changed, 5825 insertions(+), 4067 deletions(-) create mode 100644 .agents/notes/implemented/architecture/2026-10-20-session-team-mode.i18n.yaml create mode 100644 .agents/notes/implemented/architecture/2026-10-20-session-team-mode.md create mode 100644 .agents/notes/implemented/architecture/2026-10-20-session-team-mode.zh.md create mode 100644 .agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.i18n.yaml create mode 100644 .agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.md create mode 100644 .agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.zh.md delete mode 100644 apps/web/tests/agent-team-panel.overlay.yml create mode 100644 apps/web/tests/expected/subagent-conversation/navigation-only.expected.md create mode 100644 docs/persistence-changes/2026-10-19-session-team-mode.i18n.yaml create mode 100644 docs/persistence-changes/2026-10-19-session-team-mode.md create mode 100644 docs/persistence-changes/2026-10-19-session-team-mode.schema.json create mode 100644 docs/persistence-changes/2026-10-19-session-team-mode.zh.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.zh.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.i18n.yaml create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.md create mode 100644 docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.zh.md create mode 100644 packages/client/ui-agent-team/README.i18n.yaml create mode 100644 packages/client/ui-agent-team/README.md create mode 100644 packages/client/ui-agent-team/README.zh.md rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/de.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/en.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/es.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/fr.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/ja.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/pt.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/ru.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/locale/zh.json (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/package.json (85%) rename packages/{experimental/client-ui-agent-team/src/client/TeamAction.module.css => client/ui-agent-team/src/client/TeamBoard.module.css} (53%) create mode 100644 packages/client/ui-agent-team/src/client/TeamBoard.tsx create mode 100644 packages/client/ui-agent-team/src/client/TeamModeToggle.module.css create mode 100644 packages/client/ui-agent-team/src/client/TeamModeToggle.tsx rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/src/client/index.ts (61%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/src/client/locales.ts (84%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/src/client/mount.ts (50%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/src/css-modules.d.ts (100%) rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/src/index.ts (100%) create mode 100644 packages/client/ui-agent-team/tests/browser-plugin.client.spec.ts create mode 100644 packages/client/ui-agent-team/tests/team-board.client.spec.tsx create mode 100644 packages/client/ui-agent-team/tests/team-mode.client.spec.tsx create mode 100644 packages/client/ui-agent-team/tsconfig.json rename packages/{experimental/client-ui-agent-team => client/ui-agent-team}/tsdown.config.ts (54%) create mode 100644 packages/client/ui-subagent/src/client/slots.ts delete mode 100644 packages/experimental/agent-team-profile/README.i18n.yaml delete mode 100644 packages/experimental/agent-team-profile/README.md delete mode 100644 packages/experimental/agent-team-profile/README.zh.md delete mode 100644 packages/experimental/agent-team-profile/cordis.patch.yml delete mode 100644 packages/experimental/agent-team-profile/docs/DETAIL.md delete mode 100644 packages/experimental/agent-team-profile/docs/DETAIL.zh.md delete mode 100644 packages/experimental/agent-team-profile/docs/preview.svg delete mode 100644 packages/experimental/agent-team-profile/docs/preview.zh.svg delete mode 100644 packages/experimental/agent-team-profile/icon.svg delete mode 100644 packages/experimental/agent-team-profile/locale/de.json delete mode 100644 packages/experimental/agent-team-profile/locale/en.json delete mode 100644 packages/experimental/agent-team-profile/locale/es.json delete mode 100644 packages/experimental/agent-team-profile/locale/fr.json delete mode 100644 packages/experimental/agent-team-profile/locale/ja.json delete mode 100644 packages/experimental/agent-team-profile/locale/pt.json delete mode 100644 packages/experimental/agent-team-profile/locale/ru.json delete mode 100644 packages/experimental/agent-team-profile/locale/zh.json delete mode 100644 packages/experimental/agent-team-profile/package.json delete mode 100644 packages/experimental/agent-team-profile/src/index.ts delete mode 100644 packages/experimental/agent-team-profile/tests/profile.spec.ts delete mode 100644 packages/experimental/agent-team-profile/tsconfig.json delete mode 100644 packages/experimental/agent-team/README.i18n.yaml delete mode 100644 packages/experimental/agent-team/src/projection.ts delete mode 100644 packages/experimental/client-ui-agent-team/README.i18n.yaml delete mode 100644 packages/experimental/client-ui-agent-team/README.md delete mode 100644 packages/experimental/client-ui-agent-team/README.zh.md delete mode 100644 packages/experimental/client-ui-agent-team/src/client/TeamAction.tsx delete mode 100644 packages/experimental/client-ui-agent-team/src/client/team-root.ts delete mode 100644 packages/experimental/client-ui-agent-team/tests/browser-plugin.client.spec.ts delete mode 100644 packages/experimental/client-ui-agent-team/tests/team-action.client.spec.tsx delete mode 100644 packages/experimental/client-ui-agent-team/tsconfig.json delete mode 100644 packages/experimental/tool-agent-team/README.i18n.yaml delete mode 100644 packages/experimental/tool-agent-team/src/index.ts create mode 100644 packages/team/README.i18n.yaml create mode 100644 packages/team/README.md create mode 100644 packages/team/README.zh.md create mode 100644 packages/team/agent-team/README.i18n.yaml rename packages/{experimental => team}/agent-team/README.md (70%) rename packages/{experimental => team}/agent-team/README.zh.md (69%) rename packages/{experimental => team}/agent-team/locale/de.json (100%) rename packages/{experimental => team}/agent-team/locale/en.json (100%) rename packages/{experimental => team}/agent-team/locale/es.json (100%) rename packages/{experimental => team}/agent-team/locale/fr.json (100%) rename packages/{experimental => team}/agent-team/locale/ja.json (100%) rename packages/{experimental => team}/agent-team/locale/pt.json (100%) rename packages/{experimental => team}/agent-team/locale/ru.json (100%) rename packages/{experimental => team}/agent-team/locale/zh.json (100%) rename packages/{experimental => team}/agent-team/package.json (71%) rename packages/{experimental => team}/agent-team/src/client.ts (100%) rename packages/{experimental => team}/agent-team/src/error.ts (100%) rename packages/{experimental => team}/agent-team/src/index.ts (68%) rename packages/{experimental => team}/agent-team/src/journal.ts (100%) create mode 100644 packages/team/agent-team/src/projection.ts rename packages/{experimental => team}/agent-team/src/task-board.ts (97%) rename packages/{experimental => team}/agent-team/src/task-graph.ts (100%) rename packages/{experimental => team}/agent-team/src/task-view.ts (100%) rename packages/{experimental => team}/agent-team/src/types.ts (95%) rename packages/{experimental => team}/agent-team/src/validation.ts (100%) rename packages/{experimental => team}/agent-team/tests/built-lib.e2e.ts (63%) create mode 100644 packages/team/agent-team/tests/enable-team-fixture.ts create mode 100644 packages/team/agent-team/tests/historical-records-fixture.ts rename packages/{experimental => team}/agent-team/tests/persistence.spec.ts (68%) rename packages/{experimental => team}/agent-team/tests/projection-events.spec.ts (55%) create mode 100644 packages/team/agent-team/tests/session-mode-fixture.ts rename packages/{experimental => team}/agent-team/tests/team.spec.ts (86%) rename packages/{experimental => team}/agent-team/tests/test-session-query.ts (100%) rename packages/{experimental => team}/agent-team/tsconfig.json (93%) rename packages/{experimental => team}/agent-team/tsdown.config.ts (100%) create mode 100644 packages/team/tool-agent-team/README.i18n.yaml rename packages/{experimental => team}/tool-agent-team/README.md (77%) rename packages/{experimental => team}/tool-agent-team/README.zh.md (77%) rename packages/{experimental => team}/tool-agent-team/locale/de.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/en.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/es.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/fr.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/ja.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/pt.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/ru.json (100%) rename packages/{experimental => team}/tool-agent-team/locale/zh.json (100%) rename packages/{experimental => team}/tool-agent-team/package.json (89%) create mode 100644 packages/team/tool-agent-team/src/index.ts rename packages/{experimental => team}/tool-agent-team/tests/tool-team.spec.ts (70%) rename packages/{experimental => team}/tool-agent-team/tsconfig.json (100%) create mode 100644 python/sdk/tests/expected/team-mode-projection.json create mode 100644 snapshots/sdk/team-session-mode/cordis.snapshot.yml create mode 100644 snapshots/sdk/team-session-mode/cordis.yml create mode 100644 snapshots/sdk/team-session-mode/session.v4.jsonl create mode 100644 snapshots/sdk/team-session-mode/snapshot.yml create mode 100644 snapshots/sdk/team-session-mode/system-prompt.expected.md create mode 100644 snapshots/sdk/team-session-mode/tool-schemas.expected.json create mode 100644 snapshots/session/team-historical-records/runtime.cordis.yml create mode 100644 snapshots/session/team-historical-records/session.v4.jsonl create mode 100644 snapshots/session/team-historical-records/snapshot.yml create mode 100644 snapshots/session/team-session-mode/cordis.snapshot.yml create mode 100644 snapshots/session/team-session-mode/cordis.yml create mode 100644 snapshots/session/team-session-mode/session.v4.jsonl create mode 100644 snapshots/session/team-session-mode/snapshot.yml create mode 100644 snapshots/session/team-session-mode/system-prompt.expected.md create mode 100644 snapshots/session/team-session-mode/tool-schemas.expected.json diff --git a/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.i18n.yaml b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.i18n.yaml new file mode 100644 index 0000000000..afbff73943 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-20-session-team-mode.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 .agents/notes/implemented/architecture/2026-10-20-session-team-mode.md +/agent-note-team-availability-is-selected-per-session: + en: 5f36d81bd414ea67 + zh: 71883b0fe2afd1b6 +/agent-note-team-availability-is-selected-per-session/problem: + en: db7a81ab452fdd0f + zh: a14fdcdb8ff4cd47 +/agent-note-team-availability-is-selected-per-session/decision: + en: f4e4ba7be0eda796 + zh: 410649e86ff5ca72 +/agent-note-team-availability-is-selected-per-session/alternatives-considered: + en: cd3cc1f3fab84f80 + zh: 6d7792742d62f9fe +/agent-note-team-availability-is-selected-per-session/consequences: + en: b56a6a1c8ad1aa7a + zh: 43bdd8a5bccce8bf diff --git a/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.md b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.md new file mode 100644 index 0000000000..2449e1e4d0 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.md @@ -0,0 +1,29 @@ +# Agent Note: Team availability is selected per Session + +Status: implemented + +English | [中文](2026-10-20-session-team-mode.zh.md) + +## Problem + +A plugin-level Team switch requires users to change the shared profile before choosing collaboration for one conversation. It can disable another Session’s capability and leaves a second control beside the Session selection. Ordinary tasks still need their existing tool catalog, and disabling collaboration must not discard task history or children. + +## Decision + +The base bundle loads the Team service and task tools, and the Web bundle loads the Team UI. The packages belong to the subagent and client product groups. A saved root-Session selection gates task tools and Team guidance throughout the tree, and the shared board in the root conversation; new Sessions default off. Ordinary subagent creation, communication, and navigation remain available. + +The model and reasoning menu owns the user’s selection. The root conversation’s Subagent panel displays members above shared tasks. Disabling Team keeps the journal and children; re-enabling restores the board. Historical task boards without a saved selection stay enabled. Profile loading removes the retired optional bundle selection, retaining row overrides. + +This supersedes the explicit-composition choice in [Agent Teams](../feature/2026-08-05-agent-teams.md). Its durability, ownership, and lifecycle decisions remain active. [Optional bundles](../process/2026-09-15-shipped-optional-bundles.md) still governs other experimental features. + +## Alternatives considered + +**Keep both switches.** Capability installation remains a prerequisite that the conversation control cannot explain or resolve; users must manage shared profile state for a Session decision. + +**Hide the plugin card without loading Team.** New profiles would have no way to obtain the capability. Loading experimental packages in the default profile would violate product isolation; moving them to their owning product groups makes the delivered support explicit. + +## Consequences + +Default profiles carry the Team implementation while exposing no task tools until enabled. The removed package names require custom compositions to migrate; durable event names and task data remain unchanged. [The upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md) owns those steps. + +Verification covers default startup, new-Session opt-in, independent roots, persisted selections, retained tasks, old profile migration, and absence of the plugin switch. Product-isolation checks retain an unrelated experimental plugin as their negative control. diff --git a/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.zh.md b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.zh.md new file mode 100644 index 0000000000..5b8baec113 --- /dev/null +++ b/.agents/notes/implemented/architecture/2026-10-20-session-team-mode.zh.md @@ -0,0 +1,29 @@ +# Agent Note: 按 Session 选择 Team 能力 + +Status: implemented + +[English](2026-10-20-session-team-mode.md) | 中文 + +## Problem + +插件级 Team 开关要求用户先修改共享 profile,才能为一个会话选择协作。它可能关闭另一个 Session 的能力,并在会话选项之外留下第二个控件。普通任务仍需要原有工具目录,关闭协作也不能丢弃任务历史或子智能体。 + +## Decision + +基础 bundle 加载 Team 服务与任务工具,Web bundle 加载 Team UI。相关包归属 subagent 与 client 产品组。根 Session 保存的选择控制整棵树的任务工具与 Team 指引,以及根会话中的共享任务板;新 Session 默认关闭。普通 subagent 创建、通信与导航继续可用。 + +模型与推理等级菜单承载用户选择。根会话的 Subagent 面板在成员下方显示共享任务。关闭 Team 保留日志与子智能体;重新开启恢复任务板。没有保存选择的历史任务板保持开启。profile 加载时移除已停用的可选 bundle 选择,保留配置行覆盖。 + +本决策替代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 中显式选择组合的部分;其持久性、归属与生命周期决策继续有效。[可选 bundle](../process/2026-09-15-shipped-optional-bundles.zh.md)仍约束其他实验性功能。 + +## Alternatives considered + +**保留两个开关。** 安装能力仍是会话控件无法解释或解决的前置条件;用户必须为 Session 决策管理共享 profile 状态。 + +**隐藏插件卡片但不加载 Team。** 新 profile 将无法获得该能力。默认 profile 加载实验性包会违反产品隔离;将相关包移入所属产品组可明确随产品提供的支持。 + +## Consequences + +默认 profile 包含 Team 实现,但开启前不暴露任务工具。自定义组合必须迁移已移除的包名;持久事件名称与任务数据保持不变。[升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md)负责具体步骤。 + +验证覆盖默认启动、新 Session 手动开启、独立根、选择持久化、任务保留、旧 profile 迁移和插件开关移除。产品隔离检查使用另一个无关实验性插件作为负对照。 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml index 797998aff0..831eb56f91 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.i18n.yaml @@ -6,14 +6,14 @@ en: c914d8148bfc4bf9 zh: a8115f87c0ac6cae /agent-note-durable-agent-teams-over-continuable-children/problem: - en: bab62798c2dc07b4 - zh: fe42c5abf9127d37 + en: 567b68ab8eb4b5b2 + zh: 31a40aa22821d63a /agent-note-durable-agent-teams-over-continuable-children/decision: - en: fb13f2ba96339bd5 - zh: 9f4391a2b6004d57 + en: 85b185779886bf3d + zh: 201588eda21cc005 /agent-note-durable-agent-teams-over-continuable-children/profile-delegation: - en: 5e938a21b2785b4c - zh: 4e85e7e2cdbcf7e0 + en: f4c8d8760c7ebcf4 + zh: 32ccca202a7769ac /agent-note-durable-agent-teams-over-continuable-children/team-identity: en: d79f4425f2c1929f zh: 6586410db4a02cd1 @@ -21,8 +21,8 @@ en: 624e9d42af1af859 zh: 020dcbb26211ea67 /agent-note-durable-agent-teams-over-continuable-children/mailbox-and-task-transactions: - en: a80f5b07e3b704c4 - zh: 19296eb95bd58e3b + en: ee27e04d2c84782e + zh: 4b5b07f64e53efb1 /agent-note-durable-agent-teams-over-continuable-children/shared-checkout-boundary: en: 384f75638b0f1fb6 zh: b7ab1191784d3d27 @@ -33,8 +33,8 @@ en: 446fa0404d5df81b zh: 46121e6971bf53a7 /agent-note-durable-agent-teams-over-continuable-children/testing: - en: 1f99ff4e740cdeac - zh: 28818401618671b9 + en: 7151cc5f849c7ef1 + zh: c70c2283c731ff31 /agent-note-durable-agent-teams-over-continuable-children/consequences: en: c071fa2ccdb911b5 zh: 3b8cf0d3303a4d40 diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md index 33cd6947ab..6160c64af2 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.md @@ -10,19 +10,19 @@ The subagent seam supplies fresh/fork providers, durable child Sessions, FIFO fo All same-process Agents also share one checkout. Filesystem edit tools can reject an observed stale version, but Bash, formatters, generators, and external writers bypass that fence. Treating a teammate name or task owner as a file lock would hide rather than solve this concurrency boundary. -Agent Teams needs an explicit opt-in composition while its public contracts remain experimental. The default tool catalog and simple-task behavior must remain unchanged, while an explicitly requested Team must survive child Activation settlement and mailbox delivery races long enough for the Lead to aggregate the result before process teardown. +Team collaboration requires explicit opt-in; [Session selection](../architecture/2026-10-20-session-team-mode.md) owns its granularity and default composition. The default tool catalog and simple-task behavior must remain unchanged, while an explicitly requested Team must survive child Activation settlement and mailbox delivery races long enough for the Lead to aggregate the result before process teardown. ## Decision Every ordinary root and its descendants form the Team identified by the root `SessionId`; durable task state begins with the first task event. [Subagent teammates](../architecture/2026-10-15-agent-teams-as-subagents.md) owns the single agent identity. [Task-board wait coordination](../architecture/2026-10-19-team-task-board-inbox.md) owns task-board signals and ordinary inbox waiting. An ordinary Session fork leads a new tree and excludes inherited Team authority. -The implementation is split into `@deepseek-ai/dsh-experimental-agent-team`, which owns `ctx.agentTeams` and durable semantics, and `@deepseek-ai/dsh-experimental-tool-agent-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly; Team registers no tool that shares a name with the subagent controls. The shared policy announces Team availability and describes coordination responsibilities. Both packages are public members of `packages/experimental/`; the [experimental package reference](../../../../packages/experimental/AGENTS.md) owns publication, dependency isolation, and promotion. +The implementation is split into `@deepseek-ai/dsh-agent-team`, which owns `ctx.agentTeams` and durable semantics, and `@deepseek-ai/dsh-tool-agent-team`, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each `execute` against what the model is promised and no result spends tokens on indentation. The base bundle mounts both plugins; Team registers no tool that shares a name with the subagent controls. The shared policy announces Team availability and describes coordination responsibilities. Both packages belong to the [subagent family](../../../../packages/subagent/README.md); the root Session’s saved selection gates the task tools and policy. The [Session-mode upgrade guide](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md) describes profile migration. The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit. ## Profile delegation -The [Team profile](../../../../packages/experimental/agent-team-profile/README.md) inserts the Team rows and keeps the base delegation and control tools; teammates are started with those tools as described in [subagents](../architecture/2026-10-15-agent-teams-as-subagents.md). Ordinary Session forks retain their history; [current identity handling](../architecture/2026-10-13-team-identity-runtime-context.md) supplies the fork's own role. +The [base bundle](../../../../packages/bundle/base/README.md) inserts the Team rows and keeps the base delegation and control tools; teammates are started with those tools as described in [subagents](../architecture/2026-10-15-agent-teams-as-subagents.md). Ordinary Session forks retain their history; [current identity handling](../architecture/2026-10-13-team-identity-runtime-context.md) supplies the fork's own role. ## Team identity @@ -34,9 +34,7 @@ Teammate creation and cold resume belong to the subagent runtime ([subagent team ## Mailbox and task transactions -The historical Lead-log mailbox remains a readable format: queued and delivered records keep their stable identity and sender, and Team payload validation still applies to them. Team never delivers or acknowledges them; messages between members use the target Agent inbox through the subagent operation ([subagents](../architecture/2026-10-15-agent-teams-as-subagents.md)). - -Unknown mailbox content stays as decoded JSON during event projection and checkpoint admission. Validation inspects its type without rebuilding its fields, because generic object parsing can omit a valid own `__proto__` key. Locally declared content variants undergo structural validation; opaque plugin fields do not gain Team semantics. Cache invalidation is necessary because parser correction alone cannot recover keys omitted from cached state. The [Team documentation](../../../../packages/experimental/agent-team/README.md) owns the restore behavior. +The historical Lead-log mailbox remains readable in original Session logs. The [task-only projection decision](../simplification/2026-10-29-team-task-only-projection.md) supersedes mailbox projection, checkpoint retention, and Team payload validation. Historical queued messages are never delivered or acknowledged. Member messages use the subagent `send_message` operation, which steers running, idle, and cold targets and returns inbox acceptance. @@ -78,7 +76,7 @@ Publication requires `apply` to return a new state per applied event and replace Package tests cover task transitions, tree-based assignment, historical roster and mailbox decoding, and post-commit wait signals. Ordinary subagent tests cover inbox waiting; recorded Team compositions cover identity, task policy, and tools. -Inbox waiting follows [subagent controls](../../../../packages/subagent/tool-subagent-control/README.md); Team identity and task policy follow the [Team tool reference](../../../../packages/experimental/tool-agent-team/README.md). +Inbox waiting follows [subagent controls](../../../../packages/subagent/tool-subagent-control/README.md); Team identity and task policy follow the [Team tool reference](../../../../packages/team/tool-agent-team/README.md). ## Consequences diff --git a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md index 3c7cfd0f4a..93261a2c76 100644 --- a/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md +++ b/.agents/notes/implemented/feature/2026-08-05-agent-teams.zh.md @@ -10,19 +10,19 @@ subagent seam 已提供 fresh/fork provider、持久 child Session、FIFO foll 同进程 Agent 还共享一个 checkout。文件系统 edit 工具可以拒绝已观察到的陈旧版本,但 Bash、formatter、generator 与外部 writer 会绕过该屏障。把 teammate name 或 task owner 当作文件锁只会掩盖而不是解决该并发边界。 -Agent Teams 的公开约定仍处于实验阶段,因此需要显式启用的组合。默认工具目录与简单任务行为必须保持不变;而显式请求的 Team 必须能跨越 child Activation settlement 与 mailbox 投递竞争,使 Lead 在进程 teardown 前汇总结果。 +Team 协作需要显式开启;[Session 选择](../architecture/2026-10-20-session-team-mode.zh.md)负责选择粒度与默认组合。默认工具目录与简单任务行为必须保持不变;而显式请求的 Team 必须能跨越 child Activation settlement 与 mailbox 投递竞争,使 Lead 在进程 teardown 前汇总结果。 ## Decision 每个普通根及其后代组成以根 `SessionId` 标识的 Team;持久任务状态从首个任务事件开始。[subagent 队友](../architecture/2026-10-15-agent-teams-as-subagents.zh.md)负责单一 agent 身份。[任务板等待协调](../architecture/2026-10-19-team-task-board-inbox.zh.md)负责任务板信号和普通收件箱等待。普通 Session fork 成为一棵新树的根,并排除继承的 Team 权限。 -实现拆分为 `@deepseek-ai/dsh-experimental-agent-team` 与 `@deepseek-ai/dsh-experimental-tool-agent-team`:前者负责 `ctx.agentTeams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件;Team 不注册与 subagent control 同名的工具。共享策略声明 Team 可用,并说明协调责任。两个包都是 `packages/experimental/` 的公开成员;[实验性包参考](../../../../packages/experimental/AGENTS.md)负责发布、依赖隔离与 promotion。 +实现拆分为 `@deepseek-ai/dsh-agent-team` 与 `@deepseek-ai/dsh-tool-agent-team`:前者负责 `ctx.agentTeams` 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 `execute` 是否符合对模型的承诺,也没有结果把 token 花在缩进上。基础 bundle 挂载两个插件;Team 不注册与 subagent control 同名的工具。共享策略声明 Team 可用,并说明协调责任。两个包属于 [subagent 家族](../../../../packages/subagent/README.zh.md);根 Session 保存的选择控制任务工具与策略。[Session 模式升级指南](../../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md)说明 profile 迁移。 Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。 ## Profile delegation -[Team profile](../../../../packages/experimental/agent-team-profile/README.zh.md) 插入 Team 行,并保留 base 的委派与控制工具;teammate 按[subagent](../architecture/2026-10-15-agent-teams-as-subagents.zh.md)所述使用这些工具启动。普通 Session fork 保留历史;[当前身份处理](../architecture/2026-10-13-team-identity-runtime-context.zh.md)提供 fork 自己的角色。 +[基础 bundle](../../../../packages/bundle/base/README.zh.md) 插入 Team 行,并保留 base 的委派与控制工具;teammate 按[subagent](../architecture/2026-10-15-agent-teams-as-subagents.zh.md)所述使用这些工具启动。普通 Session fork 保留历史;[当前身份处理](../architecture/2026-10-13-team-identity-runtime-context.zh.md)提供 fork 自己的角色。 ## Team identity @@ -34,9 +34,7 @@ Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍 ## Mailbox and task transactions -历史 Lead-log mailbox 仍是可读格式:queued 与 delivered 记录保留稳定身份与发送者,Team payload 校验仍适用于它们。Team 从不投递或确认这些记录;member 之间的消息通过 subagent 操作进入目标 Agent inbox([subagent](../architecture/2026-10-15-agent-teams-as-subagents.zh.md))。 - -事件投影和 checkpoint 准入期间,未知 mailbox 内容保持为已解码 JSON。校验只检查其 type,不重建字段,因为通用对象解析可能省略有效的自有 `__proto__` 键。本地声明的内容变体接受结构校验;不透明的插件字段不获得 Team 语义。缓存失效是必要的,因为只修正解析器无法恢复缓存状态中省略的键。[Team 文档](../../../../packages/experimental/agent-team/README.zh.md)拥有恢复行为的说明。 +历史 Lead-log mailbox 仍可从原始 Session 日志读取。[仅投影任务的决策](../simplification/2026-10-29-team-task-only-projection.zh.md)取代邮箱投影、检查点保留和 Team 载荷校验。历史排队消息绝不再投递或确认。 member 消息使用 subagent 的 `send_message` 操作,它对 running、idle 和 cold target 使用 Steer,并返回 inbox 接收结果。 @@ -78,7 +76,7 @@ Web panel 读取 Lead Session 的 `agentTeam` 传输投影,因为现有投影 包测试覆盖任务转换、基于树的分配、历史名册和邮箱解码,以及提交后等待信号。普通 subagent 测试覆盖收件箱等待;录制 Team 组合覆盖身份、任务策略和工具。 -收件箱等待遵循 [subagent 控制工具](../../../../packages/subagent/tool-subagent-control/README.zh.md);Team 身份和任务策略遵循 [Team 工具参考](../../../../packages/experimental/tool-agent-team/README.zh.md)。 +收件箱等待遵循 [subagent 控制工具](../../../../packages/subagent/tool-subagent-control/README.zh.md);Team 身份和任务策略遵循 [Team 工具参考](../../../../packages/team/tool-agent-team/README.zh.md)。 ## Consequences diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml index 9e538f26c6..970eaf9557 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.i18n.yaml @@ -9,8 +9,8 @@ en: dc43ba84345e1405 zh: dbc03572ab7710d2 /agent-note-ship-optional-bundles-with-the-installation/decision: - en: 2f20e356517715a6 - zh: 35c7ff8c4cd8a712 + en: 0f3e0daee65b6510 + zh: 2e9c8000da75fb6c /agent-note-ship-optional-bundles-with-the-installation/alternatives-considered: en: 95b48b61757f1f13 zh: e9fb2d6ea492a4bc diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md index 74f824fc88..aaa055f7b5 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.md @@ -14,7 +14,6 @@ The launcher names in `OPTIONAL_BUNDLES` (`packages/util/official-bundles/src/bu Default-product isolation keeps its rules with one declared exception: an optional bundle's dependency graph is outside the default product. The static gate skips the `dependencies` edge from `@deepseek-ai/dsh` to a listed bundle and still rejects a runtime import, a shipped composition, a preset, or a default template that names it, an experimental dependency the list does not name, and a listed name that is not a runtime dependency or not a bundle. The workspace-constraints check accepts the same `dependencies` edges and no other runtime section, and the packed-install release check skips them from the installed entry package while requiring each listed bundle to be installed. -The [single-bundle reference](../../../../packages/experimental/agent-team-profile/README.md) supersedes the two Agent Teams selections with one `@deepseek-ai/dsh-experimental-agent-team-profile` selection. This note retains the installation-owned optional-bundle policy; the [optional-bundle decision for experimental capabilities](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.md) owns composition and display metadata; `OPTIONAL_BUNDLES` owns the current shipped list. ## Alternatives considered diff --git a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md index 129f76c454..6135f08763 100644 --- a/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md +++ b/.agents/notes/implemented/process/2026-09-15-shipped-optional-bundles.zh.md @@ -14,7 +14,6 @@ Web 插件页只管理用户装进 profile 的组合包。像 Agent Teams 这样 默认产品隔离的规则保持不变,只声明一个例外:可选组合包的依赖图在默认产品之外。静态门禁跳过从 `@deepseek-ai/dsh` 到列表中组合包的 `dependencies` 边,仍然拒绝运行时 import、随附组合、preset 或默认模板对它的引用,拒绝列表没有点名的实验依赖,也拒绝不是运行时依赖或不是组合包的列表项。workspace 约束检查接受同样的 `dependencies` 边而不接受其他运行时依赖段;发布时的 packed-install 检查对已安装入口包跳过这些边,并要求列表中的每个组合包都已安装。 -[单组合包参考](../../../../packages/experimental/agent-team-profile/README.zh.md)将两个 Agent Teams 选项替换为一个 `@deepseek-ai/dsh-experimental-agent-team-profile` 选项。本记录保留由安装方决定可选组合包的策略;[实验能力的可选 bundle 决策](../architecture/2026-09-21-experimental-capabilities-as-optional-bundles.zh.md)定义组合与显示元数据;当前随附名单由 `OPTIONAL_BUNDLES` 管理。 ## 考虑过的替代方案 diff --git a/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.i18n.yaml b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.i18n.yaml new file mode 100644 index 0000000000..cf5fb34bc3 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.i18n.yaml @@ -0,0 +1,19 @@ +# Bilingual-pair consistency record for 2026-10-29-team-task-only-projection.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 .agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.md +/agent-note-team-projection-retains-only-tasks-and-mode: + en: 030f2abb8b5137fe + zh: e661455f374c8b1f +/agent-note-team-projection-retains-only-tasks-and-mode/problem: + en: 43bd1c0daacd2b86 + zh: a35efb4e84cd9288 +/agent-note-team-projection-retains-only-tasks-and-mode/decision: + en: dd21640eb965f332 + zh: 345bc4df573cbadf +/agent-note-team-projection-retains-only-tasks-and-mode/alternatives-considered: + en: 96a0f421f9f92746 + zh: 314f4557f391f90d +/agent-note-team-projection-retains-only-tasks-and-mode/consequences: + en: 1c1b767b7905869f + zh: 1241351bfc433b70 diff --git a/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.md b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.md new file mode 100644 index 0000000000..57a85f4105 --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.md @@ -0,0 +1,29 @@ +# Agent Note: Team projection retains only tasks and mode + +Status: implemented + +English | [中文](2026-10-29-team-task-only-projection.zh.md) + +## Problem + +The Team projection retained historical member, message, and delivery collections after subagent discovery and messaging replaced their runtime consumers. Its retired lifecycle and mailbox validators could still fail the current task board, while checkpoints retained message bodies with no current reader. + +## Decision + +Project only tasks, saved mode, and their recovery metadata. Ignore `team/member` and `team/message/*` payloads without rebuilding or validating their former state machines. This deliberately gives up historical teammate and mailbox state recovery and the associated validation failures. The user accepted that loss; task recovery remains required. + +Checkpoint version 7 invalidates prior state, including failures caused by retired records. The registry rebuilds from the unchanged Session log. Historical event declarations, format migrations, message sources, and attachment export remain supported; no Session format bump or log conversion is needed. + +This partially supersedes the projection and mailbox-validation decisions in [Agent Teams](../feature/2026-08-05-agent-teams.md). The [subagent identity](../architecture/2026-10-15-agent-teams-as-subagents.md) and [inbox-only wait](2026-10-29-inbox-only-agent-wait.md) decisions remain active, as do task durability and shared-checkout semantics. + +## Alternatives considered + +**Retain decode-only roster and mailbox projections.** No production consumer reads these collections. Keeping them preserves message history in checkpoints and lets obsolete validation stop current task operations. + +**Migrate or delete historical log events.** The task board can ignore these records directly. Rewriting them adds migration work and would discard archival data that existing format readers and exports can still use. + +## Consequences + +Old teammate and mailbox state is unavailable through Team projection. Original log records remain readable, but neither grant authority nor trigger delivery. Reintroducing recovery requires a concrete consumer and an explicit decision about its retained data and failure scope. + +Tests cover ignored legacy payloads, version-6 checkpoint invalidation, current checkpoint suffix replay, JSONL restart with task mutation, stable view references, and a recorded headless profile accepting a task after retired lifecycle and mailbox records. Task and mode validation still fail visibly. diff --git a/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.zh.md b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.zh.md new file mode 100644 index 0000000000..221abe771b --- /dev/null +++ b/.agents/notes/implemented/simplification/2026-10-29-team-task-only-projection.zh.md @@ -0,0 +1,29 @@ +# Agent Note: Team 投影仅保留任务与模式 + +Status: implemented + +[English](2026-10-29-team-task-only-projection.md) | 中文 + +## Problem + +子代理发现和通信替代运行时消费者后,Team 投影仍保留历史成员、消息与投递集合。已退役的生命周期和邮箱校验器仍可能使当前任务板失败,而检查点保留着没有当前读取方的消息正文。 + +## Decision + +仅投影任务、保存的模式及其恢复元数据。忽略 `team/member` 和 `team/message/*` 载荷,不再重建或校验原有状态机。这明确放弃历史队友与邮箱状态恢复,以及相应的校验失败行为。用户接受这一能力损失;任务恢复仍为必要能力。 + +检查点版本 7 使旧状态失效,包括退役记录导致的失败。注册表从未改写的 Session 日志重建状态。历史事件声明、格式迁移、消息来源和附件导出仍受支持;无需提升 Session 格式版本或转换日志。 + +本决策部分取代 [Agent Teams](../feature/2026-08-05-agent-teams.zh.md) 的投影与邮箱校验决策。[子代理身份](../architecture/2026-10-15-agent-teams-as-subagents.zh.md)和[仅等待收件箱](2026-10-29-inbox-only-agent-wait.zh.md)决策仍有效,任务持久性与共享检出语义也保持有效。 + +## Alternatives considered + +**保留仅供解码的名册与邮箱投影。** 没有生产消费者读取这些集合。保留它们会在检查点中保存消息历史,并允许过时校验阻断当前任务操作。 + +**迁移或删除历史日志事件。** 任务板可以直接忽略这些记录。改写增加迁移工作,还会丢失现有格式读取器和导出功能仍可使用的归档数据。 + +## Consequences + +Team 投影不再提供旧队友与邮箱状态。原始日志记录仍可读取,但不授予权限,也不触发投递。重新引入恢复需要具体消费者,并明确决定保留的数据和失败影响范围。 + +测试覆盖忽略旧载荷、版本 6 检查点失效、当前检查点后缀重放、JSONL 重启后修改任务、稳定视图引用,以及录制的 headless profile 在退役生命周期与邮箱记录之后接受任务。任务和模式校验仍可见地报错。 diff --git a/AGENTS.md b/AGENTS.md index 69dbd7d69e..db164319da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,7 @@ packages/ @deepseek-ai/dsh- workspaces at packages/// compaction/ context compaction context/ request context subagent/ delegated agents + team/ shared task coordination jobs/ background jobs bundle/ profile bundles workflow/ workflow execution diff --git a/apps/cli/composition.md b/apps/cli/composition.md index 21150774cc..96b711a333 100644 --- a/apps/cli/composition.md +++ b/apps/cli/composition.md @@ -140,6 +140,10 @@ flowchart LR cfg --> plugin_dsh_base_command_compact plugin_dsh_base_subagent["subagent
@deepseek-ai/dsh-subagent"] cfg --> plugin_dsh_base_subagent + plugin_dsh_base_agent_team["agent-team
@deepseek-ai/dsh-agent-team"] + cfg --> plugin_dsh_base_agent_team + plugin_dsh_base_tool_agent_team["tool-agent-team
@deepseek-ai/dsh-tool-agent-team"] + cfg --> plugin_dsh_base_tool_agent_team plugin_dsh_base_subagent_spawn_in_process["subagent-spawn-in-process
@deepseek-ai/dsh-subagent-spawn-in-process"] cfg --> plugin_dsh_base_subagent_spawn_in_process plugin_dsh_base_subagent_fork_in_process["subagent-fork-in-process
@deepseek-ai/dsh-subagent-fork-in-process"] @@ -272,6 +276,8 @@ flowchart LR | `compaction-basic` | `@deepseek-ai/dsh-compaction-basic` | | `command-compact` | `@deepseek-ai/dsh-command-compact` | | `subagent` | `@deepseek-ai/dsh-subagent` | +| `agent-team` | `@deepseek-ai/dsh-agent-team` | +| `tool-agent-team` | `@deepseek-ai/dsh-tool-agent-team` | | `subagent-spawn-in-process` | `@deepseek-ai/dsh-subagent-spawn-in-process` | | `subagent-fork-in-process` | `@deepseek-ai/dsh-subagent-fork-in-process` | | `tool-subagent-control` | `@deepseek-ai/dsh-tool-subagent-control` | diff --git a/apps/cli/package.json b/apps/cli/package.json index 463f4f1b9e..c194a246b9 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -38,7 +38,6 @@ "@deepseek-ai/dsh-compaction-basic": "workspace:*", "@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:*", "@deepseek-ai/dsh-cordis-client-runner": "workspace:*", - "@deepseek-ai/dsh-experimental-agent-team-profile": "workspace:*", "@deepseek-ai/dsh-experimental-inspector-profile": "workspace:*", "@deepseek-ai/dsh-fs-local": "workspace:*", "@deepseek-ai/dsh-goal": "workspace:*", @@ -127,9 +126,9 @@ "@deepseek-ai/dsh-bash-local": "workspace:*", "@deepseek-ai/dsh-credentials-local": "workspace:*", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-experimental-agent-team": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*", "@deepseek-ai/dsh-experimental-ptc-runtime-python": "workspace:*", - "@deepseek-ai/dsh-experimental-tool-agent-team": "workspace:*", + "@deepseek-ai/dsh-tool-agent-team": "workspace:*", "@deepseek-ai/dsh-fs-observation-policy": "workspace:*", "@deepseek-ai/dsh-fs-sandbox": "workspace:*", "@deepseek-ai/dsh-host-frontend-static": "workspace:*", diff --git a/apps/cli/tests/agent-team-headless.e2e.ts b/apps/cli/tests/agent-team-headless.e2e.ts index a7c7f34e99..a01ce5dad2 100644 --- a/apps/cli/tests/agent-team-headless.e2e.ts +++ b/apps/cli/tests/agent-team-headless.e2e.ts @@ -27,15 +27,12 @@ describe('dsh run with Agent Teams enabled', () => { await writeFile(join(profileDir, 'package.json'), JSON.stringify({ name: 'dsh-profile-headless', private: true, - dependencies: { - '@deepseek-ai/dsh-experimental-agent-team-profile': 'workspace:^', - }, + dependencies: {}, dsh: { profile: { bundles: [ '@deepseek-ai/dsh-base', '@deepseek-ai/dsh-headless', - '@deepseek-ai/dsh-experimental-agent-team-profile', ], }, }, @@ -91,8 +88,8 @@ describe('dsh run with Agent Teams enabled', () => { const workflowChild = parsed.find(log => log.some(record => record.type === 'user/message' && JSON.stringify((record.data as { content: unknown }).content) === JSON.stringify([{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }]))) expect(workflowChild).toBeDefined() - expect(workflowChild!.find(record => record.type === 'subagent/descriptor')?.data) - .toMatchObject({ mode: 'continuable', provider: 'spawn' }) + const workflowDescriptor = workflowChild!.find(record => record.type === 'subagent/descriptor')?.data as Record + expect(workflowDescriptor).toMatchObject({ mode: 'continuable', provider: 'spawn' }) expect(workflowChild!.filter(record => record.type === 'user/message' && (record.data as { source: { kind: string } }).source.kind === 'user').map(record => record.data)) .toEqual([expect.objectContaining({ content: [{ type: 'text', text: 'TEAM_WORKFLOW_CHILD' }] })]) @@ -105,10 +102,15 @@ describe('dsh run with Agent Teams enabled', () => { expect(eventTypes).not.toContain('team/member') const catalog = root!.filter(record => record.type === 'subagent/catalog').map(record => record.data) expect(catalog).toEqual(expect.arrayContaining([ + expect.objectContaining({ version: 0, childId: workflowChild![0]?.id }), expect.objectContaining({ version: 0, label: 'Own deterministic implementation.' }), expect.objectContaining({ version: 0, label: 'Own deterministic research.' }), ])) - for (const entry of catalog) expect(entry).not.toHaveProperty('name') + const names = root!.filter(record => record.type === 'subagent-name/bound').map(record => record.data) + expect(names).toEqual(expect.arrayContaining([ + expect.objectContaining({ version: 1, name: 'lead/implementer' }), + expect.objectContaining({ version: 1, name: 'lead/researcher' }), + ])) expect(eventTypes).not.toContain('team/message/queued') expect(eventTypes).not.toContain('team/message/delivered') const peerMessages = parsed.flatMap(log => log.filter(record => record.type === 'user/message' diff --git a/apps/cli/tests/optional-bundle-transitions.e2e.ts b/apps/cli/tests/optional-bundle-transitions.e2e.ts index b5a5eecbaf..8db33a3a87 100644 --- a/apps/cli/tests/optional-bundle-transitions.e2e.ts +++ b/apps/cli/tests/optional-bundle-transitions.e2e.ts @@ -171,8 +171,8 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option it('keeps the default host and minimal tool catalogs while every optional bundle is off', async () => { const minimal = await agent('minimal') const standard = await agent() - expect(names()).toEqual(['working_directory']) - expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'read_image', 'working_directory']) + expect(names()).toEqual(['team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) for (const name of ['str_replace_editor', 'session_search', 'terminal_open', 'ralph']) expect(names(standard.agent)).not.toContain(name) expect((await ctx.skills.list({ cwd: workspace, scope: standard.agent })).some(skill => skill.name === 'dsh-badge')).toBe(false) }) @@ -246,7 +246,7 @@ describe.skipIf(!existsSync(join(root, 'apps/cli/lib/bin.js')))('Official option expect(toolResult(owner.agent, 'badge-on')).toContain('dsh-badge.png') const minimal = await agent('minimal') expect((await ctx.skills.list({ cwd: workspace, scope: minimal.agent })).map(skill => skill.name)).toContain('dsh-badge') - expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'read_image', 'working_directory']) + expect(names(minimal.agent)).toEqual([process.platform === 'win32' ? 'pwsh' : 'bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) await select(bundles.badge, false) expect((await load('badge-after')).isError).toBe(true) }) diff --git a/apps/cli/tests/profiles/headless/team-snapshot.patch.yml b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml index 4e5c8139f9..7a1d4c8a24 100644 --- a/apps/cli/tests/profiles/headless/team-snapshot.patch.yml +++ b/apps/cli/tests/profiles/headless/team-snapshot.patch.yml @@ -22,14 +22,6 @@ compression: none - insert: - - id: agent-team - name: '@deepseek-ai/dsh-experimental-agent-team' - config: - maxTasks: 256 - - - id: tool-agent-team - name: '@deepseek-ai/dsh-experimental-tool-agent-team' - - id: team-fixture-llm name: './tests/fixtures/team-llm.mjs' diff --git a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs index 2cba5a8ffc..5fbceed744 100644 --- a/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs +++ b/apps/cli/tests/profiles/headless/tests/fixtures/team-llm.mjs @@ -215,10 +215,13 @@ class TeamFixtureAdapter extends LlmAdapter { /** Cordis plugin name. */ export const name = 'team-fixture-llm' /** LLM registry dependency. */ -export const inject = ['llm', 'tools'] +export const inject = ['llm', 'tools', 'agentTeams'] /** Register the keyless adapter on the shipped default provider route. */ export function apply(ctx) { + ctx.on('agent/created', async ({ agent }) => { + if (agent.session.header.parentSession === undefined) await ctx.agentTeams.setEnabled(agent, true) + }) const implementerWaiting = Promise.withResolvers() const leadWaiting = Promise.withResolvers() for (const gate of [implementerWaiting, leadWaiting]) void gate.promise.catch(() => undefined) diff --git a/apps/cli/tests/profiles/web/tests/default-web-process.ts b/apps/cli/tests/profiles/web/tests/default-web-process.ts index 537ac77702..155f8d6670 100644 --- a/apps/cli/tests/profiles/web/tests/default-web-process.ts +++ b/apps/cli/tests/profiles/web/tests/default-web-process.ts @@ -55,7 +55,7 @@ export async function withDefaultWeb( await writeFile(patch, JSON.stringify([{ insert: [{ id: 'runtime-roster-observer', name: pathToFileURL(join(root, 'fixtures/runtime-roster-observer.js')).href, - config: { negativeControl: pathToFileURL(join(repoRoot, 'packages/experimental/client-ui-agent-team/lib/index.js')).href }, + config: { negativeControl: pathToFileURL(join(repoRoot, 'packages/experimental/client-ui-voice-input/lib/index.js')).href }, }] }]) + '\n') const launch = resolveExampleLaunch({ srcBin: join(repoRoot, 'apps/cli/src/bin.ts'), diff --git a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts index 7563a323aa..687afcb6d8 100644 --- a/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts +++ b/apps/cli/tests/profiles/web/tests/web-default-isolation.expected.e2e.ts @@ -7,7 +7,7 @@ import { modulePackage } from './runtime-roster.ts' import { experimentalRuntimeReferences } from './runtime-roster-isolation.ts' import { withDefaultWeb, webGet } from './default-web-process.ts' -const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-agent-team' +const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-voice-input' it('boots default Web with the shipped Schedule rows, without experimental modules, time context, or an active built-in Browser', async (test) => { await withDefaultWeb(test, async ({ url, request }) => { @@ -37,6 +37,9 @@ it('boots default Web with the shipped Schedule rows, without experimental modul // The shipped composition mounts the Host Schedule service and its task // page; the clock stays preset-level, so neither plane carries a row for it. expect(roster.entries).toEqual(expect.arrayContaining([ + expect.objectContaining({ name: '@deepseek-ai/dsh-agent-team', state: FiberState.ACTIVE }), + expect.objectContaining({ name: '@deepseek-ai/dsh-tool-agent-team', state: FiberState.ACTIVE }), + expect.objectContaining({ name: '@deepseek-ai/dsh-client-ui-agent-team', state: FiberState.ACTIVE }), expect.objectContaining({ name: '@deepseek-ai/dsh-schedule', state: FiberState.ACTIVE }), expect.objectContaining({ name: '@deepseek-ai/dsh-client-ui-schedule', state: FiberState.ACTIVE }), ])) @@ -50,7 +53,7 @@ it('boots default Web with the shipped Schedule rows, without experimental modul const mounted = contaminated.plugins.filter(plugin => plugin.modules.some(url => modulePackage(url) === experimentalName)) expect(mounted).toEqual([expect.objectContaining({ state: FiberState.ACTIVE })]) expect(experimentalRuntimeReferences(contaminated)).toEqual(expect.arrayContaining([ - expect.stringContaining('/packages/experimental/client-ui-agent-team/'), + expect.stringContaining('/packages/experimental/client-ui-voice-input/'), ])) }) }) diff --git a/apps/cli/tests/web-agent-presets.e2e.ts b/apps/cli/tests/web-agent-presets.e2e.ts index 1dc516bc1b..ff5fd57cae 100644 --- a/apps/cli/tests/web-agent-presets.e2e.ts +++ b/apps/cli/tests/web-agent-presets.e2e.ts @@ -248,9 +248,9 @@ beforeAll(async () => { }, 120_000) describe('the shipped Web composition', () => { - it('exposes only the directory tool in the global layer', () => { - // Directory selection is global; action tools belong to their Agent presets. - expect(toolNames(ctx)).toEqual(['working_directory']) + it('registers the directory and conditional Team tools in the global layer', () => { + // Team registrations are shared; session mode controls their step-time availability. + expect(toolNames(ctx)).toEqual(['team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) }) it('keeps the token meter and its context-meter projections on the host plane', async () => { @@ -304,7 +304,7 @@ describe('the shipped Web composition', () => { 'get_goal', 'interrupt_agent', 'job_kill', 'job_list', 'job_output', 'list_agents', 'list_subagent_models', 'plugin_manager', 'present', 'read', 'read_image', 'schedule_create', 'schedule_edit', 'schedule_list', 'schedule_view', 'send_message', 'skill', - 'subagent', 'subagent_fork', 'todo_write', 'update_goal', 'wait_agent', 'web_fetch', 'web_search', + 'subagent', 'subagent_fork', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'todo_write', 'update_goal', 'wait_agent', 'web_fetch', 'web_search', 'workflow', 'working_directory', 'write', ]) expect(ctx.commands.find(handle.agent, 'goal')).toBeDefined() @@ -408,7 +408,7 @@ describe('the shipped Web composition', () => { expect(assembly.sections).toEqual([ { name: 'deployment:persona-prefix', text: MINIMAL_PROMPT }, ]) - expect(assembly.tools.map(tool => tool.name)).toEqual(['bash', 'read_image', 'working_directory']) + expect(assembly.tools.map(tool => tool.name)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) expect(assembly.tools.find(tool => tool.name === 'bash')?.description).toBe(MINIMAL_BASH_DESCRIPTION) expect(ctx.commands.find(handle.agent, 'goal')).toBeUndefined() // serviceFor reports preset-owned providers; unisolated consumers inherit the host fs. @@ -485,7 +485,7 @@ describe('the shipped Web composition', () => { const last = adapter.requests.at(-1) expect(last?.messages.flatMap(message => message.content) .some(block => block.type === 'text' && block.text.includes('Preserve the requested project task.'))).toBe(true) - expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'working_directory']) + expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) } finally { await handle.dispose() } @@ -529,7 +529,7 @@ describe('the shipped Web composition', () => { setup: agentCtx => ctx.agentPresets.mount(agentCtx, 'minimal').then(() => undefined), }) try { - expect(toolNames(ctx, minimal.agent)).toEqual(['bash', 'read_image', 'working_directory']) + expect(toolNames(ctx, minimal.agent)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10) expect(ctx.agentPresets.serviceFor(minimal.agent, 'compaction')) .not.toBe(ctx.agentPresets.serviceFor(full.agent, 'compaction')) @@ -538,7 +538,7 @@ describe('the shipped Web composition', () => { // Tearing the minimal session down leaves the full one whole. expect(toolNames(ctx, full.agent).length).toBeGreaterThan(10) - expect(toolNames(ctx)).toEqual(['working_directory']) + expect(toolNames(ctx)).toEqual(['team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) } finally { await full.dispose() } @@ -697,7 +697,7 @@ describe('the shipped Web composition', () => { // stays the preset's choice — minimal mounts no `tool-skill`, so its // tool table has no loader even though the global layer is readable. expect((await ctx.skills.list({ scope: handle.agent })).map(skill => skill.name)).toContain('dsh-badge') - expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'working_directory']) + expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) } finally { await handle.dispose() } @@ -881,6 +881,8 @@ describe('a user preset declared from the shipped cordis rows', () => { // The `Tool` provider is the one built over the host context: it // answers with the shared registry's view of the REQUESTING agent, // so the copy sees its own preset's tools, not the host's empty set. + const spills = vi.spyOn(copyCtx.spillStore, 'saveText') + onTestFinished(() => { spills.mockRestore() }) const queried = await executeToolFixture(copyCtx, { callId: ToolCallId('copied-preset-inspect-tools'), name: 'cordis_inspect_query', @@ -889,11 +891,8 @@ describe('a user preset declared from the shipped cordis rows', () => { agent: copied.agent, }) expect(queried.isError).toBe(false) - // The whole-table answer can pass the inline token budget once the - // preset's tool table grows, and the spill policy then replaces its - // middle with a gap plus a spill footer. What this case proves is the - // provider's answer, so assert the names it must carry. - const queriedText = resultText(queried) + // The tool catalog may exceed the inline budget; assert its complete stored text. + const queriedText = spills.mock.calls.at(-1)?.[0].content ?? resultText(queried) for (const toolName of ['bash', 'cordis_inspect_list', 'cordis_inspect_query', 'plugin_manager']) { expect(queriedText).toContain(`"name": "${toolName}"`) } @@ -1088,7 +1087,7 @@ describe('the default preset as a user setting', () => { }) try { // An omitted preset id resolves the stored default instead of the composed default. - expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'working_directory']) + expect(toolNames(ctx, handle.agent)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) } finally { await handle.dispose() } @@ -1134,7 +1133,7 @@ describe('a profile patch stored before Developer tools owned preset selection', setup: agentCtx => legacy.agentPresets.mount(agentCtx).then(() => undefined), }) try { - expect(toolNames(legacy, handle.agent)).toEqual(['bash', 'read_image', 'working_directory']) + expect(toolNames(legacy, handle.agent)).toEqual(['bash', 'read_image', 'team_task_create', 'team_task_get', 'team_task_list', 'team_task_update', 'working_directory']) } finally { await handle.dispose() } diff --git a/apps/web/package.json b/apps/web/package.json index 51ecc68787..b011aa916d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -43,7 +43,7 @@ "@deepseek-ai/dsh-client-web": "workspace:*", "@deepseek-ai/dsh-cmdline": "workspace:*", "@deepseek-ai/dsh-pwsh-local": "workspace:*", - "@deepseek-ai/dsh-experimental-agent-team": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*", "@deepseek-ai/dsh-experimental-webworker-packer": "workspace:*", "@deepseek-ai/dsh-experimental-webworker-runtime": "workspace:*", "@deepseek-ai/dsh-remote-mock": "workspace:*", diff --git a/apps/web/tests/agent-team-panel.e2e.ts b/apps/web/tests/agent-team-panel.e2e.ts index 0583ac9c3f..aca5fa451d 100644 --- a/apps/web/tests/agent-team-panel.e2e.ts +++ b/apps/web/tests/agent-team-panel.e2e.ts @@ -1,17 +1,15 @@ -// Keyless assembled-browser coverage for the opt-in Agent Teams bundle +// Keyless assembled-browser coverage for per-Session Agent Teams // over the real Host projection frame flow. import { fileURLToPath } from 'node:url' import { join } from 'node:path' -import { readFileSync } from 'node:fs' import type { Browser, Page } from 'playwright' import { chromium } from 'playwright' import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished, vi } from 'vitest' -import * as yaml from 'js-yaml' -import { entryListSchema } from '@deepseek-ai/cordis-plugin-include' -import type {} from '@deepseek-ai/dsh-experimental-agent-team' +import type {} from '@deepseek-ai/dsh-agent-team' +import { SessionId } from '@deepseek-ai/dsh-session' import { createMessage, createUserMessage } from '@deepseek-ai/dsh-llm' import { - assertFixtureInventory, captureStableAria, compareOrRefreshGolden, + acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, launchWebScaffold, watchConsole, webSnapshotMode, type WebScaffold, } from './scaffold.ts' import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts' @@ -19,25 +17,8 @@ import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './suppor const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/agent-team-panel', import.meta.url)) const PANEL_EXPECTED = join(SNAPSHOT_DIR, 'task.expected.md') const RESIZE_EXPECTED = join(SNAPSHOT_DIR, 'resize.expected.md') -const OVERLAY = fileURLToPath(new URL('./agent-team-panel.overlay.yml', import.meta.url)) -const TEAM_PATCH = fileURLToPath(new URL('../../../packages/experimental/agent-team-profile/cordis.patch.yml', import.meta.url)) -const INSTALL_ANCHORS = [ - fileURLToPath(new URL('../../../packages/experimental/agent-team-profile/package.json', import.meta.url)), -] const MODE = webSnapshotMode() -function profileEntries(path: string): unknown[] { - const parsed = yaml.load(readFileSync(path, 'utf8'), { schema: entryListSchema }) - if (!Array.isArray(parsed)) throw new Error(`profile layer at ${path} must be a list`) - return parsed -} - -describe('Agent Teams panel overlay', () => { - it('matches the shipped Agent Teams bundle', () => { - expect(profileEntries(OVERLAY)).toEqual(profileEntries(TEAM_PATCH)) - }) -}) - describe('web e2e: Agent Teams panel', () => { let scaffold: WebScaffold let browser: Browser @@ -45,7 +26,7 @@ describe('web e2e: Agent Teams panel', () => { let tripwire: ReturnType beforeAll(async () => { - scaffold = await launchWebScaffold({ extraOverlayPath: OVERLAY, extraInstallAnchors: INSTALL_ANCHORS }) + scaffold = await launchWebScaffold() browser = await chromium.launch() page = await newEnglishPage(browser) tripwire = watchConsole(page) @@ -81,12 +62,43 @@ describe('web e2e: Agent Teams panel', () => { await scaffold?.close() }) - it('displays agent-owned task changes through a read-only board without a refresh action', async () => { + it('defaults off and saves a Session selection in the model menu', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-team-mode')) + expect(await page.locator('[data-subagent-action]').count()).toBe(0) + await page.getByRole('button', { name: /^Select model/u }).click() + const toggle = page.getByRole('switch', { name: 'Use Agent Teams', exact: true }) + await toggle.waitFor() + expect(await toggle.getAttribute('aria-checked')).toBe('false') + await toggle.focus() + await page.keyboard.press('ArrowUp') + expect(await toggle.evaluate(element => element === document.activeElement)).toBe(false) + await page.keyboard.press('ArrowDown') + expect(await toggle.evaluate(element => element === document.activeElement)).toBe(true) + await page.keyboard.press('Space') + await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true') + await page.keyboard.press('Escape') + await page.locator('[data-subagent-action]').waitFor() + const agent = scaffold.ctx.agents.list()[0]! + expect(scaffold.ctx.commands.list(agent).some(command => command.name === 'team')).toBe(false) + expect(agent.session.ownEvents().some(event => event.type === 'command/run')).toBe(false) + const other = await scaffold.ctx.agents.create({ sessionId: SessionId('other-team-mode'), agentOptions: agent.options }) + expect(scaffold.ctx.agentTeams.isEnabled(other.agent)).toBe(false) + await other.dispose() + const beforeReload = tripwire.warnings.length + await page.reload({ waitUntil: 'load' }) + await page.locator('[data-subagent-action]').waitFor() + await page.getByRole('button', { name: /^Select model/u }).click() + expect(await toggle.getAttribute('aria-checked')).toBe('true') + await page.keyboard.press('Escape') + acknowledgeReloadConnectionLoss(tripwire, beforeReload) + }, 60_000) + + it('opens the member tree and live read-only tasks in one panel', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-team-panel')) const projectionReads = vi.spyOn(scaffold.ctx.sessionController, 'projections') onTestFinished(() => { projectionReads.mockRestore() }) - await page.locator('[data-team-action]').getByRole('button', { name: 'Task board', exact: true }).click() - const action = page.getByRole('dialog', { name: 'Task board', exact: true }) + await page.locator('[data-subagent-action]').getByRole('button', { name: /subagents?$/u }).click() + const action = page.locator('[data-subagent-panel]') await action.getByText('No shared tasks yet. Create them through the conversation.').waitFor() expect(await action.getByRole('heading', { name: 'Members', exact: true }).count()).toBe(0) expect(projectionReads).not.toHaveBeenCalled() @@ -96,6 +108,19 @@ describe('web e2e: Agent Teams panel', () => { // Host-side Team mutations reach the open panel through projection frames only. const agent = scaffold.ctx.agents.list()[0]! + const memberId = SessionId('team-panel-researcher') + agent.session.append('subagent/catalog', { + version: 2, childId: memberId, childCreatedAt: Date.now(), mode: 'external', + label: 'Researcher: investigate the construction and verify every bound before reporting the result. '.repeat(8).trim(), + }) + await action.getByRole('treeitem', { name: /Researcher/u }).waitFor() + expect(await page.locator('[data-subagent-panel]').count()).toBe(1) + expect(await page.getByRole('button', { name: 'Task board', exact: true }).count()).toBe(0) + expect(await action.evaluate((element) => { + const tree = element.querySelector('[role="tree"]')! + const board = element.querySelector('[data-team-panel]')! + return Boolean(tree.compareDocumentPosition(board) & Node.DOCUMENT_POSITION_FOLLOWING) + })).toBe(true) const task = await scaffold.ctx.agentTeams.createTask(agent, { subject: 'Agent task', description: 'Created by the root agent', writeScopes: ['src/web'], }) @@ -109,7 +134,7 @@ describe('web e2e: Agent Teams panel', () => { expect(await action.getByRole('button', { name: /^(New task|Edit|Complete|Reopen|Delete)$/u }).count()).toBe(0) expect(await action.locator('input, select, textarea').count()).toBe(0) - const snapshot = await captureStableAria(page, '[data-team-panel]', scaffold.workspaceCwd) + const snapshot = await captureStableAria(page, '[data-subagent-panel]', scaffold.workspaceCwd) await compareOrRefreshGolden(PANEL_EXPECTED, snapshot, MODE) expect(projectionReads).not.toHaveBeenCalled() expect(tripwire.pageErrors).toEqual([]) @@ -117,10 +142,30 @@ describe('web e2e: Agent Teams panel', () => { await page.keyboard.press('Escape') }, 60_000) + it('hides the board while disabled and restores its tasks when re-enabled', async () => { + await page.getByRole('button', { name: /^Select model/u }).click() + const toggle = page.getByRole('switch', { name: 'Use Agent Teams', exact: true }) + await toggle.click() + await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('false') + expect(await page.locator('[data-subagent-action]').count()).toBe(1) + await page.keyboard.press('Escape') + await page.locator('[data-subagent-action]').getByRole('button', { name: '1 subagent', exact: true }).click() + await page.getByRole('treeitem', { name: /Researcher/u }).waitFor() + expect(await page.locator('[data-team-panel]').count()).toBe(0) + await page.keyboard.press('Escape') + await page.getByRole('button', { name: /^Select model/u }).click() + await toggle.click() + await expect.poll(() => toggle.getAttribute('aria-checked')).toBe('true') + await page.keyboard.press('Escape') + await page.locator('[data-subagent-action]').getByRole('button', { name: /subagents?$/u }).click() + await page.locator('[data-subagent-panel]').getByText('Agent task', { exact: true }).waitFor() + await page.keyboard.press('Escape') + }) + it('keeps the panel inside the viewport and closes on outside click or Escape', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-agent-team-panel-keyboard')) - const panel = page.getByRole('dialog', { name: 'Task board', exact: true }) - const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Task board', exact: true }) + const panel = page.locator('[data-subagent-panel]') + const trigger = page.locator('[data-subagent-action]').getByRole('button', { name: /subagents?$/u }) const viewport = page.viewportSize()! await trigger.click() await panel.getByRole('heading', { name: /Shared tasks/u }).waitFor() @@ -136,6 +181,18 @@ describe('web e2e: Agent Teams panel', () => { .every(([x, y]) => element.contains(document.elementFromPoint(x!, y!))) })).toBe(true) } + const description = panel.getByRole('button', { name: 'Expand description for team-panel-researcher', exact: true }) + const preview = description.locator('span') + expect(await preview.evaluate(element => element.scrollWidth > element.clientWidth)).toBe(true) + await description.focus() + await page.keyboard.press('Enter') + const expanded = panel.getByRole('button', { name: 'Collapse description for team-panel-researcher', exact: true }) + expect(await expanded.getAttribute('aria-expanded')).toBe('true') + expect(await expanded.locator('span').evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true) + expect(await panel.evaluate(element => element.scrollWidth <= element.clientWidth)).toBe(true) + expect(await expanded.evaluate(element => element === document.activeElement)).toBe(true) + await expanded.click() + expect(await description.getAttribute('aria-expanded')).toBe('false') await panel.getByRole('heading', { name: /Shared tasks/u }).click() expect(await panel.count()).toBe(1) await page.mouse.click(2, 2) @@ -162,8 +219,8 @@ describe('web e2e: Agent Teams panel', () => { }) it('keeps the hovered panel open when the pointer returns directly to its trigger', async () => { - const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Task board', exact: true }) - const panel = page.getByRole('dialog', { name: 'Task board', exact: true }) + const trigger = page.locator('[data-subagent-action]').getByRole('button', { name: /subagents?$/u }) + const panel = page.locator('[data-subagent-panel]') await trigger.hover() await panel.waitFor() await panel.hover() @@ -182,8 +239,8 @@ describe('web e2e: Agent Teams panel', () => { writeScopes: [], }) const viewport = page.viewportSize()! - const trigger = page.locator('[data-team-action]').getByRole('button', { name: 'Task board', exact: true }) - const panel = page.getByRole('dialog', { name: 'Task board', exact: true }) + const trigger = page.locator('[data-subagent-action]').getByRole('button', { name: /subagents?$/u }) + const panel = page.locator('[data-subagent-panel]') const card = panel.locator('article').filter({ hasText: 'Responsive task' }) await trigger.click() await card.waitFor() @@ -192,7 +249,7 @@ describe('web e2e: Agent Teams panel', () => { await page.setViewportSize({ width: 390, height: 700 }) await card.getByRole('button', { name: 'Show more' }).waitFor() expect(await card.locator('p').evaluate(p => p.scrollHeight > p.clientHeight)).toBe(true) - await compareOrRefreshGolden(RESIZE_EXPECTED, await captureStableAria(page, '[data-team-panel]', scaffold.workspaceCwd), MODE) + await compareOrRefreshGolden(RESIZE_EXPECTED, await captureStableAria(page, '[data-subagent-panel]', scaffold.workspaceCwd), MODE) await card.getByRole('button', { name: 'Show more' }).click() expect(await card.locator('p').evaluate(p => p.scrollHeight === p.clientHeight)).toBe(true) await card.getByRole('button', { name: 'Show less' }).click() diff --git a/apps/web/tests/agent-team-panel.overlay.yml b/apps/web/tests/agent-team-panel.overlay.yml deleted file mode 100644 index 1c552e8c1f..0000000000 --- a/apps/web/tests/agent-team-panel.overlay.yml +++ /dev/null @@ -1,12 +0,0 @@ -# Equivalent to the Agent Teams bundle over the stable base and Web bundles. -- insert: - - id: agent-team - name: '@deepseek-ai/dsh-experimental-agent-team' - config: - maxTasks: 256 - - - id: tool-agent-team - name: '@deepseek-ai/dsh-experimental-tool-agent-team' - - - id: ui-agent-team - name: '@deepseek-ai/dsh-experimental-client-ui-agent-team' diff --git a/apps/web/tests/default-model.e2e.ts b/apps/web/tests/default-model.e2e.ts index fa6c518add..2ee5907a5a 100644 --- a/apps/web/tests/default-model.e2e.ts +++ b/apps/web/tests/default-model.e2e.ts @@ -170,7 +170,8 @@ describe('web e2e: the composer model switch is the default for later sessions', expect(popupBounds!.width).toBeCloseTo(commandMenuBounds!.width) expect(popupBounds!.x).toBeCloseTo(commandMenuBounds!.x) expect(await search.getAttribute('placeholder')).toBe('搜索模型…') - await page.getByRole('option').first().waitFor() + // Command menu height changes where the pointer lands in the model popup. + await page.getByRole('option', { name: 'Acme Large', exact: true }).hover() await compareOrRefreshGolden( fileURLToPath(new URL('./expected/default-model/command-picker.expected.md', import.meta.url)), await captureStableAria(page, '[aria-label="/model 选项"]', scaffold.workspaceCwd), diff --git a/apps/web/tests/default-product-isolation.e2e.ts b/apps/web/tests/default-product-isolation.e2e.ts index 4b535448e1..603a35753b 100644 --- a/apps/web/tests/default-product-isolation.e2e.ts +++ b/apps/web/tests/default-product-isolation.e2e.ts @@ -94,7 +94,7 @@ it('activates the actual default Client registry without experimental packages', expect(roster.plugins.some(plugin => plugin.modules.includes('@deepseek-ai/dsh-client-ui-layout'))).toBe(true) expect(experimentalClientReferences(roster)).toEqual([]) const contaminatedHost = await request('mount-experimental-entry') - const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-agent-team' + const experimentalName = '@deepseek-ai/dsh-experimental-client-ui-voice-input' expect(contaminatedHost.client.entries.map(entry => entry.id)).toContain(experimentalName) await page.reload() await expect.poll(async () => { diff --git a/apps/web/tests/expected/plugin-config/official.expected.md b/apps/web/tests/expected/plugin-config/official.expected.md index 8e36bea43c..bdf3d7033a 100644 --- a/apps/web/tests/expected/plugin-config/official.expected.md +++ b/apps/web/tests/expected/plugin-config/official.expected.md @@ -22,11 +22,6 @@ - heading "实验性插件" [level=3] - button "更多" - list: - - listitem: - - button "查看 智能体团队": 智能体团队 - - text: 实验性 为子智能体树添加共享任务板工具与任务面板。 - - status - - switch "启用 智能体团队" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音,首次使用需安装依赖。 diff --git a/apps/web/tests/expected/plugin-manager/icons.expected.md b/apps/web/tests/expected/plugin-manager/icons.expected.md index 4510accc6c..f42be11885 100644 --- a/apps/web/tests/expected/plugin-manager/icons.expected.md +++ b/apps/web/tests/expected/plugin-manager/icons.expected.md @@ -1,6 +1,6 @@ Third-party bundle card: image, 36×36, decoded -Disabled Agent Teams card: image, 36×36, decoded -Agent Teams detail: image, 36×36, decoded +Disabled Voice Input card: image, 36×36, decoded +Voice Input detail: image, 36×36, decoded Third-party bundle detail: image, 36×36, decoded Independent search row: image, 30×30, decoded Independent review row: generic artwork diff --git a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md index c2b51bfa59..8a7e68999a 100644 --- a/apps/web/tests/expected/plugin-manager/live-enabled.expected.md +++ b/apps/web/tests/expected/plugin-manager/live-enabled.expected.md @@ -22,11 +22,6 @@ - heading "实验性插件" [level=3] - button "更多" - list: - - listitem: - - button "查看 智能体团队": 智能体团队 - - text: 实验性 为子智能体树添加共享任务板工具与任务面板。 - - status - - switch "启用 智能体团队" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音,首次使用需安装依赖。 diff --git a/apps/web/tests/expected/plugin-manager/manager.expected.md b/apps/web/tests/expected/plugin-manager/manager.expected.md index 9454ad97f4..1053a94651 100644 --- a/apps/web/tests/expected/plugin-manager/manager.expected.md +++ b/apps/web/tests/expected/plugin-manager/manager.expected.md @@ -22,11 +22,6 @@ - heading "实验性插件" [level=3] - button "更多" - list: - - listitem: - - button "查看 智能体团队": 智能体团队 - - text: 实验性 为子智能体树添加共享任务板工具与任务面板。 - - status - - switch "启用 智能体团队" - listitem: - button "查看 语音输入": 语音输入 - text: 实验性 在本机使用 SenseVoice 转写录音,首次使用需安装依赖。 diff --git a/apps/web/tests/expected/plugin-manager/refresh.expected.md b/apps/web/tests/expected/plugin-manager/refresh.expected.md index 85d666fd3e..75dda78845 100644 --- a/apps/web/tests/expected/plugin-manager/refresh.expected.md +++ b/apps/web/tests/expected/plugin-manager/refresh.expected.md @@ -6,7 +6,7 @@ Focus at 500ms: - tooltip "刷新" failure: pending - button "刷新" [disabled] -{"busy":"true","spinners":1,"cards":12,"skeletons":0,"alerts":[],"retry":0,"toasts":[]} +{"busy":"true","spinners":1,"cards":11,"skeletons":0,"alerts":[],"retry":0,"toasts":[]} failure: settled - button "刷新" -{"busy":"false","spinners":0,"cards":12,"skeletons":0,"alerts":[],"retry":0,"toasts":["刷新失败,请重试"]} +{"busy":"false","spinners":0,"cards":11,"skeletons":0,"alerts":[],"retry":0,"toasts":["刷新失败,请重试"]} diff --git a/apps/web/tests/expected/subagent-conversation/migration-required-child.expected.md b/apps/web/tests/expected/subagent-conversation/migration-required-child.expected.md index 0e4f8f018b..96b930ea1f 100644 --- a/apps/web/tests/expected/subagent-conversation/migration-required-child.expected.md +++ b/apps/web/tests/expected/subagent-conversation/migration-required-child.expected.md @@ -2,12 +2,12 @@ - tree "Subagent sessions": - treeitem "historical researcher Migration required · not running" [level=1]: - - text: historical researcher Migration required · not running + - text: historical researcher Migration required - button "Open historical researcher in sidebar" # After opening the child - tree "Subagent sessions": - treeitem "historical editor Migration required · not running" [level=1]: - - text: historical editor Migration required · not running + - text: historical editor Migration required - button "Open historical editor in sidebar" diff --git a/apps/web/tests/expected/subagent-conversation/navigation-only.expected.md b/apps/web/tests/expected/subagent-conversation/navigation-only.expected.md new file mode 100644 index 0000000000..8796ad9eba --- /dev/null +++ b/apps/web/tests/expected/subagent-conversation/navigation-only.expected.md @@ -0,0 +1,10 @@ +- dialog "Subagent sessions": + - tree "Subagent sessions": + - treeitem "researcher event-sourcing researcher · continuable · not running 15.7K tok · {{duration}}" [level=1]: + - button "Expand researcher descendants" + - text: researcher + - button "Expand description for researcher": event-sourcing researcher + - text: 15.7K tok {{duration}} + - treeitem "event-sourcing reviewer one-shot · completed 0 tok · {{duration}}" [level=1]: + - text: event-sourcing reviewer 0 tok ~6mo 12d + - button "Open event-sourcing reviewer in sidebar" diff --git a/apps/web/tests/minimal-preset.snapshot.ts b/apps/web/tests/minimal-preset.snapshot.ts index ee65497459..8a131ed29b 100644 --- a/apps/web/tests/minimal-preset.snapshot.ts +++ b/apps/web/tests/minimal-preset.snapshot.ts @@ -142,8 +142,11 @@ describe('minimal agent preset', () => { ], } `) + const requestedTools = new Set(requestHeader.tools?.map(tool => tool.name)) expect(requestHeader.tools?.toSorted((left, right) => left.name.localeCompare(right.name))) - .toEqual(scaffold.ctx.tools.schemas(agentHandle.agent).toSorted((left, right) => left.name.localeCompare(right.name))) + .toEqual(scaffold.ctx.tools.schemas(agentHandle.agent) + .filter(tool => requestedTools.has(tool.name)) + .toSorted((left, right) => left.name.localeCompare(right.name))) }) it.skipIf(MODE === 'record')('expands the completed persistent Bash call in the Web conversation', async () => { diff --git a/apps/web/tests/plugin-manager.e2e.ts b/apps/web/tests/plugin-manager.e2e.ts index 124ac12ddb..87ab8b2267 100644 --- a/apps/web/tests/plugin-manager.e2e.ts +++ b/apps/web/tests/plugin-manager.e2e.ts @@ -9,7 +9,6 @@ import type { Browser, Locator, Page } from 'playwright' import { chromium } from 'playwright' import { FiberState } from '@deepseek-ai/cordis' import { readProfileManifest } from '@deepseek-ai/dsh-app-boot' -import { createUserMessage } from '@deepseek-ai/dsh-llm' import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished, vi } from 'vitest' import { join } from 'node:path' import { @@ -869,7 +868,6 @@ describe('web e2e: plugin manager', () => { await card.getAttribute('data-plugin-package'), await card.getByText('实验性', { exact: true }).count(), ]))).toEqual([ - ['@deepseek-ai/dsh-experimental-agent-team-profile', 1], ['@deepseek-ai/dsh-experimental-voice-input-bundle', 1], ['@deepseek-ai/dsh-experimental-plugin-market-bundle', 1], ['@deepseek-ai/dsh-experimental-auto-review', 1], @@ -973,7 +971,7 @@ describe('web e2e: plugin manager', () => { onTestFinished(closeSettings) const fixtureIcon = `data:image/svg+xml;base64,${(await readFile(join(FIXTURE_PLUGINS, 'fixture-bundle/icon.svg'))).toString('base64')}` const fallbackIcon = `data:image/svg+xml;base64,${(await readFile(join(FIXTURE_PLUGINS, 'fixture-bundle/fallback-icon.svg'))).toString('base64')}` - const teamIcon = `data:image/svg+xml;base64,${(await readFile(fileURLToPath(new URL('../../../packages/experimental/agent-team-profile/icon.svg', import.meta.url)))).toString('base64')}` + const voiceIcon = `data:image/svg+xml;base64,${(await readFile(fileURLToPath(new URL('../../../packages/experimental/voice-input-bundle/icon.svg', import.meta.url)))).toString('base64')}` const images: string[] = [] const checkImage = async (selector: string, source: string, label: string) => { const image = panel.locator(`${selector} img`).first() @@ -987,9 +985,9 @@ describe('web e2e: plugin manager', () => { images.push(`${label}: image, ${size.width}×${size.height}, decoded`) } await checkImage('[data-plugin-package="@fixture/bundle"]', fixtureIcon, 'Third-party bundle card') - const team = panel.locator('[data-plugin-package="@deepseek-ai/dsh-experimental-agent-team-profile"]') + const team = panel.locator('[data-plugin-package="@deepseek-ai/dsh-experimental-voice-input-bundle"]') expect(await team.getByRole('switch').getAttribute('aria-checked')).toBe('false') - await checkImage('[data-plugin-package="@deepseek-ai/dsh-experimental-agent-team-profile"]', teamIcon, 'Disabled Agent Teams card') + await checkImage('[data-plugin-package="@deepseek-ai/dsh-experimental-voice-input-bundle"]', voiceIcon, 'Disabled Voice Input card') try { for (const colorScheme of ['light', 'dark'] as const) { await page.emulateMedia({ colorScheme }) @@ -1003,8 +1001,8 @@ describe('web e2e: plugin manager', () => { } finally { await page.emulateMedia({ colorScheme: null }) } - await panel.getByRole('button', { name: '查看 智能体团队', exact: true }).click() - await checkImage('[data-plugin-detail]', teamIcon, 'Agent Teams detail') + await panel.getByRole('button', { name: '查看 语音输入', exact: true }).click() + await checkImage('[data-plugin-detail]', voiceIcon, 'Voice Input detail') await panel.getByRole('button', { name: '返回插件' }).click() await panel.getByRole('button', { name: '查看 @fixture/bundle', exact: true }).click() await checkImage('[data-plugin-detail]', fixtureIcon, 'Third-party bundle detail') @@ -1104,23 +1102,18 @@ describe('web e2e: plugin manager', () => { onTestFinished(closeSettings) onTestFailed(() => saveFailureShot(page, 'web-e2e-plugin-manager-locale')) const panel = await openPluginsPanel() - await panel.getByRole('button', { name: '查看 智能体团队', exact: true }).click() + await panel.getByRole('button', { name: '查看 语音输入', exact: true }).click() const packageName = panel.locator('[data-plugin-name]') - expect(await packageName.textContent()).toBe('@deepseek-ai/dsh-experimental-agent-team-profile') - expect(await panel.getByText(experimentalBundleDescription('agent-team-profile', 'zh')).count()).toBe(1) - const child = panel.locator('[data-plugin-row]', { hasText: 'tool-agent-team' }) - await child.getByText('团队工具', { exact: true }).waitFor() - expect(await child.getByText('为智能体提供共享任务板管理工具。', { exact: true }).count()).toBe(1) + expect(await packageName.textContent()).toBe('@deepseek-ai/dsh-experimental-voice-input-bundle') + expect(await panel.getByText('在本机使用 SenseVoice 转写录音,首次使用需安装依赖。').count()).toBe(1) try { await setLanguage('en') - await panel.getByRole('heading', { name: 'Agent teams', exact: true }).waitFor() - expect(await packageName.textContent()).toBe('@deepseek-ai/dsh-experimental-agent-team-profile') - expect(await panel.getByText(experimentalBundleDescription('agent-team-profile', 'en')).count()).toBe(1) - await child.getByText('Team tools', { exact: true }).waitFor() - expect(await child.getByText('Give agents tools to manage their shared task board.', { exact: true }).count()).toBe(1) + await panel.getByRole('heading', { name: 'Voice input', exact: true }).waitFor() + expect(await packageName.textContent()).toBe('@deepseek-ai/dsh-experimental-voice-input-bundle') + expect(await panel.getByText('Transcribe recordings locally with SenseVoice; first use requires installing dependencies').count()).toBe(1) await panel.getByRole('button', { name: 'Back to plugins' }).click() - await panel.getByRole('button', { name: 'View Agent teams', exact: true }).waitFor() - expect(await panel.getByRole('switch', { name: 'Enable Agent teams', exact: true }).count()).toBe(1) + await panel.getByRole('button', { name: 'View Voice input', exact: true }).waitFor() + expect(await panel.getByRole('switch', { name: 'Enable Voice input', exact: true }).count()).toBe(1) // Schedule ships in the delivered composition, so no bundle card carries it in either language. expect(await panel.getByRole('button', { name: 'View Automation tasks', exact: true }).count()).toBe(0) // The official configuration pages follow the language too, from their own dictionary. @@ -1130,69 +1123,35 @@ describe('web e2e: plugin manager', () => { } finally { await setLanguage('zh') } - await panel.getByRole('button', { name: '查看 智能体团队', exact: true }).waitFor() + await panel.getByRole('button', { name: '查看 语音输入', exact: true }).waitFor() expect(tripwire.pageErrors).toEqual([]) }, 60_000) - it('enables the Team tools and browser plugin with one bundle switch', async () => { + it('provides Team mode per Session without an experimental plugin switch', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-plugin-manager-team')) + onTestFinished(closeSettings) const panel = await openPluginsPanel() - const toggle = panel.getByRole('switch', { name: '启用 智能体团队', exact: true }) - const teamRows = () => [...scaffold.ctx.loader.entries()] - .filter(entry => ['agent-team', 'tool-agent-team', 'ui-agent-team'].includes(entry.options.id)) + expect(await panel.getByRole('switch', { name: '启用 智能体团队', exact: true }).count()).toBe(0) + expect(await panel.locator('[data-plugin-package="@deepseek-ai/dsh-experimental-agent-team-profile"]').count()).toBe(0) + expect([...scaffold.ctx.loader.entries()].filter(entry => + ['agent-team', 'tool-agent-team', 'ui-agent-team'].includes(entry.options.id) + && entry.fiber?.state === FiberState.ACTIVE)).toHaveLength(3) const teamPage = await browser.newPage({ viewport: { width: 1680, height: 1000 }, locale: ZH_BROWSER_LOCALE }) const teamTripwire = watchConsole(teamPage) try { await teamPage.goto(scaffold.authenticatedUrl, { waitUntil: 'load' }) await connectFreshWorkspaceZh(teamPage, scaffold.workspaceCwd) - const agent = scaffold.ctx.agents.list()[0] - if (agent === undefined) throw new Error('connected Team workspace did not create an Agent') - // Session actions render only after the conversation leaves its blank state. - agent.session.append('turn/start', { turn: 1 }) - agent.session.append('user/message', createUserMessage({ - content: [{ type: 'text', text: 'Team UI lifecycle' }], source: { kind: 'user' }, - }), { surfaceOp: 'append' }) - agent.session.append('turn/end', { turn: 1, reason: { kind: 'completed' } }) - await scaffold.ctx.sessions.flush(agent.session) - // The current crumb renders as plain text inside the zh-labeled hierarchy nav. - await teamPage.getByRole('navigation', { name: '会话层级' }) - .getByText('Team UI lifecycle', { exact: true }).waitFor() - const action = teamPage.locator('[data-team-action]') - expect(await action.count()).toBe(0) - await toggle.click() - try { - await expect.poll(() => teamRows().filter(entry => entry.fiber?.state === FiberState.ACTIVE).length, { timeout: 20_000 }).toBe(3) - await expect.poll(() => toggle.getAttribute('aria-checked'), { timeout: PLUGIN_TOGGLE_SETTLE_MS }).toBe('true') - await action.waitFor({ timeout: 20_000 }) - await action.getByRole('button', { name: '任务板', exact: true }).click() - const teamPanel = teamPage.getByRole('dialog', { name: '任务板', exact: true }) - await teamPanel.getByText('任务板暂不可用', { exact: true }).waitFor() - await teamPage.reload({ waitUntil: 'load' }) - await action.getByRole('button', { name: '任务板', exact: true }).click() - await teamPanel.getByText('暂无共享任务,可以通过对话创建').waitFor() - expect(await teamPanel.getByRole('heading', { name: '成员', exact: true }).count()).toBe(0) - const manifest = JSON.parse(await homeFile('profiles', 'scaffold', 'package.json')) as { - dsh: { profile: { bundles: string[] } } - } - expect(manifest.dsh.profile.bundles).toEqual([...SCAFFOLD_BUNDLES, '@deepseek-ai/dsh-experimental-agent-team-profile']) - await panel.getByRole('button', { name: '查看 智能体团队', exact: true }).click() - for (const id of ['agent-team', 'tool-agent-team', 'ui-agent-team']) { - await panel.locator('[data-plugin-row]', { hasText: id }).first().waitFor() - } - await panel.getByRole('button', { name: '返回插件' }).click() - } finally { - const back = panel.getByRole('button', { name: '返回插件' }) - if (await back.count() > 0) await back.click() - if (await toggle.getAttribute('aria-checked') === 'true') await toggle.click() - await expect.poll(() => teamRows().filter(entry => entry.fiber?.state === FiberState.ACTIVE).length, { timeout: 20_000 }).toBe(0) - await expect.poll(() => action.count(), { timeout: 20_000 }).toBe(0) - } + await teamPage.getByRole('button', { name: /^选择模型/u }).click() + const mode = teamPage.getByRole('switch', { name: '使用智能体团队', exact: true }) + await mode.waitFor() + expect(await mode.getAttribute('aria-checked')).toBe('false') + await mode.click() + await expect.poll(() => mode.getAttribute('aria-checked')).toBe('true') expect(teamTripwire.pageErrors).toEqual([]) expect(teamTripwire.warnings).toEqual([]) } finally { await teamPage.close() } - expect(tripwire.pageErrors).toEqual([]) }, 60_000) it('mounts the delivered Automation tasks rows without a bundle switch', async () => { diff --git a/apps/web/tests/shipped-composition.e2e.ts b/apps/web/tests/shipped-composition.e2e.ts index d125fd3e6e..1e0ae9dfad 100644 --- a/apps/web/tests/shipped-composition.e2e.ts +++ b/apps/web/tests/shipped-composition.e2e.ts @@ -497,6 +497,10 @@ const EXPECTED_TOOLS = [ 'skill', 'subagent', 'subagent_fork', + 'team_task_create', + 'team_task_get', + 'team_task_list', + 'team_task_update', 'todo_write', 'update_goal', 'wait_agent', @@ -593,8 +597,10 @@ it('assembles the shipped Web transport, catalog, guidance, and defaults', async "mode": "always", } `) - // The directory tool is global; the deployment's default preset supplies the agent's remaining tools. - expect(ctx.tools.schemas().map(schema => schema.name)).toEqual(['working_directory']) + // Directory and conditional Team tools are global; presets supply the remaining registrations. + expect(ctx.tools.schemas().map(schema => schema.name)).toEqual([ + 'working_directory', 'team_task_create', 'team_task_list', 'team_task_get', 'team_task_update', + ]) const handle = await ctx.agents.create({ sessionId: SessionId('shipped-composition'), setup: agentCtx => ctx.agentPresets.mount(agentCtx).then(() => undefined), diff --git a/apps/web/tests/snapshots/agent-team-panel/resize.expected.md b/apps/web/tests/snapshots/agent-team-panel/resize.expected.md index 6e420e796f..c0d94e729b 100644 --- a/apps/web/tests/snapshots/agent-team-panel/resize.expected.md +++ b/apps/web/tests/snapshots/agent-team-panel/resize.expected.md @@ -1,13 +1,18 @@ -- dialog "Task board": - - heading "Shared tasks 2" [level=3] - - article: - - strong: Agent task - - text: In progress - - paragraph: Created by the root agent - - text: "task-1Owner: LeadWrite scopes: src/web" - - article: - - strong: Responsive task - - text: Pending - - paragraph: Check the task description before you start work. Check the task description before you start work. Check the task description before you start work. - - button "Show more" - - text: "task-2Owner: UnownedReady" +- dialog "Subagent sessions": + - tree "Subagent sessions": + - 'treeitem "researcher Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. · External task" [disabled] [level=1]': + - text: researcher + - button "Expand description for researcher": "Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result." + - region "Task board": + - heading "Shared tasks 2" [level=3] + - article: + - strong: Agent task + - text: In progress + - paragraph: Created by the root agent + - text: "task-1Owner: LeadWrite scopes: src/web" + - article: + - strong: Responsive task + - text: Pending + - paragraph: Check the task description before you start work. Check the task description before you start work. Check the task description before you start work. + - button "Show more" + - text: "task-2Owner: UnownedReady" diff --git a/apps/web/tests/snapshots/agent-team-panel/task.expected.md b/apps/web/tests/snapshots/agent-team-panel/task.expected.md index 160bcd4937..ea7069a278 100644 --- a/apps/web/tests/snapshots/agent-team-panel/task.expected.md +++ b/apps/web/tests/snapshots/agent-team-panel/task.expected.md @@ -1,7 +1,12 @@ -- dialog "Task board": - - heading "Shared tasks 1" [level=3] - - article: - - strong: Agent task - - text: In progress - - paragraph: Created by the root agent - - text: "task-1Owner: LeadWrite scopes: src/web" +- dialog "Subagent sessions": + - tree "Subagent sessions": + - 'treeitem "researcher Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. · External task" [disabled] [level=1]': + - text: researcher + - button "Expand description for researcher": "Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result. Researcher: investigate the construction and verify every bound before reporting the result." + - region "Task board": + - heading "Shared tasks 1" [level=3] + - article: + - strong: Agent task + - text: In progress + - paragraph: Created by the root agent + - text: "task-1Owner: LeadWrite scopes: src/web" diff --git a/apps/web/tests/subagent-conversation.e2e.ts b/apps/web/tests/subagent-conversation.e2e.ts index 033f89e14e..b1a1827bc6 100644 --- a/apps/web/tests/subagent-conversation.e2e.ts +++ b/apps/web/tests/subagent-conversation.e2e.ts @@ -5,7 +5,7 @@ import { dirname, join } from 'node:path' import { zstdCompressSync } from 'node:zlib' import type { Browser, Page } from 'playwright' import { chromium } from 'playwright' -import { afterAll, beforeAll, describe, expect, it, onTestFailed, vi } from 'vitest' +import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished, vi } from 'vitest' import { prepareSessionSnapshotFixtureForComparison } from '@deepseek-ai/dsh-llm-replay' import { SESSION_FORMAT_VERSION, SessionId as sessionId, SessionLogOffset, SessionSeq, @@ -102,6 +102,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = let browser: Browser let page: Page let sidecarRoot: string + let parentId: SessionId let childId: SessionId let oneShotId: SessionId let grandchildId: SessionId @@ -141,6 +142,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const parent = scaffold.ctx.agents.list().filter(agent => agent.session.header.origin !== 'subagent')[0] if (parent === undefined) throw new Error('fresh workspace did not publish its parent Agent') + parentId = parent.id const parentSettled = scaffold.whenTurnSettled() const parentInput = page.locator('[data-composer-input][contenteditable="true"]').first() await parentInput.fill(PARENT_PROMPT) @@ -375,6 +377,27 @@ describe('web e2e: persisted subagent conversation and human continuation', () = await writeComposerDraft(page, input, '') }) + it('enters the member tree after pointer or keyboard activation of its trigger', async () => { + onTestFailed(() => saveFailureShot(page, 'web-e2e-subagent-trigger-focus')) + const trigger = page.getByRole('button', { name: '2 subagents', exact: true }) + const panel = page.locator('[data-subagent-panel]') + try { + for (const activation of ['click', 'Enter', 'Space']) { + if (activation === 'click') await trigger.click() + else await trigger.press(activation) + await expect.poll(() => panel.evaluate(element => element === document.activeElement)).toBe(true) + await page.keyboard.press('ArrowDown') + expect(await panel.locator('[role="treeitem"]:not([aria-disabled="true"])').first() + .evaluate(element => element === document.activeElement)).toBe(true) + await page.keyboard.press('Escape') + await panel.waitFor({ state: 'detached' }) + expect(await trigger.evaluate(element => element === document.activeElement)).toBe(true) + } + } finally { + await page.locator('[data-composer-input]').first().hover() + } + }) + it('recovers a current-format cold child catalog cache miss without activating either level', async () => { onTestFailed(() => saveFailureShot(page, 'screenshots/non-migrating-session-reads/web-e2e-subagent-tree')) await page.getByRole('button', { name: '2 subagents' }).hover() @@ -395,7 +418,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const observe = vi.spyOn(scaffold.ctx.sessionQuery, 'observeSession') try { const projections = page.waitForResponse(response => new URL(response.url()).pathname === '/api/session/projections') - await page.getByRole('button', { name: `Expand ${LABEL} descendants` }).click() + await page.getByRole('button', { name: 'Expand researcher descendants' }).click() await projections await page.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }).waitFor({ timeout: 15_000 }) expect(observe.mock.calls.some(([id]) => id === childId)).toBe(true) @@ -407,7 +430,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const childLabel = await childRow.getAttribute('aria-label') await page.waitForTimeout(1_100) expect(await childRow.getAttribute('aria-label')).toBe(childLabel) - expect(await page.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }).textContent()).toContain('continuable') + expect(await page.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }).getAttribute('aria-label')).toContain('continuable') expect(scaffold.ctx.agents.get(childId)).toBeUndefined() expect(scaffold.ctx.agents.get(grandchildId)).toBeUndefined() const snapshot = await captureStableAria( @@ -419,7 +442,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = await page.getByRole('treeitem', { name: new RegExp(NESTED_LABEL) }).click() await page.getByText(NESTED_PROMPT).waitFor() const hierarchy = page.getByRole('navigation', { name: 'Session hierarchy' }) - await hierarchy.getByRole('button', { name: `Switch subagent: ${LABEL}`, exact: true }).waitFor() + await hierarchy.getByRole('button', { name: 'Switch subagent: researcher', exact: true }).waitFor() expect(scaffold.ctx.agents.get(childId)).toBeUndefined() expect(scaffold.ctx.agents.get(grandchildId)).toBeUndefined() await hierarchy.getByRole('button').first().click() @@ -429,7 +452,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = it('opens child history in the right Sidebar and releases it when closed', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-subagent-sidebar-chat')) await page.getByRole('button', { name: '2 subagents' }).hover() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByRole('button', { name: `Open ${LABEL} in sidebar`, exact: true }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByRole('button', { name: 'Open researcher in sidebar', exact: true }).click() const sidebarChat = page.locator('[data-sidebar-chat]') await sidebarChat.getByText(/^Explain event sourcing in one sentence\./).waitFor({ timeout: 15_000 }) await compareOrRefreshGolden( @@ -452,7 +475,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = it('opens the completed child from persistence without activating it', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-subagent-open')) await page.getByRole('button', { name: '2 subagents' }).hover() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByText('researcher', { exact: true }).click() await expect.poll( () => page.getByText(/^Explain event sourcing in one sentence\./).count(), { timeout: 15_000 }, @@ -461,7 +484,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = throw new Error(`viewing the child activated it; API calls: ${apiCalls.join(', ')}`) } const hierarchy = page.getByRole('navigation', { name: 'Session hierarchy' }) - await hierarchy.getByRole('button', { name: `Switch subagent: ${LABEL}` }).waitFor() + await hierarchy.getByRole('button', { name: 'Switch subagent: researcher' }).waitFor() const sidebar = await captureStableAria( page, '[role="tree"][aria-label="Sessions"]', @@ -492,7 +515,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = expect(await page.getByRole('navigation', { name: 'Session hierarchy' }).count()).toBe(0) expect(await page.getByText(/^Explain event sourcing in one sentence\./).count()).toBe(0) releaseCatalog() - await page.getByRole('button', { name: `Switch subagent: ${LABEL}` }).waitFor({ timeout: 15_000 }) + await page.getByRole('button', { name: 'Switch subagent: researcher' }).waitFor({ timeout: 15_000 }) const input = page.getByRole('textbox', { name: 'Message or run a task, / commands, @ files or sessions' }) await input.waitFor({ timeout: 15_000 }) await expect.poll(() => input.isEnabled(), { timeout: 15_000 }).toBe(true) @@ -558,7 +581,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const clickArea = nestedRow.locator(':scope > div') expect(await clickArea.count()).toBe(1) const [menuBox, clickAreaBox] = await Promise.all([ - tree.locator('..').boundingBox(), + page.locator('[data-subagent-panel]').boundingBox(), clickArea.boundingBox(), ]) expect(menuBox).not.toBeNull() @@ -578,7 +601,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = expect(await page.getByRole('textbox', { name: 'Message or run a task, / commands, @ files or sessions' }).isEnabled()).toBe(true) const hierarchy = page.getByRole('navigation', { name: 'Session hierarchy' }) const crumbs = await hierarchy.getByRole('button').allTextContents() - expect(crumbs.slice(-2)).toEqual([LABEL, NESTED_LABEL]) + expect(crumbs.slice(-2)).toEqual(['researcher', NESTED_LABEL]) expect(scaffold.ctx.agents.get(childId)).toBeUndefined() expect(scaffold.ctx.agents.get(grandchildId)).toBeUndefined() await compareOrRefreshGolden( @@ -605,7 +628,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = .getByRole('treeitem', { name: /Ask a research subagent to/ }) .click() await page.getByRole('button', { name: '2 subagents' }).hover() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByText('researcher', { exact: true }).click() await page.getByRole('textbox', { name: 'Message or run a task, / commands, @ files or sessions' }).waitFor() const forkResponse = page.waitForResponse(response => new URL(response.url()).pathname === '/api/session/fork') @@ -634,7 +657,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = const sessions = page.getByRole('tree', { name: 'Sessions' }) await sessions.getByRole('treeitem', { name: /Ask a research subagent to/ }).click() await page.getByRole('button', { name: '2 subagents' }).hover() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByText('researcher', { exact: true }).click() await page.locator('[data-composer-input][contenteditable="true"]').first().waitFor() expect(scaffold.ctx.agents.get(childId)).toBeUndefined() @@ -651,7 +674,7 @@ describe('web e2e: persisted subagent conversation and human continuation', () = await sessions.getByRole('treeitem', { name: /Ask a research subagent to/ }).click() await page.getByRole('button', { name: '2 subagents' }).press('ArrowDown') - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByText('researcher', { exact: true }).click() const input = page.locator('[data-composer-input][contenteditable="true"]').first() await input.waitFor() const promptResponse = page.waitForResponse(response => @@ -675,6 +698,34 @@ describe('web e2e: persisted subagent conversation and human continuation', () = expect(scaffold.ctx.agents.get(forkId)).not.toBeUndefined() await expect.poll(() => scaffold.ctx.agents.get(childId), { timeout: 10_000 }).toBeUndefined() }) + + it('shows shared tasks only in the root conversation while keeping child navigation', async () => { + const parent = scaffold.ctx.agents.get(parentId) + if (parent === undefined) throw new Error('task-board fixture requires the live parent') + const enabled = scaffold.ctx.agentTeams.isEnabled(parent) + onTestFinished(() => scaffold.ctx.agentTeams.setEnabled(parent, enabled)) + await scaffold.ctx.agentTeams.setEnabled(parent, true) + await scaffold.ctx.agentTeams.createTask(parent, { + subject: 'Root-only shared task', description: 'Inspect from the root conversation.', writeScopes: [], + }) + await page.getByRole('tree', { name: 'Sessions' }) + .getByRole('treeitem', { name: /Ask a research subagent to/ }).click() + await page.getByRole('button', { name: '2 subagents', exact: true }).click() + await page.locator('[data-team-panel]').getByText('Root-only shared task', { exact: true }).waitFor() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).getByText('researcher', { exact: true }).click() + const switcher = page.getByRole('button', { name: 'Switch subagent: researcher', exact: true }) + await switcher.click() + const panel = page.locator('[data-subagent-panel]') + await panel.getByRole('treeitem', { name: new RegExp(ONE_SHOT_LABEL) }).waitFor() + expect(await panel.locator('[data-team-panel]').count()).toBe(0) + await compareOrRefreshGolden( + fileURLToPath(new URL('./expected/subagent-conversation/navigation-only.expected.md', import.meta.url)), + await captureStableAria(page, '[data-subagent-panel]', scaffold.workspaceCwd), MODE, + ) + await page.keyboard.press('Escape') + }) + + }) describe('web e2e: migration-required subagent catalog', () => { diff --git a/apps/web/tests/subagent-interrupt-ui.e2e.ts b/apps/web/tests/subagent-interrupt-ui.e2e.ts index ad0c288f61..8c5f8fb1ea 100644 --- a/apps/web/tests/subagent-interrupt-ui.e2e.ts +++ b/apps/web/tests/subagent-interrupt-ui.e2e.ts @@ -210,7 +210,7 @@ describe.skipIf(MODE === 'record')('web e2e: composer interrupt for a running co await page.waitForSelector('[class*="frame"]', { timeout: 30_000 }) acknowledgeReloadConnectionLoss(tripwire, warningStart) await page.getByRole('button', { name: /1 subagent/ }).click() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).press('Enter') const input = page.locator('[data-composer-input]').first() await input.waitFor({ timeout: 15_000 }) await page.getByText(/^Explain event sourcing in one sentence\./) @@ -270,7 +270,7 @@ describe.skipIf(MODE === 'record')('web e2e: composer interrupt for a running co await page.getByRole('navigation', { name: 'Session hierarchy' }) .getByRole('button').first().click() await page.getByRole('button', { name: /1 subagent/ }).click() - await page.getByRole('treeitem', { name: new RegExp(LABEL) }).click() + await page.getByRole('treeitem', { name: new RegExp(LABEL) }).press('Enter') // A live continuable child advertises the ordinary steer-all gesture, so // that placeholder is the composer's accessible name in this window. It // changes back as the queue drains, so later interactions address the diff --git a/apps/web/tests/workflow-run.e2e.ts b/apps/web/tests/workflow-run.e2e.ts index e43a7c7fb8..7fbd53a759 100644 --- a/apps/web/tests/workflow-run.e2e.ts +++ b/apps/web/tests/workflow-run.e2e.ts @@ -220,7 +220,7 @@ describe.skipIf(MODE === 'record')('web e2e: durable workflow run in Chat', () = await page.getByRole('button', { name: '1 subagent', exact: true }).hover() const catalog = page.getByRole('tree', { name: 'Subagent sessions' }) - await catalog.getByRole('treeitem', { name: /snapshot-flow child 1/ }).click() + await catalog.getByRole('treeitem', { name: /snapshot-flow child 1/ }).press('Enter') await page.getByText(CHILD_PROMPT, { exact: true }).waitFor({ timeout: 15_000 }) expect(await page.locator('[data-composer-input][contenteditable="true"]').count()).toBe(1) }, 60_000) diff --git a/docs/capability-seams.md b/docs/capability-seams.md index d8cdc3fa32..2b5e9978b2 100644 --- a/docs/capability-seams.md +++ b/docs/capability-seams.md @@ -267,7 +267,7 @@ flowchart LR pkg_experimental_speech_to_text["experimental-speech-to-text"] svc_speechToText["ctx.speechToText
Experimental speech recognition providers"] pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] - pkg_experimental_agent_team["experimental-agent-team"] + pkg_agent_team["agent-team"] svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] pkg_experimental_claude_code_mods["experimental-claude-code-mods"] @@ -320,6 +320,7 @@ flowchart LR pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop pkg_agent_preset_registry --> svc_agentPresets + pkg_agent_team --> svc_agentTeams pkg_api_gateway --> svc_typertGateway pkg_api_job_controller --> svc_jobController pkg_api_remote_access --> svc_remoteAccess @@ -359,7 +360,6 @@ flowchart LR pkg_deepseek_account --> svc_deepseekAccount pkg_deepseek_account_platform --> svc_deepseekAccount pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions - pkg_experimental_agent_team --> svc_agentTeams pkg_experimental_api_speech_to_text --> svc_speechController pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse pkg_experimental_browser_use_playwright_mcp --> svc_browserUse @@ -488,7 +488,7 @@ flowchart LR svc_agentDefaultModel --> pkg_headless svc_agentLoop --> pkg_base svc_agentLoop --> pkg_sdk_minimal - svc_agentTeams --> pkg_experimental_tool_agent_team + svc_agentTeams --> pkg_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop svc_agents --> pkg_subagent @@ -742,7 +742,7 @@ flowchart LR | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | The basic backend consumes post-step pressure and request-error recovery events; there is no model-facing compact tool. | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | Providers prepare local children or execute external tasks; one activation manager owns both lifecycles. The delegation tool selects a backend, control tools deliver local follow-ups, and workflows await activation results. | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | Routes explicit recognizers; the browser uses the authenticated Remote and keeps transcripts in the draft until submission. | -| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | Owns the shared task DAG for a subagent tree; tool-agent-team contributes task controls. | +| `ctx.agentTeams` | `core` | [`agent-team`](../packages/team/agent-team) | - | [`tool-agent-team`](../packages/team/tool-agent-team) | - | Owns the shared task DAG for a subagent tree; tool-agent-team contributes task controls. | | `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | Loads mods that defineMod plugins add, raises their hook chains from harness extension points, and draws the band above the prompt over its Remote. | | `ctx.inspector` | `core` | `inspector` | - | - | - | Owns the Worker-hosted CDP target and the transport-independent Host and Client observation and Cordis-tree query API. | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | Producers (background bash/pwsh and PTY sends) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry. | diff --git a/docs/capability-seams.zh.md b/docs/capability-seams.zh.md index 63188c7fbe..a92a944566 100644 --- a/docs/capability-seams.zh.md +++ b/docs/capability-seams.zh.md @@ -269,7 +269,7 @@ flowchart LR pkg_experimental_speech_to_text["experimental-speech-to-text"] svc_speechToText["ctx.speechToText
Experimental speech recognition providers"] pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] - pkg_experimental_agent_team["experimental-agent-team"] + pkg_agent_team["agent-team"] svc_agentTeams["ctx.agentTeams
Agent Teams coordination domain"] pkg_experimental_tool_agent_team["experimental-tool-agent-team"] pkg_experimental_claude_code_mods["experimental-claude-code-mods"] @@ -322,6 +322,7 @@ flowchart LR pkg_agent_default_model --> svc_agentDefaultModel pkg_agent_loop --> svc_agentLoop pkg_agent_preset_registry --> svc_agentPresets + pkg_agent_team --> svc_agentTeams pkg_api_gateway --> svc_typertGateway pkg_api_job_controller --> svc_jobController pkg_api_remote_access --> svc_remoteAccess @@ -361,7 +362,6 @@ flowchart LR pkg_deepseek_account --> svc_deepseekAccount pkg_deepseek_account_platform --> svc_deepseekAccount pkg_deepseek_llm_api_extensions --> svc_deepseekLlmApiExtensions - pkg_experimental_agent_team --> svc_agentTeams pkg_experimental_api_speech_to_text --> svc_speechController pkg_experimental_browser_use_chrome_devtools_mcp --> svc_browserUse pkg_experimental_browser_use_playwright_mcp --> svc_browserUse @@ -490,7 +490,7 @@ flowchart LR svc_agentDefaultModel --> pkg_headless svc_agentLoop --> pkg_base svc_agentLoop --> pkg_sdk_minimal - svc_agentTeams --> pkg_experimental_tool_agent_team + svc_agentTeams --> pkg_tool_agent_team svc_agents --> pkg_acp svc_agents --> pkg_agent_loop svc_agents --> pkg_subagent @@ -744,7 +744,7 @@ flowchart LR | `ctx.compaction` | `seam` | [`compaction`](../packages/compaction/compaction) | [`compaction-basic`](../packages/compaction/compaction-basic) | [`compaction-basic`](../packages/compaction/compaction-basic) | - | 基础后端消费步骤后的压力事件和请求错误恢复事件;不存在面向模型的压缩工具。 | | `ctx.subagents` | `seam` | [`subagent`](../packages/subagent/subagent) | [`subagent-spawn-in-process`](../packages/subagent/subagent-spawn-in-process), [`subagent-fork-in-process`](../packages/subagent/subagent-fork-in-process), [`subagent-acp`](../packages/subagent/subagent-acp), [`subagent-codex`](../packages/subagent/subagent-codex), [`subagent-claude-code`](../packages/subagent/subagent-claude-code), [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | [`tool-subagent`](../packages/subagent/tool-subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control), [`tool-ralph`](../packages/experimental/tool-ralph) | - | 提供方准备本地子级或执行外部任务;一个 activation 管理器持有两类生命周期。委派工具选择后端,控制工具投递本地后续消息,工作流等待 activation 结果。 | | `ctx.speechToText` | `seam` | [`experimental-speech-to-text`](../packages/experimental/speech-to-text) | [`experimental-speech-to-text-sensevoice`](../packages/experimental/speech-to-text-sensevoice) | [`experimental-api-speech-to-text`](../packages/experimental/api-speech-to-text) | - | 路由显式选择的识别器;浏览器使用带认证的 Remote,并在提交前将转写保留在草稿中。 | -| `ctx.agentTeams` | `core` | [`experimental-agent-team`](../packages/experimental/agent-team) | - | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | - | 负责子代理树共享的任务 DAG;tool-agent-team 提供任务控制工具。 | +| `ctx.agentTeams` | `core` | [`agent-team`](../packages/team/agent-team) | - | [`tool-agent-team`](../packages/team/tool-agent-team) | - | 负责子代理树共享的任务 DAG;tool-agent-team 提供任务控制工具。 | | `ctx.claudeCodeMods` | `core` | [`experimental-claude-code-mods`](../packages/experimental/claude-code-mods) | - | - | - | 加载 defineMod 插件添加的模组,从 harness 扩展点触发其钩子链,并通过其 Remote 绘制提示框上方的横幅。 | | `ctx.inspector` | `core` | `inspector` | - | - | - | 负责 Worker 托管的 CDP target,以及独立于传输的 Host 和 Client observation 与 Cordis tree query API。 | | `ctx.jobs` | `seam` | [`jobs`](../packages/jobs/jobs) | [`jobs-local`](../packages/jobs/jobs-local) | [`tool-bash`](../packages/shell/tool-bash), [`tool-pwsh`](../packages/shell/tool-pwsh), [`tool-terminal`](../packages/experimental/tool-terminal), [`tool-jobs`](../packages/jobs/tool-jobs), [`api-job-controller`](../packages/api/job-controller) | - | 生产方(后台 bash/pwsh 与 PTY 发送)登记正在运行的工作;声明 record 的 job 还为非消费观察者流式提供原始输出;tool-jobs 是面向模型的控制器,用于读取、列出和终止这些工作;jobs-local 是进程本地注册表。 | diff --git a/docs/config-catalog.md b/docs/config-catalog.md index ce6195a370..09cfc53303 100644 --- a/docs/config-catalog.md +++ b/docs/config-catalog.md @@ -171,6 +171,23 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-agent-team` + +- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents` +- `source`: [`packages/team/agent-team/src/types.ts:132`](../packages/team/agent-team/src/types.ts) + +```ts config-catalog +/** Team-service deployment limits. */ +export interface Config { + /** Maximum non-deleted tasks retained by one Team. */ + readonly maxTasks?: number +} +``` + + @@ -875,23 +892,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-experimental-agent-team` - -- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents` -- `source`: [`packages/experimental/agent-team/src/types.ts:131`](../packages/experimental/agent-team/src/types.ts) - -```ts config-catalog -/** Team-service deployment limits. */ -export interface Config { - /** Maximum non-deleted tasks retained by one Team. */ - readonly maxTasks?: number -} -``` - - @@ -3899,6 +3899,20 @@ export type TokenMeterConfig = Record ``` + + + +## `@deepseek-ai/dsh-tool-agent-team` + +- `inject`: `agentTeams` · `subagents` · `subagentNames` · `tools` · `systemPrompt` +- `source`: [`packages/team/tool-agent-team/src/index.ts:19`](../packages/team/tool-agent-team/src/index.ts) + +```ts config-catalog +/** The Team tool plugin takes no configuration. */ +export type Config = Record +``` + + @@ -4836,6 +4850,7 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-client-resources` | — | [`packages/client/resources/src/index.ts`](../packages/client/resources/src/index.ts) | | `@deepseek-ai/dsh-client-ui-action-cards` | — | [`packages/experimental/client-ui-action-cards/src/index.ts`](../packages/experimental/client-ui-action-cards/src/index.ts) | | `@deepseek-ai/dsh-client-ui-agent-preset` | — | [`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-agent-team` | — | [`packages/client/ui-agent-team/src/index.ts`](../packages/client/ui-agent-team/src/index.ts) | | `@deepseek-ai/dsh-client-ui-approval` | — | [`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts) | | `@deepseek-ai/dsh-client-ui-attachment` | — | [`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts) | | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | @@ -4901,7 +4916,6 @@ These load from a `cordis.yml` entry with no `config:` block; they declare no co | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | | `@deepseek-ai/dsh-deliverable-file-references` | `systemPrompt` | [`packages/deliverables/deliverable-file-references/src/index.ts`](../packages/deliverables/deliverable-file-references/src/index.ts) | | `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` · `workingDirectory` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | -| `@deepseek-ai/dsh-experimental-client-ui-agent-team` | — | [`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-claude-code-mods` | — | [`packages/experimental/client-ui-claude-code-mods/src/index.ts`](../packages/experimental/client-ui-claude-code-mods/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-stats` | — | [`packages/experimental/client-ui-stats/src/index.ts`](../packages/experimental/client-ui-stats/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) | @@ -4992,7 +5006,6 @@ Imported as libraries by other packages; a `cordis.yml` cannot load them. | `@deepseek-ai/dsh-cmdline` | — | [`packages/boot/cmdline/src/index.ts`](../packages/boot/cmdline/src/index.ts) | | `@deepseek-ai/dsh-cua-bundle` | — | [`packages/experimental/cua-bundle/src/index.ts`](../packages/experimental/cua-bundle/src/index.ts) | | `@deepseek-ai/dsh-deque` | — | [`packages/util/deque/src/index.ts`](../packages/util/deque/src/index.ts) | -| `@deepseek-ai/dsh-experimental-agent-team-profile` | — | [`packages/experimental/agent-team-profile/src/index.ts`](../packages/experimental/agent-team-profile/src/index.ts) | | `@deepseek-ai/dsh-experimental-badge-skill-bundle` | — | [`packages/experimental/badge-skill-bundle/src/index.ts`](../packages/experimental/badge-skill-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-browser-use-runtime` | — | [`packages/experimental/browser-use-runtime/src/index.ts`](../packages/experimental/browser-use-runtime/src/index.ts) | | `@deepseek-ai/dsh-experimental-cot-translation-bundle` | — | [`packages/experimental/cot-translation-bundle/src/index.ts`](../packages/experimental/cot-translation-bundle/src/index.ts) | diff --git a/docs/config-catalog.zh.md b/docs/config-catalog.zh.md index 2b81cf900c..4319bad0ba 100644 --- a/docs/config-catalog.zh.md +++ b/docs/config-catalog.zh.md @@ -173,6 +173,23 @@ export interface Config { ``` + + + +## `@deepseek-ai/dsh-agent-team` + +- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents` +- `source`: [`packages/team/agent-team/src/types.ts:132`](../packages/team/agent-team/src/types.ts) + +```ts config-catalog +/** Team-service deployment limits. */ +export interface Config { + /** Maximum non-deleted tasks retained by one Team. */ + readonly maxTasks?: number +} +``` + + @@ -877,23 +894,6 @@ export interface Config { ``` - - - -## `@deepseek-ai/dsh-experimental-agent-team` - -- `inject`: `agents` · `sessions` · `sessionPersistence` · `sessionProjections` · `subagents` -- `source`: [`packages/experimental/agent-team/src/types.ts:131`](../packages/experimental/agent-team/src/types.ts) - -```ts config-catalog -/** Team-service deployment limits. */ -export interface Config { - /** Maximum non-deleted tasks retained by one Team. */ - readonly maxTasks?: number -} -``` - - @@ -3901,6 +3901,20 @@ export type TokenMeterConfig = Record ``` + + + +## `@deepseek-ai/dsh-tool-agent-team` + +- `inject`: `agentTeams` · `subagents` · `subagentNames` · `tools` · `systemPrompt` +- `source`: [`packages/team/tool-agent-team/src/index.ts:19`](../packages/team/tool-agent-team/src/index.ts) + +```ts config-catalog +/** The Team tool plugin takes no configuration. */ +export type Config = Record +``` + + @@ -4838,6 +4852,7 @@ export interface Config { | `@deepseek-ai/dsh-client-resources` | — | [`packages/client/resources/src/index.ts`](../packages/client/resources/src/index.ts) | | `@deepseek-ai/dsh-client-ui-action-cards` | — | [`packages/experimental/client-ui-action-cards/src/index.ts`](../packages/experimental/client-ui-action-cards/src/index.ts) | | `@deepseek-ai/dsh-client-ui-agent-preset` | — | [`packages/client/ui-agent-preset/src/index.ts`](../packages/client/ui-agent-preset/src/index.ts) | +| `@deepseek-ai/dsh-client-ui-agent-team` | — | [`packages/client/ui-agent-team/src/index.ts`](../packages/client/ui-agent-team/src/index.ts) | | `@deepseek-ai/dsh-client-ui-approval` | — | [`packages/client/ui-approval/src/index.ts`](../packages/client/ui-approval/src/index.ts) | | `@deepseek-ai/dsh-client-ui-attachment` | — | [`packages/client/ui-attachment/src/index.ts`](../packages/client/ui-attachment/src/index.ts) | | `@deepseek-ai/dsh-client-ui-brand-official` | — | [`packages/client/ui-brand-official/src/index.ts`](../packages/client/ui-brand-official/src/index.ts) | @@ -4903,7 +4918,6 @@ export interface Config { | `@deepseek-ai/dsh-deepseek-llm-api-extensions` | — | [`packages/llm/deepseek-llm-api-extensions/src/index.ts`](../packages/llm/deepseek-llm-api-extensions/src/index.ts) | | `@deepseek-ai/dsh-deliverable-file-references` | `systemPrompt` | [`packages/deliverables/deliverable-file-references/src/index.ts`](../packages/deliverables/deliverable-file-references/src/index.ts) | | `@deepseek-ai/dsh-experimental-auto-review` | `approval` · `llm` · `permissionPresets` · `sessions` · `tools` · `workingDirectory` | [`packages/experimental/auto-review/src/index.ts`](../packages/experimental/auto-review/src/index.ts) | -| `@deepseek-ai/dsh-experimental-client-ui-agent-team` | — | [`packages/experimental/client-ui-agent-team/src/index.ts`](../packages/experimental/client-ui-agent-team/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-claude-code-mods` | — | [`packages/experimental/client-ui-claude-code-mods/src/index.ts`](../packages/experimental/client-ui-claude-code-mods/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-stats` | — | [`packages/experimental/client-ui-stats/src/index.ts`](../packages/experimental/client-ui-stats/src/index.ts) | | `@deepseek-ai/dsh-experimental-client-ui-voice-input` | — | [`packages/experimental/client-ui-voice-input/src/index.ts`](../packages/experimental/client-ui-voice-input/src/index.ts) | @@ -4994,7 +5008,6 @@ export interface Config { | `@deepseek-ai/dsh-cmdline` | — | [`packages/boot/cmdline/src/index.ts`](../packages/boot/cmdline/src/index.ts) | | `@deepseek-ai/dsh-cua-bundle` | — | [`packages/experimental/cua-bundle/src/index.ts`](../packages/experimental/cua-bundle/src/index.ts) | | `@deepseek-ai/dsh-deque` | — | [`packages/util/deque/src/index.ts`](../packages/util/deque/src/index.ts) | -| `@deepseek-ai/dsh-experimental-agent-team-profile` | — | [`packages/experimental/agent-team-profile/src/index.ts`](../packages/experimental/agent-team-profile/src/index.ts) | | `@deepseek-ai/dsh-experimental-badge-skill-bundle` | — | [`packages/experimental/badge-skill-bundle/src/index.ts`](../packages/experimental/badge-skill-bundle/src/index.ts) | | `@deepseek-ai/dsh-experimental-browser-use-runtime` | — | [`packages/experimental/browser-use-runtime/src/index.ts`](../packages/experimental/browser-use-runtime/src/index.ts) | | `@deepseek-ai/dsh-experimental-cot-translation-bundle` | — | [`packages/experimental/cot-translation-bundle/src/index.ts`](../packages/experimental/cot-translation-bundle/src/index.ts) | diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 5d9eec10a8..672fd57664 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -13,8 +13,8 @@ Application listeners are outside this package matrix. Desktop Host subscribes t | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:198`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index a28100d742..368277a1ef 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -15,8 +15,8 @@ | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:198`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | | `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, `tool-agent-team`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), `tool-agent-team`, [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | | `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | | `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | | `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | diff --git a/docs/module-graph.md b/docs/module-graph.md index dee321a1dc..b8b97c101b 100644 --- a/docs/module-graph.md +++ b/docs/module-graph.md @@ -160,6 +160,7 @@ flowchart TD pkg_client_shortcuts["client-shortcuts"] pkg_client_store["client-store"] pkg_client_ui_agent_preset["client-ui-agent-preset"] + pkg_client_ui_agent_team["client-ui-agent-team"] pkg_client_ui_approval["client-ui-approval"] pkg_client_ui_attachment["client-ui-attachment"] pkg_client_ui_brand_official["client-ui-brand-official"] @@ -262,8 +263,6 @@ flowchart TD pkg_creation_recommendations["creation-recommendations"] pkg_cua["cua"] pkg_cua_bundle["cua-bundle"] - pkg_experimental_agent_team["experimental-agent-team"] - pkg_experimental_agent_team_profile["experimental-agent-team-profile"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] pkg_experimental_auto_review["experimental-auto-review"] pkg_experimental_badge_skill_bundle["experimental-badge-skill-bundle"] @@ -272,7 +271,6 @@ flowchart TD pkg_experimental_browser_use_runtime["experimental-browser-use-runtime"] pkg_experimental_browser_use_stagehand_native["experimental-browser-use-stagehand-native"] pkg_experimental_claude_code_mods["experimental-claude-code-mods"] - pkg_experimental_client_ui_agent_team["experimental-client-ui-agent-team"] pkg_experimental_client_ui_claude_code_mods["experimental-client-ui-claude-code-mods"] pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] pkg_experimental_client_ui_forge["experimental-client-ui-forge"] @@ -300,7 +298,6 @@ flowchart TD pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] pkg_experimental_subagent_oar["experimental-subagent-oar"] pkg_experimental_terminal_bundle["experimental-terminal-bundle"] - pkg_experimental_tool_agent_team["experimental-tool-agent-team"] pkg_experimental_tool_subagent_oar["experimental-tool-subagent-oar"] pkg_experimental_tool_worktree["experimental-tool-worktree"] pkg_experimental_translator["experimental-translator"] @@ -451,6 +448,10 @@ flowchart TD pkg_subprocess_local["subprocess-local"] pkg_win32_process["win32-process"] end + subgraph group_team["packages/team"] + pkg_agent_team["agent-team"] + pkg_tool_agent_team["tool-agent-team"] + end subgraph group_telemetry["packages/telemetry"] pkg_otel["otel"] end @@ -1395,12 +1396,6 @@ flowchart TD pkg_session_reference --> pkg_subagent pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol - pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_llm - pkg_experimental_agent_team --> pkg_session - pkg_experimental_agent_team --> pkg_session_persistence - pkg_experimental_agent_team --> pkg_session_projection - pkg_experimental_agent_team --> pkg_subagent pkg_experimental_subagent_oar --> pkg_llm pkg_experimental_subagent_oar --> pkg_session pkg_experimental_subagent_oar --> pkg_subagent @@ -1425,6 +1420,13 @@ flowchart TD pkg_sdk_protocol --> pkg_session pkg_sdk_protocol --> pkg_subagent pkg_sdk_protocol --> pkg_user_questions + pkg_agent_team --> pkg_agent + pkg_agent_team --> pkg_llm + pkg_agent_team --> pkg_session + pkg_agent_team --> pkg_session_persistence + pkg_agent_team --> pkg_session_projection + pkg_agent_team --> pkg_subagent + pkg_agent_team --> pkg_typert_protocol pkg_workflow_ptc --> pkg_agent pkg_workflow_ptc --> pkg_llm pkg_workflow_ptc --> pkg_ptc_runtime @@ -1457,6 +1459,13 @@ flowchart TD pkg_sdk_client --> pkg_llm pkg_sdk_client --> pkg_sdk_protocol pkg_sdk_client --> pkg_session + pkg_tool_agent_team --> pkg_agent + pkg_tool_agent_team --> pkg_agent_team + pkg_tool_agent_team --> pkg_session + pkg_tool_agent_team --> pkg_subagent + pkg_tool_agent_team --> pkg_subagent_names + pkg_tool_agent_team --> pkg_system_prompt + pkg_tool_agent_team --> pkg_tools pkg_client_test_runtime --> pkg_api_gateway pkg_client_test_runtime --> pkg_api_session_controller pkg_client_test_runtime --> pkg_api_workspace_controller @@ -1549,6 +1558,7 @@ flowchart TD | [`client-shortcuts`](../packages/client/shortcuts) | `client` | — | | [`client-store`](../packages/client/store) | `client` | — | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | — | +| [`client-ui-agent-team`](../packages/client/ui-agent-team) | `client` | — | | [`client-ui-approval`](../packages/client/ui-approval) | `client` | — | | [`client-ui-attachment`](../packages/client/ui-attachment) | `client` | — | | [`client-ui-brand-official`](../packages/client/ui-brand-official) | `client` | — | @@ -1614,9 +1624,7 @@ flowchart TD | [`client-ui-creation-recommendations`](../packages/experimental/client-ui-creation-recommendations) | `experimental` | — | | [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | `experimental` | — | | [`cua-bundle`](../packages/experimental/cua-bundle) | `experimental` | — | -| [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | -| [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | — | | [`experimental-client-ui-claude-code-mods`](../packages/experimental/client-ui-claude-code-mods) | `experimental` | — | | [`experimental-client-ui-stats`](../packages/experimental/client-ui-stats) | `experimental` | — | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | — | @@ -1864,16 +1872,17 @@ flowchart TD | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`subagent`](../packages/subagent/subagent), [`user-approval`](../packages/interaction/user-approval) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | | [`experimental-subagent-oar`](../packages/experimental/subagent-oar) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`experimental-tool-subagent-oar`](../packages/experimental/tool-subagent-oar) | `experimental` | [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-ralph`](../packages/experimental/tool-ralph) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| [`agent-team`](../packages/team/agent-team) | `team` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow), [`working-directory`](../packages/session/working-directory) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | +| [`tool-agent-team`](../packages/team/tool-agent-team) | `team` | [`agent`](../packages/core/agent), [`agent-team`](../packages/team/agent-team), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-names`](../packages/subagent/subagent-names), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-context-menu`](../packages/client/ui-context-menu), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`tool-schedule`](../packages/schedule/tool-schedule) | `schedule` | [`llm`](../packages/llm/llm), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | diff --git a/docs/module-graph.zh.md b/docs/module-graph.zh.md index c596d9e427..f85b309e79 100644 --- a/docs/module-graph.zh.md +++ b/docs/module-graph.zh.md @@ -162,6 +162,7 @@ flowchart TD pkg_client_shortcuts["client-shortcuts"] pkg_client_store["client-store"] pkg_client_ui_agent_preset["client-ui-agent-preset"] + pkg_client_ui_agent_team["client-ui-agent-team"] pkg_client_ui_approval["client-ui-approval"] pkg_client_ui_attachment["client-ui-attachment"] pkg_client_ui_brand_official["client-ui-brand-official"] @@ -264,8 +265,6 @@ flowchart TD pkg_creation_recommendations["creation-recommendations"] pkg_cua["cua"] pkg_cua_bundle["cua-bundle"] - pkg_experimental_agent_team["experimental-agent-team"] - pkg_experimental_agent_team_profile["experimental-agent-team-profile"] pkg_experimental_api_speech_to_text["experimental-api-speech-to-text"] pkg_experimental_auto_review["experimental-auto-review"] pkg_experimental_badge_skill_bundle["experimental-badge-skill-bundle"] @@ -274,7 +273,6 @@ flowchart TD pkg_experimental_browser_use_runtime["experimental-browser-use-runtime"] pkg_experimental_browser_use_stagehand_native["experimental-browser-use-stagehand-native"] pkg_experimental_claude_code_mods["experimental-claude-code-mods"] - pkg_experimental_client_ui_agent_team["experimental-client-ui-agent-team"] pkg_experimental_client_ui_claude_code_mods["experimental-client-ui-claude-code-mods"] pkg_experimental_client_ui_cot_translation["experimental-client-ui-cot-translation"] pkg_experimental_client_ui_forge["experimental-client-ui-forge"] @@ -302,7 +300,6 @@ flowchart TD pkg_experimental_speech_to_text_sensevoice["experimental-speech-to-text-sensevoice"] pkg_experimental_subagent_oar["experimental-subagent-oar"] pkg_experimental_terminal_bundle["experimental-terminal-bundle"] - pkg_experimental_tool_agent_team["experimental-tool-agent-team"] pkg_experimental_tool_subagent_oar["experimental-tool-subagent-oar"] pkg_experimental_tool_worktree["experimental-tool-worktree"] pkg_experimental_translator["experimental-translator"] @@ -453,6 +450,10 @@ flowchart TD pkg_subprocess_local["subprocess-local"] pkg_win32_process["win32-process"] end + subgraph group_team["packages/team"] + pkg_agent_team["agent-team"] + pkg_tool_agent_team["tool-agent-team"] + end subgraph group_telemetry["packages/telemetry"] pkg_otel["otel"] end @@ -1397,12 +1398,6 @@ flowchart TD pkg_session_reference --> pkg_subagent pkg_session_reference --> pkg_system_prompt pkg_session_reference --> pkg_typert_protocol - pkg_experimental_agent_team --> pkg_agent - pkg_experimental_agent_team --> pkg_llm - pkg_experimental_agent_team --> pkg_session - pkg_experimental_agent_team --> pkg_session_persistence - pkg_experimental_agent_team --> pkg_session_projection - pkg_experimental_agent_team --> pkg_subagent pkg_experimental_subagent_oar --> pkg_llm pkg_experimental_subagent_oar --> pkg_session pkg_experimental_subagent_oar --> pkg_subagent @@ -1427,6 +1422,13 @@ flowchart TD pkg_sdk_protocol --> pkg_session pkg_sdk_protocol --> pkg_subagent pkg_sdk_protocol --> pkg_user_questions + pkg_agent_team --> pkg_agent + pkg_agent_team --> pkg_llm + pkg_agent_team --> pkg_session + pkg_agent_team --> pkg_session_persistence + pkg_agent_team --> pkg_session_projection + pkg_agent_team --> pkg_subagent + pkg_agent_team --> pkg_typert_protocol pkg_workflow_ptc --> pkg_agent pkg_workflow_ptc --> pkg_llm pkg_workflow_ptc --> pkg_ptc_runtime @@ -1459,6 +1461,13 @@ flowchart TD pkg_sdk_client --> pkg_llm pkg_sdk_client --> pkg_sdk_protocol pkg_sdk_client --> pkg_session + pkg_tool_agent_team --> pkg_agent + pkg_tool_agent_team --> pkg_agent_team + pkg_tool_agent_team --> pkg_session + pkg_tool_agent_team --> pkg_subagent + pkg_tool_agent_team --> pkg_subagent_names + pkg_tool_agent_team --> pkg_system_prompt + pkg_tool_agent_team --> pkg_tools pkg_client_test_runtime --> pkg_api_gateway pkg_client_test_runtime --> pkg_api_session_controller pkg_client_test_runtime --> pkg_api_workspace_controller @@ -1551,6 +1560,7 @@ flowchart TD | [`client-shortcuts`](../packages/client/shortcuts) | `client` | — | | [`client-store`](../packages/client/store) | `client` | — | | [`client-ui-agent-preset`](../packages/client/ui-agent-preset) | `client` | — | +| [`client-ui-agent-team`](../packages/client/ui-agent-team) | `client` | — | | [`client-ui-approval`](../packages/client/ui-approval) | `client` | — | | [`client-ui-attachment`](../packages/client/ui-attachment) | `client` | — | | [`client-ui-brand-official`](../packages/client/ui-brand-official) | `client` | — | @@ -1616,9 +1626,7 @@ flowchart TD | [`client-ui-creation-recommendations`](../packages/experimental/client-ui-creation-recommendations) | `experimental` | — | | [`client-ui-plugin-recommendations`](../packages/experimental/client-ui-plugin-recommendations) | `experimental` | — | | [`cua-bundle`](../packages/experimental/cua-bundle) | `experimental` | — | -| [`experimental-agent-team-profile`](../packages/experimental/agent-team-profile) | `experimental` | — | | [`experimental-badge-skill-bundle`](../packages/experimental/badge-skill-bundle) | `experimental` | — | -| [`experimental-client-ui-agent-team`](../packages/experimental/client-ui-agent-team) | `experimental` | — | | [`experimental-client-ui-claude-code-mods`](../packages/experimental/client-ui-claude-code-mods) | `experimental` | — | | [`experimental-client-ui-stats`](../packages/experimental/client-ui-stats) | `experimental` | — | | [`experimental-client-ui-voice-input`](../packages/experimental/client-ui-voice-input) | `experimental` | — | @@ -1866,16 +1874,17 @@ flowchart TD | [`headless`](../packages/bundle/headless) | `bundle` | [`agent`](../packages/core/agent), [`agent-default-model`](../packages/core/agent-default-model), [`fs`](../packages/fs/fs), [`llm`](../packages/llm/llm), [`permission-presets`](../packages/interaction/permission-presets), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`session-query`](../packages/session-query/session-query), [`subagent`](../packages/subagent/subagent), [`user-approval`](../packages/interaction/user-approval) | | [`compaction-basic`](../packages/compaction/compaction-basic) | `compaction` | [`agent`](../packages/core/agent), [`commands`](../packages/interaction/commands), [`compaction`](../packages/compaction/compaction), [`compaction-tool-result-pruner`](../packages/compaction/compaction-tool-result-pruner), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`token-meter`](../packages/llm/token-meter) | | [`session-reference`](../packages/context/session-reference) | `context` | [`agent`](../packages/core/agent), [`compaction`](../packages/compaction/compaction), [`llm`](../packages/llm/llm), [`output-retention`](../packages/util/output-retention), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection), [`session-projection-cache`](../packages/session/session-projection-cache), [`session-query`](../packages/session-query/session-query), [`session-title`](../packages/session/session-title), [`spill`](../packages/spill/spill), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`typert-protocol`](../packages/typert/protocol) | -| [`experimental-agent-team`](../packages/experimental/agent-team) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent) | | [`experimental-subagent-oar`](../packages/experimental/subagent-oar) | `experimental` | [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`experimental-tool-subagent-oar`](../packages/experimental/tool-subagent-oar) | `experimental` | [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools) | | [`hooks-claude-code`](../packages/experimental/hooks-claude-code) | `experimental` | [`agent`](../packages/core/agent), [`hook-protocol`](../packages/experimental/hook-protocol), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`working-directory`](../packages/session/working-directory) | | [`tool-ralph`](../packages/experimental/tool-ralph) | `experimental` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow) | | [`sdk-protocol`](../packages/sdk/protocol) | `sdk` | [`commands`](../packages/interaction/commands), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| [`agent-team`](../packages/team/agent-team) | `team` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`workflow-ptc`](../packages/workflow/workflow-ptc) | `workflow` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`ptc-runtime`](../packages/ptc-runtime/ptc-runtime), [`sandbox`](../packages/sandbox/sandbox), [`sandbox-policy`](../packages/sandbox/sandbox-policy), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`workflow`](../packages/workflow/workflow), [`working-directory`](../packages/session/working-directory) | | [`experimental-tool-agent-team`](../packages/experimental/tool-agent-team) | `experimental` | [`agent`](../packages/core/agent), [`experimental-agent-team`](../packages/experimental/agent-team), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`schedule`](../packages/schedule/schedule) | `schedule` | [`agent`](../packages/core/agent), [`api-session-controller`](../packages/api/session-controller), [`brand`](../packages/util/brand), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain), [`subagent`](../packages/subagent/subagent), [`tools`](../packages/core/tools), [`typert-protocol`](../packages/typert/protocol), [`util-time`](../packages/util/time), [`workspace`](../packages/workspace/workspace) | | [`sdk-client`](../packages/sdk/client) | `sdk` | [`llm`](../packages/llm/llm), [`sdk-protocol`](../packages/sdk/protocol), [`session`](../packages/core/session) | +| [`tool-agent-team`](../packages/team/tool-agent-team) | `team` | [`agent`](../packages/core/agent), [`agent-team`](../packages/team/agent-team), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subagent-names`](../packages/subagent/subagent-names), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | | [`client-test-runtime`](../packages/test-support/client-runtime) | `test-support` | [`api-gateway`](../packages/api/gateway), [`api-session-controller`](../packages/api/session-controller), [`api-workspace-controller`](../packages/api/workspace-controller), [`attachment`](../packages/attachment/attachment), [`client-connection`](../packages/client/connection), [`client-hmr`](../packages/client/hmr), [`client-modules`](../packages/client/modules), [`client-store`](../packages/client/store), [`client-ui-chat`](../packages/client/ui-chat), [`client-ui-context-menu`](../packages/client/ui-context-menu), [`client-ui-conversation`](../packages/client/ui-conversation), [`client-ui-renderer`](../packages/client/ui-renderer), [`client-ui-session`](../packages/client/ui-session), [`client-ui-settings`](../packages/client/ui-settings), [`client-ui-slots`](../packages/client/ui-slots), [`client-web`](../packages/client/web), [`remote-mock`](../packages/test-support/remote-mock), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`typert-protocol`](../packages/typert/protocol) | | [`subagent-dsh-sdk`](../packages/subagent/subagent-dsh-sdk) | `subagent` | [`agent`](../packages/core/agent), [`llm`](../packages/llm/llm), [`sdk-client`](../packages/sdk/client), [`session`](../packages/core/session), [`subagent`](../packages/subagent/subagent), [`subprocess`](../packages/subprocess/subprocess) | | [`tool-schedule`](../packages/schedule/tool-schedule) | `schedule` | [`llm`](../packages/llm/llm), [`schedule`](../packages/schedule/schedule), [`subagent`](../packages/subagent/subagent), [`system-prompt`](../packages/core/system-prompt), [`tools`](../packages/core/tools) | diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml index c37e4597ce..4dc41ba13e 100644 --- a/docs/persistence-catalog.i18n.yaml +++ b/docs/persistence-catalog.i18n.yaml @@ -9,8 +9,8 @@ en: 1387c4505dde2faf zh: 48570fa1c13d6910 /session-persistence-event-catalog/persistence-type-fingerprints: - en: 31c6dab0878b05a8 - zh: 176d0f7cbdc3fce3 + en: 66330e1827762689 + zh: c6585351a958ddac /session-persistence-event-catalog/event-envelope: en: 56414ffd00cdc32a zh: 6dc11eb013e95e41 @@ -225,17 +225,20 @@ en: a640800a38b3c874 zh: a640800a38b3c874 /session-persistence-event-catalog/events/team/team-member-log-only: - en: 8710c60e94d6115c - zh: ca1c923e2c43ea3c + en: de16dcf2046b402c + zh: b6c73eb866413523 /session-persistence-event-catalog/events/team/team-message-delivered-log-only: - en: 79ecdb0f03a0f310 - zh: d0db8dc2b0414fa1 + en: 9c9d9238de0eeb89 + zh: 1f52e5da217dae3d /session-persistence-event-catalog/events/team/team-message-queued-log-only: - en: 29baf1c57a73e0e6 - zh: 0275630a61028e59 + en: 184b700f0a0363ae + zh: 74b7939bf1bface1 +/session-persistence-event-catalog/events/team/team-mode-log-only: + en: 9481b9f0697707e4 + zh: 83aa2d33782a7b00 /session-persistence-event-catalog/events/team/team-task-log-only: - en: 2c0bab8d5420ed5e - zh: 79b203f7a341a43f + en: 16a961e45e7ff021 + zh: c61e51809823dc17 /session-persistence-event-catalog/events/todo: en: 308d4bb4b6b96e35 zh: 308d4bb4b6b96e35 @@ -816,8 +819,11 @@ en: 6795a23aa9bc8d2e zh: 6795a23aa9bc8d2e /session-persistence-event-catalog/resolved-persistence-types/team-message-queued: - en: d683235390472024 - zh: d683235390472024 + en: efe4678972a1a64e + zh: efe4678972a1a64e +/session-persistence-event-catalog/resolved-persistence-types/team-mode: + en: 91a387e367e07f73 + zh: 91a387e367e07f73 /session-persistence-event-catalog/resolved-persistence-types/team-task: en: 7aa9286372341561 zh: 7aa9286372341561 @@ -1248,26 +1254,26 @@ en: 94876079d6696df9 zh: d25c7ad9f5054eb7 /session-persistence-event-catalog/resolved-persistence-types/systempromptmessagesource: - en: a3eabd0e9fc565dd - zh: fa763aefdbc1bdb8 + en: 967c82dcda03d0d4 + zh: ad590611be701d97 /session-persistence-event-catalog/resolved-persistence-types/teammemberphase: - en: 562d6349db44cc77 - zh: 5a6839b217a3fa34 + en: bf78ae1a14d64469 + zh: 0d5c34aff23a133c /session-persistence-event-catalog/resolved-persistence-types/teammembersnapshot: - en: fa7208409888557c - zh: 01f948c369fd2973 + en: 1815ad87650025e6 + zh: d2a0dceabcc2090e /session-persistence-event-catalog/resolved-persistence-types/teammessagesnapshot: - en: 216b8c901ee69d3f - zh: b80bb7aa936a1b69 + en: d7ba2fe5b24c90cb + zh: b26f7e1ef0eccce0 /session-persistence-event-catalog/resolved-persistence-types/teammessagesource: - en: e0025c306befe6d3 - zh: 59bc543e134f0dbf + en: 2ca6c43fc056fd37 + zh: 3d5b5f2917f326fc /session-persistence-event-catalog/resolved-persistence-types/teamtasksnapshot: - en: fba012d93cb1a16a - zh: e3b0461d6e251a59 + en: 0c6aac89e3dcb25c + zh: d772017667ffc2a1 /session-persistence-event-catalog/resolved-persistence-types/teamtaskstatus: - en: a5fefaa709230c60 - zh: e08a6d4ef753aa1b + en: b6bd8e9986773217 + zh: ed177c2f090964af /session-persistence-event-catalog/resolved-persistence-types/textblock: en: 7ad0f4915397b511 zh: bcaa8bd449927989 @@ -1350,11 +1356,11 @@ en: 0223d7ffb67ac272 zh: c2a8221caab6d350 /session-persistence-event-catalog/resolved-persistence-types/number~2: - en: 2786fef560851bb4 - zh: 5d787475356b2606 + en: 4147b170f52814af + zh: e78991ac1c41ebec /session-persistence-event-catalog/resolved-persistence-types/string: - en: e5a7ee4569d7d428 - zh: 9c7b9d92151feb2f + en: c178efefdf01cade + zh: 200b1ab00ed8bfd3 /session-persistence-event-catalog/resolved-persistence-types/string~2: en: 0089620bf4638263 zh: 1922bef1713094bb @@ -1491,8 +1497,11 @@ en: 9748b4f0facfe9f8 zh: 83ae2cd2cd02b804 /session-persistence-event-catalog/resolved-persistence-types/displayname-providerid-subjectid-tenantid: - en: d2c98ed9b98325ae - zh: bf8c2f7967cf9cc8 + en: c6fbe4268a4430db + zh: 8fec9d6def3c775c +/session-persistence-event-catalog/resolved-persistence-types/enabled: + en: b8b8f28b5bfcd153 + zh: 602d970c3c090522 /session-persistence-event-catalog/resolved-persistence-types/end-start: en: 4b7a8793663d3fef zh: 249459c825d330c3 @@ -1905,17 +1914,17 @@ en: 3216b23c131d0070 zh: 73ca171c6014f16e /session-persistence-event-catalog/resolved-persistence-types/member-teamid-version: - en: fa8d55aef7cbc4ee - zh: aff1a32316984f09 + en: 815fc4d70ba0375b + zh: ae9f6d2a163ff62b /session-persistence-event-catalog/resolved-persistence-types/message-step-turn: en: 86fe91fe83eeb08a zh: 2ebf2f1904829753 /session-persistence-event-catalog/resolved-persistence-types/message-teamid-version: - en: a563c1f1cd1a5091 - zh: f1dc81b4b46fe088 + en: edb364403e9e9f84 + zh: d1298ec2e67a0eeb /session-persistence-event-catalog/resolved-persistence-types/messageid-targetid-teamid-version: - en: e3de1d2c12631337 - zh: 50f98d422c7f9de0 + en: 42a1e343b5e87854 + zh: ae777d3cd40835a6 /session-persistence-event-catalog/resolved-persistence-types/mode-source: en: 3028b222670ce5ad zh: cae4c9437e560d61 @@ -1947,8 +1956,8 @@ en: 5360af614fe753e3 zh: 96427c489cc71de6 /session-persistence-event-catalog/resolved-persistence-types/task-teamid-version: - en: 03858dd78e884161 - zh: 73e7da84b85be71c + en: 4952b12d354b8aaf + zh: b8c2ef51b7ab082e /session-persistence-event-catalog/resolved-persistence-types/todos: en: 8ab873d30e062fd5 zh: a138832c6e299ebb @@ -2112,8 +2121,11 @@ en: 2a00c9dbe70dcd8e zh: 96573157cd613c91 /session-persistence-event-catalog/resolved-persistence-types/type-team-message-queued: - en: 3baf69fe52f2d751 - zh: 185be1b973fac77e + en: 735e6acfb6180948 + zh: de7626c523ae57a2 +/session-persistence-event-catalog/resolved-persistence-types/type-team-mode: + en: 0b50dd6b99450d8e + zh: a9f8d2b85e2092af /session-persistence-event-catalog/resolved-persistence-types/type-team-task: en: 872aad5b748d70d5 zh: 396f986a703b76fd diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md index 8bb8ec86b0..0588d0b3d3 100644 --- a/docs/persistence-catalog.md +++ b/docs/persistence-catalog.md @@ -68,6 +68,7 @@ The [format references](persistence-changes/historical-formats/README.md) cover | `event:team/member` | event | `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` | [`{ type: "team/member" }`](#persistence-type-sha256-4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc) | | `event:team/message/delivered` | event | `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` | [`{ type: "team/message/delivered" }`](#persistence-type-sha256-48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb) | | `event:team/message/queued` | event | `21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d` | [`{ type: "team/message/queued" }`](#persistence-type-sha256-21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d) | +| `event:team/mode` | event | `920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75` | [`{ type: "team/mode" }`](#persistence-type-sha256-920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75) | | `event:team/task` | event | `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` | [`{ type: "team/task" }`](#persistence-type-sha256-d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a) | | `event:todo/write` | event | `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` | [`{ type: "todo/write" }`](#persistence-type-sha256-b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26) | | `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`{ type: "tool-workflow/agent-end" }`](#persistence-type-sha256-babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7) | @@ -1014,7 +1015,7 @@ Source: [`packages/core/session/src/types.ts:338`](../packages/core/session/src/ Types: [TeamId](subsystems/agent-team.md) · [TeamMemberSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/agent-team/src/types.ts:170`](../packages/experimental/agent-team/src/types.ts) +Source: [`packages/team/agent-team/src/types.ts:173`](../packages/team/agent-team/src/types.ts) @@ -1032,7 +1033,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:170`](../packages/experi Types: [TeamId](subsystems/agent-team.md) · [TeamMessageId](subsystems/agent-team.md) -Source: [`packages/experimental/agent-team/src/types.ts:176`](../packages/experimental/agent-team/src/types.ts) +Source: [`packages/team/agent-team/src/types.ts:179`](../packages/team/agent-team/src/types.ts) @@ -1045,7 +1046,18 @@ Source: [`packages/experimental/agent-team/src/types.ts:176`](../packages/experi Types: [TeamId](subsystems/agent-team.md) · [TeamMessageSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/agent-team/src/types.ts:174`](../packages/experimental/agent-team/src/types.ts) +Source: [`packages/team/agent-team/src/types.ts:177`](../packages/team/agent-team/src/types.ts) + + + +#### `team/mode` — log-only + +```ts persistence-catalog +/** User-selected Team mode; child Agents use the selection of their runtime root. */ +'team/mode': { enabled: boolean } +``` + +Source: [`packages/team/agent-team/src/types.ts:171`](../packages/team/agent-team/src/types.ts) @@ -1058,7 +1070,7 @@ Source: [`packages/experimental/agent-team/src/types.ts:174`](../packages/experi Types: [TeamId](subsystems/agent-team.md) · [TeamTaskSnapshot](subsystems/agent-team.md) -Source: [`packages/experimental/agent-team/src/types.ts:172`](../packages/experimental/agent-team/src/types.ts) +Source: [`packages/team/agent-team/src/types.ts:175`](../packages/team/agent-team/src/types.ts) ### `todo/*` @@ -2706,6 +2718,14 @@ SHA-256: `4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159` `"team/message/queued"` + + +### `"team/mode"` + +SHA-256: `d8a476d2c6fd30df6ec0e799f91716d1518c780d23b4121dce545abfcfb7c5a5` + +`"team/mode"` + ### `"team/task"` @@ -4956,7 +4976,7 @@ Sources: [`packages/llm/llm/src/message.ts:35`](../packages/llm/llm/src/message. - + @@ -4964,7 +4984,7 @@ Sources: [`packages/llm/llm/src/message.ts:35`](../packages/llm/llm/src/message. SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` -Sources: [`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:47`](../packages/team/agent-team/src/types.ts) One of: @@ -4974,7 +4994,7 @@ One of: - + @@ -4982,7 +5002,7 @@ One of: SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` -Sources: [`packages/experimental/agent-team/src/types.ts:50`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:50`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -4996,7 +5016,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:50`](../packages/experi - + @@ -5004,7 +5024,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:50`](../packages/experi SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e` -Sources: [`packages/experimental/agent-team/src/types.ts:107`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:108`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -5016,7 +5036,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:107`](../packages/exper - + @@ -5024,7 +5044,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:107`](../packages/exper SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` -Sources: [`packages/experimental/agent-team/src/types.ts:116`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:117`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -5036,7 +5056,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:116`](../packages/exper - + @@ -5044,7 +5064,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:116`](../packages/exper SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` -Sources: [`packages/experimental/agent-team/src/types.ts:64`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:64`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -5059,7 +5079,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:64`](../packages/experi - + @@ -5067,7 +5087,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:64`](../packages/experi SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943` -Sources: [`packages/experimental/agent-team/src/types.ts:61`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:61`](../packages/team/agent-team/src/types.ts) One of: @@ -5536,12 +5556,6 @@ Array of `number`. - - - - - - @@ -5574,6 +5588,12 @@ Array of `number`. + + + + + + @@ -5610,7 +5630,7 @@ Array of `number`. SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -Sources: [`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +Sources: [`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -6233,6 +6253,18 @@ Sources: [`packages/experimental/liveshare/src/host/viewer-principal.ts:29`](../ | `subjectId` | required | `string` | | `tenantId` | required | `string` | + + +### `{ enabled }` + +SHA-256: `c2a13242efc3bea4b36a843ddf1524ff4d33c765a71b582fa1fdbfa4fa148434` + +Sources: [`packages/team/agent-team/src/types.ts:171`](../packages/team/agent-team/src/types.ts) + +| Property | Presence | Type | +|---|---|---| +| `enabled` | required | `boolean` | + ### `{ end, start }` @@ -7857,7 +7889,7 @@ Sources: [`packages/web/web-search-deepseek/src/provider.ts:61`](../packages/web SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1` -Sources: [`packages/experimental/agent-team/src/types.ts:170`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:173`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -7885,7 +7917,7 @@ Sources: [`packages/core/session/src/types.ts:338`](../packages/core/session/src SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403` -Sources: [`packages/experimental/agent-team/src/types.ts:174`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:177`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -7899,7 +7931,7 @@ Sources: [`packages/experimental/agent-team/src/types.ts:174`](../packages/exper SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1` -Sources: [`packages/experimental/agent-team/src/types.ts:176`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:179`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8045,7 +8077,7 @@ Sources: [`packages/compaction/compaction-image-offload/src/projection.ts:25`](. SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c` -Sources: [`packages/experimental/agent-team/src/types.ts:172`](../packages/experimental/agent-team/src/types.ts) +Sources: [`packages/team/agent-team/src/types.ts:175`](../packages/team/agent-team/src/types.ts) | Property | Presence | Type | |---|---|---| @@ -8916,6 +8948,22 @@ SHA-256: `21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d` | `time` | required | `number` | | `type` | required | `"team/message/queued"` | + + + + +### `{ type: "team/mode" }` + +SHA-256: `920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75` + +| Property | Presence | Type | +|---|---|---| +| `data` | required | [`{ enabled }`](#persistence-type-sha256-c2a13242efc3bea4b36a843ddf1524ff4d33c765a71b582fa1fdbfa4fa148434) | +| `ignorable` | optional | `true` | +| `seq` | required | `number` | +| `time` | required | `number` | +| `type` | required | `"team/mode"` | + diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md index fb78e77e2a..81b83e2cd3 100644 --- a/docs/persistence-catalog.zh.md +++ b/docs/persistence-catalog.zh.md @@ -70,6 +70,7 @@ | `event:team/member` | event | `4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc` | [`{ type: "team/member" }`](#persistence-type-sha256-4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc) | | `event:team/message/delivered` | event | `48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb` | [`{ type: "team/message/delivered" }`](#persistence-type-sha256-48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb) | | `event:team/message/queued` | event | `21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d` | [`{ type: "team/message/queued" }`](#persistence-type-sha256-21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d) | +| `event:team/mode` | event | `920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75` | [`{ type: "team/mode" }`](#persistence-type-sha256-920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75) | | `event:team/task` | event | `d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a` | [`{ type: "team/task" }`](#persistence-type-sha256-d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a) | | `event:todo/write` | event | `b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26` | [`{ type: "todo/write" }`](#persistence-type-sha256-b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26) | | `event:tool-workflow/agent-end` | event | `babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7` | [`{ type: "tool-workflow/agent-end" }`](#persistence-type-sha256-babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7) | @@ -1016,7 +1017,7 @@ export type SessionEvent = { 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMemberSnapshot](subsystems/agent-team.zh.md) -来源:[`packages/experimental/agent-team/src/types.ts:170`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:173`](../packages/team/agent-team/src/types.ts) @@ -1034,7 +1035,7 @@ export type SessionEvent = { 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageId](subsystems/agent-team.zh.md) -来源:[`packages/experimental/agent-team/src/types.ts:176`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:179`](../packages/team/agent-team/src/types.ts) @@ -1047,7 +1048,18 @@ export type SessionEvent = { 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamMessageSnapshot](subsystems/agent-team.zh.md) -来源:[`packages/experimental/agent-team/src/types.ts:174`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:177`](../packages/team/agent-team/src/types.ts) + + + +#### `team/mode` — log-only + +```ts persistence-catalog +/** User-selected Team mode; child Agents use the selection of their runtime root. */ +'team/mode': { enabled: boolean } +``` + +来源:[`packages/team/agent-team/src/types.ts:171`](../packages/team/agent-team/src/types.ts) @@ -1060,7 +1072,7 @@ export type SessionEvent = { 类型:[TeamId](subsystems/agent-team.zh.md) · [TeamTaskSnapshot](subsystems/agent-team.zh.md) -来源:[`packages/experimental/agent-team/src/types.ts:172`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:175`](../packages/team/agent-team/src/types.ts) ### `todo/*` @@ -2708,6 +2720,14 @@ SHA-256: `4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159` `"team/message/queued"` + + +### `"team/mode"` + +SHA-256: `d8a476d2c6fd30df6ec0e799f91716d1518c780d23b4121dce545abfcfb7c5a5` + +`"team/mode"` + ### `"team/task"` @@ -4958,7 +4978,7 @@ SHA-256: `65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad` - + @@ -4966,7 +4986,7 @@ SHA-256: `65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad` SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` -来源:[`packages/experimental/agent-team/src/types.ts:47`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:47`](../packages/team/agent-team/src/types.ts) 以下类型之一: @@ -4976,7 +4996,7 @@ SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` - + @@ -4984,7 +5004,7 @@ SHA-256: `dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd` SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` -来源:[`packages/experimental/agent-team/src/types.ts:50`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:50`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -4998,7 +5018,7 @@ SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` - + @@ -5006,7 +5026,7 @@ SHA-256: `6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25` SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e` -来源:[`packages/experimental/agent-team/src/types.ts:107`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:108`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5018,7 +5038,7 @@ SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e` - + @@ -5026,7 +5046,7 @@ SHA-256: `2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e` SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` -来源:[`packages/experimental/agent-team/src/types.ts:116`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:117`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5038,7 +5058,7 @@ SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` - + @@ -5046,7 +5066,7 @@ SHA-256: `2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0` SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` -来源:[`packages/experimental/agent-team/src/types.ts:64`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:64`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -5061,7 +5081,7 @@ SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` - + @@ -5069,7 +5089,7 @@ SHA-256: `51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357` SHA-256: `b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943` -来源:[`packages/experimental/agent-team/src/types.ts:61`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:61`](../packages/team/agent-team/src/types.ts) 以下类型之一: @@ -5538,12 +5558,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` - - - - - - @@ -5576,6 +5590,12 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` + + + + + + @@ -5612,7 +5632,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78` SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b` -来源:[`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) +来源:[`packages/api/session-controller/src/types.ts:388`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:21`](../packages/core/session/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/team/agent-team/src/types.ts:20`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:32`](../packages/team/agent-team/src/types.ts) · [`packages/team/agent-team/src/types.ts:8`](../packages/team/agent-team/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts) `string` @@ -6235,6 +6255,18 @@ SHA-256: `0a92c5bdfa06c2e2615b777728f3aa95f408d43b9012e7b754568311251db623` | `subjectId` | 必需 | `string` | | `tenantId` | 必需 | `string` | + + +### `{ enabled }` + +SHA-256: `c2a13242efc3bea4b36a843ddf1524ff4d33c765a71b582fa1fdbfa4fa148434` + +来源:[`packages/team/agent-team/src/types.ts:171`](../packages/team/agent-team/src/types.ts) + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `enabled` | 必需 | `boolean` | + ### `{ end, start }` @@ -7859,7 +7891,7 @@ SHA-256: `9c001ecb5e1d3c3894410db2a1e383842e5f8454fb52182338686158abc1b3ee` SHA-256: `b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1` -来源:[`packages/experimental/agent-team/src/types.ts:170`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:173`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7887,7 +7919,7 @@ SHA-256: `6bc8e51124d0874bc89e1d0ceff381a9c3cd0fc240667d69f841ce4f60f65fe5` SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403` -来源:[`packages/experimental/agent-team/src/types.ts:174`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:177`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -7901,7 +7933,7 @@ SHA-256: `ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403` SHA-256: `0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1` -来源:[`packages/experimental/agent-team/src/types.ts:176`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:179`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8047,7 +8079,7 @@ SHA-256: `477614bca595a9e7adb5a1870be446b9a8684f3ba8950259d6289f2d3b74f028` SHA-256: `a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c` -来源:[`packages/experimental/agent-team/src/types.ts:172`](../packages/experimental/agent-team/src/types.ts) +来源:[`packages/team/agent-team/src/types.ts:175`](../packages/team/agent-team/src/types.ts) | 属性 | 存在性 | 类型 | |---|---|---| @@ -8918,6 +8950,22 @@ SHA-256: `21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d` | `time` | 必需 | `number` | | `type` | 必需 | `"team/message/queued"` | + + + + +### `{ type: "team/mode" }` + +SHA-256: `920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75` + +| 属性 | 存在性 | 类型 | +|---|---|---| +| `data` | 必需 | [`{ enabled }`](#persistence-type-sha256-c2a13242efc3bea4b36a843ddf1524ff4d33c765a71b582fa1fdbfa4fa148434) | +| `ignorable` | 可选 | `true` | +| `seq` | 必需 | `number` | +| `time` | 必需 | `number` | +| `type` | 必需 | `"team/mode"` | + diff --git a/docs/persistence-changes/2026-10-19-session-team-mode.i18n.yaml b/docs/persistence-changes/2026-10-19-session-team-mode.i18n.yaml new file mode 100644 index 0000000000..d69a435040 --- /dev/null +++ b/docs/persistence-changes/2026-10-19-session-team-mode.i18n.yaml @@ -0,0 +1,28 @@ +# Bilingual-pair consistency record for 2026-10-19-session-team-mode.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/persistence-changes/2026-10-19-session-team-mode.md +/: + en: 3d5a424ae7d7e46b + zh: c22a8e4a62d8cc1a +/2026-10-19-session-team-mode: + en: a882cc1bc383bf80 + zh: 33f40913b70ad97b +/2026-10-19-session-team-mode/summary: + en: 665465829d68c487 + zh: 2d5bd37335c79aad +/2026-10-19-session-team-mode/table-of-contents: + en: 6cf314a069ceb451 + zh: 9adff678e9ecab3b +/2026-10-19-session-team-mode/declaration: + en: 6907136f28e48c90 + zh: 02f8298485f3d8e7 +/2026-10-19-session-team-mode/compatibility: + en: c0ca565e19ad32a7 + zh: 548c334b7e15b464 +/2026-10-19-session-team-mode/verification: + en: 7f3a3e4b1ffb9f85 + zh: dd9258af01f40937 +/2026-10-19-session-team-mode/dev-note: + en: b75c8458928b72b9 + zh: 003ba4e17b4b5269 diff --git a/docs/persistence-changes/2026-10-19-session-team-mode.md b/docs/persistence-changes/2026-10-19-session-team-mode.md new file mode 100644 index 0000000000..ca350836a8 --- /dev/null +++ b/docs/persistence-changes/2026-10-19-session-team-mode.md @@ -0,0 +1,48 @@ +--- +description: "Records a persistence type transition and its compatibility acknowledgement." +kind: persistence-change +--- + +# 2026-10-19-session-team-mode + +English | [中文](2026-10-19-session-team-mode.zh.md) + +## Summary + +Records the root Session’s Agent Teams selection with the required team/mode event. + +## Table of Contents + +- [Declaration](#declaration) +- [Compatibility](#compatibility) +- [Verification](#verification) +- [Dev Note](#dev-note) + + +## Declaration + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-19-session-team-mode +baseline: false +changes: + - root: "event:team/mode" + previous: null + after: "920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75" + decision: same-version +``` + + +## Compatibility + +Existing V4 logs remain readable. Without a selection, historical tasks enable Team; without historical tasks it remains disabled. Readers predating team/mode refuse the unknown required discriminator, so they cannot silently restore the wrong mode. Ordinary forks ignore inherited mode selections and start disabled. No existing payload or Session header version changes. + + +## Verification + +197 focused Team, tool, model-menu and client tests passed; the Web mode and task-board scenario passed 8 tests. The authored team-session-mode headless and TypeScript SDK snapshots exercise on/off commands; Python SDK raw-event projection retains both selections. + + +## Dev Note + +None. diff --git a/docs/persistence-changes/2026-10-19-session-team-mode.schema.json b/docs/persistence-changes/2026-10-19-session-team-mode.schema.json new file mode 100644 index 0000000000..4e854f51d3 --- /dev/null +++ b/docs/persistence-changes/2026-10-19-session-team-mode.schema.json @@ -0,0 +1,15 @@ +{ + "formatVersion": 2, + "roots": [ + {"key":"event:team/mode","kind":"event","event":"team/mode","surface":false,"digest":"920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75"} + ], + "types": [], + "nodes": { + "0657d3ca5ac4e7bf": {"kind":"primitive","type":"number"}, + "920d87e22b7409bc": {"kind":"object","properties":[{"name":"data","type":"c2a13242efc3bea4","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"d8a476d2c6fd30df","optional":false}],"indices":[]}, + "aa93010d0e521ad7": {"kind":"primitive","type":"boolean"}, + "c2a13242efc3bea4": {"kind":"object","properties":[{"name":"enabled","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, + "d8a476d2c6fd30df": {"kind":"literal","value":"team/mode"}, + "dacf3d5f3e4ff619": {"kind":"literal","value":true} + } +} diff --git a/docs/persistence-changes/2026-10-19-session-team-mode.zh.md b/docs/persistence-changes/2026-10-19-session-team-mode.zh.md new file mode 100644 index 0000000000..6ea99093a6 --- /dev/null +++ b/docs/persistence-changes/2026-10-19-session-team-mode.zh.md @@ -0,0 +1,48 @@ +--- +description: "记录持久化类型更改及其兼容性确认。" +kind: persistence-change +--- + +# 2026-10-19-session-team-mode + +[English](2026-10-19-session-team-mode.md) | 中文 + +## 概述 + +通过必读的 team/mode 事件记录根 Session 的智能体团队选择。 + +## 目录 + +- [声明](#declaration) +- [兼容性](#compatibility) +- [验证](#verification) +- [开发备注](#dev-note) + + +## 声明 + +```yaml persistence-change +schemaVersion: 1 +id: 2026-10-19-session-team-mode +baseline: false +changes: + - root: "event:team/mode" + previous: null + after: "920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75" + decision: same-version +``` + + +## 兼容性 + +已有 V4 日志仍可读取。没有显式选择时,历史任务使 Team 开启;没有历史任务时保持关闭。旧读取器拒绝未知的必读 team/mode 判别值,避免静默恢复错误模式。普通分叉忽略继承的模式选择,初始保持关闭。不修改已有载荷或 Session 头部版本。 + + +## 验证 + +197 个 Team、工具、模型菜单和客户端定向测试通过;Web 开关与任务板场景的 8 个测试通过。team-session-mode 的 headless 和 TypeScript SDK 人工编写快照覆盖开关命令;Python SDK 原始事件投影保留两次选择。 + + +## 开发备注 + +无。 diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json index a03d759eb5..ee2b10f05e 100644 --- a/docs/persistence-schema.json +++ b/docs/persistence-schema.json @@ -50,6 +50,7 @@ {"key":"event:team/member","kind":"event","event":"team/member","surface":false,"digest":"4fb59762612c3e3ac3a3bd4f84c9c148d3c3893bd422ba2b201cc039fabd49bc"}, {"key":"event:team/message/delivered","kind":"event","event":"team/message/delivered","surface":false,"digest":"48f9c19417a1abbedfa59f4667bba36b93ac2db407adf5e84cb3ba0de30942cb"}, {"key":"event:team/message/queued","kind":"event","event":"team/message/queued","surface":false,"digest":"21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d"}, + {"key":"event:team/mode","kind":"event","event":"team/mode","surface":false,"digest":"920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75"}, {"key":"event:team/task","kind":"event","event":"team/task","surface":false,"digest":"d595ec73b32b016a6055333c67a5d646032b22e672da1d4a09c5b1ae398a093a"}, {"key":"event:todo/write","kind":"event","event":"todo/write","surface":false,"digest":"b978cff734e62143eb56c9125423ec275405eda969802d42aaf73ecb987d3b26"}, {"key":"event:tool-workflow/agent-end","kind":"event","event":"tool-workflow/agent-end","surface":false,"digest":"babf9ee4d1af62bf6c3a8103737f7a5e4e78ce179be835ce05a38803e15884b7"}, @@ -90,7 +91,7 @@ {"digest":"0af3ecfb8c60d416fc40a7cc4fb2c4851a8e46608b2b38ab9e510670c971aa1f","names":[],"sources":[]}, {"digest":"0b6d718c693fdf7f06dcccb0bcbbabe78cc63514eafc62d6b2a3899735279d6f","names":["packages/goal/goal/src/types.ts#GoalRef"],"sources":["packages/goal/goal/src/types.ts:20"]}, {"digest":"0b9b8facbe2c19773b3fb0ecc824ccbcc603d41b8f18069faa3807d449572fce","names":[],"sources":[]}, - {"digest":"0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1","names":[],"sources":["packages/experimental/agent-team/src/types.ts:176"]}, + {"digest":"0bcfaf5566d271028429ac7b562cb59053c9d78b8ae14708c35fac835108d1b1","names":[],"sources":["packages/team/agent-team/src/types.ts:179"]}, {"digest":"0bdaf9cb63594be4ef034b68901fffe792fe09d6d5f6bac42d7f56f6e7834fb9","names":[],"sources":[]}, {"digest":"0c4205180f75b9ee1e0d878bd9a9851628b5dc18448486389d9c5fdc3f1ccda3","names":[],"sources":[]}, {"digest":"0c556c9b9f2a96238a6b9bbb99e748206cbff2ed6203041c9571f04ae5802f52","names":[],"sources":[]}, @@ -153,7 +154,7 @@ {"digest":"1fdad624334d354820dfb3a26e28d0bc9fc1b62a33f87bab63f715b3f4391bfd","names":[],"sources":[]}, {"digest":"20a57544bbf204dacd1ae6beb2ab29444365c3c869f2afdf489dcdcbc3d9c8c5","names":[],"sources":["packages/plan/plan-mode/src/index.ts:53"]}, {"digest":"2142e391b9ccb60d487afcf373ce74db98d952f69e8289eddb352a7e16e996c5","names":[],"sources":[]}, - {"digest":"2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0","names":["packages/experimental/agent-team/src/types.ts#TeamMessageSource"],"sources":["packages/experimental/agent-team/src/types.ts:116"]}, + {"digest":"2152caf1a71e588e4df2358a806dea24936c1c47131134f01a12939d12cdc0c0","names":["packages/team/agent-team/src/types.ts#TeamMessageSource"],"sources":["packages/team/agent-team/src/types.ts:117"]}, {"digest":"21fb6a90d5068f6a0003b7ab316ed2f56342477146a65c00db0f13c4d8df667d","names":[],"sources":[]}, {"digest":"2235d957bf4b7c6cf959231d480f0ba1f3a6d72d82981493fb6c7c963021d3d5","names":[],"sources":["packages/session/working-directory/src/index.ts:29"]}, {"digest":"224aa11d4d5f88c450822d01b99e6ca5e3a9c7f26a5ede10bae33384a09ef4dc","names":[],"sources":[]}, @@ -184,7 +185,7 @@ {"digest":"2e3523541e942ef6cc9fbc5b91f242e0ca4c6ada479b79b96025b797f6e41f3f","names":["packages/schedule/schedule/src/types.ts#ScheduleChange"],"sources":["packages/schedule/schedule/src/types.ts:232"]}, {"digest":"2e4debca12f2c449f96dc6574f3fbf2fb346193443114a241d01a236362ec48c","names":[],"sources":[]}, {"digest":"2e505098f9209e52eebd577f1a6f9d4e773ec4d78987aff28a7c786442b9f54c","names":[],"sources":[]}, - {"digest":"2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e","names":["packages/experimental/agent-team/src/types.ts#TeamMessageSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:107"]}, + {"digest":"2eafefdc6f79a4922d68a787a09eaf4dd2b2369c3777fba4c05e800b5678ac8e","names":["packages/team/agent-team/src/types.ts#TeamMessageSnapshot"],"sources":["packages/team/agent-team/src/types.ts:108"]}, {"digest":"2f1e6a7ebace43d727ae138ddcd352eb3813960009e5805b8c5ac84300c97716","names":["packages/attachment/attachment/src/types.ts#ImageMediaType"],"sources":["packages/attachment/attachment/src/types.ts:8"]}, {"digest":"2f3f201c8846e19c20bb75c02280e3d4eb3c57509e4715f0c0a4d1d4f91a7a06","names":[],"sources":[]}, {"digest":"2fe5026daf14bd4ff2b7bfb7cf9791c302d9cbba06802178b6dfec00b892698f","names":["packages/core/tool-executor-ptc/src/types.ts#PtcDispatchStartEventData"],"sources":["packages/core/tool-executor-ptc/src/types.ts:12"]}, @@ -252,7 +253,7 @@ {"digest":"4b259f7f8f0a44ad5e90b70a73553ced1df27012d126921d397ff403b6c86731","names":["packages/llm/llm/src/message.ts#ToolResultMessage"],"sources":["packages/llm/llm/src/message.ts:173"]}, {"digest":"4b7ddaf3710b88ff9b8df66304f5f97bf03688fd19730eedc2e9535203c820ea","names":[],"sources":[]}, {"digest":"4bb451330ff699f36b45539ceb0eb2606ead72084e2c5bb637c36cc80e2252db","names":[],"sources":["packages/core/session/src/types.ts:198"]}, - {"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/agent-team/src/types.ts#TeamId","packages/experimental/agent-team/src/types.ts#TeamMessageId","packages/experimental/agent-team/src/types.ts#TeamTaskId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:388","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/agent-team/src/types.ts:20","packages/experimental/agent-team/src/types.ts:32","packages/experimental/agent-team/src/types.ts:8","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/workflow/workflow/src/types.ts:13"]}, + {"digest":"4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b","names":["packages/api/session-controller/src/types.ts#SessionRequestId","packages/attachment/attachment/src/brand.ts#AttachmentId","packages/compaction/compaction/src/brand.ts#CompactionId","packages/core/session/src/types.ts#SessionId","packages/experimental/webhook/src/brand.ts#WebhookDeliveryId","packages/experimental/webhook/src/brand.ts#WebhookRuleId","packages/experimental/webhook/src/brand.ts#WebhookSourceId","packages/feedback/message-feedback/src/types.ts#MessageFeedbackVersion","packages/goal/goal/src/types.ts#GoalId","packages/interaction/commands/src/brand.ts#CommandId","packages/interaction/user-approval/src/types.ts#ApprovalRequestId","packages/jobs/jobs/src/brand.ts#JobId","packages/llm/llm-retry/src/brand.ts#RetryId","packages/llm/llm/src/brand.ts#MessageId","packages/llm/llm/src/brand.ts#ProviderRequestId","packages/llm/llm/src/brand.ts#ReasoningEffortId","packages/llm/llm/src/brand.ts#ToolCallId","packages/schedule/schedule/src/types.ts#ScheduleId","packages/session/session-title/src/index.ts#SessionTitleProviderId","packages/session/session-title/src/types.ts#SessionTitleProviderId","packages/team/agent-team/src/types.ts#TeamId","packages/team/agent-team/src/types.ts#TeamMessageId","packages/team/agent-team/src/types.ts#TeamTaskId","packages/workflow/workflow/src/types.ts#WorkflowRunId"],"sources":["packages/api/session-controller/src/types.ts:388","packages/attachment/attachment/src/brand.ts:6","packages/compaction/compaction/src/brand.ts:4","packages/core/session/src/types.ts:21","packages/experimental/webhook/src/brand.ts:12","packages/experimental/webhook/src/brand.ts:6","packages/experimental/webhook/src/brand.ts:9","packages/feedback/message-feedback/src/types.ts:14","packages/goal/goal/src/types.ts:17","packages/interaction/commands/src/brand.ts:31","packages/interaction/user-approval/src/types.ts:17","packages/jobs/jobs/src/brand.ts:19","packages/llm/llm-retry/src/brand.ts:4","packages/llm/llm/src/brand.ts:16","packages/llm/llm/src/brand.ts:31","packages/llm/llm/src/brand.ts:43","packages/llm/llm/src/brand.ts:67","packages/schedule/schedule/src/types.ts:14","packages/session/session-title/src/index.ts:42","packages/session/session-title/src/types.ts:17","packages/team/agent-team/src/types.ts:20","packages/team/agent-team/src/types.ts:32","packages/team/agent-team/src/types.ts:8","packages/workflow/workflow/src/types.ts:13"]}, {"digest":"4d60dddaa65d3da8d4cd014d85ce3d2c2875664dd0433e331d8e9d395289686b","names":["packages/experimental/liveshare/src/host/viewer-principal.ts#ShareViewerPrincipal"],"sources":["packages/experimental/liveshare/src/host/viewer-principal.ts:24"]}, {"digest":"4e011753f8263f436efaabfbb3b4db53a98fc55342c5ded2daf7208b217be159","names":[],"sources":[]}, {"digest":"4e44c9118152f6829634bfd508e47669ab0497caffa380c94b4be91f9390d35d","names":[],"sources":[]}, @@ -265,7 +266,7 @@ {"digest":"50cf02d875f8a1f1411f4a6fbcffab9120ccef5f74a47331fbf29480e5f68f48","names":[],"sources":[]}, {"digest":"511801b0a6470d98566e8385d3dffddb2b67f79fcf73ed6056f46c3cc0a245a7","names":[],"sources":[]}, {"digest":"516da4cdd6d2f1e5ce488e648578ca51f40e458f707b803e5b24de870e799415","names":[],"sources":[]}, - {"digest":"51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357","names":["packages/experimental/agent-team/src/types.ts#TeamTaskSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:64"]}, + {"digest":"51edec100659da3a9bd9c3f6ba1180f5e64ec9a1585e5057080df45c180bd357","names":["packages/team/agent-team/src/types.ts#TeamTaskSnapshot"],"sources":["packages/team/agent-team/src/types.ts:64"]}, {"digest":"525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336","names":[],"sources":[]}, {"digest":"529e0ccd0e34079ab4ff21ed87f810cee0388246263bf22e8aba5986b56fae05","names":[],"sources":["packages/util/values/src/index.ts:4"]}, {"digest":"52c6113cb0e53f345324bf84f01928bea5d5df57ee80bc0dd65407b8bc5a3734","names":[],"sources":[]}, @@ -316,7 +317,7 @@ {"digest":"62ecb62dc0fe1b5e70217f8a80c39256a40ee3a1917be14e5ca899d20c35f7fd","names":[],"sources":[]}, {"digest":"6487b10ba7542f3c447b1e41a1c6a590b7bfafe0b9b66b49f34028da85bb970f","names":["packages/subagent/subagent/src/descriptor.ts#ContinuableSubagentDescriptorData"],"sources":["packages/subagent/subagent/src/descriptor.ts:72"]}, {"digest":"64ef542b1bfc988382d337f2ea85aab8ad76ea55341cf082163919d315688428","names":["packages/llm/llm/src/message.ts#DeveloperMessage"],"sources":["packages/llm/llm/src/message.ts:157"]}, - {"digest":"6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25","names":["packages/experimental/agent-team/src/types.ts#TeamMemberSnapshot"],"sources":["packages/experimental/agent-team/src/types.ts:50"]}, + {"digest":"6598cd6da253948d6b207c6374a45335804c79b576866d6215649c2d1bc85e25","names":["packages/team/agent-team/src/types.ts#TeamMemberSnapshot"],"sources":["packages/team/agent-team/src/types.ts:50"]}, {"digest":"65a8400e451e667e5b6e34df72ceb81fca8c12330330fa72083ad40279b189ad","names":["packages/llm/llm/src/message.ts#SystemPromptMessageSource"],"sources":["packages/llm/llm/src/message.ts:35"]}, {"digest":"65d583e66518c5236015e0846470fb32efcf92375ed306aef14e50a9d2cd2099","names":["packages/skill/tool-skill/src/index.ts#SkillCatalogSource"],"sources":["packages/skill/tool-skill/src/index.ts:35"]}, {"digest":"66ba70f6f068da443f969a0b947a013c3addd776b9b60a0a393759c19825e9a2","names":["packages/llm/llm/src/types.ts#ToolSchema"],"sources":["packages/llm/llm/src/types.ts:517"]}, @@ -425,6 +426,7 @@ {"digest":"8f6d609794bf5afc01d7ccf3e03811d32f4b8047f58a2c9a98db0154c2ad7cb9","names":[],"sources":[]}, {"digest":"8fcb0b44a575e998665c053643d1ab04c82f39253ebd190ac0c975b78e40b543","names":[],"sources":["packages/core/tools/src/index.ts:28"]}, {"digest":"904bf499d98be4a3a5f836fcac533b6c4899569b018071e6604e1d32657d00fd","names":[],"sources":[]}, + {"digest":"920d87e22b7409bcc7224f8b1dc7f5101c33574c75627c65baab12b85cc96e75","names":[],"sources":[]}, {"digest":"930c1cd289def119d443e19dd90008916bf035f0a4d23e07241e7f050f2b89b7","names":["packages/interaction/user-approval/src/types.ts#ApprovalOutcome"],"sources":["packages/interaction/user-approval/src/types.ts:32"]}, {"digest":"935c8d486292b13c87b57ca280eb8c58b5b2f5239be86fcd4334c33fdeb87682","names":[],"sources":[]}, {"digest":"938f7c7357bc403ea3b281a9dd016939df7a0880fc8db12612585be574d0d615","names":[],"sources":[]}, @@ -471,7 +473,7 @@ {"digest":"a3a259a1aa5401af786ce08c4a8419681d3000d73467991b5fc563389bc4ebd2","names":[],"sources":[]}, {"digest":"a3c6f1ea38e9a61d7a9706057ba65aabe02dfe6d9dd90d4736aa62325d874531","names":[],"sources":[]}, {"digest":"a3edd1efd1fef21b72c55e213c8a8b24196b091abde2b89db52be0b2aaae3cfa","names":[],"sources":[]}, - {"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/experimental/agent-team/src/types.ts:172"]}, + {"digest":"a40d12070f6f4a124f32fb3cc86e7857554702e32f0eda3080a25c4b9ac9b18c","names":[],"sources":["packages/team/agent-team/src/types.ts:175"]}, {"digest":"a4afec59b75aad5d13ef82d6931f35cc2d98918503916cf069355b77c5e42302","names":[],"sources":["packages/api/session-controller/src/types.ts:397"]}, {"digest":"a6567ccb2e530606b775371eb4fa31468d72084339968e8b0440a516a23b39dc","names":[],"sources":[]}, {"digest":"a6b41f5bede8094d7bf635be5d69b1bd7a25e006ca9cf9415bceb9322f1a7feb","names":["packages/experimental/hook-protocol/src/types.ts#HookDialect"],"sources":["packages/experimental/hook-protocol/src/types.ts:48"]}, @@ -510,12 +512,12 @@ {"digest":"b0b0eb1b16490f6bee135b2377e8f8c39227974c799ad7d49ff418af23ecbc35","names":["packages/attachment/attachment/src/types.ts#ImageAttachmentRef"],"sources":["packages/attachment/attachment/src/types.ts:11"]}, {"digest":"b158f3ff6923714807c0d73882f0b05dc87f8fa85c9e90814852d48bbc866081","names":["packages/core/session/src/types.ts#RequestContext"],"sources":["packages/core/session/src/types.ts:260"]}, {"digest":"b1664fa89f8eab49a14e7e3168d8bc4da4aa9fd76332f6c4a053d839e5276ae7","names":[],"sources":[]}, - {"digest":"b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1","names":[],"sources":["packages/experimental/agent-team/src/types.ts:170"]}, + {"digest":"b1ebd5cda47d90d980bb05bffc3f970c51537b9aff15b3bc3d639811727741b1","names":[],"sources":["packages/team/agent-team/src/types.ts:173"]}, {"digest":"b1f62380f6746fedd629e71065e3583cef83334875b499ac9665c2eb87acfbf5","names":[],"sources":[]}, {"digest":"b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e","names":[],"sources":[]}, {"digest":"b2245a906e13aa94f2d3cc6cf7b04cfa0267f7b7f4d468de1b00b1a314e54626","names":[],"sources":[]}, {"digest":"b2a5c8c008a31e3dc6c3013502f2356aca13894b31c8e70601fa92f2d40d1dba","names":[],"sources":[]}, - {"digest":"b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943","names":["packages/experimental/agent-team/src/types.ts#TeamTaskStatus"],"sources":["packages/experimental/agent-team/src/types.ts:61"]}, + {"digest":"b2d2a01e51341a3bfec809198952c8c830a7899f4cb6ecd3c1dc0b5d9a2af943","names":["packages/team/agent-team/src/types.ts#TeamTaskStatus"],"sources":["packages/team/agent-team/src/types.ts:61"]}, {"digest":"b3681c026a0b98e98b103f057dfd217fbe904129c090efd668253ec9aea7fece","names":[],"sources":[]}, {"digest":"b38f206d59bad891fa58c89f97a0036987b3defe7685acbd318fa55eb85f9751","names":[],"sources":[]}, {"digest":"b4ec0af39fd52ed971990e42f04e5be3204882e555c409eb330f8238eec21569","names":[],"sources":[]}, @@ -558,6 +560,7 @@ {"digest":"c1f0f17b6493b85db6453bb30e01b208d703731b25bc1e68e5d4fc72eea9cdea","names":["packages/llm/llm/src/types.ts#ToolCallBlock"],"sources":["packages/llm/llm/src/types.ts:112"]}, {"digest":"c1f9e0405de6d18cabb9ee70782a027f9bbdc57e5abec9dcccdd56119e2e9058","names":[],"sources":[]}, {"digest":"c219efbdfd53b836786202ab4532913d555798f0a8615a4e9629736e3799fb70","names":[],"sources":[]}, + {"digest":"c2a13242efc3bea4b36a843ddf1524ff4d33c765a71b582fa1fdbfa4fa148434","names":[],"sources":["packages/team/agent-team/src/types.ts:171"]}, {"digest":"c2deb7c9183735d4e0fffddf5c623f4e34daa1c8e96b111982e957dfcbbcf896","names":["packages/workflow/workflow/src/types.ts#WorkflowAgentOutcome"],"sources":["packages/workflow/workflow/src/types.ts:110"]}, {"digest":"c2f49004d9ae38a964e80850c87bc6849d6a872091a1d43e99eedf00c641e2a3","names":[],"sources":[]}, {"digest":"c43b119ce579b3e9983a0d3adc4ba402ebb9ba773d89738a2dab01ecf5813943","names":[],"sources":[]}, @@ -593,6 +596,7 @@ {"digest":"d66e11dddf5dfa3c71547e2cbd7b0608824cd8d6243fc16f532017ff28e8fba5","names":[],"sources":[]}, {"digest":"d6e654b88b584fcdf290a56802b1730bc7b9847ae86baa4265ab7769eb48990c","names":[],"sources":[]}, {"digest":"d85590a5675c6e2fd8164e5792a6cceac714605b8ac228adae5ee668facb1490","names":[],"sources":[]}, + {"digest":"d8a476d2c6fd30df6ec0e799f91716d1518c780d23b4121dce545abfcfb7c5a5","names":[],"sources":[]}, {"digest":"d9196086e41e4dd5c2ce87f5c25529c8112a1731d258cf0728747d41a9ee3241","names":[],"sources":[]}, {"digest":"d9c462b9f62fdd3efc76a44b9524dc8781f6b91cd9c7e584b17d971defafebab","names":["packages/llm/llm/src/message.ts#ToolMessageSource"],"sources":["packages/llm/llm/src/message.ts:29"]}, {"digest":"d9edf1862e4471d6ad8a106a3f4160e0643874368707d50ecd8e27e5ee7a2567","names":[],"sources":[]}, @@ -614,7 +618,7 @@ {"digest":"deebfc7c62177ea840f23c6362b8615bc78ffa93377dd5d243c04e541c413af5","names":[],"sources":["packages/typert/protocol/src/types.ts:457"]}, {"digest":"df5d9709b7db2a72510dc52052a23f0c38f39730af1504bab8bccabc14fae528","names":[],"sources":[]}, {"digest":"df843efd87c78cf8de98f8e0d0216956cd568d0d0e56a22c853663e274aa1a18","names":[],"sources":[]}, - {"digest":"dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd","names":["packages/experimental/agent-team/src/types.ts#TeamMemberPhase"],"sources":["packages/experimental/agent-team/src/types.ts:47"]}, + {"digest":"dfce5db4aa42b7ebe0bb79eca92553587287e18c06b9eba85cbfb9d332e26acd","names":["packages/team/agent-team/src/types.ts#TeamMemberPhase"],"sources":["packages/team/agent-team/src/types.ts:47"]}, {"digest":"e0a787e6ec76c7c94fecbc501b489164ab0293db05bc947914077ad01e674f05","names":[],"sources":[]}, {"digest":"e2ad8f5bcd813f268ffef1b10a12d7ddd2b8ced8b9b6d0275b3f6b7ed094a187","names":[],"sources":[]}, {"digest":"e2d0367e5716bc188435e51718ebff8ce873190625fa93b1c5331102d98758fa","names":[],"sources":[]}, @@ -640,7 +644,7 @@ {"digest":"e7f4cbb3a57f4cbdb61ea41e95bcba626ad1fff6bb9b1e063b1d50a32da6e864","names":[],"sources":[]}, {"digest":"e8de0f457fed5b9ff94dc4950b59ef75fd8ba8af665cc7899485924a62653367","names":[],"sources":[]}, {"digest":"e95f957154b2b1b58692af53d4131180b6dd19e2264869f876dbaa959991494e","names":[],"sources":[]}, - {"digest":"ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403","names":[],"sources":["packages/experimental/agent-team/src/types.ts:174"]}, + {"digest":"ea08ec1df0c33108fd03345b9af199cf04dea450aa045d3c683f216311403403","names":[],"sources":["packages/team/agent-team/src/types.ts:177"]}, {"digest":"eaa71be0fbf0f702df8ea6c11d0aa2f9782263d45a10d0fb14eda662f4bfe04c","names":[],"sources":["packages/core/session/src/types.ts:201"]}, {"digest":"ebb768d85ec87fb68e42fc14fd7d8535b5ac11791b14d04c6770180f0a719bf8","names":[],"sources":[]}, {"digest":"ec38b5949af8eacaf00df002f4acbe344f934f8a061e9cdc65a52a48e5f6dd93","names":[],"sources":[]}, @@ -1053,6 +1057,7 @@ "8f6d609794bf5afc": {"kind":"tuple","elements":[{"type":"4e88776858652856","optional":false,"rest":false}]}, "8fcb0b44a575e998": {"kind":"object","properties":[{"name":"kind","type":"a720757f704a2512","optional":false}],"indices":[]}, "904bf499d98be4a3": {"kind":"union","types":["7382c4fc95fe8c5d","b765ee31033cea46","f6cb462f808309e3"]}, + "920d87e22b7409bc": {"kind":"object","properties":[{"name":"data","type":"c2a13242efc3bea4","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"d8a476d2c6fd30df","optional":false}],"indices":[]}, "930c1cd289def119": {"kind":"union","types":["2142e391b9ccb60d","2c4c21f19f426a58","811229eedcec5797","fab35c4520883c0d"]}, "935c8d486292b13c": {"kind":"literal","value":"web_search"}, "938f7c7357bc403e": {"kind":"primitive","type":"null"}, @@ -1186,6 +1191,7 @@ "c1f0f17b6493b85d": {"kind":"object","properties":[{"name":"arguments","type":"4cd48f3e5108bd6e","optional":false},{"name":"id","type":"4cd48f3e5108bd6e","optional":false},{"name":"name","type":"4cd48f3e5108bd6e","optional":false},{"name":"type","type":"1f0133217eeeffd8","optional":false}],"indices":[]}, "c1f9e0405de6d18c": {"kind":"object","properties":[{"name":"data","type":"a6da9ad5d42ae83a","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"cdca80b8dad24d4e","optional":false}],"indices":[]}, "c219efbdfd53b836": {"kind":"literal","value":"step/end"}, + "c2a13242efc3bea4": {"kind":"object","properties":[{"name":"enabled","type":"aa93010d0e521ad7","optional":false}],"indices":[]}, "c2deb7c9183735d4": {"kind":"union","types":["1ebc6f9ff3aa9e3a","2142e391b9ccb60d","b57a2889408de36e"]}, "c2f49004d9ae38a9": {"kind":"union","types":["1ebc6f9ff3aa9e3a","b57a2889408de36e","ef226a90ddac6cd8"]}, "c43b119ce579b3e9": {"kind":"literal","value":"paused"}, @@ -1221,6 +1227,7 @@ "d66e11dddf5dfa3c": {"kind":"object","properties":[{"name":"data","type":"cef171adf021a186","optional":false},{"name":"ignorable","type":"dacf3d5f3e4ff619","optional":true},{"name":"seq","type":"0657d3ca5ac4e7bf","optional":false},{"name":"time","type":"0657d3ca5ac4e7bf","optional":false},{"name":"type","type":"fb5e0517050d2c6d","optional":false}],"indices":[]}, "d6e654b88b584fcd": {"kind":"literal","value":"fallback"}, "d85590a5675c6e2f": {"kind":"object","properties":[{"name":"form","type":"aec150797b655c5f","optional":false},{"name":"kind","type":"1f1385dbc3e875c0","optional":false}],"indices":[]}, + "d8a476d2c6fd30df": {"kind":"literal","value":"team/mode"}, "d9196086e41e4dd5": {"kind":"literal","value":"complete"}, "d9c462b9f62fdd3e": {"kind":"object","properties":[{"name":"callId","type":"4cd48f3e5108bd6e","optional":false},{"name":"kind","type":"a13d8f379222ece8","optional":false}],"indices":[]}, "d9edf1862e4471d6": {"kind":"literal","value":"schedule"}, diff --git a/docs/subsystems/agent-team.i18n.yaml b/docs/subsystems/agent-team.i18n.yaml index b39898a2de..2423ffd1c9 100644 --- a/docs/subsystems/agent-team.i18n.yaml +++ b/docs/subsystems/agent-team.i18n.yaml @@ -3,8 +3,8 @@ # After editing either side, bring the other along and re-record with: # pnpm run verify-translation-pairing --write docs/subsystems/agent-team.md /agent-teams: - en: e88336ccc7a42f3a - zh: 9a5c1381d3ca2559 + en: 152195aeb3b5dea1 + zh: 1cf6b3b36bd6f02c /agent-teams/identity-and-historical-roster: en: 3a574287e378ecf9 zh: 818667289271c1af @@ -15,8 +15,8 @@ en: c70f0fd199fc7db5 zh: c6b77cb5a5cfd1c2 /agent-teams/web-projection: - en: 3fce926f1d135d5a - zh: a835885262792d89 + en: fcee011d13daf409 + zh: 47000c16b3517291 /agent-teams/replay: - en: 9a3cc5b173014a3b - zh: 531632d2a7eefe94 + en: 448d32856f87b067 + zh: 7fafd96314c7e2c5 diff --git a/docs/subsystems/agent-team.md b/docs/subsystems/agent-team.md index 1bd3155216..3bfc64d559 100644 --- a/docs/subsystems/agent-team.md +++ b/docs/subsystems/agent-team.md @@ -2,7 +2,7 @@ English | [中文](agent-team.zh.md) -Types shared by the experimental task board, model tools, and host adapters. The [task-board wait decision](../../.agents/notes/implemented/architecture/2026-10-19-team-task-board-inbox.md) owns coordination semantics; this page records durable and client-visible forms from [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts). +Types shared by the shared task board, model tools, and host adapters. The [task-board wait decision](../../.agents/notes/implemented/architecture/2026-10-19-team-task-board-inbox.md) owns coordination semantics; this page records durable and client-visible forms from [`packages/team/agent-team/src/types.ts`](../../packages/team/agent-team/src/types.ts). ## Identity and historical roster @@ -75,7 +75,9 @@ interface TeamTaskSnapshot { ## Web projection -The root Session publishes non-deleted task views through `SessionProjectionMap.agentTeam`. `failure` reports a rejected persisted Team record beside the last valid state. Historical member fields remain in persisted projection state but are omitted from the browser view. The task panel labels the root as Lead and other owners from catalog labels, Session titles, or ids. +`agentTeam.enabled` exposes the root Session’s saved selection. New Sessions default to false; `team/mode` records explicit selections. Historical tasks imply enabled until an explicit selection. Only the root can call `setEnabled`; descendants share that mode. The Web menu calls the same method through `remote.agentTeams.setEnabled`, with the root Session id resolved to its live Agent by the Gateway. Mode writes take effect before awaiting persistence. Tools resolve availability at step preparation; prompt providers read the selection when evaluated. Task mutations check the current mode when their queued operation starts; admitted mutations may finish. Mode changes do not end inbox waits. A failed flush leaves the appended selection active; retrying the same selection flushes it without a duplicate event. + +The root Session publishes non-deleted task views through `SessionProjectionMap.agentTeam`. `failure` reports a rejected persisted task or mode record beside the last valid state. Historical roster and mailbox records are not projected. The task panel labels the root as Lead and other owners from catalog labels, Session titles, or ids. ```ts type-equiv /** Runtime-enriched task view returned to tools and hosts. */ @@ -100,6 +102,7 @@ interface TeamTaskView { * record; tasks then stay at the last valid state. */ interface TeamProjection { + readonly enabled: boolean readonly tasks: TeamTaskView[] readonly failure?: string } @@ -107,7 +110,7 @@ interface TeamProjection { ## Replay -The `agentTeam` projection replays historical roster, task, and mailbox records selected by `TeamId`. Subagent discovery owns current tree membership and ignores inherited catalog authority. Session event `seq` and `time` own ordering and timing. Committed durable generations are preserved. The [package README](../../packages/experimental/agent-team/README.md) defines task operations and limits. +The `agentTeam` projection replays task records selected by `TeamId` and the Session’s own mode selections. Subagent discovery owns current tree membership and ignores inherited catalog authority. Session event `seq` and `time` own ordering and timing. Committed durable generations are preserved. The [package README](../../packages/team/agent-team/README.md) defines task operations and limits. @@ -124,6 +127,27 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp Agent Teams service backed by the exact live Lead Session log. ```ts cordis-catalog +/** + * Read the Team selection shared by the caller's subagent tree. + * @param caller - exact live root or descendant. + * @returns whether Team tools and guidance are enabled. + */ +isEnabled(caller: Agent): boolean + +/** + * Save a root Session's Team selection without stopping children or deleting tasks. + * The selection takes effect before awaiting persistence. Tools resolve availability at + * step preparation; prompt providers read the selection when evaluated. Task mutations + * check the current mode when their queued operation starts; admitted operations finish. + * @param agent - exact live root whose tree receives the selection. + * @param enabled - selected Team mode. + * @param signal - cancellation checked before changing the selection. + * @returns fulfillment after the selection flushes. + * @throws when called by a descendant or persistence fails. An appended selection remains active; + * retrying the same selection flushes without a duplicate event. + */ +@Remote async setEnabled(agent: Agent, enabled: boolean, signal?: AbortSignal): Promise + /** * Create one unowned pending task in the Team Lead log. * @param caller - exact live Team member creating the task. @@ -162,5 +186,5 @@ async updateTask(caller: Agent, request: UpdateTeamTaskRequest, signal?: AbortSi Types: [Agent](core.md) -Source: [`packages/experimental/agent-team/src/index.ts`](../../packages/experimental/agent-team/src/index.ts) +Source: [`packages/team/agent-team/src/index.ts`](../../packages/team/agent-team/src/index.ts) diff --git a/docs/subsystems/agent-team.zh.md b/docs/subsystems/agent-team.zh.md index 0d11c9617c..2375162b69 100644 --- a/docs/subsystems/agent-team.zh.md +++ b/docs/subsystems/agent-team.zh.md @@ -2,7 +2,7 @@ [English](agent-team.md) | 中文 -实验性任务板、模型工具和 host 适配器共享的类型。[任务板等待决策](../../.agents/notes/implemented/architecture/2026-10-19-team-task-board-inbox.zh.md)负责协调语义;本页记录 [`packages/experimental/agent-team/src/types.ts`](../../packages/experimental/agent-team/src/types.ts) 中的持久化和客户端可见形式。 +共享任务板、模型工具和 host 适配器共享的类型。[任务板等待决策](../../.agents/notes/implemented/architecture/2026-10-19-team-task-board-inbox.zh.md)负责协调语义;本页记录 [`packages/team/agent-team/src/types.ts`](../../packages/team/agent-team/src/types.ts) 中的持久化和客户端可见形式。 ## 身份与历史名册 @@ -75,7 +75,9 @@ interface TeamTaskSnapshot { ## Web 投影 -根 Session 通过 `SessionProjectionMap.agentTeam` 发布未删除任务视图。`failure` 在最后有效状态旁报告被拒绝的持久化 Team 记录。历史成员字段保留在持久化投影状态中,但不发送到浏览器。任务面板将根标记为主代理,其他负责人依次使用目录标签、Session 标题或 id。 +`agentTeam.enabled` 提供根 Session 保存的选择。新 Session 默认为 false;`team/mode` 记录显式选择。历史任务在显式选择前视为开启。仅根可调用 `setEnabled`;后代共享该模式。Web 菜单通过 `remote.agentTeams.setEnabled` 调用同一方法,Gateway 将根 Session id 解析为其存活 Agent。模式写入先立即生效,再等待刷盘。工具在步骤准备时确定可用性;提示词提供方在求值时读取选择。任务修改在排队操作开始时检查当前模式;已获准的修改可继续完成。模式变更不会结束收件箱等待。刷盘失败时已追加的选择仍然生效;重试相同选择会再次刷盘,不重复追加事件。 + +根 Session 通过 `SessionProjectionMap.agentTeam` 发布未删除任务视图。`failure` 在最后有效状态旁报告被拒绝的持久化任务或模式记录。历史名册与邮箱记录不参与投影。任务面板将根标记为主代理,其他负责人依次使用目录标签、Session 标题或 id。 ```ts type-equiv /** Runtime-enriched task view returned to tools and hosts. */ @@ -100,6 +102,7 @@ interface TeamTaskView { * record; tasks then stay at the last valid state. */ interface TeamProjection { + readonly enabled: boolean readonly tasks: TeamTaskView[] readonly failure?: string } @@ -107,7 +110,7 @@ interface TeamProjection { ## 回放 -`agentTeam` 投影按 `TeamId` 重放历史名册、任务和邮箱记录。subagent 发现负责当前树成员关系,不将继承目录作为权限来源。Session 事件的 `seq` 和 `time` 负责排序和时间。已提交的持久化代次保持不变。[包 README](../../packages/experimental/agent-team/README.zh.md)定义任务操作和限制。 +`agentTeam` 投影按 `TeamId` 重放任务记录,并重放 Session 自身的模式选择。subagent 发现负责当前树成员关系,不将继承目录作为权限来源。Session 事件的 `seq` 和 `time` 负责排序和时间。已提交的持久化代次保持不变。[包 README](../../packages/team/agent-team/README.zh.md)定义任务操作和限制。 @@ -124,6 +127,27 @@ Generated from source by `scripts/gen-cordis-catalog.ts` (verified fresh by `pnp Agent Teams service backed by the exact live Lead Session log. ```ts cordis-catalog +/** + * Read the Team selection shared by the caller's subagent tree. + * @param caller - exact live root or descendant. + * @returns whether Team tools and guidance are enabled. + */ +isEnabled(caller: Agent): boolean + +/** + * Save a root Session's Team selection without stopping children or deleting tasks. + * The selection takes effect before awaiting persistence. Tools resolve availability at + * step preparation; prompt providers read the selection when evaluated. Task mutations + * check the current mode when their queued operation starts; admitted operations finish. + * @param agent - exact live root whose tree receives the selection. + * @param enabled - selected Team mode. + * @param signal - cancellation checked before changing the selection. + * @returns fulfillment after the selection flushes. + * @throws when called by a descendant or persistence fails. An appended selection remains active; + * retrying the same selection flushes without a duplicate event. + */ +@Remote async setEnabled(agent: Agent, enabled: boolean, signal?: AbortSignal): Promise + /** * Create one unowned pending task in the Team Lead log. * @param caller - exact live Team member creating the task. @@ -162,5 +186,5 @@ async updateTask(caller: Agent, request: UpdateTeamTaskRequest, signal?: AbortSi Types: [Agent](core.zh.md) -Source: [`packages/experimental/agent-team/src/index.ts`](../../packages/experimental/agent-team/src/index.ts) +Source: [`packages/team/agent-team/src/index.ts`](../../packages/team/agent-team/src/index.ts) diff --git a/docs/tool-catalog.i18n.yaml b/docs/tool-catalog.i18n.yaml index af9faf7f07..247a5563fe 100644 --- a/docs/tool-catalog.i18n.yaml +++ b/docs/tool-catalog.i18n.yaml @@ -273,23 +273,23 @@ en: 0c37fb1fed979d72 zh: 5ea9d07f5a4a8a6c /tool-schema-catalog/deepseek-ai-dsh-tool-jobs/job-output: - en: 1e084359a8cd42b3 - zh: 3f6c245dbbb2b920 -/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team: - en: 6b1106f417b50db6 - zh: 6b1106f417b50db6 -/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/team-task-create: - en: 91e84febe063c3bd - zh: 550555d0f44f603b -/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/team-task-get: - en: 1c6ae0aba0b8041d - zh: f8d634387b74121d -/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/team-task-list: - en: 2084d494aae8798a - zh: e4c136c91028a7b1 -/tool-schema-catalog/deepseek-ai-dsh-experimental-tool-agent-team/team-task-update: - en: 7c1e0b1f9a33fe13 - zh: 16260fb20d6f210e + en: 2977f731fb90d147 + zh: 4f422613ff720055 +/tool-schema-catalog/deepseek-ai-dsh-tool-agent-team: + en: 3f1a9c3509853972 + zh: 3f1a9c3509853972 +/tool-schema-catalog/deepseek-ai-dsh-tool-agent-team/team-task-create: + en: ac9ed819b705b4b8 + zh: 306477d2d457369a +/tool-schema-catalog/deepseek-ai-dsh-tool-agent-team/team-task-get: + en: bca1abf330638c19 + zh: 903bfa977f75c046 +/tool-schema-catalog/deepseek-ai-dsh-tool-agent-team/team-task-list: + en: 8001eda7e9ff6a25 + zh: 2414761029f21404 +/tool-schema-catalog/deepseek-ai-dsh-tool-agent-team/team-task-update: + en: 7363eeff5e5e1a56 + zh: 97a8fd0100c5de0f /tool-schema-catalog/deepseek-ai-dsh-tool-todo: en: dea62d26fb18ff68 zh: dea62d26fb18ff68 diff --git a/docs/tool-catalog.md b/docs/tool-catalog.md index 4e1f121f68..d87112db37 100644 --- a/docs/tool-catalog.md +++ b/docs/tool-catalog.md @@ -2227,9 +2227,9 @@ Source: [`packages/jobs/tool-jobs/src/index.ts`](../packages/jobs/tool-jobs/src/ The kind-agnostic background-job controller: background bash commands and PTY sends are read, listed, and killed through the same three tools. Loading the plugin attaches the controller that arms producers' `ctx.jobs.start()`. - + -## `@deepseek-ai/dsh-experimental-tool-agent-team` +## `@deepseek-ai/dsh-tool-agent-team` ### `team_task_create` @@ -2269,7 +2269,7 @@ Create one unowned pending task on the shared Team task board. } ``` -Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +Source: [`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_get` @@ -2290,7 +2290,7 @@ Read the complete latest value of one shared task before changing or executing i } ``` -Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +Source: [`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_list` @@ -2329,7 +2329,7 @@ List shared tasks, including readiness, owner, revision, blockers, and write-sco } ``` -Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +Source: [`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_update` @@ -2396,9 +2396,9 @@ Compare-and-set a shared task action using the latest revision from team_task_ge } ``` -Source: [`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +Source: [`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) -Four task-board tools share the subagent tree’s root Session. The optional Agent Teams profile adds task-board guidance and required Team identity derived from the subagent root, alongside standard subagent delegation and control tools. +Four task-board tools share the subagent tree’s root Session. Enabling Team mode on the root Session adds task-board guidance and required Team identity alongside standard subagent delegation and control tools. diff --git a/docs/tool-catalog.zh.md b/docs/tool-catalog.zh.md index 7cc6fd6e75..413190a3e7 100644 --- a/docs/tool-catalog.zh.md +++ b/docs/tool-catalog.zh.md @@ -2233,9 +2233,9 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录 与任务种类无关的后台任务控制器:后台 bash 命令与 PTY 发送 都通过相同的 3 个工具读取、列出和终止。加载该插件会挂接控制器,从而启用生产方的 `ctx.jobs.start()`。 - + -## `@deepseek-ai/dsh-experimental-tool-agent-team` +## `@deepseek-ai/dsh-tool-agent-team` ### `team_task_create` @@ -2275,7 +2275,7 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录 } ``` -来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +来源:[`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_get` @@ -2296,7 +2296,7 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录 } ``` -来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +来源:[`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_list` @@ -2335,7 +2335,7 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录 } ``` -来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +来源:[`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) ### `team_task_update` @@ -2402,9 +2402,9 @@ subagent_oar 工具在为外部提供方配置的别名之间选择。本目录 } ``` -来源:[`packages/experimental/tool-agent-team/src/index.ts`](../packages/experimental/tool-agent-team/src/index.ts) +来源:[`packages/team/tool-agent-team/src/index.ts`](../packages/team/tool-agent-team/src/index.ts) -四个任务板工具共享 subagent 树的根 Session。可选 Agent Teams profile 在标准子代理委派和控制工具之外增加任务板指导,以及从 subagent 根派生的必需 Team 身份。 +四个任务板工具共享 subagent 树的根 Session。在根 Session 开启 Team 模式后,会在标准子代理委派和控制工具之外增加任务板指导与必需的 Team 身份。 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.i18n.yaml index 0f10d46677..fee15bfbc4 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.i18n.yaml +++ b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.i18n.yaml @@ -12,5 +12,5 @@ en: 9968decf3dd340af zh: 736a32caac4b5103 /agent-teams-messages-use-target-inboxes/migration: - en: 7ffd442bfd55769e - zh: ffb11a143f11ce7f + en: a4b8999943c76cc4 + zh: 042927b5c857c8d5 diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md index 974c1aa13c..d2e03eacd5 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md +++ b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.md @@ -18,4 +18,4 @@ The `maxPendingMessagesPerMember` configuration key is removed. Historical `team 1. Before upgrading, let pending Team messages finish and confirm that their targets received them. After upgrading, explicitly resend any required undelivered message; check the target's history first to avoid duplicates. Do not edit historical Session records. 2. Remove `maxPendingMessagesPerMember` from the `@deepseek-ai/dsh-experimental-agent-team` plugin configuration in your `cordis.yml` or overlays. There is no replacement Team queue limit; `maxMessageBytes` still bounds each complete sender-framed message. 3. Update any consumer of `send_message` results to read `sent` instead of message IDs or delivery statuses, and handle send errors explicitly. Do not treat success as completed target work. The `ctx.agentTeams.sendMessage()` service still returns `messageId`. -4. Start your Team-enabled profile, send a message to another member, and confirm that the tool returns `{ "sent": true }` and that the target receives it. Consult the [package reference](../../../../packages/experimental/agent-team/README.md#use-this-package) for delivery semantics. +4. Start your Team-enabled profile, send a message to another member, and confirm that the tool returns `{ "sent": true }` and that the target receives it. Consult the [package reference](../../../../packages/team/agent-team/README.md#use-this-package) for delivery semantics. diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.zh.md index c10791fbf7..1f5014e0c7 100644 --- a/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.zh.md +++ b/docs/upgrade-guide/v0.2.1-alpha.1/team-direct-inbox/guide.zh.md @@ -18,4 +18,4 @@ description: "Agent Teams 移除待投递消息数量限制,并停止投递历 1. 升级前,让待投递的 Team 消息完成投递,并确认目标已收到。升级后,显式重发仍需处理的未投递消息;先检查目标历史,避免重复。不要编辑历史 Session 记录。 2. 从 `cordis.yml` 或覆盖配置中 `@deepseek-ai/dsh-experimental-agent-team` 插件的配置移除 `maxPendingMessagesPerMember`。没有替代的 Team 队列限制;`maxMessageBytes` 仍限制包含发送者标识的完整消息大小。 3. 更新所有读取 `send_message` 结果的调用方,改为读取 `sent` 而非消息 ID 或投递状态,并显式处理发送错误。不要把发送成功视为目标工作已完成。`ctx.agentTeams.sendMessage()` 服务仍返回 `messageId`。 -4. 启动启用 Team 的 profile,向其他成员发送消息,确认工具返回 `{ "sent": true }` 且目标收到消息。投递语义参见[包参考](../../../../packages/experimental/agent-team/README.zh.md#use-this-package)。 +4. 启动启用 Team 的 profile,向其他成员发送消息,确认工具返回 `{ "sent": true }` 且目标收到消息。投递语义参见[包参考](../../../../packages/team/agent-team/README.zh.md#use-this-package)。 diff --git a/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.i18n.yaml new file mode 100644 index 0000000000..7ca41a3b2e --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.i18n.yaml @@ -0,0 +1,16 @@ +# 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.2/session-team-mode/guide.md +/: + en: 2b713df685f8b593 + zh: 4e185e636a3b5d99 +/select-agent-teams-per-session: + en: 9e82418f46f6275b + zh: d1282eec79b37b59 +/select-agent-teams-per-session/change: + en: 3ba5920179726841 + zh: 940e0d956a523dbc +/select-agent-teams-per-session/migration: + en: 972659e493910060 + zh: a7a87c7327b9b8fe diff --git a/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md new file mode 100644 index 0000000000..ee068943c9 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md @@ -0,0 +1,22 @@ +--- +kind: upgrade-guide +description: "Agent Teams defaults off for new Sessions and uses a saved per-Session selection." +--- + +# Select Agent Teams per Session + +English | [中文](guide.zh.md) + +## Change + +Agent Teams ships with base-backed profiles; new Sessions default to off. The Plugins page has no separate Agent Teams switch. The root Session’s selection controls task tools and Team guidance throughout its subagent tree. The model and reasoning menu offers **Use Agent Teams**, and the root conversation’s Subagent header control opens members above shared tasks in one panel while enabled. Child conversations retain navigation without a shared task board. + +Disabling Team retains tasks and subagents. Admitted task operations finish; queued operations check the current mode when they start. The selection survives restart. A historical task board without a saved selection remains enabled. The required `team/mode` event records selections; older readers reject this unfamiliar event. See the [persistence acknowledgement](../../../persistence-changes/2026-10-19-session-team-mode.md). + +## Migration + +1. Start each existing profile once. Profile loading removes the retired `@deepseek-ai/dsh-experimental-agent-team-profile` bundle selection; the base and Web bundles supply its plugins. Keep `agent-team`, `tool-agent-team`, and `ui-agent-team` row overrides. Custom compositions replace package names `@deepseek-ai/dsh-experimental-agent-team`, `@deepseek-ai/dsh-experimental-tool-agent-team`, and `@deepseek-ai/dsh-experimental-client-ui-agent-team` with `@deepseek-ai/dsh-agent-team`, `@deepseek-ai/dsh-tool-agent-team`, and `@deepseek-ai/dsh-client-ui-agent-team`. +2. Enable **Use Agent Teams** in the root conversation’s model and reasoning menu, or execute `/team on`. Use `/team off` to disable it. +3. Verify that enabling shows shared tasks in the root conversation below the Subagent tree in the same panel and exposes four task tools. Reload the Session to confirm the saved selection. Re-enabling after disabling restores the existing tasks. + +Client plugin consumers replace `TeamActionProps` with `TeamBoardProps`; task content registers in `conversation.session.header.actions.subagent.footer` instead of the header actions slot. diff --git a/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md new file mode 100644 index 0000000000..2768e06659 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md @@ -0,0 +1,22 @@ +--- +kind: upgrade-guide +description: "Agent Teams 在新 Session 中默认关闭,并使用按 Session 保存的选择。" +--- + +# 按 Session 选择智能体团队 + +[English](guide.md) | 中文 + +## 变更 + +Agent Teams 随基于 base 的 profile 提供;新 Session 默认关闭。插件页不再提供独立的智能体团队开关。根 Session 的选择控制整棵子代理树中的任务工具和 Team 提示词。模型与推理等级菜单提供**使用智能体团队**开关,开启后通过根会话的 Subagent 页头入口,在同一面板中查看上方成员与下方共享任务。子会话保留导航,不显示共享任务板。 + +关闭 Team 保留任务和子代理。已经进入执行的任务操作继续完成;排队中的操作在开始时检查当前模式。选择在重启后仍有效。没有保存选择的历史任务板保持开启。必读事件 `team/mode` 记录选择;旧读取器会拒绝该未知事件。参见[持久化变更确认](../../../persistence-changes/2026-10-19-session-team-mode.zh.md)。 + +## 迁移 + +1. 启动每个已有 profile 一次。加载时会移除已停用的 `@deepseek-ai/dsh-experimental-agent-team-profile` 组合选项;base 与 Web 组合提供对应插件。保留 `agent-team`、`tool-agent-team` 与 `ui-agent-team` 行覆盖。自定义组合将包名 `@deepseek-ai/dsh-experimental-agent-team`、`@deepseek-ai/dsh-experimental-tool-agent-team`、`@deepseek-ai/dsh-experimental-client-ui-agent-team` 分别替换为 `@deepseek-ai/dsh-agent-team`、`@deepseek-ai/dsh-tool-agent-team`、`@deepseek-ai/dsh-client-ui-agent-team`。 +2. 在根会话的模型与推理等级菜单中打开**使用智能体团队**,或执行 `/team on`。使用 `/team off` 关闭。 +3. 在根会话中确认开启后通过 Subagent 入口在树下方查看共享任务板,并提供四个任务工具。重新加载 Session,确认选择已保存。关闭后再开启会恢复已有任务。 + +Client 插件消费者将 `TeamActionProps` 替换为 `TeamBoardProps`;任务内容注册到 `conversation.session.header.actions.subagent.footer`,而非页头操作插槽。 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.i18n.yaml new file mode 100644 index 0000000000..8916474764 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.i18n.yaml @@ -0,0 +1,16 @@ +# 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-mode-menu/guide.md +/: + en: 1c39dada6c6162a7 + zh: ab2f5e2824bfbf0c +/select-team-mode-from-the-conversation-menu: + en: d570d0f970f6a77a + zh: 7a722461f27bc620 +/select-team-mode-from-the-conversation-menu/change: + en: 37b569a98d41587d + zh: ce9c8ee4c35ac634 +/select-team-mode-from-the-conversation-menu/migration: + en: d548bbca6276ed74 + zh: 2468a89bd342b3ff diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.md new file mode 100644 index 0000000000..0d54d68096 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "The Team mode slash command is removed; use the root conversation menu." +--- + +# Select Team mode from the conversation menu + +English | [中文](guide.zh.md) + +## Change + +`/team on` and `/team off` are no longer registered. The root conversation’s **Use Agent Teams** switch calls `remote.agentTeams.setEnabled` directly. Saved selections, task retention, and root-only authorization are unchanged. + +## Migration + +1. Enable or disable **Use Agent Teams** in the root conversation’s model and reasoning menu. +2. Plugins that execute the command should call `ctx.agentTeams.setEnabled(rootAgent, enabled)` instead; browser clients call `remote.agentTeams.setEnabled(rootSessionId, enabled)`. +3. Confirm the switch reflects the saved selection after reloading the Session. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.zh.md new file mode 100644 index 0000000000..c8e847e91f --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-mode-menu/guide.zh.md @@ -0,0 +1,18 @@ +--- +kind: upgrade-guide +description: "Team 模式斜杠命令已移除,请使用根会话菜单。" +--- + +# 从会话菜单选择 Team 模式 + +[English](guide.md) | 中文 + +## 变更 + +`/team on` 和 `/team off` 不再注册。根会话的**使用智能体团队**开关直接调用 `remote.agentTeams.setEnabled`。保存的选择、任务保留以及仅允许根会话修改的授权规则不变。 + +## 迁移 + +1. 在根会话的模型与推理等级菜单中打开或关闭**使用智能体团队**。 +2. 执行该命令的插件改为调用 `ctx.agentTeams.setEnabled(rootAgent, enabled)`;浏览器客户端调用 `remote.agentTeams.setEnabled(rootSessionId, enabled)`。 +3. 重新加载 Session,确认开关反映已保存的选择。 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.i18n.yaml new file mode 100644 index 0000000000..b709e46f55 --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.i18n.yaml @@ -0,0 +1,16 @@ +# 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-projection/guide.md +/: + en: 185304d86b229610 + zh: b98e40567653a55f +/team-projection-retains-tasks-and-mode: + en: c20510084cf9d412 + zh: ee26f2365ab5d216 +/team-projection-retains-tasks-and-mode/change: + en: 5b92baca861b7281 + zh: 57b4162fb425163c +/team-projection-retains-tasks-and-mode/migration: + en: 6c30148dea17af5e + zh: 81b7fa72cc118252 diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.md b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.md new file mode 100644 index 0000000000..fe0258e51e --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "Team projection stops restoring historical teammates and mailbox state while retaining tasks and saved mode." +--- + +# Team projection retains tasks and mode + +English | [中文](guide.zh.md) + +## Change + +The `agentTeam` projection no longer restores `members`, `messages`, or `delivered` from historical `team/member` and `team/message/*` events. It ignores their payloads, so historical roster or mailbox validation failures no longer block task reads or writes. Historical teammates are not restored and queued mail is not delivered. + +Task revisions, task owners, deleted-task records, and the saved Team mode remain supported. Ordinary subagent discovery and messaging are unchanged. The Session format and committed log files do not change; projection checkpoint version 7 rebuilds older cached state from the log. + +## Migration + +1. No data conversion or configuration change is required. Open the Session normally to rebuild incompatible projection checkpoints. +2. Use `list_agents` and `send_message` for current teammates. Read historical roster or mailbox events from the original Session log if you need an archive; the Team projection no longer supplies these collections. +3. Confirm that the task board and saved Team mode are restored. Historical roster and mailbox records do not grant task-assignment authority. diff --git a/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.zh.md b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.zh.md new file mode 100644 index 0000000000..b6b131599e --- /dev/null +++ b/docs/upgrade-guide/v0.2.1-rc.3/team-projection/guide.zh.md @@ -0,0 +1,20 @@ +--- +kind: upgrade-guide +description: "Team 投影停止恢复历史队友与邮箱状态,同时保留任务和保存的模式。" +--- + +# Team 投影保留任务与模式 + +[English](guide.md) | 中文 + +## 变更 + +`agentTeam` 投影不再从历史 `team/member` 和 `team/message/*` 事件恢复 `members`、`messages` 或 `delivered`。投影忽略这些载荷,因此历史名册或邮箱校验失败不再阻止任务读写。不恢复历史队友,也不投递排队邮件。 + +任务修订、任务所有者、已删除任务记录和保存的 Team 模式仍受支持。普通子代理发现与通信保持不变。Session 格式和已提交日志文件不变;投影检查点版本 7 从日志重建旧缓存状态。 + +## 迁移 + +1. 无需转换数据或修改配置。正常打开 Session 即可重建不兼容的投影检查点。 +2. 使用 `list_agents` 和 `send_message` 与当前队友协作。需要归档信息时,从原始 Session 日志读取历史名册或邮箱事件;Team 投影不再提供这些集合。 +3. 确认任务板和保存的 Team 模式已恢复。历史名册与邮箱记录不授予任务分配权限。 diff --git a/packages/README.i18n.yaml b/packages/README.i18n.yaml index 92c89f5ff2..d96e805d23 100644 --- a/packages/README.i18n.yaml +++ b/packages/README.i18n.yaml @@ -15,8 +15,8 @@ en: ffc55792d6e922e1 zh: 83bd759638857e2d /packages/package-groups: - en: c397fc0c1885058a - zh: c720d0e91dd1d15e + en: 60a6056460b8d4c1 + zh: fce319504ae9b6ef /packages/release-expectations: en: 236081c1659b25ab zh: 809110adf003802e diff --git a/packages/README.md b/packages/README.md index ed7c55ec5a..85a63643d0 100644 --- a/packages/README.md +++ b/packages/README.md @@ -52,6 +52,7 @@ Every package lives in exactly one group; new packages join existing groups, and | [`compaction/`](compaction/README.md) | Compaction capability family: Service Definition + basic provider + command Consumer | | [`context/`](context/README.md) | Model-visible request context: workspace instructions, time context, references | | [`subagent/`](subagent/README.md) | Subagent capability family: provider-registry contract and model-facing delegation tools | +| [`team/`](team/README.md) | Team coordination: shared task boards and model-facing task tools | | [`jobs/`](jobs/README.md) | Generic background-job runtime and model-facing job control tools | | [`experimental/`](experimental/README.md) | Capabilities under evaluation; [status and support policy](experimental/README.md#status) | | [`workflow/`](workflow/README.md) | Workflow seam, PTC process engine, and the model-facing `workflow` tool | diff --git a/packages/README.zh.md b/packages/README.zh.md index ae3a18bbc1..f293666d2f 100644 --- a/packages/README.zh.md +++ b/packages/README.zh.md @@ -52,6 +52,7 @@ harness 由 `packages/` 下的 npm 包组装而成,按能力系列分组:会 | [`compaction/`](compaction/README.zh.md) | 压缩(compaction)能力系列:Service Definition + 基础提供方 + 命令 Consumer | | [`context/`](context/README.zh.md) | 模型可见请求上下文:workspace 指令、时间上下文、引用 | | [`subagent/`](subagent/README.zh.md) | subagent 能力系列:提供方注册表约定和面向模型的委托工具 | +| [`team/`](team/README.zh.md) | Team 协调:共享任务板与面向模型的任务工具 | | [`jobs/`](jobs/README.zh.md) | 通用后台任务运行时和面向模型的作业控制工具 | | [`experimental/`](experimental/README.zh.md) | 仍在评估中的能力;[状态与支持策略](experimental/README.zh.md#status) | | [`workflow/`](workflow/README.zh.md) | 工作流 seam、PTC 进程引擎、面向模型的 `workflow` 工具 | diff --git a/packages/api/remotes/README.i18n.yaml b/packages/api/remotes/README.i18n.yaml index f3b9eab6fc..64b8496be6 100644 --- a/packages/api/remotes/README.i18n.yaml +++ b/packages/api/remotes/README.i18n.yaml @@ -15,8 +15,8 @@ en: a7a78c6d1aeebeb3 zh: 4bb69f32d8b05ba3 /deepseek-ai-dsh-api-remotes/use-this-package: - en: a80d44e0683768a5 - zh: 379833ac180e5418 + en: 6e3448339c646a7e + zh: 5dc00ff4cceaa88c /deepseek-ai-dsh-api-remotes/forwarded-host-events: en: 31bb9a2f50094b84 zh: 226c055739d5def7 diff --git a/packages/api/remotes/README.md b/packages/api/remotes/README.md index 8d49cb49a5..a05773a4af 100644 --- a/packages/api/remotes/README.md +++ b/packages/api/remotes/README.md @@ -29,7 +29,7 @@ Two-sided BFF for Host Remote capabilities selected by this application. The Hos [`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.md) owns Agent and Session identity policy, including the Typert lookup resolvers used by other namespaces. This package selects and mounts that generated Session contribution without a direct runtime dependency on `@deepseek-ai/dsh-session`; activation policy stays with Session Controller. -The Client assembly mounts Office conversion, Commands, credentials, settings, Goal, Schedule, dynamic Cordis, file and Session references, read-only Host plugin inventory, plugin-installation registry response comparison, message feedback, permission presets, Session Controller, subagents, and Workspace Controller contributions. The `permissionPresets` namespace returns the complete process-level catalog used by current-session controls. Cordis effect ownership withdraws every contribution when this assembly unloads, while `@deepseek-ai/dsh-api-gateway/client` owns descriptor validation, traced namespace Services, direct and scoped methods, invocation, streams, and cancellation. The Client entry consumes the shared `TypertClientRemote` interface through Cordis and does not import the concrete Gateway. It re-exports the Gateway Client face's declaration merges type-only, so a consumer reaching the forwarded-event vocabulary through this facade gains no runtime edge to the Gateway implementation. +The Client assembly mounts Office conversion, Commands, Agent Teams, credentials, settings, Goal, Schedule, dynamic Cordis, file and Session references, read-only Host plugin inventory, plugin-installation registry response comparison, message feedback, permission presets, Session Controller, subagents, and Workspace Controller contributions. The `permissionPresets` namespace returns the complete process-level catalog used by current-session controls. Cordis effect ownership withdraws every contribution when this assembly unloads, while `@deepseek-ai/dsh-api-gateway/client` owns descriptor validation, traced namespace Services, direct and scoped methods, invocation, streams, and cancellation. The Client entry consumes the shared `TypertClientRemote` interface through Cordis and does not import the concrete Gateway. It re-exports the Gateway Client face's declaration merges type-only, so a consumer reaching the forwarded-event vocabulary through this facade gains no runtime edge to the Gateway implementation. This facade is also the front door for the wire type vocabulary a Client package names. It re-exports, type-only, the Remote failure vocabulary (`RemoteResult`, `RemoteFailure`, `RemoteErrorCode`, `RemoteErrorDetailsMap`), the Host facts (`RemoteHostFacts`), and each selected domain's client-safe payload types, so a Client feature package imports one specifier instead of reaching into `dsh-typert-protocol`, the Gateway, or an owner's Host entry. Two kinds of package deliberately skip this door: the API-layer packages this assembly itself selects — importing it back would close a dependency cycle — and their tests, which take the failure vocabulary from `dsh-typert-protocol` directly. A UI package's tests instead take the `RemoteError` constructor from [`dsh-client-test-runtime`](../../test-support/client-runtime/README.md). diff --git a/packages/api/remotes/README.zh.md b/packages/api/remotes/README.zh.md index 4f9772403d..bc616b9588 100644 --- a/packages/api/remotes/README.zh.md +++ b/packages/api/remotes/README.zh.md @@ -29,7 +29,7 @@ kind: "package-reference" [`@deepseek-ai/dsh-api-session-controller`](../session-controller/README.zh.md) 拥有 agent(智能体)与会话身份策略,包括供其他 namespace 使用的 Typert lookup 解析器。本包选择并挂载生成的会话 contribution,无需直接依赖 `@deepseek-ai/dsh-session` 的运行时;激活策略由会话控制器负责。 -Client 组合挂载 Office 转换、Commands、凭据、settings、Goal、Schedule、动态 Cordis、文件与会话引用、只读 Host 插件清单、插件安装源响应比较、消息反馈、权限预设、会话控制器、subagents 和 Workspace 控制器 contribution。`permissionPresets` namespace 返回 current-session 控件使用的完整进程级目录。该组合卸载时,Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-api-gateway/client` 负责描述符校验、可追踪的 namespace 服务、直接与作用域方法、调用、流与取消。Client 入口通过 Cordis 消费共享的 `TypertClientRemote` 接口,不导入具体 Gateway;它只以 type-only 形式重新导出 Gateway Client face 的声明合并,因此消费端经由本外观取到转发事件词汇时,运行时不会多出一条通往 Gateway 实现的边。 +Client 组合挂载 Office 转换、Commands、Agent Teams、凭据、settings、Goal、Schedule、动态 Cordis、文件与会话引用、只读 Host 插件清单、插件安装源响应比较、消息反馈、权限预设、会话控制器、subagents 和 Workspace 控制器 contribution。`permissionPresets` namespace 返回 current-session 控件使用的完整进程级目录。该组合卸载时,Cordis effect 的所有权机制会撤回所有贡献;`@deepseek-ai/dsh-api-gateway/client` 负责描述符校验、可追踪的 namespace 服务、直接与作用域方法、调用、流与取消。Client 入口通过 Cordis 消费共享的 `TypertClientRemote` 接口,不导入具体 Gateway;它只以 type-only 形式重新导出 Gateway Client face 的声明合并,因此消费端经由本外观取到转发事件词汇时,运行时不会多出一条通往 Gateway 实现的边。 本 facade 同时是 Client 包指称 wire 类型词汇的正门。它以 type-only 方式转出 Remote 失败词汇(`RemoteResult`、`RemoteFailure`、`RemoteErrorCode`、`RemoteErrorDetailsMap`)、Host 事实(`RemoteHostFacts`),以及各已选领域对 Client 安全的载荷类型,因此 Client 功能包只 import 一个 specifier,不必伸手进 `dsh-typert-protocol`、Gateway 或某个拥有方的 Host 入口。有两类包刻意不走这道门:本装配自己选中的 API 层包——反向 import 会形成依赖环——以及它们的测试,后者直接从 `dsh-typert-protocol` 取失败词汇。UI 包的测试则从 [`dsh-client-test-runtime`](../../test-support/client-runtime/README.zh.md) 取 `RemoteError` 构造器。 diff --git a/packages/api/remotes/package.json b/packages/api/remotes/package.json index 8735bc4339..1fe16a4a87 100644 --- a/packages/api/remotes/package.json +++ b/packages/api/remotes/package.json @@ -99,6 +99,7 @@ "@deepseek-ai/dsh-api-account-controller": "workspace:*", "@deepseek-ai/dsh-deepseek-account": "workspace:*", "@deepseek-ai/dsh-client-product-analytics": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*", "@deepseek-ai/dsh-client-ui-trajectory": "workspace:*" } } diff --git a/packages/api/remotes/src/client/index.ts b/packages/api/remotes/src/client/index.ts index 8a4b7dea8a..86112b7cc2 100644 --- a/packages/api/remotes/src/client/index.ts +++ b/packages/api/remotes/src/client/index.ts @@ -6,6 +6,8 @@ export type {} from '@deepseek-ai/dsh-client-product-analytics/remote' import agentPresetsRemote from '@deepseek-ai/dsh-agent-preset-registry/remote' import userQuestionsRemote from '@deepseek-ai/dsh-user-questions/remote' import commandsRemote from '@deepseek-ai/dsh-commands/remote' +import agentTeamsRemote from '@deepseek-ai/dsh-agent-team/remote' +export type {} from '@deepseek-ai/dsh-agent-team/remote' import accountRemote from '@deepseek-ai/dsh-api-account-controller/remote' import settingsControllerRemote from '@deepseek-ai/dsh-api-settings-controller/remote' import officeToPdfRemote from '@deepseek-ai/dsh-office-to-pdf/remote' @@ -189,7 +191,7 @@ export async function apply(ctx: Context): Promise<() => Promise> { const disposers: Array<() => Promise> = [] try { for (const contribution of [ - productAnalyticsRemote, agentPresetsRemote, commandsRemote, settingsControllerRemote, accountRemote, + productAnalyticsRemote, agentPresetsRemote, commandsRemote, agentTeamsRemote, settingsControllerRemote, accountRemote, goalsRemote, llmRemote, dynamicRemote, scheduleRemote, pluginInventoryRemote, pluginManagerRemote, pluginMarketRemote, pluginRegistryProbeRemote, toolResultsRemote, messageFeedbackRemote, sessionFeedbackRemote, diff --git a/packages/api/remotes/tsconfig.client.json b/packages/api/remotes/tsconfig.client.json index 70181b08c5..49b3180c43 100644 --- a/packages/api/remotes/tsconfig.client.json +++ b/packages/api/remotes/tsconfig.client.json @@ -11,6 +11,7 @@ "src/types.ts" ], "references": [ + { "path": "../../team/agent-team" }, { "path": "../../client/product-analytics/tsconfig.client.json" }, { "path": "../../../vendor/cordis" diff --git a/packages/boot/app-boot/README.i18n.yaml b/packages/boot/app-boot/README.i18n.yaml index 9ce0beae8e..98cb511540 100644 --- a/packages/boot/app-boot/README.i18n.yaml +++ b/packages/boot/app-boot/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-app-boot/use-this-package: - en: f6fa378225009c8f - zh: 324c0e4640bb1358 + en: 8e42ac55d685e0f1 + zh: e9e8804af1b2df2b /deepseek-ai-dsh-app-boot/use-this-package/when-to-use-it: en: 2fdfe02f2ed4a258 zh: d748228ece579c4a diff --git a/packages/boot/app-boot/README.md b/packages/boot/app-boot/README.md index 4d30457f81..46fc9be865 100644 --- a/packages/boot/app-boot/README.md +++ b/packages/boot/app-boot/README.md @@ -27,6 +27,8 @@ English | [中文](README.zh.md) Starting an app with this package is a small, explicit entry point: you give it a config file and it runs the whole boot. This section covers what you can do and what you get; the helper calls behind each outcome are documented in the folded implementation section. +Profile loading removes retired installation-bundle selections from `dsh.profile.bundles` and rewrites the manifest only when needed, preserving other fields. [Team migration](../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.md) describes the default composition and retained row overrides. + ### When to use it Use it when implementing the shared `dsh` launcher or embedding its lower-level boot helpers. Product features belong in profile bundles instead of new application bins; code that only adds plugins to an already-running app mounts those plugins directly. diff --git a/packages/boot/app-boot/README.zh.md b/packages/boot/app-boot/README.zh.md index a4d8554ca5..1bc7234f55 100644 --- a/packages/boot/app-boot/README.zh.md +++ b/packages/boot/app-boot/README.zh.md @@ -27,6 +27,8 @@ kind: "package-library" 用此包启动应用是一个小而显式的入口:你给它一个配置文件,它运行整个启动过程。本节说明你能做什么、能得到什么;每个结果背后的 helper 调用记录在下方可折叠的实现章节中。 +profile 加载时会从 `dsh.profile.bundles` 移除已停用的安装方 bundle 选项,仅在需要时重写 manifest,保留其他字段。[Team 迁移](../../../docs/upgrade-guide/v0.2.1-rc.2/session-team-mode/guide.zh.md)说明默认组合与保留的行覆盖。 + ### 何时使用 在实现共享 `dsh` launcher 或嵌入其低层启动 helper 时使用它。产品功能应放入 profile 组合包,而不是新增应用 bin;只向已运行应用添加插件的代码直接挂载插件即可。 diff --git a/packages/boot/app-boot/tests/profile.spec.ts b/packages/boot/app-boot/tests/profile.spec.ts index 0a67d476e1..238f316dc9 100644 --- a/packages/boot/app-boot/tests/profile.spec.ts +++ b/packages/boot/app-boot/tests/profile.spec.ts @@ -394,13 +394,13 @@ describe('loadProfile', () => { ]) }) - it('removes a retired bundle from an application-owned profile and keeps the rest of the manifest', () => { + it.each(['@deepseek-ai/dsh-experimental-schedule-bundle', '@deepseek-ai/dsh-experimental-agent-team-profile'])('removes retired %s and keeps the rest of an application-owned manifest', (retired) => { const anchor = stageInstallation({ '@deepseek-ai/dsh-base': { patch: '[]\n' }, 'custom-bundle': { patch: '[]\n' }, }) const dir = join(tmp(), 'app-profile') - initProfile(dir, ['@deepseek-ai/dsh-base', '@deepseek-ai/dsh-experimental-schedule-bundle', 'custom-bundle']) + initProfile(dir, ['@deepseek-ai/dsh-base', retired, 'custom-bundle']) writeProfileManifest(dir, { ...readProfileManifest('t', dir), dependencies: { 'custom-bundle': '^1.0.0' } }) const profile = loadProfileDirectory('t', dir, anchor) expect(profile.skippedBundles).toEqual([]) diff --git a/packages/bundle/base/README.md b/packages/bundle/base/README.md index f928d433d6..db77c345ec 100644 --- a/packages/bundle/base/README.md +++ b/packages/bundle/base/README.md @@ -51,6 +51,8 @@ Out of the box, every profile built on this core provides: a DeepSeek model conn Both `subagent` and `subagent_fork` create managed children and return a child id immediately. Use `send_message` to steer active children or continue their conversations after completion. [Delegation-tool behavior](../../subagent/tool-subagent/README.md) describes result delivery and backend capabilities. +The bundle also mounts [Agent Teams](../../team/agent-team/README.md) and its task tools. Team mode defaults off per root Session; the root conversation’s **Use Agent Teams** menu switch enables collaboration for that tree without changing the profile. + The built-in `working_directory` tool changes the directory for subsequent operations while existing processes and write permissions remain independent. Current directory information appears in user context. Default file editing uses `read`, `write`, and `edit`. The `str_replace_editor` tool remains available as an explicit opt-in. To add it to a base-backed profile, put this entry in the profile, home, or invocation patch: ```yaml diff --git a/packages/bundle/base/README.zh.md b/packages/bundle/base/README.zh.md index 132a40bab3..53c04f4b52 100644 --- a/packages/bundle/base/README.zh.md +++ b/packages/bundle/base/README.zh.md @@ -51,6 +51,8 @@ kind: "package-bundle" `subagent` 与 `subagent_fork` 都创建受管理的子任务,并立即返回 child id。使用 `send_message` 可引导活跃子任务,或在完成后继续其对话。[委派工具行为](../../subagent/tool-subagent/README.zh.md)说明了结果交付和后端能力。 +此 bundle 还挂载 [Agent Teams](../../team/agent-team/README.zh.md) 及其任务工具。每个根 Session 的 Team 模式默认关闭;根会话菜单中的**使用智能体团队**开关为当前树开启协作,无需修改 profile。 + 内置 `working_directory` 工具改变后续操作使用的目录,已有进程与写权限保持独立。当前目录信息出现在用户上下文中。默认文件编辑使用 `read`、`write` 和 `edit`。`str_replace_editor` 工具仍可显式启用。要将它加入基于 base 的 profile,请在 profile、home 或逐次调用 patch 中添加以下条目: ```yaml diff --git a/packages/bundle/base/cordis.patch.yml b/packages/bundle/base/cordis.patch.yml index 863d194bf5..07cd0d03ce 100644 --- a/packages/bundle/base/cordis.patch.yml +++ b/packages/bundle/base/cordis.patch.yml @@ -359,6 +359,14 @@ - id: subagent name: '@deepseek-ai/dsh-subagent' + - id: agent-team + name: '@deepseek-ai/dsh-agent-team' + config: + maxTasks: 256 + + - id: tool-agent-team + name: '@deepseek-ai/dsh-tool-agent-team' + - id: subagent-spawn-in-process name: '@deepseek-ai/dsh-subagent-spawn-in-process' config: diff --git a/packages/bundle/base/package.json b/packages/bundle/base/package.json index 6c6e508732..68a395af92 100644 --- a/packages/bundle/base/package.json +++ b/packages/bundle/base/package.json @@ -34,13 +34,16 @@ } }, "dependencies": { - "@deepseek-ai/dsh-hmr": "workspace:*", "@deepseek-ai/cordis-plugin-timer": "workspace:~", "@deepseek-ai/dsh-agent": "workspace:*", "@deepseek-ai/dsh-agent-default-model": "workspace:*", + "@deepseek-ai/dsh-agent-instructions": "workspace:*", "@deepseek-ai/dsh-agent-loop": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*", + "@deepseek-ai/dsh-api-gateway": "workspace:*", + "@deepseek-ai/dsh-api-remote-access": "workspace:*", "@deepseek-ai/dsh-attachment-local": "workspace:*", - "@deepseek-ai/dsh-shell-env": "workspace:*", + "@deepseek-ai/dsh-authorization": "workspace:*", "@deepseek-ai/dsh-bash-sandbox": "workspace:*", "@deepseek-ai/dsh-command-compact": "workspace:*", "@deepseek-ai/dsh-command-feedback": "workspace:*", @@ -49,24 +52,29 @@ "@deepseek-ai/dsh-compaction-basic": "workspace:*", "@deepseek-ai/dsh-compaction-image-offload": "workspace:*", "@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:*", + "@deepseek-ai/dsh-config-editor": "workspace:*", "@deepseek-ai/dsh-credentials-local": "workspace:*", + "@deepseek-ai/dsh-deepseek-account-platform": "workspace:*", "@deepseek-ai/dsh-deepseek-llm-api-extensions": "workspace:*", - "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:*", "@deepseek-ai/dsh-fs-local": "workspace:*", "@deepseek-ai/dsh-fs-observation-policy": "workspace:*", "@deepseek-ai/dsh-fs-sandbox": "workspace:*", "@deepseek-ai/dsh-goal": "workspace:*", "@deepseek-ai/dsh-goal-round-driver": "workspace:*", - "@deepseek-ai/dsh-api-gateway": "workspace:*", - "@deepseek-ai/dsh-api-remote-access": "workspace:*", + "@deepseek-ai/dsh-hmr": "workspace:*", + "@deepseek-ai/dsh-jobs-local": "workspace:*", "@deepseek-ai/dsh-llm": "workspace:*", - "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", "@deepseek-ai/dsh-llm-deepseek-account": "workspace:*", + "@deepseek-ai/dsh-llm-deepseek-api-key": "workspace:*", "@deepseek-ai/dsh-llm-pi-ai": "workspace:*", "@deepseek-ai/dsh-llm-retry": "workspace:*", "@deepseek-ai/dsh-mcp-resources": "workspace:*", + "@deepseek-ai/dsh-otel": "workspace:*", "@deepseek-ai/dsh-permission-presets": "workspace:*", "@deepseek-ai/dsh-plan-mode": "workspace:*", + "@deepseek-ai/dsh-plugin-manager": "workspace:*", + "@deepseek-ai/dsh-plugin-package-inventory-deepseek": "workspace:*", + "@deepseek-ai/dsh-ptc-runtime-node": "workspace:*", "@deepseek-ai/dsh-pwsh-sandbox": "workspace:*", "@deepseek-ai/dsh-repeat-tool-reminder": "workspace:*", "@deepseek-ai/dsh-sandbox-local": "workspace:*", @@ -81,6 +89,8 @@ "@deepseek-ai/dsh-session-telemetry-otel": "workspace:*", "@deepseek-ai/dsh-session-title": "workspace:*", "@deepseek-ai/dsh-session-title-first-prompt-llm": "workspace:*", + "@deepseek-ai/dsh-settings": "workspace:*", + "@deepseek-ai/dsh-shell-env": "workspace:*", "@deepseek-ai/dsh-skill": "workspace:*", "@deepseek-ai/dsh-skill-filesystem": "workspace:*", "@deepseek-ai/dsh-spill-local": "workspace:*", @@ -93,44 +103,36 @@ "@deepseek-ai/dsh-subagent-spawn-in-process": "workspace:*", "@deepseek-ai/dsh-subprocess-local": "workspace:*", "@deepseek-ai/dsh-system-prompt": "workspace:*", - "@deepseek-ai/dsh-jobs-local": "workspace:*", - "@deepseek-ai/dsh-tool-call-timeout-policy": "workspace:*", "@deepseek-ai/dsh-token-meter": "workspace:*", + "@deepseek-ai/dsh-tool-agent-team": "workspace:*", "@deepseek-ai/dsh-tool-bash": "workspace:*", - "@deepseek-ai/dsh-tool-present": "workspace:*", + "@deepseek-ai/dsh-tool-call-timeout-policy": "workspace:*", "@deepseek-ai/dsh-tool-executor-native": "workspace:*", "@deepseek-ai/dsh-tool-executor-ptc": "workspace:*", "@deepseek-ai/dsh-tool-fs": "workspace:*", "@deepseek-ai/dsh-tool-fs-search": "workspace:*", "@deepseek-ai/dsh-tool-goal": "workspace:*", + "@deepseek-ai/dsh-tool-jobs": "workspace:*", + "@deepseek-ai/dsh-tool-present": "workspace:*", "@deepseek-ai/dsh-tool-pwsh": "workspace:*", "@deepseek-ai/dsh-tool-skill": "workspace:*", "@deepseek-ai/dsh-tool-subagent": "workspace:*", "@deepseek-ai/dsh-tool-subagent-control": "workspace:*", - "@deepseek-ai/dsh-tool-jobs": "workspace:*", "@deepseek-ai/dsh-tool-todo": "workspace:*", "@deepseek-ai/dsh-tool-web": "workspace:*", "@deepseek-ai/dsh-tool-workflow": "workspace:*", + "@deepseek-ai/dsh-tool-working-directory": "workspace:*", "@deepseek-ai/dsh-tools": "workspace:*", "@deepseek-ai/dsh-typert-loader": "workspace:*", "@deepseek-ai/dsh-typert-registry": "workspace:*", "@deepseek-ai/dsh-user-approval": "workspace:*", "@deepseek-ai/dsh-user-questions": "workspace:*", "@deepseek-ai/dsh-web": "workspace:*", + "@deepseek-ai/dsh-web-deepseek-api": "workspace:*", "@deepseek-ai/dsh-web-fetch-http": "workspace:*", "@deepseek-ai/dsh-web-search-deepseek": "workspace:*", "@deepseek-ai/dsh-workflow-ptc": "workspace:*", - "@deepseek-ai/dsh-agent-instructions": "workspace:*", - "@deepseek-ai/dsh-ptc-runtime-node": "workspace:*", - "@deepseek-ai/dsh-plugin-manager": "workspace:*", - "@deepseek-ai/dsh-authorization": "workspace:*", - "@deepseek-ai/dsh-deepseek-account-platform": "workspace:*", - "@deepseek-ai/dsh-config-editor": "workspace:*", - "@deepseek-ai/dsh-settings": "workspace:*", - "@deepseek-ai/dsh-otel": "workspace:*", - "@deepseek-ai/dsh-working-directory": "workspace:*", - "@deepseek-ai/dsh-tool-working-directory": "workspace:*", - "@deepseek-ai/dsh-web-deepseek-api": "workspace:*" + "@deepseek-ai/dsh-working-directory": "workspace:*" }, "peerDependencies": { "@deepseek-ai/cordis": "workspace:~" diff --git a/packages/bundle/web-app/README.i18n.yaml b/packages/bundle/web-app/README.i18n.yaml index ac78994ab7..adb17d9e6d 100644 --- a/packages/bundle/web-app/README.i18n.yaml +++ b/packages/bundle/web-app/README.i18n.yaml @@ -15,8 +15,8 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-web-app/use-this-package: - en: 3bd37b85139d2fa7 - zh: ba86c807eb83866e + en: 1f69a4c52a27e7e5 + zh: e4431a252fb86f7c /deepseek-ai-dsh-web-app/use-this-package/starting-the-web-gui: en: e3cac911f080d8ef zh: 60749e9f5c39c1bc diff --git a/packages/bundle/web-app/README.md b/packages/bundle/web-app/README.md index cb974443e5..b621d1e8c2 100644 --- a/packages/bundle/web-app/README.md +++ b/packages/bundle/web-app/README.md @@ -31,6 +31,8 @@ Run `dsh --profile web` for browser chat, model and settings management, and ses Start the GUI, open your browser, and start talking to the agent. The flags fine-tune the invocation. +[Agent Teams UI](../../client/ui-agent-team/README.md) loads with this bundle. Enable it per Session in the model and reasoning menu; shared tasks open below the member tree from the Subagent control. + ### Starting the Web GUI ```sh diff --git a/packages/bundle/web-app/README.zh.md b/packages/bundle/web-app/README.zh.md index 88bbae54a5..9df9111bf3 100644 --- a/packages/bundle/web-app/README.zh.md +++ b/packages/bundle/web-app/README.zh.md @@ -31,6 +31,8 @@ kind: "package-bundle" 启动 GUI、打开浏览器,然后开始与 agent(智能体)对话。flag 用于微调本次调用。 +[Agent Teams UI](../../client/ui-agent-team/README.zh.md) 随本 bundle 加载。在模型与推理等级菜单中按 Session 开启;通过 Subagent 控件打开成员树,下方显示共享任务。 + ### 启动 Web GUI ```sh diff --git a/packages/bundle/web-app/cordis.patch.yml b/packages/bundle/web-app/cordis.patch.yml index 65e1c11b10..dfaccf6812 100644 --- a/packages/bundle/web-app/cordis.patch.yml +++ b/packages/bundle/web-app/cordis.patch.yml @@ -382,6 +382,9 @@ - id: ui-skill name: '@deepseek-ai/dsh-client-ui-skill' + - id: ui-agent-team + name: '@deepseek-ai/dsh-client-ui-agent-team' + - id: ui-subagent name: '@deepseek-ai/dsh-client-ui-subagent' diff --git a/packages/bundle/web-app/package.json b/packages/bundle/web-app/package.json index 9ecc295724..89cc800d9f 100644 --- a/packages/bundle/web-app/package.json +++ b/packages/bundle/web-app/package.json @@ -58,6 +58,7 @@ "@deepseek-ai/dsh-agent-instructions": "workspace:*", "@deepseek-ai/dsh-agent-preset": "workspace:*", "@deepseek-ai/dsh-agent-preset-registry": "workspace:*", + "@deepseek-ai/dsh-agent-tool-presentation": "workspace:*", "@deepseek-ai/dsh-api-job-controller": "workspace:*", "@deepseek-ai/dsh-api-remotes": "workspace:*", "@deepseek-ai/dsh-api-session-controller": "workspace:*", @@ -71,8 +72,11 @@ "@deepseek-ai/dsh-client-hmr": "workspace:*", "@deepseek-ai/dsh-client-locale": "workspace:*", "@deepseek-ai/dsh-client-modules": "workspace:*", + "@deepseek-ai/dsh-client-product-analytics": "workspace:*", "@deepseek-ai/dsh-client-resources": "workspace:*", + "@deepseek-ai/dsh-client-shortcuts": "workspace:*", "@deepseek-ai/dsh-client-ui-agent-preset": "workspace:*", + "@deepseek-ai/dsh-client-ui-agent-team": "workspace:*", "@deepseek-ai/dsh-client-ui-approval": "workspace:*", "@deepseek-ai/dsh-client-ui-attachment": "workspace:*", "@deepseek-ai/dsh-client-ui-brand-official": "workspace:*", @@ -97,18 +101,21 @@ "@deepseek-ai/dsh-client-ui-plugin-manager": "workspace:*", "@deepseek-ai/dsh-client-ui-reference": "workspace:*", "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", + "@deepseek-ai/dsh-client-ui-schedule": "workspace:*", "@deepseek-ai/dsh-client-ui-secret-mask": "workspace:*", "@deepseek-ai/dsh-client-ui-session": "workspace:*", - "@deepseek-ai/dsh-client-ui-schedule": "workspace:*", "@deepseek-ai/dsh-client-ui-settings": "workspace:*", + "@deepseek-ai/dsh-client-ui-settings-account": "workspace:*", + "@deepseek-ai/dsh-client-ui-settings-agent-loop": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-general": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-models": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-plugin-inventory": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-plugins": "workspace:*", + "@deepseek-ai/dsh-client-ui-settings-session-log": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-shell": "workspace:*", - "@deepseek-ai/dsh-client-ui-settings-agent-loop": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-subagent": "workspace:*", "@deepseek-ai/dsh-client-ui-settings-web-search": "workspace:*", + "@deepseek-ai/dsh-client-ui-shortcuts": "workspace:*", "@deepseek-ai/dsh-client-ui-sidebar": "workspace:*", "@deepseek-ai/dsh-client-ui-sidebar-browser": "workspace:*", "@deepseek-ai/dsh-client-ui-sidebar-documentpreview": "workspace:*", @@ -130,14 +137,17 @@ "@deepseek-ai/dsh-compaction-tool-result-pruner": "workspace:*", "@deepseek-ai/dsh-cordis-client-runner": "workspace:*", "@deepseek-ai/dsh-cordis-host-runner": "workspace:*", + "@deepseek-ai/dsh-deliverable-file-references": "workspace:*", "@deepseek-ai/dsh-file-reference": "workspace:*", "@deepseek-ai/dsh-file-reference-local": "workspace:*", + "@deepseek-ai/dsh-harness-dev-context": "workspace:*", "@deepseek-ai/dsh-host-directory-picker-auto": "workspace:*", "@deepseek-ai/dsh-host-directory-picker-browse": "workspace:*", "@deepseek-ai/dsh-host-directory-picker-native": "workspace:*", "@deepseek-ai/dsh-host-frontend-static": "workspace:*", "@deepseek-ai/dsh-host-open-in-app": "workspace:*", "@deepseek-ai/dsh-host-plugin-inventory": "workspace:*", + "@deepseek-ai/dsh-host-product-telemetry-otel": "workspace:*", "@deepseek-ai/dsh-host-webserver": "workspace:*", "@deepseek-ai/dsh-launch-environment": "workspace:*", "@deepseek-ai/dsh-message-feedback": "workspace:*", diff --git a/packages/client/README.i18n.yaml b/packages/client/README.i18n.yaml index 503d65a1c8..1c3494a2f5 100644 --- a/packages/client/README.i18n.yaml +++ b/packages/client/README.i18n.yaml @@ -15,8 +15,8 @@ en: 1eb35b015a6c423f zh: a8a3dceee2c9e943 /client-web-gui-browser-half/packages: - en: b6fae58a79112e2b - zh: c95394e541d421ba + en: d7adae6baeed495b + zh: 9e49fb00adc8eec1 /client-web-gui-browser-half/related-documentation: en: d1d506e68fab7333 zh: 1a90fe6f3f8f24a9 diff --git a/packages/client/README.md b/packages/client/README.md index fe33ee2a20..a61299c2d2 100644 --- a/packages/client/README.md +++ b/packages/client/README.md @@ -64,6 +64,7 @@ The kernel packages boot and serve the page; the UI feature packages present it. | [`ui-skill/`](ui-skill/README.md) | Adds skill references to inline suggestions | — | | [`ui-reference/`](ui-reference/README.md) | Unified Web `@file` / `@session` reference source | — | | [`ui-secret-mask/`](ui-secret-mask/README.md) | Masks recognized credentials in sent user text | — | +| [`ui-agent-team/`](ui-agent-team/README.md) | Session Team switch and shared tasks below the Subagent tree | — | | [`ui-subagent/`](ui-subagent/README.md) | Provides subagent navigation, child transcript states, and inline references | — | | [`ui-jobs/`](ui-jobs/README.md) | Lists this session's background jobs with on-demand streaming record panels | — | | [`ui-schedule/`](ui-schedule/README.md) | Provides the sidebar Automation tasks page and right Sidebar task tab, which create, edit, run, pause, resume, and delete Host scheduled tasks, plus the Session header clock and row mark | — | diff --git a/packages/client/README.zh.md b/packages/client/README.zh.md index de0479c889..1e865a79c2 100644 --- a/packages/client/README.zh.md +++ b/packages/client/README.zh.md @@ -64,6 +64,7 @@ kind: "package-group" | [`ui-skill/`](ui-skill/README.zh.md) | 向内联建议添加 skill(技能)引用 | — | | [`ui-reference/`](ui-reference/README.zh.md) | 统一的 Web `@file` / `@session` 引用 source | — | | [`ui-secret-mask/`](ui-secret-mask/README.zh.md) | 遮挡已发送用户文本中可识别的凭据 | — | +| [`ui-agent-team/`](ui-agent-team/README.zh.md) | Session Team 开关与 Subagent 树下方的共享任务 | — | | [`ui-subagent/`](ui-subagent/README.zh.md) | 提供 subagent 导航、子级 transcript(文本记录)状态与内联引用 | — | | [`ui-jobs/`](ui-jobs/README.zh.md) | 列出本会话的后台任务,并提供按需展开的流式输出面板 | — | | [`ui-schedule/`](ui-schedule/README.zh.md) | 提供侧栏“自动化任务”页面与右侧栏任务页签,用于创建、编辑、立即运行、暂停、恢复和删除宿主的计划任务,并提供 Session 顶部时钟与行标记 | — | diff --git a/packages/client/locale/src/locales/de.ts b/packages/client/locale/src/locales/de.ts index dd88cf6fc4..cb0bb45579 100644 --- a/packages/client/locale/src/locales/de.ts +++ b/packages/client/locale/src/locales/de.ts @@ -33,6 +33,8 @@ export const de = { 'more': 'Mehr', 'collapse': 'Einklappen', 'expand': 'Ausklappen', + 'subtitle.expand': 'Beschreibung für {name} ausklappen', + 'subtitle.collapse': 'Beschreibung für {name} einklappen', 'back': 'Zurück', 'brand.localBuild': 'Lokaler DSH-Build', 'workspace.defaultName': 'Standardarbeitsbereich', diff --git a/packages/client/locale/src/locales/en.ts b/packages/client/locale/src/locales/en.ts index b649bbc3dc..47a03d69ef 100644 --- a/packages/client/locale/src/locales/en.ts +++ b/packages/client/locale/src/locales/en.ts @@ -32,6 +32,8 @@ export const en = { 'more': 'More', 'collapse': 'Collapse', 'expand': 'Expand', + 'subtitle.expand': 'Expand description for {name}', + 'subtitle.collapse': 'Collapse description for {name}', 'back': 'Back', 'brand.localBuild': 'DSH Local Build', 'workspace.defaultName': 'Default workspace', diff --git a/packages/client/locale/src/locales/es.ts b/packages/client/locale/src/locales/es.ts index 7eb95374e1..4fba984885 100644 --- a/packages/client/locale/src/locales/es.ts +++ b/packages/client/locale/src/locales/es.ts @@ -33,6 +33,8 @@ export const es = { 'more': 'Más', 'collapse': 'Contraer', 'expand': 'Expandir', + 'subtitle.expand': 'Expandir la descripción de {name}', + 'subtitle.collapse': 'Contraer la descripción de {name}', 'back': 'Atrás', 'brand.localBuild': 'Compilación local de DSH', 'workspace.defaultName': 'Espacio de trabajo predeterminado', diff --git a/packages/client/locale/src/locales/fr.ts b/packages/client/locale/src/locales/fr.ts index c57f1a3b11..f0e5030f41 100644 --- a/packages/client/locale/src/locales/fr.ts +++ b/packages/client/locale/src/locales/fr.ts @@ -33,6 +33,8 @@ export const fr = { 'more': 'Plus', 'collapse': 'Réduire', 'expand': 'Développer', + 'subtitle.expand': 'Développer la description de {name}', + 'subtitle.collapse': 'Réduire la description de {name}', 'back': 'Retour', 'brand.localBuild': 'Version locale de DSH', 'workspace.defaultName': 'Espace de travail par défaut', diff --git a/packages/client/locale/src/locales/ja.ts b/packages/client/locale/src/locales/ja.ts index 1186838c31..2b70cbb156 100644 --- a/packages/client/locale/src/locales/ja.ts +++ b/packages/client/locale/src/locales/ja.ts @@ -33,6 +33,8 @@ export const ja = { 'more': 'その他', 'collapse': '折りたたむ', 'expand': '展開', + 'subtitle.expand': '{name} の説明を展開', + 'subtitle.collapse': '{name} の説明を折りたたむ', 'back': '戻る', 'brand.localBuild': 'DSHローカルビルド', 'workspace.defaultName': 'デフォルトワークスペース', diff --git a/packages/client/locale/src/locales/pt.ts b/packages/client/locale/src/locales/pt.ts index 75cdb19c29..adf711152d 100644 --- a/packages/client/locale/src/locales/pt.ts +++ b/packages/client/locale/src/locales/pt.ts @@ -33,6 +33,8 @@ export const pt = { 'more': 'Mais', 'collapse': 'Recolher', 'expand': 'Expandir', + 'subtitle.expand': 'Expandir descrição de {name}', + 'subtitle.collapse': 'Recolher descrição de {name}', 'back': 'Voltar', 'brand.localBuild': 'Versão local do DSH', 'workspace.defaultName': 'Espaço de trabalho padrão', diff --git a/packages/client/locale/src/locales/ru.ts b/packages/client/locale/src/locales/ru.ts index a8a6d829da..e75620fe21 100644 --- a/packages/client/locale/src/locales/ru.ts +++ b/packages/client/locale/src/locales/ru.ts @@ -33,6 +33,8 @@ export const ru = { 'more': 'Ещё', 'collapse': 'Свернуть', 'expand': 'Развернуть', + 'subtitle.expand': 'Развернуть описание {name}', + 'subtitle.collapse': 'Свернуть описание {name}', 'back': 'Назад', 'brand.localBuild': 'Локальная сборка DSH', 'workspace.defaultName': 'Рабочая область по умолчанию', diff --git a/packages/client/locale/src/locales/zh.ts b/packages/client/locale/src/locales/zh.ts index 11ff789b8a..6350caae3c 100644 --- a/packages/client/locale/src/locales/zh.ts +++ b/packages/client/locale/src/locales/zh.ts @@ -30,6 +30,8 @@ export const zh = { 'more': '更多', 'collapse': '收起', 'expand': '展开', + 'subtitle.expand': '展开{name}的描述', + 'subtitle.collapse': '收起{name}的描述', 'back': '返回', 'brand.localBuild': 'DSH 本地构建', 'workspace.defaultName': '默认工作区', diff --git a/packages/client/ui-agent-team/README.i18n.yaml b/packages/client/ui-agent-team/README.i18n.yaml new file mode 100644 index 0000000000..f28fa028b1 --- /dev/null +++ b/packages/client/ui-agent-team/README.i18n.yaml @@ -0,0 +1,40 @@ +# Bilingual-pair consistency record for README.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 packages/client/ui-agent-team/README.md +/: + en: cecfe90c9079279f + zh: 58062db454093088 +/deepseek-ai-dsh-client-ui-agent-team: + en: 39c47e6e926425bf + zh: 0fd155c454355e3a +/deepseek-ai-dsh-client-ui-agent-team/summary: + en: 88103cec4c91d755 + zh: 6536e4307a5ce122 +/deepseek-ai-dsh-client-ui-agent-team/table-of-contents: + en: d152484eb41ac6b4 + zh: 09388d293f9be9cb +/deepseek-ai-dsh-client-ui-agent-team/use-this-package: + en: 0c148e32e4784884 + zh: 13a884a02f917947 +/deepseek-ai-dsh-client-ui-agent-team/use-this-package/inspect-the-task-board: + en: a39df39a750e5fb0 + zh: 2f7cb084010be7c7 +/deepseek-ai-dsh-client-ui-agent-team/understand-the-implementation: + en: 3d780acbe6ffbf5a + zh: 137789540af7a335 +/deepseek-ai-dsh-client-ui-agent-team/further-exploration: + en: 4d9214370681b02e + zh: 4acb21b9ecf25e85 +/deepseek-ai-dsh-client-ui-agent-team/model-experience: + en: 47ebeba07d72759d + zh: 8fe8133f6dd845a9 +/deepseek-ai-dsh-client-ui-agent-team/model-experience/kv-cache-effect: + en: b51b320f5e4dacfc + zh: 6695947c3f9fed28 +/deepseek-ai-dsh-client-ui-agent-team/known-limitations-and-deferred-work: + en: 8be0a045f2f42e9e + zh: 36f98d5cc1189f7a +/deepseek-ai-dsh-client-ui-agent-team/known-limitations-and-deferred-work/dev-note: + en: e67f633dc6fe7773 + zh: 3bb0c20c93f16f98 diff --git a/packages/client/ui-agent-team/README.md b/packages/client/ui-agent-team/README.md new file mode 100644 index 0000000000..bf17757698 --- /dev/null +++ b/packages/client/ui-agent-team/README.md @@ -0,0 +1,94 @@ +--- +description: "Inspect the shared task board from the Web conversation header." +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-agent-team + +English | [中文](README.zh.md) + +## Summary + +This package displays shared tasks below the Subagent tree in one header panel. Host projection frames keep task state current without a refresh control. Ordinary subagent navigation owns the member list and teammate conversations. The Web bundle loads this package; it adds no model-facing input. + +## Table of Contents + +- [Use this package](#use-this-package) +- [Understand the implementation](#understand-the-implementation) +- [Further Exploration](#further-exploration) +- [Model Experience](#model-experience) +- [Known Limitations and Deferred Work](#known-limitations-and-deferred-work) +- [Dev Note](#dev-note) + +----- + + +## Use this package + +In the root conversation’s model and reasoning menu, turn on **Use Agent Teams**. New Sessions default to off. While enabled, open the root conversation’s Subagent control to see members above shared tasks. Child conversations keep subagent navigation without a task board or Team switch, including before their subagent address is discovered. The same control remains available before the first member exists. Turning it off retains tasks and subagents. The switch calls `remote.agentTeams.setEnabled`, waits for the saved projection and reports failures through the menu host. + +The [Web bundle](../../bundle/web-app/README.md) loads this package by default. The Web Client loader mounts the `/client` export; the root Host export is inert, and the package has no user configuration fields. + +### Inspect the task board + +Ready pending tasks use idle, blocked pending tasks use warning, in-progress tasks use ongoing, and completed tasks use done. + +The read-only board shows task identity, owner Session id, blockers, readiness, and advisory write scopes. Long descriptions can expand. The heading shows the task count and an empty board has a short explanation. Agents mutate tasks through tools; the panel provides no mutation controls. A projection failure appears above the last valid tasks. + +----- + + +## Understand the implementation + +
+Implementation internals — click to expand + +The Client export registers its locale dictionaries and subagent-catalog and model-menu slots through Cordis effects; it mounts no Remote namespace. Disposing the plugin fiber removes its registrations. + +The Subagent catalog owns the shared panel, scrolling, hover, keyboard, and dismissal interactions. This package contributes the task section only below the root conversation’s tree. The board reads the current Session’s mode and tasks through `useProjection('agentTeam')`. It appears only when that projection is available and enabled. + +| File | Role | +|---|---| +| [`src/client/mount.ts`](src/client/mount.ts) | Locale, catalog-section, and model-menu registrations | +| [`src/client/TeamBoard.tsx`](src/client/TeamBoard.tsx) | Projection-derived task section | +| [`src/client/locales.ts`](src/client/locales.ts) | English and Chinese panel copy | +| [`src/index.ts`](src/index.ts) | Inert Host entry | + +
+ +----- + + +## Further Exploration + +- [Web bundle](../../bundle/web-app/README.md) — mounts the Client plugin by default. +- [Agent Teams service](../../team/agent-team/README.md) — authoritative task and projection behavior. +- [Conversation UI](../ui-conversation/README.md) — the stable header slot and addressed-subagent navigation surface. + +----- + + +## Model Experience + +None, as this browser projection registers no model-facing input. + +#### KV Cache effect + +No direct effect; the Team tools and ordinary conversation submission own any later model-visible use. + +## Known Limitations and Deferred Work + + + +- **No mailbox timeline** — the panel displays tasks, not messages. +- **No lifecycle or workspace controls** — the panel cannot spawn, rename, delete, or interrupt teammates, and write scopes remain advisory metadata. + + +### Dev Note + +
+Working context for maintainers — click to expand + +None. + +
diff --git a/packages/client/ui-agent-team/README.zh.md b/packages/client/ui-agent-team/README.zh.md new file mode 100644 index 0000000000..06435b37f2 --- /dev/null +++ b/packages/client/ui-agent-team/README.zh.md @@ -0,0 +1,94 @@ +--- +description: "从 Web 对话标题栏查看共享任务板。" +kind: "package-reference" +--- + +# @deepseek-ai/dsh-client-ui-agent-team + +[English](README.md) | 中文 + +## 概述 + +此包在同一个页头面板中,将共享任务显示在 Subagent 树下方。Host 投影帧持续更新任务状态,无需刷新控件。普通 subagent(子智能体)导航负责成员列表和队友对话。Web bundle 默认加载此包;它不添加模型可见输入。 + +## 目录 + +- [使用本包](#use-this-package) +- [理解实现](#understand-the-implementation) +- [进一步探索](#further-exploration) +- [模型体验](#model-experience) +- [已知限制与延期工作](#known-limitations-and-deferred-work) +- [开发备注](#dev-note) + +----- + + +## 使用本包 + +在根会话的模型与推理等级菜单中打开**使用智能体团队**。新 Session 默认关闭。开启后,通过根会话的 Subagent 入口同时查看上方的成员树和下方的共享任务。子会话保留子智能体导航,不显示任务板或 Team 开关,包括尚未发现子代理地址时。尚无成员时也保留根会话入口。关闭后保留任务和子代理。开关调用 `remote.agentTeams.setEnabled`,等待保存后的投影更新,失败由菜单宿主提示。 + +[Web bundle](../../bundle/web-app/README.zh.md) 默认加载本包。Web Client loader 挂载 `/client` export;root Host export 不执行行为,本包也没有用户配置字段。 + +### 查看任务板 + +可开始的 pending 任务使用 idle 灰点,被依赖阻塞的 pending 任务使用 warning 橙点,in-progress 任务使用 ongoing loading,completed 任务使用 done 绿点。 + +只读任务板显示任务身份、所有者 Session id、阻塞项、就绪状态和建议写入范围。长描述可展开。标题显示任务数量,空任务板提供简短说明。Agent 通过工具修改任务;面板不提供修改控件。投影失败显示在最后有效任务上方。 + +----- + + +## 理解实现 + +
+实现细节——点击展开 + +Client export 通过 Cordis effect 注册语言字典、子代理目录与模型菜单 slot;它不挂载 Remote namespace。销毁插件 fiber 会移除这些注册。 + +Subagent 目录负责共享面板的滚动、悬停、键盘和关闭交互。本包仅在根会话的树下方提供任务区。任务板通过 `useProjection('agentTeam')` 读取当前 Session 的模式与任务;仅在投影可用且已开启时显示。 + +| 文件 | 职责 | +|---|---| +| [`src/client/mount.ts`](src/client/mount.ts) | 语言字典、目录任务区和模型菜单注册 | +| [`src/client/TeamBoard.tsx`](src/client/TeamBoard.tsx) | 从投影派生的任务区 | +| [`src/client/locales.ts`](src/client/locales.ts) | 中英文 panel 文案 | +| [`src/index.ts`](src/index.ts) | 不执行行为的 Host entry | + +
+ +----- + + +## 进一步探索 + +- [Web bundle](../../bundle/web-app/README.zh.md)——默认挂载本 Client plugin。 +- [Agent Teams 服务](../../team/agent-team/README.zh.md) — 任务和投影行为的权威来源。 +- [会话 UI](../ui-conversation/README.zh.md)——稳定 header slot 与 addressed-subagent 导航表层。 + +----- + + +## 模型体验 + +无直接影响,因为该浏览器 projection 不注册面向模型的输入。 + +#### KV Cache 影响 + +无直接影响;Team 工具与普通会话提交负责后续任何模型可见用途。 + +## 已知限制与延期工作 + + + +- **无邮箱时间线** — 面板显示任务,不显示消息。 +- **没有 lifecycle 或 workspace control**——panel 不能 spawn、rename、delete 或 interrupt teammate,write scope 仍只是提示性 metadata。 + + +### 开发备注 + +
+维护者的工作上下文——点击展开 + +无。 + +
diff --git a/packages/experimental/client-ui-agent-team/locale/de.json b/packages/client/ui-agent-team/locale/de.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/de.json rename to packages/client/ui-agent-team/locale/de.json diff --git a/packages/experimental/client-ui-agent-team/locale/en.json b/packages/client/ui-agent-team/locale/en.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/en.json rename to packages/client/ui-agent-team/locale/en.json diff --git a/packages/experimental/client-ui-agent-team/locale/es.json b/packages/client/ui-agent-team/locale/es.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/es.json rename to packages/client/ui-agent-team/locale/es.json diff --git a/packages/experimental/client-ui-agent-team/locale/fr.json b/packages/client/ui-agent-team/locale/fr.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/fr.json rename to packages/client/ui-agent-team/locale/fr.json diff --git a/packages/experimental/client-ui-agent-team/locale/ja.json b/packages/client/ui-agent-team/locale/ja.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/ja.json rename to packages/client/ui-agent-team/locale/ja.json diff --git a/packages/experimental/client-ui-agent-team/locale/pt.json b/packages/client/ui-agent-team/locale/pt.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/pt.json rename to packages/client/ui-agent-team/locale/pt.json diff --git a/packages/experimental/client-ui-agent-team/locale/ru.json b/packages/client/ui-agent-team/locale/ru.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/ru.json rename to packages/client/ui-agent-team/locale/ru.json diff --git a/packages/experimental/client-ui-agent-team/locale/zh.json b/packages/client/ui-agent-team/locale/zh.json similarity index 100% rename from packages/experimental/client-ui-agent-team/locale/zh.json rename to packages/client/ui-agent-team/locale/zh.json diff --git a/packages/experimental/client-ui-agent-team/package.json b/packages/client/ui-agent-team/package.json similarity index 85% rename from packages/experimental/client-ui-agent-team/package.json rename to packages/client/ui-agent-team/package.json index e02e6bfe82..b086b67286 100644 --- a/packages/experimental/client-ui-agent-team/package.json +++ b/packages/client/ui-agent-team/package.json @@ -1,5 +1,5 @@ { - "name": "@deepseek-ai/dsh-experimental-client-ui-agent-team", + "name": "@deepseek-ai/dsh-client-ui-agent-team", "description": "Web shared task board for a subagent tree", "version": "0.2.2-alpha.2", "publishConfig": { @@ -8,7 +8,7 @@ "repository": { "type": "git", "url": "git+https://github.com/deepseek-ai/deepseek-harness.git", - "directory": "packages/experimental/client-ui-agent-team" + "directory": "packages/client/ui-agent-team" }, "type": "module", "main": "lib/index.js", @@ -54,12 +54,15 @@ "@deepseek-ai/dsh-client-ui-primitives": "workspace:*", "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", "@deepseek-ai/dsh-client-ui-slots": "workspace:*", - "@deepseek-ai/dsh-experimental-agent-team": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*", "@deepseek-ai/dsh-session": "workspace:*", "@testing-library/react": "^16.3.3", "@deepseek-ai/cordis": "workspace:~", "react": "^18.2.0", "react-dom": "^18.2.0", + "@deepseek-ai/dsh-client-ui-model-selection": "workspace:*", + "@deepseek-ai/dsh-api-remotes": "workspace:*", + "@deepseek-ai/dsh-client-ui-subagent": "workspace:*", "@deepseek-ai/dsh-agent": "workspace:*" }, "files": [ diff --git a/packages/experimental/client-ui-agent-team/src/client/TeamAction.module.css b/packages/client/ui-agent-team/src/client/TeamBoard.module.css similarity index 53% rename from packages/experimental/client-ui-agent-team/src/client/TeamAction.module.css rename to packages/client/ui-agent-team/src/client/TeamBoard.module.css index f95c65052e..8fedb02354 100644 --- a/packages/experimental/client-ui-agent-team/src/client/TeamAction.module.css +++ b/packages/client/ui-agent-team/src/client/TeamBoard.module.css @@ -1,40 +1,3 @@ -.root { - position: relative; -} - -.trigger { - display: inline-flex; - align-items: center; - gap: 5px; - min-height: 28px; - padding: 3px 7px; - border: 0; - border-radius: 6px; - background: transparent; - color: var(--dsw-alias-label-tertiary); - font-size: 12px; - cursor: pointer; -} - -.trigger:focus-visible { - color: var(--dsw-alias-label-primary); -} - -@media (hover: hover) { - .trigger:hover { - color: var(--dsw-alias-label-primary); - } -} - -/* Second sacrifice after the preset label: past this title-row width the - trigger keeps only its icon. TeamAction reads the collapsed state off this - rule to switch from hover-open to click-only. */ -@container (max-width: 480px) { - .triggerLabel { - display: none; - } -} - .count { color: var(--dsw-alias-label-caption); font-size: 12px; @@ -43,47 +6,10 @@ line-height: 16px; } -.panel { - --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2); - --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2); - - display: flex; - flex-direction: column; - position: fixed; - z-index: 100; - box-sizing: border-box; - width: min(500px, 100vw - 32px); - max-height: min(680px, calc(100vh - 32px)); - padding: 8px 2px 0; - overflow: hidden; - border: 0; - border-radius: 12px; - --dsw-elevation-stroke-color: var(--dsw-alias-border-l1); - box-shadow: var(--dsw-elevation-prominent); -} - -.panel::before { - content: ''; - position: absolute; - z-index: -1; - inset: 0; - border-radius: 12px; - background: var(--dsw-specific-menu); - backdrop-filter: var(--dsw-menu-backdrop-filter); -} - -.panelCompact { - width: min(320px, 100vw - 32px); -} - -.body { - --dsh-scrollbar-track-margin: 8px; - - flex: 1 1 auto; - min-height: 0; - padding: 0 9px 16px 14px; - overflow-y: auto; - scrollbar-gutter: stable; +.board { + margin-top: 8px; + padding: 8px 8px 4px; + border-top: 0.5px solid var(--dsw-alias-border-l2); } .taskTitle { @@ -98,29 +24,22 @@ font-weight: 500; } -.panel h3 { +.board h3 { display: flex; align-items: center; gap: 8px; - margin: 16px 0 8px 4px; + margin: 0 0 8px; color: var(--dsw-alias-label-primary); font-size: 13px; font-weight: 500; } -/* Without a toolbar the first heading sits 16px under the panel edge: 8px - panel padding plus this trimmed margin. */ -.body section:first-of-type h3 { - margin-top: 8px; -} - .meta { color: var(--dsw-alias-label-tertiary); font-size: 11px; } -.error, -.warning { +.error { color: var(--dsw-alias-state-error-primary); } @@ -130,10 +49,8 @@ gap: 7px; } -/* Empty task board: a plain description in place of the section heading, - flush with the body's bottom padding. */ .emptyNotice { - margin: 16px 0 0 4px; + margin: 0 0 8px; color: var(--dsw-alias-label-tertiary); font-size: 12px; } @@ -142,7 +59,7 @@ overflow-wrap: anywhere; padding: 11px 13px; border: 0.5px solid var(--dsw-alias-border-l2); - border-radius: 9px; + border-radius: var(--dsw-radius-lg); background: var(--dsw-alias-bg-layer-2); } diff --git a/packages/client/ui-agent-team/src/client/TeamBoard.tsx b/packages/client/ui-agent-team/src/client/TeamBoard.tsx new file mode 100644 index 0000000000..6d29983544 --- /dev/null +++ b/packages/client/ui-agent-team/src/client/TeamBoard.tsx @@ -0,0 +1,117 @@ +import { useLayoutEffect, useRef, useState } from 'react' +import type { + TeamTaskView as TeamTask, +} from '@deepseek-ai/dsh-agent-team/client' +import type {} from '@deepseek-ai/dsh-api-session-controller/client' +import { + IconChevronDownOutlineRegular, + StateDot, type StateDotState, +} from '@deepseek-ai/dsh-client-ui-primitives' +import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' +import type { TranslateNS } from '@deepseek-ai/dsh-client-ui-slots' +import type {} from '@deepseek-ai/dsh-client-ui-subagent/client' +import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' +import { NS, type TeamKey } from './locales.ts' +import css from './TeamBoard.module.css' + +/** Full props of the task board below the shared subagent catalog. */ +export type TeamBoardProps = + PropsRuntime<'conversation.session.header.actions.subagent.footer'> & PropsLocale + +function statusKey(status: TeamTask['status']): TeamKey { + switch (status) { + case 'pending': return 'status.pending' + case 'in_progress': return 'status.in_progress' + case 'completed': return 'status.completed' + /* v8 ignore next -- Team views omit deleted task tombstones. */ + case 'deleted': return 'status.completed' + } +} + +function taskDotState(task: TeamTask): StateDotState { + switch (task.status) { + case 'pending': return task.ready ? 'idle' : 'warning' + case 'in_progress': return 'ongoing' + case 'completed': return 'done' + /* v8 ignore next -- Team views omit deleted task tombstones. */ + case 'deleted': return 'idle' + } +} + +/** Task card with a two-line description clamp expanded from a toggle in the meta row. */ +function TaskCard({ task, t }: { + task: TeamTask + t: TranslateNS +}) { + const owner = task.ownerId + const [expanded, setExpanded] = useState(false) + const [clamped, setClamped] = useState(false) + const textRef = useRef(null) + useLayoutEffect(() => { + if (expanded) return + const paragraph = textRef.current + /* v8 ignore next -- the paragraph mounts in the same commit as the effect. */ + if (paragraph === null) return + const measure = (): void => { setClamped(paragraph.scrollHeight > paragraph.clientHeight + 1) } + measure() + if (typeof ResizeObserver === 'undefined') return + const observer = new ResizeObserver(measure) + observer.observe(paragraph) + return () => { observer.disconnect() } + }, [task.description, expanded]) + return ( +
+
+ {task.subject} + + + {t(statusKey(task.status))} + +
+

{task.description}

+
+ {(clamped || expanded) && ( + + )} + {task.id} + {t('owner')}: {owner ?? t('unowned')} + {task.status === 'pending' && {task.ready ? t('ready') : t('blocked')}} + {task.blockedBy.length > 0 && {t('blockedBy')}: {task.blockedBy.join(', ')}} + {task.writeScopes.length > 0 && {t('writeScopes')}: {task.writeScopes.join(', ')}} +
+
+ ) +} + +/** Render the root conversation's read-only task board from its current projection. */ +export function TeamBoard({ + useProjection, t, +}: TeamBoardProps) { + const team = useProjection('agentTeam') + if (!team?.enabled) return null + return ( +
+

{t('tasks')}{team.tasks.length}

+ {team.failure !== undefined && ( +
{t('failure', { message: team.failure })}
+ )} + {team.tasks.length === 0 + ?

{t('empty')}

+ : ( +
+ {team.tasks.map(task => ( + + ))} +
+ )} +
+ ) +} diff --git a/packages/client/ui-agent-team/src/client/TeamModeToggle.module.css b/packages/client/ui-agent-team/src/client/TeamModeToggle.module.css new file mode 100644 index 0000000000..ef588891dc --- /dev/null +++ b/packages/client/ui-agent-team/src/client/TeamModeToggle.module.css @@ -0,0 +1,11 @@ +.row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + min-height: 34px; + padding: 0 8px; + color: var(--dsw-alias-label-primary); + font-size: 13px; + line-height: 20px; +} diff --git a/packages/client/ui-agent-team/src/client/TeamModeToggle.tsx b/packages/client/ui-agent-team/src/client/TeamModeToggle.tsx new file mode 100644 index 0000000000..ba613752bc --- /dev/null +++ b/packages/client/ui-agent-team/src/client/TeamModeToggle.tsx @@ -0,0 +1,52 @@ +/** Session-owned Agent Teams selection in the model and reasoning menu. */ + +import { useState } from 'react' +import { Switch } from '@deepseek-ai/dsh-client-ui-primitives' +import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots' +import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client' +import type {} from '@deepseek-ai/dsh-agent-team/client' +import type { NS } from './locales.ts' +import css from './TeamModeToggle.module.css' + +/** Session command supplied by the Client plugin. */ +export interface TeamModeToggleInjected { + /** Save the selection; null means success, otherwise the string describes the failure. */ + selectTeamMode: (enabled: boolean) => Promise +} + +/** Full props of the model-menu Team switch. */ +export type TeamModeToggleProps = PropsRuntime<'conversation.input.model.options'> + & PropsLocale & TeamModeToggleInjected + +/** + * Render the saved selection without an optimistic update. + * @param props - Session projection, menu state, localized copy, and selection command. + * @returns the Team switch when the capability projection is available. + */ +export function TeamModeToggle(props: TeamModeToggleProps) { + const addressedChild = props.useSession(snapshot => snapshot.subagent !== null) + const listedChild = props.useSessions(state => state.byId[props.sessionId]?.origin === 'subagent') + if (addressedChild || listedChild) return null + return +} + +/** Read and change the selection only for root Sessions. */ +function RootTeamModeToggle({ useProjection, locked, reportFailure, selectTeamMode, t }: TeamModeToggleProps) { + const mode = useProjection('agentTeam') + const [pending, setPending] = useState(false) + if (mode === undefined) return null + const select = (enabled: boolean): void => { + setPending(true) + void selectTeamMode(enabled).then((failure) => { + if (failure !== null) reportFailure(t('mode.failed', { message: failure })) + }, (error: unknown) => { + reportFailure(t('mode.failed', { message: error instanceof Error ? error.message : String(error) })) + }).finally(() => { setPending(false) }) + } + return ( +
+ {t('mode.label')} + +
+ ) +} diff --git a/packages/experimental/client-ui-agent-team/src/client/index.ts b/packages/client/ui-agent-team/src/client/index.ts similarity index 61% rename from packages/experimental/client-ui-agent-team/src/client/index.ts rename to packages/client/ui-agent-team/src/client/index.ts index 63655bc6d6..23de9e7ee5 100644 --- a/packages/experimental/client-ui-agent-team/src/client/index.ts +++ b/packages/client/ui-agent-team/src/client/index.ts @@ -1,14 +1,14 @@ -/** Browser entry registering the task-board conversation-header action. */ +/** Browser entry registering the task-board sections in the subagent catalog. */ import type { Context as ClientContext } from '@deepseek-ai/cordis' import { registerAgentTeamUi } from './mount.ts' export { inject } from './mount.ts' -export type { TeamActionProps } from './TeamAction.tsx' +export type { TeamBoardProps } from './TeamBoard.tsx' export type { TeamKey } from './locales.ts' /** - * Register the task-board locale dictionaries and header action on the Client Context. + * Register the task-board locale dictionaries and catalog sections on the Client Context. * @param ctx - Client Context with the declared `inject` services available. */ export function apply(ctx: ClientContext): void { diff --git a/packages/experimental/client-ui-agent-team/src/client/locales.ts b/packages/client/ui-agent-team/src/client/locales.ts similarity index 84% rename from packages/experimental/client-ui-agent-team/src/client/locales.ts rename to packages/client/ui-agent-team/src/client/locales.ts index a054ea400c..c7ebca8cf3 100644 --- a/packages/experimental/client-ui-agent-team/src/client/locales.ts +++ b/packages/client/ui-agent-team/src/client/locales.ts @@ -6,6 +6,8 @@ export const NS = 'agent-team' /** Simplified Chinese dictionary and key source. */ export const zh = { trigger: '任务板', + 'mode.label': '使用智能体团队', + 'mode.failed': '无法确认智能体团队设置已保存,请重试:{message}', loading: '正在加载任务板…', unavailable: '任务板暂不可用', failure: '任务记录无效:{message}', @@ -31,6 +33,8 @@ export type TeamKey = keyof typeof zh /** English dictionary checked against the Chinese key set. */ export const en = { trigger: 'Task board', + 'mode.label': 'Use Agent Teams', + 'mode.failed': 'Could not confirm Agent Teams was saved. Try again: {message}', loading: 'Loading task board…', unavailable: 'Task board is unavailable', failure: 'Invalid persisted task record: {message}', @@ -52,6 +56,8 @@ export const en = { /** Japanese copy, checked against the English keys. */ export const ja = { + 'mode.label': 'エージェントチームを使用', + 'mode.failed': 'エージェントチーム設定の保存を確認できませんでした。再試行してください: {message}', 'trigger': 'タスクボード', 'loading': 'タスクボードを読み込み中…', 'unavailable': 'タスクボードは利用できません', @@ -74,6 +80,8 @@ export const ja = { /** Spanish dictionary, checked against the English keys. */ export const es = { + 'mode.label': 'Usar equipos de agentes', + 'mode.failed': 'No se pudo confirmar que se guardó la configuración del equipo. Inténtalo de nuevo: {message}', trigger: 'Tablero de tareas', loading: 'Cargando tablero de tareas…', unavailable: 'El tablero de tareas no está disponible', @@ -96,6 +104,8 @@ export const es = { /** French dictionary, checked against the English keys. */ export const fr = { + 'mode.label': 'Utiliser les équipes d’agents', + 'mode.failed': 'Impossible de confirmer l’enregistrement des équipes d’agents. Réessayez : {message}', trigger: 'Tableau des tâches', loading: 'Chargement du tableau des tâches…', unavailable: 'Le tableau des tâches n’est pas disponible', @@ -118,6 +128,8 @@ export const fr = { /** German dictionary, checked against the English keys. */ export const de = { + 'mode.label': 'Agententeams verwenden', + 'mode.failed': 'Das Speichern der Agententeams konnte nicht bestätigt werden. Erneut versuchen: {message}', trigger: 'Aufgabenübersicht', loading: 'Aufgabenübersicht wird geladen…', unavailable: 'Aufgabenübersicht ist nicht verfügbar', @@ -140,6 +152,8 @@ export const de = { /** Russian dictionary, checked against the English keys. */ export const ru = { + 'mode.label': 'Использовать команды агентов', + 'mode.failed': 'Не удалось подтвердить сохранение команд агентов. Повторите попытку: {message}', trigger: 'Доска задач', loading: 'Загрузка доски задач…', unavailable: 'Доска задач недоступна', @@ -162,6 +176,8 @@ export const ru = { /** Portuguese dictionary, checked against the English keys. */ export const pt = { + 'mode.label': 'Usar equipes de agentes', + 'mode.failed': 'Não foi possível confirmar que as equipes de agentes foram salvas. Tente novamente: {message}', trigger: 'Quadro de tarefas', loading: 'Carregando quadro de tarefas…', unavailable: 'O quadro de tarefas não está disponível', diff --git a/packages/experimental/client-ui-agent-team/src/client/mount.ts b/packages/client/ui-agent-team/src/client/mount.ts similarity index 50% rename from packages/experimental/client-ui-agent-team/src/client/mount.ts rename to packages/client/ui-agent-team/src/client/mount.ts index 1b9d7cd568..489d50eb6e 100644 --- a/packages/experimental/client-ui-agent-team/src/client/mount.ts +++ b/packages/client/ui-agent-team/src/client/mount.ts @@ -2,10 +2,14 @@ import type { Context as ClientContext } from '@deepseek-ai/cordis' import type {} from '@deepseek-ai/dsh-api-session-controller/client' +import type {} from '@deepseek-ai/dsh-client-ui-subagent/client' import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' import type {} from '@deepseek-ai/dsh-client-locale/client' import type {} from '@deepseek-ai/dsh-client-ui-renderer/client' -import { TeamAction } from './TeamAction.tsx' +import type {} from '@deepseek-ai/dsh-api-remotes/client' +import type {} from '@deepseek-ai/dsh-client-ui-model-selection/client' +import { TeamModeToggle, type TeamModeToggleInjected } from './TeamModeToggle.tsx' +import { TeamBoard } from './TeamBoard.tsx' import { en, ja, es, fr, de, ru, pt, NS, zh, type TeamKey } from './locales.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { @@ -19,19 +23,26 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { export const inject = ['slots', 'locale'] /** - * Register the task-board locale dictionaries and conversation-header action. + * Register the task-board dictionaries and subagent catalog sections. * The panel reads the root Session’s `agentTeam` projection through standard Session hooks. * @param ctx - Client Context carrying the injected locale and slot services. */ export function registerAgentTeamUi(ctx: ClientContext): void { ctx.effect(() => ctx.locale.register(NS, { zh, en, ja, es, fr, de, ru, pt }), 'client-ui-agent-team: dictionaries') - ctx.slots.inject( - 'conversation.session.header.actions', - () => ctx.slots.register({ - name: 'conversation.session.header.actions', + ctx.inject(['remote', 'remote.agentTeams'], (scope) => { + scope.slots.inject('conversation.input.model.options', () => scope.slots.register({ + name: 'conversation.input.model.options', id: 'agent-team', - order: -20, locale: NS, - }, TeamAction), - ) + inject: (sessionId): TeamModeToggleInjected => ({ + selectTeamMode: async (enabled) => { + const response = await scope.remote.agentTeams.setEnabled(sessionId, enabled) + return response.ok ? null : response.error.message + }, + }), + }, TeamModeToggle)) + }) + ctx.slots.inject('conversation.session.header.actions.subagent.footer', () => ctx.slots.register({ + name: 'conversation.session.header.actions.subagent.footer', id: 'agent-team', locale: NS, + }, TeamBoard)) } diff --git a/packages/experimental/client-ui-agent-team/src/css-modules.d.ts b/packages/client/ui-agent-team/src/css-modules.d.ts similarity index 100% rename from packages/experimental/client-ui-agent-team/src/css-modules.d.ts rename to packages/client/ui-agent-team/src/css-modules.d.ts diff --git a/packages/experimental/client-ui-agent-team/src/index.ts b/packages/client/ui-agent-team/src/index.ts similarity index 100% rename from packages/experimental/client-ui-agent-team/src/index.ts rename to packages/client/ui-agent-team/src/index.ts diff --git a/packages/client/ui-agent-team/tests/browser-plugin.client.spec.ts b/packages/client/ui-agent-team/tests/browser-plugin.client.spec.ts new file mode 100644 index 0000000000..dbdee096ce --- /dev/null +++ b/packages/client/ui-agent-team/tests/browser-plugin.client.spec.ts @@ -0,0 +1,92 @@ +import { Context } from '@deepseek-ai/cordis' +import { describe, expect, it, onTestFinished, vi } from 'vitest' +import { SessionId } from '@deepseek-ai/dsh-session/types' +import { RemoteError, TestRemote } from '@deepseek-ai/dsh-client-test-runtime' +import { TeamModeToggle, type TeamModeToggleInjected } from '../src/client/TeamModeToggle.tsx' +import { LocaleRuntime } from '@deepseek-ai/dsh-client-locale/client' +import { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client' +import { TeamBoard } from '../src/client/TeamBoard.tsx' +import { apply, inject } from '../src/client/index.ts' +import { apply as nodeApply } from '../src/index.ts' + +async function bench() { + const ctx = new Context() + onTestFinished(() => ctx.fiber.dispose()) + ctx.provide('locale', new LocaleRuntime(ctx)) + await ctx.plugin(SlotRegistry).await() + const collapseHeader = ctx.slots.register({ + name: 'root', + children: { + 'conversation.session.header.actions.subagent.footer': { kind: 'list', scope: 'session' }, + 'conversation.input.model': { kind: 'single', scope: 'session' }, + }, + } as never, () => null) + const fiber = ctx.plugin({ inject: [...inject], apply }) + await fiber.await() + const entry = () => ctx.slots.entries('conversation.session.header.actions.subagent.footer') + .find(candidate => candidate.component === TeamBoard) + return { ctx, fiber, entry, collapseHeader } +} + +describe('task-board browser plugin', () => { + it('registers a disposable root task-board section without navigation or Team Remote services', async () => { + const b = await bench() + expect(inject).toEqual(['slots', 'locale']) + expect(b.entry()).toMatchObject({ + options: { id: 'agent-team' }, + locale: 'agent-team', + }) + expect(b.entry()!.inject).toBeUndefined() + expect(b.ctx.slots.entries('conversation.session.header.actions')).toHaveLength(0) + const t = b.ctx.locale.bind('agent-team') + expect(t('trigger')).toBe('Task board') + await b.fiber.dispose() + expect(b.entry()).toBeUndefined() + expect(t('empty')).toBe('empty') + }) + + it('re-registers when the conversation header slot is declared again', async () => { + const b = await bench() + expect(b.entry()).toBeDefined() + b.collapseHeader() + expect(b.entry()).toBeUndefined() + b.ctx.slots.register({ + name: 'root', + children: { 'conversation.session.header.actions.subagent.footer': { kind: 'list', scope: 'session' } }, + } as never, () => null) + await Promise.resolve() + expect(b.entry()).toBeDefined() + await b.fiber.dispose() + }) + + it('registers the mode switch when Agent Teams arrives and reports each execution outcome', async () => { + const b = await bench() + b.ctx.slots.register({ + name: 'conversation.input.model', + children: { 'conversation.input.model.options': { kind: 'list', scope: 'session' } }, + } as never, () => null) + const entry = () => b.ctx.slots.entries('conversation.input.model.options') + .find(candidate => candidate.component === TeamModeToggle) + expect(entry()).toBeUndefined() + const setEnabled = vi.fn() + new TestRemote(b.ctx, { agentTeams: { setEnabled } }) + await vi.waitFor(() => { expect(entry()).toBeDefined() }) + const injected = entry()!.inject as (sessionId: SessionId) => Record + const value: object = injected(SessionId('chosen-session')) + const mode = value as TeamModeToggleInjected + setEnabled.mockResolvedValueOnce({ ok: true, value: undefined }) + await expect(mode.selectTeamMode(true)).resolves.toBeNull() + expect(setEnabled).toHaveBeenLastCalledWith('chosen-session', true) + setEnabled.mockResolvedValueOnce({ ok: true, value: undefined }) + await expect(mode.selectTeamMode(false)).resolves.toBeNull() + expect(setEnabled).toHaveBeenLastCalledWith('chosen-session', false) + setEnabled.mockResolvedValueOnce({ ok: false, error: new RemoteError('gateway/internal', 'connection failed', {}) }) + await expect(mode.selectTeamMode(true)).resolves.toBe('connection failed') + await b.fiber.dispose() + expect(entry()).toBeUndefined() + }) + + it('keeps the node half inert', () => { + expect(() => { nodeApply() }).not.toThrow() + }) +}) diff --git a/packages/client/ui-agent-team/tests/team-board.client.spec.tsx b/packages/client/ui-agent-team/tests/team-board.client.spec.tsx new file mode 100644 index 0000000000..0c45bf100e --- /dev/null +++ b/packages/client/ui-agent-team/tests/team-board.client.spec.tsx @@ -0,0 +1,286 @@ +// @vitest-environment jsdom + +import { afterEach, describe, expect, it, vi } from 'vitest' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import type { SessionId } from '@deepseek-ai/dsh-session/types' +import type { + TeamProjection, TeamTaskId, TeamTaskView as TeamTask, +} from '@deepseek-ai/dsh-agent-team/client' +import type { SessionListState, SessionSummary } from '@deepseek-ai/dsh-api-session-controller/client' +import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' +import { bindSnapshotSelector, makeTranslate } from '@deepseek-ai/dsh-client-test-runtime' +import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts' +import { TeamBoard, type TeamBoardProps } from '../src/client/TeamBoard.tsx' +import { zh } from '../src/client/locales.ts' + +afterEach(() => { + cleanup() + vi.useRealTimers() + vi.restoreAllMocks() + vi.unstubAllGlobals() +}) + +const SESSION = 'lead' as SessionId +const WORKER = 'worker-id' as SessionId +const NESTED = 'nested-id' as SessionId +const TASK_1 = 'task-1' as TeamTaskId +const TASK_2 = 'task-2' as TeamTaskId +const task: TeamTask = { + id: TASK_1, + revision: 1, + subject: 'Implement runtime', + description: 'Build the Team runtime', + status: 'in_progress', + ownerId: SESSION, + blockedBy: [], + writeScopes: ['src'], + ready: false, + writeScopeWarnings: ['write scopes overlap with task-2'], +} +const team: TeamProjection = { enabled: true, tasks: [task] } + +type Projections = SessionListState['projectionsBySession'] + +function summary(id: SessionId, title?: string): SessionSummary { + return { + id, ...(title === undefined ? {} : { title }), displayTitle: title ?? id, + running: false, retainedBy: {}, blank: false, updatedAt: 0, + } +} + +function bench(options: { + projections?: Projections + sessionId?: SessionId + byId?: Record +} = {}) { + const sessionId = options.sessionId ?? SESSION + const byId = options.byId ?? { [SESSION]: summary(SESSION) } + const sessions = createSnapshotStore({ + ids: Object.keys(byId) as SessionId[], byId, phase: 'ready', + projectionsBySession: options.projections ?? { [SESSION]: { state: 'ready', error: null, values: { agentTeam: team } } }, + }) + const useProjection = ((_key: 'agentTeam', selector?: (value: TeamProjection | undefined) => unknown) => + bindSnapshotSelector(sessions)((state) => { + const value = state.projectionsBySession[sessionId]?.values.agentTeam + return selector === undefined ? value : selector(value) + })) as TeamBoardProps['useProjection'] + const props = { + sessionId, + useProjection, + useSessions: bindSnapshotSelector(sessions), + t: makeTranslate(zh, commonZh), + } as TeamBoardProps + return { props, sessions } +} + +function setProjectionSnapshot( + sessions: ReturnType['sessions'], + sessionId: SessionId, + snapshot: Projections[SessionId], +): void { + act(() => { + const current = sessions.getSnapshot() + sessions.set({ ...current, projectionsBySession: { ...current.projectionsBySession, [sessionId]: snapshot } }) + }) +} + +function setProjection(sessions: ReturnType['sessions'], sessionId: SessionId, value: TeamProjection): void { + setProjectionSnapshot(sessions, sessionId, { state: 'ready', error: null, values: { agentTeam: value } }) +} + +describe('TeamBoard', () => { + it('hides retained tasks when Team is disabled and shows them again after enabling', () => { + const b = bench() + render() + expect(screen.getByText(task.subject)).toBeTruthy() + setProjectionSnapshot(b.sessions, SESSION, { state: 'ready', error: null, values: { agentTeam: { ...team, enabled: false } } }) + expect(screen.queryByRole('button', { name: zh.trigger })).toBeNull() + expect(screen.queryByText(task.subject)).toBeNull() + setProjection(b.sessions, SESSION, team) + expect(screen.getByText(task.subject)).toBeTruthy() + }) + + it('shows the task board without a roster or a subagent catalog', () => { + const b = bench({ byId: { [SESSION]: summary(SESSION, 'Open the task board.') } }) + render() + expect(screen.getByText(task.subject)).toBeTruthy() + expect(screen.getByText('负责人: lead')).toBeTruthy() + expect(screen.getByRole('heading', { name: '共享任务1' })).toBeTruthy() + expect(screen.queryByText('historical member')).toBeNull() + expect(screen.queryByText('old failure')).toBeNull() + expect(screen.queryByRole('status')).toBeNull() + expect(screen.queryByRole('button', { name: /打开成员会话/u })).toBeNull() + }) + + it('keeps owner ids when catalog descriptions and Session titles change', () => { + const b = bench({ + byId: { + [SESSION]: summary(SESSION), + [WORKER]: { ...summary(WORKER), origin: 'subagent', parentId: SESSION }, + [NESTED]: { ...summary(NESTED), origin: 'subagent', parentId: WORKER }, + }, + projections: { + [SESSION]: { state: 'ready', error: null, values: { + agentTeam: { ...team, tasks: [{ ...task, ownerId: NESTED }] }, + } }, + [WORKER]: { state: 'ready', error: null, values: { + subagentCatalog: [{ id: NESTED, createdAt: 0, mode: 'continuable', label: 'Nested implementer' }], + } }, + }, + }) + render() + expect(screen.getByText('负责人: nested-id')).toBeTruthy() + act(() => { b.sessions.update((draft) => { draft.byId[NESTED] = { ...draft.byId[NESTED]!, title: 'Runtime work' } }) }) + expect(screen.getByText('负责人: nested-id')).toBeTruthy() + act(() => { b.sessions.update((draft) => { draft.projectionsBySession = { [SESSION]: draft.projectionsBySession[SESSION]! } }) }) + expect(screen.getByText('负责人: nested-id')).toBeTruthy() + act(() => { b.sessions.set({ + ...b.sessions.getSnapshot(), byId: { [SESSION]: summary(SESSION), [WORKER]: summary(WORKER) }, + }) }) + expect(screen.getByText(`负责人: ${NESTED}`)).toBeTruthy() + }) + + it('shows owner ids when the owner has no Session summary', () => { + const b = bench({ projections: { [SESSION]: { state: 'ready', error: null, values: { + agentTeam: { ...team, tasks: [{ ...task, ownerId: WORKER }] }, + subagentCatalog: [{ id: WORKER, createdAt: 0, mode: 'continuable', label: 'Current responsibility' }], + } } } }) + render() + expect(screen.getByText('负责人: worker-id')).toBeTruthy() + setProjection(b.sessions, SESSION, { ...team, tasks: [{ ...task, ownerId: WORKER }] }) + expect(screen.getByText(`负责人: ${WORKER}`)).toBeTruthy() + }) + + it('applies later projection frames without a refresh action', () => { + const b = bench() + render() + expect(screen.queryByText('write scopes overlap with task-2')).toBeNull() + expect(screen.queryByRole('button', { name: /刷新|Refresh/u })).toBeNull() + setProjection(b.sessions, SESSION, { + ...team, + tasks: [task, { ...task, id: TASK_2, subject: 'Pushed task', status: 'pending', ready: true, writeScopeWarnings: [] }], + }) + expect(screen.getByText('Pushed task')).toBeTruthy() + expect(screen.getByRole('heading', { name: '共享任务2' })).toBeTruthy() + }) + + it('accepts current Session baselines and capability updates', () => { + const b = bench({ + projections: {}, + }) + render() + expect(screen.queryByRole('button', { name: zh.trigger })).toBeNull() + setProjectionSnapshot(b.sessions, SESSION, { state: 'idle', error: null, values: { agentTeam: team } }) + expect(screen.getByText(task.subject)).toBeTruthy() + expect(screen.queryByRole('status')).toBeNull() + setProjectionSnapshot(b.sessions, SESSION, { state: 'idle', error: null, values: {} }) + expect(screen.queryByRole('region')).toBeNull() + act(() => { b.sessions.set({ ...b.sessions.getSnapshot(), phase: 'pending' }) }) + expect(screen.queryByRole('region')).toBeNull() + setProjection(b.sessions, SESSION, { enabled: true, tasks: [] }) + expect(screen.getByText(zh.empty)).toBeTruthy() + }) + + it('reads only the current Session task projection', () => { + const b = bench({ sessionId: WORKER }) + render() + expect(screen.queryByRole('region')).toBeNull() + setProjection(b.sessions, WORKER, { ...team, tasks: [{ ...task, subject: 'Current task' }] }) + expect(screen.getByText('Current task')).toBeTruthy() + expect(screen.queryByText(task.subject)).toBeNull() + setProjection(b.sessions, SESSION, { ...team, tasks: [{ ...task, subject: 'Other task' }] }) + expect(screen.getByText('Current task')).toBeTruthy() + expect(screen.queryByText('Other task')).toBeNull() + }) + + it('surfaces projection failure beside the last valid tasks', () => { + const b = bench({ projections: { [SESSION]: { state: 'ready', error: null, values: { + agentTeam: { ...team, failure: 'revision is not contiguous' }, + } } } }) + render() + expect(screen.getByRole('alert').textContent).toBe('任务记录无效:revision is not contiguous') + expect(screen.getByText(task.subject)).toBeTruthy() + }) + + it('renders ready, blocked, completed, and unassigned tasks', () => { + const { ownerId: _ownerId, ...unownedTask } = task + const b = bench({ projections: { [SESSION]: { state: 'ready', error: null, values: { agentTeam: { + ...team, + tasks: [ + { ...unownedTask, id: 'ready-task' as TeamTaskId, status: 'pending', ready: true }, + { ...unownedTask, id: 'blocked-task' as TeamTaskId, status: 'pending', ready: false, blockedBy: [TASK_1] }, + { ...task, id: 'completed-task' as TeamTaskId, status: 'completed', ownerId: WORKER }, + ], + } } } } }) + render() + expect(screen.getByText(zh.ready)).toBeTruthy() + expect(screen.getByText(zh.blocked)).toBeTruthy() + expect(screen.getAllByText('负责人: 未分配')).toHaveLength(2) + expect(screen.getByText(`负责人: ${WORKER}`)).toBeTruthy() + const tasks = [...document.querySelectorAll('article')] + expect(tasks.map(card => card.querySelector('[data-state]')?.getAttribute('data-state'))) + .toEqual(['idle', 'warning', 'done']) + for (const card of tasks) expect(card.querySelector('button, input, select, textarea')).toBeNull() + }) + + it.each([true, false])('clamps a long task description behind an expand toggle (ResizeObserver: %s)', async (resizeObserver) => { + if (!resizeObserver) vi.stubGlobal('ResizeObserver', undefined) + const scrollHeight = vi.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(120) + const clientHeight = vi.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValue(60) + try { + render() + const expand = await screen.findByRole('button', { name: zh['task.expand'] }) + const description = screen.getByText('Build the Team runtime') + expect(expand.getAttribute('aria-expanded')).toBe('false') + expect(description.className).not.toBe('') + + fireEvent.click(expand) + const collapse = screen.getByRole('button', { name: zh['task.collapse'] }) + expect(collapse.getAttribute('aria-expanded')).toBe('true') + expect(screen.getByText('Build the Team runtime').className).toBe('') + + fireEvent.click(collapse) + expect(screen.getByRole('button', { name: zh['task.expand'] })).toBeTruthy() + } finally { + scrollHeight.mockRestore() + clientHeight.mockRestore() + } + }) + + it('keeps projection updates live without polling while pinned', async () => { + vi.useFakeTimers() + const b = bench() + render() + await act(async () => { await vi.advanceTimersByTimeAsync(60_000) }) + setProjection(b.sessions, SESSION, { ...team, tasks: [{ ...task, subject: 'Pushed while pinned' }] }) + expect(screen.getByText('Pushed while pinned')).toBeTruthy() + }) + + +}) + +it('updates expansion availability on paragraph resize and disconnects its observer', () => { + const observers: TestResizeObserver[] = [] + class TestResizeObserver implements ResizeObserver { + observe = vi.fn() + unobserve = vi.fn() + disconnect = vi.fn() + constructor(readonly callback: ResizeObserverCallback) { observers.push(this) } + } + vi.stubGlobal('ResizeObserver', TestResizeObserver) + const scrollHeight = vi.spyOn(Element.prototype, 'scrollHeight', 'get').mockReturnValue(36) + vi.spyOn(Element.prototype, 'clientHeight', 'get').mockReturnValue(36) + const view = render() + const paragraph = screen.getByText('Build the Team runtime') + const observer = observers.find(item => item.observe.mock.calls.some(([target]) => target === paragraph))! + expect(observer).toBeDefined() + expect(screen.queryByRole('button', { name: zh['task.expand'] })).toBeNull() + scrollHeight.mockReturnValue(72) + act(() => { observer.callback([], observer) }) + expect(screen.getByRole('button', { name: zh['task.expand'] })).toBeTruthy() + scrollHeight.mockReturnValue(36) + act(() => { observer.callback([], observer) }) + expect(screen.queryByRole('button', { name: zh['task.expand'] })).toBeNull() + view.unmount() + expect(observer.disconnect).toHaveBeenCalledOnce() +}) diff --git a/packages/client/ui-agent-team/tests/team-mode.client.spec.tsx b/packages/client/ui-agent-team/tests/team-mode.client.spec.tsx new file mode 100644 index 0000000000..e0dccd0a13 --- /dev/null +++ b/packages/client/ui-agent-team/tests/team-mode.client.spec.tsx @@ -0,0 +1,63 @@ +// @vitest-environment jsdom + +import { afterEach, expect, it, vi } from 'vitest' +import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' +import { SessionId } from '@deepseek-ai/dsh-session' +import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime' +import { TeamModeToggle, type TeamModeToggleProps } from '../src/client/TeamModeToggle.tsx' +import { zh } from '../src/client/locales.ts' + +afterEach(cleanup) + +function props(enabled: boolean | undefined, selectTeamMode = vi.fn(async () => null as string | null), child?: 'addressed' | 'listed'): TeamModeToggleProps { + return { + sessionId: SessionId('root'), + useSession: (select: (value: { subagent: object | null }) => unknown) => select({ subagent: child === 'addressed' ? {} : null }), + useSessions: (select: (value: { byId: object }) => unknown) => select({ byId: child === 'listed' ? { root: { origin: 'subagent' } } : {} }), + useProjection: () => enabled === undefined ? undefined : { enabled, tasks: [] }, + locked: false, reportFailure: vi.fn(), selectTeamMode, + t: makeTranslate(zh), + } as TeamModeToggleProps +} + +it('renders only available mode and waits for persisted state before displaying a new selection', async () => { + let finish!: (result: string | null) => void + const select = vi.fn(() => new Promise((resolve) => { finish = resolve })) + const view = render() + expect(screen.queryByRole('switch')).toBeNull() + view.rerender() + const toggle = screen.getByRole('switch', { name: zh['mode.label'] }) + fireEvent.click(toggle) + expect(select).toHaveBeenCalledWith(true) + expect(toggle.getAttribute('aria-checked')).toBe('false') + expect(toggle.hasAttribute('disabled')).toBe(true) + await act(async () => { finish(null) }) + view.rerender() + expect(toggle.getAttribute('aria-checked')).toBe('true') +}) + +it.each(['returned', 'error', 'string'])('reports rejected mode writes through the menu host (%s)', async (failure) => { + const p = props(false, vi.fn(async () => { + if (failure === 'error') throw new Error('offline') + if (failure === 'string') throw 'offline' + return 'offline' + })) + render() + await act(async () => { fireEvent.click(screen.getByRole('switch')) }) + expect(p.reportFailure).toHaveBeenCalledWith(expect.stringContaining('offline')) + expect(screen.getByRole('switch').getAttribute('aria-checked')).toBe('false') +}) + +it('disables selection while the Session is locked', () => { + render() + expect(screen.getByRole('switch').hasAttribute('disabled')).toBe(true) +}) + +it.each(['addressed', 'listed'] as const)('hides the switch on a child Session (%s)', (source) => { + const input = props(false, undefined, source) + const useProjection = vi.fn((): never => { throw new Error('Child must not read Team mode') }) + render() + expect(screen.queryByRole('switch')).toBeNull() + expect(useProjection).not.toHaveBeenCalled() + expect(input.selectTeamMode).not.toHaveBeenCalled() +}) diff --git a/packages/client/ui-agent-team/tsconfig.json b/packages/client/ui-agent-team/tsconfig.json new file mode 100644 index 0000000000..53dfea8eeb --- /dev/null +++ b/packages/client/ui-agent-team/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.client.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib/types" + }, + "include": ["src"], + "references": [ + { "path": "../../../vendor/cordis" }, + { "path": "../../team/agent-team" }, + { "path": "../../api/session-controller/tsconfig.client.json" }, + { "path": "../../api/remotes/tsconfig.client.json" }, + { "path": "../ui-model-selection" }, + { "path": "../ui-subagent" }, + { "path": "../locale/tsconfig.client.json" }, + { "path": "../store" }, + { "path": "../ui-conversation/tsconfig.client.json" }, + { "path": "../ui-primitives" }, + { "path": "../ui-renderer" }, + { "path": "../ui-slots" }, + { "path": "../../core/session" } + ] +} diff --git a/packages/experimental/client-ui-agent-team/tsdown.config.ts b/packages/client/ui-agent-team/tsdown.config.ts similarity index 54% rename from packages/experimental/client-ui-agent-team/tsdown.config.ts rename to packages/client/ui-agent-team/tsdown.config.ts index 71d5bd77f8..7aa03cebc1 100644 --- a/packages/experimental/client-ui-agent-team/tsdown.config.ts +++ b/packages/client/ui-agent-team/tsdown.config.ts @@ -1,3 +1,3 @@ -import { clientBundle } from '../../client/tsdown.client.ts' +import { clientBundle } from '../tsdown.client.ts' export default clientBundle(import.meta.dirname, ['lib/types/index.js']) diff --git a/packages/client/ui-commands/src/client/presentation.ts b/packages/client/ui-commands/src/client/presentation.ts index f5abacd2a4..95204d468e 100644 --- a/packages/client/ui-commands/src/client/presentation.ts +++ b/packages/client/ui-commands/src/client/presentation.ts @@ -2,7 +2,8 @@ import type { ComponentType } from 'react' import type { InputTriggerCandidate } from '@deepseek-ai/dsh-client-ui-input-trigger/client' import { - IconCompactOutlineRegular, IconDownloadOutlineRegular, IconGoalOutlineRegular, IconPaperPlaneOutlineRegular, IconPlanOutlineRegular, + IconCompactOutlineRegular, IconDownloadOutlineRegular, + IconGoalOutlineRegular, IconPaperPlaneOutlineRegular, IconPlanOutlineRegular, PermissionIconFullAccessRegular, } from '@deepseek-ai/dsh-client-ui-primitives' import type { IconProps } from '@deepseek-ai/dsh-client-ui-primitives' diff --git a/packages/client/ui-conversation/src/client/locales.ts b/packages/client/ui-conversation/src/client/locales.ts index c69e672464..a448d94593 100644 --- a/packages/client/ui-conversation/src/client/locales.ts +++ b/packages/client/ui-conversation/src/client/locales.ts @@ -184,7 +184,7 @@ export const zh = { 'tool.title.getTeamTask': '读取团队任务', 'tool.title.updateTeamTask': '更新团队任务', 'tool.title.listTeamTasks': '查看团队任务', - 'tool.title.waitAgent': '等待输入', + 'tool.title.waitAgent': '等待消息…', 'detail.recordedResult': '调用结果', 'detail.empty': '暂无结果', 'detail.none': '无', @@ -241,7 +241,6 @@ export const zh = { 'detail.field.agents': '启动智能体数', 'detail.field.result': '结果', 'detail.field.parent': '父级', - 'detail.field.depth': '层级', 'detail.field.exitCode': '退出码', 'detail.field.signal': '信号', 'detail.field.previousStatus': '中断前状态', @@ -270,8 +269,8 @@ export const zh = { 'detail.receipt.closing': '关闭中', 'detail.wait.noProgress': '没有正在运行的子智能体', 'detail.wait.title': '等待结果', - 'detail.wait.timeout': '等待超时', - 'detail.wait.changed': '等待结束', + 'detail.wait.timeout': '等待已超时', + 'detail.wait.ended': '等待已结束', 'detail.agent.reply': '智能体回复', 'detail.models.title': '可用模型', 'detail.output.lines': '第 {begin}–{end} 行,共 {total} 行', @@ -572,7 +571,7 @@ export const en = { 'tool.title.getTeamTask': 'Read team task', 'tool.title.updateTeamTask': 'Update team task', 'tool.title.listTeamTasks': 'List team tasks', - 'tool.title.waitAgent': 'Wait for input', + 'tool.title.waitAgent': 'Waiting for messages…', 'detail.recordedResult': 'Recorded result', 'detail.empty': 'No results', 'detail.none': 'None', @@ -629,7 +628,6 @@ export const en = { 'detail.field.agents': 'Agents started', 'detail.field.result': 'Result', 'detail.field.parent': 'Parent', - 'detail.field.depth': 'Depth', 'detail.field.exitCode': 'Exit code', 'detail.field.signal': 'Signal', 'detail.field.previousStatus': 'Previous status', @@ -659,7 +657,7 @@ export const en = { 'detail.wait.noProgress': 'No active subagents', 'detail.wait.title': 'Wait result', 'detail.wait.timeout': 'Wait timed out', - 'detail.wait.changed': 'Wait ended', + 'detail.wait.ended': 'Wait ended', 'detail.agent.reply': 'Agent response', 'detail.models.title': 'Available models', 'detail.output.lines': 'Lines {begin}–{end} of {total}', @@ -959,7 +957,7 @@ export const ja = { 'tool.title.getTeamTask': 'チームタスクを読み取り', 'tool.title.updateTeamTask': 'チームタスクを更新', 'tool.title.listTeamTasks': 'チームタスクを一覧表示', - 'tool.title.waitAgent': 'サブエージェントを待機', + 'tool.title.waitAgent': 'メッセージを待機中…', 'detail.recordedResult': '記録された結果', 'detail.empty': '結果なし', 'detail.none': 'なし', @@ -1016,7 +1014,6 @@ export const ja = { 'detail.field.agents': '起動したエージェント', 'detail.field.result': '結果', 'detail.field.parent': '親', - 'detail.field.depth': '深さ', 'detail.field.exitCode': '終了コード', 'detail.field.signal': 'シグナル', 'detail.field.previousStatus': '以前のステータス', @@ -1046,7 +1043,7 @@ export const ja = { 'detail.wait.noProgress': 'アクティブなサブエージェントなし', 'detail.wait.title': 'サブエージェントのアクティビティ', 'detail.wait.timeout': '待機がタイムアウトしました', - 'detail.wait.changed': '変更を検出', + 'detail.wait.ended': '待機が終了しました', 'detail.agent.reply': 'エージェントの応答', 'detail.models.title': '利用可能なモデル', 'detail.output.lines': '{total} 行中 {begin}–{end} 行', @@ -1339,7 +1336,7 @@ export const es = { 'tool.title.getTeamTask': 'Leer tarea de equipo', 'tool.title.updateTeamTask': 'Actualizar tarea de equipo', 'tool.title.listTeamTasks': 'Listar tareas del equipo', - 'tool.title.waitAgent': 'Espere al subagente', + 'tool.title.waitAgent': 'Esperando mensajes…', 'detail.recordedResult': 'Resultado registrado', 'detail.empty': 'Sin resultados', 'detail.none': 'Ninguno', @@ -1396,7 +1393,6 @@ export const es = { 'detail.field.agents': 'Los agentes comenzaron', 'detail.field.result': 'Resultado', 'detail.field.parent': 'Padre', - 'detail.field.depth': 'Profundidad', 'detail.field.exitCode': 'código de salida', 'detail.field.signal': 'señal', 'detail.field.previousStatus': 'Estado anterior', @@ -1426,7 +1422,7 @@ export const es = { 'detail.wait.noProgress': 'No hay subagentes activos', 'detail.wait.title': 'Actividad del subagente', 'detail.wait.timeout': 'Se agotó el tiempo de espera', - 'detail.wait.changed': 'Cambio detectado', + 'detail.wait.ended': 'Espera finalizada', 'detail.agent.reply': 'Respuesta del agente', 'detail.models.title': 'Modelos disponibles', 'detail.output.lines': 'Líneas {begin}–{end} de {total}', @@ -1726,7 +1722,7 @@ export const fr = { 'tool.title.getTeamTask': "Lire la tâche d'équipe", 'tool.title.updateTeamTask': "Mettre à jour la tâche de l'équipe", 'tool.title.listTeamTasks': "Répertorier les tâches de l'équipe", - 'tool.title.waitAgent': 'Attendez le sous-agent', + 'tool.title.waitAgent': 'En attente de messages…', 'detail.recordedResult': 'Résultat enregistré', 'detail.empty': 'Aucun résultat', 'detail.none': 'Aucun', @@ -1783,7 +1779,6 @@ export const fr = { 'detail.field.agents': 'Les agents ont commencé', 'detail.field.result': 'Résultat', 'detail.field.parent': 'Parent', - 'detail.field.depth': 'Profondeur', 'detail.field.exitCode': 'Code de sortie', 'detail.field.signal': 'Signal', 'detail.field.previousStatus': 'Statut précédent', @@ -1813,7 +1808,7 @@ export const fr = { 'detail.wait.noProgress': 'Aucun sous-agent actif', 'detail.wait.title': 'Activité du sous-agent', 'detail.wait.timeout': "Le délai d'attente a expiré", - 'detail.wait.changed': 'Changement détecté', + 'detail.wait.ended': 'Attente terminée', 'detail.agent.reply': "Réponse de l'agent", 'detail.models.title': 'Modèles disponibles', 'detail.output.lines': 'Lignes {begin}–{end} de {total}', @@ -2113,7 +2108,7 @@ export const de = { 'tool.title.getTeamTask': 'Teamaufgabe lesen', 'tool.title.updateTeamTask': 'Teamaufgabe aktualisieren', 'tool.title.listTeamTasks': 'Teamaufgaben auflisten', - 'tool.title.waitAgent': 'Warten Sie auf den Subagenten', + 'tool.title.waitAgent': 'Warten auf Nachrichten…', 'detail.recordedResult': 'Aufgezeichnetes Ergebnis', 'detail.empty': 'Keine Ergebnisse', 'detail.none': 'Keine', @@ -2170,7 +2165,6 @@ export const de = { 'detail.field.agents': 'Agenten begannen', 'detail.field.result': 'Ergebnis', 'detail.field.parent': 'Eltern', - 'detail.field.depth': 'Tiefe', 'detail.field.exitCode': 'Exit-Code', 'detail.field.signal': 'Signal', 'detail.field.previousStatus': 'Vorheriger Stand', @@ -2200,7 +2194,7 @@ export const de = { 'detail.wait.noProgress': 'Keine aktiven Subagenten', 'detail.wait.title': 'Aktivität des Subagenten', 'detail.wait.timeout': 'Wartezeit abgelaufen', - 'detail.wait.changed': 'Änderung erkannt', + 'detail.wait.ended': 'Warten beendet', 'detail.agent.reply': 'Antwort des Agenten', 'detail.models.title': 'Verfügbare Modelle', 'detail.output.lines': 'Linien {begin}–{end} von {total}', @@ -2500,7 +2494,7 @@ export const ru = { 'tool.title.getTeamTask': 'Прочитать командное задание', 'tool.title.updateTeamTask': 'Обновить групповую задачу', 'tool.title.listTeamTasks': 'Перечислить командные задачи', - 'tool.title.waitAgent': 'Подождите субагента', + 'tool.title.waitAgent': 'Ожидание сообщений…', 'detail.recordedResult': 'Записанный результат', 'detail.empty': 'Нет результатов', 'detail.none': 'Нет', @@ -2557,7 +2551,6 @@ export const ru = { 'detail.field.agents': 'Агенты запущены', 'detail.field.result': 'Результат', 'detail.field.parent': 'Родитель', - 'detail.field.depth': 'Глубина', 'detail.field.exitCode': 'Код выхода', 'detail.field.signal': 'Сигнал', 'detail.field.previousStatus': 'Предыдущий статус', @@ -2587,7 +2580,7 @@ export const ru = { 'detail.wait.noProgress': 'Нет активных субагентов', 'detail.wait.title': 'Субагентская деятельность', 'detail.wait.timeout': 'Время ожидания истекло', - 'detail.wait.changed': 'Обнаружено изменение', + 'detail.wait.ended': 'Ожидание завершено', 'detail.agent.reply': 'Ответ агента', 'detail.models.title': 'Доступные модели', 'detail.output.lines': 'Линии {begin} – {end} из {total}', @@ -2887,7 +2880,7 @@ export const pt = { 'tool.title.getTeamTask': 'Ler tarefa da equipe', 'tool.title.updateTeamTask': 'Atualizar tarefa da equipe', 'tool.title.listTeamTasks': 'Listar tarefas da equipe', - 'tool.title.waitAgent': 'Aguarde o subagente', + 'tool.title.waitAgent': 'Aguardando mensagens…', 'detail.recordedResult': 'Resultado registrado', 'detail.empty': 'Nenhum resultado', 'detail.none': 'Nenhum', @@ -2944,7 +2937,6 @@ export const pt = { 'detail.field.agents': 'Agentes iniciados', 'detail.field.result': 'Resultado', 'detail.field.parent': 'Pai', - 'detail.field.depth': 'Profundidade', 'detail.field.exitCode': 'Código de saída', 'detail.field.signal': 'Sinal', 'detail.field.previousStatus': 'Estado anterior', @@ -2974,7 +2966,7 @@ export const pt = { 'detail.wait.noProgress': 'Nenhum subagente ativo', 'detail.wait.title': 'Atividade do subagente', 'detail.wait.timeout': 'A espera expirou', - 'detail.wait.changed': 'Alteração detectada', + 'detail.wait.ended': 'Espera encerrada', 'detail.agent.reply': 'Resposta do agente', 'detail.models.title': 'Modelos disponíveis', 'detail.output.lines': 'Linhas {begin}–{end} de {total}', diff --git a/packages/client/ui-model-selection/README.i18n.yaml b/packages/client/ui-model-selection/README.i18n.yaml index e66c1a88ac..bbdf6a9681 100644 --- a/packages/client/ui-model-selection/README.i18n.yaml +++ b/packages/client/ui-model-selection/README.i18n.yaml @@ -27,8 +27,8 @@ en: 7f20428c747ff052 zh: 975e6de728e02fa0 /deepseek-ai-dsh-client-ui-model-selection/use-this-package/selection-failures: - en: 261bbf42296243af - zh: 72779fb7b8fbc473 + en: 76d33d012d54f19c + zh: c08cdc6506077d5e /deepseek-ai-dsh-client-ui-model-selection/understand-the-implementation: en: 56003671ca5acf24 zh: 251e02201ea82142 diff --git a/packages/client/ui-model-selection/README.md b/packages/client/ui-model-selection/README.md index a3e3dae81f..b212d73748 100644 --- a/packages/client/ui-model-selection/README.md +++ b/packages/client/ui-model-selection/README.md @@ -61,6 +61,8 @@ Catalog availability does not block sending with a saved selection; request exec When another writer owns the Session, model-selection failures tell the user to quit other running DSH instances and retry. +The session-scoped `conversation.input.model.options` list slot appends controls below model and reasoning selection. Owner props expose `locked` and `reportFailure(message)`; contributions read their own projections and supply narrow commands. Enabled switch buttons participate in the menu’s keyboard navigation. + ----- diff --git a/packages/client/ui-model-selection/README.zh.md b/packages/client/ui-model-selection/README.zh.md index 63c2cd9924..e506906012 100644 --- a/packages/client/ui-model-selection/README.zh.md +++ b/packages/client/ui-model-selection/README.zh.md @@ -61,6 +61,8 @@ composer 菜单中的模型名称和推理等级均使用 400(regular)字重 当会话被其他写句柄占用时,模型选择失败提示用户退出其他正在运行的 DSH 后重试。 +会话级列表 slot `conversation.input.model.options` 在模型与推理等级选项下方追加控件。Owner props 提供 `locked` 和 `reportFailure(message)`;贡献方读取自身投影并提供专用命令。启用的开关按钮参与菜单键盘导航。 + ----- diff --git a/packages/client/ui-model-selection/src/client/ModelSelect.tsx b/packages/client/ui-model-selection/src/client/ModelSelect.tsx index f68d355317..1a3042b5d2 100644 --- a/packages/client/ui-model-selection/src/client/ModelSelect.tsx +++ b/packages/client/ui-model-selection/src/client/ModelSelect.tsx @@ -66,7 +66,7 @@ const MEASURE_STYLE: CSSProperties = { visibility: 'hidden', left: 0, top: 0 } export function ModelSelect( { locked, available, directory, load, select, resolveText, t, renderSlot }: ModelSelectInjected & Pick, 'locked'> & PropsLocale<'model'> - & Omit, 'SessionProvider'>, + & Omit, 'SessionProvider'>, ) { const state = useSyncExternalStore( fn => directory.subscribe(fn), @@ -290,7 +290,9 @@ export function ModelSelect( } const moveFocus = (offset: number): void => { - const items = itemRefs.current.filter(item => item !== null) + const items = Array.from(menuRef.current?.querySelectorAll( + 'button[role="menuitem"]:not([disabled]), button[role="menuitemradio"]:not([disabled]), button[role="switch"]:not([disabled])', + ) ?? []) if (items.length === 0) return const active = items.findIndex(item => item === document.activeElement) // Focus outside the rows (the trigger, which keeps it while the menu @@ -344,7 +346,9 @@ export function ModelSelect( // control inside the card (a retry button) keeps the browser's traversal, // so the keystroke stays unconsumed there. const focused = document.activeElement - const rows = itemRefs.current.filter((item): item is HTMLButtonElement => item !== null) + const rows = Array.from(menuRef.current?.querySelectorAll( + 'button[role="menuitem"]:not([disabled]), button[role="menuitemradio"]:not([disabled]), button[role="switch"]:not([disabled])', + ) ?? []) if (focused instanceof HTMLButtonElement && rows.includes(focused)) { event.preventDefault() focused.click() @@ -530,6 +534,10 @@ export function ModelSelect( )} + {renderSlot('conversation.input.model.options', { + locked: locked || busy, + reportFailure: (message) => { setToast({ seq: ++toastSeq.current, text: message }) }, + })} )} diff --git a/packages/client/ui-model-selection/src/client/index.ts b/packages/client/ui-model-selection/src/client/index.ts index b91ee20b48..224990e848 100644 --- a/packages/client/ui-model-selection/src/client/index.ts +++ b/packages/client/ui-model-selection/src/client/index.ts @@ -174,6 +174,7 @@ export function apply(ctx: ClientContext): void { locale: NS, children: { 'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true }, + 'conversation.input.model.options': { kind: 'list', scope: 'session' }, }, inject: (sessionId): ModelSelectInjected => { const directory = models.directoryFor(sessionId) diff --git a/packages/client/ui-model-selection/src/client/slots.ts b/packages/client/ui-model-selection/src/client/slots.ts index 4ffddee580..fb29e0dc38 100644 --- a/packages/client/ui-model-selection/src/client/slots.ts +++ b/packages/client/ui-model-selection/src/client/slots.ts @@ -19,6 +19,17 @@ export interface ModelLabelOwner { declare module '@deepseek-ai/dsh-client-ui-slots' { interface SlotMap { + /** Session options appended after model and reasoning controls in the root menu. */ + 'conversation.input.model.options': { + kind: 'list' + scope: 'session' + owner: { + /** Whether the composer currently refuses selection changes. */ + locked: boolean + /** Report a failed selection through the menu owner's persistent toast. */ + reportFailure: (message: string) => void + } + } /** * Non-interactive inline model label; selection controls remain owner-managed. * Without a contribution, the badge precedes the catalog name in the trigger diff --git a/packages/client/ui-model-selection/tests/model-select.client.spec.tsx b/packages/client/ui-model-selection/tests/model-select.client.spec.tsx index dbb5b247db..b35226ec25 100644 --- a/packages/client/ui-model-selection/tests/model-select.client.spec.tsx +++ b/packages/client/ui-model-selection/tests/model-select.client.spec.tsx @@ -98,6 +98,7 @@ describe('ModelSelect label slot', () => { }) await runtime.root.declare({ 'conversation.input.model.label': { kind: 'single', scope: 'session', inline: true }, + 'conversation.input.model.options': { kind: 'list', scope: 'session' }, }, ({ renderSlot, SessionProvider }) => diff --git a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx index adacb15d71..242ca6f291 100644 --- a/packages/client/ui-plugin-manager/tests/components.client.spec.tsx +++ b/packages/client/ui-plugin-manager/tests/components.client.spec.tsx @@ -735,14 +735,14 @@ describe('PluginManagerPage', () => { pkg({ name: '@acme/dsh-tool', enabled: false }), // Selected by the profile but not a bundle: a problem the person can switch off, in the profile's own group. pkg({ name: 'dsh-selected', installed: false, error: { code: 'not-bundle' } }), - pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false, enabled: false }), + pkg({ name: '@deepseek-ai/dsh-experimental-voice-input-bundle', installed: false, optional: true, removable: false, enabled: false }), ], busy: ['dsh-protected'], }) const cards = screen.getAllByRole('listitem') // Offered extensions precede installed bundles. expect(cards.map(card => card.getAttribute('data-plugin-package'))).toEqual([ - '@deepseek-ai/dsh-experimental-agent-team-profile', 'dsh-better-sidebar', 'dsh-broken', 'dsh-protected', '@acme/dsh-tool', 'dsh-selected', + '@deepseek-ai/dsh-experimental-voice-input-bundle', 'dsh-better-sidebar', 'dsh-broken', 'dsh-protected', '@acme/dsh-tool', 'dsh-selected', ]) expect(cards.map(card => card.getAttribute('data-plugin-status'))).toEqual(['disabled', 'running', 'problem', 'running', 'disabled', 'problem']) // Group titles have no counts; the official bundle retains its experimental marker. @@ -776,11 +776,11 @@ describe('PluginManagerPage', () => { ].map(name => pkg({ name })), pkg({ name: '@acme/dsh-base', readOnlyReason: 'management-required' }), pkg({ name: 'dsh-better-sidebar' }), - pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false }), + pkg({ name: '@deepseek-ai/dsh-experimental-voice-input-bundle', installed: false, optional: true, removable: false }), ], }) expect(screen.getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package'))).toEqual([ - '@deepseek-ai/dsh-experimental-agent-team-profile', '@acme/dsh-base', 'dsh-better-sidebar', + '@deepseek-ai/dsh-experimental-voice-input-bundle', '@acme/dsh-base', 'dsh-better-sidebar', ]) expect(document.querySelector('[data-plugin-count]')).toBeNull() }) @@ -789,7 +789,7 @@ describe('PluginManagerPage', () => { const name = '@deepseek-ai/dsh-experimental-terminal-bundle' const b = renderTab({ packages: [ pkg({ name, official: true, enabled, rows: [row()] }), - pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', official: true }), + pkg({ name: '@deepseek-ai/dsh-experimental-voice-input-bundle', official: true }), ] }) expect(document.querySelector(`[data-plugin-package="${name}"]`)).toBeNull() fireEvent.click(screen.getByRole('button', { name: en.moreAction })) @@ -807,7 +807,7 @@ describe('PluginManagerPage', () => { it('keeps a shipped bundle out of both lists until one of its declared rows runs', () => { const hidden = '@deepseek-ai/dsh-experimental-liveshare' - const shown = '@deepseek-ai/dsh-experimental-agent-team-profile' + const shown = '@deepseek-ai/dsh-experimental-voice-input-bundle' const offer = (rows: readonly PackageRow[]): PackageView => pkg({ name: hidden, official: true, installed: false, optional: true, removable: false, enabled: false, listedByDefault: false, rows, }) @@ -881,16 +881,16 @@ describe('PluginManagerPage', () => { }) it('opens an official bundle\'s page with its beta tag and no uninstall, and switches it on', () => { - const title = 'Agent Teams' + const title = 'Voice input' const { actions } = renderTab({ - packages: [pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', meta: { title }, installed: false, optional: true, removable: false, enabled: false })], + packages: [pkg({ name: '@deepseek-ai/dsh-experimental-voice-input-bundle', meta: { title }, installed: false, optional: true, removable: false, enabled: false })], }) fireEvent.click(screen.getByRole('button', { name: en.openDetail.replace('{name}', title) })) const detail = document.querySelector('[data-plugin-detail]') as HTMLElement expect(within(detail).getByText(en.statusBeta)).toBeTruthy() expect(within(detail).queryByRole('button', { name: en.uninstallLabel.replace('{name}', title) })).toBeNull() fireEvent.click(within(detail).getByRole('switch', { name: en.enableToggle.replace('{name}', title) })) - expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith('@deepseek-ai/dsh-experimental-agent-team-profile', true) + expect(actions.setEnabled).toHaveBeenCalledExactlyOnceWith('@deepseek-ai/dsh-experimental-voice-input-bundle', true) }) it('keeps the persisted selection while marking a failed component as a problem', () => { @@ -915,7 +915,7 @@ describe('PluginManagerPage', () => { }) it.each([ - ['@deepseek-ai/dsh-experimental-agent-team-profile', true], + ['@deepseek-ai/dsh-experimental-voice-input-bundle', true], ['@deepseek-ai/dsh-experimental-auto-review', true], ['@deepseek-ai/dsh-experimental-fixture-input', false], ['@deepseek-ai/dsh-cua-bundle', true], @@ -1198,11 +1198,11 @@ describe('PluginManagerPage', () => { } it('keeps configuration and installed plugins on the main list and shows eligible official plugins on the secondary page', () => { - const team = '@deepseek-ai/dsh-experimental-agent-team-profile' + const team = '@deepseek-ai/dsh-experimental-voice-input-bundle' renderTab( { packages: [ pkg({ name: '@deepseek-ai/dsh-experimental-badge-skill-bundle', official: true, enabled: false }), - pkg({ name: '@deepseek-ai/dsh-experimental-agent-team-profile', installed: false, optional: true, removable: false, enabled: false }), + pkg({ name: '@deepseek-ai/dsh-experimental-voice-input-bundle', installed: false, optional: true, removable: false, enabled: false }), pkg({ name: '@deepseek-ai/dsh-subagent-claude-code', official: true, availability: 'missing', installed: false, enabled: false }), pkg({ name: '@deepseek-ai/dsh-subagent-codex', official: true, enabled: false }), pkg({ name: '@deepseek-ai/dsh-experimental-tool-worktree', official: true, enabled: false }), @@ -1223,7 +1223,7 @@ describe('PluginManagerPage', () => { expect(within(extensions).getByRole('heading', { name: en.extensionsTitle })).toBeTruthy() expect(within(extensions).getByRole('button', { name: en.moreAction }).textContent).toBe(en.moreAction) expect(within(extensions).getAllByRole('listitem').map(card => card.getAttribute('data-plugin-package'))) - .toEqual(['@deepseek-ai/dsh-experimental-agent-team-profile', '@acme/new-official']) + .toEqual(['@deepseek-ai/dsh-experimental-voice-input-bundle', '@acme/new-official']) expect(document.querySelector('[data-plugin-count]')).toBeNull() expect(within(basic).getByText('Limits every command.')).toBeTruthy() expect(within(basic).queryByRole('switch', { name: en.enableToggle.replace('{name}', 'Shell') })).toBeNull() @@ -1364,7 +1364,7 @@ describe('PluginManagerPage', () => { '@deepseek-ai/dsh-experimental-tool-worktree', '@deepseek-ai/dsh-personalized-recommendations', ])('pins enabled %s onto the main list and removes it when disabled', (first) => { - const team = '@deepseek-ai/dsh-experimental-agent-team-profile' + const team = '@deepseek-ai/dsh-experimental-voice-input-bundle' const second = '@deepseek-ai/dsh-experimental-cot-translation-bundle' const entry = (name: string, enabled: boolean): PackageView => pkg({ name, official: true, enabled }) const b = renderTab({ packages: [entry(second, true), entry(team, true), entry(first, false)] }) diff --git a/packages/client/ui-subagent/README.i18n.yaml b/packages/client/ui-subagent/README.i18n.yaml index e9195150d7..01a4e805c1 100644 --- a/packages/client/ui-subagent/README.i18n.yaml +++ b/packages/client/ui-subagent/README.i18n.yaml @@ -15,11 +15,11 @@ en: d152484eb41ac6b4 zh: 09388d293f9be9cb /deepseek-ai-dsh-client-ui-subagent/use-this-package: - en: 9aafcfbd6a33173c - zh: 6dbd48672f58cd2d + en: cd44005baea452d1 + zh: 2c112193a970a4ac /deepseek-ai-dsh-client-ui-subagent/use-this-package/browsing-the-tree: - en: 3508631091820123 - zh: ec7da9e5bedb22ba + en: 1d668f70d099120c + zh: 6a35854d8857c4f9 /deepseek-ai-dsh-client-ui-subagent/use-this-package/continuing-a-conversation: en: e4ec261d3b122eb5 zh: 320bb2efc93dccdc @@ -30,14 +30,14 @@ en: c158f12dcaed87b7 zh: e5d315e7b1fb4fa9 /deepseek-ai-dsh-client-ui-subagent/understand-the-implementation/catalog-derivation: - en: 5531dcc1fb976cff - zh: 6fe779f4f1c34266 + en: 545a9ae030348be5 + zh: 8bc9c33b6faea32c /deepseek-ai-dsh-client-ui-subagent/understand-the-implementation/duration-completion-and-tokens: en: b817e9907392f59c zh: e42feb97d572a989 /deepseek-ai-dsh-client-ui-subagent/understand-the-implementation/composer-election: - en: b3407bce2119da41 - zh: ae5b77ac32173ca0 + en: c2672d1219428ea6 + zh: 4a1a55bc41a90d82 /deepseek-ai-dsh-client-ui-subagent/further-exploration: en: 1766e9012e307178 zh: f3ee637f5941aae5 diff --git a/packages/client/ui-subagent/README.md b/packages/client/ui-subagent/README.md index c236251943..5fe265fdcb 100644 --- a/packages/client/ui-subagent/README.md +++ b/packages/client/ui-subagent/README.md @@ -25,15 +25,17 @@ Browse local subagent conversations and external tasks from the parent header. L ## Use this package -The session header keeps the current session title as the lineage breadcrumb; when the session's direct catalog has entries or a read has failed, the descendant-count trigger renders after the preset label in the header actions band, with no breadcrumb separator. An absent catalog, an empty loading catalog, or a successfully loaded empty catalog hides the count trigger. The trigger opens that direct catalog, reports its total and running counts, and loads nested catalogs only when their rows expand. Select a local child at any depth to open that child's conversation with its exact `{parentSessionId, childSessionId, mode}` address, or use the row's trailing arrow to open the same address in the right Sidebar, preferring a separate pane when room permits. +The session header uses the session title for ordinary breadcrumbs and the Session id for subagent breadcrumbs. When the session's direct catalog has entries or a read has failed, the descendant-count trigger renders after the preset label in the header actions band, with no breadcrumb separator. An absent or empty catalog hides the count trigger unless this root Session enables Agent Teams; an enabled Team keeps the shared board reachable before its first member exists. The trigger opens that direct catalog, reports its total and running counts, and loads nested catalogs only when their rows expand. Select a local child at any depth to open that child's conversation with its exact `{parentSessionId, childSessionId, mode}` address, or use the row's trailing arrow to open the same address in the right Sidebar, preferring a separate pane when room permits. This package registers the `dsh-resource://subagentchat/session/?parent=&mode=` resource and builtin Sidebar tab type. The resource retains the child `SessionReference` directly from its address without refreshing the parent catalog, and releases the reference when the tab record closes. The tab renders the shared `conversation.content` Factory through `sidebar.chat.conversation`, fixes the local View to Chat, and omits the main Conversation header and width controls. +The root catalog exposes `conversation.session.header.actions.subagent.footer` for shared tasks. Child and ancestor breadcrumb catalogs show navigation only. + ### Browsing the tree -Hovering a trigger opens its catalog after 150ms; leaving both trigger and catalog closes it after 120ms. Clicking the descendant-count trigger pins its catalog until outside click or Escape from the trigger or tree. Breadcrumb-title clicks navigate to the corresponding conversation. +Hovering a trigger opens its catalog after 150ms; leaving both trigger and catalog closes it after 120ms. Clicking the descendant-count trigger pins its catalog until outside click or Escape. Clicking moves focus into the panel, where ArrowDown/Home enters the first tree row and ArrowUp/End enters the last; Escape closes the panel even from a description button and returns focus when it was inside. Breadcrumb-title clicks navigate to the corresponding conversation. -Rows display mode plus activity and an optional log-backed title; running uses the shared ongoing loader, an inactive child whose latest closed turn completed normally uses the shared success dot, and other inactive children use the shared idle dot. Every row reserves the same 14px status column, centering smaller dots so titles align with the loader state. The compact header trigger vertically centers its activity glyph and count with a 4px gap. The trailing column stacks total durable provider usage above active-turn duration. Keyboard navigation works with ArrowRight/ArrowLeft to expand and collapse branches and ArrowUp/ArrowDown, Home, End, and Escape to navigate or close the tree. An unlabeled one-shot row falls back to its session id. A row is a known leaf only after its own catalog loads empty. +Rows show the Session id above the creation description; when the description is absent, they use the log-backed title. Descriptions occupy one line and expand or collapse in place on click. Running uses the shared ongoing loader, an inactive child whose latest closed turn completed normally uses the shared success dot, and other inactive children use the shared idle dot. Every row reserves the same 14px status column, centering smaller dots so titles align with the loader state. The compact header trigger vertically centers its activity glyph and count with a 4px gap. The trailing column stacks total durable provider usage above active-turn duration. Keyboard navigation works with ArrowRight/ArrowLeft to expand and collapse branches and ArrowUp/ArrowDown, Home, End, and Escape to navigate or close the tree. A row is a known leaf only after its own catalog loads empty. ### Continuing a conversation @@ -55,7 +57,9 @@ The [subagent runtime](../../subagent/subagent/README.md) owns continuation and ### Catalog derivation -The header lineage renderer reads `projectionsBySession` through the standard `useSessions` hook. The renderer selects `subagentCatalog` from each Session’s shared values for membership, disclosure, and counts; Activity prefers the unified UI status and falls back to Session summaries; summaries supply titles and usage. Expanding a row reads its target's projections, including ordinary cold history. A list row with `formatStatus: migration-required` displays “Migration required” before expansion and keeps its history-opening actions without descendant expansion or changing its subagent mode; a projection read requiring migration without a catalog supplies the same notice. Opening the Session supplies its catalog through history and control projections. Otherwise, an available catalog remains usable, including an explicit empty catalog. Live projection frames update every loaded level without menu subscriptions or repeated membership queries. Other rows remain expandable while their catalogs are absent, loading, or failed, and become known leaves after ready empty catalogs. External rows are always leaves and do not load child Sessions. +The header lineage renderer reads `projectionsBySession` through the standard `useSessions` hook. The renderer selects `subagentCatalog` from each Session’s shared values for membership, disclosure, and counts, and displays Session ids with creation descriptions; activity prefers the unified UI status and falls back to Session summaries; summaries supply titles and usage. Expanding a row reads its target's projections, including ordinary cold history. A list row with `formatStatus: migration-required` displays “Migration required” before expansion and keeps its history-opening actions without descendant expansion or changing its subagent mode; a projection read requiring migration without a catalog supplies the same notice. Opening the Session supplies its catalog through history and control projections. Otherwise, an available catalog remains usable, including an explicit empty catalog. Live projection frames update every loaded level without menu subscriptions or repeated membership queries. Other rows remain expandable while their catalogs are absent, loading, or failed, and become known leaves after ready empty catalogs. External rows are always leaves and do not load child Sessions. + +Each header entry declares a footer slot below its tree. Optional UI contributes there within the same scroll container; the catalog owns opening, positioning, and dismissal. Opening a catalog dropdown does not request its root catalog. Child-catalog expansion and failed-read retries call `refreshProjection`; shared projection-value changes update the display automatically. @@ -69,7 +73,7 @@ Each visible catalog level advances its own clock while it contains a running ch One-shot and unknown-mode children elect a read-only composer. Continuable children route prompts through `subagents/prompt`. This package never receives host context or calls a model-facing tool. -Unknown-mode catalog rows remain visible and clickable, using the child id when no label is available. Their composer is read-only until child history establishes a supported mode; a read failure is reported in the child conversation. +Unknown-mode catalog rows remain visible and clickable, using the child id. Their composer is read-only until child history establishes a supported mode; a read failure is reported in the child conversation. Lineage-menu highlights follow the [standard menu edge geometry](../../../docs/ui-radius.md#nested-hover); tree disclosure controls retain their separate left gutter. diff --git a/packages/client/ui-subagent/README.zh.md b/packages/client/ui-subagent/README.zh.md index eb221f84ae..3301c410c6 100644 --- a/packages/client/ui-subagent/README.zh.md +++ b/packages/client/ui-subagent/README.zh.md @@ -25,15 +25,17 @@ kind: "package-reference" ## 使用本包 -会话页头把当前会话标题作为谱系面包屑;当会话的直接目录有子项或读取失败时,后代数量触发器渲染在页头操作区中预设标签之后,不带任何面包屑分隔符。目录缺席、空目录加载中或成功加载为空时,均隐藏数量触发器。触发器打开该直接目录,报告总数与运行数,并且只在行展开时加载嵌套目录。选择任意深度,即可用该子会话的确切 `{parentSessionId, childSessionId, mode}` 地址打开其对话;也可以使用行尾箭头在右侧 Sidebar 打开同一地址,并在空间允许时优先使用独立分栏。 +会话页头的普通面包屑使用会话标题,子智能体面包屑使用 Session id。当会话的直接目录有子项或读取失败时,后代数量触发器渲染在页头操作区中预设标签之后,不带任何面包屑分隔符。目录缺席或为空时隐藏数量触发器,但根 Session 开启 Agent Teams 时会保留入口,使首位成员创建前也能访问共享任务板。触发器打开该直接目录,报告总数与运行数,并且只在行展开时加载嵌套目录。选择任意深度,即可用该子会话的确切 `{parentSessionId, childSessionId, mode}` 地址打开其对话;也可以使用行尾箭头在右侧 Sidebar 打开同一地址,并在空间允许时优先使用独立分栏。 本包注册 `dsh-resource://subagentchat/session/?parent=&mode=` 资源与 builtin Sidebar tab 类型。资源直接根据地址保留 child 的 `SessionReference`,不刷新 parent 目录,并在 tab 记录关闭时释放 reference。tab 通过 `sidebar.chat.conversation` 渲染共享 `conversation.content` Factory,把局部 View 固定为 Chat,并省略主 Conversation 的 Header 与宽度控制。 +根会话目录通过 `conversation.session.header.actions.subagent.footer` 提供共享任务扩展。子会话与祖先面包屑目录只显示导航。 + ### 浏览目录 -悬停触发器 150ms 后打开目录;指针离开触发器和目录后,经过 120ms 关闭。点击后代数量触发器会固定目录,直到点击外部,或在触发器或树内按 Escape 关闭。点击面包屑标题会导航至对应会话。 +悬停触发器 150ms 后打开目录;指针离开触发器和目录后,经过 120ms 关闭。点击后代数量触发器会固定目录,直到点击外部或按 Escape 关闭。点击会将焦点移入面板,此时 ArrowDown/Home 进入首个树行,ArrowUp/End 进入最后一个树行;Escape 在焦点位于描述按钮时也会关闭面板,并在焦点原本位于面板内时将其送回。点击面包屑标题会导航至对应会话。 -行显示 mode、活动状态与由日志支撑的可选 title;running 使用共享 ongoing loading,最近一个已结束轮次正常完成的 inactive child 使用共享 success 绿点,其他 inactive child 使用共享 idle 灰点。每行都为状态图标预留相同的 14px 列宽,并将较小的圆点居中,使 title 与 loading 状态对齐。紧凑的页头触发器会垂直居中活动图标与数量,并保留 4px 水平间距。尾随列在上行显示提供方的持久化 token 用量总计,在下行显示活跃轮次耗时。键盘导航:ArrowRight/ArrowLeft 展开和折叠分支;ArrowUp/ArrowDown、Home、End 与 Escape 用于导航或关闭树。没有 label 的 one-shot 行回退到其会话 id。只有一行自身的目录加载为空后,它才是已知叶子。 +目录行在上方显示 Session id,下方显示创建时的描述;没有描述时使用日志中的标题。描述默认占一行,点击可原地展开或收起。running 使用共享 ongoing loading,最近一个已结束轮次正常完成的 inactive child 使用共享 success 绿点,其他 inactive child 使用共享 idle 灰点。每行都为状态图标预留相同的 14px 列宽,并将较小的圆点居中,使 title 与 loading 状态对齐。紧凑的页头触发器会垂直居中活动图标与数量,并保留 4px 水平间距。尾随列在上行显示提供方的持久化 token 用量总计,在下行显示活跃轮次耗时。键盘导航:ArrowRight/ArrowLeft 展开和折叠分支;ArrowUp/ArrowDown、Home、End 与 Escape 用于导航或关闭树。只有一行自身的目录加载为空后,它才是已知叶子。 ### 续接对话 @@ -55,7 +57,9 @@ kind: "package-reference" ### 目录派生 -页头谱系 renderer 通过标准 `useSessions` 钩子读取 `projectionsBySession`。renderer 从每个 Session 的共享值中选择 `subagentCatalog`,用于成员关系、展开控件与数量;活动状态优先使用统一 UI status,缺少时使用 Session 摘要;摘要提供标题与用量。展开行读取目标投影,包括普通冷历史。列表行的 `formatStatus` 为 `migration-required` 时,行在展开前即显示“需要迁移”,保留打开历史的操作,但不能展开下级,也不改变子会话的 mode;投影读取需要迁移且缺少目录时,也显示同一提示。打开会话后,历史与 control 投影提供其目录。其他情况下,已有目录仍可使用,包括显式空目录。实时 projection 帧更新所有已加载层级,无需菜单订阅或重复成员查询。其他行在目录缺席、加载中或失败时保持可展开,并在目录就绪且为空后成为已知叶子。外部行始终为叶子,不加载子 Session。 +页头谱系 renderer 通过标准 `useSessions` 钩子读取 `projectionsBySession`。renderer 从每个 Session 的共享值中选择 `subagentCatalog`,用于成员关系、展开控件与数量,并显示 Session id 和创建描述;活动状态优先使用统一 UI status,缺少时使用 Session 摘要;摘要提供标题与用量。展开行读取目标投影,包括普通冷历史。列表行的 `formatStatus` 为 `migration-required` 时,行在展开前即显示“需要迁移”,保留打开历史的操作,但不能展开下级,也不改变子会话的 mode;投影读取需要迁移且缺少目录时,也显示同一提示。打开会话后,历史与 control 投影提供其目录。其他情况下,已有目录仍可使用,包括显式空目录。实时 projection 帧更新所有已加载层级,无需菜单订阅或重复成员查询。其他行在目录缺席、加载中或失败时保持可展开,并在目录就绪且为空后成为已知叶子。外部行始终为叶子,不加载子 Session。 + +每个页头入口在树下方声明 footer 插槽。可选 UI 通过该插槽加入同一滚动容器;目录负责打开、定位和关闭。 打开目录下拉菜单不会请求其根目录。展开子节点目录或重试失败读取时调用 `refreshProjection`;共享投影值的变化会自动更新显示。 @@ -69,7 +73,7 @@ kind: "package-reference" one-shot 和模式未知的 child 选用只读编辑器。可继续 child 经由 `subagents/prompt` 路由提示词。本包绝不接收宿主上下文,也不调用面向模型的工具。 -模式未知的目录项仍可见、可点击,没有标签时显示子会话 id。读取子历史并确认受支持模式前,输入区保持只读;读取失败在该子会话中报告。 +模式未知的目录项仍可见、可点击,显示子会话 id。读取子历史并确认受支持模式前,输入区保持只读;读取失败在该子会话中报告。 会话树菜单高亮遵循[标准菜单边缘几何](../../../docs/ui-radius.zh.md#nested-hover);树形展开控件保留独立的左侧空间。 diff --git a/packages/client/ui-subagent/package.json b/packages/client/ui-subagent/package.json index 93f40eef08..a87b7df460 100644 --- a/packages/client/ui-subagent/package.json +++ b/packages/client/ui-subagent/package.json @@ -70,7 +70,8 @@ "@deepseek-ai/dsh-client-ui-renderer": "workspace:*", "@deepseek-ai/dsh-client-ui-sidebar-right": "workspace:*", "@deepseek-ai/dsh-client-ui-session": "workspace:*", - "@deepseek-ai/dsh-client-ui-workspace": "workspace:*" + "@deepseek-ai/dsh-client-ui-workspace": "workspace:*", + "@deepseek-ai/dsh-agent-team": "workspace:*" }, "files": [ "locale/*.json", diff --git a/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css b/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css index 1abef93da0..6dc4249ccc 100644 --- a/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css +++ b/packages/client/ui-subagent/src/client/SubagentHeaderLineage.module.css @@ -110,29 +110,18 @@ display: flex; flex-direction: column; width: 336px; - max-width: min(400px, calc(100vw - 32px)); + max-width: calc(100vw - 32px); max-height: min(560px, calc(100vh - 140px)); padding: 4px; overflow: hidden; - border-radius: var(--dsw-radius-lg); --dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l2); --dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l2); --dsw-elevation-stroke-color: var(--dsw-alias-border-l1); box-shadow: var(--dsw-elevation-prominent); } -/* The menu skin lives on a pseudo-element: backdrop-filter on the menu itself - would turn it into the containing block for fixed descendants, so the - sidebar-button Tooltip bubbles would be re-anchored into the menu and - clipped by its hidden overflow. */ -.menu::before { - content: ''; - position: absolute; - z-index: -1; - inset: 0; - border-radius: inherit; - background: var(--dsw-specific-menu); - backdrop-filter: var(--dsw-menu-backdrop-filter); +.menuWithBoard { + width: 500px; } .menuBody { @@ -147,7 +136,7 @@ position: relative; min-width: 0; } -.menuBody > .node { +.menuBody > [role="tree"] > .node { margin-left: -2px; } @@ -257,7 +246,42 @@ } .currentLabel { - font-weight: 600; + font-weight: 500; +} + +.description { + display: flex; + align-items: flex-start; + gap: 4px; + min-width: 0; + padding: 0; + border: 0; + background: transparent; + color: var(--dsw-alias-label-tertiary); + font: inherit; + text-align: left; + cursor: pointer; +} + +.description > span { + min-width: 0; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + +.description > svg { + flex: none; + margin-top: 3px; +} + +.description[aria-expanded='true'] > span { + white-space: pre-wrap; + overflow-wrap: anywhere; +} + +.description[aria-expanded='true'] > svg { + transform: rotate(90deg); } .summary, diff --git a/packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx b/packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx index e3d87c3267..23a799df2f 100644 --- a/packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx +++ b/packages/client/ui-subagent/src/client/SubagentHeaderLineage.tsx @@ -1,4 +1,4 @@ -import { useMemo, useEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type MouseEvent } from 'react' +import { useMemo, useEffect, useRef, useState, type CSSProperties, type KeyboardEvent, type MouseEvent, type ReactNode } from 'react' import { createPortal } from 'react-dom' import { type SessionProjectionMap, type SessionSummary, @@ -7,12 +7,14 @@ import { import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' import { - IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconRefreshOutlineRegular, StateDot, Tooltip, + IconChevronDownOutlineRegular, IconChevronRightOutlineRegular, IconRefreshOutlineRegular, MenuSurface, StateDot, Tooltip, } from '@deepseek-ai/dsh-client-ui-primitives' -import type { PropsLocale, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots' +import type { HostObservable, InjectFace, PropsLocale, PropsRenderSlots, PropsRuntime, TranslateNS } from '@deepseek-ai/dsh-client-ui-slots' import { NS } from './locales.ts' import type {} from '@deepseek-ai/dsh-client-ui-conversation/client' import type {} from '@deepseek-ai/dsh-token-meter/client' +import type {} from '@deepseek-ai/dsh-agent-team/client' +import type {} from './slots.ts' import css from './SubagentHeaderLineage.module.css' type SubagentCatalogSnapshot = Omit & { @@ -22,15 +24,23 @@ type SubagentCatalogSnapshot = Omit> /** Business actions supplied by the slot registration. */ -export interface SubagentCatalogInjected { +export type SubagentCatalogInjected = { openChild: (address: SubagentAddress) => void openChildAside: (address: SubagentAddress) => void refreshProjection: (parentSessionId: SessionId) => void } +/** Root catalog actions and availability of its optional footer. */ +export type SubagentCatalogActionInjected = SubagentCatalogInjected & { + hooks: { + /** Whether the catalog footer currently has a registered contributor. */ + footerAvailable: HostObservable + } +} + /** Full props for the session-header lineage renderer. */ export type SubagentHeaderLineageProps = - PropsRuntime<'conversation.session.header.lineage'> & SubagentCatalogInjected & PropsLocale + PropsRuntime<'conversation.session.header.lineage'> & InjectFace & PropsLocale interface CatalogRowsProps { parentSessionId: SessionId @@ -203,6 +213,7 @@ function CatalogRows({ openChild, openChildAside, refreshProjection, toggleBranch, closeCatalog, t, }: CatalogRowsProps & { t: TranslateNS }) { const [now, setNow] = useState(() => Date.now()) + const [expandedDescriptions, setExpandedDescriptions] = useState>(() => new Set()) const running = catalog.entries.some(entry => entry.activity === 'running') useEffect(() => { if (!running) return @@ -244,7 +255,9 @@ function CatalogRows({ const childLoading = childCatalog === undefined || (childCatalog.state === 'loading' && childCatalog.entries.length === 0) const summary = summaries[entry.id] - const label = entry.label ?? entry.id + const label = entry.id + const description = entry.label ?? summary?.title + const descriptionExpanded = expandedDescriptions.has(entry.id) const mode = external ? t('mode.external') : entry.mode === 'unknown' ? t('mode.unknown') : entry.mode === 'one-shot' ? t('mode.oneShot') : t('mode.continuable') const completed = !external && entry.activity === 'inactive' @@ -254,7 +267,7 @@ function CatalogRows({ : completed ? t('activity.completed') : t('activity.inactive') - const secondary = [summary?.title, requiresOpening ? t('catalog.migrationRequired') : mode, activity] + const secondary = [description, requiresOpening ? t('catalog.migrationRequired') : mode, activity] .filter(value => value !== undefined) .join(' · ') const totalTokens = tokenTotal(summary?.projectionValues?.tokenUsage) @@ -338,7 +351,30 @@ function CatalogRows({ {label} - {secondary} + {description !== undefined && ( + + )} + {requiresOpening && {t('catalog.migrationRequired')}} {metrics !== '' && ( @@ -409,12 +445,14 @@ function CatalogRows({ ) } -interface CatalogDropdownSharedProps extends SubagentCatalogInjected { +interface CatalogDropdownSharedProps extends InjectFace { /** Session whose direct catalog roots the tree. */ rootSessionId: SessionId useSessions: SubagentHeaderLineageProps['useSessions'] useSessionStatus: SubagentHeaderLineageProps['useSessionStatus'] t: TranslateNS + withBoard?: boolean + renderFooter?: () => ReactNode } type CatalogDropdownProps = CatalogDropdownSharedProps & ( @@ -440,9 +478,9 @@ type CatalogDropdownProps = CatalogDropdownSharedProps & ( const MENU_VIEWPORT_MARGIN = 16 /** Place a portaled catalog below its trigger without crossing the viewport edge. */ -function catalogMenuPosition(trigger: HTMLButtonElement): CSSProperties { +function catalogMenuPosition(trigger: HTMLButtonElement, withBoard: boolean): CSSProperties { const rect = trigger.getBoundingClientRect() - const width = Math.min(336, window.innerWidth - MENU_VIEWPORT_MARGIN * 2) + const width = Math.min(withBoard ? 500 : 336, window.innerWidth - MENU_VIEWPORT_MARGIN * 2) return { top: rect.bottom + 5, left: Math.min( @@ -453,10 +491,11 @@ function catalogMenuPosition(trigger: HTMLButtonElement): CSSProperties { } /** One trigger-plus-tree dropdown over the catalog rooted at `rootSessionId`. */ -function CatalogDropdown({ - rootSessionId, currentSessionId, displayTitle, openTitle, variant, - useSessions, useSessionStatus, openChild, openChildAside, refreshProjection, t, -}: CatalogDropdownProps) { +function CatalogDropdown(props: CatalogDropdownProps) { + const { + rootSessionId, currentSessionId, displayTitle, openTitle, variant, + useSessions, useSessionStatus, openChild, openChildAside, refreshProjection, t, withBoard = false, + } = props const ancestorSwitcher = variant === 'switcher' && openTitle !== undefined const projections = useSessions(state => state.projectionsBySession) const summaries = useSessions(state => state.byId) @@ -491,16 +530,16 @@ function CatalogDropdown({ const pinnedRef = useRef(false) const currentEntry = currentSessionId === undefined ? undefined - : projections[rootSessionId]?.values.subagentCatalog?.find(entry => entry.id === currentSessionId) + : catalog?.entries.find(entry => entry.id === currentSessionId) const switcherDisplayTitle = currentEntry !== undefined - ? currentEntry.label ?? currentEntry.id - : displayTitle + ? currentEntry.id + : currentSessionId ?? displayTitle const directChildren = catalog?.entries ?? [] const directCount = directChildren.length const runningCount = directChildren.filter(entry => entry.activity === 'running').length const totalCountKey = directCount === 1 ? 'count.total.one' : 'count.total.other' const runningCountKey = runningCount === 1 ? 'count.running.one' : 'count.running.other' - const presentedCatalog: SubagentCatalogSnapshot | undefined = catalog ?? (variant === 'switcher' + const presentedCatalog: SubagentCatalogSnapshot | undefined = catalog ?? (variant === 'switcher' || withBoard ? { entries: [], state: 'loading', error: null } : undefined) @@ -524,7 +563,7 @@ function CatalogDropdown({ /* v8 ignore next -- a queued callback can outlive the trigger */ if (trigger === null) return setOpen(true) - setMenuPosition(catalogMenuPosition(trigger)) + setMenuPosition(catalogMenuPosition(trigger, withBoard)) } else { pinnedRef.current = false @@ -590,7 +629,16 @@ function CatalogDropdown({ } } document.addEventListener('pointerdown', closeOutside) - return () => { document.removeEventListener('pointerdown', closeOutside) } + const dismiss = (event: globalThis.KeyboardEvent): void => { + if (event.key !== 'Escape') return + event.preventDefault() + changeOpen(false, menuRef.current?.contains(document.activeElement) === true) + } + document.addEventListener('keydown', dismiss) + return () => { + document.removeEventListener('pointerdown', closeOutside) + document.removeEventListener('keydown', dismiss) + } }, [open]) useEffect(() => { @@ -599,26 +647,26 @@ function CatalogDropdown({ const trigger = triggerRef.current /* v8 ignore next -- native resize or scroll can outlive the trigger */ if (trigger === null) return - setMenuPosition(catalogMenuPosition(trigger)) + setMenuPosition(catalogMenuPosition(trigger, withBoard)) } + placeMenu() window.addEventListener('resize', placeMenu) document.addEventListener('scroll', placeMenu, true) return () => { window.removeEventListener('resize', placeMenu) document.removeEventListener('scroll', placeMenu, true) } - }, [open]) + }, [open, withBoard]) useEffect(() => () => { cancelHoverOpen() cancelHoverClose() }, []) - // Visibility needs catalog evidence of children or a failed load worth retrying. - // An empty loading catalog is not evidence of children. + // An enabled board stays reachable before the first child is created. const visible = presentedCatalog !== undefined && (variant === 'switcher' - || presentedCatalog.state === 'error' + || withBoard || presentedCatalog.state === 'error' || presentedCatalog.entries.length > 0) useEffect(() => { if (visible) return @@ -639,12 +687,12 @@ function CatalogDropdown({ } const navigate = (event: KeyboardEvent): void => { + if (event.target instanceof Element + && event.target !== triggerRef.current && event.target !== menuRef.current + && event.target.closest('[role="tree"]') === null) return const items = treeItems(menuRef.current) const index = items.indexOf(document.activeElement as HTMLElement) - if (event.key === 'Escape') { - event.preventDefault() - changeOpen(false, true) - } else if (event.key === 'Home') { + if (event.key === 'Home') { event.preventDefault() focusAt(0) } else if (event.key === 'End') { @@ -663,6 +711,7 @@ function CatalogDropdown({
@@ -675,7 +724,7 @@ function CatalogDropdown({ className={variant === 'switcher' ? `${css.switcherTrigger} ${ancestorSwitcher ? css.ancestorSwitcherTrigger : ''}` : css.trigger} - aria-haspopup="tree" + aria-haspopup="dialog" aria-expanded={open} aria-label={variant === 'switcher' ? t('switcher.aria', { title: switcherDisplayTitle }) @@ -689,6 +738,7 @@ function CatalogDropdown({ cancelHoverClose() pinnedRef.current = true if (!open) changeOpen(true) + queueMicrotask(() => { menuRef.current?.focus() }) } : () => { cancelHoverOpen() @@ -719,33 +769,40 @@ function CatalogDropdown({ : } {open && createPortal(( -
{ if (event.pointerType !== 'touch') cancelHoverClose() }} onPointerLeave={scheduleHoverClose} > -
- { changeOpen(false) }} - t={t} - /> +
+
+ { changeOpen(false) }} + t={t} + /> +
+ {props.renderFooter?.()}
-
+ ), document.body)}
) @@ -753,7 +810,8 @@ function CatalogDropdown({ /** Full props for the root-session catalog entry in the header actions band. */ export type SubagentCatalogActionProps = - PropsRuntime<'conversation.session.header.actions'> & SubagentCatalogInjected & PropsLocale + PropsRuntime<'conversation.session.header.actions'> & InjectFace & PropsLocale + & Omit, 'SessionProvider'> /** * Session-header catalog action for root sessions: the descendant count and @@ -763,16 +821,27 @@ export type SubagentCatalogActionProps = * @param props - Session standard props plus the catalog actions and translator. * @returns The count dropdown, or null on a child session. */ -export function SubagentCatalogAction({ - sessionId, useSessions, useSessionStatus, openChild, openChildAside, refreshProjection, t, +export function SubagentCatalogAction(props: SubagentCatalogActionProps) { + const addressedChild = props.useSession(snapshot => snapshot.subagent !== null) + const listedChild = props.useSessions(state => state.byId[props.sessionId]?.origin === 'subagent') + if (addressedChild || listedChild) return null + return +} + +/** Bind task-board visibility only in root conversations. */ +function RootCatalogAction({ + sessionId, useProjection, useSessions, useSessionStatus, useFooterAvailable, + openChild, openChildAside, refreshProjection, t, renderSlot, }: SubagentCatalogActionProps) { - const isChild = useSessions(state => state.byId[sessionId]?.origin === 'subagent') - if (isChild) return null + const teamEnabled = useProjection('agentTeam', team => team?.enabled === true) + const footerAvailable = useFooterAvailable(value => value) return ( renderSlot('conversation.session.header.actions.subagent.footer', {})} useSessions={useSessions} useSessionStatus={useSessionStatus} openChild={openChild} diff --git a/packages/client/ui-subagent/src/client/index.ts b/packages/client/ui-subagent/src/client/index.ts index c88673b0c2..a84992246d 100644 --- a/packages/client/ui-subagent/src/client/index.ts +++ b/packages/client/ui-subagent/src/client/index.ts @@ -3,7 +3,7 @@ import type { Context as ClientContext } from '@deepseek-ai/cordis' import type { SubagentAddress } from '@deepseek-ai/dsh-subagent/client' import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client' -import { SubagentCatalogAction, SubagentHeaderLineage, type SubagentCatalogInjected } from './SubagentHeaderLineage.tsx' +import { SubagentCatalogAction, SubagentHeaderLineage, type SubagentCatalogInjected, type SubagentCatalogActionInjected } from './SubagentHeaderLineage.tsx' import { SubagentReadOnlyComposer, type SubagentReadOnlyMatch, } from './SubagentReadOnlyComposer.tsx' @@ -15,6 +15,7 @@ import type {} from '@deepseek-ai/dsh-client-ui-session/client' import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client' import type {} from '@deepseek-ai/dsh-client-ui-workspace/client' import { en, ja, es, fr, de, ru, pt, NS, zh, type SubagentKey } from './locales.ts' +export type * from './slots.ts' declare module '@deepseek-ai/dsh-client-ui-slots' { interface LocaleNamespaceMap { @@ -24,7 +25,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' { } export type { - SubagentCatalogActionProps, SubagentCatalogInjected, SubagentHeaderLineageProps, + SubagentCatalogActionProps, SubagentCatalogInjected, SubagentCatalogActionInjected, SubagentHeaderLineageProps, } from './SubagentHeaderLineage.tsx' export type { SubagentReadOnlyComposerProps, SubagentReadOnlyMatch, @@ -65,6 +66,10 @@ export function apply(ctx: ClientContext): void { void ctx.sessions.refreshProjections(parentSessionId) }, }) + const footerAvailable = { + getSnapshot: () => ctx.slots.entries('conversation.session.header.actions.subagent.footer').length > 0, + subscribe: (listener: () => void) => ctx.slots.subscribe('conversation.session.header.actions.subagent.footer', listener), + } ctx.slots.inject( 'conversation.session.header.lineage', () => ctx.slots.register({ @@ -81,7 +86,10 @@ export function apply(ctx: ClientContext): void { // Between the preset label (-10) and background tasks (20). order: 10, locale: NS, - inject: catalogActions, + inject: (sessionId): SubagentCatalogActionInjected => ({ + ...catalogActions(sessionId), hooks: { footerAvailable }, + }), + children: { 'conversation.session.header.actions.subagent.footer': { kind: 'list', scope: 'session' } }, }, SubagentCatalogAction), ) ctx.slots.inject( diff --git a/packages/client/ui-subagent/src/client/locales.ts b/packages/client/ui-subagent/src/client/locales.ts index ea4a4bbc67..85147e8cda 100644 --- a/packages/client/ui-subagent/src/client/locales.ts +++ b/packages/client/ui-subagent/src/client/locales.ts @@ -22,6 +22,8 @@ export const zh = { 'loading.label': '正在加载子智能体…', 'load.error': '无法加载子智能体', 'catalog.migrationRequired': '需要迁移', + 'description.expand': '展开 {name} 的描述', + 'description.collapse': '收起 {name} 的描述', 'retry': '重试', 'mode.external': '外部任务', 'mode.oneShot': '一次性', @@ -67,6 +69,8 @@ export const en: Record = { 'loading.label': 'Loading subagents…', 'load.error': 'Unable to load subagents', 'catalog.migrationRequired': 'Migration required', + 'description.expand': 'Expand description for {name}', + 'description.collapse': 'Collapse description for {name}', 'retry': 'Retry', 'mode.external': 'External task', 'mode.oneShot': 'one-shot', @@ -98,6 +102,8 @@ export type SubagentKey = keyof typeof zh /** Japanese copy, checked against the English keys. */ export const ja = { + 'description.expand': '{name} の説明を展開', + 'description.collapse': '{name} の説明を折りたたむ', 'duration.seconds': '{seconds}秒', 'duration.minutes': '{minutes}分{seconds}秒', 'duration.hours': '{hours}時間{minutes}分{seconds}秒', @@ -143,6 +149,8 @@ export const ja = { /** Spanish dictionary, checked against the English keys. */ export const es = { + 'description.expand': 'Expandir descripción de {name}', + 'description.collapse': 'Contraer descripción de {name}', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'duration.hours': '{hours}h {minutes}m {seconds}s', @@ -188,6 +196,8 @@ export const es = { /** French dictionary, checked against the English keys. */ export const fr = { + 'description.expand': 'Développer la description de {name}', + 'description.collapse': 'Réduire la description de {name}', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'duration.hours': '{hours}h {minutes}m {seconds}s', @@ -233,6 +243,8 @@ export const fr = { /** German dictionary, checked against the English keys. */ export const de = { + 'description.expand': 'Beschreibung für {name} erweitern', + 'description.collapse': 'Beschreibung für {name} einklappen', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'duration.hours': '{hours}h {minutes}m {seconds}s', @@ -278,6 +290,8 @@ export const de = { /** Russian dictionary, checked against the English keys. */ export const ru = { + 'description.expand': 'Развернуть описание {name}', + 'description.collapse': 'Свернуть описание {name}', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'duration.hours': '{hours}h {minutes}m {seconds}s', @@ -323,6 +337,8 @@ export const ru = { /** Portuguese dictionary, checked against the English keys. */ export const pt = { + 'description.expand': 'Expandir descrição de {name}', + 'description.collapse': 'Recolher descrição de {name}', 'duration.seconds': '{seconds}s', 'duration.minutes': '{minutes}m {seconds}s', 'duration.hours': '{hours}h {minutes}m {seconds}s', diff --git a/packages/client/ui-subagent/src/client/slots.ts b/packages/client/ui-subagent/src/client/slots.ts new file mode 100644 index 0000000000..a3e379e62f --- /dev/null +++ b/packages/client/ui-subagent/src/client/slots.ts @@ -0,0 +1,13 @@ +/** Extension position below the root conversation's subagent catalog. */ + +declare module '@deepseek-ai/dsh-client-ui-slots' { + interface SlotMap { + /** Content below the root Session's subagent tree. */ + 'conversation.session.header.actions.subagent.footer': { + kind: 'list' + scope: 'session' + } + } +} + +export {} diff --git a/packages/client/ui-subagent/tests/browser-plugin.client.spec.ts b/packages/client/ui-subagent/tests/browser-plugin.client.spec.ts index af5e16e286..b5006a277b 100644 --- a/packages/client/ui-subagent/tests/browser-plugin.client.spec.ts +++ b/packages/client/ui-subagent/tests/browser-plugin.client.spec.ts @@ -2,7 +2,7 @@ import { createSnapshotStore } from '@deepseek-ai/dsh-client-store' import { Context } from '@deepseek-ai/cordis' import { stubConfigForm } from '@deepseek-ai/dsh-client-test-runtime' -import { describe, expect, it } from 'vitest' +import { describe, expect, it, onTestFinished, vi } from 'vitest' import type { SessionListState, SessionSnapshot, SessionSummary, } from '@deepseek-ai/dsh-api-session-controller/client' @@ -12,7 +12,7 @@ import type { SessionId } from '@deepseek-ai/dsh-session/types' import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client' import { apply as applyLocale, inject as localeInject } from '@deepseek-ai/dsh-client-locale/client' import { - SubagentHeaderLineage, type SubagentCatalogInjected, + SubagentHeaderLineage, type SubagentCatalogInjected, type SubagentCatalogActionInjected, } from '../src/client/SubagentHeaderLineage.tsx' import { SubagentReadOnlyComposer, type SubagentReadOnlyMatch, @@ -108,6 +108,19 @@ describe('apply', () => { expect(inject).toEqual(['sessions', 'uiWorkspace', 'slots', 'locale', 'sidebarRight']) }) + it('keeps the root catalog extension available when optional Sidebar services arrive', async () => { + const { ctx } = await fullBench(FAMILY) + onTestFinished(() => ctx.fiber.dispose()) + ctx.slots.register({ name: 'conversation.session.header.actions.subagent.footer', id: 'test-content' }, () => null) + const registerResource = vi.fn(() => () => {}) + const registerTab = vi.fn(() => () => {}) + ctx.provide('resources', { register: registerResource } as never) + ctx.provide('sidebarRightTabs', { register: registerTab } as never) + await vi.waitFor(() => { expect(registerResource).toHaveBeenCalledOnce() }) + expect(registerTab).toHaveBeenCalledOnce() + expect(ctx.slots.entries('conversation.session.header.actions.subagent.footer')).toHaveLength(1) + }) + it('registers catalog actions and selects read-only subagent composers from session facts', async () => { const { ctx, face } = await fullBench(FAMILY) const catalogEntry = ctx.slots.entries('conversation.session.header.lineage') @@ -139,7 +152,20 @@ describe('apply', () => { const actionEntry = ctx.slots.entries('conversation.session.header.actions') .find(entry => entry.options.id === 'subagent-catalog')! expect(actionEntry.options.order).toBe(10) - expect(actionEntry.inject).toBe(catalogEntry.inject) + const actionProps = (actionEntry.inject as (id: SessionId) => SubagentCatalogActionInjected)(sid('parent')) + const source = actionProps.hooks.footerAvailable + const changed = vi.fn() + const unsubscribe = source.subscribe(changed) + expect(source.getSnapshot()).toBe(false) + const remove = ctx.slots.register({ + name: 'conversation.session.header.actions.subagent.footer', id: 'test-footer', + }, () => null) + expect(source.getSnapshot()).toBe(true) + await vi.waitFor(() => { expect(changed).toHaveBeenCalledOnce() }) + remove() + expect(source.getSnapshot()).toBe(false) + await vi.waitFor(() => { expect(changed).toHaveBeenCalledTimes(2) }) + unsubscribe() const composerEntry = ctx.slots.entries('conversation.composer') .find(entry => entry.component === SubagentReadOnlyComposer)! diff --git a/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx b/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx index e4274bce7f..52e7ca7cc4 100644 --- a/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx +++ b/packages/client/ui-subagent/tests/conversation-ui.client.spec.tsx @@ -27,6 +27,9 @@ const CHILD = 'child' as SessionId const GRANDCHILD = 'grandchild' as SessionId const t: SubagentHeaderLineageProps['t'] = makeTranslate(zh) +type HeaderCatalogProps = SubagentHeaderLineageProps + & Pick + type CatalogFixtureEntry = Entry extends SubagentCatalogEntry ? Omit & { activity: 'running' | 'inactive' } : never @@ -78,7 +81,9 @@ function props( phase: 'ready', projectionsBySession: Object.fromEntries(Object.entries(catalogs).map(([id, catalog]) => [id, { state: catalog.state, error: catalog.error, - values: { subagentCatalog: catalog.entries.map(({ activity: _activity, ...entry }) => ({ ...entry, createdAt: 1 })) }, + values: { + subagentCatalog: catalog.entries.map(({ activity: _activity, ...entry }) => ({ ...entry, createdAt: 1 })), + }, }])) , } satisfies SessionListState function useSessions(select: (snapshot: SessionListState) => T): T { @@ -117,15 +122,17 @@ function props( ...standard, sessionId: PARENT, useSessions, + useFooterAvailable: (select: (available: boolean) => T): T => select(true), useSessionStatus: (select: (snapshot: SessionStatusSnapshot) => T): T => select(statuses), useSession: (select: (snapshot: SessionSnapshot) => T): T => select(snapshot), openChild: vi.fn(), openChildAside: vi.fn(), refreshProjection: vi.fn(), + renderSlot: vi.fn(() => null), lineageSessionId: PARENT, displayTitle: 'Parent title', t, - } satisfies SubagentHeaderLineageProps + } satisfies HeaderCatalogProps } function summary(id: SessionId, updatedAt: number): SessionSummary { @@ -151,8 +158,12 @@ function hoverCatalog(trigger: HTMLElement): void { * sessions) — exactly one renders per form. The actions seat sees the same * session the lineage renders. */ -function HeaderCatalog(props: SubagentHeaderLineageProps) { - const actionProps: SubagentCatalogActionProps = { ...props, sessionId: props.lineageSessionId } +function HeaderCatalog(props: HeaderCatalogProps) { + const actionProps: SubagentCatalogActionProps = { + ...props, sessionId: props.lineageSessionId, + useProjection: ((_key: 'agentTeam', selector: (value: { enabled: boolean } | undefined) => boolean) => + props.useSessions(state => selector(state.projectionsBySession[props.lineageSessionId]?.values.agentTeam))) as SubagentCatalogActionProps['useProjection'], + } return ( <> @@ -162,6 +173,92 @@ function HeaderCatalog(props: SubagentHeaderLineageProps) { } describe('SubagentHeaderLineage', () => { + it('shows the Session id above its description and expands it without navigating', () => { + const description = 'Check the construction and all bounds. '.repeat(20) + const input = props(catalog({ + entries: [{ id: CHILD, mode: 'continuable', label: description, activity: 'running' }], + })) + render() + fireEvent.click(screen.getByRole('button', { name: '1 个子智能体,正在运行' })) + const row = screen.getByRole('treeitem', { name: /^child / }) + const name = within(row).getByText('child') + const toggle = within(row).getByRole('button', { name: '展开 child 的描述' }) + expect(name.compareDocumentPosition(toggle) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() + expect(toggle.textContent).toBe(description) + expect(toggle.getAttribute('aria-expanded')).toBe('false') + fireEvent.keyDown(toggle, { key: 'Enter' }) + fireEvent.keyDown(toggle, { key: ' ' }) + expect(input.openChild).not.toHaveBeenCalled() + fireEvent.click(toggle) + expect(toggle.getAttribute('aria-expanded')).toBe('true') + expect(input.openChild).not.toHaveBeenCalled() + fireEvent.click(toggle) + expect(toggle.getAttribute('aria-expanded')).toBe('false') + fireEvent.keyDown(toggle, { key: 'Escape' }) + expect(screen.queryByRole('treeitem')).toBeNull() + }) + + it('keeps the shared panel reachable with no children while Team mode is enabled', () => { + const input = props(catalog({ entries: [] })) + const initial = input.useSessions(state => state) + const mode = { enabled: true, tasks: [] } + const values = { ...initial.projectionsBySession[PARENT]!.values, agentTeam: mode } + const current = { + ...input, + useSessions: (select: (snapshot: SessionListState) => T): T => select({ + ...initial, projectionsBySession: { [PARENT]: { state: 'ready', error: null, values } }, + }), + renderSlot: () =>
No tasks
, + } + const view = render() + fireEvent.click(screen.getByRole('button', { name: '0 个子智能体' })) + const tree = screen.getByRole('tree') + const board = screen.getByRole('region', { name: 'Shared tasks' }) + expect(tree.compareDocumentPosition(board) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy() + expect(screen.getAllByRole('dialog')).toHaveLength(1) + mode.enabled = false + view.rerender() + expect(screen.queryByRole('dialog')).toBeNull() + expect(screen.queryByRole('button')).toBeNull() + }) + + it.each([false, true])('keeps child navigation without reading or rendering a task board (listed: %s)', (listed) => { + const input = props(catalog(), {}, listed ? { + [CHILD]: { ...summary(CHILD, 1), origin: 'subagent', parentId: PARENT }, + } : {}, { parentSessionId: PARENT, childSessionId: CHILD, mode: 'continuable' }) + const readProjection = vi.fn((): never => { throw new Error('child task-board projection read') }) + const readFooter = vi.fn((): never => { throw new Error('child footer subscription') }) + const renderFooter = vi.fn(() =>
Tasks
) + render(<> + + + ) + fireEvent.click(screen.getByRole('button', { name: /切换子智能体/u })) + expect(screen.getByRole('tree')).toBeTruthy() + expect(screen.queryByRole('region', { name: 'Shared tasks' })).toBeNull() + expect(readProjection).not.toHaveBeenCalled() + expect(readFooter).not.toHaveBeenCalled() + expect(renderFooter).not.toHaveBeenCalled() + }) + + it('leaves task-board keys alone and dismisses the shared panel from outside focus', () => { + const input = props(catalog()) + render(<>