为扩展添加交互和结果展示
用选择对话框、状态和结果组件展示检查过程,并为非交互模式保留可用入口。
为扩展添加交互和结果展示
工具返回了正确 JSON,终端里却不容易看出检查了哪个文件。展示层可以把相同结果整理成通知、状态或面板,但不能成为第二份业务实现。
这篇为检查功能增加文件选择和摘要展示。业务结果仍由 inspect 生成;模型调用仍使用工具返回值,用户选择则走命令入口。
本文示例固定使用 @earendil-works/pi-coding-agent@1.5.0,源码位于仓库 columns/coding-agent/examples/extension-development/。示例是独立包,先在该目录安装依赖;不需要修改专栏站点依赖。下面的命令均从这个示例目录运行。
对话与终端能力分开判断
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
import { allowedFiles, inspect } from "../src/inspect.js"
import { registerInspection } from "../src/register-inspection.js"
export default function (pi: ExtensionAPI) {
registerInspection(pi)
pi.registerCommand("lesson-pick", {
description: "选择教学文件并展示检查结果",
handler: async (_args, ctx) => {
if (!ctx.hasUI) {
throw new Error("当前模式没有对话 UI,请改用 lesson_inspect 工具")
}
const file = await ctx.ui.select("选择文件", [...allowedFiles])
if (!file) {
return
}
if (ctx.mode === "tui") {
ctx.ui.setStatus("lesson", "正在检查")
}
try {
const report = await inspect(ctx.cwd, file)
if (ctx.mode === "tui") {
ctx.ui.setWidget("lesson", [
`文件:${report.file}`,
`声明:${report.names.join(", ") || "无匹配"}`,
])
}
ctx.ui.notify(JSON.stringify(report), "info")
} finally {
if (ctx.mode === "tui") {
ctx.ui.setStatus("lesson", undefined)
}
}
},
})
}ctx.hasUI 表示宿主提供交互 UI,包含 TUI 和支持相关请求的 RPC 场景;它不等于“当前一定是终端”。状态栏和 widget 在这里额外检查 ctx.mode === 'tui',避免把终端展示方法用于不支持的模式。
npx pi --extension ./extensions/ui.ts输入 /lesson-pick,选择 fixtures/sample.ts 后应显示 greet。取消选择时直接返回,不生成检查结果。制造文件缺失后再次选择,错误应由宿主呈现,同时 finally 清除正在检查的状态。
一份结果,按使用方展示
需要自定义工具渲染时,使用 SDK 的 renderCall、renderResult 及组件接口,根据展开状态决定展示摘要还是详细内容;不要把大段源码固定输出到每次工具结果里。渲染器读 details,机器调用读 structuredContent,两者仍来源于同一个报告。
本例先用内置选择框与文本 widget,减少组件依赖。添加复杂 TUI 组件后,应单独验证键盘、窄屏、折叠和主题表现,不能用 TypeScript 编译通过替代交互检查。
无 UI 的路径也必须明确
print 或 JSON 模式不能弹出选择框,调用 /lesson-pick 会明确报错并提示工具入口。自动化调用应直接给工具参数,不等待用户输入。RPC 客户端需要实现宿主对应的 UI 请求与响应,不能仅凭 hasUI 就假设所有前端都画出了选择框。
扩展代码不要直接 console.log 写进宿主 stdout。那里可能承载 JSON 事件或 RPC 协议,一句调试文本就能破坏客户端解析。独立的审计脚本可以输出 JSON,因为脚本拥有自己的标准输出。
展示状态也有生命周期
finally 处理的是本次命令结束;切换会话、重载扩展或退出宿主时,长期 widget 和监听器还需要显式清理。本例 widget 保存最近一次展示,重新检查会覆盖;生产实现应按会话决定是否保留,并在 session_shutdown 清除相应 UI。
检查 UI 时至少走成功、取消、业务失败和无 UI 四条路径。只有成功路径可用,不能说明命令适合自动化,也不能证明状态没有残留。
依据与版本
代码以已发布的 1.5.0 包导出、类型声明和加载器为准。在线主分支会继续变化,遇到接口差异时先核对版本。