From 8383231721136ec3f0c5b1480caf260a7c5902cb Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 01:47:44 +0000 Subject: [PATCH 1/5] test: reproduce #30538, Auto on Pi falls back to Supervised Pi does not offer Auto, and the server, web composer, and mobile thread settings all fall back to the narrowest mode (Supervised) instead of the broadest supported mode at or below Auto (Auto-accept edits). These tests fail on main and reproduce #30538. Co-Authored-By: Claude Opus 5.5 --- .../threads/thread-settings-options.test.ts | 13 +++++++++++++ .../src/orchestration-v2/RuntimePolicy.test.ts | 14 ++++++++++++++ .../src/components/chat/runtimeModeConfig.test.ts | 14 ++++++++++++++ 3 files changed, 41 insertions(+) create mode 100644 apps/web/src/components/chat/runtimeModeConfig.test.ts diff --git a/apps/mobile/src/features/threads/thread-settings-options.test.ts b/apps/mobile/src/features/threads/thread-settings-options.test.ts index fb15c080e0f0a..e10eb1c8a0109 100644 --- a/apps/mobile/src/features/threads/thread-settings-options.test.ts +++ b/apps/mobile/src/features/threads/thread-settings-options.test.ts @@ -10,8 +10,10 @@ import type { import { describe, expect, it } from "vite-plus/test"; import { + compatibleRuntimeModeForChoices, RUNTIME_MODE_CHOICES, resolveNewThreadRuntimeMode, + runtimeModeChoicesForSupportedModes, selectableChoices, withManagedRuntimeModeLimits, } from "./thread-settings-options"; @@ -49,6 +51,17 @@ describe("managed runtime mode limits", () => { }); }); +describe("compatibleRuntimeModeForChoices", () => { + it("shows Auto on a provider without it as Auto-accept edits, not Supervised", () => { + const piChoices = runtimeModeChoicesForSupportedModes([ + "approval-required", + "auto-accept-edits", + "full-access", + ]); + expect(compatibleRuntimeModeForChoices("auto", piChoices)).toBe("auto-accept-edits"); + }); +}); + const effortDescriptor: Extract = { id: "effort", label: "Reasoning", diff --git a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts index ec610b093f7d3..ca15ac5dfc155 100644 --- a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts +++ b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts @@ -65,9 +65,11 @@ function makeThread(input: { const grokInstanceId = ProviderInstanceId.make("grok"); const antigravityInstanceId = ProviderInstanceId.make("antigravity"); +const piInstanceId = ProviderInstanceId.make("pi"); const supportedRuntimeModesByInstance = new Map>([ [grokInstanceId, ["approval-required", "auto", "full-access"]], [antigravityInstanceId, ["approval-required", "auto-accept-edits", "full-access"]], + [piInstanceId, ["approval-required", "auto-accept-edits", "full-access"]], ]); const providerInstanceFor = (instanceId: ProviderInstanceId) => ({ @@ -138,6 +140,18 @@ it.layer(layerTest)("RuntimePolicyV2", (it) => { }), ); + it.effect("runs a stored Auto Pi thread in Auto-accept edits, not Supervised", () => + Effect.gen(function* () { + const policy = yield* RuntimePolicy.RuntimePolicyV2; + const now = yield* DateTime.now; + const resolved = yield* policy.resolve({ + thread: makeThread({ now, worktreePath: null, runtimeMode: "auto" }), + modelSelection: { instanceId: piInstanceId, model: "default" }, + }); + assert.equal(resolved.runtimeMode, "auto-accept-edits"); + }), + ); + it.effect("runs a mode the provider does not offer in Supervised", () => Effect.gen(function* () { const policy = yield* RuntimePolicy.RuntimePolicyV2; diff --git a/apps/web/src/components/chat/runtimeModeConfig.test.ts b/apps/web/src/components/chat/runtimeModeConfig.test.ts new file mode 100644 index 0000000000000..8b593530f9dc3 --- /dev/null +++ b/apps/web/src/components/chat/runtimeModeConfig.test.ts @@ -0,0 +1,14 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { resolveCompatibleRuntimeMode } from "./runtimeModeConfig"; + +describe("resolveCompatibleRuntimeMode", () => { + it("shows Auto on a provider without it as Auto-accept edits, not Supervised", () => { + const { mode } = resolveCompatibleRuntimeMode("auto", [ + "approval-required", + "auto-accept-edits", + "full-access", + ]); + expect(mode).toBe("auto-accept-edits"); + }); +}); From 3713c50f8b005aff132b01ee522eae67a1b9777f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 01:55:33 +0000 Subject: [PATCH 2/5] fix: step down to the broadest supported runtime mode A thread whose mode its provider does not offer ran at, and the composer showed, Supervised: a new Pi thread on an Auto default lost Auto-accept edits. The server, web composer, and mobile thread settings now resolve it to the broadest offered mode at or below the requested one, the rule delegate_task and t3_thread_launch already used. One contracts helper, broadestRuntimeModeWithin, replaces the private rank table there. Fixes pingdotgg/t3code#30538 Co-Authored-By: Claude Opus 5.5 --- .../threads/thread-settings-options.ts | 12 ++++-- apps/server/src/mcp/OrchestratorMcpService.ts | 4 +- apps/server/src/mcp/delegatedTaskModes.ts | 41 ++++++------------- .../src/mcp/toolkits/project/handlers.test.ts | 2 +- .../orchestration-v2/RuntimePolicy.test.ts | 2 +- .../src/orchestration-v2/RuntimePolicy.ts | 7 ++-- .../src/provider/AntigravityProvider.ts | 2 +- .../src/components/chat/runtimeModeConfig.ts | 3 +- docs/user/permission-modes.md | 20 +++++---- docs/user/providers-fx.md | 2 +- docs/user/providers-pi.md | 2 +- packages/contracts/src/providerPolicy.ts | 14 +++++++ 12 files changed, 58 insertions(+), 53 deletions(-) diff --git a/apps/mobile/src/features/threads/thread-settings-options.ts b/apps/mobile/src/features/threads/thread-settings-options.ts index 9f9d4f32b7d52..b660ecc78b0f7 100644 --- a/apps/mobile/src/features/threads/thread-settings-options.ts +++ b/apps/mobile/src/features/threads/thread-settings-options.ts @@ -1,4 +1,5 @@ import { + broadestRuntimeModeWithin, managedRuntimeModeDenial, type ManagedPolicySummary, type RuntimeMode, @@ -78,9 +79,14 @@ export function compatibleRuntimeModeForChoices( runtimeMode: RuntimeMode, choices: ReadonlyArray<{ readonly mode: RuntimeMode }>, ): RuntimeMode { - return choices.some((choice) => choice.mode === runtimeMode) - ? runtimeMode - : (choices[0]?.mode ?? runtimeMode); + return ( + broadestRuntimeModeWithin( + runtimeMode, + choices.map((choice) => choice.mode), + ) ?? + choices[0]?.mode ?? + runtimeMode + ); } export { selectableProviderOptionChoices as selectableChoices } from "@t3tools/client-runtime/model-traits"; diff --git a/apps/server/src/mcp/OrchestratorMcpService.ts b/apps/server/src/mcp/OrchestratorMcpService.ts index e84212cab0023..1d37e3acb1c31 100644 --- a/apps/server/src/mcp/OrchestratorMcpService.ts +++ b/apps/server/src/mcp/OrchestratorMcpService.ts @@ -2,6 +2,7 @@ import { CommandId, type RunId, isProviderAvailable, + isRuntimeModeAbove, MessageId, type ModelSelection, NodeId, @@ -119,7 +120,6 @@ import { interactionModeRank, resolveInteractionMode, resolveRuntimeMode, - runtimeModeRank, taskPrompt, } from "./delegatedTaskModes.ts"; import { isLiveCallerShell, threadNotFound } from "./threadAccess.ts"; @@ -1915,7 +1915,7 @@ const make = Effect.gen(function* () { }, modes: { readonly runtimeMode: RuntimeMode; readonly interactionMode: ProviderInteractionMode }, ) => - runtimeModeRank(modes.runtimeMode) <= runtimeModeRank(limits.runtimeMode) && + !isRuntimeModeAbove(modes.runtimeMode, limits.runtimeMode) && interactionModeRank(modes.interactionMode) <= interactionModeRank(limits.interactionMode); /** diff --git a/apps/server/src/mcp/delegatedTaskModes.ts b/apps/server/src/mcp/delegatedTaskModes.ts index 54d25bbb36e02..da0410f92c1d0 100644 --- a/apps/server/src/mcp/delegatedTaskModes.ts +++ b/apps/server/src/mcp/delegatedTaskModes.ts @@ -1,4 +1,6 @@ import { + broadestRuntimeModeWithin, + isRuntimeModeAbove, type ModelSelection, OrchestratorMcpFailure, type OrchestratorMcpDelegateTaskInput, @@ -9,20 +11,6 @@ import { } from "@t3tools/contracts"; import * as Effect from "effect/Effect"; -/** How much a runtime mode allows, narrowest first. */ -export function runtimeModeRank(mode: RuntimeMode): number { - switch (mode) { - case "approval-required": - return 0; - case "auto-accept-edits": - return 1; - case "auto": - return 2; - case "full-access": - return 3; - } -} - /** How much an interaction mode allows: plan, then default. */ export function interactionModeRank(mode: ProviderInteractionMode): number { return mode === "plan" ? 0 : 1; @@ -43,7 +31,7 @@ export function resolveRuntimeMode( ): Effect.Effect { const inherited = requested === undefined || requested === "inherit"; const resolved = inherited ? parentMode : requested; - if (runtimeModeRank(resolved) > runtimeModeRank(parentMode)) { + if (isRuntimeModeAbove(resolved, parentMode)) { return Effect.fail( new OrchestratorMcpFailure({ code: "runtime_mode_escalation_denied", @@ -55,20 +43,15 @@ export function resolveRuntimeMode( if (supported === undefined || supported.length === 0 || supported.includes(resolved)) { return Effect.succeed(resolved); } - if (!inherited) { - return Effect.fail( - new OrchestratorMcpFailure({ - code: "invalid_request", - message: `Provider ${target?.modelSelection.instanceId} does not offer runtime mode ${resolved}, so it would run as approval-required. Use one of: ${supported.join(", ")}.`, - }), - ); - } - return Effect.succeed( - supported - .filter((mode) => runtimeModeRank(mode) <= runtimeModeRank(resolved)) - .toSorted((left, right) => runtimeModeRank(right) - runtimeModeRank(left))[0] ?? - "approval-required", - ); + const steppedDown = broadestRuntimeModeWithin(resolved, supported) ?? "approval-required"; + return inherited + ? Effect.succeed(steppedDown) + : Effect.fail( + new OrchestratorMcpFailure({ + code: "invalid_request", + message: `Provider ${target?.modelSelection.instanceId} does not offer runtime mode ${resolved}, so it would run as ${steppedDown}. Use one of: ${supported.join(", ")}.`, + }), + ); } /** The same, for interaction modes. */ diff --git a/apps/server/src/mcp/toolkits/project/handlers.test.ts b/apps/server/src/mcp/toolkits/project/handlers.test.ts index 6721e89478550..19b03ff45295d 100644 --- a/apps/server/src/mcp/toolkits/project/handlers.test.ts +++ b/apps/server/src/mcp/toolkits/project/handlers.test.ts @@ -278,7 +278,7 @@ it.effect( it.effect("a launch steps an inherited mode down to the provider's, and refuses one it lacks", () => Effect.gen(function* () { - // Pi offers no Auto; a mode a provider lacks would run as Supervised. + // Pi offers no Auto, so an inherited Auto steps down to Auto-accept edits. const pi = ProviderInstanceId.make("pi"); const launchPi = Effect.fn("test.launchPi")(function* (runtimeMode?: RuntimeMode) { const launched: Array = []; diff --git a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts index ca15ac5dfc155..fa573fced3c06 100644 --- a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts +++ b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts @@ -133,7 +133,7 @@ it.layer(layerTest)("RuntimePolicyV2", (it) => { options: [{ optionId: "allow", name: "Allow", kind: "allow_once" as const }], }; const auto = yield* resolve("auto"); - assert.equal(auto.runtimeMode, "approval-required"); + assert.equal(auto.runtimeMode, "auto-accept-edits"); assert.equal(acpPermissionDisposition(auto, request), "ask"); assert.equal(acpPermissionDisposition(yield* resolve("auto-accept-edits"), request), "ask"); assert.equal(acpPermissionDisposition(yield* resolve("full-access"), request), "allow"); diff --git a/apps/server/src/orchestration-v2/RuntimePolicy.ts b/apps/server/src/orchestration-v2/RuntimePolicy.ts index f0d0ae38577d1..34ed3aa45cecc 100644 --- a/apps/server/src/orchestration-v2/RuntimePolicy.ts +++ b/apps/server/src/orchestration-v2/RuntimePolicy.ts @@ -1,4 +1,5 @@ import { + broadestRuntimeModeWithin, isRuntimeModeAbove, managedRuntimeModeCeilingMessage, ModelSelection, @@ -92,11 +93,9 @@ function providerRuntimeMode( runtimeMode: RuntimeMode, supportedRuntimeModes: ReadonlyArray | undefined, ): RuntimeMode { - return supportedRuntimeModes === undefined || - supportedRuntimeModes.length === 0 || - supportedRuntimeModes.includes(runtimeMode) + return supportedRuntimeModes === undefined || supportedRuntimeModes.length === 0 ? runtimeMode - : "approval-required"; + : (broadestRuntimeModeWithin(runtimeMode, supportedRuntimeModes) ?? "approval-required"); } const refuseAboveCeiling = ( diff --git a/apps/server/src/provider/AntigravityProvider.ts b/apps/server/src/provider/AntigravityProvider.ts index ed047331e73ee..37222310ed887 100644 --- a/apps/server/src/provider/AntigravityProvider.ts +++ b/apps/server/src/provider/AntigravityProvider.ts @@ -185,7 +185,7 @@ export const makeAntigravityProvider = Effect.fn("makeAntigravityProvider")(func presentation: { displayName: "Antigravity", showInteractionModeToggle: false, - // No automatic review, so Auto falls back to Supervised instead of approving everything. + // No automatic review, so Auto steps down to Auto-accept edits instead of approving everything. supportedRuntimeModes: ["approval-required", "auto-accept-edits", "full-access"], }, enabled: settings.enabled, diff --git a/apps/web/src/components/chat/runtimeModeConfig.ts b/apps/web/src/components/chat/runtimeModeConfig.ts index 7edc187b9ba63..33b41cee7a1a8 100644 --- a/apps/web/src/components/chat/runtimeModeConfig.ts +++ b/apps/web/src/components/chat/runtimeModeConfig.ts @@ -1,4 +1,5 @@ import { + broadestRuntimeModeWithin, managedRuntimeModeDenial, type ManagedPolicySummary, type RuntimeMode, @@ -43,7 +44,7 @@ export function resolveCompatibleRuntimeMode( supportedRuntimeModes && supportedRuntimeModes.length > 0 ? runtimeModeOptions.filter((mode) => supportedRuntimeModes.includes(mode)) : runtimeModeOptions; - return { modes, mode: modes.includes(runtimeMode) ? runtimeMode : (modes[0] ?? runtimeMode) }; + return { modes, mode: broadestRuntimeModeWithin(runtimeMode, modes) ?? modes[0] ?? runtimeMode }; } export function resolveRuntimeModeOptions( diff --git a/docs/user/permission-modes.md b/docs/user/permission-modes.md index 796e3f316122d..989715b92ddaa 100644 --- a/docs/user/permission-modes.md +++ b/docs/user/permission-modes.md @@ -28,18 +28,20 @@ the parent conversation with the subagent's name, so you can respond without swi Providers enforce permissions differently. Some read-only actions can proceed in **Supervised**. **Auto** uses automatic review on Codex, Claude, Cursor, Grok, and fx; providers without an equivalent, -including OpenCode and Antigravity, fall back to asking. On Grok, commands its review blocks come -to you for approval. On fx, prompts it still sends also come to you. +including OpenCode, fall back to asking. On Grok, commands its review blocks come to you for +approval. On fx, prompts it still sends also come to you. -Muse Code offers only **Supervised** and **Full access**. A Muse thread already set to another mode -runs in **Supervised**. +A thread set to a mode its provider does not offer runs in the broadest mode the provider offers +below it. For example, **Auto** runs as **Auto-accept edits** on Pi and Antigravity, and +**Full access** runs as **Auto** on fx. -T3 Code offers no **Auto-accept edits** or **Full access** for fx. An fx thread already set to either runs in -**Supervised**. +Muse Code offers only **Supervised** and **Full access**. -Grok offers no **Auto-accept edits**. A Grok thread already set to it runs in **Supervised**. Grok -file-change approvals offer **Allow all edits this session**. Its command approvals have no -session-wide choice, because Grok would remember that command for the whole project. +T3 Code offers no **Auto-accept edits** or **Full access** for fx. + +Grok offers no **Auto-accept edits**. Grok file-change approvals offer **Allow all edits this +session**. Its command approvals have no session-wide choice, because Grok would remember that +command for the whole project. ACP Registry agents run their own tools in their own mode; T3 Code answers their approval requests by the permission mode. See [ACP Registry permissions](./providers-acp.md#permissions-and-terminals). diff --git a/docs/user/providers-fx.md b/docs/user/providers-fx.md index 430f7a705a499..00dd98ca5d3ff 100644 --- a/docs/user/providers-fx.md +++ b/docs/user/providers-fx.md @@ -36,7 +36,7 @@ fx offers two composer modes. | **Supervised** | fx asks before it changes files or runs commands. Read requests run without asking. | | **Auto** | fx handles routine actions itself. Anything it still asks about reaches you. | -T3 Code does not offer **Full access** or **Auto-accept edits** for fx. A thread already set to one of those runs in **Supervised**. Changing the permission mode restarts the connection and reloads the same fx session. +T3 Code does not offer **Full access** or **Auto-accept edits** for fx. A thread already set to **Full access** runs in **Auto**, and one set to **Auto-accept edits** runs in **Supervised**. Changing the permission mode restarts the connection and reloads the same fx session. On fx versions after [vercel-labs/fx#1189](https://github.com/vercel-labs/fx/pull/1189), fx saves the mode T3 Code sets as its default permission mode. T3 Code sets it each time it opens an fx thread, so fx in the terminal then starts in that thread's mode. diff --git a/docs/user/providers-pi.md b/docs/user/providers-pi.md index 37026d50ab76c..f912b071520da 100644 --- a/docs/user/providers-pi.md +++ b/docs/user/providers-pi.md @@ -62,7 +62,7 @@ T3 Code applies the composer permission mode through Pi's blocking tool hook: - **Full access** allows tools without T3 Code approval prompts. The **Auto** option is not shown for Pi because Pi does not expose an AI approval reviewer. -Threads that already stored Auto before Pi support was added behave and display as Supervised. +Threads that already stored Auto before Pi support was added behave and display as Auto-accept edits. Changing the mode restarts the Pi provider session and resumes the same native conversation. The policy covers Pi tool calls; it is not an operating-system sandbox, and code that a trusted Pi diff --git a/packages/contracts/src/providerPolicy.ts b/packages/contracts/src/providerPolicy.ts index 1b94f43aabd5c..fe4f393a5b2a6 100644 --- a/packages/contracts/src/providerPolicy.ts +++ b/packages/contracts/src/providerPolicy.ts @@ -42,6 +42,20 @@ const RUNTIME_MODE_RANK: Record = { export const isRuntimeModeAbove = (mode: RuntimeMode, ceiling: RuntimeMode | undefined): boolean => ceiling !== undefined && RUNTIME_MODE_RANK[mode] > RUNTIME_MODE_RANK[ceiling]; +/** The broadest of `supported` that grants no more than `mode`; none when every one grants more. */ +export const broadestRuntimeModeWithin = ( + mode: RuntimeMode, + supported: ReadonlyArray, +): RuntimeMode | undefined => + supported.reduce( + (best, candidate) => + !isRuntimeModeAbove(candidate, mode) && + (best === undefined || isRuntimeModeAbove(candidate, best)) + ? candidate + : best, + undefined, + ); + export const ProviderInteractionMode = Schema.Literals(["default", "plan"]); export type ProviderInteractionMode = typeof ProviderInteractionMode.Type; export const DEFAULT_PROVIDER_INTERACTION_MODE: ProviderInteractionMode = "default"; From 6759725319b58ca95d3330a5a4a355bbe3510717 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Wed, 4 Nov 2026 02:05:11 +0000 Subject: [PATCH 3/5] test: name the step-down tests for what they check Co-Authored-By: Claude Opus 5.5 --- .../orchestration-v2/RuntimePolicy.test.ts | 34 ++++++++++--------- .../src/provider/AntigravityProvider.test.ts | 2 +- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts index fa573fced3c06..d8b853cae51f1 100644 --- a/apps/server/src/orchestration-v2/RuntimePolicy.test.ts +++ b/apps/server/src/orchestration-v2/RuntimePolicy.test.ts @@ -152,23 +152,25 @@ it.layer(layerTest)("RuntimePolicyV2", (it) => { }), ); - it.effect("runs a mode the provider does not offer in Supervised", () => - Effect.gen(function* () { - const policy = yield* RuntimePolicy.RuntimePolicyV2; - const now = yield* DateTime.now; - const modeFor = (instanceId: ProviderInstanceId, runtimeMode: RuntimeMode) => - policy - .resolve({ - thread: makeThread({ now, worktreePath: null, runtimeMode }), - modelSelection: { instanceId, model: "test-model" }, - }) - .pipe(Effect.map((resolved) => resolved.runtimeMode)); + it.effect( + "runs Supervised when the provider offers nothing between it and the requested mode", + () => + Effect.gen(function* () { + const policy = yield* RuntimePolicy.RuntimePolicyV2; + const now = yield* DateTime.now; + const modeFor = (instanceId: ProviderInstanceId, runtimeMode: RuntimeMode) => + policy + .resolve({ + thread: makeThread({ now, worktreePath: null, runtimeMode }), + modelSelection: { instanceId, model: "test-model" }, + }) + .pipe(Effect.map((resolved) => resolved.runtimeMode)); - assert.equal(yield* modeFor(grokInstanceId, "auto-accept-edits"), "approval-required"); - assert.equal(yield* modeFor(grokInstanceId, "auto"), "auto"); - assert.equal(yield* modeFor(grokInstanceId, "full-access"), "full-access"); - assert.equal(yield* modeFor(providerInstanceId, "auto-accept-edits"), "auto-accept-edits"); - }), + assert.equal(yield* modeFor(grokInstanceId, "auto-accept-edits"), "approval-required"); + assert.equal(yield* modeFor(grokInstanceId, "auto"), "auto"); + assert.equal(yield* modeFor(grokInstanceId, "full-access"), "full-access"); + assert.equal(yield* modeFor(providerInstanceId, "auto-accept-edits"), "auto-accept-edits"); + }), ); }); diff --git a/apps/server/src/provider/AntigravityProvider.test.ts b/apps/server/src/provider/AntigravityProvider.test.ts index 791ed748de2ef..76152e09431c8 100644 --- a/apps/server/src/provider/AntigravityProvider.test.ts +++ b/apps/server/src/provider/AntigravityProvider.test.ts @@ -348,7 +348,7 @@ it.layer(layerTest)("Antigravity provider snapshots", (it) => { ), ); - it.effect("does not offer Auto, so Auto threads fall back to Supervised", () => + it.effect("does not offer Auto, so Auto threads step down to Auto-accept edits", () => Effect.scoped( Effect.gen(function* () { const harness = yield* makeHarness(); From dd39d624b59500ac6b7568ba58d4f95b8f6e6b84 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Nov 2026 23:18:59 +0000 Subject: [PATCH 4/5] test: keep the step-down case at the server layer only The web and mobile cases repeated the server's RuntimePolicy case through one-line wrappers around the shared contracts helper. AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- .../threads/thread-settings-options.test.ts | 13 ------------- .../src/components/chat/runtimeModeConfig.test.ts | 14 -------------- 2 files changed, 27 deletions(-) delete mode 100644 apps/web/src/components/chat/runtimeModeConfig.test.ts diff --git a/apps/mobile/src/features/threads/thread-settings-options.test.ts b/apps/mobile/src/features/threads/thread-settings-options.test.ts index e10eb1c8a0109..fb15c080e0f0a 100644 --- a/apps/mobile/src/features/threads/thread-settings-options.test.ts +++ b/apps/mobile/src/features/threads/thread-settings-options.test.ts @@ -10,10 +10,8 @@ import type { import { describe, expect, it } from "vite-plus/test"; import { - compatibleRuntimeModeForChoices, RUNTIME_MODE_CHOICES, resolveNewThreadRuntimeMode, - runtimeModeChoicesForSupportedModes, selectableChoices, withManagedRuntimeModeLimits, } from "./thread-settings-options"; @@ -51,17 +49,6 @@ describe("managed runtime mode limits", () => { }); }); -describe("compatibleRuntimeModeForChoices", () => { - it("shows Auto on a provider without it as Auto-accept edits, not Supervised", () => { - const piChoices = runtimeModeChoicesForSupportedModes([ - "approval-required", - "auto-accept-edits", - "full-access", - ]); - expect(compatibleRuntimeModeForChoices("auto", piChoices)).toBe("auto-accept-edits"); - }); -}); - const effortDescriptor: Extract = { id: "effort", label: "Reasoning", diff --git a/apps/web/src/components/chat/runtimeModeConfig.test.ts b/apps/web/src/components/chat/runtimeModeConfig.test.ts deleted file mode 100644 index 8b593530f9dc3..0000000000000 --- a/apps/web/src/components/chat/runtimeModeConfig.test.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { describe, expect, it } from "vite-plus/test"; - -import { resolveCompatibleRuntimeMode } from "./runtimeModeConfig"; - -describe("resolveCompatibleRuntimeMode", () => { - it("shows Auto on a provider without it as Auto-accept edits, not Supervised", () => { - const { mode } = resolveCompatibleRuntimeMode("auto", [ - "approval-required", - "auto-accept-edits", - "full-access", - ]); - expect(mode).toBe("auto-accept-edits"); - }); -}); From b6eaa87914d3a474bdb82306bcbea3d13d9cc9ac Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Tue, 10 Nov 2026 23:31:49 +0000 Subject: [PATCH 5/5] docs: unoffered stored modes step down, not to Supervised AI-assisted; verified with the tests and tools listed above. Co-Authored-By: Claude Opus 5.5 --- docs/internals/adding-a-provider.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/internals/adding-a-provider.md b/docs/internals/adding-a-provider.md index 821269954f8d3..3e43be7685ae0 100644 --- a/docs/internals/adding-a-provider.md +++ b/docs/internals/adding-a-provider.md @@ -29,7 +29,7 @@ capabilities, never the driver kind. `supportedRuntimeModes` in the provider presentation ([Grok](../../packages/provider-grok/src/server/status.ts) and [Pi](../../packages/provider-pi/src/server/status.ts) are examples). Do not imitate a missing mode by answering approvals in T3: T3's check is weaker than the agent's own enforcement. The - server runs an unoffered stored mode as Supervised + server runs an unoffered stored mode as the broadest offered mode below it ([`RuntimePolicy.ts`](../../apps/server/src/orchestration-v2/RuntimePolicy.ts)). - **Approval and question options.** Pass the provider's own option IDs through unchanged. Every request needs a way to decline that the provider honors.