# 多渠道交付设计:让 TUI / 飞书也能看到画布产物 > 问题:当前插件的产物(角色卡 / 场景卡 / 分镜图 / 视频片段 / 拼接成片)**只在 Web 的「画布」tab 可见**。当会话跑在 TUI 或飞书(dsh-lark)channel 时,用户看不到产物。 > 本文定位根因,基于对社区飞书插件 `dsh-lark` 源码的实读,给出**不改 dsh-lark、不在插件核心里做渠道探测**的最终方案。 --- ## 0. 最终结论(先看这个) - 产物送达飞书,**只有一条路**:模型调用 dsh-lark 给每个 agent 注册的 `send_file(path)` 工具。dsh-lark 不向 host 提供任何「出站服务」(全仓搜不到 `channelOutbound`),所以**插件 hook 无法主动推文件**。 - **现状(ask 前钩子自动送达,文本转 .pdf)**:插件在 `tools/pre-execute` waterfall 上注册监听器——每次 `ask_user_question` 执行前,自动把画布中未送达的产物程序化调 `ctx.tools.execute('send_file')` 送达: - **媒体**(图片/视频/音频)直接发原文件; - **文本/表格节点**(简报/大纲/镜头表/分镜)在 `canvas_write_node` 落盘时自动导出为 **.pdf**(零依赖 markdown→HTML 转换 → puppeteer-core 复用本机 Chrome `page.pdf()`,`printBackground` 保留表格样式;puppeteer 未装/失败时降级 Chrome CLI `--print-to-pdf` → soffice → cupsfilter,全无则兜底 .html),随钩子一并送达——飞书可直接预览 PDF。 - 成功打 `params.deliveredAt` 去重、失败记 `params.deliveryError` 下次重试;模型不再手动 `send_file`(GUIDANCE 已改为"不要手动调")。Web/TUI 无 `send_file` 时钩子自动跳过。 - **文件名**:`send_file` 没有 name 参数(`fileName = basename(path)`),钩子把产物以**节点标题**命名的副本放到 workspace 内 `.delivery//` 再发送(硬链接零拷贝,如 主角卡.png / 简报.pdf;title 清理非法字符、空标题回退 `kind-id`、重名加 -2 后缀),发完即删;副本建不出来退回原路径发送。 - 本轮动 `lib/index.js`、`lib/pdf.js`(新)、`package.json`(+puppeteer-core 可选依赖)与 SKILL.md 交付条款对齐,不改 dsh-lark。 --- ## 1. 根因:交付层与「Web 画布 tab」耦合,且各渠道渲染能力不同 当前插件把「生产 / 存储」与「展示」合二为一,展示只有一种实现: ``` 产物落盘 /canvas//project.json + 媒体文件 │ ├─ 媒体经 /dsh-short-video-studio/media?token=&path= 伺服(仅本机 Web) │ └─ 唯一渲染器 = Web GUI 的「画布」tab(lib/client.js 的 conversation.view 槽,iframe) ``` 而 Agent 工具的 `output.render` 只返回纯文本: ``` 已生成视频节点 相对路径(send_file 交付): canvas//.mp4 绝对路径(终端打开): /abs/.../canvas//.mp4 媒体: video/mp4 · 3.2 MiB ``` 三个渠道拿到它的下场各不相同: | 渠道 | 能看到画布 tab? | 能渲染工具结果里的图片/视频块? | 能送达产物的机制 | |---|---|---|---| | Web | ✅ iframe「画布」 | ✅(web 渲染器支持 image 块 + `