pi-proactive

给 Pi 增加主动性:空闲时主动推进或提问、按时间点主动发起任务、多个会话之间对话协作。(专有软件:分发物不含源码,使用条款见包内 LICENSE)

Packages

Package details

extensionskill

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

$ pi install npm:pi-proactive
Package
pi-proactive
Version
0.2.0-beta.2
Published
Oct 29, 2026
Downloads
293/mo · 293/wk
Author
pk09
License
SEE LICENSE IN LICENSE
Types
extension, skill
Size
2.1 MB
Dependencies
1 dependency · 2 peers
Pi manifest JSON
{
  "skills": [
    "./skills/pi-proactive"
  ],
  "extensions": [
    "./build/extensions/proactive.js"
  ]
}

Security note

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

README

pi-proactive

给 Pi 增加「主动」能力:空闲时主动推进或提问、按时间点主动发起任务、多个会话之间对话协作。

本包是 Pi 的一个扩展包(pi-package),加载后提供:

  • /proactive 命令面:查看状态、开关主动性、排期任务、取消任务;
  • schedule_task / message_agent / list_peers 三个模型工具:模型可以自己排活、给另一个会话发消息、先查清有哪些对端在线/空闲/擅长什么再发(list_peers 只读);
  • 跨进程协作层:同机 Unix socket 实时通道 + 文件邮箱兜底 + 消息去重。

许可与分发(专有)

本软件是专有软件(proprietary),非开源:源码不公开,分发物只含编译后的 JavaScript 产物。

  • 许可:package.json 的 license 为 SEE LICENSE IN LICENSE,协议正文 = 本包内的 LICENSE 文件(版权所有 / 仅限内部授权使用 / 禁止再分发 / 禁止反编译与再授权 / 授权期限与联系)。
  • 分发物不含任何 .ts 源码:安装包里只有 build/(编译产物)、skills/、README.md、CHANGELOG.md、LICENSE。
  • 内部分发走 tarball(tools/release-pack.sh 产出 + sha256);安装必须先解包再以目录方式装(直装 .tgz 会「显示成功」但宿主加载失败 —— 实测陷阱)。
  • 发布前必须跑 bash scripts/ci/publish-preflight.sh --registry <url> --public(检查私有协议 / 零 .ts 源码 / 无内部路径与文档 / 入口在包内)。

安装

方式 A:私有 npm 源(组织 scope,跨机器可用)

pi install npm:pi-proactive
  • 当前发布:0.2.0-beta.1(dist-tag beta / latest),integrity sha512-q+fSY0OvPp8BeH1MGWSWqkhoVIjmZbQmd2ry5sPAQsqL8pvAQs0i1VrrAaLvy6Tds1l3fS01yBm/XzTl2bS9mQ==
  • 校验下载到的制品是否与发布一致:npm view pi-proactive@0.2.0-beta.1 dist.integrity
  • 供应链缺口(如实披露):本次发布未带 npm provenance(本地发布无法生成;发布工作流模板见 scripts/ci/publish-npm.yml(复制到 .github/workflows/ 后生效,需 GitHub token 的 workflow scope),后续版本将从 CI 发布并自动带 provenance)。

方式 B:本地目录 / tarball(离线或内部试用)

pi install ./pi-proactive          # 本仓库内
bash pi-proactive/tools/release-pack.sh --out ./dist   # 产出**全量交付束**:tarball + sha256 + 交付目录 + zip + manifest,并跑四项自检(新鲜度/内容/校验记录值/zip 一致)
pi install ~/.pi/packages/pi-proactive   # 先解包再按目录装(见下)

宿主会把安装源记成相对路径 → 请把包放在稳定目录里、不要移动它。

# 作为 pi 包安装(写入 ~/.pi/agent/settings.json)
pi install ./pi-proactive

# 本地试用:只加载扩展本体,不写入设置
pi --extension ./pi-proactive/extensions/proactive.ts

宿主自带的包(@earendil-works/pi-coding-agent、@earendil-works/pi-agent-core、typebox)只声明为 peerDependencies("*"),由宿主提供,不随本包安装(Pi 文档明确:把宿主包放进 dependencies 会产生物理副本、绕过扩展模块映射,导致重复类与重复注册)。

版本约束:本扩展的全部宿主 API 断言只对 @earendil-works/pi-coding-agent 1.3.0 验证过。 package.json 的 engines.pi 写作 >=1.3.0(最低可用版本),但更高版本未验证 —— 升级 pi 后可能出现行为变化,升级后请先重跑 runtime 探针再依赖本插件的自动行为。

5 分钟快速上手(内测版)

24×7 数字员工(常驻形态,默认关闭)

需要一个人不在场也在干活的常驻代理时再开(它会真花你的账号额度):

# 安装(幂等;macOS 用 launchd,Linux 用 systemd --user)
bash <安装目录>/tools/daemon-install.sh

# 看看它昨晚干了什么 + 有什么等你确认
/proactive doctor
  • 默认 enabled=false + 干跑 → 装了不花钱、不投递任何任务;
  • 开启后仍受预算硬上限约束(每小时/每日动作数;token 只做估算)→ 超限自动降级为只观察并记账;
  • 不可逆动作绝不自动执行 → 进人工确认队列,早间交班时呈现;
  • 形态:macOS launchd(KeepAlive)/ Linux systemd --user(Restart=always);
  • 自愈:崩溃自动重启、幂等认领、半成品不重放;卸载用 tools/daemon-uninstall.sh(幂等、无残留)。

面向第一次拿到内测包的同事,按顺序做这 5 步就能看到插件真的主动一次。

步骤 1|装什么、装在哪

内测分发物是 tarball + sha256 校验文件(不是 git 仓库路径):

# 拿到 tarball 后先校验
cd <tarball 所在目录>
shasum -a 256 -c pi-proactive-<版本>.tgz.sha256

