diff --git a/apps/mobile/src/features/threads/SubagentRow.tsx b/apps/mobile/src/features/threads/SubagentRow.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..bef52c4b7c756ea3e3f0ff888cc73e4831044e88
--- /dev/null
+++ b/apps/mobile/src/features/threads/SubagentRow.tsx
@@ -0,0 +1,154 @@
+import { scopeProjectRef, scopeThreadRef } from "@t3tools/client-runtime/environment";
+import { resolveSubagentMetadata } from "@t3tools/client-runtime/state/subagent-display";
+import type { EnvironmentId, OrchestrationV2Subagent } from "@t3tools/contracts";
+import type { ReactNode } from "react";
+import { View } from "react-native";
+
+import { AppText as Text } from "../../components/AppText";
+import { SymbolView } from "../../components/AppSymbol";
+import { ProviderIcon } from "../../components/ProviderIcon";
+import { cn } from "../../lib/cn";
+import { useEnvironmentServerConfig, useProject, useThreadShell } from "../../state/entities";
+import { subagentCardDetail } from "./subagent-card-presentation";
+import { SUBAGENT_TONE_TEXT_CLASS, SubagentStatusDot } from "./SubagentStatusDot";
+import { resolveSubagentRowPresentation } from "./threadAgentsPresentation";
+
+type SubagentRowSubagent = Pick<
+ OrchestrationV2Subagent,
+ | "threadId"
+ | "childThreadId"
+ | "model"
+ | "driver"
+ | "providerInstanceId"
+ | "title"
+ | "prompt"
+ | "status"
+ | "progress"
+ | "result"
+>;
+
+/**
+ * One agent as shown in the Agents sheet and transcript cards: status, model,
+ * workspace changes, and recent progress or result. Callers own the press
+ * target and pass their own elapsed timer so ticking stays isolated.
+ */
+export function SubagentRow(props: {
+ readonly environmentId: EnvironmentId;
+ readonly subagent: SubagentRowSubagent;
+ readonly elapsed: ReactNode;
+}) {
+ const presentation = resolveSubagentRowPresentation(props.subagent);
+ const detail = subagentCardDetail(presentation.detail);
+ return (
+
+
+
+
+
+
+
+
+ {presentation.title}
+
+
+ ·
+
+
+ {presentation.statusLabel}
+
+
+ {props.elapsed}
+ {presentation.canOpenThread ? (
+
+ ) : null}
+
+
+ {detail ? (
+
+ {detail}
+
+ ) : null}
+
+
+ );
+}
+
+/** Uses shell data already held by the client, without loading child transcripts. */
+function SubagentMetadata(props: {
+ readonly environmentId: EnvironmentId;
+ readonly subagent: SubagentRowSubagent;
+}) {
+ const { environmentId, subagent } = props;
+ const config = useEnvironmentServerConfig(environmentId);
+ const provider = config?.providers.find(
+ (candidate) => candidate.instanceId === subagent.providerInstanceId,
+ );
+ const parent = useThreadShell(scopeThreadRef(environmentId, subagent.threadId))?.source;
+ const child = useThreadShell(
+ subagent.childThreadId === null ? null : scopeThreadRef(environmentId, subagent.childThreadId),
+ )?.source;
+ const parentProject = useProject(
+ parent ? scopeProjectRef(environmentId, parent.projectId) : null,
+ );
+ const childProject = useProject(child ? scopeProjectRef(environmentId, child.projectId) : null);
+ const { modelLabel, workspace } = resolveSubagentMetadata({
+ model: subagent.model,
+ provider,
+ parentThread: parent,
+ childThread: child,
+ parentProject,
+ childProject,
+ });
+ return (
+
+
+
+ {provider?.displayName ? `${provider.displayName} · ` : ""}
+ {modelLabel}
+
+ {workspace.map(({ label, value }) => (
+ // The row reads this label in place of the icon. collapsable keeps the
+ // view (and label) from being flattened away without making it a
+ // separate accessibility stop.
+
+ ·
+
+
+ {value}
+
+
+ ))}
+
+ );
+}
diff --git a/apps/mobile/src/features/threads/SubagentStatusDot.tsx b/apps/mobile/src/features/threads/SubagentStatusDot.tsx
index a08355effb505fc2147baf0202eaf60bbe7f29e7..348f6e534b6c71c23ca2803dbad17ea5e09abb42 100644
--- a/apps/mobile/src/features/threads/SubagentStatusDot.tsx
+++ b/apps/mobile/src/features/threads/SubagentStatusDot.tsx
@@ -10,19 +10,25 @@ const TONE_CLASS = {
stopped: "bg-foreground-muted",
} as const satisfies Record;
+export const SUBAGENT_TONE_TEXT_CLASS = {
+ working: "text-adaptive-sky-600-400",
+ completed: "text-adaptive-emerald-600-400",
+ failed: "text-adaptive-rose-600-400",
+ stopped: "text-foreground-muted",
+} as const satisfies Record;
+
export function SubagentStatusDot({
tone,
placement = "inline",
}: {
readonly tone: SubagentRowTone;
- readonly placement?: "inline" | "provider" | "sheet";
+ readonly placement?: "inline" | "sheet";
}) {
return (
diff --git a/apps/mobile/src/features/threads/ThreadAgentsSheet.tsx b/apps/mobile/src/features/threads/ThreadAgentsSheet.tsx
index 7d7c825aaa085100a9e237ab624df3f2302fa87d..1d082515172d3966920a081647aa5e041d2e2724 100644
--- a/apps/mobile/src/features/threads/ThreadAgentsSheet.tsx
+++ b/apps/mobile/src/features/threads/ThreadAgentsSheet.tsx
@@ -17,13 +17,10 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AndroidSheetHeader } from "../../components/AndroidScreenHeader";
import { AppText as Text } from "../../components/AppText";
-import { SymbolView } from "../../components/AppSymbol";
import { useUniwindTheme } from "../../lib/useUniwindTheme";
import { environmentThreadDetails } from "../../state/threads";
import { nativeHeaderScrollEdgeEffects } from "../../native/StackHeader";
-import { resolveSubagentRowPresentation } from "./threadAgentsPresentation";
-
-import { SubagentStatusDot } from "./SubagentStatusDot";
+import { SubagentRow } from "./SubagentRow";
const HEADER_SCROLL_EDGE_EFFECTS = nativeHeaderScrollEdgeEffects(Platform.OS, Platform.Version);
@@ -72,6 +69,7 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps) {
@@ -121,32 +119,21 @@ export function ThreadAgentsSheet({ route }: StaticScreenProps) {
}
function AgentRow(props: {
+ readonly environmentId: EnvironmentId;
readonly subagent: OrchestrationV2Subagent;
readonly tickSeconds: boolean;
readonly onOpen: (childThreadId: ThreadId) => void;
}) {
const { subagent } = props;
- const presentation = resolveSubagentRowPresentation(subagent);
const childThreadId = subagent.childThreadId;
- const elapsed = useSubagentElapsed(subagent, props.tickSeconds);
const row = (
-
-
-
-
- {presentation.title}
-
-
- {presentation.detail ?? presentation.statusLabel}
-
-
- {elapsed === null ? null : (
- {elapsed}
- )}
- {presentation.canOpenThread ? (
-
- ) : null}
+
+ }
+ />
);
@@ -154,7 +141,6 @@ function AgentRow(props: {
return (
{row}
@@ -165,7 +151,6 @@ function AgentRow(props: {
return (
props.onOpen(childThreadId)}
className="active:opacity-70"
@@ -175,9 +160,19 @@ function AgentRow(props: {
);
}
+function AgentElapsed(props: {
+ readonly subagent: OrchestrationV2Subagent;
+ readonly tickSeconds: boolean;
+}) {
+ const elapsed = useSubagentElapsed(props.subagent, props.tickSeconds);
+ return elapsed === null ? null : (
+ {elapsed}
+ );
+}
+
/**
- * Elapsed time for one agent. Only a roster with live work subscribes to the
- * shared second tick, so a settled sheet never repaints.
+ * Elapsed time for one agent. Only live work ticks, inside AgentElapsed,
+ * so the timer never repaints the metadata or a settled sheet.
*/
function useSubagentElapsed(
subagent: Pick,
diff --git a/apps/mobile/src/features/threads/thread-subagent-group.tsx b/apps/mobile/src/features/threads/thread-subagent-group.tsx
index 0c51c6195b7512ade195cf9f8d72aad4c41d18cc..c0f1adf4736e731d69b09c5f8005b7243baae4df 100644
--- a/apps/mobile/src/features/threads/thread-subagent-group.tsx
+++ b/apps/mobile/src/features/threads/thread-subagent-group.tsx
@@ -2,10 +2,7 @@ import { useAtomValue } from "@effect/atom-react";
import { useIsFocused, useNavigation } from "@react-navigation/native";
import { scopeThreadRef } from "@t3tools/client-runtime/environment";
import { summarizeSubagentStatuses } from "@t3tools/client-runtime/state/subagent-display";
-import {
- isActiveSubagentStatus,
- isTerminalSubagentStatus,
-} from "@t3tools/client-runtime/state/subagentRuntime";
+import { isActiveSubagentStatus } from "@t3tools/client-runtime/state/subagentRuntime";
import type {
EnvironmentId,
OrchestrationV2Subagent,
@@ -21,9 +18,8 @@ import { cn } from "../../lib/cn";
import type { ThreadFeedActivity } from "../../lib/threadActivity";
import { serverEnvironment } from "../../state/server";
import { environmentThreadDetails } from "../../state/threads";
-import { SubagentStatusDot } from "./SubagentStatusDot";
-import { subagentCardDetail, subagentCardElapsed } from "./subagent-card-presentation";
-import { resolveSubagentRowPresentation } from "./threadAgentsPresentation";
+import { subagentCardElapsed } from "./subagent-card-presentation";
+import { SubagentRow } from "./SubagentRow";
import { WorkLogBlock } from "./work-log-layout";
type SubagentItem = Extract;
@@ -47,27 +43,20 @@ function SubagentElapsed({ agents }: { readonly agents: ReadonlyArray{elapsed}
+ {elapsed}
) : null;
}
function SubagentAvatar(props: {
readonly item: SubagentItem;
readonly iconUrl?: string | null | undefined;
- readonly status?: OrchestrationV2Subagent["status"];
}) {
return (
- {props.status ? (
-
- ) : null}
);
}
@@ -99,6 +88,7 @@ export function ThreadSubagentGroup(props: {
completedAt: live?.completedAt ?? item.completedAt,
result: live?.result ?? item.result,
progress: live?.progress ?? item.progress,
+ model: live?.model ?? null,
};
});
const grouped = agents.length > 1;
@@ -157,19 +147,12 @@ export function ThreadSubagentGroup(props: {
{!grouped || expanded ? (
{agents.map((agent) => {
- const presentation = resolveSubagentRowPresentation(agent);
- const detail = subagentCardDetail(
- isTerminalSubagentStatus(agent.status)
- ? agent.result?.trim() || agent.progress || null
- : agent.progress?.trim() || agent.result,
- );
const threadId = agent.childThreadId;
return (
- }
/>
-
-
-
- {presentation.title}
-
- {detail && agent.status !== "completed" ? (
-
- {presentation.statusLabel}
-
- ) : null}
-
-
- {detail ?? presentation.statusLabel}
-
-
-
- {threadId !== null ? (
-
- ) : null}
);
})}
diff --git a/apps/mobile/src/features/threads/threadAgentsPresentation.test.ts b/apps/mobile/src/features/threads/threadAgentsPresentation.test.ts
index bf095f54187358a8e1cac9be5e3199ee284c4ba4..65c12910cea7dd75a6bb68fa4fa8e91fa8b1f0f2 100644
--- a/apps/mobile/src/features/threads/threadAgentsPresentation.test.ts
+++ b/apps/mobile/src/features/threads/threadAgentsPresentation.test.ts
@@ -65,4 +65,14 @@ describe("resolveSubagentRowPresentation", () => {
expect(row.detail).toBeNull();
expect(row.statusLabel).toBe("Working");
});
+
+ it("bounds long result previews without dropping the agent's status", () => {
+ const row = resolveSubagentRowPresentation({
+ ...base,
+ status: "failed",
+ result: "x".repeat(400),
+ });
+ expect(row.detail).toBe(`${"x".repeat(280)}…`);
+ expect(row.statusLabel).toBe("Failed");
+ });
});
diff --git a/apps/mobile/src/features/threads/threadAgentsPresentation.ts b/apps/mobile/src/features/threads/threadAgentsPresentation.ts
index d1b85b8964706ef380fc6e44f879ba7d08949849..9605891143b4ae46c3b054418f0f628880649969 100644
--- a/apps/mobile/src/features/threads/threadAgentsPresentation.ts
+++ b/apps/mobile/src/features/threads/threadAgentsPresentation.ts
@@ -1,8 +1,8 @@
-import { formatSubagentDisplayTitle } from "@t3tools/client-runtime/state/subagent-display";
import {
- isActiveSubagentStatus,
- isTerminalSubagentStatus,
-} from "@t3tools/client-runtime/state/subagentRuntime";
+ formatSubagentDisplayTitle,
+ subagentDetailPreview,
+} from "@t3tools/client-runtime/state/subagent-display";
+import { isActiveSubagentStatus } from "@t3tools/client-runtime/state/subagentRuntime";
import type { OrchestrationV2Subagent } from "@t3tools/contracts";
const PROMPT_TITLE_LIMIT = 80;
@@ -64,13 +64,9 @@ export function resolveSubagentRowPresentation(
>,
): SubagentRowPresentation {
const live = isActiveSubagentStatus(subagent.status);
- const progress = subagent.progress?.trim() ?? "";
- const result = subagent.result?.trim() ?? "";
- const settled = isTerminalSubagentStatus(subagent.status);
- const detail = settled ? result || progress : progress || result;
return {
title: rowTitle(subagent),
- detail: detail.length > 0 ? detail.replace(/\s+/gu, " ") : null,
+ detail: subagentDetailPreview(subagent),
statusLabel: rowStatusLabel(subagent.status),
tone: rowTone(subagent.status),
live,
diff --git a/apps/web/src/components/chat/SubagentTooltipContent.tsx b/apps/web/src/components/chat/SubagentTooltipContent.tsx
index 49c382ba639fb5152d8cb2c33d6caa26bfd6527f..d638736759a4368ed416e625f9e1b9023720ec4a 100644
--- a/apps/web/src/components/chat/SubagentTooltipContent.tsx
+++ b/apps/web/src/components/chat/SubagentTooltipContent.tsx
@@ -1,12 +1,14 @@
import type {
OrchestrationV2ThreadShell,
+ OrchestrationV2TurnItemStatus,
OrchestrationProjectShell,
ServerProvider,
ProviderDriverKind,
} from "@t3tools/contracts";
-import { fileBasename } from "@t3tools/client-runtime/markdown-links";
-import { formatModelSlugName, resolveSelectableModel } from "@t3tools/shared/model";
-import { getTriggerDisplayModelName } from "./providerIconUtils";
+import {
+ resolveSubagentMetadata,
+ subagentDetailPreview,
+} from "@t3tools/client-runtime/state/subagent-display";
import type { ReactNode } from "react";
import {
BotIcon,
@@ -33,48 +35,12 @@ export function SubagentTooltipContent(props: {
childThread?: Pick | undefined;
parentProject?: Pick | undefined;
childProject?: Pick | undefined;
- status: string;
+ status: OrchestrationV2TurnItemStatus;
result?: string | null | undefined;
progress?: string | null | undefined;
}) {
- const model = props.model?.trim();
- const modelSlug = props.provider
- ? resolveSelectableModel(props.provider.driver, model, props.provider.models)
- : model;
- const providerModel = props.provider?.models.find((candidate) => candidate.slug === modelSlug);
- const modelLabel = providerModel
- ? getTriggerDisplayModelName(providerModel)
- : model
- ? formatModelSlugName(model)
- : "Not reported";
- const currentWorkspace = props.parentThread?.worktreePath ?? props.parentProject?.workspaceRoot;
- const childWorkspace = props.childThread?.worktreePath ?? props.childProject?.workspaceRoot;
- const metadata = [
- ...(props.parentThread &&
- props.childProject &&
- props.childProject.id !== props.parentThread.projectId
- ? [{ label: "Project", value: props.childProject.title }]
- : []),
- ...(currentWorkspace && childWorkspace && currentWorkspace !== childWorkspace
- ? [
- {
- label: props.childThread?.branch
- ? "Branch"
- : props.childThread?.worktreePath
- ? "Worktree"
- : "Workspace",
- value: props.childThread?.branch ?? fileBasename(childWorkspace),
- },
- ]
- : []),
- ];
- const settled = ["completed", "failed", "cancelled", "interrupted"].includes(props.status);
- const result = props.result?.trim();
- const progress = props.progress?.trim();
- const detail = (settled ? result || progress : progress || result) || "";
- const compactDetail = detail.trim().replace(/\s+/g, " ");
- const preview =
- compactDetail.length > 280 ? `${compactDetail.slice(0, 280).trimEnd()}…` : compactDetail;
+ const { modelLabel, workspace: metadata } = resolveSubagentMetadata(props);
+ const preview = subagentDetailPreview(props);
const driver = props.provider?.driver ?? props.driver;
const working = ["running", "in_progress", "pending", "waiting"].includes(props.status);
const failed = ["failed", "error"].includes(props.status);
diff --git a/packages/client-runtime/src/state/subagentDisplay.test.ts b/packages/client-runtime/src/state/subagentDisplay.test.ts
index 11184d369469dbe3aca6a5d166003b46aaba492b..3485cd4f2da2dbc0c30a2937ae605a298166bc25 100644
--- a/packages/client-runtime/src/state/subagentDisplay.test.ts
+++ b/packages/client-runtime/src/state/subagentDisplay.test.ts
@@ -1,6 +1,11 @@
+import { ProjectId, ProviderDriverKind } from "@t3tools/contracts";
import type { OrchestrationV2TurnItemStatus } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";
-import { subagentGroupSummary } from "./subagentDisplay.js";
+import {
+ subagentGroupSummary,
+ resolveSubagentMetadata,
+ subagentDetailPreview,
+} from "./subagentDisplay.js";
describe("subagentGroupSummary", () => {
it.each(["pending", "running", "waiting"] as const)(
@@ -36,3 +41,126 @@ describe("subagentGroupSummary", () => {
});
});
});
+
+describe("resolveSubagentMetadata", () => {
+ it("resolves provider aliases to catalog names, including custom models", () => {
+ expect(
+ resolveSubagentMetadata({
+ model: "claude-haiku-4-5-20251001",
+ provider: {
+ driver: ProviderDriverKind.make("claudeAgent"),
+ models: [
+ {
+ slug: "claude-haiku-4-5",
+ name: "Claude Haiku 4.5",
+ shortName: "Haiku 4.5",
+ aliases: ["claude-haiku-4-5-20251001"],
+ isCustom: false,
+ capabilities: null,
+ },
+ ],
+ },
+ }).modelLabel,
+ ).toBe("Haiku 4.5");
+ expect(
+ resolveSubagentMetadata({
+ model: "my-model",
+ provider: {
+ driver: ProviderDriverKind.make("acpRegistry"),
+ models: [
+ {
+ slug: "my-model",
+ name: "Cloud+ / My custom model",
+ subProvider: "Cloud+",
+ isCustom: true,
+ capabilities: null,
+ },
+ ],
+ },
+ }).modelLabel,
+ ).toBe("My custom model");
+ });
+
+ it("keeps unknown model identities and does not invent an unreported model", () => {
+ expect(resolveSubagentMetadata({ model: " custom/model " }).modelLabel).toBe("custom/model");
+ expect(
+ resolveSubagentMetadata({
+ model: null,
+ provider: { driver: ProviderDriverKind.make("codex"), models: [] },
+ }).modelLabel,
+ ).toBe("Not reported");
+ expect(resolveSubagentMetadata({ model: " " }).modelLabel).toBe("Not reported");
+ });
+
+ const parentThread = { projectId: ProjectId.make("parent"), worktreePath: null };
+ const parentProject = { workspaceRoot: "/repo" };
+
+ it("shows another project and its branch when the child has a different workspace", () => {
+ expect(
+ resolveSubagentMetadata({
+ model: null,
+ parentThread,
+ parentProject,
+ childThread: { branch: "fix/agents", worktreePath: "/worktrees/agents" },
+ childProject: {
+ id: ProjectId.make("child"),
+ title: "Other project",
+ workspaceRoot: "/other",
+ },
+ }).workspace,
+ ).toEqual([
+ { label: "Project", value: "Other project" },
+ { label: "Branch", value: "fix/agents" },
+ ]);
+ });
+
+ it("labels a detached worktree or another project workspace without a branch", () => {
+ expect(
+ resolveSubagentMetadata({
+ model: null,
+ parentThread,
+ parentProject,
+ childThread: { branch: null, worktreePath: "/worktrees/agents" },
+ }).workspace,
+ ).toEqual([{ label: "Worktree", value: "agents" }]);
+ expect(
+ resolveSubagentMetadata({
+ model: null,
+ parentThread,
+ parentProject,
+ childProject: {
+ id: parentThread.projectId,
+ title: "Same project",
+ workspaceRoot: "/other",
+ },
+ }).workspace,
+ ).toEqual([{ label: "Workspace", value: "other" }]);
+ });
+
+ it("hides redundant workspace metadata and tolerates unavailable child shells", () => {
+ expect(
+ resolveSubagentMetadata({
+ model: null,
+ parentThread,
+ parentProject,
+ childThread: { branch: "main", worktreePath: "/repo" },
+ childProject: { id: parentThread.projectId, title: "Same project", workspaceRoot: "/repo" },
+ }).workspace,
+ ).toEqual([]);
+ expect(resolveSubagentMetadata({ model: null, parentThread, parentProject }).workspace).toEqual(
+ [],
+ );
+ });
+});
+
+describe("subagentDetailPreview", () => {
+ it("prefers progress for live work and results for settled work", () => {
+ const details = { progress: "Reading files", result: "Found two\n problems" };
+ expect(subagentDetailPreview({ ...details, status: "running" })).toBe("Reading files");
+ expect(subagentDetailPreview({ ...details, status: "completed" })).toBe("Found two problems");
+ expect(
+ subagentDetailPreview({ status: "failed", progress: "Last progress", result: " " }),
+ ).toBe("Last progress");
+ expect(subagentDetailPreview({ status: "pending" })).toBeNull();
+ });
+});
diff --git a/packages/client-runtime/src/state/subagentDisplay.ts b/packages/client-runtime/src/state/subagentDisplay.ts
index 4d1d2c5cd15d814097e8fe7ef0e187dff29809bb..0a456e7a2ddc68664a4bc521ee02f4b8d1941de5 100644
--- a/packages/client-runtime/src/state/subagentDisplay.ts
+++ b/packages/client-runtime/src/state/subagentDisplay.ts
@@ -1,4 +1,12 @@
-import type { OrchestrationV2TurnItemStatus } from "@t3tools/contracts";
+import type {
+ OrchestrationV2TurnItemStatus,
+ OrchestrationV2ThreadShell,
+ OrchestrationProjectShell,
+ ServerProvider,
+} from "@t3tools/contracts";
+import { formatModelSlugName, resolveSelectableModel } from "@t3tools/shared/model";
+import { fileBasename } from "../markdownLinks.ts";
+import { isTerminalSubagentStatus } from "./subagentRuntime.ts";
/** Summarizes one adjacent group, without changing its member identities or order. */
export function subagentGroupSummary(
@@ -44,3 +52,81 @@ export function formatSubagentDisplayTitle(title: string): string {
const name = path[1]!.replace(/[_\s]+/gu, " ").trim();
return name.replace(/(^|\s)\S/gu, (letter) => letter.toUpperCase()) || displayTitle;
}
+
+/** Match desktop's model resolution and show only changes from the parent's workspace. */
+export function resolveSubagentMetadata(input: {
+ readonly model: string | null;
+ readonly provider?: Pick | null | undefined;
+ readonly parentThread?:
+ | Pick
+ | null
+ | undefined;
+ readonly childThread?:
+ | Pick
+ | null
+ | undefined;
+ readonly parentProject?: Pick | null | undefined;
+ readonly childProject?:
+ | Pick
+ | null
+ | undefined;
+}) {
+ const model = input.model?.trim();
+ const slug = input.provider
+ ? resolveSelectableModel(input.provider.driver, model, input.provider.models)
+ : model;
+ const catalogModel = input.provider?.models.find((candidate) => candidate.slug === slug);
+ const reportedLabel = catalogModel
+ ? catalogModel.shortName || catalogModel.name
+ : model
+ ? formatModelSlugName(model)
+ : "Not reported";
+ const qualifier = catalogModel?.subProvider?.trim();
+ const modelLabel = qualifier
+ ? reportedLabel
+ .replace(
+ new RegExp(
+ `^${qualifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?:\\s*[.:/-]\\s*|\\s+)`,
+ "iu",
+ ),
+ "",
+ )
+ .trim() || reportedLabel
+ : reportedLabel;
+ const parentWorkspace = input.parentThread?.worktreePath ?? input.parentProject?.workspaceRoot;
+ const childWorkspace = input.childThread?.worktreePath ?? input.childProject?.workspaceRoot;
+ const workspace = [
+ ...(input.parentThread &&
+ input.childProject &&
+ input.childProject.id !== input.parentThread.projectId
+ ? [{ label: "Project", value: input.childProject.title }]
+ : []),
+ ...(parentWorkspace && childWorkspace && parentWorkspace !== childWorkspace
+ ? [
+ {
+ label: input.childThread?.branch
+ ? "Branch"
+ : input.childThread?.worktreePath
+ ? "Worktree"
+ : "Workspace",
+ value: input.childThread?.branch ?? fileBasename(childWorkspace),
+ },
+ ]
+ : []),
+ ];
+ return { modelLabel, workspace };
+}
+
+/** Live work leads with progress; settled work leads with its result. */
+export function subagentDetailPreview(input: {
+ readonly status: OrchestrationV2TurnItemStatus;
+ readonly result?: string | null | undefined;
+ readonly progress?: string | null | undefined;
+}): string | null {
+ const result = input.result?.trim();
+ const progress = input.progress?.trim();
+ const detail =
+ (isTerminalSubagentStatus(input.status) ? result || progress : progress || result) || "";
+ const compact = detail.replace(/\s+/gu, " ");
+ return compact.length > 280 ? `${compact.slice(0, 280).trimEnd()}…` : compact || null;
+}