Pi 插件解剖|ssh.ts:只用 221 行,让 Agent 直接在远程机器干活

一句话定位

通过 --ssh user@host 把 pi 的内置工具(read/write/edit/bash)全部切换到远程机器执行------221 行,工具改写(依赖注入换执行环境)的完整范例。

作用(为什么存在)

前三期拆过拦截、生命周期、行为替换,这期是工具改写:让 Agent 直接操作远程机器,而不是只在本地干活。

它的核心机制非常优雅:内置工具工厂接受 operations 参数 ------createReadTool(localCwd, { operations }) 替换底层实现,工具定义(参数、描述、渲染)完全不变。pi 只依赖输入输出契约,不关心真实实现是本地还是 SSH。

更妙的是一个插件同时支持两种模式--ssh 传了就远程执行,不传就完全走本地------插件对普通用户是透明的。

关键信息

内容
源码位置 examples/extensions/ssh.ts(221 行)
核心 API registerFlag/getFlag + registerTool(同名覆盖内置)+ createReadTool 等(注入 operations)+ on("user_bash") + on("before_agent_start")
插件类型 工具改写型(依赖注入换执行环境)

触发流程 / 数据流

lua 复制代码
pi -e ssh.ts --ssh user@host
  → 工厂期:registerFlag("ssh") 注册 --ssh;创建本地工具实例;同名覆盖 read/write/edit/bash
  → session_start:getFlag("ssh") 解析远程地址
      ├─ 含 :path → 直接用
      └─ 无 path → 远程执行 pwd 自动探测
  → 工具 execute:getSsh() 有值 → 用远程 ops 重建工具 → 远程执行;无值 → 本地
  → user_bash(! 命令):SSH 激活时返回远程 bash ops
  → before_agent_start:改 system prompt 的 cwd,让 LLM 认知工作在远程

架构 / 流程

关键代码解读

typescript 复制代码
// ① operations 注入:替换工具底层实现(ssh.ts:47-62)
function createRemoteReadOps(remote, remoteCwd, localCwd): ReadOperations {
  const toRemote = (p) => p.replace(localCwd, remoteCwd);  // 本地路径 → 远程路径
  return {
    readFile: (p) => sshExec(remote, `cat ${JSON.stringify(toRemote(p))}`),
    access:   (p) => sshExec(remote, `test -r ${JSON.stringify(toRemote(p))}`).then(() => {}),
    detectImageMimeType: async (p) => { /* file --mime-type ... */ },
  };
}

// ② 同名覆盖内置工具:SSH 激活时换远程实现(ssh.ts:128-140)
pi.registerTool({
  ...localRead,   // 定义(name/description/parameters)不变
  async execute(id, params, signal, onUpdate, _ctx) {
    const ssh = getSsh();
    if (ssh) {
      const tool = createReadTool(localCwd, { operations: createRemoteReadOps(ssh.remote, ssh.remoteCwd, localCwd) });
      return tool.execute(id, params, signal, onUpdate);  // 换成远程执行
    }
    return localRead.execute(id, params, signal, onUpdate);  // 默认本地
  },
});

// ③ session_start 延迟解析:工厂期 CLI flag 不可用(ssh.ts:184-199)
pi.on("session_start", async (_event, ctx) => {
  const arg = pi.getFlag("ssh") as string | undefined;  // 此时 flag 才可读
  if (arg) {
    if (arg.includes(":")) {
      const [remote, path] = arg.split(":");
      resolvedSsh = { remote, remoteCwd: path };
    } else {
      const pwd = (await sshExec(arg, "pwd")).toString().trim();  // 无路径→远程 pwd 探测
      resolvedSsh = { remote: arg, remoteCwd: pwd };
    }
    ctx.ui.setStatus("ssh", ctx.ui.theme.fg("accent", `SSH: ${resolvedSsh.remote}:${resolvedSsh.remoteCwd}`));
  }
});

亮点 / 踩坑

