<!-- ABOUTME: Expandable inline view of a subagent's conversation.
ABOUTME: Lazily loads and renders subagent messages within a parent ToolBlock. -->
<script lang="ts">
import type { Message, Session } from "../../api/types.js";
import { getMessages, getSession } from "../../api/client.js";
import { formatTokenCount } from "../../utils/format.js";
import { computeMainModel } from "../../utils/model.js";
import { sessions } from "../../stores/sessions.svelte.js";
import { router } from "../../stores/router.svelte.js";
import { context } from "../../stores/context.svelte.js";
import MessageContent from "./MessageContent.svelte";
interface Props {
sessionId: string;
}
let { sessionId }: Props = $props();
let expanded = $state(false);
let messages = $state<Message[] | null>(null);
let sessionMeta = $state<Session | null>(null);
let loading = $state(false);
let error = $state<string | null>(null);
let subagentSession = $derived(sessions.childSessions.get(sessionId) ?? null);
async function toggleExpand() {
expanded = !expanded;
if (expanded && !messages) {
loading = true;
error = null;
try {
const [resp, meta] = await Promise.all([
getMessages(sessionId, { limit: 1000 }),
getSession(sessionId).catch(() => null),
]);
messages = resp.messages;
sessionMeta = meta;
} catch (e) {
error = e instanceof Error ? e.message : "Failed to load";
} finally {
loading = false;
}
}
}
async function openAsSession(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
void context.openSession(sessionId, false);
}
let agentLabel = $derived(sessionMeta?.agent ?? null);
let messageCountLabel = $derived(
sessionMeta ? `${sessionMeta.message_count} messages` : null,
);
let subagentModel = $derived(
messages && sessionMeta &&
messages.length >= sessionMeta.message_count
? computeMainModel(messages)
: "",
);
</script>
<div class="subagent-inline">
<div class="subagent-header">
<button class="subagent-toggle" onclick={toggleExpand}>
<span class="toggle-chevron" class:open={expanded}>▸</span>
<span class="toggle-label">Subagent session</span>
{#if agentLabel}
<span class="toggle-meta">{agentLabel}</span>
{/if}
{#if messageCountLabel}
<span class="toggle-meta">{messageCountLabel}</span>
{/if}
<span class="toggle-session-id">{sessionId.slice(0, 12)}</span>
{#if subagentSession}
{@const ctxTokens = subagentSession.peak_context_tokens}
{#if ctxTokens + subagentSession.total_output_tokens > 0}
<span class="toggle-tokens">({formatTokenCount(ctxTokens)} ctx / {formatTokenCount(subagentSession.total_output_tokens)} out)</span>
{/if}
{#if subagentModel}
<span class="toggle-model" title={subagentModel}>{subagentModel}</span>
{/if}
{/if}
</button>
<a
href={router.href({ level: "session", session: sessionId })}
class="open-session-link"
onclick={openAsSession}
title="Open as full session"
>
Open session ↗
</a>
</div>
{#if expanded}
<div class="subagent-messages">
{#if loading}
<div class="subagent-status">Loading...</div>
{:else if error}
<div class="subagent-status subagent-error">{error}</div>
{:else if messages && messages.length > 0}
{#each messages as message}
<MessageContent {message} isSubagentContext={true} />
{/each}
{:else if messages}
<div class="subagent-status">No messages</div>
{/if}
</div>
{/if}
</div>
<style>
.subagent-inline {
border-top: 1px solid var(--vscode-widget-border);
margin-top: var(--wb-spacing-2);
}
.subagent-header {
display: flex;
align-items: center;
}
.subagent-toggle {
display: flex;
align-items: center;
gap: var(--wb-spacing-6);
padding: var(--wb-spacing-6) var(--wb-spacing-10);
flex: 1;
min-width: 0;
text-align: left;
font-size: var(--wb-font-size-11);
color: var(--vscode-testing-iconPassed);
border-radius: 0 0 0 var(--wsb-radius-sm);
transition: background 0.1s;
}
.subagent-toggle:hover {
background: var(--vscode-list-hoverBackground);
}
.toggle-chevron {
display: inline-block;
font-size: var(--wb-font-size-10);
transition: transform 0.15s;
flex-shrink: 0;
}
.toggle-chevron.open {
transform: rotate(90deg);
}
.toggle-label {
font-weight: 600;
white-space: nowrap;
}
.toggle-meta {
font-family: var(--wsb-font-mono);
font-size: var(--wb-font-size-10);
color: var(--vscode-disabledForeground);
background: var(--vscode-input-background);
padding: var(--wb-spacing-1) var(--wb-spacing-5);
border-radius: var(--wsb-radius-sm);
white-space: nowrap;
}
.toggle-session-id {
font-family: var(--wsb-font-mono);
font-size: var(--wb-font-size-10);
color: var(--vscode-disabledForeground);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 0;
}
.open-session-link {
font-size: var(--wb-font-size-10);
color: var(--vscode-foreground);
padding: var(--wb-spacing-6) var(--wb-spacing-10);
white-space: nowrap;
flex-shrink: 0;
text-decoration: none;
transition: color 0.1s, background 0.1s;
}
.open-session-link:hover {
color: var(--vscode-testing-iconPassed);
background: var(--vscode-list-hoverBackground);
}
.toggle-tokens {
font-size: var(--wb-font-size-10);
font-variant-numeric: tabular-nums;
color: color-mix(in srgb, var(--vscode-testing-iconPassed) 60%, var(--vscode-disabledForeground));
white-space: nowrap;
flex-shrink: 0;
}
.toggle-model {
font-size: var(--wb-font-size-10);
color: var(--vscode-disabledForeground);
white-space: nowrap;
flex-shrink: 0;
}
.subagent-messages {
border-left: 3px solid var(--vscode-testing-iconPassed);
margin: 0 0 var(--wb-spacing-4) var(--wb-spacing-10);
display: flex;
flex-direction: column;
gap: var(--wb-spacing-4);
padding: var(--wb-spacing-4) 0;
}
.subagent-status {
padding: var(--wb-spacing-8) var(--wb-spacing-14);
font-size: var(--wb-font-size-12);
color: var(--vscode-disabledForeground);
}
.subagent-error {
color: var(--vscode-errorForeground);
}
</style>