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

@weifuwujs/pi-agent

v1.3.0

Published

持久化、可审批、可审计的 agent 运行时:pi 内核 + 沙箱 provider + 至少一次语义

Readme

@weifuwujs/pi-agent —— 可恢复、可审批、可审计的 agent 运行时

把 @earendil-works/pi-coding-agent 的 agent 内核包成任务运行时: 一次任务(run)跨进程持久化、崩溃后可续跑、危险工具必须人工批准、全过程留痕可审计、成本与预算显式可查。

设计前提:你已经有一个 Postgres(会话/事件/租约都在库里),外加一个加固容器(工具执行在容器里)。 二者都是可选的——内存驱动(createMemoryStore + createMemorySandbox)足以在测试与本地开发中跑完整链路。

npm i @weifuwujs/pi-agent @earendil-works/pi-coding-agent postgres

零运行时依赖:@earendil-works/pi-coding-agent(peer >=1 <2)与 postgres(peer,可选用)都由你提供。

快速上手

import { ModelRuntime } from '@earendil-works/pi-coding-agent'
import { piAgent, createMemorySandbox, piAgentApi } from '@weifuwujs/pi-agent'

// 1. 模型:pi 的 ModelRuntime(密钥由你提供,绝不进容器)
const modelRuntime = await ModelRuntime.create()
const model = modelRuntime.getModel('anthropic', 'claude-sonnet-4-5')

// 2. agent:沙箱决定工具在哪执行;approve 决定哪些工具需要人批准
const agent = piAgent({
  sandbox: createMemorySandbox(),
  model,
  modelRuntime,
  approve: ['sandbox_exec'],
})

const run = await agent.start('把 README 里的错别字改掉')
agent.drain() // 推进到工具调用 → 挂起等人批准
console.log(await agent.get(run.id)) // { status: 'waiting_approval', ... }

await agent.approve(run.id, { by: '[email protected]' })
await agent.drain() // 继续执行 → done

最小可跑示例(零依赖,node 直接运行;test/batteries/readme-examples.test.ts 会真跑它):

import assert from 'node:assert/strict'
import { piAgentOpenApi } from '@weifuwujs/pi-agent'

const document = piAgentOpenApi() as { openapi: string; paths: Record<string, unknown> }
assert.equal(document.openapi, '3.1.0')
assert.ok(document.paths['/runs'], '运维面必须有 /runs')
assert.ok(document.paths['/runs/{id}'], '运维面必须有 /runs/{id}')

示例标注:ts exec = 由测试真跑;ts fragment = 片段(依赖前后文,不独立运行);自足示例带包标签(如 ts tasks),由测试编译。

生产形态(Postgres 持久化 + Docker 沙箱 + worker 进程):

import postgres from 'postgres'
import { piAgent, createPostgresStore, createDockerSandbox } from '@weifuwujs/pi-agent'

const sql = postgres(process.env.DATABASE_URL!)
const agent = piAgent({
  store: createPostgresStore({ sql }), // 首次使用自动建表(advisory lock 保护)
  sandbox: createDockerSandbox(),
  model,
  modelRuntime,
  workerId: `worker-${process.pid}`,
})

await agent.drain() // 恢复入口:领回**崩溃遗留**的 run 并从最后提交的回合续跑

语义契约(先读这一段)

| 语义 | 说明 | | --- | --- | | 至少一次(at-least-once) | 进程崩溃后 run 会被重新领取并续跑;同一回合可能被执行第二次。幂等是用户侧义务。 | | 幂等键 | 每条进沙箱的命令都注入环境变量 PI_AGENT_IDEMPOTENCY_KEY(<runId>:<命令与参数哈希>,同一任务内同命令同参数 → 同键)。你的副作用脚本应当按它去重(写入前 insert ... on conflict do nothing 之类)。 | | 零重放已提交回合 | 已提交的模型回合不会重发;重放的工具调用记为 history 动作 tool_replayed。 | | 崩溃自愈 | 租约(lease)+ CAS + FOR UPDATE SKIP LOCKED:worker 崩溃后租约过期,其他 worker 可领取;死 run 不会自动复活(终态永不改)。 | | 会话丢失 | 会话文件缺失/损坏 → run 显式失败 SESSION_LOST,绝不静默重头跑。 |

