Skip to content

Commit 348461d

Browse files
committed
fix(cloud): show cloud run file content from session events instead of local filesystem
1 parent 0fd4584 commit 348461d

File tree

4 files changed

+121
-9
lines changed

4 files changed

+121
-9
lines changed

apps/code/src/renderer/features/code-editor/components/CodeEditorPanel.tsx

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { PanelMessage } from "@components/ui/PanelMessage";
22
import { Tooltip } from "@components/ui/Tooltip";
33
import { CodeMirrorEditor } from "@features/code-editor/components/CodeMirrorEditor";
4+
import { useCloudFileContent } from "@features/code-editor/hooks/useCloudFileContent";
45
import { useMarkdownViewerStore } from "@features/code-editor/stores/markdownViewerStore";
56
import { getImageMimeType } from "@features/code-editor/utils/imageUtils";
67
import { isMarkdownFile } from "@features/code-editor/utils/markdownUtils";
@@ -9,6 +10,7 @@ import { isImageFile } from "@features/message-editor/utils/imageUtils";
910
import { usePanelLayoutStore } from "@features/panels";
1011
import { useFileTreeStore } from "@features/right-sidebar/stores/fileTreeStore";
1112
import { useCwd } from "@features/sidebar/hooks/useCwd";
13+
import { useIsWorkspaceCloudRun } from "@features/workspace/hooks/useWorkspace";
1214
import { Code, Eye } from "@phosphor-icons/react";
1315
import { Box, Flex, IconButton, Text } from "@radix-ui/themes";
1416
import { trpcClient, useTRPC } from "@renderer/trpc/client";
@@ -82,34 +84,50 @@ export function CodeEditorPanel({
8284
[handleMarkdownLinkClick],
8385
);
8486

87+
const isCloudRun = useIsWorkspaceCloudRun(taskId);
88+
const cloudFile = useCloudFileContent(
89+
taskId,
90+
filePath,
91+
isCloudRun && !isImage,
92+
);
93+
8594
const repoQuery = useQuery(
8695
trpcReact.fs.readRepoFile.queryOptions(
8796
{ repoPath: repoPath ?? "", filePath },
88-
{ enabled: isInsideRepo && !isImage, staleTime: Infinity },
97+
{ enabled: isInsideRepo && !isImage && !isCloudRun, staleTime: Infinity },
8998
),
9099
);
91100

92101
const absoluteQuery = useQuery(
93102
trpcReact.fs.readAbsoluteFile.queryOptions(
94103
{ filePath: absolutePath },
95-
{ enabled: !isInsideRepo && !isImage, staleTime: Infinity },
104+
{
105+
enabled: !isInsideRepo && !isImage && !isCloudRun,
106+
staleTime: Infinity,
107+
},
96108
),
97109
);
98110

99111
const imageQuery = useQuery(
100112
trpcReact.fs.readFileAsBase64.queryOptions(
101113
{ filePath: absolutePath },
102-
{ enabled: isImage, staleTime: Infinity },
114+
{ enabled: isImage && !isCloudRun, staleTime: Infinity },
103115
),
104116
);
105117

106-
const {
107-
data: fileContent,
108-
isLoading,
109-
error,
110-
} = isInsideRepo ? repoQuery : absoluteQuery;
118+
const localQuery = isInsideRepo ? repoQuery : absoluteQuery;
119+
const fileContent = isCloudRun ? cloudFile.content : localQuery.data;
120+
const isLoading = isCloudRun ? cloudFile.isLoading : localQuery.isLoading;
121+
const error = isCloudRun ? null : localQuery.error;
111122

112123
if (isImage) {
124+
if (isCloudRun) {
125+
return (
126+
<PanelMessage detail={filePath}>
127+
Images not available for cloud runs
128+
</PanelMessage>
129+
);
130+
}
113131
if (imageQuery.isLoading) {
114132
return <PanelMessage>Loading image...</PanelMessage>;
115133
}
@@ -140,6 +158,22 @@ export function CodeEditorPanel({
140158
return <PanelMessage>Loading file...</PanelMessage>;
141159
}
142160

161+
if (isCloudRun && !cloudFile.touched) {
162+
return (
163+
<PanelMessage detail={filePath}>
164+
File content not available — the agent did not read or write this file
165+
</PanelMessage>
166+
);
167+
}
168+
169+
if (isCloudRun && cloudFile.touched && cloudFile.content == null) {
170+
return (
171+
<PanelMessage detail={filePath}>
172+
This file was deleted by the agent
173+
</PanelMessage>
174+
);
175+
}
176+
143177
if (error || fileContent == null) {
144178
return (
145179
<PanelMessage detail={absolutePath}>Failed to load file</PanelMessage>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useSessionForTask } from "@features/sessions/hooks/useSession";
2+
import {
3+
buildCloudEventSummary,
4+
type CloudFileContent,
5+
extractCloudFileContent,
6+
} from "@features/task-detail/utils/cloudToolChanges";
7+
import { useMemo } from "react";
8+
9+
export type CloudFileResult = CloudFileContent & { isLoading: boolean };
10+
11+
export function useCloudFileContent(
12+
taskId: string,
13+
filePath: string,
14+
enabled: boolean,
15+
): CloudFileResult {
16+
const session = useSessionForTask(enabled ? taskId : undefined);
17+
const events = session?.events;
18+
const isLoading = enabled && session === undefined;
19+
20+
return useMemo(() => {
21+
if (!enabled || !events) {
22+
return { content: null, touched: false, isLoading };
23+
}
24+
const summary = buildCloudEventSummary(events);
25+
const result = extractCloudFileContent(summary.toolCalls, filePath);
26+
return { ...result, isLoading: false };
27+
}, [enabled, events, filePath, isLoading]);
28+
}

apps/code/src/renderer/features/panels/hooks/usePanelLayoutHooks.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ export function useTabInjection(
8787
let updatedData = tab.data;
8888
if (tab.data.type === "file") {
8989
const rp = tab.data.relativePath;
90-
const absolutePath = isAbsolutePath(rp) ? rp : `${repoPath}/${rp}`;
90+
const absolutePath = isAbsolutePath(rp)
91+
? rp
92+
: repoPath
93+
? `${repoPath}/${rp}`
94+
: rp;
9195
updatedData = {
9296
...tab.data,
9397
absolutePath,

apps/code/src/renderer/features/task-detail/utils/cloudToolChanges.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getReadToolContent } from "@features/sessions/components/session-update/toolCallUtils";
12
import type {
23
ToolCallContent,
34
ToolCallLocation,
@@ -283,3 +284,48 @@ export function extractCloudToolChangedFiles(
283284

284285
return [...filesByPath.values()];
285286
}
287+
288+
export interface CloudFileContent {
289+
content: string | null;
290+
/** Whether the agent read, wrote, or deleted this file during the session. */
291+
touched: boolean;
292+
}
293+
294+
/**
295+
* Combines read tool results and write/edit diffs to reconstruct the latest
296+
* known content for a file from a cloud session's tool calls.
297+
*/
298+
export function extractCloudFileContent(
299+
toolCalls: Map<string, ParsedToolCall>,
300+
filePath: string,
301+
): CloudFileContent {
302+
let latestContent: string | null = null;
303+
let touched = false;
304+
305+
for (const toolCall of toolCalls.values()) {
306+
if (toolCall.status === "failed") continue;
307+
308+
const kind = inferKind(toolCall.kind, toolCall.title);
309+
const locationPath = toolCall.locations?.[0]?.path;
310+
311+
if (kind === "read" && pathsMatch(locationPath, filePath)) {
312+
const text = getReadToolContent(toolCall.content);
313+
if (text != null) {
314+
latestContent = text;
315+
touched = true;
316+
}
317+
} else if (kind === "delete" && pathsMatch(locationPath, filePath)) {
318+
latestContent = null;
319+
touched = true;
320+
} else if (kind && ["write", "edit"].includes(kind)) {
321+
const diff = getDiffContent(toolCall.content);
322+
const diffPath = diff?.path ?? locationPath;
323+
if (pathsMatch(diffPath, filePath) && diff?.newText != null) {
324+
latestContent = diff.newText;
325+
touched = true;
326+
}
327+
}
328+
}
329+
330+
return { content: latestContent, touched };
331+
}

0 commit comments

Comments
 (0)