Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
584 changes: 0 additions & 584 deletions apps/web/src/components/AgentsPanel.tsx

This file was deleted.

36 changes: 0 additions & 36 deletions apps/web/src/components/ChatView.tsx
Comment thread
maria-rcks marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -404,11 +404,6 @@ import {
import { expandedImageKey, type ExpandedImagePreview } from "./chat/ExpandedImagePreview";
import { ThreadDetailsPanel, type ThreadDetailsPanelProps } from "./chat/ThreadDetailsPanel";
import { NoActiveThreadState } from "./NoActiveThreadState";
import { AgentsPanel } from "./AgentsPanel";
import {
deriveAgentPanelModel,
projectedSubagentsToRuntime,
} from "@t3tools/client-runtime/state/subagentRuntime";
import {
type EnvironmentOption,
resolveEffectiveEnvMode,
Expand Down Expand Up @@ -1617,17 +1612,6 @@ export default function ChatView(props: ChatViewProps) {
}
return null;
}, [serverProjection?.providerTurns]);
// Agents surface (#5219): on orchestration-v2 the panel model comes from the
// projected subagent entities — the v2 leg of the spec's mapper swap. The
// native-activity fold never runs on this branch.
const agentPanelModel = useMemo(
() =>
deriveAgentPanelModel({
agents: [],
v2Projection: projectedSubagentsToRuntime(serverProjection?.subagents ?? []),
}),
[serverProjection?.subagents],
);
const serverVisibleTurnItems = useThreadVisibleTurnItems(routeThreadDetailRef);
const serverThreadHistory = useThreadHistory(routeThreadDetailRef);
const threadHistoryControls = useMemo<MessagesTimelineHistoryControls | undefined>(() => {
Expand Down Expand Up @@ -4932,10 +4916,6 @@ export default function ChatView(props: ChatViewProps) {
},
[activeThreadRef, openPreview],
);
const addAgentsSurface = useCallback(() => {
if (!activeThreadRef) return;
useRightPanelStore.getState().open(activeThreadRef, "agents");
}, [activeThreadRef]);
const addDiffSurface = useCallback(() => {
if (!activeThreadRef || !isServerThread || !isGitRepo) return;
useDiffPanelStore.getState().selectGitScope(activeThreadRef, "unstaged");
Expand Down Expand Up @@ -10103,12 +10083,6 @@ export default function ChatView(props: ChatViewProps) {
/>
) : renderedRightPanelSurface?.kind === "pull-requests" && activeThreadRef ? (
<ThreadPullRequestsPanel threadRef={activeThreadRef} />
) : renderedRightPanelSurface?.kind === "agents" ? (
<AgentsPanel
model={agentPanelModel}
environmentId={activeThreadRef?.environmentId ?? null}
threadId={activeThreadRef?.threadId ?? null}
/>
) : renderedRightPanelSurface?.kind === "device" ? (
<Suspense fallback={null}>
<DevicePanel
Expand Down Expand Up @@ -10246,10 +10220,6 @@ export default function ChatView(props: ChatViewProps) {
rightPanelAvailable: activeProject !== null,
rightPanelOpen,
rightPanelShortcutLabel: shortcutLabelForCommand(keybindings, "rightPanel.toggle"),
// Suppressed while the Agents surface is visible: the roster itself is
// on screen, so the toggle badge would be pointing at nothing.
liveAgentCount:
rightPanelOpen && activeRightPanelSurface?.kind === "agents" ? 0 : agentPanelModel.liveCount,
onToggleTerminal: toggleTerminalVisibility,
onToggleThreadPanel: toggleThreadPanel,
onToggleRightPanel: toggleRightPanel,
Expand Down Expand Up @@ -10949,17 +10919,14 @@ export default function ChatView(props: ChatViewProps) {
onAddFiles={addFilesSurface}
onAddPullRequest={addPullRequestSurface}
onAddPullRequests={addPullRequestsSurface}
onAddAgents={addAgentsSurface}
onAddDevice={addDeviceSurface}
browserAvailable={isPreviewSupportedInRuntime()}
terminalAvailable={activeProject !== null}
diffAvailable={isServerThread && isGitRepo}
filesAvailable={activeProject !== null}
pullRequestAvailable={pullRequestSurfaceAvailable}
pullRequestsAvailable={pullRequestsSurfaceAvailable}
agentsAvailable
deviceAvailable={activeThreadRef !== null}
liveAgentCount={agentPanelModel.liveCount}
>
{rightPanelContent}
</RightPanelTabs>
Expand Down Expand Up @@ -11007,17 +10974,14 @@ export default function ChatView(props: ChatViewProps) {
onAddFiles={addFilesSurface}
onAddPullRequest={addPullRequestSurface}
onAddPullRequests={addPullRequestsSurface}
onAddAgents={addAgentsSurface}
onAddDevice={addDeviceSurface}
browserAvailable={isPreviewSupportedInRuntime()}
terminalAvailable={activeProject !== null}
diffAvailable={isServerThread && isGitRepo}
filesAvailable={activeProject !== null}
pullRequestAvailable={pullRequestSurfaceAvailable}
pullRequestsAvailable={pullRequestsSurfaceAvailable}
agentsAvailable
deviceAvailable={activeThreadRef !== null}
liveAgentCount={agentPanelModel.liveCount}
>
{rightPanelContent}
</RightPanelTabs>
Expand Down
3 changes: 0 additions & 3 deletions apps/web/src/components/RightPanelTabs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,13 @@ function renderTabs(
onAddPullRequests={() => undefined}
onAddDiff={() => undefined}
onAddFiles={() => undefined}
onAddAgents={() => undefined}
onAddDevice={() => undefined}
liveAgentCount={0}
browserAvailable
terminalAvailable={false}
diffAvailable={false}
filesAvailable={false}
pullRequestAvailable={false}
pullRequestsAvailable={false}
agentsAvailable={false}
deviceAvailable={false}
>
<div>content</div>
Expand Down
49 changes: 0 additions & 49 deletions apps/web/src/components/RightPanelTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
} from "@t3tools/contracts";
import { getTerminalLabel } from "@t3tools/shared/terminalLabels";
import {
Bot,
Smartphone,
ChevronDown,
ChevronLeft,
Expand Down Expand Up @@ -122,19 +121,15 @@ interface RightPanelTabsProps {
onAddFiles: () => void;
onAddPullRequest: () => void;
onAddPullRequests: () => void;
onAddAgents: () => void;
onAddDevice: () => void;
browserAvailable: boolean;
terminalAvailable: boolean;
diffAvailable: boolean;
filesAvailable: boolean;
pullRequestAvailable: boolean;
pullRequestsAvailable: boolean;
agentsAvailable: boolean;
deviceAvailable: boolean;
pullRequestStatusSeeds?: Readonly<Record<string, PullRequestTabStatusSeed>>;
/** Running + waiting subagents; badges the Agents card in the empty state. */
liveAgentCount: number;
children: ReactNode;
}

Expand All @@ -161,7 +156,6 @@ const SURFACE_DISABLED_REASONS = {
diff: "Diff is only available for server threads in Git repositories.",
pullRequest: "This thread's branch has no pull request yet.",
pullRequests: "No linked pull requests are available for this thread.",
agents: "Agents are only available from a thread.",
device: "Devices are only available from a thread.",
} as const;

Expand All @@ -185,7 +179,6 @@ const SURFACE_UNAVAILABLE_HINTS = {
diff: "Available for Git repositories.",
pullRequest: "No pull request on this branch yet.",
pullRequests: "No linked pull requests available.",
agents: "Available from a thread.",
device: "Available from a thread.",
} as const;

Expand Down Expand Up @@ -325,17 +318,14 @@ function RightPanelEmptyState(props: {
onAddFiles: () => void;
onAddPullRequest: () => void;
onAddPullRequests: () => void;
onAddAgents: () => void;
onAddDevice: () => void;
browserAvailable: boolean;
terminalAvailable: boolean;
diffAvailable: boolean;
filesAvailable: boolean;
pullRequestAvailable: boolean;
pullRequestsAvailable: boolean;
agentsAvailable: boolean;
deviceAvailable: boolean;
liveAgentCount: number;
}) {
// -1 means no highlight: it only appears on hover or arrow use.
const [highlight, setHighlight] = useState(-1);
Expand All @@ -348,7 +338,6 @@ function RightPanelEmptyState(props: {
available: props.browserAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.browser,
onClick: props.onAddBrowser,
badgeCount: 0,
},
{
label: "Terminal",
Expand All @@ -357,7 +346,6 @@ function RightPanelEmptyState(props: {
available: props.terminalAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.terminal,
onClick: props.onAddTerminal,
badgeCount: 0,
},
{
label: "Files",
Expand All @@ -366,7 +354,6 @@ function RightPanelEmptyState(props: {
available: props.filesAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.files,
onClick: props.onAddFiles,
badgeCount: 0,
},
{
label: "Diff",
Expand All @@ -375,7 +362,6 @@ function RightPanelEmptyState(props: {
available: props.diffAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.diff,
onClick: props.onAddDiff,
badgeCount: 0,
},
{
label: "Pull request",
Expand All @@ -384,7 +370,6 @@ function RightPanelEmptyState(props: {
available: props.pullRequestAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.pullRequest,
onClick: props.onAddPullRequest,
badgeCount: 0,
},
{
label: "Linked pull requests",
Expand All @@ -393,16 +378,6 @@ function RightPanelEmptyState(props: {
available: props.pullRequestsAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.pullRequests,
onClick: props.onAddPullRequests,
badgeCount: 0,
},
{
label: "Agents",
icon: Bot,
shortcut: "A",
available: props.agentsAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.agents,
onClick: props.onAddAgents,
badgeCount: props.liveAgentCount,
},
{
label: "Device",
Expand All @@ -412,7 +387,6 @@ function RightPanelEmptyState(props: {
available: props.deviceAvailable,
disabledReason: SURFACE_UNAVAILABLE_HINTS.device,
onClick: props.onAddDevice,
badgeCount: 0,
},
] as const;

Expand Down Expand Up @@ -486,14 +460,6 @@ function RightPanelEmptyState(props: {
return (
<span className="relative inline-flex shrink-0">
<Icon className={iconClassName} />
{action.badgeCount > 0 ? (
<span
aria-hidden
className="absolute -top-1.5 -right-2 flex h-3.5 min-w-3.5 items-center justify-center rounded-full bg-info px-1 text-[9px] font-semibold tabular-nums text-white"
>
{action.badgeCount}
</span>
) : null}
</span>
);
};
Expand Down Expand Up @@ -634,8 +600,6 @@ function surfaceTitle(
return `#${surface.number}`;
case "pull-requests":
return "Pull requests";
case "agents":
return "Agents";
case "device":
return surface.title ?? surface.target?.name ?? "Device";
case "preview": {
Expand Down Expand Up @@ -719,8 +683,6 @@ function SurfaceIcon({
);
case "pull-requests":
return <PullRequestGlyph.link className="size-3 shrink-0" />;
case "agents":
return <Bot className="size-3 shrink-0" />;
case "device":
return surface.target?.platform === "ios" ? (
<AppleIcon className="size-3 shrink-0" />
Expand Down Expand Up @@ -923,14 +885,6 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
disabledReason: SURFACE_DISABLED_REASONS.pullRequests,
onClick: props.onAddPullRequests,
},
{
label: "Agents",
icon: Bot,
shortcut: "A",
available: props.agentsAvailable,
disabledReason: SURFACE_DISABLED_REASONS.agents,
onClick: props.onAddAgents,
},
{
label: "Device",
icon: Smartphone,
Expand Down Expand Up @@ -1410,17 +1364,14 @@ export function RightPanelTabs(props: RightPanelTabsProps) {
onAddFiles={props.onAddFiles}
onAddPullRequest={props.onAddPullRequest}
onAddPullRequests={props.onAddPullRequests}
onAddAgents={props.onAddAgents}
onAddDevice={props.onAddDevice}
browserAvailable={props.browserAvailable}
terminalAvailable={props.terminalAvailable}
diffAvailable={props.diffAvailable}
filesAvailable={props.filesAvailable}
pullRequestAvailable={props.pullRequestAvailable}
pullRequestsAvailable={props.pullRequestsAvailable}
agentsAvailable={props.agentsAvailable}
deviceAvailable={props.deviceAvailable}
liveAgentCount={props.liveAgentCount}
/>
) : (
props.children
Expand Down
57 changes: 57 additions & 0 deletions apps/web/src/components/chat/AgentElapsed.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { RuntimeSubagent } from "@t3tools/client-runtime/state/subagentRuntime";
import { useEffect, useRef } from "react";

function formatElapsedSeconds(totalSeconds: number): string {
const seconds = Math.max(0, Math.floor(totalSeconds));
const minutes = Math.floor(seconds / 60);
if (minutes === 0) {
return `${seconds}s`;
}
const hours = Math.floor(minutes / 60);
if (hours === 0) {
return `${minutes}m ${String(seconds % 60).padStart(2, "0")}s`;
}
return `${hours}h ${String(minutes % 60).padStart(2, "0")}m`;
}

function elapsedBetween(startedAt: string, endIso: string | null): string {
const start = Date.parse(startedAt);
const end = endIso ? Date.parse(endIso) : Date.now();
if (Number.isNaN(start) || Number.isNaN(end)) {
return "";
}
return formatElapsedSeconds((end - start) / 1000);
}

/**
* Elapsed time for the current activation. Live agents self-tick via DOM
* writes (zero React commits per tick); settled agents freeze at completedAt.
*/
export function AgentElapsed({ agent }: { agent: RuntimeSubagent }) {
const textRef = useRef<HTMLSpanElement>(null);
const live = agent.status === "running" || agent.status === "waiting";
const startedAt = agent.startedAt;

useEffect(() => {
if (!live || !startedAt) {
return;
}
const update = () => {
if (textRef.current) {
textRef.current.textContent = elapsedBetween(startedAt, null);
}
};
update();
const id = setInterval(update, 1000);
return () => clearInterval(id);
}, [live, startedAt]);

if (!startedAt) {
return null;
}
return (
<span ref={textRef} className="tabular-nums">
{elapsedBetween(startedAt, live ? null : agent.completedAt)}
</span>
);
}
Loading
Loading