npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cr1ms0n/anon-notify

v0.3.0

Published

Pi Agent lifecycle notification extension with steer-based command handoff and a bundled Koishi webhook plugin.

Readme

anon-notify

Pi Agent 生命周期通知扩展。Agent 完成或失败后,扩展把结构化事件发送到 YesImBot Webhook;YesImBot 将事件注入 Anon 的主对话流。插件不附加模型转告策略,也不代替 Anon 发消息。HTTP 接受、Anon 上下文接收和最终 QQ 发言是三个不同阶段;前两个阶段成功不保证 Anon 一定转告。

默认路径上,扩展和 notify-webhook 插件都不直接向 QQ 发送消息,也不使用通知专用模型、QQ 引用回复轮询或本地 reply journal。插件另有一个显式旁路:请求体带 delivery: "direct" 时,事件不经过 Anon、不经过模型,按固定格式直接发到配置的 Koishi 目标(用于上游模型配额这类无需人格处理的告警)。

安装

需要 Node.js 22.19+ 和提供 agent_settled 等扩展 API 的 Pi(已验证 Pi 0.87.1)。只安装扩展,不升级 Pi 本体:

pi install npm:@cr1ms0n/[email protected]

先按下方配置服务端地址、目标 QQ 与 Token,然后在 Pi 会话中重新加载:

/reload

使用裸命令 /anon-notify 开启当前会话。新安装不带个人目标,未配置有效 QQ 号时不能开启。

若之前通过 ~/.pi/agent/extensions/anon-notify 的源码目录联接加载,请先保存旧 config.json 到下方的稳定用户配置位置,再将旧联接移出自动发现目录,避免和 npm 包重复注册。只移动联接,不删除它指向的源码仓库。Token、journal 和 session activation 无须重建。

源码开发仍可直接使用仓库目录作为 Pi extension;npm 发布包仅包含运行文件、配置示例和说明,不包含本机配置、测试、备份或开发记录。

测试

依赖 Node 内置类型擦除和 node:test,不需要安装依赖:

