Skip to content

bug(web): 新建会话后的首条消息可被另一标签页改投其他工作区 #466

Description

@tt-a1i

问题与价值

新建 Web Session 并自动发送首条消息时,前端没有把后续操作绑定到创建结果。另一个标签页若在创建响应与快照刷新之间切换活动 Session,且 SSE 切换事件尚未送达,首条消息可以被自动投递到另一个 Session,甚至另一个工作区。

建议 P1:这影响执行目标的正确性。用户为仓库 A 写的操作指令可能交给仓库 B 的 Agent。当前证据确认了错误的 prompt 请求目标,没有执行真实工具,也没有观测到用户仓库损坏。

触发前提与实际复现

源码:ed9dbc1018f890fd54375f5371990ddfee8af5df。验证日期:2026-09-08。

  1. 标签页 A 尚未创建 Session,选中 /repo-A,发送首条消息。
  2. 创建 API 成功返回该请求的 commandId 和 A 的 sessionPath
  3. 另一个标签页激活 /repo-B 的 Session B;A 的 SSE 暂时没有收到该事件。
  4. A 随后的 snapshot 请求返回当前活动 Session B。
  5. A 的发送动作继续,把原来的消息交给 B。

直接加载生产 createWebStore、以内存 client 控制响应时序,得到:

createSession(path=/repo-A)
prompt(sessionId=sB, content="Edit repository A configuration", retry=false)
sendPrompt result=true
selectedWorkspace=/repo-B
selectedSession=sB

源码原因与现有保护

现有 Session epoch 可以在外部 SSE 切换事件先到时中止旧操作;本次覆盖的是 HTTP 已观察到新 Session、SSE 尚未到达的顺序。两条独立连接不能依赖这种先后关系保证 target 正确。

Host 检查runtime 检查都要求请求里的 Session ID 是当前活动 Session。这些检查没有失效:前端已经把用户原定目标换成合法的 B ID,所以它们无法识别原来的 A 意图。

探针关键片段

base 是没有选中 Session、包含 A/B 两个工作区的快照;activeB 是 B 当前活动的快照。测试直接使用生产 store,没有复制其状态逻辑:

let created = false;
const client = {
  snapshot: async () => created ? activeB : base,
  createSession: async (path, commandId) => {
    created = true;
    return { cancelled: false, sessionPath: "/web-sessions/sA.jsonl", commandId };
  },
  prompt: async (sessionId, content, commandId) => {
    calls.push({ sessionId, content });
    return { id: commandId, accepted: true };
  },
};
const store = createWebStore(client);
await store.getState().actions.refreshSnapshot();
store.setState({ selectedWorkspace: "/repo-A" });
await store.getState().actions.sendPrompt("Edit repository A configuration");

最小验收

  • 将创建、预选模型和首条消息绑定到本次创建的稳定 Session 身份;首次落盘前也不能依赖一个可能缺失的文件路径。
  • 每个异步边界后核对该目标;外部切换时明确中止自动发送、保留原输入,不自动采用新的 canonical target。
  • 覆盖两个标签页、SSE 延迟/断开、创建响应与快照之间切换,以及预选模型;A 的内容和模型选择不得命中 B。
  • 保留 Host/runtime 的活动 Session 校验和幂等保护;在真实浏览器验证目标路径,并通过相关测试、bun run checkbun run test

验证边界:本次是生产 store 的内存响应探针及服务端源码核实,使用 Bun 和已有 Zustand 依赖;未启动 Host/Pi、调用模型、访问真实 Session 或修改仓库文件。Related: #347#455;不同于 #457 草稿归属和 #463 接收回执恢复。

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions