feat: Wave C - pi-native steer/follow-up delivery
Delivery now rides pi's OWN follow-up queue when a run is live, and a carry-everything idle wake otherwise. One registry, two vehicles; claimSteers and continuation coalescing are deleted (owner rulings, 2026-09-26: follow-ups only; one queue; the wake is the only appender). THE LIVE RUN (vehicle 1): the chat-handler registers the streaming turn's agent (registerLiveAgent, next to registerTurn); the pump hands every eligible queued marker to agent.followUp() - pi polls that queue when the run would otherwise stop, injects the markers (message_start/message_end on the live wire), and CONTINUES the same run. followUpMode "all" joins every handed marker in one continuation turn. The question stopper runs before the follow-up poll, so an open quiz is never overridden - a handed marker strands at settlement and waits (today's pacing). probe-handoffs never take this path (the handoff must START the teacher's run - a record-level branch). SETTLEMENT RECONCILIATION: settleSteers(chatId, persisted) at onFinish (after unregisterLiveAgent) resolves+flips entries the run delivered and returns stranded ones to pending - the registry stays the truth; the dead agent's queued copy is abandoned. This is what makes the handoff safe: pi owns no retry for a stranded follow-up. THE IDLE WAKE (vehicle 2): waitForIdle (unchanged gates) then ONE turn carrying EVERY pending marker as its input appends - dequeue-at-fire (the Wave B regression lesson: the wake run's own follow-up poll must never see its own cargo; a 409 pushes the cargo back and retries). CLIENT: applyWireEvent now upserts non-assistant messages by id, so in-run injected markers render live instead of dropping until settle. Tests: jobs 44/44 (live handoff, settlement stranding, handoff-waits, one-wake-carries-all); chat-flow 23/23 (the double-append pin drives the real pump through the new input); session-core 28/28 (custom-role wire appends). Full suite 997/997, tsc/lint/build green. Live smoke (PORT=3004): research job delivered mid-probe - parked behind the dangling questions exactly per semantics, landed once on the open gates, teacher folded the brief in and re-dispatched; the second (good) result rendered the full 10-source brief card and the lesson continued from it. No duplicate ids anywhere in the record. Known gap flagged to owner (NOT from this change): the server accepts malformed answer events via raw API (answers without the answers[] shape) and AnsweredCard then crashes the page render - surfaced by the smoke's scripted answers, repaired by hand in the scratch record.
This commit is contained in:
@@ -42,8 +42,12 @@ export type ChatInput =
|
||||
| { type: "verdict"; accepted: boolean; note?: string }
|
||||
| { type: "continue" };
|
||||
|
||||
/** A wake-pipeline steer delivery — same turn composition, server-origin. */
|
||||
export type SteerInput = { type: "steer"; steer: LhMessage };
|
||||
/** A wake-pipeline steer delivery — same turn composition, server-origin.
|
||||
* Wave C (2026-09-26): the idle wake carries EVERY queued marker as its
|
||||
* input appends — with claimSteers deleted, the pump's wake is the only
|
||||
* steer appender left (the in-run path hands markers to pi's own
|
||||
* follow-up queue instead, so it never builds an input event). */
|
||||
export type SteerInput = { type: "steers"; steers: LhMessage[] };
|
||||
|
||||
/** Every input the turn composition accepts. */
|
||||
export type TurnInput = ChatInput | SteerInput;
|
||||
|
||||
Reference in New Issue
Block a user