fix(exec): apply per-agent exec defaults for opaque session keys
Co-authored-by: brin-tapcart <brin-tapcart@users.noreply.github.com>
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
### Fixes
|
||||
|
||||
- Security/Voice Call: harden media stream WebSocket handling against pre-auth idle-connection DoS by adding strict pre-start timeouts, pending/per-IP connection limits, and total connection caps for streaming endpoints. This ships in the next npm release. Thanks @jiseoung for reporting.
|
||||
- Agents/Exec: honor explicit agent context when resolving `tools.exec` defaults for runs with opaque/non-agent session keys, so per-agent `host/security/ask` policies are applied consistently. (#11832)
|
||||
- Telegram/Discord extensions: propagate trusted `mediaLocalRoots` through extension outbound `sendMedia` options so extension direct-send media paths honor agent-scoped local-media allowlists. (#20029, #21903, #23227)
|
||||
- Exec/Background: stop applying the default exec timeout to background sessions (`background: true` or explicit `yieldMs`) when no explicit timeout is set, so long-running background jobs are no longer terminated at the default timeout boundary. (#23303)
|
||||
- Plugins/Media sandbox: propagate trusted `mediaLocalRoots` through plugin action dispatch (including Discord/Telegram action adapters) so plugin send paths enforce the same agent-scoped local-media sandbox roots as core outbound sends. (#20258, #22718)
|
||||
|
||||
@@ -74,15 +74,23 @@ export function resolveDefaultAgentId(cfg: OpenClawConfig): string {
|
||||
return normalizeAgentId(chosen || DEFAULT_AGENT_ID);
|
||||
}
|
||||
|
||||
export function resolveSessionAgentIds(params: { sessionKey?: string; config?: OpenClawConfig }): {
|
||||
export function resolveSessionAgentIds(params: {
|
||||
sessionKey?: string;
|
||||
config?: OpenClawConfig;
|
||||
agentId?: string;
|
||||
}): {
|
||||
defaultAgentId: string;
|
||||
sessionAgentId: string;
|
||||
} {
|
||||
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
|
||||
const explicitAgentIdRaw =
|
||||
typeof params.agentId === "string" ? params.agentId.trim().toLowerCase() : "";
|
||||
const explicitAgentId = explicitAgentIdRaw ? normalizeAgentId(explicitAgentIdRaw) : null;
|
||||
const sessionKey = params.sessionKey?.trim();
|
||||
const normalizedSessionKey = sessionKey ? sessionKey.toLowerCase() : undefined;
|
||||
const parsed = normalizedSessionKey ? parseAgentSessionKey(normalizedSessionKey) : null;
|
||||
const sessionAgentId = parsed?.agentId ? normalizeAgentId(parsed.agentId) : defaultAgentId;
|
||||
const sessionAgentId =
|
||||
explicitAgentId ?? (parsed?.agentId ? normalizeAgentId(parsed.agentId) : defaultAgentId);
|
||||
return { defaultAgentId, sessionAgentId };
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ export async function runCliAgent(params: {
|
||||
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const heartbeatPrompt =
|
||||
sessionAgentId === defaultAgentId
|
||||
|
||||
@@ -48,4 +48,21 @@ describe("resolveSessionAgentIds", () => {
|
||||
});
|
||||
expect(sessionAgentId).toBe("main");
|
||||
});
|
||||
|
||||
it("uses explicit agentId when sessionKey is missing", () => {
|
||||
const { sessionAgentId } = resolveSessionAgentIds({
|
||||
agentId: "main",
|
||||
config: cfg,
|
||||
});
|
||||
expect(sessionAgentId).toBe("main");
|
||||
});
|
||||
|
||||
it("prefers explicit agentId over non-agent session keys", () => {
|
||||
const { sessionAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: "telegram:slash:123",
|
||||
agentId: "main",
|
||||
config: cfg,
|
||||
});
|
||||
expect(sessionAgentId).toBe("main");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,11 +19,7 @@ import type {
|
||||
PluginHookBeforeAgentStartResult,
|
||||
PluginHookBeforePromptBuildResult,
|
||||
} from "../../../plugins/types.js";
|
||||
import {
|
||||
isCronSessionKey,
|
||||
isSubagentSessionKey,
|
||||
normalizeAgentId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { isCronSessionKey, isSubagentSessionKey } from "../../../routing/session-key.js";
|
||||
import { resolveSignalReactionLevel } from "../../../signal/reaction-level.js";
|
||||
import { resolveTelegramInlineButtonsScope } from "../../../telegram/inline-buttons.js";
|
||||
import { resolveTelegramReactionLevel } from "../../../telegram/reaction-level.js";
|
||||
@@ -356,11 +352,17 @@ export async function runEmbeddedAttempt(
|
||||
|
||||
const agentDir = params.agentDir ?? resolveOpenClawAgentDir();
|
||||
|
||||
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
// Check if the model supports native image input
|
||||
const modelHasVision = params.model.input?.includes("image") ?? false;
|
||||
const toolsRaw = params.disableTools
|
||||
? []
|
||||
: createOpenClawCodingTools({
|
||||
agentId: sessionAgentId,
|
||||
exec: {
|
||||
...params.execOverrides,
|
||||
elevated: params.bashElevated,
|
||||
@@ -451,10 +453,6 @@ export async function runEmbeddedAttempt(
|
||||
return undefined;
|
||||
})()
|
||||
: undefined;
|
||||
const { defaultAgentId, sessionAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
});
|
||||
const sandboxInfo = buildEmbeddedSandboxInfo(sandbox, params.bashElevated);
|
||||
const reasoningTagHint = isReasoningTagProvider(params.provider);
|
||||
// Resolve channel-specific message actions for system prompt
|
||||
@@ -1009,13 +1007,7 @@ export async function runEmbeddedAttempt(
|
||||
}
|
||||
|
||||
// Hook runner was already obtained earlier before tool creation
|
||||
const hookAgentId =
|
||||
typeof params.agentId === "string" && params.agentId.trim()
|
||||
? normalizeAgentId(params.agentId)
|
||||
: resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.config,
|
||||
}).sessionAgentId;
|
||||
const hookAgentId = sessionAgentId;
|
||||
|
||||
let promptError: unknown = null;
|
||||
let promptErrorSource: "prompt" | "compaction" | null = null;
|
||||
|
||||
@@ -690,4 +690,44 @@ describe("Agent-specific tool filtering", () => {
|
||||
}),
|
||||
).rejects.toThrow("exec host=sandbox is configured");
|
||||
});
|
||||
|
||||
it("applies explicit agentId exec defaults when sessionKey is opaque", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
tools: {
|
||||
exec: {
|
||||
host: "sandbox",
|
||||
security: "full",
|
||||
ask: "off",
|
||||
},
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
tools: {
|
||||
exec: {
|
||||
host: "gateway",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const tools = createOpenClawCodingTools({
|
||||
config: cfg,
|
||||
agentId: "main",
|
||||
sessionKey: "run-opaque-123",
|
||||
workspaceDir: "/tmp/test-main-opaque-session",
|
||||
agentDir: "/tmp/agent-main-opaque-session",
|
||||
});
|
||||
const execTool = tools.find((tool) => tool.name === "exec");
|
||||
expect(execTool).toBeDefined();
|
||||
const result = await execTool!.execute("call-main-opaque-session", {
|
||||
command: "echo done",
|
||||
yieldMs: 1000,
|
||||
});
|
||||
const details = result?.details as { status?: string } | undefined;
|
||||
expect(details?.status).toBe("completed");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ import { getChannelDock } from "../channels/dock.js";
|
||||
import { DEFAULT_SUBAGENT_MAX_SPAWN_DEPTH } from "../config/agent-limits.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveChannelGroupToolsPolicy } from "../config/group-policy.js";
|
||||
import { normalizeAgentId } from "../routing/session-key.js";
|
||||
import { resolveThreadParentSessionKey } from "../sessions/session-key-utils.js";
|
||||
import { normalizeMessageChannel } from "../utils/message-channel.js";
|
||||
import { resolveAgentConfig, resolveAgentIdFromSessionKey } from "./agent-scope.js";
|
||||
@@ -198,10 +199,17 @@ function resolveProviderToolPolicy(params: {
|
||||
export function resolveEffectiveToolPolicy(params: {
|
||||
config?: OpenClawConfig;
|
||||
sessionKey?: string;
|
||||
agentId?: string;
|
||||
modelProvider?: string;
|
||||
modelId?: string;
|
||||
}) {
|
||||
const agentId = params.sessionKey ? resolveAgentIdFromSessionKey(params.sessionKey) : undefined;
|
||||
const explicitAgentId =
|
||||
typeof params.agentId === "string" && params.agentId.trim()
|
||||
? normalizeAgentId(params.agentId)
|
||||
: undefined;
|
||||
const agentId =
|
||||
explicitAgentId ??
|
||||
(params.sessionKey ? resolveAgentIdFromSessionKey(params.sessionKey) : undefined);
|
||||
const agentConfig =
|
||||
params.config && agentId ? resolveAgentConfig(params.config, agentId) : undefined;
|
||||
const agentTools = agentConfig?.tools;
|
||||
|
||||
@@ -169,6 +169,7 @@ export const __testing = {
|
||||
} as const;
|
||||
|
||||
export function createOpenClawCodingTools(options?: {
|
||||
agentId?: string;
|
||||
exec?: ExecToolDefaults & ProcessToolDefaults;
|
||||
messageProvider?: string;
|
||||
agentAccountId?: string;
|
||||
@@ -238,6 +239,7 @@ export function createOpenClawCodingTools(options?: {
|
||||
} = resolveEffectiveToolPolicy({
|
||||
config: options?.config,
|
||||
sessionKey: options?.sessionKey,
|
||||
agentId: options?.agentId,
|
||||
modelProvider: options?.modelProvider,
|
||||
modelId: options?.modelId,
|
||||
});
|
||||
|
||||
@@ -54,6 +54,7 @@ export async function resolveCommandsSystemPromptBundle(
|
||||
try {
|
||||
return createOpenClawCodingTools({
|
||||
config: params.cfg,
|
||||
agentId: params.agentId,
|
||||
workspaceDir,
|
||||
sessionKey: params.sessionKey,
|
||||
messageProvider: params.command.channel,
|
||||
@@ -74,6 +75,7 @@ export async function resolveCommandsSystemPromptBundle(
|
||||
const { sessionAgentId } = resolveSessionAgentIds({
|
||||
sessionKey: params.sessionKey,
|
||||
config: params.cfg,
|
||||
agentId: params.agentId,
|
||||
});
|
||||
const defaultModelRef = resolveDefaultModelForAgent({
|
||||
cfg: params.cfg,
|
||||
|
||||
Reference in New Issue
Block a user