@bd7pil/reeve 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # `@bd7pil/reeve` — REEVE 编排层(opencode v2 插件)
2
2
 
3
+ > **In English:** this package is the whole REEVE system for opencode v2: eight
4
+ > agent definitions, the review skill, the task ledger with concurrency gates,
5
+ > wall-clock supervision and cross-restart reconciliation, the self-hosted
6
+ > Bifrost proxy with chain hot-reload, and performance stats. It fills the
7
+ > gaps v2 leaves open (`task_status`, silent child death, concurrency limits,
8
+ > timeouts) without ever writing `agents.*.model` — that key belongs to
9
+ > openchamber. Install with `{ "model": "reeve/pro", "plugins":
10
+ > ["@bd7pil/reeve"] }` and nothing else needs to live in the config directory.
11
+ > The rest of this document is the detailed Chinese reference.
12
+
3
13
  包里就是**整个系统**:8 个 agent + review skill + 记账/闸门/墙钟/对账 + 代理自持(Bifrost 链热更)+ 性能统计。
4
14
  配置目录里不需要放任何 REEVE 文件——装插件、写两行配置即可(见下「安装」)。
5
15
 
@@ -36,6 +46,9 @@ v2 原生缺的那几条,全靠这个插件补齐:
36
46
  插件还会注册三个档位模型、把内置 agent 藏起来、设默认 agent —— 所以引擎配置里**不用写** `models` / `agents` / `default_agent`。
37
47
  `options.proxy.enabled` 打开后,
38
48
  插件自己完成 取件 → 派生链 → 拉起/复用 → 等就绪 → 收敛 → 守护,**不需要任何外部脚本**(见 `src/proxy.js` 与 PROGRESS §6.6)。
49
+ 二进制**按平台 pin**(`BIFROST_PINS`:`linux-x64` / `win32-x64`,各带版本化 URL + sha256 + size);
50
+ 没 pin 的平台这一层显式报不可用、不装(插件其余部分照常)。给装配器读的 `provision.json`
51
+ 工件与 linux 那份 pin 由单测绑定防漂移。
39
52
 
40
53
  其中两件事在这里展开(都由面板里的可视化编辑/统计触发,见 PROGRESS §6.11):
41
54
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bd7pil/reeve",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "REEVE 编排层(opencode v2 插件):agent/skill 注册 + 子任务台账 + 代理自持(Bifrost 链热更)+ 性能统计",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -11,7 +11,9 @@
11
11
  "node": ">=22"
12
12
  },
13
13
  "scripts": {
14
- "test": "node test/unit.mjs && node test/reconcile.mjs && node test/snapshot.mjs && node test/proxy.mjs && node test/chain.mjs && node test/stats.mjs",
14
+ "test": "node test/unit.mjs && node test/reconcile.mjs && node test/snapshot.mjs && node test/proxy.mjs && node test/chain.mjs && node test/stats.mjs && node test/entry.mjs",
15
+ "test:coverage": "c8 --reporter=text --reporter=json-summary --reports-dir=coverage --exclude=test/** --exclude=bench.mjs npm test",
16
+ "check:coverage": "node ../scripts/check-coverage.mjs",
15
17
  "prepublishOnly": "npm test"
16
18
  },
17
19
  "files": [
@@ -43,5 +45,8 @@
43
45
  "opencode",
44
46
  "opencode-plugin",
45
47
  "orchestration"
46
- ]
47
- }
48
+ ],
49
+ "devDependencies": {
50
+ "c8": "^12.0.0"
51
+ }
52
+ }
package/provision.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema": 1,
3
3
  "name": "reeve",
4
- "version": "0.1.2",
4
+ "version": "0.1.4",
5
5
  "license": "MIT",
6
6
  "host": {
7
7
  "node": ">=22",
@@ -9,7 +9,7 @@
9
9
  "arch": ["x64"]
10
10
  },
