fix(runtime): tighten auth-scope and full-access hints

This commit is contained in:
Eva
2026-04-11 04:58:59 +07:00
committed by Peter Steinberger
parent 756d715ce0
commit b4fdd9c495
8 changed files with 26 additions and 19 deletions

View File

@@ -242,6 +242,15 @@ describe("formatAssistantErrorText", () => {
);
});
it("does not misdiagnose generic Codex permission failures as missing-scope failures", () => {
const msg = makeAssistantError(
'403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}',
);
expect(formatAssistantErrorText(msg, { provider: "openai-codex" })).not.toContain(
"required OpenAI Codex scopes",
);
});
it("returns an HTML-403 auth message for HTML provider auth failures", () => {
const msg = makeAssistantError("403 <!DOCTYPE html><html><body>Access denied</body></html>");
expect(formatAssistantErrorText(msg)).toBe(

View File

@@ -1124,6 +1124,16 @@ describe("classifyProviderRuntimeFailureKind", () => {
).not.toBe("auth_scope");
});
it("does not treat generic Codex permission failures as missing scope failures", () => {
expect(
classifyProviderRuntimeFailureKind({
provider: "openai-codex",
message:
'403 {"type":"error","error":{"type":"permission_error","message":"Insufficient permissions for this organization"}}',
}),
).not.toBe("auth_scope");
});
it("classifies OAuth refresh failures", () => {
expect(
classifyProviderRuntimeFailureKind(

View File

@@ -465,7 +465,7 @@ const TIMEOUT_ERROR_CODES = new Set([
"EAI_AGAIN",
]);
const AUTH_SCOPE_HINT_RE =
/\b(?:missing|required|requires|insufficient)\s+(?:the\s+following\s+)?scopes?\b|\bmissing\s+scope\b|\binsufficient\s+permissions?\b/i;
/\b(?:missing|required|requires|insufficient)\s+(?:the\s+following\s+)?scopes?\b|\bmissing\s+scope\b/i;
const AUTH_SCOPE_NAME_RE = /\b(?:api\.responses\.write|model\.request)\b/i;
const HTML_BODY_RE = /^\s*(?:<!doctype\s+html\b|<html\b)/i;
const HTML_CLOSE_RE = /<\/html>/i;

View File

@@ -119,7 +119,6 @@ describe("resolveEmbeddedFullAccessState", () => {
it("treats direct host runs with allowed elevation as full-access available", () => {
expect(
resolveEmbeddedFullAccessState({
sandboxEnabled: false,
execElevated: {
enabled: true,
allowed: true,
@@ -132,7 +131,6 @@ describe("resolveEmbeddedFullAccessState", () => {
it("keeps explicit runtime blocks even when host exec is allowed", () => {
expect(
resolveEmbeddedFullAccessState({
sandboxEnabled: false,
execElevated: {
enabled: true,
allowed: true,

View File

@@ -2,10 +2,10 @@ import type { ExecElevatedDefaults } from "../bash-tools.js";
import type { resolveSandboxContext } from "../sandbox.js";
import type { EmbeddedFullAccessBlockedReason, EmbeddedSandboxInfo } from "./types.js";
export function resolveEmbeddedFullAccessState(params: {
sandboxEnabled: boolean;
execElevated?: ExecElevatedDefaults;
}): { available: boolean; blockedReason?: EmbeddedFullAccessBlockedReason } {
export function resolveEmbeddedFullAccessState(params: { execElevated?: ExecElevatedDefaults }): {
available: boolean;
blockedReason?: EmbeddedFullAccessBlockedReason;
} {
if (params.execElevated?.fullAccessAvailable === true) {
return { available: true };
}
@@ -21,9 +21,6 @@ export function resolveEmbeddedFullAccessState(params: {
blockedReason: "host-policy",
};
}
if (!params.sandboxEnabled) {
return { available: true };
}
return { available: true };
}
@@ -37,7 +34,6 @@ export function buildEmbeddedSandboxInfo(
const elevatedConfigured = execElevated?.enabled === true;
const elevatedAllowed = Boolean(execElevated?.enabled && execElevated.allowed);
const fullAccess = resolveEmbeddedFullAccessState({
sandboxEnabled: true,
execElevated,
});
return {

View File

@@ -112,7 +112,6 @@ export async function resolveCommandsSystemPromptBundle(
},
});
const fullAccessState = resolveEmbeddedFullAccessState({
sandboxEnabled: sandboxRuntime.sandboxed,
execElevated: {
enabled: params.elevated.enabled,
allowed: params.elevated.allowed,

View File

@@ -48,7 +48,7 @@ describe("buildExecOverridePromptHint", () => {
expect(result).toContain("Current elevated level: full.");
expect(result).toContain(
"Auto-approved /elevated full is unavailable here (runtime). Use ask/on instead and do not ask the user to switch to /elevated full.",
"Auto-approved /elevated full is unavailable here (runtime). Do not ask the user to switch to /elevated full.",
);
});
});

View File

@@ -4,7 +4,6 @@ import type { ExecToolDefaults } from "../../agents/bash-tools.js";
import { resolveFastModeState } from "../../agents/fast-mode.js";
import { resolveEmbeddedFullAccessState } from "../../agents/pi-embedded-runner/sandbox-info.js";
import type { EmbeddedFullAccessBlockedReason } from "../../agents/pi-embedded-runner/types.js";
import { resolveSandboxRuntimeStatus } from "../../agents/sandbox.js";
import type { OpenClawConfig } from "../../config/config.js";
import { resolveGroupSessionKey } from "../../config/sessions/group.js";
import {
@@ -76,7 +75,7 @@ export function buildExecOverridePromptHint(params: {
const elevatedLine = `Current elevated level: ${params.elevatedLevel}.`;
const fullAccessLine =
params.fullAccessAvailable === false
? `Auto-approved /elevated full is unavailable here (${params.fullAccessBlockedReason ?? "runtime"}). Use ask/on instead and do not ask the user to switch to /elevated full.`
? `Auto-approved /elevated full is unavailable here (${params.fullAccessBlockedReason ?? "runtime"}). Do not ask the user to switch to /elevated full.`
: undefined;
return [
"## Current Exec Session State",
@@ -226,10 +225,6 @@ export async function runPreparedReply(
isFastTestEnv: process.env.OPENCLAW_TEST_FAST === "1",
});
const fullAccessState = resolveEmbeddedFullAccessState({
sandboxEnabled: resolveSandboxRuntimeStatus({
cfg,
sessionKey,
}).sandboxed,
execElevated: {
enabled: elevatedEnabled,
allowed: elevatedAllowed,