@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 +13 -0
- package/package.json +9 -4
- package/provision.json +2 -2
- package/src/constants.js +1 -1
- package/src/proxy.js +68 -16
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
/**
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 = `代理二进制首次取件中(${
|
|
583
|
+
result.detail = `代理二进制首次取件中(${pin.size} B)—— 后台下载,不挡引擎启动`
|
|
532
584
|
publish()
|
|
533
585
|
logger.debug(`[reeve] ${result.detail}`)
|
|
534
586
|
bootstrapping = bootstrap()
|