fix(cli): classify scope-limited status probes as reachable

This commit is contained in:
Peter Steinberger
2026-04-28 11:09:21 +01:00
parent 1fcf0a422f
commit ade9aaae89
3 changed files with 39 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai
- Tasks/media: infer agent ownership for session-scoped task records so `/tasks` agent-local fallback includes session-backed `video_generate` and other async media jobs even when the current chat session has no linked rows. Thanks @vincentkoc.
- Agents/media: keep long-running `video_generate` and `music_generate` tasks fresh while provider jobs are still pending, so task maintenance does not mark active Discord media renders lost before completion. Thanks @vincentkoc.
- CLI/status: treat scope-limited gateway probes as reachable-but-degraded in shared status scans, so `openclaw status --all` no longer reports a live gateway as unreachable after `missing scope: operator.read`. Fixes #49180; supersedes #47981. Thanks @openjay.
- Plugins/inspector: keep bundled plugin runtime capture quiet and config-tolerant for Codex, memory-lancedb, Feishu, Mattermost, QQBot, and Tlon so plugin-inspector JSON checks can validate the full bundled set. Thanks @vincentkoc.
- Slack/auto-reply: keep fully consumed text reset triggers such as `new session` out of `BodyForAgent` after directive cleanup, so configured Slack reset phrases do not leak into the fresh model turn. Fixes #73137. Thanks @neeravmakwana.
- Plugins/runtime deps: prune stale retained bundled runtime deps and keep doctor/secret channel contract scans on lightweight artifacts, so disabled bundled channels stop preserving old dependency trees or importing heavy plugin surfaces. Thanks @SymbolStar and @vincentkoc.

View File

@@ -142,6 +142,42 @@ describe("resolveGatewayProbeSnapshot", () => {
expect(result.gatewayProbe?.error).toBe("timeout; warn");
expect(result.gatewayProbeAuthWarning).toBeUndefined();
});
it("treats scope-limited read probes as reachable", async () => {
mocks.resolveGatewayProbeTarget.mockReturnValue({
mode: "local",
gatewayMode: "local",
remoteUrlMissing: false,
});
mocks.probeGateway.mockResolvedValue({
ok: false,
url: "ws://127.0.0.1:18789",
connectLatencyMs: 51,
error: "missing scope: operator.read",
close: null,
auth: {
role: "operator",
scopes: [],
capability: "connected_no_operator_scope",
},
server: {
version: null,
connId: null,
},
health: null,
status: null,
presence: null,
configSnapshot: null,
});
const result = await resolveGatewayProbeSnapshot({
cfg: {},
opts: {},
});
expect(result.gatewayReachable).toBe(true);
expect(result.gatewayProbe?.error).toBe("missing scope: operator.read; warn");
});
});
describe("resolveSharedMemoryStatusSnapshot", () => {

View File

@@ -11,6 +11,7 @@ import {
normalizeOptionalString,
} from "../shared/string-coerce.js";
import { pickGatewaySelfPresence } from "./gateway-presence.js";
import { isProbeReachable } from "./gateway-status/helpers.js";
export { pickGatewaySelfPresence } from "./gateway-presence.js";
let gatewayProbeModulePromise: Promise<typeof import("./status.gateway-probe.js")> | undefined;
@@ -142,7 +143,7 @@ export async function resolveGatewayProbeSnapshot(params: {
: gatewayProbeAuthWarning;
gatewayProbeAuthWarning = undefined;
}
const gatewayReachable = gatewayProbe?.ok === true;
const gatewayReachable = gatewayProbe ? isProbeReachable(gatewayProbe) : false;
const gatewaySelf = gatewayProbe?.presence
? pickGatewaySelfPresence(gatewayProbe.presence)
: null;