Skip to content

Commit 2fb4ab3

Browse files
authored
fix(sessions): preserve permission mode and effort on cloud resume (#2993)
1 parent 5ae61cb commit 2fb4ab3

3 files changed

Lines changed: 64 additions & 1 deletion

File tree

packages/core/src/sessions/cloudRunOptions.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,5 +84,34 @@ describe("getCloudRuntimeOptions", () => {
8484
expect(result.model).toBeUndefined();
8585
expect(result.reasoningLevel).toBeUndefined();
8686
expect(result.adapter).toBeUndefined();
87+
expect(result.initialPermissionMode).toBeUndefined();
88+
});
89+
90+
it.each([
91+
{
92+
label: "prefers the session mode config option for permission mode",
93+
configOptions: [
94+
{ category: "mode", currentValue: "acceptEdits" },
95+
// biome-ignore lint/suspicious/noExplicitAny: minimal config option shape
96+
] as any,
97+
previousRun: {
98+
state: { initial_permission_mode: "plan" },
99+
} as unknown as TaskRun,
100+
expected: "acceptEdits",
101+
},
102+
{
103+
label: "falls back to the previous run state for permission mode",
104+
configOptions: [],
105+
previousRun: {
106+
state: { initial_permission_mode: "acceptEdits" },
107+
} as unknown as TaskRun,
108+
expected: "acceptEdits",
109+
},
110+
])("$label", ({ configOptions, previousRun, expected }) => {
111+
const result = getCloudRuntimeOptions(
112+
session({ configOptions }),
113+
previousRun,
114+
);
115+
expect(result.initialPermissionMode).toBe(expected);
87116
});
88117
});

packages/core/src/sessions/cloudRunOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type Adapter,
33
type AgentSession,
44
type CloudRunSource,
5+
type ExecutionMode,
56
getConfigOptionByCategory,
67
type PrAuthorshipMode,
78
} from "@posthog/shared";
@@ -34,6 +35,7 @@ export interface CloudRuntimeOptions {
3435
adapter?: Adapter;
3536
model?: string;
3637
reasoningLevel?: string;
38+
initialPermissionMode?: ExecutionMode;
3739
}
3840

