sidechat.start passed `seed` without `meta.isSeeded: true` +
`inheritedEventCount`, so dsh-session treated the whole seed as the
child's OWN events and the child's Inbox constructor replayed the
parent's `agent/inbox/spliced` history — inheriting whatever input sat
UNCLAIMED in the parent at the click moment:
- next-turn: a queued follow-up behind a still-running turn;
- next-step (the reported long-context case, parent queue empty): a
tool-result context / steering spliced between step boundaries.
The first side prompt then claimed [phantom, boundary, question] — the
stale parent message was sent to the model BEFORE the boundary and
logged as a user/message. The fix passes the exact marker pair the
host's own session.fork uses (api-session-controller); ownEvents() now
stops at the end-seed boundary and the inherited inbox replays empty.
Regression tests run the REAL Session.create + Inbox in both
directions (old shape inherits the phantom, marked shape stays empty);
routes tests pin the two create options. Verified: pnpm typecheck /
test (5 pre-existing unrelated UI-spec failures on main unchanged) /
build.
DSH 0.1.2-alpha.1 removed the client `ctx.connection.api` face (Remote
gateway migration), so every Side Chat transcript poll (`sessions.history`)
threw TypeError into the view's catch and was swallowed — the tab rendered
an empty transcript forever, silently (no console error, so the mount lane
never caught it). The successor APIs are not a drop-in either:
session/follow|page reject plain session addresses for origin:'subagent'
logs (agent-busy fence) and paging cannot read past the current cursor.
Move the transcript to the plugin's own wire — one transport for every
DSH generation:
- new `sidechat.events` route: live threads read the in-memory
`agent.session.events`; cold threads read `sessionPersistence.inspect`
(both seams exist since 0.1.0-rc.8; alpha.1's inspect returns the
expanded logical log, never the packed chunk-row encoding). The
inherited seed is cut host-side (`threadOwnLogEvents`), polls ride an
`afterSeq` delta, and the response is tail-capped at 8000 events (the
old client walk ceiling).
- SideChatView.fetchThread is single-path; collectOwnEvents (the client
walk) is gone, and so are the ctx.connection mirror and inject entry
(no consumer left).
- transcriptRows keeps re-cutting at session/end-seed as defense in
depth.
Verification:
- unit: sidechat.events route (seed cut, afterSeq delta + never
resurrecting the seed, cap, cold/live sources, 404/503/bad-request),
full suite 1104 passed; typecheck and build (purity gate) green.
- mount e2e vs a real dsh-v0.1.2-alpha.1 source build: 14/14, including
the Side Chat tab sweep polling sidechat.events (performance resource
assertion) and live + post-dispose (persisted) route reads.
- mount e2e vs PATH dsh 0.1.0-rc.8: 14/14 (no regression).