Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
64b9cba2 feat: Subscription Lifecycle Amendment (#356)
* feat(ai): align start event types with AG-UI
Widen TextMessageStartEvent.role to accept all message roles and add
optional parentMessageId to ToolCallStartEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): add MessageStreamState type for per-message stream tracking
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): refactor StreamProcessor to per-message state
Replace single-message instance variables with a Map<string, MessageStreamState>
keyed by messageId. Add explicit handlers for TEXT_MESSAGE_START, TEXT_MESSAGE_END,
and STATE_SNAPSHOT events. Route tool calls via toolCallToMessage mapping.
Maintains backward compat: startAssistantMessage() sets pendingManualMessageId
which TEXT_MESSAGE_START associates with. ensureAssistantMessage() auto-creates
state for streams without TEXT_MESSAGE_START.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai): replace STATE_SNAPSHOT with MESSAGES_SNAPSHOT event
Add MessagesSnapshotEvent as a first-class AG-UI event type for
conversation hydration. Replace the previous STATE_SNAPSHOT handler
(which extracted messages from arbitrary state) with a dedicated
MESSAGES_SNAPSHOT handler that accepts a typed messages array.
- Add MessagesSnapshotEvent type to AGUIEventType and AGUIEvent unions
- Add MESSAGES_SNAPSHOT case in StreamProcessor.processChunk()
- Remove STATE_SNAPSHOT handler (falls through to default no-op)
- Fix onStreamEnd to fire per-message (not only when no active messages remain)
- Fix getActiveAssistantMessageId to return on first reverse match
- Fix ensureAssistantMessage to emit onStreamStart and onMessagesChange
- Add proposal docs for resumeable session support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai-client): add SessionAdapter interface and createDefaultSession
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(ai-client): refactor ChatClient to use SessionAdapter subscription model
Replace direct ConnectionAdapter usage in ChatClient with a SessionAdapter-based
subscription loop. When only a ConnectionAdapter is provided, it is wrapped in a
DefaultSessionAdapter internally. This enables persistent session support while
preserving existing timing semantics and backwards compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-preact): thread option through.
* fix(ai): finalizeStream when RUN_FINISHED.
* fix(ai-client): handle reload during active stream with generation counter
reload() now cancels the active stream (abort controllers, subscription,
processing promise) before starting a new one. A stream generation counter
prevents a superseded stream's async cleanup from clobbering the new
stream's state (abortController, isLoading, processor).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: remove proposal docs.
* fix(ai, ai-client): address stream lifecycle edge cases from PR review
- Guard against double onStreamEnd when RUN_FINISHED arrives before TEXT_MESSAGE_END
- Clear dead waiters on subscribe exit to prevent chunk loss on reconnection
- Reset transient processor state (messageStates, activeMessageIds, etc.) on MESSAGES_SNAPSHOT
- Remove optimistic startAssistantMessage() from streamResponse(); let stream events create the message naturally via TEXT_MESSAGE_START or ensureAssistantMessage()
- Clean up abort listeners on normal waiter resolution to prevent listener accumulation
- Make handleStepFinishedEvent use ensureAssistantMessage() for backward compat with streams that lack TEXT_MESSAGE_START
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): fix reload failures from stale stream state and waiter race
Reset processor stream state (prepareAssistantMessage) in streamResponse()
before the subscription loop, preventing stale messageStates from blocking
new assistant message creation on reload.
Rewrite createDefaultSession with per-subscribe queue isolation: each
subscribe() synchronously installs fresh buffer/waiters, drains pre-buffered
chunks via splice(0), and removes async cleanup that raced with new
subscription cycles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* ci: apply automated fixes
* fix(ai): resolve eslint errors in stream processor
Remove unnecessary `chunk.delta !== undefined` condition (delta is
always a string on TextMessageContentEvent) and remove redundant `!`
non-null assertion inside an already-narrowed `if` block.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): resolve eslint errors in chat-client and session-adapter
Fix import ordering: move value import `createDefaultSession` above
type-only imports. Convert shorthand method signatures to function
property style in the SessionAdapter interface.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai-client): propagate send() errors to subscribe() consumers
Wrap createDefaultSession's send() in try/catch and push a RUN_ERROR
AG-UI event to the queue before re-throwing, so subscribe() consumers
learn about connection failures through the standard protocol. Also
resolve processingResolve on RUN_ERROR in consumeSubscription (same as
RUN_FINISHED) to prevent hangs.
Tests updated: error assertions now check message content rather than
referential identity, since errors flowing through RUN_ERROR create
new Error instances from the message string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): map 'tool' role to 'assistant' in message state to fix lookups
The stream processor mapped 'tool' to 'assistant' for UIMessage but
stored the raw 'tool' role in MessageStreamState. This caused
getActiveAssistantMessageId() and getCurrentAssistantMessageId() to miss
tool-role messages, so subsequent stream events couldn't attach to the
existing message. Now the uiRole mapping is applied consistently across
all three cases in handleTextMessageStartEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): normalize chunk.delta to avoid "undefined" string concatenation
When chunk.delta was undefined, the check `chunk.delta !== ''` evaluated
to true, causing "undefined" to be concatenated into nextText. Use
`chunk.delta ?? ''` to normalize before comparison, matching the safe
pattern already used in handleToolCallArgsEvent.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): use || instead of ?? for chunk.delta fallback to satisfy eslint
The no-unnecessary-condition rule flags ?? since TypeScript types delta
as string. Using || preserves runtime safety and matches existing patterns.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(ai): reset stream flags on MESSAGES_SNAPSHOT to avoid stale state
handleMessagesSnapshotEvent was clearing maps but not resetting isDone,
hasError, and finishReason. Use resetStreamState() which handles all of
these, ensuring finalizeStream() sees fresh state after a snapshot.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor(ai-client): finalize connection adapter unification
Squash all post-groundwork changes into a single commit that completes the connection adapter unification, stream lifecycle hardening, and restoration of ai stream snapshot/state behavior.
Co-authored-by: Cursor <cursoragent@cursor.com>
* ci: apply automated fixes
* fix: return booleans from chat client streamResponse
Keep early stream exits aligned with the Promise<boolean> contract so repo-wide type checks pass after the merge from main.
Made-with: Cursor
* chore: add changeset for durable chat updates
Document the patch releases for the durable subscribe/send chat transport, generation client wrappers, and core stream processing changes included on this branch.
Made-with: Cursor
* feat: add sessionGenerating lifecycle for shared generation activity
Add first-class `sessionGenerating` state to ChatClient and all framework
hooks, derived from stream run events (RUN_STARTED/RUN_FINISHED/RUN_ERROR).
Unlike `isLoading` (request-local), `sessionGenerating` reflects shared
generation activity visible to all subscribers, enabling typing indicators
for non-sender tabs/devices/users. Concurrent runs are tracked via a
Set<runId> so the flag stays true until the last active run completes.
Packages updated: ai-client, ai-react, ai-preact, ai-solid, ai-vue,
ai-svelte — with tests covering concurrent runs, lifecycle resets, and
callback deduplication.
Made-with: Cursor
* fix: make stream finalization run-aware and fix reconnect message dedup
RUN_FINISHED was finalizing all active messages globally, corrupting
concurrent runs. Now tracks active runs by runId and only finalizes
when the last run completes.
Also fixes reconnect/resume: when TEXT_MESSAGE_CONTENT arrives for a
message that already exists (from initialMessages or MESSAGES_SNAPSHOT),
the processor now hydrates transient state from the existing message
instead of creating a duplicate. Segment text is seeded from the
existing last text part so deltas append correctly.
Made-with: Cursor
* fix: clean up framework client type exports
Keep the framework wrapper type definitions lint-clean after the sub-lifecycle rebase so the full repo test suite passes again.
Made-with: Cursor
* chore: add changeset for subscription lifecycle updates
Made-with: Cursor
* ci: apply automated fixes
* fix: resolve approval lookup after RUN_FINISHED and update smoke test state names
Fall back to toolCallToMessage map when resolving approval requests after
RUN_FINISHED clears activeMessageIds. Update smoke test to use current
tool call state names (input-complete, approval-responded, input-streaming).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: treat input-complete as non-terminal in smoke test tool tracking
input-complete means arguments are ready, not that the tool finished
executing. Only treat tool calls as done when output is defined,
approval-responded, or a tool-result part exists.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: James Arthur <thruflo@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Jack Herrington <jherr@pobox.com>