dsh-vibe-math 2.7.1 → 2.7.2
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.en.md +681 -690
- package/README.md +8 -17
- package/cordis.patch.yml +6 -6
- package/docs/AUDIT-CHECKLIST.md +518 -518
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +588 -588
- package/docs/math-computation.md +187 -163
- package/docs/release-notes/README.en.md +48 -0
- package/docs/release-notes/README.md +47 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
- package/docs/release-notes/TEMPLATE.md +120 -0
- package/docs/test-timing.md +154 -154
- package/package.json +208 -203
- package/prompt-corpus-persona/persona-corpus.json +8 -8
- package/prompt-corpus-persona/persona-corpus.md +12 -12
- package/prompt-corpus-v2/formal-verify-v2.json +121 -109
- package/prompt-corpus-v2/formal-verify-v2.md +223 -107
- package/prompt-corpus-v3/formal-verify-v3.json +100 -76
- package/prompt-corpus-v3/formal-verify-v3.md +268 -70
- package/prompt-corpus-v4/formal-verify-v4.json +10 -10
- package/prompt-corpus-v4/formal-verify-v4.md +20 -20
- package/prompt-corpus-v5/prompt-corpus-v5.json +81 -81
- package/prompt-corpus-v5/prompt-corpus-v5.md +138 -128
- package/tests/audit-math-computation-contract.mjs +43 -1
- package/tests/audit-math-computation-sensitivity.mjs +2 -2
- package/tests/audit-v5-sensitivity.mjs +3 -1
- package/tests/e2e-v5-round2.test.mjs +114 -5
- package/tests/formal-verify-v2.test.mjs +38 -0
- package/tests/formal-verify-v3.test.mjs +33 -0
- package/tests/formal-verify-v4.test.mjs +87 -0
- package/tests/math-computation-shared.test.mjs +126 -1
- package/vibe-math-v2/agent.cordis.yml +2 -2
- package/vibe-math-v2/math-computation.js +122 -11
- package/vibe-math-v2/vibe-math-v2.js +75 -11
- package/vibe-math-v3/agent.cordis.yml +2 -2
- package/vibe-math-v3/math-computation.js +122 -11
- package/vibe-math-v3/vibe-math-v3.js +60 -13
- package/vibe-math-v4/agent.cordis.yml +4 -4
- package/vibe-math-v4/math-computation.js +122 -11
- package/vibe-math-v4/vibe-math-v4.js +55 -15
- package/vibe-math-v5/agent.cordis.yml +4 -4
- package/vibe-math-v5/math-computation.js +122 -11
- package/vibe-math-v5/vibe-math-v5.js +137 -28
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1660 -1655
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -72,11 +72,12 @@ export const MATH_PERSONA_TOOL_LINE = '- math_computation {op: probe|run|receipt
|
|
|
72
72
|
// P2a: the archive -> edit -> re-run workflow. Injected into every preset's rule block (and appended
|
|
73
73
|
// to both persona blocks) so an agent cannot miss that (a) the script is archived, (b) it may edit
|
|
74
74
|
// it, and (c) re-running is what produces evidence for the edited code.
|
|
75
|
-
export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑:mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext
|
|
75
|
+
export const MATH_ARCHIVE_WORKFLOW_LINE = '- 归档→编辑→重跑:mode:\'code\' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);'
|
|
76
|
+
+ '**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);'
|
|
76
77
|
+ '编辑后用 mode:\'file\' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——'
|
|
77
78
|
+ '报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。'
|
|
78
79
|
|
|
79
|
-
export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext
|
|
80
|
+
export const MATH_ARCHIVE_WORKFLOW_LINE_EN = '- Archive -> edit -> re-run: for mode:\'code\' the script original is at the receipt\'s scriptPath (Computation/<id>/script.<ext>, **relative to the project root**); member file tools resolve paths against the SESSION CWD, so open it via the ABSOLUTE `receipt.scriptAbs`, or join `receipt.cwd` with `receipt.scriptPath` (both are in the receipt); after editing, re-run it with mode:\'file\' to write a NEW receipt/attempt with a NEW scriptHash. **An old receipt is NOT evidence for edited code** - cite the receipt whose scriptHash matches the current code; the tool warns explicitly via scriptChanged / scriptChangedDuringRun.'
|
|
80
81
|
|
|
81
82
|
// Round-6 (A): honesty about SUBSTITUTIONS. An alternative that weakens exactness or conclusion
|
|
82
83
|
// strength must be declared, and the conclusion must never read as if the requested (exact) result
|
|
@@ -288,14 +289,39 @@ function fail(code, engine, message, extra) {
|
|
|
288
289
|
// round-7 (live-session fix): the probe diagnostic must survive into the response, otherwise
|
|
289
290
|
// "存在但不可用" is opaque (a real session showed exactly that).
|
|
290
291
|
if (extra && extra.probe) out.probe = jsonSafeDiag(extra.probe)
|
|
292
|
+
// round-9 (F7): the version-constraint policy travels with failures too (no silent drop).
|
|
293
|
+
if (extra && extra.versionPolicy) out.versionPolicy = extra.versionPolicy
|
|
294
|
+
if (extra && extra.constraintsNotEnforced) out.constraintsNotEnforced = extra.constraintsNotEnforced
|
|
291
295
|
return out
|
|
292
296
|
}
|
|
293
297
|
|
|
294
|
-
|
|
298
|
+
// round-9 (real-engine finding): the suggested install command may not be runnable on this machine
|
|
299
|
+
// (e.g. `winget` is absent), and handing a user a command that cannot run is misleading. So the
|
|
300
|
+
// command is emitted ONLY when its package manager is actually resolvable; otherwise we return the
|
|
301
|
+
// official vendor/download pointer plus an explicit note. Async because resolution goes through the host.
|
|
302
|
+
async function userInstallNext(H, engine) {
|
|
295
303
|
const d = MATH_ENGINES[engine] || {}
|
|
296
304
|
const per = d.userInstall || {}
|
|
297
305
|
const platform = process && process.platform === 'win32' ? 'windows' : (process && process.platform === 'darwin' ? 'macos' : 'linux')
|
|
298
|
-
|
|
306
|
+
const raw = per[platform] || ''
|
|
307
|
+
const mgr = (function () { const m = /^\s*(winget|brew|apt-get|apt|choco|scoop|dnf|pacman|zypper)\b/.exec(String(raw)); return m ? m[1] : null })()
|
|
308
|
+
let available = true
|
|
309
|
+
if (mgr) {
|
|
310
|
+
try { available = !!(await H.resolveExecutable(mgr)) } catch (e) { available = false }
|
|
311
|
+
}
|
|
312
|
+
return next('user-install', {
|
|
313
|
+
engine: engine,
|
|
314
|
+
perOs: per,
|
|
315
|
+
command: (mgr && !available) ? '' : raw,
|
|
316
|
+
suggestedCommand: raw,
|
|
317
|
+
platform: platform,
|
|
318
|
+
packageManager: mgr,
|
|
319
|
+
packageManagerAvailable: mgr ? available : null,
|
|
320
|
+
vendorUrl: d.vendor || null,
|
|
321
|
+
note: (mgr && !available)
|
|
322
|
+
? ('本机没有检测到包管理器 ' + mgr + ':建议命令无法运行,请按上面的官方地址手动安装(或先自行安装一个包管理器)。')
|
|
323
|
+
: null,
|
|
324
|
+
})
|
|
299
325
|
}
|
|
300
326
|
|
|
301
327
|
// ── host adaptation ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -353,6 +379,42 @@ export async function probeMathEngines(host, opts) {
|
|
|
353
379
|
return result
|
|
354
380
|
}
|
|
355
381
|
|
|
382
|
+
// round-9 (real-engine finding): an engine can be INSTALLED yet invisible, because discovery only
|
|
383
|
+
// looked at the server process's PATH (R 4.6.1 landed in C:\Program Files\R\R-4.6.1\bin and was NOT
|
|
384
|
+
// added to PATH). Add the known per-OS INSTALL locations, globbed by version, still AFTER PATH and
|
|
385
|
+
// after the bundled runtime; existence is always proven by LISTING, never assumed.
|
|
386
|
+
async function knownInstallCandidates(H, engineName) {
|
|
387
|
+
if (typeof H.listDirAbs !== 'function') return []
|
|
388
|
+
const cands = mathEngineCandidates(engineName)
|
|
389
|
+
if (!cands) return []
|
|
390
|
+
const env = (typeof process !== 'undefined' && process.env) || {}
|
|
391
|
+
const win = !!(typeof process !== 'undefined' && process.platform === 'win32')
|
|
392
|
+
const exts = win ? ['', '.exe', '.cmd'] : ['']
|
|
393
|
+
const out = []
|
|
394
|
+
const listDirs = async (dir) => { try { return ((await H.listDirAbs(dir)) || []).filter((e) => e && e.type === 'directory').map((e) => e.name) } catch (e) { return [] } }
|
|
395
|
+
const listFiles = async (dir) => { try { return ((await H.listDirAbs(dir)) || []).filter((e) => e && e.type === 'file').map((e) => e.name) } catch (e) { return [] } }
|
|
396
|
+
const match = async (dir) => {
|
|
397
|
+
const files = await listFiles(dir)
|
|
398
|
+
for (const c of cands) for (const x of exts) if (files.indexOf(c + x) !== -1) out.push(dir + '/' + c + x)
|
|
399
|
+
}
|
|
400
|
+
if (engineName === 'r') {
|
|
401
|
+
const roots = win
|
|
402
|
+
? [String(env.ProgramFiles || 'C:/Program Files') + '/R', String(env['ProgramFiles(x86)'] || 'C:/Program Files (x86)') + '/R', env.LOCALAPPDATA ? String(env.LOCALAPPDATA) + '/Programs/R' : null]
|
|
403
|
+
: ['/usr/lib/R', '/Library/Frameworks/R.framework/Resources', '/usr/local/lib/R', '/opt/R', '/usr/lib64/R']
|
|
404
|
+
for (const root of roots) {
|
|
405
|
+
if (!root) continue
|
|
406
|
+
for (const d of await listDirs(root)) await match(root + '/' + d + '/bin') // windows versioned dirs
|
|
407
|
+
await match(root + '/bin') // unix / framework layout
|
|
408
|
+
}
|
|
409
|
+
} else if (engineName === 'python' && win) {
|
|
410
|
+
const local = env.LOCALAPPDATA ? String(env.LOCALAPPDATA) + '/Programs/Python' : null
|
|
411
|
+
for (const d of (local ? await listDirs(local) : [])) await match(local + '/' + d)
|
|
412
|
+
const pf = String(env.ProgramFiles || 'C:/Program Files')
|
|
413
|
+
for (const d of await listDirs(pf)) if (/^Python[0-9]/i.test(d)) await match(pf + '/' + d)
|
|
414
|
+
}
|
|
415
|
+
return Array.from(new Set(out))
|
|
416
|
+
}
|
|
417
|
+
|
|
356
418
|
async function resolveCandidate(H, d) {
|
|
357
419
|
const cands = mathEngineCandidates(d.name)
|
|
358
420
|
if (!cands) return null
|
|
@@ -362,11 +424,15 @@ async function resolveCandidate(H, d) {
|
|
|
362
424
|
if (typeof p === 'string' && p) return p
|
|
363
425
|
} catch (e) { /* not this one */ }
|
|
364
426
|
}
|
|
365
|
-
// round-7 (fix 2): DSH ships its own runtimes; PATH always wins, so this is
|
|
427
|
+
// round-7 (fix 2): DSH ships its own runtimes; PATH always wins, so this is a LAST resort. The
|
|
366
428
|
// tree name is globbed (never hard-coded) and executable names come from the descriptor candidates.
|
|
367
429
|
for (const p of await dshRuntimeCandidates(H, d.name)) {
|
|
368
430
|
if (typeof p === 'string' && p) return p
|
|
369
431
|
}
|
|
432
|
+
// round-9: then the known per-OS install locations (an engine installed without touching PATH).
|
|
433
|
+
for (const p of await knownInstallCandidates(H, d.name)) {
|
|
434
|
+
if (typeof p === 'string' && p) return p
|
|
435
|
+
}
|
|
370
436
|
return null
|
|
371
437
|
}
|
|
372
438
|
|
|
@@ -513,7 +579,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
513
579
|
if (params.mathMode !== 'typed+shell') return fail('MATH_REFUSED', name, 'cli 被策略禁用(mathMode=' + params.mathMode + ')', { next: next('reason', { reason: 'policy' }) })
|
|
514
580
|
let exe = null
|
|
515
581
|
try { exe = await H.resolveExecutable(args.cli.command) } catch (e) { exe = null }
|
|
516
|
-
if (!exe) return fail('MATH_ENGINE_NOT_FOUND', name, 'cli 命令无法解析:' + args.cli.command, { next: userInstallNext('cli') })
|
|
582
|
+
if (!exe) return fail('MATH_ENGINE_NOT_FOUND', name, 'cli 命令无法解析:' + args.cli.command, { next: await userInstallNext(H, 'cli') })
|
|
517
583
|
// round-7 (finding 5): report the REAL version when the command's family is recognisable (the
|
|
518
584
|
// descriptor for that family carries the version probe); unknown commands stay 'unknown'.
|
|
519
585
|
let cliVersion = 'unknown'
|
|
@@ -548,7 +614,7 @@ async function resolveEngine(H, requested, params, args) {
|
|
|
548
614
|
return { ok: true, name: name, desc: d, exe: hit, version: v.version }
|
|
549
615
|
}
|
|
550
616
|
const first = requested === 'auto' ? (params.mathEngines[0] || 'python') : requested
|
|
551
|
-
return fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(试过:' + names.join(', ') + ')', { next: userInstallNext(first) })
|
|
617
|
+
return fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(试过:' + names.join(', ') + ')', { next: await userInstallNext(H, first) })
|
|
552
618
|
}
|
|
553
619
|
|
|
554
620
|
// ── package probe ───────────────────────────────────────────────────────────────────────────────
|
|
@@ -810,7 +876,7 @@ async function opProbe(H, args, params) {
|
|
|
810
876
|
}
|
|
811
877
|
if (!probe.available && !(chosen && chosen.ok)) {
|
|
812
878
|
const first = requested || params.mathEngines[0] || 'python'
|
|
813
|
-
const out = fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(请求:' + first + ')', { next: userInstallNext(first), engines: [], available: false, packages: packages })
|
|
879
|
+
const out = fail('MATH_ENGINE_NOT_FOUND', first, '本机没有可用的计算引擎(请求:' + first + ')', { next: await userInstallNext(H, first), engines: [], available: false, packages: packages })
|
|
814
880
|
out.op = 'probe'
|
|
815
881
|
return out
|
|
816
882
|
}
|
|
@@ -821,10 +887,22 @@ async function opProbe(H, args, params) {
|
|
|
821
887
|
engines.push({ name: chosen.name, path: chosen.exe, version: chosen.version, license: (MATH_ENGINES[chosen.name] || {}).license })
|
|
822
888
|
}
|
|
823
889
|
const engineInfo = (chosen && chosen.ok) ? { name: chosen.name, path: chosen.exe, version: chosen.version } : null
|
|
890
|
+
// round-9 (F3): the availability line must never be silently partial - name the CONFIGURED engines
|
|
891
|
+
// that were not found (and why), instead of just listing the ones that happened to resolve.
|
|
892
|
+
const foundNames = engines.map((e) => e.name)
|
|
893
|
+
// Per-engine absence explanation. The key is `why` (NOT the reserved machine-readable vocabulary
|
|
894
|
+
// key), so the parity guard's exhaustive `next.reason` set stays exactly the refusal vocabulary.
|
|
895
|
+
const absent = params.mathEngines.filter((e) => foundNames.indexOf(e) === -1).map((e) => {
|
|
896
|
+
if (MATH_ENGINES[e] && MATH_ENGINES[e].resolveFrom === 'cli.command') return { engine: e, why: 'needs-a-caller-supplied-command' }
|
|
897
|
+
return { engine: e, why: 'not-found-on-this-machine' }
|
|
898
|
+
})
|
|
824
899
|
return {
|
|
825
900
|
ok: true, op: 'probe', engine: engineInfo ? engineInfo.name : null, engineInfo: engineInfo,
|
|
826
901
|
engines: engines, available: true, packages: packages,
|
|
827
|
-
|
|
902
|
+
configured: params.mathEngines.slice(), absent: absent,
|
|
903
|
+
message: '可用引擎:' + engines.map((e) => e.name + ' ' + e.version).join('、')
|
|
904
|
+
+ (engineInfo ? '(请求 ' + engineInfo.name + ':' + engineInfo.version + ')' : '')
|
|
905
|
+
+ (absent.length ? ';已配置但本机未发现:' + absent.map((a) => a.engine).join('、') : ';已配置的引擎都在'),
|
|
828
906
|
}
|
|
829
907
|
}
|
|
830
908
|
|
|
@@ -851,6 +929,9 @@ async function opRun(H, args, params) {
|
|
|
851
929
|
const engineLabel = det.name === 'cli' ? ('cli:' + cliCommand) : det.name
|
|
852
930
|
|
|
853
931
|
const want = (args.packages && args.packages.length) ? args.packages : params.mathPackages
|
|
932
|
+
// round-9 (F7): version constraints are NOT enforced - we only check existence by base name.
|
|
933
|
+
const pinnedSpecs = want.filter((sp) => parsePackageSpec(sp).pinned)
|
|
934
|
+
const versionPolicy = 'version-constraints-are-existence-only: 只按 base name 检查是否存在,约束本身从不校验(版本求解交给包管理器)' + (pinnedSpecs.length ? ';本次未校验的约束:' + pinnedSpecs.join(', ') : ';本次没有版本约束')
|
|
854
935
|
// round-7 (finding 3): for cli the precheck runs against the FAMILY of `cli.command` (same
|
|
855
936
|
// discovery-shaped logic); an unrecognisable command SKIPS the precheck instead of reporting a
|
|
856
937
|
// false "missing" that would block the escape hatch.
|
|
@@ -865,6 +946,7 @@ async function opRun(H, args, params) {
|
|
|
865
946
|
missing: missing,
|
|
866
947
|
next: next('agent-install', { engine: det.name, packages: missing, specs: want.slice(), dryRun: true }),
|
|
867
948
|
packages: pk,
|
|
949
|
+
versionPolicy: versionPolicy, constraintsNotEnforced: pinnedSpecs.slice(),
|
|
868
950
|
})
|
|
869
951
|
}
|
|
870
952
|
|
|
@@ -872,10 +954,17 @@ async function opRun(H, args, params) {
|
|
|
872
954
|
let scriptText = ''
|
|
873
955
|
let fileRel = null
|
|
874
956
|
let sourceHashBefore = null
|
|
957
|
+
// round-9 (N1): `mode:'file'` is keyed by the SOURCE PATH, so re-running an ORIGINAL source lands on
|
|
958
|
+
// the same archive id (attempt>=2, scriptChanged). Pointing it at an ARCHIVED script instead targets
|
|
959
|
+
// a different path and therefore a NEW archive id by design - the prior attempt is never touched, but
|
|
960
|
+
// there is no previous receipt FOR THAT ID, so scriptChanged/previousReceipt are necessarily empty.
|
|
961
|
+
// That is not a bug, but it must be SAID rather than looking like broken change detection.
|
|
962
|
+
let archivedScriptSource = false
|
|
875
963
|
if (mode === 'code') scriptText = args.code
|
|
876
964
|
else if (mode === 'file') {
|
|
877
965
|
fileRel = projectRel('', args.file)
|
|
878
966
|
if (fileRel === null) return fail('MATH_REFUSED', det.name, 'file 必须位于项目内:' + args.file, { next: next('reason', { reason: 'path-outside-project' }) })
|
|
967
|
+
archivedScriptSource = /^Computation\//.test(String(fileRel))
|
|
879
968
|
const txt = await H.readText(fileRel)
|
|
880
969
|
if (txt === undefined) return fail('MATH_REFUSED', det.name, '找不到文件:' + args.file, { next: next('reason', { reason: 'file-not-found' }) })
|
|
881
970
|
scriptText = txt
|
|
@@ -939,6 +1028,7 @@ async function opRun(H, args, params) {
|
|
|
939
1028
|
}
|
|
940
1029
|
|
|
941
1030
|
const warnings = []
|
|
1031
|
+
if (archivedScriptSource) warnings.push(warning('ARCHIVED_SCRIPT_RERUN', 'mode:\'file\' 指向的是**归档脚本**(Computation/…):按设计归档 id 以**源路径**为键,所以这是一次**新归档**(新目录、attempt 1),没有该 id 的历史回执 ⇒ scriptChanged/previousReceipt 为空是**预期**,且**旧 attempt 绝不会被覆盖**。要做"编辑→重跑→attempt≥2 + scriptChanged"的核对,请把 mode:\'file\' 指向**原始源文件**并编辑它,而不是指向归档副本。'))
|
|
942
1032
|
if (!probeDet && want.length) warnings.push(warning('PACKAGE_PRECHECK_SKIPPED', 'engine=cli:无法从命令名判断引擎族,已跳过包预检(不阻塞执行,也不会误报缺包);如需预检请直接用 python/r/octave/julia 或改用可识别的解释器路径。'))
|
|
943
1033
|
if (pk && pk.probeFailed && want.length) warnings.push(warning('PACKAGE_PRECHECK_UNKNOWN', '包预检未能执行(宿主 subprocess 在这几次尝试里返回空/超时,会话早期常见):本次不判定缺包、不阻塞执行;如需确定性结论请重试或直接用对应引擎。probe=' + JSON.stringify(pk.argv) + ' attempts=' + JSON.stringify(pk.attempts)))
|
|
944
1034
|
if (fullOut.length > MATH_CAPS.stdout) warnings.push(warning('OUTPUT_TRUNCATED', 'stdout 超过 64KB,完整版见 ' + dir + '/stdout.txt'))
|
|
@@ -953,7 +1043,15 @@ async function opRun(H, args, params) {
|
|
|
953
1043
|
engine: { name: engineLabel, path: det.exe, version: det.version, source: det.name === 'cli' ? 'cli' : 'path' },
|
|
954
1044
|
script: { path: scriptRel, sha256: scriptHash, bytes: Buffer.byteLength(scriptText, 'utf8') },
|
|
955
1045
|
scriptPath: scriptRel, scriptHash: scriptHash,
|
|
1046
|
+
// round-8 (P0/D3): member file tools are session-cwd relative, so the ABSOLUTE locations must be
|
|
1047
|
+
// in the receipt: `scriptAbs` is directly openable, `cwd` is the project root to join with.
|
|
1048
|
+
scriptAbs: scriptAbs, cwd: root,
|
|
956
1049
|
sourceFile: fileRel || null,
|
|
1050
|
+
fileIsArchivedScript: archivedScriptSource,
|
|
1051
|
+
// round-9 (N2): a TIMEOUT must still carry the evidence it produced - the capped partial output
|
|
1052
|
+
// lives in the receipt as well as in stdout.txt/stderr.txt on disk.
|
|
1053
|
+
partialStdout: String(fullOut).slice(-2000),
|
|
1054
|
+
partialStderr: String(fullErr).slice(-2000),
|
|
957
1055
|
sourceHashBefore: sourceHashBefore, sourceHashAfter: sourceHashAfter,
|
|
958
1056
|
scriptChanged: scriptChanged, scriptChangedDuringRun: scriptChangedDuringRun,
|
|
959
1057
|
previousReceipt: prevReceipt ? { runId: prevReceipt.runId, attempt: prevReceipt.attempt || 1, scriptHash: prevReceipt.scriptHash || (prevReceipt.script && prevReceipt.script.sha256) || null } : null,
|
|
@@ -979,12 +1077,20 @@ async function opRun(H, args, params) {
|
|
|
979
1077
|
|
|
980
1078
|
const shell = {
|
|
981
1079
|
ok: true, op: 'run', engine: engineLabel, mode: mode,
|
|
1080
|
+
runId: runId, attempt: attempt,
|
|
1081
|
+
versionPolicy: versionPolicy, constraintsNotEnforced: pinnedSpecs.slice(),
|
|
982
1082
|
engineInfo: receipt.engine,
|
|
983
1083
|
argv: assembled.argv.slice(),
|
|
984
1084
|
scriptPath: scriptRel, scriptHash: scriptHash,
|
|
1085
|
+
scriptAbs: scriptAbs, cwd: root,
|
|
985
1086
|
attempt: attempt, attemptDir: dir, baseRunDir: baseDir,
|
|
986
1087
|
scriptChanged: scriptChanged, scriptChangedDuringRun: scriptChangedDuringRun,
|
|
987
1088
|
sourceFile: fileRel || null,
|
|
1089
|
+
fileIsArchivedScript: archivedScriptSource,
|
|
1090
|
+
// round-9 (N2): a TIMEOUT must still carry the evidence it produced - the capped partial output
|
|
1091
|
+
// lives in the receipt as well as in stdout.txt/stderr.txt on disk.
|
|
1092
|
+
partialStdout: String(fullOut).slice(-2000),
|
|
1093
|
+
partialStderr: String(fullErr).slice(-2000),
|
|
988
1094
|
previousReceipt: receipt.previousReceipt,
|
|
989
1095
|
cliScriptAppended: !!(receipt.cli && receipt.cli.scriptAppended),
|
|
990
1096
|
packages: pk.found,
|
|
@@ -994,8 +1100,13 @@ async function opRun(H, args, params) {
|
|
|
994
1100
|
warnings: warnings,
|
|
995
1101
|
}
|
|
996
1102
|
if (receipt.timedOut) {
|
|
997
|
-
|
|
998
|
-
|
|
1103
|
+
// round-9 (F5): failure EVIDENCE must be uniform - a timeout carries the SAME fields a non-zero
|
|
1104
|
+
// exit does (exit + stderr), plus timedOut:true.
|
|
1105
|
+
const out = fail('MATH_TIMEOUT', engineLabel, '执行超时(' + timeoutMs + 'ms)已被终止', { argv: assembled.argv.slice(), receipt: receiptRef })
|
|
1106
|
+
out.exit = null
|
|
1107
|
+
out.stdout = String(fullOut).slice(0, MATH_CAPS.stdout)
|
|
1108
|
+
out.stderr = String(fullErr).slice(0, MATH_CAPS.stderr)
|
|
1109
|
+
return Object.assign(out, archiveFields(), { ms: receipt.ms, timedOut: true })
|
|
999
1110
|
}
|
|
1000
1111
|
if (r.exit !== 0) {
|
|
1001
1112
|
const blob = fullErr + '\n' + fullOut
|
|
@@ -2049,6 +2049,8 @@ export function apply(ctx) {
|
|
|
2049
2049
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
2050
2050
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
2051
2051
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
2052
|
+
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
2053
|
+
verifyTasks: Object.keys(tasks).filter(function (k) { return tasks[k] && tasks[k].type === 'verify' }).map(function (k) { const t = tasks[k]; const v = voteCount(t); return { rId: t.rId, status: t.status, round: v.round, expected: v.expected, dispatched: v.dispatched, reported: v.reportedThisRound, quorum: v.quorum } }),
|
|
2052
2054
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)),
|
|
2053
2055
|
// Lean 形式化:可调档位与开关同处可读参数表(契约 §1),并附当前形式化记录概况。
|
|
2054
2056
|
formal: {
|
|
@@ -2165,7 +2167,22 @@ export function apply(ctx) {
|
|
|
2165
2167
|
// 不再手工累加并发计数:唤醒的是 registry 里已登记的 child,计数已由 registry 长度体现。
|
|
2166
2168
|
await saveAll()
|
|
2167
2169
|
}
|
|
2168
|
-
|
|
2170
|
+
/**
|
|
2171
|
+
* 中断一个**在册**子代理。审计 D1:旧实现 `try{…}catch(e){}` 把"没有这个 child / 宿主拒绝中断"
|
|
2172
|
+
* 都吞成静默成功(调用方拿到 undefined,还以为已经中断)。现在:空/未知 id ⇒ `code` +
|
|
2173
|
+
* `next{tool,hint}`,宿主抛错 ⇒ 保留错误文本并给替代出口;只有真的发出中断才 `ok:true`。
|
|
2174
|
+
*/
|
|
2175
|
+
async function interruptChild(childId) {
|
|
2176
|
+
const id = String(childId == null ? '' : childId).trim()
|
|
2177
|
+
if (!id) return { ok: false, code: 'VIBE_MATH_INVALID_ARGUMENT', message: 'interruptChild 失败:childId 为空——无法确定要中断哪个子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 列出本会话在册子代理的 id,再带 childId 调用。' } }
|
|
2178
|
+
if (!Object.prototype.hasOwnProperty.call(agentRegistry, id)) return { ok: false, code: 'VIBE_MATH_CHILD_NOT_FOUND', message: 'interruptChild 失败:' + id + ' 不在本会话的在册子代理里(在册 ' + Object.keys(agentRegistry).length + ' 个)——可能已经结束,或不是本会话的子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 取当前在册 id;要停整个会话用 vibe_math_abort。' } }
|
|
2179
|
+
try {
|
|
2180
|
+
await subagents.interrupt(id, { kind: 'ancestor', agent: rootAgent })
|
|
2181
|
+
return { ok: true, childId: id }
|
|
2182
|
+
} catch (e) {
|
|
2183
|
+
return { ok: false, code: 'VIBE_MATH_INTERRUPT_FAILED', message: 'interruptChild 失败:宿主拒绝中断 ' + id + '(' + String((e && e.message) || e) + ')。', next: { kind: 'reason', tool: 'vibe_math_abort', hint: '宿主不接受单点中断时,用 vibe_math_abort 终止整个会话的子代理。' } }
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2169
2186
|
|
|
2170
2187
|
// ================= prompts =================
|
|
2171
2188
|
function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
|
|
@@ -2402,7 +2419,10 @@ export function apply(ctx) {
|
|
|
2402
2419
|
if (node === 'verdict') { const overridden = resolution.action === 'override' && (resolution.verdict === 1 || resolution.verdict === 0); const v = overridden ? Number(resolution.verdict) : data.verdict; const applied = await settleVerdict(data.task, v); delete tasks[data.task.id]; return { verdict: v, overridden: overridden, applied: applied } }
|
|
2403
2420
|
return {}
|
|
2404
2421
|
}
|
|
2405
|
-
async function resolveDecision(id, resolution) {
|
|
2422
|
+
async function resolveDecision(id, resolution) {
|
|
2423
|
+
const d = decisionQueue.find(function (x) { return x.id === id })
|
|
2424
|
+
if (!d) return { ok: false, code: 'VIBE_MATH_DECISION_NOT_FOUND', message: 'decision not found:' + String(id == null ? '' : id) + ' 不在人工决策队列里(队列现有 ' + decisionQueue.length + ' 个' + (decisionQueue.length ? (':' + decisionQueue.map(function (x) { return x.id }).join(', ')) : '') + ')——可能已被 vibe_math_decide 结清,或被 start/resume/abort/换项目作废(作废是终态,不会回到 pending)。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '先用 vibe_math_list_decisions 取当前 pending 的 id;若期望它还在,检查是否发生过 start/resume/abort/换项目。' } }
|
|
2425
|
+
if (d.status !== 'pending') return { ok: false, code: 'VIBE_MATH_DECISION_ALREADY_RESOLVED', message: 'decision already resolved:' + String(id) + ' 当前状态是 ' + String(d.status) + '——每次决策只生效一次。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '用 vibe_math_list_decisions 看还有哪些 pending;要重新决策请等框架发起新的决策项。' } }; d.status = 'resolved'; d.resolution = resolution; if (scheduler.gate && scheduler.gate.decisionId === id) scheduler.gate = null; logActivity('decide', id + ' resolved: ' + resolution.action + (resolution.verdict !== undefined ? ' ' + resolution.verdict : '')); await saveAll(); scheduleTick(); return { ok: true, message: 'decision resolved' } }
|
|
2406
2426
|
/**
|
|
2407
2427
|
* 清掉 gate 时**必须结清它指向的那个人工决策**(审计 M10)。start/resume/abort/切项目都会离开
|
|
2408
2428
|
* 需要那次决策的运行,而决策若仍是 `pending`,它会永远留在 `vibe_math_list_decisions` 里反复出现——
|
|
@@ -2694,13 +2714,14 @@ export function apply(ctx) {
|
|
|
2694
2714
|
}
|
|
2695
2715
|
async function backfillVerifiers(t) {
|
|
2696
2716
|
const cap = reviewerCap()
|
|
2697
|
-
|
|
2717
|
+
const want = voteCount(t).expected // 快照目标(不再直接读 t.expectedCount,保持单一来源)
|
|
2718
|
+
while (t.children.length < want && t.children.length < cap) {
|
|
2698
2719
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
2699
2720
|
const index = t.children.length
|
|
2700
2721
|
const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
|
|
2701
2722
|
t.children.push(childId)
|
|
2702
2723
|
}
|
|
2703
|
-
if (t.children.length >=
|
|
2724
|
+
if (t.children.length >= want) t.status = 'debating'
|
|
2704
2725
|
}
|
|
2705
2726
|
async function reconcileVerify() {
|
|
2706
2727
|
const ids = Object.keys(tasks)
|
|
@@ -2716,7 +2737,7 @@ export function apply(ctx) {
|
|
|
2716
2737
|
} else continue
|
|
2717
2738
|
}
|
|
2718
2739
|
if (t.status === 'paused') {
|
|
2719
|
-
const allReported =
|
|
2740
|
+
const allReported = voteCount(t, t.round).allParticipantsReported
|
|
2720
2741
|
if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
|
|
2721
2742
|
}
|
|
2722
2743
|
if (t.status !== 'spawning') continue
|
|
@@ -2940,8 +2961,40 @@ export function apply(ctx) {
|
|
|
2940
2961
|
}
|
|
2941
2962
|
|
|
2942
2963
|
// ================= verification (验证器) =================
|
|
2964
|
+
/**
|
|
2965
|
+
* 验证任务的**票数/参与集单一来源**(跨预设审计 D4,与 v3 的 voteCount 同型)。
|
|
2966
|
+
*
|
|
2967
|
+
* 定义(本文件里"参与集"只有一个含义):一个 verify 任务的参与集 = **本轮被派出的评审**,
|
|
2968
|
+
* 也就是 `t.children`(当前 round 的 `childResults` 条目是它们的票);"需要几票"= **任务创建时的
|
|
2969
|
+
* 快照** `t.expectedCount`(processVerify 建任务时写入),**绝不**读实时 `params.verifierCount`
|
|
2970
|
+
* 或 `params.maxParallelThreshold`。旧实现把这个答案分散在三处——`consensus()` 只看
|
|
2971
|
+
* `Object.keys(t.childResults)`(已报票)且用常量 MIN_REVIEWERS 当下限、`finalizeVerification()`
|
|
2972
|
+
* 又自己数一遍、两处内联 `allReported` 用 `t.children` + round——同一个计数在不同视图里可以不同
|
|
2973
|
+
* (中途增/减评审、某个评审掉线后,辩论视图与裁决视图可能对"这一轮算不算完"给出不同答案)。
|
|
2974
|
+
* 现在所有视图都读这一个函数。
|
|
2975
|
+
*/
|
|
2976
|
+
// 契约(P2-v2 修复轮,必须保持):一致/票数下限仍按 **MIN_REVIEWERS** 判定,一致性的对象是
|
|
2977
|
+
// **所有已报结果**(reportedIds)。`voteCount` 只是把这些读法收拢到一个来源 + 提供审计投影;
|
|
2978
|
+
// 它**不**改变"一票不算共识"的既有语义(v2-fix-probes 的 M11 探针钉着这条)。
|
|
2979
|
+
function voteCount(t, round) {
|
|
2980
|
+
const tt = t || {}
|
|
2981
|
+
const expected = Math.max(MIN_REVIEWERS, Number(tt.expectedCount) || 0)
|
|
2982
|
+
const r = Number((round === undefined || round === null) ? (tt.round || 1) : round) || 1
|
|
2983
|
+
const participants = Array.isArray(tt.children) ? tt.children.slice() : []
|
|
2984
|
+
const results = tt.childResults || {}
|
|
2985
|
+
const reportedIds = Object.keys(results)
|
|
2986
|
+
const thisRoundIds = participants.filter(function (cid) { const x = results[cid]; return x && Number(x.round) === r })
|
|
2987
|
+
return {
|
|
2988
|
+
round: r, expected: expected, participants: participants,
|
|
2989
|
+
dispatched: participants.length, reported: reportedIds.length, reportedIds: reportedIds,
|
|
2990
|
+
reportedThisRound: thisRoundIds.length,
|
|
2991
|
+
// 旧语义的「本轮都报了吗」:只看**已派出的评审**,不额外要求达到快照票数(那一条属于裁决下限)。
|
|
2992
|
+
allParticipantsReported: participants.length > 0 && thisRoundIds.length === participants.length,
|
|
2993
|
+
quorum: participants.length > 0 && participants.length >= expected && thisRoundIds.length === participants.length,
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2943
2996
|
// 一票不算共识:≥MIN_REVIEWERS 份独立评审才可能达成/否决共识(审计 M11;与 v3 的最小票数同型)。
|
|
2944
|
-
function consensus(t) { const
|
|
2997
|
+
function consensus(t) { const v = voteCount(t); if (v.reportedIds.length < MIN_REVIEWERS) return false; const vs = v.reportedIds.map(function (cid) { return t.childResults[cid].Result }); return vs.every(function (x) { return x === 1 }) || vs.every(function (x) { return x === 0 }) }
|
|
2945
2998
|
function buildTranscript(t) { const parts = []; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const r = t.childResults[cids[i]]; parts.push('Reviewer ' + i + ': Result=' + r.Result + ' Reason=' + r.Reason) } return parts.join('\n') }
|
|
2946
2999
|
// 说明(审计 L17):这里曾有一个 `verifierWeight(cid, rigor)`,但全仓只有它的定义、没有任何调用点,
|
|
2947
3000
|
// 且其公式(clamped ±0.2 rigor 加成、按 childId 取准确率)与 finalVerdict 里真正在用的
|
|
@@ -3000,7 +3053,7 @@ export function apply(ctx) {
|
|
|
3000
3053
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
3001
3054
|
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, key: meta.verificationKey || verifierIdentityKey() }
|
|
3002
3055
|
delete agentRegistry[childId]
|
|
3003
|
-
const allReported =
|
|
3056
|
+
const allReported = voteCount(t, meta.round).allParticipantsReported
|
|
3004
3057
|
if (!allReported) { await saveAll(); return }
|
|
3005
3058
|
await advanceVerification(t, meta.round)
|
|
3006
3059
|
await saveAll()
|
|
@@ -3040,7 +3093,7 @@ export function apply(ctx) {
|
|
|
3040
3093
|
* `stalled`,冷却期后重试(任务保留 ⇒ processVerify 不会为同一个 rId 另造任务,也不会每 tick 重试)。
|
|
3041
3094
|
*/
|
|
3042
3095
|
async function finalizeVerification(t) {
|
|
3043
|
-
const reviews =
|
|
3096
|
+
const reviews = voteCount(t).reportedIds.length
|
|
3044
3097
|
if (reviews < MIN_REVIEWERS) {
|
|
3045
3098
|
const tried = Number(t.reviewerRespawn || 0)
|
|
3046
3099
|
if (scheduler.running && tried < 2 && reviewerCap() >= MIN_REVIEWERS) {
|
|
@@ -3071,7 +3124,8 @@ export function apply(ctx) {
|
|
|
3071
3124
|
}
|
|
3072
3125
|
}
|
|
3073
3126
|
function finalVerdict(t) {
|
|
3074
|
-
const
|
|
3127
|
+
const v = voteCount(t)
|
|
3128
|
+
const rs = v.reportedIds.map(function (cid) { return t.childResults[cid] })
|
|
3075
3129
|
if (rs.length === 0) return 0.5
|
|
3076
3130
|
if (rs.every(function (r) { return r.Result === 1 })) return 1
|
|
3077
3131
|
if (rs.every(function (r) { return r.Result === 0 })) return 0
|
|
@@ -3451,6 +3505,12 @@ export function apply(ctx) {
|
|
|
3451
3505
|
/** 证据索引(spec §3.9):只列**确实存在**的文件;绝不含 Paper/ 自己(否则输入哈希会自我污染)。
|
|
3452
3506
|
* off 档必须真的是无操作(formal-verify-v2 的语料守卫会断言"off 的提示词里零 Lean 文本"):
|
|
3453
3507
|
* formal 未启用时,形式化相关的路径与字段一概不写进材料。 */
|
|
3508
|
+
/**
|
|
3509
|
+
* 成员可见路径说明(跨预设审计 P0)。成员/子代理的**文件工具按会话 cwd 解析**相对路径,而下面列出的
|
|
3510
|
+
* 路径都是**项目根相对**的 ⇒ 必须显式说明"先拼绝对前缀";计算产物用回执里的绝对字段。一处常量、
|
|
3511
|
+
* 五处材料文本各自 append(绝不替换既有行)。
|
|
3512
|
+
*/
|
|
3513
|
+
const PAPER_PATH_NOTE = '(路径说明:成员/子代理的文件工具按**会话 cwd** 解析相对路径,因此上面列出的相对路径都必须先拼上**项目根的绝对前缀**再使用;计算产物请用回执里的绝对字段 `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来。)'
|
|
3454
3514
|
async function paperEvidenceIndex() {
|
|
3455
3515
|
const formalNow = formalOn()
|
|
3456
3516
|
const out = ['qs/qs.json']
|
|
@@ -3460,6 +3520,7 @@ export function apply(ctx) {
|
|
|
3460
3520
|
if (formalNow) for (const f of await listFiles('Verified/Lean')) out.push('Verified/Lean/' + f)
|
|
3461
3521
|
const dirs = ['Verification_logs', 'Progress_Logs'].concat(formalNow ? ['Formal'] : [])
|
|
3462
3522
|
for (let i = 0; i < dirs.length; i++) { const files = await listFiles(dirs[i]); for (let j = 0; j < files.length; j++) out.push(dirs[i] + '/' + files[j]) }
|
|
3523
|
+
out.push(PAPER_PATH_NOTE)
|
|
3463
3524
|
return out.filter(function (x) { return x.indexOf('Paper/') !== 0 }).sort()
|
|
3464
3525
|
}
|
|
3465
3526
|
/** 汇总材料(spec §4):只含既有证据 + 未决/被否证的显式标注。不含时间戳 ⇒ 哈希稳定。 */
|
|
@@ -3484,6 +3545,7 @@ export function apply(ctx) {
|
|
|
3484
3545
|
const props = await getPropos()
|
|
3485
3546
|
L.push('')
|
|
3486
3547
|
L.push('[PROPOSITIONS] (Propos/*.json)')
|
|
3548
|
+
L.push(PAPER_PATH_NOTE)
|
|
3487
3549
|
if (props.length === 0) L.push('- (none)')
|
|
3488
3550
|
for (let i = 0; i < props.length; i++) {
|
|
3489
3551
|
const p = props[i]
|
|
@@ -3937,6 +3999,8 @@ export function apply(ctx) {
|
|
|
3937
3999
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
3938
4000
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
3939
4001
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
4002
|
+
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
4003
|
+
verifyTasks: Object.keys(tasks).filter(function (k) { return tasks[k] && tasks[k].type === 'verify' }).map(function (k) { const t = tasks[k]; const v = voteCount(t); return { rId: t.rId, status: t.status, round: v.round, expected: v.expected, dispatched: v.dispatched, reported: v.reportedThisRound, quorum: v.quorum } }),
|
|
3940
4004
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,
|
|
3941
4005
|
formal: {
|
|
3942
4006
|
mode: formalMode(), required: formalRequired(),
|
|
@@ -4011,10 +4075,10 @@ export function apply(ctx) {
|
|
|
4011
4075
|
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), async function (args) { const slug = slugify(args.name); return await setProject(slug, false) })
|
|
4012
4076
|
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
|
|
4013
4077
|
registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), async function () { return { ok: true, decisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }) } })
|
|
4014
|
-
registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id });
|
|
4078
|
+
registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id }); const resolution = { action: args.action, verdict: args.verdict }; if (!d || d.status !== 'pending') return await resolveDecision(args.id, resolution); const applied = await applyDecision(d.node, d.data, resolution); const r = await resolveDecision(args.id, resolution); return Object.assign({ ok: true, applied: applied }, r) })
|
|
4015
4079
|
registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), async function () { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round, rId: m.rId }) } return { ok: true, agents: out, count: out.length } })
|
|
4016
4080
|
registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), async function (args) { if (!agentRegistry[args.childId]) return { ok: false, message: 'unknown childId' }; await followupChild(args.childId, args.message); return { ok: true, message: 'message delivered' } })
|
|
4017
|
-
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok:
|
|
4081
|
+
registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { const r = await interruptChild(args.childId); return Object.assign({}, r, { ok: !!(r && r.ok) }) })
|
|
4018
4082
|
// ---- Lean 形式化验证(契约 §5)----
|
|
4019
4083
|
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), async function (args, agent) { return await leanRunTool(memberIdOf(agent), args) })
|
|
4020
4084
|
registerTool('vibe_math_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), async function (args, agent) { return await leanArchive(memberIdOf(agent), args) })
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
|
|
70
70
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
71
71
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
72
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext
|
|
72
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
73
73
|
|
|
74
74
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
75
75
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|
|
@@ -161,7 +161,7 @@
|
|
|
161
161
|
|
|
162
162
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
163
163
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
164
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext
|
|
164
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
165
165
|
|
|
166
166
|
A /vibe slash command mirrors the main controls (/vibe start [override]|resume [override]|pause|abort|status|report|mode
|
|
167
167
|
<auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents|paper [lang=zh|en] [format=both|md|tex] [force]).
|