# 解包到一个**稳定目录**(内测包与仓库无关,但目录本身别乱挪 —— 见下面的说明)
mkdir -p ~/.pi/packages && tar -xzf pi-proactive-<版本>.tgz -C ~/.pi/packages
mv ~/.pi/packages/package ~/.pi/packages/pi-proactive

# 以 pi 包的形式安装(写入 ~/.pi/agent/settings.json)
pi install ~/.pi/packages/pi-proactive

为什么强调「稳定目录」:pi install 会把安装源记进 settings.json 的 packages, 记的形式是相对 settings.json 所在目录的相对路径(宿主行为,见 docs/packages.md)。 所以只要包目录本身不挪动,仓库怎么移动、改名都不影响; 但如果把包解到 /tmp 这类会被系统清理的位置,装完过一阵就会失效。 建议固定用 ~/.pi/packages/pi-proactive。

也可以直接从源码目录装(开发场景,注意源码目录挪动后要重新 pi install):

pi install ./pi-proactive            # 相对路径:相对 settings.json 所在目录解析
pi install /abs/path/to/pi-proactive  # 绝对路径:宿主会换算成相对 settings.json 的相对路径

自己打内测包:bash pi-proactive/tools/release-pack.sh --out <产物目录> → 产出 tarball + .sha256 + 交付目录(README / SKILL / ops / onboarding / LICENSE / CHANGELOG / DELIVERY / tools / tgz / sha256)+ zip + manifest.json(逐项 sha256 与大小);幂等:同输入同字节。 脚本自检四项,任一不符即非零退出:① 交付目录内的 tgz 与本次构建同哈希 ② 包内含 src/peer-spawn.ts 等当前能力文件 ③ .sha256 记录值与实算一致 ④ zip 与交付目录逐项一致。 复验既有交付束(不重新打包):bash pi-proactive/tools/release-pack.sh --check-only --out <产物目录>。

步骤 2|怎么确认装上了

pi list          # 列表里应出现 pi-proactive

再开一个会话,执行下面这条命令,有回执就算装上了:

/proactive status

步骤 3|看一次状态(先看懂再动手)

/proactive status

输出是 7 行「人话 + 机器可读字段」,例如:

主动性:已开启 (enabled=true)
空闲触发:关 (idleTriggerEnabled=false)
空闲检测:关闭 (idleDetect=off)
空闲计时:—(未启用) (idleForMs=1823)
提问超时:2m (askTimeoutMs=120000)
限制:最短间隔 5m (minGapMs=300000) · 每小时上限 6 (maxPerHour=6) · 静默时段 未设置 (quietHours=无)
下次空闲触发:—(空闲触发未开启) (nextTriggerInMs=—)

最后一行会说明为什么触发 / 为什么没触发(排障先看这一行)。

步骤 4|让它第一次真的主动(两处都要改)

装上后插件默认不打扰(idleTrigger.enabled=false)。要看到第一次主动,需要两个开关:

# (a) 打开空闲触发(内存态:只对当次进程有效,重启后回到配置文件里的设置)
/proactive idle on
#    ↳ 这一步同时会把 idleDetect 从 off 切到 input-only,否则开了也不会真的触发

# (b) 把阈值调小(默认 10 分钟太久,等不到)——改配置文件后重启会话
#     <agent-dir> = $PI_CODING_AGENT_DIR,未设置时是 ~/.pi/agent
cat ~/.pi/agent/proactive/config.json

把 config.json 改成(只改这两处,其余字段保留):

{
  "idleTrigger": { "enabled": true, "afterMs": 30000 },
  "limits": { "minGapMs": 60000 }
}
  • afterMs: 30000 = 空闲 30 秒就够触发(默认 600000 = 10 分钟);
  • minGapMs: 60000 = 两次主动之间至少隔 1 分钟(默认 300000 = 5 分钟)—— 不调它,即使阈值到了也会被冷却挡掉(判定说明会显示「冷却中」)。

改完重启会话,再 /proactive status,看 nextTriggerInMs 在往下走 → 到了就会主动推进一次。 想立刻自检链路,可以用 /proactive test(强制投递,绕过冷却与频率上限)。

步骤 5|怎么关掉

/proactive off        # 关闭主动性总开关(已落盘,重启后仍生效;已排期任务保留)
/proactive idle off   # 只关空闲触发(内存态)

/proactive on 可以再打开。彻底卸载见下面「卸载与回滚」。


典型场景脚本(可直接复制)

在 Pi 会话里直接粘贴命令;# 开头是注释,可以一起粘。

场景 1|空闲时主动汇报进度(最常用)

# 打开空闲触发并把阈值压到 30 秒(内存态部分 + 配置文件部分见「步骤 4」)
/proactive idle on

配置里设:

{
  "idleTrigger": {
    "enabled": true,
    "afterMs": 30000,
    "intent": "wake",
    "prompt": "用户已空闲,请用一句话汇报当前进度,并给出下一步建议。"
  },
  "limits": { "minGapMs": 60000, "maxPerHour": 6 }
}

效果:用户停手 30 秒后,插件自己起一轮,把这段提示词投给模型,模型回一句话。 恢复安静:/proactive idle off。

场景 2|定时提醒(喝水 / 站起来 / 收工)

/proactive in 30m 起来活动一下,喝口水
/proactive at 18:30 该收工了,保存一下今天的进度
/proactive every 2h 看一下构建有没有挂
  • in <时长> <内容>:时长后触发一次(30s / 10m / 2h / 1d / 纯数字按秒);
  • at <HH:MM> <内容>:到当天该时刻触发(已过则顺延到明天);
  • every <时长> <内容>:按间隔重复。

查任务 / 取消:

/proactive tasks              # 看 id、排期类型、相对到期、intent、归属、开关
/proactive cancel <id>        # 取消(id 从上一条里抄)

⚠️ 定时任务与主动触发共用同一个冷却闸门:默认 minGapMs=300000(5 分钟), 到点投递可能顺延最多约 5 分钟。要准点,把 limits.minGapMs 调小。

