Skip to content

feat(card): 新增 persistentStreamCard 配置支持会话内复用同一张流式卡片#557

Draft
yujiaze wants to merge 1 commit into
deepcoldy:masterfrom
yujiaze:feat/persistent-stream-card
Draft

feat(card): 新增 persistentStreamCard 配置支持会话内复用同一张流式卡片#557
yujiaze wants to merge 1 commit into
deepcoldy:masterfrom
yujiaze:feat/persistent-stream-card

Conversation

@yujiaze

@yujiaze yujiaze commented Jul 22, 2026

Copy link
Copy Markdown

背景

默认每轮对话都「发新流式卡 + 撤回旧卡」,长会话反复重发/撤回体验较吵。

改动

新增 per-bot 全局配置 persistentStreamCard:开启后一个会话只在首轮 POST 一张卡,
后续每轮用 updateMessage 原地 PATCH 同一张卡(标题跟随最新一轮)。与
disableStreamingCard 互斥(后者优先),默认关闭、行为不变。

实现要点

  • daemon.ts beginNewTurn:持久模式且已有存活卡片时,跳过冻结旧卡、不置
    streamCardPending,使下轮 screen_update 天然命中 worker-pool 既有复用 PATCH 分支
    (restoredCardId = streamCardId && !streamCardPending),首轮无卡仍 POST 一次。
  • 刻意不动 re-fork / relay / repo 切换路径(显式清 streamCardId,无法可靠复用)。

影响面

集中在飞书卡片编排层,不碰 im/lark 事件路由,与 CLI / 后端无关;话题群/普通群/
私聊同走 beginNewTurn 一致生效;默认关闭时行为完全一致。

验证

  • tsc 全量编译通过;dashboard 前端 bundle 成功
  • 直接相关单测 91/91、邻近单测 550/550 通过
  • 全量 unit 另有若干 pre-existing 失败(session-discovery / v3-distillation-runner /
    vc-meeting 等,与本改动无关,stash 对比干净基线同样失败)

默认行为下每轮对话都会「发一张新流式卡 + 撤回上一轮旧卡」,长会话里卡片
反复重发/撤回,体验较吵。新增 per-bot 全局配置 persistentStreamCard:
开启后一个会话只在首轮 POST 一张卡片,后续每轮都用 updateMessage 原地
PATCH 同一张卡(标题跟随最新一轮),不再发新撤旧。

实现要点:
- daemon.ts beginNewTurn:持久模式且已有存活卡片(streamCardId + workerPort)
  时,跳过冻结旧卡入 frozenCards、且不置 streamCardPending。下一轮
  screen_update 因此天然命中 worker-pool 既有的复用 PATCH 分支
  (restoredCardId = streamCardId && !streamCardPending),无需新增 PATCH 逻辑。
  首轮无卡时仍照常 POST 一次。
- 刻意不干预 re-fork / relay / repo 切换路径(它们显式清空 streamCardId,
  worker 端口已失效无法可靠复用),保持原有重发行为。
- 与 disableStreamingCard(完全不发卡)互斥,后者优先。

配置贯穿:bot-registry(字段 + normalize)、card-prefs-store(接口/默认值/
读写同步)、dashboard-ipc-server + bot-payload + bot-defaults(前端类型)+
bot-defaults-page(卡片行为区新增开关)+ i18n(中英文案)。
文档:docs-site zh|en 的 cards.md 与 bots-json.md。

影响面:改动集中在飞书卡片层(im/lark 无关,daemon 卡片编排 + card-prefs),
与 CLI / 后端(PtyBackend/TmuxBackend)无关,默认关闭时行为完全不变。
话题会话 / 群会话 / 私聊均走同一 beginNewTurn 路径,一致生效。

验证:
- tsc 全量编译通过(--noEmit 与 emit 均 exit 0)
- dashboard 前端 esbuild bundle 成功
- 直接相关单测通过:card-prefs-auto-start + bot-registry(×2) 共 91/91
- 邻近单测通过:event-dispatcher + card-handler-grant + cli-adapters 共 550/550
- 全量 unit 另有若干 pre-existing 失败(session-discovery / v3-distillation-runner
  / vc-meeting 等,与本改动无关;stash 对比干净基线上同样失败)

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有一个会绕过 persistentStreamCard 的后续 turn 路径,建议在 ready 前修掉并补回归测试:

src/im/lark/card-handler.tsretry_last_task 仍无条件执行 ds.streamCardPending = true。该分支会先 scheduleCardPatch 更新当前卡,但后续任一 screen_update 会因为 pending=true 进入 worker-pool 的 POST 分支,发新卡并 recall 旧卡。因此开启 persistentStreamCard 后,用户从限额卡点击“重试上次任务”仍会出现本 PR 想消除的“发新卡 + 撤旧卡”。现有 test/card-integration.test.ts 还固定断言 retry 后 pending=true,说明当前测试没有覆盖新开关语义。

建议:

  • retry 路径与 beginNewTurn 共用同一“本 turn 是否复用已有卡”判断(至少在 persistent + real card + live worker 时保持 pending=false);
  • 新增核心状态机回归:默认模式第二轮仍 POST;persistent 模式第二轮只 PATCH 同一 message id;retry_last_task 在 persistent 模式也只 PATCH;首轮、re-fork/relay 等刻意例外保持原行为;
  • PR 描述说“一个会话每个后续 turn 都复用”,但实现刻意排除了 re-fork/relay/repo switch,文档最好同步说明这些例外。

本地验证:pnpm build 通过;相关 6 个测试文件 325/325 通过,但本 PR 没新增测试覆盖上述分支。全量 unit 为 9861/9865,3 个时区失败可在 PR parent 复现,另 1 个 v3-host 并发用例单跑即通过,均与本改动无关。

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.

2 participants