Skip to content

Commit f6aec6c

Browse files
authored
fix(agent): preserve narration opt-in for cloud runs
Generated-By: PostHog Code Task-Id: ac30fada-8c29-4973-a253-08d70be31be6
1 parent 539c826 commit f6aec6c

15 files changed

Lines changed: 141 additions & 15 deletions

File tree

packages/agent/src/adapters/session-meta.test.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { afterEach, describe, expect, it, vi } from "vitest";
2-
import { resolveSpokenNarration } from "./session-meta";
2+
import {
3+
resolveCloudSpokenNarration,
4+
resolveSpokenNarration,
5+
} from "./session-meta";
36

47
describe("resolveSpokenNarration", () => {
58
afterEach(() => {
@@ -48,3 +51,17 @@ describe("resolveSpokenNarration", () => {
4851
},
4952
);
5053
});
54+
55+
describe("resolveCloudSpokenNarration", () => {
56+
it.each([
57+
{ state: { spoken_narration: true }, expected: true },
58+
{ state: { spoken_narration: false }, expected: false },
59+
{ state: {}, expected: false },
60+
{ state: undefined, expected: false },
61+
])(
62+
"resolves explicit cloud run opt-in to $expected",
63+
({ state, expected }) => {
64+
expect(resolveCloudSpokenNarration(state)).toBe(expected);
65+
},
66+
);
67+
});

packages/agent/src/adapters/session-meta.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ interface SpokenNarrationSource {
2020
spokenNarration?: boolean;
2121
}
2222

23+
interface SpokenNarrationRunState {
24+
spoken_narration?: unknown;
25+
}
26+
2327
/**
2428
* Spoken narration is strictly opt-in: it is on only when a caller explicitly
2529
* sets `spokenNarration` true at session start. The desktop is the only client
@@ -35,3 +39,9 @@ export function resolveSpokenNarration(
3539
): boolean {
3640
return meta?.spokenNarration === true;
3741
}
42+
43+
export function resolveCloudSpokenNarration(
44+
state: SpokenNarrationRunState | undefined,
45+
): boolean {
46+
return state?.spoken_narration === true;
47+
}

packages/agent/src/server/agent-server.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import {
4545
classifyAgentError,
4646
isPromptTooLongError,
4747
} from "../adapters/error-classification";
48+
import { resolveCloudSpokenNarration } from "../adapters/session-meta";
4849
import {
4950
SIGNED_COMMIT_QUALIFIED_TOOL_NAME,
5051
SIGNED_MERGE_QUALIFIED_TOOL_NAME,
@@ -1497,6 +1498,7 @@ export class AgentServer {
14971498
allowedDomains: this.config.allowedDomains,
14981499
jsonSchema: preTask?.json_schema ?? null,
14991500
permissionMode: initialPermissionMode,
1501+
spokenNarration: resolveCloudSpokenNarration(runState),
15001502
...(this.config.baseBranch && { baseBranch: this.config.baseBranch }),
15011503
...this.buildClaudeCodeSessionMeta(runtimeAdapter),
15021504
};

packages/api-client/src/posthog-client.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ describe("PostHogAPIClient", () => {
2525
adapter: "codex",
2626
model: "gpt-5.4",
2727
reasoningLevel: "high",
28+
spokenNarration: true,
2829
});
2930

3031
expect(post).toHaveBeenCalledWith(
@@ -37,6 +38,7 @@ describe("PostHogAPIClient", () => {
3738
runtime_adapter: "codex",
3839
model: "gpt-5.4",
3940
reasoning_effort: "high",
41+
spoken_narration: true,
4042
}),
4143
}),
4244
);

packages/api-client/src/posthog-client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ interface CloudRunOptions {
613613
autoPublish?: boolean;
614614
/** Only false is sent: opts the run out of rtk command-output compression. */
615615
rtkEnabled?: boolean;
616+
/** Explicit desktop opt-in; omitted for headless/background callers. */
617+
spokenNarration?: boolean;
616618
runSource?: CloudRunSource;
617619
signalReportId?: string;
618620
initialPermissionMode?: ExecutionMode;
@@ -708,6 +710,9 @@ function buildCloudRunRequestBody(
708710
if (options?.rtkEnabled === false) {
709711
body.rtk_enabled = false;
710712
}
713+
if (options?.spokenNarration !== undefined) {
714+
body.spoken_narration = options.spokenNarration;
715+
}
711716
if (options?.runSource) {
712717
body.run_source = options.runSource;
713718
}

packages/core/src/sessions/sessionService.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,19 +1088,14 @@ export class SessionService {
10881088
this.d.log.warn("Failed to verify workspace", { taskId, err });
10891089
});
10901090

1091-
const {
1092-
customInstructions,
1093-
rtkEnabledLocal,
1094-
spokenNotifications,
1095-
spokenNarrationEnabled,
1096-
} = this.d.settings;
1091+
const { customInstructions, rtkEnabledLocal, spokenNarrationEnabled } =
1092+
this.d.settings;
10971093
const result = await this.d.trpc.agent.reconnect.mutate({
10981094
taskId,
10991095
taskRunId,
11001096
repoPath,
11011097
rtkEnabled: rtkEnabledLocal,
1102-
spokenNarration:
1103-
spokenNotifications === true && spokenNarrationEnabled === true,
1098+
spokenNarration: spokenNarrationEnabled === true,
11041099
apiHost: auth.apiHost,
11051100
projectId: auth.projectId,
11061101
logUrl,
@@ -1423,7 +1418,6 @@ export class SessionService {
14231418
const {
14241419
customInstructions: startCustomInstructions,
14251420
rtkEnabledLocal,
1426-
spokenNotifications,
14271421
spokenNarrationEnabled,
14281422
} = this.d.settings;
14291423
const preferredModel = model ?? this.d.DEFAULT_GATEWAY_MODEL;
@@ -1437,8 +1431,7 @@ export class SessionService {
14371431
adapter,
14381432
customInstructions: startCustomInstructions || undefined,
14391433
rtkEnabled: rtkEnabledLocal,
1440-
spokenNarration:
1441-
spokenNotifications === true && spokenNarrationEnabled === true,
1434+
spokenNarration: spokenNarrationEnabled === true,
14421435
effort: effortLevelSchema.safeParse(reasoningLevel).success
14431436
? (reasoningLevel as EffortLevel)
14441437
: undefined,
@@ -3415,6 +3408,7 @@ export class SessionService {
34153408
prAuthorshipMode,
34163409
autoPublish: previousState.auto_publish === true || undefined,
34173410
rtkEnabled: this.d.settings.rtkEnabledCloud,
3411+
spokenNarration: this.d.settings.spokenNarrationEnabled === true,
34183412
runSource: getCloudRunSource(previousState),
34193413
signalReportId:
34203414
typeof previousState.signal_report_id === "string"

packages/core/src/task-detail/taskCreationApiClient.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface CreateTaskRunClientOptions {
1919
prAuthorshipMode?: PrAuthorshipMode;
2020
autoPublish?: boolean;
2121
rtkEnabled?: boolean;
22+
spokenNarration?: boolean;
2223
runSource?: CloudRunSource;
2324
signalReportId?: string;
2425
initialPermissionMode?: string;

packages/core/src/task-detail/taskCreationSaga.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ describe("TaskCreationSaga", () => {
154154
reasoningLevel: "high",
155155
cloudAutoPublish: true,
156156
cloudRtkEnabled: false,
157+
spokenNarration: true,
157158
});
158159

159160
expect(result.success).toBe(true);
@@ -172,6 +173,7 @@ describe("TaskCreationSaga", () => {
172173
prAuthorshipMode: "user",
173174
autoPublish: true,
174175
rtkEnabled: false,
176+
spokenNarration: true,
175177
runSource: "manual",
176178
signalReportId: undefined,
177179
initialPermissionMode: "auto",

packages/core/src/task-detail/taskCreationSaga.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ export class TaskCreationSaga extends Saga<
404404
prAuthorshipMode,
405405
autoPublish: input.cloudAutoPublish,
406406
rtkEnabled: input.cloudRtkEnabled,
407+
spokenNarration: input.spokenNarration,
407408
runSource: input.cloudRunSource ?? "manual",
408409
signalReportId: input.signalReportId,
409410
homeQuickAction: input.homeQuickActionLabel,

packages/core/src/task-detail/taskInput.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,19 @@ describe("prepareTaskInput", () => {
2727
});
2828
expect(input.customInstructions).toBeUndefined();
2929
});
30+
31+
it.each([
32+
{ workspaceMode: "cloud" as const, expected: true },
33+
{ workspaceMode: "local" as const, expected: undefined },
34+
{ workspaceMode: "worktree" as const, expected: undefined },
35+
])(
36+
"passes spoken narration through only for cloud (%s)",
37+
({ workspaceMode, expected }) => {
38+
const input = prepareTaskInput("do the thing", [], {
39+
workspaceMode,
40+
spokenNarration: true,
41+
});
42+
expect(input.spokenNarration).toBe(expected);
43+
},
44+
);
3045
});

0 commit comments

Comments
 (0)