From 40fc458072e0c1a120696533114e562e7e055cda Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Wed, 17 Jun 2026 21:02:46 +0800 Subject: [PATCH] feat(bridge): implement unified bridge entrypoints and routing --- .github/workflows/pipeline.yml | 24 ++-- README.md | 2 +- docs/api-reference.md | 11 +- .../adr-unified-bridge-entrypoints.md | 2 +- internal/acp/config.go | 32 ++++- internal/acp/gateway.go | 41 +++++++ internal/acp/gateway_identity.go | 14 +++ internal/acp/gateway_identity_test.go | 20 +++ internal/acp/gateway_test.go | 114 ++++++++++++++++++ internal/acp/providers_sync_test.go | 20 ++- internal/acp/routing_test.go | 19 ++- internal/acp/server.go | 11 +- internal/acp/web_contract_test.go | 26 ++++ scripts/ci/verify_api_interface_contract.sh | 4 +- scripts/ci/verify_api_scenario_contract.sh | 4 +- scripts/ci/verify_hermes_acp_scenarios.sh | 4 +- .../github-actions/deploy-native-binary.sh | 24 +++- scripts/github-actions/deploy.sh | 2 +- .../github-actions/report-production-state.sh | 6 +- .../test-deploy-native-binary.sh | 18 +-- scripts/github-actions/validate-deploy.sh | 6 +- .../validate-openclaw-session.sh | 4 +- .../verify-public-rpc-contract.sh | 4 +- 23 files changed, 356 insertions(+), 56 deletions(-) diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 9bf6d53..074319c 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -21,7 +21,7 @@ on: required: true default: true type: boolean - internal_service_token: + ai_workspace_auth_token: description: "Optional ACP auth token for deploy" required: false default: "" @@ -64,11 +64,11 @@ jobs: jwtGithubAudience: vault ignoreNotFound: true secrets: | - kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN + kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN - name: Export bridge auth token if: ${{ steps.vault.outcome == 'success' }} - run: echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}" >> "$GITHUB_ENV" + run: echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}" >> "$GITHUB_ENV" - name: Probe current production bridge id: production_state @@ -220,7 +220,7 @@ jobs: jwtGithubAudience: vault ignoreNotFound: true secrets: | - kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN ; + kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN ; kv/data/github-actions/xworkmate-bridge WORKSPACE_REPO_TOKEN | WORKSPACE_REPO_TOKEN ; kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY | SINGLE_NODE_VPS_SSH_PRIVATE_KEY ; kv/data/github-actions/xworkmate-bridge SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 | SINGLE_NODE_VPS_SSH_PRIVATE_KEY_B64 ; @@ -229,20 +229,20 @@ jobs: - name: Export deploy secrets run: | { - if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.internal_service_token }}" ]]; then - echo "BRIDGE_AUTH_TOKEN=${{ inputs.internal_service_token }}" + if [[ "${{ github.event_name }}" == "workflow_dispatch" && -n "${{ inputs.ai_workspace_auth_token }}" ]]; then + echo "AI_WORKSPACE_AUTH_TOKEN=${{ inputs.ai_workspace_auth_token }}" else - echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}" + echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}" fi } >> "$GITHUB_ENV" - name: Validate deploy secrets run: | - if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then - echo "::error::BRIDGE_AUTH_TOKEN is empty. Provide it via the workflow_dispatch input, or ensure kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN is readable from Vault." + if [[ -z "${AI_WORKSPACE_AUTH_TOKEN}" ]]; then + echo "::error::AI_WORKSPACE_AUTH_TOKEN is empty. Provide it via the workflow_dispatch input, or ensure kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN is readable from Vault." exit 1 fi - echo "BRIDGE_AUTH_TOKEN length=${#BRIDGE_AUTH_TOKEN}" + echo "AI_WORKSPACE_AUTH_TOKEN length=${#AI_WORKSPACE_AUTH_TOKEN}" - name: Checkout playbooks repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -384,10 +384,10 @@ jobs: jwtGithubAudience: vault ignoreNotFound: true secrets: | - kv/data/github-actions/xworkmate-bridge INTERNAL_SERVICE_TOKEN | INTERNAL_SERVICE_TOKEN + kv/data/github-actions/xworkmate-bridge AI_WORKSPACE_AUTH_TOKEN | AI_WORKSPACE_AUTH_TOKEN - name: Export bridge auth token - run: echo "BRIDGE_AUTH_TOKEN=${{ steps.vault.outputs.INTERNAL_SERVICE_TOKEN }}" >> "$GITHUB_ENV" + run: echo "AI_WORKSPACE_AUTH_TOKEN=${{ steps.vault.outputs.AI_WORKSPACE_AUTH_TOKEN }}" >> "$GITHUB_ENV" - name: Validate deployed endpoints run: bash ./scripts/github-actions/validate-deploy.sh "$(git rev-parse --short HEAD)" "${BRIDGE_SERVER_URL}" diff --git a/README.md b/README.md index 62314ea..4111b27 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Optional GitHub secrets: Optional workflow input: -- `internal_service_token`: manual dispatch input that is forwarded to Ansible as `INTERNAL_SERVICE_TOKEN` +- `ai_workspace_auth_token`: manual dispatch input that is forwarded as `AI_WORKSPACE_AUTH_TOKEN` ## Environment diff --git a/docs/api-reference.md b/docs/api-reference.md index 00941d5..3100631 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -39,7 +39,8 @@ 环境变量: -- `BRIDGE_AUTH_TOKEN` +- `AI_WORKSPACE_AUTH_TOKEN`:主共享 token,用于 bridge 入站鉴权、上游 provider 转发、OpenClaw Gateway 重签发与任务转发 fallback。 +- `BRIDGE_AUTH_TOKEN`:旧主 token。没有 `AI_WORKSPACE_AUTH_TOKEN` 时继续生效并参与上游转发,用于存量租户兼容,直到 `AI_WORKSPACE_AUTH_TOKEN` 完成彻底替代后下线。 - `BRIDGE_REVIEW_AUTH_TOKEN`(可选):Apple review / beta 工测专用临时 token。清空该环境变量并重启/reload bridge 即可单独关停,不影响主 token。 - `ACP_ALLOWED_ORIGINS` @@ -48,9 +49,9 @@ - `/acp` 与 `/acp/rpc` 都做 origin allowlist 校验 - 空 `Origin` 默认允许 - `/api/ping`、`/acp`、`/acp/rpc` 在任一 bridge token 非空时都要求 bearer header -- `BRIDGE_AUTH_TOKEN` 与 `BRIDGE_REVIEW_AUTH_TOKEN` 都为空时默认放行 +- `AI_WORKSPACE_AUTH_TOKEN`、`BRIDGE_AUTH_TOKEN` 与 `BRIDGE_REVIEW_AUTH_TOKEN` 都为空时默认放行 - token 非空时,接受裸 token 或 `Bearer ` -- 线上 Caddy 入口必须与 bridge origin 保持同一 token set:主 `BRIDGE_AUTH_TOKEN` 与可选 `BRIDGE_REVIEW_AUTH_TOKEN` 都应放行;无 token 仍返回 `401` +- 线上 Caddy 入口必须与 bridge origin 保持同一 token set:主 `AI_WORKSPACE_AUTH_TOKEN`、兼容 `BRIDGE_AUTH_TOKEN` 与可选 `BRIDGE_REVIEW_AUTH_TOKEN` 都应放行;无 token 仍返回 `401` - `xworkmate-app` 生产 Origin 固定为 `https://xworkmate.svc.plus` ## 3.1 Lightweight Distributed Task Forwarding @@ -139,7 +140,7 @@ distributed: - `bridge_endpoint` 是 peer bridge base URL,bridge 会按当前请求路径拼接 `/acp/rpc` 或 `/gateway/openclaw` - 同步消息不能走公网;`bridge_endpoint` 必须是 loopback、private、link-local 这类本机或 VPN 内网地址,用于 WireGuard over VLESS 等隧道已经提供加密的场景 - 只要求本机网络能路由到 endpoint;bridge 不依赖 config center 或额外注册中心 -- `task_forward_token` 为空时复用本机 `BRIDGE_AUTH_TOKEN` +- `task_forward_token` 为空时复用本机 `AI_WORKSPACE_AUTH_TOKEN`;未配置时兼容复用 `BRIDGE_AUTH_TOKEN` - 转发请求会带 `X-XWorkmate-Bridge-Forwarded: 1` - `X-XWorkmate-Forward-Source` 是源节点,`X-XWorkmate-Forward-Target` 是最终目标节点 - `X-XWorkmate-Forward-Hop` 逐跳递增,超过 `forwarding.hop_limit` 时拒绝转发,避免循环 @@ -157,7 +158,7 @@ distributed: BRIDGE_SERVER_URL=https://xworkmate-bridge.svc.plus BRIDGE_WS_URL=wss://xworkmate-bridge.svc.plus/acp BRIDGE_HTTP_RPC_URL=https://xworkmate-bridge.svc.plus/acp/rpc -Authorization: Bearer $BRIDGE_AUTH_TOKEN +Authorization: Bearer $AI_WORKSPACE_AUTH_TOKEN Origin: https://xworkmate.svc.plus ``` diff --git a/docs/architecture/adr-unified-bridge-entrypoints.md b/docs/architecture/adr-unified-bridge-entrypoints.md index 6b399d9..216e8bc 100644 --- a/docs/architecture/adr-unified-bridge-entrypoints.md +++ b/docs/architecture/adr-unified-bridge-entrypoints.md @@ -109,7 +109,7 @@ Gateway access remains bridge-owned via JSON-RPC methods: Upstream authentication is unified for both ACP and gateway routes: -- `Authorization: Bearer $INTERNAL_SERVICE_TOKEN` +- `Authorization: Bearer $AI_WORKSPACE_AUTH_TOKEN` ## Consequences diff --git a/internal/acp/config.go b/internal/acp/config.go index c4229e9..ae9d15e 100644 --- a/internal/acp/config.go +++ b/internal/acp/config.go @@ -109,17 +109,45 @@ func resolveURL(yamlVal string, envKeys ...string) string { } func bridgeUpstreamAuthorizationHeader() string { - token := bridgeSharedAuthToken() + token := bridgePublicAuthToken() if token != "" && !strings.HasPrefix(strings.ToLower(token), "bearer ") { return "Bearer " + token } return token } -func bridgeSharedAuthToken() string { +func bridgePublicAuthToken() string { + if token := strings.TrimSpace(os.Getenv("AI_WORKSPACE_AUTH_TOKEN")); token != "" { + return token + } return strings.TrimSpace(shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", "")) } +func bridgeSharedAuthToken() string { + return bridgePublicAuthToken() +} + +func bridgeInboundAuthTokens() []string { + var tokens []string + seen := map[string]struct{}{} + for _, token := range []string{ + os.Getenv("AI_WORKSPACE_AUTH_TOKEN"), + os.Getenv("BRIDGE_AUTH_TOKEN"), + os.Getenv("BRIDGE_REVIEW_AUTH_TOKEN"), + } { + trimmed := strings.TrimSpace(token) + if trimmed == "" { + continue + } + if _, ok := seen[trimmed]; ok { + continue + } + seen[trimmed] = struct{}{} + tokens = append(tokens, trimmed) + } + return tokens +} + func resolveDistributedTaskForwardToken(config *BridgeConfig) string { if token := strings.TrimSpace(os.Getenv("XWORKMATE_BRIDGE_TASK_FORWARD_TOKEN")); token != "" { return token diff --git a/internal/acp/gateway.go b/internal/acp/gateway.go index 93c39a6..4a68595 100644 --- a/internal/acp/gateway.go +++ b/internal/acp/gateway.go @@ -93,6 +93,19 @@ func handleGatewayConnect( } result := server.gateway.Connect(request, notify) + if usesBridgeIdentity && shouldRetryOpenClawGatewayWithSharedToken(result) { + clearBridgeGatewayDeviceToken() + request.Auth.DeviceToken = "" + request.HasDeviceToken = false + request.Auth.Token = bridgeSharedAuthToken() + request.HasSharedAuth = strings.TrimSpace(request.Auth.Token) != "" + if request.HasSharedAuth { + request.ConnectAuthMode = "shared-token" + request.ConnectAuthFields = []string{"token"} + request.ConnectAuthSources = []string{"bridge:repair"} + result = server.gateway.Connect(request, notify) + } + } if result.OK && usesBridgeIdentity { saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken) } @@ -285,6 +298,19 @@ func ensureProductionGatewayConnected( request.HasDeviceToken = deviceToken != "" request.ReportedRemoteAddress = resolveGatewayReportedRemoteAddress(server, request) result := server.gateway.Connect(request, notify) + if shouldRetryOpenClawGatewayWithSharedToken(result) { + clearBridgeGatewayDeviceToken() + request.Auth.DeviceToken = "" + request.HasDeviceToken = false + request.Auth.Token = bridgeSharedAuthToken() + request.HasSharedAuth = strings.TrimSpace(request.Auth.Token) != "" + if request.HasSharedAuth { + request.ConnectAuthMode = "shared-token" + request.ConnectAuthFields = []string{"token"} + request.ConnectAuthSources = []string{"bridge:repair"} + result = server.gateway.Connect(request, notify) + } + } if result.OK { saveBridgeGatewayDeviceToken(result.ReturnedDeviceToken) return nil @@ -297,6 +323,21 @@ func ensureProductionGatewayConnected( return &shared.RPCError{Code: -32002, Message: "GATEWAY_CONNECT_FAILED: " + message} } +func shouldRetryOpenClawGatewayWithSharedToken(result gatewayruntime.ConnectResult) bool { + if result.OK || strings.TrimSpace(bridgeSharedAuthToken()) == "" { + return false + } + code := strings.ToUpper(strings.TrimSpace(shared.StringArg(result.Error, "code", ""))) + message := strings.ToLower(strings.TrimSpace(shared.StringArg(result.Error, "message", ""))) + details := shared.AsMap(result.Error["details"]) + detailCode := strings.ToUpper(strings.TrimSpace(shared.StringArg(details, "code", ""))) + return detailCode == "AUTH_DEVICE_TOKEN_MISMATCH" || + detailCode == "PAIRING_REQUIRED" || + code == "NOT_PAIRED" || + strings.Contains(message, "device token mismatch") || + strings.Contains(message, "rotate/reissue device token") +} + func configureProductionOpenClawGatewayRuntime(manager *gatewayruntime.Manager) { if manager == nil { return diff --git a/internal/acp/gateway_identity.go b/internal/acp/gateway_identity.go index 669afd1..87a5fb1 100644 --- a/internal/acp/gateway_identity.go +++ b/internal/acp/gateway_identity.go @@ -138,6 +138,20 @@ func saveBridgeGatewayDeviceToken(deviceToken string) { ) } +func clearBridgeGatewayDeviceToken() { + bridgeGatewayIdentity.Lock() + defer bridgeGatewayIdentity.Unlock() + if strings.TrimSpace(bridgeGatewayIdentity.value.DeviceID) == "" { + return + } + bridgeGatewayIdentity.deviceToken = "" + _ = persistBridgeGatewayIdentity( + bridgeGatewayIdentityPath(), + bridgeGatewayIdentity.value, + "", + ) +} + func persistBridgeGatewayIdentity( path string, identity gatewayruntime.DeviceIdentity, diff --git a/internal/acp/gateway_identity_test.go b/internal/acp/gateway_identity_test.go index 3bad4c9..f8fd5ed 100644 --- a/internal/acp/gateway_identity_test.go +++ b/internal/acp/gateway_identity_test.go @@ -144,6 +144,26 @@ func TestBridgeGatewayIdentityPersistsReturnedDeviceToken(t *testing.T) { } } +func TestBridgeGatewayIdentityClearsStoredDeviceToken(t *testing.T) { + identityPath := filepath.Join(t.TempDir(), "openclaw-device.json") + t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", identityPath) + resetBridgeGatewayIdentityForTest() + t.Cleanup(resetBridgeGatewayIdentityForTest) + + identity := newBridgeGatewayIdentity() + saveBridgeGatewayDeviceToken("device-token-2") + clearBridgeGatewayDeviceToken() + resetBridgeGatewayIdentityForTest() + + reloaded, token := bridgeGatewayOpenClawCredentials() + if reloaded.DeviceID != identity.DeviceID { + t.Fatalf("reloaded identity = %q, want %q", reloaded.DeviceID, identity.DeviceID) + } + if token != "" { + t.Fatalf("device token should be cleared, got %q", token) + } +} + func resetBridgeGatewayIdentityForTest() { bridgeGatewayIdentity.Lock() defer bridgeGatewayIdentity.Unlock() diff --git a/internal/acp/gateway_test.go b/internal/acp/gateway_test.go index f63b579..e549123 100644 --- a/internal/acp/gateway_test.go +++ b/internal/acp/gateway_test.go @@ -108,3 +108,117 @@ func TestSystemLogsConnectsProductionGatewayForStatus(t *testing.T) { t.Fatalf("expected connected status to reuse gateway session, got %d connect attempts", got) } } + +func TestProductionGatewayReconnectsWithSharedTokenAfterDeviceTokenMismatch(t *testing.T) { + gateway := newAcpFakeOpenClawGateway(t) + defer gateway.Close() + gateway.rejectDeviceTokenOnce.Store(true) + + identityPath := filepath.Join(t.TempDir(), "openclaw-device.json") + t.Setenv("GATEWAY_RPC_URL", gateway.URL()) + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") + t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml")) + t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", identityPath) + resetBridgeGatewayIdentityForTest() + t.Cleanup(resetBridgeGatewayIdentityForTest) + + _ = newBridgeGatewayIdentity() + saveBridgeGatewayDeviceToken("stale-device-token") + server := NewServer() + + result, rpcErr := server.handleRequest( + shared.RPCRequest{ + ID: "status", + Method: "system.logs", + Params: map[string]any{}, + }, + func(map[string]any) {}, + ) + if rpcErr != nil { + t.Fatalf("system.logs returned rpc error: %#v", rpcErr) + } + if got := result["gatewayStatus"]; got != "connected" { + t.Fatalf("expected gatewayStatus connected after repair, got %#v", result) + } + if got := gateway.ConnectCount(); got != 2 { + t.Fatalf("expected stale device token retry with shared token, got %d connects", got) + } + resetBridgeGatewayIdentityForTest() + _, token := bridgeGatewayOpenClawCredentials() + if token != "device-token-1" { + t.Fatalf("expected repaired device token to be persisted, got %q", token) + } +} + +func TestProductionGatewayReconnectPrefersAIWorkspaceToken(t *testing.T) { + gateway := newAcpFakeOpenClawGateway(t) + defer gateway.Close() + gateway.rejectDeviceTokenOnce.Store(true) + + t.Setenv("GATEWAY_RPC_URL", gateway.URL()) + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-token") + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") + t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml")) + t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", filepath.Join(t.TempDir(), "openclaw-device.json")) + resetBridgeGatewayIdentityForTest() + t.Cleanup(resetBridgeGatewayIdentityForTest) + + _ = newBridgeGatewayIdentity() + saveBridgeGatewayDeviceToken("stale-device-token") + server := NewServer() + + result, rpcErr := server.handleRequest( + shared.RPCRequest{ + ID: "status", + Method: "system.logs", + Params: map[string]any{}, + }, + func(map[string]any) {}, + ) + if rpcErr != nil { + t.Fatalf("system.logs returned rpc error: %#v", rpcErr) + } + if got := result["gatewayStatus"]; got != "connected" { + t.Fatalf("expected gatewayStatus connected after AI workspace token repair, got %#v", result) + } + if got := gateway.ConnectCount(); got != 2 { + t.Fatalf("expected stale device token retry with AI workspace token, got %d connects", got) + } +} + +func TestProductionGatewayDoesNotUseInternalServiceTokenFallback(t *testing.T) { + gateway := newAcpFakeOpenClawGateway(t) + defer gateway.Close() + gateway.rejectDeviceTokenOnce.Store(true) + + t.Setenv("GATEWAY_RPC_URL", gateway.URL()) + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") + t.Setenv("BRIDGE_AUTH_TOKEN", "") + t.Setenv("INTERNAL_SERVICE_TOKEN", "bridge-test-token") + t.Setenv("BRIDGE_CONFIG_PATH", filepath.Join(t.TempDir(), "missing-config.yaml")) + t.Setenv("XWORKMATE_BRIDGE_OPENCLAW_IDENTITY_PATH", filepath.Join(t.TempDir(), "openclaw-device.json")) + resetBridgeGatewayIdentityForTest() + t.Cleanup(resetBridgeGatewayIdentityForTest) + + _ = newBridgeGatewayIdentity() + saveBridgeGatewayDeviceToken("stale-device-token") + server := NewServer() + + result, rpcErr := server.handleRequest( + shared.RPCRequest{ + ID: "status", + Method: "system.logs", + Params: map[string]any{}, + }, + func(map[string]any) {}, + ) + if rpcErr != nil { + t.Fatalf("system.logs returned rpc error: %#v", rpcErr) + } + if got := result["gatewayStatus"]; got != "disconnected" { + t.Fatalf("expected gatewayStatus disconnected without AI workspace token, got %#v", result) + } + if got := gateway.ConnectCount(); got != 1 { + t.Fatalf("expected no retry with internal token, got %d connects", got) + } +} diff --git a/internal/acp/providers_sync_test.go b/internal/acp/providers_sync_test.go index 038d4d1..3a1df53 100644 --- a/internal/acp/providers_sync_test.go +++ b/internal/acp/providers_sync_test.go @@ -95,8 +95,8 @@ func TestCapabilitiesExposeBuiltInProductionProviderCatalog(t *testing.T) { } func TestProductionProviderCatalogFallsBackToBridgeAuthToken(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token") - t.Setenv("INTERNAL_SERVICE_TOKEN", "") _, catalog, _ := newProductionProviderCatalog() p, ok := catalog["codex"] @@ -109,9 +109,24 @@ func TestProductionProviderCatalogFallsBackToBridgeAuthToken(t *testing.T) { } } +func TestProductionProviderCatalogPrefersAIWorkspaceAuthToken(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-token") + t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-token") + + _, catalog, _ := newProductionProviderCatalog() + p, ok := catalog["codex"] + if !ok { + t.Fatal("missing codex") + } + + if got := p.AuthorizationHeader; got != "Bearer ai-workspace-token" { + t.Fatalf("expected AI workspace bearer header, got %q", got) + } +} + func TestProductionProviderCatalogPrefersDedicatedBridgeAuthToken(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "dedicated-token") - t.Setenv("INTERNAL_SERVICE_TOKEN", "legacy-token") _, catalog, _ := newProductionProviderCatalog() p, ok := catalog["codex"] @@ -125,6 +140,7 @@ func TestProductionProviderCatalogPrefersDedicatedBridgeAuthToken(t *testing.T) } func TestProductionProviderCatalogIgnoresInternalServiceToken(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "") t.Setenv("INTERNAL_SERVICE_TOKEN", "legacy-token") diff --git a/internal/acp/routing_test.go b/internal/acp/routing_test.go index 83f5da3..769a1f9 100644 --- a/internal/acp/routing_test.go +++ b/internal/acp/routing_test.go @@ -3056,6 +3056,7 @@ type acpFakeOpenClawGateway struct { artifactCount atomic.Int32 artifactReadCount atomic.Int32 artifactReadFailures atomic.Int32 + rejectDeviceTokenOnce atomic.Bool closeNextChatSend atomic.Bool alwaysCloseChatSend atomic.Bool agentWaitDelayMs atomic.Int64 @@ -3141,7 +3142,23 @@ func newAcpFakeOpenClawGateway(t *testing.T) *acpFakeOpenClawGateway { }) return } - if got, want := shared.StringArg(shared.AsMap(params["auth"]), "token", ""), os.Getenv("BRIDGE_AUTH_TOKEN"); got != want { + auth := shared.AsMap(params["auth"]) + if fake.rejectDeviceTokenOnce.Swap(false) && strings.TrimSpace(shared.StringArg(auth, "deviceToken", "")) != "" { + _ = conn.WriteJSON(map[string]any{ + "type": "res", + "id": id, + "ok": false, + "error": map[string]any{ + "code": "INVALID_REQUEST", + "message": "unauthorized: device token mismatch (rotate/reissue device token)", + "details": map[string]any{ + "code": "AUTH_DEVICE_TOKEN_MISMATCH", + }, + }, + }) + return + } + if got, want := shared.StringArg(auth, "token", ""), bridgeSharedAuthToken(); got != want { _ = conn.WriteJSON(map[string]any{ "type": "res", "id": id, diff --git a/internal/acp/server.go b/internal/acp/server.go index 03dd173..2ab2597 100644 --- a/internal/acp/server.go +++ b/internal/acp/server.go @@ -43,13 +43,20 @@ func newHTTPServer(addr string, handler http.Handler) *http.Server { func NewServer() *Server { config := loadBridgeConfig() + authTokens := bridgeInboundAuthTokens() + authToken := "" + authExtraTokens := []string(nil) + if len(authTokens) > 0 { + authToken = authTokens[0] + authExtraTokens = authTokens[1:] + } s := &Server{ sessions: make(map[string]*session), config: config, allowedOrigins: shared.ParseAllowedOrigins(shared.EnvOrDefault("ACP_ALLOWED_ORIGINS", "https://xworkmate.svc.plus,http://localhost:*,http://127.0.0.1:*")), authService: service.NewStaticTokenAuthService( - shared.EnvOrDefault("BRIDGE_AUTH_TOKEN", ""), - shared.EnvOrDefault("BRIDGE_REVIEW_AUTH_TOKEN", ""), + authToken, + authExtraTokens..., ), openClawGate: newOpenClawGatewayAdmissionGate(config), taskRouter: newDistributedTaskRouter(distributedTaskRouterConfig{ diff --git a/internal/acp/web_contract_test.go b/internal/acp/web_contract_test.go index 5e2a643..9225b19 100644 --- a/internal/acp/web_contract_test.go +++ b/internal/acp/web_contract_test.go @@ -875,6 +875,7 @@ func (w *panicSSEWriter) Write(payload []byte) (int, error) { func (w *panicSSEWriter) WriteHeader(int) {} func TestHTTPHandlerPingRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") server := NewServer() @@ -889,7 +890,27 @@ func TestHTTPHandlerPingRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured } } +func TestHTTPHandlerPingAcceptsAIWorkspaceBearerAuthorization(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token") + t.Setenv("BRIDGE_AUTH_TOKEN", "") + t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "") + t.Setenv("INTERNAL_SERVICE_TOKEN", "") + t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") + server := NewServer() + handler := server.Handler() + + recorder := httptest.NewRecorder() + request := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/api/ping", nil) + request.Header.Set("Authorization", "Bearer ai-workspace-test-token") + handler.ServeHTTP(recorder, request) + + if recorder.Code != http.StatusOK { + t.Fatalf("expected 200 for AI workspace token, got %d", recorder.Code) + } +} + func TestHTTPHandlerPingAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") @@ -950,8 +971,10 @@ func TestHandleRPCAllowsPreflightForConfiguredOrigin(t *testing.T) { } func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "") t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "") + t.Setenv("INTERNAL_SERVICE_TOKEN", "") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") server := NewServer() recorder := httptest.NewRecorder() @@ -970,6 +993,7 @@ func TestHandleRPCAllowsUnauthenticatedRequestsWhenBridgeAuthTokenUnset(t *testi } func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") server := NewServer() @@ -990,6 +1014,7 @@ func TestHandleRPCRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *te } func TestHandleRPCCapabilitiesRequiresBearerAuthorizationWhenBridgeAuthTokenConfigured(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") server := NewServer() @@ -1009,6 +1034,7 @@ func TestHandleRPCCapabilitiesRequiresBearerAuthorizationWhenBridgeAuthTokenConf } func TestHandleRPCAllowsReviewBearerAuthorizationWhenConfigured(t *testing.T) { + t.Setenv("AI_WORKSPACE_AUTH_TOKEN", "ai-workspace-test-token") t.Setenv("BRIDGE_AUTH_TOKEN", "bridge-test-token") t.Setenv("BRIDGE_REVIEW_AUTH_TOKEN", "review-bridge-test-token") t.Setenv("BRIDGE_CONFIG_PATH", "../../example/config.yaml") diff --git a/scripts/ci/verify_api_interface_contract.sh b/scripts/ci/verify_api_interface_contract.sh index a6a8e0c..87f7844 100755 --- a/scripts/ci/verify_api_interface_contract.sh +++ b/scripts/ci/verify_api_interface_contract.sh @@ -2,10 +2,10 @@ set -euo pipefail BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" -BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then - echo "Error: BRIDGE_AUTH_TOKEN is required" >&2 + echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 exit 1 fi diff --git a/scripts/ci/verify_api_scenario_contract.sh b/scripts/ci/verify_api_scenario_contract.sh index adf9584..5ca9f5f 100755 --- a/scripts/ci/verify_api_scenario_contract.sh +++ b/scripts/ci/verify_api_scenario_contract.sh @@ -3,10 +3,10 @@ set -euo pipefail BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" -BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then - echo "Error: BRIDGE_AUTH_TOKEN is required" >&2 + echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 exit 1 fi diff --git a/scripts/ci/verify_hermes_acp_scenarios.sh b/scripts/ci/verify_hermes_acp_scenarios.sh index 88a005d..c7ce59b 100755 --- a/scripts/ci/verify_hermes_acp_scenarios.sh +++ b/scripts/ci/verify_hermes_acp_scenarios.sh @@ -2,11 +2,11 @@ set -euo pipefail BRIDGE_SERVER_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" -BRIDGE_AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" HERMES_RPC_URL="${HERMES_RPC_URL:-${BRIDGE_SERVER_URL%/}/acp/rpc}" if [[ -z "${BRIDGE_AUTH_TOKEN}" ]]; then - echo "Error: BRIDGE_AUTH_TOKEN is required" >&2 + echo "Error: AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 exit 1 fi diff --git a/scripts/github-actions/deploy-native-binary.sh b/scripts/github-actions/deploy-native-binary.sh index 25b2072..7e6a8c5 100755 --- a/scripts/github-actions/deploy-native-binary.sh +++ b/scripts/github-actions/deploy-native-binary.sh @@ -57,15 +57,22 @@ resolve_token_from_unit() { REMOTE_SYSTEM_SERVICE_UNIT_CONTENT="$(ssh -o BatchMode=yes "${SYSTEM_MIGRATION_USER}@${TARGET_HOST}" "cat '${SYSTEM_SERVICE_UNIT_PATH}' 2>/dev/null || true" 2>/dev/null || true)" -if [[ -z "${BRIDGE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then +if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then + AI_WORKSPACE_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "AI_WORKSPACE_AUTH_TOKEN")" + if [[ -n "${AI_WORKSPACE_AUTH_TOKEN}" ]]; then + echo "recovered AI_WORKSPACE_AUTH_TOKEN from ${SYSTEM_SERVICE_UNIT_PATH} on ${TARGET_HOST}" >&2 + fi +fi + +if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -z "${BRIDGE_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" ]]; then BRIDGE_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_AUTH_TOKEN")" if [[ -n "${BRIDGE_AUTH_TOKEN}" ]]; then echo "recovered BRIDGE_AUTH_TOKEN from ${SYSTEM_SERVICE_UNIT_PATH} on ${TARGET_HOST}" >&2 fi fi -if [[ -z "${BRIDGE_AUTH_TOKEN:-}" ]]; then - echo "::error::BRIDGE_AUTH_TOKEN is required: pass it via env, -e xworkmate_bridge_auth_token=, or keep the existing system service unit at ${SYSTEM_SERVICE_UNIT_PATH}" >&2 +if [[ -z "${AI_WORKSPACE_AUTH_TOKEN:-}" && -z "${BRIDGE_AUTH_TOKEN:-}" ]]; then + echo "::error::AI_WORKSPACE_AUTH_TOKEN is required: pass it via env, -e ai_workspace_auth_token=, or keep AI_WORKSPACE_AUTH_TOKEN/BRIDGE_AUTH_TOKEN in the existing system service unit at ${SYSTEM_SERVICE_UNIT_PATH}" >&2 exit 1 fi @@ -73,7 +80,12 @@ if [[ -z "${BRIDGE_REVIEW_AUTH_TOKEN:-}" && -n "${REMOTE_SYSTEM_SERVICE_UNIT_CON BRIDGE_REVIEW_AUTH_TOKEN="$(printf '%s\n' "${REMOTE_SYSTEM_SERVICE_UNIT_CONTENT}" | resolve_token_from_unit /dev/stdin "BRIDGE_REVIEW_AUTH_TOKEN")" fi -AUTH_TOKEN_LINE="Environment=\"BRIDGE_AUTH_TOKEN=$(escape_systemd_env "${BRIDGE_AUTH_TOKEN}")\"" +AUTH_TOKEN_LINE="" +if [[ -n "${AI_WORKSPACE_AUTH_TOKEN:-}" ]]; then + AUTH_TOKEN_LINE="Environment=\"AI_WORKSPACE_AUTH_TOKEN=$(escape_systemd_env "${AI_WORKSPACE_AUTH_TOKEN}")\"" +else + AUTH_TOKEN_LINE="Environment=\"BRIDGE_AUTH_TOKEN=$(escape_systemd_env "${BRIDGE_AUTH_TOKEN}")\"" +fi REVIEW_TOKEN_LINE="" if [[ -n "${BRIDGE_REVIEW_AUTH_TOKEN:-}" ]]; then @@ -127,7 +139,7 @@ existing_env="$( systemctl --user show -p Environment --value "${SERVICE_NAME}" 2>/dev/null || true systemctl show -p Environment --value "${SYSTEM_SERVICE_NAME}" 2>/dev/null || true if [[ -f "${SYSTEM_SERVICE_UNIT_PATH}" ]]; then - sed -n 's/^Environment="\(BRIDGE_AUTH_TOKEN=[^"]*\|BRIDGE_REVIEW_AUTH_TOKEN=[^"]*\)"$/\1/p' "${SYSTEM_SERVICE_UNIT_PATH}" + sed -n 's/^Environment="\(AI_WORKSPACE_AUTH_TOKEN=[^"]*\|BRIDGE_AUTH_TOKEN=[^"]*\|BRIDGE_REVIEW_AUTH_TOKEN=[^"]*\)"$/\1/p' "${SYSTEM_SERVICE_UNIT_PATH}" fi } | sed '/^$/d' | head -n 1 )" @@ -146,7 +158,7 @@ for line in lines: for item in shlex.split(os.environ.get("EXISTING_ENV", "")): key, sep, value = item.partition("=") - if sep and key in {"BRIDGE_AUTH_TOKEN", "BRIDGE_REVIEW_AUTH_TOKEN"} and key not in present: + if sep and key in {"AI_WORKSPACE_AUTH_TOKEN", "BRIDGE_AUTH_TOKEN", "BRIDGE_REVIEW_AUTH_TOKEN"} and key not in present: escaped = value.replace("\\", "\\\\").replace('"', '\\"') lines.append(f'Environment="{key}={escaped}"') present.add(key) diff --git a/scripts/github-actions/deploy.sh b/scripts/github-actions/deploy.sh index b179639..1dda836 100644 --- a/scripts/github-actions/deploy.sh +++ b/scripts/github-actions/deploy.sh @@ -20,6 +20,6 @@ if [[ "${RUN_APPLY}" != "true" ]]; then fi ANSIBLE_CONFIG="${PWD}/ansible.cfg" \ -BRIDGE_AUTH_TOKEN="${INTERNAL_SERVICE_TOKEN:-}" \ +BRIDGE_AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" \ BRIDGE_REVIEW_AUTH_TOKEN="${BRIDGE_REVIEW_AUTH_TOKEN:-}" \ "${args[@]}" diff --git a/scripts/github-actions/report-production-state.sh b/scripts/github-actions/report-production-state.sh index 737c3bd..e6dc995 100644 --- a/scripts/github-actions/report-production-state.sh +++ b/scripts/github-actions/report-production-state.sh @@ -31,8 +31,9 @@ curl_args=( --max-time 20 ) -if [[ -n "${BRIDGE_AUTH_TOKEN:-}" ]]; then - curl_args+=(-H "Authorization: Bearer ${BRIDGE_AUTH_TOKEN}") +AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" +if [[ -n "${AUTH_TOKEN}" ]]; then + curl_args+=(-H "Authorization: Bearer ${AUTH_TOKEN}") fi for ((attempt = 1; attempt <= attempts; attempt += 1)); do @@ -83,4 +84,3 @@ print(f"production_tag={deployed_tag}") print(f"production_commit={deployed_commit}") print(f"production_version={deployed_version}") PY - diff --git a/scripts/github-actions/test-deploy-native-binary.sh b/scripts/github-actions/test-deploy-native-binary.sh index b74dcbb..bcd2175 100755 --- a/scripts/github-actions/test-deploy-native-binary.sh +++ b/scripts/github-actions/test-deploy-native-binary.sh @@ -149,7 +149,7 @@ run_deploy() { run_env_token_case() { local tmp_dir - tmp_dir="$(setup_test_env "case: BRIDGE_AUTH_TOKEN env var drives the unit file")" + tmp_dir="$(setup_test_env "case: AI_WORKSPACE_AUTH_TOKEN env var drives the unit file")" local log_file="${tmp_dir}/deploy.log" local unit_file="${tmp_dir}/remote/home/ubuntu/.config/systemd/user/xworkmate-bridge.service" @@ -160,7 +160,7 @@ run_env_token_case() { BRIDGE_CONFIG_PATH="${tmp_dir}/remote/opt/cloud-neutral/xworkmate-bridge/config.yaml" \ USER_SYSTEMD_DIR="${tmp_dir}/remote/home/ubuntu/.config/systemd/user" \ DEPLOY_NATIVE_SKIP_PROC_CHECK=true \ - BRIDGE_AUTH_TOKEN="test-token" + AI_WORKSPACE_AUTH_TOKEN="test-token" local log_output log_output="$(cat "${log_file}")" @@ -168,7 +168,7 @@ run_env_token_case() { assert_contains "${log_output}" "scp ubuntu@example.test:" assert_contains "${log_output}" "ssh ubuntu@example.test" assert_contains "${log_output}" "systemctl --user restart xworkmate-bridge.service" - assert_file_contains "${unit_file}" 'Environment="BRIDGE_AUTH_TOKEN=test-token"' + assert_file_contains "${unit_file}" 'Environment="AI_WORKSPACE_AUTH_TOKEN=test-token"' assert_file_contains "${unit_file}" "WantedBy=default.target" rm -rf "${tmp_dir}" @@ -176,7 +176,7 @@ run_env_token_case() { run_unit_fallback_case() { local tmp_dir - tmp_dir="$(setup_test_env "case: BRIDGE_AUTH_TOKEN recovered from system service unit file")" + tmp_dir="$(setup_test_env "case: AI_WORKSPACE_AUTH_TOKEN recovered from system service unit file")" local system_unit_dir="${tmp_dir}/remote/etc/systemd/system" mkdir -p "${system_unit_dir}" @@ -185,7 +185,7 @@ run_unit_fallback_case() { [Unit] Description=Stale system service [Service] -Environment="BRIDGE_AUTH_TOKEN=recovered-from-systemd" +Environment="AI_WORKSPACE_AUTH_TOKEN=recovered-from-systemd" Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token" ExecStart=/bin/true EOF @@ -201,7 +201,7 @@ EOF SYSTEM_SERVICE_UNIT_PATH="${system_unit_file}" \ DEPLOY_NATIVE_SKIP_PROC_CHECK=true - assert_file_contains "${unit_file}" 'Environment="BRIDGE_AUTH_TOKEN=recovered-from-systemd"' + assert_file_contains "${unit_file}" 'Environment="AI_WORKSPACE_AUTH_TOKEN=recovered-from-systemd"' assert_file_contains "${unit_file}" 'Environment="BRIDGE_REVIEW_AUTH_TOKEN=recovered-review-token"' rm -rf "${tmp_dir}" @@ -209,7 +209,7 @@ EOF run_fail_fast_case() { local tmp_dir - tmp_dir="$(setup_test_env "case: missing BRIDGE_AUTH_TOKEN fails fast with clear error")" + tmp_dir="$(setup_test_env "case: missing AI_WORKSPACE_AUTH_TOKEN fails fast with clear error")" local log_file="${tmp_dir}/deploy.log" local stderr_file="${tmp_dir}/deploy.stderr" @@ -226,9 +226,9 @@ run_fail_fast_case() { set -e if [[ "${exit_code}" == "0" ]]; then - fail "expected deploy to fail when BRIDGE_AUTH_TOKEN is empty and no system service unit exists" + fail "expected deploy to fail when AI_WORKSPACE_AUTH_TOKEN is empty and no system service unit exists" fi - assert_contains "$(cat "${stderr_file}")" "BRIDGE_AUTH_TOKEN is required" + assert_contains "$(cat "${stderr_file}")" "AI_WORKSPACE_AUTH_TOKEN is required" rm -rf "${tmp_dir}" } diff --git a/scripts/github-actions/validate-deploy.sh b/scripts/github-actions/validate-deploy.sh index b626903..755c4bb 100755 --- a/scripts/github-actions/validate-deploy.sh +++ b/scripts/github-actions/validate-deploy.sh @@ -28,7 +28,11 @@ fi BASE_URL="$(normalize_url "${BRIDGE_SERVER_URL:-${2:-https://xworkmate-bridge.svc.plus}}")" RPC_URL="${BASE_URL%/}/acp/rpc" -AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:?BRIDGE_AUTH_TOKEN is required}" +AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" +if [[ -z "${AUTH_TOKEN}" ]]; then + echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 + exit 1 +fi fast_http_curl_common=( --silent diff --git a/scripts/github-actions/validate-openclaw-session.sh b/scripts/github-actions/validate-openclaw-session.sh index 3311101..a599cd8 100755 --- a/scripts/github-actions/validate-openclaw-session.sh +++ b/scripts/github-actions/validate-openclaw-session.sh @@ -2,14 +2,14 @@ set -euo pipefail BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" -AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" REQUEST_ORIGIN="${OPENCLAW_SMOKE_ORIGIN:-https://xworkmate.svc.plus}" RPC_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_RPC_TIMEOUT_SECONDS:-180}" POLL_TIMEOUT_SECONDS="${OPENCLAW_SMOKE_POLL_TIMEOUT_SECONDS:-120}" POLL_INTERVAL_SECONDS="${OPENCLAW_SMOKE_POLL_INTERVAL_SECONDS:-2}" if [[ -z "${AUTH_TOKEN}" ]]; then - echo "BRIDGE_AUTH_TOKEN is required" >&2 + echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 exit 1 fi diff --git a/scripts/github-actions/verify-public-rpc-contract.sh b/scripts/github-actions/verify-public-rpc-contract.sh index 32a0720..124586b 100755 --- a/scripts/github-actions/verify-public-rpc-contract.sh +++ b/scripts/github-actions/verify-public-rpc-contract.sh @@ -2,12 +2,12 @@ set -euo pipefail BASE_URL="${BRIDGE_SERVER_URL:-https://xworkmate-bridge.svc.plus}" -AUTH_TOKEN="${BRIDGE_AUTH_TOKEN:-}" +AUTH_TOKEN="${AI_WORKSPACE_AUTH_TOKEN:-${BRIDGE_AUTH_TOKEN:-}}" HTTP_TIMEOUT_SECONDS="${HTTP_TIMEOUT_SECONDS:-30}" RPC_TIMEOUT_SECONDS="${RPC_TIMEOUT_SECONDS:-90}" if [[ -z "${AUTH_TOKEN}" ]]; then - echo "BRIDGE_AUTH_TOKEN is required" >&2 + echo "AI_WORKSPACE_AUTH_TOKEN or BRIDGE_AUTH_TOKEN is required" >&2 exit 1 fi