| author | |
| committer | |
| log | fbd77420f27c1322d49726dd927861b8b5903494 |
| tree | ce1574cdb761d8f722efd97df2a814e975591592 |
| parent | 16491a84b9bb2842ad6db41635d087647fc2df5b |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>9 files changed, 79 insertions(+), 2 deletions(-)
apps/mobile/src/features/threads/NewTaskDraftScreen.tsx+4-1| ... | ... | @@ -582,10 +582,13 @@ export function NewTaskDraftScreen(props: { |
| 582 | 582 | ? "Approve actions" |
| 583 | 583 | : flow.runtimeMode === "auto-accept-edits" |
| 584 | 584 | ? "Auto-accept edits" |
| 585 | : "Full access", | |
| 585 | : flow.runtimeMode === "auto" | |
| 586 | ? "Auto" | |
| 587 | : "Full access", | |
| 586 | 588 | subactions: [ |
| 587 | 589 | { id: "options:runtime:approval-required", title: "Approve actions" }, |
| 588 | 590 | { id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" }, |
| 591 | { id: "options:runtime:auto", title: "Auto" }, | |
| 589 | 592 | { id: "options:runtime:full-access", title: "Full access" }, |
| 590 | 593 | ].map((option) => { |
| 591 | 594 | const value = option.id.replace("options:runtime:", ""); |
apps/mobile/src/features/threads/ThreadComposer.tsx+4-1| ... | ... | @@ -638,10 +638,13 @@ export const ThreadComposer = memo(function ThreadComposer(props: ThreadComposer |
| 638 | 638 | ? "Approve actions" |
| 639 | 639 | : currentRuntimeMode === "auto-accept-edits" |
| 640 | 640 | ? "Auto-accept edits" |
| 641 | : "Full access", | |
| 641 | : currentRuntimeMode === "auto" | |
| 642 | ? "Auto" | |
| 643 | : "Full access", | |
| 642 | 644 | subactions: [ |
| 643 | 645 | { id: "options:runtime:approval-required", title: "Approve actions" }, |
| 644 | 646 | { id: "options:runtime:auto-accept-edits", title: "Auto-accept edits" }, |
| 647 | { id: "options:runtime:auto", title: "Auto" }, | |
| 645 | 648 | { id: "options:runtime:full-access", title: "Full access" }, |
| 646 | 649 | ].map((option) => { |
| 647 | 650 | const value = option.id.replace("options:runtime:", ""); |
apps/server/src/provider/Layers/ClaudeAdapter.test.ts+19| ... | ... | @@ -356,6 +356,25 @@ describe("ClaudeAdapterLive", () => { |
| 356 | 356 | ); |
| 357 | 357 | }); |
| 358 | 358 | |
| 359 | it.effect("derives auto permission mode from auto runtime policy without skip flag", () => { | |
| 360 | const harness = makeHarness(); | |
| 361 | return Effect.gen(function* () { | |
| 362 | const adapter = yield* ClaudeAdapter; | |
| 363 | yield* adapter.startSession({ | |
| 364 | threadId: THREAD_ID, | |
| 365 | provider: ProviderDriverKind.make("claudeAgent"), | |
| 366 | runtimeMode: "auto", | |
| 367 | }); | |
| 368 | ||
| 369 | const createInput = harness.getLastCreateQueryInput(); | |
| 370 | assert.equal(createInput?.options.permissionMode, "auto"); | |
| 371 | assert.equal(createInput?.options.allowDangerouslySkipPermissions, undefined); | |
| 372 | }).pipe( | |
| 373 | Effect.provideService(Random.Random, makeDeterministicRandomService()), | |
| 374 | Effect.provide(harness.layer), | |
| 375 | ); | |
| 376 | }); | |
| 377 | ||
| 359 | 378 | it.effect("loads Claude filesystem settings sources for SDK sessions", () => { |
| 360 | 379 | const harness = makeHarness(); |
| 361 | 380 | return Effect.gen(function* () { |
apps/server/src/provider/Layers/ClaudeAdapter.ts+1| ... | ... | @@ -3509,6 +3509,7 @@ export const makeClaudeAdapter = Effect.fn("makeClaudeAdapter")(function* ( |
| 3509 | 3509 | const effectiveEffort = getEffectiveClaudeAgentEffort(effort, modelSelection?.model); |
| 3510 | 3510 | const runtimeModeToPermission: Record<string, PermissionMode> = { |
| 3511 | 3511 | "auto-accept-edits": "acceptEdits", |
| 3512 | auto: "auto", | |
| 3512 | 3513 | "full-access": "bypassPermissions", |
| 3513 | 3514 | }; |
| 3514 | 3515 | const permissionMode = runtimeModeToPermission[input.runtimeMode]; |
apps/server/src/provider/Layers/CodexSessionRuntime.test.ts+28| ... | ... | @@ -104,6 +104,7 @@ describe("buildTurnStartParams", () => { |
| 104 | 104 | NodeAssert.deepStrictEqual(params, { |
| 105 | 105 | threadId: "provider-thread-1", |
| 106 | 106 | approvalPolicy: "never", |
| 107 | approvalsReviewer: "user", | |
| 107 | 108 | sandboxPolicy: { |
| 108 | 109 | type: "dangerFullAccess", |
| 109 | 110 | }, |
| ... | ... | @@ -149,6 +150,7 @@ describe("buildTurnStartParams", () => { |
| 149 | 150 | NodeAssert.deepStrictEqual(params, { |
| 150 | 151 | threadId: "provider-thread-1", |
| 151 | 152 | approvalPolicy: "on-request", |
| 153 | approvalsReviewer: "user", | |
| 152 | 154 | sandboxPolicy: { |
| 153 | 155 | type: "workspaceWrite", |
| 154 | 156 | }, |
| ... | ... | @@ -193,6 +195,31 @@ describe("buildTurnStartParams", () => { |
| 193 | 195 | NodeAssert.ok(settings?.developer_instructions?.includes(`as ${DEFAULT_MODEL} with medium`)); |
| 194 | 196 | }); |
| 195 | 197 | |
| 198 | it.effect("routes approvals to the auto reviewer in auto mode", () => | |
| 199 | Effect.gen(function* () { | |
| 200 | const params = yield* buildTurnStartParams({ | |
| 201 | threadId: "provider-thread-1", | |
| 202 | runtimeMode: "auto", | |
| 203 | prompt: "Ship it", | |
| 204 | }); | |
| 205 | ||
| 206 | NodeAssert.deepStrictEqual(params, { | |
| 207 | threadId: "provider-thread-1", | |
| 208 | approvalPolicy: "on-request", | |
| 209 | approvalsReviewer: "auto_review", | |
| 210 | sandboxPolicy: { | |
| 211 | type: "workspaceWrite", | |
| 212 | }, | |
| 213 | input: [ | |
| 214 | { | |
| 215 | type: "text", | |
| 216 | text: "Ship it", | |
| 217 | }, | |
| 218 | ], | |
| 219 | }); | |
| 220 | }), | |
| 221 | ); | |
| 222 | ||
| 196 | 223 | it("omits collaboration mode when interaction mode is absent", () => { |
| 197 | 224 | const params = Effect.runSync( |
| 198 | 225 | buildTurnStartParams({ |
| ... | ... | @@ -205,6 +232,7 @@ describe("buildTurnStartParams", () => { |
| 205 | 232 | NodeAssert.deepStrictEqual(params, { |
| 206 | 233 | threadId: "provider-thread-1", |
| 207 | 234 | approvalPolicy: "untrusted", |
| 235 | approvalsReviewer: "user", | |
| 208 | 236 | sandboxPolicy: { |
| 209 | 237 | type: "readOnly", |
| 210 | 238 | }, |
apps/server/src/provider/Layers/CodexSessionRuntime.ts+15| ... | ... | @@ -264,23 +264,35 @@ function readResumeCursorThreadId( |
| 264 | 264 | function runtimeModeToThreadConfig(input: RuntimeMode): { |
| 265 | 265 | readonly approvalPolicy: EffectCodexSchema.V2ThreadStartParams__AskForApproval; |
| 266 | 266 | readonly sandbox: EffectCodexSchema.V2ThreadStartParams__SandboxMode; |
| 267 | // Always explicit: omitting the field on resume keeps the thread's previous | |
| 268 | // reviewer, which would leave auto_review sticky after switching modes. | |
| 269 | readonly approvalsReviewer: EffectCodexSchema.V2ThreadStartParams__ApprovalsReviewer; | |
| 267 | 270 | } { |
| 268 | 271 | switch (input) { |
| 269 | 272 | case "approval-required": |
| 270 | 273 | return { |
| 271 | 274 | approvalPolicy: "untrusted", |
| 272 | 275 | sandbox: "read-only", |
| 276 | approvalsReviewer: "user", | |
| 273 | 277 | }; |
| 274 | 278 | case "auto-accept-edits": |
| 275 | 279 | return { |
| 276 | 280 | approvalPolicy: "on-request", |
| 277 | 281 | sandbox: "workspace-write", |
| 282 | approvalsReviewer: "user", | |
| 283 | }; | |
| 284 | case "auto": | |
| 285 | return { | |
| 286 | approvalPolicy: "on-request", | |
| 287 | sandbox: "workspace-write", | |
| 288 | approvalsReviewer: "auto_review", | |
| 278 | 289 | }; |
| 279 | 290 | case "full-access": |
| 280 | 291 | default: |
| 281 | 292 | return { |
| 282 | 293 | approvalPolicy: "never", |
| 283 | 294 | sandbox: "danger-full-access", |
| 295 | approvalsReviewer: "user", | |
| 284 | 296 | }; |
| 285 | 297 | } |
| 286 | 298 | } |
| ... | ... | @@ -296,6 +308,7 @@ function buildThreadStartParams(input: { |
| 296 | 308 | cwd: input.cwd, |
| 297 | 309 | approvalPolicy: config.approvalPolicy, |
| 298 | 310 | sandbox: config.sandbox, |
| 311 | approvalsReviewer: config.approvalsReviewer, | |
| 299 | 312 | ...(input.model ? { model: input.model } : {}), |
| 300 | 313 | ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), |
| 301 | 314 | }; |
| ... | ... | @@ -310,6 +323,7 @@ function runtimeModeToTurnSandboxPolicy( |
| 310 | 323 | type: "readOnly", |
| 311 | 324 | }; |
| 312 | 325 | case "auto-accept-edits": |
| 326 | case "auto": | |
| 313 | 327 | return { |
| 314 | 328 | type: "workspaceWrite", |
| 315 | 329 | }; |
| ... | ... | @@ -382,6 +396,7 @@ export function buildTurnStartParams(input: { |
| 382 | 396 | threadId: input.threadId, |
| 383 | 397 | input: turnInput, |
| 384 | 398 | approvalPolicy: config.approvalPolicy, |
| 399 | approvalsReviewer: config.approvalsReviewer, | |
| 385 | 400 | sandboxPolicy: runtimeModeToTurnSandboxPolicy(input.runtimeMode), |
| 386 | 401 | ...(input.model ? { model: input.model } : {}), |
| 387 | 402 | ...(input.serviceTier ? { serviceTier: input.serviceTier } : {}), |
apps/web/src/components/chat/ChatComposer.tsx+6| ... | ... | @@ -106,6 +106,7 @@ import { |
| 106 | 106 | LockIcon, |
| 107 | 107 | LockOpenIcon, |
| 108 | 108 | PenLineIcon, |
| 109 | SparklesIcon, | |
| 109 | 110 | XIcon, |
| 110 | 111 | } from "lucide-react"; |
| 111 | 112 | import { proposedPlanTitle } from "../../proposedPlan"; |
| ... | ... | @@ -149,6 +150,11 @@ const runtimeModeConfig: Record< |
| 149 | 150 | description: "Auto-approve edits, ask before other actions.", |
| 150 | 151 | icon: PenLineIcon, |
| 151 | 152 | }, |
| 153 | auto: { | |
| 154 | label: "Auto", | |
| 155 | description: "An AI reviewer approves routine actions; risky ones still ask.", | |
| 156 | icon: SparklesIcon, | |
| 157 | }, | |
| 152 | 158 | "full-access": { |
| 153 | 159 | label: "Full access", |
| 154 | 160 | description: "Allow commands and edits without prompts.", |
apps/web/src/components/chat/CompactComposerControlsMenu.tsx+1| ... | ... | @@ -71,6 +71,7 @@ export const CompactComposerControlsMenu = memo(function CompactComposerControls |
| 71 | 71 | > |
| 72 | 72 | <MenuRadioItem value="approval-required">Supervised</MenuRadioItem> |
| 73 | 73 | <MenuRadioItem value="auto-accept-edits">Auto-accept edits</MenuRadioItem> |
| 74 | <MenuRadioItem value="auto">Auto</MenuRadioItem> | |
| 74 | 75 | <MenuRadioItem value="full-access">Full access</MenuRadioItem> |
| 75 | 76 | </MenuRadioGroup> |
| 76 | 77 | {props.activePlan ? ( |
packages/contracts/src/orchestration.ts+1| ... | ... | @@ -117,6 +117,7 @@ export type ModelSelection = typeof ModelSelection.Type; |
| 117 | 117 | export const RuntimeMode = Schema.Literals([ |
| 118 | 118 | "approval-required", |
| 119 | 119 | "auto-accept-edits", |
| 120 | "auto", | |
| 120 | 121 | "full-access", |
| 121 | 122 | ]); |
| 122 | 123 | export type RuntimeMode = typeof RuntimeMode.Type; |