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

pi-nightshift

v0.1.1

Published

Background jobs for pi with auto-wake on completion. 在后台工作,干完自己来交班。

Downloads

49

Readme

pi-nightshift

npm version npm downloads license

English | 中文

给 pi 的后台任务扩展 —— 在后台工作,干完自己来交班。

你的 agent 不再被长命令卡住:job_start 立刻返回,pi 继续干活,等任务结束时 agent 会自己醒过来收取结果——没有轮询、没有 sleep、没有人类插话。

在用 Deepseek Harness 时发现了内置的好用工具,尝试在 pi 中复现。

场景模拟

# 会话 1
你 ▸ pnpm test 要跑 3 分钟。放后台跑,继续干活。
pi  ▸ 已启动后台任务 bash-1 (bash: pnpm test)。

      (pi 继续做你的其他事……)

      ⏰ background job bash-1 (bash: pnpm test) finished [status: completed, exit code: 0].
pi  ▸ 测试通过 —— 42 过 0 挂。(自己醒过来,读了输出,回来汇报)

完成通知只带 job id 和状态;输出留在 registry,被唤醒的 agent 自己决定要不要用 job_output 去读。不为没人要的输出白花一次 turn。

安装

# 从 npm 安装(推荐)
pi install npm:pi-nightshift

# …或从本地 checkout 安装
pi install ./pi-nightshift

装完即用——四个工具(job_start、job_output、job_list、job_kill)立刻生效。可选配置在 ~/.pi/agent/nightshift.json(见配置)。

工具

| 工具 | 作用 | |------|------| | job_start(command, label?) | 后台跑一条 shell 命令;立刻返回 bash-N 式 job id | | job_output(job_id, wait?, timeout_ms?) | 读上次读之后的增量(不重复);结尾总带 [status: ...]。wait: true 阻塞到任务结束或超时(超时返回 [status: running],任务还活着) | | job_list() | <id> [<kind>] <status> - <label>,空列表返回 (no background jobs) | | job_kill(job_id, reason?) | 立即请求取消(连进程树一起杀);已结束的任务返回状态且不消费未读输出 |

文件布局

~/.pi/agent/
  nightshift.json                  # 可选配置
  pi-nightshift/
    <sessionId>/
      bash-1.json                  # 未送达的完成通知(durable)

通知文件在发送之前写入,确认送达后才删除——两者之间崩溃,文件留在磁盘上,重开会话时重放。

工作原理

完成检测

job_start 通过 pi 内置 bash 用的同一个 shell 启动命令(Windows 上是 Git Bash,其他平台 /bin/bash)。stdout/stderr 累积进 job,cursor 记录已读位置。任务在子进程的 close 事件(进程退出且 stdio 关完——此时输出才完整)时settle,不是 exit。

两条交付车道

任务 settle 时,自主决定通知去哪:

  • 空闲 agent → 唤醒 —— followUp + triggerTurn:开一个全新 turn,和用户输入完全一样。模型看到通知,可以立刻调 job_output。
  • 忙碌 agent → 注入 —— 通知进当前 turn 的下一步。

wake 预算(防自激链)

被唤醒的 turn 可能又起一个任务,完成后再唤醒它——无限循环会烧 token。maxConsecutiveWakes(默认 3)限制一次会话能靠唤醒开 turn 的次数,超出的通知降级为注入。只有真人输入回填预算——通知永远不会回填自己花掉的额度。

批处理

150ms 内结束的多个任务合成一条通知(triggerTurn 取各项的 or),一批并行的任务只唤醒一次,不是 N 次。从第一个 settle 起 1 秒硬上限,单个任务的孤立通知不会永远等着。超过 4KB 的通知退化成 job id 列表,再超退化成「N 个任务完成,用 job_list 看」。

durable 交付

通知在 settle 时先写盘(tmp+rename 原子写),再发送。送达 → 删文件。中途崩溃 → 文件存活 → 同一会话重开时以 steer 重放(不 triggerTurn)。任务本身随 pi 一起死——只有未送达的通知保证存活。

去重

模型已经知道任务结果——通过 job_kill、终态读、或返回终态的 wait: true——就不发通知。

| 途径 | 为什么模型已经知道 | |------|-------------------| | job_kill | 模型亲手杀掉了任务,当然知道它结束了——再通知就是废话 | | 终态读 | job_output 读到终态任务时,返回值里带着 [status: completed],从这次读取里就知道了 | | 返回终态的 wait: true | job_output(job_id, wait: true) 阻塞到任务结束才返回,模型是等它结束的 |

三种途径都会把任务标成 reported,settle 时通知直接丢弃(decideLane 返回 drop)。

配置

文件:~/.pi/agent/nightshift.json(不存在 = 全默认)。非法值启动即报错。

| 键 | 默认 | 含义 | |-----|---------|------| | waitTimeoutMs | 30000 | wait: true 不带 timeout_ms 时用的等待时长 | | maxWaitTimeoutMs | 600000 | 模型给的超时上限,超出被钳制 | | maxConsecutiveWakes | 3 | 一次会话能靠唤醒开 turn 的上限,之后降级为塞入 | | completionDelivery | "wakeup" | "wakeup" = 空闲就开 turn;"quiet" = 只塞不拉起,通知等下一次真实 turn |

waitTimeoutMs 大于 maxWaitTimeoutMs 时加载即失败。

故障排查

| 症状 | 原因 | 修复 | |------|------|------| | 任务永不 settle、无通知 | 有后代进程握着 stdout(daemon 型) | 任务还在产出输出——job_kill 它,或等它自然结束 | | agent 没被唤醒 | wake 预算耗尽(maxConsecutiveWakes),或 completionDelivery: "quiet",或任务在 turn 中途结束(塞入车道) | 随便输入点什么——通知会搭下一次真实 turn 的便车 | | 重启后通知又出现 | durable 重放——上次投递被中断 | 设计如此;重放只走 steer,从不自己开 turn | | 同一条通知出现两次 | 「发送已确认」和「删文件」之间崩溃 | 极小的崩溃窗口,接受的取舍 | | job_start 失败 | 找不到 bash(Windows 没装 Git Bash) | 装 Git for Windows,或在 pi 设置里配 shellPath | | 配置改了没生效 | 路径不对,或 JSON 非法(加载时大声报错) | 检查 ~/.pi/agent/nightshift.json | | job_output 输出被截断 | 输出超过 pi 的 50KB 工具上限 | 截断是设计;多次调用增量读 |

测试

# 单元测试 —— 无 LLM、确定性、快
npm test

# 端到端测试 —— 需要 pi + API key
node test/e2e-wake.mjs     # 空闲 agent 被真实任务完成自动唤醒
node test/e2e-batch.mjs    # 3 个任务同时结束恰好唤醒一次
node test/e2e-durable.mjs  # 崩溃幸存的通知在会话恢复后重放

| 层级 | 命令 | 要求 | 测什么 | |------|------|------|--------| | 单元 | npm test | 无 | 注册表状态流转、增量读、车道决策、批窗口、配置校验、durable 存储 | | E2E | node test/e2e-*.mjs | pi + API key | 完整闭环:真实任务 → settle → 唤醒 → 模型读输出 |

开发

无构建步骤——pi 直接加载 TypeScript。

pi -e ./src/index.ts   # 带扩展起交互会话
npm test               # 单元检查

结构:src/index.ts 只接 pi API;逻辑都在纯模块里(jobs.ts 注册表、notify.ts 车道决策、batch.ts 批窗口、config.ts 配置校验、durable.ts 存储),所以能用裸 node --test 跑,不需要 pi。

发布(维护者)

npm version patch   # 或 minor / major
npm publish         # 需要 npm 账号;pi-package 关键词让包出现在 pi 包目录
pi install npm:pi-nightshift   # 验证发布后的安装

已知缺口

  • agent 状态切换窗口可能吞通知 —— turn 循环最后一次检查收件箱与 agent 正式进入 idle(agent_settled)之间有一小段窗口,此刻 agent 仍被判定为忙碌,正好在这时结束的任务,通知会走 steer 车道排队但没有东西唤醒它。修这个窗口要改 pi 的 agent loop,扩展层没有对应的钩子。
  • steer 已排队但会话先结束时通知会丢 —— durable 文件在发送被接受时(sendCompletion 返回 true)就删了,但 steer 车道里「接受」只代表排队成功、不代表送达。通知排队后、送达前会话结束(退出、/new、/resume 切走),inbox 被清空、文件已删,这条通知就丢了。wake 车道当场开 run 送达,不受影响;重放路径(session_start 扫盘)也只覆盖还没发出去的文件。
  • 唤醒预算在session中不会恢复 —— wake 预算只在收到真人输入时清零回满,时间流逝不会恢复。所以无人值守的 agent 一旦花光 maxConsecutiveWakes(默认 3)次唤醒额度,之后完成的任务都只降级为 steer 排队,不会自己再醒过来;通知一直攒着,直到用户回来输入、或别的扩展开了个 turn 才送达。这是防自激链的有意设计,代价是「放它自己在后台跑一批活」最多自动唤醒 3 次。
  • 流式读适用于单消费者 —— 每个任务只有一个读游标,job_output 读一次游标就前进一次,增量被消费后不再返回。模型自己是唯一的消费者所以一切正常;但如果同一回合并行调两次 job_output,或将来有第二个观察者(另一个扩展、另一个 session),后读的人会拿到 (no new output)。多个消费者需要新的 API(按观察者各维护一个游标)。
  • 输出无上限累积 —— 长流式任务未读的输出在内存里一直长;终态型任务(构建、测试)不受影响。