diff --git a/extensions/policy/src/cli.ts b/extensions/policy/src/cli.ts index 494cf3bd6cab..aa41f140787d 100644 --- a/extensions/policy/src/cli.ts +++ b/extensions/policy/src/cli.ts @@ -198,8 +198,12 @@ async function buildPolicyCheckReport( exitCode: visibleFindings.length === 0 ? 0 : 1, }; } - const cfg = snapshot.valid ? policyCommandConfig(snapshot.config) : {}; - const cwd = options.cwd ?? resolveAgentWorkspaceDir(cfg, resolveDefaultAgentId(cfg)); + const agentId = resolvePolicyCommandAgentId( + snapshot.config, + snapshot.sourceConfigBeforeMigrations, + ); + const cfg = policyCommandConfig(snapshot.config); + const cwd = options.cwd ?? resolveAgentWorkspaceDir(cfg, agentId); const ctx: HealthCheckContext = { mode: "lint", runtime: { @@ -244,27 +248,34 @@ async function buildPolicyCheckReport( }; } +function resolvePolicyCommandAgentId( + cfg: HealthCheckContext["cfg"], + sourceConfigBeforeMigrations?: HealthCheckContext["cfg"], +): string { + return resolveDefaultAgentId(sourceConfigBeforeMigrations ?? cfg); +} + function policyCommandConfig(cfg: HealthCheckContext["cfg"]): HealthCheckContext["cfg"] { - return { - ...cfg, - plugins: { - ...cfg.plugins, - entries: { - ...cfg.plugins?.entries, - policy: { - ...cfg.plugins?.entries?.["policy"], + // Legacy agent ownership is retained by config-object identity during migration. + // Preserve the root while enabling the policy plugin for this command snapshot. + cfg.plugins = { + ...cfg.plugins, + entries: { + ...cfg.plugins?.entries, + policy: { + ...cfg.plugins?.entries?.["policy"], + enabled: true, + config: { enabled: true, - config: { - enabled: true, - ...(typeof cfg.plugins?.entries?.["policy"]?.config === "object" && - cfg.plugins.entries["policy"].config !== null - ? cfg.plugins.entries["policy"].config - : {}), - }, + ...(typeof cfg.plugins?.entries?.["policy"]?.config === "object" && + cfg.plugins.entries["policy"].config !== null + ? cfg.plugins.entries["policy"].config + : {}), }, }, }, }; + return cfg; } async function policyCompareCandidatePath(options: PolicyCompareOptions): Promise { @@ -287,7 +298,10 @@ async function policyCompareCandidatePath(options: PolicyCompareOptions): Promis } const cwd = options.cwd ?? - resolveAgentWorkspaceDir(snapshot.config, resolveDefaultAgentId(snapshot.config)); + resolveAgentWorkspaceDir( + snapshot.config, + resolvePolicyCommandAgentId(snapshot.config, snapshot.sourceConfigBeforeMigrations), + ); return resolve(cwd, policyPath); } diff --git a/src/agents/session-agent-binding.test.ts b/src/agents/session-agent-binding.test.ts index 3ad4b847c42e..9c4829815279 100644 --- a/src/agents/session-agent-binding.test.ts +++ b/src/agents/session-agent-binding.test.ts @@ -15,4 +15,12 @@ describe("resolveBoundAgentIdForSession", () => { expect(resolveBoundAgentIdForSession({ config, sessionKey: "global" })).toBe("ops"); }); + + it("does not grant default-agent authority to an arbitrary unscoped key", () => { + const config = { + agents: { entries: { main: {}, research: {} } }, + } satisfies OpenClawConfig; + + expect(resolveBoundAgentIdForSession({ config, sessionKey: "legacy-session" })).toBeUndefined(); + }); }); diff --git a/src/agents/session-agent-binding.ts b/src/agents/session-agent-binding.ts index fc45e974da64..9172dff15e08 100644 --- a/src/agents/session-agent-binding.ts +++ b/src/agents/session-agent-binding.ts @@ -3,8 +3,13 @@ * * Derives the trusted active agent from explicit agent ids, agent session keys, or configured main-session aliases. */ -import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce"; +import { + normalizeLowercaseStringOrEmpty, + normalizeOptionalString, +} from "@openclaw/normalization-core/string-coerce"; +import { resolvePersistedSessionStoreOwnerForKey } from "../config/sessions/session-store-owner.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { normalizeMainKey, parseAgentSessionKey } from "../routing/session-key.js"; import { resolveSessionAgentId } from "./agent-scope.js"; /** @@ -15,12 +20,24 @@ export function resolveBoundAgentIdForSession(params: { sessionKey?: string; agentId?: string; }): string | undefined { - if (!normalizeOptionalString(params.agentId) && !normalizeOptionalString(params.sessionKey)) { + const config = params.config ?? {}; + const agentId = normalizeOptionalString(params.agentId); + const sessionKey = normalizeOptionalString(params.sessionKey); + if (!agentId && !sessionKey) { return undefined; } - return resolveSessionAgentId({ - config: params.config, - sessionKey: params.sessionKey, - agentId: params.agentId, - }); + if (agentId) { + return resolveSessionAgentId({ config, sessionKey, agentId }); + } + + const parsed = parseAgentSessionKey(sessionKey); + const persistedOwner = resolvePersistedSessionStoreOwnerForKey(config, sessionKey); + const loweredSessionKey = normalizeLowercaseStringOrEmpty(sessionKey); + const mainKey = normalizeMainKey(config.session?.mainKey); + const hasTrustedBinding = + Boolean(parsed?.agentId) || + persistedOwner.kind !== "none" || + loweredSessionKey === "main" || + loweredSessionKey === mainKey; + return hasTrustedBinding ? resolveSessionAgentId({ config, sessionKey }) : undefined; }