diff --git a/packages/app/src/app/_layout.tsx b/packages/app/src/app/_layout.tsx
index fff114c09..b5be156b2 100644
--- a/packages/app/src/app/_layout.tsx
+++ b/packages/app/src/app/_layout.tsx
@@ -645,7 +645,7 @@ export default function RootLayout() {
options={{ gestureEnabled: false, freezeOnBlur: true }}
/>
-
+
diff --git a/packages/app/src/app/h/[serverId]/agents.tsx b/packages/app/src/app/h/[serverId]/sessions.tsx
similarity index 69%
rename from packages/app/src/app/h/[serverId]/agents.tsx
rename to packages/app/src/app/h/[serverId]/sessions.tsx
index 7772cfa71..29bc04c20 100644
--- a/packages/app/src/app/h/[serverId]/agents.tsx
+++ b/packages/app/src/app/h/[serverId]/sessions.tsx
@@ -1,9 +1,9 @@
import { useLocalSearchParams } from "expo-router";
-import { AgentsScreen } from "@/screens/agents-screen";
+import { SessionsScreen } from "@/screens/sessions-screen";
export default function HostAgentsRoute() {
const params = useLocalSearchParams<{ serverId?: string }>();
const serverId = typeof params.serverId === "string" ? params.serverId : "";
- return ;
+ return ;
}
diff --git a/packages/app/src/components/agent-list.tsx b/packages/app/src/components/agent-list.tsx
index f5d1d0738..4fdd503ea 100644
--- a/packages/app/src/components/agent-list.tsx
+++ b/packages/app/src/components/agent-list.tsx
@@ -15,7 +15,7 @@ import { formatTimeAgo } from '@/utils/time'
import { shortenPath } from '@/utils/shorten-path'
import { type AggregatedAgent } from '@/hooks/use-aggregated-agents'
import { useSessionStore } from '@/stores/session-store'
-import { AgentStatusDot } from '@/components/agent-status-dot'
+import { Archive } from 'lucide-react-native'
import { prepareWorkspaceTab } from '@/utils/workspace-navigation'
interface AgentListProps {
@@ -29,11 +29,9 @@ interface AgentListProps {
showAttentionIndicator?: boolean
}
-interface AgentListSection {
- key: string
- title: string
- data: AggregatedAgent[]
-}
+type FlatListItem =
+ | { type: 'header'; key: string; title: string }
+ | { type: 'agent'; key: string; agent: AggregatedAgent }
function deriveDateSectionLabel(lastActivityAt: Date): string {
const now = new Date()
@@ -82,9 +80,11 @@ function formatStatusLabel(status: AggregatedAgent['status']): string {
function SessionBadge({
label,
+ icon,
tone = 'neutral',
}: {
label: string
+ icon?: ReactElement
tone?: 'neutral' | 'warning' | 'danger'
}) {
return (
@@ -95,6 +95,7 @@ function SessionBadge({
tone === 'danger' && styles.badgeDanger,
]}
>
+ {icon}
void
onLongPress: (agent: AggregatedAgent) => void
}) {
+ const { theme } = useUnistyles()
const timeAgo = formatTimeAgo(agent.lastActivityAt)
const agentKey = `${agent.serverId}:${agent.id}`
const isSelected = selectedAgentId === agentKey
@@ -141,9 +143,6 @@ function SessionRow({
onLongPress={() => onLongPress(agent)}
testID={`agent-row-${agent.serverId}-${agent.id}`}
>
-
-
-
{agent.title || 'New session'}
- {agent.archivedAt ? : null}
+ {agent.archivedAt ? (
+ }
+ />
+ ) : null}
{(agent.pendingPermissionCount ?? 0) > 0 ? (
) : null}
@@ -198,47 +202,6 @@ function SessionRow({
)
}
-function SessionTableSection({
- section,
- isMobile,
- selectedAgentId,
- showAttentionIndicator,
- onAgentPress,
- onAgentLongPress,
-}: {
- section: AgentListSection
- isMobile: boolean
- selectedAgentId?: string
- showAttentionIndicator: boolean
- onAgentPress: (agent: AggregatedAgent) => void
- onAgentLongPress: (agent: AggregatedAgent) => void
-}) {
- return (
-
-
- {section.title}
-
-
-
- {section.data.map((agent, index) => (
- 0 ? styles.rowDivider : undefined}
- >
-
-
- ))}
-
-
- )
-}
export function AgentList({
agents,
@@ -276,6 +239,7 @@ export function AgentList({
serverId,
workspaceId: agent.cwd,
target: { kind: 'agent', agentId },
+ pin: Boolean(agent.archivedAt),
})
router.navigate(route as any)
},
@@ -298,7 +262,7 @@ export function AgentList({
setActionAgent(null)
}, [actionAgent, actionClient])
- const sections = useMemo((): AgentListSection[] => {
+ const flatItems = useMemo((): FlatListItem[] => {
const order = ['Today', 'Yesterday', 'This week', 'This month', 'Older'] as const
const buckets = new Map()
for (const agent of agents) {
@@ -308,41 +272,53 @@ export function AgentList({
buckets.set(label, existing)
}
- const result: AgentListSection[] = []
+ const result: FlatListItem[] = []
for (const label of order) {
const data = buckets.get(label)
if (!data || data.length === 0) {
continue
}
- result.push({ key: `date:${label}`, title: label, data })
+ result.push({ type: 'header', key: `header:${label}`, title: label })
+ for (const agent of data) {
+ result.push({ type: 'agent', key: `${agent.serverId}:${agent.id}`, agent })
+ }
}
return result
}, [agents])
- const renderSection: ListRenderItem = useCallback(
- ({ item: section }) => (
-
- ),
+ const renderItem: ListRenderItem = useCallback(
+ ({ item }) => {
+ if (item.type === 'header') {
+ return (
+
+ {item.title}
+
+ )
+ }
+ return (
+
+ )
+ },
[handleAgentLongPress, handleAgentPress, isMobile, selectedAgentId, showAttentionIndicator]
)
- const keyExtractor = useCallback((section: AgentListSection) => section.key, [])
+ const keyExtractor = useCallback((item: FlatListItem) => item.key, [])
return (
<>
({
xs: theme.spacing[3],
md: theme.spacing[6],
},
- paddingTop: theme.spacing[2],
+ paddingTop: theme.spacing[4],
paddingBottom: theme.spacing[6],
gap: theme.spacing[1],
},
- sectionBlock: {
- marginTop: theme.spacing[2],
- },
sectionHeading: {
+ marginTop: theme.spacing[2],
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing[3],
- paddingHorizontal: theme.spacing[1],
+ paddingHorizontal: theme.spacing[3],
marginBottom: theme.spacing[2],
},
sectionTitle: {
@@ -436,23 +410,6 @@ const styles = StyleSheet.create((theme) => ({
fontWeight: theme.fontWeight.medium,
color: theme.colors.foregroundMuted,
},
- listCard: {
- overflow: {
- xs: 'hidden' as const,
- md: 'visible' as const,
- },
- borderRadius: {
- xs: theme.borderRadius.lg,
- md: 0,
- },
- },
- rowDivider: {
- borderTopWidth: {
- xs: StyleSheet.hairlineWidth,
- md: 0,
- },
- borderTopColor: theme.colors.border,
- },
row: {
flexDirection: 'row',
alignItems: 'center',
@@ -467,9 +424,6 @@ const styles = StyleSheet.create((theme) => ({
md: 0,
},
},
- rowLeading: {
- marginRight: theme.spacing[3],
- },
rowContent: {
flex: 1,
minWidth: 0,
@@ -502,7 +456,7 @@ const styles = StyleSheet.create((theme) => ({
sessionTitle: {
flexShrink: 1,
fontSize: theme.fontSize.sm,
- fontWeight: '500',
+ fontWeight: '400',
color: theme.colors.foreground,
opacity: 0.86,
},
@@ -535,6 +489,9 @@ const styles = StyleSheet.create((theme) => ({
textAlign: 'right' as const,
},
badge: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: theme.spacing[1],
paddingHorizontal: theme.spacing[2],
paddingVertical: theme.spacing[1],
borderRadius: theme.borderRadius.full,
diff --git a/packages/app/src/components/left-sidebar.tsx b/packages/app/src/components/left-sidebar.tsx
index fde97c700..20eb2e3e8 100644
--- a/packages/app/src/components/left-sidebar.tsx
+++ b/packages/app/src/components/left-sidebar.tsx
@@ -37,7 +37,7 @@ import { getHostRuntimeStore, useHosts } from '@/runtime/host-runtime'
import { formatConnectionStatus } from '@/utils/daemons'
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from '@/constants/layout'
import {
- buildHostAgentsRoute,
+ buildHostSessionsRoute,
buildHostSettingsRoute,
mapPathnameToServer,
parseServerIdFromPathname,
@@ -221,7 +221,7 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
if (!activeServerId) {
return
}
- router.push(buildHostAgentsRoute(activeServerId) as any)
+ router.push(buildHostSessionsRoute(activeServerId) as any)
}, [activeServerId])
const handleHostSelect = useCallback(
@@ -283,6 +283,41 @@ export const LeftSidebar = memo(function LeftSidebar({ selectedAgentId: _selecte
)
})
+function SessionsButton({ onPress }: { onPress: () => void }) {
+ const { theme } = useUnistyles()
+ const pathname = usePathname()
+ const isActive = pathname.includes('/sessions')
+
+ return (
+ [
+ styles.newAgentButton,
+ hovered && styles.newAgentButtonHovered,
+ isActive && styles.newAgentButtonActive,
+ ]}
+ testID="sidebar-sessions"
+ accessible
+ accessibilityRole="button"
+ accessibilityLabel="Sessions"
+ onPress={onPress}
+ >
+ {({ hovered }) => (
+ <>
+
+
+ Sessions
+
+ >
+ )}
+
+ )
+}
+
function MobileSidebar({
theme,
activeServerId,
@@ -457,28 +492,7 @@ function MobileSidebar({
-
- {({ hovered }) => (
- <>
-
-
- Sessions
-
- >
- )}
-
+
@@ -623,28 +637,7 @@ function DesktopSidebar({
) : null}
-
- {({ hovered }) => (
- <>
-
-
- Sessions
-
- >
- )}
-
+
@@ -786,15 +779,14 @@ const styles = StyleSheet.create((theme) => ({
gap: theme.spacing[2],
},
newAgentButton: {
+ flex: 1,
flexDirection: 'row',
alignItems: 'center',
gap: theme.spacing[2],
- paddingVertical: theme.spacing[1],
- paddingRight: theme.spacing[1],
- paddingLeft: theme.spacing[3],
- flexShrink: 0,
+ paddingVertical: theme.spacing[2],
+ paddingHorizontal: theme.spacing[3],
+ borderRadius: theme.borderRadius.lg,
},
- newAgentButtonHovered: {},
newAgentButtonText: {
fontSize: theme.fontSize.base,
fontWeight: theme.fontWeight.normal,
@@ -803,6 +795,12 @@ const styles = StyleSheet.create((theme) => ({
newAgentButtonTextHovered: {
color: theme.colors.foreground,
},
+ newAgentButtonHovered: {
+ backgroundColor: theme.colors.surface1,
+ },
+ newAgentButtonActive: {
+ backgroundColor: theme.colors.surface1,
+ },
hostTrigger: {
flexDirection: 'row',
alignItems: 'center',
diff --git a/packages/app/src/hooks/use-aggregated-agents.ts b/packages/app/src/hooks/use-aggregated-agents.ts
index 176fee769..f9a6af058 100644
--- a/packages/app/src/hooks/use-aggregated-agents.ts
+++ b/packages/app/src/hooks/use-aggregated-agents.ts
@@ -74,6 +74,7 @@ export function useAggregatedAgents(options?: {
attentionReason: agent.attentionReason,
attentionTimestamp: agent.attentionTimestamp,
archivedAt: agent.archivedAt,
+ createdAt: agent.createdAt,
labels: agent.labels,
};
allAgents.push(nextAgent);
diff --git a/packages/app/src/hooks/use-all-agents-list.ts b/packages/app/src/hooks/use-all-agents-list.ts
index 77cd21fee..1dda484dd 100644
--- a/packages/app/src/hooks/use-all-agents-list.ts
+++ b/packages/app/src/hooks/use-all-agents-list.ts
@@ -31,6 +31,7 @@ function toAggregatedAgent(params: {
attentionReason: source.attentionReason,
attentionTimestamp: source.attentionTimestamp ?? null,
archivedAt: source.archivedAt ?? null,
+ createdAt: source.createdAt,
labels: source.labels,
};
}
diff --git a/packages/app/src/screens/agents-screen.tsx b/packages/app/src/screens/sessions-screen.tsx
similarity index 62%
rename from packages/app/src/screens/agents-screen.tsx
rename to packages/app/src/screens/sessions-screen.tsx
index 0cb90d731..8610b0b1b 100644
--- a/packages/app/src/screens/agents-screen.tsx
+++ b/packages/app/src/screens/sessions-screen.tsx
@@ -2,23 +2,21 @@ import { useMemo, useState, useCallback, useEffect } from "react";
import { View } from "react-native";
import { useIsFocused } from "@react-navigation/native";
import { StyleSheet } from "react-native-unistyles";
-import { BackHeader } from "@/components/headers/back-header";
+import { MenuHeader } from "@/components/headers/menu-header";
import { AgentList } from "@/components/agent-list";
import { useAllAgentsList } from "@/hooks/use-all-agents-list";
-import { router } from "expo-router";
-import { buildHostRootRoute } from "@/utils/host-routes";
-export function AgentsScreen({ serverId }: { serverId: string }) {
+export function SessionsScreen({ serverId }: { serverId: string }) {
const isFocused = useIsFocused();
if (!isFocused) {
return ;
}
- return ;
+ return ;
}
-function AgentsScreenContent({ serverId }: { serverId: string }) {
+function SessionsScreenContent({ serverId }: { serverId: string }) {
const { agents, isRevalidating, refreshAll } = useAllAgentsList({
serverId,
includeArchived: true,
@@ -40,24 +38,14 @@ function AgentsScreenContent({ serverId }: { serverId: string }) {
}, [isRevalidating, isManualRefresh]);
const sortedAgents = useMemo(() => {
- return [...agents].sort((a, b) => {
- const aNeedsInput = (a.pendingPermissionCount ?? 0) > 0;
- const bNeedsInput = (b.pendingPermissionCount ?? 0) > 0;
- if (aNeedsInput && !bNeedsInput) return -1;
- if (!aNeedsInput && bNeedsInput) return 1;
-
- if (a.requiresAttention && !b.requiresAttention) return -1;
- if (!a.requiresAttention && b.requiresAttention) return 1;
- return 0;
- });
+ return [...agents].sort(
+ (a, b) => b.createdAt.getTime() - a.createdAt.getTime()
+ );
}, [agents]);
return (
- router.replace(buildHostRootRoute(serverId) as any)}
- />
+
()(
requiresAttention: agent.requiresAttention ?? false,
attentionReason: agent.attentionReason ?? null,
attentionTimestamp: agent.attentionTimestamp ?? null,
+ createdAt: agent.createdAt,
labels: agent.labels,
});
}
diff --git a/packages/app/src/types/agent-directory.ts b/packages/app/src/types/agent-directory.ts
index 18bd68c56..b7d9be9b3 100644
--- a/packages/app/src/types/agent-directory.ts
+++ b/packages/app/src/types/agent-directory.ts
@@ -13,6 +13,7 @@ export type AgentDirectoryEntry = Pick<
| "attentionReason"
| "attentionTimestamp"
| "archivedAt"
+ | "createdAt"
| "labels"
> & {
pendingPermissionCount?: number;
diff --git a/packages/app/src/utils/host-routes.ts b/packages/app/src/utils/host-routes.ts
index 902f7ac4f..8f7ccb452 100644
--- a/packages/app/src/utils/host-routes.ts
+++ b/packages/app/src/utils/host-routes.ts
@@ -323,12 +323,12 @@ export function buildHostRootRoute(serverId: string): string {
return `/h/${encodeSegment(normalized)}`;
}
-export function buildHostAgentsRoute(serverId: string): string {
+export function buildHostSessionsRoute(serverId: string): string {
const base = buildHostRootRoute(serverId);
if (base === "/") {
return "/";
}
- return `${base}/agents`;
+ return `${base}/sessions`;
}
export function buildHostOpenProjectRoute(serverId: string): string {
@@ -361,8 +361,8 @@ export function mapPathnameToServer(
if (suffix.startsWith("settings")) {
return `${base}/settings`;
}
- if (suffix.startsWith("agents")) {
- return `${base}/agents`;
+ if (suffix.startsWith("sessions")) {
+ return `${base}/sessions`;
}
if (suffix.startsWith("open-project")) {
return `${base}/open-project`;