Paseo 跨主机 Agent 对话
两台机器上的 agent(CC / Codex / OpenClaw 都行)互相提问、多轮对话。人只发命令或名片;下面所有脚本都由 agent 自己执行,人不用敲命令。 机器对称,不分服务器/客户端。
核心机制:paseo <命令> --host <配对链接> 走公共 relay(端到端加密)直连对方 daemon,相当于远程操作对方机器上的 Paseo(ls≈窗口列表、send≈发消息、logs≈看记录)。
安全:LINK = 对那台 daemon 的完整控制权,当密钥保管,别外传。
装到一台新机器(agent 自己动手,三条)
第 1 条是人做的(把 skill 放上去)。第 2、3 条是你自己要做的——缺什么装什么,别让用户去装。装完接着往下走,不要停下来问"要不要我装"。
- 放 skill:二选一——
- 插件市场装:
claude plugin marketplace add bujianhe/agent-penpal→claude plugin install agent-penpal@agent-penpal - 手动拷目录:整个
agent-penpal/目录放进该 agent 的 skill 目录——Claude Code 是~/.claude/skills/agent-penpal/(Windows:%USERPROFILE%\.claude\skills\agent-penpal\)。没 skill 机制的 agent(纯 AGENTS.md / 系统提示):把本文贴进去一样生效。
- 插件市场装:
- 装 Node 18+(先
node -v;低于 18 才需要装,20/22 LTS 都行):- Windows:
winget install OpenJS.NodeJS.LTS - macOS:
brew install node - Linux:
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash - && sudo apt-get install -y nodejs
- Windows:
- 确认有 paseo CLI——两种来源,有其一即可,别重复装:
- 装了 Paseo 桌面 App 的:它自带一份 CLI,不用再装。App 装好后 CLI 在这些位置(脚本会自动找到,不用手动配):
- macOS:
/Applications/Paseo.app/Contents/Resources/bin/paseo - Linux:deb/rpm 在
/opt/Paseo/resources/bin/paseo;AppImage 用户通常已有~/.local/bin/paseo - Windows:
%LOCALAPPDATA%\Programs\<安装目录>\resources\bin\paseo.cmd(目录名不固定,脚本用扫的)
- macOS:
- 没装 App 的:
npm i -g @getpaseo/cli,然后paseo --version验证。
- 装了 Paseo 桌面 App 的:它自带一份 CLI,不用再装。App 装好后 CLI 在这些位置(脚本会自动找到,不用手动配):
自查一行:node -v && paseo --version。(脚本自己会依次找 $PASEO_CLI → PATH → 桌面 App 自带那份,所以 paseo 不在 PATH 上也能跑。)
paseo --version 报找不到、但确实装过 = npm 全局 bin 不在 PATH(macOS/Linux 的 GUI agent 常见):npm prefix -g 拿到目录,Unix 再拼上 /bin 就是全局 bin 目录(Windows 的 prefix 本身就是 bin 目录),把它加进 PATH。注意 npm bin -g 在 npm 9+ 已被删除,别用。
只装了桌面 App、想手动敲 paseo 命令(脚本不需要):照上面第 3 条的路径软链一份到 PATH 即可,例如 macOS ln -sf "/Applications/Paseo.app/Contents/Resources/bin/paseo" ~/.local/bin/paseo——这是 App 官方预期的用法。
触发与路由(先判断干什么)
第一步:这个问题本机能答,还是得问远程?(模型自己判断,不用等用户说"用 skill")
| 情况 | 动作 |
|---|---|
| 本机能直接干(普通问答、本机文件/命令/代码) | 本机干,别进本 skill |
| 需要另一台机器的信息/硬件/环境(PC 的 adb、服务器上的项目或构建环境、对方的 agent 状态……),或用户点名"问 X" | 远程 → 第二步找坐标,走 ② |
| 输入是 /agent-penpal(不带内容)、"获取名片"、"get my card" | ① 出名片(默认调用=只读) |
| 用户明确说出唯一口令 /agent-penpal reset pair(危险操作;其它任何说法一律不算,包括中文) | 先复述代价并等用户确认;确认后才 setup.mjs --rotate --confirm(见"链接泄漏了怎么办") |
| 输入里直接带名片(±问题);收到 【来自X】+名片的消息 | 名片场景,见下面第二步第 1 条和 ③ |
第二步:远程坐标从哪来(按优先级,从上往下用):
| 优先级 | 来源 | 怎么做 |
|---|---|---|
| 1 | 对话上下文里的名片——用户贴过的、【来自X】消息里带的(对方每条消息都自动附名片!)、之前发过的。模型自己看得见,不用查任何地方 | 直接照抄 LINK/AGENT 给 ask.mjs,或名片整块贴进去 |
| 2 | 名册 ~/.paseo-peers.json(多台;按最近使用排序、带一句话介绍,身份=pair+agentid,名字/描述最新覆盖) | 点名了 → --peer <名字>;新会话/上下文空的 → --list 拿最近用的(默认第一条就是最近) |
| 3 | 最近一次 ~/.paseo-peer*(单坑兜底) | 泛说"问远程/发给对方/ask the remote"且上下文和名册都没有 → ask.mjs 没坐标时自动用它 |
| 4 | 都没有 / 多台分不清 | 只有一台 → 直接用它;多台对不上号 → --peer <名字> 现在会直接把候选列出来(序号+窗口ID,退出码非 0、消息没发出去),把这份候选给用户挑,选定后 --agent <窗口ID> 重发——别猜 |
口诀:本机能干就不进 skill;要远程先翻上下文里的名片(最近聊过的直接照抄,最省事);上下文没有翻名册(只有一台直接用);再没有用"最近一次";多台分不清把列表给用户挑。
名册是"防遗忘":会话被压缩、换新会话后上下文里的名片没了,名册顶上。收到名片时顺手
--remember存一份(见 ③)。 不搞:get/:connect子命令:各 agent 语法不通用,名片内容本身就够区分。
① 出名片(让自己可被连)
agent 直接跑:
node <skill目录>/scripts/setup.mjs --name <本机名字,如 电脑>
一条龙:检查环境(缺 paseo CLI 直接给安装命令)→ 起本地 daemon(已在跑就跳过)→ 生成配对链接 → 定对话窗口(在 Paseo agent 窗口里跑就用当前会话;普通终端里跑:先沿用 ~/.paseo-self-agent 里已存的对话窗口——还在就直接用,重复跑不乱开窗;没有才后台新开一个常驻 agent,默认 --mode bypassPermissions 授权,否则执行对方请求时第一个工具调用就卡权限审批)→ 坐标写进 ~/.paseo-self-* → 打印名片。把名片原样转给用户,让用户发给对方。
一张就够:对方拿到这张名片发第一条消息时,
ask.mjs会自动把它自己的名片附在消息开头——所以对面也拿到了你的坐标,双向就通了。不需要两边各递一次。
名片的一句话介绍(可选,≤200 字):内容是职责(仓库级——这台机器/这个项目能帮对方处理哪方面问题、什么时候该找你;例:--desc "有安卓源码和 adb:查安卓构建、抓日志、跑真机测试时找我")。不传的话,首次 setup 会自动从当前目录的 README.md / CLAUDE.md / AGENTS.md 提取一句;都没有才退而用对话窗口标题;生成后固定不变(想刷新:删 ~/.paseo-self-desc 再跑,或 --desc 手动覆盖)。想措辞更贴切,agent 可以先读 README/CLAUDE.md 自己总结一句再传 --desc。拿不准就留空——宁可没有这行,也别把文档里的示例、或自己猜的职责硬编进去(这行是发给别人看的"你能帮什么",编错了比空着更糟)。"当前在处理什么"这类会变的信息别写进静态简介;需要时用单条覆盖:ask.mjs --self-desc "当前在处理:XX"。
让 daemon 一直在线(配对是一次性的;日常只要 daemon 在跑——CC/Codex 不用常开,来消息时 daemon 会自动把窗口的进程拉起来续聊,前提是对应 provider CLI 还装着)。daemon 就是 paseo CLI 起的后台进程(paseo daemon start 起的就是它,不需要桌面 App;桌面 App 只是可选的另一个宿主,App 开着时会自带一个同样的 daemon)。
1)建议开启开机自启。原因:对端找你时只认"你的 daemon 此刻在不在线"——不在线就直接连接失败、消息不会排队(没有离线补投)。不配自启 = 每次重启后都有一段"失联期",而对方并不知道你什么时候在线,那段时间找你的人就丢了。
要托管的就是这一条命令(其余是各平台的常规做法,agent 自己会):
__PASEO__ daemon run
__PASEO__ = paseo CLI 的绝对路径(which paseo,或 node setup.mjs 的探测结果;装了桌面 App 的通常是 App 里那份)。不能只写 paseo——systemd / launchd / 计划任务都不读 shell 的 rc 文件,它们眼里 PATH 上没有 paseo。
⚠️ 三个坑:
- 是
daemon run,不是daemon start。daemon start那套--foreground已被移除,用了会直接报错--foreground was removed。 - daemon 要能找到
claude/codex(来消息时要拉起它们续聊)。如果它们在~/.local/bin这类非标准位置,守护进程的最小 PATH 里没有——unit 里得显式Environment=PATH=<你的 PATH>。 - Linux 无桌面环境必须
loginctl enable-linger "$USER",否则你一退出登录它就停。
各平台怎么托管那条命令:
- Linux:systemd 用户服务,
~/.config/systemd/user/paseo.service里ExecStart=__PASEO__ daemon run、Restart=always,然后systemctl --user enable --now paseo - macOS:
~/Library/LaunchAgents/下放个 plist(RunAtLoad+KeepAlive);装了桌面 App 的话,加进「系统设置 → 通用 → 登录项」更省事(App 起 = daemon 起) - Windows:任务计划程序加一条"登录时"跑那条命令;或把桌面 App 丢进"启动"文件夹
agent 该主动配,别只把这段文档丢给用户。 这是常规的系统设置,范围也清楚。配完告诉用户一声:配了什么、怎么撤(Linux
systemctl --user disable --now paseo;macOSlaunchctl unload …或从登录项移除;Windows 删掉那条计划任务)。
配完自检(三平台通用):paseo daemon status 要显示 running,否则对方找不到你。
2)没配自启的,每次开机后手动跑一次(幂等,已在跑会跳过;装桌面 App 的直接打开 App 也行,App 起 = daemon 起):
paseo daemon status # 先看跑没跑
paseo daemon start # 没跑就起它(或 paseo onboard,效果一样、还带配对指引)
链路断了先查这里:paseo daemon status 不是 running,对方就找不到你。
② 连接(拿到名片就能问)
名片的来源无所谓——用户刚贴的、还是对话上下文里翻到的(上次收到的 【来自X】 消息里带着),照抄 LINK/AGENT 两行(或整块贴),然后跑:
node <skill目录>/scripts/ask.mjs <LINK> <AGENT> "消息"
node <skill目录>/scripts/ask.mjs --peer <名字> "消息" # 只记得名字、上下文没名片时(查名册)
- 带了问题 → 消息 = 问题原文。
- 只有名片没消息 → 脚本自动补握手语:"连接成功,请回复确认。(Connected. Please confirm.)"(想带名字/换语言就自己传消息)
- 名片整块原样粘给 ask.mjs 也行(脚本自己会扒出 LINK/AGENT)。
- ask.mjs 自动:检查环境 → 连接检查(连不上 / 窗口不存在立刻报错,不干等 10 分钟)→ 发消息(开头自动附本机名片)→ 等对方处理 → 拉回答复(末尾最新)。把答复转给用户。
- 一句话记住
--async和--replies:--async管"发"(发完就走),--replies管"收"(只拉不发送);不加--async就是默认那条——阻塞等到答复(最长 10 分钟),其它都不用记。 - 不想干等 →
--async(发完就走):立即返回;消息尾附一句【说明】——"我会来拉取;你装了 skill 也可主动发我(可选)"。收答复默认走拉取:ask.mjs --replies <LINK> <AGENT>;对方若主动发了(装了 skill),会作为消息进你窗口——当惊喜,不当承诺。群发也能配 --async。- 拉取节奏(模型自己掌握,程序无定时器):发完这轮就结束(告诉用户"已发出");之后用户问"收答复/对方回了没"、或你觉得该收了 →
--replies拉一次(还没好就过会儿再拉);想等就sleep 60 && node ... --replies ...一条命令先等再拉。
- 拉取节奏(模型自己掌握,程序无定时器):发完这轮就结束(告诉用户"已发出");之后用户问"收答复/对方回了没"、或你觉得该收了 →
- 发送成功 ask.mjs 会自动记住对方:带
NAME=的名片进名册(~/.paseo-peers.json,可存多台,--peer <名字>查);同时更新**"最近一次"**兜底文件(~/.paseo-peer*,没点名的"问远程"用它)。 - 名册管理:
--list看存了哪些(最近使用在前、带一句话介绍,方便挑;只列名字+窗口ID,链接不显示);--remember "<名片>"手动存一张(不发送)。 - 之后要再问 → 点名就稳:"问 <名字>" →
--peer <名字> "问题…";或名片照抄。
③ 回答(对方连过来了)
收到 【来自X】 + 名片块的消息 = 对方 agent 自动提问(不是人类追问),按对话约定正常干活回答、结论放最后——不管对方是阻塞等(默认)还是 --async 发完就走(消息尾会带【说明】),你这边的动作完全一样:答完就行,对方会来拉。
顺手把名片存一次:node <skill目录>/scripts/ask.mjs --remember "<名片>"——每台存一次就够(别每条都存;身份按 pair+agentid 认,重复存=刷新信息、不会多出条目),防会话被压缩后丢坐标。以后点名"问 <NAME>"就能主动找它(走 ②)。
可选:本机也装了 skill 时,可以主动把结论推回给发起方(失败就算了,别当任务):
node <skill目录>/scripts/ask.mjs --no-wait --no-card <对方LINK> <对方AGENT> "<一句话结论>"
链接泄漏了怎么办(换新链接)——危险操作,严格口令
配对链接 = 本机身份(PASEO_HOME 里的 server-id + daemon-keypair.json)推导出来的,所以"换链接 = 换身份"。
触发规则(必须全部满足,缺一不可):
- 唯一触发口令:
/agent-penpal reset pair(带了 skill 名,整串完整出现即可:斜杠可有可无、大小写不敏感、空格数量随意)。只有用户明确打出这串,才进入本流程。其它任何说法都不算——中文("换新链接""重置身份")不算,感觉像的("链接好像不对""换个链接试试")更不算:日常用语太容易撞。遇到就问一句"要换的话请明确输入口令:/agent-penpal reset pair",然后按默认出名片。 - 执行前必须二次确认:先复述代价——所有对端失效(对方要换新名片)、daemon 重启、正在跑的窗口中断、对话窗口 agentid 不变——然后问"确认现在换?"。
- 用户明确回复确认后,才运行(两个开关缺一律不动):
node <skill目录>/scripts/setup.mjs --rotate --confirm
默认调用永远是只读的:不带参数 = 只出名片(读当前 pair + agentid)。rotate 是唯一会改身份的操作,单给 --rotate 不带 --confirm 脚本会直接拒绝退出。
其他信息:
- 效果:老链接立即失效(relay 上老路由没了),生成新名片;旧身份文件自动备份成
.bak-<时间>。 - 代价:所有对端一起失效——对方存的旧名片/名册旧条目连不上了(连接检查会报错,不会静默发错地方);互发新名片即可恢复。
- agentid 不变:rotate 只换身份不换窗口;重启后窗口记录和历史都在,发消息自动续聊(Paseo 的 resume 机制)。
- 别在 Paseo agent 窗口里对本机 daemon 做(重启会停掉自己的会话)——脚本会拦住并退出;确认要做加
--force。 - 注意:
paseo daemon set-password管不了这条路——relay 连接不走 daemon 密码校验,链接本身就是唯一凭证(这就是"当密钥保管"的原因)。 - 手动版(脚本不能用时):
paseo daemon stop→ 把<PASEO_HOME>/server-id和<PASEO_HOME>/daemon-keypair.json改名/删除 →paseo onboard --voice disable --timeout 240→paseo daemon pair --json。本地 CLI 没有现成的 rotate 命令,就这四步。
对话约定(防死锁)
- 严格轮流:问 → 答 → 问 → 答。发起方阻塞等答复(最长 10 分钟),这期间别再往对方发新消息。(
--async模式例外:发完就走、不等——先记下坐标,回头--replies拉。) - 要反问对方:写在本轮答复的最后,不要中途发消息——两边会互等卡死。
- 结论放回复最后(对方
logs --tail抓尾部最省事)。
名片格式(Card — 纯 ASCII,整块照抄即通)
=== PENPAL CARD ===
NAME=电脑
DESC=PC 端对话窗口:有安卓源码和 adb,可代查安卓构建/日志问题
LINK=https://app.paseo.sh/#offer=……
AGENT=<对话窗口短ID>
==================
包边、NAME=、DESC= 都是给人/名册看的,认 LINK=/AGENT= 两行就够(老卡片没有 NAME/DESC、旧包边 === 名片 === 都认)。DESC= 可选(≤200 字的一句话"我是干嘛的")——配对时对方一眼知道你能帮什么;没有这行也完全正常。
一张名片 = 一个「机器 + 会话」:
LINK是那台机器的身份(paseo daemon pair生成,一台一个),AGENT是具体哪个对话窗口。通常每台出一张就够;同一台机器上想换别的会话参与,就再出一张(LINK 相同、AGENT 不同)。名册的主键也是link|agent,跟这个一致。
脚本路径写法
| 环境 | 写法 |
|---|---|
| Linux / macOS / Git Bash | ~/.claude/skills/agent-penpal/scripts/setup.mjs |
| Windows cmd | %USERPROFILE%\.claude\skills\agent-penpal\scripts\setup.mjs |
| Windows PowerShell | $HOME\.claude\skills\agent-penpal\scripts\setup.mjs |
(ask.mjs 同目录。)整个 skill 目录拷到别的机器、对应 agent 里就能用——脚本纯 Node,只依赖 paseo CLI(npm i -g @getpaseo/cli,Node 18+)。
排查
| 现象 | 解法 |
|---|---|
| 提示找不到 paseo | 脚本会依次找 $PASEO_CLI → PATH → 桌面 App 自带那份。都没找到才是真没有:Node 18+;npm i -g @getpaseo/cli。装过还报找不到 = npm 全局 bin 不在 PATH(npm prefix -g 拿目录,Unix 加 /bin;npm bin -g 已在 npm 9+ 删除)。只装了桌面 App 的,软链 App 里那份即可(见"装到一台新机器") |
| Windows + Git Bash:which paseo 能跑,脚本却说"找不到 paseo CLI" | Git Bash 的 PATH 可能是 POSIX 形式(/d/work/...),或 paseo 带 .cmd 后缀——早期版本这两种都探测不到。新版已修(PATH 转译 + 不再重复拼扩展名)。旧版应急:PASEO_CLI='D:\path\to\paseo.cmd' node ask.mjs … |
| --peer <名字> 说"名册里没有" | 名字得先入册:收到名片后跑一次 ask.mjs --remember "<名片>",之后"问 <名字>"才能点名。报错里会列出名册现在有谁。不想存就直接贴 LINK:--host <LINK> --agent <AGENT> |
| 配了自启,paseo daemon status 还是 not running | 多半是用了 daemon start --foreground——这个参数已被移除,要托管的是 <paseo 绝对路径> daemon run(见"让 daemon 一直在线")。另外两个常见原因:只写了 paseo 没用绝对路径(守护进程不读 shell 的 rc,PATH 里没有它);Linux 没做 loginctl enable-linger "$USER" |
| 配对链接泄漏了 | setup.mjs --rotate 换新身份:老链接立即失效、所有对端换新名片(见"链接泄漏了怎么办") |
| 对端说"连不上你" | 可能你刚 --rotate 换过身份;把新名片重发给他 |
| 想不起存过哪些远程 | ask.mjs --list(列名字+窗口ID,链接不显示);点名发送 --peer <名字>——名字重了不会替你猜,会把候选列出来让你挑,选定后用 --agent <窗口ID> 重发 |
| ask.mjs 报"连不上对端" | LINK 抄错 / 对方 daemon 没跑 / relay 或网络不通;核对无误可 --no-check 跳过检查 |
| ask.mjs 报"对端没有窗口 X" | AGENT 抄错或窗口被删;报错里列了对方现有窗口;让对方重跑 setup.mjs 出新名片 |
| 发完超时 | 对方窗口在忙或没开;ask.mjs 自动再等一轮,之后重试 |
| 对方窗口"卡住"没动静 / ask.mjs 报 permission | 窗口在等权限审批:直接远程切模式 paseo agent mode <窗口ID> bypassPermissions --host "<链接>"(最省事);或 paseo permit ls --host "<链接>" 看、paseo permit allow <窗口ID> --all --host "<链接>" 放行。新开窗口已默认 bypass |
| --async 发出后怎么收答复 | ask.mjs --replies <LINK> <AGENT> 拉对方窗口最新内容(结论通常在末尾);对方装了 skill 也可能主动发进你窗口(可选) |
| 提示"本条未附名片" | 拿不到本机对话窗口ID;先跑 setup.mjs |
| 两边互等卡死 | 违反严格轮流(被问方中途发了消息);各自超时重来,反问写答复末尾 |
| setup.mjs 窗口开去了别的 daemon | 环境里 PASEO_HOST/PASEO_LISTEN 劫持 run/ls;setup.mjs 已自动剥掉并钉在本机 daemon,手动敲 paseo 命令时注意 |
| setup.mjs 说"配对链接没生成" | 先看它打出来的 paseo 原始输出(脚本会按原因给不同提示,别一律当成 relay 问题)。真 relay 关着:在 PASEO_HOME 的 config.json 设 "relayEnabled": true → paseo daemon restart → 重跑。若是 TARGET_AMBIGUOUS = PASEO_HOME 和 PASEO_HOST 环境变量同时存在,清掉一个再跑(新版脚本已不再自己制造这个冲突) |
脚本不能用时手动三连(同 ask.mjs 原理):
paseo ls -a -g --json --host "<LINK>" # 找窗口短ID
paseo send <短ID> --host "<LINK>" "问题……" # 只返回状态(completed/timeout/permission),不含答复正文
paseo logs <短ID> --tail 40 --host "<LINK>" # 取答复正文
更多参数:node <脚本> --help。
Scan to join WeChat group