@bpinternal/overwatch

中文文档
0.3.7发布于 2026/08/08MITTypeScriptCommonJS构建来源已验证近 30 天下载 1.1万

用于构建控制回路的无头库:用于扫描一个类异常回路的预定bots,用编码代理来修复几个实例,并打开一个PR——没有服务器,UI,或持续过程.

中文使用文档
文档目录18 节
  1. 安装
  2. 快开始
  3. 从中央情报局逃跑
  4. 多个循环, 一个切入点
  5. 核心概念
  6. 哪个 run() 已经,顺序
  7. 公关审查意见
  8. 通知
  9. 索赔(安全货币)
  10. 写入自定义传感器
  11. 写入自定义拾取器
  12. 演员
  13. 默认 : AgentPrActuator
  14. 写入自定义驱动程序
  15. 配置引用( E) ControlLoopConfig (中文(简体) )
  16. 日志
  17. 扩展
  18. 免责声明

监督

用于建筑的无头图书馆 控制循环: 计划抽查一个重播 类异常,用编码代理修正几个实例,并打开一个PR. 没有服务器或 UI - 你打电话 loop.run() 从您已有的调度器( cron, CI, 无服务器) 中 每个电话都有一个完整的周期:

sense → filter claimed → pick → actuator acts
                                 └─ default: agent per signal → verify (re-sense) → commit → PR

一切都在新鲜中运行 代顿a 沙盒:repo被复制在, 一个代理CLI(Claude Code或Codex)被安装和驱动非交互,而沙盒 在运行结束时被撕毁.

安装

pnpm add @bpinternal/overwatch     # or: npm install / yarn add

船舶作为汇编的建筑物(CommonJS, 附有类型申报),从两者一起运作 require 财务报告和审定财务报表 import 在节点18+。 要黑进图书馆本身 复制回放并运行 pnpm install (pnpm是支持的软件包管理器;该版本通过 packageManager) (中文(简体) ).

起码你需要:

  • 代托纳 API 密钥——通过时为 config.sandbox.apiKey 每个循环,或通过 DAYTONA_API_KEY env var (法语)DAYTONA_API_URL 编号 : DAYTONA_TARGET 如有需要,也可作为 vars 工作)
  • Anthropic 或 OpenAI API 密钥 — 以您使用的代理
  • GitHub 令牌, 如果想要打开循环, 在目标 repo 上带有 push + PR 权限 公关而不是仅仅报告

快开始

import { ControlLoop, Github, Codex, AgentPrActuator, sensors } from "@bpinternal/overwatch";

const loop = new ControlLoop({
  // Slugified and applied as a label on every PR this loop opens; also what
  // config.maxOpenPrCount counts against.
  label: "Naked Error Should be ServiceError",

  config: {
    maxOpenPrCount: 1,
    git: new Github({
      repo: "https://github.com/my-org/my-repo.git",
      branch: "main",
      key: process.env.GITHUB_TOKEN,
    }),
    agent: new Codex({ apiKey: process.env.OPENAI_API_KEY!, model: "gpt-5.5" }),
    hooks: {
      setup: "bun install",
      preCommit: "bun run fix:format",
    },
    sandbox: { apiKey: process.env.DAYTONA_API_KEY! },
  },

  // Detects anomalies. Scans src/ for raw `throw new Error(...)`.
  sensor: sensors.astGrep({
    pattern: "throw new Error($$$)",
    language: "ts",
    paths: ["src/"],
  }),

  // The actuator decides what to do with the picked signals. The default AgentPrActuator
  // runs the agent per signal, verifies by re-sensing, then commits and opens a PR. Its
  // `instructions` builder turns one signal into the agent's prompt — see "Writing a
  // custom actuator" below for the built-in builders and for how to swap in an actuator
  // that does something other than open a PR.
  actuator: new AgentPrActuator({
    instructions: (signal) => ({
      instructions: [
        `Fix this in ${signal.location?.file ?? "the codebase"}.`,
        `Issue: ${signal.message}`,
        "Make the smallest change that resolves it — no unrelated refactors.",
      ].join("\n"),
    }),
  }),
});

// Expose the built-in CLI (see "Running from CI" below). Or call loop.run() directly.
loop.cli();

用您的 TypeScript 跑取器运行它—— 例如 . pnpm dlx tsx my-loop.ts run - 随便怎么说 您喜欢( 线圈触发器, 时间表上的 GitHub 动作等) ; 循环本身不会停留 在运行之间居住.

