From d679e1f56418ffb8ccafdaeaaa0eda148ddcd505 Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Fri, 10 Jul 2026 11:41:08 +0100 Subject: [PATCH 1/2] feat(mobile): compress cloud command output with rtk (port #3257) Adds a "Compress command output" cloud setting to the mobile app, on by default. When enabled, eligible shell command output on cloud runs is routed through rtk so verbose output is compressed before it reaches the model, reducing token usage. Only the Cloud half of #3257 is ported: mobile has no local agent sessions, no workspace-server, and no rtk binary, so the desktop Local checkbox, the agent.rtkStatus query, and the "rtk binary not found" warning have no mobile equivalent. The preference persists as rtkEnabledCloud (default true) and threads into every cloud-run start/resume path. Following the server default (enabled), only an explicit opt-out sends rtk_enabled: false; the enabled case sends nothing. Generated-By: PostHog Code Task-Id: eeb20b54-b0ff-4624-b0df-64ce78edbe50 --- apps/mobile/src/app/settings/index.tsx | 14 +++++++++++++- apps/mobile/src/app/task/[id].tsx | 2 ++ apps/mobile/src/app/task/index.tsx | 1 + .../src/features/inbox/components/TinderView.tsx | 2 ++ .../preferences/stores/preferencesStore.ts | 7 +++++++ apps/mobile/src/features/tasks/api.test.ts | 15 +++++++++++++++ apps/mobile/src/features/tasks/api.ts | 8 +++++++- 7 files changed, 47 insertions(+), 2 deletions(-) diff --git a/apps/mobile/src/app/settings/index.tsx b/apps/mobile/src/app/settings/index.tsx index d5784feddb..79ac5c10f1 100644 --- a/apps/mobile/src/app/settings/index.tsx +++ b/apps/mobile/src/app/settings/index.tsx @@ -199,6 +199,8 @@ export default function SettingsScreen() { const setAutoPublishCloudRuns = usePreferencesStore( (s) => s.setAutoPublishCloudRuns, ); + const rtkEnabledCloud = usePreferencesStore((s) => s.rtkEnabledCloud); + const setRtkEnabledCloud = usePreferencesStore((s) => s.setRtkEnabledCloud); const defaultMessagingMode = useMessagingModeStore((s) => s.defaultMode); const setDefaultMessagingMode = useMessagingModeStore( (s) => s.setDefaultMode, @@ -427,7 +429,6 @@ export default function SettingsScreen() { } /> + + } + /> {/* Integrations */} diff --git a/apps/mobile/src/app/task/[id].tsx b/apps/mobile/src/app/task/[id].tsx index 485cc79ba9..b2c4c4af75 100644 --- a/apps/mobile/src/app/task/[id].tsx +++ b/apps/mobile/src/app/task/[id].tsx @@ -304,6 +304,7 @@ export default function TaskDetailScreen() { model: composerModel, reasoningEffort: supportsReasoning ? composerReasoning : undefined, initialPermissionMode: composerMode, + rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud, }); setTask(updatedTask); await connectToTask(updatedTask); @@ -478,6 +479,7 @@ export default function TaskDetailScreen() { const updatedTask = await runTaskInCloud(taskId, { resumeFromRunId: task.latest_run?.id, + rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud, }); setTask(updatedTask); await connectToTask(updatedTask); diff --git a/apps/mobile/src/app/task/index.tsx b/apps/mobile/src/app/task/index.tsx index 882d1b92c0..75fe35e96a 100644 --- a/apps/mobile/src/app/task/index.tsx +++ b/apps/mobile/src/app/task/index.tsx @@ -352,6 +352,7 @@ export default function NewTaskScreen() { reasoningEffort: supportsReasoning ? reasoning : undefined, initialPermissionMode: mode, autoPublish: usePreferencesStore.getState().autoPublishCloudRuns, + rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud, ...(signalReport ? { runSource: "signal_report" as const, diff --git a/apps/mobile/src/features/inbox/components/TinderView.tsx b/apps/mobile/src/features/inbox/components/TinderView.tsx index caeae12e87..cc034d6ceb 100644 --- a/apps/mobile/src/features/inbox/components/TinderView.tsx +++ b/apps/mobile/src/features/inbox/components/TinderView.tsx @@ -16,6 +16,7 @@ import { useSafeAreaInsets, } from "react-native-safe-area-context"; import { MarkdownText } from "@/features/chat/components/MarkdownText"; +import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore"; import { createTask, runTaskInCloud } from "@/features/tasks/api"; import { DEFAULT_MODEL } from "@/features/tasks/composer/options"; import type { @@ -256,6 +257,7 @@ export function TinderView({ initialPermissionMode: "plan", runSource: "signal_report", signalReportId: report.id, + rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud, }); acceptReport(report.id); diff --git a/apps/mobile/src/features/preferences/stores/preferencesStore.ts b/apps/mobile/src/features/preferences/stores/preferencesStore.ts index bdf8a3b64f..627d9080c9 100644 --- a/apps/mobile/src/features/preferences/stores/preferencesStore.ts +++ b/apps/mobile/src/features/preferences/stores/preferencesStore.ts @@ -83,6 +83,9 @@ interface PreferencesState { autoPublishCloudRuns: boolean; setAutoPublishCloudRuns: (enabled: boolean) => void; + + rtkEnabledCloud: boolean; + setRtkEnabledCloud: (enabled: boolean) => void; } export const usePreferencesStore = create()( @@ -127,6 +130,9 @@ export const usePreferencesStore = create()( autoPublishCloudRuns: true, setAutoPublishCloudRuns: (enabled) => set({ autoPublishCloudRuns: enabled }), + + rtkEnabledCloud: true, + setRtkEnabledCloud: (enabled) => set({ rtkEnabledCloud: enabled }), }), { name: "posthog-preferences", @@ -144,6 +150,7 @@ export const usePreferencesStore = create()( defaultReasoningEffort: state.defaultReasoningEffort, lastUsedReasoningEffort: state.lastUsedReasoningEffort, autoPublishCloudRuns: state.autoPublishCloudRuns, + rtkEnabledCloud: state.rtkEnabledCloud, }), }, ), diff --git a/apps/mobile/src/features/tasks/api.test.ts b/apps/mobile/src/features/tasks/api.test.ts index e56acbb760..92dfece844 100644 --- a/apps/mobile/src/features/tasks/api.test.ts +++ b/apps/mobile/src/features/tasks/api.test.ts @@ -62,4 +62,19 @@ describe("runTaskInCloud", () => { const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; expect(init.body).toBeUndefined(); }); + + it("sends rtk_enabled=false when the run opts out", async () => { + await runTaskInCloud("task-1", { rtkEnabled: false }); + + expect(bodyOf(mockFetch.mock.calls[0])).toMatchObject({ + rtk_enabled: false, + }); + }); + + it("omits rtk_enabled when the run keeps compression on", async () => { + await runTaskInCloud("task-1", { rtkEnabled: true }); + + const [, init] = mockFetch.mock.calls[0] as [string, RequestInit]; + expect(init.body).toBeUndefined(); + }); }); diff --git a/apps/mobile/src/features/tasks/api.ts b/apps/mobile/src/features/tasks/api.ts index 68fae4895f..909d059b9e 100644 --- a/apps/mobile/src/features/tasks/api.ts +++ b/apps/mobile/src/features/tasks/api.ts @@ -436,6 +436,8 @@ export interface RunTaskInCloudOptions { /** When true, the cloud run pushes its changes and opens a draft PR on * completion without waiting for an explicit ask. */ autoPublish?: boolean; + /** Only false is sent: opts the run out of rtk command-output compression. */ + rtkEnabled?: boolean; } export async function runTaskInCloud( @@ -460,7 +462,8 @@ export async function runTaskInCloud( options.initialPermissionMode !== undefined || options.runSource !== undefined || options.signalReportId !== undefined || - options.autoPublish !== undefined); + options.autoPublish !== undefined || + options.rtkEnabled === false); let body: string | undefined; if (hasOptions) { @@ -490,6 +493,9 @@ export async function runTaskInCloud( if (options?.autoPublish !== undefined) { payload.auto_publish = options.autoPublish; } + if (options?.rtkEnabled === false) { + payload.rtk_enabled = false; + } body = JSON.stringify(payload); } From 74671c4517516c47ed5f17da1724c497a25fb9d0 Mon Sep 17 00:00:00 2001 From: Tom Owers Date: Fri, 10 Jul 2026 12:01:59 +0100 Subject: [PATCH 2/2] fix(mobile): preserve rtk compression opt-out on cloud resume The session-store resume path built a new cloud run with resumeFromRunId but no rtkEnabled. Since rtkEnabled only alters the wire payload when false, a user who disabled compression had it silently re-enabled after an inactive-session resume. Forward the current preference so the opt-out survives resume, matching the other cloud-run call sites. Generated-By: PostHog Code Task-Id: 3b8a4908-02e4-4350-80ad-c247be2aa546 --- .../tasks/stores/taskSessionStore.test.ts | 17 +++++++++++++++++ .../features/tasks/stores/taskSessionStore.ts | 1 + 2 files changed, 18 insertions(+) diff --git a/apps/mobile/src/features/tasks/stores/taskSessionStore.test.ts b/apps/mobile/src/features/tasks/stores/taskSessionStore.test.ts index 77e5b515c4..db60b6b667 100644 --- a/apps/mobile/src/features/tasks/stores/taskSessionStore.test.ts +++ b/apps/mobile/src/features/tasks/stores/taskSessionStore.test.ts @@ -23,6 +23,7 @@ vi.mock("../api", () => ({ sendCloudCommand: vi.fn(), })); +import { usePreferencesStore } from "@/features/preferences/stores/preferencesStore"; import { getTask, runTaskInCloud } from "../api"; import type { CloudTaskUpdatePayload, @@ -155,6 +156,7 @@ describe("_resumeCloudRun", () => { vi.clearAllMocks(); useTaskStore.setState({ composerConfigByTaskId: {} }); useTaskSessionStore.setState({ sessions: {} }); + usePreferencesStore.setState({ rtkEnabledCloud: true }); mockRunTaskInCloud.mockResolvedValue({ id: "t1", latest_run: { id: "new-run" }, @@ -180,9 +182,24 @@ describe("_resumeCloudRun", () => { pendingUserMessage: "hi", reasoningEffort: "low", initialPermissionMode: "acceptEdits", + rtkEnabled: true, }); }); + it("forwards the rtk compression opt-out so resume preserves it", async () => { + usePreferencesStore.setState({ rtkEnabledCloud: false }); + mockGetTask.mockResolvedValue(previousTask({ branch: "feature" })); + + await useTaskSessionStore + .getState() + ._resumeCloudRun("t1", "prev-run", "hi"); + + expect(mockRunTaskInCloud).toHaveBeenCalledWith( + "t1", + expect.objectContaining({ rtkEnabled: false }), + ); + }); + it("prefers the composer's current selection over the previous run", async () => { useTaskStore.setState({ composerConfigByTaskId: { t1: { mode: "plan", reasoning: "max" } }, diff --git a/apps/mobile/src/features/tasks/stores/taskSessionStore.ts b/apps/mobile/src/features/tasks/stores/taskSessionStore.ts index 67792e41ff..967d1dd7b8 100644 --- a/apps/mobile/src/features/tasks/stores/taskSessionStore.ts +++ b/apps/mobile/src/features/tasks/stores/taskSessionStore.ts @@ -1165,6 +1165,7 @@ export const useTaskSessionStore = create((set, get) => ({ pendingUserMessage: prompt, reasoningEffort, initialPermissionMode, + rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud, }); const newRun = updatedTask.latest_run;