<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tutorial on yzchen.space</title>
    <link>https://www.yzchen.space/tags/tutorial/</link>
    <description>Recent content in Tutorial on yzchen.space</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Sun, 05 Jul 2026 12:00:00 +0800</lastBuildDate>
    <atom:link href="https://www.yzchen.space/tags/tutorial/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Pi Session 管理：tree、fork、clone 与 handoff</title>
      <link>https://www.yzchen.space/posts/20260705/pi-session-management/</link>
      <pubDate>Sun, 05 Jul 2026 12:00:00 +0800</pubDate>
      <guid>https://www.yzchen.space/posts/20260705/pi-session-management/</guid>
      <description>&lt;p&gt;最喜欢 Pi 的特性之一就是对 session 的管理很方便。Pi 的 session 不是线性的——它是树形的。每次回溯到某个历史节点重新发消息，就会在那个点分出一个新分支。&lt;/p&gt;&#xA;&lt;h2 id=&#34;先看-session-长什么样&#34;&gt;先看 session 长什么样&lt;/h2&gt;&#xA;&lt;p&gt;下面是一段典型的对话树：&lt;/p&gt;&#xA;&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;user: &amp;#34;帮我写一个排序函数&amp;#34;&#xA;└─ assistant: &amp;#34;这是冒泡排序...&amp;#34;&#xA;   ├─ user: &amp;#34;改成快排&amp;#34; → assistant: &amp;#34;这是快速排序...&amp;#34;&#xA;   │  └─ user: &amp;#34;加注释&amp;#34; → assistant: &amp;#34;已加注释&amp;#34;  ← 当前活跃&#xA;   └─ user: &amp;#34;用 Rust 写&amp;#34; → assistant: &amp;#34;这是 Rust 版本...&amp;#34;&#xA;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;你随时可以回到&amp;quot;帮我写一个排序函数&amp;quot;或&amp;quot;改成快排&amp;quot;那个点，走另一条路。走了新路之后，原来的分支不会消失——它还保存在同一个 session 文件里。这就是 Pi 的 &lt;code&gt;/tree&lt;/code&gt; ，目前 Claude Code 和 Codex 都还没有到特性。&lt;/p&gt;&#xA;&lt;h2 id=&#34;tree-同一session内切换分支&#34;&gt;&lt;code&gt;/tree&lt;/code&gt; — 同一Session内切换分支&lt;/h2&gt;&#xA;&lt;p&gt;&lt;strong&gt;不创建新 session 文件。&lt;/strong&gt; 打开一个树形导航器，显示当前 session 的全部对话节点。&lt;/p&gt;&#xA;&lt;p&gt;在 &lt;code&gt;/tree&lt;/code&gt; 里你可以：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;选一条用户消息&lt;/strong&gt;：光标移到该消息的父节点，编辑器预填那条消息的原文，你可以修改后重新提交——形成新分支&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;选一条 assistant 消息或工具结果&lt;/strong&gt;：光标移到该消息位置，编辑器为空，直接从那个点继续&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;切换分支时&lt;/strong&gt;：Pi 可以（可选）把&amp;quot;被抛弃的分支&amp;quot;总结成一段摘要，附到新位置。这样不用回看旧分支也能知道那边发生了什么&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;常用按键：&lt;code&gt;↑↓&lt;/code&gt; 浏览、&lt;code&gt;Enter&lt;/code&gt; 选择、&lt;code&gt;Esc&lt;/code&gt; 取消、&lt;code&gt;Shift+L&lt;/code&gt; 打标签、&lt;code&gt;Ctrl+O&lt;/code&gt; 切换过滤模式（默认/隐藏工具结果/只看用户消息/只看有标签的）。&lt;/p&gt;</description>
    </item>
    <item>
      <title>Pi Extensions 进阶：从安装社区扩展到自己定制</title>
      <link>https://www.yzchen.space/posts/20260704/pi-extensions-advanced/</link>
      <pubDate>Sat, 04 Jul 2026 12:00:00 +0800</pubDate>
      <guid>https://www.yzchen.space/posts/20260704/pi-extensions-advanced/</guid>
      <description>&lt;p&gt;前一篇笔记讲了 extension 是什么、有什么示例、怎么写。这篇记录实际使用——怎么从官方和社区安装、以及怎么根据需求自己改造。&lt;/p&gt;&#xA;&lt;h2 id=&#34;一先自己写个简单的show-system-prompt&#34;&gt;一、先自己写个简单的：show-system-prompt&lt;/h2&gt;&#xA;&lt;h3 id=&#34;动机&#34;&gt;动机&lt;/h3&gt;&#xA;&lt;p&gt;Agent 的系统提示词由多个来源拼合而成——角色定位、工具列表、AGENTS.md、项目上下文、技能清单。Claude/Openclaw 这样的 Agent 内置系统提示词就能吃掉几千个 token。但是像 Pi 这种我们可以轻松写一个 Extention 调试提示词效果，看一眼&amp;quot;LLM 当前实际收到的 prompt 到底是什么&amp;quot;。一个 &lt;code&gt;/system-prompt&lt;/code&gt; 命令，把完整内容打印到终端。写&lt;/p&gt;&#xA;&lt;p&gt;主要就两步：&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;捕获提示词。&lt;/strong&gt; 通过 &lt;code&gt;before_agent_start&lt;/code&gt; 事件捕获 Pi 发给 LLM 的完整 system prompt，存到变量里。这是&amp;quot;post-chained&amp;quot;的值——即所有 extension 修改之后、实际发送之前的版本。如果命令被调用时还没有 turn 跑过，就降级用 &lt;code&gt;ctx.getSystemPrompt()&lt;/code&gt;。&lt;/p&gt;&#xA;&lt;p&gt;&lt;strong&gt;全屏覆盖层显示。&lt;/strong&gt; 用 &lt;code&gt;ctx.ui.custom()&lt;/code&gt; 打开一个覆盖层，渲染 prompt 全文。&lt;code&gt;handleInput&lt;/code&gt; 监听 &lt;code&gt;Escape&lt;/code&gt; 或 &lt;code&gt;q&lt;/code&gt; 关闭，&lt;code&gt;render&lt;/code&gt; 用 &lt;code&gt;wrapTextWithAnsi&lt;/code&gt; 处理终端宽度换行。加了宽度缓存，只在终端尺寸变化时重新计算。&lt;/p&gt;&#xA;&lt;h3 id=&#34;示例代码&#34;&gt;示例代码&lt;/h3&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;type&lt;/span&gt; { ExtensionAPI } &lt;span style=&#34;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;@earendil-works/pi-coding-agent&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;import&lt;/span&gt; { Key, matchesKey, wrapTextWithAnsi } &lt;span style=&#34;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;@earendil-works/pi-tui&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; (pi: &lt;span style=&#34;&#34;&gt;ExtensionAPI&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-weight:bold&#34;&gt;let&lt;/span&gt; lastPrompt: &lt;span style=&#34;&#34;&gt;string&lt;/span&gt; | &lt;span style=&#34;font-weight:bold&#34;&gt;undefined&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pi.on(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;before_agent_start&amp;#34;&lt;/span&gt;, (event) =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lastPrompt = event.systemPrompt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  pi.registerCommand(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;system-prompt&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    description: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;Print the current session&amp;#39;s system prompt&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handler: &lt;span style=&#34;&#34;&gt;async&lt;/span&gt; (_args, ctx) =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;const&lt;/span&gt; prompt = lastPrompt ?? ctx.getSystemPrompt();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; (ctx.mode !== &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;tui&amp;#34;&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        ctx.ui.notify(prompt, &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;info&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;await&lt;/span&gt; ctx.ui.custom&amp;lt;&lt;span style=&#34;font-weight:bold&#34;&gt;void&lt;/span&gt;&amp;gt;((_tui, _theme, _kb, done) =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;let&lt;/span&gt; cachedWidth = 0;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;let&lt;/span&gt; cachedLines: &lt;span style=&#34;&#34;&gt;string&lt;/span&gt;[] = [];&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          render(width: &lt;span style=&#34;&#34;&gt;number&lt;/span&gt;): &lt;span style=&#34;&#34;&gt;string&lt;/span&gt;[] {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; (width !== cachedWidth) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              cachedWidth = width;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;              cachedLines = wrapTextWithAnsi(prompt, width);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; cachedLines;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          handleInput(data: &lt;span style=&#34;&#34;&gt;string&lt;/span&gt;): &lt;span style=&#34;font-weight:bold&#34;&gt;void&lt;/span&gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;            &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; (matchesKey(data, Key.escape) || data === &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;q&amp;#34;&lt;/span&gt;) done();&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;          invalidate(): &lt;span style=&#34;font-weight:bold&#34;&gt;void&lt;/span&gt; { cachedWidth = 0; },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;        };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id=&#34;结果&#34;&gt;结果&lt;/h3&gt;&#xA;&lt;p&gt;输入 &lt;code&gt;/system-prompt&lt;/code&gt; 后，终端全屏显示 Pi 当前 session 的完整系统提示词：&lt;/p&gt;</description>
    </item>
    <item>
      <title>Pi Extensions 入门指南</title>
      <link>https://www.yzchen.space/posts/20260702/pi-extensions-intro/</link>
      <pubDate>Thu, 02 Jul 2026 12:00:00 +0800</pubDate>
      <guid>https://www.yzchen.space/posts/20260702/pi-extensions-intro/</guid>
      <description>&lt;h2 id=&#34;pi-的-extension-是什么&#34;&gt;Pi 的 Extension 是什么&lt;/h2&gt;&#xA;&lt;p&gt;Extension（扩展）是 Pi 的 TypeScript 插件系统，也是它可扩展性体系中最核心的一环。你可以把它理解成给 Pi 写&amp;quot;插件&amp;quot;，用来定制它的行为、添加新能力，或者接入外部系统。&lt;/p&gt;&#xA;&lt;p&gt;Pi 的哲学是&amp;quot;不把工作流强加给你&amp;quot;，所以像权限控制、子代理、plan mode 这些其他工具内置的功能，Pi 全都不做——而是把能力交给 extension，让你自己决定怎么做。写一个 extension、或者装一个别人写好的 Pi Package，就能把 Pi 调整成你想要的形状，不需要 fork 源码。&lt;/p&gt;&#xA;&lt;p&gt;具体来说，extension 能做这些事情：&lt;/p&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;注册自定义工具&lt;/strong&gt;，让 LLM 在对话中可以调用 —— 比如一个 &lt;code&gt;deploy&lt;/code&gt; 工具、一个 &lt;code&gt;todo&lt;/code&gt; 工具&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;拦截事件&lt;/strong&gt;，在关键节点插手 —— 比如 LLM 想执行 &lt;code&gt;rm -rf&lt;/code&gt; 的时候弹窗问你要不要放行&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;和用户交互&lt;/strong&gt;，弹确认框、选择列表、输入框、通知&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;构建自定义 UI&lt;/strong&gt;，从简单的状态栏文字，到完全接管编辑器，到游戏覆盖层&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;注册命令&lt;/strong&gt;，添加 &lt;code&gt;/mycommand&lt;/code&gt; 这样的自定义斜杠命令&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;持久化状态&lt;/strong&gt;，让数据跨重启存活，还能随着 session 的分支自动正确还原&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;自定义渲染&lt;/strong&gt;，控制工具调用的结果在终端里怎么展示&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;它还覆盖了 Pi 运行的完整生命周期——从 session 启动、LLM 对话开始、每个 turn、每次工具调用、到 session 关闭，几乎所有环节都可以插入逻辑。所以 extension 不光能&amp;quot;加功能&amp;quot;，也能&amp;quot;改行为&amp;quot;。&lt;/p&gt;&#xA;&lt;h2 id=&#34;快速看一眼代码&#34;&gt;快速看一眼代码&lt;/h2&gt;&#xA;&lt;p&gt;一个最简 extension 长这样。三件事：拦截危险命令、注册一个工具、注册一个命令：&lt;/p&gt;&#xA;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;background-color:#fff;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-typescript&#34; data-lang=&#34;typescript&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;import&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;type&lt;/span&gt; { ExtensionAPI } &lt;span style=&#34;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;@earendil-works/pi-coding-agent&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;import&lt;/span&gt; { Type } &lt;span style=&#34;font-weight:bold&#34;&gt;from&lt;/span&gt; &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;typebox&amp;#34;&lt;/span&gt;;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-weight:bold&#34;&gt;export&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;default&lt;/span&gt; &lt;span style=&#34;font-weight:bold&#34;&gt;function&lt;/span&gt; (pi: &lt;span style=&#34;&#34;&gt;ExtensionAPI&lt;/span&gt;) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-style:italic&#34;&gt;// 拦截危险 bash 命令&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;&lt;/span&gt;  pi.on(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;tool_call&amp;#34;&lt;/span&gt;, &lt;span style=&#34;font-weight:bold&#34;&gt;async&lt;/span&gt; (event, ctx) =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; (event.toolName === &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;bash&amp;#34;&lt;/span&gt; &amp;amp;&amp;amp; event.input.command?.includes(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;rm -rf&amp;#34;&lt;/span&gt;)) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;const&lt;/span&gt; ok = &lt;span style=&#34;font-weight:bold&#34;&gt;await&lt;/span&gt; ctx.ui.confirm(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;危险操作!&amp;#34;&lt;/span&gt;, &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;允许 rm -rf 吗?&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;if&lt;/span&gt; (!ok) &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; { block: &lt;span style=&#34;&#34;&gt;true&lt;/span&gt;, reason: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;用户拒绝&amp;#34;&lt;/span&gt; };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    }&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-style:italic&#34;&gt;// 注册一个 LLM 可调用的工具&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;&lt;/span&gt;  pi.registerTool({&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    name: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;greet&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    description: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;向某人打招呼&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    parameters: &lt;span style=&#34;&#34;&gt;Type.Object&lt;/span&gt;({ name: &lt;span style=&#34;&#34;&gt;Type.String&lt;/span&gt;() }),&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    &lt;span style=&#34;font-weight:bold&#34;&gt;async&lt;/span&gt; execute(_id, params) {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      &lt;span style=&#34;font-weight:bold&#34;&gt;return&lt;/span&gt; { content: [{ &lt;span style=&#34;font-weight:bold&#34;&gt;type&lt;/span&gt;: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;text&amp;#34;&lt;/span&gt;, text: &lt;span style=&#34;font-style:italic&#34;&gt;`Hello, &lt;/span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;${&lt;/span&gt;params.name&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;!`&lt;/span&gt; }] };&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  &lt;span style=&#34;font-style:italic&#34;&gt;// 注册一个用户可用的命令&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;&lt;/span&gt;  pi.registerCommand(&lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;, {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    description: &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;打招呼&amp;#34;&lt;/span&gt;,&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    handler: &lt;span style=&#34;&#34;&gt;async&lt;/span&gt; (args, ctx) =&amp;gt; {&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;      ctx.ui.notify(&lt;span style=&#34;font-style:italic&#34;&gt;`Hello &lt;/span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;${&lt;/span&gt;args || &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;world&amp;#34;&lt;/span&gt;&lt;span style=&#34;font-weight:bold;font-style:italic&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;font-style:italic&#34;&gt;!`&lt;/span&gt;, &lt;span style=&#34;font-style:italic&#34;&gt;&amp;#34;info&amp;#34;&lt;/span&gt;);&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    },&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  });&#xA;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}&#xA;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;放在 &lt;code&gt;~/.pi/agent/extensions/my-ext.ts&lt;/code&gt;，或者用 &lt;code&gt;pi -e ./my-ext.ts&lt;/code&gt; 立即测试。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
