指出代码调查、方案比较和审核队列等任务若只保存在对话中,会丢失当前状态与阻塞关系。建议保留聊天作为命令入口,同时用表格、图或专用任务界面承载结构化状态和操作。
Chat 非常适合作为一项任务的起点,但对于许多任务来说,把终点也放在 Chat 中,效果却出奇地差。
让助手调查一个代码仓库、比较多个选项,或者管理审核队列,很快,各种有用的状态就会消失在纵向排列的对话记录里:当前选中了什么、发生了哪些变化、哪个结果才是最新的,以及还有哪些事项处于阻塞状态。等到稍后回来时,你不得不从针对过去某一时刻写下的一条条消息中,重新拼凑出一个原本应该由应用承载的问题。
这不只是模型的问题,也是界面的问题。
日历需要日历界面,依赖图需要图形界面,审核队列需要行、筛选器和明确的状态。对话可以继续作为命令入口,但结构化工作需要一个真正的任务界面,由它持有状态并提供恰当的操作。
披露说明:我是 BitFun 的维护者,下面引用源码的案例研究使用了它的实现。我也使用 AI 辅助整理和编辑了本文,随后根据固定版本的源代码逐一核对了所有与产品有关的说法。本文关注的是设计模式,并非独立的安全审计或性能评估。
传统 Chat 由消息和附件组成,传统自动化则拥有固定的输入与输出。具有状态的 Agent 工作需要位于两者之间的第三种对象:一个能够呈现任务当前状态的界面。
这个界面应该持有:
领域对象及其当前状态;
选择项、筛选条件、视图模式和修订版本;
Agent 操作与其所影响对象之间的关系;
用于检查、接受、拒绝或修改结果的位置。
关键的设计决策在于,模型并不会神奇地“看到 UI”。用户提交请求时,应用会选择一份规模较小且定义明确的相关状态快照。该快照会成为一个可进行版本管理的协议的一部分。
这种区别非常重要。在对话记录中,“如果我移除这个节点,会发生什么?”这句话存在歧义。依赖关系浏览器可以通过发送当前选中的节点 ID、可见的依赖项集合、启用的筛选条件和图的修订版本,让问题变得精确。它不需要发送整个 DOM、屏幕截图,或内存中的每一个对象。
BitFun 当前的 Mini App 实现将职责划分为四个部分:
Mini App 负责呈现和领域状态。其源码模型包含一个由 HTML、CSS 和 ESM 构成的浏览器层,并且可以在非 marketplace 配置中支持 worker 逻辑。
桌面端 host 负责特权能力。iframe 通过注入的 window.app bridge 发起调用;文件系统、网络、shell、AI、Agent、通知和 host UI 均被表示为明确的 capability group。
独占的 Agent session 提供连续性。Mini App 可以创建或恢复一个专用 session,在后续轮次中继续复用,并且只接收与该应用实例关联的 session 的进度。
常规 scheduler 负责执行。Mini App 的 Agent 轮次通过桌面运行时使用的同一个 dialog scheduler 提交;任务可能立即开始,也可能进入队列。
数据流十分紧凑:
shared composer
│ token-scoped user message
▼
sandboxed task UI ── relevant state snapshot ──► host bridge
▲ │
│ session-filtered progress │ permission + ownership checks
└──────────── owned agent session ◄───────────┘
│
▼
dialog scheduler
这里有两条边界值得注意。
第一,bridge 是 API 边界,而不是公开整个桌面对象图的邀请。公共 contract 提供的是具名操作,例如 agent.ensureSession、agent.run、chat.claimComposer 和 chat.focusSession。
第二,session 身份是授权机制的一部分。只有当现有记录中的所有者与 Mini App 匹配,并且其 workspace 与预期路径一致时,才允许复用 session。Web host 还会单独追踪当前 iframe 启动了哪些 session,只有经过确认,session 才能出现在共享对话界面中。
当前 API 以显式方式建立这种绑定,而不是依赖隐式关联。
由 Agent 驱动的 Mini App 可以调用 app.chat.claimComposer()。当它所在的标签页处于活动状态时,来自共享输入框的消息会以 chat:userMessage 事件的形式,被路由到这个确切的 iframe。
该认领关系依据的是每个 runner 独有的 token,而不只是应用 ID。这一点非常重要,因为已安装的应用和草稿预览可能使用相同的 ID,并同时处于运行状态。token 可以防止同一次提交被两个 runner 同时接收。
Mini App 可以提供有限的文本内容,例如标题、placeholder 和示例 prompt。输入组件仍由 host 渲染和持有;iframe 无法使用任意的 host markup 替换它。
app.agent.ensureSession() 会创建一个专用 session,或者验证指定的已有 session。随后,app.chat.focusSession() 会将这个已知 session 与 Mini App 的 composer claim 关联起来。
这种拆分很有用:“哪个界面应该接收这次输入?”和“应该显示哪段 Agent 历史记录?”是两个相关的问题,但并不是同一个问题。
host 可以分别接收面向用户的 displayText 和 Mini App 的内部 prompt。因此,对话记录可以保留用户实际输入的内容,同时让 Agent 接收到结构化的任务协议。
下面是这一模式的简化形式:
await app.chat.claimComposer({
title: "Dependency Explorer",
composer: { placeholder: "Ask about the current graph…" }
});
const topic = await app.agent.ensureSession({
sessionName: "Dependency Explorer",
appDataWorkspace: "topics/current"
});
await app.chat.focusSession(topic.sessionId);
app.chat.onUserMessage(async ({ text, displayText }) => {
const state = collectRelevantState(); // app-owned model, not DOM scraping
const prompt = JSON.stringify({
protocol: "dependency-explorer/v1",
request: text,
state
});
await app.agent.run(prompt, {
sessionId: topic.sessionId,
displayText: displayText ?? text
});
});
collectRelevantState() 被有意设计为与具体应用相关。生产环境中的 Mini App 还需要一份输出 contract:解析结果,拒绝格式错误或修订版本已过期的内容,并且只应用经过验证的变更。启动 Agent 轮次是异步操作;进度和完成状态会以经过 session 筛选的事件传入,而不是由 agent.run() 神奇地同步返回答案。
目标并不是在每一轮交互中序列化整个应用。一个实用的任务界面应该建立一条清晰而收敛的边界:
将持久化的领域状态保留在应用中。
只发送与当前操作相关的状态。
将用户的原始表述与内部协议分开保存。
仅在确实需要对话连续性时复用 session。
如果过期输出可能造成危害,则应包含修订版本或其他新鲜度信号。
在修改应用状态或外部资源之前,先验证 Agent 输出。
这样一来,context 就变得可检查。开发者可以对状态快照进行单元测试,用户可以看到当前选中的对象,维护者也可以对协议进行版本管理。与此同时,你还能获得一个明确的位置,用来清除敏感信息并限制 payload 大小。
只有当权限同样足够具体时,有状态的界面才真正有用。
BitFun 的 manifest 模型将文件系统、shell、网络、Node、直接调用 AI、完整 Agent、通知和 host UI 权限彼此分离。shell 访问权限使用命令 allowlist 表示,网络访问权限使用域名 allowlist 表示,而 Agent 访问则拥有单独的启用标志和可选的每分钟调用限制。host 端的 handler 会在处理 bridge 调用之前检查这些权限门槛。
当前面向公开 marketplace 的配置有意采用了比通用源码模型更严格的限制:
marketplace package 必须显式禁用 Node;
package 验证过程中会拒绝远程 import 和动态代码求值;
会拒绝范围过大的主目录和绝对路径文件系统权限;
marketplace iframe 使用 sandbox="allow-scripts" 运行,不具备 same-origin 访问权限;
marketplace Mini App 发起的隐藏 Agent 轮次仅能使用一份规模较小的 allowlist,该列表主要面向只读 Web 调研,而不允许使用文件系统、shell 或 host 控制能力。
这些都是具体的控制措施,并不意味着系统实现了完美隔离。人工审核、manifest、iframe sandbox 和 allowlist 分别降低了不同类型的风险;但它们都无法让不受信任的代码天然变得安全。
该实现还为内置或本地 Mini App 保留了一套兼容配置,其中包含权限更宽松的 iframe sandbox,并可选择支持 worker。因此,不能把 marketplace 严格配置的相关结论推广到所有 Mini App。这种区分可以在 runner 代码中看到,也是威胁模型的重要组成部分。
它解决了一个真实存在的界面问题:
用户可以直接指向一个对象,而不必重新描述它;
后续轮次可以在任务自己的 Agent session 中延续;
进度可以显示在受其影响的状态旁边;
权限和所有权检查拥有明确的执行位置。
它不会让模型自动访问任意 UI 状态,也无法免除设计状态 schema 和输出 schema 的工作。它不能保证模型输出一定正确,无法把 iframe sandbox 变成完整的安全边界,也不会让所有任务都更适合以应用的形式完成。
一次性的解释可能更适合留在 Chat 中。当一项任务具备以下若干特征时,它就很适合成为 Mini App:
状态会在多轮交互中发生变化;
用户需要反复选择或比较对象;
输出在应用之前需要经过审核;
同一工作流会被再次打开;
领域专用的可视化能够揭示比文字描述更多的信息。
Chat 不应该消失。它灵活、宽容,而且通常是表达意图最快的方式。但对话记录应该只是工作的一种视图,而不应该成为承载工作的唯一容器。
这套可复用的思路很简单:
对话提供意图;
任务界面提供结构和控制;
独占 session 提供连续性;
显式状态和由 host 中介的 capability 将三者连接起来。
BitFun 是这一模式当前的一种实现,但它并不能证明这一模式已经完善。它的公开 gallery 仍处于早期阶段,因此在评估这套架构时,源码比采用率方面的说法更有参考价值。
以下内容用于核验,而非背书:
在线 Mini App gallery
BitFun 源码仓库
最新公开 release 和各平台构建版本
本文审阅时使用的固定版本 Mini App 实现
上文中与产品有关的陈述均已根据以下源码位置进行核对,并固定到特定版本,以免未来的改动悄然改变证据所在位置:
浏览器 UI 和可选 worker 的源码模型
Capability group、allowlist 和 Agent 限制
Host UI capability flag
window.app 的 Agent 与 Chat bridge contract
严格 iframe 配置和兼容 iframe 配置
Agent 权限检查与 session 注册
Composer 权限、认领和 session focus 检查
基于 token 范围的用户消息路由
按 session 筛选的 Agent 事件转发
相互分离的内部 prompt 和面向用户的显示文本
Session 复用与 scheduler 提交
复用 session 时的所有者和 workspace 验证
Marketplace package 验证
Marketplace 文件系统与动态代码限制
Marketplace Agent 工具 allowlist
如需采取进一步措施,你可以考虑屏蔽此人和/或举报滥用行为。