diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx
index 15d919a17..a8e779d75 100644
--- a/packages/app/src/components/agent-list.tsx
+++ b/packages/app/src/components/agent-list.tsx
@@ -10,7 +10,7 @@ import {
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useCallback, useMemo, useState, type ReactElement } from "react";
-import { router, usePathname } from "expo-router";
+import { router, usePathname, type Href } from "expo-router";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useQueryClient } from "@tanstack/react-query";
import { formatTimeAgo } from "@/utils/time";
@@ -33,7 +33,7 @@ import {
startNavigationTiming,
} from "@/utils/navigation-timing";
import {
- buildHostAgentDetailRoute,
+ buildHostWorkspaceAgentTabRoute,
} from "@/utils/host-routes";
interface AgentListProps {
@@ -189,7 +189,12 @@ export function AgentList({
onAgentSelect?.();
- navigate(buildHostAgentDetailRoute(serverId, agentId, agent.cwd) as any);
+ const route: Href = buildHostWorkspaceAgentTabRoute(
+ serverId,
+ agent.cwd,
+ agentId
+ ) as Href;
+ navigate(route);
},
[isActionSheetVisible, pathname, onAgentSelect]
);
diff --git a/packages/app/src/components/sidebar-workspace-list.tsx b/packages/app/src/components/sidebar-workspace-list.tsx
index ee0dd4acc..1691f8625 100644
--- a/packages/app/src/components/sidebar-workspace-list.tsx
+++ b/packages/app/src/components/sidebar-workspace-list.tsx
@@ -8,7 +8,6 @@ import {
Alert,
StatusBar,
ScrollView,
- RefreshControl,
type GestureResponderEvent,
} from 'react-native'
import { useQueries } from '@tanstack/react-query'
@@ -55,7 +54,6 @@ import { useCheckoutGitActionsStore } from '@/stores/checkout-git-actions-store'
import { buildSidebarShortcutModel } from '@/utils/sidebar-shortcuts'
import { hasVisibleOrderChanged, mergeWithRemainder } from '@/utils/sidebar-reorder'
import {
- decideLongPressMove,
shouldOpenContextMenuOnPressOut,
} from '@/utils/sidebar-gesture-arbitration'
@@ -276,10 +274,9 @@ function useLongPressDragInteraction(input: {
}
touchStartRef.current = { x: touch.absoluteX, y: touch.absoluteY }
})
- .onTouchesMove((event, stateManager) => {
+ .onTouchesMove((event) => {
const touch = event.changedTouches[0]
if (!touch || event.numberOfTouches !== 1) {
- stateManager.fail()
return
}
@@ -289,28 +286,27 @@ function useLongPressDragInteraction(input: {
return
}
- const decision = decideLongPressMove({
- longPressArmed: longPressArmedRef.current,
- didStartDrag: didStartDragRef.current,
- startPoint: start,
- currentPoint: { x: touch.absoluteX, y: touch.absoluteY },
- cancelSlopPx: CANCEL_SLOP_PX,
- dragSlopPx: DRAG_SLOP_PX,
- })
-
- if (decision === 'cancel_long_press') {
- longPressCancelledRef.current = true
- stateManager.fail()
+ if (didStartDragRef.current) {
return
}
- if (decision === 'start_drag') {
+ const dx = touch.absoluteX - start.x
+ const dy = touch.absoluteY - start.y
+ const distance = Math.sqrt(dx * dx + dy * dy)
+
+ if (!longPressArmedRef.current) {
+ if (distance > CANCEL_SLOP_PX) {
+ longPressCancelledRef.current = true
+ }
+ return
+ }
+
+ if (distance > DRAG_SLOP_PX) {
didStartDragRef.current = true
input.drag()
- stateManager.fail()
}
})
- }, [input])
+ }, [input.drag])
return {
didLongPressRef,
@@ -1038,10 +1034,6 @@ export function SidebarWorkspaceList({
]
)
- const refreshControl = onRefresh ? (
-
- ) : undefined
-
const content = (
<>
{projects.length === 0 ? (
@@ -1070,7 +1062,6 @@ export function SidebarWorkspaceList({
style={styles.list}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
- refreshControl={refreshControl}
testID="sidebar-project-workspace-list-scroll"
>
{content}
@@ -1080,7 +1071,6 @@ export function SidebarWorkspaceList({
style={styles.list}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
- refreshControl={refreshControl}
testID="sidebar-project-workspace-list-scroll"
>
{content}
diff --git a/packages/app/src/hooks/use-command-center.ts b/packages/app/src/hooks/use-command-center.ts
index 47a0394b7..8c80a80f6 100644
--- a/packages/app/src/hooks/use-command-center.ts
+++ b/packages/app/src/hooks/use-command-center.ts
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { TextInput } from "react-native";
-import { router, usePathname } from "expo-router";
+import { router, usePathname, type Href } from "expo-router";
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
import { useAggregatedAgents, type AggregatedAgent } from "@/hooks/use-aggregated-agents";
import { useSessionStore } from "@/stores/session-store";
@@ -75,7 +75,7 @@ type CommandCenterActionDefinition = {
icon?: "plus" | "settings";
shortcutKeys?: ShortcutKey[];
keywords: string[];
- buildRoute: (params: { newAgentRoute: string; settingsRoute: string }) => string;
+ buildRoute: (params: { newAgentRoute: Href; settingsRoute: Href }) => Href;
};
const COMMAND_CENTER_ACTIONS: readonly CommandCenterActionDefinition[] = [
@@ -113,7 +113,7 @@ export type CommandCenterActionItem = {
id: string;
title: string;
icon?: "plus" | "settings";
- route: string;
+ route: Href;
shortcutKeys?: ShortcutKey[];
};
@@ -154,19 +154,19 @@ export function useCommandCenter() {
[pathname]
);
- const newAgentRoute = useMemo(() => {
+ const newAgentRoute = useMemo(() => {
const serverIdFromPath =
parseServerIdFromPathname(pathname) ?? fallbackServerId;
const routeAgent = parseAgentRouteFromPathname(pathname);
if (!routeAgent) {
- return serverIdFromPath ? buildNewAgentRoute(serverIdFromPath) : "/";
+ return serverIdFromPath ? (buildNewAgentRoute(serverIdFromPath) as Href) : "/";
}
const { serverId, agentId } = routeAgent;
const currentAgent = useSessionStore.getState().sessions[serverId]?.agents?.get(agentId);
const cwd = currentAgent?.cwd?.trim();
if (!cwd) {
- return buildNewAgentRoute(serverId);
+ return buildNewAgentRoute(serverId) as Href;
}
const checkout =
@@ -174,13 +174,13 @@ export function useCommandCenter() {
checkoutStatusQueryKey(serverId, cwd)
) ?? null;
const workingDir = resolveNewAgentWorkingDir(cwd, checkout);
- return buildNewAgentRoute(serverId, workingDir);
+ return buildNewAgentRoute(serverId, workingDir) as Href;
}, [fallbackServerId, pathname]);
- const settingsRoute = useMemo(() => {
+ const settingsRoute = useMemo(() => {
const serverIdFromPath =
parseServerIdFromPathname(pathname) ?? fallbackServerId;
- return serverIdFromPath ? buildHostSettingsRoute(serverIdFromPath) : "/";
+ return serverIdFromPath ? (buildHostSettingsRoute(serverIdFromPath) as Href) : "/";
}, [fallbackServerId, pathname]);
const actionItems = useMemo(() => {
@@ -230,9 +230,12 @@ export function useCommandCenter() {
// Don't restore focus back to the prior element after we navigate.
clearCommandCenterFocusRestoreElement();
setOpen(false);
- navigate(
- buildHostWorkspaceAgentTabRoute(agent.serverId, agent.cwd, agent.id) as any
- );
+ const route: Href = buildHostWorkspaceAgentTabRoute(
+ agent.serverId,
+ agent.cwd,
+ agent.id
+ ) as Href;
+ navigate(route);
},
[pathname, requestMessageInputAction, setOpen]
);
@@ -241,7 +244,7 @@ export function useCommandCenter() {
didNavigateRef.current = true;
clearCommandCenterFocusRestoreElement();
setOpen(false);
- router.push(action.route as any);
+ router.push(action.route);
}, [setOpen]);
const handleSelectItem = useCallback(
diff --git a/packages/app/src/screens/agent/agent-ready-screen.tsx b/packages/app/src/screens/agent/agent-ready-screen.tsx
index 463477999..363ecacfd 100644
--- a/packages/app/src/screens/agent/agent-ready-screen.tsx
+++ b/packages/app/src/screens/agent/agent-ready-screen.tsx
@@ -3,30 +3,16 @@ import {
View,
Text,
ActivityIndicator,
- Pressable,
- ScrollView,
Platform,
BackHandler,
} from "react-native";
-import { useRouter } from "expo-router";
-import * as Clipboard from "expo-clipboard";
+import { useRouter, type Href } from "expo-router";
import { useFocusEffect } from "@react-navigation/native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useQueryClient } from "@tanstack/react-query";
import ReanimatedAnimated from "react-native-reanimated";
import { GestureDetector } from "react-native-gesture-handler";
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
-import {
- MoreVertical,
- GitBranch,
- Folder,
- RotateCcw,
- PanelRight,
- CheckCircle2,
-} from "lucide-react-native";
-import { MenuHeader } from "@/components/headers/menu-header";
-import { BackHeader } from "@/components/headers/back-header";
-import { HeaderToggleButton } from "@/components/headers/header-toggle-button";
import { AgentStreamView, type AgentStreamViewHandle } from "@/components/agent-stream-view";
import { AgentInputArea } from "@/components/agent-input-area";
import { ExplorerSidebar } from "@/components/explorer-sidebar";
@@ -48,13 +34,8 @@ import type { StreamItem } from "@/types/stream";
import {
buildAgentNavigationKey,
endNavigationTiming,
- HOME_NAVIGATION_KEY,
- startNavigationTiming,
} from "@/utils/navigation-timing";
-import { extractAgentModel } from "@/utils/extract-agent-model";
import { startPerfMonitor } from "@/utils/perf-monitor";
-import { shortenPath } from "@/utils/shorten-path";
-import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
import {
checkoutStatusQueryKey,
type CheckoutStatusPayload,
@@ -78,17 +59,9 @@ import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
import { shouldClearAgentAttentionOnView } from "@/utils/agent-attention";
import type { DaemonClient } from "@server/client/daemon-client";
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu";
import { buildHostAgentDraftRoute } from "@/utils/host-routes";
import type { ExplorerCheckoutContext } from "@/stores/panel-store";
-const DROPDOWN_WIDTH = 220;
const EMPTY_STREAM_ITEMS: StreamItem[] = [];
const IS_DEV = Boolean((globalThis as { __DEV__?: boolean }).__DEV__);
@@ -102,17 +75,14 @@ function logAgentExplorer(event: string, details: Record): void
export function AgentReadyScreen({
serverId,
agentId,
- showHeader = true,
showExplorerSidebar = true,
wrapWithExplorerSidebarProvider = true,
}: {
serverId: string;
agentId: string;
- showHeader?: boolean;
showExplorerSidebar?: boolean;
wrapWithExplorerSidebarProvider?: boolean;
}) {
- const router = useRouter();
const resolvedAgentId = agentId?.trim() || undefined;
const resolvedServerId = serverId?.trim() || undefined;
const { daemons } = useDaemonRegistry();
@@ -136,33 +106,6 @@ export function AgentReadyScreen({
const lastConnectionError = runtimeSnapshot?.lastError ?? null;
const isRuntimeSessionAvailable = Boolean(resolvedServerId && runtimeClient);
- const handleBackToHome = useCallback(() => {
- const targetServerId = resolvedServerId;
- const targetAgentId = resolvedAgentId ?? null;
- if (targetServerId && targetAgentId) {
- startNavigationTiming(HOME_NAVIGATION_KEY, {
- from: "agent",
- to: "home",
- targetMs: 300,
- params: {
- serverId: targetServerId,
- agentId: targetAgentId,
- },
- });
- } else {
- startNavigationTiming(HOME_NAVIGATION_KEY, {
- from: "agent",
- to: "home",
- targetMs: 300,
- });
- }
- if (targetServerId) {
- router.replace(buildHostAgentDraftRoute(targetServerId) as any);
- return;
- }
- router.replace("/" as any);
- }, [resolvedAgentId, resolvedServerId, router]);
-
const focusServerId = resolvedServerId;
const navigationStatus = isRuntimeSessionAvailable
? "ready"
@@ -187,7 +130,6 @@ export function AgentReadyScreen({
if (!resolvedServerId || !runtimeClient) {
return (
);
@@ -221,7 +162,6 @@ type AgentScreenContentProps = {
client: DaemonClient;
isConnected: boolean;
connectionStatus: HostRuntimeConnectionStatus;
- showHeader: boolean;
showExplorerSidebar: boolean;
};
@@ -242,7 +182,6 @@ function AgentScreenContent({
client,
isConnected,
connectionStatus,
- showHeader,
showExplorerSidebar,
}: AgentScreenContentProps) {
const { theme } = useUnistyles();
@@ -269,7 +208,6 @@ function AgentScreenContent({
const mobileView = usePanelStore((state) => state.mobileView);
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen);
- const toggleFileExplorer = usePanelStore((state) => state.toggleFileExplorer);
const openFileExplorer = usePanelStore((state) => state.openFileExplorer);
const closeToAgent = usePanelStore((state) => state.closeToAgent);
const setActiveExplorerCheckout = usePanelStore((state) => state.setActiveExplorerCheckout);
@@ -348,19 +286,6 @@ function AgentScreenContent({
}
openFileExplorer();
}, [activateExplorerTabForCheckout, openFileExplorer, resolveCurrentExplorerCheckout]);
- const handleToggleExplorer = useCallback(() => {
- logAgentExplorer("handleToggleExplorer", {
- isExplorerOpen,
- mobileView,
- isMobile,
- });
- if (isExplorerOpen) {
- toggleFileExplorer();
- return;
- }
- openExplorerForActiveCheckout();
- }, [isExplorerOpen, isMobile, mobileView, openExplorerForActiveCheckout, toggleFileExplorer]);
-
useEffect(() => {
if (Platform.OS !== "web") {
return;
@@ -501,7 +426,7 @@ function AgentScreenContent({
const focusedAgentId = useSessionStore(
(state) => state.sessions[serverId]?.focusedAgentId ?? null
);
- const { ensureAgentIsInitialized, refreshAgent } = useAgentInitialization({
+ const { ensureAgentIsInitialized } = useAgentInitialization({
serverId,
client: hasSession ? client : null,
});
@@ -604,7 +529,8 @@ function AgentScreenContent({
return;
}
hasRedirectedArchivedAgentRef.current = true;
- router.replace(buildHostAgentDraftRoute(serverId) as any);
+ const route: Href = buildHostAgentDraftRoute(serverId) as Href;
+ router.replace(route);
}, [agent?.archivedAt, resolvedAgentId, router, serverId]);
useEffect(() => {
@@ -730,19 +656,6 @@ function AgentScreenContent({
});
const effectiveAgent = viewState.tag === "ready" ? viewState.agent : null;
- const agentModel = extractAgentModel(effectiveAgent ?? agent);
- const modelDisplayValue = agentModel ?? "Unknown";
- const providerLabel = (effectiveAgent?.provider ?? "Provider").replace(
- /^\w/,
- (m) => m.toUpperCase()
- );
- const providerSessionId =
- effectiveAgent?.runtimeInfo?.sessionId ?? effectiveAgent?.persistence?.sessionId ?? null;
-
- // Header subtitle: project path + branch (matching agent list row format)
- const headerProjectPath = effectiveAgent
- ? deriveProjectPath(effectiveAgent.cwd, checkout)
- : null;
useEffect(() => {
if (!isPendingCreateForRoute || !pendingCreate) {
return;
@@ -952,33 +865,6 @@ function AgentScreenContent({
resolvedAgentId,
]);
- const handleRefreshAgent = useCallback(() => {
- if (!resolvedAgentId || !hasSession) {
- return;
- }
- void refreshAgent(resolvedAgentId).catch((error) => {
- console.warn("[AgentScreen] refreshAgent failed", { agentId: resolvedAgentId, error });
- });
- }, [hasSession, refreshAgent, resolvedAgentId]);
-
- const handleCopyMeta = useCallback(
- async (label: string, value: string | null | undefined) => {
- if (!value) {
- return;
- }
- try {
- await Clipboard.setStringAsync(value);
- toast.show(`Copied ${label}`, {
- variant: "success",
- icon: ,
- });
- } catch {
- toast.error("Copy failed");
- }
- },
- [theme.colors.primary, toast]
- );
-
const isHistoryRefreshCatchingUp =
viewState.tag === "ready" &&
viewState.sync.status === "catching_up" &&
@@ -1003,7 +889,6 @@ function AgentScreenContent({
if (viewState.tag === "not_found") {
return (
- {showHeader ? : null}
Agent not found
@@ -1014,7 +899,6 @@ function AgentScreenContent({
if (viewState.tag === "error") {
return (
- {showHeader ? : null}
Failed to load agent
{viewState.message}
@@ -1026,7 +910,6 @@ function AgentScreenContent({
if (viewState.tag === "boot" || !effectiveAgent) {
return (
- {showHeader ? : null}
@@ -1041,210 +924,6 @@ function AgentScreenContent({
disabled={isArchivingCurrentAgent}
>
- {/* Header */}
- {showHeader ? (
-
-
- {isMobile ? (
- checkout?.isGit ? (
-
- ) : (
-
- )
- ) : (
-
- )}
-
- {
- if (open && agent?.cwd) {
- checkoutStatusQuery.refresh().catch(() => {});
- }
- }}
- >
-
-
-
-
-
- [
- styles.menuMetaRow,
- (hovered || pressed) && styles.menuMetaRowActive,
- ]}
- onPress={() => {
- void handleCopyMeta("Directory", effectiveAgent.cwd);
- }}
- >
-
- Directory
-
-
- {shortenPath(effectiveAgent.cwd)}
-
-
-
- [
- styles.menuMetaRow,
- (hovered || pressed) && styles.menuMetaRowActive,
- ]}
- onPress={() => {
- void handleCopyMeta("Model", modelDisplayValue);
- }}
- >
-
- Model
-
-
- {modelDisplayValue}
-
-
-
- {checkout?.isGit && checkout.currentBranch && checkout.currentBranch !== "HEAD" ? (
- [
- styles.menuMetaRow,
- (hovered || pressed) && styles.menuMetaRowActive,
- ]}
- onPress={() => {
- if (checkoutStatusQuery.isFetching) {
- return;
- }
- void handleCopyMeta("Branch", checkout.currentBranch);
- }}
- >
-
- Branch
-
-
- {checkoutStatusQuery.isFetching ? "Fetching…" : checkout.currentBranch}
-
-
- ) : null}
-
- [
- styles.menuMetaRow,
- (hovered || pressed) && styles.menuMetaRowActive,
- ]}
- onPress={() => {
- void handleCopyMeta("Paseo ID", effectiveAgent.id);
- }}
- >
-
- Paseo ID
-
-
- {effectiveAgent.id}
-
-
-
- [
- styles.menuMetaRow,
- providerSessionId && (hovered || pressed) && styles.menuMetaRowActive,
- ]}
- disabled={!providerSessionId}
- onPress={() => {
- void handleCopyMeta(`${providerLabel} ID`, providerSessionId);
- }}
- >
-
- {providerLabel} ID
-
-
- {providerSessionId ?? "Not available"}
-
-
-
-
-
-
- }
- disabled={isInitializing}
- trailing={
- isInitializing ? (
-
- ) : null
- }
- onSelect={handleRefreshAgent}
- >
- {isInitializing ? "Refreshing..." : "Refresh"}
-
-
-
-
- }
- />
- ) : null}
-
{/* Content Area with Keyboard Animation */}
void;
serverLabel: string;
connectionStatus: HostRuntimeConnectionStatus;
lastError: string | null;
@@ -1347,7 +1024,6 @@ function AgentSessionUnavailableState({
if (isUnknownDaemon) {
return (
-
Cannot open this agent because {serverLabel} is not configured on
@@ -1367,7 +1043,6 @@ function AgentSessionUnavailableState({
return (
-
{isConnecting || isPreparingSession ? (
<>
@@ -1411,10 +1086,6 @@ const styles = StyleSheet.create((theme) => ({
flex: 1,
backgroundColor: theme.colors.surface0,
},
- headerRightContent: {
- flexDirection: "row",
- alignItems: "center",
- },
contentContainer: {
flex: 1,
overflow: "hidden",
@@ -1506,40 +1177,4 @@ const styles = StyleSheet.create((theme) => ({
color: theme.colors.foregroundMuted,
textAlign: "center",
},
- menuButton: {
- padding: theme.spacing[3],
- borderRadius: theme.borderRadius.lg,
- },
- menuMetaContainer: {
- paddingVertical: theme.spacing[1],
- },
- menuMetaRow: {
- minHeight: 32,
- paddingHorizontal: theme.spacing[3],
- flexDirection: "row",
- alignItems: "center",
- justifyContent: "space-between",
- gap: theme.spacing[3],
- },
- menuMetaRowActive: {
- backgroundColor: theme.colors.surface2,
- },
- menuMetaLabel: {
- fontSize: theme.fontSize.sm,
- color: theme.colors.foregroundMuted,
- flexShrink: 0,
- },
- menuMetaValue: {
- fontSize: theme.fontSize.sm,
- color: theme.colors.foreground,
- flex: 1,
- minWidth: 0,
- textAlign: "right",
- },
- menuMetaValueError: {
- color: theme.colors.destructive,
- },
- menuItemSpinner: {
- marginLeft: "auto",
- },
}));
diff --git a/packages/app/src/screens/agent/draft-agent-screen.tsx b/packages/app/src/screens/agent/draft-agent-screen.tsx
index a96415166..53bd8fa72 100644
--- a/packages/app/src/screens/agent/draft-agent-screen.tsx
+++ b/packages/app/src/screens/agent/draft-agent-screen.tsx
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState, useSyncE
import { createNameId } from 'mnemonic-id'
import type { ImageAttachment } from '@/components/message-input'
import { View, Text, Pressable, ScrollView, Keyboard, Platform } from 'react-native'
-import { useLocalSearchParams, useRouter } from 'expo-router'
+import { useLocalSearchParams, useRouter, type Href } from 'expo-router'
import { useIsFocused } from '@react-navigation/native'
import { StyleSheet, UnistylesRuntime, useUnistyles } from 'react-native-unistyles'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
@@ -1037,9 +1037,12 @@ function DraftAgentScreenContent({
const configuredWorkingDir = config.cwd.trim()
const workspaceId =
createdWorkingDir.length > 0 ? createdWorkingDir : configuredWorkingDir
- router.replace(
- buildHostWorkspaceAgentTabRoute(selectedServerId, workspaceId, agentId) as any
- )
+ const route: Href = buildHostWorkspaceAgentTabRoute(
+ selectedServerId,
+ workspaceId,
+ agentId
+ ) as Href
+ router.replace(route)
return
}
diff --git a/packages/app/src/screens/workspace/workspace-screen.tsx b/packages/app/src/screens/workspace/workspace-screen.tsx
index 76ed6b16b..b91dc9318 100644
--- a/packages/app/src/screens/workspace/workspace-screen.tsx
+++ b/packages/app/src/screens/workspace/workspace-screen.tsx
@@ -1265,11 +1265,10 @@ function WorkspaceScreenContent({
}
if (target.kind === "agent") {
- return (
+ return (
diff --git a/packages/app/src/utils/notification-routing.test.ts b/packages/app/src/utils/notification-routing.test.ts
index b4f36d6ee..d6fefc80e 100644
--- a/packages/app/src/utils/notification-routing.test.ts
+++ b/packages/app/src/utils/notification-routing.test.ts
@@ -15,6 +15,7 @@ describe("resolveNotificationTarget", () => {
).toEqual({
serverId: "server-123",
agentId: "agent-456",
+ workspaceId: null,
});
});
@@ -22,15 +23,27 @@ describe("resolveNotificationTarget", () => {
expect(resolveNotificationTarget({ serverId: "", agentId: " " })).toEqual({
serverId: null,
agentId: null,
+ workspaceId: null,
});
expect(resolveNotificationTarget(undefined)).toEqual({
serverId: null,
agentId: null,
+ workspaceId: null,
});
});
});
describe("buildNotificationRoute", () => {
+ it("routes directly to workspace tab when workspace id is present", () => {
+ expect(
+ buildNotificationRoute({
+ serverId: "srv-1",
+ agentId: "agent-1",
+ workspaceId: "/tmp/repo",
+ })
+ ).toBe("/h/srv-1/workspace/L3RtcC9yZXBv/tab/agent_agent-1");
+ });
+
it("routes directly to server-scoped agent path when both ids are present", () => {
expect(buildNotificationRoute({ serverId: "srv-1", agentId: "agent-1" })).toBe(
"/h/srv-1/agent/agent-1"
diff --git a/packages/app/src/utils/notification-routing.ts b/packages/app/src/utils/notification-routing.ts
index 055bb12ee..008a2c918 100644
--- a/packages/app/src/utils/notification-routing.ts
+++ b/packages/app/src/utils/notification-routing.ts
@@ -1,6 +1,7 @@
import {
buildHostDraftRoute,
buildHostAgentDetailRoute,
+ buildHostWorkspaceAgentTabRoute,
} from "@/utils/host-routes";
type NotificationData = Record | null | undefined;
@@ -20,16 +21,23 @@ function readNonEmptyString(
export function resolveNotificationTarget(data: NotificationData): {
serverId: string | null;
agentId: string | null;
+ workspaceId: string | null;
} {
return {
serverId: readNonEmptyString(data, "serverId"),
agentId: readNonEmptyString(data, "agentId"),
+ workspaceId:
+ readNonEmptyString(data, "workspaceId") ??
+ readNonEmptyString(data, "cwd"),
};
}
export function buildNotificationRoute(data: NotificationData): string {
- const { serverId, agentId } = resolveNotificationTarget(data);
+ const { serverId, agentId, workspaceId } = resolveNotificationTarget(data);
if (serverId && agentId) {
+ if (workspaceId) {
+ return buildHostWorkspaceAgentTabRoute(serverId, workspaceId, agentId);
+ }
return buildHostAgentDetailRoute(serverId, agentId);
}
if (serverId) {