`ssh_exec` and every SFTP tool take `host_id` as a required UUID, and
`list_hosts` was the model's only source of one. So acting on the pane
the user is looking at cost a round trip and then a match of "here"
against `display_name` — and two hosts called `web-01` in different
groups is an ordinary Vault. A wrong guess there runs a command on the
wrong server, which makes this a correctness fix rather than a
shortcut.
The session context now carries the focused pane's id. It is not a pill:
an opaque UUID tells the user nothing the host name does not, and the
host pill already discloses which machine is being described.
`host` becomes `host_name` so the two read as the pair they are. The
block instructs nothing, so a key spelled exactly like the argument it
belongs in is doing work a sentence would otherwise have to do.
A quick-connect target has an id but no Vault name, so an id alone now
counts as context worth sending — that is precisely the case where a
display name could not have identified the host at all.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Context was decoration. Three defects, all reported from use.
Session context followed the conversation. `ConversationModel` owned the
chips and the remote system info, and `refreshAssistantContext` stamped
the focused pane onto whichever chat the sidebar was showing — so sending
from the sidebar carried a host and directory into the Assistant tab,
which has no session to describe. It now lives on `WindowSidebarState`
as a `SessionContext`, and `ComposerView` takes it as a parameter
defaulting to `.none`. The Assistant tab passes nothing, so it cannot
show or send a session it does not have. Two bugs fell out with it:
leaving a Workspace clears the context instead of freezing the last
value, and opening a chat in the sidebar no longer marks it.
Nothing was sent. Only `RemoteSystemInfo` reached the model; host and
cwd reached nothing, and `ContextChip` had no payload at all — the
SSH-failure attachment was a label for a log pasted into the draft.
`SessionContext.modelContext` now renders one fenced data-only block
into the run's system prompt, chips carry their content, and
`appendUserTurn` folds attachments into the turn it returns. What was
uploaded is literally what the bubble shows.
Titles were the first message. `ConversationTitleService` mirrors
`CommandComposerService` — tool-free, strict JSON, validated — and
resolves `.chat` rather than adding a Titler role, per the argument
already written into `AIRole`. It runs once after the first reply and
persists to the `title` column that existed but was never read back, so
no migration. It reads `transcript`, not `messages`: a failed run writes
its reason to the visible list alone, and that is not a name.
`Pane.remoteSystemInfo` already withholds the probe when Shell
Integration is off, so the second gate in `AppModel` is gone — the value
sent is the value the chips were drawn from, and the two cannot
disagree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three defects in how a conversation drives and shows a run.
Stop never reached the runtime. `Task { await runtime?.cancel() }`
resolves the property when the task runs, which is after `stop()` has
already set it to nil, so the run carried on executing tools with nobody
listening — secrets undiscarded, SSH and SFTP connections uncleaned.
`messages` was replayed as the next run's prior context, but it is a
display projection: no tool calls, and no tool results at all, because
those are drawn as cards. The model lost everything it had already done,
and a tool-only turn came back as an assistant message with empty
content, which Anthropic and Google both answer with a 400. The
model-facing transcript is now kept beside the visible one and rebuilt
from the same events.
The runtime held the conversation through both its approval broker and
its interaction handler while the conversation held the runtime, so every
chat that had ever run an agent stayed alive for the session. All three
edges are weak now, and a conversation that is gone denies and cancels.
Alongside: messages and tool cards are merged into one ordered timeline
rather than drawn as two lists, a tool-only turn no longer leaves an
empty bubble, the transcript follows streaming text instead of only new
rows, and conversations live in one app-wide store so two windows share
instances rather than each building their own.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>