omp packages

pi-sleep-guard2.0.0

A Pi extension that blocks statically known long Bash sleeps and guides event-driven CI waits.

owen0225 Published 7 days ago MIT ~348 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

sleep-guard

Pi 扩展:在 Bash 和 bg_run 命令执行前检查能静态确定时长的 sleep 调用。单次等待超过 120 秒时,阻止整次调用,并把简短、可执行的纠正原则作为工具错误返回给模型。

它不自动改写命令,不执行替代命令,也不轮询后台任务。对 GitHub Actions,提示使用 gh run watch <run-id> --exit-status:构建结束就立即返回,退出码区分成功/失败;需要完成时自动弹出,就用支持完成通知的后台任务运行这条 watch 命令。

拦截规则

  • 阈值:严格大于 120 秒才拦截;120、120.0、2m 放行。
  • 识别:普通 sleep、/bin/sleep、/usr/bin/sleep,以及 command、exec、sudo、env、nohup、nice、time、timeout 的静态包装;支持 s、m、h、d 单位、静态小数和静态多参数求和。infinity/inf 视为超过上限。
  • 递归分析:bash/sh/zsh/dash/ksh -c '...' 的静态字符串、eval '...' 和 ssh host '...' 的最后一个静态命令字符串都会再按 shell 命令分析,包括被 sudo、env、nohup、nice、time、timeout 包装的情况;递归最多 4 层。
  • timeout:如果静态超时不超过 120 秒,调用最多等这么久,因此不拦;超过 120 秒时继续检查其子命令。
  • 所有位置:分号、管道、条件、循环、case、子 shell 和命令替换里的静态长 sleep 都会被检查;不执行控制流求值,所以即使分支运行时不会走到,静态长 sleep 也会被拦。
  • 后台也拦截:sleep 121 &、(sleep 121; ...) & 不会被自动豁免;bg_run 的 command 参数同样检查,避免用后台固定 sleep 绕过守卫。超时看门狗如果包含静态长 sleep,也可能被拦截;这是有意的严格规则。
  • 不误伤文本:普通引号字符串、注释、带引号 heredoc 正文中的 sleep 121 不是命令,不拦截;无引号 heredoc 中实际执行的 $(sleep 121) 会拦截。
  • 不分析函数定义体;f() { sleep 121; } 本身不拦,调用 f 也看不到这个 sleep。
  • 不确定时不拦截:sleep $DELAY、$((...))、sleep 1h2m、sleep --help、解析失败或超长输入都返回 unknown,不会仅凭猜测阻止 Bash。
  • 解析器不可用时不会阻断 Bash;交互模式会提示一次,无 UI 模式写入 stderr,避免静默声称守卫生效。

这些都是静态分析边界,不是安全沙箱。脚本文件内的 sleep、docker exec、kubectl exec 等二次命令、python -c 'time.sleep(...)' / Node/其他语言内部的 sleep、make/npm 脚本内部的 sleep、变量或算术表达式、函数调用、多个短 sleep 组成的循环,以及 watch、tail -f、journalctl -f 等长期阻塞命令,不在本版保证范围内。

场景与推荐等待方式

场景 推荐方式 守卫覆盖
Python、编译、测试、本地命令 前台直接运行,Bash 等进程退出并返回输出/退出码 不拦命令;拦命令里的静态长 sleep
本地任务不阻塞 agent bg_run 运行真实命令,完成时通知 拦 bg_run 里的固定 sleep
GitHub Actions gh run watch <id> --exit-status;需要通知时后台运行它 提示具体命令
其他 CI(GitLab、Jenkins、Buildkite 等) 使用原生 watch/API;没有就用有截止时间的短轮询 通用提示,不自动识别服务
Docker / Kubernetes docker wait、docker logs -f、kubectl wait、kubectl rollout status 不递归识别这些包装
SSH / 远程机器 ssh host 'command' 前台等待,或远程命令完成通知 最后一个静态命令字符串会递归分析;交互式 ssh 不分析
端口、数据库就绪 curl --retry、pg_isready 等,或短轮询 + 总超时 短 sleep 放行,长 sleep 拦截
长驻服务 后台启动服务,再做有截止时间的就绪检查 不等待服务进程退出
脚本/语言内部 sleep 需要脚本自己支持完成信号或看门狗 静态分析看不到

模型收到的提示

被拦截时,工具调用不会执行,模型会收到:

[sleep-guard] 本次 Bash 调用未执行:检测到 `sleep 121`,单次固定等待 121 秒,超过 120 秒上限。
请优先使用任务完成通知;其次使用原生 watch/wait。
如果等待的是自己启动的 Python、编译、测试或其他命令:直接在前台运行,Bash 会在进程退出时返回真实输出和退出码;
需要同时做别的事,就用支持完成通知的后台任务运行同一条命令。后台任务不会让固定 sleep 提前返回。
必须轮询时,先检查状态再短暂等待,并设置总截止时间及单次检查超时。
成功立即结束;失败返回非零状态;超时和取消应明确报告并及时结束。
不要仅把长 sleep 拆成没有状态检查的多个短 sleep。

如果命令中包含 GitHub Actions 相关操作,提示会追加具体做法:

等待 GitHub Actions 时不要用固定 sleep 或 bg_run sleep。先取 run id,例如:
  gh run list --limit 1 --json databaseId,status,conclusion,headBranch,workflowName
然后执行:
  gh run watch <run-id> --exit-status
它会等到结束并立即返回,退出码区分成功/失败;需要完成时自动弹出,就用支持完成通知的后台任务运行这条 watch 命令。

本地 Python、编译、测试命令本身不需要 sleep:直接前台运行,Bash 会等待进程退出并返回输出与退出码。需要并行做别的事时,用支持完成通知的后台任务运行这条命令;不要用后台任务包住固定 sleep。

失败和超时没有统一的可复制模板:等待逻辑必须按实际任务返回正确退出码,单纯把长 sleep 拆成多个短 sleep 不算修复。

依赖与加载

  • Pi 1.3.0 扩展 API:tool_call、{ block: true, reason };同时处理内置 bash 和自定义 bg_run 事件。
  • web-tree-sitter@0.27.1、tree-sitter-bash@0.25.1(使用包内官方 tree-sitter-bash.wasm,不在安装时编译本机扩展)。
  • 依赖已锁定在 package-lock.json。tree-sitter 是可选 peer,未安装;本扩展走 WASM。

源码放在 ~/.pi/local-extensions/sleep-guard/,全局入口是一个固定包装文件:

~/.pi/agent/extensions/sleep-guard.ts
  -> ../../local-extensions/sleep-guard/index.ts

Pi 启动时会自动发现并加载 ~/.pi/agent/extensions/ 下的扩展,因此不需要再传 --extension。已用 Pi 的 discoverAndLoadExtensions() 验证:全局目录能发现 sleep-guard.ts,并且它注册了 tool_call 处理器。

如果之前用 pi --extension 启动过本项目,请直接重启 Pi,不要同时保留命令行入口和全局入口,否则会注册两份守卫。正常情况下不要再传 --extension。

修改源码后,在 Pi 中执行 /reload 或重启即可;回滚只需删除全局入口文件,不会删除本地源码和测试:

rm ~/.pi/agent/extensions/sleep-guard.ts

如果移动了本地源码目录,需要同步更新这个包装文件的相对导入路径。

测试

cd ~/.pi/local-extensions/sleep-guard
npm test

测试包含:

  • tests/analyze.test.mjs:纯静态分析用例,覆盖阈值、精确小数、包装命令、引号、命令替换、heredoc、后台、函数、动态参数、畸形输入和大输入。失败会让进程以非零状态退出。
  • tests/extension.test.mjs:通过 Pi 自己的 createExtensionRuntime() / loadExtensions() 加载 index.ts,测试 handler 返回值和通知;不调用模型、不执行测试字符串中的命令、不启动外部任务。

若 Pi 安装路径不同:

PI_PACKAGE=/path/to/@earendil-works/pi-coding-agent npm run test:pi

解析器验证记录(2026-10-17):tree-sitter-bash WASM ABI 15 可由 web-tree-sitter@0.27.1 加载;静态分析测试 15/15、Pi 加载器集成测试 8/8 通过。尚未进行真实模型会话 smoke test。

发布到 npm 和 pi.dev/packages

Pi 包画廊会收录 npm 上带 "keywords": ["pi-package"] 的包;pi 清单声明扩展入口。当前包名 pi-sleep-guard 在 npm 上可用(查询于 2026-10-17),包内已包含 Bash grammar WASM,不依赖本机编译。

发布前:

cd ~/.pi/local-extensions/sleep-guard
npm test
npm pack --dry-run
npm login

确认 tarball 后发布:

npm publish

发布后用户可安装:

pi install npm:pi-sleep-guard@2.0.0

注意:

  • tree-sitter-bash 仅作为开发依赖用于更新 assets/tree-sitter-bash.wasm;运行时只需要 web-tree-sitter 和已打包的 WASM。其 MIT 许可证在 assets/tree-sitter-bash.LICENSE。
  • @earendil-works/pi-coding-agent 是 Pi 提供的宿主包,已放在 peerDependencies,不打包。
  • 发布新版本前修改 version,并重新运行测试和 npm pack --dry-run。
  • 本仓库不会自动执行 npm publish;需要 npm 账号、登录和发布确认。