让你的小程序数据,能在 WorkFly 主会话里被点进去、由你自己渲染——而不是被大模型转述成一段文字。两个一等能力:引用(Reference) 与 渲染面(Surface)。
前置:本页建立在 开发指南 · 贡献模型 之上——引用与渲染面,就是「卡片 / 详情渲染器」贡献在主会话里的应用,建议先读完开发指南。
全文用一个贯穿示例:一个审批小程序(appId
com.acme.approval),把「待审批单据」接进会话。
为什么需要
用户在会话里说「看看我有哪些待审批」,你希望:
- 返回的列表/详情长成你应用本来的样子(你的 UI、你的样式),用户一眼认出「这是该应用的真数据」,而非模型二次加工;
- 其中每一条都能点击跳进你应用里的对应界面。
这两件事,平台都给了统一、通用的接入点——所有小程序(内置与第三方)一视同仁。
引用 Reference:会话里的可点击对象
一个 Reference 就是「指向某业务对象、可点击的引用」:
interface Reference {
kind: string // '<你的appId>.<entity>',如 'com.acme.approval.bill'
id: string // 业务对象主键
label: string // 展示文字(标题 / 名称)
payload?: Record<string, unknown> // 跳转需要的附加信息
}
产出引用
你的工具(tool)在结果里带上 details.refs,会话会自动把它们渲染成可点击的 chip:
return {
content: [{ type: 'text', text: '你有 2 条待审批单据。' }],
details: {
refs: [
{ kind: 'com.acme.approval.bill', id: '1024', label: '张三的请假申请(3 天)' },
{ kind: 'com.acme.approval.bill', id: '1025', label: '市场部采购付款 ¥12,000' }
]
}
}
点击后怎么跳(导航)
平台按 kind 决定跳转,优先级:
- 你贡献了该 kind 的「详情渲染器」 → 在会话上就地展开你的详情视图(最常见,见下);
- 否则 → 通用默认:平台用已注册 manifest 中与
kind匹配的最长 App id 前缀解析归属,启动你的 standalone 界面,并把这条ref作为启动参数投递。你在界面里用wf.getLaunchOptionsSync()读出来、定位到该对象。
第三方沙箱小程序零额外宿主代码即可被导航:产出
ref+ 在自己界面读启动参数定位,就通了。
渲染面 Surface:列表 / 详情,由你自己渲染
把结构化数据由你的渲染器呈现进会话,两种模式:
- list:行式、可翻页,行点击钻取详情;
- detail:单对象完整视图。
Manifest v2 的第三方卡片通过高信任 React UI Module 贡献:Manifest 用 module + component 指向预编译 ESM,宿主提供 workfly-react@1 runtime,并把样式挂入 Shadow Root。完整应用页面仍运行在 Sandbox。平台负责统一外壳:
- 来源标识:卡片上的“卡片来自于 + 小程序类型 + 应用图标 + 应用名”标识,让用户确信数据归属;应用图标和名称是同一可点击区;
- 列表翻页:会话内点击翻页,交互与聊天一致;
- 行钻取:点击列表行 → 展开 detail。
卡片还要给 Agent 一份概要
用户能看见卡片,不代表 Agent 能读取卡片 DOM 或沙箱数据。Tool 返回使用两个通道:details.card 给用户渲染,content 给 Agent 保存上下文。第三方 launch 和声明式只读 query Tool 都可在 manifest 中用 resultCard 关联业务卡,并用 summary 提供业务概要:
"resultCard": {
"kind": "com.acme.approval.bill",
"summary": "待审批卡展示单据标题、申请人、金额、状态和业务编号,可按编号继续查询详情。"
}
summary 是必填的非空字符串。它要覆盖卡片的业务对象、关键口径和缓存/实时状态,不能只写“已展示卡片”;Token、Cookie、一次性 code 等敏感信息不得写入。launch Tool 不执行小程序业务 JavaScript,因此它的摘要是静态语义概要。声明式 query Tool 则由主进程执行固定 HTTPS 只读请求:字段受限的实时投影同时进入 Agent content 和内部会话卡的 card.data.result。Agent 用这些上下文理解后续指代,但不应逐字复述卡片已展示的内容。外部 MCP 没有卡片通道,仍只返回 query Tool 原有的字段受限结果。
卡片贡献“下一步建议”
卡片渲染器可在 cardRenderers[].suggestions 中声明最多 4 条与当前业务卡自然衔接的候选:
"suggestions": [
{ "label": "查看最近订单", "text": "查询我最近的个人订单", "act": "send" },
{ "label": "填写评价", "text": "为这笔订单填写评价", "act": "fill" }
]
label 为 1-14 字,text 可选且最多 200 字。查询、查看、分析类可用 send 点击即发;提交、发送、删除等有副作用的动作必须用 fill 预填待用户确认。宿主会再做长度、去重和风险复核,与辅助模型建议融合;辅助模型不可用时也可直接展示本地候选。用户关闭建议或处于角色态时不展示,外部 MCP 也不接收这些候选。
卡片 UI 建议:精简摘要,别堆长
会话里的卡片是摘要,不是完整页面。务必克制——长卡片会撑爆会话流、逼用户在对话里上下滚动,体验很差:
- 只放重点:列表每行一句话(标题 + 一两个关键信息),详情只放最重要的几个字段 + 一段简短描述;
- 不滚动、不超高:内容超了就截断(描述截断到两三行、字段只显前几项),别在卡片里塞滚动条;列表用翻页而不是拉长;
- 完整引导进应用:更全的内容(长描述、附件、评论、全部字段、操作)放一个「打开完整 / 在应用中查看」入口,点进应用或详情宿主里看——卡片负责「一眼看懂 + 快速进入」,不负责「全都铺开」。
平台内建的通用列表 / 详情卡(
kind:'list'/kind:'detail')已按此约束实现(行截断、描述截断、字段截断、列表翻页、统一「打开完整」)。自写卡片也请遵守同一尺度。
贡献「详情渲染器」
让你的对象在会话里就地展开详情(而非整屏切走):在贡献清单里按 kind 注册一个详情渲染器,平台的通用详情宿主会据此渲染。它拿到 { reference, onClose },你决定长什么样。
// 贡献清单(与 cards 平行)
details: [{ kind: 'com.acme.approval.bill', renderer: { type: 'native', component: BillDetail } }]
当前
details只消费内置 App 的代码贡献,第三方 manifest 尚不接受详情渲染器。第三方 Reference 在没有详情渲染器时走上述通用默认导航。
小结:你要做的
| 想要 | 你提供 |
|---|---|
| 会话里出现可点击引用 | 工具结果带 details.refs |
| 点击就地展开详情 | 贡献该 kind 的详情渲染器 |
| 会话摘要是你应用的样子 | 贡献 React 卡片渲染器,来源由平台补齐 |
| Agent 能理解卡片内容 | launch 提供 resultCard.summary;query 还同步实时投影 |
| 卡片出现后提示自然后续 | cardRenderers[].suggestions,有副作用动作使用 fill |
| 点击跳进你的完整界面 | 啥都不用做(通用默认)或自定义导航 |
设计原则:应用自渲染增强可信度、平台基建所有小程序共享。务必给会话里的数据带上「来源标识」,让用户分清「这是应用真数据」与「模型生成的话」。
下一步
- 还没建工程?回 开发指南 · 快速开始。
wf.getLaunchOptionsSync等签名 → WF API。- 想让应用也出现在磁贴 / Spotlight?见 开发指南 · 贡献模型。