见 examples/ 更完整、更现实的配置(共享配置于 examples/common.ts, (中文(简体) ), 上面的循环.

从中央情报局逃跑

循环是一个平版脚本,所以任何TypeScript跑者都可以运行. 而不是把论据串起来 处理您自己,结束文件 loop.cli() 它把剧本变成一个小 带有两个子命令的命令行工具 :

const loop = new ControlLoop({ /* ... */ });

loop.cli(); // parses process.argv and runs the matching subcommand
tsx my-loop.ts run                    # one full cycle: sense → pick → actuate (→ PR)
tsx my-loop.ts apply-comments 1234    # apply new review comments on PR #1234, push in place
tsx my-loop.ts --help                 # usage (works on each subcommand too)
  • run 输出 非零 如果一个固定点无法被验证(fix-failed)或任何掷出 错误, 因此 CI 步骤会大声失败 。 所有其他结果—— clean, (中文(简体) ), skipped, (中文(简体) ), pr-opened —— 说 输出 0。结果也作为一行摘要印发.
  • apply-comments <pr> 阅读比公关头条更新的评论并将其应用于 公关的分支(参见: 公关审查意见 (见上文)。 把它连接到一个 pull_request_review_comment 触发,通过PR号码。 它首先检查 PR 带有这个循环的标签;如果它 不——来自另一个循环或人类的公关—— 它什么都不做,而且退出 非零 (单位:千美元)wrong-loop),所以错误的接线触发器会响亮地失败,而不是推动这个循环的配置 在外国分支.

帮助和论证 指挥官这样 --help,未知命令错误,每个命令的使用都免费.

GitHub Action 工作流程运行回路时

name: control-loop
on:
  schedule:
    - cron: "0 * * * *"           # hourly
  pull_request_review_comment:
    types: [created]

jobs:
  loop:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install
      - name: run
        if: github.event_name == 'schedule'
        run: pnpm dlx tsx my-loop.ts run
        env:
          DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}
      - name: apply review comments
        if: github.event_name == 'pull_request_review_comment'
        run: pnpm dlx tsx my-loop.ts apply-comments ${{ github.event.pull_request.number }}
        env:
          DAYTONA_API_KEY: ${{ secrets.DAYTONA_API_KEY }}
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
          GITHUB_TOKEN: ${{ secrets.GH_PUSH_TOKEN }}

过 config.maxOpenPrCount 保持预定时间 run 从堆积起来的公关 比它们合并的速度快.

多个循环, 一个切入点

运行许多循环意味着许多接近相同的工作流程——a pull_request_review_comment 触发器不能知道 哪个 循环打开正在评论的 PR . LoopOrchestrator 崩溃 将它们编成单一的脚本: 记录循环, 结尾为 orchestrator.run(),和一个CLI战线 他们全部.

import { LoopOrchestrator } from "@bpinternal/overwatch";
import { nakedErrorLoop, reactDoctorLoop } from "./loops";

new LoopOrchestrator()
  .register(nakedErrorLoop)
  .register(reactDoctorLoop)
  .run();
tsx loops.ts list                     # the registered loops and their routing slugs
tsx loops.ts run react-doctor-issues  # one cycle of a specific loop (by slug)
tsx loops.ts apply-comments 1234      # route the event to whichever loop owns PR #1234

apply-comments 将 PR 与每个循环的标签相匹配,并将注释应用到该循环的标签上 拥有它——所以一个评论的webhook服务于每一个循环. 如果没有注册的循环拥有 PR,它 输出 非零 (单位:千美元)no-matching-loop) (中文(简体) ). 循环被他们的标签弹出键入,所以他们的标签必须 以相隔(登记相撞抛出物),它们应瞄准相同的还原物(a) 评论 Webhook 是每repo 。 在以上工作流程中,将两个评论/时间表步骤指向: loops.ts apply-comments … 编号 : loops.ts run <slug> 而不是每页脚本.

核心概念

控制回路是五块有线的配置 ControlLoop数字 :

块类型目的
sensorSensor扫描 repo, 返回 Signal[] 发现的异常现象
pickerPicker (可选)选择要操作的信号 此运行
actuatorActuator对所选信号采取行动; 默认运行代理并打开 PR
config.agentAgent在沙盒内驱动编码代理 CLI
config.gitGitSource在哪里复制,在哪里推和打开 PR

A 类 Signal 单位是所有其他操作:

