docs: align bridge architecture to app mainline

This commit is contained in:
Haitao Pan 2026-04-13 15:28:15 +08:00
parent 991ecb0ae2
commit 90b39eebbb
2 changed files with 134 additions and 185 deletions

View File

@ -1,129 +1,103 @@
# ACP Forwarding Topology
This document describes the bridge-only production forwarding model for `xworkmate-bridge.svc.plus`.
Last Updated: 2026-04-13
See also: [adr-unified-bridge-entrypoints.md](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/docs/architecture/adr-unified-bridge-entrypoints.md)
本文件描述当前 `xworkmate-app <-> xworkmate-bridge` 主链下的 bridge-only forwarding topology。
See also:
- [XWorkmate Core Module Inventory](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-app/docs/architecture/xworkmate-core-module-inventory-2026-04-13.md)
- [ADR: Unified Bridge Entry Points](/Users/shenlan/workspaces/cloud-neutral-toolkit/xworkmate-bridge/docs/architecture/adr-unified-bridge-entrypoints.md)
## App-Facing Mainline
对 app 来说,当前主链只有两类面向 bridge 的交互:
- `assistant` surface 进入 ACP control-plane`acp.capabilities`、`xworkmate.routing.resolve`、`session.*`
- `settings` surface 进入 gateway runtime / connection flow`acp.capabilities`、`xworkmate.gateway.*`
不管 bridge 内部还保留哪些 provider / gateway mode / capability flagapp-facing 公共入口都只有 bridge origin。
## Topology
```mermaid
flowchart TD
U["xworkmate-app"] --> B["https://xworkmate-bridge.svc.plus"]
subgraph APP["xworkmate-app"]
A1["AssistantPage"]
A2["SettingsPage"]
A3["https://xworkmate-bridge.svc.plus"]
A1 --> A3
A2 --> A3
end
B -->|POST /acp/rpc| RPC["ACP RPC handler"]
B -->|WS /acp| WS["ACP WebSocket handler"]
subgraph BRIDGE["xworkmate-bridge"]
B1["POST /acp/rpc"]
B2["GET /acp (WebSocket)"]
B3["acp.capabilities"]
B4["xworkmate.routing.resolve"]
B5["session.*"]
B6["xworkmate.gateway.*"]
B7["bridge-owned provider catalog"]
B8["bridge-owned routing"]
B9["bridge-owned gateway runtime"]
RPC --> R{"method"}
WS --> R
A3 --> B1
A3 --> B2
B1 --> B3
B1 --> B4
B1 --> B5
B1 --> B6
B2 --> B5
B3 --> B7
B4 --> B8
B5 --> B8
B6 --> B9
end
R -->|acp.capabilities| CAP["built-in provider catalog"]
R -->|xworkmate.routing.resolve| ROUTE["bridge-owned routing resolve"]
R -->|session.start / session.message| RUN["bridge-owned execution"]
R -->|xworkmate.gateway.*| GWAPI["gateway runtime proxy"]
R -->|session.cancel / session.close| LIFE["session lifecycle"]
subgraph UPSTREAM["Independent upstream services"]
C1["https://acp-server.svc.plus/codex/acp/rpc"]
C2["https://acp-server.svc.plus/opencode/acp/rpc"]
C3["https://acp-server.svc.plus/gemini/acp/rpc"]
C4["wss://openclaw.svc.plus"]
end
RUN --> ACP1["codex -> https://acp-server.svc.plus/codex/acp/rpc"]
RUN --> ACP2["opencode -> https://acp-server.svc.plus/opencode/acp/rpc"]
RUN --> ACP3["gemini -> https://acp-server.svc.plus/gemini/acp/rpc"]
GWAPI --> GW["wss://openclaw.svc.plus"]
B7 --> C1
B7 --> C2
B7 --> C3
B8 --> C1
B8 --> C2
B8 --> C3
B9 --> C4
```
## Three-Layer View
This view separates what the app sees, what the bridge owns, and what the
real upstream production targets are. The upstream ACP and gateway services
exist independently, but for the app they are all accessed through the single
public bridge origin: `https://xworkmate-bridge.svc.plus`.
```mermaid
flowchart LR
subgraph L1["APP 视角"]
APP["xworkmate-app"]
APPENTRY["https://xworkmate-bridge.svc.plus<br/>统一代理入口"]
APPMETHODS["bridge methods<br/>acp.capabilities / session.* / xworkmate.gateway.*"]
APP --> APPENTRY
APPENTRY --> APPMETHODS
end
subgraph L2["Bridge 视角"]
BRIDGE["xworkmate-bridge<br/>唯一上游发现真源"]
CAP["Bridge-owned ACP routing catalog"]
CAP1["codex"]
CAP2["opencode"]
CAP3["gemini"]
GW["Bridge-owned gateway routing"]
GW1["gatewayProviderId=openclaw"]
BRIDGE --> CAP
CAP --> CAP1
CAP --> CAP2
CAP --> CAP3
BRIDGE --> GW
GW --> GW1
end
subgraph L3["上游视角"]
U1["https://acp-server.svc.plus/codex/acp/rpc"]
U2["https://acp-server.svc.plus/opencode/acp/rpc"]
U3["https://acp-server.svc.plus/gemini/acp/rpc"]
U4["wss://openclaw.svc.plus<br/>reported as openclaw.svc.plus:443"]
end
APPMETHODS --> BRIDGE
CAP1 --> U1
CAP2 --> U2
CAP3 --> U3
GW1 --> U4
```
Important distinction:
- the upstream services are independent production services, not embedded
inside the bridge
- for the app, ACP discovery, session execution, and gateway runtime traffic
are all proxied through `https://xworkmate-bridge.svc.plus`
- upstream authentication is unified through
`Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
- `acp.capabilities.providerCatalog` currently advertises only the ACP
single-agent providers: `codex`, `opencode`, and `gemini`
- `gateway` is not part of that provider catalog; it is exposed through the
separate `xworkmate.gateway.*` bridge-owned runtime path
- for `gatewayProviderId=openclaw`, the bridge rewrites the upstream target to
`wss://openclaw.svc.plus`
## Production Truth
The production upstream services exist independently. The bridge owns the
routing map used to proxy app traffic to them:
当前 production forwarding 事实:
- `codex` -> `https://acp-server.svc.plus/codex/acp/rpc`
- `opencode` -> `https://acp-server.svc.plus/opencode/acp/rpc`
- `gemini` -> `https://acp-server.svc.plus/gemini/acp/rpc`
- gateway -> `wss://openclaw.svc.plus`
- canonical app-facing origin: `https://xworkmate-bridge.svc.plus`
- canonical app-facing ACP paths:
- `POST /acp/rpc`
- `GET /acp`
- current built-in single-agent provider catalog:
- `codex`
- `opencode`
- `gemini`
- current production gateway forwarding target:
- `openclaw -> wss://openclaw.svc.plus`
Upstream auth is unified and bridge-internal:
对 app 而言:
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
Canonical APP-facing paths stay on the bridge origin:
- `POST https://xworkmate-bridge.svc.plus/acp/rpc`
- `GET https://xworkmate-bridge.svc.plus/acp`
- provider catalog、routing、gateway runtime 都是 bridge-owned metadata / behavior
- upstream URL 存在,但不是 app 的直接合同
- gateway backend、provider IDs、可选 capability flag 也都不是 app shell 模块分类
## Invariants
- app-facing cloud entry is only `https://xworkmate-bridge.svc.plus`
- app traffic reaches upstream ACP and gateway services only through the
bridge proxy
- upstream ACP and gateway routes use the same bearer token contract:
`Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
- `acp.capabilities` returns the built-in production catalog
- no production `xworkmate.providers.sync`
- no app direct call to `acp-server.svc.plus/*`
- no app direct call to `openclaw.svc.plus`
- openclaw gateway runtime status is reported as `openclaw.svc.plus:443`, but the app still talks only to the bridge
- app traffic reaches upstream ACP and gateway services only through the bridge
- app does not call `acp-server.svc.plus/*` or `openclaw.svc.plus` directly
- upstream auth stays bridge-internal:
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
- `acp.capabilities` is the provider / capability discovery source
- `xworkmate.routing.resolve` is the routing resolution source
- `xworkmate.gateway.*` is the gateway runtime method family
- bridge may expose additional routing metadata, but that metadata must not be interpreted as extra app surfaces or legacy module shells

View File

@ -8,37 +8,51 @@ Accepted
2026-04-11
## Last Reviewed
2026-04-13
## Context
`xworkmate-bridge` currently proxies app traffic to four independent upstream
production services:
`xworkmate-bridge` 代理 app 流量到独立 upstream ACP / gateway 服务,但如果把这些 upstream 直接暴露成 app-facing entrypoint会带来几个问题
- `codex` -> `https://acp-server.svc.plus/codex/acp/rpc`
- `opencode` -> `https://acp-server.svc.plus/opencode/acp/rpc`
- `gemini` -> `https://acp-server.svc.plus/gemini/acp/rpc`
- `gateway` -> `wss://openclaw.svc.plus`
- app 必须知道 provider-specific 或 gateway-specific hostname
- routing truth 会在 URL 形状与 bridge 逻辑之间分裂
- auth contract 更难保持统一
- upstream implementation detail 会泄漏到 app shell / module 心智
These upstream services exist independently, but exposing them directly as
APP-facing endpoints creates several problems:
- the APP would need to know provider-specific or gateway-specific hostnames
- routing truth would be split between URL shape and bridge-side routing logic
- auth handling would be harder to keep consistent
- upstream implementation details would leak into client contracts
The bridge already acts as the single public integration surface for ACP
discovery, task execution, and gateway runtime operations.
当前 app 主链已经收敛到 `assistant + settings` 两个 surface因此 bridge 入口也必须保持同一口径统一公共入口bridge 内部负责 provider / gateway 路由细节。
## Decision
For APP traffic, the canonical public entry point is the bridge origin:
For APP traffic, the canonical public entry point remains:
- `https://xworkmate-bridge.svc.plus`
The canonical APP-facing ACP paths are:
Canonical app-facing contract families are:
- `POST /acp/rpc`
- `GET /acp` for WebSocket ACP
1. ACP control-plane
- `POST /acp/rpc`
- `GET /acp`
2. Gateway runtime methods
- `xworkmate.gateway.connect`
- `xworkmate.gateway.request`
- `xworkmate.gateway.disconnect`
Bridge-owned metadata may still include:
- `providerCatalog`
- `gatewayProviders`
- `resolvedExecutionTarget`
- `resolvedProviderId`
- `resolvedGatewayProviderId`
这些字段属于 bridge 返回给 app 的 routing/capability metadata不属于 app shell taxonomy。
换句话说:
- app 可以消费这些字段来展示当前可用能力或执行结果
- app 不应该把 provider/gateway 矩阵抬升成新的顶层模块、别名页面、或直接 URL 合同
The APP should not depend on provider-specific public URLs such as:
@ -47,76 +61,37 @@ The APP should not depend on provider-specific public URLs such as:
- `/gemini/acp/rpc`
- `/openclaw/`
Provider choice remains bridge-owned routing, not URL-owned routing.
If the bridge reports execution-target metadata such as `single-agent`,
`multi-agent`, or `gateway`, the app should treat those values as routing
results, not as shell-level surface categories.
APP-facing routing should be modeled in three layers:
- `executionTarget`
- `single-agent`
- `multi-agent`
- `gateway`
- `singleAgentProviders`
- `codex`
- `opencode`
- `gemini`
- `gatewayProviders`
- `local`
- `openclaw`
For APP integration, `gatewayProviders` is the stable gateway-facing concept.
APP and UI code should consume bridge state in two phases:
1. `acp.capabilities`
- discover `singleAgentProviders`
- discover `gatewayProviders`
2. `xworkmate.routing.resolve`
- determine `resolvedExecutionTarget`
- determine `resolvedProviderId` or `resolvedGatewayProviderId`
- determine unavailable state
The APP should treat `resolvedProviderId` and `resolvedGatewayProviderId` as
mutually exclusive routing outputs depending on `resolvedExecutionTarget`.
Gateway access remains bridge-owned via JSON-RPC methods:
- `xworkmate.gateway.connect`
- `xworkmate.gateway.request`
- `xworkmate.gateway.disconnect`
Upstream authentication is unified for both ACP and gateway routes:
- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN`
If the bridge reports gateway provider IDs such as `local` or `openclaw`, the
app should treat them as bridge-owned gateway backend identifiers, not as
independent app entrypoints.
## Consequences
### Positive
- APP integration stays stable behind one public origin
- provider and gateway topology remain internal bridge concerns
- auth contract is consistent across all upstream forwarding
- bridge can change upstream mappings without changing APP contracts
- provider and gateway topology remain bridge concerns
- auth handling remains consistent across ACP and gateway forwarding
- app architecture docs can stay focused on `assistant + settings` instead of a fake module matrix
### Trade-offs
- direct provider-specific bridge URLs, if exposed at all, must be treated as
aliases or operator/debug paths, not primary client contracts
- documentation must clearly distinguish canonical APP paths from independent
upstream targets
- docs must clearly separate canonical app contracts from independent upstream services
- optional bridge metadata must be documented as metadata, not as surface taxonomy
## Path Naming Guidance
Use these terms consistently in docs:
Use these terms consistently:
- `canonical APP-facing path`: `/acp/rpc` and `/acp`
- `independent upstream service`: `acp-server.svc.plus/*` and
`wss://openclaw.svc.plus`
- `bridge-owned routing`: bridge logic that selects and proxies to upstreams
- `gatewayProvider`: the APP-facing identifier for a gateway backend such as
`local` or `openclaw`
- `canonical app-facing path`: `/acp/rpc` and `/acp`
- `gateway runtime method family`: `xworkmate.gateway.*`
- `independent upstream service`: `acp-server.svc.plus/*`, `wss://openclaw.svc.plus`
- `bridge-owned routing`: provider / gateway selection performed inside bridge
- `routing metadata`: execution target and resolved provider/gateway identifiers returned to the app
Avoid describing upstream URLs as if the APP should call them directly.
If provider-specific public bridge paths are ever introduced, they should be
documented as optional aliases only. They should not replace `/acp/rpc` as the
canonical APP-facing contract.
Avoid describing upstream URLs, provider IDs, or gateway mode IDs as if they
were independent app modules or alternate primary entrypoints.