mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
perf(agents): isolate agent scope config helpers
This commit is contained in:
164
src/agents/agent-scope-config.ts
Normal file
164
src/agents/agent-scope-config.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import path from "node:path";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { 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"];
|
||||
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 {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return s.replace(/\0/g, "");
|
||||
}
|
||||
|
||||
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,
|
||||
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 resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) {
|
||||
const id = normalizeAgentId(agentId);
|
||||
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
|
||||
if (configured) {
|
||||
return stripNullBytes(resolveUserPath(configured));
|
||||
}
|
||||
const defaultAgentId = resolveDefaultAgentId(cfg);
|
||||
const fallback = cfg.agents?.defaults?.workspace?.trim();
|
||||
if (id === defaultAgentId) {
|
||||
if (fallback) {
|
||||
return stripNullBytes(resolveUserPath(fallback));
|
||||
}
|
||||
return stripNullBytes(resolveDefaultAgentWorkspaceDir(process.env));
|
||||
}
|
||||
if (fallback) {
|
||||
return stripNullBytes(path.join(resolveUserPath(fallback), id));
|
||||
}
|
||||
const stateDir = resolveStateDir(process.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");
|
||||
}
|
||||
@@ -1,12 +1,9 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { resolveAgentModelFallbackValues } from "../config/model-input.js";
|
||||
import { resolveStateDir } from "../config/paths.js";
|
||||
import type { 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,
|
||||
parseAgentSessionKey,
|
||||
resolveAgentIdFromSessionKey,
|
||||
@@ -15,19 +12,28 @@ import {
|
||||
lowercasePreservingWhitespace,
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
readStringValue,
|
||||
resolvePrimaryStringValue,
|
||||
} from "../shared/string-coerce.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import {
|
||||
listAgentEntries,
|
||||
listAgentIds,
|
||||
resolveAgentConfig,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
type ResolvedAgentConfig,
|
||||
} from "./agent-scope-config.js";
|
||||
import { resolveEffectiveAgentSkillFilter } from "./skills/agent-filter.js";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "./workspace.js";
|
||||
|
||||
let log: ReturnType<typeof createSubsystemLogger> | null = null;
|
||||
|
||||
function getLog(): ReturnType<typeof createSubsystemLogger> {
|
||||
log ??= createSubsystemLogger("agent-scope");
|
||||
return log;
|
||||
}
|
||||
export {
|
||||
listAgentEntries,
|
||||
listAgentIds,
|
||||
resolveAgentConfig,
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
type ResolvedAgentConfig,
|
||||
} from "./agent-scope-config.js";
|
||||
|
||||
/** Strip null bytes from paths to prevent ENOTDIR errors. */
|
||||
function stripNullBytes(s: string): string {
|
||||
@@ -37,72 +43,6 @@ function stripNullBytes(s: string): string {
|
||||
|
||||
export { resolveAgentIdFromSessionKey };
|
||||
|
||||
type AgentEntry = NonNullable<NonNullable<OpenClawConfig["agents"]>["list"]>[number];
|
||||
|
||||
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"];
|
||||
heartbeat?: AgentEntry["heartbeat"];
|
||||
identity?: AgentEntry["identity"];
|
||||
groupChat?: AgentEntry["groupChat"];
|
||||
subagents?: AgentEntry["subagents"];
|
||||
embeddedPi?: AgentEntry["embeddedPi"];
|
||||
sandbox?: AgentEntry["sandbox"];
|
||||
tools?: AgentEntry["tools"];
|
||||
};
|
||||
|
||||
let defaultAgentWarned = false;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
export function resolveSessionAgentIds(params: {
|
||||
sessionKey?: string;
|
||||
config?: OpenClawConfig;
|
||||
@@ -129,48 +69,6 @@ export function resolveSessionAgentId(params: {
|
||||
return resolveSessionAgentIds(params).sessionAgentId;
|
||||
}
|
||||
|
||||
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,
|
||||
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 resolveAgentExecutionContract(
|
||||
cfg: OpenClawConfig | undefined,
|
||||
agentId?: string | null,
|
||||
@@ -276,29 +174,6 @@ export function resolveEffectiveModelFallbacks(params: {
|
||||
return agentFallbacksOverride ?? defaultFallbacks;
|
||||
}
|
||||
|
||||
export function resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) {
|
||||
const id = normalizeAgentId(agentId);
|
||||
const configured = resolveAgentConfig(cfg, id)?.workspace?.trim();
|
||||
if (configured) {
|
||||
return stripNullBytes(resolveUserPath(configured));
|
||||
}
|
||||
const defaultAgentId = resolveDefaultAgentId(cfg);
|
||||
const fallback = cfg.agents?.defaults?.workspace?.trim();
|
||||
if (id === defaultAgentId) {
|
||||
if (fallback) {
|
||||
return stripNullBytes(resolveUserPath(fallback));
|
||||
}
|
||||
return stripNullBytes(resolveDefaultAgentWorkspaceDir(process.env));
|
||||
}
|
||||
// Non-default agents: use the configured default workspace as a base so that
|
||||
// agents.defaults.workspace is respected for all agents, not just the default.
|
||||
if (fallback) {
|
||||
return stripNullBytes(path.join(resolveUserPath(fallback), id));
|
||||
}
|
||||
const stateDir = resolveStateDir(process.env);
|
||||
return stripNullBytes(path.join(stateDir, `workspace-${id}`));
|
||||
}
|
||||
|
||||
function normalizePathForComparison(input: string): string {
|
||||
const resolved = path.resolve(stripNullBytes(resolveUserPath(input)));
|
||||
let normalized = resolved;
|
||||
@@ -354,17 +229,3 @@ export function resolveAgentIdByWorkspacePath(
|
||||
): string | undefined {
|
||||
return resolveAgentIdsByWorkspacePath(cfg, workspacePath)[0];
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ export {
|
||||
resolveAgentDir,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
resolveAgentSkillsFilter,
|
||||
} from "../../agents/agent-scope.js";
|
||||
type ResolvedAgentConfig,
|
||||
} from "../../agents/agent-scope-config.js";
|
||||
export { resolveAgentSkillsFilter } from "../../agents/agent-scope.js";
|
||||
export { resolveCronStyleNow } from "../../agents/current-time.js";
|
||||
export { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
|
||||
export { isCliProvider } from "../../agents/model-selection-cli.js";
|
||||
|
||||
Reference in New Issue
Block a user