Files
orca/mobile/src/components/WorktreeAgentRow.tsx
T
Brennan Benson ec9a0af3e8 Show phone subagents and open the correct parent chat (#28686)
The phone's worktree list now shows the running subagents the desktop sidebar shows, under their parent agent with their own ages, read from the host's existing child records. Tapping one opens its parent's existing chat or terminal. The feature was reviewed in #26423 and merged into this branch.

Links from the phone to native chats whose saved tab ID differs from the session-derived one (restored, reserved and headless chats) now resolve to the chat's real tab instead of missing it.
2026-10-15 21:21:05 -07:00

116 lines
3.4 KiB
TypeScript

import { memo } from 'react'
import { Pressable, StyleSheet, Text, View } from 'react-native'
import type { RuntimeWorktreeAgentRow } from '../../../src/shared/runtime-types'
import { withoutNativeChatVisualDirectiveLines } from '../../../src/shared/native-chat-visual-directive'
import { colors, spacing } from '../theme/mobile-theme'
import {
agentDisplayLabel,
agentDotState,
agentRowTimeAt,
formatTimeAgo
} from '../worktree/agent-row-display'
import { AgentStateDot } from './AgentStateDot'
import { MobileAgentIcon } from './MobileAgentIcon'
import type { AgentChildRowModel } from '../../../src/shared/agent-child-row-model'
import { mobileAgentChildRowText } from '../session/mobile-agent-child-row-text'
const INDENT_PER_DEPTH = 14
type Props = {
agent: RuntimeWorktreeAgentRow
depth: number
now: number
// Bold/foreground until the user has visited the worktree, mirroring desktop's
// unvisited rule (the workspace title and its agent rows share one signal).
unvisited: boolean
childRow?: AgentChildRowModel
isReadOnly?: boolean
onPress?: (paneKey: string) => void
}
// One inline agent row: state dot → identity → last message/prompt → time ago.
// Mirrors desktop DashboardAgentRow's compact in-card layout.
function WorktreeAgentRowComponent({
agent,
depth,
now,
unvisited,
childRow,
isReadOnly,
onPress
}: Props) {
const dotState = childRow?.displayState ?? agentDotState(agent, now)
// A visual line renders only in the transcript; the row shows the words around it, and a reply
// that is only a visual falls back like an empty one.
const reply = agent.lastAssistantMessage
const label = agentDisplayLabel(
reply
? { ...agent, lastAssistantMessage: withoutNativeChatVisualDirectiveLines(reply) }
: agent,
now
)
const childText = childRow ? mobileAgentChildRowText(childRow, now) : null
const timeAt = childRow ? childRow.firstObservedAt : agentRowTimeAt(agent)
const ts = childRow && timeAt <= 0 ? '' : formatTimeAgo(timeAt, now)
const content = (
<View style={[styles.row, { paddingLeft: depth * INDENT_PER_DEPTH }]}>
<AgentStateDot state={dotState} />
{/* Agent identity logo (Claude/Codex/…), matching the desktop sidebar's
agent icons instead of a two-letter text code. */}
{!childRow && agent.agentType ? (
<MobileAgentIcon agentId={agent.agentType} size={13} />
) : null}
<Text style={[styles.label, unvisited && styles.labelUnvisited]} numberOfLines={1}>
{childText ? (
<>
{childText.lead}
{childText.trail ? ` · ${childText.trail}` : ''}
</>
) : (
label
)}
</Text>
<Text style={styles.time}>{ts}</Text>
</View>
)
return childRow ? (
<Pressable
disabled={isReadOnly || !onPress}
accessibilityRole="button"
onPress={(event) => {
event.stopPropagation()
onPress?.(agent.paneKey)
}}
>
{content}
</Pressable>
) : (
content
)
}
export const WorktreeAgentRow = memo(WorktreeAgentRowComponent)
const styles = StyleSheet.create({
row: {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
marginTop: 3
},
label: {
flex: 1,
fontSize: 11,
color: colors.textMuted
},
labelUnvisited: {
color: colors.textPrimary,
fontWeight: '600'
},
time: {
fontSize: 10,
color: colors.textMuted
}
})