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。