node --test ./*.test.ts ./server/*.test.mjs

配置

用户配置放在包目录之外,不会被 npm 更新覆盖:

~/.pi/agent/extension-data/anon-notify/config.json

可从 config.example.json 开始,填写自己的 endpoint、recipient.qq 和 recipient.anonQq。如果设置了 PI_CODING_AGENT_DIR,此配置位于对应 Agent 目录的 extension-data/anon-notify/config.json。旧源码目录的 config.json 仍作为兼容后备,稳定用户配置优先于它;该旧文件不发布到 npm。

可信项目可以覆盖配置:

<project>/.pi/anon-notify.json

合并顺序为内置默认值、旧源码目录配置、稳定用户配置、可信项目配置、环境变量;后者覆盖前者。可用环境变量:

  • ANON_NOTIFY_ENABLED
  • ANON_NOTIFY_ENDPOINT
  • ANON_NOTIFY_TOKEN
  • ANON_NOTIFY_SOURCE
  • ANON_NOTIFY_RECIPIENT_QQ
  • ANON_NOTIFY_ANON_QQ

公开包不预设目标 QQ。请将下面的空值填为实际 QQ 号:

"recipient": {
  "qq": "",
  "anonQq": ""
}

服务端仍以 notify-webhook 的 recipientId 为最终投递目标;Pi 不会把目标 QQ 放进每次 webhook 请求,也不会把普通网络错误猜测成“未添加好友”。启用前和 TUI 状态栏会显示本地目标与服务端返回的目标状态;unknown/not_checked 只会显示警告。

Token 默认从这里读取:

~/.pi/agent/extension-data/anon-notify/token

主要配置项

  • enabled、endpoint、source、projectConfig、mainAgentOnly。默认 endpoint 是本机 http://127.0.0.1:5140/api/agent-notify/v1/events,不预设任何个人服务地址。
  • recipient.qq:通知目标 QQ;recipient.anonQq:Anon 的 QQ 号。它们用于明确显示和启用前校验,不允许请求方通过 Webhook 自行指定收件人。

其中 enabled 是扩展的全局配置总开关,不等于当前会话已经启用。没有持久 activation 记录的旧 session 和全新 /new session 默认 inactive;用户显式执行裸命令 /anon-notify 开启后,raw activation 会写入该 session 的 JSONL,之后才会发送生命周期通知、轮询 /commands,并开始发送在线 heartbeat。activation custom entry 不进入模型上下文,也不保存 Token、QQ、endpoint 或其他配置。

  • notify.*:开始、进展、完成、失败、单工具错误通知开关
  • progress.*:首次进展时间、周期、工具数阈值、错误通知冷却
  • delivery.*:HTTP 超时、重试次数、退避时间和非最终通知的最小间隔
  • command.*:指令轮询开关、journal 与空闲轮询间隔。deliveryWhenBusy 默认 steer;旧配置值 followUp 仍可读取,但会归一化为 steer,不会恢复等待整个任务空闲的行为。maxBatchSize 为配置兼容保留,新扩展每次只领取一条。
  • presence.*:heartbeatIntervalMs(本地 heartbeat 发送间隔,默认 10 秒)与 heartbeatTimeoutMs(本地参考上限,默认 30 秒)。服务端以 notify-webhook 的 sessionHeartbeatTimeoutMs 为权威 TTL;Pi 只按 heartbeatIntervalMs 发送,过期判断在服务端。
  • content.*:任务/摘要长度,以及项目、最终回复是否写入摘要。旧 includeModel、includeDuration、includeToolCounts 仅兼容读取并校验 boolean 类型,统一归一化为 false,不能再打开自动附加的模型名称、耗时或工具统计;不会修改用户配置文件。

配置会做严格类型检查。maxTaskChars 最大为 200,maxSummaryChars 最大为 1200,与 Webhook 字段限制一致。

notify.cancelled 仅为旧配置兼容而保留。TUI 中按 Esc、中止活跃低层 Agent,或退出/切换会话时始终静默,不发送 Anon 通知。Pi 默认的单次 Ctrl+C 是清空编辑器,双击 Ctrl+C 是退出,不属于活跃 Agent 的中断键。

source 模板变量

source 支持 {project}、{cwd}、{sessionId}、{model}、{mode}。默认值:

"source": "Pi Agent · {project}"

生命周期判定

  • 子 Agent 会话默认被识别并抑制,不产生通知。
  • 普通 assistant stop 视为正常完成。
  • 完整执行且每个结果都声明 terminate: true 的最终工具批次视为正常完成。
  • length、error、不完整/出错/非终止的最终 toolUse 视为失败。
  • 扩展在每个低层 agent_start 监听当前 AbortSignal,并在 TUI 中观察独立 Esc 输入,以覆盖自动重试、自动压缩和低层 run 之间的间隙。即使中断发生在工具执行期间、最后一条 assistant 仍显示为 toolUse,也会判定为用户中断并静默处理。
  • session_shutdown 的 quit、reload、new、resume、fork 都属于用户或会话生命周期操作,不发送结束通知。
  • 最终通知在 agent_settled 后发送,即 Pi 已没有自动重试、压缩重试或 follow-up。

扩展在未启用通知时仍记录主会话的运行事实,但不发送通知、不启动进展定时器。运行中执行 /anon-notify 后,heartbeat 根据 Pi 的 ctx.isIdle() 立即报告真实状态;正常结束仍发送一次完成事件。开始和结束都会请求立即 heartbeat,在途请求结束后会补发最新状态,不必等待下一个周期。

完成和失败通知会绕过本地 delivery.minIntervalMs,避免最终状态被进展通知的限流窗口吞掉;进展类通知仍受限流控制。服务端事件限额独立于 heartbeat/命令流量。HTTP 429 的 Retry-After 在有限重试内生效,单次附加等待最多 60 秒;无效或显式 failed 回执不算成功。正常 reload/shutdown 会等待实际在途通知结束,但没有跨进程崩溃的本地持久 outbox。

YesImBot 主对话流

Webhook 接收事件后,notify-webhook 插件会把事件转换成带边界标记的 runtime event,并调用 Anon 的主会话 messenger;事件正文直接位于边界标记之间:

[EXTERNAL_AUTOMATION_EVENT]
{event data}
[/EXTERNAL_AUTOMATION_EVENT]

数据块之后没有附加处理策略。payload 里出现的闭合标记会被转义成 JSON 可逆形式([\/EXTERNAL_AUTOMATION_EVENT]),因此模型生成的 summary / details 不能提前结束数据块。

事件使用 trigger: true 写入 Anon 主会话历史,并在主会话空闲时启动模型回合;如果 Anon 正忙,则通过 ifBusy: "defer" 延后到安全边界处理。已经发出的模型请求不会被追加入这条事件。queued / processing 表示服务端接受或正在处理;context_injected 表示 messenger 已接收;只有显式 direct 路径的 delivered 记录 Koishi 发送结果。若 messenger 已成功但回执落盘失败,当前进程只重试保存回执,不立即重复注入,也不移除下一条事件。进程在外部副作用与落盘之间崩溃时仍存在重复投递的不确定性。

这样可以避免两条并行的通知链:不会再出现“插件先生成固定 QQ 摘要,再由 Anon 处理一次”的重复通知,也不会把普通 QQ 消息自动转回原 Pi Agent 会话。

事件正文包含 event_id、session_id、source、task、status、summary、context_usage、details、url 和时间戳。Pi 发送方仍使用 Bearer Token、幂等键和 HTTP 重试;服务端负责队列、幂等去重和主上下文注入状态记录。

上下文占用

每条生命周期通知和当前会话的外部扩展通知都在构造 payload 时通过 Pi 的公开 ctx.getContextUsage() 同步读取一次,作为独立 context_usage 字段发送;它不会因为摘要截断而丢失,HTTP 重试也不会重新采样。

示例字段:

"context_usage": {
  "used_tokens": 48000,
  "context_window": 200000,
  "percent": 24
}

这是通知构造当下 Pi 的当前上下文估计,不是累计 token 消耗、收费量或 provider 的实时精确余量;尚未持久化的流式消息或刚完成的工具结果不保证已计入。百分比是数值,有限的超过 100% 的报告也会保留,不截断到 100%。

压缩后用量暂时未知、但窗口已知时,返回 { "used_tokens": null, "context_window": 200000, "percent": null }。没有可用模型、接口不可用、读取异常或数值非法时,整个字段为 null;未知不会冒充 0%,也不阻止原通知发送。

Koishi 会校验并保存该快照,事件查询和 Anon 的 EXTERNAL_AUTOMATION_EVENT 数据块都包含它。旧请求/旧 state 没有该字段时视为未知,无须迁移;非空对象必须含全部三个字段且无额外属性,tokens/窗口为非负/正安全整数,percent 为非负有限数,tokens 与 percent 必须同时已知或同时为 null。非法字段返回 HTTP 400 invalid_context_usage,不入队。

需要两端都更新才能让 Anon 收到上下文占用:旧 Koishi 插件会忽略新字段;新插件接收旧 Pi 请求时显示未知。本仓库的实现和测试不会自动发布 npm、部署远端插件或更新当前 Pi session。

Anon 显式指挥 Pi Agent

Anon 如果根据主对话上下文明确决定要继续处理某条工作,可以显式调用 agent_command,并同时提供:

  • event_id:通知事件的精确 ID;
  • session_id:产生该通知的 Pi Agent session ID;
  • instruction:长度受限的自然语言指令。
  • images(可选):最多 4 张受限图片,每项使用不带 data: 前缀的 base64 data 与 mime_type(支持 PNG/JPEG/GIF/WebP)。文字字段仍然必填。

服务端会验证通知与 session 的绑定,并把指令写入独立 command queue。工具返回“已入队”只表示 Pi 指令已经进入可靠队列,不表示 Agent 已完成,也不表示用户已经收到 QQ 消息。

待发内容保留在服务端 command queue。Pi 空闲时可以领取一条并立即启动处理;忙时只在当前轮次的 turn_end 边界领取一条,不提前批量交入 Pi 原生队列,也不等整个任务结束。空闲轮询的响应若在 Pi 已变忙后才到达,会推迟到安全边界交付。

扩展用 Pi 的公开 pi.sendMessage(..., { deliverAs: "steer", triggerTurn: true }) 发送类型为 anon-notify:command 的可见 custom message。没有图片时 content 仍是纯 instruction 字符串;有图片时 content 是一个文字块后跟原生 ImageContent 图片块。details 保存 command/event/session ID,扩展包装、ID、JSON、base64 和 marker 不进入模型内容,但指令与图片会正常作为用户级内容显示/发送。该路径不经过异步用户输入预处理,不修改 system prompt。API 返回只表示交付,不表示执行完成。

交付后,指令 ID 先持久写入独立 commands.jsonl journal,再 ACK 服务端,同时保留 session branch marker。租约重试、reload 或重启可通过 journal 去重。该通道不读取普通 QQ 消息,也不管理用户手工排队的内容。

在线会话发现与主动投递

agent_command 仍要求关联一个已处理的通知事件。Anon 若要根据当前对话直接管理仍然在线的 session,可改用另外两个工具:

  • agent_sessions:返回所有 heartbeat 有效的在线 session 安全投影,每项只包含 session_id、project(目录 basename)、model、idle/running、supports_images、last_activity_at 和 last_seen_at。不返回 Token、完整路径、会话正文、journal 内容或 QQ 信息。
  • agent_session_message:向指定在线 session 投递显式内容,参数为 session_id、content 和可选 images。只有服务端 registry 里 heartbeat 未过期的 session 才能接收;离线、过期、未开启或未知 session 直接被拒绝,不会排队等待下次启动。图片要求当前 Pi client 支持 image protocol。

投递复用同一套通道:服务端检查在线状态后写入 command queue,队列记录带 origin: "proactive" 与生成的关联 event_id;目标 session 按上述 steer 边界交付,本地 journal 持久化后才 ACK。相同工具调用重试按 runtime_id:turn_id:tool_call_id 幂等,不会重复创建指令;图片内容也参与 request-key 冲突判断。

图片 command 使用 protocol v2。新 Pi client 的 heartbeat 会声明 command_protocol: 2,轮询时声明 capabilities=images;旧 client 不会领取图片 command,避免把图片静默降级为纯文本。agent_sessions 返回 supports_images 供 Anon 选择目标。

图片限制为每条 command 最多 4 张、单张解码后最多 4 MiB、单条总计最多 8 MiB,服务端 pending/leased 图片总预算默认 32 MiB。MVP 只接受 raw base64,不接受 URL 远程抓图;终态 command 会清理图片原文,只保留数量、字节数和摘要元数据。agent_commands 不返回图片内容。

在线状态来自每个已启用 runtime 自己发送的 heartbeat,而不是扫描磁盘 session 文件:session 文件无法唤醒另一个 live runtime。注册表只存在于服务端内存中,并使用 sessionHeartbeatTimeoutMs TTL 清理;server 重启后由在线 runtime 自然重新登记。每个 runtime 携带随机 runtime_id,旧 runtime 的 offline 请求不能删除同一 session 的新记录。

查询与撤回待发 steer

同一个已配置私聊 scope 内,Anon 可使用:

  • agent_commands({ session_id, limit? }):查询该 session 的指令 ID、来源、状态、cancellable、时间和最多 200 字符的内容预览;最多返回 50 条,不暴露 lease token 或其他 session 的内容。
  • agent_command_cancel({ session_id, command_id }):撤回尚未开始交付的指令。重复撤回幂等;已撤回记录和请求键去重会持久保存,重启后不会复活。

创建工具也会返回 command ID、状态和 cancellable。查询和撤回已持久保存的待发内容不要求 session 当前在线;创建主动消息仍要求有效 heartbeat。

撤回截止点是服务端第一次原子领取、开始交付。 pending 且从未开始交付时可撤回;租约已开始返回 delivery_started,Pi 已 ACK 返回 already_accepted。租约过期或残缺不能证明内容未进入 Pi,因此不会重新变成可撤回。cancelled 是终态,不再领取或计入活跃队列。工具不能删除 Pi 原生 steer/follow-up 队列中的内容,也不会清空其他消息或过滤历史上下文来冒充撤回。

会话级启用与持久化

扩展仍会被 Pi 自动发现和加载,但不会默认作用于所有会话。activation 是具体 Pi session 的属性,不写入全局配置:

/anon-notify
  • /anon-notify 是唯一的命令形式,不再接受任何子命令。再次执行即关闭。带参数的输入不会改变状态,只会提示使用裸命令。
  • 开启和关闭都会先向当前 session JSONL 追加 customType: "anon-notify:activation"、version: 1 的布尔记录,再更新内存状态、TUI、EventBus、polling 和 heartbeat。写入失败时不会声称状态已经改变。
  • 同一 session 在 Pi /reload、退出后 resume、切换后返回和 runtime wrapper 重建时,会从全部 session entries 中恢复最新记录;同一文件内的 /tree branch navigation 不会改变 activation。
  • 没有记录的旧 session 和全新 /new session 默认 inactive,不继承上一个 session。
  • /fork 与 /clone 创建的新 session 会读取源 session 当前的 session-wide activation,并在子 session 写入一条继承快照;此后父子 session 独立启用或停用。
  • 最新匹配记录 malformed 或版本未知时 fail closed 为 inactive,不会回退到更旧的 active 值。
  • 启用前仍会校验目标 QQ 和 Anon QQ;如果服务端明确返回 not_added 或 unreachable,Pi 会显示包含两个 QQ 号的引导,要求先添加 Anon 或确认私聊可达后再重试。
  • 服务端没有可靠好友状态检查时会返回 unknown/not_checked;这只会显示警告,不会被误判为未添加。
  • 再次执行 /anon-notify 会持久化 inactive,停止生命周期通知与 /commands 轮询,并尽力发送一次 offline heartbeat。session_shutdown 同样清理 timer、停止 heartbeat 并 best-effort 注销;它不会写入 inactive。
  • 配置重载使用 Pi 自己的 /reload,不再提供带参数的通知命令;会话和服务端状态分别看 TUI 状态栏 / EventBus 与 agent_sessions。
  • subagent 即使恢复了 active 记录,也始终受 mainAgentOnly 规则抑制,不会发送 heartbeat,也不能通过 activation 绕过。

Pi Web 结构化状态协议

Wrapper 存活时,扩展通过当前 AgentSession 的 EventBus 发布 anon-notify:state version 1 payload,Pi Web 继续以带 runtimeId/revision 的 runtime snapshot 为权威。payload 只包含稳定枚举和布尔事实:sessionId、status、sessionActive、servicesEnabled、tokenConfigured;它不包含 Token、目标 QQ、Webhook 内容或人类展示文本。

Wrapper dormant 时,Pi Web server 从同一 session JSONL 读取 activation custom entry,并返回 source: "dormant" 的只读 projection。Dormant active 会显示为已开启且允许 disable,但不会猜测 Token、全局配置或服务 readiness;新 wrapper 启动后,runtime snapshot 会接管显示。浏览器不直接读取 JSONL,也不使用 localStorage 保存 activation。

Pi Web 工具栏开关继续通过现有 SSE + set_anon_notify RPC 调用 /anon-notify command handler(裸命令 toggle:当前 active 则关闭,否则开启);它不会调用 prompt、创建聊天气泡或模型回合,也不复制 recipient readiness、polling、webhook 或 command queue 逻辑。修改扩展后需要用户自行执行 Pi /reload;修改 Pi Web 源码后需要单独构建并重启对应 server 才会部署,本仓库测试不会自动执行这些运行时操作。

模型上下文边界

扩展不修改 system prompt、不注入扩展状态消息,也不注册模型可见的状态工具。active、inactive 和 subagent 抑制状态仅由扩展内部逻辑、结构化 EventBus 和 TUI 状态栏展示。

实际由 Anon 发回的 Agent command 作为明确标记的 custom-message steer 进入 Pi session,并由 Pi 转为用户级模型消息,因为它本身就是需要 Agent 处理的指令;这不属于扩展运行状态提示词。

命令

/anon-notify            # 当前会话未开启则开启,已开启则关闭

只有这一个裸命令。旧的带参数子命令已全部移除;带任何参数的输入都不会改变状态。

  • 开启前会校验目标 QQ 和 Anon QQ。如果目标配置缺失/无效,开启会被拒绝;如果服务端只返回网络错误,Pi 会允许开启但明确提示“状态未核验”,不会伪造好友状态。
  • 开启或关闭会写入 activation custom entry,并同步 TUI 状态栏、EventBus、/commands 轮询和在线 heartbeat。
  • 需要重读配置时使用 Pi 自带的 /reload;需要查看在线会话时由 Anon 调用服务端 agent_sessions 工具。

扩展间调用

其他 Pi 扩展可以通过事件总线发送通知:

pi.events.emit("anon-notify:send", {
  status: "info",
  source: "另一个扩展",
  task: "索引构建",
  summary: "已处理 500 个文件",
  eventId: "index-build-001"
});

状态可用:completed、failed、warning、info。

更新与回退

npm 包同时附带 server/notify-webhook.js、server/command-queue.js 和 server/session-registry.js。服务端需要已有的 Koishi 和 YesImBot 运行环境,安装 Pi 扩展不会自动部署或重启远端插件。先更新 Koishi 插件,再在 Pi 中 /reload 扩展。服务端 health 提供指令通道能力用于核对版本;本仓库的代码修改和测试不会自动部署、重启服务或更新 Pi 本体。

部署前备份服务端状态文件。旧服务器可能不认识 cancelled 和首次交付字段,回退前必须确认旧代码不会把撤回记录重新解释成可发送状态,不能通过删除 tombstone 回退。已经交入旧版 Pi 原生队列的内容也不能通过升级后撤回。

默认行为

  • 扩展自动加载;没有 activation 记录的 session 默认 inactive,已记录的 session 会恢复其最新 raw activation。
  • 只通知主 Agent,不通知 subagents。
  • 只有 active session 才会发送生命周期通知、轮询 Agent commands,并发送在线 heartbeat。
  • heartbeat 只用于在线发现;离线 session 不会被排队为后续投递目标。
  • 开始、进展和单工具错误即时通知关闭。
  • 正常完成和失败通知开启(仅对 active session 生效)。
  • 用户中断与会话切换始终不通知。
  • 通知通过 Webhook 注入 Anon 主对话流,不直接发送 QQ。
  • Webhook 超时 12 秒,最多尝试 3 次。
  • 非最终通知默认至少间隔 5 分钟;完成和失败不受该间隔限制。

许可

当前 npm 包标记为 UNLICENSED,未授予开源许可。公开下载不表示可按 MIT 等开源许可证使用。