feat: add ultracode session config option (#1438)

Adds an **Ultracode** session config option. It turns on Claude Code's
dynamic workflows for every task, at any effort level. Clients that
support boolean config options get a native switch; the rest get an
On/Off select. This follows the Fast mode pattern.

## How the CLI handles ultracode (2.1.310, checked against the bundled
binary and a live session)

- **Which models:** ultracode works on exactly the models that support
`xhigh` effort. The CLI uses the same check for both. So Opus 4.7+,
Sonnet 5+, Haiku 5.5 and Fable qualify; Opus 4.6 and Haiku 4.5 don't.
Effort level no longer matters.
- **Workflows:** dynamic workflows must be enabled. They are off by
default on Pro plans, and org or managed policy can turn them off.
- **State reporting:** `query.getSettings()` returns
`applied.ultracodeAvailable` and `applied.ultracodeRequested`. The CLI's
schema says hosts should "offer an Ultracode control only while this is
true". This method exists at runtime but is missing from the SDK's
`.d.ts`, so the adapter reads it with a cast and falls back to settings
and model data when it is absent.
- **Unsupported requests:** `applyFlagSettings({ultracode})` is never
validated. The request is stored and stays in place across model
switches; it takes effect whenever the model supports it.
- **Effort changes:** an `effortLevel` change sent without an
`ultracode` key turns ultracode off.

## Behavior

- **Visibility:** at session start, one `getSettings()` call decides
availability and the starting value. Model switches combine the
workflows result learned at start with the new model's
`supportedEffortLevels`, so the switch path makes no extra control
request.
- **Value:** the session tracks the user's choice, like
`fastModeEnabled`. The option hides on unsupported models and comes back
with the same value.
- **Effort applies:** `effortFlagSettings` now carries the session's
toggle instead of `settings.ultracode`. This applies at the initial
apply, on model switch, and from the effort picker, so effort changes no
longer turn ultracode off.
- **Clear context:** the clear-context restart restores the toggle, as
it does Fast mode.

## Known limitation

If a user types `/effort ultracode on` as a prompt, the toggle does not
update. The adapter already doesn't pick up effort changes typed as
`/effort` commands.

## Testing

- New `src/tests/ultracode-config.test.ts`. It covers the helpers and
session behavior: seeding from the CLI's report, hiding while workflows
are off, following the model's support, keeping the value across effort
and model switches, and rejected applies.
- New clear-context restore test. `effortFlagSettings` tests updated.
- Live probe against CLI 2.1.310 confirmed the `getSettings` fields, the
`xhigh` proxy (`claude-opus-4-6` reports unavailable while the request
stays in place), and the effort/key behavior.
- `npm run check`, `npm run build`, `npm run test:run` all pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ben Brandt
2026-10-26 13:34:13 +01:00
committed by GitHub
parent a4957d17c1
commit de96cef382
12 changed files with 560 additions and 26 deletions

View File

