Skip to content

Pi Agent 命令与快捷键注册

通过扩展注册自定义命令、键盘快捷键和 CLI 标志,让 Pi Agent 更好地适应你的工作流。


注册自定义命令

使用 pi.registerCommand() 注册用户可通过 / 调用的命令。

下面的扩展同时演示了基础命令和带参数自动补全的命令:

实例

javascript

// 文件路径:~/.pi/agent/extensions/stats-command.ts

// 基础命令

pi.registerCommand("stats", {

  description: "显示当前会话的统计信息", // 必填,显示在 / 自动补全列表中

  handler: async (args, ctx) => {

    const count = ctx.sessionManager.getEntries().length;

    ctx.ui.notify(`共 ${count} 条消息`, "info");

  }

});

// 带参数自动补全的命令

pi.registerCommand("deploy", {

  description: "部署到指定环境",

  // 定义参数自动补全,prefix 是用户已输入的参数前缀

  getArgumentCompletions: (prefix) => {

    const envs = ["dev", "staging", "prod"];

    const items = envs

      .filter(e => e.startsWith(prefix))

      .map(e => ({ value: e, label: e }));

    return items.length > 0 ? items : null; // 无匹配时返回 null

  },

  handler: async (args, ctx) => {

    if (!args) {

      ctx.ui.notify("用法: /deploy <环境名>", "warning");

      return;

    }

    ctx.ui.notify(`正在部署到 ${args} 环境...`, "info");

  },

});

保存后执行 /reload 加载扩展,在编辑区输入 /stats 即可看到效果:

bash
/stats
 42 条消息

如果多个扩展注册了同名的命令,Pi Agent 会保留所有注册并按加载顺序添加数字后缀,例如 /review:1 和 /review:2。

这避免了扩展之间的命名冲突。


扩展主动发消息

除了被动响应命令和事件,扩展还可以主动驱动对话。

pi.sendMessage(message, deliverAs?) 用于注入自定义消息,pi.sendUserMessage(text, deliverAs?) 则模拟一条用户输入。

deliverAs 有三个取值:steer 是默认值,把消息插入当前轮;followUp 在本轮结束后追加;nextTurn 开启新一轮(仅 sendMessage 支持)。

sendUserMessage 总是会触发一轮回复,因为它对 AI 来说就是一次真实的用户发言。

流式输出期间调用这两个方法时,必须显式给出 deliverAs,否则会直接抛错。

实例

bash

// 文件路径:~/.pi/agent/extensions/follow-up.ts

// 以下片段位于扩展工厂函数体内

// 模拟用户补充要求:等当前所有工具执行完再追加

pi.sendUserMessage("部署完成后请顺手检查生产环境日志", {

  deliverAs: "followUp",

});

// 只往上下文里塞参考信息,不模拟用户发言

pi.sendMessage({

  customType: "deploy-context",

  content: "当前部署目标是 production",

  display: true,

}, {

  deliverAs: "steer", // 默认值,插入当前轮

});

sendMessage 还支持 triggerTurn: true,在 agent 空闲时立即触发一次 LLM 响应。

两者的选择标准很简单:想让 AI 真正接话就用 sendUserMessage,只想提供参考信息就用 sendMessage。


命令上下文(ExtensionCommandContext)

命令的 handler 接收的是 ExtensionCommandContext,它继承自 ExtensionContext。

在普通上下文的基础上,它额外提供以下方法:

方法说明
ctx.getSystemPromptOptions()读取构建系统提示词的基础输入(contextFiles、skills 等),仅命令上下文可用
ctx.waitForIdle()等待 AI 完全空闲(包括重试、压缩、跟进消息全部完成)
ctx.newSession(options)创建新会话,可指定父会话和初始化逻辑
ctx.fork(entryId, options)从指定条目分叉出新会话
ctx.navigateTree(targetId, options)导航到会话树中的指定位置
ctx.switchSession(sessionPath, options)切换到另一个会话文件
ctx.reload()执行与 /reload 相同的热重载流程

下面的命令把当前项目的所有会话列成选择列表,选中后直接切换过去:

实例

sql

// 文件路径:~/.pi/agent/extensions/switch-session.ts

import { SessionManager } from "@earendil-works/pi-coding-agent";

pi.registerCommand("switch", {

  description: "切换到另一个会话",

  handler: async (args, ctx) => {

    // 列出当前项目的所有会话

    const sessions = await SessionManager.list(ctx.cwd);

    if (sessions.length === 0) {

      ctx.ui.notify("没有可用的会话", "warning");

      return;

    }

    // 弹出选择列表

    const choice = await ctx.ui.select(

      "选择要切换的会话:",

      sessions.map(s => s.file),

    );

    if (choice) {

      // 切换到选中的会话

      await ctx.switchSession(choice, {

        withSession: async (ctx) => {

          ctx.ui.notify("已切换会话", "info");

        },

      });

    }

  },

});
bash
/switch
 选择要切换的会话:
 > ~/.pi/agent/sessions/my-project/abc123.jsonl
   ~/.pi/agent/sessions/my-project/def456.jsonl

已切换会话

会话替换的注意事项:withSession 回调中的 ctx 是一个全新的上下文。

不要使用旧的 pi 对象或命令回调中捕获的 ctx,它们在会话替换后会变成无效状态。


注册快捷键

使用 pi.registerShortcut() 注册自定义键盘快捷键。

