mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
fix: honor ACP spawn model overrides (#70210)
Honor explicit ACP sessions_spawn model overrides and preserve ACP runtime cwd options.\n\nThanks @felix-miao.
This commit is contained in:
@@ -19,6 +19,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- ACP/sessions_spawn: honor explicit `model` overrides for ACP child sessions instead of silently falling back to the target agent default model. (#70210) Thanks @felix-miao.
|
||||
- CLI/Claude: hash only static extra system prompt parts when deciding whether to reuse a CLI session, so per-message inbound metadata no longer resets Claude CLI conversations on every turn. (#70122) Thanks @zijunl.
|
||||
- Hooks/Slack: standardize shared message hook routing fields (`threadId` / `replyToId`) and stop Slack outbound delivery from re-running `message_sending` inside the channel adapter, so plugins like thread-ownership make one outbound routing decision per reply. Thanks @vincentkoc.
|
||||
- Auto-reply/media: share one run-scoped reply media context between streamed block delivery and final payload filtering, so a local `MEDIA:` attachment is staged once and duplicate media sends are suppressed reliably. (#68111) Thanks @ayeshakhalid192007-dev.
|
||||
|
||||
@@ -311,7 +311,10 @@ export class AcpSessionManager {
|
||||
return await this.withSessionActor(sessionKey, async () => {
|
||||
const backend = this.deps.requireRuntimeBackend(input.backendId || input.cfg.acp?.backend);
|
||||
const runtime = backend.runtime;
|
||||
const initialRuntimeOptions = validateRuntimeOptionPatch({ cwd: input.cwd });
|
||||
const initialRuntimeOptions = validateRuntimeOptionPatch({
|
||||
...input.runtimeOptions,
|
||||
...(input.cwd !== undefined ? { cwd: input.cwd } : {}),
|
||||
});
|
||||
const requestedCwd = initialRuntimeOptions.cwd;
|
||||
this.enforceConcurrentSessionLimit({
|
||||
cfg: input.cfg,
|
||||
|
||||
@@ -1298,6 +1298,77 @@ describe("AcpSessionManager", () => {
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("persists runtime options provided during initializeSession", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
id: "acpx",
|
||||
runtime: runtimeState.runtime,
|
||||
});
|
||||
hoisted.upsertAcpSessionMetaMock.mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
storeSessionKey: "agent:codex:acp:session-a",
|
||||
acp: readySessionMeta({
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await manager.initializeSession({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-a",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
},
|
||||
});
|
||||
|
||||
expect(extractRuntimeOptionsFromUpserts()).toContainEqual({
|
||||
model: "openai-codex/gpt-5.4",
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves runtimeOptions cwd when initializeSession cwd is omitted", async () => {
|
||||
const runtimeState = createRuntime();
|
||||
hoisted.requireAcpRuntimeBackendMock.mockReturnValue({
|
||||
id: "acpx",
|
||||
runtime: runtimeState.runtime,
|
||||
});
|
||||
hoisted.upsertAcpSessionMetaMock.mockResolvedValue({
|
||||
sessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
storeSessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
acp: readySessionMeta({
|
||||
runtimeOptions: {
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
},
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
}),
|
||||
});
|
||||
|
||||
const manager = new AcpSessionManager();
|
||||
await manager.initializeSession({
|
||||
cfg: baseCfg,
|
||||
sessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
agent: "codex",
|
||||
mode: "persistent",
|
||||
runtimeOptions: {
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
},
|
||||
});
|
||||
|
||||
expect(runtimeState.ensureSession).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: "agent:codex:acp:session-cwd-runtime-options",
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
}),
|
||||
);
|
||||
expect(extractRuntimeOptionsFromUpserts()).toContainEqual({
|
||||
cwd: "/workspace/from-runtime-options",
|
||||
});
|
||||
});
|
||||
|
||||
it("drops cached runtime handles after tolerated close failures", async () => {
|
||||
const closeFailures = [
|
||||
{
|
||||
|
||||
@@ -44,6 +44,7 @@ export type AcpInitializeSessionInput = {
|
||||
agent: string;
|
||||
mode: AcpRuntimeSessionMode;
|
||||
resumeSessionId?: string;
|
||||
runtimeOptions?: Partial<AcpSessionRuntimeOptions>;
|
||||
cwd?: string;
|
||||
backendId?: string;
|
||||
};
|
||||
|
||||
@@ -718,6 +718,30 @@ describe("spawnAcpDirect", () => {
|
||||
expect(transcriptCalls[1]?.threadId).toBe("child-thread");
|
||||
});
|
||||
|
||||
it("passes model override into ACP session initialization", async () => {
|
||||
const result = await spawnAcpDirect(
|
||||
{
|
||||
task: "Investigate flaky tests",
|
||||
agentId: "codex",
|
||||
model: "openai-codex/gpt-5.4",
|
||||
},
|
||||
{
|
||||
agentSessionKey: "agent:main:main",
|
||||
},
|
||||
);
|
||||
|
||||
expectAcceptedSpawn(result);
|
||||
expect(hoisted.initializeSessionMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
sessionKey: expect.stringMatching(/^agent:codex:acp:/),
|
||||
agent: "codex",
|
||||
runtimeOptions: {
|
||||
model: "openai-codex/gpt-5.4",
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("inherits subagent envelope fields onto ACP children", async () => {
|
||||
replaceSpawnConfig({
|
||||
...hoisted.state.cfg,
|
||||
|
||||
@@ -104,6 +104,7 @@ export type SpawnAcpParams = {
|
||||
label?: string;
|
||||
agentId?: string;
|
||||
resumeSessionId?: string;
|
||||
model?: string;
|
||||
cwd?: string;
|
||||
mode?: SpawnAcpMode;
|
||||
thread?: boolean;
|
||||
@@ -890,6 +891,7 @@ async function initializeAcpSpawnRuntime(params: {
|
||||
targetAgentId: string;
|
||||
runtimeMode: AcpRuntimeSessionMode;
|
||||
resumeSessionId?: string;
|
||||
model?: string;
|
||||
cwd?: string;
|
||||
}): Promise<AcpSpawnInitializedRuntime> {
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.targetAgentId });
|
||||
@@ -914,6 +916,7 @@ async function initializeAcpSpawnRuntime(params: {
|
||||
agent: params.targetAgentId,
|
||||
mode: params.runtimeMode,
|
||||
resumeSessionId: params.resumeSessionId,
|
||||
runtimeOptions: params.model ? { model: params.model } : undefined,
|
||||
cwd: params.cwd,
|
||||
backendId: params.cfg.acp?.backend,
|
||||
});
|
||||
@@ -1249,6 +1252,7 @@ export async function spawnAcpDirect(
|
||||
targetAgentId,
|
||||
runtimeMode,
|
||||
resumeSessionId: params.resumeSessionId,
|
||||
model: params.model,
|
||||
cwd: runtimeCwd,
|
||||
});
|
||||
initializedRuntime = initializedSession.runtimeCloseHandle;
|
||||
|
||||
@@ -247,6 +247,28 @@ describe("sessions_spawn tool", () => {
|
||||
expect(hoisted.registerSubagentRunMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("forwards model override to ACP runtime spawns", async () => {
|
||||
const tool = createSessionsSpawnTool({
|
||||
agentSessionKey: "agent:main:main",
|
||||
});
|
||||
|
||||
await tool.execute("call-2-model", {
|
||||
runtime: "acp",
|
||||
task: "investigate the failing CI run",
|
||||
agentId: "codex",
|
||||
model: "github-copilot/claude-sonnet-4.6",
|
||||
});
|
||||
|
||||
expect(hoisted.spawnAcpDirectMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
task: "investigate the failing CI run",
|
||||
agentId: "codex",
|
||||
model: "github-copilot/claude-sonnet-4.6",
|
||||
}),
|
||||
expect.any(Object),
|
||||
);
|
||||
});
|
||||
|
||||
it("adds requested role to forwarded ACP failures", async () => {
|
||||
hoisted.spawnAcpDirectMock.mockResolvedValueOnce({
|
||||
status: "forbidden",
|
||||
|
||||
@@ -245,6 +245,7 @@ export function createSessionsSpawnTool(
|
||||
label: label || undefined,
|
||||
agentId: requestedAgentId,
|
||||
resumeSessionId,
|
||||
model: modelOverride,
|
||||
cwd,
|
||||
mode: mode === "run" || mode === "session" ? mode : undefined,
|
||||
thread,
|
||||
|
||||
Reference in New Issue
Block a user