test,docs: fix all stale references to deleted multi-agent subsystem
Test fixes (6 files, -303 lines): - Delete app_controller_acp_mount_resilience_test.dart (entirely about deleted types) - Remove multi-agent test cases from gateway_acp_client_auth_test.dart - Rename _manifestWithDesktopMultiAgentEnabled → _defaultDesktopManifest in assistant_execution_target_test, assistant_lower_pane_test, mobile_assistant_page_test Docs fixes (6 files): - Regenerate public-symbol-inventory.json/md via make docs-public-api - Remove multi-agent sections from public-api/models-and-config.md, app-orchestration.md, runtime-contracts.md - Fix xworkmate/ → xworkmate-app/ paths in cloud-session doc - Remove multiAgent references from app-external-service-api-test-matrix.md
This commit is contained in:
parent
c320992b95
commit
b3da52d0c5
@ -20,15 +20,15 @@ Terminal 先不作为当前目标的一等端,只保留为未来可扩展方
|
||||
|
||||
当前仓库里已经存在一组很适合作为目标架构起点的实现:
|
||||
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/runtime/runtime_models_runtime_payloads.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/runtime/runtime_models_runtime_payloads.dart)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/runtime_models_runtime_payloads.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/runtime/runtime_models_runtime_payloads.dart)
|
||||
已定义 `TaskThread`、`WorkspaceBinding`、`ExecutionBinding`、`ThreadContextState`、`ThreadLifecycleState` 等核心线程模型。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_session_repository.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_session_repository.dart)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_session_repository.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_session_repository.dart)
|
||||
已有浏览器本地缓存和远端会话仓库的抽象雏形。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_store.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_store.dart)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_store.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_store.dart)
|
||||
已有 Web 端本地持久化、会话 client id、远端 relay token 的存储能力。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/app/app_controller_web_gateway_relay.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/app/app_controller_web_gateway_relay.dart)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_web_gateway_relay.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_web_gateway_relay.dart)
|
||||
已有远端 relay 连接、会话刷新、历史同步和模型刷新逻辑。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/app/app_controller_desktop_thread_sessions.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/app/app_controller_desktop_thread_sessions.dart)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/app/app_controller_desktop_thread_sessions.dart)
|
||||
已有桌面端围绕 `sessionKey` / `TaskThread` 的线程上下文读取逻辑。
|
||||
|
||||
因此,目标不是重写,而是把“本地控制器 + relay + 线程模型”升级成“跨端会话平台”。
|
||||
@ -288,7 +288,7 @@ Terminal 先不纳入当前落地范围。
|
||||
|
||||
### 7.2 `WebSessionRepository` 升级方向
|
||||
|
||||
当前 [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_session_repository.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_session_repository.dart) 已经有 `BrowserWebSessionRepository` 和 `RemoteWebSessionRepository`。
|
||||
当前 [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_session_repository.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_session_repository.dart) 已经有 `BrowserWebSessionRepository` 和 `RemoteWebSessionRepository`。
|
||||
|
||||
建议后续演进为:
|
||||
|
||||
@ -304,7 +304,7 @@ Terminal 先不纳入当前落地范围。
|
||||
|
||||
### 7.3 `WebStore` 的边界
|
||||
|
||||
当前 [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_store.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/lib/web/web_store.dart) 适合作为:
|
||||
当前 [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_store.dart`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/lib/web/web_store.dart) 适合作为:
|
||||
|
||||
- client id 存储
|
||||
- 本地草稿缓存
|
||||
@ -456,11 +456,11 @@ sequenceDiagram
|
||||
|
||||
## 12. 与现有文档的关系
|
||||
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/task-control-plane-unification.md`](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/task-control-plane-unification.md)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-control-plane-unification.md`](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-control-plane-unification.md)
|
||||
说明当前 `TaskThread` 主模型。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/task-thread-session-key-isolation-20260329.md`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/task-thread-session-key-isolation-20260329.md)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-thread-session-key-isolation-20260329.md`](file:///Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/task-thread-session-key-isolation-20260329.md)
|
||||
说明 `sessionKey` 与线程身份隔离约束。
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/xworkmate-layered-architecture.md`](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate/docs/architecture/xworkmate-layered-architecture.md)
|
||||
- [`/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/xworkmate-layered-architecture.md`](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/xworkmate-layered-architecture.md)
|
||||
说明当前内部状态如何围绕 `TaskThread` 组织。
|
||||
|
||||
本文在这些基础上进一步上升一层,定义跨设备会话的目标架构。
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -45,9 +45,7 @@
|
||||
| `accountClientFactory` | `AccountRuntimeClient Function(String)?` | No | default impl | account runtime client 构造 |
|
||||
| `environmentOverride` | `Map<String, String>?` | No | `null` | 测试/运行时环境覆盖 |
|
||||
| `singleAgentSharedSkillScanRootOverrides` | `List<String>?` | No | `null` | 共享 skill 扫描根覆写 |
|
||||
| `arisBundleRepository` | `ArisBundleRepository?` | No | default impl | ARIS bundle 发现仓库 |
|
||||
| `goTaskServiceClient` | `GoTaskServiceClient?` | No | `DesktopGoTaskService` | 外部 ACP / gateway 任务入口 |
|
||||
| `multiAgentMountManager` | `MultiAgentMountManager?` | No | default impl | 多 agent mount 管理器 |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -161,9 +159,6 @@
|
||||
| --- | --- | --- | --- |
|
||||
| `loadAssistantArtifactSnapshot` | `{String? sessionKey}` | `Future<AssistantArtifactSnapshot>` | 加载某会话 artifact 清单快照 |
|
||||
| `loadAssistantArtifactPreview` | `AssistantArtifactEntry artifact, {String? sessionKey}` | `Future<AssistantArtifactPreview>` | 读取 artifact 预览内容 |
|
||||
| `saveMultiAgentConfig` | `MultiAgentConfig config` | `Future<void>` | 持久化多 agent 配置 |
|
||||
| `refreshMultiAgentMounts` | `{bool sync=false}` | `Future<void>` | 刷新或同步 mount |
|
||||
| `runMultiAgentCollaboration` | named args | `Future<void>` | 触发多 agent 协作执行 |
|
||||
| `openOnlineWorkspace` | none | `Future<void>` | 打开在线工作区入口 |
|
||||
|
||||
## `AppControllerDesktopWorkspaceExecution`
|
||||
|
||||
@ -184,49 +184,6 @@
|
||||
| `copyWith(...)` | `AiGatewayProfile` | 更新 profile |
|
||||
| `toJson()` / `fromJson(...)` | contract mapping | 序列化 / 恢复 |
|
||||
|
||||
## `MultiAgentConfig`
|
||||
|
||||
- Source: `lib/runtime/runtime_models_multi_agent.dart`
|
||||
- Type: `class`
|
||||
- Responsibility:
|
||||
描述 native / ARIS 多 agent 协作的总配置,包括角色 worker、迭代次数、超时、AI gateway 注入策略、managed skills/MCP/mount targets。
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
| Param Group | Meaning |
|
||||
| --- | --- |
|
||||
| enable/framework | `enabled`, `autoSync`, `framework`, `arisEnabled`, `arisMode`, `arisBundleVersion`, `arisCompatStatus` |
|
||||
| worker roles | `architect`, `engineer`, `tester` |
|
||||
| execution policy | `ollamaEndpoint`, `maxIterations`, `minAcceptableScore`, `timeoutSeconds`, `aiGatewayInjectionPolicy` |
|
||||
| managed assets | `managedSkills`, `managedMcpServers`, `mountTargets` |
|
||||
|
||||
### Key Returns
|
||||
|
||||
| API / Getter | Returns | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `defaults()` | `MultiAgentConfig` | 默认协作配置 |
|
||||
| `architectEnabled` / `architectTool` / `architectModel` | primitive | Architect 快捷访问 |
|
||||
| `engineerTool` / `engineerModel` | primitive | Engineer 快捷访问 |
|
||||
| `testerTool` / `testerModel` | primitive | Tester 快捷访问 |
|
||||
| `usesAris` | `bool` | 当前是否落到 ARIS |
|
||||
|
||||
## `AgentWorkerConfig`
|
||||
|
||||
- Source: `lib/runtime/runtime_models_multi_agent.dart`
|
||||
- Type: `class`
|
||||
- Responsibility:
|
||||
描述单个角色 worker 的 CLI、模型与重试策略。
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
| Param | Type | Required | Default | Meaning |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| `role` | `MultiAgentRole` | Yes | none | 角色 |
|
||||
| `cliTool` | `String` | Yes | none | `claude/codex/opencode/...` |
|
||||
| `model` | `String` | Yes | none | 对应模型 |
|
||||
| `enabled` | `bool` | Yes | none | 是否启用 |
|
||||
| `maxRetries` | `int` | No | `2` | 最大重试次数 |
|
||||
|
||||
## `AppTheme` / `AppPalette`
|
||||
|
||||
- Source: `lib/theme/app_theme.dart`, `lib/theme/app_palette.dart`
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
- task request / routing request / provider catalog
|
||||
- settings 与 secret 持久化访问
|
||||
- gateway session / agent / chat 控制器
|
||||
- desktop 平台能力、skill 目录授权、多 agent mount
|
||||
- desktop 平台能力、skill 目录授权
|
||||
- Codex CLI 与 config bridge
|
||||
|
||||
## `GatewayAcpException`
|
||||
@ -37,14 +37,13 @@
|
||||
- Source: `lib/runtime/gateway_acp_client.dart`
|
||||
- Type: `class`
|
||||
- Responsibility:
|
||||
保存 `acp.capabilities` 的解析结果,是 app 侧 single-agent / multi-agent / execution-target / provider catalog 的只读快照。
|
||||
保存 `acp.capabilities` 的解析结果,是 app 侧 single-agent / execution-target / provider catalog 的只读快照。
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
| Param | Type | Required | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `singleAgent` | `bool` | Yes | 是否支持单 agent |
|
||||
| `multiAgent` | `bool` | Yes | 是否支持多 agent |
|
||||
| `availableExecutionTargets` | `List<AssistantExecutionTarget>` | Yes | 当前 bridge 允许的执行目标 |
|
||||
| `providerCatalog` | `List<SingleAgentProvider>` | Yes | agent 侧 provider catalog |
|
||||
| `gatewayProviderCatalog` | `List<SingleAgentProvider>` | Yes | gateway 侧 provider catalog |
|
||||
@ -57,25 +56,6 @@
|
||||
| --- | --- | --- |
|
||||
| `GatewayAcpCapabilities.empty()` | `GatewayAcpCapabilities` | 空 capability 占位 |
|
||||
|
||||
## `GatewayAcpMultiAgentRequest`
|
||||
|
||||
- Source: `lib/runtime/gateway_acp_client.dart`
|
||||
- Type: `class`
|
||||
- Responsibility:
|
||||
描述一次 multi-agent session.start / session.message 请求。
|
||||
|
||||
### Constructor Parameters
|
||||
|
||||
| Param | Type | Required | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `sessionId` | `String` | Yes | 协作会话 ID |
|
||||
| `threadId` | `String` | Yes | thread ID |
|
||||
| `prompt` | `String` | Yes | 主任务提示词 |
|
||||
| `workingDirectory` | `String` | Yes | 工作目录 |
|
||||
| `attachments` | `List<CollaborationAttachment>` | Yes | 本地文件附件 |
|
||||
| `selectedSkills` | `List<String>` | Yes | 显式选中的技能键 |
|
||||
| `resumeSession` | `bool` | Yes | `false` 时发 `session.start`,`true` 时发 `session.message` |
|
||||
|
||||
## `GatewayAcpClient`
|
||||
|
||||
- Source: `lib/runtime/gateway_acp_client.dart`
|
||||
@ -95,19 +75,17 @@
|
||||
| Method | Parameters | Returns | Meaning |
|
||||
| --- | --- | --- | --- |
|
||||
| `loadCapabilities` | `{bool forceRefresh=false, Uri? endpointOverride, String authorizationOverride=''}` | `Future<GatewayAcpCapabilities>` | 拉取并缓存 15 秒 capability 快照 |
|
||||
| `runMultiAgent` | `GatewayAcpMultiAgentRequest request` | `Stream<MultiAgentRunEvent>` | 打开 ACP multi-agent 事件流 |
|
||||
|
||||
### Main Call Chain
|
||||
|
||||
- `AppController.refreshAcpCapabilitiesRuntimeInternal` -> `loadCapabilities`
|
||||
- `GoRuntimeDispatchDesktopClient` / multi-agent flows -> `GatewayAcpClient`
|
||||
- `GoRuntimeDispatchDesktopClient` -> `GatewayAcpClient`
|
||||
- `GatewayAcpClient` -> ACP JSON-RPC -> bridge
|
||||
|
||||
### Side Effects
|
||||
|
||||
- 网络请求
|
||||
- 本地 capability 缓存
|
||||
- 将 ACP 通知转换成 `MultiAgentRunEvent`
|
||||
|
||||
## `ExternalCodeAgentAcpCapabilities`
|
||||
|
||||
@ -188,14 +166,11 @@
|
||||
| `provider` | `SingleAgentProvider` | No | 当前 provider |
|
||||
| `remoteWorkingDirectoryHint` | `String` | No | 远端工作目录 hint |
|
||||
| `resumeSession` | `bool` | No | 是否续跑 |
|
||||
| `collaborationMode` | `GoTaskServiceCollaborationMode` | No | standard / multiAgent |
|
||||
| `multiAgent` | `bool` | No | 是否强制多 agent |
|
||||
|
||||
### Returns
|
||||
|
||||
| Getter / Method | Returns | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `isMultiAgentRequest` | `bool` | 是否走多 agent route |
|
||||
| `route` | `GoTaskServiceRoute` | 计算出的实际 route |
|
||||
| `acpMode` | `String` | ACP session mode |
|
||||
| `routingExecutionTarget` | `String` | 发送给 routing 的目标值 |
|
||||
|
||||
@ -76,7 +76,7 @@ Last Updated: 2026-04-22
|
||||
| accounts | `/api/auth/login` | `POST` | 无 | 登录并拿 session token | Apple 审核只读账号使用 |
|
||||
| accounts | `/api/auth/session` | `GET` | `Authorization: Bearer <session token>` | 获取当前会话和用户信息 | APP 端登录态校验 |
|
||||
| accounts | `/api/auth/xworkmate/profile/sync` | `GET` | `Authorization: Bearer <session token>` | 拉取 bridge 同步元数据 | 返回 `BRIDGE_SERVER_URL` / `BRIDGE_AUTH_TOKEN` |
|
||||
| bridge | `/acp/rpc` | `POST` | `Authorization: Bearer <BRIDGE_AUTH_TOKEN>` | bridge JSON-RPC 主入口 | capabilities、routing、agent / multi-agent 任务、OpenClaw gateway 任务、cancel、close |
|
||||
| bridge | `/acp/rpc` | `POST` | `Authorization: Bearer <BRIDGE_AUTH_TOKEN>` | bridge JSON-RPC 主入口 | capabilities、routing、agent 任务、OpenClaw gateway 任务、cancel、close |
|
||||
| bridge | `acp.capabilities` | JSON-RPC method | 同上 | 拉取 provider catalog / target catalog | capability 只读快照 |
|
||||
| bridge | `xworkmate.routing.resolve` | JSON-RPC method | 同上 | 解析 provider / gateway / skills 路由 | 返回 resolved / unavailable 信息 |
|
||||
| bridge | `session.start` | JSON-RPC method | 同上 | 开启新会话 | session 生命周期起点 |
|
||||
@ -243,7 +243,6 @@ Last Updated: 2026-04-22
|
||||
- `providerCatalog`
|
||||
- `gatewayProviders`
|
||||
- `singleAgent`
|
||||
- `multiAgent`
|
||||
- `capabilities`
|
||||
|
||||
#### 当前实测结果
|
||||
@ -580,7 +579,7 @@ Last Updated: 2026-04-22
|
||||
### 8.3 建议断言
|
||||
|
||||
- `Authorization` 必须是 `Bearer <token>`
|
||||
- capability、routing、agent / multi-agent 任务、OpenClaw gateway `session.start` / `session.message`、`session.cancel`、`session.close` 请求路径必须是 `/acp/rpc`
|
||||
- capability、routing、agent 任务、OpenClaw gateway `session.start` / `session.message`、`session.cancel`、`session.close` 请求路径必须是 `/acp/rpc`
|
||||
- 不允许出现 `/acp-server/` 直连请求
|
||||
- 不允许出现 `/gateway/openclaw` app-facing 请求;OpenClaw gateway 目标必须通过 routing metadata 表达
|
||||
- `session.cancel` / `session.close` 必须接受 `sessionId` + `threadId`
|
||||
@ -610,7 +609,7 @@ Last Updated: 2026-04-22
|
||||
|
||||
- 账户侧负责登录与同步元数据
|
||||
- bridge 侧负责 capability、路由解析和会话生命周期
|
||||
- capability、routing、agent / multi-agent 任务、OpenClaw gateway 任务、cancel、close 经过 `/acp/rpc`
|
||||
- capability、routing、agent 任务、OpenClaw gateway 任务、cancel、close 经过 `/acp/rpc`
|
||||
- OpenClaw `session.start` / `session.message` 通过 `/acp/rpc` routing metadata 表达 gateway/openclaw 目标
|
||||
- `session.cancel` / `session.close` 的协议入口已验证可用
|
||||
- 当前剩余风险集中在桥接后端下游 provider 连接,而不是 APP 侧接口拼接
|
||||
|
||||
@ -64,7 +64,7 @@ void main() {
|
||||
testWidgets('shows mode-specific provider catalogs', (tester) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -214,7 +214,7 @@ void main() {
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -264,7 +264,7 @@ void main() {
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -377,7 +377,7 @@ void main() {
|
||||
(tester) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -733,19 +733,9 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
UiFeatureManifest _manifestWithDesktopMultiAgentEnabled() {
|
||||
UiFeatureManifest _defaultDesktopManifest() {
|
||||
return UiFeatureManifest.fromYamlString(
|
||||
File(UiFeatureManifest.assetPath).readAsStringSync(),
|
||||
).copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'assistant',
|
||||
feature: 'multi_agent',
|
||||
enabled: true,
|
||||
buildModes: const <UiFeatureBuildMode>{
|
||||
UiFeatureBuildMode.debug,
|
||||
UiFeatureBuildMode.profile,
|
||||
UiFeatureBuildMode.release,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -105,7 +105,7 @@ void main() {
|
||||
) async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
],
|
||||
@ -248,18 +248,8 @@ Widget _buildTestApp({
|
||||
);
|
||||
}
|
||||
|
||||
UiFeatureManifest _manifestWithDesktopMultiAgentEnabled() {
|
||||
UiFeatureManifest _defaultDesktopManifest() {
|
||||
return UiFeatureManifest.fromYamlString(
|
||||
File(UiFeatureManifest.assetPath).readAsStringSync(),
|
||||
).copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'assistant',
|
||||
feature: 'multi_agent',
|
||||
enabled: true,
|
||||
buildModes: const <UiFeatureBuildMode>{
|
||||
UiFeatureBuildMode.debug,
|
||||
UiFeatureBuildMode.profile,
|
||||
UiFeatureBuildMode.release,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,163 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:xworkmate/app/app_controller.dart';
|
||||
import 'package:xworkmate/runtime/multi_agent_mount_resolver.dart';
|
||||
import 'package:xworkmate/runtime/multi_agent_mounts.dart';
|
||||
import 'package:xworkmate/runtime/runtime_models.dart';
|
||||
import 'package:xworkmate/runtime/secure_config_store.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'capability refresh does not fail when remote mount reconcile is absent',
|
||||
() async {
|
||||
final server = await _CapabilityServer.start();
|
||||
addTearDown(server.close);
|
||||
|
||||
final storeRoot = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-mount-resilience-',
|
||||
);
|
||||
addTearDown(() async {
|
||||
if (await storeRoot.exists()) {
|
||||
await storeRoot.delete(recursive: true);
|
||||
}
|
||||
});
|
||||
|
||||
final store = SecureConfigStore(
|
||||
secretRootPathResolver: () async => '${storeRoot.path}/secrets',
|
||||
appDataRootPathResolver: () async => '${storeRoot.path}/app-data',
|
||||
supportRootPathResolver: () async => '${storeRoot.path}/support',
|
||||
);
|
||||
await store.initialize();
|
||||
final settings = SettingsSnapshot.defaults();
|
||||
final bridgeConfig = settings.acpBridgeServerModeConfig;
|
||||
final selfHosted = bridgeConfig.selfHosted.copyWith(
|
||||
serverUrl: server.endpoint,
|
||||
username: 'admin',
|
||||
);
|
||||
await store.saveSecretValueByRef(selfHosted.passwordRef, 'bridge-token');
|
||||
await store.saveSettingsSnapshot(
|
||||
settings.copyWith(
|
||||
acpBridgeServerModeConfig: bridgeConfig.copyWith(
|
||||
selfHosted: selfHosted,
|
||||
effective: AcpBridgeServerEffectiveConfig(
|
||||
endpoint: server.endpoint,
|
||||
tokenRef: selfHosted.passwordRef,
|
||||
source: 'bridge',
|
||||
reason: 'test bridge',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final controller = AppController(
|
||||
store: store,
|
||||
environmentOverride: <String, String>{'HOME': storeRoot.path},
|
||||
multiAgentMountManager: MultiAgentMountManager(
|
||||
resolver: _MissingRemoteMountResolver(),
|
||||
),
|
||||
);
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await _waitForInitialization(controller);
|
||||
|
||||
expect(controller.bootstrapError, isNull);
|
||||
expect(controller.bridgeCapabilitiesRefreshErrorInternal, isEmpty);
|
||||
expect(
|
||||
controller.gatewayProviderCatalog.map((item) => item.providerId),
|
||||
contains('openclaw'),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _waitForInitialization(AppController controller) async {
|
||||
final deadline = DateTime.now().add(const Duration(seconds: 10));
|
||||
while (controller.initializing && DateTime.now().isBefore(deadline)) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
if (controller.initializing) {
|
||||
fail('controller did not initialize');
|
||||
}
|
||||
}
|
||||
|
||||
class _MissingRemoteMountResolver implements MultiAgentMountResolver {
|
||||
@override
|
||||
Future<MultiAgentConfig?> reconcile({
|
||||
required MultiAgentConfig config,
|
||||
required String aiGatewayUrl,
|
||||
required String codexHome,
|
||||
required String opencodeHome,
|
||||
required ArisMountProbe arisProbe,
|
||||
}) {
|
||||
throw StateError('unknown method: xworkmate.mounts.reconcile');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {}
|
||||
}
|
||||
|
||||
class _CapabilityServer {
|
||||
_CapabilityServer(this._server);
|
||||
|
||||
final HttpServer _server;
|
||||
|
||||
String get endpoint => 'http://${_server.address.host}:${_server.port}';
|
||||
|
||||
static Future<_CapabilityServer> start() async {
|
||||
final server = await HttpServer.bind(InternetAddress.loopbackIPv4, 0);
|
||||
final wrapper = _CapabilityServer(server);
|
||||
server.listen(wrapper._handle);
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
Future<void> close() => _server.close(force: true);
|
||||
|
||||
Future<void> _handle(HttpRequest request) async {
|
||||
final raw = await utf8.decoder.bind(request).join();
|
||||
final decoded = jsonDecode(raw) as Map<String, dynamic>;
|
||||
final method = decoded['method']?.toString() ?? '';
|
||||
request.response.headers.contentType = ContentType.json;
|
||||
if (method != 'acp.capabilities') {
|
||||
request.response.write(
|
||||
jsonEncode(<String, Object?>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': decoded['id'],
|
||||
'error': <String, Object?>{
|
||||
'code': -32601,
|
||||
'message': 'unknown method: $method',
|
||||
},
|
||||
}),
|
||||
);
|
||||
await request.response.close();
|
||||
return;
|
||||
}
|
||||
request.response.write(
|
||||
jsonEncode(<String, Object?>{
|
||||
'jsonrpc': '2.0',
|
||||
'id': decoded['id'],
|
||||
'result': <String, Object?>{
|
||||
'singleAgent': true,
|
||||
'multiAgent': true,
|
||||
'availableExecutionTargets': <String>['agent', 'gateway'],
|
||||
'providerCatalog': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'providerId': 'codex',
|
||||
'label': 'Codex',
|
||||
'targets': <String>['agent'],
|
||||
},
|
||||
],
|
||||
'gatewayProviders': <Map<String, Object?>>[
|
||||
<String, Object?>{
|
||||
'providerId': 'openclaw',
|
||||
'label': 'OpenClaw',
|
||||
'targets': <String>['gateway'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
);
|
||||
await request.response.close();
|
||||
}
|
||||
}
|
||||
@ -64,7 +64,7 @@ void main() {
|
||||
() async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.openclaw,
|
||||
@ -110,7 +110,7 @@ void main() {
|
||||
() async {
|
||||
final controller = AppController(
|
||||
environmentOverride: const <String, String>{},
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
initialBridgeProviderCatalog: const <SingleAgentProvider>[
|
||||
SingleAgentProvider.codex,
|
||||
SingleAgentProvider.opencode,
|
||||
@ -4219,26 +4219,16 @@ List<Map<String, dynamic>> _generatedArtifactPayloads() {
|
||||
];
|
||||
}
|
||||
|
||||
UiFeatureManifest _manifestWithDesktopMultiAgentEnabled() {
|
||||
UiFeatureManifest _defaultDesktopManifest() {
|
||||
return UiFeatureManifest.fromYamlString(
|
||||
File(UiFeatureManifest.assetPath).readAsStringSync(),
|
||||
).copyWithFeature(
|
||||
platform: UiFeaturePlatform.desktop,
|
||||
module: 'assistant',
|
||||
feature: 'multi_agent',
|
||||
enabled: true,
|
||||
buildModes: const <UiFeatureBuildMode>{
|
||||
UiFeatureBuildMode.debug,
|
||||
UiFeatureBuildMode.profile,
|
||||
UiFeatureBuildMode.release,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
AppController _connectedController(GoTaskServiceClient client) {
|
||||
return AppController(
|
||||
goTaskServiceClient: client,
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
environmentOverride: const <String, String>{
|
||||
'BRIDGE_AUTH_TOKEN': 'bridge-token',
|
||||
},
|
||||
@ -4254,7 +4244,7 @@ AppController _connectedController(GoTaskServiceClient client) {
|
||||
AppController _connectedGatewayController(GoTaskServiceClient client) {
|
||||
return AppController(
|
||||
goTaskServiceClient: client,
|
||||
uiFeatureManifest: _manifestWithDesktopMultiAgentEnabled(),
|
||||
uiFeatureManifest: _defaultDesktopManifest(),
|
||||
environmentOverride: const <String, String>{
|
||||
'BRIDGE_AUTH_TOKEN': 'bridge-token',
|
||||
},
|
||||
|
||||
@ -1900,14 +1900,6 @@ void main() {
|
||||
provider: SingleAgentProvider.unspecified,
|
||||
),
|
||||
);
|
||||
final multiAgentGateway = controller
|
||||
.resolveExternalAcpEndpointForRequestInternal(
|
||||
_taskRequest(
|
||||
target: AssistantExecutionTarget.gateway,
|
||||
provider: SingleAgentProvider.openclaw,
|
||||
multiAgent: true,
|
||||
),
|
||||
);
|
||||
final agentTask = controller.resolveExternalAcpEndpointForRequestInternal(
|
||||
_taskRequest(
|
||||
target: AssistantExecutionTarget.agent,
|
||||
@ -1918,7 +1910,6 @@ void main() {
|
||||
expect(openClawStart?.path, '/acp/rpc');
|
||||
expect(openClawFollowUp?.path, '/acp/rpc');
|
||||
expect(unspecifiedGateway?.path, '/acp/rpc');
|
||||
expect(multiAgentGateway?.path, '/acp/rpc');
|
||||
expect(agentTask?.path, '/acp/rpc');
|
||||
});
|
||||
|
||||
@ -2138,90 +2129,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('multi-agent execution uses session lifecycle methods', () async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
final client = GatewayAcpClient(
|
||||
endpointResolver: () => capture.baseEndpoint,
|
||||
authorizationResolver: (_) async => 'bridge-token',
|
||||
);
|
||||
|
||||
final events = await client
|
||||
.runMultiAgent(
|
||||
const GatewayAcpMultiAgentRequest(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
prompt: 'hi',
|
||||
workingDirectory: '/tmp',
|
||||
attachments: <CollaborationAttachment>[],
|
||||
selectedSkills: <String>[],
|
||||
resumeSession: false,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
expect(events, isNotEmpty);
|
||||
expect(
|
||||
capture.requestBodies,
|
||||
contains(
|
||||
predicate<String>((body) {
|
||||
return body.contains('"method":"session.start"');
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
capture.requestBodies,
|
||||
isNot(
|
||||
contains(
|
||||
predicate<String>((body) {
|
||||
return body.contains('"method":"thread/start"');
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('multi-agent follow-up uses session.message', () async {
|
||||
final capture = await _startAcpHttpServer();
|
||||
addTearDown(capture.close);
|
||||
final client = GatewayAcpClient(
|
||||
endpointResolver: () => capture.baseEndpoint,
|
||||
authorizationResolver: (_) async => 'bridge-token',
|
||||
);
|
||||
|
||||
await client
|
||||
.runMultiAgent(
|
||||
const GatewayAcpMultiAgentRequest(
|
||||
sessionId: 'session-1',
|
||||
threadId: 'session-1',
|
||||
prompt: 'hi',
|
||||
workingDirectory: '/tmp',
|
||||
attachments: <CollaborationAttachment>[],
|
||||
selectedSkills: <String>[],
|
||||
resumeSession: true,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
expect(
|
||||
capture.requestBodies,
|
||||
contains(
|
||||
predicate<String>((body) {
|
||||
return body.contains('"method":"session.message"');
|
||||
}),
|
||||
),
|
||||
);
|
||||
expect(
|
||||
capture.requestBodies,
|
||||
isNot(
|
||||
contains(
|
||||
predicate<String>((body) {
|
||||
return body.contains('"method":"turn/start"');
|
||||
}),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -2249,7 +2157,6 @@ GoTaskServiceRequest _taskRequest({
|
||||
required AssistantExecutionTarget target,
|
||||
required SingleAgentProvider provider,
|
||||
bool resumeSession = false,
|
||||
bool multiAgent = false,
|
||||
String remoteWorkingDirectoryHint = '',
|
||||
}) {
|
||||
return GoTaskServiceRequest(
|
||||
@ -2268,7 +2175,6 @@ GoTaskServiceRequest _taskRequest({
|
||||
provider: provider,
|
||||
remoteWorkingDirectoryHint: remoteWorkingDirectoryHint,
|
||||
resumeSession: resumeSession,
|
||||
multiAgent: multiAgent,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user