Merge origin/main into dev (second round)

Resolve 21 conflicts from latest main (provider profiles, Windows fixes, 0.1.55-rc.1).
Fix all type errors and syntax issues from merge artifacts.
This commit is contained in:
Mohamed Boudra
2026-04-14 04:07:36 +07:00
249 changed files with 9118 additions and 4973 deletions
@@ -1,7 +1,7 @@
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import { createNameId } from "mnemonic-id";
import type { ImageAttachment } from "@/components/message-input";
import { View, Text, Pressable, ScrollView, Keyboard, Platform } from "react-native";
import { View, Text, Pressable, ScrollView, Keyboard } from "react-native";
import { useLocalSearchParams, useRouter } from "expo-router";
import { useIsFocused } from "@react-navigation/native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -57,6 +57,8 @@ import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
import { useAgentInputDraft } from "@/hooks/use-agent-input-draft";
import { useDraftAgentCreateFlow } from "@/hooks/use-draft-agent-create-flow";
import { useDraftAgentFeatures } from "@/hooks/use-draft-agent-features";
import { isWeb } from "@/constants/platform";
const EMPTY_PENDING_PERMISSIONS = new Map();
const DRAFT_CAPABILITIES: AgentCapabilityFlags = {
@@ -67,10 +69,6 @@ const DRAFT_CAPABILITIES: AgentCapabilityFlags = {
supportsReasoningStream: false,
supportsToolInvocations: false,
};
const PROVIDER_DEFINITION_MAP = new Map(
AGENT_PROVIDER_DEFINITIONS.map((definition) => [definition.id, definition]),
);
function getParamValue(value: string | string[] | undefined) {
if (typeof value === "string") {
const trimmed = value.trim();
@@ -91,16 +89,14 @@ function getValidProvider(value: string | undefined) {
if (!value) {
return undefined;
}
return PROVIDER_DEFINITION_MAP.has(value as AgentProvider) ? (value as AgentProvider) : undefined;
return value as AgentProvider;
}
function getValidMode(provider: AgentProvider | undefined, value: string | undefined) {
if (!provider || !value) {
return undefined;
}
const definition = PROVIDER_DEFINITION_MAP.get(provider);
const modes = definition?.modes ?? [];
return modes.some((mode) => mode.id === value) ? value : undefined;
return value;
}
type DraftAgentParams = {
@@ -205,22 +201,20 @@ function DraftAgentScreenContent({
const draftIdRef = useRef(generateDraftId());
const draftAgentIdRef = useRef(generateDraftId());
const draftInput = useAgentInputDraft(
{
draftKey: ({ selectedServerId }) =>
buildDraftStoreKey({
serverId: selectedServerId ?? "",
agentId: draftAgentIdRef.current,
draftId: draftIdRef.current,
}),
composer: {
initialServerId: resolvedServerId ?? null,
initialValues,
isVisible,
onlineServerIds,
},
const draftInput = useAgentInputDraft({
draftKey: ({ selectedServerId }) =>
buildDraftStoreKey({
serverId: selectedServerId ?? "",
agentId: draftAgentIdRef.current,
draftId: draftIdRef.current,
}),
composer: {
initialServerId: resolvedServerId ?? null,
initialValues,
isVisible,
onlineServerIds,
},
);
});
const composerState = draftInput.composerState;
if (!composerState) {
throw new Error("Draft agent composer state is required");
@@ -636,7 +630,9 @@ function DraftAgentScreenContent({
useCallback(
(state) =>
resolveWorkspaceIdByExecutionDirectory({
workspaces: selectedServerId ? state.sessions[selectedServerId]?.workspaces?.values() : null,
workspaces: selectedServerId
? state.sessions[selectedServerId]?.workspaces?.values()
: null,
workspaceDirectory: explorerCwd,
}),
[explorerCwd, selectedServerId],
@@ -802,7 +798,7 @@ function DraftAgentScreenContent({
},
onBeforeSubmit: () => {
void persistFormPreferences();
if (Platform.OS === "web") {
if (isWeb) {
(document.activeElement as HTMLElement | null)?.blur?.();
}
Keyboard.dismiss();
@@ -868,9 +864,7 @@ function DraftAgentScreenContent({
cwd: resolvedWorkingDir,
...(modeId ? { modeId } : {}),
...(effectiveModelId ? { model: effectiveModelId } : {}),
...(effectiveThinkingOptionId
? { thinkingOptionId: effectiveThinkingOptionId }
: {}),
...(effectiveThinkingOptionId ? { thinkingOptionId: effectiveThinkingOptionId } : {}),
};
const effectiveBaseBranch = baseBranch.trim();
@@ -932,9 +926,7 @@ function DraftAgentScreenContent({
},
onCreateSuccess: ({ result }) => {
if (!result.workspaceId) {
router.replace(
buildHostAgentDetailRoute(selectedServerId as string, result.id) as any,
);
router.replace(buildHostAgentDetailRoute(selectedServerId as string, result.id) as any);
return;
}
const route = prepareWorkspaceTab({
@@ -3,7 +3,13 @@ import { Pressable, Text, View } from "react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { createNameId } from "mnemonic-id";
import { useQuery } from "@tanstack/react-query";
import { CircleDot, ChevronDown, ExternalLink, GitBranch, GitPullRequest } from "lucide-react-native";
import {
CircleDot,
ChevronDown,
ExternalLink,
GitBranch,
GitPullRequest,
} from "lucide-react-native";
import { GitHubIcon } from "@/components/icons/github-icon";
import { Composer } from "@/components/composer";
import { Combobox, ComboboxItem } from "@/components/ui/combobox";
@@ -26,9 +32,7 @@ import { encodeImages } from "@/utils/encode-images";
import { toErrorMessage } from "@/utils/error-messages";
import { openExternalUrl } from "@/utils/open-external-url";
import { buildGitHubAttachmentFromSearchItem } from "@/utils/review-attachments";
import {
requireWorkspaceExecutionAuthority,
} from "@/utils/workspace-execution";
import { requireWorkspaceExecutionAuthority } from "@/utils/workspace-execution";
import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
import type { ImageAttachment, MessagePayload } from "@/components/message-input";
import type { GitHubSearchItem } from "@server/shared/messages";
@@ -123,14 +127,13 @@ export function NewWorkspaceScreen({
const githubSearchOptions: ComboboxOptionType[] = useMemo(() => {
const items = githubSearchResultsQuery.data?.items ?? [];
return items
.map((item) => ({
id: `${item.kind}:${item.number}`,
label: `#${item.number} ${item.title}`,
// Include search query so the Combobox's client-side filter doesn't
// discard server-searched results that match on body but not title.
description: githubSearchQuery_trimmed,
}));
return items.map((item) => ({
id: `${item.kind}:${item.number}`,
label: `#${item.number} ${item.title}`,
// Include search query so the Combobox's client-side filter doesn't
// discard server-searched results that match on body but not title.
description: githubSearchQuery_trimmed,
}));
}, [githubSearchResultsQuery.data?.items, githubSearchQuery_trimmed]);
const handleSelectGithubItem = useCallback(
@@ -198,7 +201,9 @@ export function NewWorkspaceScreen({
const initialPrompt = text.trim();
const encodedImages = await encodeImages(images);
const reviewAttachment = buildGitHubAttachmentFromSearchItem(selectedGithubItem);
const workspaceDirectory = requireWorkspaceExecutionAuthority({ workspace }).workspaceDirectory;
const workspaceDirectory = requireWorkspaceExecutionAuthority({
workspace,
}).workspaceDirectory;
const agent = await connectedClient.createAgent({
provider: composerState.selectedProvider,
cwd: workspaceDirectory,
@@ -234,7 +239,15 @@ export function NewWorkspaceScreen({
setPendingAction(null);
}
},
[composerState, ensureWorkspace, selectedGithubItem, serverId, setAgents, toast, withConnectedClient],
[
composerState,
ensureWorkspace,
selectedGithubItem,
serverId,
setAgents,
toast,
withConnectedClient,
],
);
const workspaceTitle =
@@ -337,10 +350,7 @@ export function NewWorkspaceScreen({
active={active}
onPress={onPress}
leadingSlot={
<GitBranch
size={theme.iconSize.sm}
color={theme.colors.foregroundMuted}
/>
<GitBranch size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
}
/>
)}
@@ -366,7 +376,10 @@ export function NewWorkspaceScreen({
<>
<View style={styles.badgeIcon}>
{selectedGithubItem.kind === "pr" ? (
<GitPullRequest size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
<GitPullRequest
size={theme.iconSize.md}
color={theme.colors.foregroundMuted}
/>
) : (
<CircleDot size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
)}
@@ -400,7 +413,11 @@ export function NewWorkspaceScreen({
</Pressable>
<Combobox
options={githubSearchOptions}
value={selectedGithubItem ? `${selectedGithubItem.kind}:${selectedGithubItem.number}` : ""}
value={
selectedGithubItem
? `${selectedGithubItem.kind}:${selectedGithubItem.number}`
: ""
}
onSelect={handleSelectGithubItem}
searchable
searchPlaceholder="Search issues and PRs..."
@@ -414,9 +431,7 @@ export function NewWorkspaceScreen({
desktopPlacement="bottom-start"
anchorRef={githubAnchorRef}
emptyText={
githubSearchResultsQuery.isFetching
? "Searching..."
: "No results found."
githubSearchResultsQuery.isFetching ? "Searching..." : "No results found."
}
renderOption={({ option, selected, active, onPress }) => {
const item = (githubSearchResultsQuery.data?.items ?? []).find(
@@ -452,7 +467,9 @@ export function NewWorkspaceScreen({
{({ hovered }) => (
<ExternalLink
size={theme.iconSize.sm}
color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted}
color={
hovered ? theme.colors.foreground : theme.colors.foregroundMuted
}
/>
)}
</Pressable>
+9 -9
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useRef, useCallback } from "react";
import type { MutableRefObject, ComponentType } from "react";
import { View, Text, ScrollView, Alert, Platform, Pressable } from "react-native";
import { View, Text, ScrollView, Alert, Pressable } from "react-native";
import { router, useLocalSearchParams } from "expo-router";
import { useFocusEffect } from "@react-navigation/native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
@@ -68,10 +68,11 @@ import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon";
import { useDaemonConfig } from "@/hooks/use-daemon-config";
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
import { useIsCompactFormFactor } from "@/constants/layout";
import { AGENT_PROVIDER_DEFINITIONS } from "@server/server/agent/provider-manifest";
import { getProviderIcon } from "@/components/provider-icons";
import { ProviderDiagnosticSheet } from "@/components/provider-diagnostic-sheet";
import { StatusBadge } from "@/components/ui/status-badge";
import { buildProviderDefinitions } from "@/utils/provider-definitions";
import { isWeb } from "@/constants/platform";
// ---------------------------------------------------------------------------
// Section definitions
@@ -522,6 +523,7 @@ function ProvidersSection({ routeServerId }: ProvidersSectionProps) {
const isConnected = useHostRuntimeIsConnected(routeServerId);
const { entries, isLoading, isFetching, refresh } = useProvidersSnapshot(routeServerId);
const [diagnosticProvider, setDiagnosticProvider] = useState<string | null>(null);
const providerDefinitions = buildProviderDefinitions(entries);
const hasServer = routeServerId.length > 0;
@@ -557,7 +559,7 @@ function ProvidersSection({ routeServerId }: ProvidersSectionProps) {
</View>
) : (
<View style={[settingsStyles.card, styles.audioCard]}>
{AGENT_PROVIDER_DEFINITIONS.map((def) => {
{providerDefinitions.map((def) => {
const entry = entries?.find((e) => e.provider === def.id);
const status = entry?.status ?? "unavailable";
const ProviderIcon = getProviderIcon(def.id);
@@ -1672,22 +1674,20 @@ function DaemonCard({ daemon, onOpenSettings }: DaemonCardProps) {
<View style={styles.hostHeaderRight}>
<View
style={[
Platform.OS === "web" ? styles.statusPill : styles.statusPillMobile,
isWeb ? styles.statusPill : styles.statusPillMobile,
{ backgroundColor: statusPillBg },
]}
>
<View style={[styles.statusDot, { backgroundColor: statusColor }]} />
{Platform.OS === "web" ? (
{isWeb ? (
<Text style={[styles.statusText, { color: statusColor }]}>{badgeText}</Text>
) : null}
</View>
{connectionBadge ? (
<View
style={Platform.OS === "web" ? styles.connectionPill : styles.connectionPillMobile}
>
<View style={isWeb ? styles.connectionPill : styles.connectionPillMobile}>
{connectionBadge.icon}
{Platform.OS === "web" ? (
{isWeb ? (
<Text style={styles.connectionText} numberOfLines={1}>
{connectionBadge.text}
</Text>
@@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { View, Text, Platform } from "react-native";
import { View, Text } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import { StyleSheet } from "react-native-unistyles";
import { settingsStyles } from "@/styles/settings";
@@ -20,6 +20,7 @@ import {
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
import { getShortcutOs } from "@/utils/shortcut-platform";
import { getIsElectronRuntime } from "@/constants/layout";
import { isNative } from "@/constants/platform";
function ShortcutSequence({
chord,
@@ -138,7 +139,7 @@ export function KeyboardShortcutsSection() {
}
useEffect(() => {
if (Platform.OS !== "web") return;
if (isNative) return;
if (capturingBindingId === null) return;
function handleKeyDown(event: KeyboardEvent) {
@@ -173,7 +174,7 @@ export function KeyboardShortcutsSection() {
};
}, [setCapturingShortcut]);
if (Platform.OS !== "web") {
if (isNative) {
return (
<View style={settingsStyles.section}>
<Text style={settingsStyles.sectionTitle}>Shortcuts</Text>
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useState } from "react";
import { ActivityIndicator, Platform, ScrollView, Text, View } from "react-native";
import { ActivityIndicator, ScrollView, Text, View } from "react-native";
import * as Clipboard from "expo-clipboard";
import { openExternalUrl } from "@/utils/open-external-url";
import { BookOpen, Check, Copy, RotateCw, TriangleAlert } from "lucide-react-native";
@@ -9,6 +9,7 @@ import { Button } from "@/components/ui/button";
import { Fonts } from "@/constants/theme";
import { getDesktopDaemonLogs, type DesktopDaemonLogs } from "@/desktop/daemon/desktop-daemon";
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
import { isWeb } from "@/constants/platform";
type StartupSplashScreenProps = {
bootstrapState?: {
@@ -42,7 +43,7 @@ const styles = StyleSheet.create((theme) => ({
},
errorScrollView: {
flex: 1,
...(Platform.OS === "web"
...(isWeb
? {
overflowX: "auto",
overflowY: "auto",
@@ -145,7 +146,7 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.xs,
color: theme.colors.foreground,
lineHeight: 18,
...(Platform.OS === "web"
...(isWeb
? {
whiteSpace: "pre",
overflowWrap: "normal",
@@ -9,7 +9,6 @@ import {
} from "react";
import {
ActivityIndicator,
Platform,
Pressable,
ScrollView,
Text,
@@ -30,6 +29,7 @@ import {
} from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { SortableInlineList } from "@/components/sortable-inline-list";
import { isNative, isWeb } from "@/constants/platform";
import {
ContextMenu,
ContextMenuContent,
@@ -110,6 +110,7 @@ function useMiddleClickClose(onClose: () => void) {
const ref = useRef<View>(null);
useEffect(() => {
if (isNative) return;
const node = ref.current as unknown as HTMLElement | null;
if (!node) return;
@@ -171,17 +172,16 @@ function TabChip({
);
const [hovered, setHovered] = useState(false);
const isHighlighted = isActive || hovered || isCloseHovered;
const closeButtonDragBlockers =
Platform.OS === "web"
? ({
onPointerDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
onMouseDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
} as const)
: undefined;
const closeButtonDragBlockers = isWeb
? ({
onPointerDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
onMouseDown: (event: { stopPropagation?: () => void }) => {
event.stopPropagation?.();
},
} as const)
: undefined;
return (
<View ref={middleClickRef}>
@@ -196,7 +196,7 @@ function TabChip({
enabledOnMobile={false}
style={({ hovered, pressed }) => [
styles.tab,
Platform.OS === "web" && isDragging && ({ cursor: "grabbing" } as const),
isWeb && isDragging && ({ cursor: "grabbing" } as const),
{
minWidth: resolvedTabWidth,
width: resolvedTabWidth,
@@ -1,5 +1,5 @@
import { useCallback, useMemo, useRef } from "react";
import { Keyboard, Platform, ScrollView, Text, View } from "react-native";
import { Keyboard, ScrollView, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import invariant from "tiny-invariant";
import { Composer } from "@/components/composer";
@@ -17,6 +17,7 @@ import { getWorkspaceExecutionAuthority } from "@/utils/workspace-execution";
import { shouldAutoFocusWorkspaceDraftComposer } from "@/screens/workspace/workspace-draft-pane-focus";
import type { AgentCapabilityFlags } from "@server/server/agent/agent-sdk-types";
import type { AgentSnapshotPayload } from "@server/shared/messages";
import { isWeb } from "@/constants/platform";
const EMPTY_PENDING_PERMISSIONS = new Map();
const DRAFT_CAPABILITIES: AgentCapabilityFlags = {
@@ -63,18 +64,16 @@ export function WorkspaceDraftAgentTab({
}),
[draftId, serverId, tabId],
);
const draftInput = useAgentInputDraft(
{
draftKey: draftStoreKey,
composer: {
initialServerId: serverId,
initialValues: workspaceDirectory ? { workingDir: workspaceDirectory } : undefined,
isVisible: true,
onlineServerIds: isConnected ? [serverId] : [],
lockedWorkingDir: workspaceDirectory ?? undefined,
},
const draftInput = useAgentInputDraft({
draftKey: draftStoreKey,
composer: {
initialServerId: serverId,
initialValues: workspaceDirectory ? { workingDir: workspaceDirectory } : undefined,
isVisible: true,
onlineServerIds: isConnected ? [serverId] : [],
lockedWorkingDir: workspaceDirectory ?? undefined,
},
);
});
const composerState = draftInput.composerState;
if (!composerState) {
throw new Error("Workspace draft composer state is required");
@@ -112,7 +111,7 @@ export function WorkspaceDraftAgentTab({
},
onBeforeSubmit: () => {
void composerState.persistFormPreferences();
if (Platform.OS === "web") {
if (isWeb) {
(document.activeElement as HTMLElement | null)?.blur?.();
}
Keyboard.dismiss();
@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from "react";
import { ActivityIndicator, Platform, Pressable, Text, View } from "react-native";
import { ActivityIndicator, Pressable, Text, View } from "react-native";
import { useMutation, useQuery } from "@tanstack/react-query";
import { Check, ChevronDown } from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
@@ -15,6 +15,7 @@ import { useToast } from "@/contexts/toast-context";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { resolvePreferredEditorId, usePreferredEditor } from "@/hooks/use-preferred-editor";
import { isAbsolutePath } from "@/utils/path";
import { isWeb } from "@/constants/platform";
interface WorkspaceOpenInEditorButtonProps {
serverId: string;
@@ -29,10 +30,7 @@ export function WorkspaceOpenInEditorButton({ serverId, cwd }: WorkspaceOpenInEd
const { preferredEditorId, updatePreferredEditor } = usePreferredEditor();
const shouldLoadEditors =
Platform.OS === "web" &&
Boolean(client && isConnected) &&
cwd.trim().length > 0 &&
isAbsolutePath(cwd);
isWeb && Boolean(client && isConnected) && cwd.trim().length > 0 && isAbsolutePath(cwd);
const availableEditorsQuery = useQuery<EditorTargetDescriptorPayload[]>({
queryKey: ["available-editors", serverId],
@@ -1,15 +1,7 @@
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useStoreWithEqualityFn } from "zustand/traditional";
import { useIsFocused } from "@react-navigation/native";
import {
ActivityIndicator,
BackHandler,
Keyboard,
Platform,
Pressable,
Text,
View,
} from "react-native";
import { ActivityIndicator, BackHandler, Keyboard, Pressable, Text, View } from "react-native";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import * as Clipboard from "expo-clipboard";
import { DiffStat } from "@/components/diff-stat";
@@ -125,6 +117,7 @@ import {
import { findAdjacentPane } from "@/utils/split-navigation";
import { isAbsolutePath } from "@/utils/path";
import { useIsCompactFormFactor, supportsDesktopPaneSplits } from "@/constants/layout";
import { isWeb, isNative } from "@/constants/platform";
const TERMINALS_QUERY_STALE_TIME = 5_000;
const WORKSPACE_SETUP_AUTO_OPEN_WINDOW_MS = 30_000;
@@ -263,7 +256,7 @@ function WorkspaceDocumentTitleEffect({
titleState: "ready" | "loading";
}) {
useEffect(() => {
if (Platform.OS !== "web" || typeof document === "undefined") {
if (isNative || typeof document === "undefined") {
return;
}
const resolvedLabel = label.trim();
@@ -659,9 +652,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
type ListTerminalsPayload = ListTerminalsResponse["payload"];
const terminalsQuery = useQuery({
queryKey: terminalsQueryKey,
enabled:
Boolean(client && isConnected) &&
Boolean(workspaceDirectory),
enabled: Boolean(client && isConnected) && Boolean(workspaceDirectory),
queryFn: async () => {
if (!client || !workspaceDirectory) {
throw new Error("Host is not connected");
@@ -763,9 +754,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
normalizedServerId,
workspaceDirectory ?? `missing-workspace-directory:${normalizedWorkspaceId}`,
),
enabled:
Boolean(client && isConnected) &&
Boolean(workspaceDirectory),
enabled: Boolean(client && isConnected) && Boolean(workspaceDirectory),
queryFn: async () => {
if (!client || !workspaceDirectory) {
throw new Error("Host is not connected");
@@ -840,7 +829,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
});
useEffect(() => {
if (Platform.OS === "web" || !isExplorerOpen) {
if (isWeb || !isExplorerOpen) {
return;
}
@@ -868,7 +857,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
persistenceKey ? (state.layoutByWorkspace[persistenceKey] ?? null) : null,
);
const workspaceSetupSnapshot = useWorkspaceSetupStore((state) =>
persistenceKey ? state.snapshots[persistenceKey] ?? null : null,
persistenceKey ? (state.snapshots[persistenceKey] ?? null) : null,
);
const uiTabs = useMemo(
() => (workspaceLayout ? collectAllTabs(workspaceLayout.root) : EMPTY_UI_TABS),
@@ -880,7 +869,9 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const unpinWorkspaceAgent = useWorkspaceLayoutStore((state) => state.unpinAgent);
const hideWorkspaceAgent = useWorkspaceLayoutStore((state) => state.hideAgent);
const retargetWorkspaceTab = useWorkspaceLayoutStore((state) => state.retargetTab);
const convertWorkspaceDraftToAgent = useWorkspaceLayoutStore((state) => state.convertDraftToAgent);
const convertWorkspaceDraftToAgent = useWorkspaceLayoutStore(
(state) => state.convertDraftToAgent,
);
const reconcileWorkspaceTabs = useWorkspaceLayoutStore((state) => state.reconcileTabs);
const splitWorkspacePane = useWorkspaceLayoutStore((state) => state.splitPane);
const splitWorkspacePaneEmpty = useWorkspaceLayoutStore((state) => state.splitPaneEmpty);
@@ -1079,8 +1070,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const hasSetupTab = useMemo(
() =>
uiTabs.some(
(tab) =>
tab.target.kind === "setup" && tab.target.workspaceId === normalizedWorkspaceId,
(tab) => tab.target.kind === "setup" && tab.target.workspaceId === normalizedWorkspaceId,
),
[normalizedWorkspaceId, uiTabs],
);
@@ -1350,7 +1340,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
if (isRunning) {
const confirmed = await confirmDialog({
title: "Archive running agent?",
message: "This agent is still running. Archiving it will stop the agent and close the tab.",
message:
"This agent is still running. Archiving it will stop the agent and close the tab.",
confirmLabel: "Archive",
cancelLabel: "Cancel",
destructive: true,
@@ -1369,7 +1360,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
});
}
void archiveAgent({ serverId: normalizedServerId, agentId });
// Errors (e.g. timeout) are handled by the mutation's onSettled callback
void archiveAgent({ serverId: normalizedServerId, agentId }).catch(() => {});
});
},
[archiveAgent, closeTab, closeWorkspaceTabWithCleanup, normalizedServerId, persistenceKey],
@@ -1803,7 +1795,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
const canRenderDesktopPaneSplits = supportsDesktopPaneSplits();
const shouldRenderDesktopPaneFallback = !isMobile && !canRenderDesktopPaneSplits;
useEffect(() => {
if (Platform.OS !== "web" || typeof document === "undefined" || activeTabDescriptor) {
if (isNative || typeof document === "undefined" || activeTabDescriptor) {
return;
}
document.title = "Workspace";
@@ -2038,7 +2030,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
return (
<View style={[styles.container, { backgroundColor: mainBackgroundColor }]}>
{Platform.OS === "web" && activeTabDescriptor ? (
{isWeb && activeTabDescriptor ? (
<WorkspaceTabPresentationResolver
tab={activeTabDescriptor}
serverId={normalizedServerId}
@@ -2377,8 +2369,8 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
</View>
</View>
{(!isFocusModeEnabled || isMobile) && (
workspaceDirectory ? (
{(!isFocusModeEnabled || isMobile) &&
(workspaceDirectory ? (
<ExplorerSidebar
serverId={normalizedServerId}
workspaceId={normalizedWorkspaceId}
@@ -2386,8 +2378,7 @@ function WorkspaceScreenContent({ serverId, workspaceId }: WorkspaceScreenProps)
isGit={isGitCheckout}
onOpenFile={handleOpenFileFromExplorer}
/>
) : null
)}
) : null)}
</View>
</View>
);
@@ -1,7 +1,14 @@
import { type ReactElement } from "react";
import { Pressable, Text, View } from "react-native";
import { useMutation } from "@tanstack/react-query";
import { ChevronDown, ExternalLink, Globe, LoaderCircle, Play, SquareTerminal } from "lucide-react-native";
import {
ChevronDown,
ExternalLink,
Globe,
LoaderCircle,
Play,
SquareTerminal,
} from "lucide-react-native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import type { WorkspaceDescriptor } from "@/stores/session-store";
import { useSessionStore } from "@/stores/session-store";
@@ -58,10 +65,9 @@ export function WorkspaceScriptsButton({
return result;
},
onError: (error, scriptName) => {
toast.show(
error instanceof Error ? error.message : `Failed to start ${scriptName}`,
{ variant: "error" },
);
toast.show(error instanceof Error ? error.message : `Failed to start ${scriptName}`, {
variant: "error",
});
},
});
@@ -88,9 +94,7 @@ export function WorkspaceScriptsButton({
<Play
size={14}
color={
hasAnyRunning
? theme.colors.palette.blue[500]
: theme.colors.foregroundMuted
hasAnyRunning ? theme.colors.palette.blue[500] : theme.colors.foregroundMuted
}
fill="transparent"
/>