mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-05 22:32:12 +00:00
fix(acp): fall back when inherited target workspace is missing
This commit is contained in:
committed by
Peter Steinberger
parent
6507f54965
commit
d718d17b5b
@@ -1,3 +1,6 @@
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import * as acpSessionManager from "../acp/control-plane/manager.js";
|
||||
import type { AcpInitializeSessionInput } from "../acp/control-plane/manager.types.js";
|
||||
@@ -538,6 +541,111 @@ describe("spawnAcpDirect", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the target agent workspace for cross-agent ACP spawns when cwd is omitted", async () => {
|
||||
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acp-spawn-"));
|
||||
try {
|
||||
const mainWorkspace = path.join(workspaceRoot, "main");
|
||||
const targetWorkspace = path.join(workspaceRoot, "claude-code");
|
||||
await fs.mkdir(mainWorkspace, { recursive: true });
|
||||
await fs.mkdir(targetWorkspace, { recursive: true });
|
||||
|
||||
replaceSpawnConfig({
|
||||
...hoisted.state.cfg,
|
||||
acp: {
|
||||
...hoisted.state.cfg.acp,
|
||||
allowedAgents: ["codex", "claude-code"],
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
default: true,
|
||||
workspace: mainWorkspace,
|
||||
},
|
||||
{
|
||||
id: "claude-code",
|
||||
workspace: targetWorkspace,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await spawnAcpDirect(
|
||||
{
|
||||
task: "Inspect the queue owner state",
|
||||
agentId: "claude-code",
|
||||
mode: "run",
|
||||
},
|
||||
{
|
||||
agentSessionKey: "agent:main:main",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe("accepted");
|
||||
expect(hoisted.initializeSessionMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: expect.stringMatching(/^agent:claude-code:acp:/),
|
||||
agent: "claude-code",
|
||||
cwd: targetWorkspace,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(workspaceRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("falls back to backend default cwd when the inherited target workspace does not exist", async () => {
|
||||
const workspaceRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acp-spawn-"));
|
||||
try {
|
||||
const mainWorkspace = path.join(workspaceRoot, "main");
|
||||
const missingTargetWorkspace = path.join(workspaceRoot, "claude-code-missing");
|
||||
await fs.mkdir(mainWorkspace, { recursive: true });
|
||||
|
||||
replaceSpawnConfig({
|
||||
...hoisted.state.cfg,
|
||||
acp: {
|
||||
...hoisted.state.cfg.acp,
|
||||
allowedAgents: ["codex", "claude-code"],
|
||||
},
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
default: true,
|
||||
workspace: mainWorkspace,
|
||||
},
|
||||
{
|
||||
id: "claude-code",
|
||||
workspace: missingTargetWorkspace,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const result = await spawnAcpDirect(
|
||||
{
|
||||
task: "Inspect the queue owner state",
|
||||
agentId: "claude-code",
|
||||
mode: "run",
|
||||
},
|
||||
{
|
||||
agentSessionKey: "agent:main:main",
|
||||
},
|
||||
);
|
||||
|
||||
expect(result.status).toBe("accepted");
|
||||
expect(hoisted.initializeSessionMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: expect.stringMatching(/^agent:claude-code:acp:/),
|
||||
agent: "claude-code",
|
||||
cwd: undefined,
|
||||
}),
|
||||
);
|
||||
} finally {
|
||||
await fs.rm(workspaceRoot, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("binds LINE ACP sessions to the current conversation when the channel has no native threads", async () => {
|
||||
enableLineCurrentConversationBindings();
|
||||
hoisted.sessionBindingBindMock.mockImplementationOnce(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import crypto from "node:crypto";
|
||||
import fs from "node:fs/promises";
|
||||
import { getAcpSessionManager } from "../acp/control-plane/manager.js";
|
||||
import {
|
||||
cleanupFailedAcpSpawn,
|
||||
@@ -58,6 +59,7 @@ import {
|
||||
} from "./acp-spawn-parent-stream.js";
|
||||
import { resolveAgentConfig, resolveDefaultAgentId } from "./agent-scope.js";
|
||||
import { resolveSandboxRuntimeStatus } from "./sandbox/runtime-status.js";
|
||||
import { resolveSpawnedWorkspaceInheritance } from "./spawned-context.js";
|
||||
import { resolveInternalSessionKey, resolveMainSessionAlias } from "./tools/sessions-helpers.js";
|
||||
|
||||
const log = createSubsystemLogger("agents/acp-spawn");
|
||||
@@ -367,6 +369,24 @@ function summarizeError(err: unknown): string {
|
||||
return "error";
|
||||
}
|
||||
|
||||
async function resolveRuntimeCwdForAcpSpawn(params: {
|
||||
resolvedCwd?: string;
|
||||
explicitCwd?: string;
|
||||
}): Promise<string | undefined> {
|
||||
if (!params.resolvedCwd) {
|
||||
return undefined;
|
||||
}
|
||||
if (typeof params.explicitCwd === "string" && params.explicitCwd.trim()) {
|
||||
return params.resolvedCwd;
|
||||
}
|
||||
try {
|
||||
await fs.access(params.resolvedCwd);
|
||||
return params.resolvedCwd;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function resolveRequesterInternalSessionKey(params: {
|
||||
cfg: OpenClawConfig;
|
||||
requesterSessionKey?: string;
|
||||
@@ -918,6 +938,16 @@ export async function spawnAcpDirect(
|
||||
|
||||
const sessionKey = `agent:${targetAgentId}:acp:${crypto.randomUUID()}`;
|
||||
const runtimeMode = resolveAcpSessionMode(spawnMode);
|
||||
const resolvedCwd = resolveSpawnedWorkspaceInheritance({
|
||||
config: cfg,
|
||||
targetAgentId,
|
||||
requesterSessionKey: ctx.agentSessionKey,
|
||||
explicitWorkspaceDir: params.cwd,
|
||||
});
|
||||
const runtimeCwd = await resolveRuntimeCwdForAcpSpawn({
|
||||
resolvedCwd,
|
||||
explicitCwd: params.cwd,
|
||||
});
|
||||
|
||||
let preparedBinding: PreparedAcpThreadBinding | null = null;
|
||||
if (requestThreadBinding) {
|
||||
@@ -958,7 +988,7 @@ export async function spawnAcpDirect(
|
||||
targetAgentId,
|
||||
runtimeMode,
|
||||
resumeSessionId: params.resumeSessionId,
|
||||
cwd: params.cwd,
|
||||
cwd: runtimeCwd,
|
||||
});
|
||||
initializedRuntime = initializedSession.runtimeCloseHandle;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user