From 57b7d8bd0f19add26aaac4692796fc60b86d5fd5 Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Sat, 21 Feb 2026 14:05:37 -0800 Subject: [PATCH] fix: restore workspace pass-through dropped by design merge --- src/agents/skills/types.ts | 2 ++ src/agents/skills/workspace.ts | 21 +++++++++++++++++++-- src/commands/agent-via-gateway.ts | 6 ++++++ src/commands/agent.ts | 8 ++++++-- src/commands/agent/types.ts | 2 ++ src/config/sessions/types.ts | 2 ++ src/gateway/protocol/schema/agent.ts | 1 + src/gateway/server-methods/agent.ts | 2 ++ 8 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/agents/skills/types.ts b/src/agents/skills/types.ts index 8d02ea6c64c..043f6f4be77 100644 --- a/src/agents/skills/types.ts +++ b/src/agents/skills/types.ts @@ -94,4 +94,6 @@ export type SkillSnapshot = { /** Skills with `inject: true` whose full content should be included in the system prompt. */ injectedSkills?: InjectedSkillContent[]; version?: number; + /** Workspace dir this snapshot was built for (used to invalidate on profile switch). */ + workspaceDir?: string; }; diff --git a/src/agents/skills/workspace.ts b/src/agents/skills/workspace.ts index 77ae2ce1a60..6864806a8a5 100644 --- a/src/agents/skills/workspace.ts +++ b/src/agents/skills/workspace.ts @@ -265,12 +265,28 @@ export function buildWorkspaceSkillSnapshot( const remoteNote = opts?.eligibility?.remote?.note?.trim(); const prompt = [remoteNote, formatSkillsForPrompt(resolvedSkills)].filter(Boolean).join("\n"); - // Read full content of injected skills, substituting workspace path placeholders + // Read full content of injected skills, substituting workspace path placeholders. + // We replace both the tilde form and the expanded default path to handle + // cases where the replacement target is a profile-specific workspace dir. + // + // Use regex with a negative lookahead so "~/.openclaw/workspace" doesn't + // match inside "~/.openclaw/workspace-", which would double the + // profile suffix (e.g. workspace-kumareth -> workspace-kumareth-kumareth). + const defaultExpandedWorkspace = resolveUserPath("~/.openclaw/workspace"); + const escapeRegex = (s: string) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const tildePattern = new RegExp(escapeRegex("~/.openclaw/workspace") + "(?![\\w-])", "g"); const injectedSkills: InjectedSkillContent[] = []; for (const entry of injectedEntries) { const rawContent = readSkillContent(entry.skill.filePath); if (rawContent) { - const content = rawContent.replaceAll("~/.openclaw/workspace", workspaceDir); + let content = rawContent.replace(tildePattern, workspaceDir); + if (workspaceDir !== defaultExpandedWorkspace) { + const expandedPattern = new RegExp( + escapeRegex(defaultExpandedWorkspace) + "(?![\\w-])", + "g", + ); + content = content.replace(expandedPattern, workspaceDir); + } injectedSkills.push({ name: entry.skill.name, content }); } } @@ -284,6 +300,7 @@ export function buildWorkspaceSkillSnapshot( resolvedSkills, injectedSkills: injectedSkills.length > 0 ? injectedSkills : undefined, version: opts?.snapshotVersion, + workspaceDir, }; } diff --git a/src/commands/agent-via-gateway.ts b/src/commands/agent-via-gateway.ts index 12d7c0e1272..d7745910c02 100644 --- a/src/commands/agent-via-gateway.ts +++ b/src/commands/agent-via-gateway.ts @@ -140,6 +140,8 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim const channel = normalizeMessageChannel(opts.channel) ?? DEFAULT_CHAT_CHANNEL; const idempotencyKey = opts.runId?.trim() || randomIdempotencyKey(); + const workspaceOverride = process.env.OPENCLAW_WORKSPACE?.trim() || undefined; + const response = await withProgress( { label: "Waiting for agent reply…", @@ -165,6 +167,7 @@ export async function agentViaGatewayCommand(opts: AgentCliOpts, runtime: Runtim lane: opts.lane, extraSystemPrompt: opts.extraSystemPrompt, idempotencyKey, + workspace: workspaceOverride, }, expectFinal: true, timeoutMs: gatewayTimeoutMs, @@ -236,6 +239,8 @@ async function agentViaGatewayStreamJson(opts: AgentCliOpts, _runtime: RuntimeEn const channel = normalizeMessageChannel(opts.channel) ?? DEFAULT_CHAT_CHANNEL; const idempotencyKey = opts.runId?.trim() || randomIdempotencyKey(); + const streamWorkspaceOverride = process.env.OPENCLAW_WORKSPACE?.trim() || undefined; + // Capture the runId from early gateway events so we can abort the // correct run when the process receives SIGTERM/SIGINT. let capturedRunId: string | undefined; @@ -267,6 +272,7 @@ async function agentViaGatewayStreamJson(opts: AgentCliOpts, _runtime: RuntimeEn lane: opts.lane, extraSystemPrompt: opts.extraSystemPrompt, idempotencyKey, + workspace: streamWorkspaceOverride, }, expectFinal: true, timeoutMs: gatewayTimeoutMs, diff --git a/src/commands/agent.ts b/src/commands/agent.ts index d2985c8c303..e892a478375 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -217,7 +217,7 @@ export async function agentCommand( } const agentCfg = cfg.agents?.defaults; const sessionAgentId = agentIdOverride ?? resolveAgentIdFromSessionKey(opts.sessionKey?.trim()); - const workspaceDirRaw = resolveAgentWorkspaceDir(cfg, sessionAgentId); + const workspaceDirRaw = opts.workspace?.trim() || resolveAgentWorkspaceDir(cfg, sessionAgentId); const agentDir = resolveAgentDir(cfg, sessionAgentId); const workspace = await ensureAgentWorkspace({ dir: workspaceDirRaw, @@ -332,7 +332,11 @@ export async function agentCommand( }); } - const needsSkillsSnapshot = isNewSession || !sessionEntry?.skillsSnapshot; + const cachedSnapshot = sessionEntry?.skillsSnapshot; + const needsSkillsSnapshot = + isNewSession || + !cachedSnapshot || + (cachedSnapshot.workspaceDir && cachedSnapshot.workspaceDir !== workspaceDir); const skillsSnapshotVersion = getSkillsSnapshotVersion(workspaceDir); const skillFilter = resolveAgentSkillsFilter(cfg, sessionAgentId); const skillsSnapshot = needsSkillsSnapshot diff --git a/src/commands/agent/types.ts b/src/commands/agent/types.ts index 5e606448f47..3377aa0f310 100644 --- a/src/commands/agent/types.ts +++ b/src/commands/agent/types.ts @@ -76,6 +76,8 @@ export type AgentCommandOpts = { runId?: string; extraSystemPrompt?: string; inputProvenance?: InputProvenance; + /** Workspace directory override (passed via RPC from the web UI for profile switching). */ + workspace?: string; /** Per-call stream param overrides (best-effort). */ streamParams?: AgentStreamParams; }; diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 1d0012a749f..9c9c0b2920f 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -146,6 +146,8 @@ export type SessionSkillSnapshot = { skills: Array<{ name: string; primaryEnv?: string }>; resolvedSkills?: Skill[]; version?: number; + /** Workspace dir this snapshot was built for (used to invalidate on profile switch). */ + workspaceDir?: string; }; export type SessionSystemPromptReport = { diff --git a/src/gateway/protocol/schema/agent.ts b/src/gateway/protocol/schema/agent.ts index b27a5c1b8f6..fc0f16eaca5 100644 --- a/src/gateway/protocol/schema/agent.ts +++ b/src/gateway/protocol/schema/agent.ts @@ -73,6 +73,7 @@ export const AgentParamsSchema = Type.Object( timeout: Type.Optional(Type.Integer({ minimum: 0 })), lane: Type.Optional(Type.String()), extraSystemPrompt: Type.Optional(Type.String()), + workspace: Type.Optional(Type.String()), inputProvenance: Type.Optional( Type.Object( { diff --git a/src/gateway/server-methods/agent.ts b/src/gateway/server-methods/agent.ts index e03fd596611..0664d7cdb4e 100644 --- a/src/gateway/server-methods/agent.ts +++ b/src/gateway/server-methods/agent.ts @@ -191,6 +191,7 @@ export const agentHandlers: GatewayRequestHandlers = { groupSpace?: string; lane?: string; extraSystemPrompt?: string; + workspace?: string; idempotencyKey: string; timeout?: number; label?: string; @@ -558,6 +559,7 @@ export const agentHandlers: GatewayRequestHandlers = { runId, lane: request.lane, extraSystemPrompt: request.extraSystemPrompt, + workspace: typeof request.workspace === "string" ? request.workspace.trim() : undefined, inputProvenance, }, defaultRuntime,