mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 20:21:13 +00:00
@@ -116,6 +116,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Matrix/migration: keep packaged warning-only crypto migrations from being misclassified as actionable when only helper chunks are present, so startup and doctor stay on the warning-only path instead of creating unnecessary migration snapshots. (#64373) Thanks @gumadeiras.
|
||||
- Matrix/ACP thread bindings: preserve canonical room casing and parent conversation routing during ACP session spawn so mixed-case room ids bind correctly from top-level rooms and existing Matrix threads. (#64343) Thanks @gumadeiras.
|
||||
- Agents/subagents: deduplicate delivered completion announces so retry or re-entry cleanup does not inject duplicate internal-context completion turns into the parent session. (#61525) Thanks @100yenadmin.
|
||||
- Agents/exec: keep sandboxed `tools.exec.host=auto` sessions from honoring per-call `host=node` or `host=gateway` overrides while a sandbox runtime is active, and stop advertising node routing in that state so exec stays on the sandbox host. (#63880)
|
||||
|
||||
## 2026.4.9
|
||||
|
||||
|
||||
@@ -127,7 +127,20 @@ describe("resolveExecTarget", () => {
|
||||
sandboxAvailable: true,
|
||||
}),
|
||||
).toThrow(
|
||||
"exec host not allowed (requested gateway; configured host is auto; set tools.exec.host=gateway or auto to allow this override).",
|
||||
"exec host not allowed (requested gateway; configured host is auto; set tools.exec.host=gateway to allow this override).",
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects per-call host=node override from auto when sandbox is available", () => {
|
||||
expect(() =>
|
||||
resolveExecTarget({
|
||||
configuredTarget: "auto",
|
||||
requestedTarget: "node",
|
||||
elevatedRequested: false,
|
||||
sandboxAvailable: true,
|
||||
}),
|
||||
).toThrow(
|
||||
"exec host not allowed (requested node; configured host is auto; set tools.exec.host=node to allow this override).",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -228,7 +228,10 @@ export function isRequestedExecTargetAllowed(params: {
|
||||
return true;
|
||||
}
|
||||
if (params.configuredTarget === "auto") {
|
||||
if (params.sandboxAvailable && params.requestedTarget === "gateway") {
|
||||
if (
|
||||
params.sandboxAvailable &&
|
||||
(params.requestedTarget === "gateway" || params.requestedTarget === "node")
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -254,9 +257,13 @@ export function resolveExecTarget(params: {
|
||||
) {
|
||||
const allowedConfig = Array.from(
|
||||
new Set(
|
||||
requestedTarget === "gateway" && !params.sandboxAvailable
|
||||
? ["gateway", "auto"]
|
||||
: [renderExecTargetLabel(requestedTarget), "auto"],
|
||||
configuredTarget === "auto" &&
|
||||
params.sandboxAvailable &&
|
||||
(requestedTarget === "gateway" || requestedTarget === "node")
|
||||
? [renderExecTargetLabel(requestedTarget)]
|
||||
: requestedTarget === "gateway" && !params.sandboxAvailable
|
||||
? ["gateway", "auto"]
|
||||
: [renderExecTargetLabel(requestedTarget), "auto"],
|
||||
),
|
||||
).join(" or ");
|
||||
throw new Error(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../config/sessions.js";
|
||||
import * as execApprovals from "../infra/exec-approvals.js";
|
||||
import { resolveExecDefaults } from "./exec-defaults.js";
|
||||
import { canExecRequestNode, resolveExecDefaults } from "./exec-defaults.js";
|
||||
|
||||
describe("resolveExecDefaults", () => {
|
||||
beforeEach(() => {
|
||||
@@ -27,7 +27,7 @@ describe("resolveExecDefaults", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps node routing available when exec host is auto", () => {
|
||||
it("does not advertise node routing when exec host is auto and sandbox is available", () => {
|
||||
expect(
|
||||
resolveExecDefaults({
|
||||
cfg: {
|
||||
@@ -42,6 +42,25 @@ describe("resolveExecDefaults", () => {
|
||||
).toMatchObject({
|
||||
host: "auto",
|
||||
effectiveHost: "sandbox",
|
||||
canRequestNode: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps node routing available when exec host is auto without sandbox", () => {
|
||||
expect(
|
||||
resolveExecDefaults({
|
||||
cfg: {
|
||||
tools: {
|
||||
exec: {
|
||||
host: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
sandboxAvailable: false,
|
||||
}),
|
||||
).toMatchObject({
|
||||
host: "auto",
|
||||
effectiveHost: "gateway",
|
||||
canRequestNode: true,
|
||||
});
|
||||
});
|
||||
@@ -104,4 +123,19 @@ describe("resolveExecDefaults", () => {
|
||||
ask: "off",
|
||||
});
|
||||
});
|
||||
|
||||
it("blocks node advertising in helper calls when sandbox is available", () => {
|
||||
expect(
|
||||
canExecRequestNode({
|
||||
cfg: {
|
||||
tools: {
|
||||
exec: {
|
||||
host: "auto",
|
||||
},
|
||||
},
|
||||
},
|
||||
sandboxAvailable: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -53,16 +53,38 @@ function resolveExecConfigState(params: {
|
||||
};
|
||||
}
|
||||
|
||||
function resolveExecSandboxAvailability(params: {
|
||||
cfg: OpenClawConfig;
|
||||
sessionKey?: string;
|
||||
sandboxAvailable?: boolean;
|
||||
}) {
|
||||
return (
|
||||
params.sandboxAvailable ??
|
||||
(params.sessionKey
|
||||
? resolveSandboxRuntimeStatus({
|
||||
cfg: params.cfg,
|
||||
sessionKey: params.sessionKey,
|
||||
}).sandboxed
|
||||
: false)
|
||||
);
|
||||
}
|
||||
|
||||
export function canExecRequestNode(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
sessionEntry?: SessionEntry;
|
||||
agentId?: string;
|
||||
sessionKey?: string;
|
||||
sandboxAvailable?: boolean;
|
||||
}): boolean {
|
||||
const { host } = resolveExecConfigState(params);
|
||||
const { cfg, host } = resolveExecConfigState(params);
|
||||
return isRequestedExecTargetAllowed({
|
||||
configuredTarget: host,
|
||||
requestedTarget: "node",
|
||||
sandboxAvailable: resolveExecSandboxAvailability({
|
||||
cfg,
|
||||
sessionKey: params.sessionKey,
|
||||
sandboxAvailable: params.sandboxAvailable,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -81,14 +103,11 @@ export function resolveExecDefaults(params: {
|
||||
canRequestNode: boolean;
|
||||
} {
|
||||
const { cfg, host, agentExec, globalExec } = resolveExecConfigState(params);
|
||||
const sandboxAvailable =
|
||||
params.sandboxAvailable ??
|
||||
(params.sessionKey
|
||||
? resolveSandboxRuntimeStatus({
|
||||
cfg,
|
||||
sessionKey: params.sessionKey,
|
||||
}).sandboxed
|
||||
: false);
|
||||
const sandboxAvailable = resolveExecSandboxAvailability({
|
||||
cfg,
|
||||
sessionKey: params.sessionKey,
|
||||
sandboxAvailable: params.sandboxAvailable,
|
||||
});
|
||||
const resolved = resolveExecTarget({
|
||||
configuredTarget: host,
|
||||
elevatedRequested: false,
|
||||
@@ -115,6 +134,7 @@ export function resolveExecDefaults(params: {
|
||||
canRequestNode: isRequestedExecTargetAllowed({
|
||||
configuredTarget: host,
|
||||
requestedTarget: "node",
|
||||
sandboxAvailable,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user