人工审批(HITL)

  • approve: ['sandbox_exec', 'write_file'] 白名单里的工具每次执行前都要批准;不在名单里的工具直接执行。
  • 请求批准 → run 变 waiting_approval,不占调度(drain() 返回,其他 run 照跑;可以挂到明天)。
  • agent.approve(id, { by }) 批准;agent.deny(id, { by, reason }) 拒绝 → run 显式失败 APPROVAL_DENIED: <reason> 且零执行。
  • 一次性:批准只覆盖下一次执行,工具再次运行要再次批准。
  • 重复批准 → APPROVAL_DUPLICATE;没有待批准请求时批准 → APPROVAL_NOT_PENDING。
  • 全过程落 history:approval_requested / approval_granted / approval_denied(带 by)。

取消与超时(硬杀)

  • agent.cancel(id, { by, reason }):先 AbortSignal 硬杀在途命令(进程组 / 容器 kill -KILL -1),再通知会话中止;被杀的调用记 aborted: true。run 终态 cancelled。
  • agent.start(prompt, { timeoutMs }):执行段计时——等人批准的时间不算超时;批准后重新计时。超时 → 终态 failed + RUN_TIMEOUT: <ms>。
  • 两条路径都留痕:cancel_requested / cancelled / timed_out。

成本与预算

  • 每个任务的 token 与美元成本落在 run 上(tokensIn / tokensOut / costUsd),history 里有 usage 事件,HTTP GET /runs 直接可查。
  • budget: { maxTokens, maxTurns } 在回合边界检查;超限 → run 显式失败 BUDGET_EXCEEDED: <detail> + history budget_exceeded,绝不静默续跑。
  • 诚实边界:超限发生在某个回合的工具已开始执行之后时,该工具会跑完(回合边界检查,不是逐 token 熔断)。

会话与事件(分层)

| 层 | 位置 | 用途 | | --- | --- | --- | | 会话(文件态) | pi 原生 JSONL 文件(sessionDir,路径落在 DB 的 session_file 列) | 续跑:SessionManager.open() 恢复完整上下文;本机 pi 也能打开同一文件 | | 会话(DB 态,1.3 起 opt-in) | _pi_sessions(会话行)+ _pi_events(append-only 条目 delta) | 多副本无共享磁盘也能续跑;exportRun()/importSession() 的数据源;exportRun().jsonl 就是上面那种 pi 原生 JSONL | | 权威事件序列 | Postgres _pi_history(run_id, seq, action, at, detail) | 审计与回放:动作含 start/turn/tool/tool_replayed/usage/budget_exceeded/approval_*/cancel_*/cancelled/timed_out/end/fail | | 工具幂等记录 | Postgres _pi_tool_calls(key, status, result) | 崩溃后判断某次工具调用是否已提交 |

会话导出/导入(1.3)

默认不落库(sessionPersistence: 'none',行为与 1.2 一致——会话只在磁盘文件或内存)。 要多副本续跑、或要把会话交给本机 pi,开 sessionPersistence: 'store':

const agent = piAgent({ sandbox, model, store, sessionPersistence: 'store' })

// 导出:jsonl 是 pi 原生格式——写文件后本机 `pi` 直接能打开(真 SDK 验收在案)
const snapshot = await agent.exportRun(runId)   // { sessionId, cwd, entries, jsonl, exportedAt }
writeFileSync('resume.jsonl', snapshot.jsonl!)

// 导入:逐行校验(坏行报「第 N 行」,绝不静默丢历史)
const imported = await agent.importSession({ jsonl: readFileSync('other.jsonl', 'utf8') })

// 续跑:把导入的会话绑到新任务(模型真的能看到历史——脚本模型断言在案)
await agent.start('接着上次继续', { sessionId: imported.sessionId })
weifuwu-pi-agent export <runId>              # stdout 纯 JSON(含 jsonl 字段)
weifuwu-pi-agent import --file resume.jsonl  # → { sessionId, cwd, entries }
weifuwu-pi-agent start '继续' --session <sessionId>

