From 9fcc1d9ed5d46990996cfdfe72ecf16ae4e31a40 Mon Sep 17 00:00:00 2001 From: Harsh Batheja Date: Wed, 29 Apr 2026 03:53:32 +0530 Subject: [PATCH] fix(plugin-kimicode): plumb workspacePath into AgentLaunchConfig MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The kimicode plugin's --work-dir was passing projectConfig.path, which breaks worktree-mode workspaces. In worktree mode, projectConfig.path is the original repo root while session.workspacePath is the per-session checkout — they differ. Either kimi would write to the project root (breaking worktree isolation) or md5(projectConfig.path) would diverge from md5(session.workspacePath), so getActivityState/getSessionInfo would never find this session's bucket. Fix: - Add optional `workspacePath` field to AgentLaunchConfig. - Plumb it through all 3 launch call sites in session-manager.ts. - kimicode getLaunchCommand uses config.workspacePath, falling back to config.projectConfig.path when undefined. - Tests for the divergent-paths case. Public-interface change: AgentLaunchConfig grows one optional field. Invariants preserved: - Agent.getLaunchCommand signature unchanged — still takes one AgentLaunchConfig. - Existing plugins (claude-code, aider, codex, opencode) compile and run unchanged; the new field is optional and they ignore it. - Clone-mode workspaces (where workspacePath === projectConfig.path) produce the same launch command as before. - Fallback to projectConfig.path keeps callers that don't pass the new field working — no flag day required. --- packages/core/src/session-manager.ts | 3 +++ packages/core/src/types.ts | 9 +++++++++ .../plugins/agent-kimicode/src/index.test.ts | 19 +++++++++++++++++++ packages/plugins/agent-kimicode/src/index.ts | 17 +++++++++++++---- 4 files changed, 44 insertions(+), 4 deletions(-) diff --git a/packages/core/src/session-manager.ts b/packages/core/src/session-manager.ts index 29a4b20ad..6adc465c0 100644 --- a/packages/core/src/session-manager.ts +++ b/packages/core/src/session-manager.ts @@ -1290,6 +1290,7 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM ...(reusedOpenCodeSessionId ? { opencodeSessionId: reusedOpenCodeSessionId } : {}), }, }, + workspacePath, issueId: spawnConfig.issueId, prompt: composedPrompt, permissions: selection.permissions, @@ -1641,6 +1642,7 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM ...(reusableOpenCodeSessionId ? { opencodeSessionId: reusableOpenCodeSessionId } : {}), }, }, + workspacePath, permissions: "permissionless" as const, model: selection.model, systemPromptFile, @@ -2764,6 +2766,7 @@ export function createSessionManager(deps: SessionManagerDeps): OpenCodeSessionM const agentLaunchConfig = { sessionId, projectConfig: projectConfigForLaunch, + workspacePath, issueId: session.issueId ?? undefined, permissions: selection.role === "orchestrator" ? "permissionless" : selection.permissions, model: selection.model, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 54de8c894..043e61dfd 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -541,6 +541,15 @@ export interface Agent { export interface AgentLaunchConfig { sessionId: SessionId; projectConfig: ProjectConfig; + /** + * Per-session workspace path. Differs from `projectConfig.path` when the + * workspace plugin (e.g. worktree mode) creates an isolated checkout per + * session. Plugins that need the agent's actual cwd — for cwd-derived + * lookups, --work-dir flags, file-based discovery — must use this when + * present. Falls back to `projectConfig.path` when undefined (clone-mode + * workspaces, or plugins not yet plumbing it through). + */ + workspacePath?: string; issueId?: string; prompt?: string; permissions?: AgentPermissionInput; diff --git a/packages/plugins/agent-kimicode/src/index.test.ts b/packages/plugins/agent-kimicode/src/index.test.ts index 7b5d78c33..1a1f9946f 100644 --- a/packages/plugins/agent-kimicode/src/index.test.ts +++ b/packages/plugins/agent-kimicode/src/index.test.ts @@ -309,6 +309,25 @@ describe("getLaunchCommand", () => { "kimi --work-dir '/workspace/repo' --yolo --model 'kimi-k2' --agent 'default' --agent-file '/tmp/sp.md' --prompt 'Go'", ); }); + + // Worktree-mode parity: when AO runs in worktree workspace mode, + // workspacePath (per-session checkout) differs from projectConfig.path + // (the original repo root). --work-dir must take the workspacePath so + // kimi's md5(cwd) bucket matches the one session-discovery scans. + it("--work-dir prefers workspacePath over projectConfig.path", () => { + const cmd = agent.getLaunchCommand( + makeLaunchConfig({ + workspacePath: "/worktrees/sess-1", + }), + ); + expect(cmd).toContain("--work-dir '/worktrees/sess-1'"); + expect(cmd).not.toContain("/workspace/repo"); + }); + + it("--work-dir falls back to projectConfig.path when workspacePath is undefined", () => { + const cmd = agent.getLaunchCommand(makeLaunchConfig({ workspacePath: undefined })); + expect(cmd).toContain("--work-dir '/workspace/repo'"); + }); }); // ============================================================================= diff --git a/packages/plugins/agent-kimicode/src/index.ts b/packages/plugins/agent-kimicode/src/index.ts index a523cb78c..4da494683 100644 --- a/packages/plugins/agent-kimicode/src/index.ts +++ b/packages/plugins/agent-kimicode/src/index.ts @@ -500,10 +500,19 @@ function createKimicodeAgent(): Agent { const parts: string[] = ["kimi"]; // Explicit --work-dir prevents shell-rc / tmux-hook cwd drift from - // making our md5(cwd) hash diverge from kimi's. projectConfig.path - // is the project root — the canonical workspace directory. - if (config.projectConfig.path) { - parts.push("--work-dir", shellEscape(config.projectConfig.path)); + // making our md5(cwd) hash diverge from kimi's. + // + // Prefer config.workspacePath (per-session worktree) over + // projectConfig.path (the original repo root). When the workspace + // plugin is "worktree", these differ — passing projectConfig.path + // would either (a) make kimi write to the project root, breaking + // worktree isolation, or (b) cause md5(cwd) to diverge from + // session.workspacePath, so getActivityState/getSessionInfo never + // find this session's bucket. Falls back to projectConfig.path + // for clone-mode workspaces or older callers that don't plumb it. + const workDir = config.workspacePath ?? config.projectConfig.path; + if (workDir) { + parts.push("--work-dir", shellEscape(workDir)); } appendApprovalFlags(parts, config.permissions);