Repository navigation
fix(cloud-agent-sdk): tidy session manager state updates - #8516
Conversation
- Publish the pending-delivery map only when its entries differ from the published one, so a notification for preparation output or a stale event does not replace the atom with an equivalent new map. - Scope the interrupt abort guard to the interrupt call: clear it when the call rejects and expire it 30s after it settles, so a later Aborted reaches errorAtom. - Remove parts, the parts snapshot and the ordering evidence for an id whose message row does not exist, in both storage backends.
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Executive SummaryThe incremental change extracts the interrupt suppression expiry into Assumptions: Files Reviewed (2 files)
Prior finding resolved: re-arming now goes through Previous Review Summary (commit 7962573)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 7962573)Status: 1 Issue Found | Recommendation: Address before merge Executive SummaryThe pending-delivery gating and the storage Assumptions: Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (6 files)
Reviewed by deepseek-flash-latest · Input: 34K · Output: 3.1K · Cached: 207.4K Review guidance: REVIEW.md from base branch |
Re-arming the abort guard for an overlapping interrupt call now drops the previous expiry timer, so its callback cannot end the newer call's window early and let a delayed Aborted reach errorAtom.
Change
Three state-update fixes in
packages/cloud-agent-sdk/src.Pending-delivery map (
session-manager.ts): the service-state subscriber publishednew Map(getPendingMessages())on every notification. The service state notifies for every event and mutates one shared map in place, so preparation step output and stale preparation events replaced the atom with an equivalent new map and invalidated every dependent atom. The write is now gated on an entry comparison (pendingDeliveryStatesEqual): same size, and for each id the samestatus, and for a failed delivery the sameerror,reason, andattempts. A changed entry still publishes a fresh map.Interrupt abort guard (
session-manager.ts): the guard that suppresses theAbortederror produced by the manager's own Stop was cleared only by a matchingAbortedor a full reset, so a rejected or acknowledged-without-abort interrupt left it armed and swallowed a later genuineAbortedinstead of storing it inerrorAtom. It now clears when the interrupt call rejects, and expiresINTERRUPT_ABORT_SUPPRESSION_MS(30 s) after the call settles; the matching-Abortedand reset paths use the sameclearPendingInterrupt()helper, which also drops the pending expiry timer, and re-arming througharmPendingInterruptExpiry()drops a previous expiry first so overlapping interrupt calls cannot end the newer call's window early. A late abort — including one replayed after a reconnect, whose backoff caps at 30 s per attempt — still stays suppressed inside the window. The expiry timer does not hold a Node host open (scheduleUnrefdTimer).Storage
deleteMessage(storage/memory.ts,storage/jotai.ts): both backends drop the message row first and returned early when the row was absent, but both create part rows from a delta before the row exists. A removal for such an id left parts, the parts snapshot, and the ordering evidence behind. Parts cleanup now runs regardless of the row, andparts:<id>is notified when part state existed;message:<id>and the id-list notification stay tied to the row existing.End-to-end evidence
Run on the work VM, in the package:
pnpm exec jestinpackages/cloud-agent-sdk: 35 suites, 1756 tests passed.pnpm exec tsgo --noEmit: clean.oxlintandoxfmt --checkon the six changed files: clean.Behavior observed, not just asserted:
service state integrationtest): acreateServiceStateinstance feeds the manager's subscriber. A preparation step-output event and a question event leave the published pending map at the same identity, and acloud.message.queuedevent publishes the new entry.errorAtominstead of the surfacedAborted;deleteMessagetests, in both backends, read the parts back after the delete.The package is a library with no runnable surface of its own; the service-state integration test above is the runtime proof, driven by the real notifier rather than a mocked subscriber.
Proxy answers
The owner's proxy gave these answers in the owner's place.
Abortedsuppression: bounded window after the interrupt settles, next user turn start, or both?Answer: Bounded window after the interrupt settles
Confidence: 0.60 · Precedent: Speed and least work: do the least amount of work possible, with no drop in quality. One clearing path that never leaves the guard armed indefinitely.
Abortedstill be suppressed?Answer: 30 seconds
Confidence: 0.55 · Precedent: Autonomy: use "is this reasonable?" as the test. 30 s matches the SDK's reconnect backoff cap.
Answer: Gate the atom write only
Confidence: 0.75 · Precedent: Speed and least work: skip work that does not need to be done. Put each separate concern in its own PR.