目标:把 AgentPulse 从"私人工具"推进到"可分发、可信任、可维护"的产品形态,同时加固我们相对 Happy/HAPI 的差异化护城河(macOS 桌面 app observer + 自定义 agent + iOS 原生体验)。
一句话策略:先堵上安全/工程两个硬伤(P0),再夯实当前已有功能的可靠性(P1),最后做平台扩张和深度差异化(P2+)。不要在基础不稳的时候追求新功能。
没有这些,项目无法分发给任何非本人的用户。
现状:无 LICENSE 文件,任何人法律上都不能使用我们的代码。
做什么:顶层加 LICENSE(推荐 MIT,和 Happy 一致;如果想学 HAPI 限制闭源商用则用 AGPL-3.0)。README 声明 license。
验证:GitHub repo page 自动识别 license。
现状:relay 监听 0.0.0.0:8350,WS 连上就能 sendInput 控制用户的 Claude/Codex。任何同网络的设备都能打。
做什么:
- relay 启动时生成
~/.agentpulse/pairing-secret(UUID),CLI 同时输出 QR code - iOS 扫 QR → 解析为
agentpulse://pair?host=192.168.x.x&port=8350&secret=xxx - WS 握手 + REST 请求强制带
Authorization: Bearer <secret>;不通过的拒绝 - mDNS 只广播 host/port,不广播 secret
验证:
- 删除 secret 后 iOS 显示"未配对"而非默默连不上
- 旁路设备
curl http://host:8350/api/v1/status返回 401
关键文件:
- 新
relay-server/src/auth/pairing.ts(生成 + 校验 secret) - 新
relay-server/src/auth/ws-auth.ts插在setupWebSocket前 AgentPulse/Views/SettingsView.swift加"扫码配对"按钮ConnectionManager.swift握手时发Authorizationheader
现状:无 .github/workflows/,代码改了不知道编不编得过。
做什么:
.github/workflows/ci.yml:pnpm install →npx tsc --noEmit(relay + bridge) → SwiftLint(iOS)- PR 必须过才能 merge(Branch Protection) 验证:故意提一个语法错误的 PR,CI 报红。
现状:零测试。Happy 93、HAPI 102。 做什么:用 Vitest(和 tsx 兼容),对最易回归的 3 块先写测试:
relay-server/src/utils/session-reader.test.ts:解析 Claude JSONL(fixture 来自真实 session 片段),覆盖 Thinking / ToolUse / UserPrompt / EndTurn 四种relay-server/src/utils/codex-session-reader.test.ts:Codex JSONL 解析relay-server/src/utils/approval.test.ts:input delivery chain 的 fallback 顺序(mock sub-deps)
目标:30 个测试起步,CI 跑 < 30s。后面每修一个 bug 加一个对应的回归测试("fix 驱动测试")。
现状:approval.ts:248 把用户输入拼进 osascript 和 shell 命令。即使用 JSON.stringify/replace escape 了,走的路径多,易漏。
做什么:
- 列出所有走
execAsync的用户输入入口,统一过一个escapeForShell()/escapeForAppleScript()helper - 加一个 test fixture:输入
"; rm -rf ~/ "、$(whoami)、``` ` `` 等常见 payload,确保不被命令注入 验证:renamed test 通过,加到 CI。
修现在还在犯的错、补之前遗留的坑。
现状:SSH 隧道通了,但 relay 不会去远端 fetch agent 列表。iOS 上点远程 session 直接弹回主页(因为 connectionManager.agents 里没有)。
做什么:
- relay 启动时读
~/.agentpulse/remote-hosts.json,对每个connectedhost 起一个轮询 task(3-5s) - 轮询
http://localhost:<tunnelPort>/api/v1/status,把 agents 加remote-{hostId}-前缀 merge 进本地agentStore - host 断开/删除时清理对应 agents
- iOS
AgentListView增加 "Remote" section 分组显示
验证:
- 开发机跑 relay → 本机 relay 自动同步 → iOS 看到
remote-xxx-claude-code-123 - 远端 relay 挂掉 → iOS 5s 内看到 agent 消失
- 点击远程 agent 能进入对话界面(不再 boomerang)
关键文件:
- 新
relay-server/src/remote/remote-sync.ts relay-server/src/index.ts里在autoConnectHosts()后挂钩ConnectionManager.swift的 snapshot 处理不需要改——只要 agent 在 store 里就行
现状:AgentListView.swift:57-70 sheet binding 在 agents.first { $0.agentId == id } 返回 nil 时自动关闭,用户体验是"点了没反应"。
做什么:
- 绑定 fallback 到 placeholder
AgentStatus(status: .completed, agentName: "Session no longer available"),显示友好提示 + "Dismiss" 按钮 - 保留最近 5 个已消失 agent 的 stub 30s,让进入聊天界面看历史消息
验证:关掉一个 Claude 的 Terminal → iOS 马上点它能看到"session 已结束,这是历史对话"而不是回主页。
现状:websocket.ts:82 成功就广播 "Input delivered via X",失败只抛一个干巴巴的 error。iOS 绿对勾 ≠ 真送达,已经坑到用户两次。 做什么:
- sendInput 返回扩展到
{ method, reliable: boolean, warning?: string } reliable=false的情况(osascript-paste 无法验证焦点、bridge 无 ACK)在 iOS 显示橙色叹号 + 悬浮提示"本次通过 osascript 发送,若未收到请手动激活对应窗口后重试"- 已经有
isUncertainDelivery()在 AgentChatView.swift:334,扩展它
验证:关掉 Codex Desktop 发一条消息 → iOS 显示橙警告而不是绿对勾。
现状:每个 VS Code 窗口一个 extension host,8348 只有第一个抢到;其他窗口的 session 通过 AXRaise 能绕过来,但如果目标 session 不在监听 bridge 那个窗口里,还是会失败。 做什么:
- 每个 bridge 端口递增尝试:8348, 8349, 8350...(最多 20 个)
- bridge 启动时往
~/.agentpulse/bridges.json写{port, workspace, pid, windowTitle} - relay sendInput 时根据 session 的 cwd 匹配 bridges,POST 到正确端口
- bridge 退出时 unregister
验证:开 3 个 VS Code 窗口,每个有 Claude session,iOS 分别发消息,每条都进对应 session。
现状:SQLite 扫 bubbleId 工作正常,但 Cursor 重启后 DB WAL 状态、用户切 workspace、多 Cursor 窗口都可能漏。 做什么:
- 测试 workspace 切换时的行为(希望:老 workspace 的 bubble 继续被读,新 workspace 启动后也开始读)
- 多 Cursor 窗口场景下 pane 归属(iOS 上哪个
cursor-app-*收到回复)
验证:操作上述两种场景 30 分钟,回复不丢、不串到错误 agent。
现状:Xcode 提示 webSocketTask 不 Sendable。当前是 warning 但 Swift 6 会变 error。
做什么:把 WebSocketClient 改成 actor,或者 @unchecked Sendable 且显式加锁。前者更正确。
验证:切到 Swift 6 模式编译通过。
让改代码不再提心吊胆。
- 单元(Vitest):utility 函数、parser、delivery chain 逻辑。目标 200+ 测试。
- 集成(新):用
supertest启动整个 express + WS,mock 掉 execAsync/fs,跑端到端的 "iOS → relay → response" 场景。 - Swift 单元:XCTest 跑
ChatMessage过滤、ConnectionManager的消息路由逻辑(不需要真连 WS)。
现状:console.log 散落 300+ 处,字符串拼接。调试全靠 grep。
做什么:
- 换
pino(快),LOG_LEVEL环境变量控制 - 关键事件(agent 发现、输入投递、WS 连断)走结构化 log:
logger.info({ event: 'input-delivered', agentId, method, latencyMs }) - 旧
console.log保留但标注废弃
现状:tools/simulate-ios-app.ts 存在但用法不明。
做什么:加一个 npm run simulate 打开 CLI,可以交互式:
- 列出 relay 检测到的 agents
- 向某 agent 发消息
- 模拟 approve/deny 这让我们不用装 Xcode 就能回归测试 relay。
阶段 0 的 token 认证是及格线,这里做到"可信"。
场景:用户通过 cloudflared 把 relay 暴露公网,或者在不信任的 WiFi 用 SSH 隧道。 做什么:
- 配对时除了 secret,再生成一个 ECDH 共享密钥(Curve25519)
- 所有
input/approve/focuspayload 用 AES-256-GCM 加密 - relay 在本机解密,但如果走中转隧道,隧道看不到明文
- 参考 Happy 的
docs/encryption.md实现
非目标:不做 zero-knowledge server(我们没有 server)。
现状:/tmp/agentpulse-{uid}.sock 用 uid 隔离,但 tmp 路径本身是广开放的。
做什么:move 到 ~/.agentpulse/run/hook.sock,显式 chmod 600。
做什么:所有 iOS 发起的写操作(sendInput、approve、interrupt)写到 ~/.agentpulse/audit.log(append-only),包含时间戳 + agentId + 操作类型 + 来源 IP。便于事后溯源。
为什么:一次开发覆盖 Android、macOS 浏览器、Windows、Linux。Cost < Native Android。
技术栈:React 19 + Vite + Tailwind(和 HAPI 的 web/ 类似),Service Worker 做离线缓存。
做什么:
- 新增
web/workspace - 复用 relay 现有的 REST/WS API,不改 protocol
- 核心页面:Agent List、Chat、Settings(配对通过手动输入 host + secret,没有 QR 扫描也 OK)
- 打包为 PWA(manifest.json + SW),可"Add to Home Screen"
- Relay 增加
GET /托管 web bundle(可选)
验证:在 Chrome 开 localhost:8350 能用,Android 上能 Install to Home Screen 当原生 app 用。
做什么:
- Web Push(VAPID keys + service worker subscription),接入 relay
- 触发场景:agent 进入
waitingForApproval/waitingForInput/ 完成一个长任务
做什么:
- Apple Developer Program 账号($99/yr)
- GitHub Actions 自动 build + upload
- 避免要求用户本机 Xcode
我们已经在独占跑道上,跑得更远。
现状:已有 Claude Code、Codex Desktop、ChatGPT Atlas、ChatGPT Desktop、Claude Desktop、Cursor。 做什么:
- Windsurf(Codeium 的 IDE)—— Electron-based,大概率和 Cursor 类似套路
- Zed —— 有 AI 助手,SQLite 存,查一下
- Jupyter + Copilot Chat —— 想都别想,可能需要 iframe AX 魔法
- 每个新 app 开一个 adapter 文件 + detection 规则
现状:custom-cli-adapter.ts 只能正则匹配进程名。
做什么:用户能写 JS 插件注册 agent,暴露钩子:
// ~/.agentpulse/plugins/my-agent.js
module.exports = {
name: 'my-agent',
detectProcess: (proc) => proc.command.includes('my-cli'),
readOutput: async (pid) => { /* read some file */ return messages },
sendInput: async (pid, text) => { /* write to some pipe */ }
}风险:代码执行不受 sandbox,需警告用户。
现状:AgentStatus 是扁平结构,不同 agent 的工具调用/thinking/approval 复用度低。
做什么:定义 v2 protocol agent:tool-call-start / agent:stream-delta / agent:turn-end 等事件类型,向后兼容 v1。
收益:Codex、Cursor、Gemini 的回复 UI 可以共用;将来支持更多 agent 时添加简单。
做什么:当 agent 运行在 cmux/tmux 中,web 客户端可以直接嵌入 pty 流,用户能看到终端完整输出。
复用:HAPI 的 hub/src/terminal/ 是好参考。
做什么:iOS/Web 按住麦克风 → 转文字 → 作为 input 发送。ElevenLabs 质量高但付费,Whisper.cpp 本地但模型大。 优先级:在用户明确表达需求前先放一边。
一些场景 observer 做不到,提供可选 wrapper 增强。
做什么:类似 happy/hapi 的做法,但是可选。用户可以继续裸跑 claude(observer 模式),或者 agentpulse claude(wrapper 模式获得 SDK 级可靠发送)。
收益:
- osascript 失败场景的硬退路
- Accessibility 权限不需要了
- pty stream 可以直接广播给 web 终端
做什么:把每个 session 的 JSONL 索引到本地 SQLite FTS5,iOS/Web 可以全文搜"上次改 auth 相关是哪个 session"。 成本:中,但 Happy 都没做好,是差异化机会。
| 阶段 | 时间 | 核心产出 | 可分发? |
|---|---|---|---|
| 0 基础 | 2-3w | LICENSE、配对认证、CI、初始测试 | |
| 1 稳定性 | 3-4w | 远程同步、错误透传、bridge 多窗口 | ✓ alpha 用户 |
| 2 测试/观测 | 2w | 测试完善、日志、simulator | ✓ 内部闭合 |
| 3 安全 | 3-4w | E2EE、audit log | ✓ 公开可用 |
| 4 平台扩张 | 4-6w | PWA、TestFlight | ✓ 广泛分发 |
| 5 差异化 | 4-6w | 更多桌面 app、custom plugin、v2 protocol | ✓ 有护城河 |
| 6 可选 | — | wrapper 模式、搜索、语音 | 按需 |
总计:~5-6 个月到"公开可分发 + 差异化"。
- 不做中央 server(Happy 的路):违背"本机优先"定位,维护成本高
- 不抢 CLI wrapper 赛道(HAPI 的路):他们先发,我们的 observer 是互补品而非替代
- 不移植 Electron / Tauri 桌面端:用户已有 iOS+PWA 就够覆盖,成本不值
- 不做社交/分享功能:和产品定位无关
- 每周一:从 plan 挑 2-3 个 issue,开 GitHub issue 追踪
- 每个 merge 必带:(a) 测试 (b) 日志 (c) 文档更新
- 每月一次:回顾 plan,调整优先级
- ✅ Codex Desktop UI scraper(AX tree walker 带错误恢复)
- ✅ Cursor 回复读取(SQLite bubble tail + 去重)
- ✅ iOS chat view 自动激活 native app(focus WS 消息)
- ✅ Bridge 多窗口初步修复(AXRaise workspace 匹配)
- ✅ Budget UI(手动设置 + 24h/7d 图表修复)
- ✅ Tmux session 名打标(
[tmux:wjy]前缀) - ✅ Detached tmux 消息投递(tmux send-keys 代替 TTY write)
这些都是 plan 要巩固的基础,下一步应该给每一项加上自动化测试,固化回归能力。