(appEvent: AppEvent, meta: { sessionId: string; seq: number })
| 757 | type PendingEvent = { appEvent: AppEvent; meta: { sessionId: string; seq: number } }; |
| 758 | |
| 759 | function processEvent(appEvent: AppEvent, meta: { sessionId: string; seq: number }): void { |
| 760 | // meta carries wire-level seq/sessionId so the reducer can advance |
| 761 | // lastSeqBySession[sessionId] = seq. Compaction completion appends a |
| 762 | // persistent divider marker in the reducer (TUI parity: the scrollback |
| 763 | // is kept, only a marker line records the compaction). |
| 764 | applyEvent(appEvent, meta.sessionId, meta.seq); |
| 765 | |
| 766 | const sideTarget = sideChat.sideChatTargetBySession.value[meta.sessionId]; |
| 767 | if (sideTarget) { |
| 768 | const { agentId } = sideTarget; |
| 769 | const parentId = meta.sessionId; |
| 770 | if (appEvent.type === 'agentDelta' && appEvent.agentId === agentId) { |
| 771 | if (appEvent.delta.text) { |
| 772 | sideChat.appendSideChatAssistantText(agentId, parentId, appEvent.delta.text); |
| 773 | } |
| 774 | } else if (appEvent.type === 'agentTurnEnded' && appEvent.agentId === agentId) { |
| 775 | sideChat.finishSideChatAgent(agentId, parentId); |
| 776 | } else if (appEvent.type === 'taskProgress' && appEvent.taskId === agentId) { |
| 777 | sideChat.appendSideChatAssistantText(agentId, parentId, appEvent.outputChunk); |
| 778 | } else if (appEvent.type === 'taskCompleted' && appEvent.taskId === agentId) { |
| 779 | sideChat.finishSideChatAgent(agentId, parentId, appEvent.outputPreview); |
| 780 | } |
| 781 | } |
| 782 | |
| 783 | // The daemon's prompt.submitted event is projected as a user messageCreated |
| 784 | // carrying the real prompt_id. When the HTTP submit response is lost |
| 785 | // (timeout / network error) this is the fallback that lets Stop work. |
| 786 | if ( |
| 787 | appEvent.type === 'messageCreated' && |
| 788 | appEvent.message.role === 'user' && |
| 789 | appEvent.message.promptId !== undefined |
| 790 | ) { |
| 791 | const sid = appEvent.message.sessionId; |
| 792 | if (rawState.promptIdBySession[sid] !== appEvent.message.promptId) { |
| 793 | rawState.promptIdBySession = { |
| 794 | ...rawState.promptIdBySession, |
| 795 | [sid]: appEvent.message.promptId, |
| 796 | }; |
| 797 | } |
| 798 | } |
| 799 | |
| 800 | if (appEvent.type === 'assistantDelta' && meta.sessionId === rawState.activeSessionId) { |
| 801 | appearance.recordMoonDelta((appEvent.delta.text?.length ?? 0) + (appEvent.delta.thinking?.length ?? 0)); |
| 802 | } |
| 803 | |
| 804 | // Turn-end cleanup for the session the event belongs to — including |
| 805 | // sessions running in the background (see onSessionIdle). |
| 806 | // Turn-end: both 'idle' and 'aborted' mean the prompt is no longer in |
| 807 | // flight, so both must flush in-flight/queued state. (Awaiting-* is still |
| 808 | // in flight — it's waiting on the user — and must NOT flush.) |
| 809 | if ( |
| 810 | appEvent.type === 'sessionStatusChanged' && |
| 811 | (appEvent.status === 'idle' || appEvent.status === 'aborted') |
| 812 | ) { |
| 813 | onSessionIdle(appEvent.sessionId, appEvent.status); |
| 814 | } |
| 815 | |
| 816 | // The agent asked a question and is waiting for an answer — surface it so |
no test coverage detected