11
11
  "enginePlugin": {
12
- "spec": "@bd7pil/reeve@0.1.2",
12
+ "spec": "@bd7pil/reeve@0.1.4",
13
13
  "kind": "package",
14
14
  "entry": "~/.ocrc/components/reeve/node_modules/@bd7pil/reeve",
15
15
  "requiresPeer": []
package/src/constants.js CHANGED
@@ -4,7 +4,7 @@
4
4
  export const PLUGIN_ID = 'reeve'
5
5
 
6
6
  /** 与 package.json 的 version 保持一致;快照文件里会带上,便于面板判断版本。 */
7
- export const PLUGIN_VERSION = '0.1.2'
7
+ export const PLUGIN_VERSION = '0.1.4'
8
8
 
9
9
  /** 工具注册的命名空间。注意:真正决定"模型能否看见"的是 codemode:false(施工图 §3.1)。 */
10
10
  export const TOOL_NAMESPACE = 'reeve'
package/src/proxy.js CHANGED
@@ -28,15 +28,43 @@ import { defaultChainPath, parseChainText, readChainFile, resolveChain, rulesDri
28
28
  /** 插件自带的默认链(与仓库 proxy/config.json 同源;单测里有一条断言盯着它俩不漂移)。 */
29
29
  export const BUNDLED_CHAIN_PATH = fileURLToPath(new URL('../default-chain.json', import.meta.url))
30
30
 
31
- /** pin 住的那一份(与 ocrc 的 pin 一致;实测版本化 URL 与已部署二进制的 sha256 逐字节相同)。 */
32
- export const BIFROST_PIN = Object.freeze({
33
- version: '2.3.0',
34
- url: 'https://downloads.getmaxim.ai/bifrost/v2.3.0/linux/amd64/bifrost-http',
35
- sha256: 'e62a6425218d037e2aea10446f1f4ad1979291902623575a04b7e0ed46886f92',
36
- size: 134_264_480,
37
- binary: 'bifrost-http',
31
+ /**
32
+ * pin 住的二进制,**按平台一份**(同版本、不同平台的二进制与校验和各不同)。
33
+ * URL 一律版本化(`/latest` 会漂)。ocrc 侧没有第二份 pin;本仓库里的第二份拷贝在
34
+ * `plugin/provision.json`(装配器读的工件表),两者由单测绑定防漂移。
35
+ */
36
+ export const BIFROST_PINS = Object.freeze({
37
+ 'linux-x64': Object.freeze({
38
+ version: '2.3.0',
39
+ url: 'https://downloads.getmaxim.ai/bifrost/v2.3.0/linux/amd64/bifrost-http',
40
+ sha256: 'e62a6425218d037e2aea10446f1f4ad1979291902623575a04b7e0ed46886f92',
41
+ size: 134_264_480,
42
+ binary: 'bifrost-http',
43
+ }),
44
+ 'win32-x64': Object.freeze({
45
+ version: '2.3.0',
46
+ url: 'https://downloads.getmaxim.ai/bifrost/v2.3.0/windows/amd64/bifrost-http.exe',
47
+ // 2026-10-21 实测下载所得(HTTP 200,128,677,376 B),本机起过一次 `/api/version` → 200
48
+ sha256: '3f53213ca9e4696b5ce999f372e52a891492ab7b7bc2226ef923c64f02e1fed5',
49
+ size: 128_677_376,
50
+ binary: 'bifrost-http.exe',
51
+ }),
38
52
  })
39
53
 
54
+ /** linux-x64 那一份(历史引用点,值逐字节未变)。 */
55
+ export const BIFROST_PIN = BIFROST_PINS['linux-x64']
56
+
57
+ /**
58
+ * 指定/当前平台该用哪份 pin。**未知平台显式抛** —— 宁可吵在这里,
59
+ * 也别拿别的平台的二进制去装(装上了也要等 spawn 才炸,更难查)。
60
+ */
61
+ export function pinFor(platform = process.platform, arch = process.arch) {
62
+ const key = `${platform}-${arch}`
63
+ const pin = BIFROST_PINS[key]
64
+ if (!pin) throw new Error(`没有 ${key} 平台的 Bifrost pin(现有:${Object.keys(BIFROST_PINS).join('、')})`)
65
+ return pin
66
+ }
67
+
40
68
  export const PROXY_DEFAULTS = Object.freeze({
41
69
  port: 8080,
42
70
  host: '127.0.0.1',
@@ -49,11 +77,11 @@ export const PROXY_DEFAULTS = Object.freeze({
49
77
  chainWatchMs: 5_000,
50
78
  })
51
79
 
52
- export function proxyPaths({ home = homedir(), dir = null } = {}) {
80
+ export function proxyPaths({ home = homedir(), dir = null, pin = pinFor() } = {}) {
53
81
  const root = dir ?? join(home, '.reeve', 'proxy')
54
82
  return {
55
83
  root,
56
- binary: join(root, BIFROST_PIN.binary),
84
+ binary: join(root, pin.binary),
57
85
  config: join(root, 'config.json'),
58
86
  token: join(root, 'setup-token'),
59
87
  log: join(root, 'bifrost.log'),
@@ -124,7 +152,7 @@ export function buildProxyConfig(chain, { noAuth = true } = {}) {
124
152
  }
125
153
 
126
154
  /** 二进制要不要重下:不存在 / 大小不符 / 校验和不对(后者只有读了才算,交给 verifyBinary)。 */
127
- export function needsDownload(fileStat, pin = BIFROST_PIN) {
155
+ export function needsDownload(fileStat, pin = pinFor()) {
128
156
  if (!fileStat || !fileStat.isFile?.()) return true
129
157
  return Number(fileStat.size) !== pin.size
130
158
  }
@@ -181,7 +209,7 @@ async function sha256File(path) {
181
209
  }
182
210
 
183
211
  /** 幂等取件。返回 'ok' | 'installed';失败抛(**吵**,不静默)。 */
184
- export async function ensureBinary({ paths, pin = BIFROST_PIN, fetchImpl = fetch, logger }) {
212
+ export async function ensureBinary({ paths, pin = pinFor(), fetchImpl = fetch, logger }) {
185
213
  await mkdir(paths.root, { recursive: true })
186
214
  try {
187
215
  const st = await stat(paths.binary)
@@ -332,7 +360,18 @@ export async function startOwnedProxy({
332
360
  } = {}) {
333
361
  const port = Number(opts.port ?? PROXY_DEFAULTS.port)
334
362
  const host = opts.host ?? PROXY_DEFAULTS.host
335
- const paths = proxyPaths({ dir: opts.dir ?? null })
363
+ // 平台 pin:不支持的平台在这里就降级返回(不抛)—— 代理层不可用是这一层的事,
364
+ // 不该把插件加载(agent/skill/台账)一起搞崩。
365
+ let pin
366
+ let paths
367
+ try {
368
+ pin = opts.pin ?? pinFor()
369
+ paths = proxyPaths({ dir: opts.dir ?? null, pin })
370
+ } catch (err) {
371
+ const detail = `代理自持不可用:${String(err).slice(0, 180)}`
372
+ logger?.debug?.(`[reeve] ${detail}`)
373
+ return { port, host, paths: null, chainFilePath: null, ready: false, detail, dispose: async () => {} }
374
+ }
336
375
  const readyTimeoutMs = Number(opts.readyTimeoutMs ?? PROXY_DEFAULTS.readyTimeoutMs)
337
376
  const superviseMs = Number(opts.superviseMs ?? PROXY_DEFAULTS.superviseMs)
338
377
  const chainWatchMs = Number(opts.chainWatchMs ?? PROXY_DEFAULTS.chainWatchMs)
@@ -343,7 +382,20 @@ export async function startOwnedProxy({
343
382
  const chainFilePath = opts.chainFilePath ?? opts.tiersPath ?? defaultChainPath({ home: opts.home ?? homedir() })
344
383
 
345
384
  const disposeFns = []
346
- const result = { port, host, paths, chainFilePath, ready: false, detail: null, dispose: async () => { for (const f of disposeFns.reverse()) await f().catch(() => {}) } }
385
+ // dispose 尽力而为:`clearInterval` 返回 undefined(不是 promise),**不能** `.catch` 它;
386
+ // 一个清理项炸了也不该挡着其余清理项。
387
+ const result = {
388
+ port, host, paths, chainFilePath, ready: false, detail: null,
389
+ dispose: async () => {
390
+ for (const f of disposeFns.reverse()) {
391
+ try {
392
+ await f()
393
+ } catch {
394
+ /* 清理尽力而为 */
395
+ }
396
+ }
397
+ },
398
+ }
347
399
  /**
348
400
  * 链的状态(进快照 → 面板据此显示来源/是否已生效/还有没有差异)。
349
401
  * `appliedAt` 只在对运行时**复核过**之后才更新 —— 它表达的是"运行时真跟上了",
@@ -407,7 +459,7 @@ export async function startOwnedProxy({
407
459
 
408
460
  /** 取件 → 派生配置 → 拉起/复用 → 等就绪 → 收敛。可重复调用(幂等)。 */
409
461
  const bootstrap = async () => {
410
- const binaryState = await ensureBinary({ paths, fetchImpl, logger })
462
+ const binaryState = await ensureBinary({ paths, pin, fetchImpl, logger })
411
463
  const d = await deriveChain()
412
464
  // 启动路径上"坏文件"没有上一份好的可留 —— 用基座(它至少是能跑的)
413
465
  const effective = d.effective ?? d.base
@@ -518,7 +570,7 @@ export async function startOwnedProxy({
518
570
  // 慢路径:二进制不在 → 后台 bootstrap(不挡引擎启动)
519
571
  let present = false
520
572
  try {
521
- present = !needsDownload(await stat(paths.binary))
573
+ present = !needsDownload(await stat(paths.binary), pin)
522
574
  } catch {
523
575
  present = false
524
576
  }
@@ -528,7 +580,7 @@ export async function startOwnedProxy({
528
580
  logger.debug(`[reeve] ${result.detail}`)
529
581
  })
530
582
  } else {
531
- result.detail = `代理二进制首次取件中(${BIFROST_PIN.size} B)—— 后台下载,不挡引擎启动`
583
+ result.detail = `代理二进制首次取件中(${pin.size} B)—— 后台下载,不挡引擎启动`
532
584
  publish()
533
585
  logger.debug(`[reeve] ${result.detail}`)
534
586
  bootstrapping = bootstrap()