pi-timeline-widget

实时会话时间轴 widget: 轮次/全部/统计行(可配置段码)/上次输入, 含 /timeline 命令与二级配置菜单

Packages

Package details

extension

Install pi-timeline-widget from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-timeline-widget
Package
pi-timeline-widget
Version
1.0.4
Published
Sep 2, 2026
Downloads
713/mo · 41/wk
Author
mr.time
License
MIT
Types
extension
Size
362.5 KB
Dependencies
0 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    ".pi/extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi 时间轴插件 (timeline)

显示实时会话时间轴的 pi 扩展:工具/思考/输入事件在输入框下以时间条、统计、状态形式呈现。

本项目使用 AI 开发。

界面构成

4 行信息(组成与上下位置可配置):

字母(含义) 内容
T (Turn) 轮次 当前轮/历史轮事件耗时条,ctrl+shift+←/→ 切换历史轮次
A (All) 全部 本次 session 全部事件条(压缩空闲段)
S (Stats) 统计 耗时占比 + 计数器 + 模型/思考级别/上下文占用 + ⚡token 速率 + ⚠失败计数
I (Input) 输入 上次用户输入内容(自动换行,最多 2 行)

颜色图例:工具=█(accent) 思考=░(dim) 输入=▓(success) 空闲=空格 光标=▌

布局配置

布局用有含义的单字母,- 表示输入栏位置:- 左侧在上方、右侧在下方,无 - 时全部在下方。

  • 字母:T=轮次(Turn) A=全部(All) S=统计(Stats) I=输入(Input)
  • 配置文件:项目 .pi/package_timeline.json 或全局 <agent目录>/package_timeline.json
  • 格式:{ "layout": "TI-AS" }
  • 默认:-TASI(= 全部在输入框下方)
  • 旧格式自动迁移:行号字母 abcdTASI;中文关键词 轮次全部统计输入TASI

示例:

/timeline set TASI                  # 全部在下方(等价默认)
/timeline set I-TAS                 # 输入在上, 轮次/全部/统计在下
/timeline setdir S-I                # 写入项目配置(统计在上、输入在下)

统计行内容配置(S 行)

S 行各段由 stats 配置组合:小写字母 = 一段,| = 段间分隔符,顺序 = 显示顺序 = 优先级(放不下时按序从尾部整段丢弃,不硬截断)。

