diff --git a/CHANGELOG.md b/CHANGELOG.md index b810b006527..e422d7639a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai - Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07. - Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt. - Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81. +- Gateway/Scopes: include `operator.read` and `operator.write` in default operator connect scope bundles across CLI, Control UI, and macOS clients so write-scoped announce/sub-agent follow-up calls no longer hit `pairing required` disconnects on loopback gateways. (#22582) thanks @YuzuruS. - Plugins/CLI: make `openclaw plugins enable` and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl. - Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07. - Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces. diff --git a/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift b/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift index d2cf9a5e2dd..151b7fdda94 100644 --- a/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift +++ b/apps/macos/Sources/OpenClawMacCLI/ConnectCommand.swift @@ -15,7 +15,7 @@ struct ConnectOptions { var clientMode: String = "ui" var displayName: String? var role: String = "operator" - var scopes: [String] = ["operator.admin", "operator.read", "operator.write", "operator.approvals", "operator.pairing"] + var scopes: [String] = defaultOperatorConnectScopes var help: Bool = false static func parse(_ args: [String]) -> ConnectOptions { diff --git a/apps/macos/Sources/OpenClawMacCLI/GatewayScopes.swift b/apps/macos/Sources/OpenClawMacCLI/GatewayScopes.swift new file mode 100644 index 00000000000..479c176d5d8 --- /dev/null +++ b/apps/macos/Sources/OpenClawMacCLI/GatewayScopes.swift @@ -0,0 +1,7 @@ +let defaultOperatorConnectScopes: [String] = [ + "operator.admin", + "operator.read", + "operator.write", + "operator.approvals", + "operator.pairing", +] diff --git a/apps/macos/Sources/OpenClawMacCLI/WizardCommand.swift b/apps/macos/Sources/OpenClawMacCLI/WizardCommand.swift index ec8c706d3ba..ebe3e8ae626 100644 --- a/apps/macos/Sources/OpenClawMacCLI/WizardCommand.swift +++ b/apps/macos/Sources/OpenClawMacCLI/WizardCommand.swift @@ -251,7 +251,7 @@ actor GatewayWizardClient { let clientMode = "ui" let role = "operator" // Explicit scopes; gateway no longer defaults empty scopes to admin. - let scopes: [String] = ["operator.admin", "operator.read", "operator.write", "operator.approvals", "operator.pairing"] + let scopes = defaultOperatorConnectScopes let client: [String: ProtoAnyCodable] = [ "id": ProtoAnyCodable(clientId), "displayName": ProtoAnyCodable(Host.current().localizedName ?? "OpenClaw macOS Wizard CLI"), diff --git a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift index 0836d00ec9a..30935df79d4 100644 --- a/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift +++ b/apps/shared/OpenClawKit/Sources/OpenClawKit/GatewayChannel.swift @@ -127,6 +127,14 @@ private enum ConnectChallengeError: Error { case timeout } +private let defaultOperatorConnectScopes: [String] = [ + "operator.admin", + "operator.read", + "operator.write", + "operator.approvals", + "operator.pairing", +] + public actor GatewayChannelActor { private let logger = Logger(subsystem: "ai.openclaw", category: "gateway") private var task: WebSocketTaskBox? @@ -318,7 +326,7 @@ public actor GatewayChannelActor { let primaryLocale = Locale.preferredLanguages.first ?? Locale.current.identifier let options = self.connectOptions ?? GatewayConnectOptions( role: "operator", - scopes: ["operator.admin", "operator.read", "operator.write", "operator.approvals", "operator.pairing"], + scopes: defaultOperatorConnectScopes, caps: [], commands: [], permissions: [:], diff --git a/ui/src/ui/gateway.ts b/ui/src/ui/gateway.ts index c6c9e824f98..ef2c418a014 100644 --- a/ui/src/ui/gateway.ts +++ b/ui/src/ui/gateway.ts @@ -61,6 +61,13 @@ export type GatewayBrowserClientOptions = { // 4008 = application-defined code (browser rejects 1008 "Policy Violation") const CONNECT_FAILED_CLOSE_CODE = 4008; +const DEFAULT_OPERATOR_CONNECT_SCOPES = [ + "operator.admin", + "operator.read", + "operator.write", + "operator.approvals", + "operator.pairing", +]; export class GatewayBrowserClient { private ws: WebSocket | null = null; @@ -145,13 +152,7 @@ export class GatewayBrowserClient { // Gateways may reject this unless gateway.controlUi.allowInsecureAuth is enabled. const isSecureContext = typeof crypto !== "undefined" && !!crypto.subtle; - const scopes = [ - "operator.admin", - "operator.read", - "operator.write", - "operator.approvals", - "operator.pairing", - ]; + const scopes = DEFAULT_OPERATOR_CONNECT_SCOPES; const role = "operator"; let deviceIdentity: Awaited> | null = null; let canFallbackToShared = false;