mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(agents): canonicalize node binding selectors (#66985)
This commit is contained in:
@@ -223,9 +223,35 @@ vi.mock("./tools/gateway.js", () => ({
|
||||
callGatewayTool: callGatewayToolMock,
|
||||
}));
|
||||
|
||||
const resolveNodeIdFromListMock = vi.hoisted(() =>
|
||||
vi.fn((nodes: Array<{ nodeId: string; displayName?: string }>, query?: string) => {
|
||||
if (!query) {
|
||||
if (nodes.length === 1) {
|
||||
return nodes[0].nodeId;
|
||||
}
|
||||
throw new Error("node required");
|
||||
}
|
||||
const byId = nodes.find((n) => n.nodeId === query);
|
||||
if (byId) {
|
||||
return byId.nodeId;
|
||||
}
|
||||
const byName = nodes.find((n) => n.displayName === query);
|
||||
if (byName) {
|
||||
return byName.nodeId;
|
||||
}
|
||||
if (query.length >= 6) {
|
||||
const byPrefix = nodes.find((n) => n.nodeId.startsWith(query));
|
||||
if (byPrefix) {
|
||||
return byPrefix.nodeId;
|
||||
}
|
||||
}
|
||||
throw new Error(`unknown node: ${query}`);
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("./tools/nodes-utils.js", () => ({
|
||||
listNodes: listNodesMock,
|
||||
resolveNodeIdFromList: vi.fn(() => "node-1"),
|
||||
resolveNodeIdFromList: resolveNodeIdFromListMock,
|
||||
}));
|
||||
|
||||
vi.mock("../logger.js", () => ({
|
||||
@@ -2659,6 +2685,145 @@ describe("executeNodeHostCommand", () => {
|
||||
expectSystemRunInvoke({ invokeTimeoutMs: 35_000, runTimeoutMs: 0 });
|
||||
});
|
||||
|
||||
it("allows exec when requestedNode is display name matching boundNode's device", async () => {
|
||||
listNodesMock.mockResolvedValue([
|
||||
{
|
||||
nodeId: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
displayName: "home-wsl-debian",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
]);
|
||||
const result = await executeNodeHostCommand({
|
||||
command: "echo hello",
|
||||
workdir: "/tmp/work",
|
||||
env: {},
|
||||
security: "full",
|
||||
ask: "off",
|
||||
defaultTimeoutSec: 30,
|
||||
approvalRunningNoticeMs: 0,
|
||||
warnings: [],
|
||||
agentId: "test-agent",
|
||||
sessionKey: "test-session",
|
||||
boundNode: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
requestedNode: "home-wsl-debian",
|
||||
});
|
||||
expect(result.details?.status).toBeDefined();
|
||||
});
|
||||
|
||||
it("allows exec when requestedNode is partial ID matching boundNode's device", async () => {
|
||||
listNodesMock.mockResolvedValue([
|
||||
{
|
||||
nodeId: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
displayName: "home-wsl-debian",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
]);
|
||||
const result = await executeNodeHostCommand({
|
||||
command: "echo hello",
|
||||
workdir: "/tmp/work",
|
||||
env: {},
|
||||
security: "full",
|
||||
ask: "off",
|
||||
defaultTimeoutSec: 30,
|
||||
approvalRunningNoticeMs: 0,
|
||||
warnings: [],
|
||||
agentId: "test-agent",
|
||||
sessionKey: "test-session",
|
||||
boundNode: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
requestedNode: "f2396b588d391d",
|
||||
});
|
||||
expect(result.details?.status).toBeDefined();
|
||||
});
|
||||
|
||||
it("rejects exec when requestedNode resolves to a different node than boundNode", async () => {
|
||||
listNodesMock.mockResolvedValue([
|
||||
{
|
||||
nodeId: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
displayName: "home-wsl-debian",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
{
|
||||
nodeId: "aaaa1111bbbb2222cccc3333dddd4444eeee5555ffff6666aaa7777bbb88889999",
|
||||
displayName: "other-node",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
]);
|
||||
await expect(
|
||||
executeNodeHostCommand({
|
||||
command: "echo hello",
|
||||
workdir: "/tmp/work",
|
||||
env: {},
|
||||
security: "full",
|
||||
ask: "off",
|
||||
defaultTimeoutSec: 30,
|
||||
approvalRunningNoticeMs: 0,
|
||||
warnings: [],
|
||||
agentId: "test-agent",
|
||||
sessionKey: "test-session",
|
||||
boundNode: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
requestedNode: "other-node",
|
||||
}),
|
||||
).rejects.toThrow("exec node not allowed (bound to f2396b588d391d30");
|
||||
});
|
||||
|
||||
it("preserves original error when requestedNode matches no known node", async () => {
|
||||
listNodesMock.mockResolvedValue([
|
||||
{
|
||||
nodeId: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
displayName: "home-wsl-debian",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
]);
|
||||
await expect(
|
||||
executeNodeHostCommand({
|
||||
command: "echo hello",
|
||||
workdir: "/tmp/work",
|
||||
env: {},
|
||||
security: "full",
|
||||
ask: "off",
|
||||
defaultTimeoutSec: 30,
|
||||
approvalRunningNoticeMs: 0,
|
||||
warnings: [],
|
||||
agentId: "test-agent",
|
||||
sessionKey: "test-session",
|
||||
requestedNode: "nonexistent-node",
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
"requested node not found: nonexistent-node (unknown node: nonexistent-node)",
|
||||
);
|
||||
});
|
||||
|
||||
it("allows exec when boundNode is a display name matching the same device as requestedNode", async () => {
|
||||
listNodesMock.mockResolvedValue([
|
||||
{
|
||||
nodeId: "f2396b588d391d30a79d300e196a17cf197f34969b5e2485d2734c953567f44e",
|
||||
displayName: "home-wsl-debian",
|
||||
commands: ["system.run"],
|
||||
platform: process.platform,
|
||||
},
|
||||
]);
|
||||
const result = await executeNodeHostCommand({
|
||||
command: "echo hello",
|
||||
workdir: "/tmp/work",
|
||||
env: {},
|
||||
security: "full",
|
||||
ask: "off",
|
||||
defaultTimeoutSec: 30,
|
||||
approvalRunningNoticeMs: 0,
|
||||
warnings: [],
|
||||
agentId: "test-agent",
|
||||
sessionKey: "test-session",
|
||||
boundNode: "home-wsl-debian",
|
||||
requestedNode: "home-wsl-debian",
|
||||
});
|
||||
expect(result.details?.status).toBeDefined();
|
||||
});
|
||||
|
||||
it("auto-reviews strict inline-eval commands with full/off host policy when node policy is available", async () => {
|
||||
const inlinePlan = {
|
||||
argv: ["python3", "-c", "print(1)"],
|
||||
|
||||
Reference in New Issue
Block a user