From 9b787e5b38a56a6eef7b21889523c33fb314a4d7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 Jun 2026 17:04:01 +0000 Subject: [PATCH] Version Packages --- .changeset/client-avatar-url-align.md | 5 -- .changeset/instant-gzip-request-body.md | 5 -- .changeset/server-vapid-subject-fix.md | 5 -- .../shared-validation-vapid-reasoning.md | 9 --- package-lock.json | 18 +++--- .../rei-standard-amsg/client/CHANGELOG.md | 29 +++++++--- .../rei-standard-amsg/client/package.json | 4 +- .../rei-standard-amsg/instant/CHANGELOG.md | 55 ++++++++++++------- .../rei-standard-amsg/instant/package.json | 4 +- .../rei-standard-amsg/server/CHANGELOG.md | 22 +++++--- .../rei-standard-amsg/server/package.json | 4 +- .../rei-standard-amsg/shared/CHANGELOG.md | 25 ++++++--- .../rei-standard-amsg/shared/package.json | 2 +- packages/rei-standard-amsg/sw/CHANGELOG.md | 34 ++++++++---- packages/rei-standard-amsg/sw/package.json | 4 +- 15 files changed, 127 insertions(+), 98 deletions(-) delete mode 100644 .changeset/client-avatar-url-align.md delete mode 100644 .changeset/instant-gzip-request-body.md delete mode 100644 .changeset/server-vapid-subject-fix.md delete mode 100644 .changeset/shared-validation-vapid-reasoning.md diff --git a/.changeset/client-avatar-url-align.md b/.changeset/client-avatar-url-align.md deleted file mode 100644 index 3bc211e..0000000 --- a/.changeset/client-avatar-url-align.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rei-standard/amsg-client": minor ---- - -`avatarUrl` 本地预检改用 `@rei-standard/amsg-shared` 的统一校验,与 server / instant 对齐。现在非法(非 `data:`)URL —— 例如缺少协议的 `foo.com/a.png` —— 也会在客户端被 `console.warn` 并置空;此前 client 只检查 `data:` 与长度,会放行这类 URL(之后由服务端兜底置空)。软清空策略不变:装饰性字段不合法时只做清空,不会让整条请求失败。 diff --git a/.changeset/instant-gzip-request-body.md b/.changeset/instant-gzip-request-body.md deleted file mode 100644 index 6b824bd..0000000 --- a/.changeset/instant-gzip-request-body.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rei-standard/amsg-instant": minor ---- - -接收端支持 gzip 压缩的请求体。带 `X-Amsg-Request-Encoding: gzip` 头的请求会先 gunzip 再解析,不带这个头的请求按原样读取,行为不变。CORS 预检白名单里也加上了这个头。这样 `@rei-standard/amsg-client` 的 `deliver({ compressRequest })` 就能直接发到 `amsg-instant` 的 `/instant` / `/continue`,不用自己在后端解压。 diff --git a/.changeset/server-vapid-subject-fix.md b/.changeset/server-vapid-subject-fix.md deleted file mode 100644 index 7c9ec1d..0000000 --- a/.changeset/server-vapid-subject-fix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@rei-standard/amsg-server": patch ---- - -VAPID subject 规范化支持 `https:` 形式:RFC 8292 允许 subject 使用 `https:`,规范化时按原样保留,不另加 `mailto:` 前缀。reasoning 私有思考过滤、`avatarUrl` 校验、VAPID subject 规范化统一改用 `@rei-standard/amsg-shared` 的实现。 diff --git a/.changeset/shared-validation-vapid-reasoning.md b/.changeset/shared-validation-vapid-reasoning.md deleted file mode 100644 index 501f4e5..0000000 --- a/.changeset/shared-validation-vapid-reasoning.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@rei-standard/amsg-shared": minor ---- - -新增三组共享纯函数,让 server / instant / client 复用同一份规则,不再各自维护副本: - -- `validateAvatarUrl`(含 `isValidUrl` 与 `AVATAR_URL_MAX_LENGTH`)—— 头像 URL 校验 -- `normalizeVapidSubject` —— VAPID subject 规范化(`mailto:` / `https:` 均保留,裸邮箱补 `mailto:`) -- `readReasoningContent` / `stripReasoningTags` —— 读取推理内容与剥离私有 `` 链式思考 diff --git a/package-lock.json b/package-lock.json index f387f14..f613710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3090,10 +3090,10 @@ }, "packages/rei-standard-amsg/client": { "name": "@rei-standard/amsg-client", - "version": "2.7.0", + "version": "2.8.0", "license": "MIT", "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0", @@ -3105,10 +3105,10 @@ }, "packages/rei-standard-amsg/instant": { "name": "@rei-standard/amsg-instant", - "version": "0.9.1", + "version": "0.10.0", "license": "MIT", "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0", @@ -3120,11 +3120,11 @@ }, "packages/rei-standard-amsg/server": { "name": "@rei-standard/amsg-server", - "version": "2.5.2", + "version": "2.5.3", "license": "MIT", "dependencies": { "@netlify/blobs": "^8.1.0", - "@rei-standard/amsg-shared": "^0.2.0", + "@rei-standard/amsg-shared": "^0.3.0", "web-push": "^3.6.7" }, "devDependencies": { @@ -3151,7 +3151,7 @@ }, "packages/rei-standard-amsg/shared": { "name": "@rei-standard/amsg-shared", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "devDependencies": { "tsup": "^8.0.0", @@ -3163,10 +3163,10 @@ }, "packages/rei-standard-amsg/sw": { "name": "@rei-standard/amsg-sw", - "version": "2.3.1", + "version": "2.3.2", "license": "MIT", "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0", diff --git a/packages/rei-standard-amsg/client/CHANGELOG.md b/packages/rei-standard-amsg/client/CHANGELOG.md index 7047e24..7d886ca 100644 --- a/packages/rei-standard-amsg/client/CHANGELOG.md +++ b/packages/rei-standard-amsg/client/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog — @rei-standard/amsg-client +## 2.8.0 + +### Minor Changes + +- 5c0e047: `avatarUrl` 本地预检改用 `@rei-standard/amsg-shared` 的统一校验,与 server / instant 对齐。现在非法(非 `data:`)URL —— 例如缺少协议的 `foo.com/a.png` —— 也会在客户端被 `console.warn` 并置空;此前 client 只检查 `data:` 与长度,会放行这类 URL(之后由服务端兜底置空)。软清空策略不变:装饰性字段不合法时只做清空,不会让整条请求失败。 + +### Patch Changes + +- Updated dependencies [5c0e047] + - @rei-standard/amsg-shared@0.3.0 + ## 2.7.0 — `deliver()` 新增 `compressRequest` 请求体 gzip 压缩 给 `deliver()` 加一个**可选**的 `compressRequest`,把要发出去的请求体在上网线之前 gzip 压一下。中文 + 重复结构的 JSON 压缩比很高(实测 ~322KB 能压到 ~50KB),网线上字节小了,大 body 在慢/不稳的上行链路上就能在「发了没回应就杀」的超时之前传完。压的是**请求**,不是响应;上下文内容一字不动,只是传输层省字节。 @@ -63,11 +74,11 @@ ### Migration -| 旧写法 | 新写法 | -| --- | --- | +| 旧写法 | 新写法 | +| ----------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `try { await consumeInstantStream(p, '/instant', { onPayload }) } catch { fail() }` | `const r = await deliver(p, { delivery: { mode: 'observed', observed }, timeoutMs, onChunk: onPayload }); if (r.outcome !== 'delivered') ...` | -| `const r = await sendInstant(p); if (!r.success) fail()` | `const r = await deliver(p, { delivery: { mode: 'observed', observed }, timeoutMs }); if (r.outcome === 'send-failed') ...` | -| `sendInstant(p, '/instant', { authorization: 'Bearer ...' })` | `deliver(p, { delivery, timeoutMs, authorization: 'Bearer ...' })` | +| `const r = await sendInstant(p); if (!r.success) fail()` | `const r = await deliver(p, { delivery: { mode: 'observed', observed }, timeoutMs }); if (r.outcome === 'send-failed') ...` | +| `sendInstant(p, '/instant', { authorization: 'Bearer ...' })` | `deliver(p, { delivery, timeoutMs, authorization: 'Bearer ...' })` | 详见 README 的 `deliver()` 标准用法与「为什么需要 `deliver()`」段。 @@ -136,11 +147,11 @@ Self-review 时(仿 ultrareview 多角度分派)抓到的 correctness 修复 POST 到 amsg-instant 的 `/instant` 或 `/continue` 端点,按 SSE frame 解析 `event: payload` / `event: error` / `event: done`,分发到 `options.onPayload` 回调;可被 `options.signal` 中止。 ```js -await client.consumeInstantStream(payload, '/instant', { - onPayload: async (p) => routeToIDB(p), // 必填 - onError: (err) => log(err), // 可选;通知用,不抑制 throw - onDone: () => stopSpinner(), // 可选 - signal: abortController.signal, // 可选 +await client.consumeInstantStream(payload, "/instant", { + onPayload: async (p) => routeToIDB(p), // 必填 + onError: (err) => log(err), // 可选;通知用,不抑制 throw + onDone: () => stopSpinner(), // 可选 + signal: abortController.signal, // 可选 }); ``` diff --git a/packages/rei-standard-amsg/client/package.json b/packages/rei-standard-amsg/client/package.json index 5cba075..3261878 100644 --- a/packages/rei-standard-amsg/client/package.json +++ b/packages/rei-standard-amsg/client/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-client", - "version": "2.7.0", + "version": "2.8.0", "description": "ReiStandard Active Messaging browser client SDK — also re-exports shared push types, builders, and guards from @rei-standard/amsg-shared", "repository": { "type": "git", @@ -33,7 +33,7 @@ "node": ">=20" }, "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0", diff --git a/packages/rei-standard-amsg/instant/CHANGELOG.md b/packages/rei-standard-amsg/instant/CHANGELOG.md index 51aa429..276fd94 100644 --- a/packages/rei-standard-amsg/instant/CHANGELOG.md +++ b/packages/rei-standard-amsg/instant/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog — @rei-standard/amsg-instant +## 0.10.0 + +### Minor Changes + +- f4812ce: 接收端支持 gzip 压缩的请求体。带 `X-Amsg-Request-Encoding: gzip` 头的请求会先 gunzip 再解析,不带这个头的请求按原样读取,行为不变。CORS 预检白名单里也加上了这个头。这样 `@rei-standard/amsg-client` 的 `deliver({ compressRequest })` 就能直接发到 `amsg-instant` 的 `/instant` / `/continue`,不用自己在后端解压。 + +### Patch Changes + +- Updated dependencies [5c0e047] + - @rei-standard/amsg-shared@0.3.0 + ## 0.9.1 — SSE stream lifecycle owns LLM + push completion - **Fix**: SSE 模式下 LLM 调用与每条 payload 的 Web Push backup / fallback 完整运行在 `ReadableStream.start()` 内——`start()` 先 await 所有 backup 推送,再 `controller.close()`。响应仍在产出期间 runtime 不会施加 wall-clock 上限,慢 LLM + 客户端中途断开(iOS Safari 杀掉后台 SSE socket、页面切走等)的组合下也能把这一轮消息送达。 @@ -191,12 +202,12 @@ Install with `npm install @rei-standard/amsg-instant@next`. Pre-release — brea ### Migration cheat sheet -| next.3 | next.4 | -|-------------------------------------------------------------------------|-------------------------------------------------------------------------------| -| `return { decision: 'finish', pushPayload: { ... } }` | `return { decision: 'finish', pushPayloads: [{ ... }] }` | -| Request body `splitPattern: '([。!?!?]+)'` | Implement the split in your hook; return one push per segment | -| `pushPayload.splitPattern: null` (per-push disable from next.3) | Return `pushPayloads: [singleUnsplit]` | -| `reasoningSplitPattern` request field | Set `autoEmitReasoning: false`, build N reasoning pushes yourself with `buildReasoningPush(...)`, include them at the start of `pushPayloads` | +| next.3 | next.4 | +| --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| `return { decision: 'finish', pushPayload: { ... } }` | `return { decision: 'finish', pushPayloads: [{ ... }] }` | +| Request body `splitPattern: '([。!?!?]+)'` | Implement the split in your hook; return one push per segment | +| `pushPayload.splitPattern: null` (per-push disable from next.3) | Return `pushPayloads: [singleUnsplit]` | +| `reasoningSplitPattern` request field | Set `autoEmitReasoning: false`, build N reasoning pushes yourself with `buildReasoningPush(...)`, include them at the start of `pushPayloads` | ### Why breaking in pre-release @@ -239,19 +250,20 @@ Coordinated with `@rei-standard/amsg-shared@0.1.0-next.2`. Install with `npm ins - **`reasoningSplitPattern` / `errorSplitPattern` payload 字段** — 按 `messageKind` 独立的句号切配置: - | `messageKind` | 字段 | 默认 | - |----------------|---------------------------|---------------------| - | `content` | `splitPattern` | `/([。!?!?]+)/` (开) | - | `tool_request` | `splitPattern` | `/([。!?!?]+)/` (开) | - | `reasoning` | `reasoningSplitPattern` | **不切** | - | `error` | `errorSplitPattern` | **不切** | - | 自由 payload | — | 不切 | + | `messageKind` | 字段 | 默认 | + | -------------- | ----------------------- | ---------------------- | + | `content` | `splitPattern` | `/([。!?!?]+)/` (开) | + | `tool_request` | `splitPattern` | `/([。!?!?]+)/` (开) | + | `reasoning` | `reasoningSplitPattern` | **不切** | + | `error` | `errorSplitPattern` | **不切** | + | 自由 payload | — | 不切 | 四个 kind 共享的「禁用」语义:显式 `null` 或 `[]` 关闭切分。差别在 `undefined`(字段省略):`content` / `tool_request` 回落默认句号正则;`reasoning` / `error` 保持不切(这俩历史上就没切片 UX,默认 off 才符合预期)。 - **`reasoningChunkBytes` handler option(默认 2000,`null` 禁用)** — `ReasoningPush.reasoningContent` 的 UTF-8 字节上限。reasoning-heavy LLM(DeepSeek-R1 / GLM-4.5 / Qwen3-Thinking)经常输出 3-10 KB reasoning,超 Web Push ~2.6 KB 上限。next.2 内置 transparent 字节切分:超限时按 UTF-8 codepoint 边界切成 N 份,每片带 `chunkIndex` / `totalChunks`,SW 按这两个字段拼回完整字符串。**绝大多数 reasoning-heavy 部署不再需要 BlobStore。** `createInstantHandler` 构造期校验 `reasoningChunkBytes ∈ [500, maxInlineBytes - 600]`(600 B 余量给 push payload 元字段),不合法抛 `TypeError`。 - **两层 cascade(Layer 1 句切 → Layer 2 字节切)** — `reasoningSplitPattern` 先按句切成 M 段,每段单独量字节,超阈值的段再字节切成 N 块。最终 push 同时带两组索引: + - Layer 1:`messageIndex` 1..M / `totalMessages` M(M=1 时不写) - Layer 2:`chunkIndex` 1..N / `totalChunks` N(N=1 时不写) @@ -315,15 +327,15 @@ Coordinated minor across the whole amsg ecosystem. This release replaces the leg ### Migration from 0.7.x -| 0.7.x | 0.8.0 | -|------------------------------------------------------------------------|------------------------------------------------------------------------------------------| -| `buildInstantPushPayload({ message, index, total, contactName, ... })` | `buildContentPush({ messageType: 'instant', source: 'instant', messageId, sessionId, message, messageIndex, totalMessages, contactName, ... })` from `@rei-standard/amsg-instant` | -| Hook payload `{ type: 'tool-request', ... }` (free-form) | Either keep it free-form (still legal — `pushPayload: unknown`) or call `buildToolRequestPush({ ... })` for a typed envelope | -| SW dispatch by ad-hoc field sniffing on push payload | SW dispatch by `payload.messageKind` switch (consume the shared `AmsgPush` discriminated union) | -| `{ type: 'error', code: 'HOOK_THREW', message, sessionId, iteration }` | Auto-built — no caller-side change needed; the wire shape now uses `messageKind: 'error'` instead of `type: 'error'` | -| Hook fully owned every push (incl. reasoning, if you built one) | Framework auto-emits `ReasoningPush` before the hook runs. Set `autoEmitReasoning: false` on `createInstantHandler({...})` to restore total hook control. | +| 0.7.x | 0.8.0 | +| ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `buildInstantPushPayload({ message, index, total, contactName, ... })` | `buildContentPush({ messageType: 'instant', source: 'instant', messageId, sessionId, message, messageIndex, totalMessages, contactName, ... })` from `@rei-standard/amsg-instant` | +| Hook payload `{ type: 'tool-request', ... }` (free-form) | Either keep it free-form (still legal — `pushPayload: unknown`) or call `buildToolRequestPush({ ... })` for a typed envelope | +| SW dispatch by ad-hoc field sniffing on push payload | SW dispatch by `payload.messageKind` switch (consume the shared `AmsgPush` discriminated union) | +| `{ type: 'error', code: 'HOOK_THREW', message, sessionId, iteration }` | Auto-built — no caller-side change needed; the wire shape now uses `messageKind: 'error'` instead of `type: 'error'` | +| Hook fully owned every push (incl. reasoning, if you built one) | Framework auto-emits `ReasoningPush` before the hook runs. Set `autoEmitReasoning: false` on `createInstantHandler({...})` to restore total hook control. | | Hook returned `pushPayload` without a `sessionId` field | **Set `sessionId: ctx.sessionId`** in your hook's `pushPayload`. The framework does NOT auto-inject it (the `pushPayload: unknown` contract is preserved). Without this the SW can't pair your content push with the auto-emitted ReasoningPush. | -| Legacy path push failure aborted the whole burst | Reasoning-push failure is now best-effort (`reasoning_push_failed` event + continue). Content-push failures still abort, same as before. | +| Legacy path push failure aborted the whole burst | Reasoning-push failure is now best-effort (`reasoning_push_failed` event + continue). Content-push failures still abort, same as before. | If you have a hook that builds its own pushPayload object, **set `sessionId: ctx.sessionId`** in it so the SW can pair your content push with the auto-emitted ReasoningPush. @@ -423,6 +435,7 @@ If you have a hook that builds its own pushPayload object, **set `sessionId: ctx - **CORS 内置**:handler 在入口处短路 `OPTIONS` 预检请求 → `204 No Content`,所有响应(含 200 / 4xx / 5xx)自动叠 `Access-Control-Allow-Origin / -Methods / -Headers` + `Access-Control-Max-Age: 86400`。浏览器跨域调用零配置 work。 - `options.cors?: { allowOrigin?: string }`:自定义允许来源,默认 `'*'`。配成具体来源时自动附 `Vary: Origin`,避免反向代理缓存把 CORS policy 串到错的站点。 - **`normalizeAiApiUrl(apiUrl)`** 智能补全 OpenAI 兼容路径,**幂等**(跑两次 = 跑一次): + - 裸 host(如 `https://api.openai.com`)→ 补 `/v1/chat/completions` - 末尾是 `/v1` 或 `/v2` 等版本段 → 只补 `/chat/completions`,**不会重复加 v1** - 已含 `/chat/completions` → 原样返回 diff --git a/packages/rei-standard-amsg/instant/package.json b/packages/rei-standard-amsg/instant/package.json index dd37d7c..ea85afd 100644 --- a/packages/rei-standard-amsg/instant/package.json +++ b/packages/rei-standard-amsg/instant/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-instant", - "version": "0.9.1", + "version": "0.10.0", "description": "ReiStandard Active Messaging — agentic-loop framework for instant push. Pluggable per-turn hook + optional blob envelope for oversize payloads. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content. Pure Web Crypto. Deployable to Cloudflare Workers / Vercel Edge / Netlify / Node with no flags.", "repository": { "type": "git", @@ -84,7 +84,7 @@ "node": ">=18" }, "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0", diff --git a/packages/rei-standard-amsg/server/CHANGELOG.md b/packages/rei-standard-amsg/server/CHANGELOG.md index 27ebfe0..65c9615 100644 --- a/packages/rei-standard-amsg/server/CHANGELOG.md +++ b/packages/rei-standard-amsg/server/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog — @rei-standard/amsg-server +## 2.5.3 + +### Patch Changes + +- 5c0e047: VAPID subject 规范化支持 `https:` 形式:RFC 8292 允许 subject 使用 `https:`,规范化时按原样保留,不另加 `mailto:` 前缀。reasoning 私有思考过滤、`avatarUrl` 校验、VAPID subject 规范化统一改用 `@rei-standard/amsg-shared` 的实现。 +- Updated dependencies [5c0e047] + - @rei-standard/amsg-shared@0.3.0 + ## 2.5.2 — in-server instant 路径恢复为一等公民 - **文档**:移除 `schedule-message` 中 `messageType: 'instant'` 两处 JSDoc 的 `@deprecated Soft-deprecated` 标记;该路径(create task → process by UUID → delete task)现以正式支持路径身份记录,不再携带弃用暗示。 @@ -50,13 +58,13 @@ Coordinated minor across the whole amsg ecosystem. The server's push wire shape ### Migration from 2.3.x -| 2.3.x | 2.4.0 | -|--------------------------------------------------------|-------------------------------------------------------------------------------------| -| Hand-rolled 13-field `notificationPayload` | `buildContentPush({...})` from `@rei-standard/amsg-shared` | -| `messagesSent` reflects sentence count | Unchanged — still sentence count. ReasoningPush is auxiliary, not counted. | -| Push payload has no `messageKind` | Push payload carries `messageKind: 'content'`. SW dispatch on `payload.messageKind` | -| Push payload has no `sessionId` | Push payload carries `sessionId`. Same id across ReasoningPush + ContentPush burst | -| No reasoning push | If LLM returns non-empty `reasoning_content`, a separate `ReasoningPush` is sent first | +| 2.3.x | 2.4.0 | +| ------------------------------------------ | -------------------------------------------------------------------------------------- | +| Hand-rolled 13-field `notificationPayload` | `buildContentPush({...})` from `@rei-standard/amsg-shared` | +| `messagesSent` reflects sentence count | Unchanged — still sentence count. ReasoningPush is auxiliary, not counted. | +| Push payload has no `messageKind` | Push payload carries `messageKind: 'content'`. SW dispatch on `payload.messageKind` | +| Push payload has no `sessionId` | Push payload carries `sessionId`. Same id across ReasoningPush + ContentPush burst | +| No reasoning push | If LLM returns non-empty `reasoning_content`, a separate `ReasoningPush` is sent first | If you have a SW that hand-sniffs push fields, switch to the `messageKind` discriminator. If you have a client that pairs server-sent sentences (e.g. via `messageId` regex), use `sessionId` instead — it's stable and explicit. diff --git a/packages/rei-standard-amsg/server/package.json b/packages/rei-standard-amsg/server/package.json index 63c9cbd..2efeaa7 100644 --- a/packages/rei-standard-amsg/server/package.json +++ b/packages/rei-standard-amsg/server/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-server", - "version": "2.5.2", + "version": "2.5.3", "description": "ReiStandard Active Messaging server SDK with pluggable database adapters. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content.", "repository": { "type": "git", @@ -33,7 +33,7 @@ "node": ">=20" }, "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0", + "@rei-standard/amsg-shared": "^0.3.0", "web-push": "^3.6.7", "@netlify/blobs": "^8.1.0" }, diff --git a/packages/rei-standard-amsg/shared/CHANGELOG.md b/packages/rei-standard-amsg/shared/CHANGELOG.md index 9ddaadb..3b3f1b5 100644 --- a/packages/rei-standard-amsg/shared/CHANGELOG.md +++ b/packages/rei-standard-amsg/shared/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog — @rei-standard/amsg-shared +## 0.3.0 + +### Minor Changes + +- 5c0e047: 新增三组共享纯函数,让 server / instant / client 复用同一份规则,不再各自维护副本: + + - `validateAvatarUrl`(含 `isValidUrl` 与 `AVATAR_URL_MAX_LENGTH`)—— 头像 URL 校验 + - `normalizeVapidSubject` —— VAPID subject 规范化(`mailto:` / `https:` 均保留,裸邮箱补 `mailto:`) + - `readReasoningContent` / `stripReasoningTags` —— 读取推理内容与剥离私有 `` 链式思考 + ## 0.2.0 — Notification silent support ### New @@ -17,6 +27,7 @@ ## 0.1.0 — NotificationDirective 与 Shared utilities ### New + - **Shared Utilities**:新增并导出了底层工具函数 `base64UrlToBytes`, `toUint8`, 和 `concatBytes`,统一了底层依赖。 - **NotificationDirective**:新增了对 `notification.show` (`"auto"` | `"always"` | `"when-hidden"` | `false`) 参数的类型定义与验证逻辑。 @@ -75,7 +86,7 @@ the reverse. - `MessageKind` / `MessageType` / `PushSource` type aliases + matching runtime constants (`MESSAGE_KIND`, `MESSAGE_TYPE`, `PUSH_SOURCE`). - Discriminated union `AmsgPush = ContentPush | ReasoningPush | - ToolRequestPush | ErrorPush`, with `messageKind` as the literal-type +ToolRequestPush | ErrorPush`, with `messageKind` as the literal-type tag (TS consumers can `switch (push.messageKind)` and narrow). - Common-fields `@typedef` `AmsgPushCommon` capturing the universal shape (`messageType` / `source` / `messageId` / `sessionId` / @@ -102,9 +113,9 @@ The 0.7.x `amsg-instant` legacy push (13 fields, no `messageKind`) and the standalone `{ type: 'error', code: '...' }` envelope are both gone in the upstream packages that consume this. Use: -| Was (0.7.x) | Now (≥ 0.1.0 of shared, ≥ 0.8.0 of instant) | -|-------------------------------------------------|--------------------------------------------------| -| 13-field instant push | `buildContentPush({...})` | -| `{ type: 'error', code: 'HOOK_THREW', ...}` | `buildErrorPush({ code: 'HOOK_THREW', ... })` | -| `{ type: 'error', code: 'LOOP_EXCEEDED', ...}` | `buildErrorPush({ code: 'LOOP_EXCEEDED', ... })` | -| (no equivalent — reasoning was discarded) | `buildReasoningPush({ reasoningContent, ... })` | +| Was (0.7.x) | Now (≥ 0.1.0 of shared, ≥ 0.8.0 of instant) | +| ---------------------------------------------- | ------------------------------------------------ | +| 13-field instant push | `buildContentPush({...})` | +| `{ type: 'error', code: 'HOOK_THREW', ...}` | `buildErrorPush({ code: 'HOOK_THREW', ... })` | +| `{ type: 'error', code: 'LOOP_EXCEEDED', ...}` | `buildErrorPush({ code: 'LOOP_EXCEEDED', ... })` | +| (no equivalent — reasoning was discarded) | `buildReasoningPush({ reasoningContent, ... })` | diff --git a/packages/rei-standard-amsg/shared/package.json b/packages/rei-standard-amsg/shared/package.json index a792384..0953e41 100644 --- a/packages/rei-standard-amsg/shared/package.json +++ b/packages/rei-standard-amsg/shared/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-shared", - "version": "0.2.0", + "version": "0.3.0", "description": "ReiStandard Active Messaging shared types and push builders — the lowest layer (no deps on other amsg packages)", "repository": { "type": "git", diff --git a/packages/rei-standard-amsg/sw/CHANGELOG.md b/packages/rei-standard-amsg/sw/CHANGELOG.md index 2bf9297..eedd254 100644 --- a/packages/rei-standard-amsg/sw/CHANGELOG.md +++ b/packages/rei-standard-amsg/sw/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog — @rei-standard/amsg-sw +## 2.3.2 + +### Patch Changes + +- Updated dependencies [5c0e047] + - @rei-standard/amsg-shared@0.3.0 + ## 2.3.1 — `showNotification` 拒绝不再卡死 dedupe 状态 - **Fix**: `dispatchBusinessPayload` 给 `sw.registration.showNotification(...)` 加了 `.catch(...)` 兜底。原链路只挂了成功分支 `.then(() => notificationState.shown = true)`,当浏览器拒绝展示(权限被撤、quota / OS 限制等)时整个 `Promise.all(notificationWork)` 会 reject,`onNotificationSettled` 被跳过,dedupe 记录永远停在 `notificationStatePending: true`。后续同 key 的 backup transport 重复会被 `maybeShowDuplicateNotification` 当成 `first-delivery-pending` 吞掉,用户彻底看不到通知。现在拒绝只记录到 `console.error`,`notificationState.shown` 保持 false,但 `onNotificationSettled` 一定执行,dedupe 状态正常推进。 @@ -24,19 +31,20 @@ 原因:同一 dbName 下换 storeName 需要做 IndexedDB 版本升级,本包不打算维护跨 storeName 的 migration 逻辑;继续暴露 storeName 配置只会让用户踩 IDB upgrade 坑(升级一次后 store 永远建不出来,所有 dedupe transaction 都抛 NotFoundError)。 - | 之前配置 | 之前行为 | 现在 | - | --- | --- | --- | - | 不传 dbName / storeName | 用默认 | 不变 | - | 只传 `dbName` | 静默失效(store 建不出来) | 正常隔离 | - | 只传 `storeName` | 静默失效 | 装包时抛 Error | - | 同时传 `dbName` + `storeName`(首次部署) | OK | 装包时抛 Error | - | 同时传 `dbName` + 后续改 `storeName` | 老 client 上 store 建不出来,整条 dedupe 链路挂掉 | 装包时抛 Error | + | 之前配置 | 之前行为 | 现在 | + | ----------------------------------------- | ------------------------------------------------- | -------------- | + | 不传 dbName / storeName | 用默认 | 不变 | + | 只传 `dbName` | 静默失效(store 建不出来) | 正常隔离 | + | 只传 `storeName` | 静默失效 | 装包时抛 Error | + | 同时传 `dbName` + `storeName`(首次部署) | OK | 装包时抛 Error | + | 同时传 `dbName` + 后续改 `storeName` | 老 client 上 store 建不出来,整条 dedupe 链路挂掉 | 装包时抛 Error | - **Fix**: 慢的 `onBusinessPayload` 回调不再阻塞 dedupe 的通知补救判定。 之前:业务回调长时间未 resolve + 前台从可见变隐藏 + 同 `messageId` 的 Web Push backup 在窗口内到达 → backup 被判为 "first-delivery-pending" 丢弃,用户看不到通知。 现在:通知决策一确定就解锁补救路径,backup 照常补出系统通知。业务回调依旧 await,`event.waitUntil` 生命周期不变。 + - **Changed**: `@rei-standard/amsg-shared` 精确依赖升级到 `0.2.0`,并支持 `notification.silent` 透传到 `showNotification()`。 ## 2.1.1 — multipart 并发与 hook thenable 修复 @@ -47,9 +55,11 @@ ## 2.1.0 — notification.show 及 Multipart chunk store ### New + - **`notification.show`** 通知显示策略: 支持 `"auto"` | `"always"` | `"when-hidden"` | `false`。现在可以直接通过包级策略实现 "有可见窗口时静默,无可见窗口时弹通知" (`"when-hidden"`) 等应用场景。 ### Changed + - **性能优化**:`dispatchBusinessPayload` 现在只会调用一次 `sw.clients.matchAll` 从而避免多余的 IPC 开销。 - **IndexedDB 性能优化**:通过 `cachedDB` 保持 DB 连接,防止碎片化的 `openQueueDatabase` 导致的延迟。`REI_SW_DB_VERSION` 升级至 `3`。 - **Multipart Chunk Store**:新增 `multipart-chunk` object store 用于独立存储分片的 payload,提升了超大 payload 还原的内存稳定性和入库速度。添加了 `expiresAt` 索引大幅加速清理超时数据的过程。 @@ -92,16 +102,16 @@ next 阶段统一 multipart transport。SW 现在识别 `messageKind: "_multipar Cherry-pick stable `2.0.2` 的标题 fallback 修复到 next 预发布线。`createNotificationFromPayload` 的标题链从 ```js -pushNotification.title || payload.title || 'New notification' +pushNotification.title || payload.title || "New notification"; ``` 加一档 `contactName` 兜底,与 server / instant 默认 envelope 的 `title: '来自 ${contactName}'` 行为对齐: ```js -pushNotification.title - || payload.title - || (payload.contactName && `来自 ${payload.contactName}`) - || 'New notification' +pushNotification.title || + payload.title || + (payload.contactName && `来自 ${payload.contactName}`) || + "New notification"; ``` custom hook(0.7.x / 0.8.0-next.x 自定义 envelope)忘了塞 `title` 但塞了 `contactName` 的情况,通知不再掉到 'New notification' 这种英文兜底上。 diff --git a/packages/rei-standard-amsg/sw/package.json b/packages/rei-standard-amsg/sw/package.json index 4c341d0..4cf410b 100644 --- a/packages/rei-standard-amsg/sw/package.json +++ b/packages/rei-standard-amsg/sw/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-sw", - "version": "2.3.1", + "version": "2.3.2", "description": "ReiStandard Active Messaging service worker SDK — three-axis push schema (content / reasoning / tool_request / error) with per-kind client postMessage events", "repository": { "type": "git", @@ -33,7 +33,7 @@ "node": ">=20" }, "dependencies": { - "@rei-standard/amsg-shared": "^0.2.0" + "@rei-standard/amsg-shared": "^0.3.0" }, "devDependencies": { "tsup": "^8.0.0",