| author | |
| committer | |
| log | 57dd091e05904dfe07721254863249305932c0b1 |
| tree | fec6784d9367a9eda20f323e3ba391c2b7b2d3f5 |
| parent | a1f208f1e6bb44e90ff97221fe839a4eeee26fdf |
- Add `approvalPolicy` and `sandboxMode` to turn-start command and requested payload contracts
- Pass runtime mode-derived values from `ChatView` when dispatching `thread.turn.start`
- Thread provider options through decider/reactor into session startup, with tests updated6 files changed, 43 insertions(+), 1 deletions(-)
apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts+4| ... | ... | @@ -171,6 +171,8 @@ describe("ProviderCommandReactor", () => { |
| 171 | 171 | text: "hello reactor", |
| 172 | 172 | attachments: [], |
| 173 | 173 | }, |
| 174 | approvalPolicy: "on-request", | |
| 175 | sandboxMode: "workspace-write", | |
| 174 | 176 | createdAt: now, |
| 175 | 177 | }), |
| 176 | 178 | ); |
| ... | ... | @@ -181,6 +183,8 @@ describe("ProviderCommandReactor", () => { |
| 181 | 183 | expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({ |
| 182 | 184 | cwd: "/tmp/provider-project", |
| 183 | 185 | model: "gpt-5-codex", |
| 186 | approvalPolicy: "on-request", | |
| 187 | sandboxMode: "workspace-write", | |
| 184 | 188 | }); |
| 185 | 189 | |
| 186 | 190 | const readModel = await Effect.runPromise(harness.engine.getReadModel()); |
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts+16-1| ... | ... | @@ -3,7 +3,9 @@ import { |
| 3 | 3 | CommandId, |
| 4 | 4 | EventId, |
| 5 | 5 | type OrchestrationEvent, |
| 6 | type ProviderApprovalPolicy, | |
| 6 | 7 | type ProviderKind, |
| 8 | type ProviderSandboxMode, | |
| 7 | 9 | type OrchestrationSession, |
| 8 | 10 | type ThreadId, |
| 9 | 11 | type TurnId, |
| ... | ... | @@ -128,6 +130,10 @@ const make = Effect.gen(function* () { |
| 128 | 130 | const ensureSessionForThread = Effect.fnUntraced(function* ( |
| 129 | 131 | threadId: ThreadId, |
| 130 | 132 | createdAt: string, |
| 133 | options?: { | |
| 134 | readonly approvalPolicy?: ProviderApprovalPolicy; | |
| 135 | readonly sandboxMode?: ProviderSandboxMode; | |
| 136 | }, | |
| 131 | 137 | ) { |
| 132 | 138 | const readModel = yield* orchestrationEngine.getReadModel(); |
| 133 | 139 | const thread = readModel.threads.find((entry) => entry.id === threadId); |
| ... | ... | @@ -153,6 +159,8 @@ const make = Effect.gen(function* () { |
| 153 | 159 | ...(preferredProvider ? { provider: preferredProvider } : {}), |
| 154 | 160 | ...(effectiveCwd ? { cwd: effectiveCwd } : {}), |
| 155 | 161 | ...(thread.model ? { model: thread.model } : {}), |
| 162 | ...(options?.approvalPolicy !== undefined ? { approvalPolicy: options.approvalPolicy } : {}), | |
| 163 | ...(options?.sandboxMode !== undefined ? { sandboxMode: options.sandboxMode } : {}), | |
| 156 | 164 | }); |
| 157 | 165 | |
| 158 | 166 | yield* setThreadSession({ |
| ... | ... | @@ -180,13 +188,18 @@ const make = Effect.gen(function* () { |
| 180 | 188 | readonly attachments?: ReadonlyArray<ChatAttachment>; |
| 181 | 189 | readonly model?: string; |
| 182 | 190 | readonly effort?: string; |
| 191 | readonly approvalPolicy?: ProviderApprovalPolicy; | |
| 192 | readonly sandboxMode?: ProviderSandboxMode; | |
| 183 | 193 | readonly createdAt: string; |
| 184 | 194 | }) { |
| 185 | 195 | const thread = yield* resolveThread(input.threadId); |
| 186 | 196 | if (!thread) { |
| 187 | 197 | return; |
| 188 | 198 | } |
| 189 | const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt); | |
| 199 | const sessionId = yield* ensureSessionForThread(input.threadId, input.createdAt, { | |
| 200 | ...(input.approvalPolicy !== undefined ? { approvalPolicy: input.approvalPolicy } : {}), | |
| 201 | ...(input.sandboxMode !== undefined ? { sandboxMode: input.sandboxMode } : {}), | |
| 202 | }); | |
| 190 | 203 | const normalizedInput = toNonEmptyProviderInput(input.messageText); |
| 191 | 204 | const normalizedAttachments = input.attachments ?? []; |
| 192 | 205 | |
| ... | ... | @@ -232,6 +245,8 @@ const make = Effect.gen(function* () { |
| 232 | 245 | ...(event.payload.model !== undefined ? { model: event.payload.model } : {}), |
| 233 | 246 | ...(event.payload.effort !== undefined ? { effort: event.payload.effort } : {}), |
| 234 | 247 | createdAt: event.payload.createdAt, |
| 248 | approvalPolicy: event.payload.approvalPolicy, | |
| 249 | sandboxMode: event.payload.sandboxMode, | |
| 235 | 250 | }); |
| 236 | 251 | }); |
| 237 | 252 |
apps/server/src/orchestration/decider.projectScripts.test.ts+10| ... | ... | @@ -152,6 +152,8 @@ describe("decider project scripts", () => { |
| 152 | 152 | }, |
| 153 | 153 | model: "gpt-5", |
| 154 | 154 | effort: "high", |
| 155 | approvalPolicy: "on-request", | |
| 156 | sandboxMode: "workspace-write", | |
| 155 | 157 | createdAt: now, |
| 156 | 158 | }, |
| 157 | 159 | readModel, |
| ... | ... | @@ -169,5 +171,13 @@ describe("decider project scripts", () => { |
| 169 | 171 | return; |
| 170 | 172 | } |
| 171 | 173 | expect(turnStartEvent.payload.assistantDeliveryMode).toBe("buffered"); |
| 174 | expect(turnStartEvent.payload).toMatchObject({ | |
| 175 | threadId: ThreadId.makeUnsafe("thread-1"), | |
| 176 | messageId: asMessageId("message-user-1"), | |
| 177 | model: "gpt-5", | |
| 178 | effort: "high", | |
| 179 | approvalPolicy: "on-request", | |
| 180 | sandboxMode: "workspace-write", | |
| 181 | }); | |
| 172 | 182 | }); |
| 173 | 183 | }); |
apps/server/src/orchestration/decider.ts+4| ... | ... | @@ -254,6 +254,10 @@ 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 | 261 | createdAt: command.createdAt, |
| 258 | 262 | }, |
| 259 | 263 | }; |
apps/web/src/components/ChatView.tsx+5| ... | ... | @@ -1522,6 +1522,9 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1522 | 1522 | ), |
| 1523 | 1523 | ); |
| 1524 | 1524 | attemptedTurnStart = true; |
| 1525 | const approvalPolicy = state.runtimeMode === "full-access" ? "never" : "on-request"; | |
| 1526 | const sandboxMode = | |
| 1527 | state.runtimeMode === "full-access" ? "danger-full-access" : "workspace-write"; | |
| 1525 | 1528 | await api.orchestration.dispatchCommand({ |
| 1526 | 1529 | type: "thread.turn.start", |
| 1527 | 1530 | commandId: newCommandId(), |
| ... | ... | @@ -1535,6 +1538,8 @@ export default function ChatView({ threadId }: ChatViewProps) { |
| 1535 | 1538 | model: selectedModel || undefined, |
| 1536 | 1539 | effort: selectedEffort || undefined, |
| 1537 | 1540 | assistantDeliveryMode: settings.enableAssistantStreaming ? "streaming" : "buffered", |
| 1541 | approvalPolicy, | |
| 1542 | sandboxMode, | |
| 1538 | 1543 | createdAt: messageCreatedAt, |
| 1539 | 1544 | }); |
| 1540 | 1545 | } catch (err) { |
packages/contracts/src/orchestration.ts+4| ... | ... | @@ -282,6 +282,8 @@ export const ThreadTurnStartCommand = Schema.Struct({ |
| 282 | 282 | model: Schema.optional(TrimmedNonEmptyString), |
| 283 | 283 | effort: Schema.optional(TrimmedNonEmptyString), |
| 284 | 284 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 285 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | |
| 286 | sandboxMode: Schema.optional(ProviderSandboxMode), | |
| 285 | 287 | createdAt: IsoDateTime, |
| 286 | 288 | }); |
| 287 | 289 | |
| ... | ... | @@ -496,6 +498,8 @@ export const ThreadTurnStartRequestedPayload = Schema.Struct({ |
| 496 | 498 | model: Schema.optional(TrimmedNonEmptyString), |
| 497 | 499 | effort: Schema.optional(TrimmedNonEmptyString), |
| 498 | 500 | assistantDeliveryMode: Schema.optional(AssistantDeliveryMode), |
| 501 | approvalPolicy: Schema.optional(ProviderApprovalPolicy), | |
| 502 | sandboxMode: Schema.optional(ProviderSandboxMode), | |
| 499 | 503 | createdAt: IsoDateTime, |
| 500 | 504 | }); |
| 501 | 505 |