diff --git a/apps/mobile/src/features/threads/ThreadFeed.tsx b/apps/mobile/src/features/threads/ThreadFeed.tsx
index 58191639331a..221782f4cd26 100644
--- a/apps/mobile/src/features/threads/ThreadFeed.tsx
+++ b/apps/mobile/src/features/threads/ThreadFeed.tsx
@@ -1619,6 +1619,7 @@ function renderFeedEntry(
environmentId={props.environmentId}
projectedItem={entry.activities[0]!.projectedItem}
iconColor={iconSubtleColor}
+ surfaceColor={props.screenColor}
/>
);
}
diff --git a/apps/mobile/src/features/threads/thread-handoff-row.tsx b/apps/mobile/src/features/threads/thread-handoff-row.tsx
index 3b1b2894a8fc..1af6c1ec0099 100644
--- a/apps/mobile/src/features/threads/thread-handoff-row.tsx
+++ b/apps/mobile/src/features/threads/thread-handoff-row.tsx
@@ -1,9 +1,14 @@
import { useAtomValue } from "@effect/atom-react";
import { scopeThreadRef } from "@t3tools/client-runtime/environment";
import { resolveHandoffEndpoints } from "@t3tools/client-runtime/handoff";
-import { resolveProviderInstanceDisplayName } from "@t3tools/client-runtime/state/provider-instance-display";
+import {
+ normalizeProviderAccentColor,
+ resolveProviderInstanceDisplayName,
+ shouldShowInstanceBadge,
+} from "@t3tools/client-runtime/state/provider-instance-display";
import type {
EnvironmentId,
+ ProviderInstanceConfig,
OrchestrationV2ProjectedTurnItem,
ProviderInstanceId,
ServerProvider,
@@ -13,7 +18,7 @@ import { Fragment, useMemo } from "react";
import { Alert, Pressable, View, type ColorValue } from "react-native";
import { AppText as Text } from "../../components/AppText";
import { SymbolView } from "../../components/AppSymbol";
-import { ProviderIcon } from "../../components/ProviderIcon";
+import { ProviderIcon, ProviderInstanceIcon } from "../../components/ProviderIcon";
import { environmentThreadDetails } from "../../state/threads";
import { serverEnvironment } from "../../state/server";
import { ThreadContextDivider } from "./thread-context-divider";
@@ -38,6 +43,7 @@ export function ThreadHandoffRow(props: {
environmentId: EnvironmentId;
projectedItem: OrchestrationV2ProjectedTurnItem;
iconColor: ColorValue;
+ surfaceColor: string;
}) {
const { item } = props.projectedItem;
const endpointsAtom = useMemo(
@@ -48,6 +54,11 @@ export function ThreadHandoffRow(props: {
const config = useAtomValue(serverEnvironment.configValueAtom(props.environmentId));
if (item.type !== "handoff" || endpoints === null) return null;
const color = item.status === "failed" ? "#e11d48" : props.iconColor;
+ const endpointProps = {
+ providers: config?.providers ?? [],
+ providerInstances: config?.settings.providerInstances,
+ surfaceColor: props.surfaceColor,
+ };
return (
) : null}
-
+
))}
{endpoints.from.length > 0 ? (
) : null}
-
+
);
@@ -79,6 +90,8 @@ function HandoffEndpoint(props: {
instanceId: ProviderInstanceId;
model?: string | undefined;
providers: ReadonlyArray;
+ providerInstances: Readonly> | undefined;
+ surfaceColor: string;
}) {
const provider = props.providers.find((candidate) => candidate.instanceId === props.instanceId);
const model = provider?.models.find((candidate) => candidate.slug === props.model);
@@ -87,12 +100,36 @@ function HandoffEndpoint(props: {
model?.name ??
props.model ??
(provider ? resolveProviderInstanceDisplayName(provider) : props.instanceId);
+ const accentColor = normalizeProviderAccentColor(provider?.accentColor);
+ // Same account badge as the thread rows: a handoff between two accounts of
+ // one provider would otherwise show the same glyph on both sides. Settings
+ // carry each ACP instance's agent, which tells its glyph apart.
+ const badgeEntry = (candidate: ServerProvider) => {
+ const config = props.providerInstances?.[candidate.instanceId]?.config;
+ // A local ACP command has no registry agent, as on web.
+ const agentId =
+ typeof config === "object" && config && Reflect.get(config, "source") !== "local"
+ ? Reflect.get(config, "agentId")
+ : null;
+ return {
+ driverKind: candidate.driver,
+ ...(typeof agentId === "string" && agentId.trim()
+ ? { acpRegistryAgentId: agentId.trim() }
+ : {}),
+ };
+ };
+ const showBadge =
+ provider !== undefined &&
+ shouldShowInstanceBadge(
+ { ...badgeEntry(provider), accentColor },
+ props.providers.map(badgeEntry),
+ );
return (
Alert.alert(
@@ -101,11 +138,23 @@ function HandoffEndpoint(props: {
)
}
>
-
+ {showBadge && provider !== undefined ? (
+
+ ) : (
+
+ )}
{label}
diff --git a/apps/web/src/components/chat/V2LifecycleRow.tsx b/apps/web/src/components/chat/V2LifecycleRow.tsx
index cdd469f7acb4..ee3d2add3b83 100644
--- a/apps/web/src/components/chat/V2LifecycleRow.tsx
+++ b/apps/web/src/components/chat/V2LifecycleRow.tsx
@@ -37,7 +37,12 @@ import {
} from "lucide-react";
import { Tooltip, TooltipTrigger, TooltipPopup } from "../ui/tooltip";
-import { getProviderInstanceEntry } from "../../providerInstances";
+import {
+ applyProviderInstanceSettings,
+ deriveProviderInstanceEntries,
+ shouldShowInstanceBadge,
+} from "../../providerInstances";
+import { useEnvironmentSettings } from "../../hooks/useSettings";
import { formatShortTimestamp } from "../../timestampFormat";
import { getTriggerDisplayModelName } from "./providerIconUtils";
import { ProviderInstanceIcon, providerTextColorClassName } from "./ProviderInstanceIcon";
@@ -137,6 +142,7 @@ export function V2LifecycleRow(props: {
) : null}
) : null}
;
readonly instanceId: ProviderInstanceId;
readonly model?: string | undefined;
}) {
- const entry = getProviderInstanceEntry(props.providers, props.instanceId);
+ // Settings carry each ACP instance's agent, which tells its glyph apart.
+ const entries = applyProviderInstanceSettings(
+ deriveProviderInstanceEntries(props.providers),
+ useEnvironmentSettings(props.environmentId),
+ );
+ const entry = entries.find((candidate) => candidate.instanceId === props.instanceId);
+ // Same account badge as the sidebar: a handoff between two accounts of one
+ // provider would otherwise show the same glyph on both sides.
+ const showBadge = entry !== undefined && shouldShowInstanceBadge(entry, entries);
const model = props.model?.trim();
const providerModel =
model === undefined || model.length === 0
@@ -578,14 +594,19 @@ function HandoffEndpoint(props: {
render={
,
instanceId: ProviderInstanceId,
): ProviderInstanceEntry | undefined {