fix(plugin-kimicode): plumb workspacePath into AgentLaunchConfig

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.
This commit is contained in:
Harsh Batheja 2026-04-29 03:53:32 +05:30
parent bc6f1cf70d
commit 9fcc1d9ed5
4 changed files with 44 additions and 4 deletions

View File

@ -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,

View File

@ -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;

View File

@ -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'");
});
});
// =============================================================================

View File

@ -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);