语义边界:导入后开新任务不是「崩溃续跑」——发的是你的新 prompt;只有本 run 已被执行过(崩溃/批准后重试)才发续跑提示词。 未开 sessionPersistence: 'store' 时 importSession/start --session 显式报 SESSION_DISABLED(不静默忽略)。

沙箱(隔离分级——请诚实阅读)

  • 双驱动同契约:createMemorySandbox()(本地/测试)与 createDockerSandbox()(生产);exec(command, { timeoutMs, signal, env })。
  • 加固基线(docker 驱动默认):--read-only + --tmpfs /tmp、工作区只写挂载、--cap-drop ALL、no-new-privileges、非 root --user、--pids-limit、--memory、--network none、容器标签 pi-agent.managed。
  • 没有 Docker 时明确失败:抛 SANDBOX_UNAVAILABLE(绝不回退宿主执行);容器启动失败是 SANDBOX_FAILED;命令非零退出不是异常,是 { isError: true } 的工具结果。
  • 信任边界:沙箱挡住的是事故(误删、误跑、误联网、资源打满),不是对抗性隔离——容器内没有 docker.sock,但恶意代码仍不应与你的宿主机共处。对抗性多租户请上 gVisor / microVM。
  • 隔离分级 L0–L4 的判据与实测:判据即源码(src/isolation.ts: ISOLATION_CRITERIA:可用性 / 性能 p50≤1500ms 且 p95≤基线×3 / 依赖 ≤2 二进制+1 配置+0 代码改动 / 运维单命令且有据可查;样本 <10 不下结论)。 在仓库内一条命令复现全部数字:npm run spike:isolation(脚本不随包发布;门控 e2e:PI_SANDBOX_E2E=1 node --test test/isolation.docker.e2e.test.ts)。 实测(2026-10-05,Arch / 内核 7.2.8 / docker 29.8.2):L2 加固容器成立且逐条可归因——非 root CapEff=0(注意:非 root 切换本身就会清零能力)、root 对照 --user 0:0 下 CapEff 也为 0(这才证明 --cap-drop ALL 本身生效;无 cap-drop 的 root 对照为 0xa80405fb)、unshare -U 被拒、--network none 生效、内存/pid 限额可见、workspace 宿主侧可回读、退出码 0/1/124,启动 p50 ≈ 134 ms(n=15); 但容器内 uname -r 与宿主一致 ⇒ 共享内核。L3(gVisor)/ L4(microVM)本仓未落地:runsc/kata-runtime/firecracker 缺席即判红并留 docker 原话(unknown or invalid runtime name), 不假绿;拿到 runsc 的宿主跑同一条命令即可得到四准则真数据。长表(L0–L4 分级、原始样本、结论、撤回成本)见仓库 spike/isolation.md(不随包发布)。
  • 容器清理:gcSandboxes() 扫 pi-agent.managed 标签清残留(含进程 kill -9 之后)。
  • 镜像:docker pull alpine:3(默认;可用 image 选项换)。

升级到强隔离(L3 / L4)

宿主装好 runsc(gVisor)并在 /etc/docker/daemon.json 注册 runtime 后,零代码改动接上(参数插在加固基线之后、image 之前):

