Stopping a conversation previously disposed the outer reactive stream without reliably cancelling synchronous tool callbacks running on worker threads. Shell commands, skill scripts, and Playwright sessions could therefore outlive the visible chat turn.
Propagate cancellation through run-scoped hooks, interrupt active tool execution and parallel batches, terminate subprocess trees, close per-conversation browser sessions, and wait briefly for final stream persistence before acknowledging Stop.
Expose an explicit interrupting state in the chat input to block duplicate stop clicks and show progress, with regression coverage for cancelling an in-flight synchronous tool callback.
A tool/MCP call could render 2+ times in the timeline (issue #521). The
tool is invoked once — this is a display artifact. The segment de-dup in
MessageBubble keyed on `toolName::toolArgs`, which fails two ways:
- The same logical call rendered on both the live SSE stream and the
reloaded/persisted path can carry differing toolArgs strings
(whitespace / key-order from re-serialization), so the two are NOT
de-duplicated and both survive → the reported duplicate.
- Genuine repeated calls of the same tool with identical args (e.g. shell
/ python retries) share the key and get wrongly collapsed to one.
Prefer the LLM-provided toolCallId (carried end-to-end on both live and
persisted segments, stable across serialization) and fall back to
toolName::toolArgs only for legacy segments without an id. This fixes
both the visible duplication and the over-collapse.
Adds pure-function tests for the de-dup logic.
Use const for a never-reassigned local, drop an unused v-for index,
add a default branch to an exhaustive switch the linter cannot prove,
and remove eslint-disable directives that no longer match enabled rules.
The lint script referenced eslint with --ext flags but the repo never had
an ESLint config file, so pnpm lint always failed. Add a flat config
(typescript-eslint recommended + vue essential) with legacy-code rules
downgraded to warnings, drop the flat-config-incompatible --ext flags,
and move pnpm build approvals from the no-longer-read
pnpm.onlyBuiltDependencies field to pnpm-workspace.yaml allowBuilds.
Wire MCP standard notifications/progress into the existing SSE stream so long-running MCP tool calls surface live progress instead of a bare spinner. A per-call progressToken maps back to (conversationId, toolCallId); ProgressAwareMcpToolCallback injects it into tools/call _meta and calls McpSyncClient directly (falling back to the delegate on error, and applying identity forwarding first). Progress events skip the ring buffer and are replayed from a latest-value snapshot on SSE reconnect. Frontend renders a gradient progress bar in ToolCallSegment when a running tool reports progress.
Extract generated-file download links from tool results — on the backend (persisted to message metadata for history) and on the frontend (live during SSE) — de-duplicate by URL, and render them as a Generated Files section with file-type icons and a rail badge.