Skip to content

release: amsg shared 0.2.0 / sw 2.2.0 / instant 0.9.0 / client 2.4.0 / server 2.5.0#10

Merged
Tosd0 merged 9 commits into
mainfrom
dev
May 31, 2026
Merged

release: amsg shared 0.2.0 / sw 2.2.0 / instant 0.9.0 / client 2.4.0 / server 2.5.0#10
Tosd0 merged 9 commits into
mainfrom
dev

Conversation

@Tosd0

@Tosd0 Tosd0 commented May 31, 2026

Copy link
Copy Markdown
Owner

Summary

跨 5 个包的协调发版,主线是 SSE 默认传输 + always-on Web Push backup + SW 端 dedupe 收敛

版本 关键改动
amsg-shared 0.2.0 notification.silent 字段;NotificationDirective typedef 与 SW 实际 fallback 行为对齐
amsg-sw 2.2.0 Packagewise delivery dedupe(IndexedDB 原子 claim)+ REI_AMSG_DELIVER 页面投递桥 + notification 补救
amsg-instant 0.9.0 SSE 默认传输;成功 enqueue 也发同 messageId 的 Web Push backup;keepalive 可配;SSE 事件诊断补齐
amsg-client 2.4.0 consumeInstantStream() SSE consumer;maxPayloadBytes 改为 opt-in(默认不限制)
amsg-server 2.5.0 仅 shared 依赖 bump,无运行时改动

整套链路:SSE 直送主线程 → 同时 Web Push backup 常开 → 在 SW 端按 messageId dedupe 收敛成一次业务投递与最多一次通知;第一次到达没弹通知时,backup 可以补一次。

Test plan

  • amsg-sw tests: 40/40 pass(含 dedupe / notification repair / storeName 配置校验回归测试)
  • amsg-instant tests: 180/180 pass
  • amsg-client tests: 9/9 pass
  • amsg-shared tests: 33/33 pass
  • 下游 app 本地联调(5 个包 npm pack 安装)已通过
  • 5 个包已发到 npm latest(shared@0.2.0 / sw@2.2.0 / instant@0.9.0 / client@2.4.0 / server@2.5.0),OIDC trusted publishing 走通

Notes

  • 发包用了 `workflow_dispatch` sweep-all,因为之前 push 的 5 个 per-package tag 没触发 webhook(GitHub 偶发漏触发)。tag 已经在远端 `a7f73a9` 上保留,npm 端版本以 `isVersionPublished()` 兜底,不会重复发。

🤖 Generated with Claude Code

Tosd0 and others added 7 commits May 30, 2026 18:51
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>

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread packages/rei-standard-amsg/sw/src/index.js Outdated
Comment thread packages/rei-standard-amsg/client/src/index.js Outdated
Tosd0 and others added 2 commits June 1, 2026 07:24
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>
@Tosd0 Tosd0 merged commit 9be45ff into main May 31, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant