01

Paseo 是什么?

把开发环境装进口袋 — 点下"运行"后到底发生了什么

一句话介绍

Paseo 让你从手机、终端或桌面监控和操控运行在机器上的 AI 编程助手。你的代码永远不会离开你的机器。它是本地优先的。

📱

移动端 App

随时随地实时查看 Agent 运行状态 — 公交上、咖啡厅、沙发上。

⌨️

CLI

类似 Docker 的终端命令,用于创建、列出和等待 Agent。

🖥️

桌面端

Electron 封装,内置守护进程 — 一键启动。

Agent 都是谁?

Paseo 本身不运行 AI。它管理你已经使用的 AI 编程工具 — 把它想象成 AI Agent 的万能遥控器。

C
Claude Code

Anthropic 的 Agent SDK — 调用 Claude 模型,在终端中运行

C
Codex

OpenAI 的编程 Agent — 作为独立的服务进程运行

O
OpenCode

开源 AI 编程 Agent — 轻量级、基于命令行

全局视野

💡
为什么要关心这个?

Paseo 是一个教科书级别的客户端-服务器系统,具备实时流式传输。理解它的工作原理,你就学到了 Slack、Discord 以及每个实时更新仪表盘都在使用的架构模式。

packages/整个系统,组织为 monorepo
server/守护进程 — 管理 Agent 进程,流式输出
app/移动端 + Web 客户端(Expo / React Native)
cli/类似 Docker 的终端命令
relay/加密隧道,用于远程访问
desktop/Electron 封装 — 内置守护进程
website/官网 paseo.sh

快速测验

你在手机上想看 AI Agent 正在做什么。Paseo 的哪个包负责移动端 UI?

朋友说:"我想做一个 App,让用户从手机控制后台进程。"他应该研究 Paseo 的什么模式?

02

认识主角团

六个包各自的角色 — 像一支劫案团队,各有专长

守护进程 (packages/server)

守护进程是 Paseo 的大脑。把它想象成守护进程 — 它始终在线,监控着你的 Agent。它监听 WebSocket 连接、生成 Agent 进程,并实时流式输出结果。

CODE
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 (packages/app)

移动端 App 使用 ExpoReact Native)。它通过 WebSocket 连接守护进程,使用 xterm.js 渲染 Agent 输出。

CODE
// 直连 TCP
return new DaemonClient({
  ...base,
  url: buildDaemonWebSocketUrl(connection.endpoint),
});

// 中继连接(加密隧道)
return new DaemonClient({
  ...base,
  url: buildRelayWebSocketUrl({
    endpoint,
    enabled: true,
    daemonPublicKeyB64: connection.daemonPublicKeyB64,
  }),
});
中文解释

在同一网络时,直连守护进程的 WebSocket 地址...

远程时,通过加密中继隧道连接...

无论哪种方式,App 获得的都是同一个 DaemonClient — 传输层可互换。

其余成员

⌨️

CLI (packages/cli)

Commander.js 命令:run、ls、logs、wait、daemon start/stop。与 App 使用相同的 WebSocket协议。

🔐

Relay (packages/relay)

零知识加密桥梁。中继服务器只能看到加密后的字节流 — 无法读取你的数据。

🖥️

Desktop (packages/desktop)

Electron 封装。将守护进程作为子进程启动,无需手动操作。

想一想

你想给 Paseo 加一个"深色模式"开关。需要修改哪个包?

03

Agent 生命周期

从"创建"到"空闲" — 管理 Agent 的状态机

四种状态

Agent 就像一个可以在四种状态之间切换的工人。守护进程用状态机来追踪 — 这个概念从红绿灯到游戏角色无处不在。

1
initializing(初始化)

守护进程收到了你的请求,正在和 Provider 建立 Agent 会话。

2
idle(空闲)

Agent 就绪,等待任务。就像出租车在站点等客。

3
running(运行中)

Agent 正在工作 — 读取文件、编辑代码、运行命令。

4
error / closed(错误/关闭)

出了问题,或者 Agent 完成任务后被关闭。

🔄
循环

运行结束后 → Agent 回到 idle(而不是关闭)。这就是循环:idle → running → idle。Agent 保持存活,等待你的下一条指令。就像私人助理完成一项任务后,等着下一项。

创建 Agent — 代码解读