场景 3|跨会话提问与回复(两个会话之间)

会话 A(发起方)直接跟模型说:

用 message_agent 工具给会话 "builder" 发一条 kind="ask" 的消息:
标题「进度同步」,正文「你那边跑到哪一步了?还剩什么没做完?」。

会话 B(对端)会收到提问,模型回答后自动回送一条 reply(携带原 correlationId)。

  • 手工点名也可以:在会话 A 里执行 /proactive peers 看在线 peer 的名字与 agentId;
  • 对端必须也装了本插件,否则消息不会被消费;
  • 对端离线也能发:消息落盘到对端邮箱,等对方以同一 sessionId 重启后取回 —— 这种情况下回执是 queued(已落盘、尚未送达,不是「已送达」)。

场景 4|让模型自己排活(schedule_task 工具)

跟模型说:

帮我把「每 30 分钟检查一次 CI」排成一个重复任务,用 schedule_task 工具,when 传 30m。

模型会调用 schedule_task 真正落库(进任务表、进程退出后仍在),到期由调度器投递。

场景 5|只看不改(值班/观察模式)

/proactive status             # 总览 + 为什么触发/为什么没触发
/proactive tasks              # 待触发任务
/proactive peers              # 在线 peer、心跳、当前任务

只读这三条不会产生任何投递动作。


卸载与回滚

停用(推荐先做这步)

/proactive off        # 主动性总开关关闭(已落盘);已排期任务保留在任务表里

彻底卸载(三步)

# 1) 卸包
pi remove <安装时用的包路径或包名>
#    或直接编辑 ~/.pi/agent/settings.json 的 "packages" 数组,删掉 pi-proactive 那一项

# 2) 删运行期数据目录(可选,但这是「彻底」)
#    数据目录 = <agent-dir>/proactive/
#    <agent-dir> = $PI_CODING_AGENT_DIR,未设置时是 ~/.pi/agent
rm -rf ~/.pi/agent/proactive          # 先确认路径!别手抖删错目录

# 3) 确认
pi list                                # 列表里不应再有 pi-proactive

数据目录在哪、删了会丢什么

数据目录:<agent-dir>/proactive/(默认 ~/.pi/agent/proactive/),目录权限 0700、文件 0600。 删掉它 = 删掉本机全部运行期数据:

