fix: move the agent meta line and system prompt below the conversation

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-17 10:07:09 +02:00
co-authored by Claude Opus 5
parent 92bafaaf5e
commit 7f7bf72da8
4 changed files with 49 additions and 65 deletions
@@ -1,5 +1,4 @@
<script lang="ts">
import { Bot } from 'lucide-svelte'
import type { Snippet } from 'svelte'
import { Badge } from '$lib/components/common'
import GfmMarkdown from './GfmMarkdown.svelte'
@@ -27,11 +26,26 @@
let textOutput = $derived(typeof result.output === 'string' ? result.output : undefined)
</script>
<!-- pt-2 clears the toggle group and control bar the result viewer puts directly
above this, which otherwise sit on the meta line. -->
<div class="flex flex-col gap-2 w-full pt-2">
<div class="flex flex-col gap-2 w-full pt-1">
{#if view === 'transcript'}
<AgentTranscript messages={result.messages} {workspaceId} />
{:else if textOutput !== undefined}
{#if textOutput === ''}
<span class="text-tertiary text-xs">The agent returned no answer</span>
{:else}
<!-- A model writes this answer, and what it writes is steerable by whatever
reached its context — a user message, a tool's output. So it is
untrusted input and goes through the shared sanitizing chain, which is
also what makes it inert on the public replay page. -->
<GfmMarkdown md={textOutput} noPadding />
{/if}
{:else}
{@render structuredOutput(result.output)}
{/if}
<!-- What the run cost sits under what it produced: it is the footnote to the
answer, not the heading above it. -->
<div class="flex items-center gap-2 flex-wrap text-xs">
<Bot size={14} class="text-tertiary shrink-0" />
{#if summary.toolCalls > 0}
<Badge color="blue">
{summary.toolCalls}
@@ -51,20 +65,4 @@
<Badge color="gray">{formatTokenCount(summary.cachedTokens)} cached</Badge>
{/if}
</div>
{#if view === 'transcript'}
<AgentTranscript messages={result.messages} {workspaceId} />
{:else if textOutput !== undefined}
{#if textOutput === ''}
<span class="text-tertiary text-xs">The agent returned no answer</span>
{:else}
<!-- A model writes this answer, and what it writes is steerable by whatever
reached its context — a user message, a tool's output. So it is
untrusted input and goes through the shared sanitizing chain, which is
also what makes it inert on the public replay page. -->
<GfmMarkdown md={textOutput} noPadding />
{/if}
{:else}
{@render structuredOutput(result.output)}
{/if}
</div>
@@ -19,8 +19,15 @@
let { messages, workspaceId }: Props = $props()
let entries = $derived(buildTranscript(messages))
const all = $derived(buildTranscript(messages))
// The prompt is the run's configuration, not its opening line: it is the same
// on every run of the step, and reading the conversation from it buries what
// the user actually asked. Kept reachable, at the end.
const entries = $derived(all.filter((entry) => entry.kind !== 'system'))
const systemPrompt = $derived(all.find((entry) => entry.kind === 'system'))
// Out of the entry index space, since the prompt is rendered outside the list.
const SYSTEM_PROMPT_KEY = -1
let expanded = new SvelteSet<number>()
// A tool's own job holds what the envelope does not: its logs, how long it
// took, and whether it succeeded. Fetched when a row is opened rather than
@@ -89,16 +96,6 @@
</div>
{/if}
</div>
{:else if entry.kind === 'system'}
<div class="px-2 mb-1">
<ChatCollapsibleCard
label="System prompt"
expanded={expanded.has(index)}
onToggle={() => toggle(index, entry)}
>
<div class="whitespace-pre-wrap text-2xs text-primary">{entry.content}</div>
</ChatCollapsibleCard>
</div>
{:else if entry.kind === 'search'}
<div class="px-2 mb-1">
<ChatCollapsibleCard
@@ -153,4 +150,15 @@
</div>
{/if}
{/each}
{#if systemPrompt}
<div class="px-2 pt-2">
<ChatCollapsibleCard
label="System prompt"
expanded={expanded.has(SYSTEM_PROMPT_KEY)}
onToggle={() => toggle(SYSTEM_PROMPT_KEY, systemPrompt)}
>
<div class="whitespace-pre-wrap text-2xs text-primary">{systemPrompt.content}</div>
</ChatCollapsibleCard>
</div>
{/if}
</div>
@@ -3,8 +3,6 @@
import DisplayResult from './DisplayResult.svelte'
import LogViewer from './LogViewer.svelte'
import { twMerge } from 'tailwind-merge'
import AgentTranscript from './AgentTranscript.svelte'
import { parseAgentResult } from './aiAgentResult'
interface Props {
waitingForExecutor?: boolean
@@ -36,10 +34,6 @@
downloadLogs = true,
tagLabel = undefined
}: Props = $props()
// An agent step's own logs are worker chatter; what happened is its conversation.
// Derived from the result rather than passed in, so every caller gets it.
let agentResult = $derived(parseAgentResult(result))
</script>
<div
@@ -65,22 +59,16 @@
</div>
</div>
<div class="relative flex flex-col gap-1">
<span class="text-emphasis text-xs font-semibold">{agentResult ? 'Transcript' : 'Logs'}</span>
{#if agentResult}
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-auto p-2">
<AgentTranscript messages={agentResult.messages} {workspaceId} />
</div>
{:else}
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-hidden">
<LogViewer
{tagLabel}
download={downloadLogs}
content={logs ?? ''}
{jobId}
isLoading={waitingForExecutor}
{tag}
/>
</div>
{/if}
<span class="text-emphasis text-xs font-semibold">Logs</span>
<div class="rounded-md grow min-h-0 border bg-surface-tertiary overflow-hidden">
<LogViewer
{tagLabel}
download={downloadLogs}
content={logs ?? ''}
{jobId}
isLoading={waitingForExecutor}
{tag}
/>
</div>
</div>
</div>
@@ -10,8 +10,6 @@
import type { FlowEditorContext, OutputViewerJob } from './flows/types'
import { getContext } from 'svelte'
import { getStringError } from './copilot/chat/utils'
import AgentTranscript from './AgentTranscript.svelte'
import { parseAgentResult } from './aiAgentResult'
interface Props {
lang: Script['language']
@@ -57,10 +55,6 @@
)
const logJob = $derived(testJob ?? selectedJob)
const preview = $derived.by(() => outputPickerInner?.getPreview?.())
// The trace of an agent step is its conversation, which its own result carries.
const agentResult = $derived(
logJob?.type === 'CompletedJob' ? parseAgentResult(logJob.result) : undefined
)
</script>
<Splitpanes horizontal>
@@ -105,10 +99,6 @@
customEmptyMessage="Using pinned data"
{tagLabel}
/>
{:else if agentResult}
<div class="h-full overflow-auto p-2">
<AgentTranscript messages={agentResult.messages} workspaceId={logJob?.workspace_id} />
</div>
{:else}
<LogViewer
small