fix(web): fold the preset frame into the session row for every client

The frame carried `agentPreset` for surfaces that label the session, but
nothing consumed it: `noteAgentPreset` ran only in the switching tab's RPC
callback, so a second connected client refetched its catalogs while its
session row — the header label's source, and the hero chip's no-op input —
kept the composition the session had replaced.

`SessionManager.handleHostEnvelope` now folds the frame like the other
session frames. Re-applying the switching tab's own frame is a no-op: the
merge lowers `blank` only and keeps the row's `updatedAt`.
This commit is contained in:
Yichen Jiang
2026-08-10 15:12:07 +08:00
parent ec8cbd7513
commit 0be9bf312a
11 changed files with 48 additions and 16 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-10-slash-catalog-follows-preset-switch.md
2026-08-10-slash-catalog-follows-preset-switch.md: 4f32347e04e9b1cde024a59a32fcfd3cca64172a
2026-08-10-slash-catalog-follows-preset-switch.zh.md: fb30df74a93a9eb913dc43b43c3065c6255bcab9
2026-08-10-slash-catalog-follows-preset-switch.md: 85bd5b2134fd20c86fdeb13f3ce5b007449105b5
2026-08-10-slash-catalog-follows-preset-switch.zh.md: 97c8f08a7b3dfec7c17fbb00bef626e28505c500

View File

@@ -16,7 +16,9 @@ The menu therefore kept serving the composition the session no longer ran. Switc
The switch's commit point is the logged `agent-preset/selected` event. The host stream frames it as `host/session-preset-changed { sessionId, agentPreset }`, the browser runtime bridges that frame to the typed `session/preset-changed` ctx event beside the registry-invalidation bridges it already owns, and each catalog owner drops its own entry for that session: `ui-command` soft-refreshes the key (the old snapshot keeps serving the open menu until the new one lands), `ui-skill` invalidates it (aborting an in-flight prewarm, so a warm racing the switch cannot publish the stale catalog).
The frame is per session and carries no catalog. Deriving it from the logged event rather than from the RPC handler's return keeps one authority for "this session's composition changed": every connected client observes the switch, not only the tab that issued it, and a client that is not the switcher never has to infer it from a registry signal that will not come.
The frame is per session and carries no catalog, only the preset id — which the manager folds into the session row, because the `agentPresets.select` echo reaches only the client that issued the switch and the row is what the session header labels itself from (and what the hero chip compares the next pick against).
Deriving the frame from the logged event rather than from the RPC handler's return keeps one authority for "this session's composition changed": every connected client observes the switch, not only the tab that issued it, and a client that is not the switcher never has to infer it from a registry signal that will not come.
## Alternatives considered
@@ -38,4 +40,4 @@ That e2e also stopped reading its staged-pick assertion off the serialized sessi
## Related
Reaching the host on a SECOND switch is a separate defect with its own cause and fix: [the session-row identity guard](2026-08-10-session-row-identity-covers-the-preset.md). Until it landed, the e2e below could only exercise the first switch — the invalidation edge here is direction-blind, but the switch it reacts to has to happen.
Reaching the host on a SECOND switch is a separate defect with its own cause and fix: [the session-row identity guard](2026-08-10-session-row-identity-covers-the-preset.md). Until it landed, `agent-preset-selection.e2e.ts` could only exercise the first switch — the invalidation edge here is direction-blind, but the switch it reacts to has to happen.

View File

@@ -16,7 +16,9 @@ preset 把决定 `/` 菜单内容的那些行搬走了。Web 组装禁用了宿
这次切换的提交点是落账的 `agent-preset/selected` 事件。宿主流把它成帧为 `host/session-preset-changed { sessionId, agentPreset }`,浏览器运行时在它已经拥有的那组注册表失效桥接旁,把该帧桥接为类型化的 `session/preset-changed` ctx 事件,两份目录各自丢弃该会话的那一项:`ui-command` 软刷新该键(新快照落地前,旧快照继续服务已打开的菜单),`ui-skill` 让它失效(并中止在途的预热,使一次与切换赛跑的 warm 无法发布过期目录)。
该帧按会话粒度,不携带目录。从落账事件而不是 RPC 处理器的返回值派生它,使「这个会话的组成变了」只有一个权威来源:每个已连接的客户端都能观察到这次切换,而不只是发起它的那个标签页;不是发起方的客户端也无需从一个根本不会到来的注册表信号里去推断
该帧按会话粒度,不携带目录,只带 preset id——manager 会把它折进会话行,因为 `agentPresets.select` 的回执只会到达发起切换的那个客户端而会话头部标签正是以这一行为准hero chip 比较下一次选择时读的也是它)
从落账事件而不是 RPC 处理器的返回值派生该帧,使「这个会话的组成变了」只有一个权威来源:每个已连接的客户端都能观察到这次切换,而不只是发起它的那个标签页;不是发起方的客户端也无需从一个根本不会到来的注册表信号里去推断。
## Alternatives considered
@@ -38,4 +40,4 @@ preset 把决定 `/` 菜单内容的那些行搬走了。Web 组装禁用了宿
## Related
第二次切换能否到达宿主是另一个缺陷,有各自的成因与修复:[会话行的标识判定](2026-08-10-session-row-identity-covers-the-preset.md)。在它落地之前,下面那条 e2e 只能演练第一次切换——这里的失效边对方向无感,但它所响应的那次切换必须真的发生。
第二次切换能否到达宿主是另一个缺陷,有各自的成因与修复:[会话行的标识判定](2026-08-10-session-row-identity-covers-the-preset.md)。在它落地之前,`agent-preset-selection.e2e.ts` 只能演练第一次切换——这里的失效边对方向无感,但它所响应的那次切换必须真的发生。