亮点 1:依赖注入换执行环境。 operations 是接口契约(Read/Write/Edit/Bash 四个接口各不同),注入实现即可换环境------工具定义、LLM 感知、渲染全都不变。这是「面向接口编程」在插件层的落地。

亮点 2:生命周期分离。 注册动作(registerFlag/registerTool)在工厂期做,读运行时状态(getFlag)必须等 session_start------因为工厂期 CLI flag 还没注入。一个 resolvedSsh = null 占位 + getSsh() 现查,让本地/远程两种模式同时成立。

踩坑:只支持 key 认证。 源码注释明确「SSH key-based auth (no password prompts)」,且 spawn("ssh", ..., { stdio: ["ignore", "pipe", "pipe"] }) 把 stdin 设成 ignore------远程要密码时没法输入,只能等超时 。现象:每次工具调用卡十几秒到几十秒(17s/53s),全是等密码超时。解法:配置 SSH 免密(ssh-keygen + ssh-copy-id + 远程 chmod 700 ~/.ssh && chmod 600 authorized_keys)。诊断口诀:先直连 time ssh host "echo ok" 排除网络,再查认证方式

边界(Limitations)

边界 表现
认证 必须 SSH key 免密(stdin ignore 无法输密码)
生效时机 --ssh 在 session_start 才解析;无 flag 时完全走本地
路径映射 toRemotelocalCwd → remoteCwd 字符串替换,路径结构不同会错
覆盖范围 read/write/edit/bash 四个内置工具 + 用户 ! 命令

场景(Scenarios)

  • SSH 模式pi -e ssh.ts --ssh user@hostuser@host:/path → 所有工具远程执行,状态栏显示 SSH
  • 本地模式:无 --ssh → 完全等同内置工具(插件透明)
  • 无路径--ssh user@host → session_start 时远程 pwd 探测
  • 失败:认证失败 / 远程无 bash → sshExec reject → 工具报错

可借鉴的模式

  1. 依赖注入换执行环境 :工具工厂接受 operations 替换底层实现------「定义不变、实现可换」。pi 只依赖输入输出契约。
  2. 同名覆盖内置工具:registerTool 注册与内置同名工具即可接管行为------LLM 感知的工具接口完全不变。
  3. 工厂期/运行期分离:注册动作工厂期做,读运行时状态 session_start 才做------生命周期决定 API 可用阶段。
  4. JSON.stringify 防注入:命令拼接时把路径序列化成带引号的合法字面量,防止空格/特殊字符破坏 shell(也是命令注入防护)。
  5. system prompt 改写:before_agent_start 改 cwd,让 LLM 的认知与实际执行环境一致。

一句话总结

221 行教你「工具改写」:依赖注入换执行环境、同名覆盖接管内置工具、工厂期/运行期分离------一个插件同时支持本地和远程两种模式。

相关推荐
Patrick_Wilson1 小时前
当执行不再稀缺:AI Agent 时代的技术判断力
人工智能·架构·ai编程
dong_junshuai1 小时前
每天一个开源项目#73 Munder Difflin:2.3K Star 的本地多Agent办公室
开源·github·agent
SpaceAIGlobal1 小时前
AI PPT生成工具哪些支持PDF文档导入?
人工智能·ai·pdf·powerpoint·办公
leeyi1 小时前
Langfuse 集成源码:batch 协议、media 上传与 mock 测试(第89篇-E75)
llm·aigc·agent
王中阳Go1 小时前
杰富瑞实测 8 款 AI Agent,国产千问 95 分登顶:我连夜把项目的 OpenAI 硬编码全拆了
人工智能·go
hyunbar7771 小时前
Tools、Function Calling、MCP 都在说什么?
人工智能
智能运维指南2 小时前
智能体自治运维选型指南:2026年企业如何从“AI辅助”走向“AI自治”
大数据·运维·人工智能
修远客2 小时前
风格进化:让Agent越来越懂你 — 从"工具"到"助手"的关键跃迁
llm·agent
桃西西呀2 小时前
dsh能接生产吗?fail-closed 沙箱到底保不保底
人工智能