现在位置: 首页 > Pi Agent > 正文

Pi Agent 命令与快捷键注册

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


注册自定义命令

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

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

实例

// 文件路径:~/.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 即可看到效果:

/stats
共 42 条消息

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

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


扩展主动发消息

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

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

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

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

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

实例

// 文件路径:~/.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 相同的热重载流程

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

实例

// 文件路径:~/.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");
        },
      });
    }
  },
});
/switch
┌ 选择要切换的会话:
│ > ~/.pi/agent/sessions/my-project/abc123.jsonl
│   ~/.pi/agent/sessions/my-project/def456.jsonl
└
已切换会话

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

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


注册快捷键

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

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

实例

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

pi.registerShortcut("ctrl+shift+g", {
  description: "切换 Plan 模式",
  handler: async (ctx) => {
    ctx.ui.notify("已切换 Plan 模式!", "info");
  },
});

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

已切换 Plan 模式!

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

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

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


注册 CLI 标志

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

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

实例

// 文件路径:~/.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:先提出计划,经用户确认后再执行。",
  };
});

使用方式:

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

扩展间通信

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

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

实例

// 文件路径:~/.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 命令,用来列出所有由扩展注册的命令:

实例

// 文件路径:~/.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);
  },
});
/my-commands
扩展命令 2 个:/stats、/deploy

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

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

动态工具管理

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

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

实例

// 文件路径:~/.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 的工具会重建系统提示词,即使模型支持延迟加载,也可能使缓存的提示词前缀失效。