group activity entries by plan step + remove inline renderers from chat
- Tool calls in Activity timeline are now tagged with current plan step - Indented entries show which step they belong to - Step tracking via update_plan_step(status=running) tool calls - Removed inline tool renderers from chat (health summary, fleet snapshot, etc.) — all tool output now visible only in sidebar Activity timeline
This commit is contained in:
@@ -59,7 +59,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-start gap-2 px-3 py-1.5 text-left text-xs hover:bg-muted/30 {hasDetail ? 'cursor-pointer' : 'cursor-default'}"
|
class="flex w-full items-start gap-2 {entry.indent ? 'pl-7' : 'px-3'} py-1.5 text-left text-xs hover:bg-muted/30 {hasDetail ? 'cursor-pointer' : 'cursor-default'}"
|
||||||
onclick={() => hasDetail && toggle(entry.id)}
|
onclick={() => hasDetail && toggle(entry.id)}
|
||||||
>
|
>
|
||||||
<!-- status icon -->
|
<!-- status icon -->
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export interface ActivityEntry {
|
|||||||
detail?: string
|
detail?: string
|
||||||
timestamp: number
|
timestamp: number
|
||||||
toolName?: string
|
toolName?: string
|
||||||
|
stepSeq?: number
|
||||||
|
indent?: boolean
|
||||||
status: 'running' | 'done' | 'failed'
|
status: 'running' | 'done' | 'failed'
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,11 +41,23 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool calls (from messages)
|
// Tool calls (from messages). Tag each tool with the plan step that's
|
||||||
|
// currently running when it fires.
|
||||||
|
let currentStepSeq = 0
|
||||||
let entryIdx = 0
|
let entryIdx = 0
|
||||||
for (let mi = 0; mi < $msgs.length; mi++) {
|
for (let mi = 0; mi < $msgs.length; mi++) {
|
||||||
for (const t of $msgs[mi].tools) {
|
for (const t of $msgs[mi].tools) {
|
||||||
|
// Track current step from update_plan_step calls
|
||||||
|
if (t.type === 'tool_use' && t.name === 'update_plan_step') {
|
||||||
|
const s = (t.args as any)?.seq as number | undefined
|
||||||
|
const status = (t.args as any)?.status as string | undefined
|
||||||
|
if (s && status === 'running') currentStepSeq = s
|
||||||
|
} else if (t.name === 'set_goal' || t.name === 'propose_plan' || t.name === 'complete_task') {
|
||||||
|
currentStepSeq = 0
|
||||||
|
}
|
||||||
|
|
||||||
const label = toolActivityLabel(t)
|
const label = toolActivityLabel(t)
|
||||||
|
const stepTag = currentStepSeq > 0 ? currentStepSeq : undefined
|
||||||
if (t.type === 'tool_use') {
|
if (t.type === 'tool_use') {
|
||||||
entries.push({
|
entries.push({
|
||||||
id: t.id ?? `tool_${mi}_${entryIdx++}`,
|
id: t.id ?? `tool_${mi}_${entryIdx++}`,
|
||||||
@@ -51,10 +65,11 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
|||||||
description: label,
|
description: label,
|
||||||
timestamp: now - ($msgs.length - mi) * 1000,
|
timestamp: now - ($msgs.length - mi) * 1000,
|
||||||
toolName: t.name,
|
toolName: t.name,
|
||||||
|
stepSeq: stepTag,
|
||||||
|
indent: stepTag != null,
|
||||||
status: 'running'
|
status: 'running'
|
||||||
})
|
})
|
||||||
} else if (t.type === 'tool_result') {
|
} else if (t.type === 'tool_result') {
|
||||||
// Find and update matching tool_use entry
|
|
||||||
const running = entries.find((e) =>
|
const running = entries.find((e) =>
|
||||||
e.type === 'tool_running' && e.id === t.id && e.status === 'running'
|
e.type === 'tool_running' && e.id === t.id && e.status === 'running'
|
||||||
)
|
)
|
||||||
@@ -76,6 +91,8 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
|
|||||||
detail: !t.error ? (typeof t.result === 'string' ? t.result.slice(0, 200) : '') : undefined,
|
detail: !t.error ? (typeof t.result === 'string' ? t.result.slice(0, 200) : '') : undefined,
|
||||||
timestamp: now - ($msgs.length - mi) * 1000,
|
timestamp: now - ($msgs.length - mi) * 1000,
|
||||||
toolName: t.name,
|
toolName: t.name,
|
||||||
|
stepSeq: stepTag,
|
||||||
|
indent: stepTag != null,
|
||||||
status: t.error ? 'failed' : 'done'
|
status: t.error ? 'failed' : 'done'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
import TaskContextPanel from '$lib/components/TaskContextPanel.svelte'
|
import TaskContextPanel from '$lib/components/TaskContextPanel.svelte'
|
||||||
import InlineApproval from '$lib/components/InlineApproval.svelte'
|
import InlineApproval from '$lib/components/InlineApproval.svelte'
|
||||||
import AgentIndicator from '$lib/components/AgentIndicator.svelte'
|
import AgentIndicator from '$lib/components/AgentIndicator.svelte'
|
||||||
import { getToolRenderer } from '$lib/tool-renderers'
|
|
||||||
import { Button } from '$lib/components/ui/button'
|
import { Button } from '$lib/components/ui/button'
|
||||||
import { Textarea } from '$lib/components/ui/textarea'
|
import { Textarea } from '$lib/components/ui/textarea'
|
||||||
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up'
|
import ArrowUpIcon from '@lucide/svelte/icons/arrow-up'
|
||||||
@@ -45,14 +44,6 @@
|
|||||||
scrolledUp = false
|
scrolledUp = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const MAX_INLINE_CARDS = 5
|
|
||||||
|
|
||||||
function getInlineTools(msg: { tools: any[] }): any[] {
|
|
||||||
const matched = msg.tools.filter((t) => getToolRenderer(t))
|
|
||||||
if (matched.length <= MAX_INLINE_CARDS) return matched
|
|
||||||
return matched.slice(0, MAX_INLINE_CARDS)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resizable right rail (session graph). Persisted so it survives reloads.
|
// Resizable right rail (session graph). Persisted so it survives reloads.
|
||||||
const RAIL_MIN = 260
|
const RAIL_MIN = 260
|
||||||
const RAIL_MAX = 620
|
const RAIL_MAX = 620
|
||||||
@@ -147,12 +138,6 @@
|
|||||||
<div class="max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-4 py-2.5 text-sm text-primary-foreground whitespace-pre-wrap">{msg.text}</div>
|
<div class="max-w-[85%] rounded-2xl rounded-br-sm bg-primary px-4 py-2.5 text-sm text-primary-foreground whitespace-pre-wrap">{msg.text}</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex w-full flex-col gap-2">
|
<div class="flex w-full flex-col gap-2">
|
||||||
{#each getInlineTools(msg) as tool (tool.id)}
|
|
||||||
{@const renderer = getToolRenderer(tool)}
|
|
||||||
{#if renderer}
|
|
||||||
<renderer.component {tool} />
|
|
||||||
{/if}
|
|
||||||
{/each}
|
|
||||||
{#if msg.text}
|
{#if msg.text}
|
||||||
<div class="prose-chat max-w-none text-sm leading-relaxed">
|
<div class="prose-chat max-w-none text-sm leading-relaxed">
|
||||||
<!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized via DOMPurify -->
|
<!-- eslint-disable-next-line svelte/no-at-html-tags — sanitized via DOMPurify -->
|
||||||
|
|||||||
Reference in New Issue
Block a user