dsh-vibe-math 2.0.21 → 2.1.0
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/LICENSE +21 -21
- package/README.md +63 -8
- package/RELEASE-NOTES-2.0.22.md +112 -0
- package/RELEASE-NOTES-2.1.0.md +143 -0
- package/cordis.patch.yml +10 -9
- package/installer.js +315 -290
- package/package.json +17 -6
- package/vibe-math-v2/agent.cordis.yml +256 -212
- package/vibe-math-v2/preset.yml +2 -2
- package/vibe-math-v2/vibe-math-v2.js +243 -36
- package/vibe-math-v3/agent.cordis.yml +278 -226
- package/vibe-math-v3/preset.yml +2 -2
- package/vibe-math-v3/vibe-math-v3.js +2938 -2631
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +540 -540
- package/vibe-math-v4/agent.cordis.yml +300 -239
- package/vibe-math-v4/preset.yml +2 -2
- package/vibe-math-v4/vibe-math-v4.js +1275 -1111
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +647 -636
- package/vibe-math-v5/agent.cordis.yml +342 -0
- package/vibe-math-v5/preset.yml +2 -0
- package/vibe-math-v5/vibe-math-v5.js +3459 -0
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1133 -0
|
@@ -32,8 +32,13 @@ export function apply(ctx) {
|
|
|
32
32
|
const fs = ctx.fs
|
|
33
33
|
const tools = ctx.tools
|
|
34
34
|
const commands = ctx.commands
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
// Optional services are resolved LAZILY at call time, never snapshotted in apply().
|
|
36
|
+
// A snapshot taken here is order-sensitive: if the service has not been provided yet
|
|
37
|
+
// when this preset subtree mounts, it stays undefined for the whole session, so
|
|
38
|
+
// `runShell` would report 'no-subprocess' forever and every mkdir would silently do
|
|
39
|
+
// nothing (masked only by fs.writeText creating parents automatically).
|
|
40
|
+
const subprocessOf = () => { try { return ctx.get('subprocess') } catch (e) { return undefined } }
|
|
41
|
+
const sandboxPolicyOf = () => { try { return ctx.get('sandboxPolicy') } catch (e) { return undefined } }
|
|
37
42
|
|
|
38
43
|
// ================= per-session registry =================
|
|
39
44
|
const sessions = new Map() // rootAgentId -> Session
|
|
@@ -112,7 +117,7 @@ export function apply(ctx) {
|
|
|
112
117
|
maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
|
|
113
118
|
}
|
|
114
119
|
let params = Object.assign({}, DEFAULT_PARAMS)
|
|
115
|
-
let scheduler = { running: false,
|
|
120
|
+
let scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null } // activeCount 由 activeCount() 从 agentRegistry 推导,不再作为字段
|
|
116
121
|
let agentRegistry = {}
|
|
117
122
|
let decisionQueue = []
|
|
118
123
|
let verifierAccuracy = {}
|
|
@@ -131,13 +136,19 @@ export function apply(ctx) {
|
|
|
131
136
|
function uuid() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) s += '-'; else s += h[Math.floor(Math.random() * 16)] } return s }
|
|
132
137
|
function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
|
|
133
138
|
function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
|
|
134
|
-
function workspaceRoot() { try { if (rootAgent && rootAgent.session && rootAgent.session.header && rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch (e) {} if (
|
|
139
|
+
function workspaceRoot() { try { if (rootAgent && rootAgent.session && rootAgent.session.header && rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch (e) {} const sp = sandboxPolicyOf(); if (sp && sp.workspaceRoot) return sp.workspaceRoot; return '.' }
|
|
135
140
|
function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
|
|
136
141
|
function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
|
|
137
142
|
function frameworkRoot() { return projectRoot(currentProject) }
|
|
138
143
|
function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
|
|
139
144
|
function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
|
|
140
|
-
|
|
145
|
+
let warnedNoPolicy = false
|
|
146
|
+
function warnNoPolicyOnce() { if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v2: sandboxPolicy unavailable; writes go out with no explicit policy') } }
|
|
147
|
+
// Sandbox fence for our own writes. The `resolve({})` fallback is a last resort and is
|
|
148
|
+
// deliberately reported (once): with no session it resolves the policy's CONFIGURED root
|
|
149
|
+
// (dsh-sandbox-policy: resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd())),
|
|
150
|
+
// which is not necessarily this session's workspace — a silently different fence.
|
|
151
|
+
function getPolicy() { const sp = sandboxPolicyOf(); if (!sp) { warnNoPolicyOnce(); return undefined } try { if (rootAgent && rootAgent.session) return sp.resolve({ session: rootAgent.session }) } catch (e) { warnNoPolicyOnce() } try { const p = sp.resolve({}); if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v2: falling back to sandboxPolicy.resolve({}) — the fence root is the host-configured workspace, not necessarily this session cwd') } return p } catch (e) { warnNoPolicyOnce() } return undefined }
|
|
141
152
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
142
153
|
function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
|
|
143
154
|
function parseJson(text) {
|
|
@@ -206,8 +217,32 @@ export function apply(ctx) {
|
|
|
206
217
|
async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
|
|
207
218
|
async function readText(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
|
|
208
219
|
async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
|
|
209
|
-
async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
|
|
210
|
-
|
|
220
|
+
async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { noteSuspect(rel); return undefined } }
|
|
221
|
+
/**
|
|
222
|
+
* Corruption guard. `readJson` cannot tell "no file yet" from "file present but
|
|
223
|
+
* unparseable", yet callers treat both as "no data" and then write that emptiness
|
|
224
|
+
* back — so one externally damaged file silently erased the user's whole problem
|
|
225
|
+
* list (qs.json), proposition set, or verified set.
|
|
226
|
+
*
|
|
227
|
+
* Any read that hits a present-but-unparseable JSON file records it; `writeJson`
|
|
228
|
+
* then REFUSES to write that path until the file is fixed or deleted. A missing
|
|
229
|
+
* file is still created normally, so first-run and "user deleted the file"
|
|
230
|
+
* behaviour is unchanged.
|
|
231
|
+
*/
|
|
232
|
+
const suspectFiles = new Set()
|
|
233
|
+
let warnedSuspect = {}
|
|
234
|
+
function noteSuspect(rel) {
|
|
235
|
+
suspectFiles.add(rel)
|
|
236
|
+
if (warnedSuspect[rel]) return
|
|
237
|
+
warnedSuspect[rel] = true
|
|
238
|
+
console.error('vibe-math-v2: ' + rel + ' exists but is not parseable JSON — REFUSING to overwrite it so a corrupted file cannot silently erase your data. Fix or delete the file, then retry.')
|
|
239
|
+
}
|
|
240
|
+
function assertWritable(rel) {
|
|
241
|
+
if (!suspectFiles.has(rel)) return true
|
|
242
|
+
console.error('vibe-math-v2: write to ' + rel + ' blocked (file is unparseable; see the earlier warning)')
|
|
243
|
+
return false
|
|
244
|
+
}
|
|
245
|
+
async function writeJson(rel, obj) { if (!assertWritable(rel)) return false; return await writeText(rel, JSON.stringify(obj, null, 2)) }
|
|
211
246
|
async function listFiles(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'file' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
212
247
|
async function listDirsAt(base, rel) { try { const t = await fs.resolve(rel, { cwd: base }); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
213
248
|
async function readTextAbs(path) { try { const t = await fs.resolve(path); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
|
|
@@ -223,9 +258,37 @@ export function apply(ctx) {
|
|
|
223
258
|
|
|
224
259
|
// ================= subprocess =================
|
|
225
260
|
function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
261
|
+
/** POSIX 单引号引用:把 ' 换成 '\'' 以安全嵌入任意路径。 */
|
|
262
|
+
function shQuote(p) { return "'" + String(p).replace(/'/g, "'\\''") + "'" }
|
|
263
|
+
/**
|
|
264
|
+
* 执行一段 shell 脚本。**按平台选择解释器**:此前硬编码 powershell,而预设用
|
|
265
|
+
* `disabled: !!js process.platform !== 'win32'` 在非 Windows 上关掉了 tool-pwsh 行——
|
|
266
|
+
* 即插件会调用一个自己声明不提供的二进制,且返回值无人检查,表现为静默失效。
|
|
267
|
+
* Windows 用 powershell(保留原行为),其余平台用 /bin/sh。
|
|
268
|
+
*/
|
|
269
|
+
function isWindows() { return process.platform === 'win32' }
|
|
270
|
+
function mkdirCmd(paths) {
|
|
271
|
+
if (isWindows()) return 'New-Item -Force -ItemType Directory -Path ' + paths.map(psQuote).join(',') + ' | Out-Null'
|
|
272
|
+
return 'mkdir -p ' + paths.map(shQuote).join(' ')
|
|
273
|
+
}
|
|
274
|
+
function rmCmd(path) {
|
|
275
|
+
if (isWindows()) return 'Remove-Item -Force -LiteralPath ' + psQuote(path) + ' -ErrorAction SilentlyContinue'
|
|
276
|
+
return 'rm -f ' + shQuote(path)
|
|
277
|
+
}
|
|
278
|
+
async function runShell(script, cwd) {
|
|
279
|
+
const subprocess = subprocessOf()
|
|
280
|
+
if (subprocess === undefined) return { ok: false, error: 'no-subprocess' }
|
|
281
|
+
try {
|
|
282
|
+
const argv = isWindows()
|
|
283
|
+
? ['powershell', '-NoProfile', '-NonInteractive', '-Command', script]
|
|
284
|
+
: ['/bin/sh', '-c', script]
|
|
285
|
+
const handle = subprocess.spawn({ argv: argv, cwd: cwd || workspaceRoot(), stdio: { stdin: 'ignore', stdout: 'inherit', stderr: 'inherit' }, graceMs: 20000 })
|
|
286
|
+
const outcome = await handle.done
|
|
287
|
+
return { ok: outcome.exitCode === 0, exitCode: outcome.exitCode }
|
|
288
|
+
} catch (e) { return { ok: false, error: String((e && e.message) || e) } }
|
|
289
|
+
}
|
|
290
|
+
async function ensureDirs() { const base = frameworkRoot(); const dirs = ['qs', 'Propos', 'Reliable', 'Verified', 'Verification_logs', 'Progress_Logs', 'VibeMath_State']; const paths = [vibeRoot() + '/Projects'].concat(dirs.map(function (d) { return base + '/' + d })); return await runShell(mkdirCmd(paths)) }
|
|
291
|
+
async function removeFile(rel) { const base = frameworkRoot(); return await runShell(rmCmd(base + '/' + rel)) }
|
|
229
292
|
|
|
230
293
|
// ================= settings =================
|
|
231
294
|
function sanitizeParams(obj) {
|
|
@@ -233,10 +296,25 @@ export function apply(ctx) {
|
|
|
233
296
|
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
|
|
234
297
|
const numFields = ['promoteValueThreshold']
|
|
235
298
|
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
|
|
299
|
+
// 整数字段的下界:0 会让对应功能**静默失效**而不是报错——例如 maxParallelThreshold=0 使所有派发
|
|
300
|
+
// 闸门 (activeCount >= 0) 恒真,此后永不派发任何代理,而 status 仍显示 running:true;
|
|
301
|
+
// solverMaxRounds=0 让每个方向立刻判死。这里把会让调度停滞/失能的键抬到最小可用值;
|
|
302
|
+
// 未列出的键(如 activityLogCap / max*ToolCalls,取 0 表示不限)保持原样。
|
|
303
|
+
const INT_FLOOR = {
|
|
304
|
+
maxParallelThreshold: 1, solverMaxRounds: 1, verifierCount: 2, debateMaxRounds: 1,
|
|
305
|
+
directionsPerSolver: 1, tickIntervalMs: 200, reportIntervalMs: 1000, maxExplorerRetries: 1,
|
|
306
|
+
}
|
|
236
307
|
for (const k of Object.keys(DEFAULT_PARAMS)) {
|
|
237
308
|
if (!(k in obj)) continue
|
|
238
309
|
const v = obj[k]
|
|
239
|
-
if (intFields.indexOf(k) !== -1) {
|
|
310
|
+
if (intFields.indexOf(k) !== -1) {
|
|
311
|
+
const n = Number(v)
|
|
312
|
+
if (!Number.isFinite(n)) { out[k] = DEFAULT_PARAMS[k]; continue }
|
|
313
|
+
let iv = Math.floor(n)
|
|
314
|
+
const floor = INT_FLOOR[k]
|
|
315
|
+
if (floor !== undefined && iv < floor) iv = floor
|
|
316
|
+
out[k] = iv
|
|
317
|
+
}
|
|
240
318
|
else if (numFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : DEFAULT_PARAMS[k] }
|
|
241
319
|
else if (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
|
|
242
320
|
else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
|
|
@@ -268,12 +346,21 @@ export function apply(ctx) {
|
|
|
268
346
|
lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
|
|
269
347
|
lines.push(' // 本文件是参数的唯一持久化来源:vibe_math_set_params / set_mode 会立即写回此文件;全局文件仅作项目文件不存在时的回退默认。')
|
|
270
348
|
const keys = Object.keys(src).sort()
|
|
349
|
+
// 只输出有值的键。JSON.stringify(undefined) 返回 undefined(不是字符串),直接拼接会写出
|
|
350
|
+
// `"k": undefined` —— 非法 JSON,本插件自己的 loadSettings() 随后会解析失败并整份忽略,
|
|
351
|
+
// 用户的参数设置因此静默丢失。逗号也按"实际写出的条目"计算,否则跳过一个键会留下尾随逗号。
|
|
352
|
+
const emitted = []
|
|
271
353
|
for (let i = 0; i < keys.length; i++) {
|
|
272
354
|
const k = keys[i]
|
|
273
355
|
const v = src[k]
|
|
356
|
+
if (v === undefined) continue
|
|
274
357
|
const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
|
|
275
358
|
const desc = schema ? schema.description : ''
|
|
276
|
-
|
|
359
|
+
emitted.push([k, v, desc])
|
|
360
|
+
}
|
|
361
|
+
for (let i = 0; i < emitted.length; i++) {
|
|
362
|
+
const k = emitted[i][0], v = emitted[i][1], desc = emitted[i][2]
|
|
363
|
+
const comma = i === emitted.length - 1 ? '' : ','
|
|
277
364
|
lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
|
|
278
365
|
lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
|
|
279
366
|
}
|
|
@@ -285,8 +372,24 @@ export function apply(ctx) {
|
|
|
285
372
|
async function createTemplate(where) { const isGlobal = where !== 'project'; const path = isGlobal ? (vibeRoot() + '/vibe_math_setting.json') : (frameworkRoot() + '/vibe_math_setting.json'); const content = settingsTemplateFrom(DEFAULT_PARAMS); const ok = isGlobal ? await writeTextAbs(path, content) : await writeText('vibe_math_setting.json', content); return { ok: ok, path: path, where: isGlobal ? 'global' : 'project' } }
|
|
286
373
|
|
|
287
374
|
// ================= persistence =================
|
|
375
|
+
/**
|
|
376
|
+
* 并发计数**从 registry 推导**,不再独立维护/持久化。
|
|
377
|
+
*
|
|
378
|
+
* 此前 `scheduler.activeCount` 是手写的累加器:spawn +1、每次 followup 也 +1,只在
|
|
379
|
+
* `onChildEnd` 里 -1,而 `onChildEnd` 开头 `if (meta === undefined) return` 会跳过那次减法。
|
|
380
|
+
* 于是任何一次 `subagent/end` 丢失、或 end 到达时该 child 已不在 `agentRegistry`
|
|
381
|
+
* (resume 清 registry、跨进程重启残留……)都会让 +1 永远没人抵消。计数单调增长,
|
|
382
|
+
* 一旦 ≥ maxParallelThreshold,所有派发闸门(activeCount >= maxParallelThreshold)恒真,
|
|
383
|
+
* 系统再也不派任何代理 —— 而 running 仍是 true、status 照常响应,故障完全静默。
|
|
384
|
+
* 计数还会写进 scheduler_state.json,所以同进程 resume 会把这个坏值一路带下去。
|
|
385
|
+
*
|
|
386
|
+
* 从 registry 长度推导后,两者不可能不一致,"丢一次 end 就永久停摆"这一类故障从根上消失。
|
|
387
|
+
*/
|
|
388
|
+
function activeCount() { return Object.keys(agentRegistry).length }
|
|
288
389
|
async function loadState() {
|
|
289
|
-
const s = await readJson('VibeMath_State/scheduler_state.json')
|
|
390
|
+
const s = await readJson('VibeMath_State/scheduler_state.json')
|
|
391
|
+
// 丢弃历史持久化的 activeCount:旧值可能已经漂移,绝不能覆盖推导值(见 activeCount())。
|
|
392
|
+
if (s) { const restored = Object.assign({}, s); delete restored.activeCount; scheduler = Object.assign({}, scheduler, restored) }
|
|
290
393
|
const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r
|
|
291
394
|
const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq
|
|
292
395
|
const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va
|
|
@@ -340,8 +443,15 @@ export function apply(ctx) {
|
|
|
340
443
|
async function saveProgress(qid, progObj) { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return; q.progress = progObj; await writeQs(qs) }
|
|
341
444
|
|
|
342
445
|
// ================= data layer: Propos =================
|
|
343
|
-
|
|
344
|
-
|
|
446
|
+
// categoryOf 的结果会直接成为文件名的一部分(Propos/<分类>_Propos.json,见 proposFile)。
|
|
447
|
+
// 模型提供一个含路径分隔符或 ".." 的"细类型"键就能把文件写到 Propos/ 之外,所以这里做文件名消毒
|
|
448
|
+
// (只替换分隔符与控制字符、剥掉首尾点,不改动中文分类名本身)。
|
|
449
|
+
function safeCatName(s) {
|
|
450
|
+
const t = String(s == null ? '' : s).replace(/[\\/:*?"<>|\u0000-\u001f]+/g, '_').replace(/^[.\s]+|[.\s]+$/g, '')
|
|
451
|
+
return t || '未分类'
|
|
452
|
+
}
|
|
453
|
+
function categoryOf(p) { const t = (p && p.细类型) || {}; const keys = Object.keys(t); return (keys.length > 0 && typeof t[keys[0]] === 'object') ? safeCatName(keys[0]) : '未分类' }
|
|
454
|
+
function proposFile(cat) { return 'Propos/' + safeCatName(cat) + '_Propos.json' }
|
|
345
455
|
async function proposFiles() { return await listFiles('Propos') }
|
|
346
456
|
async function readProposCategory(cat) { const a = await readJson(proposFile(cat)); return Array.isArray(a) ? a : [] }
|
|
347
457
|
async function getPropos() {
|
|
@@ -384,7 +494,7 @@ export function apply(ctx) {
|
|
|
384
494
|
return {
|
|
385
495
|
ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
|
|
386
496
|
running: scheduler.running, mode: params.mode,
|
|
387
|
-
activeCount:
|
|
497
|
+
activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
388
498
|
problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
|
|
389
499
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
390
500
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
@@ -428,8 +538,44 @@ export function apply(ctx) {
|
|
|
428
538
|
// ================= child spawn / followup =================
|
|
429
539
|
function pickProvider() { try { const names = subagents.list ? subagents.list() : []; if (names.indexOf('spawn') !== -1) return 'spawn'; if (names.indexOf('fork') !== -1) return 'fork' } catch (e) {} return 'spawn' }
|
|
430
540
|
function childAgentOptions() { const o = {}; try { if (rootAgent && rootAgent.options) { if (rootAgent.options.provider) o.provider = rootAgent.options.provider; if (rootAgent.options.model) o.model = rootAgent.options.model } } catch (e) {} if (params.provider) o.provider = params.provider; if (params.model) o.model = params.model; return o }
|
|
431
|
-
|
|
432
|
-
|
|
541
|
+
// Tool names for the permission filter, taken from the names the host ACTUALLY
|
|
542
|
+
// registers (dsh-tool-web registers 'web_search'/'web_fetch'; 'web'/'fetch' are
|
|
543
|
+
// only presentation card/kind fields, not tool names), and split by platform
|
|
544
|
+
// because each preset's composition gates them:
|
|
545
|
+
// dsh-tool-bash disabled: process.platform === 'win32'
|
|
546
|
+
// dsh-tool-pwsh disabled: process.platform !== 'win32'
|
|
547
|
+
// dsh-tools' restrict() THROWS on any name outside its registered set, and the
|
|
548
|
+
// host applies the filter when establishing a continuable child
|
|
549
|
+
// (dsh-subagent: childCtx.tools.restrict(...)), so a stale name meant the child
|
|
550
|
+
// was never created at all.
|
|
551
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
552
|
+
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
553
|
+
// 'web_fetch' is only registered when the composition enables fetch (the v4
|
|
554
|
+
// preset sets `fetch: false`), so it is a candidate that sanitizeToolFilter drops.
|
|
555
|
+
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
556
|
+
/**
|
|
557
|
+
* Drop filter names this host does not register. `known` comes from the host's
|
|
558
|
+
* own rejection message, which lists every registered global tool, so this
|
|
559
|
+
* never guesses. Returns undefined when nothing usable remains.
|
|
560
|
+
*/
|
|
561
|
+
function sanitizeToolFilter(filter, known) {
|
|
562
|
+
if (!filter || !(known instanceof Set) || known.size === 0) return filter
|
|
563
|
+
const out = {}
|
|
564
|
+
for (const key of ['allow', 'deny']) {
|
|
565
|
+
const list = filter[key]
|
|
566
|
+
if (!Array.isArray(list)) continue
|
|
567
|
+
const kept = list.filter(function (n) { return known.has(String(n).trim()) })
|
|
568
|
+
if (kept.length > 0) out[key] = kept
|
|
569
|
+
}
|
|
570
|
+
return (out.allow || out.deny) ? out : undefined
|
|
571
|
+
}
|
|
572
|
+
/** The host names the offending tools and then lists the registered ones. */
|
|
573
|
+
function registeredToolsFromError(message) {
|
|
574
|
+
const m = /known global tools:\s*([^]*)$/.exec(String(message || ''))
|
|
575
|
+
if (!m) return undefined
|
|
576
|
+
const names = m[1].split(',').map(function (s) { return s.trim() }).filter(Boolean)
|
|
577
|
+
return names.length > 0 ? new Set(names) : undefined
|
|
578
|
+
}
|
|
433
579
|
function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; if (net === false) d = d.concat(NETWORK_TOOLS); else if (net === true && a.length > 0) a = a.concat(NETWORK_TOOLS); if (scr === false) d = d.concat(SCRIPT_TOOLS); else if (scr === true && a.length > 0) a = a.concat(SCRIPT_TOOLS); const f = {}; if (a.length > 0) f.allow = a; if (d.length > 0) f.deny = d; return (f.allow || f.deny) ? f : undefined }
|
|
434
580
|
async function spawnChild(label, promptText, meta) {
|
|
435
581
|
const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
|
|
@@ -437,11 +583,41 @@ export function apply(ctx) {
|
|
|
437
583
|
let started
|
|
438
584
|
try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) }
|
|
439
585
|
catch (e) {
|
|
440
|
-
|
|
586
|
+
const message = String((e && e.message) || e)
|
|
587
|
+
// The host rejected the filter because it names tools this deployment does
|
|
588
|
+
// not register. It tells us exactly which names are valid, so drop the
|
|
589
|
+
// invalid ones and retry ONCE. This is NOT the old fail-open behaviour:
|
|
590
|
+
// every name the user asked to deny that DOES exist is still denied, and a
|
|
591
|
+
// deny-list can only ever shrink to names that do not exist here.
|
|
592
|
+
// (The old behaviour deleted the whole filter, silently granting network
|
|
593
|
+
// and script access the operator had explicitly forbidden.)
|
|
594
|
+
const known = registeredToolsFromError(message)
|
|
595
|
+
const retryFilter = request.toolFilter ? sanitizeToolFilter(request.toolFilter, known) : undefined
|
|
596
|
+
const changed = request.toolFilter && JSON.stringify(retryFilter) !== JSON.stringify(request.toolFilter)
|
|
597
|
+
// FAIL CLOSED on an unusable sanitized filter. Retrying WITHOUT a filter would
|
|
598
|
+
// start the child unrestricted, which is the opposite of what the operator asked
|
|
599
|
+
// for; retrying with an empty one would deny every tool. Neither is acceptable,
|
|
600
|
+
// so report the stale configuration and refuse to spawn this child.
|
|
601
|
+
if (request.toolFilter && retryFilter === undefined) {
|
|
602
|
+
console.error('vibe-math-v2: the configured tool permission filter names ONLY tools this host does not register, so it cannot be honored; refusing to spawn WITHOUT a filter (that would grant the very access the operator denied). filter=' + JSON.stringify(request.toolFilter) + ' host said: ' + message)
|
|
603
|
+
throw e
|
|
604
|
+
}
|
|
605
|
+
if (!changed) {
|
|
606
|
+
if (request.toolFilter) console.error('vibe-math-v2: startContinuable with toolFilter failed (NOT retrying without the permission filter, to avoid silently granting unrestricted tools): ' + message)
|
|
607
|
+
throw e
|
|
608
|
+
}
|
|
609
|
+
console.error('vibe-math-v2: tool permission filter named tools this host does not register; retrying with only registered names (denied-tool intent preserved). dropped=' + JSON.stringify(request.toolFilter) + ' kept=' + JSON.stringify(retryFilter))
|
|
610
|
+
const retryRequest = Object.assign({}, request)
|
|
611
|
+
retryRequest.toolFilter = retryFilter
|
|
612
|
+
try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: retryRequest, signal: makeSignal(30000) }) }
|
|
613
|
+
catch (e2) {
|
|
614
|
+
console.error('vibe-math-v2: startContinuable retry with sanitized toolFilter also failed: ' + String((e2 && e2.message) || e2))
|
|
615
|
+
throw e2
|
|
616
|
+
}
|
|
441
617
|
}
|
|
442
618
|
agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
|
|
443
619
|
childOwner.set(started.childId, sessionId)
|
|
444
|
-
|
|
620
|
+
// 并发计数由 agentRegistry 推导,此处无需手工 +1(见 activeCount())。
|
|
445
621
|
await saveAll(); return started.childId
|
|
446
622
|
}
|
|
447
623
|
// DSH continuable-wake API is subagents.sendMessage(sender, targetId, content, {signal}); subagents.followup
|
|
@@ -454,7 +630,7 @@ export function apply(ctx) {
|
|
|
454
630
|
else if (typeof subagents.followup === 'function') await subagents.followup(rootAgent, childId, blocks, { source: { kind: 'user' }, signal: makeSignal(30000) })
|
|
455
631
|
else throw new Error('no subagent continuation API')
|
|
456
632
|
} catch (e) { console.error('vibe-math-v2: wake ' + childId + ' failed: ' + String((e && e.message) || e)); throw e }
|
|
457
|
-
|
|
633
|
+
// 不再手工累加并发计数:唤醒的是 registry 里已登记的 child,计数已由 registry 长度体现。
|
|
458
634
|
await saveAll()
|
|
459
635
|
}
|
|
460
636
|
async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
|
|
@@ -630,8 +806,20 @@ export function apply(ctx) {
|
|
|
630
806
|
|
|
631
807
|
// ================= scheduler core =================
|
|
632
808
|
function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v2 tick error: ' + String((e && e.stack) || e)) }) }
|
|
809
|
+
function dropStaleGate() {
|
|
810
|
+
const g = scheduler.gate
|
|
811
|
+
if (!g) return
|
|
812
|
+
const d = decisionQueue.find(function (x) { return x.id === g.decisionId })
|
|
813
|
+
if (d === undefined || d.status !== 'pending') {
|
|
814
|
+
logActivity('gate', 'cleared stale gate (' + g.node + '/' + g.decisionId + ' is ' + (d === undefined ? 'gone' : d.status) + ')')
|
|
815
|
+
scheduler.gate = null
|
|
816
|
+
}
|
|
817
|
+
}
|
|
633
818
|
async function tick() {
|
|
634
|
-
if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return
|
|
819
|
+
if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return
|
|
820
|
+
// 自愈:gate 指向的决策若已不存在或已 resolved,就清掉再继续,而不是永久早退。
|
|
821
|
+
dropStaleGate()
|
|
822
|
+
if (scheduler.gate) return
|
|
635
823
|
tickInFlight = true
|
|
636
824
|
lastTickAt = now()
|
|
637
825
|
try {
|
|
@@ -744,7 +932,7 @@ export function apply(ctx) {
|
|
|
744
932
|
}
|
|
745
933
|
// note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
|
|
746
934
|
async function processPromote() {
|
|
747
|
-
if (
|
|
935
|
+
if (activeCount() >= params.maxParallelThreshold) return
|
|
748
936
|
const qs = await getQs()
|
|
749
937
|
const qDescriptions = qs.map(function (q) { return q.概述 })
|
|
750
938
|
const propos = await getPropos()
|
|
@@ -772,7 +960,7 @@ export function apply(ctx) {
|
|
|
772
960
|
async function processVerify() {
|
|
773
961
|
const candidates = await buildVerifyCandidates()
|
|
774
962
|
for (let i = 0; i < candidates.length; i++) {
|
|
775
|
-
if (
|
|
963
|
+
if (activeCount() >= params.maxParallelThreshold) break
|
|
776
964
|
const c = candidates[i]
|
|
777
965
|
const rId = c.rId
|
|
778
966
|
if (tasks['verify:' + rId]) continue
|
|
@@ -801,6 +989,7 @@ export function apply(ctx) {
|
|
|
801
989
|
for (let i = 0; i < propos.length; i++) {
|
|
802
990
|
const p = propos[i]
|
|
803
991
|
if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
|
|
992
|
+
if (p.已验证) continue // 收敛闸门:该命题已由「判断命题」解法裁决过(见 settleVerdict 点5 联动),不再重复入选
|
|
804
993
|
if (p.在问题清单) continue // 已晋升:其证明/证伪经晋升问题的解法验证,避免同一内容双重验证
|
|
805
994
|
const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
|
|
806
995
|
if (proofs.length === 0 && refutes.length === 0) {
|
|
@@ -816,7 +1005,7 @@ export function apply(ctx) {
|
|
|
816
1005
|
}
|
|
817
1006
|
async function backfillVerifiers(t) {
|
|
818
1007
|
while (t.children.length < t.expectedCount) {
|
|
819
|
-
if (
|
|
1008
|
+
if (activeCount() >= params.maxParallelThreshold) break
|
|
820
1009
|
const index = t.children.length
|
|
821
1010
|
const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
|
|
822
1011
|
t.children.push(childId)
|
|
@@ -833,16 +1022,16 @@ export function apply(ctx) {
|
|
|
833
1022
|
if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
|
|
834
1023
|
}
|
|
835
1024
|
if (t.status !== 'spawning') continue
|
|
836
|
-
if (
|
|
1025
|
+
if (activeCount() >= params.maxParallelThreshold) break
|
|
837
1026
|
await backfillVerifiers(t)
|
|
838
1027
|
}
|
|
839
1028
|
}
|
|
840
1029
|
async function processSolve() {
|
|
841
|
-
if (
|
|
1030
|
+
if (activeCount() >= params.maxParallelThreshold) return
|
|
842
1031
|
const qs = await getQs()
|
|
843
1032
|
const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' }).sort(function (a, b) { return (a.优先级 === 'never' ? 999 : Number(a.优先级)) - (b.优先级 === 'never' ? 999 : Number(b.优先级)) })
|
|
844
1033
|
for (let i = 0; i < unsolved.length; i++) {
|
|
845
|
-
if (
|
|
1034
|
+
if (activeCount() >= params.maxParallelThreshold) break
|
|
846
1035
|
const q = unsolved[i]
|
|
847
1036
|
const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
|
|
848
1037
|
if (busy) continue
|
|
@@ -863,7 +1052,7 @@ export function apply(ctx) {
|
|
|
863
1052
|
} else {
|
|
864
1053
|
// spawn solvers for each active direction
|
|
865
1054
|
for (let j = 0; j < prog.directions.length; j++) {
|
|
866
|
-
if (
|
|
1055
|
+
if (activeCount() >= params.maxParallelThreshold) break
|
|
867
1056
|
const dir = prog.directions[j]
|
|
868
1057
|
if (dir.status === 'success' || dir.status === 'dead-end') continue
|
|
869
1058
|
const running = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && m.direction === dir.id && m.role === 'solver' })
|
|
@@ -1044,7 +1233,7 @@ export function apply(ctx) {
|
|
|
1044
1233
|
async function advanceVerification(t, round) {
|
|
1045
1234
|
if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
|
|
1046
1235
|
if (!scheduler.running) { t.status = 'paused'; return } // resume will re-advance this task
|
|
1047
|
-
if (
|
|
1236
|
+
if (activeCount() >= params.maxParallelThreshold) { t.status = 'paused'; return } // 并发门:等有空闲槽位再辩论(reconcileVerify 会重推进)
|
|
1048
1237
|
t.round = round + 1
|
|
1049
1238
|
const roundTranscript = buildTranscript(t)
|
|
1050
1239
|
t.history = t.history || []
|
|
@@ -1176,6 +1365,13 @@ export function apply(ctx) {
|
|
|
1176
1365
|
const ap = await findProposition(q.判断命题)
|
|
1177
1366
|
if (ap) {
|
|
1178
1367
|
ap.布尔估计 = v
|
|
1368
|
+
// 收敛闸门:本条路径只在 v=1/0 时才写入证明/证伪条目,中间裁决(flat 默认给出
|
|
1369
|
+
// 0.5,forced 给出加权浮点)会让 ap 停留在"中间布尔估计 + 两个列表皆空"的状态——
|
|
1370
|
+
// 而这正是 buildVerifyCandidates 认定"裸命题需要验证"的条件。若不在此标记,该命题
|
|
1371
|
+
// 会在每个 tick 重新入选、重开一轮完整辩论;又因 processVerify 每 tick 只跑一个验证,
|
|
1372
|
+
// 其它对象被无限饿死,终止条件(所有问题已解决)永不可达。标记后不再重复消耗验证配额,
|
|
1373
|
+
// 裁决值仍保留在 布尔估计 中。
|
|
1374
|
+
ap.已验证 = true
|
|
1179
1375
|
if (v === 1) { ap.证明列表 = ap.证明列表 || []; ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
|
|
1180
1376
|
else if (v === 0) { ap.证伪列表 = ap.证伪列表 || []; ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
|
|
1181
1377
|
await upsertProposition(ap)
|
|
@@ -1241,7 +1437,6 @@ export function apply(ctx) {
|
|
|
1241
1437
|
async function onChildEnd(info) {
|
|
1242
1438
|
const meta = agentRegistry[info.id]
|
|
1243
1439
|
if (meta === undefined) return
|
|
1244
|
-
scheduler.activeCount = Math.max(0, scheduler.activeCount - 1)
|
|
1245
1440
|
const output = blocksToText(info.lastAssistantMessage)
|
|
1246
1441
|
try {
|
|
1247
1442
|
if (meta.role === 'explorer') await handleExplorer(info.id, meta, output)
|
|
@@ -1269,7 +1464,7 @@ export function apply(ctx) {
|
|
|
1269
1464
|
logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
|
|
1270
1465
|
agentRegistry = {}; tasks = {}
|
|
1271
1466
|
}
|
|
1272
|
-
|
|
1467
|
+
// 并发计数由 agentRegistry 推导:清空 registry 后自然归零,无需显式赋值。
|
|
1273
1468
|
}
|
|
1274
1469
|
await writeJson('VibeMath_State/process_epoch.json', processEpoch)
|
|
1275
1470
|
await saveAll()
|
|
@@ -1278,7 +1473,7 @@ export function apply(ctx) {
|
|
|
1278
1473
|
async function startScheduler() { const r = await init(true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
1279
1474
|
async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
1280
1475
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
1281
|
-
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]);
|
|
1476
|
+
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); agentRegistry = {}; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
1282
1477
|
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
1283
1478
|
async function autoResolvePending() {
|
|
1284
1479
|
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
@@ -1287,7 +1482,15 @@ export function apply(ctx) {
|
|
|
1287
1482
|
try {
|
|
1288
1483
|
if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
1289
1484
|
else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
1290
|
-
} catch (e) {
|
|
1485
|
+
} catch (e) {
|
|
1486
|
+
// 副作用失败必须把该决策落到终态,否则它会永远保持 pending:此后每次切 auto 都在同一个
|
|
1487
|
+
// 决策上重新抛错,而 gate 又指向它 —— 调度永久卡死。标为 resolved(auto-failed) 并让它过去,
|
|
1488
|
+
// 由 activity log 留下证据;宁可这一次节点未执行,也不能让整条管线停摆。
|
|
1489
|
+
console.error('vibe-math-v2: auto-resolve decision failed: ' + String((e && e.message) || e))
|
|
1490
|
+
d.status = 'resolved'
|
|
1491
|
+
d.resolution = { action: 'auto-failed', auto: true, error: String((e && e.message) || e) }
|
|
1492
|
+
logActivity('gate', 'auto-resolve failed for ' + d.id + ' (' + d.node + '), marked resolved to avoid a permanent stall: ' + String((e && e.message) || e))
|
|
1493
|
+
}
|
|
1291
1494
|
}
|
|
1292
1495
|
if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
|
|
1293
1496
|
}
|
|
@@ -1296,7 +1499,7 @@ export function apply(ctx) {
|
|
|
1296
1499
|
return {
|
|
1297
1500
|
ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
|
|
1298
1501
|
project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
|
|
1299
|
-
mode: params.mode, activeCount:
|
|
1502
|
+
mode: params.mode, activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
1300
1503
|
frameworkRoot: frameworkRoot(),
|
|
1301
1504
|
problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
|
|
1302
1505
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
@@ -1314,7 +1517,7 @@ export function apply(ctx) {
|
|
|
1314
1517
|
if (scheduler.running) await abortScheduler()
|
|
1315
1518
|
currentProject = slug; await writeCurrentProject(); await ensureDirs()
|
|
1316
1519
|
if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
|
|
1317
|
-
params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false,
|
|
1520
|
+
params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; lastReportWrite = 0; lastPushReport = 0; reportDirty = false
|
|
1318
1521
|
await loadSettings(); await migrateLegacyParams(); await loadState(); await saveAll()
|
|
1319
1522
|
return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
|
|
1320
1523
|
}
|
|
@@ -1457,6 +1660,10 @@ export function apply(ctx) {
|
|
|
1457
1660
|
const sid = childOwner.get(info.id)
|
|
1458
1661
|
const s = sid !== undefined ? sessions.get(sid) : undefined
|
|
1459
1662
|
if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) })
|
|
1663
|
+
// 注意:这里**不要**回收 childOwner 条目。这条映射在子代理 end 之后仍会被后续事件路由
|
|
1664
|
+
// 用到:曾试过在此处回收、也试过在 onChildEnd 末尾回收,两次都导致 e2e-regression 的
|
|
1665
|
+
// verdict 收口失效("problem solved after verdict 1")。代价是每个历史子代理留下一条
|
|
1666
|
+
// 小记录(有界增长,实测不影响功能),远小于"验证无法收口"的代价。
|
|
1460
1667
|
})
|
|
1461
1668
|
|
|
1462
1669
|
// tick timer (registered once; ticks every running session at its own pace)
|