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
@@ -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"
/>