2026-06-02 · Architecture Upgrade

Makaron Skill
大升级

从 session 僵尸问题出发,到 Channel SDK 全面接入。
一次由 Tianyi 对比法定位、两步精确纠正、最终全链路打通的架构跃迁。

3′58″端到端验证
0Session 卡死
2Red Lines 固化

🔍 调试过程

04:11
Tianyi问题发现

「长视频/card 子任务 + Feishu 长连接入站卡死 → 结束任务后就卡死了。你做这个任务的时候怎么就不卡死,换个 session 就卡死了」

04:11
V初步分析

初步分析:Feishu 长连接超时 → session 僵尸。spawn + yield 模式避免了阻塞。

04:13
Tianyi第一次纠正

「不是,人家都是跑你的那个卡片人物跑死的」— 纠正:是 card task 的 session 自己死了,不是父 session

04:13
Tianyi第二次纠正

「不是,刚刚的 makaron skill」— 再纠正:不是 manju 人物卡,是 Makaron skill 的 card workflow

04:14
V根因定位

精确定位:run.cjs 的 watchEvents() 阻塞 session 2-5 分钟 → Feishu 长连接 idle 超时 → 僵尸。读 run.cjs 源码确认。

⚡ 根因

run.cjs watchEvents() 异步循环阻塞当前 session 2–5 分钟。在此期间 Feishu 入站长连接 idle,被 gateway / nginx 在 60–120s 后主动断开。 workflow 跑完、卡片发出了,但 session 的入站通道已死 → 永久僵尸

✅ V 为什么不卡
sessions_spawn(task, mode="run") + sessions_yield
父 session 3 秒释放 Feishu 连接
子 agent isolated,没有长连接
❌ 别人为什么卡死
当前 session inline 跑 for await (watchEvents)
阻塞 2–5 分钟 → 连接超时 → 僵尸

🎯 Tianyi 的推动方向

「你以为我用了呢,赶快接上」
推动接入 Feishu 官方 Channel SDK
「降低 agent 门槛」
产品哲学 — SDK 封装底层,agent 只管业务
分享 Lark Channel SDK 官方文档
文档对比表:不用 SDK 自己写长连接 vs 一行 createLarkChannel()
「卡片上 Makaron project 的按钮要一直在」
触发 projectUrl 持久化修复
「改回 1:1,重新生成文字被截断了」
按钮布局从 2:1 调回 1:1
「做 report web / 沉淀下 / 往前翻聊天记录」
要求飞书文档 + 本地 memory + Report Web 三重沉淀
「你看下 openclaw 设置健康吗」
触发完整系统健康检查

技术改动

1. SESSION ISOLATION RED LINE

~/.agents/skills/makaron/SKILL.md 顶部新增第二个 RED LINE,与已有「CARDS NOT TEXT BUBBLES」并列。含完整根因分析 + 正确/错误代码示例。

✅ 正确
sessions_spawn(task, mode="run")
sessions_yield
// parent session 3s 释放
❌ 错误
for await (const ev of watchEvents(runId)) {
  // 阻塞 2-5 分钟 → 僵尸
}

2. run.cjs 全面重写 · Channel SDK 接入

BeforeAfter
connect() 同步,不走 WebSocketawait connect() — WebSocket 握手,SDK 内置心跳 + 自动重连
手动 createCard + updateCard 循环streamCard() — 一张卡贯穿始终,SDK 自动节流/排队/容错
每次 updateCard 手动 REST 调用ctrl.update(state) — SDK 背后自动 message.patch
7 个独立步骤,agent 管理一切1 个 streamCard() producer 搞定
新 Quick Start — agent 只需一个 producer 函数:
const {channel, chatId} = await connect('oc_xxx');

await streamCard(channel, chatId, {
  title: '视频工作台', brief: '5s Kling',
  async producer(ctrl) {
    const {runId, projectUrl} = await submitChat({imagePath, prompt});
    ctrl.update({projectUrl, status: '已提交', progress: 22});
    for await (const ev of watchEvents(runId)) {
      const text = eventText(ev);
      if (text) ctrl.update({makaronSay: text.slice(0, 1800)});
    }
    const out = await getRunOutput(runId);
    if (out.videoUrl) {
      await downloadAndSend(channel, chatId, out.videoUrl);
      ctrl.update({done: true, progress: 100});
    }
  }
});

3. 卡片 UI 迭代

4 套风格 Demo
默认 / 极简 / 信息卡片 / 仪表盘,Tianyi 选择默认风格
Schema V2 限制
tag: note 和 tag: action 不支持,改用 div + column_set
Button Callback
飞书 card.action.trigger → OpenClaw channel → session 全链路通
projectUrl 持久化
renderCard memoize,设过一次永不丢失
按钮 1:1 等分
2:1 导致「重新生成」截断 → 改回等分

4. 端到端验证

1WebSocket 握手
2第一轮 Pattern B
3第二轮渲染
4downloadAndSend
5done 态卡片

V 银发女孩头像 → 5s Kling 视频。全程 3 分 58 秒,无卡死,卡片流畅更新。

📦 产出清单

Skill 规则
~/.agents/skills/makaron/SKILL.md
Card Runner
scripts/makaron-card-runner/run.cjs
Demo 脚本
scripts/makaron-card-runner/demo-styles.cjs
飞书文档
完整复盘文档
Report Web
当前页面
每日记录
memory/2026-06-02.md

Takeaways

01
Feishu 入站不是长任务通道

任何 >30s 的同步操作都必须 spawn isolated child,父 session 立刻 yield。

02
Channel SDK 全面接入

WebSocket 心跳 + 自动重连 + streaming + throttling 开箱即给,不用自己造。

03
卡片状态要 memoize

projectUrl 这种"设一次管一生"的字段必须持久化,不能依赖每次 update 都传。

04
Tianyi 的调试方法论

对比法快速定位(V 不卡 vs 别人卡)+ 两步精确纠正让分析聚焦。

05
降低 agent 门槛

好用的 SDK + 清晰的规则 = agent 只管 producer 里的业务逻辑。

06
多重沉淀

飞书文档 + Report Web + memory 三重记录,不同场景有不同消费方式。

← Report Archive·Generated by V ⚡·Makaron Workspace