The "no questions" early return sat above two useMemo calls, so any mounted
instance whose question count crossed zero threw "Rendered fewer/more hooks than
expected" and took the surrounding message list down with it. `input` is not
fixed for the lifetime of the instance — chatStore rebuilds tool_use messages
from the transcript under a stable id — so both directions are reachable.
Move the early return below every hook. The two new tests assert on rendering
rather than hook counts, and both go red against the old ordering.
The turn checkpoint only sees tracked Write/Edit changes, so media produced
via Bash (e.g. a PIL render at /tmp/result.png) left filesChanged empty and
the gallery filtered the just-rendered image away a beat after it appeared.
Absolute paths are explicit prose, not guesses, and an empty changedFiles is
"no evidence" rather than "no files": both now fall back to legacy text-only
rendering. Non-empty changedFiles still reconciles relative mentions onto
the real paths, and file-chip semantics are unchanged.
The service-region picker in the provider form was the last native
<select> in the dialog, rendering with platform chrome inconsistent
with the neighboring API format / auth strategy dropdowns. Switch it
to the shared Dropdown component with per-endpoint description rows,
and fall back to a "Custom" label when the base URL matches no
regional endpoint instead of going blank.
Resuming a stopped agent adopted the resuming tool's tool_use id, so a
follow-up sent via SendMessage re-filed the agent's tool activity and its
completion notification under the SendMessage card instead of the Agent
card that spawned it.
Persist the spawning Agent tool_use id in agent metadata and prefer it on
resume, falling back to the still-registered task. Never fall back to the
caller's own id — that is the bug itself; leaving it undefined only costs
live activity streaming, which is what pre-existing agents did anyway.
runAsyncAgentLifecycle now takes parentToolUseId as a required parameter so
a missed call site is a compile error rather than a silent regression, and
registerTask keeps the original id when a resume re-registers the task.
Mirror the workbench return pattern: returnFromSubagent switches to the
source session tab and closes the ephemeral subagent tab, with fallback
to plain close when the parent tab is gone.
Move the sticky header out of the scroll region so the list is hard-clipped
below it instead of relying on engine compositing above the scrolling layer.
d1415437 hardcoded permissionMode: 'default' in the shared adapter
createSession, which pins every Feishu/Telegram/WhatsApp session to ask
mode and short-circuits the server's fallback to the user's configured
default. Omit the field so ws/handler resolves the global setting at
launch, restoring v0.5.0 behavior while keeping the workDir root check.
The activity panel derived visibility from volatile per-session caches, and
three lossy paths could drain them: the connectToSession reset branch dropped
backgroundAgentTasks/agentTaskNotifications, reloadHistory replaced background
tasks with transcript-only records (running tasks are never persisted), and a
history load aborted by a concurrent task mutation was silently discarded.
ActiveSession's one-way auto-close then made any transient empty beat
permanent.
Preserve activity fields on reconnect reset, always merge unresolved
background tasks into reloaded history (transcript terminal records still win,
keeping the stopped-task reconcile intact), retry aborted empty history loads
with a bounded delay, and debounce the auto-close behind a history-ready grace
period.
The SubAgent run detail tab polls every 2s while the run is live, and
mapHistoryMessagesToUiMessages minted a fresh nextId() for every
thinking/tool_use/tool_result block on each pass. The new ids changed
the ToolCallGroup/ToolCallTree React keys, remounting ToolCallBlock and
dropping its local expanded state, so any tool card the user expanded
collapsed again on the next poll.
Derive the UI message id from the transcript identity instead:
msg.id (always set by the server-side entriesToMessages boundary) plus
the block index is deterministic across remaps and unique within the
source message, so polling keeps keys stable and expansion survives.
The MarkdownRenderer security hardening strips every img src that is not
blob:/data:, which is right for untrusted assistant output but also broke
the workbench file preview: relative images lost their src and remote
badges never loaded. The document preview is user-owned local content, so
give MarkdownRenderer an opt-in resolveImageSrc hook and wire it in the
workspace MarkdownSurface: relative sources resolve against the markdown
file's directory and are served through the sandboxed /preview-fs (or
/local-file for absolute paths and workspace escapes), http(s) URLs pass
through to CSP, and other schemes stay stripped.