从 session 僵尸问题出发,到 Channel SDK 全面接入。
一次由 Tianyi 对比法定位、两步精确纠正、最终全链路打通的架构跃迁。
「长视频/card 子任务 + Feishu 长连接入站卡死 → 结束任务后就卡死了。你做这个任务的时候怎么就不卡死,换个 session 就卡死了」
初步分析:Feishu 长连接超时 → session 僵尸。spawn + yield 模式避免了阻塞。
「不是,人家都是跑你的那个卡片人物跑死的」— 纠正:是 card task 的 session 自己死了,不是父 session
「不是,刚刚的 makaron skill」— 再纠正:不是 manju 人物卡,是 Makaron skill 的 card workflow
精确定位:run.cjs 的 watchEvents() 阻塞 session 2-5 分钟 → Feishu 长连接 idle 超时 → 僵尸。读 run.cjs 源码确认。
run.cjs 的 watchEvents() 异步循环阻塞当前 session 2–5 分钟。在此期间 Feishu 入站长连接 idle,被 gateway / nginx 在 60–120s 后主动断开。 workflow 跑完、卡片发出了,但 session 的入站通道已死 → 永久僵尸。
sessions_spawn(task, mode="run") + sessions_yieldfor await (watchEvents)在 ~/.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 分钟 → 僵尸
}| Before | After |
|---|---|
| connect() 同步,不走 WebSocket | await connect() — WebSocket 握手,SDK 内置心跳 + 自动重连 |
| 手动 createCard + updateCard 循环 | streamCard() — 一张卡贯穿始终,SDK 自动节流/排队/容错 |
| 每次 updateCard 手动 REST 调用 | ctrl.update(state) — SDK 背后自动 message.patch |
| 7 个独立步骤,agent 管理一切 | 1 个 streamCard() 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});
}
}
});V 银发女孩头像 → 5s Kling 视频。全程 3 分 58 秒,无卡死,卡片流畅更新。
← Report Archive·Generated by V ⚡·Makaron Workspace