删掉的东西 后果
tasks.json 所有排期任务消失(不可重建:排期只有这一个来源)
mailbox/ 未处理 / 待重试 / 已归档的消息全部消失(对端可能一直在等这些消息的回复)
processed/(含 seen.json) 去重账本消失 → 窗口内的重复消息会被重新处理(不丢消息,但会重复动作)
threads/ 线程日志(审计 + 轮次计数)消失 → 轮次上限判定失准(可能多跑几轮)
agents/ 在线名单消失 → 对端暂时找不到你(重新启动会话会重新注册)
config.json 配置回到默认值(enabled=true、idleTrigger.enabled=false)
run/*.sock socket 端点(进程退出时会自动清理;僵尸端点下次启动会重建)

只想静音、不想丢数据:只做 /proactive off,不要删数据目录。

回滚到旧版本

# 1) 备份当前数据(回滚前必做)
cp -a ~/.pi/agent/proactive ~/.pi/agent/proactive.bak.$(date +%s)

# 2) 装回旧版
pi install <旧版本包目录>

# 3) 重启会话,用 /proactive status 确认读到的是旧版配置

# 4) 若旧版读不懂新版数据(新版写了旧版不认识的落盘版本号)→ 恢复备份
#    rm -rf ~/.pi/agent/proactive
#    cp -a ~/.pi/agent/proactive.bak.<时间戳> ~/.pi/agent/proactive

当前落盘格式版本是 v1,没有 version 字段的文件按 v1 读(升级不丢数据), 所以在版本号不变的前提下,回滚不需要动数据。


反馈渠道(内测)

报给谁:直接报给发给你这个 tarball 的同学(内测发包人);也可以在本仓库开 issue 记录。

请一并带上这三样(缺一样都很难复现):

  1. 版本:pi-proactive 的版本号(tarball 文件名里就有,如 0.2.0-beta.1);
  2. /proactive status 的完整输出(它含 enabled / idleDetect / minGapMs / maxPerHour / quietHours / nextTriggerInMs 与「为什么触发/为什么没触发」的判定说明);
  3. 宿主版本:pi --version(本扩展的宿主 API 断言只对 pi 1.3.0 做过验证)。

再附上(有则更好):复现步骤、期望 vs 实际、插件 stderr 里带 [proactive] 前缀的日志片段、 <agent-dir>/proactive/ 下相关文件(如 tasks.json、某个信封)。


内测边界(先知道这些,免得误判)

这是内测版,以下四条是当前已知边界,不是待修的 bug 清单:

  1. 跨机行为未验证 —— 跨主机(共享目录 / NFS)只有落盘排队(约一个轮询周期,秒级), 掉线或过期都可能导致消息不被处理。内测期请限定同机使用。
  2. 无鉴权 —— 威胁模型是「同 UID 可信」:同 UID 的任意进程可以往邮箱写文件并冒用 from。 本插件不是安全边界,不要用它防御同机其它进程。
  3. TUI 完整链路未验证 —— 真人按键下的「提问 → 回答 → 回填」完整闭环只做了部分场景抽验 (见 docs/review/tui-smoke.md 自述的 7 项未验证项);rpc / json / print 等非交互模式的降级行为有实测。
  4. 定时投递可能被 5 分钟冷却顺延 —— 排期任务与主动触发共用同一个冷却闸门 (默认 limits.minGapMs=300000),到点投递可能晚约 5 分钟。要准点就调小 minGapMs。

其它未验证项(长时驻留 > 10 分钟、every 的小时级节拍、跨模型家族独立复验等) 见下方「能力边界与限制」一节,那里是逐条如实列出的完整清单。


命令(Usage)

Usage: /proactive <子命令>。下表逐条对应实现里实际注册的分支(src/commands.ts 的命令处理 switch),未列出的子命令会被拒绝并提示可用清单。

命令面真源 = 实现:下表由 scripts/gen-doc-tables.sh 从 pi-proactive/src/commands.ts 的 SUBCOMMANDS + switch (sub) 的 case 分派自动生成(共 16 个命令),不要手改; 要改命令面请改实现,然后跑 bash scripts/gen-doc-tables.sh --apply(门禁 scripts/check-doc-tables.sh 校验)。

命令 参数形态 做什么 状态
/proactive status 无 总览:开关、空闲计时、待触发任务、对端在线、上次主动、本小时用量。 已实现 · pi-proactive/src/commands.ts:550
/proactive on 无 打开全部「代理自发」行为(空闲触发、模型自调度、跨会话自发请求)。 已实现 · pi-proactive/src/commands.ts:565
/proactive off 无 关闭全部「代理自发」行为;已排期任务保留但不再触发。 已实现 · pi-proactive/src/commands.ts:566
/proactive test 无 立即模拟一次触发(走完整「护栏 → 投递」链路),用于验证主动能力是否真的生效。 已实现 · pi-proactive/src/commands.ts:644
/proactive tasks 无 列出所有待触发任务(编号 / 类型 / 下次到期 / 归属 / 触发次数)。 已实现 · pi-proactive/src/commands.ts:647
/proactive cancel <id> 取消一个任务。 已实现 · pi-proactive/src/commands.ts:673
/proactive idle on|off 打开 / 关闭空闲触发并设定阈值(on 可带时长参数,省略 = 用上次设定)。 已实现 · pi-proactive/src/commands.ts:713
/proactive peers [--all|--idle|--name <子串>|--detail <agentId>] 列出已注册的对端与在线状态(名字 / 忙闲 / 最后心跳);可加 --all / --idle / --name / --detail。 已实现 · pi-proactive/src/commands.ts:586
/proactive spawn [名字] 创建一个对端会话(子进程 pi --mode rpc);本会话退出时一并终止。 已实现 · pi-proactive/src/commands.ts:621
/proactive in <时长> <内容> 排一个延迟任务(默认只属于当前会话)。 已实现 · pi-proactive/src/commands.ts:691
/proactive at <HH:MM> <内容> 排一个绝对时间任务。 已实现 · pi-proactive/src/commands.ts:692
/proactive every <时长> <内容> 排一个周期任务。 已实现 · pi-proactive/src/commands.ts:693
/proactive help 无 打印命令速查表(命令表 / 配置键 / 最常见故障 / 详细文档路径)。 已实现 · pi-proactive/src/commands.ts:728
/proactive history [peer|me] [条数] 查看最近的主动行为记录(时间 / 类型 / 目标 / 结果 / 被护栏拦下的原因)。 已实现 · pi-proactive/src/commands.ts:651
/proactive doctor [--json] 只读体检:逐项健康判定 + 处置建议(--json 机器可读);不打扰、不耗额度。 已实现 · pi-proactive/src/commands.ts:558
/proactive tell <peer> <内容> [--kind ask|task|notify] 人侧给另一个会话下指令(--kind ask|task|notify,缺省 ask 要回话);回信落回本会话。 已实现 · pi-proactive/src/commands.ts:641

排期命令的排期结果会当场回执(任务 id、相对到期、绝对到期),任务落盘在任务表里,进程退出后仍在, 下次启动继续按表调度。

注:/proactive quiet 不是命令(输入会被拒绝;静默时段是配置项 limits.quietHours); /proactive notify 不是命令(notify 是消息语义 kind:"notify",由模型工具 message_agent 承载, 人侧给对端下指令用 /proactive tell);/proactive history [peer|me] [条数] 已实现。 各命令的行为细节(落盘 / 内存态、参数怎么写、失败怎么提示)见下方「典型场景脚本」「配置」「排障」三节。


模型工具

工具 参数 作用 定义位置
schedule_task prompt(必填):任务提示词intent(可选):投递意图,默认 wake(取值:wake | interrupt | queue | notify)askUser(可选):触发时是否向用户提问(仅本地)when(可选):触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理 预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。when 支持时长(10m)、时刻(18:30)或 ISO 时间;缺省按 10m 处理。 pi-proactive/extensions/proactive.ts:624
message_agent to(必填):目标 agent 的名字或 agentIdkind(可选):消息语义(对方要不要回我);缺省 notify(取值:notify | ask | task | reply | broadcast)subject(可选):标题body(必填):正文intent(可选):投递意图(怎么投给对方);缺省 wake(取值:wake | interrupt | queue | notify)threadId(可选):话题 / 会话 id(可选) 给另一个正在运行的 agent 会话发消息(跨进程协作)。对端在线时走 socket 直达;不在线时消息落盘到对端邮箱,待其上线后处理。 pi-proactive/extensions/proactive.ts:669
list_peers name(可选):名字子串(大小写敏感;也命中 agentId)。同名歧义时返回与 message_agent 报错完全一致的候选status(可选):按在场状态过滤(online = 在线 = 空闲 ∪ 忙碌)(取值:online | idle | busy | offline)capability(可选):按能力标签过滤(如 message_agent)includeOffline(可选):是否包含离线对端(缺省 false = 只给在线) 列出本机可见的其它 agent 对端(名字 / agentId / 在场状态 / 心跳龄 / 能力标签 / 在途任务数 / 传输方式),可按名字、状态、能力过滤。只读:不产生任何对外行为。发消息前先查它(先查后发),别盲发 —— 离线对端现在打扰它不合适。 pi-proactive/extensions/proactive.ts:743
proactive_query action(必填):查询动作(与 /proactive 子命令同名)(取值:peers | history | status | tasks | doctor | help)name(可选):peers:名字子串过滤all(可选):peers:是否包含离线对端idle(可选):peers:只看空闲对端detail(可选):peers:只看这一个 agentIdscope(可选):history:范围(对端 / 我)(取值:peer | me)peer(可选):history:按对端 agentId 过滤limit(可选):history:条数(1-200 的整数)json(可选):doctor:输出 JSON 查询域:查对端(peers)/ 看主动行为历史(history)/ 看状态(status)/ 看任务(tasks)/ 只读体检(doctor)/ 看命令手册(help)。只读:不产生任何对外行为。 pi-proactive/extensions/proactive.ts:780
proactive_schedule action(必填):排程动作(与 /proactive 子命令同名)(取值:in | at | every | cancel)when(可选):in/at/every:触发时间(时长 10m / 时刻 18:30)content(可选):in/at/every:任务内容id(可选):cancel:任务 id 排程域:定时(in)/ 定点(at)/ 周期(every)/ 取消(cancel)。落到真实任务表,到点由调度器投递;与命令面 /proactive in|at|every|cancel 同源。 pi-proactive/extensions/proactive.ts:806
proactive_peer action(必填):对端动作(与 /proactive 子命令同名)(取值:tell | spawn)peer(可选):tell:目标对端(名字或 agentId)content(可选):tell:指令内容kind(可选):tell:指令类型(缺省 ask)(取值:ask | task | notify)subject(可选):tell:标题thread(可选):tell:线程 idname(可选):spawn:对端名字 对端协作域:给对端下指令(tell)/ 创建对端会话(spawn,受同时存活数上限)。tell 的目标解析与回执三态与命令面 /proactive tell 完全同源。 pi-proactive/extensions/proactive.ts:825
proactive_manage action(必填):管理动作(与 /proactive 子命令同名)(取值:on | off | idle | test)state(可选):idle:开 / 关(取值:on | off) 管理域:主动性总开关(on/off)、空闲触发开关(idle)、自检(test)。这些能力属 human-gated:调用只会回报「需人工确认」并给出可复制的人工指令,不会自行改动全局开关。 pi-proactive/extensions/proactive.ts:847

模型工具面真源 = 实现:上表由 scripts/gen-doc-tables.sh 从 pi-proactive/extensions/proactive.ts 的 pi.registerTool({ name: … }) 注册项与参数 schema 自动生成(共 7 个模型工具),不要手改; 表中参数即真实 schema。下面三个小节是逐个工具的展开说明。

schedule_task

预约一个稍后触发的任务(真正落库到任务表,到点由调度器投递)。

参数 类型 说明
prompt string(必填) 任务提示词
intent "wake" | "interrupt" | "queue" | "notify" 投递意图,缺省取配置里的 idleTrigger.intent(默认 wake)
askUser boolean 触发时是否向用户提问(仅本地)。提问必须带超时;用户取消与超时是两个结局:取消 → 不消耗额度、不记待答(只留一条 declined 审计事件)、文案「已取消」;超时 → 消耗额度、记待答(pending)、文案「未在时限内收到回答,已记为待答」。额度在提问有结论之后才记账(回答 / 超时才扣,取消不扣);提问窗口内不叠加提问与本机投递
when string 触发时间:时长(10m)/ 时刻(18:30)/ ISO 8601;缺省按 10m 处理

返回:details.created === true 时带 taskId / dueAt / schedule;参数非法或任务表未初始化时 details.created === false 并带 error 文案。

list_peers

只读查询对端目录:谁在线、谁空闲、谁擅长什么、有多少在途请求。

  • 参数:name(名字子串)|status(online/idle/busy/offline)|capability(能力标签)|includeOffline(默认只给在线)
  • 返回:名字 / agentId / 在场状态 / 心跳多久前 / 能力标签 / 在途任务数 / 传输方式
  • 为什么要先查:对端离线时打扰它不合适;多个对端同名时 message_agent 会因歧义直接报错 → list_peers 会把候选列给你

message_agent

给另一个正在运行的 agent 会话发消息(跨进程协作)。

参数 类型 说明
to string(必填) 目标 agent 的名字或 agentId
kind "notify" | "ask" | "task" | "reply" | "broadcast" 消息语义(对方要不要回我),缺省 notify
subject string 标题
body string(必填) 正文
intent "wake" | "interrupt" | "queue" | "notify" 投递意图(怎么投给对方),缺省 wake
threadId string 话题 / 会话 id(可选)

返回 details.status,共 8 种,语义严格区分:

status 含义
accepted 已送达:对端在线并已接收
duplicate 对端已处理过这条消息,未重复投递
queued 已落盘、尚未送达 —— 有两种语义,都不得写成「已送达」:① 对端离线待上线(消息写入对端邮箱,对方上线后才收到);② 对端在线但本轮未接收(原因随附,如对端处理抛错 / 投递失败)
rejected 被拒绝(信封非法 / 参数非法 / 找不到目标 agent)
expired 消息已过期,对方不会再处理
error 投递失败(写盘失败等)
unreachable 对端不可达(端点不存在或无进程监听)
timeout 等待回执超时(对端未在时限内回执)

queued 不是「已送达」。 它有两种语义:对端离线待上线(消息落在对端邮箱,对方以同一 sessionId 重启后取回)与对端在线但本轮未投递(原因随附)。两种情况下对端都尚未收到。

kind:"ask" 时,对端模型回答后会自动回送一条 kind:"reply"(携带原 correlationId、继承 threadId)。

同一线程的往返上限 = 4 轮(只计已完成的 reply;未获回答的提问不占额度;投递失败的回信不虚增计数)—— 达到上限后不再回信,并必须通知请求方(该通知走 socket 优先 + 邮箱兜底:离线请求方也能收到——落盘到其邮箱、待其上线后取回;去重粒度=(线程, 请求方),同一线程同一请求方只通知一次;见下方残余限制①)。


配置

配置文件:<agent-dir>/proactive/config.json (<agent-dir> = $PI_CODING_AGENT_DIR,未设置时为 ~/.pi/agent)

{
  "enabled": true,
  "idleDetect": "off",
  "askTimeoutMs": 120000,
  "idleTrigger": {
    "enabled": false,
    "afterMs": 600000,
    "intent": "wake",
    "askUser": false,
    "prompt": ""
  },
  "limits": {
    "minGapMs": 300000,
    "maxPerHour": 6,
    "quietHours": { "from": "22:00", "to": "08:00" }
  },
  "peerWatch": {
    "enabled": false,
    "afterIdleMs": 600000
  }
}
配置键 默认值 作用 定义位置
enabled true 主动性总开关(关掉则定时投递与空闲触发都不发生)。 pi-proactive/src/config.ts:336
idleDetect "off" 空闲检测档位:terminal(仅 TUI 生效)/ input-only(只靠输入事件刷新基线)/ off(关闭)。 pi-proactive/src/config.ts:337
askTimeoutMs 120000 提问等待用户回答的超时(毫秒)。 pi-proactive/src/config.ts:338
idleTrigger.enabled false 空闲触发开关。默认关闭 → 装上插件不会立刻开始打扰。 pi-proactive/src/config.ts:340
idleTrigger.afterMs 600000 空闲多久后触发(毫秒)。 pi-proactive/src/config.ts:341
idleTrigger.intent "wake" 空闲触发的投递意图。 pi-proactive/src/config.ts:342
idleTrigger.askUser 未设 空闲触发时是否向用户提问。 pi-proactive/src/config.ts:432
idleTrigger.prompt "" 空闲触发的提示词。 pi-proactive/src/config.ts:343
limits.minGapMs 300000 两次主动触发之间的最短间隔(毫秒,护栏冷却)。 pi-proactive/src/config.ts:346
limits.maxPerHour 6 每小时主动触发次数上限。 pi-proactive/src/config.ts:347
limits.quietHours 未设 静默时段 {from,to}(HH:MM,from 大于 to 表示跨零点),时段内不主动打扰。 pi-proactive/src/config.ts:386
peerWatch.enabled false 跨会话空闲协作(peer watch)开关。 pi-proactive/src/config.ts:351
peerWatch.afterIdleMs 600000 连续空闲多久后才去询问 peer(毫秒)。 pi-proactive/src/config.ts:352
version "v1" 落盘格式版本;无法识别时拒绝按已知格式解析(保留原文件,不覆盖)。 pi-proactive/src/config.ts:516

配置面真源 = 实现:上表由 scripts/gen-doc-tables.sh 从 pi-proactive/src/config.ts 的 defaultConfig() / mergeConfig() / loadConfig() 自动生成,不要手改; 键集合、默认值与定义位置都以生成表为准。

配置文件缺失或损坏时回退到默认值,不会抛错。

运行时产生的状态目录(都在 <agent-dir>/proactive/ 下):

路径 内容
tasks.json 任务表(排期任务;每条记录带 version:"v1",版本不识别的文件会被忽略并记日志)
agents/<agentId>.json 在线 agent 注册表(含心跳时间戳)
run/<agentId>.sock Unix domain socket 端点(同机实时通道)
mailbox/<agentId>/ 文件邮箱(socket 不可用时的落盘兜底)
processed/<agentId>/ 已处理信封与去重集合 seen.json 的归档位置
mailbox/<agentId>/claimed/、expired/、corrupt/ 认领中 / 过期 / 损坏信封的归档位置

配置外置(提示词 / agent 定义)

配置里篇幅会长、要人工编辑的内容(提示词、agent 角色说明、rubric、模板)改用独立文件维护, 配置文件只留标量与引用。布尔开关 / 数字阈值 / 短枚举不要外置——那只会把「看一眼配置」变成「跳目录猜」。

目录约定(相对 config.json 所在目录,即 <agent-dir>/proactive/):

prompts/<ref>.md    # 提示词
agents/<ref>.md     # agent 定义

引用写法:

{
  "idleTrigger": { "promptRef": "idle-probe" },
  "contentRefs": { "reviewer": { "agentRef": "example-reviewer" } }
}
  • promptRef → prompts/<ref>.md;agentRef → agents/<ref>.md;
  • 文件可选带 front-matter(--- 包裹的 key: value 块),其余部分是正文;
  • 优先级:同层仍有内联 prompt(旧配置)时内联优先,并给一次性提示(ref 不生效);
  • 失败即报错:文件不存在 / 空文件 / 格式错 / 路径越界(../)→ 加载失败并指名 ref 与路径, 不会静默回退默认值;
  • 解析后的路径必须留在配置根目录内(越界路径与指向外部的符号链接都拒绝);
  • 内容单次读入快照(外部程序同时改文件也不会读到半截内容)。

示例:pi-proactive/agents/example-reviewer.md —— 复制到 <agent-dir>/proactive/agents/ 后 即可被上面的 agentRef 引用。

<agent-dir>/proactive/agents/ 下同时存在两种东西:对端注册表 agents/<agentId>.json 与外置 agent 定义 agents/<ref>.md —— 按扩展名区分,互不覆盖。


空闲判定基线(按实测修订)

空闲判定现在用**「最近任何活动」**做基线:用户输入 + 代理活动(模型回合起止 / 工具调用 / 助手消息落定)都会把计时归零。 代理在干活时不再被当成「用户离开了」。

  • 两条基线:lastUserActivityAt(只有用户输入刷新;idleDetect 三档的差异只决定用户输入是否计入)+ 「最近任何活动」(判定用)。
  • 触发前必须确认代理已交回控制权:回合进行中 / 工具链未结束时判定原因为「代理正在忙」,不触发。
  • 插件自己投递的主动消息不刷新任何基线 —— 否则插件会被自己的动作无限推迟下一次触发(自锁)。
  • 阈值维持 10 分钟(afterMs=600000)+ 5 分钟冷却(minGapMs=300000)。

实测数字(日志级测量;同一份会话日志数据集、同一 10 分钟阈值;来源:pi-proactive/docs/idle-ground-truth.md):

配置 工作期误触发率 漏报率 离开覆盖率 触发时机正确率
旧基线(只算用户输入)+ 冷却 20.07% 55.07% 44.93% 20.80%
新基线(任何事件都刷新)+ 冷却 + 忙碌门控 3.01% 68.77% 31.23% 73.31%

表述纪律:在日志级测量下,工作期误触发率由 20.07% 降到 3.01%;这不等于「误触发率已消除」。

这份测量不能证明什么(如实披露):

  1. 不能证明「用户离开了」—— 只能证明「用户没有交互」;用户可能在读屏幕(与离开在日志里同形)。
  2. 不能证明某个阈值对某个用户 / 场景最优 —— 结论只在「单用户 · 重度 agentic 编码」场景内成立。
  3. 不能证明「打扰率 = 20.07%」等于用户被打扰的真实概率 —— 护栏、频率上限与投递成功率未建模。
  4. 不能证明新基线「一定更好」—— 漏报率从 55.07% 升到 68.77%(更少被叫醒,换更少打错时机)。
  5. 不能评估 terminal 档 —— 终端按键在会话日志里不可见。
  6. 不能替代真人实验 —— 这是日志级(实现级 / 测试级)证据,不是现场级证据。

能力边界与限制(如实披露)

  • 对端必须加载本插件,跨会话 / 跨进程协作才成立。对方没装插件 → 消息永远不会被消费。
  • 跨机器只有落盘排队(~1s),没有实时通道。 同机走 Unix socket 直连;跨机(共享目录 / NFS) 只能靠文件邮箱轮询,延迟约一个轮询周期(秒级),且不可靠(掉线、过期都可能导致消息不被处理)。
  • 对端正在流式输出时收到 reply,只会多出一个 turn(turn_start),不会多出一个新的 agent_start。 reply 以 deliverAs:"followUp" 注入同一个 agent run;对端空闲时收到 reply 才走 triggerTurn:true 起新 run, 那时才出现新的 agent_start。按「agent_start 计数」判定「被唤起了几轮」的监控 / 验收脚本会因此误判。
  • 跨会话 / 跨进程协作要求双方都加载本插件。
  • agentId = <name>#<host>#<sessionId>(name = 工作目录 basename,host = 主机名, sessionId = 宿主会话 id):会话稳定(同一会话跨进程重启地址不变)、会话间唯一 (不同会话不会互相投递)。宿主未提供会话 id 时退回 <name>#<host>#<pid>(进程级唯一, 能力退化但不报错);PI_PROACTIVE_AGENT_ID 可显式覆盖。
  • 离线投递:对端优雅退出 → 注册表保留条目并标 offline → 发送方仍可解析 → 消息落盘到对方邮箱 → 对方以同一 sessionId 重启后取回。只有从未注册过的纯名字 才返回 error(无法确定邮箱目录,不得谎称已排队)。
  • 空闲触发默认关闭(idleTrigger.enabled=false),不显式打开前不会打扰用户。
  • 残余限制(如实列出,不掩盖): ① 达线程轮次上限的通知走 socket 优先 + 邮箱兜底 —— 离线请求方也能收到(落盘到其邮箱、待其上线后取回),但不是实时;去重粒度=(线程, 请求方)(同一线程同一请求方只通知一次,不同请求方各自都被交代); ② 注入标记串会出现在模型可见正文与转录里(用于精确识别「本插件自己注入的消息」,代价是正文多一串标记); ③ 旧的 scope:"session" 任务(无 ownerSessionId)在进程重启后不再匹配 —— 归属退回按 ownerAgentId 判定,而旧任务里的 ownerAgentId 是旧的 pid 形态、与当前的会话形态 agentId 不一致 → 这些旧任务不再被投递; ④ socket 路径过长会退化(macOS 上限约 104 字符)→ 退化为文件邮箱落盘排队(延迟变大,功能不中断); ⑤ 注册表条目永不删除 —— 优雅退出只标 offline 不删文件 → agents/ 目录会缓慢积压; ⑥ TUI 完整链路(提问 → 回答 → 回填)未验证 —— pty 档只做了部分场景抽验,完整链路未跑通(见 docs/review/tui-smoke.md 的 7 项未验证); ⑦ 跨家族独立复验未做(本机只有 minimax 一个模型家族); ⑧ 跨机行为未验证(仅落盘排队 ~1s); ⑨ >10 分钟长时驻留未验证 —— 实测中每个宿主生命周期 ≤ 4 分钟,长驻下的资源 / 孤儿进程 / 内存行为未观测; ⑩ every 的小时级节拍未验证 —— 短时实测节拍正常,小时级是否漂移未观测; ⑪ 默认 /proactive idle on 只切到 input-only 档 —— TUI 下不订阅终端输入(attachTerminalInput 只在 terminal 档调用)→ 敲键本身不刷新空闲基线(基线只在宿主发出 input 事件时刷新);需要按终端输入判定空闲,请把配置 idleDetect 设为 terminal; ⑫ 定时投递可能因与主动触发共享冷却而被顺延(最多约 5 分钟) —— 默认 minGapMs=300000(5 分钟),排期任务与主动触发共用同一个冷却闸门(Guard.check() 返回 defer → 任务的 dueAt 顺延);需要准点投递请调小 minGapMs。
  • 威胁模型 = 同 UID 可信。 同 UID 进程之间没有认证,信封里的 from 是自声明字段 —— 本插件不是安全边界,不要用它防御同机其它进程。
  • 护栏按 agent 作用域计数。 每个 agent 进程各有一份护栏状态 ⇒ N 个 agent ⇒ N × 单 agent 上限, 不是全局上界。
  • 适用版本 pi 1.3.0。 全部宿主 API 断言只对 @earendil-works/pi-coding-agent 1.3.0 验证过; 更高版本未验证。
  • TUI 档 pty 仅抽验部分场景(docs/review/tui-smoke.md:该报告自述不构成验收结论、并列 7 项未验证,其基线早于提问路径重写 → 需重跑才算数);rpc / json / print 等非交互模式的降级行为已实测。
  • 跨进程端到端实测(V-03)已完成(docs/review/cross-process-e2e.md:两个真实 Pi 宿主 + 真实模型回合)。 ⚠️ 该基线早于「agentId 会话稳定化」与 M5c 修复轮 → 合并态已复测部分场景(核心闭环与达上限通知,见 docs/review/final-acceptance.md §7.5/§8),原 11 场景在合并态的逐条复测未做。
  • 协作接线层(M5c)独立验收首轮判「不通过」,修复轮已合入,第三轮独立复验已出结论:有条件通过(S-1S-8 全关、P-1P-7 全关;仍有 3 项 Minor 残余与若干未验证项,见 docs/review/m5c-reverify-3.md) → 该层不得称已验收通过。
  • /proactive on|off 已落盘(原子写 <agent-dir>/proactive/config.json 的 enabled 字段),重启后仍生效;/proactive idle on|off 不落盘,仅当次进程内有效(重启后回到配置文件里的设置)。

排障

先看插件日志(stderr,日志前缀 [proactive]),再看状态:/proactive status 会直接给出 「为什么触发 / 为什么没触发」的判定说明。

现象 排查方向
任务没触发 ① /proactive status 看 enabled 是否为「已开启」——off 时定时投递与空闲触发都不发生;② 看「下次空闲触发 / 限制」行确认是否被冷却(minGapMs)、每小时上限(maxPerHour)、静默时段挡下;③ 确认任务在 /proactive tasks 里且 enabled=true;④ 任务归属是 scope=session,由会话 id 判定(会话 id 跨进程重启稳定);若宿主未提供 session id,则退回按进程归属兜底 —— 这种情况下进程重启后旧任务不再归新进程所有
消息一直 queued queued = 已落盘、尚未送达,两种语义:① 对端离线待上线 —— 查 <agent-dir>/proactive/mailbox/<对方 agentId>/ 是否有文件;对方以同一 sessionId 重启后会自动取回(agentId 会话稳定 → 邮箱路径不变);若对端从未注册过(纯名字)则直接 error。② 对端在线但本轮未接收 —— 回执附原因(对端处理抛错 / 投递失败),消息同样落在对方邮箱待重试。两种都不是「已送达」
空闲不触发 ① 空闲触发默认是关的 → /proactive idle on(不落盘,仅当次进程内有效)或改配置 idleTrigger.enabled=true;② idleDetect 默认是 off,没有输入信号时不会猜你在不在 → 设成 terminal(仅 TUI)或 input-only;③ 非 TUI 模式下 terminal 会自动降级为 input-only;④ 确认不是「代理正在忙」(busy)或没到阈值(threshold)
对端找不到 message_agent 的 to 按 agentId 精确匹配 → 名字唯一匹配 → 前缀唯一匹配 解析(同名 / 前缀命中多个会直接报错);离线条目同样可解析(优雅退出保留条目并标 offline,心跳过期=崩溃也可解析)→ 消息落盘待其上线;只有从未注册过的纯名字才 error。注册表条目靠 30s 心跳刷新,超过 3 个心跳周期未刷新即视为离线
socket 起不来 端点被占用(另一个进程还在)时会降级为文件邮箱并继续工作,日志里会有 socket 通道启动失败(降级为文件邮箱);功能不中断,只是延迟变大

团队级排障与看门狗告警语义见 docs/team/WATCHDOG-RUNBOOK.md(告警类别与处置责任人)、 未验证项台账见 docs/team/iteration-backlog.md。


里程碑边界

里程碑 内容 状态
M1 包骨架、/proactive 命令、工具接口、1s tick 与状态栏 已实现
M2 空闲触发、投递与护栏闭环 已实现
M3 任务落盘与调度 已实现
M4 跨进程协作(注册表 / socket / 邮箱 / 去重 / 接线) 已实现(测试级验证);V-03 核心闭环已合并态复测
M5 协作闭环(逐条 reply / 线程 / 轮次上限) 已实现(测试级验证);V-03 核心闭环已合并态复测
M6 分发与文档(README / 包元数据 / 版本约束) 已实现
跨进程端到端实测(V-03) 两个真实 Pi 宿主跑通全链路 已完成(docs/review/cross-process-e2e.md);⚠️ 基线早于「agentId 会话稳定化」与 M5c 修复轮 → 合并态已复测部分场景(核心闭环与达上限通知),原 11 场景在合并态的逐条复测未做
TUI 交互语义 真人按键下验证主动提问 pty 仅抽验部分场景(docs/review/tui-smoke.md;7 项未验证,基线早于提问路径重写,需重跑才算数)
跨家族独立复验 异族模型家族独立复审 未验证(本机只有 minimax 一个模型家族)
跨机行为 两机 / NFS 实测 未验证(仅落盘排队 ~1s)
TUI 完整链路(提问→回答→回填) pty 下的端到端提问闭环 未验证(pty 档只抽验了部分场景;7 项未验证,需重跑才算数)
长时驻留(>10 分钟) 长时间运行下的资源 / 孤儿进程 / 内存行为 未验证(实测每个宿主生命周期 ≤ 4 分钟)
every 小时级节拍 小时级运行下的节拍漂移是否累积 未验证(短时实测节拍正常)
门禁 G11(文档↔实现一致性断言) 机器化的文档↔实现一致性门禁 已存在(判据在 scripts/merge-gate.sh 的 G11 段,默认零容忍;含反向断言:文档的否定式声明若指向仓库里已存在的对象 → 判为违规;回归自测在 scripts/tests/gate-doc-consistency.test.sh)