本文是「深入 SwiftWork」系列第 0 篇。
前面七篇文章加上番外篇,我们把 Open Agent SDK 的内部机制翻了个底朝天------Agent Loop、工具系统、MCP 集成、多 Agent 协作、会话持久化、多 LLM 支持。番外篇还把 SDK 塞进了一个 macOS 原生应用 Motive 里跑了跑。
但 Motive 只是一个替换后端的实验。真正的问题是:拿到 SDK 之后,怎么从零开始构建一个完整的 Agent 应用? SDK 给了你 Agent 的"大脑"------但用户看到的不是 Agent Loop,而是一个界面。Agent 在调工具、读文件、执行命令的时候,用户需要知道它在干什么、进展如何、结果是什么。
这就是 SwiftWork 要解决的问题------一个 macOS 原生的 Agent 可视化工作台。
SwiftWork 是什么
SwiftWork 是一个 macOS 原生的 AI Agent 桌面应用。它做的事情用一句话概括:让用户看到 Agent 正在做什么。
具体来说:
- 用户在输入框输入 prompt
- Agent 在后台跑 Agent Loop(调工具、读文件、执行命令)
- 每一步执行都实时显示在时间线上------文本输出、工具调用、执行结果、错误信息,全部可视化
这不是一个终端里的 CLI 工具,也不是一个网页应用。它是用 SwiftUI 写的原生 macOS 应用,用 @Observable 做状态管理,用 SwiftData 做数据持久化,用 Apple 原生的渲染管线做 Markdown 和代码高亮。
为什么做 SwiftWork
做 SwiftWork 有两个动机。
第一,SDK 需要一个"展示应用"。 SDK 的 31 个示例项目覆盖了各种用法------流式输出、自定义工具、MCP 集成------但都是命令行工具。SDK 的能力需要一个 GUI 来完整展示,尤其是工具调用的可视化、事件流的实时渲染这些 CLI 做不好的事情。
第二,Agent 应用的可视化是一个被低估的问题。 当前的 Agent 应用(包括 Claude Code 自己)在终端里跑,用户看到的是滚动的文字流。但 Agent 在执行一个复杂任务时,可能调用十几次工具、读写多个文件、执行多条命令。终端里的线性输出很难让用户理解全局进展。SwiftWork 试图用事件时间线和工具卡片来改善这个问题。
架构总览
SwiftWork 采用事件驱动架构。整条数据流是一条单向管线:
SDK Agent Loop
│
│ AsyncStream<SDKMessage>
▼
AgentBridge (@Observable)
│
│ EventMapper.map() → AgentEvent
▼
AgentBridge.events: [AgentEvent]
│
│ SwiftUI 自动响应 @Observable 变化
▼
TimelineView → 各 EventView
四个角色:
| 组件 | 职责 |
|---|---|
| Agent Loop | SDK 提供,跑 Agent 的推理循环,产出 SDKMessage 流 |
| AgentBridge | 消费 AsyncStream<SDKMessage>,映射成 AgentEvent,管理生命周期 |
| EventMapper | 纯函数,SDKMessage → AgentEvent 的类型映射 |
| TimelineView | SwiftUI 视图,消费 AgentEvent 数组,渲染时间线 |
核心设计决策:视图不直接接触 SDK 类型。 AgentEvent 是 SwiftWork 自己定义的 UI 模型,跟 SDK 的 SDKMessage 完全解耦。视图只认 AgentEvent,不知道也不关心事件来自哪个 SDK 版本。
核心数据流
用户发一条消息,整条管线的运转过程:
1. 用户输入 → Agent 启动
swift
// AgentBridge.swift
func sendMessage(_ text: String) {
// 用户消息直接追加到事件列表
let userEvent = AgentEvent(type: .userMessage, content: text, timestamp: .now)
appendAndPersist(userEvent)
isRunning = true
// 在后台 Task 中消费 stream
currentTask = Task { [weak self] in
let stream = agent.stream(text)
for await message in stream {
let event = EventMapper.map(message)
self.appendAndPersist(event)
}
self.isRunning = false
}
}
用户消息先追加到事件列表(即时显示),然后启动一个 Task 来消费 SDK 的 AsyncStream。
2. SDKMessage → AgentEvent
EventMapper 是一个纯函数,把 SDK 的 18 种 SDKMessage 映射成 SwiftWork 的 AgentEventType:
swift
// EventMapper.swift
static func map(_ message: SDKMessage) -> AgentEvent {
switch message {
case .assistant(let data):
return AgentEvent(type: .assistant, content: data.text,
metadata: ["model": data.model, "stopReason": data.stopReason], timestamp: .now)
case .toolUse(let data):
return AgentEvent(type: .toolUse, content: data.toolName,
metadata: ["toolName": data.toolName, "toolUseId": data.toolUseId, "input": data.input],
timestamp: .now)
case .toolResult(let data):
return AgentEvent(type: .toolResult, content: data.content,
metadata: ["toolUseId": data.toolUseId, "isError": data.isError], timestamp: .now)
// ... 18 种消息类型
}
}
为什么要有这一层映射?因为 SDK 的类型是给 Agent 运行时用的,它包含很多 UI 不需要的细节。AgentEvent 只保留 UI 渲染需要的字段:类型、内容、元数据、时间戳。视图不需要知道 SDKMessage 的枚举定义,只需要处理 AgentEventType。
3. 事件追加 + 持久化
每条事件都经过 appendAndPersist,同时更新内存数组和 SwiftData 数据库:
swift
private func appendAndPersist(_ event: AgentEvent) {
events.append(event)
processToolContentMap(for: event)
guard event.type != .partialMessage,
let eventStore, let currentSession else { return }
try eventStore.persist(event, session: currentSession, order: eventOrder)
eventOrder += 1
trimOldEvents()
}
注意 partialMessage 不持久化------它是流式文本的中间片段,累积完成后会生成一条完整的 .assistant 事件。
4. SwiftUI 自动渲染
AgentBridge 标记了 @Observable。当 events 数组变化时,TimelineView 自动重新渲染:
swift
// TimelineView.swift
ForEach(virtualizedEvents) { event in
eventView(for: event)
}
eventView 根据 event.type 分派到不同的视图组件------UserMessageView、AssistantMessageView、ToolCardView、SystemEventView 等。
项目结构
SwiftWork/
├── App/
│ ├── SwiftWorkApp.swift # @main 入口,注册 SwiftData 模型
│ └── ContentView.swift # NavigationSplitView 根视图
├── Models/
│ ├── UI/ # UI 模型层
│ │ ├── AgentEvent.swift # 事件模型(SwiftUI 渲染用)
│ │ ├── AgentEventType.swift # 18 种事件类型枚举
│ │ ├── ToolContent.swift # 工具内容(配对 toolUse + toolResult)
│ │ ├── PermissionDecision.swift # 权限决策
│ │ └── AppError.swift # 错误模型
│ └── SwiftData/ # 持久化模型层
│ ├── Session.swift # 会话
│ ├── Event.swift # 持久化事件
│ ├── AppConfiguration.swift # 应用配置
│ └── PermissionRule.swift # 权限规则
├── ViewModels/
│ ├── SessionViewModel.swift # 会话 CRUD
│ └── SettingsViewModel.swift # 设置管理
├── Views/
│ ├── Sidebar/ # 会话列表
│ ├── Workspace/
│ │ ├── Timeline/
│ │ │ ├── TimelineView.swift # 时间线主视图 + 虚拟化
│ │ │ ├── EventViews/ # 各事件类型视图 + ToolCardView
│ │ │ │ ├── ToolRenderers/ # 5 个内置工具渲染器
│ │ │ │ ├── StreamingTextView.swift
│ │ │ │ ├── MarkdownContentView.swift
│ │ │ │ └── ...
│ │ │ └── Inspector/ # 事件详情面板
│ │ └── InputBar/ # 消息输入框
│ ├── Settings/ # 设置界面
│ ├── Onboarding/ # 首次启动引导
│ └── Permission/ # 权限审批弹窗
├── SDKIntegration/
│ ├── AgentBridge.swift # SDK ↔ ViewModel 桥接
│ ├── AgentBridge+ToolContentMap.swift # 工具内容配对逻辑
│ ├── EventMapper.swift # SDKMessage → AgentEvent
│ ├── ToolRenderable.swift # 工具渲染协议
│ └── ToolRendererRegistry.swift # 工具渲染注册表
├── Services/
│ ├── CodeHighlighter.swift # Splash 代码高亮
│ ├── MarkdownRenderer.swift # swift-markdown 渲染
│ ├── KeychainManager.swift # API Key 安全存储
│ ├── EventStore.swift # 事件持久化接口
│ ├── AppStateManager.swift # 应用状态保存/恢复
│ └── TitleGenerator.swift # 自动生成会话标题
└── Utils/
└── Extensions/ # 颜色、日期格式化等
结构上的核心分层:
- Models/UI/ 和 Models/SwiftData/ 是两套独立的模型层。UI 模型(
AgentEvent)是给 SwiftUI 渲染用的,SwiftData 模型(Event)是给持久化用的。两者之间有转换逻辑。 - SDKIntegration/ 是 SDK 和 UI 之间的桥梁层。视图和 ViewModel 不直接 import
OpenAgentSDK。 - Views/ 按功能分区,每个事件类型一个视图文件,工具渲染器有独立的子目录。
技术选型
| 组件 | 选择 | 原因 |
|---|---|---|
| 语言 | Swift 6.1 严格并发 | Agent SDK 要求,Sendable 保证线程安全 |
| UI | SwiftUI + @Observable |
macOS 14+ 支持,跟 Swift 并发配合好 |
| 持久化 | SwiftData | 跟 SwiftUI 深度集成,比 Core Data 简洁 |
| Markdown | swift-markdown (Apple) | 原生 Apple 库,CommonMark 兼容 |
| 代码高亮 | Splash (John Sundell) | 轻量、支持 Swift/Python/JS/Bash |
| 自动更新 | Sparkle 2.x | macOS 应用更新的标准方案 |
| Agent SDK | Open Agent SDK | 自己写的 SDK,当然用自己的 |
系列预告
这篇文章给了一个全景图。接下来的文章会逐层拆开,看每个子系统怎么实现:
- 第 1 篇 :SDK 集成层------AgentBridge 如何消费
AsyncStream<SDKMessage>、映射事件、管理生命周期 - 第 2 篇:事件时间线------18 种事件的可视化、流式文本、虚拟化
- 第 3 篇 :Tool Card 系统------
ToolRenderable协议和可扩展的工具渲染器 - 第 4 篇:数据层与服务------SwiftData 持久化、状态恢复、Markdown 渲染、代码高亮
相关链接:
- SwiftWork :terryso/SwiftWork
- Open Agent SDK :terryso/open-agent-sdk-swift