From 970c281daf05f340bede78d8577094991e5ca78b Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 2 Nov 2026 00:31:08 +0100 Subject: [PATCH] fix(server): a plugin provider's reported options are stored like any provider's A control's `defaultValue` only says what the control shows for a key with no value. Opening and applying store every option the session reports, defaults and values nobody set included, as built-in providers do; the special case that left a value at the default unset is removed. --- .../src/server/agent/plugin-provider.test.ts | 10 ++++------ .../server/src/server/agent/plugin-provider.ts | 14 +++----------- public-docs/plugins/providers.md | 6 +++--- public-docs/plugins/providers.zh-CN.md | 2 +- 4 files changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/server/src/server/agent/plugin-provider.test.ts b/packages/server/src/server/agent/plugin-provider.test.ts index 00c239eeaa..2772cd1030 100644 --- a/packages/server/src/server/agent/plugin-provider.test.ts +++ b/packages/server/src/server/agent/plugin-provider.test.ts @@ -1148,12 +1148,10 @@ describe("PluginAgentClientRegistry", () => { await session.close(); }); - test("options the plugin opened with that nobody set are in the open patch, unless they are the control's default", async () => { + test("options the plugin opened with that nobody set are in the open patch, defaults included", async () => { const { session, events } = await openStubbornVoiceSession({}); - expect(session.openOptionsPatch).toEqual( - shape === "controls" ? {} : { voice: "direct", loud: false }, - ); + expect(session.openOptionsPatch).toEqual({ voice: "direct", loud: false }); expect(eventsOfType(events, "options_changed")).toEqual([]); await session.close(); }); @@ -1181,8 +1179,8 @@ describe("PluginAgentClientRegistry", () => { expect(lastConfigure(harness.inputs)).toMatchObject({ changes: { providerOptions: { loud: null } }, }); - // It went back to its default; only a plugin without a declared default pins that value. - expect(ended).toEqual(shape === "controls" ? {} : { loud: false }); + // It went back to its default, and what it ended up with is reported like any provider's. + expect(ended).toEqual({ loud: false }); await session.close(); }); diff --git a/packages/server/src/server/agent/plugin-provider.ts b/packages/server/src/server/agent/plugin-provider.ts index 5b58e4a63f..8a0fe677b1 100644 --- a/packages/server/src/server/agent/plugin-provider.ts +++ b/packages/server/src/server/agent/plugin-provider.ts @@ -1639,19 +1639,11 @@ class PluginAgentSession implements AgentSession { return readPluginOptionState(this.bridge.config).options; } - /** - * Options whose value differs from `known`: the plugin's own change or its opening state. A key - * with no value in `known` that the plugin reports at its control's declared default stays - * unset, so the agent follows the default instead of pinning it. - */ + /** Options whose value differs from `known`: the plugin's own change or its opening state. */ private optionDifferences(known: Readonly>): Record { - const { controls, options } = readPluginOptionState(this.bridge.config); const patch: Record = {}; - for (const [id, value] of Object.entries(options)) { - if (isDeepStrictEqual(known[id], value)) continue; - const control = controls.find((candidate) => candidate.key === id); - if (known[id] == null && control?.defaultValue === value) continue; - patch[id] = value; + for (const [id, value] of Object.entries(this.currentOptions())) { + if (!isDeepStrictEqual(known[id], value)) patch[id] = value; } return patch; } diff --git a/public-docs/plugins/providers.md b/public-docs/plugins/providers.md index bac1c19b93..75c12abdd6 100644 --- a/public-docs/plugins/providers.md +++ b/public-docs/plugins/providers.md @@ -269,9 +269,9 @@ emit({ }); ``` -`icon` is a Lucide icon name. `defaultValue` is what an unset key shows. A key you report at its -`defaultValue` that nobody set stays unset, so the agent keeps following the default; report any -other value and Paseo stores it. The same controls appear in the New workspace screen, in draft +`icon` is a Lucide icon name. `defaultValue` is what a control shows for a key that has no value. +Paseo stores every value you report when a session opens or a change is applied, including values +at the default and values nobody set. The same controls appear in the New workspace screen, in draft tabs, and on running agents. Offer a control only for a key you can change on a running session. The provider may change its controls at runtime, including after a model change. diff --git a/public-docs/plugins/providers.zh-CN.md b/public-docs/plugins/providers.zh-CN.md index 2d5cc9abff..5f9b4a5a06 100644 --- a/public-docs/plugins/providers.zh-CN.md +++ b/public-docs/plugins/providers.zh-CN.md @@ -242,7 +242,7 @@ emit({ }); ``` -`icon` 是 Lucide 图标名称。`defaultValue` 是键未设置时显示的值。如果你为一个无人设置过的键报告了它的 `defaultValue`,该键保持未设置,Agent 继续跟随默认值;报告其他任何值,Paseo 都会保存它。相同的控件会出现在“新建工作区”界面、草稿标签页和运行中的 Agent 上。只为你能在运行中的会话里修改的键提供控件。Provider 可以在运行时改变控件,包括在模型变化之后。 +`icon` 是 Lucide 图标名称。`defaultValue` 是控件在键没有值时显示的值。会话打开或应用修改时,你报告的每个值(包括处于默认值的值和无人设置过的值)Paseo 都会保存。相同的控件会出现在“新建工作区”界面、草稿标签页和运行中的 Agent 上。只为你能在运行中的会话里修改的键提供控件。Provider 可以在运行时改变控件,包括在模型变化之后。 用户的修改会以 `session.configure` 的形式到达,`changes.providerOptions` 只包含发生变化的键。应用它,并在 `request.completed` 之前发布已确定的 `session.config`;或者用 `request.failed` 使请求失败,并让每个键保持原样。值为 `null` 表示把该键恢复为 Provider 的默认值。请发布会话最终实际使用的值,而不是请求的副本:Paseo 会保存你发布的值,使保存的选项与实际运行的一致。