mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
fix(acp): pass configured agent model to ACP binding sessions (#120046)
This commit is contained in:
committed by
GitHub
parent
7729100775
commit
d0fe7bcb3c
@@ -59,6 +59,7 @@ function createPersistentSpec(
|
||||
function mockReadySession(params: {
|
||||
spec: ConfiguredAcpBindingSpec;
|
||||
cwd: string;
|
||||
model?: string;
|
||||
state?: "idle" | "running" | "error";
|
||||
}) {
|
||||
const sessionKey = buildConfiguredAcpSessionKey(params.spec);
|
||||
@@ -70,7 +71,10 @@ function mockReadySession(params: {
|
||||
agent: params.spec.acpAgentId ?? params.spec.agentId,
|
||||
runtimeSessionName: "existing",
|
||||
mode: params.spec.mode,
|
||||
runtimeOptions: { cwd: params.cwd },
|
||||
runtimeOptions: {
|
||||
cwd: params.cwd,
|
||||
...(params.model ? { model: params.model } : {}),
|
||||
},
|
||||
state: params.state ?? "idle",
|
||||
lastActivityAt: Date.now(),
|
||||
},
|
||||
@@ -102,6 +106,7 @@ describe("ensureConfiguredAcpBindingSession", () => {
|
||||
const sessionKey = mockReadySession({
|
||||
spec,
|
||||
cwd: "/workspace/openclaw",
|
||||
model: "manual/selected-model",
|
||||
});
|
||||
|
||||
const ensured = await ensureConfiguredAcpBindingSession({
|
||||
@@ -112,6 +117,32 @@ describe("ensureConfiguredAcpBindingSession", () => {
|
||||
expect(ensured).toEqual({ ok: true, sessionKey });
|
||||
expect(managerMocks.closeSession).not.toHaveBeenCalled();
|
||||
expect(managerMocks.initializeSession).not.toHaveBeenCalled();
|
||||
expect(managerMocks.updateSessionRuntimeOptions).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("updates a configured model in place for a structurally matching session", async () => {
|
||||
const spec = createPersistentSpec({
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
});
|
||||
const sessionKey = mockReadySession({
|
||||
spec,
|
||||
cwd: "/workspace/openclaw",
|
||||
model: "anthropic/claude-haiku-4-5",
|
||||
});
|
||||
|
||||
const ensured = await ensureConfiguredAcpBindingSession({
|
||||
cfg: baseCfg,
|
||||
spec,
|
||||
});
|
||||
|
||||
expect(ensured).toEqual({ ok: true, sessionKey });
|
||||
expect(managerMocks.updateSessionRuntimeOptions).toHaveBeenCalledWith({
|
||||
cfg: baseCfg,
|
||||
sessionKey,
|
||||
patch: { model: "anthropic/claude-sonnet-4-6" },
|
||||
});
|
||||
expect(managerMocks.closeSession).not.toHaveBeenCalled();
|
||||
expect(managerMocks.initializeSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reinitializes a ready session when binding config explicitly sets mismatched cwd", async () => {
|
||||
@@ -155,10 +186,11 @@ describe("ensureConfiguredAcpBindingSession", () => {
|
||||
expect(managerMocks.initializeSession).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("initializes ACP session with runtime agent override when provided", async () => {
|
||||
it("initializes ACP session with runtime agent override and configured model", async () => {
|
||||
const spec = createPersistentSpec({
|
||||
agentId: "coding",
|
||||
acpAgentId: "codex",
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
});
|
||||
managerMocks.resolveSession.mockReturnValue({ kind: "none" });
|
||||
|
||||
@@ -170,5 +202,9 @@ describe("ensureConfiguredAcpBindingSession", () => {
|
||||
expect(ensured.ok).toBe(true);
|
||||
const initializeArgs = expectInitializeArgs();
|
||||
expect(initializeArgs.agent).toBe("codex");
|
||||
expect(initializeArgs.runtimeOptions).toEqual({
|
||||
model: "anthropic/claude-sonnet-4-6",
|
||||
});
|
||||
expect(initializeArgs).not.toHaveProperty("modelExplicit");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
} from "./persistent-bindings.types.js";
|
||||
|
||||
// Binding lifecycle keeps configured channel conversations attached to matching ACP sessions.
|
||||
function sessionMatchesConfiguredBinding(params: {
|
||||
function sessionStructurallyMatchesConfiguredBinding(params: {
|
||||
cfg: OpenClawConfig;
|
||||
spec: ConfiguredAcpBindingSpec;
|
||||
meta: SessionAcpMeta;
|
||||
@@ -67,12 +67,23 @@ export async function ensureConfiguredAcpBindingSession(params: {
|
||||
});
|
||||
if (
|
||||
resolution.kind === "ready" &&
|
||||
sessionMatchesConfiguredBinding({
|
||||
sessionStructurallyMatchesConfiguredBinding({
|
||||
cfg: params.cfg,
|
||||
spec: params.spec,
|
||||
meta: resolution.meta,
|
||||
})
|
||||
) {
|
||||
// Model drift is live-configurable; preserve the bound conversation and patch it in place.
|
||||
if (
|
||||
params.spec.model &&
|
||||
normalizeText(resolution.meta.runtimeOptions?.model) !== params.spec.model
|
||||
) {
|
||||
await acpManager.updateSessionRuntimeOptions({
|
||||
cfg: params.cfg,
|
||||
sessionKey,
|
||||
patch: { model: params.spec.model },
|
||||
});
|
||||
}
|
||||
return {
|
||||
ok: true,
|
||||
sessionKey,
|
||||
@@ -95,6 +106,7 @@ export async function ensureConfiguredAcpBindingSession(params: {
|
||||
sessionKey,
|
||||
agent: params.spec.acpAgentId ?? params.spec.agentId,
|
||||
mode: params.spec.mode,
|
||||
runtimeOptions: params.spec.model ? { model: params.spec.model } : undefined,
|
||||
cwd: params.spec.cwd,
|
||||
backendId: params.spec.backend,
|
||||
});
|
||||
|
||||
@@ -391,18 +391,26 @@ beforeEach(() => {
|
||||
|
||||
describe("resolveConfiguredAcpBindingRecord", () => {
|
||||
it("resolves discord channel ACP binding from top-level typed bindings", () => {
|
||||
const cfg = createCfgWithBindings([
|
||||
createDiscordBinding({
|
||||
agentId: "codex",
|
||||
conversationId: defaultDiscordConversationId,
|
||||
acp: { cwd: "/repo/openclaw" },
|
||||
}),
|
||||
]);
|
||||
const cfg = createCfgWithBindings(
|
||||
[
|
||||
createDiscordBinding({
|
||||
agentId: "codex",
|
||||
conversationId: defaultDiscordConversationId,
|
||||
acp: { cwd: "/repo/openclaw" },
|
||||
}),
|
||||
],
|
||||
{
|
||||
agents: {
|
||||
list: [{ id: "codex", model: { primary: "anthropic/claude-sonnet-4-6" } }],
|
||||
},
|
||||
},
|
||||
);
|
||||
const resolved = resolveBindingRecord(cfg);
|
||||
|
||||
expect(resolved?.spec.channel).toBe("discord");
|
||||
expect(resolved?.spec.conversationId).toBe(defaultDiscordConversationId);
|
||||
expect(resolved?.spec.agentId).toBe("codex");
|
||||
expect(resolved?.spec.model).toBe("anthropic/claude-sonnet-4-6");
|
||||
expect(resolved?.record.targetSessionKey).toContain("agent:codex:acp:binding:discord:default:");
|
||||
expect(resolved?.record.metadata?.source).toBe("config");
|
||||
});
|
||||
|
||||
@@ -25,6 +25,7 @@ export type ConfiguredAcpBindingSpec = {
|
||||
/** ACP harness agent id override (falls back to agentId when omitted). */
|
||||
acpAgentId?: string;
|
||||
mode: AcpRuntimeSessionMode;
|
||||
model?: string;
|
||||
cwd?: string;
|
||||
backend?: string;
|
||||
label?: string;
|
||||
@@ -101,6 +102,7 @@ export function toConfiguredAcpBindingRecord(spec: ConfiguredAcpBindingSpec): Se
|
||||
agentId: spec.agentId,
|
||||
...(spec.acpAgentId ? { acpAgentId: spec.acpAgentId } : {}),
|
||||
label: spec.label,
|
||||
...(spec.model ? { model: spec.model } : {}),
|
||||
...(spec.backend ? { backend: spec.backend } : {}),
|
||||
...(spec.cwd ? { cwd: spec.cwd } : {}),
|
||||
},
|
||||
@@ -158,6 +160,7 @@ export function resolveConfiguredAcpBindingSpecFromRecord(
|
||||
agentId,
|
||||
acpAgentId: normalizeText(record.metadata?.acpAgentId),
|
||||
mode: normalizeMode(record.metadata?.mode),
|
||||
model: normalizeText(record.metadata?.model),
|
||||
cwd: normalizeText(record.metadata?.cwd),
|
||||
backend: normalizeText(record.metadata?.backend),
|
||||
label: normalizeText(record.metadata?.label),
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "../../acp/persistent-bindings.types.js";
|
||||
import {
|
||||
resolveAgentConfig,
|
||||
resolveAgentExplicitModelPrimary,
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
@@ -74,6 +75,7 @@ function buildConfiguredAcpSpec(params: {
|
||||
agentId: string;
|
||||
acpAgentId?: string;
|
||||
mode: "persistent" | "oneshot";
|
||||
model?: string;
|
||||
cwd?: string;
|
||||
backend?: string;
|
||||
label?: string;
|
||||
@@ -86,6 +88,7 @@ function buildConfiguredAcpSpec(params: {
|
||||
agentId: params.agentId,
|
||||
acpAgentId: params.acpAgentId,
|
||||
mode: params.mode,
|
||||
model: params.model,
|
||||
cwd: params.cwd,
|
||||
backend: params.backend,
|
||||
label: params.label,
|
||||
@@ -109,6 +112,8 @@ function buildAcpTargetFactory(params: {
|
||||
});
|
||||
const bindingOverrides = normalizeBindingConfig(params.binding.acp);
|
||||
const mode = normalizeMode(bindingOverrides.mode ?? runtimeDefaults.mode);
|
||||
// Every ACP binding uses its owner's explicit model, regardless of the owner's runtime type.
|
||||
const model = resolveAgentExplicitModelPrimary(params.cfg, params.agentId);
|
||||
const cwd =
|
||||
bindingOverrides.cwd ??
|
||||
runtimeDefaults.cwd ??
|
||||
@@ -132,6 +137,7 @@ function buildAcpTargetFactory(params: {
|
||||
agentId: params.agentId,
|
||||
acpAgentId,
|
||||
mode,
|
||||
model,
|
||||
cwd,
|
||||
backend,
|
||||
label,
|
||||
|
||||
Reference in New Issue
Block a user