跳转至

2026-05-23 学习日志

今日主题

  • Pi 扩展机制与 Slot UI 模式
  • Pi Skill 与数据目录设计
  • Pi Agent 架构分层
  • TypeBox StringEnum Google 兼容

新增认知

Pi 扩展机制与 Slot UI 模式

  • 扩展入口是工厂函数而非类:Pi 扩展通过 export default function(pi, ctx) 声明式注册,由框架自动发现加载。
    工厂函数执行期间只能注册(on/registerTool/registerCommand),不能动作(sendMessage 等),
    因为动作方法在 bindCore() 前是桩函数。这与类继承式插件不同——扩展不需要了解框架内部生命周期,只声明自己要做什么。

  • UI 扩展是 Slot 模式而非布局模式
    Pi TUI 在几个固定位置(editor 上下方 Widget、底部状态栏、editor 组件、全屏 overlay、消息/工具渲染器)暴露扩展点,
    扩展在 Slot 中注入组件。两个扩展各自 setStatus 不会冲突(按 key 去重),但不能新增 Slot 之外的固定区域(如侧边栏)。
    深度定制只能通过 ctx.ui.custom() 全屏接管。

  • ctx 分三层继承而非单一对象:ExtensionContext(事件处理器用)→ ExtensionCommandContext(命令处理器用,
    增加 waitForIdle/newSession/fork/reload 等会话操作方法)→ ReplacedSessionContext(withSession 回调用,
    增加异步 sendMessage/sendUserMessage)。命令专用的方法不能出现在事件处理器中,
    因为从事件回调调用 newSession/fork 会导致死锁。

Pi Skill 与数据目录设计

  • Skill 加载只取 frontmatter 元数据,body 在 /skill:name 时才读取
    loadSkillFromFile 阶段剥离 YAML 头后 body 被丢弃,
    返回的 Skill 对象只含 name/description/filePath 等元数据。用户执行 /skill:name 命令时,
    _expandSkillCommand 重新 readFileSync + stripFrontmatter,
    用 XML 标签包裹后作为 system prompt 内容注入。这意味着修改 SKILL.md body 不需要热重载即可生效。

  • SKILL.md 不支持模板变量替换:Pi 遵循 Agent Skills 标准,标准中不含变量替换功能。
    {currentDate}、{projectName} 等不会被替换,会以原文进入 system prompt。
    当前日期和工作目录是 buildSystemPrompt 时硬编码注入到 system prompt 末尾,与 SKILL.md 无关。如果需要动态信息,
    skill 应指导 LLM 去读文件或执行命令获取。

  • ~/.pi/agent 多一层 agent 是为了命名空间隔离:.pi 由 package.json 的 piConfig.configDir 指定,
    agent 是 getAgentDir() 中硬编码的子目录名。设计理由:~/.pi 命名空间可能被其他工具共享,agent 子目录明确划分归属。
    项目级 cwd/.pi 不需要 agent 子目录,因为它已在仓库上下文中身份明确。

Pi Agent 架构分层

  • Agent 是纯 ReAct 循环引擎,不关心文件系统和 UI
    packages/agent 的 Agent 类只做 prompt → streamFn(LLM) → tool calls → continue 的循环,
    AgentOptions 不含 agentDir、持久化路径、工具实现等参数。
    工具执行通过 beforeToolCall/afterToolCall 回调交给外层,生命周期通过 subscribe() 暴露。
    coding-agent 通过 AgentSession 组合 Agent(非继承),
    在上面叠加扩展系统、会话持久化、压缩、system prompt 构建、TUI 等全部编码代理功能。

  • before_agent_start 是每次消息注入的网关,不是会话操作:它在用户发消息后、agent 开始处理前触发,每轮对话都会经过。
    可以用来注入选中文本上下文、修改 system prompt。而 /new 是创建全新会话(清空历史),
    触发 session_shutdown → session_start 整条链路。IDE 扩展用 before_agent_start 注入选中文本是合理选择—
    —每轮都带当前选中,不改动会话。

TypeBox StringEnum Google 兼容

  • TypeBox 枚举必须用 StringEnum 而非 Type.Union 才能兼容 Google API
    TypeBox 的 Type.Union([Type.Literal('a')]) 序列化为 anyOf 格式,
    Google Gemini 的 tool use 接口不识别。
    Pi 提供的 StringEnum(['a','b'] as const) 输出 {type: 'string', enum: ['a','b']},
    所有 Provider 通用。这是 Pi 封装层的适配代价——统一参数 schema 需要兼容最严格的 Provider。