CODE
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 时,这些组件在幕后互相通信:

Agent 创建 — 幕后揭秘
?
0 / 5 条消息

想一想

你的 Agent 状态是 "running" 但已经 10 分钟没有输出了。你应该先排查哪里?

04

万物如何通信

WebSocket 协议 — 一种语言,三个客户端

握手

在实际工作开始之前,客户端和守护进程会先进行一次"握手" — 就像两人见面先交换名片再开始交谈。

CODE
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" — 证明连接仍然存活。

💡
Ping/Pong:心跳检测

每个 WebSocket 连接都会定期发送 ping。如果对方停止回复,你就知道连接断开了 — 就像打电话没人接。这就是 Paseo 如何知道在手机上显示"重新连接中..."的原理。

二进制多路复用

Paseo 在同一个连接上发送两种数据:控制消息(JSON)和终端数据(原始字节)。就像电视频道同时传输视频信号和字幕 — 同一根线,不同内容。

CODE
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)

之后的所有内容:原始终端字节 — 就是你终端窗口里看到的东西。

消息流:从 Agent 到手机

点击步骤,看一条消息如何从 Claude 的"大脑"传到你的手机屏幕:

C
Claude Agent
D
Daemon
📱
你的手机
点击"下一步"追踪消息路径
步骤 0 / 5

想一想

你在做一个聊天 App。Web、iOS 和 Android 用户都需要实时看到消息。该用什么?

05

运行 Agent

完整旅程:从手机点击到代码写入你的机器

七个步骤

1
你发送请求

"修复登录 Bug" — 从手机、CLI 或桌面端。

2
会话路由到 AgentManager

守护进程解包你的消息,告诉管理器创建/启动 Agent。

3
Provider 生成 Agent 进程

Claude/Codex/OpenCode 作为真正的进程在你的机器上运行。

4
事件实时流式传输

每个操作(读取文件、编辑代码、Shell 命令)都变成时间线事件。

5
工具调用被标准化

不同 Provider 格式不同 — Paseo 将它们统一映射到一种通用格式。

6
权限请求发送给你

如果 Agent 想执行危险操作,你的手机会弹出确认提示。

7
结果广播给所有客户端

手机、桌面、CLI — 所有设备同时看到相同输出。

权限请求 — 安全网

当 Claude 想做可能有风险的事情(删除文件、运行 Shell 命令)时,它会征求你的许可。Paseo 通过守护进程将请求路由到你正在使用的客户端。

CODE
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 同时弹出同一条提醒的烦人场景。

想一想

你在咖啡厅,Agent 需要权限删除一个文件。你同时在手机和笔记本上打开了 Paseo。应该发生什么?

06

远程访问与加密

Paseo 如何让你从任何地方控制 Agent — 无需打开防火墙

中继难题

你的守护进程运行在家里的电脑上。你的手机在咖啡厅。你家网络有防火墙。它们怎么通信?

答案:中继服务器 — 一个你的守护进程和手机都能访问的公共中间人。但你怎么信任中继服务器不偷看你的数据?

你不需要信任它。这就是巧妙之处:端到端加密。中继只能看到乱码。

加密之舞

想象两个间谍在酒店大堂接头。他们素未谋面,但需要共享一个秘密。方法是 — 使用一种叫做ECDH 密钥交换的技术。

CODE
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 用的是同一个原理。

握手过程

CODE
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 中继服务器。他们能看到什么?

你在做一个即时通讯 App。安全专家说:"用户应该能验证服务器无法读取他们的消息。"你应该推荐什么加密方案?

你学到了什么

🎯
核心收获

Paseo 展示了几乎每个生产系统都会用到的六个模式:

客户端-服务器架构 — 中央管理器(守护进程)服务多个 UI
WebSocket 流式传输 — 实时数据推送给所有已连接的客户端
状态机 — 安全地管理生命周期转换
二进制多路复用 — 在一个连接上传输多种数据类型
智能通知路由 — 在多设备间避免重复通知
端到端加密 — 信任通道而不信任中间人

现在你可以阅读 Paseo 的源代码,也有足够的词汇来指导 AI 编程工具构建类似的系统。当你说"用 WebSocket 做实时流式传输"或"加 ECDH 密钥交换实现端到端加密"时,你会清楚地知道自己要什么 — 以及在结果中应该检查什么。