@@ -652,198 +628,6 @@ function ToolRunSummaryLabel({
);
}
-function TurnPromptBlock({
- context,
- profiles,
- setup,
- user,
-}: {
- context: Extract
| null;
- profiles?: UserProfileLookup;
- setup: Extract[];
- user: Extract;
-}) {
- return (
-
- {SHOW_TRANSCRIPT_ACP_SOURCE ? (
-
-
- {context ? (
-
- ) : null}
-
- ) : null}
-
-
- );
-}
-
-function PromptUserMessage({
- context = null,
- item,
- profiles,
- setup = [],
-}: {
- context?: Extract | null;
- item: Extract;
- profiles?: UserProfileLookup;
- setup?: Extract[];
-}) {
- const [contextOpen, setContextOpen] = React.useState(false);
- const contextSections = React.useMemo(
- () => [...(context?.sections ?? [])],
- [context],
- );
-
- return (
- <>
- 0}
- items={setup}
- messageLink={getTranscriptMessageLink(item)}
- onContextOpenChange={setContextOpen}
- timestamp={item.timestamp}
- />
- }
- item={item}
- profiles={profiles}
- />
-
- >
- );
-}
-
-function PromptContextDialog({
- onOpenChange,
- open,
- sections,
- setup,
-}: {
- onOpenChange: (open: boolean) => void;
- open: boolean;
- sections: PromptSection[];
- setup: Extract[];
-}) {
- if (!open || sections.length === 0) {
- return null;
- }
-
- const setupText = formatPromptSetupSummary(setup);
-
- return (
-
- );
-}
-
-function formatPromptSetupSummary(
- items: Extract[],
-) {
- const label = formatTurnSetupLabel(items);
- const detail = turnSetupDetail(items);
- return [label, detail].filter(Boolean).join(" · ");
-}
-
-function TurnSetupFooter({
- contextOpen = false,
- hasContext = false,
- items,
- messageLink = null,
- onContextOpenChange,
- showTimestamp = true,
- timestamp,
-}: {
- contextOpen?: boolean;
- hasContext?: boolean;
- items: Extract[];
- messageLink?: { channelId: string; messageId: string } | null;
- onContextOpenChange?: (open: boolean) => void;
- showTimestamp?: boolean;
- timestamp: string;
-}) {
- const label = formatTurnSetupLabel(items);
- const detail = turnSetupDetail(items);
- const tooltipText = [label, detail].filter(Boolean).join(" · ");
- const showSetup = items.length > 0;
- const showContext = hasContext && onContextOpenChange != null;
-
- if (!showSetup && !showContext) {
- return showTimestamp ? (
-
- ) : null;
- }
-
- return (
-
- {showContext ? (
-
-
-
- ) : (
-
-
- {tooltipText}
-
- )}
- {showTimestamp ? (
-
- ) : null}
-
- );
-}
-
-function getTranscriptMessageLink(
- item: Extract,
-) {
- if (!item.channelId || !item.messageId) return null;
- return {
- channelId: item.channelId,
- messageId: item.messageId,
- };
-}
-
function TranscriptItemRow({
agentAvatarUrl,
agentName,
@@ -906,74 +690,6 @@ function TranscriptRowTimestamp({
);
}
-function TurnSetupStatus({
- items,
-}: {
- items: Extract[];
-}) {
- const timestamp = turnSetupTimestamp(items);
- if (items.length === 0 || !timestamp) {
- return null;
- }
-
- return (
-
-
-
- );
-}
-
-/**
- * Horizontal rule rendered between session runs in the observer transcript.
- *
- * Three label states (based on live-frame observation, not harness affinity):
- * - `"current"` — most recent session observed via the live relay subscription.
- * - `"most-recent"` — newest visible session with no matching live frames
- * (loaded from archive or session ended before observation).
- * - `"earlier"` — an older session preceding the most-recent one.
- */
-function SessionBoundaryDivider({
- labelState,
- sessionStartTimestamp,
-}: {
- labelState: "current" | "most-recent" | "earlier";
- sessionStartTimestamp: string;
-}) {
- const label =
- labelState === "current"
- ? "Latest live-observed session"
- : labelState === "most-recent"
- ? "Most recent observed session"
- : "Earlier observed session";
- const formattedDate = new Date(sessionStartTimestamp).toLocaleString();
- return (
-
-
-
- {labelState === "current" ? (
-
- ) : (
-
- )}
- {label}
- {" · "}
- {formattedDate}
-
-
-
- );
-}
-
const TranscriptItemView = React.memo(function TranscriptItemView({
agentAvatarUrl,
agentName,
diff --git a/desktop/src/features/agents/ui/activityRenderClasses/ConversationDivider.tsx b/desktop/src/features/agents/ui/activityRenderClasses/ConversationDivider.tsx
new file mode 100644
index 00000000000..d36d0cd6a9e
--- /dev/null
+++ b/desktop/src/features/agents/ui/activityRenderClasses/ConversationDivider.tsx
@@ -0,0 +1,42 @@
+import type * as React from "react";
+
+import { cn } from "@/shared/lib/cn";
+
+/**
+ * Quiet centered rule used by the `conversation` variant for items that mark
+ * structure rather than work: session boundaries and turn setup/status.
+ *
+ * VISION_ACTIVITY's "failures rise; reads recede" applies to salience, not just
+ * color — in focus mode these items recede to a hairline with a small centered
+ * caption so the reader's eye stays on the prompt/response rhythm. Errors and
+ * permission gates keep their loud treatment and never route here.
+ */
+export function ConversationDivider({
+ className,
+ icon,
+ label,
+ testId,
+ title,
+}: {
+ className?: string;
+ icon?: React.ReactNode;
+ label: string;
+ testId?: string;
+ title?: string;
+}) {
+ return (
+
+
+
+ {icon}
+ {label}
+
+
+
+ );
+}
diff --git a/desktop/src/features/agents/ui/activityRenderClasses/LifecycleActivity.tsx b/desktop/src/features/agents/ui/activityRenderClasses/LifecycleActivity.tsx
index 53a00e64638..43598315b90 100644
--- a/desktop/src/features/agents/ui/activityRenderClasses/LifecycleActivity.tsx
+++ b/desktop/src/features/agents/ui/activityRenderClasses/LifecycleActivity.tsx
@@ -1,7 +1,9 @@
import { AlertCircle, CheckCircle2, ShieldCheck, XCircle } from "lucide-react";
+import { useAgentSessionTranscriptVariant } from "../agentSessionTranscriptContext";
import { formatTranscriptTimestampTitle } from "../agentSessionUtils";
import { ActivityRow, ActivityRowLabel } from "./ActivityRow";
+import { ConversationDivider } from "./ConversationDivider";
import { ToolActivity } from "./ToolActivity";
import type { ActivityRenderClassItemProps } from "./types";
@@ -118,12 +120,45 @@ export function LifecycleActivity(props: ActivityRenderClassItemProps) {
);
}
+ return (
+
+ );
+}
+
+/**
+ * Plain lifecycle status ("Turn started", "Context compacted") — structure, not
+ * work. Focus mode recedes it to a quiet centered divider so the reader's eye
+ * stays on the prompt/response rhythm; errors and permission gates never reach
+ * here and keep their loud treatment.
+ *
+ * Split into its own component so `LifecycleActivity` stays hook-free above its
+ * early returns.
+ */
+function LifecycleStatusRow({
+ item,
+ timestampTitle,
+}: {
+ item: Extract;
+ timestampTitle: string | undefined;
+}) {
+ const variant = useAgentSessionTranscriptVariant();
+
+ if (variant === "conversation") {
+ return (
+
+ );
+ }
+
return (
);
diff --git a/desktop/src/features/agents/ui/activityRenderClasses/MessageActivity.tsx b/desktop/src/features/agents/ui/activityRenderClasses/MessageActivity.tsx
index 00651b3525d..b819050b528 100644
--- a/desktop/src/features/agents/ui/activityRenderClasses/MessageActivity.tsx
+++ b/desktop/src/features/agents/ui/activityRenderClasses/MessageActivity.tsx
@@ -28,6 +28,7 @@ function MessageItem({
}) {
const variant = useAgentSessionTranscriptVariant();
const isCompactPreview = variant === "compactPreview";
+ const isConversation = variant === "conversation";
const isAssistant = item.role === "assistant";
const text = item.text.trim();
const messageLink = getTranscriptMessageLink(item);
@@ -63,7 +64,16 @@ function MessageItem({
title={formatTranscriptTimestampTitle(item.timestamp)}
>
diff --git a/desktop/src/features/agents/ui/activityRenderClasses/PlanActivity.tsx b/desktop/src/features/agents/ui/activityRenderClasses/PlanActivity.tsx
index 334a1b0807f..59c1cca1b3c 100644
--- a/desktop/src/features/agents/ui/activityRenderClasses/PlanActivity.tsx
+++ b/desktop/src/features/agents/ui/activityRenderClasses/PlanActivity.tsx
@@ -1,3 +1,6 @@
+import { Check, ListChecks, Loader } from "lucide-react";
+
+import { cn } from "@/shared/lib/cn";
import { Markdown } from "@/shared/ui/markdown";
import {
ActivityRow,
@@ -5,6 +8,12 @@ import {
ActivityRowLabel,
} from "./ActivityRow";
import { ToolActivity } from "./ToolActivity";
+import {
+ formatPlanChecklistProgress,
+ parsePlanChecklist,
+ type PlanChecklistEntry,
+} from "../agentSessionPlanChecklist";
+import { useAgentSessionTranscriptVariant } from "../agentSessionTranscriptContext";
import { formatTranscriptTimestampTitle } from "../agentSessionUtils";
import type { ActivityRenderClassItemProps } from "./types";
@@ -16,14 +25,27 @@ export function PlanActivity(props: ActivityRenderClassItemProps) {
return null;
}
- if (props.item.isUpdate) {
+ return