Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion apps/mobile/src/features/threads/NewTaskDraftScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -582,10 +582,13 @@ export function NewTaskDraftScreen(props: {
? "Approve actions"
: flow.runtimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: flow.runtimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
{ id: "options:runtime:auto", title: "Auto" },
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/features/threads/ThreadComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -627,10 +627,13 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer
? "Approve actions"
: currentRuntimeMode === "auto-accept-edits"
? "Auto-accept edits"
: "Full access",
: currentRuntimeMode === "auto"
? "Auto"
: "Full access",
subactions: [
{ id: "options:runtime:approval-required", title: "Approve actions" },
{ id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" },
{ id: "options:runtime:auto", title: "Auto" },
{ id: "options:runtime:full-access", title: "Full access" },
].map((option) => {
const value = option.id.replace("options:runtime:", "");
Expand Down
19 changes: 19 additions & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,25 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect("derives auto permission mode from auto runtime policy without skip flag", () => {
const harness = makeHarness();
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;
yield* adapter.startSession({
threadId: THREAD_ID,
provider: ProviderDriverKind.make("claudeAgent"),
runtimeMode: "auto",
});

const createInput = harness.getLastCreateQueryInput();
assert.equal(createInput?.options.permissionMode, "auto");
assert.equal(createInput?.options.allowDangerouslySkipPermissions, undefined);
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
});

it.effect("loads Claude filesystem settings sources for SDK sessions", () => {
const harness = makeHarness();
return Effect.gen(function* () {
Expand Down
1 change: 1 addition & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3509,6 +3509,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* (
const effectiveEffort = getEffectiveClaudeAgentEffort(effort, modelSelection?.model);
const runtimeModeToPermission: Record<string, PermissionMode> = {
"auto-accept-edits": "acceptEdits",
auto: "auto",
"full-access": "bypassPermissions",
};
const permissionMode = runtimeModeToPermission[input.runtimeMode];
Expand Down
28 changes: 28 additions & 0 deletions apps/server/src/provider/Layers/CodexSessionRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ describe("buildTurnStartParams", () => {
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "never",
approvalsReviewer: "user",
sandboxPolicy: {
type: "dangerFullAccess",
},
Expand Down Expand Up @@ -149,6 +150,7 @@ describe("buildTurnStartParams", () => {
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "on-request",
approvalsReviewer: "user",
sandboxPolicy: {
type: "workspaceWrite",
},
Expand Down Expand Up @@ -193,6 +195,31 @@ describe("buildTurnStartParams", () => {
NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`));
});

it.effect("routes approvals to the auto reviewer in auto mode", () =>
Effect.gen(function* () {
const params = yield* buildTurnStartParams({
threadId: "provider-thread-1",
runtimeMode: "auto",
prompt: "Ship it",
});

NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "on-request",
approvalsReviewer: "auto_review",
sandboxPolicy: {
type: "workspaceWrite",
},
input: [
{
type: "text",
text: "Ship it",
},
],
});
}),
);

it("omits collaboration mode when interaction mode is absent", () => {
const params = Effect.runSync(
buildTurnStartParams({
Expand All @@ -205,6 +232,7 @@ describe("buildTurnStartParams", () => {
NodeAssert.deepStrictEqual(params, {
threadId: "provider-thread-1",
approvalPolicy: "untrusted",
approvalsReviewer: "user",
sandboxPolicy: {
type: "readOnly",
},
Expand Down
15 changes: 15 additions & 0 deletions apps/server/src/provider/Layers/CodexSessionRuntime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -264,23 +264,35 @@ function readResumeCursorThreadId(
function runtimeModeToThreadConfig(input: RuntimeMode): {
readonly approvalPolicy: EffectCodexSchema.V2ThreadStartParams__AskForApproval;
readonly sandbox: EffectCodexSchema.V2ThreadStartParams__SandboxMode;
// Always explicit: omitting the field on resume keeps the thread's previous
// reviewer, which would leave auto_review sticky after switching modes.
readonly approvalsReviewer: EffectCodexSchema.V2ThreadStartParams__ApprovalsReviewer;
} {
switch (input) {
case "approval-required":
return {
approvalPolicy: "untrusted",
sandbox: "read-only",
approvalsReviewer: "user",
};
case "auto-accept-edits":
return {
approvalPolicy: "on-request",
sandbox: "workspace-write",
approvalsReviewer: "user",
};
case "auto":
return {
approvalPolicy: "on-request",
sandbox: "workspace-write",
approvalsReviewer: "auto_review",
};
case "full-access":
default:
return {
approvalPolicy: "never",
sandbox: "danger-full-access",
approvalsReviewer: "user",
};
}
}
Expand All @@ -296,6 +308,7 @@ function buildThreadStartParams(input: {
cwd: input.cwd,
approvalPolicy: config.approvalPolicy,
sandbox: config.sandbox,
approvalsReviewer: config.approvalsReviewer,
...(input.model ? { model: input.model } : {}),
...(input.serviceTier ? { serviceTier: input.serviceTier } : {}),
};
Expand All @@ -310,6 +323,7 @@ function runtimeModeToTurnSandboxPolicy(
type: "readOnly",
};
case "auto-accept-edits":
case "auto":
return {
type: "workspaceWrite",
};
Expand Down Expand Up @@ -382,6 +396,7 @@ export function buildTurnStartParams(input: {
threadId: input.threadId,
input: turnInput,
approvalPolicy: config.approvalPolicy,
approvalsReviewer: config.approvalsReviewer,
sandboxPolicy: runtimeModeToTurnSandboxPolicy(input.runtimeMode),
...(input.model ? { model: input.model } : {}),
...(input.serviceTier ? { serviceTier: input.serviceTier } : {}),
Expand Down
6 changes: 6 additions & 0 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ import {
LockIcon,
LockOpenIcon,
PenLineIcon,
SparklesIcon,
XIcon,
} from "lucide-react";
import { proposedPlanTitle } from "../../proposedPlan";
Expand Down Expand Up @@ -148,6 +149,11 @@ const runtimeModeConfig: Record<
description: "Auto-approve edits, ask before other actions.",
icon: PenLineIcon,
},
auto: {
label: "Auto",
description: "An AI reviewer approves routine actions; risky ones still ask.",
icon: SparklesIcon,
},
"full-access": {
label: "Full access",
description: "Allow commands and edits without prompts.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ export const CompactComposerControlsMenu = memo(function CompactComposerControls
>
<MenuRadioItem value="approval-required">Supervised</MenuRadioItem>
<MenuRadioItem value="auto-accept-edits">Auto-accept edits</MenuRadioItem>
<MenuRadioItem value="auto">Auto</MenuRadioItem>
<MenuRadioItem value="full-access">Full access</MenuRadioItem>
</MenuRadioGroup>
{props.activePlan ? (
Expand Down
1 change: 1 addition & 0 deletions packages/contracts/src/orchestration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export type ModelSelection = typeof ModelSelection.Type;
export const RuntimeMode = Schema.Literals([
"approval-required",
"auto-accept-edits",
"auto",
"full-access",
]);
export type RuntimeMode = typeof RuntimeMode.Type;
Expand Down
Loading