const sandbox = createDockerSandbox({
  image: 'alpine:3',
  extraRunArgs: ['--runtime', 'runsc'],   // 强化类参数默认放行;真正生效由 docker inspect 的 HostConfig.Runtime 证明
})
  • 验证方法:docker inspect <容器名> --format '{{.HostConfig.Runtime}}';仓库内可一条命令复测四准则:npm run spike:isolation(判据与分级见 spike/isolation.md)。
  • 逃生舱默认拒绝削弱参数:extraRunArgs 含 --privileged / --cap-add / --user / --network / --pid=host / --ipc=host / --userns=host / --device / --security-opt / --mount / 绑定 docker.sock → 抛 SANDBOX_UNSAFE(启动容器之前拒绝),确需时显式 allowUnsafeArgs: true(你自己负责)。
  • 实测的逃生舱语义(2026-10-05,出处 plan/1.3.md 叶子 2.3 Plan):--cap-drop 是并集(追加丢得更多,无法撤销 ALL);--cap-add 能把能力加回来(--cap-drop ALL --cap-add SYS_ADMIN → CapEff=0x200000);重复 --user 后者生效;--network 与 none 冲突会显式报错(不会静默放开);flag 放在 image 之后会静默失效(所以升级参数必须由 extraRunArgs 传,位置由本包保证)。
  • L3/L4 现状:本仓未预置 gVisor/microVM(2.2 spike 判定红:分发 28.9 KB/s + 无 root),候选与触发条件见 plan/1.3.md 的 1.4 候选表。

HTTP 面

import { piAgentApi, piAgentOpenApi } from '@weifuwujs/pi-agent'

const api = piAgentApi(agent, {
  authorize: ({ action, runId }) => policy.check(user, action, runId), // 缺省 = 全部 403
})
for (const route of api.routes()) app.route(route.method, route.path, route.handler)
app.get('/openapi.json', () => Response.json(piAgentOpenApi())) // OpenAPI 3.1,可被 openapi-typescript 消费
  • 默认拒绝:不传 authorize → 所有接口 403。
  • 四包对照:docs/authorization.md——四包默认拒绝矩阵(本包为严格 === true)与组合边界(可执行契约)。
  • 动作级授权:read / start / approve / deny / cancel(回调收到 { action, runId })。
  • 错误映射稳定:400 / 403 / 404(RUN_NOT_FOUND)/ 409(APPROVAL_*)/ 500。
  • 事件流 GET /runs/{id}/events(SSE):用 after=<seq> 或 Last-Event-ID 断线续传;纯读,回放不会触发执行。

CLI(weifuwu-pi-agent)

weifuwu-pi-agent runs --status waiting_approval   # 每行一个 JSON,可直接 | jq
weifuwu-pi-agent status <runId>
weifuwu-pi-agent history <runId>                  # 权威事件序列
weifuwu-pi-agent start '修一下线上 500'
weifuwu-pi-agent approve <runId> --by [email protected]
weifuwu-pi-agent deny <runId> --reason '改动面太大'
weifuwu-pi-agent cancel <runId> --reason '换个方案'
weifuwu-pi-agent worker --once                    # 或 --drain
weifuwu-pi-agent export <runId>                   # 会话快照(pi 原生 JSONL)
weifuwu-pi-agent import --file resume.jsonl       # 导入会话(逐行校验)
  • 配置文件:weifuwu-agent.config.{ts,mjs,js}(默认导出 agent 或工厂函数;ESM),或用 --config <path>。
  • stdout 纯 JSON(结果),诊断走 stderr(CODE: message);退出码:0 成功 / 1 业务失败(RUN_NOT_FOUND、APPROVAL_*)/ 2 用法或配置错误。
  • CLI 与 HTTP 指向同一个 store,两边看到的状态逐字一致(有真库 e2e 守着)。

非目标(诚实边界)

  • 不做 会话内容与磁盘文件的双向同步:'store' 模式下 DB 是唯一真源(同一会话两种模式不要混用)。
  • 不做 导入时的条目改写/迁移:只接受 pi 原生条目(坏行/不可解析条目显式拒绝),不做静默升级。
  • 不提供 gc 子命令:GC 是宿主/驱动侧职责(导出 gcSandboxes()),HTTP 面也没有 /gc 路由。
  • 不重建 agent 循环:模型循环、工具协议、会话格式全部来自 pi 内核(本包只做任务化、持久化、审批、沙箱与交付面)。
  • 不是对抗性隔离:见上文信任边界。

环境

  • Node ≥ 20(开发验证于 v26);type: module 的 ESM 工程。
  • Postgres:任何 12+ 版本(表由本包自建:_pi_runs / _pi_history / _pi_tool_calls)。
  • Docker:可选;没有则 SANDBOX_UNAVAILABLE(本地用 memory 驱动)。