interface Signal<TData = unknown> {
  // Where the anomaly is. Optional — some anomalies are repo-wide. When present,
  // `location.file` anchors the signal's identity (see below).
  location?: { file: string; line?: number };
  message: string;
  priority?: "low" | "medium" | "high";
  data?: TData; // anything sensor-specific; carried through to picker and actuator untouched
}

信号通过运行来匹配 location.file + message 仅限 —— 说 line 是信息和 自然改变为固定土地,所以永远不要烤出行号或时间戳入 message这个 身份是让循环在验证时从"固定"中分辨出"仍然被打破,重试"的,以及 是什么让它跳过信号 一个已经打开的公关声称.

哪个 run() 已经,顺序

  1. 如果 config.maxOpenPrCount 打开的 PR 已经带有此循环的标签 .
  2. 旋转沙盒,复制回波,运行 config.hooks.setup。 。 。 .
  3. 运行传感器。 无信号 ~ 完成 (status: "clean") (中文(简体) ).
  4. 丢弃此循环的另一个开放 PR 已声称的信号( 见 内存声称( C) 页:1.
  5. 运行拾取器( 默认 ): pickers.count(1),即传感器顺序的第一个信号). 没有 信号已选中 .
  6. 把选中的信号交给 actuator.act(...) 并归还它所报告的一切。 步骤6a-6c 下面是 默认 AgentPrActuator 是否 —— 由不同的驱动器拥有此步骤 他们中的任何人不得:
    • 第6条a款. 检查一个分支,运行一个代理 每一个选定的信号使用该信号 指示,然后重新运行传感器,以检查哪些信号已经消失。 还有什么 重试(用“ 尝试不同的方法” ) 至 config.maxFixAttempts 时间( 默认 3).
    • 第6条b款. 如果在一切尝试之后 事情仍未解决 status: "fix-failed", (中文(简体) ), 无所取舍.
    • 第6条c款. 否则:运行 config.hooks.preCommit,承诺一切 --no-verify, (中文(简体) ), 推出,打开公关(贴上标签,在正文中贴有索赔标记) status: "pr-opened"。 。 。 .

run() 决定: ControlLoopRunResult - 总是检查 .status 而不是假设 成功 干净的感应器 和"我们尝试和失败"都是正常的结果 例外情况.

公关审查意见

loop.applyPrComments(prNumber) 是另一个切入点——将它接通您的 GitHub webhook(或 民意调查),用于对打开的环路的公关评论活动. 它获取的评论比 PR的头部承诺,让特工在PR现有的分支上应用,并推进到位. 比头部年长的评论被当做已经处理过, 所以重新触发在同一方面 线程是安全的 (status: "no-new-comments") (中文(简体) ).

以 /feedback 与其他注释一样应用,但文本(前缀) (被剥去) .github/control-loop/memory-<label>.md 在回购。 那个 文件在同一个循环的每个未来运行中被读取,并附在每一个代理指令之后,作为 常年指导——这是你如何纠正一个循环的一次行为,并把它粘住,因为 记忆文件船与 PR 记录它.

通知

config.notifications 是循环报告最终结果的目的地列表. Slack 带有 lib 的船舶; 抽象的下级 Notification (一个) send(text, state?) 方法),用于其他一切.

import { Slack } from "@bpinternal/overwatch";

notifications: [
  new Slack({
    auth: { token: process.env.SLACK_BOT_TOKEN! }, // bot token with chat:write
    channel: "#overwatch",
    onSuccess: (context) => `A new PR was open: ${context.url}`,
    onFailure: (reason) => reason,
  }),
],

语言完全属于你——每个处理者将消息发回,然后返回 null编号 :undefined 所以一个目的地只说起事件 它有一个处理器:

处理器着火了接收
onSuccessrun() 打开 PR{ label, url, signals }
onFailurerun() 已返回 fix-failed输入点reason: string (已经命名循环)
onCommentsAppliedapplyPrComments() 被推入公关{ label, branch, comments }

clean 财务报告和审定财务报表 skipped 运行——并评论意味着另一个循环的事件——不通知: 大部分滴滴的循环回报率是多少 并宣布这些滴滴值 看着 无法到达的目的地( 已撤销的令牌, 不在频道中) 是 登录为警告并跳过; 它从未失败过运行, 也从未替换运行本身的错误 .

在第一次公关之后,每条关于同一公关的信息都与该公关相接: Slack 员额 “应用的评论”更新(以及任何后来的失败) 在线索中回复 信息内容 所以公关读作谈话.

