Add a branded AgentMessageId assigned to each accepted send message and
returned from send/followup/steer/inject (was void). Rename the inbox
event payload InboxItemInfo to AgentMessage, carrying that id so a caller
can correlate a queued item with its enqueue/dequeue/discard events.
The FIFO-conservation invariant fired on the loop-authored continuation
reason path: a continue-with-reason decision entered the steering FIFO
without an agent/inbox/enqueue, so its later dequeue/discard had no
matching enqueue. Emit the enqueue for that steer too, add a regression
test that mounts the invariant over a continue-with-reason turn and a
cancel, and hoist the duplicated inboxInfo helper into inbox.ts.
Found by fresh-eye review.
Replace send/steer/inject with one Agent.send primitive over the
(target × wakeup) matrix; followup/steer/inject become fixed-preset
alias methods on the now-abstract Agent class. Coalesce context/message
into user/message (injected context is a non-user source). Replace
agent/queued with agent/inbox/enqueue/dequeue/discard, add cancel
keepInbox, and add a FIFO-conservation invariant.
Four rounds of structural rework on the conversation surface, converging
on one registration model for the whole client:
- Review fixes: open() leaves the inject factory (SessionsService owns
the semantic); ConversationService mounts via ctx.plugin(); the
bespoke view registry retires into the 'conversation.view' list slot.
- Ring alignment: createChatView factory retired (components get
everything through checkable shares at the register call site); the
hand-rolled t/i18n threading is deleted wholesale — a future
framework-level i18n will supply t as a standard prop keyed by slot
name, so no interim manual channel.
- Toolview dissolution: ToolViewRegistry / ToolViewResolver /
ToolViewOutlet / ctx.toolviews retire. Tool rows are entries of the
'conversation.chat.toolview' keyed slot (scope: session) declared by
the chat entry; ToolRowOwnerProps is the unified owner payload;
GenericToolCard becomes the call-site fallback; registrants are plain
plugins (inject ['slots','conversation'] as the load-order seam);
session-dimension dispatch moves into components (useSessions reads
parentId); trajectory/waterfall gain same-shape slots the day they
render tool rows (RendersCheck rejects empty declarations). Slot
names mirror the composition path (<domain>.<entry>.<hole>).
- Staging follows current: cell()/binding() are pure resolution
(render-safe); the constructor subscribes to the list store and
followCurrent opens the event window when the current session
changes — staging IS the open signal, business verbs are the timing,
React render/commit is decoupled from window lifecycle. A masked
current (projection gap) keeps the stage untouched so deferred
teardown semantics survive reconnects.
Agent Note: .agents/notes/implemented/architecture/
2026-07-23-toolview-dissolution.md (bilingual pair) records the
decision, the four rejected alternatives, and the accepted semantic
changes; the web client architecture note and packages/client/AGENTS.md
carry the current-state narrative.
Verified: typecheck 0, duplication 0 clones (478 files), full coverage
run 6190 passed with zero threshold errors, knip 0, doc-sync 24/24,
client aggregate tsc 0, render-count checks (one commit per chunk, zero
row re-renders under streaming) green.
Three notes stated tsconfig.build.json as present-tense fact; the
aggregates (tsconfig.host.json / tsconfig.client.json) now own the
explicit reference lists and the root solution owns the emit graph.
Historical narration in dated notes is left untouched.
New authoritative section docs/development.md#typescript-project-layout
(five files, three roles, the program-vs-resolution principle and its two
disciplines), two repo conventions in AGENTS.md, package tsconfig shape in
packages/AGENTS.md, cookbook touch-lists updated for the two aggregates,
test source-plane rule in docs/testing.md. Decision record: new Agent Note
2026-07-22-tsconfig-solution-root-two-aggregates; ts-build-config note
updated in place (tsc-first pipeline unchanged); the two GUI RFCs now name
tsconfig.host.json. All bilingual pairs re-recorded. Doc budget ceilings
raised: AGENTS.md 1680, docs/testing.md 1020, packages/AGENTS.md 660 (two
new one-line conventions and one new section on already-near-ceiling docs).
Mission spec for the code-side migration: missions/tsconfig-single-graph-migration.md.
The data layer no longer depends on the React glue package, and business
plugins no longer depend on web-react at all:
- The store engine (zustand vanilla + immer + persist + dev freeze),
defineStore, and shallowEqual move to @deepseek-ai/dsh-client-runtime,
exported from the ./client main entry — no ./store subpath survives on
either package (the web-react one is deleted, none is opened on runtime).
- Store products are bare snapshot sources: useSelector leaves
SnapshotStore/StoreInstance and Session; every hook is composed at the
binding site in web-react's renderer (per-source cached uSES binding).
The SlotRendererHost sessions face carries bare observables only.
- SessionProvider becomes a standard-kit seat: an entry whose children
declare a session-scope slot receives the framework component as a prop,
retiring the last value import of web-react from plugin packages.
UseSession and the session-area types now live in ui-slots.
- web-react shrinks to the shell-only React glue (renderer, providers,
uSES bridge); zustand/immer belong to runtime alone; the module-table
seed and tsdown externals drop the web-react/store seat.
- NODE_ENV replacement is defined once in the shared tsdown client preset
(browser bundles inline the engine and lost vite's define); the 3-line
process.env typecheck shim moves to runtime with the engine.
- Stray tsc artifacts (.js/.d.ts/.d.ts.map beside sources under src/)
swept repo-wide; they shadow real sources under vitest resolution.
Verified: both aggregate typecheck programs at zero; 604 client tests
green; repo-wide grep for web-react/store at zero; real-host playwright
run 7/7 including persist round-trip.
ci: fix test/docs
The definitive slot model for the web client, replacing the first-generation
define/register two-step, ScopedSlots whitelist faces, and binding handles:
- 'root' is the only a-priori slot (SlotsService built-in); the shell renders
exactly ctx.slots.renderSlot('root', {}).
- register is the single API: children = slot declaration + render
authorization + runtime spec in one options object; misconfiguration fails
loud at load (duplicate declaration, undeclared contribution, one store
handle under two scopes).
- Component props arrive in four auto-derived shares: PropsRuntime<K>
(owner params + session/global standard kits via declare-merge),
PropsRenderSlots<S>, PropsStore<H>, and the inject business face.
sessionId is framework-supplied; hooks are framework-made only.
- Framework store seat: defineStore factories declare schema/actions/persist;
read = useStore, write = baked actions only; store scope derives from the
mounting entry; per-session persist keys and clearPersisted lifecycle.
- inject factories read the apply closure's own ctx (binding handles retired;
root-ctx back door closed); SessionProvider is self-wired render-prop.
- Rendering sits behind the SlotRenderer install seam; runtime stays
React-free; ownership ledger keyed to the single entry axis closes the
stale-authority window (StaleAuthorizationError probes).
Docs: the slot type-chain note is refreshed in place as the slot system
standard RFC (bilingual pair re-recorded); the web client architecture RFC
defers its slot sections there; packages/client/AGENTS.md gains the slot and
props discipline; gui-testing/web-styling notes drop missions/ references.
Tests: suites rewritten to the standard (props fed directly, real store
engines via createXXXStore().create(), no render machinery); load-time
negative samples for declaration/authorization/store conflicts; verified by
real-host playwright run (three columns, empty state, collapse, keyed session
remount, cross-slot selection sharing).
docs(ui-sidebar): point contract reference at the committed slot standard RFC
missions/ is workspace-local and never committed; the README must not cite it.