diff --git a/docs/architecture/bridge-runtime-routing-map.md b/docs/architecture/bridge-runtime-routing-map.md index 64aa045a..1f71d189 100644 --- a/docs/architecture/bridge-runtime-routing-map.md +++ b/docs/architecture/bridge-runtime-routing-map.md @@ -4,7 +4,7 @@ Last Updated: 2026-04-21 本文记录 `xworkmate-app` 当前对 `xworkmate-bridge` 的运行时路由合同。UI 不直接承载这些路径;Assistant UI 仍由 `acp.capabilities` 返回的 `providerCatalog`、`gatewayProviders`、`availableExecutionTargets` 驱动。 -App 侧任务发送默认调用 bridge 主入口 `/acp/rpc`,不再拼接 provider-specific 直连 URL。OpenClaw `session.start` 和同一任务的 `session.message` 是唯一例外,使用 bridge 公开的 task submit 专用路径 `/gateway/openclaw`。该路径不是全局 ACP base endpoint。 +App 侧任务发送统一调用 bridge 主入口 `/acp/rpc`,不再拼接 provider-specific 或 gateway-specific 直连 URL。OpenClaw `session.start` 和同一任务的 `session.message` 也走 `/acp/rpc`,通过 `routing.explicitExecutionTarget=gateway` 与 `routing.preferredGatewayProviderId=openclaw` 表达目标。 ## App Runtime Flow @@ -22,11 +22,8 @@ flowchart TD D --> J["OpenClaw"] A --> P["POST https://xworkmate-bridge.svc.plus/acp/rpc"] - A --> T["OpenClaw task POST /gateway/openclaw"] P --> Q["Authorization: Bearer token"] - T --> Q - P --> R["provider / requestedExecutionTarget params"] - T --> R + P --> R["provider / routing / requestedExecutionTarget params"] R --> S["bridge-owned routing"] S --> K["Hermes internal runtime"] @@ -38,10 +35,10 @@ flowchart TD ## Routing Rules -- App runtime control-plane requests, agent tasks, multi-agent tasks, `session.cancel`, and `session.close` use `https://xworkmate-bridge.svc.plus/acp/rpc`. -- OpenClaw gateway `session.start` and follow-up `session.message` use `https://xworkmate-bridge.svc.plus/gateway/openclaw`. +- App runtime control-plane requests, agent tasks, multi-agent tasks, OpenClaw gateway tasks, `session.cancel`, and `session.close` use `https://xworkmate-bridge.svc.plus/acp/rpc`. +- OpenClaw gateway `session.start` and follow-up `session.message` are identified by routing metadata, not by a separate public path. - Provider and gateway selection are passed as request params, including `provider`, `routing`, and `requestedExecutionTarget`. - Bridge-owned internal routing is opaque to the App; it is not represented as public provider paths. - The app must not route managed bridge tasks to local or LAN endpoints such as `127.0.0.1:*` or `192.168.*:*`. - The app must not route managed bridge tasks by directly constructing `/acp-server/*` URLs. -- `/gateway/openclaw` is allowed only for OpenClaw task submit; it must not be reused for capabilities, routing, gateway control-plane, cancel, close, or as an ACP base endpoint. +- `/gateway/openclaw` must not participate in app runtime routing; it is neither a task submit path nor an ACP base endpoint. diff --git a/docs/architecture/cross-repo-task-state-workflow.md b/docs/architecture/cross-repo-task-state-workflow.md index 0caace65..26e38bc6 100644 --- a/docs/architecture/cross-repo-task-state-workflow.md +++ b/docs/architecture/cross-repo-task-state-workflow.md @@ -17,7 +17,7 @@ The core ownership split is: ```mermaid flowchart LR U["User input / follow-up"] --> APP["xworkmate-app
TaskThread + UI state"] - APP -->|session.start / session.message| BR["xworkmate-bridge
/acp/rpc or /gateway/openclaw"] + APP -->|session.start / session.message| BR["xworkmate-bridge
/acp/rpc"] BR -->|xworkmate.routing.resolve| ROUTE{"Execution target"} ROUTE -->|single-agent| AG["codex / opencode / gemini / hermes"] ROUTE -->|gateway=openclaw| OC["OpenClaw Gateway Runtime"] @@ -108,7 +108,7 @@ flowchart TD E --> G["drain queue"] G --> C - C --> H["Bridge /gateway/openclaw"] + C --> H["Bridge /acp/rpc
routing=gateway/openclaw"] H --> I["OpenClaw execution"] I --> J{"Result"} J -->|success + output/files| K["APP ready
lastResultCode=success
sync artifacts"] @@ -118,13 +118,12 @@ flowchart TD ## Bridge Session And Routing Workflow -The bridge exposes one public session contract while keeping provider-specific behavior behind compatibility layers. `/gateway/openclaw` is a narrow OpenClaw task-submit lane, not a general ACP base endpoint. +The bridge exposes one public session contract while keeping provider-specific behavior behind bridge-owned routing. OpenClaw task submit uses `/acp/rpc` with explicit gateway routing metadata, not a separate app-facing path. ```mermaid flowchart TD REQ["APP request"] --> EP{"HTTP / WS entry"} EP -->|/acp or /acp/rpc| RPC["General JSON-RPC"] - EP -->|/gateway/openclaw| GW["OpenClaw task-submit endpoint"] RPC --> METHOD{"method"} METHOD -->|acp.capabilities| CAP["Return agent providers + gatewayProviders=openclaw"] @@ -133,11 +132,6 @@ flowchart TD METHOD -->|session.message| MSG["Continue existing session"] METHOD -->|session.cancel / session.close| CTRL["Cancel / close session"] - GW --> GUARD{"method is session.start / session.message ?"} - GUARD -->|no| REJECT["Reject: not a global ACP base"] - GUARD -->|yes| FORCE["Force routing=gateway/openclaw
Reject multiAgent"] - FORCE --> START - START --> ORCH["session_orchestrator"] MSG --> ORCH ORCH --> PROVIDER{"provider compat"} @@ -190,7 +184,7 @@ flowchart LR ## Boundary Rules - The app does not store OpenClaw URLs. It only consumes bridge capabilities where `gatewayProviders` includes `openclaw`. -- `/gateway/openclaw` is only for OpenClaw `session.start` and `session.message`; it is not a global ACP endpoint. +- OpenClaw `session.start` and `session.message` use `/acp/rpc` with explicit OpenClaw gateway routing metadata; `/gateway/openclaw` is not an app-facing endpoint. - Follow-up conversation uses the same `sessionKey` / `threadId`. Bridge `session.message` must continue the provider session state or return a structured continuation error. - Artifact ownership is enforced by `openclaw-multi-session-plugins` with `tasks//` scope. The app syncs only the current run's artifacts into the local thread workspace. - Upgrade/install flows must preserve real local history. Cleanup must only remove explicitly known test-pollution session keys. diff --git a/docs/architecture/unified-routing-architecture.md b/docs/architecture/unified-routing-architecture.md index 18732515..73efc484 100644 --- a/docs/architecture/unified-routing-architecture.md +++ b/docs/architecture/unified-routing-architecture.md @@ -2,7 +2,7 @@ ## 1. 架构概览 (Unified Routing Architecture) -当前系统采用 `xworkmate-bridge.svc.plus` 作为统一入口。App 侧通过 managed bridge ACP 主入口处理能力发现、路由解析、agent / multi-agent 任务和会话控制;OpenClaw `session.start` / `session.message` 使用 bridge 暴露的 `/gateway/openclaw` task submit 专用入口。Provider runtime 地址仍由 bridge 后端内部拥有,不暴露为 App-facing public mapping。 +当前系统采用 `xworkmate-bridge.svc.plus` 作为统一入口。App 侧通过 managed bridge ACP 主入口 `/acp/rpc` 处理能力发现、路由解析、agent / multi-agent 任务、OpenClaw gateway 任务和会话控制。Provider runtime 与 gateway runtime 地址仍由 bridge 后端内部拥有,不暴露为 App-facing public mapping。 ```mermaid graph TD @@ -16,7 +16,6 @@ graph TD subgraph "Bridge-owned Routing" ManagedBridge["Managed Bridge ACP
/acp/rpc"] - OpenClawSubmit["OpenClaw task submit
/gateway/openclaw"] CodexProvider["Codex internal runtime"] OpenCodeProvider["OpenCode internal runtime"] GeminiAdapter["Gemini internal runtime"] @@ -27,11 +26,10 @@ graph TD Client -->|HTTPS/WSS| Bridge_Domain Bridge_Domain -->|/acp/rpc| ManagedBridge - Bridge_Domain -->|/gateway/openclaw| OpenClawSubmit ManagedBridge -->|provider routing| CodexProvider ManagedBridge -->|provider routing| OpenCodeProvider ManagedBridge -->|provider routing| GeminiAdapter - OpenClawSubmit -->|forced openclaw routing| OpenClawGateway + ManagedBridge -->|gateway routing| OpenClawGateway %% Service Connections ManagedBridge -.->|Capabilities Discovery| Client @@ -41,15 +39,14 @@ graph TD | Bridge-owned mapping | App 侧行为 | 备注 | | :--- | :--- | :--- | -| `/acp/rpc` | 直接调用 | 能力发现、路由解析、agent / multi-agent 任务、cancel、close | -| `/gateway/openclaw` | 仅 OpenClaw task submit | 只用于 OpenClaw `session.start` / `session.message`,不是 ACP base endpoint | +| `/acp/rpc` | 直接调用 | 能力发现、路由解析、agent / multi-agent 任务、OpenClaw gateway 任务、cancel、close | | provider runtime | 不直连 | Bridge 后端内部解析 provider | | gateway runtime | 不直连 | Bridge 后端内部解析 gateway provider | ## 3. 运维配置优化 ### 3.1 统一鉴权 -App 发往 `xworkmate-bridge.svc.plus/acp/rpc` 和 `xworkmate-bridge.svc.plus/gateway/openclaw` 的请求必须携带: +App 发往 `xworkmate-bridge.svc.plus/acp/rpc` 的请求必须携带: - **Header**: `Authorization: Bearer ` - **未授权响应**: `401 Unauthorized` @@ -66,5 +63,5 @@ App 发往 `xworkmate-bridge.svc.plus/acp/rpc` 和 `xworkmate-bridge.svc.plus/ga - App 不写入或拼接本地 provider endpoint。 - App 不直接调用 `/acp-server/*`。 -- App 仅可在 OpenClaw `session.start` / `session.message` task submit 中调用 `/gateway/openclaw`,不得把它作为全局 ACP base endpoint。 +- App 不调用 `/gateway/openclaw`;OpenClaw `session.start` / `session.message` task submit 通过 `/acp/rpc` 的 routing metadata 表达。 - `acp.capabilities` 是 provider catalog、gateway catalog、available execution targets 的唯一来源。 diff --git a/docs/testing/app-external-service-api-test-matrix.md b/docs/testing/app-external-service-api-test-matrix.md index 6028eee4..0b7d4796 100644 --- a/docs/testing/app-external-service-api-test-matrix.md +++ b/docs/testing/app-external-service-api-test-matrix.md @@ -40,7 +40,7 @@ Last Updated: 2026-04-22 - 安全边界确认: - `BRIDGE_AUTH_TOKEN` 是否只作为 Bearer token 使用 - `BRIDGE_SERVER_URL` 是否仅作为元数据 - - 会话接口是否按当前 bridge contract 选择 `/acp/rpc` 或 OpenClaw task submit 专用入口 `/gateway/openclaw` + - 会话接口是否按当前 bridge contract 统一选择 `/acp/rpc`,并用 routing metadata 表达 OpenClaw gateway 目标 ## 2. 统一测试前提 @@ -76,8 +76,7 @@ Last Updated: 2026-04-22 | accounts | `/api/auth/login` | `POST` | 无 | 登录并拿 session token | Apple 审核只读账号使用 | | accounts | `/api/auth/session` | `GET` | `Authorization: Bearer ` | 获取当前会话和用户信息 | APP 端登录态校验 | | accounts | `/api/auth/xworkmate/profile/sync` | `GET` | `Authorization: Bearer ` | 拉取 bridge 同步元数据 | 返回 `BRIDGE_SERVER_URL` / `BRIDGE_AUTH_TOKEN` | -| bridge | `/acp/rpc` | `POST` | `Authorization: Bearer ` | bridge JSON-RPC 主入口 | capabilities、routing、agent / multi-agent 任务、cancel、close | -| bridge | `/gateway/openclaw` | `POST` | `Authorization: Bearer ` | OpenClaw task submit 专用入口 | 仅 OpenClaw `session.start` / `session.message`,不是全局 ACP base endpoint | +| bridge | `/acp/rpc` | `POST` | `Authorization: Bearer ` | bridge JSON-RPC 主入口 | capabilities、routing、agent / multi-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 生命周期起点 | @@ -581,10 +580,9 @@ Last Updated: 2026-04-22 ### 8.3 建议断言 - `Authorization` 必须是 `Bearer ` -- capability、routing、agent / multi-agent 任务、`session.cancel`、`session.close` 请求路径必须是 `/acp/rpc` -- OpenClaw gateway `session.start` / `session.message` 请求路径必须是 `/gateway/openclaw` +- capability、routing、agent / multi-agent 任务、OpenClaw gateway `session.start` / `session.message`、`session.cancel`、`session.close` 请求路径必须是 `/acp/rpc` - 不允许出现 `/acp-server/` 直连请求 -- `/gateway/openclaw` 不得用于 capabilities、routing、gateway control-plane、cancel、close,且不得作为全局 ACP base endpoint +- 不允许出现 `/gateway/openclaw` app-facing 请求;OpenClaw gateway 目标必须通过 routing metadata 表达 - `session.cancel` / `session.close` 必须接受 `sessionId` + `threadId` - `BRIDGE_SERVER_URL` 不可参与运行时路径拼接 @@ -612,7 +610,7 @@ Last Updated: 2026-04-22 - 账户侧负责登录与同步元数据 - bridge 侧负责 capability、路由解析和会话生命周期 -- capability、routing、agent / multi-agent 任务、cancel、close 经过 `/acp/rpc` -- OpenClaw `session.start` / `session.message` 经过 `/gateway/openclaw` +- capability、routing、agent / multi-agent 任务、OpenClaw gateway 任务、cancel、close 经过 `/acp/rpc` +- OpenClaw `session.start` / `session.message` 通过 `/acp/rpc` routing metadata 表达 gateway/openclaw 目标 - `session.cancel` / `session.close` 的协议入口已验证可用 - 当前剩余风险集中在桥接后端下游 provider 连接,而不是 APP 侧接口拼接 diff --git a/docs/xworkmate-app-core-functional-test-plan-v1.md b/docs/xworkmate-app-core-functional-test-plan-v1.md index bfef8370..3376125f 100644 --- a/docs/xworkmate-app-core-functional-test-plan-v1.md +++ b/docs/xworkmate-app-core-functional-test-plan-v1.md @@ -31,8 +31,8 @@ │ │ (SSE/HTTP) │ │ successfully via /acp/rpc │ │ /acp-server/opencode/ │ JSON-RPC │ PASS │ acp.capabilities returned │ │ │ (SSE/HTTP) │ │ successfully via /acp/rpc │ - │ /gateway/openclaw/ │ WSS / RPC │ PASS │ WebSocket handshake successful at │ - │ │ │ │ /acp; received connect.challenge + │ /acp/rpc openclaw │ JSON-RPC │ PASS │ OpenClaw routing is expressed via │ + │ routing │ │ │ routing metadata on /acp/rpc │ ### 2. Assistant 线程体验 diff --git a/lib/app/app_controller_desktop_runtime_helpers.dart b/lib/app/app_controller_desktop_runtime_helpers.dart index ca3c6e4f..9e101e6b 100644 --- a/lib/app/app_controller_desktop_runtime_helpers.dart +++ b/lib/app/app_controller_desktop_runtime_helpers.dart @@ -1045,9 +1045,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { if (bridgeEndpoint == null) { return null; } - if (_usesOpenClawTaskSubmitEndpointInternal(request)) { - return bridgeEndpoint.replace(path: '/gateway/openclaw'); - } return resolveAcpHttpRpcEndpoint(bridgeEndpoint); } @@ -1148,22 +1145,6 @@ extension AppControllerDesktopRuntimeHelpers on AppController { ) => kGatewayRemoteProfileIndex; } -bool _usesOpenClawTaskSubmitEndpointInternal(GoTaskServiceRequest request) { - if (request.isMultiAgentRequest || !request.target.isGateway) { - return false; - } - final providerId = normalizeSingleAgentProviderId( - request.provider.providerId, - ); - if (providerId == kCanonicalGatewayProviderId) { - return true; - } - return normalizeSingleAgentProviderId( - request.effectiveRouting.preferredGatewayTarget, - ) == - kCanonicalGatewayProviderId; -} - String _normalizeAuthorizationHeaderInternal(String raw) { final trimmed = raw.trim(); if (trimmed.isEmpty) { diff --git a/lib/runtime/external_code_agent_acp_desktop_transport.dart b/lib/runtime/external_code_agent_acp_desktop_transport.dart index d90f8e4e..01af6485 100644 --- a/lib/runtime/external_code_agent_acp_desktop_transport.dart +++ b/lib/runtime/external_code_agent_acp_desktop_transport.dart @@ -276,16 +276,6 @@ class ExternalCodeAgentAcpDesktopTransport if (controlEndpoint != null) { return controlEndpoint; } - final taskPath = taskEndpoint?.path.trim() ?? ''; - if (taskEndpoint != null && - (taskPath == '/gateway/openclaw' || - taskPath.endsWith('/gateway/openclaw'))) { - return taskEndpoint.replace( - path: '/acp/rpc', - query: null, - fragment: null, - ); - } return resolveAcpHttpRpcEndpoint(taskEndpoint); } diff --git a/lib/runtime/gateway_acp_client.dart b/lib/runtime/gateway_acp_client.dart index d8931c17..cd27047b 100644 --- a/lib/runtime/gateway_acp_client.dart +++ b/lib/runtime/gateway_acp_client.dart @@ -1311,14 +1311,6 @@ class GatewayAcpClient { Uri? _resolveHttpRpcEndpoint([Uri? endpointOverride, String method = '']) { final endpoint = endpointOverride ?? endpointResolver(); - if (_isOpenClawTaskSubmitEndpoint(endpoint) && - _isOpenClawTaskSubmitMethod(method)) { - return endpoint?.replace( - path: '/gateway/openclaw', - query: null, - fragment: null, - ); - } return resolveAcpHttpRpcEndpoint(endpoint); } @@ -1470,15 +1462,6 @@ bool _isOpenClawTaskSubmitMethod(String method) { return normalized == 'session.start' || normalized == 'session.message'; } -bool _isOpenClawTaskSubmitEndpoint(Uri? endpoint) { - var path = endpoint?.path.trim() ?? ''; - if (!path.startsWith('/')) { - path = '/$path'; - } - path = path.replaceFirst(RegExp(r'/+$'), ''); - return path == '/gateway/openclaw'; -} - Duration gatewayAcpHttpResponseTimeoutFor( Uri endpoint, String method, [ @@ -1487,15 +1470,6 @@ Duration gatewayAcpHttpResponseTimeoutFor( if (!_isOpenClawTaskSubmitMethod(method)) { return const Duration(seconds: 120); } - if (_isOpenClawTaskSubmitEndpoint(endpoint)) { - return Duration( - minutes: gatewayAcpTaskRuntimeBudgetMinutesForParams({ - 'requestedExecutionTarget': - AssistantExecutionTarget.gateway.promptValue, - ...params, - }), - ); - } return Duration(minutes: gatewayAcpTaskRuntimeBudgetMinutesForParams(params)); } diff --git a/test/features/app/app_shell_surface_test.dart b/test/features/app/app_shell_surface_test.dart index a5d4fc88..758f012b 100644 --- a/test/features/app/app_shell_surface_test.dart +++ b/test/features/app/app_shell_surface_test.dart @@ -28,8 +28,13 @@ void main() { ); await tester.pumpAndSettle(); - expect(find.text('助手'), findsOneWidget); - expect(find.text('设置'), findsOneWidget); + expect(find.byKey(const Key('mobile-assistant-page')), findsOneWidget); + expect(find.byKey(const Key('mobile-settings-page')), findsNothing); + + controller.openSettings(); + await tester.pump(); + + expect(find.byKey(const Key('mobile-settings-page')), findsOneWidget); expect(find.text('Mobile-safe'), findsNothing); expect(find.text('安全审批'), findsNothing); expect(find.byKey(const Key('mobile-safe-strip')), findsNothing); diff --git a/test/runtime/gateway_acp_client_auth_test.dart b/test/runtime/gateway_acp_client_auth_test.dart index 0d58026d..f1b04615 100644 --- a/test/runtime/gateway_acp_client_auth_test.dart +++ b/test/runtime/gateway_acp_client_auth_test.dart @@ -748,8 +748,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: GatewayAcpClient(endpointResolver: () => endpoint), endpointResolver: (_) => endpoint, - taskEndpointResolver: (_) => - endpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => endpoint, ); addTearDown(transport.dispose); @@ -777,10 +776,7 @@ void main() { expect(result.artifacts.single.relativePath, 'exports/snapshot.md'); expect(result.remoteWorkingDirectory, '/remote/openclaw/workspace'); expect(result.remoteWorkspaceRefKind, WorkspaceRefKind.remotePath); - expect( - requestPaths, - containsAll(['/gateway/openclaw', '/acp/rpc']), - ); + expect(requestPaths, everyElement('/acp/rpc')); }, ); @@ -842,8 +838,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: GatewayAcpClient(endpointResolver: () => endpoint), endpointResolver: (_) => endpoint, - taskEndpointResolver: (_) => - endpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => endpoint, recoveryPollDelay: Duration.zero, recoveryMaxAttempts: 1, ); @@ -871,10 +866,7 @@ void main() { expect(result.success, isTrue); expect(result.message, 'recovered after SSE no result'); - expect( - requestPaths, - containsAll(['/gateway/openclaw', '/acp/rpc']), - ); + expect(requestPaths, everyElement('/acp/rpc')); }, ); @@ -942,8 +934,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: GatewayAcpClient(endpointResolver: () => endpoint), endpointResolver: (_) => endpoint, - taskEndpointResolver: (_) => - endpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => endpoint, recoveryPollDelay: Duration.zero, recoveryMaxAttempts: 4, ); @@ -1033,8 +1024,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: GatewayAcpClient(endpointResolver: () => endpoint), endpointResolver: (_) => endpoint, - taskEndpointResolver: (_) => - endpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => endpoint, recoveryPollDelay: Duration.zero, recoveryMaxAttempts: 1, ); @@ -1544,7 +1534,7 @@ void main() { ); test( - 'desktop task execution rejects OpenClaw gateway path for non-task methods', + 'desktop task execution rejects OpenClaw gateway path as bridge RPC base', () async { final capture = await _startAcpHttpServer(); addTearDown(capture.close); @@ -1573,7 +1563,7 @@ void main() { ); test( - 'desktop task execution routes OpenClaw through required task endpoint', + 'desktop task execution routes OpenClaw through unified bridge RPC', () async { final capture = await _startAcpHttpServer( streamResponse: true, @@ -1606,8 +1596,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: client, endpointResolver: (_) => capture.baseEndpoint, - taskEndpointResolver: (_) => - capture.baseEndpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => capture.baseEndpoint, ); final result = await transport.executeTask( @@ -1620,9 +1609,9 @@ void main() { expect(capture.authorizationHeader, 'Bearer bridge-token'); expect(capture.acceptHeader, 'text/event-stream, application/json'); - expect(capture.requestPath, '/gateway/openclaw'); + expect(capture.requestPath, '/acp/rpc'); expect(capture.requestPath, isNot(contains('/acp-server'))); - expect(capture.requestPath, isNot(contains('/acp-server/gateway'))); + expect(capture.requestPath, isNot(contains('/gateway/openclaw'))); final params = _lastRequestParams(capture); final routing = params['routing'] as Map; expect(params.containsKey('gatewayProvider'), isFalse); @@ -1706,18 +1695,17 @@ void main() { 'preferredGatewayProviderId': 'openclaw', }, }, - endpointOverride: capture.baseEndpoint.replace( - path: '/gateway/openclaw', - ), + endpointOverride: capture.baseEndpoint, onNotification: notifications.add, ); final diagnostics = (response['_xworkmateDiagnostics'] as Map) .cast(); - expect(capture.requestPath, '/gateway/openclaw'); + expect(capture.requestPath, '/acp/rpc'); expect((response['result'] as Map)['output'], 'done'); expect(diagnostics['transport'], 'http-sse'); - expect(diagnostics['requestUrl'], contains('/gateway/openclaw')); + expect(diagnostics['requestUrl'], contains('/acp/rpc')); + expect(diagnostics['requestUrl'], isNot(contains('/gateway/openclaw'))); expect(diagnostics['bodyRead'], isTrue); expect(diagnostics['sseKeepaliveReceived'], isTrue); expect(diagnostics['sseLastEventAtMs'], isPositive); @@ -1734,7 +1722,7 @@ void main() { ); test( - 'desktop OpenClaw follow-up routes through required task endpoint', + 'desktop OpenClaw follow-up routes through unified bridge RPC', () async { final capture = await _startAcpHttpServer(); addTearDown(capture.close); @@ -1746,8 +1734,7 @@ void main() { final transport = ExternalCodeAgentAcpDesktopTransport( client: client, endpointResolver: (_) => capture.baseEndpoint, - taskEndpointResolver: (_) => - capture.baseEndpoint.replace(path: '/gateway/openclaw'), + taskEndpointResolver: (_) => capture.baseEndpoint, ); await transport.executeTask( @@ -1760,7 +1747,7 @@ void main() { ); expect(capture.acceptHeader, 'text/event-stream, application/json'); - expect(capture.requestPath, '/gateway/openclaw'); + expect(capture.requestPath, '/acp/rpc'); expect(capture.requestBody, contains('"method":"session.message"')); }, ); @@ -1769,9 +1756,6 @@ void main() { final openClawEndpoint = Uri.parse( 'https://xworkmate-bridge.svc.plus/acp/rpc', ); - final openClawTaskEndpoint = Uri.parse( - 'https://xworkmate-bridge.svc.plus/gateway/openclaw', - ); final acpEndpoint = Uri.parse( 'https://xworkmate-bridge.svc.plus/acp/rpc', ); @@ -1795,22 +1779,6 @@ void main() { ), const Duration(minutes: 30), ); - expect( - gatewayAcpHttpResponseTimeoutFor( - openClawTaskEndpoint, - 'session.start', - const {'taskPrompt': 'Reply after a long wait'}, - ), - const Duration(minutes: 10), - ); - expect( - gatewayAcpHttpResponseTimeoutFor( - openClawTaskEndpoint, - 'session.message', - const {'taskPrompt': '输出 PPTX 和 Markdown 文件'}, - ), - const Duration(minutes: 30), - ); expect( gatewayAcpHttpResponseTimeoutFor(acpEndpoint, 'session.start'), const Duration(minutes: 2), @@ -1821,62 +1789,58 @@ void main() { ); }); - test( - 'desktop controller uses OpenClaw endpoint only for gateway task submit', - () { - final controller = AppController( - environmentOverride: const {}, - ); - addTearDown(controller.dispose); + test('desktop controller uses unified bridge RPC for all task submits', () { + final controller = AppController( + environmentOverride: const {}, + ); + addTearDown(controller.dispose); - final openClawStart = controller - .resolveExternalAcpEndpointForRequestInternal( - _taskRequest( - target: AssistantExecutionTarget.gateway, - provider: SingleAgentProvider.openclaw, - ), - ); - final openClawFollowUp = controller - .resolveExternalAcpEndpointForRequestInternal( - _taskRequest( - target: AssistantExecutionTarget.gateway, - provider: SingleAgentProvider.openclaw, - resumeSession: true, - ), - ); - final unspecifiedGateway = controller - .resolveExternalAcpEndpointForRequestInternal( - _taskRequest( - target: AssistantExecutionTarget.gateway, - provider: SingleAgentProvider.unspecified, - ), - ); - final multiAgentGateway = controller - .resolveExternalAcpEndpointForRequestInternal( - _taskRequest( - target: AssistantExecutionTarget.gateway, - provider: SingleAgentProvider.openclaw, - multiAgent: true, - ), - ); - final agentTask = controller - .resolveExternalAcpEndpointForRequestInternal( - _taskRequest( - target: AssistantExecutionTarget.agent, - provider: SingleAgentProvider.codex, - ), - ); + final openClawStart = controller + .resolveExternalAcpEndpointForRequestInternal( + _taskRequest( + target: AssistantExecutionTarget.gateway, + provider: SingleAgentProvider.openclaw, + ), + ); + final openClawFollowUp = controller + .resolveExternalAcpEndpointForRequestInternal( + _taskRequest( + target: AssistantExecutionTarget.gateway, + provider: SingleAgentProvider.openclaw, + resumeSession: true, + ), + ); + final unspecifiedGateway = controller + .resolveExternalAcpEndpointForRequestInternal( + _taskRequest( + target: AssistantExecutionTarget.gateway, + provider: SingleAgentProvider.unspecified, + ), + ); + final multiAgentGateway = controller + .resolveExternalAcpEndpointForRequestInternal( + _taskRequest( + target: AssistantExecutionTarget.gateway, + provider: SingleAgentProvider.openclaw, + multiAgent: true, + ), + ); + final agentTask = controller.resolveExternalAcpEndpointForRequestInternal( + _taskRequest( + target: AssistantExecutionTarget.agent, + provider: SingleAgentProvider.codex, + ), + ); - expect(openClawStart?.path, '/gateway/openclaw'); - expect(openClawFollowUp?.path, '/gateway/openclaw'); - expect(unspecifiedGateway?.path, '/gateway/openclaw'); - expect(multiAgentGateway?.path, '/acp/rpc'); - expect(agentTask?.path, '/acp/rpc'); - }, - ); + 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'); + }); test( - 'desktop controller resolves OpenClaw gateway submit to required task endpoint', + 'desktop controller resolves OpenClaw gateway submit to unified bridge RPC', () { final controller = AppController( environmentOverride: const {}, @@ -1893,10 +1857,10 @@ void main() { expect( endpoint.toString(), - 'https://xworkmate-bridge.svc.plus/gateway/openclaw', + 'https://xworkmate-bridge.svc.plus/acp/rpc', ); expect(endpoint, isNotNull); - expect(endpoint!.path, isNot('/acp/rpc')); + expect(endpoint!.path, '/acp/rpc'); }, );