Skip to content
This repository was archived by the owner on Aug 6, 2026. It is now read-only.

Commit 81daa39

Browse files
authored
Merge branch 'main' into posthog-code/cloud-resume-keep-message-visible
2 parents ed39201 + 5ee94e2 commit 81daa39

145 files changed

Lines changed: 11109 additions & 2666 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/code/src/renderer/di/bindings.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ import {
1313
type AutoresearchSessionClient,
1414
type AutoresearchStorageClient,
1515
} from "@posthog/core/autoresearch/identifiers";
16+
import {
17+
CLOUD_TASK_CLIENT,
18+
type CloudTaskClient,
19+
} from "@posthog/core/cloud-task/cloudTaskClient";
1620
import {
1721
CODE_REVIEW_WORKSPACE_CLIENT,
1822
REVERT_HUNK_SERVICE,
@@ -76,8 +80,10 @@ import {
7680
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
7781
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
7882
import {
79-
PI_SESSION_CLIENT,
80-
type PiSessionClient,
83+
LOCAL_PI_SESSION_FACTORY,
84+
PI_SESSION_PROVIDER,
85+
type PiSessionFactory,
86+
type PiSessionProvider,
8187
} from "@posthog/core/pi-runtime/piSessionController";
8288
import {
8389
type BundleLocalSkill,
@@ -295,7 +301,9 @@ export interface RendererBindings {
295301
[ANALYTICS_TRACKER]: AnalyticsTracker;
296302
[TASK_CREATION_HOST]: ITaskCreationHost;
297303
[PI_RUNNER]: PiRunner;
298-
[PI_SESSION_CLIENT]: PiSessionClient;
304+
[PI_SESSION_PROVIDER]: PiSessionProvider;
305+
[LOCAL_PI_SESSION_FACTORY]: PiSessionFactory;
306+
[CLOUD_TASK_CLIENT]: CloudTaskClient;
299307
[TASK_CREATION_EFFECTS]: TaskCreationEffects;
300308
[RENDERER_TASK_SERVICE]: TaskService;
301309
[TASK_SERVICE]: TaskService;

apps/code/src/renderer/di/container.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import "reflect-metadata";
22
import { useDevFlagsStore } from "@features/dev-toolbar/devFlagsStore";
33
import { TypedContainer } from "@inversifyjs/strongly-typed";
44
import type { TrpcRouter } from "@main/trpc/router";
5+
import {
6+
CLOUD_TASK_CLIENT,
7+
type CloudTaskClient,
8+
} from "@posthog/core/cloud-task/cloudTaskClient";
59
import {
610
CODE_REVIEW_WORKSPACE_CLIENT,
711
REVERT_HUNK_SERVICE,
@@ -35,7 +39,7 @@ import type { LocalMcpWorkspaceClient } from "@posthog/core/local-mcp/localMcpIm
3539
import { PI_RUNNER } from "@posthog/core/pi-runtime/identifiers";
3640
import { piRuntimeModule } from "@posthog/core/pi-runtime/pi-runtime.module";
3741
import type { PiRunner } from "@posthog/core/pi-runtime/piRunner";
38-
import { PI_SESSION_CLIENT } from "@posthog/core/pi-runtime/piSessionController";
42+
import { LOCAL_PI_SESSION_FACTORY } from "@posthog/core/pi-runtime/piSessionController";
3943
import {
4044
CLOUD_ARTIFACT_BUNDLE_LOCAL_SKILL,
4145
CLOUD_ARTIFACT_READ_FILE_AS_BASE64,
@@ -89,7 +93,9 @@ import {
8993
import { WorkspaceSetupService } from "@posthog/core/workspace/WorkspaceSetupService";
9094
import { setRootContainer } from "@posthog/di/container";
9195
import { HOST_TRPC_CLIENT } from "@posthog/host-router/client";
92-
import { TrpcPiSessionClient } from "@posthog/host-router/pi-session-client";
96+
import { TrpcCloudTaskClient } from "@posthog/host-router/cloud-task-client";
97+
import { TrpcPiRunner } from "@posthog/host-router/pi-runner";
98+
import { TrpcPiSessionFactory } from "@posthog/host-router/pi-session-factory";
9399
import {
94100
BROWSER_TABS_CLIENT,
95101
type BrowserTabsClient,
@@ -156,7 +162,6 @@ import { trpcClient } from "@renderer/trpc";
156162
import { hostTrpcClient } from "@renderer/trpc/client";
157163
import type { TRPCClient } from "@trpc/client";
158164
import { hostLog, logger } from "@utils/logger";
159-
import { TrpcPiRunner } from "../platform-adapters/trpc-pi-runner";
160165
import type { RendererBindings } from "./bindings";
161166
import { TASK_SERVICE as RENDERER_TASK_SERVICE, TRPC_CLIENT } from "./tokens";
162167

@@ -297,7 +302,8 @@ container
297302
// Bind services
298303
container.bind<ITaskCreationHost>(TASK_CREATION_HOST).to(TrpcTaskCreationHost);
299304
container.bind<PiRunner>(PI_RUNNER).to(TrpcPiRunner);
300-
container.bind(PI_SESSION_CLIENT).to(TrpcPiSessionClient);
305+
container.bind(LOCAL_PI_SESSION_FACTORY).to(TrpcPiSessionFactory);
306+
container.bind<CloudTaskClient>(CLOUD_TASK_CLIENT).to(TrpcCloudTaskClient);
301307
container.load(piRuntimeModule);
302308
container.bind(TASK_CREATION_EFFECTS).toConstantValue(taskCreationEffects);
303309
container.bind<TaskService>(RENDERER_TASK_SERVICE).to(TaskService);

apps/mobile/eas.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,27 @@
44
"appVersionSource": "remote"
55
},
66
"build": {
7+
"base": {
8+
"node": "22.19.0"
9+
},
710
"development": {
11+
"extends": "base",
812
"developmentClient": true,
913
"distribution": "internal"
1014
},
1115
"preview": {
16+
"extends": "base",
1217
"distribution": "internal"
1318
},
1419
"production": {
20+
"extends": "base",
1521
"autoIncrement": true,
1622
"android": {
1723
"buildType": "app-bundle"
1824
}
1925
},
2026
"play-registration": {
27+
"extends": "base",
2128
"distribution": "internal",
2229
"android": {
2330
"buildType": "apk"

apps/mobile/metro.config.js

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@ config.resolver.nodeModulesPaths = [
1616
path.resolve(monorepoRoot, "node_modules"),
1717
];
1818

19-
// Force React to resolve from monorepo root, and alias workspace packages
20-
// directly to their TypeScript source so Babel can transpile them (Metro
21-
// does not consume the ESM `dist/` build cleanly).
19+
// Force React to resolve from monorepo root
2220
config.resolver.extraNodeModules = {
2321
react: path.resolve(monorepoRoot, "node_modules/react"),
24-
"@posthog/shared": path.resolve(monorepoRoot, "packages/shared/src/index.ts"),
2522
};
2623

2724
// Apply NativeWind first so its resolver/transformer changes are in place
@@ -42,4 +39,35 @@ nativeWindConfig.resolver = {
4239
sourceExts: [...nativeWindConfig.resolver.sourceExts, "svg"],
4340
};
4441

42+
// Resolve @posthog/shared to its TypeScript source so Babel transpiles it.
43+
// Its package.json `exports` point at `dist/`, which is only present after a
44+
// `pnpm build` -- EAS Build never runs one, so bundling there fails outright.
45+
// This must be a resolveRequest hook rather than an `extraNodeModules` alias:
46+
// pnpm symlinks the package into node_modules, so Metro resolves it there
47+
// first and never consults the alias, and an alias also cannot map subpath
48+
// imports like `@posthog/shared/domain-types`.
49+
const SHARED_PACKAGE = "@posthog/shared";
50+
const sharedSrc = path.resolve(monorepoRoot, "packages/shared/src");
51+
const upstreamResolveRequest = nativeWindConfig.resolver.resolveRequest;
52+
53+
nativeWindConfig.resolver.resolveRequest = (context, moduleName, platform) => {
54+
if (
55+
moduleName === SHARED_PACKAGE ||
56+
moduleName.startsWith(`${SHARED_PACKAGE}/`)
57+
) {
58+
const subpath = moduleName.slice(SHARED_PACKAGE.length + 1) || "index";
59+
return context.resolveRequest(
60+
context,
61+
path.join(sharedSrc, subpath),
62+
platform,
63+
);
64+
}
65+
66+
return (upstreamResolveRequest ?? context.resolveRequest)(
67+
context,
68+
moduleName,
69+
platform,
70+
);
71+
};
72+
4573
module.exports = nativeWindConfig;

apps/mobile/src/app/task/[id].tsx

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,29 @@
11
import { Text } from "@components/text";
2-
import { DEFAULT_CLAUDE_EXECUTION_MODE } from "@posthog/core/sessions/executionModes";
2+
import { getCloudReasoningConfigOptionId } from "@posthog/core/sessions/cloudSessionConfig";
3+
import { getDefaultExecutionModeForAdapter } from "@posthog/core/sessions/executionModes";
34
import {
45
countUserMessages,
56
getSessionActivityPhase,
67
} from "@posthog/core/sessions/sessionActivity";
8+
import type { CloudComposerSelection } from "@posthog/core/task-detail/composerModelPolicy";
79
import { isTaskRunning } from "@posthog/core/tasks/taskArchive";
810
import {
11+
type Adapter,
12+
DEFAULT_CODEX_MODEL,
913
DEFAULT_GATEWAY_MODEL,
1014
DEFAULT_REASONING_EFFORT,
1115
type ExecutionMode,
12-
getReasoningEffortOptions,
16+
isModalModelId,
17+
isSupportedReasoningEffort,
18+
KIMI_MODEL_FLAG,
1319
type SupportedReasoningEffort,
1420
serializeCloudPrompt,
1521
type Task,
1622
} from "@posthog/shared";
1723
import { useQueryClient } from "@tanstack/react-query";
1824
import * as Haptics from "expo-haptics";
1925
import { useLocalSearchParams, useRouter } from "expo-router";
26+
import { useFeatureFlag } from "posthog-react-native";
2027
import { useCallback, useEffect, useRef, useState } from "react";
2128
import {
2229
ActivityIndicator,
@@ -57,6 +64,7 @@ import {
5764
import { useTaskSessionStore } from "@/features/tasks/stores/taskSessionStore";
5865
import { useTaskStore } from "@/features/tasks/stores/taskStore";
5966
import { confirmStopRun } from "@/features/tasks/utils/archiveGuard";
67+
import { buildCloudTaskRunConfig } from "@/features/tasks/utils/cloudTaskRunConfig";
6068
import { useScreenInsets } from "@/hooks/useScreenInsets";
6169
import {
6270
ANALYTICS_EVENTS,
@@ -167,11 +175,45 @@ export default function TaskDetailScreen() {
167175
const [initialComposerMessage, setInitialComposerMessage] = useState<
168176
string | undefined
169177
>();
178+
const composerAdapter: Adapter =
179+
task?.latest_run?.runtime_adapter &&
180+
!session?.terminalStatus &&
181+
composerConfig?.adapter !== task.latest_run.runtime_adapter
182+
? task.latest_run.runtime_adapter
183+
: (composerConfig?.adapter ??
184+
task?.latest_run?.runtime_adapter ??
185+
"claude");
186+
const composerConfigMatchesAdapter =
187+
composerConfig?.adapter === undefined
188+
? composerAdapter === "claude"
189+
: composerConfig.adapter === composerAdapter;
170190
const composerMode: ExecutionMode =
171-
composerConfig?.mode ?? DEFAULT_CLAUDE_EXECUTION_MODE;
172-
const composerModel = composerConfig?.model ?? DEFAULT_GATEWAY_MODEL;
191+
(composerConfigMatchesAdapter ? composerConfig?.mode : undefined) ??
192+
getDefaultExecutionModeForAdapter(composerAdapter);
193+
const kimiEnabled = !!useFeatureFlag(KIMI_MODEL_FLAG);
194+
const persistedComposerModel =
195+
(composerConfigMatchesAdapter ? composerConfig?.model : undefined) ??
196+
task?.latest_run?.model ??
197+
(composerAdapter === "codex" ? DEFAULT_CODEX_MODEL : DEFAULT_GATEWAY_MODEL);
198+
// Fall a persisted Kimi selection back to the default when the flag is off so
199+
// a hidden model never gets sent on the retry-after-terminal path. The
200+
// composer independently re-resolves against the live config once mounted.
201+
const composerModel =
202+
!kimiEnabled && isModalModelId(persistedComposerModel)
203+
? DEFAULT_GATEWAY_MODEL
204+
: persistedComposerModel;
205+
const requestedComposerReasoning = composerConfigMatchesAdapter
206+
? composerConfig?.reasoning
207+
: undefined;
173208
const composerReasoning: SupportedReasoningEffort =
174-
composerConfig?.reasoning ?? DEFAULT_REASONING_EFFORT;
209+
requestedComposerReasoning &&
210+
isSupportedReasoningEffort(
211+
composerAdapter,
212+
composerModel,
213+
requestedComposerReasoning,
214+
)
215+
? requestedComposerReasoning
216+
: DEFAULT_REASONING_EFFORT;
175217

176218
const messagingMode = useMessagingMode(taskId);
177219
const queuedCount = useQueuedCount(taskId);
@@ -314,18 +356,18 @@ export default function TaskDetailScreen() {
314356
)
315357
: text;
316358

317-
const supportsReasoning =
318-
getReasoningEffortOptions("claude", composerModel) !== null;
319359
const updatedTask = await getPostHogApiClient().runTaskInCloud(
320360
taskId,
321361
undefined,
322362
{
323363
resumeFromRunId: task.latest_run?.id,
324364
pendingUserMessage,
325-
adapter: "claude",
326-
model: composerModel,
327-
reasoningLevel: supportsReasoning ? composerReasoning : undefined,
328-
initialPermissionMode: composerMode,
365+
...buildCloudTaskRunConfig({
366+
adapter: composerAdapter,
367+
mode: composerMode,
368+
model: composerModel,
369+
reasoning: composerReasoning,
370+
}),
329371
rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud,
330372
},
331373
);
@@ -351,6 +393,7 @@ export default function TaskDetailScreen() {
351393
connectToTask,
352394
updateTaskInCache,
353395
composerMode,
396+
composerAdapter,
354397
composerModel,
355398
composerReasoning,
356399
],
@@ -507,6 +550,17 @@ export default function TaskDetailScreen() {
507550
[taskId, setComposerConfig, setConfigOption],
508551
);
509552

553+
const handleAdapterChange = useCallback(
554+
(selection: CloudComposerSelection) => {
555+
if (!taskId) return;
556+
setComposerConfig(taskId, selection);
557+
const preferences = usePreferencesStore.getState();
558+
preferences.setLastNewTaskMode(selection.mode);
559+
preferences.setLastUsedReasoningEffort(selection.reasoning);
560+
},
561+
[taskId, setComposerConfig],
562+
);
563+
510564
const handleModelChange = useCallback(
511565
(value: string) => {
512566
if (!taskId) return;
@@ -520,10 +574,14 @@ export default function TaskDetailScreen() {
520574
(value: SupportedReasoningEffort) => {
521575
if (!taskId) return;
522576
setComposerConfig(taskId, { reasoning: value });
523-
setConfigOption(taskId, "effort", value).catch(() => {});
577+
setConfigOption(
578+
taskId,
579+
getCloudReasoningConfigOptionId(composerAdapter),
580+
value,
581+
).catch(() => {});
524582
usePreferencesStore.getState().setLastUsedReasoningEffort(value);
525583
},
526-
[taskId, setComposerConfig, setConfigOption],
584+
[taskId, composerAdapter, setComposerConfig, setConfigOption],
527585
);
528586

529587
const handleStop = useCallback(() => {
@@ -575,6 +633,12 @@ export default function TaskDetailScreen() {
575633
undefined,
576634
{
577635
resumeFromRunId: task.latest_run?.id,
636+
...buildCloudTaskRunConfig({
637+
adapter: composerAdapter,
638+
mode: composerMode,
639+
model: composerModel,
640+
reasoning: composerReasoning,
641+
}),
578642
rtkEnabled: usePreferencesStore.getState().rtkEnabledCloud,
579643
},
580644
);
@@ -591,7 +655,17 @@ export default function TaskDetailScreen() {
591655
"Could not restart the task. Please try again.",
592656
);
593657
}
594-
}, [taskId, task, disconnectFromTask, connectToTask, updateTaskInCache]);
658+
}, [
659+
taskId,
660+
task,
661+
disconnectFromTask,
662+
connectToTask,
663+
updateTaskInCache,
664+
composerAdapter,
665+
composerModel,
666+
composerReasoning,
667+
composerMode,
668+
]);
595669

596670
// Clear retrying once the agent finishes a turn or the run terminates.
597671
useEffect(() => {
@@ -785,6 +859,9 @@ export default function TaskDetailScreen() {
785859
/>
786860
) : null}
787861
<TaskChatComposer
862+
key={taskId}
863+
adapter={composerAdapter}
864+
canChangeAdapter={!!session?.terminalStatus}
788865
onSend={handleSendPrompt}
789866
restoredDraft={restoredDraft}
790867
editing={!!editingQueuedId}
@@ -798,6 +875,7 @@ export default function TaskDetailScreen() {
798875
mode={composerMode}
799876
model={composerModel}
800877
reasoning={composerReasoning}
878+
onAdapterChange={handleAdapterChange}
801879
onModeChange={handleModeChange}
802880
onModelChange={handleModelChange}
803881
onReasoningChange={handleReasoningChange}

0 commit comments

Comments
 (0)