From 90b39eebbbe2990221a48cf02e2b163d0a289caf Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 13 Apr 2026 15:28:15 +0800 Subject: [PATCH] docs: align bridge architecture to app mainline --- docs/architecture/acp-forwarding-topology.md | 188 ++++++++---------- .../adr-unified-bridge-entrypoints.md | 131 +++++------- 2 files changed, 134 insertions(+), 185 deletions(-) diff --git a/docs/architecture/acp-forwarding-topology.md b/docs/architecture/acp-forwarding-topology.md index 8430ff1..411d6aa 100644 --- a/docs/architecture/acp-forwarding-topology.md +++ b/docs/architecture/acp-forwarding-topology.md @@ -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 flag,app-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
统一代理入口"] - APPMETHODS["bridge methods
acp.capabilities / session.* / xworkmate.gateway.*"] - APP --> APPENTRY - APPENTRY --> APPMETHODS - end - - subgraph L2["Bridge 视角"] - BRIDGE["xworkmate-bridge
唯一上游发现真源"] - - 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
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 diff --git a/docs/architecture/adr-unified-bridge-entrypoints.md b/docs/architecture/adr-unified-bridge-entrypoints.md index 0862c59..11f2a1d 100644 --- a/docs/architecture/adr-unified-bridge-entrypoints.md +++ b/docs/architecture/adr-unified-bridge-entrypoints.md @@ -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.