下面把「切换 Plan 模式」绑到一个尚无内置占用的组合键上:

实例

javascript

// 文件路径:~/.pi/agent/extensions/plan-shortcut.ts

pi.registerShortcut("ctrl+shift+g", {

  description: "切换 Plan 模式",

  handler: async (ctx) => {

    ctx.ui.notify("已切换 Plan 模式!", "info");

  },

});

启动后按下组合键即可触发:

bash
已切换 Plan 模式!

选择组合键前先核对默认快捷键表,避免覆盖内置绑定。

例如 ctrl+shift+p 已被「向后切换模型」占用,再注册同名组合键会导致两个行为互相抢键。

快捷键格式为 修饰键+按键,例如 ctrl+shift+g、alt+x 等。


注册 CLI 标志

使用 pi.registerFlag() 为 pi 命令添加自定义 CLI 参数。

下面的例子注册一个 --plan 布尔标志,用于让 AI 先出计划再动手:

实例

javascript

// 文件路径:~/.pi/agent/extensions/plan-flag.ts

pi.registerFlag("plan", {

  description: "以 Plan 模式启动",

  type: "boolean",

  default: false,

});

// 在扩展代码中检查标志值

// 注意:pi.getFlag() 要等到命令行参数解析完成后才有值,

// 因此建议把判断放进事件回调里,而不是写在扩展模块顶层。

// 模块顶层求值发生在扩展加载阶段,此时读到的永远是默认值 false。

pi.on("before_agent_start", async (event, ctx) => {

  if (!pi.getFlag("plan")) return; // 未启用 --plan 则不做任何修改

  return {

    systemPrompt: event.systemPrompt

      + "\n\nPlan Mode:先提出计划,经用户确认后再执行。",

  };

});

使用方式:

bash
$ pi --plan "帮我实现用户认证功能"

扩展间通信

使用 pi.events 在扩展之间共享事件。

发送方不需要知道谁在监听,监听方也不需要知道事件从哪里来:

实例

javascript

// 文件路径:~/.pi/agent/extensions/deploy-events.ts

// 扩展 A:发送事件

pi.events.emit("deploy:completed", {

  env: "production",

  timestamp: Date.now(),

});

// 扩展 B:监听事件(通常写在另一个扩展文件里)

pi.events.on("deploy:completed", (data) => {

  console.log(`部署完成:${data.env}`);

});

获取已注册的命令

使用 pi.getCommands() 获取当前会话中所有可用的命令。

下面的例子注册一个 /my-commands 命令,用来列出所有由扩展注册的命令:

实例

javascript

// 文件路径:~/.pi/agent/extensions/list-commands.ts

pi.registerCommand("my-commands", {

  description: "列出扩展注册的所有命令",

  handler: async (args, ctx) => {

    const commands = pi.getCommands();

    // 按来源分类

    const extCommands = commands.filter(

      c => c.source === "extension"

    );

    const templates = commands.filter(

      c => c.source === "prompt"

    );

    const skills = commands.filter(

      c => c.source === "skill"

    );

    // 把结果展示出来,避免只计算不输出

    ctx.ui.notify(

      `扩展命令 ${extCommands.length} 个:`

        + extCommands.map(c => "/" + c.name).join("、"),

      "info"

    );

    console.table(commands);

  },

});
bash
/my-commands
扩展命令 2 个:/stats、/deploy

每个命令条目包含以下字段:

字段类型说明
namestring命令名,在编辑区以 /name 的形式调用
descriptionstring命令描述,显示在自动补全列表中
sourcestring来源类型:extension、prompt、skill 等
sourceInfoobject详细的来源元数据,例如注册它的扩展路径

动态工具管理

运行时管理工具的启用和禁用。

典型用途是临时关掉写操作工具,让 AI 在一次任务里只能读不能改:

实例

javascript

// 文件路径:~/.pi/agent/extensions/manage-tools.ts

// 获取当前启用的工具列表

const active = pi.getActiveTools();

// 例如:["read", "bash", "edit", "write"]

// 获取所有已注册工具的元数据

const all = pi.getAllTools();

// 筛选内置工具

const builtinTools = all.filter(

  t => t.sourceInfo.source === "builtin"

);

// 筛选扩展注册的工具

const extTools = all.filter(

  t => t.sourceInfo.source !== "builtin"

    && t.sourceInfo.source !== "sdk"

);

// 动态启用自定义工具(保留现有工具)

pi.setActiveTools([...new Set([...active, "my_tool"])]);

// 切换到只读模式:只保留读取类工具,

// 不包含 bash / edit / write,AI 在此状态下无法执行命令或改动文件

pi.setActiveTools(["read", "grep", "find", "ls"]);

这里的 setActiveTools 与命令行的 \--tools / \--exclude-tools 参数作用于同一个工具集合。

CLI 参数决定启动时的初始集合,setActiveTools 在运行时动态调整,两者可以配合使用。

传入 setActiveTools 的变更必须是增量的(additive),在同一次调用中移除当前已激活的工具会失去延迟加载优化。

另外,激活带 promptSnippet 或 promptGuidelines 的工具会重建系统提示词,即使模型支持延迟加载,也可能使缓存的提示词前缀失效。

AI 思考中...

Pi Agent 事件系统

Pi Agent 包管理

基于 VitePress 构建,部署于 GitHub Pages