mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 14:12:51 +00:00
fix(server): reopen the created native plugin session, keep ACP reset acknowledgements from storing removed options
When a plugin session declines configuration and is reopened with its settings, the reopen continues the native session the first open created instead of creating a second one. An ACP provider response to a write that only resets an absent option to its default no longer emits an option change, so a removed option stays absent.
This commit is contained in:
@@ -908,6 +908,7 @@ function startVoicePlugin(input: {
|
||||
...CAPABILITIES.filter((name) => input.configurable !== false || name !== "session.configure"),
|
||||
"session.revert.conversation",
|
||||
];
|
||||
let nativeSessions = 0;
|
||||
const harness = createProviderHarness({
|
||||
capabilities,
|
||||
async handleInput(request, emit) {
|
||||
@@ -920,7 +921,10 @@ function startVoicePlugin(input: {
|
||||
? capabilities.filter((name) => name !== "session.configure")
|
||||
: capabilities,
|
||||
restoration: "core",
|
||||
persistence: request.persistence ?? { version: 1, data: { token: "root" } },
|
||||
persistence: request.persistence ?? {
|
||||
version: 1,
|
||||
data: { token: `native-${++nativeSessions}` },
|
||||
},
|
||||
cwd: request.config.cwd,
|
||||
});
|
||||
emit({
|
||||
@@ -1808,6 +1812,11 @@ describe("PluginAgentClientRegistry", () => {
|
||||
input.type === "session.open" ? [input.config.settings] : [],
|
||||
);
|
||||
expect(opens.at(-1)).toEqual({ voice: "soft" });
|
||||
// The second open continues the native session the first one created.
|
||||
const persistence = harness.inputs.flatMap((input) =>
|
||||
input.type === "session.open" ? [input.persistence] : [],
|
||||
);
|
||||
expect(persistence).toEqual([undefined, { version: 1, data: { token: "native-1" } }]);
|
||||
expect(configuredSettings(harness)).toEqual([]);
|
||||
expect(toAgentPayload(manager.getAgent(agent.id)!).options).toEqual({ voice: "soft" });
|
||||
} finally {
|
||||
|
||||
@@ -1104,11 +1104,11 @@ class PluginAgentClient implements AgentClient {
|
||||
// removal returns to. So a plugin that can be configured opens without setting overrides and
|
||||
// has them applied after. One that cannot takes them in the open request.
|
||||
const openConfig = mapSessionConfig(input.config, input.launchContext, input.persist);
|
||||
const openWith = (config: ProviderSessionConfig) =>
|
||||
const openWith = (config: ProviderSessionConfig, persistence = input.persistence) =>
|
||||
this.runtime.openSession({
|
||||
sessionId: randomUUID(),
|
||||
config,
|
||||
persistence: input.persistence,
|
||||
persistence,
|
||||
history: input.history,
|
||||
});
|
||||
let bridge: ProviderRuntimeSession;
|
||||
@@ -1119,8 +1119,10 @@ class PluginAgentClient implements AgentClient {
|
||||
if (!bridge.negotiatedCapabilities.includes("session.configure")) {
|
||||
configurable = false;
|
||||
if (Object.keys(openConfig.settings ?? {}).length > 0) {
|
||||
// Reopen the native session the first open created rather than creating another.
|
||||
const created = input.persistence ?? bridge.persistence ?? undefined;
|
||||
await bridge.close();
|
||||
bridge = await openWith(openConfig);
|
||||
bridge = await openWith(openConfig, created);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2523,8 +2523,11 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
requestedValue,
|
||||
label: featureOption.label,
|
||||
});
|
||||
// A write that only returns an absent option to its default has no stored option to correct.
|
||||
const held = this.optionsInFlight ?? this.config.providerOptions;
|
||||
const isHeld = Object.prototype.hasOwnProperty.call(held ?? {}, featureId);
|
||||
this.config.providerOptions = { ...this.config.providerOptions, [featureId]: currentValue };
|
||||
if (currentValue !== requestedValue) {
|
||||
if (isHeld && currentValue !== requestedValue) {
|
||||
// The provider kept a different selection than the one asked for.
|
||||
this.pushEvent({
|
||||
type: "options_changed",
|
||||
|
||||
@@ -489,6 +489,28 @@ describe("CursorACPAgentClient session start", () => {
|
||||
expect(events.filter((event) => event.type === "options_changed")).toEqual([]);
|
||||
});
|
||||
|
||||
test("a reset response that keeps the previous value does not store the removed option", async () => {
|
||||
const setSessionConfigOption = vi.fn(async () => ({
|
||||
configOptions: [fastConfigOption("true")],
|
||||
}));
|
||||
const session = createCursorSession(
|
||||
{ model: "composer-2.5" },
|
||||
{
|
||||
currentModelId: "composer-2.5",
|
||||
configOptions: [fastConfigOption("false")],
|
||||
setSessionConfigOption,
|
||||
},
|
||||
);
|
||||
await session.initializeNewSession();
|
||||
const events: AgentStreamEvent[] = [];
|
||||
session.subscribe((event) => events.push(event));
|
||||
|
||||
await session.applyOptions({ [CURSOR_FAST_FEATURE_OPTION.id]: "true" });
|
||||
await session.applyOptions({});
|
||||
|
||||
expect(events.filter((event) => event.type === "options_changed")).toEqual([]);
|
||||
});
|
||||
|
||||
test("removing the Fast option returns the session to the value it started with", async () => {
|
||||
const setSessionConfigOption = vi.fn(async (request: { value: string }) => ({
|
||||
configOptions: [fastConfigOption(request.value === "true" ? "true" : "false")],
|
||||
|
||||
Reference in New Issue
Block a user