产品对象与平台边界
产品层将对话、执行和交付分成不同对象
用户操作的是对话、任务、团队、定时工作和交付物,而不是 Agent Loop、工具注册表或上下文窗口。aibuddy 将这些概念建模为独立对象,使一次工作可以在页面关闭、连接中断或沙箱释放后继续被检查。
| 产品对象 | 负责保存什么 | 与执行的关系 |
|---|---|---|
| Chat | 持续会话、参与者和消息时间线 | 承载普通 Agent 轮次,也可容纳 Team 成员的过程与交付 |
| Task | 目标、消息、状态、步骤与运行配置 | 承载一项可追踪执行;Schedule 可以新建或继续已有 Task |
| Team | 成员、依赖关系与共同目标 | 在会话中协调多个长期成员,并把成员结果汇回时间线 |
| Schedule | 时间规则、目标类型、模型与运行记录 | 到点后创建新 Task,或验证所有权后继续父 Task |
| Project、Knowledge、Skill | 文件环境、正式资料和可复用流程 | 在运行前限定工作环境和可用能力 |
| Delivery、Artifact | 一次交付事件及其中的长期文件 | 由 complete 触发,从消息和沙箱中分离结果 |
这些对象不是功能菜单的平铺清单。Chat 与 Task 是两类执行宿主;Team 和 Schedule 分别提供协作与时间触发;Project、Knowledge 和 Skill 为执行提供资源;Delivery 则记录已经对用户交付的结果。对象边界使所有权、状态与失败恢复可以落到具体记录上。
交付物在 complete 后脱离任务沙箱
写入沙箱的文件仍属于执行过程。只有 Agent 调用 complete 并声明路径后,DeliveryService 才会从沙箱读取文件字节,将其写入平台文件存储,并保存 Delivery 事件与 Artifact 索引。后续预览和下载从文件存储读取,并重新校验请求用户是否拥有该交付物,因此不依赖原沙箱继续存在。
物化过程还维持四个边界:
- 只处理文件,不把目录记录成可下载产物;
- 单文件当前最多物化 25 MiB,超限或读取失败的文件不会生成打开后返回 404 的空索引;
- 同一执行宿主下的同一路径复用稳定 Artifact 标识,重新交付会更新当前内容,而不是无限累积副本;
- Delivery 事件最后写入,且至少一个文件物化成功时才创建。
因此,任务成功交付与平台长期保存是相邻但独立的步骤。长期保存采用逐文件 best-effort,不会反过来将已经完成的 Agent 工作改写成执行失败。
共享核心通过端口复用,而不是共享基础设施
Web 与 Desktop 共同使用 React 产品界面、数据契约、Hono 路由装配和领域服务。平台差异集中在 composition root:宿主向仓储、文件存储、鉴权、队列、实时传输、调度器、沙箱和 MCP 客户端等端口注入不同实现。
这套边界的关键不只是共享 TypeScript 类型。Desktop 主进程会用 SQLite 服务和固定本地身份启动与 Server 相同的 createApp,再通过 loopback HTTP、SSE 与 WebSocket 提供给 Renderer;Web Server 则把同一套路由连接到托管身份、PostgreSQL、对象存储、Redis 和 worker。路由与服务语义可以共同演进,而基础设施故障不会被伪装成相同问题。
Web 与 Desktop 对同一端口给出不同实现
| 平台关注点 | Web | Desktop |
|---|---|---|
| 身份 | 托管会话、用户所有权与角色检查 | 固定 local-user;本地实例授予该用户管理角色 |
| 结构化持久化 | PostgreSQL | 本地 SQLite |
| 文件持久化 | R2/S3 兼容对象存储 | 应用数据目录中的本地文件存储 |
| API | 远程 Server 上的 createApp | 主进程内相同的 createApp,监听 loopback 端口 8003 |
| Agent 执行 | Server 或 worker 中推进,当前使用 Node 沙箱适配器 | 主进程内推进,可进入用户选择的本地项目 |
| 实时路径 | Task 使用 SSE;Chat 使用 WebSocket;配置 Redis 时可跨进程缓冲与分发 | Task 使用 loopback SSE,并由主进程内存缓冲支持重放与续流;Chat 使用 loopback WebSocket |
| MCP | 连接服务器可达的 MCP,并在服务端注入凭证 | 可通过 stdio 启动本机 MCP 子进程 |
| 知识摘要 | 配置 summarizer 时异步生成 | 当前未接 summarizer,由审核者填写 |
Desktop 的 API Key 由 Electron safeStorage 保护后写入本地记录;MCP 环境变量仍属于对应传输配置。Web 的凭证保护则取决于服务端配置与部署存储边界。两端都必须在服务层继续执行所有权检查,不能因为传输位于本机或内网就跳过资源授权。
后台执行复用领域管线,完成确认由宿主负责
Schedule 是平台端口设计的直接例子。ScheduleService 与 createScheduleFireHandler 共同负责读取最新配置、计算下次时间、检查启用状态与配额、选择新任务或续接任务、保存运行记录,并在成功投递后递减 runsRemaining。调度器和实际执行方式由宿主提供。
配置完整的 Web 部署使用 Redis/BullMQ 调度和 worker 执行。运行动作返回 drainPromise,任务事件流结束后,Schedule 运行记录才能从 running 进入 success 或 failed。Desktop 使用主进程的间隔调度器直接启动后台任务,不依赖 Renderer 保持打开。
当前 Desktop 的 Schedule 适配器没有把 runInBackground 的完成 Promise 返回给共享触发管线,因此投递后的运行记录不会立即得到终态;进程重启时,reconcile 会将遗留的 running 记录标记为未完成。这是宿主完成确认尚未对齐的具体边界,而不是共享 Schedule 模型已经覆盖的能力。
平台一致性不等于功能逐项对等
共享层保证同一个 Task、Delivery 或 Knowledge 状态在两个宿主上具有相同含义,但不会消除宿主能力差异:
- Web 未配置 Redis 时,队列、锁和流缓冲中的部分跨实例能力会退化为进程内实现;
- Desktop 的 Task 流恢复覆盖主进程存活期间的 Renderer 断线:刷新、导航或关闭 dock 后,客户端可以重放内存缓冲中的分片并继续接收实时输出;主进程退出后只能从持久化消息恢复。普通 Chat 仍不接受 Web 路径中的 HITL 输出与 resume 消息;
- Prompt 版本与部分偏好管理路由尚未注入 Desktop 的 loopback API;
- Sandbox 类型包含 Daytona 与 E2B 配置,但当前工厂只实现 Node 适配器;
- Web 与 Desktop 的知识摘要和 Schedule 完成确认仍存在前述差异。
这些限制应在 composition root 和产品能力表中显式存在。共享接口的价值是让差异可定位、可替换,而不是在文档中把两个平台描述成完全相同。
实现锚点
| 设计职责 | 实现位置 |
|---|---|
| 共享产品界面与客户端契约 | @aibuddy/app、@aibuddy/common |
| 共享 HTTP 路由装配 | @aibuddy/http 的 createApp |
| Agent、Task、Schedule 与 Delivery 语义 | @aibuddy/core |
| Web 仓储和服务组合 | apps/server/src/service-registry.ts |
| Desktop 服务组合 | registerAllHandlers |
| Desktop loopback API | startLocalApiServer |
| Desktop Task 流恢复 | createDesktopTaskStreamer、createInMemoryStreamBuffer |
| 交付物化 | DeliveryService |
| 平台无关定时触发 | ScheduleService、createScheduleFireHandler |