这在程序上是可行的 打开公关的运行 以及更新它的评论 除了公关之外 NotifyState: 随便 send 返回存储在 PR 正文中的隐形标记(紧接索赔标记)并移交 回到同一个目的地 在下一个活动。 斯莱克只是第一个信息 ts。 。 。 . 它从来没有到达你的处理器; 它是交付力学,不是措辞。 为了运送你 本身: 返回状态来记录它, 不返回任何内容来保存已存储的内容( 这就是 Slack 如何保存) 持续回答线根而不是自己最后的回覆).

索赔(安全货币)

每个打开的PR嵌入了一个隐形标记,列出它修补的信号密钥. 在接下来的比赛中, 已声明的信号 打开 同一循环的PR在选择之前被排除在外——所以 在先前的 PR 合并之前再次运行回路不会产生相同的重复修复 问题。 合并的PR删除源信号(传感器停止报告); a 关闭的不合并的PR会自动释放其索赔,因为它不再开放.

写入自定义传感器

传感器要么是一种平庸的功能,要么是一个上传到沙盒中的脚本. 使用 sensors.* 页:1 普通案件; 不适合时写自己的案件.

主机侧函数 (单位:千美元)SensorFn)——运行在你的机器/CI跑道上,有一个 RepoHandle 那个 代理人 exec, (中文(简体) ), readFile, (中文(简体) ), glob,以及 grep 进入沙盒:

import type { RepoHandle } from "@bpinternal/overwatch";
import type { Signal } from "@bpinternal/overwatch";

const myTodoSensor = async (repo: RepoHandle): Promise<Signal[]> => {
  const matches = await repo.grep("FIXME:", ["src/"]);
  return matches.map((match) => ({
    location: { file: match.file, line: match.line },
    message: `unresolved FIXME: ${match.content.trim()}`,
    priority: "low",
  }));
};

const loop = new ControlLoop({ /* ... */ sensor: myTodoSensor, /* ... */ });

当检查便宜且可表达时使用此形状 exec编号 :grep编号 :glob ——关闭 和东道方的进口正常工作,因为它永远不会离开你的流程.

沙盒脚本 (单位:千美元)sensors.script(localPath)——用于更重的扫描仪(海关静态) 应运行的项目工具 内部 以还原根作为沙盒 cwd, 而不是通过主机进行圆通:

// sensors/find-unhandled-promises.ts
export default async function (): Promise<import("@bpinternal/overwatch").Signal[]> {
  // this file is uploaded and run with `bun` inside the sandbox; cwd is the repo root
  // ... your scanning logic ...
  return [];
}
sensor: sensors.script("./sensors/find-unhandled-promises.ts"),

默认导出必须是 () => Promise<Signal[]>循环将它包裹在一个小跑道上 将结果写入 JSON 文件, 而不是 stdout, 因此脚本可以自由使用 console.log 页:1 它自己的调试.

内置传感器 (单位:千美元)sensors.grep, (中文(简体) ), sensors.astGrep, (中文(简体) ), sensors.reactDoctor) 覆盖正文 搜索,结构AST搜索,以及 反应医生 诊断 检查 src/sensors/ 用于在写自定义形状前的完整选项; (单位:千美元) grep编号 :astGrep 用自定义调用 message 函数经常取代原本的 一个感应器.

无论你选择的形状: 保留 paths (或等同范围)故意. 无瞄准传感器 将"修复这件事情"变成"修复一切,无所不在,永远"

写入自定义拾取器

一个拾取者将感知(和无感知)信号缩小到运行时应该采取的行动:

type Picker = (signals: Signal[]) => Promise<Signal[]> | Signal[];
import type { Signal } from "@bpinternal/overwatch";

const highPriorityFirst = (max: number) => (signals: Signal[]): Signal[] =>
  [...signals]
    .sort((a, b) => rank(b.priority) - rank(a.priority))
    .slice(0, max);

function rank(p: Signal["priority"]): number {
  return { high: 2, medium: 1, low: 0 }[p];
}

