-
Notifications
You must be signed in to change notification settings - Fork 7
feat(amsg): client_state 大值透明分块 + fire 级 scratch + /capabilities 特性探测 #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
848b074
docs: amsg 通用缺口补齐实现计划
Tosd0 509bddd
feat(amsg-shared): buildSessionContext 支持可选 scratch 透传
Tosd0 65ad518
feat(amsg-server): client_state 分块纯函数(marker/切片/拼回)
Tosd0 2da8d4b
feat(amsg-server): D1 upsertClientState 支持 cleanups 与逐条 outcomes
Tosd0 1aad34f
feat(amsg-server): client_state 大值透明分块 + 整批局部失败
Tosd0 601d316
feat(amsg-server): fire 级 scratch 进 hook ctx;readState 拼回分块值
Tosd0 9c4a394
feat(amsg-server): GET /capabilities 特性探测端点
Tosd0 0881128
feat(amsg-client): getCapabilities() 特性探测
Tosd0 f13f2f1
docs(amsg): 单用户 README 补 capabilities 与大值说明;三个 changeset
Tosd0 e10b134
docs(amsg-server): changeset 补 DELETE 计数含切片行的说明
Tosd0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| "@rei-standard/amsg-shared": minor | ||
| "@rei-standard/amsg-server": minor | ||
| --- | ||
|
|
||
| fire 级 scratch:hook 之间传上下文不再自己维护 Map | ||
|
|
||
| 单次 fire 开始时库创建一个空对象,`onBeforeFire` 的 fireCtx 和同一次 fire 里每轮 `onLLMOutput` / `executeToolCalls` 的 sessionCtx 都拿到同一个 `scratch` 引用;fire 结束(finish / skip-push / 抛错 / 轮数超限)后随之丢弃。不落库、不进日志、不跨 fire 共享。amsg-shared 的 `buildSessionContext` 新增可选 `scratch` 参数(不传则字段缺席,amsg-instant 行为不变)。 |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| --- | ||
| "@rei-standard/amsg-server": minor | ||
| "@rei-standard/amsg-client": minor | ||
| --- | ||
|
|
||
| `GET /capabilities` 特性探测端点 + 客户端 `getCapabilities()` | ||
|
|
||
| worker 部署版本落后时,新功能只是「探测不到」而不是静默失效。单用户 worker 新增 `GET /capabilities`,返回 `{ serverVersion, features }`(feature 名如 `client-state` / `client-state-chunking` / `agentic-hooks`,随版本演进追加;表达代码能力,不反映部署配置);鉴权与 `/vapid-public-key` 一致。客户端 SDK 新增 `getCapabilities()`:打到没有该路由的老 worker(404)返回 `null` 不抛错,前端可据此在设置里提示「worker 需要重新部署」。 |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| --- | ||
| "@rei-standard/amsg-server": minor | ||
| --- | ||
|
|
||
| client_state 大值透明分块 + 整批局部失败(单用户 worker) | ||
|
|
||
| - `PUT /client-state` 单条 value 不再受 200KB 整批 413 的限制:超过 200KB 的值由服务端切片跨行存储,`GET /client-state` 与 hook 的 `ctx.readState()` 返回拼好的原值,客户端和 hook 作者无感。单条总上限默认 5MB,工厂配置 `maxStateValueBytes` 可调。切片在码点边界(中文 / emoji 不会被劈开);覆盖写变小不残留旧切片;块不齐全(写到一半断了)时该 key 视为不存在,读方走自己的兜底。 | ||
| - 整批局部失败:批里某条超限 / 非法只拒它自己,其余照常入库。有拒绝时响应带 `data.rejected: [{ index, namespace, key, code, message }]`;全部成功时响应形状与之前完全一致。 | ||
| - namespace / key 里的控制字符(`\u0000`-`\u001f`)为库内部保留,逐条拒绝。 | ||
| - adapter 的 `upsertClientState` 新增可选第三参 `cleanups` 与返回值 `outcomes`;自定义 adapter 不实现也能工作(只损失存储卫生,不影响正确性)。 | ||
| - `DELETE /client-state` 返回的 `deleted` 计数包含内部切片行(有分块值时会大于逻辑条目数)。 |
1,518 changes: 1,518 additions & 0 deletions
1,518
docs/superpowers/plans/2026-07-18-amsg-universal-gaps.md
Large diffs are not rendered by default.
Oops, something went wrong.
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
64 changes: 64 additions & 0 deletions
64
packages/rei-standard-amsg/client/test/capabilities.test.mjs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| import { test } from 'node:test'; | ||
| import assert from 'node:assert/strict'; | ||
| import { ReiClient } from '../src/index.js'; | ||
|
|
||
| const USER = '550e8400-e29b-41d4-a716-446655440000'; | ||
|
|
||
| async function withFetch(impl, run) { | ||
| const original = globalThis.fetch; | ||
| globalThis.fetch = impl; | ||
| try { | ||
| return await run(); | ||
| } finally { | ||
| globalThis.fetch = original; | ||
| } | ||
| } | ||
|
|
||
| test('getCapabilities() GET /capabilities,带 X-Client-Token,返回 { serverVersion, features }', async () => { | ||
| const captured = []; | ||
| await withFetch(async (url, init) => { | ||
| captured.push({ url: String(url), method: init && init.method, headers: (init && init.headers) || {} }); | ||
| return new Response(JSON.stringify({ | ||
| success: true, serverVersion: '2.7.0', features: ['client-state', 'client-state-chunking'], | ||
| }), { status: 200, headers: { 'Content-Type': 'application/json' } }); | ||
| }, async () => { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER, serverToken: 's3cret' }); | ||
| const caps = await client.getCapabilities(); | ||
| assert.deepEqual(caps, { serverVersion: '2.7.0', features: ['client-state', 'client-state-chunking'] }); | ||
| }); | ||
| assert.equal(captured.length, 1); | ||
| assert.equal(captured[0].url, 'https://w.dev/capabilities'); | ||
| assert.equal(captured[0].method, 'GET'); | ||
| assert.equal(captured[0].headers['X-Client-Token'], 's3cret'); | ||
| }); | ||
|
|
||
| test('老 worker 404(JSON 或非 JSON 页面)→ null 不抛错', async () => { | ||
| await withFetch(async () => new Response( | ||
| JSON.stringify({ success: false, error: { code: 'NOT_FOUND', message: 'Unknown route' } }), | ||
| { status: 404, headers: { 'Content-Type': 'application/json' } } | ||
| ), async () => { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER }); | ||
| assert.equal(await client.getCapabilities(), null); | ||
| }); | ||
| await withFetch(async () => new Response('<html>Not Found</html>', { status: 404 }), async () => { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER }); | ||
| assert.equal(await client.getCapabilities(), null); | ||
| }); | ||
| }); | ||
|
|
||
| test('非 404 但响应不是 JSON(代理错误页)→ null', async () => { | ||
| await withFetch(async () => new Response('<html>Bad Gateway</html>', { status: 200 }), async () => { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER }); | ||
| assert.equal(await client.getCapabilities(), null); | ||
| }); | ||
| }); | ||
|
|
||
| test('success:false(如 token 错 401)→ 抛错带服务端 message', async () => { | ||
| await withFetch(async () => new Response( | ||
| JSON.stringify({ success: false, error: { code: 'UNAUTHORIZED', message: '缺少或错误的 X-Client-Token' } }), | ||
| { status: 401, headers: { 'Content-Type': 'application/json' } } | ||
| ), async () => { | ||
| const client = new ReiClient({ baseUrl: 'https://w.dev', userId: USER, serverToken: 'wrong' }); | ||
| await assert.rejects(() => client.getCapabilities(), /X-Client-Token/); | ||
| }); | ||
| }); |
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
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
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
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
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the server returns a non-200 and non-404 HTTP status (such as a temporary 502 Bad Gateway or 503 Service Unavailable HTML page),
res.json()will throw an error. Currently, this error is caught and the method silently returnsnull. This can mislead the client into thinking the worker is outdated (sincenullindicates the worker predates the endpoint), prompting incorrect UI redeployment hints. We should check the HTTP status and throw an error if it is not 200 or 404.