dsh-cherry-bridge 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.en.md CHANGED
@@ -38,13 +38,13 @@ flowchart TB
38
38
 
39
39
  ```bash
40
40
  # From npm (recommended, pinned)
41
- npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge@0.1.8
41
+ npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge@0.1.10
42
42
 
43
43
  # Install the latest npm release
44
44
  npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge
45
45
 
46
46
  # From GitHub source (same commit as the npm release)
47
- npx @deepseek-ai/dsh plugin --profile web add github:SAXEM1997/dsh-cherry-bridge#v0.1.8
47
+ npx @deepseek-ai/dsh plugin --profile web add github:SAXEM1997/dsh-cherry-bridge#v0.1.10
48
48
  ```
49
49
 
50
50
  The package ships `cordis.patch.yml` (`package.json`'s `dsh.bundle.patch` points
@@ -92,7 +92,7 @@ cherry_interactions → see pending question/approval cards; cherry_respond answ
92
92
  | --- | --- | --- | --- |
93
93
  | `dataDir` | string | `$DSH_HOME/cherry-remote` (fallback `~/.dsh/cherry-remote`) | Plugin data directory holding `identity.key` and `devices.json` (both `0600`). |
94
94
  | `perSessionCacheBytes` | number | `2097152` (2 MiB) | Per-session cap on the **local** subscription event cache; beyond it the oldest events are dropped LRU-style with a `warn`. |
95
- | `rateBudgetPerSecond` | number | `32` | Per-device smoothed request budget (req/s). The desktop's hard limit is 64 req/s with no negotiation channel; exceeding it terminates the session with close `1011` — hence the conservative value. |
95
+ | `rateBudgetPerSecond` | number | `32` | Per-device smoothed request budget (req/s). The desktop's shared allowance is 64 req/s through 2.1.6 and 256 req/s from 2.1.7, and only the newer generation downgrades an overrun to a single `RATE_LIMITED` refusal (the older one terminates the session with close `1011`) — hence a value sized for the harsher generation. Content reads (`agent.content.read`) derive their own tier at 8:1, **capped at 512/s** (that allowance is the same in both generations). |
96
96
 
97
97
  > ⚠️ `cordis.patch.yml`'s `config` is **whole-replace**, not merged with the
98
98
  > defaults. Removing a key gets you the plugin default; a key that exists but is
@@ -106,7 +106,7 @@ cherry_interactions → see pending question/approval cards; cherry_respond answ
106
106
  | `cherry_agents` | List the agents available on a device (paginated). A truncated answer always carries `truncated` + `truncationReason` (`limit` or `safety-bound`, the latter the 1000-item internal ceiling); when the stop point can be named it also carries a `resumeCursor`: replay it as `cursor` with the same device and filters to continue from that stop point (`limit` then bounds the next answer). A token from another tool, device or filter set is refused **locally**, never forwarded. |
107
107
  | `cherry_workspaces` | List an agent's **registered** workspaces, and whether a system workspace exists. A truncated answer always carries `truncated` + `truncationReason`; when the stop point can be named it also carries a `resumeCursor` — replay it as `cursor` with the same filters to continue. |
108
108
  | `cherry_sessions` | List an agent's sessions, optionally filtered by workspace. A truncated answer always carries `truncated` + `truncationReason` (`limit` or `safety-bound`); when the stop point can be named it also carries a `resumeCursor`: replay it as `cursor` with the same device and **identical filters** to continue (`limit` then bounds the next answer); a token from another tool, device or filter set is refused **locally**. |
109
- | `cherry_read` | Read a session's message history snapshot, **newest-first** (`limit` drops the **oldest** messages). Attachment parts come back as `name`/`mediaType`/`byteLength` with an empty `content` — the bytes are never fetched. A truncated walk always says so (`truncated` + `truncationReason`): `limit` means your own ceiling was reached and raising `limit` helps; `aggregate-bytes` means the answer hit its **output size limit** — independent of `limit` (a larger `limit` only costs more round trips and stops at the same budget). **The older messages are reached by the resume token**: pass the `messagesResumeCursor` a truncated answer returned as this tool's own `messagesResumeCursor` argument to continue from that stop point (`limit` then bounds how many messages come back **from there**), walking a long history in successive answers. When the stop point cannot be named the token is **absent** (`truncated` stays `true`, and the absence does **not** mean the history ended); a wider `limit` is not a general remedy — it moves a `limit` stop only, while an `aggregate-bytes` stop leaves the older messages unreachable in that answer (see [Errors and limits](#errors-and-limits)). Two token forms: `cursor:<peer position>` (the peer page position after a whole window was consumed) and `after:<messageId>` (the walk stopped inside its window; the resume scans from the newest end to that message and returns the older ones). A token this tool did not emit is **refused locally** (never forwarded — the desktop silently falls back to the newest page for one), and an `after:` anchor that is no longer in the history is an **error**, not an empty result. |
109
+ | `cherry_read` | Read a session's message history snapshot, **newest-first** (`limit` drops the **oldest** messages). A failed message carries `failure: {reasonCode, retryable, message}` when the peer sent one (its classification, whether a retry is worthwhile, and its own bounded summary; the peer's `source`/`context` are not projected). Attachment parts come back as `name`/`mediaType`/`byteLength` with an empty `content` — the bytes are never fetched. A truncated walk always says so (`truncated` + `truncationReason`): `limit` means your own ceiling was reached and raising `limit` helps; `aggregate-bytes` means the answer hit its **output size limit** — independent of `limit` (a larger `limit` only costs more round trips and stops at the same budget). **The older messages are reached by the resume token**: pass the `messagesResumeCursor` a truncated answer returned as this tool's own `messagesResumeCursor` argument to continue from that stop point (`limit` then bounds how many messages come back **from there**), walking a long history in successive answers. When the stop point cannot be named the token is **absent** (`truncated` stays `true`, and the absence does **not** mean the history ended); a wider `limit` is not a general remedy — it moves a `limit` stop only, while an `aggregate-bytes` stop leaves the older messages unreachable in that answer (see [Errors and limits](#errors-and-limits)). Two token forms: `cursor:<peer position>` (the peer page position after a whole window was consumed) and `after:<messageId>` (the walk stopped inside its window; the resume scans from the newest end to that message and returns the older ones). A token this tool did not emit is **refused locally** (never forwarded — the desktop silently falls back to the newest page for one), and an `after:` anchor that is no longer in the history is an **error**, not an empty result. |
110
110
  | `cherry_events` | Read the session's buffered events newer than `cursor`, acking them after delivery. The answer is `{events, nextCursor}`; pass that `nextCursor` back as `cursor` to continue from it. |
111
111
  | `cherry_create` | Create a session in an agent workspace (workspace selection is **required**; identity is read back). |
112
112
  | `cherry_send` | Send a message, by default waiting for a terminal state and returning the assistant text; `wait: false` returns the command receipt immediately. `messagesTruncated`/`messagesTruncationReason` report a **message**-level cut (older messages dropped); `deadline` means the **answer's own assembly budget** ran out (see below), with the content read so far still returned. |
@@ -133,9 +133,15 @@ subscription was lost and could not be rebuilt, so the outcome is
133
133
 
134
134
  ## Errors and limits
135
135
 
136
- - Read methods are replayed **once** after a reconnect; **commands are never
137
- replayed** — a write that loses its reply is recovered by retrying with the
138
- **original `commandId`**, and the desktop's command ledger replays the receipt.
136
+ - Read methods are replayed **once** after a reconnect, and **once** after a
137
+ desktop >= 2.1.7 rate-limit refusal (`RATE_LIMITED` + `retryAfterMs`, waited
138
+ out within the caller's budget); **commands are never replayed** — a write
139
+ that loses its reply is recovered by retrying with the **original
140
+ `commandId`**, and the desktop's command ledger replays the receipt. A
141
+ rate-limited COMMAND is the other case: the desktop refuses it **before
142
+ dispatch**, so this attempt was never applied — the error text says so, names
143
+ the wait the peer asked for (`retryAfterMs`, omitted when it sent none), and
144
+ points at `rateBudgetPerSecond` (the default budget is far below the limit).
139
145
  - One externalized content is capped at 32 MiB; a larger part is marked
140
146
  `unavailable` rather than returning unverifiable partial content.
141
147
  - **Externalized-content assembly is gated process-wide**: at most 4 `{ref}`
@@ -203,7 +209,7 @@ subscription was lost and could not be rebuilt, so the outcome is
203
209
 
204
210
  The plugin is also a **web client bundle** (`package.json`'s `dsh.client`,
205
211
  exporting `./client`). Through the `settings.section` slot it registers a
206
- **`cherry-remote`** page (nav label “Cherry Studio Bridge”): device cards
212
+ **`cherry-remote`** page (nav label “Cherry Bridge”): device cards
207
213
  (enable/disable/remove), a pairing wizard (paste → 6-digit code → verdict), a
208
214
  scope panel, and live state pushed over the same-origin SSE feed
209
215
  (`/cherry-remote/api/events`).
package/README.md CHANGED
@@ -36,13 +36,13 @@ flowchart TB
36
36
 
37
37
  ```bash
38
38
  # 从 npm 安装(推荐,钉住版本)
39
- npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge@0.1.8
39
+ npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge@0.1.10
40
40
 
41
41
  # 安装 npm 最新发布版
42
42
  npx @deepseek-ai/dsh plugin --profile web add dsh-cherry-bridge
43
43
 
44
44
  # 从 GitHub 源码安装(与 npm 发布同一提交)
45
- npx @deepseek-ai/dsh plugin --profile web add github:SAXEM1997/dsh-cherry-bridge#v0.1.8
45
+ npx @deepseek-ai/dsh plugin --profile web add github:SAXEM1997/dsh-cherry-bridge#v0.1.10
46
46
  ```
47
47
 
48
48
  本包自带 `cordis.patch.yml`(`package.json` 的 `dsh.bundle.patch` 指向它),
@@ -84,7 +84,7 @@ cherry_interactions → 看待答的提问/审批卡;cherry_respond 作答
84
84
  | --- | --- | --- | --- |
85
85
  | `dataDir` | string | `$DSH_HOME/cherry-remote`(回退 `~/.dsh/cherry-remote`) | 插件数据目录,存放 `identity.key` 与 `devices.json`(均 `0600`)。 |
86
86
  | `perSessionCacheBytes` | number | `2097152`(2 MiB) | 每个会话的**本地**订阅事件缓存上限,超出按 LRU 丢弃最旧事件并 `warn`。 |
87
- | `rateBudgetPerSecond` | number | `32` | 每设备平滑请求预算(请求/秒)。桌面端硬上限是 64 req/s 且无协商通道,超限会以 close `1011` 终止会话,故取保守值。 |
87
+ | `rateBudgetPerSecond` | number | `32` | 每设备平滑请求预算(请求/秒)。桌面端共享额度在 ≤2.1.6 是 64 req/s、≥2.1.7 是 256 req/s,且只有新版本才把超限降级为单请求 `RATE_LIMITED`(旧版本直接 close `1011` 终止会话),故按更严格的一代取保守值。内容读取(`agent.content.read`)按 8:1 派生独立一档,且**封顶 512/s**(该额度两代一致)。 |
88
88
 
89
89
  > ⚠️ `cordis.patch.yml` 的 `config` 是**整体替换**,不与默认值合并。删掉一个键得到的是
90
90
  > 插件默认值;键存在但值非法(`0`、负数、字符串)会按名 `warn` 后回落默认值。
@@ -97,7 +97,7 @@ cherry_interactions → 看待答的提问/审批卡;cherry_respond 作答
97
97
  | `cherry_agents` | 列出某设备上可用的 agent(分页)。被截断时必带 `truncated`+`truncationReason`(`limit` 或 `safety-bound`,后者是 1000 条内部上限);停点可命名时还给出 `resumeCursor`——用同一 device 与过滤条件把它回传为 `cursor` 即可从停点继续(此时 `limit` 约束的是下一次回答);跨工具/设备/过滤条件的令牌会被**本地拒绝**,不会转发给对端。 |
98
98
  | `cherry_workspaces` | 列出某 agent 的**已注册**工作区,以及是否存在系统工作区。被截断时必带 `truncated`+`truncationReason`;停点可命名时还给出 `resumeCursor`——用相同过滤条件回传为 `cursor` 即可续读。 |
99
99
  | `cherry_sessions` | 列出某 agent 的会话,可按工作区过滤。被截断时必带 `truncated`+`truncationReason`(`limit` 或 `safety-bound`),停点可命名时还给出 `resumeCursor`——用同一 device 与**相同过滤条件**把它回传为 `cursor` 即可从停点继续(此时 `limit` 约束的是下一次回答);跨工具/设备/过滤条件的令牌会被**本地拒绝**。 |
100
- | `cherry_read` | 读会话消息历史快照,**从新到旧**返回(`limit` 丢掉的是**最旧**的消息)。附件 part 只回 `name`/`mediaType`/`byteLength`,`content` 为空——字节不取。被截断时 `truncated` + `truncationReason` 一定说明:`limit` 表示你设的条数到了,提高 `limit` 即可;`aggregate-bytes` 表示单次回答达到了**输出大小上限**——与 `limit` 无关,提高 `limit` 只会多付往返、仍停在同一预算。**被挡下的更旧消息用 `messagesResumeCursor` 续读**:把本次返回的 `messagesResumeCursor` 原样传给下一次调用的 `messagesResumeCursor` 参数即可从停点继续(`limit` 此时约束的是**从停点起**本次最多返回多少条),长历史分几次回答读完。停点不可命名时**没有**这个令牌(`truncated` 仍为 `true`,**缺失本身不代表历史已到底**;补救按 `truncationReason` 分:`limit` 可以提高 `limit`,`aggregate-bytes` 不行——被挡住的旧消息在这一回答里拿不到,详见[错误与限制](#错误与限制))。两种令牌:`cursor:<peer position>`(整窗读完后的 peer 分页位置)与 `after:<messageId>`(停在窗内,续读从最新端扫到该消息、返回它更旧的那些)。不是本工具发出的令牌会被**本地拒绝**(不转发给桌面端——桌面端会静默回退到最新页);`after:` 锚点在历史里找不到则**报错**,不会返回空结果。 |
100
+ | `cherry_read` | 读会话消息历史快照,**从新到旧**返回(`limit` 丢掉的是**最旧**的消息)。失败消息在桌面端给出时附带 `failure: {reasonCode, retryable, message}`(对端分类、是否值得重试、对端自己的有界摘要;其 `source`/`context` 不投影到回答里)。附件 part 只回 `name`/`mediaType`/`byteLength`,`content` 为空——字节不取。被截断时 `truncated` + `truncationReason` 一定说明:`limit` 表示你设的条数到了,提高 `limit` 即可;`aggregate-bytes` 表示单次回答达到了**输出大小上限**——与 `limit` 无关,提高 `limit` 只会多付往返、仍停在同一预算。**被挡下的更旧消息用 `messagesResumeCursor` 续读**:把本次返回的 `messagesResumeCursor` 原样传给下一次调用的 `messagesResumeCursor` 参数即可从停点继续(`limit` 此时约束的是**从停点起**本次最多返回多少条),长历史分几次回答读完。停点不可命名时**没有**这个令牌(`truncated` 仍为 `true`,**缺失本身不代表历史已到底**;补救按 `truncationReason` 分:`limit` 可以提高 `limit`,`aggregate-bytes` 不行——被挡住的旧消息在这一回答里拿不到,详见[错误与限制](#错误与限制))。两种令牌:`cursor:<peer position>`(整窗读完后的 peer 分页位置)与 `after:<messageId>`(停在窗内,续读从最新端扫到该消息、返回它更旧的那些)。不是本工具发出的令牌会被**本地拒绝**(不转发给桌面端——桌面端会静默回退到最新页);`after:` 锚点在历史里找不到则**报错**,不会返回空结果。 |
101
101
  | `cherry_events` | 读取该会话缓冲区内比 `cursor` 更新的事件,并在投递后 ack。返回 `{events, nextCursor}`;把 `nextCursor` 作为下次的 `cursor` 传回即可续读。 |
102
102
  | `cherry_create` | 在 agent 工作区创建会话(工作区选择**必填**;创建后回读身份)。 |
103
103
  | `cherry_send` | 向会话发消息,默认等待运行到终态并返回助手文本;`wait: false` 立即返回命令回执。内容被截断时 `messagesTruncated`/`messagesTruncationReason` 说的是**消息**被截断(丢更旧的);`deadline` 表示**回答自身的装配时限**到了(见下),已读到的内容照常返回。 |
@@ -119,8 +119,11 @@ cherry_interactions → 看待答的提问/审批卡;cherry_respond 作答
119
119
 
120
120
  ## 错误与限制
121
121
 
122
- - 读方法在重连后**单次**重放;**命令永不重放**——写操作丢失应答时,携**原 `commandId`**
123
- 重试即可,桌面端的命令账本会回放原回执。
122
+ - 读方法在重连后**单次**重放;桌面端 ≥2.1.7 的限流拒绝(`RATE_LIMITED` + `retryAfterMs`)
123
+ 也在调用方预算内等待后**单次**重放。**命令永不重放**——写操作丢失应答时,携**原 `commandId`**
124
+ 重试即可,桌面端的命令账本会回放原回执。命令被限流拒绝是另一回事:桌面端在**派发前**就拒绝,
125
+ 命令根本没执行,错误文本会说明这一点、给出对端要求的等待时间(`retryAfterMs`,对端没给就不写),
126
+ 并提示该检查的 `rateBudgetPerSecond`(默认预算远到不了限流)。
124
127
  - 单个外置内容上限 32 MiB,超过的 part 标 `unavailable`,不回无法校验的部分内容。
125
128
  - **外置内容装配是进程级限流的**:同时最多 4 个 `{ref}` 在装配(官方客户端的读并发策略)。
126
129
  单个 ref 的装配峰值约 3× 它的大小,所以**装配峰值**上界是 4×3×32 MiB = 384 MiB,日常远低于此
@@ -159,7 +162,7 @@ cherry_interactions → 看待答的提问/审批卡;cherry_respond 作答
159
162
  ## 设置页与启用范围
160
163
 
161
164
  插件同时是 **web client bundle**(`package.json` 的 `dsh.client`,导出 `./client`),通过
162
- `settings.section` 槽位注册 **`cherry-remote`**(导航标签「Cherry Studio 互联」)页面:
165
+ `settings.section` 槽位注册 **`cherry-remote`**(导航标签「Cherry 互联」)页面:
163
166
  设备卡片(启用/停用/删除)、配对向导(粘贴 → 6 位验证码 → 终态)、启用范围面板,以及经
164
167
  `/cherry-remote/api/events` SSE 推送的实时状态。
165
168
 
package/cordis.patch.yml CHANGED
@@ -12,8 +12,9 @@
12
12
  # 键值与 spec §8 一致:
13
13
  # - perSessionCacheBytes:每个会话的本地订阅事件缓存上限(字节)。2 MiB。
14
14
  # - rateBudgetPerSecond:每设备二级平滑请求预算(请求/秒),与
15
- # `DeviceConnection` 默认值一致,远低于桌面端实现的 64 req/s 硬上限
16
- # (该上限无协商通道,超限会以 close 1011 终止整条会话)。
15
+ # `DeviceConnection` 默认值一致。桌面端共享额度在 ≤2.1.6 是 64 req/s,
16
+ # ≥2.1.7 是 256 req/s;只有新版本把超限降级为单请求 RATE_LIMITED 拒绝,
17
+ # 旧版本直接以 close 1011 终止整条会话,故按更严格的一代取保守值。
17
18
  # - dataDir 故意不写:留空时装配层按 $DSH_HOME/cherry-remote 解析,
18
19
  # 写死会让所有 profile 共用同一份设备档案。
19
20
  #
package/dist/index.d.mts CHANGED
@@ -136,18 +136,21 @@ interface CherryHttpContext {
136
136
  * This is the SHARED tier: it covers every business RPC EXCEPT the content-read
137
137
  * tier below (see {@link DEFAULT_CONTENT_READ_BUDGET_PER_SECOND}).
138
138
  *
139
- * WHY 32: the desktop's own allowance for "other" RPCs is 64/s
140
- * (`RemoteAccessService.ts:223-225`; the official client paces them at 40/s,
141
- * `DesktopSession.ts:242-244`), and exceeding the allowance is not a refusal
142
- * but a whole-session close (1011). Half the peer's line leaves room for the
143
- * housekeeping traffic that shares the socket (hello/authenticate/ping/refresh
144
- * bypass pacing by design) and for arrival compression between our admission
145
- * times and the peer's arrival times.
139
+ * WHY 32: the desktop's allowance for "other" RPCs is 64/s through 2.1.6 and
140
+ * 256/s from 2.1.7 (`RemoteAccessService.ts`; the official client paces them at
141
+ * 40/s, `DesktopSession.ts:36-37`). What an over-budget request does also
142
+ * changed: through 2.1.6 it closes the WHOLE session (1011), while 2.1.7 (PR
143
+ * #21687) refuses that one request (`RATE_LIMITED` + `retryAfterMs`) and keeps
144
+ * the session. The generations cannot be told apart before one answers, so the
145
+ * default is sized for the older, harsher peer: 32 is half of 64 and an eighth
146
+ * of 256, leaving room for the housekeeping traffic that shares the socket
147
+ * (hello/authenticate/ping/refresh bypass pacing by design) and for arrival
148
+ * compression between our admission times and the peer's arrival times.
146
149
  *
147
150
  * SOURCES (neither is in this repository): the desktop lines are
148
- * `_research/cherry-studio` @ v2.1.6 (90b234e); the official-client lines are
149
- * CherryHQ/cherry-studio-app @ main 18329fe (app 0.3.0), read on 2026-10-18 —
150
- * recorded in `.superpowers/review-v013-r1/context.md`.
151
+ * `_research/cherry-studio` @ v2.1.6 (90b234e) and CherryHQ/cherry-studio @
152
+ * v2.1.7; the official-client lines are CherryHQ/cherry-studio-app @ v0.4.0,
153
+ * both read on 2026-10-22 (issue #102).
151
154
  */
152
155
  export declare const DEFAULT_RATE_BUDGET_PER_SECOND = 32;
153
156
  /**
@@ -163,9 +166,10 @@ export declare const DEFAULT_RATE_BUDGET_PER_SECOND = 32;
163
166
  * (`RemoteAccessService.ts:223-225`) and the official client issues them at
164
167
  * 480/s (`DesktopSession.ts:33-37`). Pacing them with the 32/s shared tier
165
168
  * bound a multi-slice walk to ~670 KiB/s and queued it behind unrelated RPCs;
166
- * 256 is half the peer's own kill line — the same 2× margin the shared tier
167
- * keeps on 64/s — and lifts the bound to the multi-MiB/s range of the official
168
- * client while leaving the desktop a session-sized headroom.
169
+ * 256 is half the 512/s content allowance in BOTH desktop generations (the
170
+ * shared-tier allowance changed in 2.1.7; this one did not) and lifts the bound
171
+ * to the multi-MiB/s range of the official client while leaving the desktop a
172
+ * session-sized headroom.
169
173
  *
170
174
  * NOT a plugin-config key: the two tiers derive from ONE injected shared budget
171
175
  * at the ratio below, so an operator who lowers `rateBudgetPerSecond` lowers
@@ -174,12 +178,18 @@ export declare const DEFAULT_RATE_BUDGET_PER_SECOND = 32;
174
178
  *
175
179
  * THE MARGINS ARE A DEFAULT-INJECTION CLAIM: the 2× headroom above holds at
176
180
  * the default injection (32/s), where this tier is exactly 256/s. An injected
177
- * budget consumes BOTH tiers' headroom in proportion — at 64/s the shared tier
178
- * is at the peer's 64/s line and this tier derives to 512/s, i.e. nominal
179
- * saturation of both allowances. The limiter's integer gap means the ACHIEVED
180
- * rate stays below the nominal one in that case (≤ 334/s on this tier, 55.6/s
181
- * on the shared one; see `SecondWindowRateLimiter` in `connection.ts`), which
182
- * is margin by accident rather than by design.
181
+ * budget consumes BOTH tiers' headroom up to the point where this tier reaches
182
+ * the PEER'S CEILING — at 64/s the shared tier sits on the ≤2.1.6 line (the
183
+ * harsher generation, where overrun still closes the session) and this tier
184
+ * derives to exactly 512/s, the content allowance BOTH generations share. Above
185
+ * 64/s the clamp holds it at 512/s: `contentBudgetFor` (issue #111) still
186
+ * derives the ratio, then bounds it — because a knob copied from 2.1.7's shared
187
+ * figure (256)
188
+ * would otherwise derive 2,048/s and spend the tier being refused. The
189
+ * limiter's integer gap means the ACHIEVED rate stays below the nominal one in
190
+ * that case (≤ 334/s on this tier, 55.6/s on the shared one; see
191
+ * `SecondWindowRateLimiter` in `connection.ts`), which is margin by accident
192
+ * rather than by design.
183
193
  */
184
194
  export declare const DEFAULT_CONTENT_READ_BUDGET_PER_SECOND = 256;
185
195
  /** 2 MiB of event text cached per session (spec §3.5). */
@@ -240,9 +250,13 @@ export interface CherryBridgeConfig {
240
250
  * The CONTENT tier is not configurable on its own: it DERIVES from this value
241
251
  * at the 8 : 1 production ratio (`DEFAULT_CONTENT_READ_BUDGET_PER_SECOND` /
242
252
  * `DEFAULT_RATE_BUDGET_PER_SECOND`), so the default 32 yields the 256/s
243
- * content tier and any other value scales both tiers together. Raising this
244
- * toward the peer's own lines therefore spends BOTH tiers' safety margin —
245
- * see `DEFAULT_CONTENT_READ_BUDGET_PER_SECOND` for the numbers.
253
+ * content tier and lowering this lowers both. UPWARD the derivation stops at
254
+ * the peer's 512/s content allowance (issue #111): a value above 64 no longer
255
+ * scales that tier, it only spends the shared tier's margin. Raising this
256
+ * toward the peer's own lines spends margin: on a <=2.1.6 desktop that is
257
+ * BOTH tiers (64 shared / 512 content), while on 2.1.7 only the shared line
258
+ * moves (256 there). See `DEFAULT_CONTENT_READ_BUDGET_PER_SECOND` for the
259
+ * numbers.
246
260
  */
247
261
  rateBudgetPerSecond?: number;
248
262
  /**