Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ const startTurn = (input: {
text: input.text,
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: nowIso(),
});

Expand Down
16 changes: 16 additions & 0 deletions apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -394,6 +396,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -445,6 +449,8 @@ describe("CheckpointReactor", () => {
text: "start turn",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: new Date().toISOString(),
}),
);
Expand Down Expand Up @@ -481,6 +487,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-missing"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-missing"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: asTurnId("turn-missing-cwd"),
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -529,6 +537,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -580,6 +590,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -634,6 +646,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down Expand Up @@ -713,6 +727,8 @@ describe("CheckpointReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: createdAt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ describe("OrchestrationEngine", () => {
text: "hello",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt,
}),
);
Expand Down Expand Up @@ -428,6 +430,8 @@ describe("OrchestrationEngine", () => {
text: "hello",
attachments: [],
},
approvalPolicy: "on-request" as const,
sandboxMode: "workspace-write" as const,
createdAt,
};

Expand Down Expand Up @@ -586,6 +590,8 @@ describe("OrchestrationEngine", () => {
text: "hello",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: now(),
}),
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,8 @@ it.effect("restores pending turn-start metadata across projection pipeline resta
payload: {
threadId,
messageId,
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: turnStartedAt,
},
});
Expand Down Expand Up @@ -827,6 +829,8 @@ it.effect("restores pending turn-start metadata across projection pipeline resta
providerName: "codex",
providerSessionId: ProviderSessionId.makeUnsafe("provider-session-restart"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-restart"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: turnId,
lastError: null,
updatedAt: sessionSetAt,
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/orchestration/Layers/ProjectionPipeline.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,8 @@ const makeOrchestrationProjectionPipeline = Effect.gen(function* () {
providerName: event.payload.session.providerName,
providerSessionId: event.payload.session.providerSessionId,
providerThreadId: event.payload.session.providerThreadId,
approvalPolicy: event.payload.session.approvalPolicy,
sandboxMode: event.payload.session.sandboxMode,
activeTurnId: event.payload.session.activeTurnId,
lastError: event.payload.session.lastError,
updatedAt: event.payload.session.updatedAt,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
provider_name,
provider_session_id,
provider_thread_id,
approval_policy,
sandbox_mode,
active_turn_id,
last_error,
updated_at
Expand All @@ -153,6 +155,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
'codex',
'provider-session-1',
'provider-thread-1',
'on-request',
'workspace-write',
'turn-1',
NULL,
'2026-02-24T00:00:07.000Z'
Expand Down Expand Up @@ -282,6 +286,8 @@ projectionSnapshotLayer("ProjectionSnapshotQuery", (it) => {
providerName: "codex",
providerSessionId: asProviderSessionId("provider-session-1"),
providerThreadId: asProviderThreadId("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: asTurnId("turn-1"),
lastError: null,
updatedAt: "2026-02-24T00:00:07.000Z",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
provider_name AS "providerName",
provider_session_id AS "providerSessionId",
provider_thread_id AS "providerThreadId",
approval_policy AS "approvalPolicy",
sandbox_mode AS "sandboxMode",
active_turn_id AS "activeTurnId",
last_error AS "lastError",
updated_at AS "updatedAt"
Expand Down Expand Up @@ -378,6 +380,8 @@ const makeProjectionSnapshotQuery = Effect.gen(function* () {
providerName: row.providerName,
providerSessionId: row.providerSessionId,
providerThreadId: row.providerThreadId,
approvalPolicy: row.approvalPolicy,
sandboxMode: row.sandboxMode,
activeTurnId: row.activeTurnId,
lastError: row.lastError,
updatedAt: row.updatedAt,
Expand Down
134 changes: 128 additions & 6 deletions apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,18 @@ describe("ProviderCommandReactor", () => {
async function createHarness() {
const now = new Date().toISOString();
const runtimeEventPubSub = Effect.runSync(PubSub.unbounded<ProviderRuntimeEvent>());
const startSession = vi.fn((_: unknown, __: unknown) =>
Effect.succeed({
sessionId: asSessionId("sess-1"),
let nextSessionIndex = 1;
const startSession = vi.fn((_: unknown, __: unknown) => {
const sessionIndex = nextSessionIndex++;
return Effect.succeed({
sessionId: asSessionId(`sess-${sessionIndex}`),
provider: "codex" as const,
status: "ready" as const,
threadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
threadId: ProviderThreadId.makeUnsafe(`provider-thread-${sessionIndex}`),
createdAt: now,
updatedAt: now,
}),
);
});
});
const sendTurn = vi.fn((_: unknown) =>
Effect.succeed({
threadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
Expand Down Expand Up @@ -171,6 +173,8 @@ describe("ProviderCommandReactor", () => {
text: "hello reactor",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: now,
}),
);
Expand All @@ -181,11 +185,123 @@ describe("ProviderCommandReactor", () => {
expect(harness.startSession.mock.calls[0]?.[1]).toMatchObject({
cwd: "/tmp/provider-project",
model: "gpt-5-codex",
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
});

const readModel = await Effect.runPromise(harness.engine.getReadModel());
const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
expect(thread?.session?.providerSessionId).toBe("sess-1");
expect(thread?.session?.approvalPolicy).toBe("on-request");
expect(thread?.session?.sandboxMode).toBe("workspace-write");
});

it("reuses the same provider session when runtime mode is unchanged", async () => {
const harness = await createHarness();
const now = new Date().toISOString();

await Effect.runPromise(
harness.engine.dispatch({
type: "thread.turn.start",
commandId: CommandId.makeUnsafe("cmd-turn-start-unchanged-1"),
threadId: ThreadId.makeUnsafe("thread-1"),
message: {
messageId: asMessageId("user-message-unchanged-1"),
role: "user",
text: "first",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: now,
}),
);

await waitFor(() => harness.startSession.mock.calls.length === 1);
await waitFor(() => harness.sendTurn.mock.calls.length === 1);

await Effect.runPromise(
harness.engine.dispatch({
type: "thread.turn.start",
commandId: CommandId.makeUnsafe("cmd-turn-start-unchanged-2"),
threadId: ThreadId.makeUnsafe("thread-1"),
message: {
messageId: asMessageId("user-message-unchanged-2"),
role: "user",
text: "second",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: now,
}),
);

await waitFor(() => harness.sendTurn.mock.calls.length === 2);
expect(harness.startSession.mock.calls.length).toBe(1);
expect(harness.stopSession.mock.calls.length).toBe(0);
});

it("restarts the provider session when runtime mode changes", async () => {
const harness = await createHarness();
const now = new Date().toISOString();

await Effect.runPromise(
harness.engine.dispatch({
type: "thread.turn.start",
commandId: CommandId.makeUnsafe("cmd-turn-start-runtime-mode-1"),
threadId: ThreadId.makeUnsafe("thread-1"),
message: {
messageId: asMessageId("user-message-runtime-mode-1"),
role: "user",
text: "first",
attachments: [],
},
approvalPolicy: "never",
sandboxMode: "danger-full-access",
createdAt: now,
}),
);

await waitFor(() => harness.startSession.mock.calls.length === 1);
await waitFor(() => harness.sendTurn.mock.calls.length === 1);

await Effect.runPromise(
harness.engine.dispatch({
type: "thread.turn.start",
commandId: CommandId.makeUnsafe("cmd-turn-start-runtime-mode-2"),
threadId: ThreadId.makeUnsafe("thread-1"),
message: {
messageId: asMessageId("user-message-runtime-mode-2"),
role: "user",
text: "second",
attachments: [],
},
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
createdAt: now,
}),
);

await waitFor(() => harness.stopSession.mock.calls.length === 1);
await waitFor(() => harness.startSession.mock.calls.length === 2);
await waitFor(() => harness.sendTurn.mock.calls.length === 2);

expect(harness.stopSession.mock.calls[0]?.[0]).toEqual({ sessionId: asSessionId("sess-1") });
expect(harness.startSession.mock.calls[1]?.[1]).toMatchObject({
resumeThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
});
expect(harness.sendTurn.mock.calls[1]?.[0]).toMatchObject({
sessionId: asSessionId("sess-2"),
});

const readModel = await Effect.runPromise(harness.engine.getReadModel());
const thread = readModel.threads.find((entry) => entry.id === ThreadId.makeUnsafe("thread-1"));
expect(thread?.session?.providerSessionId).toBe("sess-2");
expect(thread?.session?.approvalPolicy).toBe("on-request");
expect(thread?.session?.sandboxMode).toBe("workspace-write");
});

it("reacts to thread.turn.interrupt-requested by calling provider interrupt", async () => {
Expand All @@ -203,6 +319,8 @@ describe("ProviderCommandReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: asTurnId("turn-1"),
lastError: null,
updatedAt: now,
Expand Down Expand Up @@ -242,6 +360,8 @@ describe("ProviderCommandReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: now,
Expand Down Expand Up @@ -284,6 +404,8 @@ describe("ProviderCommandReactor", () => {
providerName: "codex",
providerSessionId: asSessionId("sess-1"),
providerThreadId: ProviderThreadId.makeUnsafe("provider-thread-1"),
approvalPolicy: "on-request",
sandboxMode: "workspace-write",
activeTurnId: null,
lastError: null,
updatedAt: now,
Expand Down
Loading