Skip to content

Feature/preview devserver#1

Open
yhsunshining wants to merge 2 commits into
mainfrom
feature/preview-devserver
Open

Feature/preview devserver#1
yhsunshining wants to merge 2 commits into
mainfrom
feature/preview-devserver

Conversation

@yhsunshining
Copy link
Copy Markdown
Member

No description provided.

Xxxxie pushed a commit to Xxxxie/OpenVibeCoding that referenced this pull request May 13, 2026
投产硬门槛:让 OpenCode 会话历史与 Tencent 一致地落 vibe_agent_messages 集合,
前端 /api/tasks/:id/messages 读回后能正常渲染完整对话(下次打开仍可见)。

## 架构(三时间点对齐 Tencent)

    chatStream(prompt)
        ├─ findLastRecordIds → 建 replyTo 链
        ├─ preSavePendingRecords → user(done) + assistant(pending)
        ├─ turnId = assistantRecordId  (与 Tencent 一致)
        ├─ launchAgent:
        │    ├─ new OpencodeMessageBuilder({assistantRecordId})
        │    ├─ 每个事件: pushEvent + SSE + stream_events 三路分发
        │    └─ tool_result 触发 flushToDb (里程碑)
        └─ finally:
             builder.finalize(status)
               ├─ setRecordParts(turnId, finalParts)
               └─ finalizePendingRecords(turnId, 'done'|'error'|'cancel')

## 新增

- src/agent/runtime/opencode-message-builder.ts
  class OpencodeMessageBuilder 累积事件 → UnifiedMessagePart[] → 落库
    pushEvent(msg) 按 contentType 分支:
      text chunks 合并到 currentTextBuffer
      tool_use/thinking 前 flush buffer 成 part
      tool_input_update 就地更新原 tool_call part
      tool_result 同步 tool_call 的 status='completed'/'error'
      顺序保持 [text, tool_call, tool_result, text]
    flushToDb() tool_result 里程碑触发
    finalize(status) setRecordParts + finalizePendingRecords
  findLastRecordIds() 找上轮 records 维护 replyTo/parentId 链

- scripts/test-persistence-e2e.mts
  真实 TCB 环境 e2e:prompt → 跑完 → loadDBMessages 读回验证
  + 模拟前端 tasks.ts 转换逻辑不报错
  10/10 断言全 PASS

## 改动

- persistence.service.ts
  新增 public setRecordParts(recordId, parts) — 给非 Claude SDK runtime
  用(它们不写 JSONL,直接一次性替换 parts)

- opencode-acp-runtime.ts
  chatStream 非 resume 分支:
    preSavePendingRecords → turnId = assistantRecordId(替代之前的随机 uuid)
  launchAgent:
    new OpencodeMessageBuilder(envId ? opts : null)
    makeEmitter 接受 messageBuilder 参数
    事件循环:builder.pushEvent + tool_result 触发 flushToDb
    finally:builder.finalize(status 按 completed/abort/error 映射)

## AgentCallbackMessage → UnifiedMessagePart 映射

| 事件         | contentType   | metadata                             |
|--------------|---------------|--------------------------------------|
| text chunks  | text (合并)   | {id, type:'message', role}           |
| thinking     | reasoning     | -                                    |
| tool_use     | tool_call     | {toolCallName, toolName, input, status} + toolCallId |
| tool_result  | tool_result   | {status, isError} + toolCallId       |

## 测试结果

### e2e #42 持久化
```
records count: 2
  - user      status=done  parts=1  [text]
  - assistant status=done  parts=3  [tool_call, tool_result, text]

frontend convert result: 2 TaskMessage
  user parts:  text
  agent parts: tool_call,tool_result,text  ← 前端原样消化

10/10 assertions PASS
```

### 回归
- e2e TencentCloudBase#1 local: PASS
- e2e ToolConfirm: PASS
- type-check / lint / build / format 全通过

## 与 stream_events 的分工

两条数据通道独立:
- vibe_agent_messages = 永久会话历史(前端 /messages 读)
- vibe_agent_stream_events = 实时 SSE(observe 重连 replay,turn 完成后清理)

## 已知边界

- text-only + server 崩溃 → 丢失尾部 text(stream_events 保底,可重启 replay 补偿)
- 无定时 flush(仅 tool_result + finalize 时写),长 text 可加 5s 定时
Xxxxie pushed a commit to Xxxxie/OpenVibeCoding that referenced this pull request May 13, 2026
## 背景:和 Tencent SDK 的对齐审视

用户提问:"这个持久化和之前的 ToolConfirm/AskUser 能力能对应吗?"
深度调研 Tencent SDK 持久化链路后发现:

我之前只在 tool_result 时 flushToDb。问题:
- AskUserQuestion / ToolConfirm 场景,tool_call 发出后会挂起等待用户答复
- 挂起期间 DB 里 assistant record 是空的 parts(flushToDb 还没触发)
- 前端此时若打开历史,看不到"系统在问你问题"的卡片

Tencent 不受此影响:它通过 SDK JSONL 写 + updateToolResult 立刻同步;
OpenCode 走事件流,需要主动在 tool_use 时触发 flush。

实测 opencode 事件顺序(test-event-order.mjs):
  tool_call (status=pending)   ← 工具调用,execute 即将开始
  [execute 阻塞等用户答复]
  tool_update (status=completed, rawOutput=...)   ← execute 返回

所以 tool_use 是挂起前的"最后一个可见事件",必须立刻 flush。

## 改动

opencode-acp-runtime.ts makeEmitter:
  if (msg.type === 'tool_use' || msg.type === 'tool_result') {
    messageBuilder.flushToDb().catch(...)
  }

## 验证

新增 scripts/test-persistence-suspend-e2e.mts:
  Round 1: LLM 调 AskUserQuestion → 等 ask_user 事件
  ★ 挂起期间 loadDBMessages:
    assistant status=pending parts=[text, tool_call(in_progress)]
    无 tool_result ← 预期行为(还没答)
  Round 2: askAnswers 恢复 → 最终 loadDBMessages:
    assistant status=done parts=[text, tool_call(completed),
                                 tool_result(completed), text(继续)]

所有断言 PASS。

## 额外:关于 Tencent 独有的持久化步骤

调研发现 Tencent SDK 还做:
  - providerData 继承(从原 tool_call 继承 messageId/model/agent)
  - providerData 清理(剥离 SDK 的 skipRun/error deny 标记)
  - Resume 时立刻同步调 updateToolResult

这些是 Claude SDK 的 JSONL 行为兼容需要的(防 deny 死循环)。
OpenCode 不走 JSONL,通过事件流自然同步 tool_result,没有这些兼容性压力。
所以"不实现"是正确的设计简化,文档 §14.10 明确记录了这点。

## 回归

e2e TencentCloudBase#1 local + ToolConfirm + Persistence 全 PASS
type-check / lint / build / format 全通过
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant