* fix: guard resolveUserPath against undefined input When subagent spawner omits workspaceDir, resolveUserPath receives undefined and crashes on .trim(). Add a falsy guard that falls back to process.cwd(), matching the behavior callers already expect. Closes #10089 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: harden runner workspace fallback (#10176) (thanks @Yida-Dev) * fix: harden workspace fallback scoping (#10176) (thanks @Yida-Dev) * refactor: centralize workspace fallback classification and redaction (#10176) (thanks @Yida-Dev) * test: remove explicit any from utils mock (#10176) (thanks @Yida-Dev) * security: reject malformed agent session keys for workspace resolution (#10176) (thanks @Yida-Dev) --------- Co-authored-by: Yida-Dev <reyifeijun@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Gustavo Madeira Santana <gumadeiras@gmail.com>
107 lines
3.0 KiB
TypeScript
107 lines
3.0 KiB
TypeScript
import type { OpenClawConfig } from "../config/config.js";
|
|
import { redactIdentifier } from "../logging/redact-identifier.js";
|
|
import {
|
|
classifySessionKeyShape,
|
|
DEFAULT_AGENT_ID,
|
|
normalizeAgentId,
|
|
parseAgentSessionKey,
|
|
} from "../routing/session-key.js";
|
|
import { resolveUserPath } from "../utils.js";
|
|
import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "./agent-scope.js";
|
|
|
|
export type WorkspaceFallbackReason = "missing" | "blank" | "invalid_type";
|
|
type AgentIdSource = "explicit" | "session_key" | "default";
|
|
|
|
export type ResolveRunWorkspaceResult = {
|
|
workspaceDir: string;
|
|
usedFallback: boolean;
|
|
fallbackReason?: WorkspaceFallbackReason;
|
|
agentId: string;
|
|
agentIdSource: AgentIdSource;
|
|
};
|
|
|
|
function resolveRunAgentId(params: {
|
|
sessionKey?: string;
|
|
agentId?: string;
|
|
config?: OpenClawConfig;
|
|
}): {
|
|
agentId: string;
|
|
agentIdSource: AgentIdSource;
|
|
} {
|
|
const rawSessionKey = params.sessionKey?.trim() ?? "";
|
|
const shape = classifySessionKeyShape(rawSessionKey);
|
|
if (shape === "malformed_agent") {
|
|
throw new Error("Malformed agent session key; refusing workspace resolution.");
|
|
}
|
|
|
|
const explicit =
|
|
typeof params.agentId === "string" && params.agentId.trim()
|
|
? normalizeAgentId(params.agentId)
|
|
: undefined;
|
|
if (explicit) {
|
|
return { agentId: explicit, agentIdSource: "explicit" };
|
|
}
|
|
|
|
const defaultAgentId = resolveDefaultAgentId(params.config ?? {});
|
|
if (shape === "missing" || shape === "legacy_or_alias") {
|
|
return {
|
|
agentId: defaultAgentId || DEFAULT_AGENT_ID,
|
|
agentIdSource: "default",
|
|
};
|
|
}
|
|
|
|
const parsed = parseAgentSessionKey(rawSessionKey);
|
|
if (parsed?.agentId) {
|
|
return {
|
|
agentId: normalizeAgentId(parsed.agentId),
|
|
agentIdSource: "session_key",
|
|
};
|
|
}
|
|
|
|
// Defensive fallback, should be unreachable for non-malformed shapes.
|
|
return {
|
|
agentId: defaultAgentId || DEFAULT_AGENT_ID,
|
|
agentIdSource: "default",
|
|
};
|
|
}
|
|
|
|
export function redactRunIdentifier(value: string | undefined): string {
|
|
return redactIdentifier(value, { len: 12 });
|
|
}
|
|
|
|
export function resolveRunWorkspaceDir(params: {
|
|
workspaceDir: unknown;
|
|
sessionKey?: string;
|
|
agentId?: string;
|
|
config?: OpenClawConfig;
|
|
}): ResolveRunWorkspaceResult {
|
|
const requested = params.workspaceDir;
|
|
const { agentId, agentIdSource } = resolveRunAgentId({
|
|
sessionKey: params.sessionKey,
|
|
agentId: params.agentId,
|
|
config: params.config,
|
|
});
|
|
if (typeof requested === "string") {
|
|
const trimmed = requested.trim();
|
|
if (trimmed) {
|
|
return {
|
|
workspaceDir: resolveUserPath(trimmed),
|
|
usedFallback: false,
|
|
agentId,
|
|
agentIdSource,
|
|
};
|
|
}
|
|
}
|
|
|
|
const fallbackReason: WorkspaceFallbackReason =
|
|
requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type";
|
|
const fallbackWorkspace = resolveAgentWorkspaceDir(params.config ?? {}, agentId);
|
|
return {
|
|
workspaceDir: resolveUserPath(fallbackWorkspace),
|
|
usedFallback: true,
|
|
fallbackReason,
|
|
agentId,
|
|
agentIdSource,
|
|
};
|
|
}
|