diff --git a/packages/server/src/server/agent/providers/acp-agent.test.ts b/packages/server/src/server/agent/providers/acp-agent.test.ts index c8a24085f9..ae7ede3579 100644 --- a/packages/server/src/server/agent/providers/acp-agent.test.ts +++ b/packages/server/src/server/agent/providers/acp-agent.test.ts @@ -1956,6 +1956,24 @@ describe("ACPAgentSession options against an ACP agent", () => { ]), ); }); + + test("a select that appears after startup is reported with its native value", async () => { + const { session, optionEvents } = await openSession({ + scenario: { + options: [thoughtOption("low")], + adds: { thought: [copilotAgentConfigOption("Probe Agent")] }, + }, + }); + expect(session.openOptionsPatch).toEqual({}); + + await session.setThinkingOption("high"); + + await vi.waitFor(() => + expect(optionEvents).toEqual([ + { type: "options_changed", provider: "copilot", patch: { agent: "Probe Agent" } }, + ]), + ); + }); }); describe("deriveModelDefinitionsFromACP", () => { diff --git a/packages/server/src/server/agent/providers/acp-agent.ts b/packages/server/src/server/agent/providers/acp-agent.ts index f48ae85361..1055a56a2c 100644 --- a/packages/server/src/server/agent/providers/acp-agent.ts +++ b/packages/server/src/server/agent/providers/acp-agent.ts @@ -2467,12 +2467,10 @@ export class ACPAgentSession implements AgentSession, ACPClient { return values; } - /** The selects whose value is not what `expected` says; selects it does not name are skipped. */ + /** The selects whose value is not what `expected` says, including ones it does not name. */ private featureDifferences(expected: Record): Record { return Object.fromEntries( - Object.entries(this.featureValues()).filter( - ([id, value]) => id in expected && expected[id] !== value, - ), + Object.entries(this.featureValues()).filter(([id, value]) => expected[id] !== value), ); } @@ -3130,11 +3128,7 @@ export class ACPAgentSession implements AgentSession, ACPClient { } // The options are the state, so every select the session opened with is reported, including // ones nobody set (a native default is stored like any other value). - this.openOptionsPatch = Object.fromEntries( - Object.entries(this.featureValues()).filter( - ([id, value]) => configuredFeatureValues[id] !== value, - ), - ); + this.openOptionsPatch = this.featureDifferences(configuredFeatureValues); } /** diff --git a/packages/server/src/server/agent/providers/test-utils/fake-acp-agent.mjs b/packages/server/src/server/agent/providers/test-utils/fake-acp-agent.mjs index 1ea77896f2..e284a326da 100644 --- a/packages/server/src/server/agent/providers/test-utils/fake-acp-agent.mjs +++ b/packages/server/src/server/agent/providers/test-utils/fake-acp-agent.mjs @@ -5,6 +5,7 @@ // "notify-then-reject" does the same and then fails the write // moves: { [configId]: { [id]: value } } writes that also move another select, announced // by a config_option_update after the write is answered +// adds: { [configId]: option[] } writes that make more selects appear, announced the same way import { Readable, Writable } from "node:stream"; import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk"; @@ -39,8 +40,10 @@ export const connection = new AgentSideConnection( return { configOptions: options }; } const moves = scenario.moves?.[configId]; - if (moves) { - for (const [id, moved] of Object.entries(moves)) set(id, moved); + const adds = scenario.adds?.[configId]; + if (moves || adds) { + for (const [id, moved] of Object.entries(moves ?? {})) set(id, moved); + options = [...options, ...(adds ?? [])]; setTimeout(() => void announce(), 20); } return { configOptions: options };