把开发环境装进口袋 — 点下"运行"后到底发生了什么
Paseo 让你从手机、终端或桌面监控和操控运行在机器上的 AI 编程助手。你的代码永远不会离开你的机器。它是本地优先的。
随时随地实时查看 Agent 运行状态 — 公交上、咖啡厅、沙发上。
类似 Docker 的终端命令,用于创建、列出和等待 Agent。
Electron 封装,内置守护进程 — 一键启动。
Paseo 本身不运行 AI。它管理你已经使用的 AI 编程工具 — 把它想象成 AI Agent 的万能遥控器。
Anthropic 的 Agent SDK — 调用 Claude 模型,在终端中运行
OpenAI 的编程 Agent — 作为独立的服务进程运行
开源 AI 编程 Agent — 轻量级、基于命令行
Paseo 是一个教科书级别的客户端-服务器系统,具备实时流式传输。理解它的工作原理,你就学到了 Slack、Discord 以及每个实时更新仪表盘都在使用的架构模式。
六个包各自的角色 — 像一支劫案团队,各有专长
守护进程是 Paseo 的大脑。把它想象成守护进程 — 它始终在线,监控着你的 Agent。它监听 WebSocket 连接、生成 Agent 进程,并实时流式输出结果。
const beginShutdown = (signal: string) => {
if (!shutdownPromise) {
logger.info(`${signal} received, shutting down...`);
shutdownPromise = (async () => {
const forceExit = setTimeout(() => {
logger.warn("Forcing shutdown — didn't close in time");
process.exit(1);
}, 10000);
await daemon.stop();
clearTimeout(forceExit);
})();
}
};
当操作系统发送"停止"信号(比如 Ctrl+C)时...
确保只处理一次关闭,即使收到多个信号也不重复...
设置 10 秒定时器 — 如果清理超时,强制退出...
优雅地停止守护进程,然后取消强制退出定时器。
"带超时的优雅关闭"模式在生产软件中无处不在。就像搬家合同:"我给你 10 分钟打包,到时间卡车就开走。"这能防止进程永远挂着不退出。
移动端 App 使用 Expo(React Native)。它通过 WebSocket 连接守护进程,使用 xterm.js 渲染 Agent 输出。
// 直连 TCP
return new DaemonClient({
...base,
url: buildDaemonWebSocketUrl(connection.endpoint),
});
// 中继连接(加密隧道)
return new DaemonClient({
...base,
url: buildRelayWebSocketUrl({
endpoint,
enabled: true,
daemonPublicKeyB64: connection.daemonPublicKeyB64,
}),
});
在同一网络时,直连守护进程的 WebSocket 地址...
远程时,通过加密中继隧道连接...
无论哪种方式,App 获得的都是同一个 DaemonClient — 传输层可互换。
Commander.js 命令:run、ls、logs、wait、daemon start/stop。与 App 使用相同的 WebSocket协议。
零知识加密桥梁。中继服务器只能看到加密后的字节流 — 无法读取你的数据。
Electron 封装。将守护进程作为子进程启动,无需手动操作。
从"创建"到"空闲" — 管理 Agent 的状态机
Agent 就像一个可以在四种状态之间切换的工人。守护进程用状态机来追踪 — 这个概念从红绿灯到游戏角色无处不在。
守护进程收到了你的请求,正在和 Provider 建立 Agent 会话。
Agent 就绪,等待任务。就像出租车在站点等客。
Agent 正在工作 — 读取文件、编辑代码、运行命令。
出了问题,或者 Agent 完成任务后被关闭。
运行结束后 → Agent 回到 idle(而不是关闭)。这就是循环:idle → running → idle。Agent 保持存活,等待你的下一条指令。就像私人助理完成一项任务后,等着下一项。
async createAgent(
config: AgentSessionConfig,
agentId?: string,
): Promise<ManagedAgent> {
const injectedConfig = this.mcpBaseUrl == null
? config
: {
...config,
mcpServers: {
paseo: {
type: "http" as const,
url: `${this.mcpBaseUrl}?callerAgentId=${resolvedAgentId}`,
},
...(config.mcpServers ?? {}),
},
};
const session = await client.createSession(injectedConfig);
return this.registerSession(session, injectedConfig, resolvedAgentId);
}
如果配置了 MCP 服务器,就把它注入 Agent 的配置中,让 Agent 能和其他 Agent 通信...
让 Provider(Claude、Codex 等)用这个配置创建一个新会话...
在我们的追踪器中注册这个会话,以便监控它、流式输出并管理生命周期。
当你创建一个 Agent 时,这些组件在幕后互相通信:
WebSocket 协议 — 一种语言,三个客户端
在实际工作开始之前,客户端和守护进程会先进行一次"握手" — 就像两人见面先交换名片再开始交谈。
if (message.type === "hello") {
this.handleHello({
ws,
message,
pending: pendingConnection,
});
return;
}
if (message.type === "ping") {
this.sendToClient(ws, { type: "pong" });
return;
}
如果是 "hello" 消息(首次联系),执行握手逻辑 — 验证版本、创建会话、返回 "welcome"...
如果是 "ping"(心跳检测),立即回复 "pong" — 证明连接仍然存活。
每个 WebSocket 连接都会定期发送 ping。如果对方停止回复,你就知道连接断开了 — 就像打电话没人接。这就是 Paseo 如何知道在手机上显示"重新连接中..."的原理。
Paseo 在同一个连接上发送两种数据:控制消息(JSON)和终端数据(原始字节)。就像电视频道同时传输视频信号和字幕 — 同一根线,不同内容。
export function encodeTerminalStreamFrame(input: {
opcode: TerminalStreamOpcode;
slot: number;
payload?: Uint8Array;
}): Uint8Array {
const payload = input.payload ?? new Uint8Array(0);
const bytes = new Uint8Array(2 + payload.byteLength);
bytes[0] = input.opcode; // What kind of data?
bytes[1] = input.slot; // Which terminal?
bytes.set(payload, 2); // The actual data
return bytes;
}
每个二进制帧以 2 个字节的头部开始...
第一个字节:这是什么类型的数据?(输入、输出、调整大小...)
第二个字节:属于哪个终端槽位?(你可能同时有多个 Agent)
之后的所有内容:原始终端字节 — 就是你终端窗口里看到的东西。
点击步骤,看一条消息如何从 Claude 的"大脑"传到你的手机屏幕:
完整旅程:从手机点击到代码写入你的机器
"修复登录 Bug" — 从手机、CLI 或桌面端。
守护进程解包你的消息,告诉管理器创建/启动 Agent。
Claude/Codex/OpenCode 作为真正的进程在你的机器上运行。
每个操作(读取文件、编辑代码、Shell 命令)都变成时间线事件。
不同 Provider 格式不同 — Paseo 将它们统一映射到一种通用格式。
如果 Agent 想执行危险操作,你的手机会弹出确认提示。
手机、桌面、CLI — 所有设备同时看到相同输出。
当 Claude 想做可能有风险的事情(删除文件、运行 Shell 命令)时,它会征求你的许可。Paseo 通过守护进程将请求路由到你正在使用的客户端。
export function computeShouldNotifyClient({
clientState,
allClientStates,
agentId,
}): boolean {
if (hasActiveClientOnAgent(allClientStates, agentId)) {
return false;
}
if (!clientState.isStale && clientState.appVisible) {
return true;
}
if (clientState.deviceType === "mobile") {
return !hasActiveWebClient(allClientStates);
}
return true;
}
如果有人已经在其他屏幕上查看这个 Agent,就不要重复通知...
如果这个客户端的 App 正在打开且可见,直接通知...
在移动端,只有当没有 Web 客户端活跃时才发送通知(避免重复提醒)...
拿不准就通知 — 权限请求宁可多提醒,不可漏掉。
这是一个真实的生产问题:如何通知用户又不刷屏?Paseo 会先检查是否有其他客户端正在显示该 Agent、App 是否可见、是移动端还是 Web — 然后才决定是否发送通知。这种"先检查再通知"的模式,避免了手机、桌面和 Web 同时弹出同一条提醒的烦人场景。
Paseo 如何让你从任何地方控制 Agent — 无需打开防火墙
你的守护进程运行在家里的电脑上。你的手机在咖啡厅。你家网络有防火墙。它们怎么通信?
答案:中继服务器 — 一个你的守护进程和手机都能访问的公共中间人。但你怎么信任中继服务器不偷看你的数据?
你不需要信任它。这就是巧妙之处:端到端加密。中继只能看到乱码。
想象两个间谍在酒店大堂接头。他们素未谋面,但需要共享一个秘密。方法是 — 使用一种叫做ECDH 密钥交换的技术。
export function encrypt(
sharedKey: SharedKey,
data: string | ArrayBuffer,
): ArrayBuffer {
const nonce = nacl.randomBytes(NONCE_LENGTH);
const plaintext = toUint8(data);
const ciphertext = nacl.box.after(
plaintext, nonce, sharedKey
);
const out = new Uint8Array(
nonce.byteLength + ciphertext.byteLength
);
out.set(nonce, 0);
out.set(ciphertext, nonce.byteLength);
return toArrayBuffer(out);
}
生成随机 "nonce" — 一个一次性随机数,防止重放攻击(就像每封信上的唯一时间戳)...
将数据转为原始字节...
用共享密钥和 nonce 加密 — 魔法就在这里。XSalsa20-Poly1305 是加密算法...
打包:[nonce(24 字节)] + [加密数据]。nonce 随消息一起发送,接收方用它解密。
加密最难的部分是:对方怎么拿到你的公钥?Paseo 用二维码解决这个问题。你扫描守护进程终端输出的二维码到手机上。
中继服务器是零知识的。它在守护进程和手机之间转发加密字节,但无法解密。中继没有共享密钥 — 只有你的守护进程和手机有。即使中继服务器被攻破,你的数据也是安全的。Signal 和 WhatsApp 用的是同一个原理。
export async function createClientChannel(
transport: Transport,
daemonPublicKeyB64: string,
): Promise<EncryptedChannel> {
const keyPair = generateKeyPair();
const daemonPubKey = importPublicKey(daemonPublicKeyB64);
const sharedKey = deriveSharedKey(
keyPair.secretKey, daemonPubKey
);
const channel = new EncryptedChannel(transport, sharedKey);
const hello: E2EEHelloMessage = {
type: "e2ee_hello",
key: exportPublicKey(keyPair.publicKey),
};
transport.send(JSON.stringify(hello));
return channel;
}
为这次连接生成一个全新的密钥对...
加载守护进程的公钥(从二维码获取的)...
数学魔法:你的私钥 + 对方的公钥 → 只有你们俩知道的共享密钥...
用这个共享密钥创建加密通道...
把你的公钥发给守护进程,让它推导出相同的共享密钥...
完成!现在所有消息都加密了。中继无法读取。
Paseo 展示了几乎每个生产系统都会用到的六个模式:
客户端-服务器架构 — 中央管理器(守护进程)服务多个 UI
WebSocket 流式传输 — 实时数据推送给所有已连接的客户端
状态机 — 安全地管理生命周期转换
二进制多路复用 — 在一个连接上传输多种数据类型
智能通知路由 — 在多设备间避免重复通知
端到端加密 — 信任通道而不信任中间人
现在你可以阅读 Paseo 的源代码,也有足够的词汇来指导 AI 编程工具构建类似的系统。当你说"用 WebSocket 做实时流式传输"或"加 ECDH 密钥交换实现端到端加密"时,你会清楚地知道自己要什么 — 以及在结果中应该检查什么。