- projection.spec covers applyGoalProjection's non-message early return and
the foreign-change-kind arm directly (the pure-function posture the unit
requires — a throwing apply would tear down the registry drive).
- New ui-goal browser-plugin spec exercises the plugin body over a real
cordis Context with fake faces: dock registration (id/order), verbs
reading the CAS ref from the current projected value, the
no-current-goal short circuit, RPC-error mapping, HMR disposal, the
GoalDock adapter's three projection states, and the inert node half.
dsh-goal/types becomes the client-safe pure outlet (tool-todo dual-outlet
shape): GoalId/GoalRef/GoalPhase/GoalBlockReason/GoalSnapshot, the new
GoalProjection payload, and the SessionProjectionMap 'goal' key merge — zero
host imports. Host-coupled vocabulary (GoalView/activation, change metas,
message source, folds, GoalError codes, the scoped goal/changed event) moves
to src/domain.ts, re-exported from the package root. ./client re-exports the
outlet for client aggregates.
GoalService registers the projection unit under ctx.inject(['sessionProjections']):
applyGoalProjection is a projection-grade last-wins fold — plain-JSON state,
same-reference return on non-goal or malformed events (a throwing apply
would tear down the registry drive; strict validation stays with the write
side and foldGoal). Activation is process-local and deliberately absent from
the projection value.
Downstream packages lost the tests that exercised their agent-loop-facing
edges when the loop was rewritten. Restore 100% per-file coverage with
behavior tests through public seams: llm-retry config validation and
cancellation races, goal replay drift/staleness/teardown edges, plan-mode
disposed-flush, workspace-context empty-change commits, api-proxy
synchronous send failures, acp-snapshot spill-path extraction and refresh
write-back, ACP injection-triggered turns, cli-demo and tui inbox
lifecycle edges, and agent-loop retry/settlement/lifecycle branches.
The only source changes are narrowly-justified v8 ignore annotations on
invariant guards and one redundant-guard removal (workspace-context).
Address the review bot's five genuinely-new findings on the current code:
- disposal now discards any still-pending inbox items before the loop
exits, so every enqueued id gets a terminal lifecycle event.
- injection (next-step/no-wakeup) validates its payload up front, before
opening the idle one-shot turn, honoring 'invalid input throws before
any append'; and rejects attached contexts (which belong only to inbox
messages) rather than silently dropping them.
- agentMessage() freezes the agent/inbox/* payload so a listener cannot
mutate the shared correlation object mid-dispatch.
- refresh the package READMEs (compact, goal, guard, hook-protocol,
plan-mode, time-context, workspace-context) that still referenced the
removed context/message event, with the source-based user/message
distinction.
The up-front injection validation makes two finally branches unreachable
(v8-ignored as the turn-enclosure backstop). Adds regression tests for
disposal discard, context rejection, up-front validation, and the frozen
payload; per-file coverage stays 100%.
- tui.spec: exercise a goal-sourced injected context card (labels by
source kind, not plugin name), closing the last uncovered branch in
tui/src/index.ts that the CI coverage gate caught.
- time-context.e2e / goal.e2e: filter injected context by source now
that it is a user/message (plugin/goal source), and count goal
continuation rounds by round>0 rather than event type.
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.
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.