Skip to content

Commit 825a6e6

Browse files
committed
Merge branch 'mobile-app' of github.com:PostHog/Array into mobile-app
2 parents 88e2355 + d39747d commit 825a6e6

File tree

11 files changed

+556
-105
lines changed

11 files changed

+556
-105
lines changed

apps/array/src/main/services/folders.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import type { RegisteredFolder } from "../../shared/types";
77
import { generateId } from "../../shared/utils/id";
88
import { createIpcHandler } from "../lib/ipcHandler";
99
import { logger } from "../lib/logger";
10-
import { isGitRepository } from "./git";
10+
import { getRemoteUrl, isGitRepository, parseGitHubUrl } from "./git";
1111
import { getWorktreeLocation } from "./settingsStore";
1212
import { clearAllStoreData, foldersStore } from "./store";
1313
import { deleteWorktreeIfExists } from "./worktreeUtils";
@@ -25,8 +25,38 @@ function extractFolderName(folderPath: string): string {
2525
return path.basename(folderPath);
2626
}
2727

28+
async function getRepositoryString(folderPath: string): Promise<string | undefined> {
29+
try {
30+
const remoteUrl = await getRemoteUrl(folderPath);
31+
if (!remoteUrl) return undefined;
32+
33+
const parsed = parseGitHubUrl(remoteUrl);
34+
if (!parsed) return undefined;
35+
36+
return `${parsed.organization}/${parsed.repository}`;
37+
} catch {
38+
return undefined;
39+
}
40+
}
41+
2842
async function getFolders(): Promise<RegisteredFolder[]> {
29-
return foldersStore.get("folders", []);
43+
const folders = foldersStore.get("folders", []);
44+
45+
let needsUpdate = false;
46+
for (const folder of folders) {
47+
if (!folder.repository) {
48+
folder.repository = await getRepositoryString(folder.path);
49+
if (folder.repository) {
50+
needsUpdate = true;
51+
}
52+
}
53+
}
54+
55+
if (needsUpdate) {
56+
foldersStore.set("folders", folders);
57+
}
58+
59+
return folders;
3060
}
3161

