From 3cfd3dbd1d879f7c9f916abe143a651ddd8cd54c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 23 Jun 2026 01:48:51 +0000 Subject: [PATCH 1/3] feat: Add agent "superpower" content types MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the five dynamic agent content cards that show how Convos works for the user, surfacing the right thing at the right time: - Thinking → live reasoning steps while the agent works - Sources → citations/references backing an answer - Action → a tool call/action, with approve/decline when needed - Memory → what the agent remembers (stored with the user) - Suggestions → tappable follow-up prompts Implemented as a self-contained agent-content module (types, assertions, a shared expandable AgentCard, per-type render components, a dispatcher mirroring conversation-message.tsx, and mock fixtures). Kept decoupled from the XMTP-bound message union so it has no wire-format codec yet and doesn't touch the protocol conversion layer. Added an "Agent content examples" dev screen (reachable from App Settings) that renders every card with mock data for review. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01Rov93B6jifGmgS6EGyw94c --- features/app-settings/app-settings.screen.tsx | 10 +- .../agent-content/agent-card.tsx | 152 ++++++++++++++++ .../agent-content/agent-content-action.tsx | 172 ++++++++++++++++++ .../agent-content-examples.screen.tsx | 50 +++++ .../agent-content/agent-content-memory.tsx | 80 ++++++++ .../agent-content/agent-content-sources.tsx | 120 ++++++++++++ .../agent-content-suggestions.tsx | 83 +++++++++ .../agent-content/agent-content-thinking.tsx | 105 +++++++++++ .../agent-content/agent-content.assertions.ts | 38 ++++ .../agent-content/agent-content.fixtures.ts | 155 ++++++++++++++++ .../agent-content/agent-content.tsx | 51 ++++++ .../agent-content/agent-content.types.ts | 123 +++++++++++++ navigation/app-navigator.tsx | 5 + navigation/navigation.types.tsx | 1 + 14 files changed, 1143 insertions(+), 2 deletions(-) create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-card.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-action.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-examples.screen.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-memory.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-sources.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-suggestions.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content-thinking.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content.assertions.ts create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content.fixtures.ts create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content.tsx create mode 100644 features/conversation/conversation-chat/conversation-message/agent-content/agent-content.types.ts diff --git a/features/app-settings/app-settings.screen.tsx b/features/app-settings/app-settings.screen.tsx index 14035c06..fa330c8a 100644 --- a/features/app-settings/app-settings.screen.tsx +++ b/features/app-settings/app-settings.screen.tsx @@ -23,8 +23,14 @@ export const AppSettingsScreen = memo(function AppSettingsScreen() { }) const generalSettings = useMemo((): ISettingsListRow[] => { - return [{ label: "Environment", value: getEnv() }].filter(Boolean) - }, []) + return [ + { label: "Environment", value: getEnv() }, + { + label: "Agent content examples", + onPress: () => router.navigate("AgentContentExamples"), + }, + ].filter(Boolean) + }, [router]) return ( +
+ +
+ + + {title} + {!!subtitle && ( + + {subtitle} + + )} + + + {trailing} + + {collapsible && ( + + )} + + ) + + return ( + + {collapsible ? ( + setExpanded((prev) => !prev)}> + {Header} + + ) : ( + Header + )} + + {(!collapsible || expanded) && !!children && ( + {children} + )} + + ) +}) + +function getAccentColor(args: { + accent: IAgentCardAccent + theme: ReturnType["theme"] +}) { + const { accent, theme } = args + switch (accent) { + case "accent": + return theme.colors.fill.accent + case "green": + return theme.colors.global.green + case "caution": + return theme.colors.global.caution + case "neutral": + default: + return theme.colors.text.secondary + } +} + +// Lightweight translucent background for the icon badge. Colors in the palette +// are hex strings, so append an alpha channel. +function withAlpha(color: string) { + if (color.startsWith("#") && color.length === 7) { + return `${color}1A` // ~10% opacity + } + return color +} + +const $card: ThemedStyle = ({ colors, spacing, borderRadius, borderWidth }) => ({ + width: "100%", + borderRadius: borderRadius.sm, + borderWidth: borderWidth.sm, + borderColor: colors.border.subtle, + backgroundColor: colors.background.surface, + paddingVertical: spacing.xs, + paddingHorizontal: spacing.sm, + rowGap: spacing.xs, +}) + +const $header: ThemedStyle = ({ spacing }) => ({ + alignItems: "center", + columnGap: spacing.xs, +}) + +const $iconBadge: ThemedStyle = ({ spacing, borderRadius }) => ({ + width: spacing.lg, + height: spacing.lg, + borderRadius: borderRadius.xs, +}) + +const $headerText: ThemedStyle = ({ spacing }) => ({ + flex: 1, + rowGap: spacing["6xs"], +}) + +const $body: ThemedStyle = ({ spacing }) => ({ + rowGap: spacing.xs, +}) diff --git a/features/conversation/conversation-chat/conversation-message/agent-content/agent-content-action.tsx b/features/conversation/conversation-chat/conversation-message/agent-content/agent-content-action.tsx new file mode 100644 index 00000000..025d863c --- /dev/null +++ b/features/conversation/conversation-chat/conversation-message/agent-content/agent-content-action.tsx @@ -0,0 +1,172 @@ +import { memo } from "react" +import { ViewStyle } from "react-native" +import { ActivityIndicator } from "@/design-system/activity-indicator" +import { Button } from "@/design-system/Button/Button" +import { HStack } from "@/design-system/HStack" +import { Icon } from "@/design-system/Icon/Icon" +import { Text } from "@/design-system/Text" +import { VStack } from "@/design-system/VStack" +import { + AgentCard, + IAgentCardAccent, +} from "@/features/conversation/conversation-chat/conversation-message/agent-content/agent-card" +import { + IAgentActionContent, + IAgentActionStatus, +} from "@/features/conversation/conversation-chat/conversation-message/agent-content/agent-content.types" +import { ThemedStyle, useAppTheme } from "@/theme/use-app-theme" + +type IAgentContentActionProps = { + content: IAgentActionContent + onApprove?: () => void + onDecline?: () => void +} + +/** + * Bucket 3 — Action / Tool call. + * + * Surfaces an action the agent took or wants to take. When the action requires + * approval we show Approve / Decline so the user stays in control. + */ +export const AgentContentAction = memo(function AgentContentAction( + props: IAgentContentActionProps, +) { + const { content, onApprove, onDecline } = props + const { theme, themed } = useAppTheme() + + const accent = getAccentForStatus(content.status) + const needsApproval = content.requiresApproval && content.status === "proposed" + + return ( + } + > + {!!content.description && ( + + {content.description} + + )} + + {!!content.params?.length && ( + + {content.params.map((param) => ( + + + {param.label} + + + {param.value} + + + ))} + + )} + + {!!content.resultSummary && ( + + + + {content.resultSummary} + + + )} + + {needsApproval && ( + +