fix(server): a plugin that moves settings and then refuses an apply reports what moved

This commit is contained in:
Mohamed Boudra
2026-11-01 15:34:06 +01:00
parent 0fef7414fc
commit 3a3300b69e
2 changed files with 26 additions and 2 deletions
@@ -1005,7 +1005,10 @@ describe("PluginAgentClientRegistry", () => {
describe("settings", () => {
// A plugin that keeps `voice` on "direct" whatever it is asked, and takes `loud` as given.
async function openStubbornVoiceSession(given: Record<string, boolean | string>) {
async function openStubbornVoiceSession(
given: Record<string, boolean | string>,
{ refuseAfterChanging = false }: { refuseAfterChanging?: boolean } = {},
) {
const state = { voice: "direct", loud: false };
let openedSessionId = "";
const settings = () => [
@@ -1055,7 +1058,11 @@ describe("PluginAgentClientRegistry", () => {
const loud = input.changes.settings?.loud;
if (typeof loud === "boolean") state.loud = loud;
emitConfig(emit, input.sessionId);
emit({ type: "request.completed", requestId: input.requestId });
emit(
refuseAfterChanging
? { type: "request.failed", requestId: input.requestId, error: { message: "no" } }
: { type: "request.completed", requestId: input.requestId },
);
return true;
}
return false;
@@ -1088,6 +1095,17 @@ describe("PluginAgentClientRegistry", () => {
await session.close();
});
test("a rejected apply reports what the plugin moved before refusing, since nothing is stored", async () => {
const { session, events } = await openStubbornVoiceSession({}, { refuseAfterChanging: true });
await expect(session.applyOptions?.({ loud: true })).rejects.toThrow("no");
expect(eventsOfType(events, "options_changed")).toEqual([
{ type: "options_changed", provider: "plugin-direct", patch: { loud: true } },
]);
await session.close();
});
test("a setting the plugin opened differently is the session's open patch, not an event", async () => {
const { session, events } = await openStubbornVoiceSession({ voice: "other", loud: false });
@@ -1448,6 +1448,12 @@ class PluginAgentSession implements AgentSession {
this.applyingOptions = true;
try {
await this.bridge.configure({ settings });
} catch (error) {
// Nothing is stored for a refused patch, so what the plugin moved is reported once the
// call has ended.
this.applyingOptions = false;
for (const event of this.reportSettingChanges()) this.publish(event);
throw error;
} finally {
this.applyingOptions = false;
}