-
-
{$currentTask.goal}
+
+
+ {$currentTask.goal}
{/if}
{#if planTotal > 0}
-
-
{planDone}/{planTotal} done
+
+
+ {planDone} of {planTotal} done
+ {planPct}%
+
+
-
-
- {#each $planSteps as step (step.id)}
- -
-
- {#if step.status === 'done'}
-
- {:else if step.status === 'running'}
-
+
+ {#each $planSteps as step, i (step.id)}
+ {@const isDone = step.status === 'done'}
+ {@const isRunning = step.status === 'running'}
+ -
+ {#if i < $planSteps.length - 1}
+
+ {/if}
+
+ {#if isDone}
+
+ {:else if isRunning}
+
{:else if step.status === 'failed'}
-
+
+ {:else if step.status === 'blocked'}
+
+ {:else if step.status === 'skipped' || step.status === 'replaced'}
+
{:else}
-
+
{/if}
- {step.title}
+ {step.title}
{/each}
+ {:else if planPhase === 'drafting'}
+
+
+
Drafting a plan…
+
{:else}
-
-
-
Awaiting plan…
+
+
+
+ {planPhase === 'none' ? 'Handled directly — no plan needed' : 'No plan for this task yet'}
+
{/if}
@@ -172,10 +240,10 @@
onclick={() => (activityOpen = !activityOpen)}
>
{#if activityOpen}{:else}{/if}
- Activity
+ Event log
{#if !activityOpen}
{#if $streaming && activityRunning > 0}
-
+
{activityRunning} running
{:else}
{activityCount || '—'} action{activityCount === 1 ? '' : 's'}
diff --git a/web/src/lib/components/ToolCallGroup.svelte b/web/src/lib/components/ToolCallGroup.svelte
index 829d952..afcab06 100644
--- a/web/src/lib/components/ToolCallGroup.svelte
+++ b/web/src/lib/components/ToolCallGroup.svelte
@@ -4,7 +4,7 @@
import CheckIcon from '@lucide/svelte/icons/check'
import XIcon from '@lucide/svelte/icons/x'
import ChevronDownIcon from '@lucide/svelte/icons/chevron-down'
- import LoaderCircleIcon from '@lucide/svelte/icons/loader-circle'
+ import Spinner from './Spinner.svelte'
let { tools, unmatched, active = false }: { tools: ToolCallResult[]; unmatched?: ToolCallResult[]; active?: boolean } = $props()
@@ -41,7 +41,7 @@
{#if active && doneCount < total}
-
+
{:else if hasError}
{:else}
@@ -79,7 +79,7 @@
{:else if tool.type === 'tool_result'}
{:else}
-
+
{/if}
{toolLabel(tool)}
diff --git a/web/src/lib/stores/activity.ts b/web/src/lib/stores/activity.ts
index 2313c76..93d5bb5 100644
--- a/web/src/lib/stores/activity.ts
+++ b/web/src/lib/stores/activity.ts
@@ -12,6 +12,7 @@ export interface ActivityEntry {
'approval'
description: string
detail?: string
+ args?: string
timestamp: number
toolName?: string
stepSeq?: number
@@ -19,6 +20,26 @@ export interface ActivityEntry {
status: 'running' | 'done' | 'failed'
}
+// Detail text is kept full-length (not hard-truncated to a preview snippet)
+// so the expanded view has something worth pretty-printing — capped only as
+// a safety net against pathological payloads (a full fleet dump, etc).
+const DETAIL_MAX = 8000
+
+function summarizeArgs(args: unknown): string | undefined {
+ if (!args || typeof args !== 'object' || Array.isArray(args)) return undefined
+ if (Object.keys(args).length === 0) return undefined
+ try {
+ return JSON.stringify(args)
+ } catch {
+ return undefined
+ }
+}
+
+function stringifyResult(result: unknown): string {
+ const s = typeof result === 'string' ? result : JSON.stringify(result ?? '')
+ return s.length > DETAIL_MAX ? `${s.slice(0, DETAIL_MAX)}\n… truncated` : s
+}
+
export const activityLog = derived([messages, planSteps, currentTask], ([$msgs, $steps, $task]) => {
const entries: ActivityEntry[] = []
const now = Date.now()
@@ -64,6 +85,7 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
id: t.id ?? `tool_${mi}_${entryIdx++}`,
type: 'tool_running',
description: label,
+ args: summarizeArgs(t.args),
timestamp: now - ($msgs.length - mi) * 1000,
toolName: t.name,
stepSeq: stepTag,
@@ -77,19 +99,25 @@ export const activityLog = derived([messages, planSteps, currentTask], ([$msgs,
if (running && t.error) {
running.type = 'tool_error'
running.status = 'failed'
- running.description = `${t.name}: ${t.error.slice(0, 80)}`
+ running.description = `${label}: ${t.error.slice(0, 80)}`
+ running.detail = t.error
} else if (running) {
running.type = 'tool_done'
running.status = 'done'
- running.detail = typeof t.result === 'string'
- ? t.result.slice(0, 200)
- : JSON.stringify(t.result ?? '').slice(0, 200)
+ running.detail = stringifyResult(t.result)
} else {
+ // Historical/persisted tool calls arrive as one merged record (args
+ // + result on the same object, see mergeToolCalls in chat.ts) rather
+ // than a separate tool_use/tool_result pair — there's never a
+ // "running" entry to attach to, so this branch has to build the
+ // full entry itself. It used to fall back to the raw tool name
+ // (e.g. "get_entity") instead of the humanized label here.
entries.push({
id: t.id ?? `tool_${mi}_${entryIdx++}`,
type: t.error ? 'tool_error' : 'tool_done',
- description: t.error ? `${t.name}: ${t.error.slice(0, 80)}` : t.name,
- detail: !t.error ? (typeof t.result === 'string' ? t.result.slice(0, 200) : '') : undefined,
+ description: t.error ? `${label}: ${t.error.slice(0, 80)}` : label,
+ detail: t.error ? t.error : stringifyResult(t.result),
+ args: summarizeArgs(t.args),
timestamp: now - ($msgs.length - mi) * 1000,
toolName: t.name,
stepSeq: stepTag,
@@ -181,6 +209,8 @@ function toolActivityLabel(t: ToolCallResult): string {
case 'complete_task': return 'Complete task'
case 'ping_service': return 'Check service'
case 'ask_operator': return 'Ask operator'
- default: return t.name
+ // Unmapped tool (new/uncommon) — humanize the raw name rather than
+ // showing it verbatim, e.g. "revoke_execution" -> "Revoke execution".
+ default: return t.name.replace(/_/g, ' ').replace(/^./, (c) => c.toUpperCase())
}
}
\ No newline at end of file
diff --git a/web/src/pages/Chat.svelte b/web/src/pages/Chat.svelte
index b5baaee..d3b3ae5 100644
--- a/web/src/pages/Chat.svelte
+++ b/web/src/pages/Chat.svelte
@@ -1,7 +1,6 @@
- {#if showRail}
-
-
-
- {/if}