mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 22:23:07 +00:00
fix(server): resolve supplied options against configured defaults before applying them live
setAgentOptions hands a running provider the keys the agent holds, each resolved against the config.json defaults, so a nested patch (for example a sandbox object) keeps the configured restrictions. Removed keys stay absent.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { mergeProviderOptions } from "./provider-options.js";
|
||||
import { createIdleRuntimeCleanup } from "./idle-runtime-cleanup/index.js";
|
||||
import {
|
||||
prepareAgentMessage,
|
||||
@@ -12880,6 +12881,37 @@ describe("agent options", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
test("a supplied option is resolved against the configured defaults before it reaches the provider", async () => {
|
||||
class DefaultsClient extends OptionsClient {
|
||||
override effectiveOptions(own: ProviderOptions | undefined): ProviderOptions {
|
||||
return (
|
||||
mergeProviderOptions({ sandbox: { enabled: true, failIfUnavailable: true } }, own) ?? {}
|
||||
);
|
||||
}
|
||||
}
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-options-"));
|
||||
const client = new DefaultsClient();
|
||||
const manager = new AgentManager({
|
||||
clients: { codex: client },
|
||||
registry: new AgentStorage(join(workdir, "agents"), logger),
|
||||
logger,
|
||||
});
|
||||
const created = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {
|
||||
workspaceId: undefined,
|
||||
});
|
||||
|
||||
await manager.setAgentOptions(created.id, { sandbox: { network: { allowed: ["a.example"] } } });
|
||||
|
||||
expect(client.session?.applied).toEqual([
|
||||
{
|
||||
sandbox: { enabled: true, failIfUnavailable: true, network: { allowed: ["a.example"] } },
|
||||
},
|
||||
]);
|
||||
expect(manager.getAgent(created.id)?.config.providerOptions).toEqual({
|
||||
sandbox: { network: { allowed: ["a.example"] } },
|
||||
});
|
||||
});
|
||||
|
||||
test("an option without a control is settable through the patch and readable in the snapshot", async () => {
|
||||
const { manager, client, storage, agentId } = await createOptionsAgent();
|
||||
|
||||
|
||||
@@ -2290,9 +2290,7 @@ export class AgentManager {
|
||||
// a rejection leaves the record, its snapshots and its stored copy as they were.
|
||||
this.pendingOptionCandidates.set(agentId, candidate);
|
||||
try {
|
||||
// Only the agent's own options: a removed key is absent, so the running provider keeps
|
||||
// its setting. `config.json` defaults apply when the session is next opened.
|
||||
await agent.session.applyOptions(candidate);
|
||||
await agent.session.applyOptions(this.liveOptionsFor(agent.provider, candidate));
|
||||
} finally {
|
||||
// Events the provider queued before answering must reach the candidate, not the record.
|
||||
await this.drainSessionEvents(agentId);
|
||||
@@ -2306,6 +2304,16 @@ export class AgentManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* The options a running provider is handed: the keys the agent holds, each resolved against
|
||||
* the `config.json` defaults. A removed key is absent, so the provider keeps its setting;
|
||||
* defaults for absent keys apply when the session is next opened.
|
||||
*/
|
||||
private liveOptionsFor(provider: AgentProvider, own: ProviderOptions): ProviderOptions {
|
||||
const effective = this.effectiveOptionsFor(provider, own);
|
||||
return Object.fromEntries(Object.keys(own).map((key) => [key, effective[key]]));
|
||||
}
|
||||
|
||||
private effectiveOptionsFor(
|
||||
provider: AgentProvider,
|
||||
own: ProviderOptions | undefined,
|
||||
|
||||
Reference in New Issue
Block a user