Skip to content

[Refactor] ChatStreamManager: the Main process owns the live execution registry #140

Closed

Description

@mrsibe

Part of #138.

Second step of the epic. #139 puts the outcome on the message; this puts the running turn somewhere that can be asked about.

The problem with what is there now

const activeStreams = new Map<string, AbortController>()

That map is the entire live state of every in-flight turn. It cannot answer any question the features on the roadmap need:

  • Is this turn still streaming, and since when? (needed for a timeout)
  • How much text has it produced so far? (needed to persist a partial turn without the renderer's copy)
  • What does the renderer get when it opens the notebook mid-turn? (needed for attach/replay)
  • Which turn is this chunk for, and is it in order? (needed before the protocol change)

What this introduces

A Main-process registry keyed by execution id, holding one object per running turn:

interface ChatExecution {
  readonly id: string
  readonly messageId: string
  readonly sessionId: string
  readonly notebookId: string
  status: ChatExecutionStatus            // pending → streaming → terminal (once)
  outcome?: ChatExecutionOutcome
  readonly abort: AbortController
  readonly startedAt: number
  seq: number                            // monotonic per execution
  message: AccumulatedMessage            // the final message, assembled here
  buffer: StreamEvent[]                  // bounded; for attach while streaming
}
  • The only place a terminal transition happens is this object, and it accepts one outcome ([Refactor] Chat execution semantics: one terminal outcome per turn, persisted #139's rule, now enforced at the registry rather than at the call site).
  • abort(reason) records aborted before cancelling upstream, so the outcome is decided by the stopper rather than inferred from a rejection arriving later.
  • The registry owns subscriber bookkeeping: a renderer that attaches mid-turn gets the current accumulated message and subsequent events; a renderer that goes away does not cancel the turn by itself (that becomes a policy, not an accident of WebContentsListener lifetime).
  • Persistence is driven from here, not from a callback closure inside an IPC handler.

Acceptance

Not here

Closed by #145 (ChatStreamManager), which moved the lifecycle and its tests into
src/main/services/chat/ and left the renderer-facing events unchanged.

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:foundationModule boundaries, architecture, tech debtrefactorInternal restructuring, no behavior change

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions