# pi-background-job

[English](README.md) | 简体中文

Pi 的后台任务与输出监视插件，设计参考 Claude Code 的后台任务和 Monitor。agent 启动构建、服务或日志监视后可以继续处理其他工作，有事件发生时会自动收到通知，无需轮询，也无需编写 `sleep` 循环。

```text
background_job(action="start", command="npm run build", name="build")
background_job(action="status")
background_job(action="stop", name="build")
monitor(command="tail -f app.log | grep --line-buffered -E 'ready|ERROR'", description="errors in app.log")
```

## 为什么这样设计

- **事件驱动，无需轮询。** 任务退出或 monitor 收到一批新输出行时，都会作为一条消息发回启动它的会话。若会话处于空闲状态，消息会直接触发新一轮执行；若会话正忙，消息会在当前工具批次执行完毕、下一次调用模型之前送达。agent 不必消耗额外轮次去轮询任务状态。
- **生命周期归属 Pi 进程。** 任务的生命周期与启动它的 Pi 进程一致，退出 Pi 时会自动停止本进程的所有任务。无需安装或清理常驻守护进程；需要比 Pi 存活更久的任务应交给 systemd 或 tmux。
- **同一套机制，两个工具。** `background_job` 和 `monitor` 共用同一套底层任务机制，区别仅在于监听目标：前者监听进程退出，后者监听 stdout 新增行。
- **普通文件存储，单一写者。** 每个 Pi 进程将任务记录和日志保存在各自的目录中，仅由本进程写入，因此不需要文件锁。

## 特性

- 支持按名字启动、列出和停止任务；`/jobs` 可列出任务，选中运行中的任务即可将其停止。
- 退出消息包含退出码或信号、运行时长、日志路径以及最后 20 行输出。
- monitor 将 200 ms 内的 stdout 输出行合并为一批发送；达到超时上限（默认 5 分钟，最长 30 分钟）或输出过于密集（每分钟超过 30 条消息）时自动停止。
- `stop` 对整个进程组发送信号：先发 SIGTERM，2 秒后仍有残留进程再发 SIGKILL。
- footer 实时显示当前会话正在运行的后台任务和 monitor 数量。
- 在 codemode 的 `on` 和 `only` 模式下，脚本内均可直接调用。
- 自动清理超过 30 天的历史记录和日志，仍在运行的任务除外。

## 安装

```sh
pi install npm:@spinejit/pi-background-job
```

不安装，仅在当前会话中试用：

```sh
pi -e npm:@spinejit/pi-background-job
```

当前回合必须先拿到结果才能继续的命令（例如决定下一步怎么改的测试），请使用前台 `bash`。配合 [pi-timeout-policy](https://github.com/GhabiX/pi-timeout-policy) 使用，可避免前台命令无限挂住。

## 要点

完整参考（消息格式、codemode 细节、生命周期）见 [English README](README.md#reference)。核心规则如下：

- 消息只投递给启动该任务的会话；若该会话不在前台则不投递，但任务结束信息仍会照常写入任务记录。
- 任务记录保存在 `<Pi agent dir>/state/background-job/<bootId>/<pid>-<start time>/`（通常位于 `~/.pi/agent/state/` 下），每个名字对应一个 JSON 记录，每次运行对应一个日志文件。
- 任务名字在单个 Pi 进程内唯一。`status` 会列出所有 Pi 进程的任务；`stop` 不会停止其他存活 Pi 进程的任务，但可以停止孤儿任务（启动它的 Pi 进程已退出）。
- `/reload` 保留运行中的任务；`/new`、`/resume` 和 fork 保留后台任务，但会停止原会话的 monitor；退出 Pi（包括 SIGTERM/SIGHUP）会终止本进程的所有任务。
- 在 print 和 JSON 模式下，模型停止输出即结束运行，仅投递运行期间到达的消息，启动结果中会写明这一限制。
- 后台 shell 使用 Pi 的 shell 配置，继承环境变量，并将 Pi 管理的 `<Pi agent dir>/bin` 置于 `PATH` 最前面。插件不读取 `shellCommandPrefix` 设置，所需前缀请直接写进命令中。

## 限制

- 仅支持 Unix 系统（依赖进程组）。Windows 需另行编写基于进程树或 job object 的适配层。
- 按设计，任务不会比启动它的 Pi 进程存活更久。
- 已脱离原进程组的进程（例如通过 `setsid` 启动）不受 `stop` 控制。

## 兼容性

已在 Pi 1.3.0 和 1.4.0、Node 22.23 下测试通过。要求 Node >= 22.19。Pi 相关包均声明为 `"*"` peer dependencies。

## 开发

```sh
npm test              # 核心逻辑和事件层
npm run test:e2e      # 用脚本化模型驱动的真实 Pi 会话
npm run test:codemode # 真实 codemode，在 on/only 模式下成对比较直接调用和嵌套调用
```

端到端测试使用临时的 `PI_CODING_AGENT_DIR`，不会修改本机的用户 Pi 配置。共存测试还会加载本仓库旁边（或 `PI_TIMEOUT_POLICY_DIR` 指定）的 [pi-timeout-policy](https://github.com/GhabiX/pi-timeout-policy) checkout，没有时跳过。核心测试和端到端测试会按设计向自身启动的测试任务发送 SIGTERM 和 SIGKILL，并清理过期的测试记录。

## @spinejit 的其他插件

- [pi-btw-cached](https://github.com/GhabiX/pi-btw-cached)：在主会话自己的请求上发起 `/btw` 侧问。
- [pi-timeout-policy](https://github.com/GhabiX/pi-timeout-policy)：为 bash、grep、find 提供统一的超时策略。
- [pi-compact-tree](https://github.com/GhabiX/pi-compact-tree)：Pi 终端界面的紧凑过程树视图。

## 许可证

Apache-2.0，见 [LICENSE](LICENSE)。
