openclaw/src/agents/workspace-run.ts
Yida-Dev 4216449405
fix: guard resolveUserPath against undefined input (#10176)
* 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>
2026-02-06 13:16:58 -05:00

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,
};
}