前面讲够了机制,这一集真刀真枪改一个——而且全程写实:一个不懂代码的人,怎么把一个崩溃的扩展修好。我们加载 pi 官方的 Todo 扩展,让它列一份公众号待办,结果它当场崩溃退出。不慌:看懂报错、找到根因、改一行、验证。最后还白捡一个漂亮的演示。
前面几课把”扩展怎么改”的机制讲透了。这一集换挡——真刀真枪改一个,全程写实。我不是程序员,你会看到一个不懂代码的人,遇到扩展崩溃时,是怎么一步步把它修好的。
选题挑了最简单的:Todo 待办清单扩展。它自包含、没有复杂判断,而且——你等会儿会看到——它还能反过来把 Session 章那套”账本”演活。
一、玩坏它:让官方 Todo 列一份公众号待办
pi 仓库自带一个官方示例 todo.ts。我加载它,让模型帮我列一份”写公众号”的待办清单。它欢快地加了七八条——然后当场崩溃退出:

关键那行:
TypeError: Cannot read properties of undefined (reading 'render')
at Box.render (packages/tui/src/components/box.ts)
at ToolExecutionComponent.render (…/tool-execution.ts:250)
先别慌。 报错不是骂你,是给你的线索。第一件事:顺着那几行 at 某某 (文件:行号) 往下读——它明明白白告诉你崩在哪个文件、哪一行。
二、看懂报错:双重根因
顺着栈读到源码,发现这不是”我们用错了”,是两个坑凑在一起。
根因 ①(pi 自己的):渲染工具调用时,pi 把渲染结果塞进一个容器,却没检查它是不是空:
// tool-execution.ts —— 少了一道判空
const component = resultRenderer(...);
renderContainer.addChild(component); // component 是 undefined 也照塞
根因 ②(官方 todo.ts 的):它那个决定”怎么显示结果”的函数,是个 switch,却没写兜底的 default:
switch (details.action) {
case "list": return ...
case "add": return ...
case "toggle": return ...
case "clear": return ...
// 没有 default —— 万一 action 不是这四个,函数就返回了 undefined
}
两个单独都不致命,凑一起就崩:todo 的显示函数在某种情况下返回了 undefined(②),pi 又没拦住(①),这个 undefined 一路飘到渲染那一刻 → 整个界面崩掉。
三、改一行:给它补个兜底
我不动 pi 的原文件,复制一份官方 todo.ts 成我自己的 my-todo.ts,只在那个 switch 末尾补一个 default:
case "clear":
return new Text(... "Cleared all todos" ...);
// —— 我们的改造:补一个 default 兜底 ——
default: {
const text = result.content[0];
return new Text(text?.type === "text" ? text.text : "", 0, 0);
}
一行核心思想:让这个显示函数永远返回点东西,绝不返回 undefined——pi 那个没判空的坑,就再也踩不到了。
📦 想自己跑一遍? 这一集的修复版
my-todo.ts(含上手说明、延伸练习)都在配套练习仓库: github.com/pkm365/lucid-learn-agent 先按仓库根SETUP.md把 pi 跑起来,再把my-todo.ts挂上,你就能复现这整段——包括故意把它玩崩。
这就是”非开发者也能改”的真相:你不用读懂整个代码库。你只要能顺着报错找到那一处,理解它为什么会空,补上兜底——就够了。
四、验证:它活了
加载修复版 my-todo.ts,还是那份公众号待办。这回模型连着加了七条,一次没崩:

敲 /todos,那个自绘的清单面板漂亮地弹出来——七条待办、0/7 completed:

你的第一个 extension 改造,成了。 官方示例崩给你看,你诊断出双重根因,改一行,修好,亲手验证——这一整套,你走完了。
五、白捡的演示:清单会跟着”分支”走
修好之后有个意外之喜,正好把 Session 章那套账本演活。
我在同一个会话里,先让它列了七条公众号待办;后来又让它换成”精益生产培训”的三条。于是这条历史线上,前半段是七条、后半段是三条。
现在用 /tree(会话树)跳一下位置——跳到”七条”那个点:

跳过去、敲 /todos → 七条公众号:

再跳到尾巴那个点、敲 /todos → 变成三条精益(注意上面那句 Navigated to selected point):

同一个会话、同一份代码,你只是在树里跳了一下位置,清单就当场从七条变成三条——你什么都没存。
它怎么做到的?回想 Session 章:todo 每次操作,都把当时的整份清单快照塞进那条工具结果(账本里的一条 message)。你在树里跳到哪个点,它就扫这条历史、拿到那个点最后一份快照,还原出来。
诚实一句:这不是严格意义的”重放增量”,是快照 + 最后写入者赢——但骨架就是 Session 章讲的账本 + 投影(event sourcing)。你亲眼看到了它活着的样子。
带走这一句
改一个 extension,不需要你懂整个代码库。这一集你走完的四步,就是非开发者改扩展的通用打法——① 玩坏它(让它真跑、真崩);② 看懂报错(顺着栈的”文件:行号”找到根因);③ 改一行(补上那处缺失的兜底);④ 验证(再跑一遍,崩没崩一目了然)。官方的代码也会有坑,而你,能修好它。
下一集
这一集是”捡现成的、修好它”。下一集我们从需求出发,亲手设计并做一个 pi 里没有的扩展——从”为什么要它”到”怎么算做好”,一步步走完一个非开发者的完整开发流程。