From 104ab263568d1515f375d95873c204fc54c262f3 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 20 Apr 2026 11:30:42 +0800 Subject: [PATCH] refactor(acp): unify ingress topology and migrate all services to xworkmate-bridge.svc.plus --- docs/acp-public-validation-2026-04-09.md | 4 +- docs/architecture/acp-forwarding-topology.md | 10 ++--- .../adr-unified-bridge-entrypoints.md | 2 +- docs/architecture/bridge-runtime-design.md | 9 +++++ internal/acp/gateway_runtime.go | 3 +- internal/acp/gateway_runtime_test.go | 2 +- internal/acp/provider_catalog.go | 37 +++++++++++++++---- .../runtime_reported_address_test.go | 4 +- internal/mounts/reconcile.go | 23 ++++++++---- scripts/github-actions/validate-deploy.sh | 6 +-- 10 files changed, 70 insertions(+), 30 deletions(-) diff --git a/docs/acp-public-validation-2026-04-09.md b/docs/acp-public-validation-2026-04-09.md index bfe511c..e9d2590 100644 --- a/docs/acp-public-validation-2026-04-09.md +++ b/docs/acp-public-validation-2026-04-09.md @@ -32,6 +32,7 @@ Verified public HTTP JSON-RPC endpoints: - Codex: `https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc` - OpenCode: `https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc` - Gemini: `https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc` +- OpenClaw: `https://xworkmate-bridge.svc.plus/gateway/openclaw/` The `.../acp` path remains reserved for WebSocket ACP. @@ -46,11 +47,12 @@ Missing bearer auth returns a JSON-RPC error envelope with code `-32001`. ## Public Validation Results -The ingress returned `200 OK` on all three public routes after re-apply, and the deployment response confirmed the active upstream mappings: +The ingress returned `200 OK` on all public routes after re-apply, and the deployment response confirmed the active upstream mappings: - `codex` -> `127.0.0.1:9010` - `opencode` -> `127.0.0.1:3910` - `gemini` -> `127.0.0.1:8791` +- `openclaw` -> `127.0.0.1:18789` (Host process) ### Codex diff --git a/docs/architecture/acp-forwarding-topology.md b/docs/architecture/acp-forwarding-topology.md index a7ab665..e17622c 100644 --- a/docs/architecture/acp-forwarding-topology.md +++ b/docs/architecture/acp-forwarding-topology.md @@ -58,7 +58,7 @@ flowchart TD C1["https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc"] C2["https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc"] C3["https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc"] - C4["wss://openclaw.svc.plus"] + C4["https://xworkmate-bridge.svc.plus/gateway/openclaw/"] end B7 --> C1 @@ -110,7 +110,7 @@ flowchart LR U1["https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc"] U2["https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc"] U3["https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc"] - U4["wss://openclaw.svc.plus
reported as openclaw.svc.plus:443"] + U4["https://xworkmate-bridge.svc.plus/gateway/openclaw/
reported as xworkmate-bridge.svc.plus:443"] end APPMETHODS --> BRIDGE @@ -138,7 +138,7 @@ Important distinction: - `availableExecutionTargets` tells the app which first-level task dialog modes are currently available - for `gatewayProviderId=openclaw`, the bridge rewrites the upstream target to - `wss://openclaw.svc.plus` + `https://xworkmate-bridge.svc.plus/gateway/openclaw/` ## Production Truth 当前 production forwarding 事实: @@ -152,7 +152,7 @@ Important distinction: - `opencode` - `gemini` - current production gateway forwarding target: - - `openclaw -> wss://openclaw.svc.plus` + - `openclaw -> https://xworkmate-bridge.svc.plus/gateway/openclaw/` 对 app 而言: @@ -163,7 +163,7 @@ Important distinction: ## Invariants - app traffic reaches upstream ACP and gateway services only through the bridge -- app does not call `xworkmate-bridge.svc.plus/acp-server/*` or `openclaw.svc.plus` directly +- app does not call `xworkmate-bridge.svc.plus/acp-server/*` or `xworkmate-bridge.svc.plus/gateway/openclaw/` directly - upstream auth stays bridge-internal: - `Authorization: Bearer $INTERNAL_SERVICE_TOKEN` - `acp.capabilities` is the provider / capability discovery source diff --git a/docs/architecture/adr-unified-bridge-entrypoints.md b/docs/architecture/adr-unified-bridge-entrypoints.md index cc9c43f..97c3110 100644 --- a/docs/architecture/adr-unified-bridge-entrypoints.md +++ b/docs/architecture/adr-unified-bridge-entrypoints.md @@ -127,7 +127,7 @@ Use these terms consistently: - `canonical app-facing path`: `/acp/rpc` and `/acp` - `gateway runtime method family`: `xworkmate.gateway.*` -- `independent upstream service`: `xworkmate-bridge.svc.plus/acp-server/*`, `wss://openclaw.svc.plus` +- `independent upstream service`: `xworkmate-bridge.svc.plus/acp-server/*`, `xworkmate-bridge.svc.plus/gateway/openclaw/` - `bridge-owned routing`: provider / gateway selection performed inside bridge - `routing metadata`: execution target and resolved provider/gateway identifiers returned to the app diff --git a/docs/architecture/bridge-runtime-design.md b/docs/architecture/bridge-runtime-design.md index a807487..c3588b3 100644 --- a/docs/architecture/bridge-runtime-design.md +++ b/docs/architecture/bridge-runtime-design.md @@ -13,6 +13,15 @@ | `gemini-acp-adapter` | `geminiadapter.Serve` | 启动 Gemini 专用 ACP adapter,把 Gemini CLI 包装成 ACP HTTP / WebSocket 服务。 | | 默认模式 | `toolbridge.Run` | 启动 MCP 风格的本地工具桥,暴露 `chat`、`claude_review`、`vault_kv` 等工具。 | + |服务 │ 外部入口 (HTTPS/WSS) │ 后端转发目标 (Local) │ 部署方式 │ + ├──────────┼────────────────────────────────────────────────┼──────────────────────────┼─────────────┤ + │ Bridge │ xworkmate-bridge.svc.plus/ │ 127.0.0.1:8787 │ Docker 容器 │ + │ OpenClaw │ xworkmate-bridge.svc.plus/gateway/openclaw/ │ 127.0.0.1:18789 │ 主机进程 │ + │ Codex │ xworkmate-bridge.svc.plus/acp-server/codex/ │ acp-server-codex:3911 │ Docker 容器 │ + │ OpenCode │ xworkmate-bridge.svc.plus/acp-server/opencode/ │ acp-server-opencode:3910 │ Docker 容器 │ + │ Gemini │ xworkmate-bridge.svc.plus/acp-server/gemini/ │ acp-server-gemini:3912 │ Docker 容器 + + 设计含义: - `main.go` 不承载业务决策,只做模式分发。 diff --git a/internal/acp/gateway_runtime.go b/internal/acp/gateway_runtime.go index 45acc9c..96c56e1 100644 --- a/internal/acp/gateway_runtime.go +++ b/internal/acp/gateway_runtime.go @@ -74,8 +74,9 @@ func applyProductionGatewayRouting( if strings.TrimSpace(strings.ToLower(request.Mode)) != "openclaw" { return request } + // Route through the unified bridge ingress request.Endpoint = gatewayruntime.Endpoint{ - Host: "openclaw.svc.plus", + Host: "xworkmate-bridge.svc.plus", Port: 443, TLS: true, } diff --git a/internal/acp/gateway_runtime_test.go b/internal/acp/gateway_runtime_test.go index 9aa187c..8c3a5e5 100644 --- a/internal/acp/gateway_runtime_test.go +++ b/internal/acp/gateway_runtime_test.go @@ -36,7 +36,7 @@ func TestResolveGatewayReportedRemoteAddressNormalizesExplicitPublicRemoteHost( got := resolveGatewayReportedRemoteAddress(server, gatewayruntime.ConnectRequest{ Mode: "openclaw", Endpoint: gatewayruntime.Endpoint{ - Host: "openclaw.svc.plus", + Host: "xworkmate-bridge.svc.plus", Port: 443, TLS: true, }, diff --git a/internal/acp/provider_catalog.go b/internal/acp/provider_catalog.go index 2bedaef..38d4ef7 100644 --- a/internal/acp/provider_catalog.go +++ b/internal/acp/provider_catalog.go @@ -71,16 +71,34 @@ func newProductionProviderCatalog() (map[string]syncedProvider, []string) { config := loadBridgeConfig() authorizationHeader := bridgeUpstreamAuthorizationHeader() - // Map both legacy OPENCLAW_*_URL and new *_RPC_URL patterns for compatibility providers := []struct { - id string - label string - yaml string - envKeys []string + id string + label string + yaml string + envKeys []string + defaultURL string }{ - {"codex", "Codex", config.Upstream.CodexURL, []string{"CODEX_RPC_URL", "OPENCLAW_CODEX_URL"}}, - {"opencode", "OpenCode", config.Upstream.OpenCodeURL, []string{"OPENCODE_RPC_URL", "OPENCLAW_OPENCODE_URL"}}, - {"gemini", "Gemini", config.Upstream.GeminiURL, []string{"GEMINI_RPC_URL", "OPENCLAW_GEMINI_URL"}}, + { + id: "codex", + label: "Codex", + yaml: config.Upstream.CodexURL, + envKeys: []string{"CODEX_RPC_URL"}, + defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc", + }, + { + id: "opencode", + label: "OpenCode", + yaml: config.Upstream.OpenCodeURL, + envKeys: []string{"OPENCODE_RPC_URL"}, + defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc", + }, + { + id: "gemini", + label: "Gemini", + yaml: config.Upstream.GeminiURL, + envKeys: []string{"GEMINI_RPC_URL"}, + defaultURL: "https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc", + }, } catalog := make(map[string]syncedProvider) @@ -88,6 +106,9 @@ func newProductionProviderCatalog() (map[string]syncedProvider, []string) { for _, p := range providers { endpoint := resolveURL(p.yaml, p.envKeys...) + if endpoint == "" { + endpoint = p.defaultURL + } catalog[p.id] = syncedProvider{ ProviderID: p.id, Label: p.label, diff --git a/internal/gatewayruntime/runtime_reported_address_test.go b/internal/gatewayruntime/runtime_reported_address_test.go index 79ab86d..79fe10f 100644 --- a/internal/gatewayruntime/runtime_reported_address_test.go +++ b/internal/gatewayruntime/runtime_reported_address_test.go @@ -25,7 +25,7 @@ func TestManagerConnectUsesReportedRemoteAddressInSnapshot(t *testing.T) { Port: server.Port(), TLS: false, }, - ReportedRemoteAddress: "openclaw.svc.plus:443", + ReportedRemoteAddress: "xworkmate-bridge.svc.plus:443", ConnectAuthMode: "shared-token", ConnectAuthFields: []string{"token"}, ConnectAuthSources: []string{"shared:form"}, @@ -54,7 +54,7 @@ func TestManagerConnectUsesReportedRemoteAddressInSnapshot(t *testing.T) { t.Fatalf("expected connect success, got %#v", result.Error) } - if got := result.Snapshot["remoteAddress"]; got != "openclaw.svc.plus:443" { + if got := result.Snapshot["remoteAddress"]; got != "xworkmate-bridge.svc.plus:443" { t.Fatalf("expected reported remote address, got %#v", got) } } diff --git a/internal/mounts/reconcile.go b/internal/mounts/reconcile.go index 952fbe5..5d2e8ad 100644 --- a/internal/mounts/reconcile.go +++ b/internal/mounts/reconcile.go @@ -213,11 +213,9 @@ func reconcileCodex( } state.DiscoveredMCPCount = discovered state.ManagedMCPCount = len(managedServers) - if strings.TrimSpace(aiGatewayURL) != "" { - state.Detail = "LLM API uses launch-scoped defaults for collaboration runs." - } else { - state.Detail = "LLM API not configured." - } + state.Detail = "Codex public base URL: https://xworkmate-bridge.svc.plus/acp-server/codex\n" + + "Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/codex/acp\n" + + "Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/codex/acp/rpc" return state } @@ -246,8 +244,15 @@ func reconcileCLIListTarget( } state.DiscoveredMCPCount = discovered state.ManagedMCPCount = len(enabledServers(config.ManagedMCPServers)) - state.Detail = "MCP discovery uses `" + strings.Join(command, " ") + - "`; LLM API stays launch-scoped." + + if targetID == "gemini" { + state.Detail = "Gemini public base URL: https://xworkmate-bridge.svc.plus/acp-server/gemini\n" + + "Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/gemini/acp\n" + + "Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/gemini/acp/rpc" + } else { + state.Detail = "MCP discovery uses `" + strings.Join(command, " ") + + "`; LLM API stays launch-scoped." + } return state } @@ -286,7 +291,9 @@ func reconcileOpencode(config Config, opencodeHome string) MountTargetState { } state.DiscoveredMCPCount = discovered state.ManagedMCPCount = len(managedServers) - state.Detail = "Managed MCP config is preserved in ~/.opencode/config.toml." + state.Detail = "OpenCode public base URL: https://xworkmate-bridge.svc.plus/acp-server/opencode\n" + + "Preferred WebSocket endpoint: https://xworkmate-bridge.svc.plus/acp-server/opencode/acp\n" + + "Compatibility HTTP RPC endpoint: https://xworkmate-bridge.svc.plus/acp-server/opencode/acp/rpc" return state } diff --git a/scripts/github-actions/validate-deploy.sh b/scripts/github-actions/validate-deploy.sh index d6512c8..47ae1e9 100644 --- a/scripts/github-actions/validate-deploy.sh +++ b/scripts/github-actions/validate-deploy.sh @@ -54,9 +54,9 @@ fi BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")" OPENCLAW_BASE_URL="$(normalize_url "${OPENCLAW_URL:-${3:-${BASE_URL}/gateway/openclaw}}")" -CODEX_BASE_URL="$(normalize_url "${CODEX_RPC_URL:-${4:-${BASE_URL}/acp-server/codex}}")" -OPENCODE_BASE_URL="$(normalize_url "${OPENCODE_RPC_URL:-${5:-${BASE_URL}/acp-server/opencode}}")" -GEMINI_BASE_URL="$(normalize_url "${GEMINI_RPC_URL:-${6:-${BASE_URL}/acp-server/gemini}}")" +CODEX_BASE_URL="$(normalize_url "${CODEX_RPC_URL:-${4:-${BASE_URL}/acp-server/codex/acp/rpc}}")" +OPENCODE_BASE_URL="$(normalize_url "${OPENCODE_RPC_URL:-${5:-${BASE_URL}/acp-server/opencode/acp/rpc}}")" +GEMINI_BASE_URL="$(normalize_url "${GEMINI_RPC_URL:-${6:-${BASE_URL}/acp-server/gemini/acp/rpc}}")" AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-${INTERNAL_SERVICE_TOKEN:-${7:-}}}" ensure_rpc_path() {