diff --git a/src/agents/agent-scope-config.ts b/src/agents/agent-scope-config.ts index fe7850675ca..f10c405f4a6 100644 --- a/src/agents/agent-scope-config.ts +++ b/src/agents/agent-scope-config.ts @@ -146,24 +146,28 @@ export function resolveAgentContextLimits( return resolveAgentConfig(cfg, agentId)?.contextLimits ?? defaults; } -export function resolveAgentWorkspaceDir(cfg: OpenClawConfig, agentId: string) { +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)); + 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)); + return stripNullBytes(resolveUserPath(fallback, env)); } - return stripNullBytes(resolveDefaultAgentWorkspaceDir(process.env)); + return stripNullBytes(resolveDefaultAgentWorkspaceDir(env)); } if (fallback) { - return stripNullBytes(path.join(resolveUserPath(fallback), id)); + return stripNullBytes(path.join(resolveUserPath(fallback, env), id)); } - const stateDir = resolveStateDir(process.env); + const stateDir = resolveStateDir(env); return stripNullBytes(path.join(stateDir, `workspace-${id}`)); } diff --git a/src/agents/workspace-run.test.ts b/src/agents/workspace-run.test.ts index e29ccf3bf5c..f0c12476513 100644 --- a/src/agents/workspace-run.test.ts +++ b/src/agents/workspace-run.test.ts @@ -83,17 +83,23 @@ describe("resolveRunWorkspaceDir", () => { }); it("uses explicit agent id for per-agent fallback when config is unavailable", () => { + const env = { + ...process.env, + HOME: "/home/runner", + OPENCLAW_HOME: undefined, + OPENCLAW_STATE_DIR: "/tmp/openclaw-state", + } satisfies NodeJS.ProcessEnv; const result = resolveRunWorkspaceDir({ workspaceDir: undefined, sessionKey: "definitely-not-a-valid-session-key", agentId: "research", config: undefined, + env, }); expect(result.agentId).toBe("research"); expect(result.agentIdSource).toBe("explicit"); - expect(path.isAbsolute(result.workspaceDir)).toBe(true); - expect(path.basename(result.workspaceDir)).toBe("workspace-research"); + expect(result.workspaceDir).toBe(path.resolve("/tmp/openclaw-state", "workspace-research")); }); it("throws for malformed agent session keys even when config has a default agent", () => { diff --git a/src/agents/workspace-run.ts b/src/agents/workspace-run.ts index c3648c41f0a..64dfb0dc152 100644 --- a/src/agents/workspace-run.ts +++ b/src/agents/workspace-run.ts @@ -76,7 +76,9 @@ export function resolveRunWorkspaceDir(params: { sessionKey?: string; agentId?: string; config?: OpenClawConfig; + env?: NodeJS.ProcessEnv; }): ResolveRunWorkspaceResult { + const env = params.env ?? process.env; const requested = params.workspaceDir; const { agentId, agentIdSource } = resolveRunAgentId({ sessionKey: params.sessionKey, @@ -91,7 +93,7 @@ export function resolveRunWorkspaceDir(params: { logWarn("Control/format characters stripped from workspaceDir (OC-19 hardening)."); } return { - workspaceDir: resolveUserPath(sanitized), + workspaceDir: resolveUserPath(sanitized, env), usedFallback: false, agentId, agentIdSource, @@ -101,13 +103,13 @@ export function resolveRunWorkspaceDir(params: { const fallbackReason: WorkspaceFallbackReason = requested == null ? "missing" : typeof requested === "string" ? "blank" : "invalid_type"; - const fallbackWorkspace = resolveAgentWorkspaceDir(params.config ?? {}, agentId); + const fallbackWorkspace = resolveAgentWorkspaceDir(params.config ?? {}, agentId, env); const sanitizedFallback = sanitizeForPromptLiteral(fallbackWorkspace); if (sanitizedFallback !== fallbackWorkspace) { logWarn("Control/format characters stripped from fallback workspaceDir (OC-19 hardening)."); } return { - workspaceDir: resolveUserPath(sanitizedFallback), + workspaceDir: resolveUserPath(sanitizedFallback, env), usedFallback: true, fallbackReason, agentId, diff --git a/src/commands/doctor/shared/stale-plugin-config.test.ts b/src/commands/doctor/shared/stale-plugin-config.test.ts index 181a4f3008e..5ebc4d63490 100644 --- a/src/commands/doctor/shared/stale-plugin-config.test.ts +++ b/src/commands/doctor/shared/stale-plugin-config.test.ts @@ -154,21 +154,11 @@ describe("doctor stale plugin config helpers", () => { } as OpenClawConfig; expect(scanStalePluginConfig(cfg)).toEqual([ - { - pluginId: "openai-codex", - pathLabel: "plugins.allow", - surface: "allow", - }, { pluginId: "acpx", pathLabel: "plugins.allow", surface: "allow", }, - { - pluginId: "openai-codex", - pathLabel: "plugins.entries.openai-codex", - surface: "entries", - }, { pluginId: "acpx", pathLabel: "plugins.entries.acpx", @@ -177,7 +167,9 @@ describe("doctor stale plugin config helpers", () => { ]); const result = maybeRepairStalePluginConfig(cfg); - expect(result.config.plugins?.allow).toEqual([]); - expect(result.config.plugins?.entries).toEqual({}); + expect(result.config.plugins?.allow).toEqual(["openai-codex"]); + expect(result.config.plugins?.entries).toEqual({ + "openai-codex": { enabled: true }, + }); }); }); diff --git a/src/plugins/config-state.ts b/src/plugins/config-state.ts index c74af6c1e51..3bf6905967a 100644 --- a/src/plugins/config-state.ts +++ b/src/plugins/config-state.ts @@ -34,6 +34,13 @@ export type NormalizedPluginsConfig = SharedNormalizedPluginsConfig; let bundledPluginAliasLookupCache: ReadonlyMap | undefined; +const BUILT_IN_PLUGIN_ALIAS_FALLBACKS: ReadonlyArray = [ + ["openai-codex", "openai"], + ["google-gemini-cli", "google"], + ["minimax-portal", "minimax"], + ["minimax-portal-auth", "minimax"], +] as const; + function getBundledPluginAliasLookup(): ReadonlyMap { if (bundledPluginAliasLookupCache) { return bundledPluginAliasLookupCache; @@ -61,6 +68,9 @@ function getBundledPluginAliasLookup(): ReadonlyMap { } } } + for (const [alias, pluginId] of BUILT_IN_PLUGIN_ALIAS_FALLBACKS) { + lookup.set(alias, pluginId); + } bundledPluginAliasLookupCache = lookup; return lookup; }