3262
async function addFolder(folderPath: string): Promise<RegisteredFolder> {
@@ -35,16 +65,22 @@ async function addFolder(folderPath: string): Promise<RegisteredFolder> {
3565
const existing = folders.find((f) => f.path === folderPath);
3666
if (existing) {
3767
existing.lastAccessed = new Date().toISOString();
68+
if (!existing.repository) {
69+
existing.repository = await getRepositoryString(folderPath);
70+
}
3871
foldersStore.set("folders", folders);
3972
return existing;
4073
}
4174

75+
const repository = await getRepositoryString(folderPath);
76+
4277
const newFolder: RegisteredFolder = {
4378
id: generateFolderId(),
4479
path: folderPath,
4580
name: extractFolderName(folderPath),
4681
lastAccessed: new Date().toISOString(),
4782
createdAt: new Date().toISOString(),
83+
repository,
4884
};
4985

5086
folders.push(newFolder);

apps/array/src/renderer/features/sessions/components/VirtualizedList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export function VirtualizedList<T>({
136136
{virtualItems.map((virtualRow) => (
137137
<div
138138
key={virtualRow.key}
139+
ref={virtualizer.measureElement}
139140
data-index={virtualRow.index}
140141
style={{
141142
position: "absolute",

apps/array/src/renderer/features/sessions/stores/sessionStore.ts

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
445445
events: [...state.sessions[session.taskRunId].events, userEvent],
446446
processedLineCount:
447447
(state.sessions[session.taskRunId].processedLineCount ?? 0) + 1,
448+
isPromptPending: true,
448449
},
449450
},
450451
}));
@@ -482,6 +483,41 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
482483
const session = get().getSessionForTask(taskId);
483484
if (!session) return false;
484485

486+
if (session.isCloud) {
487+
const authState = useAuthStore.getState();
488+
const { client } = authState;
489+
if (!client) {
490+
log.error("API client not available for cloud cancel");
491+
return false;
492+
}
493+
494+
const cancelNotification: StoredLogEntry = {
495+
type: "notification" as const,
496+
timestamp: new Date().toISOString(),
497+
direction: "client" as const,
498+
notification: {
499+
method: "session/cancel",
500+
params: {
501+
sessionId: session.taskRunId,
502+
},
503+
},
504+
};
505+
506+
try {
507+
await client.appendTaskRunLog(taskId, session.taskRunId, [
508+
cancelNotification,
509+
]);
510+
log.info("Sent cloud cancel request via S3", {
511+
taskId,
512+
runId: session.taskRunId,
513+
});
514+
return true;
515+
} catch (error) {
516+
log.error("Failed to send cloud cancel request", error);
517+
return false;
518+
}
519+
}
520+
485521
try {
486522
return await window.electronAPI.agentCancelPrompt(session.taskRunId);
487523
} catch (error) {
@@ -552,6 +588,8 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
552588
const processedCount = session.processedLineCount ?? 0;
553589
if (lines.length > processedCount) {
554590
const newLines = lines.slice(processedCount);
591+
let receivedAgentMessage = false;
592+
555593
for (const line of newLines) {
556594
try {
557595
const entry = JSON.parse(line);
@@ -581,19 +619,32 @@ export const useSessionStore = create<SessionStore>((set, get) => ({
581619
.params as SessionNotification,
582620
};
583621
get()._handleEvent(taskRunId, sessionUpdateEvent);
622+
623+
// Check if this is an agent message - means agent is responding
624+
const sessionUpdate =
625+
entry.notification?.params?.update?.sessionUpdate;
626+
if (
627+
sessionUpdate === "agent_message_chunk" ||
628+
sessionUpdate === "agent_thought_chunk"
629+
) {
630+
receivedAgentMessage = true;
631+
}
584632
}
585633
} catch {
586634
// Skip invalid JSON
587635
}
588636
}
589637

590-
// Update processed line count
638+
// Update processed line count and clear pending state if agent responded
591639
set((state) => ({
592640
sessions: {
593641
...state.sessions,
594642
[taskRunId]: {
595643
...state.sessions[taskRunId],
596644
processedLineCount: lines.length,
645+
isPromptPending: receivedAgentMessage
646+
? false
647+
: state.sessions[taskRunId]?.isPromptPending ?? false,
597648
},
598649
},
599650
}));

apps/array/src/renderer/features/sidebar/components/SidebarMenu.tsx

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { useTasks } from "@features/tasks/hooks/useTasks";
66
import { useTaskStore } from "@features/tasks/stores/taskStore";
77
import { useMeQuery } from "@hooks/useMeQuery";
88
import { useTaskContextMenu } from "@hooks/useTaskContextMenu";
9-
import { FolderIcon, FolderOpenIcon } from "@phosphor-icons/react";
9+
import { CloudIcon, FolderIcon, FolderOpenIcon } from "@phosphor-icons/react";
1010
import { Box, Flex } from "@radix-ui/themes";
1111
import { useRegisteredFoldersStore } from "@renderer/stores/registeredFoldersStore";
1212
import type { Task } from "@shared/types";
@@ -19,6 +19,7 @@ import { useTaskViewedStore } from "../stores/taskViewedStore";
1919
import { HomeItem } from "./items/HomeItem";
2020
import { NewTaskItem } from "./items/NewTaskItem";
2121
import { TaskItem } from "./items/TaskItem";
22+
import { SidebarSection } from "./SidebarSection";
2223
import { SortableFolderSection } from "./SortableFolderSection";
2324

2425
function SidebarMenuComponent() {
@@ -226,6 +227,31 @@ function SidebarMenuComponent() {
226227
}
227228
</DragOverlay>
228229
</DragDropProvider>
230+
231+
{sidebarData.cloudRepos.map((cloudRepo) => (
232+
<SidebarSection
233+
key={cloudRepo.repository}
234+
id={`cloud-${cloudRepo.repository}`}
235+
label={cloudRepo.repoName}
236+
icon={<CloudIcon size={14} weight="regular" />}
237+
isExpanded={!collapsedSections.has(`cloud-${cloudRepo.repository}`)}
238+
onToggle={() => toggleSection(`cloud-${cloudRepo.repository}`)}
239+
>
240+
{cloudRepo.tasks.map((task) => (
241+
<TaskItem
242+
key={task.id}
243+
id={task.id}
244+
label={task.title}
245+
isActive={sidebarData.activeTaskId === task.id}
246+
lastActivityAt={task.lastActivityAt}
247+
isGenerating={task.isGenerating}
248+
isUnread={task.isUnread}
249+
onClick={() => handleTaskClick(task.id)}
250+
onContextMenu={(e) => handleTaskContextMenu(task.id, e)}
251+
/>
252+
))}
253+
</SidebarSection>
254+
))}
229255
</Flex>
230256
</Box>
231257
</>

0 commit comments

Comments
 (0)