示例
s 状态 ▷ 就绪 / ▶ 工具
l 图例(占比) ▓输入 ░思考72% █工具28% 3m12s
c 计数 工具×14 思考×3
r 速率 ⚡41 tok/s
n 累计 token Σ ↑12.3k ↓8.1k
e 失败 ⚠1失败
m 模型 🤖 gpt-4o
h 思考级 🧠 high
f 上下文占用 🔥 85.2k/128k
d 目录 📁 pi_test4
t 时钟 🕒 20:31:05
b Git 分支+脏计数 🌿 main+2
p 模型提供方 ◎ opencode-go
k 提示缓存读写 🔁 r45k w3k
g 缓存命中率 💿 命中75%
y 今日累计(跨会话) 📅 今日2h15m ↑120k ↓40k $0.52
$ 费用 $0.1234
0-9 自定义文本(每数字对应一段文本; = 在 S 行/标题编辑器内编辑内容) 备份中…
  • 图标主题可整体切换(icons): emoji(默认) / geo 几何单宽 / none 无图标,menu→显示设置→标签图标 或配置键 icons

  • A 行事件数超过可用列数时按时长占比自动合并相邻段(组内最长者定色): 保序、保比例、不丢尾段; 单轮全精度用 ctrl+shift+←/→ 在 T 行查看

  • 默认 slmrcf(状态/图例/模型/速率/计数/上下文);配置缺省或非法时回退默认

  • b 段每 5s 后台轮询 git status;Σ/cost 等累计值随 summary 持久化(续跑/恢复后延续)(仅该段启用时);计数符号:⇣落后 ⇡领先 +暂存 ~改动 ?未跟踪 !冲突

  • f 上下文 ≥70% 变黄、≥90% 变红;s 并行工具显示 ▶ 工具 ×N

  • 分隔符只出现在实际相邻段之间:空段(如速率尚无数据)自动跳过,不会产生悬空 ; 前导/尾随 | 不渲染;连续字母(如 sss)按字面重复

  • 写入按 key 合并,layout/stats` 互不覆盖;同样经 realpath/symlink/长度上限校验

示例:

/timeline stats s|l|c|r          # 四段带分隔(推荐写法)
/timeline stats slmrcf           # 默认档: 状态 图例 模型 速率 计数 上下文
/timeline stats st               # 只要 状态+时钟
/timeline statsdir slmcf          # 只写项目配置

命令

命令 作用
/timeline 直接弹出配置菜单(无参数时, 等价 /timeline menu)
/timeline menu 页面栈式配置菜单(包边居中):一级 显示设置/外观配色/数据与统计/通用/退出; 数字键 1-5 或 ↑↓+空格/Enter 进入, Esc 回上级, Ctrl+Shift+Z 退出全部。可带页面 id 直达: menu display / row-layout / row-stats / icons / row-title / scheme / data / insights / general / keys / lang / experimental / today(Esc 沿父链逐级返回)
/timeline on / off 显式开/关
/timeline reset [global] 恢复默认布局并清空事件;global 才删除全局配置
/timeline set <键名> [参数] [-g] 统一设置入口(菜单可设的项全部可用命令设)。键名: layout/stats/scheme/opacity/bgcolor/budget/today/icons/lang/footer/title/text/experimental/keys; 无参数=总览全部当前值; 参数 list=候选值, default=重置该层键(git --unset 语义, 回落另一层/内置默认); 默认写项目层, -g/--global 写全局。例: set opacity 30set scheme pastelset scheme delete my-idset budget cost=5,tokens=500000set keys prev ctrl+alt+pset text 0 pi |。越界值拒绝而非鉗制; 非官方语言包仍需菜单 5s 确认(命令不启用)
/timeline setdir / stats / statsdir (弃用)旧语法, 等价 set layout -p / set stats / set stats -p; 保留兼容并提示, 后续版本移除提示
/timeline cost 开启后把耗时就地注释进 tool result(模型可见)
/timeline report [md] 输出会话报告卡片(耗时占比/工具TopN/token/费用/异常调用);加 md 同时导出 timeline-report-*.md
/timeline today [now|off|<间隔>] 今日累计: 立即扫描 / 关闭刷新 / 设间隔(如 10m1h30m9s)
/timeline help 输出命令帮助卡片到聊天流
快捷键 作用 可配
ctrl+shift+left 上一轮事件 menu→通用→按键绑定 4 套方案 + 命令 set keys prev <键名>
ctrl+shift+right 下一轮事件 ✅ 同上(新组合重启后生效)
alt+m 打开配置菜单 set keys menu <键名> 或菜单自定义绑定(按键捕获)
alt+t 开/关悬浮时间轴 set keys toggle <键名> 或菜单自定义绑定
alt+z 退出全部菜单(不保存) ✅ 配置键 quitAllKey(默认改自 ctrl+shift+z: legacy 终端无法区分 shift)/set keys quitall <键名>

高级功能

配色方案(scheme + customSchemes)

  • 内置 5 套护眼配色: pastel柔和粉彩 / sunset暮色暖阳 / forest森林晨雾 / ocean深海蓝调 / inkwash水墨微彩; 缺省跟随 pi 主题(theme)
  • 可视化切换: menu→外观配色→配色方案; 支持多套自定义(菜单内 = 新建双栏编辑器, space 改键、调色盘 Enter 赋色), 列表页 space 修改、Backspace 删除
  • 自定义格式(全局或项目 package_timeline.json):
{
  "scheme": "my-id",
  "customSchemes": {
    "my-id": { "name": "我的配色", "colors": { "tool": 110, "think": 245, "input": 108 } }
  }
}
  • 角色键: tool/think/input 类型色(半格/复合条优先采用)与 dim/accent/success/muted/error/warning 角色色, 缺省回退 ANSI 常量表

透明度(opacity + bgColor)

  • SGR 无 alpha, 用"前景色向终端背景色混色"模拟视觉透明: "opacity": 60 即 60% 向底色混合, 0=实色 100=全隐
  • 底色来源: bgColor 显式配置 > COLORFGBG 环境 > 深色兜底; 仅时间轴行生效(菜单保持实色), 不支持真彩的终端量化到最近 256 色

预算告警(budget)

{ "budget": { "cost": 5, "tokens": 500000 } }

S 行 $/n 段达 80% 变黄、100% 变红并 notify 提示一次(不重复打扰)。

数据洞察(menu→数据与统计→数据洞察)

近 14 天窗口一次遍历扫描 session 文件聚合:

  • 工具性能基线: 每工具调用次数/平均/最长耗时; 本次调用超基线 3× 时 S 行标 ⚡N×、报告列出异常调用
  • 节奏分析: 24 小时活跃度 sparkline + 高峰时段 Top3
  • 会话对比: 今日 vs 昨日 / 本周 vs 上周(周一起算)的耗时/token/费用

今日累计(y 段 + /timeline today)

跨会话聚合当天全部 session 的 summary(自动跳过当前会话防双计); 刷新间隔 todayRefresh 默认 10m, 支持 h/m/s 组合(如 1h30m9s), off 关闭定时(手动 now 仍可扫)。

实验性功能(menu→通用→实验性功能, 互斥开关默认全关)

下列功能可能随时调整或移除:

  • half 左右半块×2: T/A 行每格拆左右两个子单位渲染(单位数·精度×2), 半格颜色优先用自定义方案的类型色
  • stack 上下复合条: 布局新增字母 X(仅开启时可选), 一行内叠示"上=当前轮、下=全程"两层压缩条
  • quad 四分象限: 规划中

Footer 接管(menu→显示设置→Footer 接管, 配置键 footer, 默认关)

把输入框下方的时间轴行改为渲染在屏幕最底行(pi 的 footer 槽位):

  • 渲染管线与 widget 完全同构(render(width) → 行数组), 只是换容器; 显示内容即布局配置的"输入栏以下"部分, 横竖排布局规则不变
  • 接管期间代显其它扩展的 setStatus 状态行(按 key 字母序, 与内置行为一致)——内置 footer 消失后它们不至于无处可去
  • 切换即时生效并写入全局配置; 关闭后恢复输入框下方显示, 内置 footer 照常
  • 时间轴字段已覆盖内置 footer 的信息(↑↓token/缓存/命中率/费用/目录/分支/模型/思考级/上下文), 接管几乎零信息损失

⚠️ 独占槽位 + 后写者胜(已实测):

  • pi 的 footer 只有一个自定义槽, 多扩展同时 setFooter 接管时最后写入者胜, 先前的被静默顶掉(pi 无协商/引用计数/查询机制); 任一方调 setFooter(undefined) 恢复时, 其它扩展的接管一并失效
  • 与 pi-statusline 的冲突(实测复现路径): statusline 同样在 session_start 接管 footer。安装 statusline 且本插件开启 footer: true 时, 启动注册顺序中 statusline 后写 → 它顶掉本插件的 footer; 此时输入框下方的行已被撤除、底部又被 statusline 占用 → 时间轴整体不可见(表现为"时间轴莫名消失")。菜单里把开关切一次可临时恢复(用户操作天然是"后写"), 但重开会话后仍会被顶掉
  • 选择策略(默认/推荐): 与任何接管 footer 的扩展(statusline 等)共存时, 保持 footer 关(默认), 时间轴显示在输入框下方, 底部槽位让给对方扩展; 本插件不发起"footer 战争"(不周期性重申接管、不在 dispose 里夺回)。只有确认环境里没有其它接管方时才开启接管

跨扩展接口(pi.events 共享总线)

时间轴与其它扩展双向通信都走 pi 官方的共享事件总线, 不扫盘、不写全局文件:

// 其它扩展 → 时间轴: 投递一条时间轴事件(会实时出现在 T/A 段与统计里)
pi.events.emit("timeline:event", {
  type: "tool",              // 必填: "tool" | "think" | "input"
  label: "my-plugin:fetch", // 必填; 上限 120 字符, 控制字符会被剔除
  detail: "GET /api/users", // 选填; 上限 400 字符
  id: "my-call-42",         // 选填; 工具事件携带 toolCallId 用于精确配对
});

// 时间轴 → 其它扩展: 持久化摘要落盘时广播同一快照(与会话文件 timeline-summary 同构)
pi.events.on("timeline:summary", (snap) => {
  // snap.started / snap.events[] / snap.stats{totalMs,thinkMs,toolMs,idleMs} / snap.cum{in,out,...}
});
  • 入站数据按不可信输入处理: type 白名单校验、label/detail/id 限长 + 剔除控制字符(含 BiDi), 时间轴关闭时静默丢弃
  • 多副本/会话切换安全: dormant 副本不响应入站; 重入 session_start 时先退订旧监听, 不叠加双计
  • 时间开启时事件即参与当前轮/全程统计与小时活跃度, 无其它 API 需要调用

多语言(菜单一级 → 语言 language)

⚠️ 官方语言包仅两个: 简体中文(默认)与 English(AI 翻译)。除此之外任何语言均为外部社区产物, 非官方维护、内容未经审查, 可能含错漏/乱码/恶意内容。请只安装可信来源的语言包, 启用非官方包前务必确认其内容。

  • 官方语言包: 中文(默认, 无需任何文件)与 en.json(English, AI 翻译)。官方身份不取文件自声明——只认硬编码路径 <插件包>/.pi/lang/en.json(按 realpath 比对), 语言包 JSON 里的 official / aiTranslated 字段一律不采信(自声明可被伪造)。发现来源(四类全部列出, 同名多包共存):
    1. 项目级 <项目根>/.pi/lang——仅项目受信任(isProjectTrusted())时读取,与项目配置/扩展同一策略(否则未信任仓库可用自带的 en.json 静默接管界面文案)
    2. 全局 <agent目录>/lang(即 ~/.pi/agent/lang
    3. 插件自身包内 .pi/lang--由 __filename 反推, 三种安装方式(npm 包/全局副本/项目副本)官方 English 开箱即用, 无需手动复制
    4. 注册式第三方包(见下)
  • 菜单中每条语言包都带来源标签 + 文件路径(如 Français (fr) · …/.pi/lang/fr.json), 同名包不隐式互盖; 未在菜单里手选时(配置只存 id), 启动按 项目 > 全局 > 包内 > 注册 取首个命中
  • 插件扫描 node_modules 等盘外目录——只读自己发布物内的文件, 避免越权访问非工作目录
  • 启用时复查: applyLang 重新校验大小上限并比对扫描时记录的 SHA-256; 不一致(扫描→启用期间被改)则不启用并强制下次重扫
  • 第三方语言包(作者向): 发布为一个普通 pi 包, 在自己的扩展入口把包内 JSON 登记到全局注册表, 用户 pi install 后自动出现在语言菜单(无需用户手动拷文件, 也不需插件扫盘):
// 例: 包内 .pi/lang/fr.json + 入口 index.ts
import { fileURLToPath } from "node:url";
export default function () {
  const g = globalThis as any;
  (g.__timelineLangPacks ??= []).push({
    id: "fr",                                    // 菜单 id / 文件名格式
    name: "Français",                            // 可选, JSON 内 name 优先
    file: fileURLToPath(new URL("./.pi/lang/fr.json", import.meta.url)),
  });
}
  • 外部语言包格式 { "name": "Français", "strings": { "中文原文": "译文" } }; 非官方包启用需二级确认页, 页内展示完整路径/来源/大小/词条数/修改时间/SHA-256 摘要, 并有 5 秒倒计时 + yes 二次确认——倒计时结束前 Enter 无效(防误按与自动化秒确认), 结束后需逐键输入 yes(忽略大小写)再按 Enter 才会启用; 命令 set lang 启用非官方包同样弹出 yes 输入确认; 注册式条目同样经过 symlink/大小上限/realpath/结构与注入清洗校验

⚠️ 安装语言包的安全风险(请务必阅读):

  • 语言包是在聊天界面逐词注入的第三方文本, 恶意包可伪造界面文案、诱导行为或注入终端控制序列。文本已经清洗: C0(含 ESC/TAB/CR/LF)、DEL、C1(含 8-bit CSI/OSC)、零宽与 BiDi 方向控制字符均剔除, 但清洗仍可能被绕过(如尚未归类的 Unicode 控制属性), 不构成安全保障
  • 插件对语言包做了 symlink 拒绝/大小上限/路径穿越校验/ANSI 注入清洗等护栏, 但任何本地文件都可被绕过检测, 护栏仅降低风险而非免疫
  • 非官方包启用时的二次确认仅为风险告知, 不验证内容安全性; 启用即视为自担风险
  • 建议: 仅从插件作者发布的渠道获取语言包; 安装后检查文件内容是否为纯词典映射(无 URL/ANSI/控制序列)
  • 缺失 key 自动回退中文

首次启动/升级提示

语言包开发规范

  • 包格式 <目录>/<id>.json:{ "name": "Français", "version": "1.0.0", "strings": { "中文原文": "译文" } };name 建议写该语言的原生名字(语言面板按原生名展示, 不随界面语言翻译);version 为包自声明版本号(仅展示)
  • 建议发布时提供包的 sha256 摘要(sha256sum en.json):存在多个同 id 包时, 用户可用 /timeline set lang en:<sha前缀> 精确寻址与校验

配置内 seenVersion 对比 PLUGIN_VERSION: 全新安装显示功能介绍卡, 版本落后显示更新内容卡, 相同则静默; 每进程至多展示一次, 展示即写回。

更新历史

  • v1.0.4 — 安全加固 / 快捷键扩展 / 语言包版本号
    • 安全修复: 会话条目重放转义注入——报告卡/trace 行/本轮报告卡/欢迎卡渲染入口统一经 safeEntryData 清洗(字符串剔控制字符+限长, 数值经 safeNum, 结构限深限宽); 此前 entry.dataturns/clock/count/anomalies 等字段未清洗, 篡改的会话文件或恶意扩展 appendEntry 可向 TUI 注入 OSC/CSI 转义序列
    • 安全修复: 配置原子写临时文件名可预测(仅 Date.now)存在竞态预置 symlink 写穿窗口——临时名改加随机后缀并以 wx 独占创建, 失败清理残留
    • 修复: /timeline report md 导出不再静默覆盖同名文件(存在同名时自动追加序号), 并改用原子写
    • 安全修复: keys 按键绑定加载侧补白名单校验(保存侧与 quitAllKey 原有); quitAllKey 正则收紧移除字面反斜杠
    • 安全修复: stripDisplay/sanitizePackText 补剔 U+2028/U+2029 行分隔符(此前可穿透进 trace 文本/终端标题/语言包译文)
    • 轮次总结卡模板化+渲染契约化: 每轮结束总结卡显示内容由段码模板(turnReport)决定——段码复用 S 行段码集(图例占比/计数/模型/速率/上下文/n token/$ 费用/f 缓存/e 失败/0-9 自定义文本……, 由 statsSegments 同一条流水线产出, n/$/f/e/g 等段显示轮内差值),. 换行 | 分隔,空段自动省略; 风格=pi-tui 四种内容组件仅呈现差异——text 多行文本/box 包边框卡片/markdown 渲染/truncated 单行截断(set turnstyle 切换, 默认 text, 默认模板 l|c|n|$); 卡数据与渲染均不进 LLM 上下文
    • cost 持久化开关: 耗时写入上下文(costInContext)改为持久化配置——菜单通用页/set cost on|off|default//timeline cost 均可切换, 默认关闭
    • 双列编辑器工厂化: 布局/S 行/标题/轮次总结四处共用同一抽象, 新增段码类设置零成本
    • 修复: 配置分层键级合并——此前项目层整文件优先, git 式分层后单键项目文件会遮蔽全局配置的其它所有设置(重启/新会话配置"失效"); 现全局打底+项目键级覆盖(受信任时), 损坏层跳过回落; 轮次总结模板同步支持 0-9 自定义文本段
    • 快捷键自定义扩展: 可绑定动作从 2 个增至 4 个(新增「打开配置菜单」默认 Alt+M、「开/关悬浮时间轴」默认 Alt+T); 按键绑定菜单新增**「自定义绑定」页, 逐动作按键捕获**即可改绑任意键(pi-tui parseKey 归一化, 兼容 legacy/kitty 协议); Backspace 恢复默认, 冲突提示; 护栏: 裸可打印键不可捕获(需 Ctrl/Alt/Shift 修饰或 F1-F12)
    • 语言包版本号: 包格式新增可选 version 字段(包自声明), 非官方包确认页展示; 仅信息性展示, 不参与任何信任判定; 官方 en.json 自带与插件同步的版本号
    • 命令系统统一化(set 注册表): /timeline set <键名> [参数] 覆盖全部菜单设置项(layout/stats/scheme/opacity/bgcolor/budget/today/icons/lang/footer/title/text/experimental/keys); 无参数=总览, list=候选, default=重置该层键(git --unset 分层回落); 默认写项目层, -g 写全局(git config 惯例); 旧语法 set 布局串/setdir/stats/statsdir 保留兼容并提示弃用; 抽象化为设置项描述符注册表(SET_DEFS), 新增设置项只需追加一个描述符(解析/默认/候选/当前值内聚), 命令/补全/总览自动获得; 越界值拒绝而非鉗制; 非官方语言包仍需菜单 5s 确认, 命令不绕过
    • 测试拆分: timeline.test.mjs(61 项)按域拆为 render/stats/config/security/misc 五个独立文件(共享 extract.mjs 抽取管线), 支持选择性运行 node tests/<域>.test.mjs; npm test 仍全量运行
    • 性能优化: 渲染按需化——时间轴五行组件与 S 行段内容都只构建实际显示的部分: 布局未使用的行(T/A/S/I/X)不再做轮次分割/统计/画条/点行, S 行/终端标题/轮次总结卡按各自配置串只构建被消费的段, 未配置段的遍历与外部读取(思考计数/工具基线/模型名/提供方/上下文占用/今日累计/自定义文本 0-9 等)全部跳过(每帧少算一半以上段); 输出与之前逐字一致, 纯省 CPU 与数据访问
    • 修复: 参数补全契约——pi-tui 会用候选 value 整体替换参数区且不自动加空格, 此前二级候选只输出裸 token(/timeline set lan+Tab 会变成 /timeline lang); 现所有候选输出补全后的完整参数串, 精确命中键名后直接进值候选层, 旧语法补全直接给新规范形式; 逐级候选均带说明
    • 修复: 补全弹出不及时——pi-tui 仅在弹窗已开时响应空格刷新且空格不在自动触发字符集内; 补全函数改为 slash 语境下永不返回 null(任何键入阶段都回退到最接近的候选列表), 键入 set 后按空格立即弹出全部键名, /timeline 后按空格立即弹出全部子命令
    • 修复: 未知子命令不再静默切换时间轴开关(打错子命令会静默开关时间轴的 else 陷阱)——改为报错提示可用命令
    • 中途打断(steer)输入独立成轮开关(默认关闭=归入当前轮, 时长连续统计): 菜单通用页与 set steer on|off 切换; steer 标记随会话持久化, trace 行标签区分「追加输入」
    • 语言面板原生语言名: 候选显示语言包自述的原生名字(如 Français/中文), 不随当前界面语言被翻译; 语言包 name 缺失时回退 id
    • 语言包 sha 寻址与 yes 确认: 同 id 重名包用 id:sha256前缀 精确区分; 配置记录包文件路径重启精确恢复(applyLang 沿用 sha TOCTOU 复查); 非官方包启用需二次输入 yes(忽略大小写)——菜单确认页倒计时后逐键输入, 命令路径弹输入框确认
    • 菜单层级调整: 语言设置从「通用」二级页提升至一级菜单直达(/timeline menu lang 不变); 通用页保留 按键绑定/实验性功能
    • 修复: 轮次总结子菜单 Esc 无法退出(Esc 返回值未处理循环原地重开)——补 Esc 返回上级处理; 弹窗加宽(64→84)并在标题/提示中显示上级菜单
    • 修复: 轮次总结风格注册表引用残留(安全加固): 渲染契约化时 TURN_STYLES 定义已删却留 3 处引用(菜单轮次总结页/配置加载/保存), 未定义标识符使配置加载在 turnStyle 判定处抛 ReferenceError, 其后的配置项(costInContext/titleConfig/customTexts 等)全部静默失效; 现统一切换 TURN_CONTRACTS 白名单判定, 并补齐 markdown 渲染契约缺失的主题派生函数(markdownThemeFrom, 14 个必填字段由 pi 主题色派生, theme 缺失兜底原文)
    • 快捷键默认巩固: 「打开配置菜单」默认键保持 alt+m——曾试用 ctrl+shift+m, 但 legacy 终端(未开 modifyOtherKeys/kitty 键盘协议)下与 ctrl+m(回车)同字节不可区分, 回退 alt+m; 此前用户配置的 ctrl+f 会抢占 pi 内置 tui.altScreen.search(启动告警), 已同步改回 alt+m; 显式配置仍按白名单加载生效
    • 轮次总结设置菜单整合: 总览页(数据与统计 → 轮次总结)收敛为「开关 + 两个入口」——开关(切换)与进入子页(导航)分行, 渲染样式入口直接显示当前值; 互斥样式选择移入独立子页(turnreport-style, ●/○ 单选, 选中即保存并返回), 模板编辑维持双列编辑器子页; 每页单一操作模型, 消除开关/互斥/导航三种操作混排
  • v1.0.3 — 修复 / 功能 / 结构
    • 修复: /timeline 在部分设备上不出现于 / 命令补全列表(命令注册晚于 pi 烘焙补全表的时机; 现为 session_start 内同步注册)
    • 修复: /timeline cost 的耗时就地注释失效(safeHandler 吞掉了 handler 返回值, pi 的 tool_result 靠返回值修改结果; 现改为透传)
    • 修复: 聊天流时间戳行**"开始"与"完成"堆在工具块之后同时出现**(时间戳本身正确, 是渲染位置错)。根因: pi 在流式期间(message_update 见到 toolCall)就把工具组件加入聊天, 而"开始"行等 tool_execution_start 才写入——此时流已结束, 条目只能追加到聊天末尾即工具块后。修复: "开始"行提前到首见 toolCall 即写入(此时插在流式文本之前=工具块之前, 按 toolCallId 去重); 统计时长仍以 tool_execution_start/end 精确配对, 不受影响
    • 修复(续): "开始"行与 tool_execution_start 双写(traced 返回值混入"已写过"与"没写成"两种含义)导致每条调用两条开始行; 现开始行仅由 traceToolStart 单出口写出(统一带 toolCallId 前 8 位 detail)
    • 不可信输入加固: 会话文件数值有界、事件时长/时间戳上限(防小时切桶循环放大卡死 UI); 模型名/分支名/工具名/上次输入等上屏前统一剔控制字符
    • Footer 接管(menu→显示设置): 输入框下方时间轴可移至屏幕最底行, 并代显其它扩展 setStatus(见「Footer 接管」章节); 默认关, 会话内切换即时生效
    • 聊天流本轮报告卡: 每轮结束总结总耗时/工具调用次数与耗时 top3/思考时长(起点锚 before_agent_start, 重试不重置窗口; TUI-only 不进上下文)
    • 终端标题设置(menu→显示设置): 与布局编辑器同款双列 UI, 字段直接复用 S 行段码与渲染内容(剥 ANSI 取纯文本); 无 pi 前缀与分隔符, 字段按配置串直接拼接
    • 自定义文本字段: S 行与终端标题新增段码 0-9(每数字对应一段用户文本, 原样显示); 编辑器内按 = 进入自定义菜单(0-9 列表, 空格/Enter 编辑, 配置键 customTexts)
    • 菜单层级重组 + 框架重写: 一级收敛为 显示设置/外观配色/数据与统计/通用 四组; 菜单框架重写为页面栈导航(Esc 统一弹出/退出全部键统一关闭, 消除各级硬编码返回目标); /timeline menu <页面id> 直达任意设置页
    • 节奏分析精度翻倍: 每小时拆 2 列(半小时桶, 宽度×2)×6 行(12 级半格, 高度×2)直方图, 小时标尺与柱列一一对齐
    • 退出全部键默认改为 alt+z(原 ctrl+shift+z 在 legacy 终端与 ctrl+z 同字节无法区分, 按下无反应; 配置键 quitAllKey 仍可自定义)
    • trace 行的 toolCallId 展示扩展到前 10 位(降低长会话前缀碰撞概率; 配对仍用完整 id)
    • 跨扩展接口: 其他扩展可经 pi.events 双向对接时间轴(投递事件/消费统计快照, 见「跨扩展接口」章节)
    • 语言包: 官方 English 改为从插件自身包内 .pi/lang 发现(npm 安装后开箱即用, 不扫 node_modules 等未授权目录); 新增注册式第三方语言包 API(globalThis.__timelineLangPacks)
    • 语言包安全: 官方身份改由硬编码路径判定(不再采信文件自标 official/aiTranslated); 注入清洗扩展到 C1/DEL/零宽/BiDi 控制字符
    • 语言包菜单: 同名多包共存并逐条标注来源路径; 非官方包确认页展示路径/大小/词条数/修改时间/SHA-256, 并需 5 秒倒计时结束才能确认
    • 启动优化: 今日累计扫描与欢迎卡延至注册之后, 不再阻塞启动与补全表烘焙
  • v1.0.2 — 功能 / 渲染与结构
    • 渲染优化: 布局配置预编译缓存(热路径不再逐帧解析); 每帧仅一次轮次分割与窗口计算(T/S/X 行共享, 去除逐行重复扫描)
    • 结构整理: 段布局算法(最短相邻段合并 + 最大余数列宽分配)提取为共用工具, 去除两处重复实现
    • 数据洞察: 工具性能基线/节奏分析/会话对比(menu→数据洞察)
    • 统计行菜单重构 + 标签图标主题(emoji/几何/无)
    • 实验性: 左右半块×2 / 上下复合条 X(menu→实验性功能)
    • 多语言语言包(menu→语言 language); 预算告警 / report 报告导出
  • v1.0.1 — 初始发布

核心逻辑

pi 事件 ──采集──▶ TimelineEvent 内存环形缓冲(MAX 200)
                    │  type: input|think|tool, startTime/endTime, toolCallId
                    ├─ 轮次切分 splitTurns: 每个 input 事件为新轮起点
                    ├─ 统计 statEvents: think/tool 区间并集 unionMs(并行工具不重复计时)
                    ├─ 渲染 renderTimeline: 按布局字母取行, widget factory pull 模式
                    │     (真实 width + theme.fg 语义色, 超宽 truncateToWidth)
                    └─ 持久化 persistSummary: agent_settled 时写全量快照(脏标记防重复),
                          崩溃最多丢当前轮; 启动时恢复最新一条并做 schema 校验

事件采集映射

pi 事件 记录
input 用户输入(记为新轮起点;跳过 / 命令与扩展注入)
agent_start / agent_end 思考事件开/合(深度计数,覆盖自动重试/压缩重试/续跑)
tool_execution_start / end 工具事件开/合(按 toolCallId 精确匹配,并行工具按完成顺序结束)
agent_settled 闭合所有遗留事件、清零深度、增量持久化
message_start / update / end 输出速率 ⚡tok/s(字符增量 EMA 估算 + usage 精确回填)与会话累计 Σ
session_tree 切分支后按新分支 entries 重建时间轴
session_shutdown 最终持久化 + 清理 widget

关键设计

  • 区间并集统计:并行工具/思考时间重叠时只计一次,百分比基于活动时间(think+tool)。
  • 双副本去重:扩展按 session 加载,全局副本与项目副本可能同存;按 __filename 识别身份, 非全局副本在模块加载期登记全局标记;session_start 时同步判定活动权 (全局副本发现存在项目/npm 副本即让位 dormant),保证进程内恰一份激活。
  • 注册时机(勿回退): pi await emit(session_start) 后即一次性烘焙补全 provider (getRegisteredCommands 快照, 仅在 session 切换/reload 时重建)。因此命令/渲染器/快捷键 注册必须在 session_start handler 内同步完成: 延到 setImmediate/微任务会导致 "命令可执行(派发实时查表)但补全表永远没有它";重 IO(如今日累计全盘扫描)同样不得挡在注册前。 tests/registration-order.test.mjs 对此有静态断言守护。
  • 安全:
    • 项目配置仅 isProjectTrusted() 为真才读取;文件 >8KB 或为 symlink 跳过;
    • 项目级语言包目录同样受信任门控(v1.0.3), 未信任仓库不能注入界面文案;
    • 布局写/删前做 realpath 目录校验(目标目录必须落在 root 内)+ 叶子 symlink 检查,防投毒仓库写穿;
    • 恢复数据 schema 校验 + label/detail 长度截断,防外部条目污染渲染。
  • 不可信输入加固(session 文件可由任意扩展 appendEntry 写入, 故视为外部输入):
    • 数值走 safeNum: 非有限/负数/超 1e15 归零(防 Infinity 污染统计与渲染);
    • 事件时长与 started 时间戳有界(MAX_EVENT_MS = 1 天, started 不得晚于当前时间+1 天)—— 避免恶意超长区间把按小时切桶的循环放大到上亿次迭代卡死 UI, 也防日桶表无界增长;
    • 上屏文本 sink 统一 stripDisplay(剔 C0/C1/DEL/零宽/BiDi): 模型名/提供方/目录名/ 思考级别/工具名与事标签/分支名/上次用户输入(可来自扩展注入或 RPC 消息)。
  • 渲染容错:widget render 抛异常返回空行,不影响 TUI;持久化/配置读写失败静默回退默认。

目录结构

.pi/
├── extensions/timeline.ts   # 插件本体(全局副本 = ~/.pi/agent/extensions/timeline.ts, 同步同版)
└── types/shims.d.ts         # 本地 LSP 类型 shim(运行时由 pi 的 jiti 解析真实模块)

package_timeline.json        # 布局配置(项目/全局各一份, 项目优先且需项目信任)

说明

  • trace 条目(timeline-trace)与 summary(timeline-summary)均为 custom entry,不参与 LLM 上下文,仅 TUI 渲染与崩溃恢复。
  • 事件环形缓冲固定 200 条,最旧事件被丢弃;恢复/展示以此为上限。

核心逻辑测试

npm test                      # 或分别: node tests/timeline.test.mjs / node tests/registration-order.test.mjs
  • 机制: tests/extract.mjs.pi/extensions/timeline.ts 运行时抽取被测函数(每次运行重新生成到 tests/.gen/,保证测当前源码、不漂移),闭包依赖(t/experimental/timelineEvents/viewOffset 等)注入为 stubs.*,测试可直接改写桩以切换场景。
  • 覆盖: 布局解析(normalizeLayout/buildLayoutTokens/normalizeStats 含旧格式迁移与非法输入)、轮次/窗口(splitTurns/windowOf/lastEventEnd 含 agent 运行态)、统计(statEvents 并行去重/窗口裁剪/百分比)、段布局算法(mergeShortestSegments/largestRemainderWidths 保底与总宽)、渲染输出(renderBar 压缩/绝对/视口裁剪/光标/input 标记、renderBarHalf 半格、renderStackedBar 双层、renderLineA/B)、配色(resolveScheme/parseCustomSchemes/256 色与 ANSI 回退)、格式化工具(fmtDuration/fmtK/parseDurationSec/charWidth/displayWidth 等)。
  • 注册时序回归(registration-order.test.mjs): 对源码做静态断言——session_start handler 内不得用 setImmediate 包裹 setupSession、命令注册前不得有 refreshToday 重 IO、加载期非全局副本标记存在。防止"命令可执行但不在 / 补全列表"的回退。
  • 边界: 依赖 pi 运行时的事件流、菜单交互、/timeline 命令、文件持久化等集成部分无单测(需在 TUI 中人工验证)。
  • 新增函数: 若函数不引入新的闭包状态引用,自动被抽取;若引用了新的模块级状态,在 tests/extract.mjsMAP 注入表中补一条替词即可;状态声明类常量加入 CONSTS/LETS 列表。

安装与发布

  • 安装: pi install npm:pi-timeline-widget(全局);pi install -l npm:pi-timeline-widget(写入项目 .pi/settings.json, 可随仓库共享);试用不落盘:pi -e <路径>
  • 发布: 仓库根 package.json 即发布物(files 白名单 = .pi/extensions + .pi/lang; 悬空型引用运行时无影响, 故 .pi/types 不入包)。完整流程:
npm test                       # 1. 回归: 47 项断言须全绿
npm login                      # 2. 登录 npm 账号(maintainer: mr.time)
npm pack --dry-run             # 3. 复核 tarball(应含 timeline.ts / en.json / LICENSE / README / package.json)
npm publish                    # 4. 发布(版本号须高于 npm 上已存在版本)
  • tarball 内容: .pi/extensions/timeline.ts(插件本体) + .pi/lang/en.json(官方英文语言包) + LICENSE(MIT) + README + package.json;.git/tests//.pi/types/ 均不入包
  • 版本约定: package.json version 与插件内部 PLUGIN_VERSION 保持一致(当前 1.0.2), 否则新装用户会看到"更新卡"而非"介绍卡"
  • 去重: 进程内按语义化单例(Symbol.for("pi-timeline-widget.active"))去重——全局/项目/npm 包副本任意组合共存时恰一份激活, 且保留"项目优先于全局"的约定
  • 安全: pi 扩展以用户全权运行; 本插件仅读取时间轴事件与 git status, 不执行任意代码