Conversation
amsg-instant 0.9.0-next.0:
- Default transport switched to SSE (Server-Sent Events). Requests without
`Accept: application/json` now receive `text/event-stream` with each push
framed as `event: payload`, terminated by `event: done`, and `event: error`
for in-stream business failures. HTTP 200 throughout; status codes no
longer carry error semantics.
- `Accept: application/json` opts back into the 0.8.x pure Web Push path,
byte-for-byte preserved (`{success, data}` body, 1500ms pacing, HTTP
status mapping).
- New `onBeforeLoop` / `onAfterLoop` lifecycle hooks for kicking off side
tasks in parallel with the main LLM loop and flushing extra payloads
before stream close. Active in both transport modes.
- Best-effort fallback: SSE write failure or client disconnect routes
subsequent payloads to Web Push via `pushSubscription` (still required
in the SSE body). Same `messageId` across both transports; client
dedupes by id.
- HookError no longer echoed as `event: error` after its in-loop diagnostic
already shipped as `event: payload` (dedup decision — see CHANGELOG).
- Internal transport abstraction: all push paths funnel through
`deliverPush()` + `ensureStableMessageId()`. Auto-generated messageId
format changed from `msg_<uuid>_chunk_<i>` to `msg_<uuid>`.
- SSE mode drops the 1500ms inter-push spacing (push gateway concern, not
stream concern); pure-push keeps it.
- Module-scoped TextEncoder + pre-encoded keepalive/done bytes; constants
(`MESSAGE_TYPE.INSTANT` / `PUSH_SOURCE.INSTANT`) instead of literals
through the processor; safer controller.close() / enqueue paths.
amsg-client 2.4.0-next.0:
- New `consumeInstantStream(payload, endpointPath?, options)` for consuming
amsg-instant 0.9.0+ SSE responses. Calls `options.onPayload` per frame,
optional `onError` / `onDone` / `signal`.
- Always-rejects-on-failure semantics. `onError` is a notification
side-channel that fires before the rejection, not a try/catch replacement.
- SSE spec compliance: multi-line `data:` concatenated with `\n`; non-2xx
and non-`text/event-stream` responses surface as immediate errors.
- `reader.cancel(err)` on failure to close the underlying connection.
- `sendInstant()` byte-for-byte unchanged.
Pre-release pinned to `next` dist-tag — do NOT promote
amsg-instant@0.9.0 to latest before the downstream SSE consumer
(amsg-client 2.4.0-next.0+ `consumeInstantStream`) is wired up in the app.
Tests: 177/177 pass (171 instant + 6 client). New parallel SSE-mode
coverage added for each existing pure-push test scenario.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
0.9.0-next.0 introduced the SSE-default transport but did not register the async tail of `ReadableStream.start()` with `ctx.waitUntil`. On runtimes like Cloudflare Workers that reclaim the isolate once the response stream stops emitting bytes, this left the post-disconnect fallback path — `await sendPushWithMaybeBlob(...)` against the push gateway — at risk of being killed mid-flight, even though the rest of the framework treats that fallback as the documented best-effort recovery for client disconnect. Wire a `startDone` deferred that the runtime adapter can attach via `ctx.waitUntil`; `start()`'s finally resolves it after cleanup, so the isolate stays alive long enough for the in-progress fallback fetch to complete. Actual window is bounded by the runtime / plan budget, not the framework — CHANGELOG and README intentionally avoid quoting any specific seconds. Adds a cloudflare-adapter.test.mjs case that asserts SSE responses register exactly one waitUntil and that the deferred resolves once the stream drains. Full Miniflare/workerd disconnect-path verification remains future work. Tests: 172/172 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…irective typedef - Add silent? boolean field; validate in buildContentPush / buildToolRequestPush. - Typedef now acknowledges per-field top-level fallback for tag / renotify / requireInteraction / silent / data, matching what amsg-sw has always done at runtime. - README gains a Notification directive section with per-field reference table. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ation repair fixes - Add packagewise delivery dedupe (IndexedDB add() + keyPath atomic claim, TTL lazy cleanup). Web Push, multipart, blob envelope, and page bridge payloads all flow through the same gate before notification / postMessage / onBusinessPayload. - Add REI_AMSG_DELIVER message protocol so SSE page bridges and Web Push share the same SW pipeline. - Add notification repair: if the first delivery suppresses the notification (e.g. visible client) but a later backup satisfies notification.show, SW renders exactly one notification through onDuplicate. - Fix: notificationStatePending now clears as soon as the notification policy is settled. Previously a slow onBusinessPayload kept pending set, swallowing backup-driven repairs as 'first-delivery-pending'. - Fix: dedupe.storeName is no longer configurable (passing it throws). Changing storeName under the same dbName requires IDB version migration that this package does not provide; isolate via dbName instead. - Spec updated to reflect the new dedupe + bridge flow. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SSE is the default transport. Successful SSE enqueue ALSO schedules a Web Push backup with the same messageId (always-on); amsg-sw / client dedupe collapses the two transports back to one delivery. - Reject sse.backupPush:'off'|'delayed' and sse.backupDelayMs configs at normalization — those modes had known payload-loss windows in production. - Add SSE keepalive controls: immediateKeepalive (default true) and keepaliveMs (default 1000, clamped to >= 250). - Add SSE transport onEvent diagnostics: sse_payload_enqueued / _failed / _aborted / _canceled, backup_push_scheduled / _sent / _failed, fallback_push_sent / _failed. - Fix: ReadableStream.cancel(reason) marks the stream unusable so subsequent payloads fall back to Web Push. - Fix: blob envelopes carry the original payload's messageId / id / dedupeKey so SSE + blob backup share the same dedupe key in amsg-sw. - Drop delayMs field from backup_push_* events (always 0 after the always-on rewrite). - Centralise waitForPushCalls helper; handler tests reuse the shared one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…adBytes
- Add consumeInstantStream(payload, endpointPath?, options) — SSE consumer
for amsg-instant 0.9.0+. Parses event: payload / error / done and dispatches
to options.onPayload. Encryption / cleartext transports share the same
constructor config as sendInstant().
- Remove default 3KB payload size cap. Web Push body limits are handled by
amsg-instant's BlobStore / multipart path; client only keeps avatarUrl
soft-clear to prevent base64 avatars blowing up push delivery.
- New constructor option maxPayloadBytes?: number | null. Default null (no
SDK-level cap); set explicitly to enable PAYLOAD_TOO_LARGE_LOCAL preflight.
- README updated to describe SSE + always-on Web Push backup + dedupe
topology accurately ('fallback' now strictly means stream-unusable /
enqueue-throw, not the always-on backup itself).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… 2.5.0 Version bumps: - @rei-standard/amsg-shared: 0.1.0 → 0.2.0 - @rei-standard/amsg-sw: 2.1.1 → 2.2.0 - @rei-standard/amsg-instant: 0.9.0-next.1 → 0.9.0 - @rei-standard/amsg-client: 2.4.0-next.0 → 2.4.0 - @rei-standard/amsg-server: 2.4.1 → 2.5.0 (shared dep bump only, no behavior change) - bump.mjs updated to current targets - package-lock.json refreshed - repository URLs normalized to git+https://...git form across packages - server CHANGELOG records the shared 0.2.0 dependency bump - root + amsg workspace README tables updated with new versions and feature blurbs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Code Review
This pull request updates the @rei-standard/amsg ecosystem to support Server-Sent Events (SSE) as the default transport mode with always-on Web Push backup, introducing stream consumption in the client, lifecycle hooks in the instant handler, and a robust delivery deduplication gate in the Service Worker. Feedback on the changes highlights a JSDoc typo in the Service Worker configuration regarding IndexedDB migration boundaries, and suggests refactoring an unintuitive control flow in the client's SSE parser where a return is used to implicitly trigger a finally block error throw.
The note read "本包不维护跨 dbName 的迁移逻辑", which describes a non-existent IDB operation (each dbName is an independent IndexedDB instance). The actual reason storeName is locked down is that changing storeName under the same dbName needs version migration, which this package does not implement. Correct the note to "跨 storeName 的迁移 逻辑". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…inally re-throw The 'error' event branch in consumeInstantStream used to assign to a 'thrown' var and return, relying on the finally block to re-throw. Semantically equivalent to throwing directly, but reads like a successful Promise resolution — which is the opposite of the actual behavior. Throw the error inline; the outer catch still captures it into 'thrown' and finally still surfaces the same rejection. No behavior change. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
跨 5 个包的协调发版,主线是 SSE 默认传输 + always-on Web Push backup + SW 端 dedupe 收敛。
amsg-sharednotification.silent字段;NotificationDirectivetypedef 与 SW 实际 fallback 行为对齐amsg-swREI_AMSG_DELIVER页面投递桥 + notification 补救amsg-instantmessageId的 Web Push backup;keepalive 可配;SSE 事件诊断补齐amsg-clientconsumeInstantStream()SSE consumer;maxPayloadBytes改为 opt-in(默认不限制)amsg-server整套链路:SSE 直送主线程 → 同时 Web Push backup 常开 → 在 SW 端按
messageIddedupe 收敛成一次业务投递与最多一次通知;第一次到达没弹通知时,backup 可以补一次。Test plan
amsg-swtests: 40/40 pass(含 dedupe / notification repair /storeName配置校验回归测试)amsg-instanttests: 180/180 passamsg-clienttests: 9/9 passamsg-sharedtests: 33/33 passnpm pack安装)已通过shared@0.2.0/sw@2.2.0/instant@0.9.0/client@2.4.0/server@2.5.0),OIDC trusted publishing 走通Notes
🤖 Generated with Claude Code