内设: pickers.count(n) (第一届) n 按传感器顺序排列——默认)和 pickers.busiestFile(maxCount?) (无论哪个文件的信号都最多,所以运行 在一个连贯的公关中共同修复土地。 摸摸感应器,或者写一个像那个 改变"第一"的意思.

演员

这个 演员 选择后拥有所有的东西——它决定了实际是什么 发生 页:1 并报告结果。 这是一个抽象的阶级 只有一个方法:

abstract class Actuator<TData = unknown> {
  abstract act(input: ActuatorInput<TData>): Promise<ControlLoopRunResult<TData>>;
}

act 接收选中的 signals,提供 sandbox,一个 AgentContext (单位:千美元)ctx用于 运行命令/ 在其中写入文件, a sense() 回调以重运行传感器,循环 config页:1 label编号 :displayLabel运行 log - 做这些事的所有事情 返回 a ControlLoopRunResult。 。 。 .

默认 : AgentPrActuator

大多数循环想要内置行为:在每个信号上运行代理,通过再感应(与 重复,然后承诺并打开一个公关。 这是 AgentPrActuator,它需要一个 指令构建器 函数将一个信号变成代理的提示:

type InstructionBuilder<TData = unknown> =
  (signal: Signal<TData>) => Promise<{ instructions: string }> | { instructions: string };
import { AgentPrActuator, actuators } from "@bpinternal/overwatch";

new AgentPrActuator({
  instructions: (signal) => ({
    instructions: [
      `Fix this in ${signal.location?.file ?? "the codebase"}.`,
      `Issue: ${signal.message}`,
      "Make the smallest change that resolves it — no unrelated refactors.",
    ].join("\n"),
  }),
});

到达 signal.data 用于任何特定传感器——例如:. sensors.reactDoctor 附上 全面诊断(包括其 help 这里,和 sensors.astGrep编号 :sensors.grep 附 录 原始的比赛.

两艘建造船 actuators (通过一个作为 instructions,或者省略它——默认是 fromSignal():

  • actuators.fromSignal() ——仅从信号中创建的最小指令;一个好的默认.
  • actuators.fromFile(localPath) ——读取本地标记下/文本文件一次(已刻出)并用作 说明书,并附上信号的细节。 当固定跟随一个 作为医生写作比重建每个符号更容易的房子风格——见: examples/blank-errors-instructions.md 真实的(带有地图的完整移徙指南) 表格和片断前后.

AgentPrActuator 在每个指令中自动附加两件事: 重试调整 重复尝试,这个循环积累 /feedback 内存( 见 公关审查意见 (见上文)。 不要重复 任何关心 在您的指令构建者.

写入自定义驱动程序

子类 Actuator 当你想逃跑的时候 其他 打开 PR — 张贴报告到 Slack,写一个摘要文件,呼叫一个API,打开一个问题而不是一个PR. 开动器不需要 触摸编码代理; 它只需要返回一个 ControlLoopRunResult数字 :

import { Actuator, type ActuatorInput, type ControlLoopRunResult } from "@bpinternal/overwatch";

class ReportOnly extends Actuator {
  async act({ signals, log }: ActuatorInput): Promise<ControlLoopRunResult> {
    log.info(`found ${signals.length} signal(s) — reporting without touching the repo`);
    await postToSlack(signals.map((s) => `• ${s.location?.file ?? "repo"}: ${s.message}`).join("\n"));
    return { status: "clean" }; // nothing was changed or opened
  }
}

循环仍然处理感知, 索赔过滤,采摘 maxOpenPrCount 大门周围 您的驱动器 —— 带有循环标签的 PR 键, 所以它们只是不给驱动器 从来没有打开一个.

配置引用( E)ControlLoopConfig(中文(简体) )

静态的,每升起的设置 config数字 :

  • git (单位:千美元)GitSource——使用 new Github({ repo, branch, key })。 。 。 . key 实值 用于克隆/计算公共回购的PR的可选性,要求推取或打开PR.
  • agent (单位:千美元)Agent—— new Claude({ apiKey, model?, timeoutSec? }) 或者说 new Codex({ apiKey, model?, timeoutSec? })扩展抽象 Agent 类 (单位:千美元)prepare, (中文(简体) ), executeAgent插入另一个CLI.
  • maxOpenPrCount ——跳出直接跑道,如果这些带有循环标签的PR是 已经打开.
  • notifications (单位:千美元)Notification[]——目的地告知跑道结果 通知 见上文.
  • branchPrefix ——默认 "control-loop";被推的树枝看起来像 <prefix>/<label-slug>-<run-id>。 。 。 .
  • hooks.setup —— shell 命令在克隆后立即运行(例如. "bun install"非零级 退出中止运行 .
  • hooks.preCommit ——贝壳命令追随代理人,在犯罪前(例如: repo的自动处理器). 规范代理编辑和任何循环,所以 PR 通过 CI 格式化。 非零退出中止运行 .
  • env - 创建时在沙盒上设置了环境 vars(可用 hooks 和代理人.
  • maxFixAttempts ——默认3;见上文步骤6.
  • sandbox.apiKey —— Daytona API 密钥沙盒是用创建的,所以在 一个过程可以使用不同的代托纳账户. 掉回原点 DAYTONA_API_KEY 环境 var 当省略时.
  • sandbox.snapshot 来创建沙盒而非默认的 Daytona 快照 图像。 预装工具链和代理CLI在此跳过每个运行的安装. 说明: 资源大小必须被烤入快照本身——无法在创建时要求它 时间用一个.

日志

每跑步都会说出步道 RunLog (沙盒的创造、克隆、感知、, 每个信号代理运行,承诺/推出,公关打开)——无可配置,仅当观察stderr时 手动运行一个循环。 设定 CONTROL_LOOP_SILENT=1 变音; 否则颜色会随之而来 NO_COLOR 和TTY检测.

扩展

  • 新传感器口味: 将文件添加到 src/sensors/ 返回时 SensorFn 再从 src/sensors/index.ts (或使用) sensors.script原始函数内含——你不需要 用于一次性扫描的内置).
  • 新拾取器: 任意 (signals: Signal[]) => Signal[] | Promise<Signal[]> 工作内置; 添加一个 文件为 src/pickers/ (从其 index.ts),但需广泛再用.
  • 新指令样式: 添加一个 InstructionBuilder 工厂改为 src/actuators/instructions.ts (比如说) fromFile编号 :fromSignal),或通过一个内线 AgentPrActuator。 。 。 .
  • 新建起动器类型 (不打开 PR / 不运行代理): 添加文件到 src/actuators/ 一个分类抽象的类 Actuator,从 src/actuators/index.ts。 。 。 .
  • 新剂CLI: 扩展抽象 Agent 类 src/agents.ts。 。 。 .
  • 新的通知目的地: 将文件添加到 src/notifications/ 与一个类 子分类抽象 Notification 执行 send(text, state?),从 src/notifications/index.ts。基地处理哪些事件起火和它们说什么;返回 a NotifyState 从 send 在下一次关于公关的事件上 将循环的手拿回来.
  • 新伪造 (吉特拉布等):执行 GitSource 界面从 src/github.ts。 。 。 .

免责声明

此软件包将在 @bpinternal 组织。 这个组织的所有软件包都用于 瓶装 内部的团队 并不适合我们的社区。 由于这些软件包适合我们自己的使用情况,它们可能不稳定的API,在没有警告的情况下接受中断的更改,文件很少,缺乏以社区为重点的支持。 然而,这些软件包仍然被故意公开,其重要原因如下: 我们爱开源. 因此,如果您想要安装或叉出这个软件包,就完全可以自由操作。 我们强烈建议你正确标记你的版本.

博讯工程组.

版本与发布记录

@bpinternal/overwatch 共发布 10 个版本,首次发布于 2026/07/17;过去 12 个月发布了 10 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.3.7latest2026/08/083 个月前
0.3.62026/08/053 个月前
0.3.52026/08/043 个月前
0.3.42026/08/033 个月前
0.3.32026/07/313 个月前
0.3.22026/07/313 个月前

关于 @bpinternal/overwatch 的常见问题

@bpinternal/overwatch 是什么?

用于构建控制回路的无头库:用于扫描一个类异常回路的预定bots,用编码代理来修复几个实例,并打开一个PR——没有服务器,UI,或持续过程.

如何安装 @bpinternal/overwatch?

在项目目录运行 npm install @bpinternal/overwatch,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:typescript(^5)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@bpinternal/overwatch 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@bpinternal/overwatch 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@bpinternal/overwatch 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@bpinternal/overwatch 可以商用吗?

@bpinternal/overwatch 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@bpinternal/overwatch 的下载量有多少?

根据 npm 官方下载统计,@bpinternal/overwatch 在 2026/10/09 – 2026/11/07 这 30 天内被下载 10,724 次,日均约 357 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@bpinternal/overwatch 还在维护吗?

最新版本 0.3.7 发布于 2026/08/08(3 个月前);至今共发布 10 个版本,其中过去 12 个月发布了 10 个;首次发布于 2026/07/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。