mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-08 17:34:51 +00:00
Return the draft's options from AgentClient.listControls
listControls now answers { controls, options } like AgentManager.listDraftControls:
the options a session opened from the draft would run with, with config
defaults merged in and anything the provider resolves natively for the ones
nobody set. ACP fills unset feature selects from the probe session's
currentValue, so a fresh Cursor draft shows the Fast selection the agent opens
with instead of no selection. The legacy features response and the MCP draft
listing already project from the manager's result, so they follow.
This commit is contained in:
+1
-1
@@ -174,7 +174,7 @@ Submitted user-message wire items carry the same Paseo ID in `messageId` and `cl
|
||||
|
||||
Provider adapters must terminalize every transient timeline row before emitting the turn's terminal event. Codex may omit the completed `contextCompaction` item when a turn ends during compaction, so its adapter closes any pending root compaction before forwarding `turn_completed`, `turn_failed`, or `turn_canceled`. A terminal turn must never leave the client showing an operation as still loading.
|
||||
|
||||
Draft metadata lookups should avoid creating provider sessions when the upstream provider has top-level APIs for that metadata. Prefer `AgentClient.fetchCatalog`, `listCommands`, or `listControls` over creating a scratch `AgentSession`; scratch sessions can show up as empty native sessions in provider import/history UIs. `fetchCatalog` is the single discovery API for models and modes — provider implementations may use one process, separate upstream calls, or static data internally, but callers outside the provider do not get separate runtime model/mode probes. Draft command listing and scratch-session control listing require an explicit draft model. Do not resolve a default model through catalog discovery. A client-level `listControls` implementation may return controls from an incomplete, model-less draft and owns which features are valid in that state.
|
||||
Draft metadata lookups should avoid creating provider sessions when the upstream provider has top-level APIs for that metadata. Prefer `AgentClient.fetchCatalog`, `listCommands`, or `listControls` over creating a scratch `AgentSession`; scratch sessions can show up as empty native sessions in provider import/history UIs. `fetchCatalog` is the single discovery API for models and modes — provider implementations may use one process, separate upstream calls, or static data internally, but callers outside the provider do not get separate runtime model/mode probes. Draft command listing and scratch-session control listing require an explicit draft model. Do not resolve a default model through catalog discovery. A client-level `listControls` implementation may return controls from an incomplete, model-less draft and owns which features are valid in that state. It returns `{ controls, options }`: the options a session opened from the draft would run with, so a value the provider resolves natively (an ACP select nobody set) shows in the draft the same way it would on the agent.
|
||||
|
||||
Provider session import has its own contract. The picker calls `listImportableSessions` and receives rows only: provider handle, cwd, title, prompt previews, and last activity. Import calls `importSession({ providerHandleId, cwd })` for the selected row and must not call listing again. The provider returns the resumed session, storage config, persistence handle, and hydrated timeline for that one native session; `AgentManager.importProviderSession` seeds the daemon timeline and publishes the Paseo agent only after it is ready.
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import type {
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentCreateSessionOptions,
|
||||
AgentDraftControls,
|
||||
AgentOptionControl,
|
||||
AgentLaunchContext,
|
||||
AgentPromptInput,
|
||||
@@ -61,6 +62,8 @@ import type { ProviderDefinition } from "./provider-registry.js";
|
||||
import { mergeProviderOptions } from "./provider-options.js";
|
||||
|
||||
import { OpenCodeV2AgentClient } from "./providers/opencode/v2/agent.js";
|
||||
import { CursorACPAgentClient } from "./providers/cursor-acp-agent.js";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { V2Harness } from "./providers/opencode/test-utils/v2-harness.js";
|
||||
|
||||
const DESKTOP_OPEN_AGENT_TAB_LABEL = getOpenAgentTabLabel("desktop-client");
|
||||
@@ -2493,9 +2496,9 @@ test("listDraftControls uses client feature listing without a model", async () =
|
||||
return await super.createSession(config);
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
this.featureConfigs.push(config);
|
||||
return [draftControl];
|
||||
return { controls: [draftControl], options: config.providerOptions ?? {} };
|
||||
}
|
||||
}
|
||||
const client = new DraftFeatureClient();
|
||||
@@ -2559,9 +2562,9 @@ test("listDraftControls uses explicit model config without default model fetchin
|
||||
return await super.createSession(config);
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
this.featureConfigs.push(config);
|
||||
return [draftControl];
|
||||
return { controls: [draftControl], options: config.providerOptions ?? {} };
|
||||
}
|
||||
}
|
||||
const client = new DraftFeatureClient();
|
||||
@@ -2591,6 +2594,60 @@ test("listDraftControls uses explicit model config without default model fetchin
|
||||
]);
|
||||
});
|
||||
|
||||
test("listDraftControls reports the Fast selection a fresh Cursor draft opens with", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-draft-cursor-"));
|
||||
const storage = new AgentStorage(join(workdir, "agents"), logger);
|
||||
const fakeAgentPath = fileURLToPath(
|
||||
new URL("./providers/test-utils/fake-acp-agent.mjs", import.meta.url),
|
||||
);
|
||||
const scenario = {
|
||||
options: [
|
||||
{
|
||||
id: "fast",
|
||||
name: "Fast",
|
||||
type: "select",
|
||||
currentValue: "true",
|
||||
options: [
|
||||
{ value: "false", name: "Off" },
|
||||
{ value: "true", name: "Fast" },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
// The registry maps "cursor" onto the generic ACP provider id; this manager has no registry.
|
||||
const client = new CursorACPAgentClient({
|
||||
logger,
|
||||
command: [process.execPath, fakeAgentPath, JSON.stringify(scenario)],
|
||||
});
|
||||
const manager = new AgentManager({ clients: { acp: client }, registry: storage, logger });
|
||||
const controls = [
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept" }),
|
||||
expect.objectContaining({
|
||||
type: "select",
|
||||
key: "fast",
|
||||
choices: [
|
||||
{ value: "false", label: "Off" },
|
||||
{ value: "true", label: "Fast" },
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
// Nobody set Fast, so the draft shows the value the agent opens with.
|
||||
await expect(manager.listDraftControls({ provider: "acp", cwd: workdir })).resolves.toEqual({
|
||||
controls,
|
||||
options: { fast: "true" },
|
||||
});
|
||||
|
||||
// A set value stands over the probe's reading.
|
||||
await expect(
|
||||
manager.listDraftControls({
|
||||
provider: "acp",
|
||||
cwd: workdir,
|
||||
providerOptions: { fast: "false" },
|
||||
}),
|
||||
).resolves.toEqual({ controls, options: { fast: "false" } });
|
||||
});
|
||||
|
||||
test("createAgent injects daemon append system prompt at runtime only", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
type AgentCreateSessionOptions,
|
||||
type AgentResumePurpose,
|
||||
type AgentResumeSessionOptions,
|
||||
type AgentDraftControls,
|
||||
type AgentOptionControl,
|
||||
type AgentLaunchContext,
|
||||
type AgentSlashCommand,
|
||||
@@ -1324,9 +1325,7 @@ export class AgentManager {
|
||||
}
|
||||
}
|
||||
|
||||
async listDraftControls(
|
||||
config: AgentSessionConfig,
|
||||
): Promise<{ controls: AgentOptionControl[]; options: ProviderOptions }> {
|
||||
async listDraftControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
const normalizedConfig = await this.normalizeConfig(config, { resolveDefaultModel: false });
|
||||
const client = this.requireClient(normalizedConfig.provider);
|
||||
const options = this.effectiveOptionsFor(
|
||||
@@ -1344,7 +1343,7 @@ export class AgentManager {
|
||||
}
|
||||
|
||||
if (client.listControls) {
|
||||
return { controls: await client.listControls(normalizedConfig), options };
|
||||
return await client.listControls(normalizedConfig);
|
||||
}
|
||||
|
||||
const session = await client.createSession(normalizedConfig);
|
||||
|
||||
@@ -798,6 +798,11 @@ export interface ResolveAgentDefaultModeInput {
|
||||
signal?: AbortSignal;
|
||||
}
|
||||
|
||||
export interface AgentDraftControls {
|
||||
controls: AgentOptionControl[];
|
||||
options: ProviderOptions;
|
||||
}
|
||||
|
||||
export interface AgentClient {
|
||||
readonly provider: AgentProvider;
|
||||
readonly capabilities: AgentCapabilityFlags;
|
||||
@@ -840,7 +845,12 @@ export interface AgentClient {
|
||||
resolveCreateConfig?(input: ResolveAgentCreateConfigInput): ResolveAgentCreateConfigResult;
|
||||
isCreateConfigUnattended?(input: AgentCreateConfigUnattendedInput): boolean;
|
||||
listCommands?(config: AgentSessionConfig): Promise<AgentSlashCommand[]>;
|
||||
listControls?(config: AgentSessionConfig): Promise<AgentOptionControl[]>;
|
||||
/**
|
||||
* The controls of a draft, with the options a session opened from `config` would run with:
|
||||
* `config.providerOptions` (defaults already merged in) plus what the provider resolves
|
||||
* natively for the ones nobody set.
|
||||
*/
|
||||
listControls?(config: AgentSessionConfig): Promise<AgentDraftControls>;
|
||||
/** Effective options: provider defaults from `config.json` deep-merged with the agent's own. */
|
||||
effectiveOptions?(own: ProviderOptions | undefined): ProviderOptions;
|
||||
listImportableSessions?(
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { ToolPolicy } from "@getpaseo/protocol/agent-types";
|
||||
import { createTestLogger } from "../../test-utils/test-logger.js";
|
||||
import type {
|
||||
AgentClient,
|
||||
AgentDraftControls,
|
||||
AgentOptionControl,
|
||||
AgentModelDefinition,
|
||||
AgentMode,
|
||||
@@ -392,17 +393,20 @@ vi.mock("./providers/cursor-acp-agent.js", () => ({
|
||||
return true;
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
mockState.cursorListFeaturesConfigs.push(config);
|
||||
return [
|
||||
{
|
||||
type: "select",
|
||||
key: "fast",
|
||||
label: "Fast",
|
||||
defaultValue: "false",
|
||||
choices: [{ value: "false", label: "Off" }],
|
||||
},
|
||||
];
|
||||
return {
|
||||
options: config.providerOptions ?? {},
|
||||
controls: [
|
||||
{
|
||||
type: "select",
|
||||
key: "fast",
|
||||
label: "Fast",
|
||||
defaultValue: "false",
|
||||
choices: [{ value: "false", label: "Off" }],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -868,15 +872,18 @@ test("wrapped cursor client lists ACP controls through the inner provider", asyn
|
||||
provider: "cursor",
|
||||
cwd: "/tmp/cursor",
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
type: "select",
|
||||
key: "fast",
|
||||
label: "Fast",
|
||||
defaultValue: "false",
|
||||
choices: [{ value: "false", label: "Off" }],
|
||||
},
|
||||
]);
|
||||
).resolves.toEqual({
|
||||
controls: [
|
||||
{
|
||||
type: "select",
|
||||
key: "fast",
|
||||
label: "Fast",
|
||||
defaultValue: "false",
|
||||
choices: [{ value: "false", label: "Off" }],
|
||||
},
|
||||
],
|
||||
options: {},
|
||||
});
|
||||
expect(mockState.cursorListFeaturesConfigs).toEqual([
|
||||
{
|
||||
provider: "acp",
|
||||
|
||||
@@ -2334,9 +2334,12 @@ describe("ACPAgentClient config features", () => {
|
||||
cwd: "/tmp/acp-features",
|
||||
providerOptions: { auto_accept: true },
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept", label: "Auto Accept" }),
|
||||
]);
|
||||
).resolves.toEqual({
|
||||
controls: [
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept", label: "Auto Accept" }),
|
||||
],
|
||||
options: { auto_accept: true },
|
||||
});
|
||||
});
|
||||
|
||||
test("derives controls from configured ACP select options", async () => {
|
||||
@@ -2364,22 +2367,26 @@ describe("ACPAgentClient config features", () => {
|
||||
configFeatureOptions: [COPILOT_AGENT_FEATURE_OPTION],
|
||||
});
|
||||
|
||||
// The select nobody set reads as the agent opened it.
|
||||
await expect(
|
||||
client.listControls({
|
||||
provider: "copilot",
|
||||
cwd: "/tmp/acp-features",
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept" }),
|
||||
expect.objectContaining({
|
||||
type: "select",
|
||||
key: "agent",
|
||||
choices: [
|
||||
expect.objectContaining({ value: "", label: "Default" }),
|
||||
expect.objectContaining({ value: "Probe Agent", label: "Probe Agent" }),
|
||||
],
|
||||
}),
|
||||
]);
|
||||
).resolves.toEqual({
|
||||
controls: [
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept" }),
|
||||
expect.objectContaining({
|
||||
type: "select",
|
||||
key: "agent",
|
||||
choices: [
|
||||
expect.objectContaining({ value: "", label: "Default" }),
|
||||
expect.objectContaining({ value: "Probe Agent", label: "Probe Agent" }),
|
||||
],
|
||||
}),
|
||||
],
|
||||
options: { agent: "Probe Agent" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
type AgentCapabilityFlags,
|
||||
type AgentClient,
|
||||
type AgentCreateConfigUnattendedInput,
|
||||
type AgentDraftControls,
|
||||
type AgentOptionControl,
|
||||
type AgentLaunchContext,
|
||||
type AgentMetadata,
|
||||
@@ -893,6 +894,19 @@ export function deriveControlsFromACP(
|
||||
});
|
||||
}
|
||||
|
||||
/** The feature selects as the agent reports them, keyed by feature id. */
|
||||
function featureValuesFromACP(
|
||||
configOptions: SessionConfigOption[] | null | undefined,
|
||||
featureOptions: ACPConfigFeatureOption[],
|
||||
): Record<string, string> {
|
||||
const values: Record<string, string> = {};
|
||||
for (const featureOption of featureOptions) {
|
||||
const current = findSelectConfigFeatureOption(configOptions, featureOption)?.currentValue;
|
||||
if (current != null) values[featureOption.id] = current;
|
||||
}
|
||||
return values;
|
||||
}
|
||||
|
||||
function isACPAutoAcceptEnabled(config: AgentSessionConfig): boolean {
|
||||
return config.providerOptions?.[ACP_AUTO_ACCEPT_FEATURE_ID] === true;
|
||||
}
|
||||
@@ -1209,9 +1223,10 @@ export class ACPAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
const options = config.providerOptions ?? {};
|
||||
if (this.configFeatureOptions.length === 0) {
|
||||
return [ACP_AUTO_ACCEPT_CONTROL];
|
||||
return { controls: [ACP_AUTO_ACCEPT_CONTROL], options };
|
||||
}
|
||||
|
||||
this.assertProvider(config);
|
||||
@@ -1226,10 +1241,18 @@ export class ACPAgentClient implements AgentClient {
|
||||
);
|
||||
probeSessionId = response.sessionId;
|
||||
const transformed = this.transformSessionResponse(response);
|
||||
return [
|
||||
ACP_AUTO_ACCEPT_CONTROL,
|
||||
...deriveControlsFromACP(transformed.configOptions, this.configFeatureOptions),
|
||||
];
|
||||
return {
|
||||
controls: [
|
||||
ACP_AUTO_ACCEPT_CONTROL,
|
||||
...deriveControlsFromACP(transformed.configOptions, this.configFeatureOptions),
|
||||
],
|
||||
// The probe opened with the agent's own defaults, which a session would overwrite with
|
||||
// the set values, so those stand over the probe's reading.
|
||||
options: {
|
||||
...featureValuesFromACP(transformed.configOptions, this.configFeatureOptions),
|
||||
...options,
|
||||
},
|
||||
};
|
||||
} finally {
|
||||
await this.closeProbe(probe, probeSessionId);
|
||||
}
|
||||
@@ -2502,15 +2525,7 @@ export class ACPAgentSession implements AgentSession, ACPClient {
|
||||
|
||||
/** The selects as the provider reports them now. */
|
||||
private featureValues(): Record<string, string> {
|
||||
const values: Record<string, string> = {};
|
||||
for (const featureOption of this.configFeatureOptions) {
|
||||
const current = findSelectConfigFeatureOption(
|
||||
this.configOptions,
|
||||
featureOption,
|
||||
)?.currentValue;
|
||||
if (current != null) values[featureOption.id] = current;
|
||||
}
|
||||
return values;
|
||||
return featureValuesFromACP(this.configOptions, this.configFeatureOptions);
|
||||
}
|
||||
|
||||
/** The selects whose value is not what `expected` says, including ones it does not name. */
|
||||
|
||||
@@ -1153,7 +1153,10 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-opus-4-8",
|
||||
}),
|
||||
).resolves.toEqual([expect.objectContaining({ key: "fast_mode", type: "toggle" })]);
|
||||
).resolves.toEqual({
|
||||
controls: [expect.objectContaining({ key: "fast_mode", type: "toggle" })],
|
||||
options: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1161,7 +1164,10 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-opus-4-8[1m]",
|
||||
}),
|
||||
).resolves.toEqual([expect.objectContaining({ key: "fast_mode", type: "toggle" })]);
|
||||
).resolves.toEqual({
|
||||
controls: [expect.objectContaining({ key: "fast_mode", type: "toggle" })],
|
||||
options: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1169,7 +1175,10 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-opus-4-8-20260101",
|
||||
}),
|
||||
).resolves.toEqual([expect.objectContaining({ key: "fast_mode", type: "toggle" })]);
|
||||
).resolves.toEqual({
|
||||
controls: [expect.objectContaining({ key: "fast_mode", type: "toggle" })],
|
||||
options: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1177,7 +1186,10 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-opus-5",
|
||||
}),
|
||||
).resolves.toEqual([expect.objectContaining({ key: "fast_mode", type: "toggle" })]);
|
||||
).resolves.toEqual({
|
||||
controls: [expect.objectContaining({ key: "fast_mode", type: "toggle" })],
|
||||
options: {},
|
||||
});
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1185,7 +1197,7 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "openrouter/anthropic/claude-opus-4-8",
|
||||
}),
|
||||
).resolves.toEqual([]);
|
||||
).resolves.toEqual({ controls: [], options: {} });
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1193,7 +1205,7 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-sonnet-5",
|
||||
}),
|
||||
).resolves.toEqual([]);
|
||||
).resolves.toEqual({ controls: [], options: {} });
|
||||
|
||||
await expect(
|
||||
client.listControls({
|
||||
@@ -1201,7 +1213,7 @@ describe("ClaudeAgentSession features", () => {
|
||||
cwd: process.cwd(),
|
||||
model: "claude-sonnet-4-6",
|
||||
}),
|
||||
).resolves.toEqual([]);
|
||||
).resolves.toEqual({ controls: [], options: {} });
|
||||
});
|
||||
|
||||
test("opens with Fast off on a model that has none, so unrelated options still apply", async () => {
|
||||
|
||||
@@ -106,6 +106,7 @@ import {
|
||||
type AgentCapabilityFlags,
|
||||
type AgentClient,
|
||||
type AgentCreateSessionOptions,
|
||||
type AgentDraftControls,
|
||||
type AgentOptionControl,
|
||||
type AgentLaunchContext,
|
||||
type AgentMetadata,
|
||||
@@ -1760,8 +1761,11 @@ export class ClaudeAgentClient implements AgentClient {
|
||||
});
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
return buildClaudeControls(this.assertConfig(config).model);
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
return {
|
||||
controls: buildClaudeControls(this.assertConfig(config).model),
|
||||
options: config.providerOptions ?? {},
|
||||
};
|
||||
}
|
||||
|
||||
async listImportableSessions(
|
||||
|
||||
@@ -167,28 +167,31 @@ describe("CursorACPAgentClient model discovery", () => {
|
||||
provider: "acp",
|
||||
cwd: "/tmp/cursor",
|
||||
}),
|
||||
).resolves.toEqual([
|
||||
{
|
||||
type: "toggle",
|
||||
key: "auto_accept",
|
||||
label: "Auto Accept",
|
||||
description: "Automatically approves ACP permission prompts.",
|
||||
tooltip: "Auto accept permission prompts",
|
||||
icon: "shield-check",
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
key: CURSOR_FAST_FEATURE_OPTION.id,
|
||||
label: "Fast",
|
||||
description: "Cursor fast mode",
|
||||
tooltip: "Select Cursor fast mode",
|
||||
icon: "zap",
|
||||
choices: [
|
||||
{ value: "false", label: "Off" },
|
||||
{ value: "true", label: "Fast" },
|
||||
],
|
||||
},
|
||||
]);
|
||||
).resolves.toEqual({
|
||||
controls: [
|
||||
{
|
||||
type: "toggle",
|
||||
key: "auto_accept",
|
||||
label: "Auto Accept",
|
||||
description: "Automatically approves ACP permission prompts.",
|
||||
tooltip: "Auto accept permission prompts",
|
||||
icon: "shield-check",
|
||||
},
|
||||
{
|
||||
type: "select",
|
||||
key: CURSOR_FAST_FEATURE_OPTION.id,
|
||||
label: "Fast",
|
||||
description: "Cursor fast mode",
|
||||
tooltip: "Select Cursor fast mode",
|
||||
icon: "zap",
|
||||
choices: [
|
||||
{ value: "false", label: "Off" },
|
||||
{ value: "true", label: "Fast" },
|
||||
],
|
||||
},
|
||||
],
|
||||
options: { fast: "false" },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import stripAnsi from "strip-ansi";
|
||||
import {
|
||||
type AgentCapabilityFlags,
|
||||
type AgentClient,
|
||||
type AgentDraftControls,
|
||||
type AgentOptionControl,
|
||||
type AgentLaunchContext,
|
||||
type AgentMetadata,
|
||||
@@ -2584,8 +2585,8 @@ export class OmpAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
async listControls(_config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
return [];
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
return { controls: [], options: config.providerOptions ?? {} };
|
||||
}
|
||||
|
||||
async listImportableSessions(
|
||||
|
||||
@@ -115,15 +115,18 @@ describe("OpenCode auto_accept feature", () => {
|
||||
serverManager: runtime,
|
||||
createClient: runtime.createClient,
|
||||
});
|
||||
const controls = await client.listControls({
|
||||
provider: "opencode",
|
||||
cwd: "/tmp/project",
|
||||
providerOptions: { auto_accept: true },
|
||||
await expect(
|
||||
client.listControls({
|
||||
provider: "opencode",
|
||||
cwd: "/tmp/project",
|
||||
providerOptions: { auto_accept: true },
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
controls: [
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept", label: "Auto Accept" }),
|
||||
],
|
||||
options: { auto_accept: true },
|
||||
});
|
||||
|
||||
expect(controls).toEqual([
|
||||
expect.objectContaining({ type: "toggle", key: "auto_accept", label: "Auto Accept" }),
|
||||
]);
|
||||
});
|
||||
|
||||
test("keeps legacy full-access as an alias for build plus auto accept", async () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
type AgentCapabilityFlags,
|
||||
type AgentClient,
|
||||
type AgentCreateSessionOptions,
|
||||
type AgentDraftControls,
|
||||
type AgentOptionControl,
|
||||
type AgentLaunchContext,
|
||||
type AgentMode,
|
||||
@@ -1686,9 +1687,9 @@ export class OpenCodeAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
this.assertConfig(config);
|
||||
return [OPENCODE_AUTO_ACCEPT_CONTROL];
|
||||
return { controls: [OPENCODE_AUTO_ACCEPT_CONTROL], options: config.providerOptions ?? {} };
|
||||
}
|
||||
|
||||
async listImportableSessions(
|
||||
|
||||
@@ -100,13 +100,13 @@ test.each([
|
||||
});
|
||||
try {
|
||||
await writeFile(script, initialSource);
|
||||
expect((await client.listControls({ provider: "opencode", cwd: root }))[0]?.label).toBe(
|
||||
"Auto Accept",
|
||||
);
|
||||
expect(
|
||||
(await client.listControls({ provider: "opencode", cwd: root })).controls[0]?.label,
|
||||
).toBe("Auto Accept");
|
||||
await writeFile(script, 'console.log("opencode v2.0.10")');
|
||||
expect((await client.listControls({ provider: "opencode", cwd: root }))[0]?.label).toBe(
|
||||
"Auto-accept",
|
||||
);
|
||||
expect(
|
||||
(await client.listControls({ provider: "opencode", cwd: root })).controls[0]?.label,
|
||||
).toBe("Auto-accept");
|
||||
} finally {
|
||||
await client.shutdown();
|
||||
await rm(root, { recursive: true, force: true });
|
||||
@@ -221,7 +221,7 @@ test("selects v2 without probing the binary when an external server is configure
|
||||
env: { PASEO_OPENCODE_SERVER_URL: "http://127.0.0.1:4096" },
|
||||
});
|
||||
try {
|
||||
const controls = await client.listControls({ provider: "opencode", cwd: "/tmp/project" });
|
||||
const { controls } = await client.listControls({ provider: "opencode", cwd: "/tmp/project" });
|
||||
expect(controls.map((control) => control.label)).toEqual(["Auto-accept"]);
|
||||
} finally {
|
||||
await client.shutdown();
|
||||
|
||||
@@ -17,7 +17,7 @@ import type {
|
||||
AgentCapabilityFlags,
|
||||
AgentClient,
|
||||
AgentCreateSessionOptions,
|
||||
AgentOptionControl,
|
||||
AgentDraftControls,
|
||||
AgentLaunchContext,
|
||||
AgentPersistenceHandle,
|
||||
AgentSession,
|
||||
@@ -222,8 +222,8 @@ export class OpenCodeV2AgentClient implements AgentClient {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async listControls(): Promise<AgentOptionControl[]> {
|
||||
return controls;
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
return { controls, options: config.providerOptions ?? {} };
|
||||
}
|
||||
async listCommands(config: AgentSessionConfig) {
|
||||
const connection = await this.runtime.acquire();
|
||||
|
||||
@@ -3084,7 +3084,7 @@ describe("PiRpcAgentClient", () => {
|
||||
|
||||
await expect(
|
||||
client.listControls(createConfig({ model: "openrouter/test/model" })),
|
||||
).resolves.toEqual([]);
|
||||
).resolves.toEqual({ controls: [], options: {} });
|
||||
|
||||
expect(pi.recordedLaunches).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import { z } from "zod";
|
||||
import {
|
||||
type AgentCapabilityFlags,
|
||||
type AgentClient,
|
||||
type AgentOptionControl,
|
||||
type AgentDraftControls,
|
||||
type AgentLaunchContext,
|
||||
type AgentMetadata,
|
||||
type AgentMode,
|
||||
@@ -2833,8 +2833,8 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
async listControls(_config: AgentSessionConfig): Promise<AgentOptionControl[]> {
|
||||
return [];
|
||||
async listControls(config: AgentSessionConfig): Promise<AgentDraftControls> {
|
||||
return { controls: [], options: config.providerOptions ?? {} };
|
||||
}
|
||||
|
||||
async listImportableSessions(
|
||||
|
||||
Reference in New Issue
Block a user