@@ -17,6 +17,7 @@ This tool implements an ACP agent by using the official [Claude Agent SDK](https
- Interactive (and background) terminals - Interactive (and background) terminals
- Custom [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands) - Custom [Slash commands](https://docs.anthropic.com/en/docs/claude-code/slash-commands)
- Client MCP servers - Client MCP servers
- An Ultracode session config option, which turns on Claude Code's dynamic workflows for every task. It appears when dynamic workflows are enabled and the model supports ultracode (any model with `xhigh` effort)
- `/mcp` in the chat: the MCP server status as a list. The adapter runs `/mcp reconnect`, `/mcp enable`, and `/mcp disable` through the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation - `/mcp` in the chat: the MCP server status as a list. The adapter runs `/mcp reconnect`, `/mcp enable`, and `/mcp disable` through the SDK control API, because Claude Code refuses them in SDK mode. A reconnect of an ACP server that needs authentication starts MCP OAuth through URL elicitation
- Session-scoped long-running goals for AIR through the [goal extension](docs/air-extensions.md#goal) under `_meta.jetbrains.air.goal` - Session-scoped long-running goals for AIR through the [goal extension](docs/air-extensions.md#goal) under `_meta.jetbrains.air.goal`
- Structured errors, recovery, and warnings through the opt-in [session failure extension](docs/air-extensions.md#session-failure) - Structured errors, recovery, and warnings through the opt-in [session failure extension](docs/air-extensions.md#session-failure)

View File

@@ -707,7 +707,7 @@ A non-empty plan also gets one clear-context choice with the same priority.
After that choice, the adapter stops the old private Claude query and hides that internal rejection. After that choice, the adapter stops the old private Claude query and hides that internal rejection.
It starts a new private query under the same ACP session. It starts a new private query under the same ACP session.
It continues the turn with `Implement the following plan:` and the complete plan. It continues the turn with `Implement the following plan:` and the complete plan.
The mode, model, agent, effort, Fast mode, and turn usage stay the same. The mode, model, agent, effort, Fast mode, ultracode, and turn usage stay the same.
The SDK cannot apply `allowedPrompts` again, so those hints do not survive the handoff. The SDK cannot apply `allowedPrompts` again, so those hints do not survive the handoff.
The adapter does not allow a callback only because the session mode is `bypassPermissions`. The adapter does not allow a callback only because the session mode is `bypassPermissions`.

View File

@@ -365,6 +365,15 @@ import {
mergeEffortSettings, mergeEffortSettings,
settingsEffortForModel, settingsEffortForModel,
} from "./session-effort.js"; } from "./session-effort.js";
import {
createUltracodeConfigOption,
readUltracodeSettings,
resolveUltracodeEnabled,
ULTRACODE_CONFIG_ID,
ultracodeSupported,
ultracodeWorkflowsEnabled,
type UltracodeOptionState,
} from "./session-ultracode.js";
export { EFFORT_CONFIG_ID, settingsEffortForModel } from "./session-effort.js"; export { EFFORT_CONFIG_ID, settingsEffortForModel } from "./session-effort.js";
export { export {
@@ -1191,6 +1200,15 @@ export type Session = {
* user's intent so it persists across model switches; the Fast mode config * user's intent so it persists across model switches; the Fast mode config
* option is only surfaced while the selected model supports it. */ * option is only surfaced while the selected model supports it. */
fastModeEnabled: boolean; fastModeEnabled: boolean;
/** Whether ultracode is turned on for this session. Tracked as the user's
* intent, like `fastModeEnabled`: the CLI keeps the request across model
* switches and applies it whenever the model supports ultracode, while the
* config option is only surfaced on supporting models. */
ultracodeEnabled: boolean;
/** Whether dynamic workflows are enabled, learned from the CLI's
* `get_settings` at session start; undefined when unknown. Combined with
* the model's support to decide whether to show the ultracode option. */
ultracodeWorkflowsEnabled?: boolean;
/** The non-default effort the user picked through the ACP picker this /** The non-default effort the user picked through the ACP picker this
* session. A pin lives at the SDK's flag layer, which overrides the CLI's * session. A pin lives at the SDK's flag layer, which overrides the CLI's
* persisted effort (including the per-model `modelSettings` entries), so it * persisted effort (including the per-model `modelSettings` entries), so it
@@ -2801,6 +2819,7 @@ export class ClaudeAcpAgent {
return session; return session;
}, },
applyFastMode: (session, enabled) => this.applyFastMode(session, enabled), applyFastMode: (session, enabled) => this.applyFastMode(session, enabled),
applyUltracode: (session, enabled) => this.applyUltracode(session, enabled),
sessionUpdate: (notification) => this.client.sessionUpdate(notification), sessionUpdate: (notification) => this.client.sessionUpdate(notification),
supportsPlanOperations: () => this.clientCapabilities?.plan != null, supportsPlanOperations: () => this.clientCapabilities?.plan != null,
ensureConsumer: (session, id) => this.ensureConsumer(session, id), ensureConsumer: (session, id) => this.ensureConsumer(session, id),
@@ -9203,13 +9222,18 @@ export class ClaudeAcpAgent {
throw new Error(`Unknown config option: ${params.configId}`); throw new Error(`Unknown config option: ${params.configId}`);
} }
// Fast mode carries a boolean value (for Clients that opted into boolean // Fast mode and ultracode carry a boolean value (for Clients that opted
// config options) or the "on"/"off" select fallback, so it bypasses the // into boolean config options) or the "on"/"off" select fallback, so they
// bypass the
// string-only validation the select-style options below rely on. // string-only validation the select-style options below rely on.
if (params.configId === FAST_MODE_CONFIG_ID) { if (params.configId === FAST_MODE_CONFIG_ID) {
await this.applyFastMode(session, resolveFastModeEnabled(params)); await this.applyFastMode(session, resolveFastModeEnabled(params));
return { configOptions: session.configOptions }; return { configOptions: session.configOptions };
} }
if (params.configId === ULTRACODE_CONFIG_ID) {
await this.applyUltracode(session, resolveUltracodeEnabled(params));
return { configOptions: session.configOptions };
}
if (typeof params.value !== "string") { if (typeof params.value !== "string") {
throw new Error(`Invalid value for config option ${params.configId}: ${params.value}`); throw new Error(`Invalid value for config option ${params.configId}: ${params.value}`);
@@ -11038,6 +11062,13 @@ export class ClaudeAcpAgent {
{ {
useRecommendedValue, useRecommendedValue,
}, },
{
// Like Fast mode, the toggle hides on models without ultracode
// support and comes back with the retained intent on one that has it.
supported: ultracodeSupported(session.ultracodeWorkflowsEnabled, newModelInfo),
enabled: session.ultracodeEnabled,
useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities),
},
); );
// Opted-in clients apply the concrete displayed effort on every switch, // Opted-in clients apply the concrete displayed effort on every switch,
@@ -11064,10 +11095,7 @@ export class ClaudeAcpAgent {
// deliberately apply their concrete displayed effort. // deliberately apply their concrete displayed effort.
effortFlagSettings( effortFlagSettings(
useRecommendedValue ? newEffort : undefined, useRecommendedValue ? newEffort : undefined,
mergeEffortSettings( session.ultracodeEnabled,
session.settingsManager.getSettings(),
session.effortSettingsOverride,
),
), ),
); );
session.effortPinnedLevel = effortPinnedForNewModel ? pinnedEffort : undefined; session.effortPinnedLevel = effortPinnedForNewModel ? pinnedEffort : undefined;
@@ -11136,15 +11164,7 @@ export class ClaudeAcpAgent {
} else if (configId === EFFORT_CONFIG_ID) { } else if (configId === EFFORT_CONFIG_ID) {
// Apply first so a rejected control request cannot leave the displayed // Apply first so a rejected control request cannot leave the displayed
// value ahead of the SDK flag layer. // value ahead of the SDK flag layer.
await session.query.applyFlagSettings( await session.query.applyFlagSettings(effortFlagSettings(value, session.ultracodeEnabled));
effortFlagSettings(
value,
mergeEffortSettings(
session.settingsManager.getSettings(),
session.effortSettingsOverride,
),
),
);
session.configOptions = session.configOptions.map((o) => session.configOptions = session.configOptions.map((o) =>
o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o, o.id === configId && typeof o.currentValue === "string" ? { ...o, currentValue: value } : o,
); );
@@ -11226,6 +11246,22 @@ export class ClaudeAcpAgent {
this.refreshFastModeOption(session, enabled); this.refreshFastModeOption(session, enabled);
} }
/** Turn ultracode on or off for a session. Sends an explicit boolean, which
* leaves the effort level alone (`null` would reset the flag layer instead).
* The SDK flag is applied first so a rejected request leaves the session
* state and option untouched. */
private async applyUltracode(session: Session, enabled: boolean): Promise<void> {
await session.query.applyFlagSettings({ ultracode: enabled });
session.ultracodeEnabled = enabled;
const refreshed = createUltracodeConfigOption(
enabled,
clientSupportsBooleanConfigOptions(this.clientCapabilities),
);
session.configOptions = session.configOptions.map((o) =>
o.id === ULTRACODE_CONFIG_ID ? refreshed : o,
);
}
/** Reconcile the session's Fast mode toggle with an SDK-reported /** Reconcile the session's Fast mode toggle with an SDK-reported
* `fast_mode_state` (delivered on `system`/init and on user-turn `result`s). * `fast_mode_state` (delivered on `system`/init and on user-turn `result`s).
* The SDK can flip fast mode independently of the user — e.g. back to `on` * The SDK can flip fast mode independently of the user — e.g. back to `on`
@@ -12064,6 +12100,21 @@ export class ClaudeAcpAgent {
settingsManager.getSettings(), settingsManager.getSettings(),
configuredSettingsObject, configuredSettingsObject,
); );
// The CLI reports whether ultracode can run (workflows enabled and a
// supporting model) and whether the session already requests it (an
// `ultracode: true` setting). Without a report, fall back to settings.
const ultracodeReport = await readUltracodeSettings(q);
timing.phase("ultracode");
const ultracodeEnabled = ultracodeReport?.requested ?? effortSettings.ultracode === true;
const workflowsEnabled = ultracodeWorkflowsEnabled(ultracodeReport, currentModelInfo);
const ultracode: UltracodeOptionState = {
supported:
ultracodeReport?.available ?? ultracodeSupported(workflowsEnabled, currentModelInfo),
enabled: ultracodeEnabled,
useBooleanOption: clientSupportsBooleanConfigOptions(this.clientCapabilities),
};
const configOptions = buildConfigOptions( const configOptions = buildConfigOptions(
modes, modes,
models, models,
@@ -12075,10 +12126,11 @@ export class ClaudeAcpAgent {
{ {
useRecommendedValue, useRecommendedValue,
}, },
ultracode,
); );
const initialEffort = configOptions.find((option) => option.id === EFFORT_CONFIG_ID); const initialEffort = configOptions.find((option) => option.id === EFFORT_CONFIG_ID);
if (useRecommendedValue && typeof initialEffort?.currentValue === "string") { if (useRecommendedValue && typeof initialEffort?.currentValue === "string") {
await q.applyFlagSettings(effortFlagSettings(initialEffort.currentValue, effortSettings)); await q.applyFlagSettings(effortFlagSettings(initialEffort.currentValue, ultracodeEnabled));
} }
// Seed the context window without awaited IPC. The cached authoritative // Seed the context window without awaited IPC. The cached authoritative
// window from a prior turn wins (`result.modelUsage`, cross-session), // window from a prior turn wins (`result.modelUsage`, cross-session),
@@ -12146,6 +12198,8 @@ export class ClaudeAcpAgent {
currentAgent, currentAgent,
fastModeEnabled, fastModeEnabled,
fastModeDisabledReason, fastModeDisabledReason,
ultracodeEnabled,
ultracodeWorkflowsEnabled: workflowsEnabled,
abortController, abortController,
emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false, emitRawSDKMessages: sessionMeta?.claudeCode?.emitRawSDKMessages ?? false,
forwardSubagentText, forwardSubagentText,
@@ -12975,6 +13029,7 @@ export function buildConfigOptions(
currentAgent: string = DEFAULT_AGENT_ID, currentAgent: string = DEFAULT_AGENT_ID,
fastMode?: FastModeOptionState, fastMode?: FastModeOptionState,
presentation?: ConfigOptionPresentation, presentation?: ConfigOptionPresentation,
ultracode?: UltracodeOptionState,
): SessionConfigOption[] { ): SessionConfigOption[] {
const options: SessionConfigOption[] = [SessionModeManager.configOption(modes)]; const options: SessionConfigOption[] = [SessionModeManager.configOption(modes)];
// A gateway that lists none of the SDK's models leaves no model to offer, so // A gateway that lists none of the SDK's models leaves no model to offer, so
@@ -13005,6 +13060,12 @@ export function buildConfigOptions(
); );
} }
// Surface the ultracode toggle only while the CLI can run it: dynamic
// workflows are enabled and the current model supports it.
if (ultracode?.supported) {
options.push(createUltracodeConfigOption(ultracode.enabled, ultracode.useBooleanOption));
}
// Only surface the Agent picker when there's a real choice — i.e. the user // Only surface the Agent picker when there's a real choice — i.e. the user
// has configured at least one custom agent (built-ins are filtered out in // has configured at least one custom agent (built-ins are filtered out in
// discoverCustomAgents). With none configured, "Default" would be the only // discoverCustomAgents). With none configured, "Default" would be the only

View File

@@ -39,6 +39,7 @@ export type ClearContextSession<Turn extends ClearContextTurn = ClearContextTurn
configOptions: SessionConfigOption[]; configOptions: SessionConfigOption[];
currentAgent: string; currentAgent: string;
fastModeEnabled: boolean; fastModeEnabled: boolean;
ultracodeEnabled: boolean;
effortPinnedLevel?: string; effortPinnedLevel?: string;
activeTurn?: Turn | null; activeTurn?: Turn | null;
turnQueue?: Turn[]; turnQueue?: Turn[];
@@ -59,6 +60,7 @@ export type ClearContextCoordinatorHost<
options: { publicSessionId: string; permissionMode: PermissionMode }, options: { publicSessionId: string; permissionMode: PermissionMode },
): Promise<Session>; ): Promise<Session>;
applyFastMode(session: Session, enabled: boolean): Promise<void>; applyFastMode(session: Session, enabled: boolean): Promise<void>;
applyUltracode(session: Session, enabled: boolean): Promise<void>;
publishSessionState( publishSessionState(
sessionId: string, sessionId: string,
mode: PermissionMode, mode: PermissionMode,
@@ -146,6 +148,16 @@ export async function continuePlanInFreshContext<
} }
assertRestartActive(freshSession); assertRestartActive(freshSession);
} }
// The fresh query seeds ultracode from settings only, so a toggle made
// during the old session would otherwise be lost.
if (oldSession.ultracodeEnabled !== freshSession.ultracodeEnabled) {
try {
await host.applyUltracode(freshSession, oldSession.ultracodeEnabled);
} catch (error) {
host.logError("Failed to restore ultracode after clearing context:", error);
}
assertRestartActive(freshSession);
}
oldSession.activeTurn = null; oldSession.activeTurn = null;
oldSession.turnQueue = []; oldSession.turnQueue = [];

View File

@@ -2,3 +2,4 @@
export const DEFAULT_MODEL_ID = "default"; export const DEFAULT_MODEL_ID = "default";
export const DEFAULT_AGENT_ID = "default"; export const DEFAULT_AGENT_ID = "default";
export const EFFORT_CONFIG_ID = "effort"; export const EFFORT_CONFIG_ID = "effort";
export const ULTRACODE_CONFIG_ID = "ultracode";

View File

@@ -14,14 +14,15 @@ export function toSdkEffortLevel(value: string | undefined): EffortLevel | null
// Since CLI 2.1.284 an `effortLevel` that changes the level, sent without an // Since CLI 2.1.284 an `effortLevel` that changes the level, sent without an
// `ultracode` key, turns ultracode off. The CLI's own effort control keeps // `ultracode` key, turns ultracode off. The CLI's own effort control keeps
// ultracode on at any level, so carry a settings-requested ultracode along // ultracode on at any level, so send `ultracode: true` with every effort apply
// with every effort apply. // while the session has ultracode on. When it is off, the key is omitted: an
// effort change then leaves ultracode off, as it already was.
export function effortFlagSettings( export function effortFlagSettings(
value: string | undefined, value: string | undefined,
settings: Settings, ultracode: boolean,
): { effortLevel: EffortLevel | null; ultracode?: true } { ): { effortLevel: EffortLevel | null; ultracode?: true } {
const effortLevel = toSdkEffortLevel(value); const effortLevel = toSdkEffortLevel(value);
return settings.ultracode === true ? { effortLevel, ultracode: true } : { effortLevel }; return ultracode ? { effortLevel, ultracode: true } : { effortLevel };
} }
function canonicalizeModelSettingsKey(value: string): string { function canonicalizeModelSettingsKey(value: string): string {

136
src/session-ultracode.ts Normal file
View File

@@ -0,0 +1,136 @@
import type {
SessionConfigOption,
SetSessionConfigOptionRequest,
} from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo, Query } from "@anthropic-ai/claude-agent-sdk";
import { ULTRACODE_CONFIG_ID } from "./session-config-ids.js";
export { ULTRACODE_CONFIG_ID } from "./session-config-ids.js";
/** Select-fallback values for clients without boolean config options. */
export const ULTRACODE_ON = "on";
export const ULTRACODE_OFF = "off";
const ULTRACODE_DESCRIPTION = "Dynamic workflows on every task, at any effort level";
/** Ultracode state from the CLI's `get_settings` control response
* (`applied.ultracodeAvailable` / `applied.ultracodeRequested`). Each field is
* undefined when the CLI does not report it. */
export type UltracodeSettingsReport = {
/** Workflows are enabled and the current model supports ultracode. */
available?: boolean;
/** Ultracode is turned on for the session, whether or not it can take
* effect on the current model. */
requested?: boolean;
};
// `Query.getSettings()` exists at runtime (sdk.mjs) but is missing from the
// SDK's type declarations as of 0.3.310.
type GetSettingsQuery = Query & {
getSettings?: () => Promise<{
applied?: { ultracodeAvailable?: unknown; ultracodeRequested?: unknown };
}>;
};
/** Ask the CLI whether ultracode is available and requested for the session.
* Returns undefined when the SDK has no `getSettings` or the request fails;
* callers then fall back to {@link modelSupportsUltracode}. */
export async function readUltracodeSettings(
query: Query,
): Promise<UltracodeSettingsReport | undefined> {
const getSettings = (query as GetSettingsQuery).getSettings;
if (typeof getSettings !== "function") return undefined;
try {
const applied = (await getSettings.call(query))?.applied;
return {
available:
typeof applied?.ultracodeAvailable === "boolean" ? applied.ultracodeAvailable : undefined,
requested:
typeof applied?.ultracodeRequested === "boolean" ? applied.ultracodeRequested : undefined,
};
} catch {
return undefined;
}
}
/** The CLI supports ultracode on exactly the models that support `xhigh`
* effort (both use the same capability check), and `supportedEffortLevels`
* lists `xhigh` only for those models. E.g. Opus 4.7+, Sonnet 5+ and Haiku
* 5.5 qualify; Opus 4.6 and Haiku 4.5 do not. */
export function modelSupportsUltracode(modelInfo: ModelInfo | undefined): boolean {
return (
modelInfo?.supportsEffort === true &&
modelInfo.supportedEffortLevels?.includes("xhigh") === true
);
}
/** Whether dynamic workflows are enabled for the session, inferred from the
* CLI's `ultracodeAvailable` for the model the session started on. This is
* the part of availability that does not change with the model: the plan
* default (off on Pro), `enableWorkflows`/`disableWorkflows` and org policy.
* Undefined when the report can't tell, e.g. `ultracodeAvailable: false` on a
* model that lacks ultracode support anyway. */
export function ultracodeWorkflowsEnabled(
report: UltracodeSettingsReport | undefined,
modelInfo: ModelInfo | undefined,
): boolean | undefined {
if (report?.available === true) return true;
if (report?.available === false && modelSupportsUltracode(modelInfo)) return false;
return undefined;
}
/** Whether to offer the ultracode toggle for `modelInfo`. An unknown workflows
* state shows the toggle: the CLI keeps an unavailable request without
* error, so the toggle is merely inert there. */
export function ultracodeSupported(
workflowsEnabled: boolean | undefined,
modelInfo: ModelInfo | undefined,
): boolean {
return workflowsEnabled !== false && modelSupportsUltracode(modelInfo);
}
/** Ultracode state threaded into `buildConfigOptions`. The option is only
* surfaced while `supported`. */
export type UltracodeOptionState = {
supported: boolean;
enabled: boolean;
/** Whether the client opted into boolean config options. */
useBooleanOption: boolean;
};
/** Build the ultracode toggle: a native boolean for clients that opted into
* boolean config options, otherwise an "on"/"off" select. */
export function createUltracodeConfigOption(
enabled: boolean,
useBooleanOption: boolean,
): SessionConfigOption {
const base = {
id: ULTRACODE_CONFIG_ID,
name: "Ultracode",
description: ULTRACODE_DESCRIPTION,
category: "model_config",
} as const;
if (useBooleanOption) {
return { ...base, type: "boolean", currentValue: enabled };
}
return {
...base,
type: "select",
currentValue: enabled ? ULTRACODE_ON : ULTRACODE_OFF,
options: [
{ value: ULTRACODE_ON, name: "On" },
{ value: ULTRACODE_OFF, name: "Off" },
],
};
}
/** Read the requested ultracode value from a `session/set_config_option`
* request: a boolean, or the "on"/"off" select-fallback strings. */
export function resolveUltracodeEnabled(params: SetSessionConfigOptionRequest): boolean {
const value = params.value;
if (typeof value === "boolean") return value;
if (value === ULTRACODE_ON) return true;
if (value === ULTRACODE_OFF) return false;
throw new Error(`Invalid value for config option ${ULTRACODE_CONFIG_ID}: ${value}`);
}

View File

@@ -13978,6 +13978,7 @@ describe("session/close", () => {
agents: [], agents: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,
@@ -14099,6 +14100,7 @@ describe("session/delete", () => {
agents: [], agents: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,
@@ -14258,6 +14260,7 @@ describe("getOrCreateSession param change detection", () => {
agents: [], agents: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,
@@ -18712,6 +18715,7 @@ describe("post-error recovery", () => {
agents: [], agents: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,
@@ -27068,6 +27072,7 @@ describe("session/cancel wedge recovery (issue #680)", () => {
agents: [], agents: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,
@@ -28975,6 +28980,7 @@ describe("agent selection config option", () => {
agents, agents,
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
abortController: new AbortController(), abortController: new AbortController(),
emitRawSDKMessages: false, emitRawSDKMessages: false,
forwardSubagentText: false, forwardSubagentText: false,

View File

@@ -31,6 +31,7 @@ function testSession(overrides: Partial<TestSession> = {}): TestSession {
configOptions: [], configOptions: [],
currentAgent: "default", currentAgent: "default",
fastModeEnabled: false, fastModeEnabled: false,
ultracodeEnabled: false,
input: { push: vi.fn() }, input: { push: vi.fn() },
...overrides, ...overrides,
}; };
@@ -49,6 +50,7 @@ function testHost(
return freshSession; return freshSession;
}), }),
applyFastMode: vi.fn(async () => {}), applyFastMode: vi.fn(async () => {}),
applyUltracode: vi.fn(async () => {}),
publishSessionState: vi.fn(async () => {}), publishSessionState: vi.fn(async () => {}),
continuationMessage: vi.fn((sessionId, plan, promptUuid) => ({ continuationMessage: vi.fn((sessionId, plan, promptUuid) => ({
type: "user" as const, type: "user" as const,
@@ -252,6 +254,25 @@ describe("continuePlanInFreshContext", () => {
expect(host.applyFastMode).toHaveBeenCalledWith(freshSession, false); expect(host.applyFastMode).toHaveBeenCalledWith(freshSession, false);
}); });
it("restores ultracode turned on before clearing context", async () => {
const turn: ClearContextTurn = {
settled: false,
promptUuid: "00000000-0000-4000-8000-000000000000",
};
const oldSession = testSession({ activeTurn: turn, ultracodeEnabled: true });
const freshSession = testSession({ ultracodeEnabled: false });
const host = testHost(oldSession, freshSession);
await continuePlanInFreshContext(
"public-session",
oldSession,
{ toolUseId: "tool-plan", plan: "Ship it", mode: "auto" },
host,
);
expect(host.applyUltracode).toHaveBeenCalledWith(freshSession, true);
});
it("does not publish or continue a restart cancelled while creating the fresh session", async () => { it("does not publish or continue a restart cancelled while creating the fresh session", async () => {
const turn: ClearContextTurn = { const turn: ClearContextTurn = {
settled: false, settled: false,

View File

@@ -31,6 +31,9 @@ export function makeMockQuery(overrides: Record<string, unknown> = {}) {
supportedCommands: async () => [], supportedCommands: async () => [],
mcpServerStatus: async () => [], mcpServerStatus: async () => [],
getContextUsage: async () => DEFAULT_CONTEXT_USAGE, getContextUsage: async () => DEFAULT_CONTEXT_USAGE,
// No ultracode fields: session creation falls back to settings and the
// model's effort levels, as with a CLI that does not report them.
getSettings: async () => ({ applied: {} }),
close: () => {}, close: () => {},
interrupt: async () => undefined, interrupt: async () => undefined,
[Symbol.asyncIterator]: async function* () {}, [Symbol.asyncIterator]: async function* () {},

View File

@@ -381,17 +381,17 @@ describe("settingsEffortForModel", () => {
}); });
describe("effortFlagSettings", () => { describe("effortFlagSettings", () => {
it("carries ultracode only when settings request it", () => { it("carries ultracode only while the session has it on", () => {
expect(effortFlagSettings("high", { ultracode: true })).toEqual({ expect(effortFlagSettings("high", true)).toEqual({
effortLevel: "high", effortLevel: "high",
ultracode: true, ultracode: true,
}); });
expect(effortFlagSettings("default", { ultracode: true })).toEqual({ expect(effortFlagSettings("default", true)).toEqual({
effortLevel: null, effortLevel: null,
ultracode: true, ultracode: true,
}); });
expect(effortFlagSettings("high", { ultracode: false })).toEqual({ effortLevel: "high" }); expect(effortFlagSettings("high", false)).toEqual({ effortLevel: "high" });
expect(effortFlagSettings(undefined, {})).toEqual({ effortLevel: null }); expect(effortFlagSettings(undefined, false)).toEqual({ effortLevel: null });
}); });
}); });

View File

@@ -0,0 +1,292 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import type { ClientCapabilities } from "@agentclientprotocol/sdk/unstable";
import type { ModelInfo, Query } from "@anthropic-ai/claude-agent-sdk";
import type { AcpClient, ClaudeAcpAgent as ClaudeAcpAgentType } from "../acp-agent.js";
import {
createUltracodeConfigOption,
modelSupportsUltracode,
readUltracodeSettings,
resolveUltracodeEnabled,
ULTRACODE_CONFIG_ID,
ULTRACODE_OFF,
ULTRACODE_ON,
ultracodeSupported,
ultracodeWorkflowsEnabled,
} from "../session-ultracode.js";
import { makeMockQuery } from "./helpers.js";
const { querySpy } = vi.hoisted(() => ({ querySpy: vi.fn() }));
vi.mock("@anthropic-ai/claude-agent-sdk", async () => {
const actual = await vi.importActual<any>("@anthropic-ai/claude-agent-sdk");
return { ...actual, query: querySpy };
});
const OPUS: ModelInfo = {
value: "opus",
displayName: "Opus",
description: "",
supportsEffort: true,
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
};
const FABLE: ModelInfo = {
value: "fable",
displayName: "Fable",
description: "",
supportsEffort: true,
supportedEffortLevels: ["low", "medium", "high", "xhigh", "max"],
};
// Opus 4.6 and Haiku 4.5 have no `xhigh` effort, so the CLI has no ultracode
// for them either.
const OLD_OPUS: ModelInfo = {
value: "claude-opus-4-6",
displayName: "Opus 4.6",
description: "",
supportsEffort: true,
supportedEffortLevels: ["low", "medium", "high", "max"],
};
function getSettingsQuery(getSettings: unknown): Query {
return { getSettings } as unknown as Query;
}
describe("ultracode helpers", () => {
it("builds a boolean option or an on/off select", () => {
expect(createUltracodeConfigOption(true, true)).toEqual({
id: ULTRACODE_CONFIG_ID,
name: "Ultracode",
description: "Dynamic workflows on every task, at any effort level",
category: "model_config",
type: "boolean",
currentValue: true,
});
expect(createUltracodeConfigOption(false, false)).toMatchObject({
type: "select",
currentValue: ULTRACODE_OFF,
options: [
{ value: ULTRACODE_ON, name: "On" },
{ value: ULTRACODE_OFF, name: "Off" },
],
});
});
it("accepts booleans and the select strings", () => {
const request = (value: unknown) =>
({ sessionId: "s", configId: ULTRACODE_CONFIG_ID, value }) as never;
expect(resolveUltracodeEnabled(request(true))).toBe(true);
expect(resolveUltracodeEnabled(request(ULTRACODE_OFF))).toBe(false);
expect(() => resolveUltracodeEnabled(request("maybe"))).toThrow(
"Invalid value for config option ultracode: maybe",
);
});
it("treats a model as supporting ultracode when it supports xhigh effort", () => {
expect(modelSupportsUltracode(OPUS)).toBe(true);
expect(modelSupportsUltracode(OLD_OPUS)).toBe(false);
expect(modelSupportsUltracode({ ...OPUS, supportsEffort: false })).toBe(false);
expect(modelSupportsUltracode(undefined)).toBe(false);
});
it("infers the workflows gate only when the start model can tell", () => {
expect(ultracodeWorkflowsEnabled({ available: true }, OLD_OPUS)).toBe(true);
expect(ultracodeWorkflowsEnabled({ available: false }, OPUS)).toBe(false);
// Opus 4.6 is unavailable regardless of workflows.
expect(ultracodeWorkflowsEnabled({ available: false }, OLD_OPUS)).toBeUndefined();
expect(ultracodeWorkflowsEnabled(undefined, OPUS)).toBeUndefined();
expect(ultracodeSupported(undefined, OPUS)).toBe(true);
expect(ultracodeSupported(false, OPUS)).toBe(false);
expect(ultracodeSupported(true, OLD_OPUS)).toBe(false);
});
it("reads the CLI's ultracode report and tolerates CLIs without one", async () => {
await expect(
readUltracodeSettings(
getSettingsQuery(async () => ({
applied: { ultracodeAvailable: true, ultracodeRequested: false },
})),
),
).resolves.toEqual({ available: true, requested: false });
await expect(
readUltracodeSettings(getSettingsQuery(async () => ({ applied: {} }))),
).resolves.toEqual({ available: undefined, requested: undefined });
await expect(readUltracodeSettings(getSettingsQuery(undefined))).resolves.toBeUndefined();
await expect(
readUltracodeSettings(
getSettingsQuery(async () => {
throw new Error("unsupported control request");
}),
),
).resolves.toBeUndefined();
});
});
describe("ultracode session config option", () => {
let tempDir: string;
let originalClaudeConfigDir: string | undefined;
beforeEach(async () => {
tempDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), "acp-ultracode-"));
originalClaudeConfigDir = process.env.CLAUDE_CONFIG_DIR;
process.env.CLAUDE_CONFIG_DIR = tempDir;
querySpy.mockReset();
vi.resetModules();
});
afterEach(async () => {
if (originalClaudeConfigDir) {
process.env.CLAUDE_CONFIG_DIR = originalClaudeConfigDir;
} else {
delete process.env.CLAUDE_CONFIG_DIR;
}
await fs.promises.rm(tempDir, { recursive: true, force: true });
});
async function createSession(opts: {
models?: ModelInfo[];
getSettings?: () => Promise<unknown>;
settings?: Record<string, unknown>;
clientCapabilities?: ClientCapabilities;
}) {
if (opts.settings) {
await fs.promises.writeFile(
path.join(tempDir, "settings.json"),
JSON.stringify(opts.settings),
);
}
const applyFlagSettings = vi.fn();
querySpy.mockReturnValue(
makeMockQuery({
initializationResult: async () => ({ models: opts.models ?? [OPUS, FABLE, OLD_OPUS] }),
applyFlagSettings,
...(opts.getSettings ? { getSettings: opts.getSettings } : {}),
}),
);
const { ClaudeAcpAgent } = await import("../acp-agent.js");
const agent: ClaudeAcpAgentType = new ClaudeAcpAgent({
sessionUpdate: async () => {},
requestPermission: async () => ({ outcome: { outcome: "cancelled" } }),
readTextFile: async () => ({ content: "" }),
writeTextFile: async () => ({}),
} as unknown as AcpClient);
(agent as any).clientCapabilities = opts.clientCapabilities ?? {
session: { configOptions: { boolean: {} } },
};
const response = await (agent as any).createSession({
cwd: tempDir,
mcpServers: [],
_meta: { disableBuiltInTools: true },
});
const sessionId: string = response.sessionId;
const ultracodeOption = () =>
agent.sessions[sessionId].configOptions.find((o) => o.id === ULTRACODE_CONFIG_ID);
return { agent, sessionId, applyFlagSettings, ultracodeOption };
}
it("seeds the option from the CLI's report", async () => {
const { ultracodeOption, agent, sessionId } = await createSession({
getSettings: async () => ({
applied: { ultracodeAvailable: true, ultracodeRequested: true },
}),
});
expect(ultracodeOption()).toEqual(createUltracodeConfigOption(true, true));
expect(agent.sessions[sessionId].ultracodeEnabled).toBe(true);
});
it("hides the option while dynamic workflows are off, across model switches", async () => {
const { agent, sessionId, ultracodeOption } = await createSession({
getSettings: async () => ({
applied: { ultracodeAvailable: false, ultracodeRequested: false },
}),
});
expect(ultracodeOption()).toBeUndefined();
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "fable" });
expect(ultracodeOption()).toBeUndefined();
});
it("follows the model's support and keeps the toggle across switches", async () => {
const { agent, sessionId, applyFlagSettings, ultracodeOption } = await createSession({
clientCapabilities: {},
});
expect(ultracodeOption()).toEqual(createUltracodeConfigOption(false, false));
await agent.setSessionConfigOption({
sessionId,
configId: ULTRACODE_CONFIG_ID,
value: ULTRACODE_ON,
});
expect(applyFlagSettings).toHaveBeenLastCalledWith({ ultracode: true });
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "claude-opus-4-6" });
expect(ultracodeOption()).toBeUndefined();
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "fable" });
expect(ultracodeOption()).toEqual(createUltracodeConfigOption(true, false));
});
it("keeps a toggled-on ultracode when effort changes, and not a toggled-off one", async () => {
// An effortLevel apply without an ultracode key turns ultracode off, so
// the effort picker must carry the session's toggle.
const { agent, sessionId, applyFlagSettings } = await createSession({
settings: { ultracode: true },
});
await agent.setSessionConfigOption({
sessionId,
configId: ULTRACODE_CONFIG_ID,
type: "boolean",
value: false,
});
expect(applyFlagSettings).toHaveBeenLastCalledWith({ ultracode: false });
await agent.setSessionConfigOption({ sessionId, configId: "effort", value: "high" });
expect(applyFlagSettings).toHaveBeenLastCalledWith({ effortLevel: "high" });
await agent.setSessionConfigOption({
sessionId,
configId: ULTRACODE_CONFIG_ID,
type: "boolean",
value: true,
});
await agent.setSessionConfigOption({ sessionId, configId: "effort", value: "low" });
expect(applyFlagSettings).toHaveBeenLastCalledWith({ effortLevel: "low", ultracode: true });
});
it("keeps ultracode on through the effort apply that follows a model switch", async () => {
// recommendedValue clients apply the new model's effort on every switch.
const { agent, sessionId, applyFlagSettings } = await createSession({
clientCapabilities: {
session: { configOptions: { boolean: {} } },
_meta: { jetbrains: { air: { version: 1, capabilities: ["recommendedValue"] } } },
},
});
await agent.setSessionConfigOption({
sessionId,
configId: ULTRACODE_CONFIG_ID,
type: "boolean",
value: true,
});
await agent.setSessionConfigOption({ sessionId, configId: "model", value: "fable" });
expect(applyFlagSettings).toHaveBeenLastCalledWith({ effortLevel: "medium", ultracode: true });
});
it("does not change session state when the SDK rejects the flag", async () => {
const { agent, sessionId, applyFlagSettings, ultracodeOption } = await createSession({});
applyFlagSettings.mockRejectedValueOnce(new Error("nope"));
await expect(
agent.setSessionConfigOption({
sessionId,
configId: ULTRACODE_CONFIG_ID,
type: "boolean",
value: true,
}),
).rejects.toThrow("nope");
expect(agent.sessions[sessionId].ultracodeEnabled).toBe(false);
expect(ultracodeOption()).toEqual(createUltracodeConfigOption(false, true));
});
});