Skip to content
Merged
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
51 changes: 51 additions & 0 deletions apps/server/src/provider/Layers/ClaudeAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,57 @@ describe("ClaudeAdapterLive", () => {
);
});

it.effect(
"restores default permission mode on sendTurn when interactionMode is chat for approval-required sessions",
() => {
const harness = makeHarness();
return Effect.gen(function* () {
const adapter = yield* ClaudeAdapter;

const session = yield* adapter.startSession({
threadId: THREAD_ID,
provider: "claudeAgent",
runtimeMode: "approval-required",
});

yield* adapter.sendTurn({
threadId: session.threadId,
input: "plan this",
interactionMode: "plan",
attachments: [],
});

const turnCompletedFiber = yield* Stream.filter(
adapter.streamEvents,
(event) => event.type === "turn.completed",
).pipe(Stream.runHead, Effect.forkChild);

harness.query.emit({
type: "result",
subtype: "success",
is_error: false,
errors: [],
session_id: "sdk-session-approval-plan-restore",
uuid: "result-approval-plan",
} as unknown as SDKMessage);

yield* Fiber.join(turnCompletedFiber);

yield* adapter.sendTurn({
threadId: session.threadId,
input: "now answer directly",
interactionMode: "chat",
attachments: [],
});

assert.deepEqual(harness.query.setPermissionModeCalls, ["plan", "default"]);
}).pipe(
Effect.provideService(Random.Random, makeDeterministicRandomService()),
Effect.provide(harness.layer),
);
},
);

it.effect("does not call setPermissionMode when interactionMode is absent", () => {
const harness = makeHarness();
return Effect.gen(function* () {
Expand Down
8 changes: 4 additions & 4 deletions apps/server/src/provider/Layers/ClaudeAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ interface ClaudeSessionContext {
readonly query: ClaudeQueryRuntime;
streamFiber: Fiber.Fiber<void, Error> | undefined;
readonly startedAt: string;
readonly basePermissionMode: PermissionMode | undefined;
readonly basePermissionMode: PermissionMode;
resumeSessionId: string | undefined;
readonly pendingApprovals: Map<ApprovalRequestId, PendingApproval>;
readonly pendingUserInputs: Map<ApprovalRequestId, PendingUserInput>;
Expand Down Expand Up @@ -2790,6 +2790,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
const permissionMode =
toPermissionMode(providerOptions?.permissionMode) ??
(input.runtimeMode === "full-access" ? "bypassPermissions" : undefined);
const basePermissionMode = permissionMode ?? "default";
const settings = {
...(typeof thinking === "boolean" ? { alwaysThinkingEnabled: thinking } : {}),
...(fastMode ? { fastMode: true } : {}),
Expand Down Expand Up @@ -2859,7 +2860,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
query: queryRuntime,
streamFiber: undefined,
startedAt,
basePermissionMode: permissionMode,
basePermissionMode,
resumeSessionId: sessionId,
pendingApprovals,
pendingUserInputs,
Expand Down Expand Up @@ -2965,8 +2966,7 @@ function makeClaudeAdapter(options?: ClaudeAdapterLiveOptions) {
});
} else if (input.interactionMode === "chat" || input.interactionMode === "code") {
yield* Effect.tryPromise({
try: () =>
context.query.setPermissionMode(context.basePermissionMode ?? "bypassPermissions"),
try: () => context.query.setPermissionMode(context.basePermissionMode),
catch: (cause) => toRequestError(input.threadId, "turn/setPermissionMode", cause),
});
}
Expand Down
Loading