mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 12:40:43 +00:00
fix(agents): preserve pairing guidance for node invoke upgrades
This commit is contained in:
@@ -320,4 +320,21 @@ describe("createNodesTool screen_record duration guardrails", () => {
|
||||
}),
|
||||
).rejects.toThrow('invokeCommand "system.run" is reserved for shell execution');
|
||||
});
|
||||
|
||||
it("keeps invoke pairing guidance for scope upgrade rejections", async () => {
|
||||
gatewayMocks.callGatewayTool.mockRejectedValueOnce(
|
||||
new Error("scope upgrade pending approval (requestId: req-123)"),
|
||||
);
|
||||
const tool = createNodesTool();
|
||||
|
||||
await expect(
|
||||
tool.execute("call-1", {
|
||||
action: "invoke",
|
||||
node: "macbook",
|
||||
invokeCommand: "device.status",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"pairing required before node invoke. Approve pairing request req-123 and retry.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,9 +2,9 @@ import crypto from "node:crypto";
|
||||
import { Type } from "@sinclair/typebox";
|
||||
import type { OpenClawConfig } from "../../config/types.openclaw.js";
|
||||
import type { OperatorScope } from "../../gateway/method-scopes.js";
|
||||
import { readConnectPairingRequiredMessage } from "../../gateway/protocol/connect-error-details.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
import { resolveNodePairApprovalScopes } from "../../infra/node-pairing-authz.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../../shared/string-coerce.js";
|
||||
import type { GatewayMessageChannel } from "../../utils/message-channel.js";
|
||||
import { resolveSessionAgentId } from "../agent-scope.js";
|
||||
import { resolveImageSanitizationLimits } from "../image-sanitization.js";
|
||||
@@ -74,20 +74,6 @@ async function resolveNodePairApproveScopes(
|
||||
return resolveApproveScopes(match?.commands);
|
||||
}
|
||||
|
||||
function isPairingRequiredMessage(message: string): boolean {
|
||||
const lower = normalizeLowercaseStringOrEmpty(message);
|
||||
return lower.includes("pairing required") || lower.includes("not_paired");
|
||||
}
|
||||
|
||||
function extractPairingRequestId(message: string): string | null {
|
||||
const match = message.match(/\(requestId:\s*([^)]+)\)/i);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const value = (match[1] ?? "").trim();
|
||||
return value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
// Flattened schema: runtime validates per-action requirements.
|
||||
const NodesToolSchema = Type.Object({
|
||||
action: stringEnum(NODES_TOOL_ACTIONS),
|
||||
@@ -307,8 +293,9 @@ export function createNodesTool(options?: {
|
||||
: "default";
|
||||
const agentLabel = agentId ?? "unknown";
|
||||
let message = formatErrorMessage(err);
|
||||
if (action === "invoke" && isPairingRequiredMessage(message)) {
|
||||
const requestId = extractPairingRequestId(message);
|
||||
const pairing = action === "invoke" ? readConnectPairingRequiredMessage(message) : null;
|
||||
if (pairing) {
|
||||
const requestId = pairing.requestId ?? null;
|
||||
const approveHint = requestId
|
||||
? `Approve pairing request ${requestId} and retry.`
|
||||
: "Approve the pending pairing request and retry.";
|
||||
|
||||
Reference in New Issue
Block a user