Snapshot this.ctx.sessionManager.getSessionId() for the tan clone's local://
mapping instead of session.sessionId. The two diverge after /fresh or a
provider session override, and the Windows short-root fallback keys
%TEMP%/omp-local/<id> off the session-manager id used by the parent's
large-paste writes and '/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' reads, so the mismatched id left attachments
unreachable.
Diverge the mocked session id from the manager id in the regression test so
it pins the session-manager id.
Fixes#6971
(cherry picked from commit 1efcd22326d76fdb8b50c0977a836c892e80ab76)
Capture the parent artifacts directory and session ID when /tan dispatches
instead of resolving them through the mutable interactive SessionManager.
This keeps background tan '/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' reads pinned to the dispatching transcript
after the user switches or resumes another session.
Extend the regression test to switch the mocked interactive session before
the background job starts and assert the original local mapping is retained.
Fixes#6971
(cherry picked from commit e05db428eabd5087e4b2b4a462f47927c0628e72)
TanCommandController.start nests the tan clone at
<parent-artifacts>/Tan-<id>.jsonl, so the clone's session manager derived
its own artifacts dir and hence local root <parent-artifacts>/Tan-<id>/local.
Its sdk.createAgentSession call omitted localProtocolOptions, unlike the
task-subagent path which inherits the parent's mapping, so parent-session
'/data/workspaces/can1357__oh-my-pi__6971/.omp-session/2026-07-29T06-08-45-283Z_019fac7d-5ee3-7000-a7aa-16fe9394fdc9/local' attachments (pasted files, generated references) were unreadable.
Thread the parent session manager's localProtocolOptions into the tan clone
so local:// resolves against <parent-artifacts>/local.
Fixes#6971
(cherry picked from commit 1ded46e182fc24f9f57d8e9a907aaad58f790783)
handleEvent has no blanket pre-render (removed for issue #4353), so
each handler must explicitly schedule one when it changes something
visible -- every other statusLine.invalidate() call site in this file
pairs it with ui.requestRender(). The new model_changed handler only
invalidated the cache, so an internal model switch (prewalk hand-off,
retry-fallback) while the TUI was otherwise idle left the status line
showing the stale model until an unrelated event happened to render.
Flagged by @chatgpt-codex-connector on PR #6908.
(cherry picked from commit f565e3a4956bda467b6679a3c3f1e48379395a78)
Zed (and any other ACP client) never learned about a model switch that
happened from inside the agent loop — prewalk hand-offs, retry-fallback,
model cycling — because #pushConfigOptionUpdate was only ever wired to
the client-initiated setSessionConfigOption/setSessionMode RPCs and to
the thinking_level_changed lifetime event. The model itself did switch
correctly (subsequent requests used the new model), but the client's
model picker/status bar kept showing the session's starting model.
#handleLifetimeEvent now also reacts to the model_changed event added
in the previous commit and re-pushes config_option_update. Extend the
existing thinking-only subscription dedupe in setSessionConfigOption to
cover the model config id too, so a client-initiated model change still
produces exactly one notification once the lifetime subscription is
installed.
Regression tests mirror the existing thinking-level coverage:
- 'pushes config_option_update when the model changes internally'
- 'emits a single config_option_update per setSessionConfigOption(model) call'
Verified: bun test test/acp-agent.test.ts (57/57), plus
agent-session-prewalk.test.ts, agent-session-retry-fallback.test.ts,
retry-fallback.test.ts, model-resolver.test.ts, and the other acp-*.test.ts
files all still pass; tsgo --noEmit and biome check clean.
(cherry picked from commit f5c5081088e8cbac2650a9de89049731de1b2777)
AgentSession#setModelWithProviderSessionReset is the single choke point
every model mutation runs through (explicit /model, prewalk hand-offs,
retry-fallback, model cycling). It previously changed agent.state.model
silently — no session event told subscribers (ACP, RPC, TUI) that the
active model moved.
Emit a new model_changed AgentSessionEvent from that choke point
whenever the model actually changes, and wire it into every consumer
that must exhaustively handle AgentSessionEvent: the TUI event
controller (invalidates the status line, same as thinking_level_changed)
and the RPC client's forwarded-event allowlist.
(cherry picked from commit f76325de2c7821dd7046ddb67546577c3575a263)
GitHub Copilot's call_id|id transport (and any stream that delivers a tool's name/args before its id) makes a streamed tool-call block appear with an empty or partial id, then rewrites it in a later delta. The transcript keyed the live card by that mutable content.id, so the changed id passed the creation guard and spawned a second card: the old-id card orphaned as a blue pending preview while the new-id card took the result.
Track the streamed id per tool-call block position (reset per assistant message) and migrate the live card's id-keyed trackers (pendingTools, tool timeline, args reveal, read tracking, and the shared read group's entry) when the id changes, so the populated id reuses the existing card.
Fixes#6879
When a successful read result was persisted before its live tool_execution_end reached the UI, a transcript rebuild replayed the completed card and removed the pending handle. The delayed read completion then created a fallback ReadToolGroupComponent beside replay.
Treat the existing tool timeline entry as proof that the read already had a card; clear stale read tracking and skip fallback creation. Added an exact memory:// success -> persisted replay -> delayed completion regression.
Fixes#6879
Track tool-call ids whose failed-attempt cards were retracted until agent-loop emits their synthetic skipped-tool completion. Ignore the synthetic start and consume the matching end so the no-pending handler cannot recreate the failed card.
Extended the retry regression through the exact message_end -> synthetic tool start/end -> auto-retry sequence.
Fixes#6879
Seal an uncommitted pending tool card before removing it from the transcript so ToolExecutionComponent cancels its spinner, todo strike, and edit-preview work instead of continuing to schedule renders while detached.
Added a fake-timer regression proving an animated write card renders while pending and stops component-scoped renders after an aborted turn retracts it.
Fixes#6879
Reset #lastReadGroup before retracting a superseded grouped read so the retry creates and mounts a fresh group instead of updating the detached component. Clear the removed call's read tracking as well.
Added regression coverage for an aborted grouped read followed by a visible successful retry.
Fixes#6879
An assistant turn that streamed tool calls and then ended with error/aborted
(a provider blip that auto-retries, a TTSR rewind) has its never-run cards
dropped from the active context, but EventController#handleMessageEnd only
sealed them in place. The retry re-streamed fresh cards below, so each call
rendered twice.
Retract the never-run pending tool cards still in the live region (never
committed to native scrollback, so removable) and forget them from pendingTools
and the timeline map at message_end; a card already on the scrollback tape is
still sealed in place. Follow-up to #6516 / #6519.
Fixes#6879
Only a peer-scoped wait (from, no ids) is internal messaging; bare and ids waits settle on background-job delivery whose snapshot is the job result.
Fixes#6872
- Reworked the `/guided-goal` command to send a hidden interview brief instead of a modal popup flow.
- Removed the deprecated `guided-setup.ts` module and system prompt template.
- Updated goal tool availability and activation logic to support goal creation during the interview.
- Replaced existing tests and added new verification for the updated guided-goal workflow.
- Replaced the `XdevRegistry` class with the `XdevState` interface and pure helper functions across core and session tools.
- Updated session configurations, tool execution, and renderers to utilize canonical tool map initialization and sharing.
- Adapted unit tests and mocks to use `XdevState` and associated helper functions for permission and dispatch verification.
- branch() and navigateTree() now return the selected user message's image
parts (selectedImages/editorImages) alongside the text, extracted in marker
order by #extractUserMessageImages.
- CustomEditor.setDraft() replaces the composer draft with text plus its
pending images, so restored [Image #N] markers resolve on resubmit instead
of degrading to literal text.
- Wired all six restore call sites (selector-controller, extension-ui-controller)
through setDraft; updated rpc-subagents mocks for the new branch shape.
- Added offline regression tests for branch/navigateTree image restitution,
multi-image marker order, and text-only prompts.
- Reconcile inspect_image centrally from setModelWithProviderSessionReset
so retry-fallback model changes (turn-recovery.ts) that bypass
syncAfterModelChange cannot leave a stale tool set
- Apply persisted inspect_image.mode changes immediately from the
settings selector via a new handleSettingChange branch
- Refresh the read tool's advertised description during reconciliation,
before applyActiveToolsByName rebuilds the prompt, instead of only
lazily on the next image read
- Fix the flat (quoted-dotted) enabled->mode migration to write the
nested target form the resolver actually reads
- Add committed regression tests: tri-state x capability matrix,
override precedence, and enabled->mode migration (nested, flat, and
explicit-mode-wins)
- Add an LRU cache to `scanSessionFile` in `session-listing.ts` keyed by file path, stat identity, and scan mode.
- Add a match key union probe in `CustomEditor` in `custom-editor.ts` to bypass per-action lookups on plain text input.
- Add tests covering cache hits, size and mtime invalidations, and negative result caching.