Repository navigation
Agent message card, and an alias that actually resolves (v0.10.0) - #19
Merged
Merged
Conversation
A peer message used to reach the transcript as a generic Context injection row, indistinguishable from a skill catalog or a reconciled instruction file, attributed to an opaque session id. Nothing about the transcript said another agent had spoken. This ships the plugin's browser half. A conversation node recognizes this plugin's own messages in the durable log; a renderer registered against the harness's keyed conversation.chat.node slot draws them. The card names the sender, says how the message arrived and what this session may do about it, and shows the message rather than the framing around it. The harness's own context row stays directly beneath. Every projection that matches an event contributes a node -- no plugin can claim an event away from the harness -- and that is the right arrangement anyway: the card is the human view, the row beneath it is the exact bytes the model read. A custom durable event was the first design, and it does not survive contact: a log carrying an event type outside KNOWN_SESSION_EVENT_TYPES is refused on reload unless marked ignorable, and Session.append cannot mark one. Shipping it would have made a user's session unopenable. Nothing is added to the log; the card derives from the event the harness already writes. Also fixes address resolution, found by running this live: a session that published an alias could not be reached by it. peer_send resolved session ids and folded display names only, so a peer that read a capability card and repeated the alias addressed a stranger. Aliases now reach the peer directory itself, so sending, verifying, group fan-out and wait graphs share one vocabulary -- and the special case in the composition root is gone rather than duplicated. Verified in a real dsh web host with two model sessions: the bundle loads, the card renders in light and dark, and the alias resolves. - 358 tests, 33 of them on the card's projection, formatting and record - the record the host writes is read back by the card in agent-sink.test.ts, so the two halves cannot drift apart quietly - removes summarizeInbound, which nothing called
happyren
force-pushed
the
feat/agent-message-card
branch
from
August 15, 2026 10:05
bbaa966 to
3ca64f5
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
An arriving peer message now renders as its own transcript card instead of a generic
Context injectionrow.It names the sender, says what the delivery cost (
interrupted this step/next turn/delivered quietly), says what this session was told it may do about it, and shows the message rather than the framing around it. Verified live in a realdsh webhost with two model sessions, in light and dark.This is the plugin's first browser half — a client bundle the harness serves to the Web UI, built separately from the host half with its own tsconfig, DOM and JSX.
Two rows, deliberately
The harness's own context row stays directly beneath the card. Every registered projection that matches an event contributes a node — a plugin cannot claim an event away from the harness — and that is the right arrangement anyway: the card is the human-facing view, and the row beneath it holds the exact bytes the model read, untrusted-content notice included. The card never claims to be that text, and says so when it cannot parse it.
The design that did not survive contact
A custom durable event would have given the card a row of its own. But a session log carrying an event type outside
KNOWN_SESSION_EVENT_TYPESis refused on reload unless the envelope is markedignorable, andSession.appendexposes no way to mark one — the harness records that a registration surface for downstream plugin events is deferred until a consumer exists. Shipping it would have made a user's session unopenable. The card derives from the event the harness already writes; this plugin adds nothing to the log.Bug found by running it live
A session that published an alias could not be addressed by it.
peer_sendmatched session ids and folded display names only, so a peer that read a capability card and repeated the alias addressed a stranger — the failurev0.9.1fixed forblocked_on, still present in the one place that matters most.Aliases now reach the peer directory itself, so sending, verifying, group fan-out and wait graphs share one address vocabulary, and the special case in the composition root is deleted rather than duplicated.
Verification
tests/agent-sink.test.ts, so the two halves cannot drift apart quietlytests/render.test.tspins the framing round trip: a change to either side that the other does not follow fails, rather than silently blanking a cardsummarizeInbound, which nothing calledStill not visible
presentCall/presentResulton tool calls — re-checked atv0.10.0, the Web UI still renders the generic row. Declarations stay (correct against the published types, free to carry); no claim is made for them.