
Em dash是排版术语中的“破折号”—长度等于一个字母 “m”em的宽度。破折号通常用来引出解释、插入独立的想法或者连接两个相关的子句。在这个项目里它隐喻了产品的核心形态——为主线开发流程“开辟一条支线”独立的 Git worktree让各个 AI Agent 在这些支线里独立、并行地执行任务最后再把结果无缝“连接”回主分支PR/Diff。这个项目是一个本地优先的桌面控制台核心能力源自 README “What You Can Do”同时跑多个 coding agent不用手动切多个终端每个 agent 关在独立 Git worktree 独立分支里隔离把 Linear/GitHub/Jira/GitLab 等的issue/ticket 直接派给某个 agent在一处看 diff、建 PR、查 CI、合并本地或 SSH 远程机器上跑同一套流程用户如何把任务交给 AgentEmdash 把“创建工作区”和“启动 Agent”放在同一个Create Task窗口里但两者不是一回事用户可以只创建一个隔离的 Task之后再启动 Agent也可以同时配置Initial Conversation创建后立即让 Agent 开工。用户需要提供或确认这些信息项目Agent 要修改哪个代码仓库。通常由用户当前所在的项目页面自动带入不必重复选择。Task name这条开发支线的名称。它是创建 Task 的必填项Emdash 可以根据 issue、PR 或随机规则自动生成用户也可以修改。Workspace Settings代码在哪里改。默认是在项目中创建独立 worktree用户也可以选择仓库根目录、已有 workspace、PR 分支或远程 sandbox。创建新 worktree 时还可确认从哪个分支开始是直接 checkout 该分支还是基于它创建新分支新分支名称是否把新分支 push 到远端。Based on可选关联一个 Issue 或 Pull Request。Issue 可以来自 Linear、GitHub、Jira 等集成选中后标题和描述可以用于生成 Task name并作为上下文交给 Agent。Initial Conversation可选Agent选择 Claude Code、Codex、OpenCode 等已安装并被 Emdash 检测到的 provider通常会预选默认 Agent。Prompt描述希望 Agent 做什么例如“修复登录超时并补充回归测试”。可以留空也可以插入 Prompt Library 模板、文件或 issue 上下文。Model如果该 Agent 暴露了模型列表可以指定模型不选则使用 CLI 默认模型。Auto-approve permissions是否自动批准 Agent 的权限请求。Use chat UIAgent 支持 ACP 时可选择结构化聊天界面关闭时走传统 PTY 终端。真正的创建条件只有项目存在、Task name 非空、Workspace 配置合法。Agent 和 Prompt 都不是必填项没有选择 Agent 时Emdash 只创建 Task 和 workspace不创建初始 conversation。点击Create后系统自动完成将 Task、workspace 配置及可选的 initial conversation 写入 SQLite根据 Workspace Settings 创建或复用目录并执行建分支、checkout、创建 worktree 等 Git 操作如果项目本身通过 SSH 连接便在远程机器上准备 workspace本地项目则在本机准备如果配置了 Initial Conversation在准备好的 workspace 路径中启动所选 Agent传统 CLI 走 PTY支持 ACP 且开启 Chat UI 时走 ACP将用户 Prompt 与可选的 issue 上下文发送给 Agent持续接收状态和输出。所以这里的“委派”不是 Agent 自己领取任务也不是一个 Agent 再拆给其他 Agent而是用户明确指定在哪份代码上工作、由哪个 Agent 执行、要它做什么Emdash 负责准备隔离环境、启动进程和汇总结果。痛点与解决的问题emdash 的多 agent 不是为了让 agent 们合力完成一件大事而是为了多路并行押注 隔离防污染。真实痛点在同一个仓库里同时跑多个 CLI agent 会互相覆盖文件、污染分支、终端输出混杂。所以多 agent要解决的是并行提效任务 A 修 bug、任务 B 做 feature同时进行不干扰赛马择优同一个问题让不同 agent/不同方案各跑一版挑最好的 merge核心实现复现思路整体结构远程/本地 workspace-server 守护进程Electron 主进程Preload 独木桥Renderer 浏览器进程wire over socket/stdiochat-ui / ui Reactwire clientcontextBridgerpc.ts controllermain/core 40 域模块SQLite drizzleapi/controlleracp hostptygit/files如果要从零复刻 Emdash其核心思路如下第一步基于 Git Worktree 的物理隔离任务底座思路绝不能让所有 Agent 都在同一个目录下运行。当用户创建一个 Task 时系统应自动为该任务分配一个独立的物理路径。实现在 Electron 主进程Main中操作 Git CLI为项目创建一个隔离的 Worktree。同时在本地 SQLite 数据库中写入tasks和workspaces记录确保任务状态即使应用重启能被持久化恢复。第二步双轨制的 Agent 运行时抽象执行引擎思路需要兼容“传统纯命令行 Agent”和“现代结构化 Agent”。实现均在主进程中运行避免阻塞 UIPTY 模式针对传统 CLI通过node-pty在对应的 Worktree 路径下 spawn 子进程注入环境变量hooks来劫持其行为并处理终端字符流。ACP 模式针对支持 Agent Client Protocol 的现代 Agent通过SessionManager和SessionCell维护强类型的状态机解析意图并管理权限与日志。第三步远程代理workspace-server要解决的场景你的项目代码不在本地 Mac而在一台远程服务器上通过 SSH 连的。桌面 App 想在远程跑 Agent、读文件、执行 git最笨的做法是每个操作都开一条 SSH 命令发过去。但 Agent 干活是高频的读几百个文件、频繁跑 git status每次都新建 SSH 连接、启动进程延迟高、开销大还没法维持长会话状态。Emdash 的做法干脆在远程机器上常驻一个小程序workspace-server一个 Node 守护进程。桌面端只跟这个常驻程序说话由它在远程本地就近执行 git/文件/Agent 操作。通信走Unix Socket本地进程间管道SSH 转发过来而不是每次开新 SSH 命令。它们之间用一套固定格式的协议对话Wire Protocol标了版本号 v2.0.0方便两端升级时对得上。一句话把每次远程喊话变成在远程派个常驻代表你只跟代表沟通代表就近帮你操作文件系统、git 和 Agent 进程。第四步展示层闭环前端 UI要解决的问题前端界面你看到的聊天窗、diff 面板要不要直接碰数据库、文件、子进程Electron 里前端是浏览器环境Renderer 进程。如果让它直接读 SQLite、spawn 进程一旦前端代码有漏洞比如渲染了恶意内容攻击者就直接拿到了你机器的文件和 shell 权限。这是 Electron 安全的大忌。Emdash 的做法前端和后端彻底隔离只留一个细窄的通道。所有底层能力DB、git、进程都关在**主进程Main**里。前端Renderer想要数据必须通过contextBridgePreload 脚本暴露的一座独木桥发强类型 RPC 请求给主进程主进程做完再把结果传回来。前端自己只干两件事渲染 UI展示从数据库拉来的数据比如聚合各任务的 PR 状态、diff、消息做成审查面板。一句话前端只当显示器所有危险操作都托管给主进程中间靠一座受控的独木桥Typed RPC传话。两步都是同一个设计哲学上层永远不直接碰底层中间用一个受控的代理/通道转发。这样既安全第四步又高效第三步。ACP全称Agent Client Protocol代码中对应agentclientprotocol/sdk。ACPAgent Client Protocol的大致内容是什么定位ACP 是专门为了让宿主应用如 Emdash 这种 GUI 控制台、IDE和 AI Agent 之间进行结构化通信而设计的协议。大致内容它定义了一套基于 JSON-RPC或类似结构的双向通信标准。主要包括Session Lifecycle会话生命周期initialize,newSession,loadSession,closeSession。State Transcript状态与日志Agent 通过SessionUpdate如思考中、发送消息、工具调用、计划更新将内部状态流式推给客户端客户端可以渲染出比纯文本终端更丰富的 UI如带有折叠的思维链、清晰的工具调用卡片。Permission Control权限控制Agent 要执行危险命令如写文件、执行 Shell时必须通过requestPermission向客户端申请客户端可以弹出拦截框让用户“允许”或“拒绝”。Host Capabilities宿主能力调用Agent 可以反向调用客户端暴露的接口如readTextFile,writeTextFile,createTerminal等将文件系统操作或终端环境外包给客户端。ACP 是抽象规则还是具体实现它类似 HTTP/IP 协议本身是抽象规则规范定义了消息的 Schema 和交互时序。但它有具体的 SDK 实现在 Emdash 中引用了agentclientprotocol/sdk这个包它提供了 TypeScript 的强类型定义如SessionNotification,RequestPermissionRequest。Emdash 内部有一个专门的acp-agents模块实现了这个协议的客户端Host端点负责把原生的SessionUpdate转换toAgentUpdate成状态机SessionMachine可以消费的统一领域事件NormalizedEvent。ACP 和 A2AAgent-to-Agent流程层面 A2A 确实可以复用 ACP 的骨架分歧不在流程而在几个隐含假设上。ACP 的核心时序其实是一个通用的会话协议initialize能力协商→newSession→prompt发指令→SessionUpdate流式回状态→requestPermission回调审批。把这个套到 A2A 上完全成立一个 orchestrator agent 只要扮演 ACP 里的Client 角色把 sub-agent 当成被调用的 Agentprompt就是子任务SessionUpdate就是子任务进度。所以传输层、会话生命周期、流式回传这套东西是可复用的。但是语义层需要扩展——去掉人在回路的非对称假设补上对等角色、agent 能力发现、远程寻址。所以业界把它们做成两个协议不是因为流程不同而是因为**另一端是不是人这个假设不同**导致权限、能力词汇、发现机制三处必须重做。复用会断掉的 4 个点分歧不在流程而在 ACP 内置了一端是人机 UI另一端是干活的 Agent这个非对称假设角色对称性ACP 里 Client 天然带人类能力弹权限框、渲染 UIAgent 带计算能力。A2A 两端都是 Agent是对等的——ACP 的角色划分在这里失效。权限模型ACP 的requestPermission本质假设背后有个人点允许/拒绝。A2A 里 sub-agent 申请权限时对面是另一个 Agent没有人——这个回调要么退化成自动批准要么得往上层再转发一跳语义变了。能力语义ACP 协商的是面向人的能力readTextFile、terminal、permission UI。A2A 需要的是面向 agent 的能力——任务委派、能力发现“你会干什么”、Agent Card、技能广告、产物artifact交付。这批词汇 ACP 里没有。寻址与发现ACP 是本地的一个 host 通过 stdio spawn 一个 CLI 子进程点对点。Google 的 A2A 强调跨网络、跨厂商发现HTTP Agent Card要解决我怎么找到并信任一个远程 agent这是 ACP 完全没覆盖的层。