| author | |
| committer | |
| log | 8ef653fcc0270855a7e39140664e584083e759b4 |
| tree | 4b109cf58599e2baaa20c060855fe0d93048fe0a |
| parent | fb8d66b8d321ce58a52768560cdc3349d7db878d |
- Make `approvalPolicy` and `sandboxMode` required in orchestration contracts and turn-start flow
- Add defaults in provider reactors/ingestion to guarantee values are always set
- Store and hydrate both fields in projection persistence, snapshots, and related tests19 files changed, 106 insertions(+), 36 deletions(-)
apps/server/integration/orchestrationEngine.integration.test.ts+2| ... | ... | @@ -136,6 +136,8 @@ const startTurn = (input: { |
| 136 | 136 | text: input.text, |
| 137 | 137 | attachments: [], |
| 138 | 138 | }, |
| 139 | approvalPolicy: "on-request", | |
| 140 | sandboxMode: "workspace-write", | |
| 139 | 141 | createdAt: nowIso(), |
| 140 | 142 | }); |
| 141 | 143 |
apps/server/src/orchestration/Layers/CheckpointReactor.test.ts+16| ... | ... | @@ -317,6 +317,8 @@ describe("CheckpointReactor", () => { |
| 317 | 317 | providerName: "codex", |
| 318 | 318 | providerSessionId: asSessionId("sess-1"), |
| 319 | 319 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 320 | approvalPolicy: "on-request", | |
| 321 | sandboxMode: "workspace-write", | |
| 320 | 322 | activeTurnId: null, |
| 321 | 323 | lastError: null, |
| 322 | 324 | updatedAt: createdAt, |
| ... | ... | @@ -394,6 +396,8 @@ describe("CheckpointReactor", () => { |
| 394 | 396 | providerName: "codex", |
| 395 | 397 | providerSessionId: asSessionId("sess-1"), |
| 396 | 398 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 399 | approvalPolicy: "on-request", | |
| 400 | sandboxMode: "workspace-write", | |
| 397 | 401 | activeTurnId: null, |
| 398 | 402 | lastError: null, |
| 399 | 403 | updatedAt: createdAt, |
| ... | ... | @@ -445,6 +449,8 @@ describe("CheckpointReactor", () => { |
| 445 | 449 | text: "start turn", |
| 446 | 450 | attachments: [], |
| 447 | 451 | }, |
| 452 | approvalPolicy: "on-request", | |
| 453 | sandboxMode: "workspace-write", | |
| 448 | 454 | createdAt: new Date().toISOString(), |
| 449 | 455 | }), |
| 450 | 456 | ); |
| ... | ... | @@ -481,6 +487,8 @@ describe("CheckpointReactor", () => { |
| 481 | 487 | providerName: "codex", |
| 482 | 488 | providerSessionId: asSessionId("sess-missing"), |
| 483 | 489 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"), |
| 490 | approvalPolicy: "on-request", | |
| 491 | sandboxMode: "workspace-write", | |
| 484 | 492 | activeTurnId: asTurnId("turn-missing-cwd"), |
| 485 | 493 | lastError: null, |
| 486 | 494 | updatedAt: createdAt, |
| ... | ... | @@ -529,6 +537,8 @@ describe("CheckpointReactor", () => { |
| 529 | 537 | providerName: "codex", |
| 530 | 538 | providerSessionId: asSessionId("sess-1"), |
| 531 | 539 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 540 | approvalPolicy: "on-request", | |
| 541 | sandboxMode: "workspace-write", | |
| 532 | 542 | activeTurnId: null, |
| 533 | 543 | lastError: null, |
| 534 | 544 | updatedAt: createdAt, |
| ... | ... | @@ -580,6 +590,8 @@ describe("CheckpointReactor", () => { |
| 580 | 590 | providerName: "codex", |
| 581 | 591 | providerSessionId: asSessionId("sess-1"), |
| 582 | 592 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 593 | approvalPolicy: "on-request", | |
| 594 | sandboxMode: "workspace-write", | |
| 583 | 595 | activeTurnId: null, |
| 584 | 596 | lastError: null, |
| 585 | 597 | updatedAt: createdAt, |
| ... | ... | @@ -634,6 +646,8 @@ describe("CheckpointReactor", () => { |
| 634 | 646 | providerName: "codex", |
| 635 | 647 | providerSessionId: asSessionId("sess-1"), |
| 636 | 648 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 649 | approvalPolicy: "on-request", | |
| 650 | sandboxMode: "workspace-write", | |
| 637 | 651 | activeTurnId: null, |
| 638 | 652 | lastError: null, |
| 639 | 653 | updatedAt: createdAt, |
| ... | ... | @@ -713,6 +727,8 @@ describe("CheckpointReactor", () => { |
| 713 | 727 | providerName: "codex", |
| 714 | 728 | providerSessionId: asSessionId("sess-1"), |
| 715 | 729 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 730 | approvalPolicy: "on-request", | |
| 731 | sandboxMode: "workspace-write", | |
| 716 | 732 | activeTurnId: null, |
| 717 | 733 | lastError: null, |
| 718 | 734 | updatedAt: createdAt, |
apps/server/src/orchestration/Layers/OrchestrationEngine.test.ts+6| ... | ... | @@ -92,6 +92,8 @@ describe("OrchestrationEngine", () => { |
| 92 | 92 | text: "hello", |
| 93 | 93 | attachments: [], |
| 94 | 94 | }, |
| 95 | approvalPolicy: "on-request", | |
| 96 | sandboxMode: "workspace-write", | |
| 95 | 97 | createdAt, |
| 96 | 98 | }), |
| 97 | 99 | ); |
| ... | ... | @@ -428,6 +430,8 @@ describe("OrchestrationEngine", () => { |
| 428 | 430 | text: "hello", |
| 429 | 431 | attachments: [], |
| 430 | 432 | }, |
| 433 | approvalPolicy: "on-request" as const, | |
| 434 | sandboxMode: "workspace-write" as const, | |
| 431 | 435 | createdAt, |
| 432 | 436 | }; |
| 433 | 437 | |
| ... | ... | @@ -586,6 +590,8 @@ describe("OrchestrationEngine", () => { |
| 586 | 590 | text: "hello", |
| 587 | 591 | attachments: [], |
| 588 | 592 | }, |
| 593 | approvalPolicy: "on-request", | |
| 594 | sandboxMode: "workspace-write", | |
| 589 | 595 | createdAt: now(), |
| 590 | 596 | }), |
| 591 | 597 | ), |
apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts+4| ... | ... | @@ -794,6 +794,8 @@ it.effect("restores pending turn-start metadata across projection pipeline resta |
| 794 | 794 | payload: { |
| 795 | 795 | threadId, |
| 796 | 796 | messageId, |
| 797 | approvalPolicy: "on-request", | |
| 798 | sandboxMode: "workspace-write", | |
| 797 | 799 | createdAt: turnStartedAt, |
| 798 | 800 | }, |
| 799 | 801 | }); |
| ... | ... | @@ -827,6 +829,8 @@ it.effect("restores pending turn-start metadata across projection pipeline resta |
| 827 | 829 | providerName: "codex", |
| 828 | 830 | providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"), |
| 829 | 831 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"), |
| 832 | approvalPolicy: "on-request", | |
| 833 | sandboxMode: "workspace-write", | |
| 830 | 834 | activeTurnId: turnId, |
| 831 | 835 | lastError: null, |
| 832 | 836 | updatedAt: sessionSetAt, |
apps/server/src/orchestration/Layers/ProjectionPipeline.ts+2| ... | ... | @@ -473,6 +473,8 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () { |
| 473 | 473 | providerName: event.payload.session.providerName, |
| 474 | 474 | providerSessionId: event.payload.session.providerSessionId, |
| 475 | 475 | providerThreadId: event.payload.session.providerThreadId, |
| 476 | approvalPolicy: event.payload.session.approvalPolicy, | |
| 477 | sandboxMode: event.payload.session.sandboxMode, | |
| 476 | 478 | activeTurnId: event.payload.session.activeTurnId, |
| 477 | 479 | lastError: event.payload.session.lastError, |
| 478 | 480 | updatedAt: event.payload.session.updatedAt, |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.test.ts+6| ... | ... | @@ -143,6 +143,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 143 | 143 | provider_name, |
| 144 | 144 | provider_session_id, |
| 145 | 145 | provider_thread_id, |
| 146 | approval_policy, | |
| 147 | sandbox_mode, | |
| 146 | 148 | active_turn_id, |
| 147 | 149 | last_error, |
| 148 | 150 | updated_at |
| ... | ... | @@ -153,6 +155,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 153 | 155 | 'codex', |
| 154 | 156 | 'provider-session-1', |
| 155 | 157 | 'provider-thread-1', |
| 158 | 'on-request', | |
| 159 | 'workspace-write', | |
| 156 | 160 | 'turn-1', |
| 157 | 161 | NULL, |
| 158 | 162 | '2026-02-24T00:00:07.000Z' |
| ... | ... | @@ -282,6 +286,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => { |
| 282 | 286 | providerName: "codex", |
| 283 | 287 | providerSessionId: asProviderSessionId("provider-session-1"), |
| 284 | 288 | providerThreadId: asProviderThreadId("provider-thread-1"), |
| 289 | approvalPolicy: "on-request", | |
| 290 | sandboxMode: "workspace-write", | |
| 285 | 291 | activeTurnId: asTurnId("turn-1"), |
| 286 | 292 | lastError: null, |
| 287 | 293 | updatedAt: "2026-02-24T00:00:07.000Z", |
apps/server/src/orchestration/Layers/ProjectionSnapshotQuery.ts+4| ... | ... | @@ -196,6 +196,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 196 | 196 | provider_name AS "providerName", |
| 197 | 197 | provider_session_id AS "providerSessionId", |
| 198 | 198 | provider_thread_id AS "providerThreadId", |
| 199 | approval_policy AS "approvalPolicy", | |
| 200 | sandbox_mode AS "sandboxMode", | |
| 199 | 201 | active_turn_id AS "activeTurnId", |
| 200 | 202 | last_error AS "lastError", |
| 201 | 203 | updated_at AS "updatedAt" |
| ... | ... | @@ -378,6 +380,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () { |
| 378 | 380 | providerName: row.providerName, |
| 379 | 381 | providerSessionId: row.providerSessionId, |
| 380 | 382 | providerThreadId: row.providerThreadId, |
| 383 | approvalPolicy: row.approvalPolicy, | |
| 384 | sandboxMode: row.sandboxMode, | |
| 381 | 385 | activeTurnId: row.activeTurnId, |
| 382 | 386 | lastError: row.lastError, |
| 383 | 387 | updatedAt: row.updatedAt, |
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+6| ... | ... | @@ -319,6 +319,8 @@ describe("ProviderCommandReactor", () => { |
| 319 | 319 | providerName: "codex", |
| 320 | 320 | providerSessionId: asSessionId("sess-1"), |
| 321 | 321 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 322 | approvalPolicy: "on-request", | |
| 323 | sandboxMode: "workspace-write", | |
| 322 | 324 | activeTurnId: asTurnId("turn-1"), |
| 323 | 325 | lastError: null, |
| 324 | 326 | updatedAt: now, |
| ... | ... | @@ -358,6 +360,8 @@ describe("ProviderCommandReactor", () => { |
| 358 | 360 | providerName: "codex", |
| 359 | 361 | providerSessionId: asSessionId("sess-1"), |
| 360 | 362 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 363 | approvalPolicy: "on-request", | |
| 364 | sandboxMode: "workspace-write", | |
| 361 | 365 | activeTurnId: null, |
| 362 | 366 | lastError: null, |
| 363 | 367 | updatedAt: now, |
| ... | ... | @@ -400,6 +404,8 @@ describe("ProviderCommandReactor", () => { |
| 400 | 404 | providerName: "codex", |
| 401 | 405 | providerSessionId: asSessionId("sess-1"), |
| 402 | 406 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 407 | approvalPolicy: "on-request", | |
| 408 | sandboxMode: "workspace-write", | |
| 403 | 409 | activeTurnId: null, |
| 404 | 410 | lastError: null, |
| 405 | 411 | updatedAt: now, |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+18-14| ... | ... | @@ -64,6 +64,8 @@ const serverCommandId = (tag: string): CommandId => |
| 64 | 64 | |
| 65 | 65 | const HANDLED_TURN_START_KEY_MAX = 10_000; |
| 66 | 66 | const HANDLED_TURN_START_KEY_TTL = Duration.minutes(30); |
| 67 | const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never"; | |
| 68 | const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; | |
| 67 | 69 | |
| 68 | 70 | const make = Effect.gen(function* () { |
| 69 | 71 | const orchestrationEngine = yield* OrchestrationEngineService; |
| ... | ... | @@ -143,8 +145,10 @@ const make = Effect.gen(function* () { |
| 143 | 145 | return yield* Effect.die(new Error(`Thread '${threadId}' was not found in read model.`)); |
| 144 | 146 | } |
| 145 | 147 | |
| 146 | const desiredApprovalPolicy = options?.approvalPolicy ?? thread.session?.approvalPolicy; | |
| 147 | const desiredSandboxMode = options?.sandboxMode ?? thread.session?.sandboxMode; | |
| 148 | const desiredApprovalPolicy = | |
| 149 | options?.approvalPolicy ?? thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY; | |
| 150 | const desiredSandboxMode = | |
| 151 | options?.sandboxMode ?? thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE; | |
| 148 | 152 | const preferredProvider: ProviderKind | undefined = |
| 149 | 153 | thread.session?.providerName === "codex" || thread.session?.providerName === "claudeCode" |
| 150 | 154 | ? thread.session.providerName |
| ... | ... | @@ -160,8 +164,8 @@ const make = Effect.gen(function* () { |
| 160 | 164 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), |
| 161 | 165 | ...(thread.model ? { model: thread.model } : {}), |
| 162 | 166 | ...(resumeThreadId ? { resumeThreadId } : {}), |
| 163 | ...(desiredApprovalPolicy !== undefined ? { approvalPolicy: desiredApprovalPolicy } : {}), | |
| 164 | ...(desiredSandboxMode !== undefined ? { sandboxMode: desiredSandboxMode } : {}), | |
| 167 | approvalPolicy: desiredApprovalPolicy, | |
| 168 | sandboxMode: desiredSandboxMode, | |
| 165 | 169 | }); |
| 166 | 170 | |
| 167 | 171 | const bindSessionToThread = (session: ProviderSession) => |
| ... | ... | @@ -173,8 +177,8 @@ const make = Effect.gen(function* () { |
| 173 | 177 | providerName: session.provider, |
| 174 | 178 | providerSessionId: session.sessionId, |
| 175 | 179 | providerThreadId: session.threadId ?? null, |
| 176 | ...(desiredApprovalPolicy !== undefined ? { approvalPolicy: desiredApprovalPolicy } : {}), | |
| 177 | ...(desiredSandboxMode !== undefined ? { sandboxMode: desiredSandboxMode } : {}), | |
| 180 | approvalPolicy: desiredApprovalPolicy, | |
| 181 | sandboxMode: desiredSandboxMode, | |
| 178 | 182 | // Provider turn ids are not orchestration turn ids. |
| 179 | 183 | activeTurnId: null, |
| 180 | 184 | lastError: session.lastError ?? null, |
| ... | ... | @@ -211,8 +215,8 @@ const make = Effect.gen(function* () { |
| 211 | 215 | readonly attachments?: ReadonlyArray<ChatAttachment>; |
| 212 | 216 | readonly model?: string; |
| 213 | 217 | readonly effort?: string; |
| 214 | readonly approvalPolicy?: ProviderApprovalPolicy; | |
| 215 | readonly sandboxMode?: ProviderSandboxMode; | |
| 218 | readonly approvalPolicy: ProviderApprovalPolicy; | |
| 219 | readonly sandboxMode: ProviderSandboxMode; | |
| 216 | 220 | readonly createdAt: string; |
| 217 | 221 | }) { |
| 218 | 222 | const thread = yield* resolveThread(input.threadId); |
| ... | ... | @@ -220,8 +224,8 @@ const make = Effect.gen(function* () { |
| 220 | 224 | return; |
| 221 | 225 | } |
| 222 | 226 | const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, { |
| 223 | ...(input.approvalPolicy !== undefined ? { approvalPolicy: input.approvalPolicy } : {}), | |
| 224 | ...(input.sandboxMode !== undefined ? { sandboxMode: input.sandboxMode } : {}), | |
| 227 | approvalPolicy: input.approvalPolicy, | |
| 228 | sandboxMode: input.sandboxMode, | |
| 225 | 229 | }); |
| 226 | 230 | const normalizedInput = toNonEmptyProviderInput(input.messageText); |
| 227 | 231 | const normalizedAttachments = input.attachments ?? []; |
| ... | ... | @@ -267,10 +271,8 @@ const make = Effect.gen(function* () { |
| 267 | 271 | ...(message.attachments !== undefined ? { attachments: message.attachments } : {}), |
| 268 | 272 | ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), |
| 269 | 273 | ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}), |
| 270 | ...(event.payload.approvalPolicy !== undefined | |
| 271 | ? { approvalPolicy: event.payload.approvalPolicy } | |
| 272 | : {}), | |
| 273 | ...(event.payload.sandboxMode !== undefined ? { sandboxMode: event.payload.sandboxMode } : {}), | |
| 274 | approvalPolicy: event.payload.approvalPolicy, | |
| 275 | sandboxMode: event.payload.sandboxMode, | |
| 274 | 276 | createdAt: event.payload.createdAt, |
| 275 | 277 | }); |
| 276 | 278 | }); |
| ... | ... | @@ -347,6 +349,8 @@ const make = Effect.gen(function* () { |
| 347 | 349 | providerName: thread.session?.providerName ?? null, |
| 348 | 350 | providerSessionId: null, |
| 349 | 351 | providerThreadId: null, |
| 352 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | |
| 353 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | |
| 350 | 354 | activeTurnId: null, |
| 351 | 355 | lastError: thread.session?.lastError ?? null, |
| 352 | 356 | updatedAt: now, |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.test.ts+4| ... | ... | @@ -164,6 +164,8 @@ describe("ProviderRuntimeIngestion", () => { |
| 164 | 164 | providerName: "codex", |
| 165 | 165 | providerSessionId: asSessionId("sess-1"), |
| 166 | 166 | providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"), |
| 167 | approvalPolicy: "on-request", | |
| 168 | sandboxMode: "workspace-write", | |
| 167 | 169 | activeTurnId: null, |
| 168 | 170 | updatedAt: createdAt, |
| 169 | 171 | lastError: null, |
| ... | ... | @@ -332,6 +334,8 @@ describe("ProviderRuntimeIngestion", () => { |
| 332 | 334 | attachments: [], |
| 333 | 335 | }, |
| 334 | 336 | assistantDeliveryMode: "streaming", |
| 337 | approvalPolicy: "on-request", | |
| 338 | sandboxMode: "workspace-write", | |
| 335 | 339 | createdAt: now, |
| 336 | 340 | }), |
| 337 | 341 | ); |
apps/server/src/orchestration/Layers/ProviderRuntimeIngestion.ts+8-12| ... | ... | @@ -3,6 +3,8 @@ import { |
| 3 | 3 | CommandId, |
| 4 | 4 | MessageId, |
| 5 | 5 | type OrchestrationEvent, |
| 6 | type ProviderApprovalPolicy, | |
| 7 | type ProviderSandboxMode, | |
| 6 | 8 | ProviderThreadId, |
| 7 | 9 | type ThreadId, |
| 8 | 10 | TurnId, |
| ... | ... | @@ -24,6 +26,8 @@ const providerCommandId = (event: ProviderRuntimeEvent, tag: string): CommandId |
| 24 | 26 | CommandId.makeUnsafe(`provider:${event.eventId}:${tag}:${crypto.randomUUID()}`); |
| 25 | 27 | |
| 26 | 28 | const DEFAULT_ASSISTANT_DELIVERY_MODE: AssistantDeliveryMode = "buffered"; |
| 29 | const DEFAULT_APPROVAL_POLICY: ProviderApprovalPolicy = "never"; | |
| 30 | const DEFAULT_SANDBOX_MODE: ProviderSandboxMode = "workspace-write"; | |
| 27 | 31 | const TURN_MESSAGE_IDS_BY_TURN_CACHE_CAPACITY = 10_000; |
| 28 | 32 | const TURN_MESSAGE_IDS_BY_TURN_TTL = Duration.minutes(120); |
| 29 | 33 | const BUFFERED_MESSAGE_TEXT_BY_MESSAGE_ID_CACHE_CAPACITY = 20_000; |
| ... | ... | @@ -371,12 +375,8 @@ const make = Effect.gen(function* () { |
| 371 | 375 | providerName: event.provider, |
| 372 | 376 | providerSessionId: event.sessionId, |
| 373 | 377 | providerThreadId, |
| 374 | ...(thread.session?.approvalPolicy !== undefined | |
| 375 | ? { approvalPolicy: thread.session.approvalPolicy } | |
| 376 | : {}), | |
| 377 | ...(thread.session?.sandboxMode !== undefined | |
| 378 | ? { sandboxMode: thread.session.sandboxMode } | |
| 379 | : {}), | |
| 378 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | |
| 379 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | |
| 380 | 380 | activeTurnId, |
| 381 | 381 | lastError, |
| 382 | 382 | updatedAt: now, |
| ... | ... | @@ -485,12 +485,8 @@ const make = Effect.gen(function* () { |
| 485 | 485 | providerName: event.provider, |
| 486 | 486 | providerSessionId: event.sessionId, |
| 487 | 487 | providerThreadId, |
| 488 | ...(thread.session?.approvalPolicy !== undefined | |
| 489 | ? { approvalPolicy: thread.session.approvalPolicy } | |
| 490 | : {}), | |
| 491 | ...(thread.session?.sandboxMode !== undefined | |
| 492 | ? { sandboxMode: thread.session.sandboxMode } | |
| 493 | : {}), | |
| 488 | approvalPolicy: thread.session?.approvalPolicy ?? DEFAULT_APPROVAL_POLICY, | |
| 489 | sandboxMode: thread.session?.sandboxMode ?? DEFAULT_SANDBOX_MODE, | |
| 494 | 490 | activeTurnId: toTurnId(event.turnId) ?? null, |
| 495 | 491 | lastError: event.message, |
| 496 | 492 | updatedAt: now, |
apps/server/src/orchestration/commandInvariants.test.ts+2| ... | ... | @@ -90,6 +90,8 @@ const messageSendCommand: OrchestrationCommand = { |
| 90 | 90 | text: "hello", |
| 91 | 91 | attachments: [], |
| 92 | 92 | }, |
| 93 | approvalPolicy: "on-request", | |
| 94 | sandboxMode: "workspace-write", | |
| 93 | 95 | createdAt: now, |
| 94 | 96 | }; |
| 95 | 97 |
apps/server/src/orchestration/decider.ts+2-4| ... | ... | @@ -254,10 +254,8 @@ export const decideOrchestrationCommand = Effect.fn("decideOrchestrationCommand" |
| 254 | 254 | ...(command.model !== undefined ? { model: command.model } : {}), |
| 255 | 255 | ...(command.effort !== undefined ? { effort: command.effort } : {}), |
| 256 | 256 | assistantDeliveryMode: command.assistantDeliveryMode ?? DEFAULT_ASSISTANT_DELIVERY_MODE, |
| 257 | ...(command.approvalPolicy !== undefined | |
| 258 | ? { approvalPolicy: command.approvalPolicy } | |
| 259 | : {}), | |
| 260 | ...(command.sandboxMode !== undefined ? { sandboxMode: command.sandboxMode } : {}), | |
| 257 | approvalPolicy: command.approvalPolicy, | |
| 258 | sandboxMode: command.sandboxMode, | |
| 261 | 259 | createdAt: command.createdAt, |
| 262 | 260 | }, |
| 263 | 261 | }; |
apps/server/src/orchestration/projector.test.ts+4| ... | ... | @@ -135,6 +135,8 @@ describe("orchestration projector", () => { |
| 135 | 135 | payload: { |
| 136 | 136 | threadId: "thread-1", |
| 137 | 137 | messageId: "message-1", |
| 138 | approvalPolicy: "on-request", | |
| 139 | sandboxMode: "workspace-write", | |
| 138 | 140 | createdAt: "2026-01-01T00:00:00.000Z", |
| 139 | 141 | }, |
| 140 | 142 | }), |
| ... | ... | @@ -193,6 +195,8 @@ describe("orchestration projector", () => { |
| 193 | 195 | providerName: "codex", |
| 194 | 196 | providerSessionId: "session-1", |
| 195 | 197 | providerThreadId: "provider-thread-1", |
| 198 | approvalPolicy: "on-request", | |
| 199 | sandboxMode: "workspace-write", | |
| 196 | 200 | activeTurnId: "turn-1", |
| 197 | 201 | lastError: null, |
| 198 | 202 | updatedAt: startedAt, |
apps/server/src/persistence/Layers/ProjectionThreadSessions.ts+8| ... | ... | @@ -25,6 +25,8 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 25 | 25 | provider_name, |
| 26 | 26 | provider_session_id, |
| 27 | 27 | provider_thread_id, |
| 28 | approval_policy, | |
| 29 | sandbox_mode, | |
| 28 | 30 | active_turn_id, |
| 29 | 31 | last_error, |
| 30 | 32 | updated_at |
| ... | ... | @@ -35,6 +37,8 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 35 | 37 | ${row.providerName}, |
| 36 | 38 | ${row.providerSessionId}, |
| 37 | 39 | ${row.providerThreadId}, |
| 40 | ${row.approvalPolicy}, | |
| 41 | ${row.sandboxMode}, | |
| 38 | 42 | ${row.activeTurnId}, |
| 39 | 43 | ${row.lastError}, |
| 40 | 44 | ${row.updatedAt} |
| ... | ... | @@ -45,6 +49,8 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 45 | 49 | provider_name = excluded.provider_name, |
| 46 | 50 | provider_session_id = excluded.provider_session_id, |
| 47 | 51 | provider_thread_id = excluded.provider_thread_id, |
| 52 | approval_policy = excluded.approval_policy, | |
| 53 | sandbox_mode = excluded.sandbox_mode, | |
| 48 | 54 | active_turn_id = excluded.active_turn_id, |
| 49 | 55 | last_error = excluded.last_error, |
| 50 | 56 | updated_at = excluded.updated_at |
| ... | ... | @@ -62,6 +68,8 @@ const makeProjectionThreadSessionRepository = Effect.gen(function* () { |
| 62 | 68 | provider_name AS "providerName", |
| 63 | 69 | provider_session_id AS "providerSessionId", |
| 64 | 70 | provider_thread_id AS "providerThreadId", |
| 71 | approval_policy AS "approvalPolicy", | |
| 72 | sandbox_mode AS "sandboxMode", | |
| 65 | 73 | active_turn_id AS "activeTurnId", |
| 66 | 74 | last_error AS "lastError", |
| 67 | 75 | updated_at AS "updatedAt" |
apps/server/src/persistence/Migrations/005_Projections.ts+2| ... | ... | @@ -65,6 +65,8 @@ export default Effect.gen(function* () { |
| 65 | 65 | provider_name TEXT, |
| 66 | 66 | provider_session_id TEXT, |
| 67 | 67 | provider_thread_id TEXT, |
| 68 | approval_policy TEXT NOT NULL, | |
| 69 | sandbox_mode TEXT NOT NULL, | |
| 68 | 70 | active_turn_id TEXT, |
| 69 | 71 | last_error TEXT, |
| 70 | 72 | updated_at TEXT NOT NULL |
apps/server/src/persistence/Services/ProjectionThreadSessions.ts+4| ... | ... | @@ -9,6 +9,8 @@ |
| 9 | 9 | import { |
| 10 | 10 | IsoDateTime, |
| 11 | 11 | OrchestrationSessionStatus, |
| 12 | ProviderApprovalPolicy, | |
| 13 | ProviderSandboxMode, | |
| 12 | 14 | ProviderSessionId, |
| 13 | 15 | ProviderThreadId, |
| 14 | 16 | ThreadId, |
| ... | ... | @@ -25,6 +27,8 @@ export const ProjectionThreadSession = Schema.Struct({ |
| 25 | 27 | providerName: Schema.NullOr(Schema.String), |
| 26 | 28 | providerSessionId: Schema.NullOr(ProviderSessionId), |
| 27 | 29 | providerThreadId: Schema.NullOr(ProviderThreadId), |
| 30 | approvalPolicy: ProviderApprovalPolicy, | |
| 31 | sandboxMode: ProviderSandboxMode, | |
| 28 | 32 | activeTurnId: Schema.NullOr(TurnId), |
| 29 | 33 | lastError: Schema.NullOr(Schema.String), |
| 30 | 34 | updatedAt: IsoDateTime, |
apps/server/src/wsServer.test.ts+2| ... | ... | @@ -1055,6 +1055,8 @@ describe("WebSocket Server", () => { |
| 1055 | 1055 | attachments: [], |
| 1056 | 1056 | }, |
| 1057 | 1057 | assistantDeliveryMode: "streaming", |
| 1058 | approvalPolicy: "on-request", | |
| 1059 | sandboxMode: "workspace-write", | |
| 1058 | 1060 | createdAt, |
| 1059 | 1061 | }); |
| 1060 | 1062 | expect(startTurnResponse.error).toBeUndefined(); |
packages/contracts/src/orchestration.ts+6-6| ... | ... | @@ -142,8 +142,8 @@ export const OrchestrationSession = Schema.Struct({ |
| 142 | 142 | providerName: Schema.NullOr(TrimmedNonEmptyString), |
| 143 | 143 | providerSessionId: Schema.NullOr(ProviderSessionId), |
| 144 | 144 | providerThreadId: Schema.NullOr(ProviderThreadId), |
| 145 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | |
| 146 | sandboxMode: Schema.optional(ProviderSandboxMode), | |
| 145 | approvalPolicy: ProviderApprovalPolicy, | |
| 146 | sandboxMode: ProviderSandboxMode, | |
| 147 | 147 | activeTurnId: Schema.NullOr(TurnId), |
| 148 | 148 | lastError: Schema.NullOr(TrimmedNonEmptyString), |
| 149 | 149 | updatedAt: IsoDateTime, |
| ... | ... | @@ -284,8 +284,8 @@ export const ThreadTurnStartCommand = Schema.Struct({ |
| 284 | 284 | model: Schema.optional(TrimmedNonEmptyString), |
| 285 | 285 | effort: Schema.optional(TrimmedNonEmptyString), |
| 286 | 286 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 287 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | |
| 288 | sandboxMode: Schema.optional(ProviderSandboxMode), | |
| 287 | approvalPolicy: ProviderApprovalPolicy, | |
| 288 | sandboxMode: ProviderSandboxMode, | |
| 289 | 289 | createdAt: IsoDateTime, |
| 290 | 290 | }); |
| 291 | 291 | |
| ... | ... | @@ -500,8 +500,8 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({ |
| 500 | 500 | model: Schema.optional(TrimmedNonEmptyString), |
| 501 | 501 | effort: Schema.optional(TrimmedNonEmptyString), |
| 502 | 502 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 503 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | |
| 504 | sandboxMode: Schema.optional(ProviderSandboxMode), | |
| 503 | approvalPolicy: ProviderApprovalPolicy, | |
| 504 | sandboxMode: ProviderSandboxMode, | |
| 505 | 505 | createdAt: IsoDateTime, |
| 506 | 506 | }); |
| 507 | 507 |