agentscope-ai / agentscope-ai/QwenPaw
[Bug]: Console 会话/消息层受限于 @agentscope-ai/chat SDK 的“单会话 pull”模型,阻塞多 Agent / 多工作空间演进
- Ngôn ngữ chính
- Python
- Star
- 34.9k
- Fork
- 3.1k
- Merge trung bình
- 1 ngày 15 giờ
- Pull request đã merge (30 ngày)
- 225
Mô tả
# [Architecture] Console 会话/消息层受限于 @agentscope-ai/chat SDK 的“单会话 pull”模型,阻塞多 Agent / 多工作空间演进
## QwenPaw Version
- **QwenPaw:** `1.1.12`
- **Console SDK:** `@agentscope-ai/chat@^1.1.71`(见 `console/package.json`)
- **SDK 源:** `agentscope-ai/agentscope-runtime`(spark-design),文件
`AgentScopeRuntimeWebUI/core/Context/ChatAnywhereSessionsContext.tsx`
## Description
QwenPaw Console 的「会话切换 + 历史消息渲染」这一层完全由 vendored 的
`@agentscope-ai/chat` SDK 驱动。该 SDK 的会话模型在概念上仍停留在
**“单个活跃会话的聊天控件”**,而不是 **“多会话并发运行的 Agent IDE”**。
这带来两类问题:
1. **可复现的体验缺陷** —— 切换会话白屏 / 闪回;切走正在生成的会话再切回,历史与
流式状态易丢失或错乱。
2. **结构性演进阻塞** —— 会话对象没有 agent / workspace 维度、没有并发会话模型、没有
push/订阅与事件游标,导致 QwenPaw 想做的多 Agent、多工作空间、后台并发运行等能力
无法在数据模型层自然表达,只能在上层反复打补丁。
根因集中在 SDK 的会话加载器:
```tsx
// ChatAnywhereSessionsContext.tsx
// (1) 进入即硬编码自动选中列表第一个会话;无“不选中 / 恢复上次 / 深链”概念
useMount(async () => {
const sessionList = await options.api.getSessionList();
setSessions(sessionList);
setCurrentSessionId(sessionList?.[0]?.id); // ← sessions[0] 硬编码
});
// (2) 每次切会话都“同步清空 → 再整份异步拉取”
export const useChatAnywhereSessionLoader = () => {
useAsyncEffect(async () => {
ReactDOM.flushSync(() => { setMessages([]); }); // ← 同步清空 = 白屏空窗
const session = await options.api.getSession(currentSessionId); // ← pull 整份
setMessages((session?.messages || []).map(m => ({ ...m, history: true })));
if (session?.generating) emit({ type: 'handleReconnect', ... });
}, [currentSessionId]); // ← 每次切换整体重跑
};
// (3) 会话对象只有 { id, name, messages } —— 无 agent_id / workspace_path
// 见 types/ISessions 的 IAgentScopeRuntimeWebUISession
```
### 五个结构性缺陷(均可在上面源码中定位)
| # | 缺陷 | 代码依据 | 后果 |
|---|------|---------|------|
| L1 | **Pull-only,无 push/subscribe** | 对外仅 `options.api.getSession(id)` 返回整份会话 | 离屏/后台生成中的会话无法把增量推进视图 |
| L2 | **切换即清空再重取** | `flushSync(setMessages([]))` → `await getSession` | 清空与返回之间必有空窗 → 白屏 / 闪回上一会话或 sessions[0] |
| L3 | **单会话渲染** | 单一 messages 上下文 + 单值 `currentSessionId` | “多会话并发运行”在模型层不存在,切走的运行态无一等表示 |
| L4 | **无 agent/workspace 维度** | `IAgentScopeRuntimeWebUISession = {id,name,messages}` | 多 Agent / 多工作空间隔离只能在 SDK 之外硬拼 |
| L5 | **无 seq / 无游标 / 无事件模型** | `getSession` 一次性全量返回 | 流式与持久化两条路不对账;断线不能按序重放 |
## Related PR(s)
(无)
## Security considerations
无直接安全影响,属架构 / 体验缺陷。
## Component(s) Affected
- [x] Core / Backend(会话历史读取与持久化契约需配合演进)
- [x] Console (frontend web UI)(SDK 会话层,根因所在)
- [ ] Channels
- [ ] Skills
- [ ] CLI
- [ ] Documentation
- [ ] Tests
- [ ] CI/CD
- [ ] Scripts / Deploy
## Environment
- **QwenPaw version:**
- **Console SDK:** `@agentscope-ai/chat@^1.1.71`
- **OS:** 与平台无关(Windows / macOS / Linux 均可复现)
- **Install method:** from source(后端 `app` + 前端 `npm run dev`)
- **Browser:** 任意 Chromium / Firefox
## Steps to Reproduce
1. 创建 3 个会话,每个各发 ≥5 轮问答,使其中 ≥2 个处于“正在生成”状态。
2. 在这 3 个会话之间随机快速来回切换 10~20 次。
3. 观察每次切换瞬间的消息区,以及切回“正在生成”会话后的历史 + 流式状态。
## Actual vs Expected
**Actual**
- 每次切换先清空、再异步取数 → 白屏 / 一闪而过的上一个会话内容。
- 切走一个正在生成的会话再切回,历史/流式状态依赖单次 `getSession` 重取,
容易历史不全、流式中断态丢失。
- 会话之间没有 agent / workspace 维度,同一入口下不同 Agent、不同工作空间的会话
无法在模型层天然区分。
**Expected**
- 切换会话应“同步命中内存中该会话状态并立即渲染”,无空窗、无闪回。
- 多个会话可并发运行;切走的会话继续在后台推进且状态可见,切回即完整。
- 会话身份自带 agent / workspace 维度,隔离由数据结构保证,而非上层拼接。
## Logs / Screenshots
体验类问题,建议录屏展示切换白屏 / 闪回。无异常堆栈(非崩溃类 bug)。
## Additional Notes — 概念对照与建议演进方向
同类现代 Agent IDE(opencode)在协议/SDK 层已采用 **事件溯源 + 投影 + 会话所有权**
模型,正是 `@agentscope-ai/chat` 当前缺失、也是 QwenPaw 多 Agent/多工作空间最需要的:
**A. 消息是有类型的事件流,而非一坨 messages**
opencode `packages/schema/src/session-message.ts`:消息是 tagged union
(`User / Assistant / AgentSwitched / ModelSwitched / Compaction / Shell / System`),
`Assistant.content` 是 `text | reasoning | tool` parts,`tool` 带
`pending/running/completed/error` 状态机,消息 id 用单调可排序的 `msg_`。
→ `AgentSwitched` / `ModelSwitched` 是一等事件,**多 Agent、多模型天生可表达**;
反观 agentscope SDK 只有 `{id,name,messages}`。
**B. 持久事件带 (aggregateID, seq, version),可按序重放**
opencode `packages/schema/src/event.ts`:
```ts
durable?: { aggregateID: string; seq: number; version: number }
```
→ 事件按 `(会话, seq)` 有序 append-only、可版本化、可游标重放(另见 `session-cursor`、
`event-manifest`、`effect-drizzle-sqlite/.../session` 的 SQLite 投影)。
支持“断线不丢、重连按 seq 补拉、离屏会话直接收事件”,`getSession` 全量拉取做不到。
**C. 会话所有权围栏,并发切换安全“由构造保证”**
opencode `packages/app/.../session-ownership`(含 test)验证:
“导航到 B 后不再运行 A 捕获的续作”“A→B→A 不复活旧续作”。
→ 这是“切走正在生成的会话再切回不串、不错乱”的关键原语;
pull + `flushSync([])` 模型无法做到,只能靠上层补偿。
### 建议(按成本递增,可分阶段)
1. **短期**:为 SDK 增加 push/subscribe,提供“切换同步取数、不清空”的渲染路径;
去掉 `sessions[0]` 硬编码自动选中,支持“不选中 / 恢复上次 / 深链”。
2. **中期**:在会话对象上引入 `agent_id` / `workspace_path` 一等维度,让隔离进入数据模型。
3. **长期**:把会话历史真源从“整份 messages 快照 + pull”迁到“事件日志
(按 `(session_id, seq)` 有序、append-only)+ 投影”,对齐 opencode 的事件溯源模型,
从架构上消除切换白屏、并发串话与多 Agent 表达缺失。
Hướng dẫn đóng góp
Đánh giá
Issue này chưa được đánh giá.