Gateway: allow operator admin scope for pairing and approvals
This commit is contained in:
@@ -25,6 +25,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
|
||||
- Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.
|
||||
- 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.
|
||||
- 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:<id>]]`, `[[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.
|
||||
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
|
||||
|
||||
@@ -43,6 +43,33 @@ describe("roleScopesAllow", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("treats operator.approvals/operator.pairing as satisfied by operator.admin", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.approvals"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["operator.pairing"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("does not treat operator.admin as satisfying non-operator scopes", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
role: "operator",
|
||||
requestedScopes: ["system.run"],
|
||||
allowedScopes: ["operator.admin"],
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("uses strict matching for non-operator roles", () => {
|
||||
expect(
|
||||
roleScopesAllow({
|
||||
|
||||
@@ -2,6 +2,7 @@ const OPERATOR_ROLE = "operator";
|
||||
const OPERATOR_ADMIN_SCOPE = "operator.admin";
|
||||
const OPERATOR_READ_SCOPE = "operator.read";
|
||||
const OPERATOR_WRITE_SCOPE = "operator.write";
|
||||
const OPERATOR_SCOPE_PREFIX = "operator.";
|
||||
|
||||
function normalizeScopeList(scopes: readonly string[]): string[] {
|
||||
const out = new Set<string>();
|
||||
@@ -15,15 +16,14 @@ function normalizeScopeList(scopes: readonly string[]): string[] {
|
||||
}
|
||||
|
||||
function operatorScopeSatisfied(requestedScope: string, granted: Set<string>): boolean {
|
||||
if (granted.has(OPERATOR_ADMIN_SCOPE) && requestedScope.startsWith(OPERATOR_SCOPE_PREFIX)) {
|
||||
return true;
|
||||
}
|
||||
if (requestedScope === OPERATOR_READ_SCOPE) {
|
||||
return (
|
||||
granted.has(OPERATOR_READ_SCOPE) ||
|
||||
granted.has(OPERATOR_WRITE_SCOPE) ||
|
||||
granted.has(OPERATOR_ADMIN_SCOPE)
|
||||
);
|
||||
return granted.has(OPERATOR_READ_SCOPE) || granted.has(OPERATOR_WRITE_SCOPE);
|
||||
}
|
||||
if (requestedScope === OPERATOR_WRITE_SCOPE) {
|
||||
return granted.has(OPERATOR_WRITE_SCOPE) || granted.has(OPERATOR_ADMIN_SCOPE);
|
||||
return granted.has(OPERATOR_WRITE_SCOPE);
|
||||
}
|
||||
return granted.has(requestedScope);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user