3941
export function getCloudRuntimeOptions(
@@ -45,6 +47,8 @@ export function getCloudRuntimeOptions(
4547
session.configOptions,
4648
"thought_level",
4749
);
50+
const modeOption = getConfigOptionByCategory(session.configOptions, "mode");
51+
const previousMode = previousRun?.state?.initial_permission_mode;
4852

4953
return {
5054
adapter: session.adapter ?? previousRun?.runtime_adapter ?? undefined,
@@ -56,5 +60,11 @@ export function getCloudRuntimeOptions(
5660
typeof thoughtLevelOption?.currentValue === "string"
5761
? thoughtLevelOption.currentValue
5862
: (previousRun?.reasoning_effort ?? undefined),
63+
initialPermissionMode:
64+
typeof modeOption?.currentValue === "string"
65+
? (modeOption.currentValue as ExecutionMode)
66+
: typeof previousMode === "string"
67+
? (previousMode as ExecutionMode)
68+
: undefined,
5969
};
6070
}

packages/core/src/sessions/sessionService.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import { classifyCloudLogAppend } from "./cloudLogGap";
4848
import { CloudLogGapReconciler } from "./cloudLogGapReconciler";
4949
import { CloudRunIdleTracker } from "./cloudRunIdleTracker";
5050
import {
51+
type CloudRuntimeOptions,
5152
getCloudPrAuthorshipMode,
5253
getCloudRunSource,
5354
getCloudRuntimeOptions,
@@ -2589,6 +2590,7 @@ export class SessionService {
25892590
};
25902591

25912592
let updatedTask: Task;
2593+
let runtimeOptions: CloudRuntimeOptions;
25922594
try {
25932595
const artifactIds = await this.d.h.uploadTaskStagedAttachments(
25942596
authCredentials.client,
@@ -2625,7 +2627,7 @@ export class SessionService {
26252627
previousStatus: session.cloudStatus,
26262628
});
26272629

2628-
const runtimeOptions = getCloudRuntimeOptions(session, previousRun);
2630+
runtimeOptions = getCloudRuntimeOptions(session, previousRun);
26292631

26302632
// Backend derives the snapshot from resumeFromRunId and restores the sandbox.
26312633
updatedTask = await authCredentials.client.runTaskInCloud(
@@ -2635,6 +2637,7 @@ export class SessionService {
26352637
adapter: runtimeOptions.adapter,
26362638
model: runtimeOptions.model,
26372639
reasoningLevel: runtimeOptions.reasoningLevel,
2640+
initialPermissionMode: runtimeOptions.initialPermissionMode,
26382641
resumeFromRunId: session.taskRunId,
26392642
pendingUserMessage: transport.messageText,
26402643
pendingUserArtifactIds:
@@ -2693,6 +2696,8 @@ export class SessionService {
26932696
)?.currentValue;
26942697
const initialModel =
26952698
newRun.model ?? (typeof priorModel === "string" ? priorModel : undefined);
2699+
const initialReasoningEffort =
2700+
newRun.reasoning_effort ?? runtimeOptions.reasoningLevel;
26962701
this.watchCloudTask(
26972702
session.taskId,
26982703
newRun.id,
@@ -2705,6 +2710,8 @@ export class SessionService {
27052710
initialModel,
27062711
undefined,
27072712
resumeFromEntryCount,
2713+
undefined,
2714+
initialReasoningEffort,
27082715
);
27092716

27102717
this.d.queryClient.invalidateQueries({ queryKey: ["tasks"] });
@@ -3217,6 +3224,7 @@ export class SessionService {
32173224
apiHost: string,
32183225
adapter: Adapter,
32193226
initialModel?: string,
3227+
initialReasoningEffort?: string,
32203228
): Promise<void> {
32213229
const cacheKey = `${apiHost}::${adapter}`;
32223230
let entry = this.previewConfigOptionsCache.get(cacheKey);
@@ -3260,6 +3268,16 @@ export class SessionService {
32603268
return { ...opt, currentValue: initialModel };
32613269
}
32623270
}
3271+
if (
3272+
opt.category === "thought_level" &&
3273+
opt.type === "select" &&
3274+
typeof initialReasoningEffort === "string"
3275+
) {
3276+
const flat = flattenSelectOptions(opt.options);
3277+
if (flat.some((o) => o.value === initialReasoningEffort)) {
3278+
return { ...opt, currentValue: initialReasoningEffort };
3279+
}
3280+
}
32633281
return opt;
32643282
});
32653283

@@ -3298,6 +3316,7 @@ export class SessionService {
32983316
taskDescription?: string,
32993317
resumeFromEntryCount?: number,
33003318
runStatus?: TaskRunStatus,
3319+
initialReasoningEffort?: string,
33013320
): () => void {
33023321
const taskRunId = runId;
33033322

@@ -3337,6 +3356,7 @@ export class SessionService {
33373356
apiHost,
33383357
adapter,
33393358
initialModel,
3359+
initialReasoningEffort,
33403360
);
33413361
}
33423362
return () => {};
@@ -3428,6 +3448,7 @@ export class SessionService {
34283448
apiHost,
34293449
adapter,
34303450
initialModel,
3451+
initialReasoningEffort,
34313452
);
34323453

34333454
if (shouldHydrateSession) {
@@ -4104,6 +4125,8 @@ export class SessionService {
41044125
const adapter =
41054126
task.latest_run?.runtime_adapter === "codex" ? "codex" : "claude";
41064127
const initialModel = task.latest_run?.model ?? undefined;
4128+
const initialReasoningEffort =
4129+
task.latest_run?.reasoning_effort ?? undefined;
41074130

41084131
return this.watchCloudTask(
41094132
task.id,
@@ -4118,6 +4141,7 @@ export class SessionService {
41184141
task.description ?? undefined,
41194142
undefined,
41204143
task.latest_run?.status,
4144+
initialReasoningEffort,
41214145
);
41224146
}
41234147

0 commit comments

Comments
 (0)