跳到主要内容

Loop 0.5.0 · 2026-10-31

文档对应最新 npm 发布版本,保留英文原文。

SSE Events

The server pushes session snapshots and coding-agent events to the browser using Server-Sent Events. HTTP commands flow downward, while SSE reports execution state without importing frontend code into lower layers.

Connections and Frames

Connect to GET /api/sessions/:id/events. SSE data is a JSON Frame and its id is streamId:seq. Business frames have no custom SSE event field; browsers consume them through onmessage. Named heartbeat events are transport-only and have no business sequence.

Frame type Content
session.snapshot Snapshot with the latest three user rounds on first connection or when replay is unavailable
session.state Snapshot with the latest three user rounds after command-state changes and run finalization
run.accepted Accepted requestId and runId
loop.event SDK SessionEvent, runId, and optional messageIndex

Every frame includes sessionId, streamId, and seq. streamId belongs to one SessionEvents instance. seq increments on publish; a connection snapshot uses the current sequence without incrementing it.

SDK events include message_start, message_update, message_end, tool_execution_start, tool_execution_end, prompt_timing, agent_settled and session_title. Web wraps them in loop.event without renaming them. message_update still carries the original AI event. See coding-agent events for the full contract.

Session-level title, permission_changed, approval_requested and approval_resolved updates publish session.state snapshots, including when no prompt is active. Approval snapshots carry state.pendingApprovals; resolution removes the matching request and records compact lastApproval outcome metadata. Replay can include an earlier pending snapshot followed by its resolution; fresh snapshots contain only currently pending requests. A server restart restores none. Ordinary SSE viewers do not register a handler; /events?approvals=1 explicitly opts into the session's shared interaction adapter. See permission endpoints for decisions, indefinite pending waits across reconnects and disconnect cleanup.

Replay and Snapshots

Clients can supply a cursor through Last-Event-ID or the cursor query parameter; the header takes precedence. If the cursor belongs to the same stream, precedes the current sequence, and the buffer still contains contiguous subsequent frames, replay them in order. Otherwise send a current snapshot, then subscribe to new events. SessionEvents projects the latest three user rounds before structured cloning, byte accounting and buffering state frames. Earlier rounds remain in the controller and load through the history endpoint; absolute indexes and totals retain their meaning. Native loop.event payloads and the SDK stream are unchanged.

The default cache holds at most 256 frames and 4 MiB of serialized data, evicting old frames when either limit is exceeded. There is no await between snapshot/replay and live subscription, avoiding lost events at that boundary. State projection replaces drafts and completed messages by messageIndex and merges tool results by call ID.

Connection Lifecycle

Named heartbeat events with an empty JSON object are sent every 15 seconds without an id or business sequence number. Each connection has a 1 MiB output-buffer target. A client that stops consuming data is disconnected; the frontend reconnects using replay or a snapshot.

SSE responses use text/event-stream with Cache-Control: no-cache, no-transform and X-Accel-Buffering: no to discourage intermediary transformation and buffering. The remote ingress replaces the cache policy with no-store, no-transform, retaining the anti-transformation directive and buffering header. Session updates are forwarded as they arrive rather than waiting for model completion; reconnect uses the same replay/snapshot contract. These headers request streaming behavior but cannot guarantee that every external relay or mobile network honors it.

Aborting a request or cancelling its stream removes only that connection's listeners and timer, without cancelling AgentSession. Disposing an instance closes its connections. Server restart creates a new streamId. Replay is not a durable event log, does not recover unsaved execution, and does not re-execute commands.

List Change Notifications

Connect once per page to GET /api/workspaces/events independently of the selected session. SSE data is a JSON ListFrame with streamId and seq, using the same id format and onmessage handling as session events.

Frame type Client action
lists.reset Refresh projects, cached session lists and archived chats
sessions.changed Refresh the session list for workspaceId and archived chats
projects.changed Refresh projects, the session list for workspaceId and archived chats

Incremental notifications contain only workspaceId and cursor metadata, without messages or snapshots. They are published after project registration/rename/removal and archive/restore/deletion operations, on accepted runs, first user messages, command-state changes, run settlement, asynchronous title updates and durable read receipts. All loaded controllers are observed, even without a per-session browser connection. Token deltas and tool events do not trigger list refreshes. Operations that partially complete before failing also notify clients so completed changes remain visible.

Each new connection always receives lists.reset at the current sequence, even if Last-Event-ID is supplied. The list stream retains no replay buffer; clients reload lists to recover missed changes. It shares the transport's 15-second named heartbeat, backpressure and disconnect cleanup. Heartbeats do not read history or refresh lists. Controller disposal removes its watcher; registry shutdown closes all list connections.

Notifications cover changes made through this Web process. They do not watch external CLI writes or filesystem edits; reload the page to pick up those changes. This stream synchronizes list queries, not another page's selected session or unsent drafts.

Source and Tests