import path from "node:path" ;
import { resolveStateDir } from "../config/paths.js" ;
import type {
AgentContextLimitsConfig,
AgentDefaultsConfig,
} from "../config/types.agent-defaults.js" ;
import type { OpenClawConfig } from "../config/types.js" ;
import { createSubsystemLogger } from "../logging/subsystem.js" ;
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js" ;
import { readStringValue } from "../shared/string-coerce.js" ;
import { resolveUserPath } from "../utils.js" ;
import { resolveDefaultAgentWorkspaceDir } from "./workspace.js" ;
type AgentEntry = NonNullable<NonNullable<OpenClawConfig["agents" ]>["list" ]>[number];
export type ResolvedAgentConfig = {
name?: string;
workspace?: string;
agentDir?: string;
systemPromptOverride?: AgentEntry["systemPromptOverride" ];
model?: AgentEntry["model" ];
thinkingDefault?: AgentEntry["thinkingDefault" ];
verboseDefault?: AgentDefaultsConfig["verboseDefault" ];
reasoningDefault?: AgentEntry["reasoningDefault" ];
fastModeDefault?: AgentEntry["fastModeDefault" ];
skills?: AgentEntry["skills" ];
memorySearch?: AgentEntry["memorySearch" ];
humanDelay?: AgentEntry["humanDelay" ];
contextLimits?: AgentContextLimitsConfig;
heartbeat?: AgentEntry["heartbeat" ];
identity?: AgentEntry["identity" ];
groupChat?: AgentEntry["groupChat" ];
subagents?: AgentEntry["subagents" ];
embeddedPi?: AgentEntry["embeddedPi" ];
sandbox?: AgentEntry["sandbox" ];
tools?: AgentEntry["tools" ];
};
let log: ReturnType<typeof createSubsystemLogger> | null = null ;
let defaultAgentWarned = false ;
function getLog(): ReturnType<typeof createSubsystemLogger> {
log ??= createSubsystemLogger("agent-scope" );
return log;
}
/** Strip null bytes from paths to prevent ENOTDIR errors. */
function stripNullBytes(s: string): string {
return s.replaceAll("\0" , "" );
}
export function listAgentEntries(cfg: OpenClawConfig): AgentEntry[] {
const list = cfg.agents?.list;
if (!Array.isArray(list)) {
return [];
}
return list.filter((entry): entry is AgentEntry => entry !== null && typeof entry === "object" );
}
export function listAgentIds(cfg: OpenClawConfig): string[] {
const agents = listAgentEntries(cfg);
if (agents.length === 0 ) {
return [DEFAULT_AGENT_ID];
}
const seen = new Set<string>();
const ids: string[] = [];
for (const entry of agents) {
const id = normalizeAgentId(entry?.id);
if (seen.has(id)) {
continue ;
}
seen.add(id);
ids.push(id);
}
return ids.length > 0 ? ids : [DEFAULT_AGENT_ID];
}
export function resolveDefaultAgentId(cfg: OpenClawConfig): string {
const agents = listAgentEntries(cfg);
if (agents.length === 0 ) {
return DEFAULT_AGENT_ID;
}
const defaults = agents.filter((agent) => agent?.default );
if (defaults.length > 1 && !defaultAgentWarned) {
defaultAgentWarned = true ;
getLog().warn("Multiple agents marked default=true; using the first entry as default." );
}
const chosen = (defaults[0 ] ?? agents[0 ])?.id?.trim();
return normalizeAgentId(chosen || DEFAULT_AGENT_ID);
}
function resolveAgentEntry(cfg: OpenClawConfig, agentId: string): AgentEntry | undefined {
const id = normalizeAgentId(agentId);
return listAgentEntries(cfg).find((entry) => normalizeAgentId(entry.id) === id);
}
export function resolveAgentConfig(
cfg: OpenClawConfig,
agentId: string,
): ResolvedAgentConfig | undefined {
const id = normalizeAgentId(agentId);
const entry = resolveAgentEntry(cfg, id);
if (!entry) {
return undefined;
}
const agentDefaults = cfg.agents?.defaults;
return {
name: readStringValue(entry.name),
workspace: readStringValue(entry.workspace),
agentDir: readStringValue(entry.agentDir),
systemPromptOverride: readStringValue(entry.systemPromptOverride),
model:
typeof entry.model === "string" || (entry.model && typeof entry.model === "object" )
? entry.model
: undefined,
thinkingDefault: entry.thinkingDefault,
verboseDefault: entry.verboseDefault ?? agentDefaults?.verboseDefault,
reasoningDefault: entry.reasoningDefault,
fastModeDefault: entry.fastModeDefault,
skills: Array.isArray(entry.skills) ? entry.skills : undefined,
memorySearch: entry.memorySearch,
humanDelay: entry.humanDelay,
contextLimits:
typeof entry.contextLimits === "object" && entry.contextLimits
? { ...agentDefaults?.contextLimits, ...entry.contextLimits }
: agentDefaults?.contextLimits,
heartbeat: entry.heartbeat,
identity: entry.identity,
groupChat: entry.groupChat,
subagents: typeof entry.subagents === "object" && entry.subagents ? entry.subagents : undefined,
embeddedPi:
typeof entry.embeddedPi === "object" && entry.embeddedPi ? entry.embeddedPi : undefined,
sandbox: entry.sandbox,
tools: entry.tools,
};
}
export function resolveAgentContextLimits(
cfg: OpenClawConfig | undefined,
agentId?: string | null ,
): AgentContextLimitsConfig | undefined {
const defaults = cfg?.agents?.defaults?.contextLimits;
if (!cfg || !agentId) {
return defaults;
}
return resolveAgentConfig(cfg, agentId)?.contextLimits ?? defaults;
}
export function resolveAgentWorkspaceDir(
cfg: OpenClawConfig,
agentId: string,
env: NodeJS.ProcessEnv = process.env,
) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
if (configured) {
return stripNullBytes(resolveUserPath(configured, env));
}
const defaultAgentId = resolveDefaultAgentId(cfg);
const fallback = cfg.agents?.defaults?.workspace?.trim();
if (id === defaultAgentId) {
if (fallback) {
return stripNullBytes(resolveUserPath(fallback, env));
}
return stripNullBytes(resolveDefaultAgentWorkspaceDir(env));
}
if (fallback) {
return stripNullBytes(path.join(resolveUserPath(fallback, env), id));
}
const stateDir = resolveStateDir(env);
return stripNullBytes(path.join(stateDir, `workspace-${id}`));
}
export function resolveAgentDir(
cfg: OpenClawConfig,
agentId: string,
env: NodeJS.ProcessEnv = process.env,
) {
const id = normalizeAgentId(agentId);
const configured = resolveAgentConfig(cfg, id)?.agentDir?.trim();
if (configured) {
return resolveUserPath(configured, env);
}
const root = resolveStateDir(env);
return path.join(root, "agents" , id, "agent" );
}
Messung V0.5 in Prozent C=95 H=98 G=96
¤ Dauer der Verarbeitung: 0.10 Sekunden
(vorverarbeitet am 2026-05-26)
¤
*© Formatika GbR, Deutschland