mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 22:23:07 +00:00
fix(server): a loaded agent's options stay what its session opened with when a provider default is edited
effectiveOptions was recomputed from the current config.json defaults on every state emission, so editing a default changed the snapshot of an agent whose session never received it. It is now computed when the session opens and moves only through an apply or an options_changed. Editing a default affects agents the next time they open.
This commit is contained in:
+3
-1
@@ -41,7 +41,9 @@ or without a control.
|
||||
differs from what it was given, including a setting nobody asked for (a native default such as
|
||||
Cursor opening with Fast on). It is set before `createSession`/`resumeSession` resolves and the
|
||||
daemon stores it before the agent is published. A reopened agent resumes that state, so a
|
||||
native default the provider later changes does not follow.
|
||||
native default the provider later changes does not follow. `options` on the snapshot is what
|
||||
the session opened with, and afterwards it moves only through an apply or an `options_changed`.
|
||||
Editing a provider default in `config.json` affects agents the next time they open.
|
||||
- **Live apply.** `applyOptions` receives the keys the patch touches, resolved against the
|
||||
`config.json` defaults. A removal arrives as the default the key falls back to, or `null` when
|
||||
there is none; the provider returns the setting to its own default if it can, or throws to
|
||||
|
||||
@@ -12939,6 +12939,22 @@ describe("agent options", () => {
|
||||
expect(states.at(-1)?.effectiveOptions).toEqual({ service_tier: "default", fast_mode: true });
|
||||
});
|
||||
|
||||
test("editing a provider default does not change the options of a loaded agent", async () => {
|
||||
const { manager, client, agentId } = await createOptionsAgent({ fast_mode: true });
|
||||
expect(manager.getAgent(agentId)?.effectiveOptions).toEqual({
|
||||
service_tier: "default",
|
||||
fast_mode: true,
|
||||
});
|
||||
|
||||
// The user edits the default in config.json while the agent is running.
|
||||
client.effectiveOptions = (own) => ({ service_tier: "fast", ...own });
|
||||
await manager.markAgentUnread(agentId);
|
||||
|
||||
const agent = manager.getAgent(agentId);
|
||||
expect(agent?.effectiveOptions).toEqual({ service_tier: "default", fast_mode: true });
|
||||
expect(toAgentPayload(agent!).options).toEqual({ service_tier: "default", fast_mode: true });
|
||||
});
|
||||
|
||||
test("a provider that rejects an option leaves the stored options unchanged", async () => {
|
||||
const { manager, client, agentId } = await createOptionsAgent({ fast_mode: false });
|
||||
client.session!.rejectWith = new Error("fast mode unavailable");
|
||||
|
||||
@@ -2284,6 +2284,7 @@ export class AgentManager {
|
||||
async setAgentOptions(agentId: string, patch: ProviderOptionsPatch): Promise<void> {
|
||||
const agent = this.requireAgent(agentId);
|
||||
let ended: ProviderOptionsPatch = {};
|
||||
let resolvedForApply: ProviderOptions = {};
|
||||
if (Object.keys(patch).length > 0) {
|
||||
// A provider that cannot apply options to a running agent must not store them either.
|
||||
if (!agent.session.applyOptions) {
|
||||
@@ -2297,6 +2298,7 @@ export class AgentManager {
|
||||
}
|
||||
const set = Object.fromEntries(Object.entries(patch).filter(([, value]) => value != null));
|
||||
const resolved = this.effectiveOptionsFor(agent.provider, set);
|
||||
resolvedForApply = resolved;
|
||||
ended =
|
||||
(await agent.session.applyOptions(
|
||||
Object.fromEntries(Object.keys(patch).map((key) => [key, resolved[key] ?? null])),
|
||||
@@ -2305,6 +2307,7 @@ export class AgentManager {
|
||||
await this.drainSessionEvents(agentId);
|
||||
}
|
||||
this.storeOptionsPatch(agent, { ...patch, ...ended });
|
||||
this.updateEffectiveOptions(agent, { ...patch, ...ended }, resolvedForApply);
|
||||
this.touchUpdatedAt(agent);
|
||||
this.emitState(agent);
|
||||
}
|
||||
@@ -2322,6 +2325,24 @@ export class AgentManager {
|
||||
agent.config.providerOptions = Object.keys(own).length > 0 ? own : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the running session has: the options it opened with, moved only by an apply or a change
|
||||
* the provider reports. A removal shows the default it fell back to, as the provider was told.
|
||||
*/
|
||||
private updateEffectiveOptions(
|
||||
agent: ManagedAgent,
|
||||
patch: ProviderOptionsPatch,
|
||||
fallbacks: ProviderOptions = {},
|
||||
): void {
|
||||
const moved = Object.fromEntries(
|
||||
Object.entries(patch).map(([key, value]) => [key, value ?? fallbacks[key] ?? null]),
|
||||
);
|
||||
agent.effectiveOptions = applyProviderOptionsPatch({
|
||||
options: agent.effectiveOptions,
|
||||
patch: moved,
|
||||
});
|
||||
}
|
||||
|
||||
private effectiveOptionsFor(
|
||||
provider: AgentProvider,
|
||||
own: ProviderOptions | undefined,
|
||||
@@ -4068,6 +4089,8 @@ export class AgentManager {
|
||||
session,
|
||||
capabilities: session.capabilities,
|
||||
config,
|
||||
// What the session opened with. Provider defaults edited later reach it when it next opens.
|
||||
effectiveOptions: this.effectiveOptionsFor(config.provider, config.providerOptions),
|
||||
runtimeInfo: undefined,
|
||||
lifecycle: "initializing",
|
||||
createdAt: options?.createdAt ?? now,
|
||||
@@ -4726,6 +4749,7 @@ export class AgentManager {
|
||||
}): void {
|
||||
const { agent, patch, flags } = params;
|
||||
this.storeOptionsPatch(agent, patch);
|
||||
this.updateEffectiveOptions(agent, patch);
|
||||
flags.shouldDispatchEvent = false;
|
||||
this.emitState(agent);
|
||||
}
|
||||
@@ -5285,7 +5309,6 @@ export class AgentManager {
|
||||
if ("session" in agent && agent.session?.controls) {
|
||||
agent.controls = agent.session.controls;
|
||||
}
|
||||
agent.effectiveOptions = this.effectiveOptionsFor(agent.provider, agent.config.providerOptions);
|
||||
}
|
||||
|
||||
private checkAndSetAttention(agent: ManagedAgent): void {
|
||||
|
||||
Reference in New Issue
Block a user