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
|
|
@@ -255,6 +255,11 @@ export function apply(ctx) {
|
|
|
255
255
|
}
|
|
256
256
|
let running = false, autoDone = false, phase = 'idle'
|
|
257
257
|
let residents = new Map(), mailboxes = new Map(), taskboard = [], decisions = []
|
|
258
|
+
// round-9 (P2/D4): the participant set is frozen per verify/meeting together with this version, so
|
|
259
|
+
// every view reads the SAME roster instead of re-deriving it from the live map (mid-flight
|
|
260
|
+
// addMember/removeMember is possible: removeMember reconciles in-flight work, addMember has no
|
|
261
|
+
// verify/meeting gate).
|
|
262
|
+
let rosterVersion = 0
|
|
258
263
|
let meetings = [], reports = [], activityLog = []
|
|
259
264
|
let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
|
|
260
265
|
// Lean formalization records, keyed by object id. v4 has no session-log projection, so this
|
|
@@ -1557,6 +1562,7 @@ export function apply(ctx) {
|
|
|
1557
1562
|
+' 然后 ## 陈述 <陈述>;## 证明尝试;## 证伪尝试。\n'
|
|
1558
1563
|
+' Methods/<你>/<id>.md —— 你的理论/方法/工具。格式:- ID: m-<id>; - 状态: 经验; - 可信断言: []; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 核心内容;## 定义与记号;## 应用记录;## 改进历史。\n'
|
|
1559
1564
|
+' Subproblems/<你>/<id>.md —— 你的子问题。格式:- ID: s-<id>; - 状态: 求解中; - 价值程度: <0-1>; - 动机用途计划: ...;然后 ## 陈述;## 进度。\n'
|
|
1565
|
+
+' ⚠ 你的文件工具(读/写)按**会话 cwd** 解析路径:上面列出的相对子路径都要先拼上**绝对前缀** '+base+'/ 再使用(例如 '+base+'/Propos/<你>/p-1.md);计算产物同理——请用回执里的**绝对**字段 receipt.scriptAbs,或把 receipt.cwd 与 receipt.scriptPath 拼起来,不要用相对路径。\n'
|
|
1560
1566
|
}
|
|
1561
1567
|
function toolList(){
|
|
1562
1568
|
return 'vibe_v4_send_message {to, content} —— 给某常驻发消息(to=all 广播)。\n'
|
|
@@ -1898,10 +1904,23 @@ export function apply(ctx) {
|
|
|
1898
1904
|
function byChild(childId){ for(const [,r] of residents){ if(r.childId===childId) return r } return undefined }
|
|
1899
1905
|
|
|
1900
1906
|
// ---- artifact writers (resident-facing) ----
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1907
|
+
/** Cross-preset audit D1: a member-facing tool must never answer with a bare `no such resident`.
|
|
1908
|
+
* "not started yet" / "wrong id" / "already dismissed" are DIFFERENT problems, so the answer
|
|
1909
|
+
* carries a code, the LIVE roster state and an actionable next step (v5's `memberDiagnosis`). */
|
|
1910
|
+
function noSuchResident(id, tool){
|
|
1911
|
+
const ids=[...residents.keys()]
|
|
1912
|
+
const active=[...residents.values()].filter(r=>r&&r.childId).length
|
|
1913
|
+
const nextTool=tool||(running?'vibe_v4_list_members':'vibe_v4_start')
|
|
1914
|
+
return {ok:false,code:'V4_NO_SUCH_RESIDENT',
|
|
1915
|
+
message:'no such resident: '+String(id||'(none)')+(running?('(当前在册 '+residents.size+' 名,active '+active+' 名)'):'(本次运行尚未 start)'),
|
|
1916
|
+
status:{running:!!running,autoDone:!!autoDone,residentCount:residents.size,activeCount:active,ids:ids},
|
|
1917
|
+
next:{tool:nextTool,
|
|
1918
|
+
hint:running?'核对有效 id(新建成员可能仍在 spawn 中);列表用 vibe_v4_list_members':'先 vibe_v4_start 或 vibe_v4_add_member,再调用 '+nextTool}}
|
|
1919
|
+
}
|
|
1920
|
+
async function publishProgress(rId,content){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const rel='Progress/'+rId+'/progress.md'; const prev=(await readText(rel))||''; await writeText(rel, prev+'\n### '+fmtTime()+'|'+rId+'\n'+String(content||'')+'\n'); return {ok:true} }
|
|
1921
|
+
async function recordProposition(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('p-'+shortId()); const lines=['# 命题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: 命题','- 状态: 未定论','- 概率: '+cl(o.prob!=null?o.prob:0.5),'- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 证明尝试','','## 证伪尝试','']; await writeText('Propos/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 命题 '+id); bumpArtifacts(); return {ok:true,id,file:'Propos/'+rId+'/'+id+'.md'} }
|
|
1922
|
+
async function recordMethod(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('m-'+shortId()); const lines=['# 方法|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 类型: '+(o.type||'方法'),'- 状态: 经验','- 可信断言: []','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'','## 核心内容',String(o.content||''),'','## 定义与记号',String(o.notation||''),'','## 应用记录','## 改进历史','']; await writeText('Methods/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 方法 '+id); bumpArtifacts(); return {ok:true,id,file:'Methods/'+rId+'/'+id+'.md'} }
|
|
1923
|
+
async function recordSubproblem(rId,o){ if(!rId||!residents.has(rId)) return noSuchResident(rId) ; const id=o.id?idSafe(o.id):('s-'+shortId()); const lines=['# 子问题|'+(o.title||id),'- 标题: '+(o.title||id),'- ID: '+id,'- 状态: 求解中','- 价值程度: '+cl(o.value!=null?o.value:0.5),'- 动机用途计划: '+(o.motivation||''),'- 依赖: []','','## 陈述',String(o.statement||''),'','## 进度','']; await writeText('Subproblems/'+rId+'/'+id+'.md',lines.join('\n')); logActivity('record',rId+' 子问题 '+id); bumpArtifacts(); return {ok:true,id,file:'Subproblems/'+rId+'/'+id+'.md'} }
|
|
1905
1924
|
// ── auto-sync meeting: every `meetingKeepEvery` NEW artifacts, convene a coordination meeting ──
|
|
1906
1925
|
//
|
|
1907
1926
|
// The counting basis is the RESIDENTS' card libraries themselves (Propos/Methods/Subproblems),
|
|
@@ -2015,7 +2034,7 @@ export function apply(ctx) {
|
|
|
2015
2034
|
|
|
2016
2035
|
// ---- messaging ----
|
|
2017
2036
|
async function postMessage(from,to,content){
|
|
2018
|
-
const r=residents.get(to); if(!r) return
|
|
2037
|
+
const r=residents.get(to); if(!r) return noSuchResident(to,'vibe_v4_send_message')
|
|
2019
2038
|
if(!busy.has(to)){
|
|
2020
2039
|
currentResident=r.rId
|
|
2021
2040
|
const ok=await wakeResident(r, (await normalPrompt(r))+'\n\n[NEW MESSAGE from '+from+']\n'+content,'normal')
|
|
@@ -2075,7 +2094,7 @@ export function apply(ctx) {
|
|
|
2075
2094
|
// who sees no one else's contribution"; a real discussion lets each member lead sometimes.
|
|
2076
2095
|
const rot=Math.floor(Math.random()*Math.max(1,ids.length))
|
|
2077
2096
|
const order=ids.slice(rot).concat(ids.slice(0,rot))
|
|
2078
|
-
meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order,at:now(),lastInputAt:now()}
|
|
2097
|
+
meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order,at:now(),lastInputAt:now(),rosterSnapshot:order.slice(),rosterVersion}
|
|
2079
2098
|
markProgress();
|
|
2080
2099
|
logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
|
|
2081
2100
|
}
|
|
@@ -2090,7 +2109,7 @@ export function apply(ctx) {
|
|
|
2090
2109
|
meetingState=null; wakeKind.clear(); logActivity('meeting','abandoned (stuck: no resident spoke)')
|
|
2091
2110
|
await saveAll(); await scheduleNext(); return
|
|
2092
2111
|
}
|
|
2093
|
-
const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
|
|
2112
|
+
const ids=(st.rosterSnapshot||Array.from(residents.keys())).slice(); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
|
|
2094
2113
|
if(allSpoke){ await finalizeMeeting(); return }
|
|
2095
2114
|
// only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
|
|
2096
2115
|
// NOTE: we deliberately do NOT flush mailboxes here — drafting an un-spoken resident into a normal
|
|
@@ -2109,7 +2128,7 @@ export function apply(ctx) {
|
|
|
2109
2128
|
let doSchedule=false
|
|
2110
2129
|
try {
|
|
2111
2130
|
const st=meetingState
|
|
2112
|
-
const ids=Array.from(residents.keys()); const allSpoke=ids.length>0 && ids.every(id=>st.inputs[id]!==undefined)
|
|
2131
|
+
const ids=(st.rosterSnapshot||Array.from(residents.keys())).slice(); const allSpoke=ids.length>0 && ids.every(id=>st.inputs[id]!==undefined)
|
|
2113
2132
|
const lines=['# 会议 '+st.id+'|'+fmtTime(),'','**议程**:'+st.agenda,'']
|
|
2114
2133
|
for(const [id,iv] of Object.entries(st.inputs)){ lines.push('### '+id); lines.push(iv.input||''); lines.push('') }
|
|
2115
2134
|
await writeText('Shared/meetings/'+st.id+'.md', lines.join('\n'))
|
|
@@ -2178,7 +2197,7 @@ export function apply(ctx) {
|
|
|
2178
2197
|
await saveAll(); await scheduleNext(); return
|
|
2179
2198
|
}
|
|
2180
2199
|
}
|
|
2181
|
-
verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now()}
|
|
2200
|
+
verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now(),rosterSnapshot:Array.from(residents.keys()),rosterVersion}
|
|
2182
2201
|
markProgress();
|
|
2183
2202
|
logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
|
|
2184
2203
|
}
|
|
@@ -2193,7 +2212,7 @@ export function apply(ctx) {
|
|
|
2193
2212
|
verifyState=null; wakeKind.clear(); logActivity('verify',vs.targetId+' abandoned (stuck: no unanimous verdict reachable)')
|
|
2194
2213
|
await saveAll(); await scheduleNext(); return
|
|
2195
2214
|
}
|
|
2196
|
-
const ids=Array.from(residents.keys()); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
|
|
2215
|
+
const ids=(vs.rosterSnapshot||Array.from(residents.keys())).slice(); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
|
|
2197
2216
|
if(allVoted){ await finalizeVerify(); return }
|
|
2198
2217
|
// NOTE: we deliberately do NOT flush mailboxes here — drafting an un-voted resident into a normal
|
|
2199
2218
|
// mail round would delay its verdict and can starve the verify past its watchdog when the backlog
|
|
@@ -3357,6 +3376,10 @@ export function apply(ctx) {
|
|
|
3357
3376
|
return {
|
|
3358
3377
|
params:{finalPaper:params.finalPaper,paperFormat:params.paperFormat,paperLanguage:params.paperLanguage,paperCompilePdf:params.paperCompilePdf,paperEditor:params.paperEditor,paperLatexCommand:params.paperLatexCommand},
|
|
3359
3378
|
id:ps?ps.id:paperIdFor(), dir:ps?ps.dir:paperDirFor(), status:ps?ps.status:'idle',
|
|
3379
|
+
// round-9 (F2): with no paper state this path is a PROJECTION (f2: paperIdFor/paperDirFor are
|
|
3380
|
+
// pure - reading `status` touches no fs and creates no directory). Say so explicitly instead of
|
|
3381
|
+
// handing callers a path that looks like it exists.
|
|
3382
|
+
dirProjected:!ps, dirSource:ps?'paper-state':'projection', readSideEffect:false,
|
|
3360
3383
|
running:!!(ps&&ps.status!=='done'&&ps.status!=='failed'), trigger:ps?ps.trigger:'',
|
|
3361
3384
|
rounds:ps?ps.round:0, maxRounds:ps?ps.maxRounds:PAPER_MAX_ROUNDS, participants:ps?ps.participants:[],
|
|
3362
3385
|
editor:ps?(ps.editorId||ps.cfg.editor):params.paperEditor, editorDowngraded:!!(ps&&ps.editorDowngraded),
|
|
@@ -3368,6 +3391,8 @@ export function apply(ctx) {
|
|
|
3368
3391
|
function paperStatusSummary(){
|
|
3369
3392
|
const v=paperStatusView()
|
|
3370
3393
|
return {ok:true, id:v.id, dir:v.dir, status:v.status, running:v.running, trigger:v.trigger, lang:v.params.paperLanguage,
|
|
3394
|
+
// round-9 (F2): carry the projection flags through the SUMMARY too (this is what `status` returns).
|
|
3395
|
+
dirProjected:v.dirProjected, dirSource:v.dirSource, readSideEffect:v.readSideEffect,
|
|
3371
3396
|
format:v.params.paperFormat, editor:v.editor, editorDowngraded:v.editorDowngraded, participants:v.participants,
|
|
3372
3397
|
parts:v.parts, reviews:v.reviews, rounds:v.rounds+'/'+v.maxRounds, finalizedAt:v.finalizedAt, files:v.files,
|
|
3373
3398
|
params:v.params,
|
|
@@ -3566,6 +3591,10 @@ export function apply(ctx) {
|
|
|
3566
3591
|
logActivity('resume','restarted'+(crossProcess?' (cross-process: re-spawned)':needRespawn?' (re-spawned)':'')); await saveAll(); await scheduleNext(); return {ok:true,message:'resumed',project:currentProject}
|
|
3567
3592
|
}
|
|
3568
3593
|
function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
|
|
3594
|
+
// round-9 (P2): the roster VERSION travels with every status read, and an active verify/meeting
|
|
3595
|
+
// reports the participant set it froze (never a re-derivation from the live map).
|
|
3596
|
+
rosterVersion,
|
|
3597
|
+
frozenParticipants: verifyState&&Array.isArray(verifyState.rosterSnapshot)?verifyState.rosterSnapshot:(meetingState&&Array.isArray(meetingState.rosterSnapshot)?meetingState.rosterSnapshot:null),
|
|
3569
3598
|
residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
|
|
3570
3599
|
// Residents the host's live-child cap refused (maxActiveSubagents): queued, not lost. Without
|
|
3571
3600
|
// this the only trace would be the one console line, and `residentCount` alone cannot tell a
|
|
@@ -3576,8 +3605,8 @@ export function apply(ctx) {
|
|
|
3576
3605
|
// The coordination counters (`spoke k/N`, `voted k/N`, the verification round) used to live only
|
|
3577
3606
|
// in `report()`: from `status()` alone an operator could not tell "the group is progressing"
|
|
3578
3607
|
// from "the group is stuck at 2/4 votes", which is exactly how F1/F2/F7 stay invisible.
|
|
3579
|
-
consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,spoke:Object.keys(meetingState.inputs).length,expected:residents.
|
|
3580
|
-
:(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,voted:Object.keys(verifyState.verdicts).length,expected:residents.
|
|
3608
|
+
consensus: meetingState?{kind:'meeting',id:meetingState.id,agenda:meetingState.agenda,round:meetingState.round,spoke:Object.keys(meetingState.inputs).length,expected:(meetingState.rosterSnapshot||Array.from(residents.keys())).length,solvedVotes:Object.values(meetingState.inputs).filter(iv=>iv.voteSolved===true).length,rosterVersion:meetingState.rosterVersion}
|
|
3609
|
+
:(verifyState?{kind:'verify',target:verifyState.targetId,targetType:verifyState.targetType,stage:verifyState.stage,round:verifyState.round,voted:Object.keys(verifyState.verdicts).length,expected:(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null),
|
|
3581
3610
|
artifactCount: artifactCount, artifactBaseline: artifactBaseline,
|
|
3582
3611
|
paper: paperStatusSummary(),
|
|
3583
3612
|
// The Lean knobs and the per-object formal records are part of the readable status: without
|
|
@@ -3598,8 +3627,8 @@ export function apply(ctx) {
|
|
|
3598
3627
|
}
|
|
3599
3628
|
function report(){ return { ok:true, running, phase, autoDone, project:currentProject, problem:problemText,
|
|
3600
3629
|
residents:listResidents(), taskboard:taskboard.filter(t=>t.status!=='done'),
|
|
3601
|
-
meeting: meetingState?{id:meetingState.id, agenda:meetingState.agenda, spoke:Object.keys(meetingState.inputs).length+'/'+residents.
|
|
3602
|
-
verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+residents.
|
|
3630
|
+
meeting: meetingState?{id:meetingState.id, agenda:meetingState.agenda, spoke:Object.keys(meetingState.inputs).length+'/'+(meetingState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:meetingState.rosterVersion}:null,
|
|
3631
|
+
verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+(verifyState.rosterSnapshot||Array.from(residents.keys())).length,rosterVersion:verifyState.rosterVersion}:null,
|
|
3603
3632
|
pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null,
|
|
3604
3633
|
parkedMeeting: pendingMeeting?pendingMeeting.agenda:null,
|
|
3605
3634
|
formal: formalView(),
|
|
@@ -3619,10 +3648,15 @@ export function apply(ctx) {
|
|
|
3619
3648
|
// residents) can never be true for the new member (not in the snapshot order) and the meeting is
|
|
3620
3649
|
// only ever released by the stuck watchdog instead of finalizing with everyone's input.
|
|
3621
3650
|
if(meetingState){ if(!Array.isArray(meetingState.order)) meetingState.order=Array.from(residents.keys()); if(!meetingState.order.includes(r.rId)) meetingState.order.push(r.rId) }
|
|
3651
|
+
// P2: the roster CHANGED - bump the version so any frozen participant set is visibly stale.
|
|
3652
|
+
rosterVersion++
|
|
3622
3653
|
// Mid-verify additions are automatically asked to vote (continueVerifyRound recomputes ids from
|
|
3623
3654
|
// the live residents map), so no extra handling is needed there.
|
|
3624
3655
|
return {ok:true,id:r.rId,direction:r.direction} }
|
|
3625
|
-
async function removeMember(id){ const r=residents.get(id); if(!r) return
|
|
3656
|
+
async function removeMember(id){ const r=residents.get(id); if(!r) return noSuchResident(id,'vibe_v4_remove_member'); if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } residents.delete(id); busy.delete(id); mailboxes.delete(id); wakeKind.delete(id); if(currentResident===id) currentResident=''
|
|
3657
|
+
// P2: the roster CHANGED - bump the version (a frozen participant set stays readable, but is
|
|
3658
|
+
// now visibly stale; every view reports the same version it snapshotted).
|
|
3659
|
+
rosterVersion++
|
|
3626
3660
|
// Reconcile in-progress coordination so a removed member cannot hang consensus or crash a round:
|
|
3627
3661
|
// drop its meeting speech / verify verdict and prune it from the meeting's speaking order so the
|
|
3628
3662
|
// find() there never selects a ghost. Its QUEUED verify proposals are deliberately KEPT: a
|
|
@@ -3632,6 +3666,12 @@ export function apply(ctx) {
|
|
|
3632
3666
|
// only the first entry, which may belong to the removed member).
|
|
3633
3667
|
if(meetingState){ delete meetingState.inputs[id]; meetingState.order=(meetingState.order||[]).filter(x=>x!==id) }
|
|
3634
3668
|
if(verifyState){ delete verifyState.verdicts[id] }
|
|
3669
|
+
// P2 (round-9): REMOVAL prunes the FROZEN participant set as well - v4's long-standing contract is
|
|
3670
|
+
// that removing an in-flight voter RELEASES the wait (T26/T31), while the snapshot still gives
|
|
3671
|
+
// every view one identical set + version. Additions never touch a frozen set (that is the point
|
|
3672
|
+
// of freezing); the version bump above marks the set as changed.
|
|
3673
|
+
if(meetingState&&Array.isArray(meetingState.rosterSnapshot)) meetingState.rosterSnapshot=meetingState.rosterSnapshot.filter(x=>x!==id)
|
|
3674
|
+
if(verifyState&&Array.isArray(verifyState.rosterSnapshot)) verifyState.rosterSnapshot=verifyState.rosterSnapshot.filter(x=>x!==id)
|
|
3635
3675
|
await saveAll()
|
|
3636
3676
|
// Re-drive the scheduler right away. If the removed member was the ONLY turn in flight (e.g. the
|
|
3637
3677
|
// last unspoken meeting speaker / the last unvoted voter, interrupted mid-turn), NO subagent/end
|
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
verification (execute / archive / list the reuse library / read an archived file
|
|
103
103
|
verbatim / watch the background compile queue). Members use them too; they work in every mode.
|
|
104
104
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
105
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext
|
|
106
|
-
- Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext
|
|
105
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
106
|
+
- 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.
|
|
107
107
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
108
108
|
- Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
|
|
109
109
|
A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
|
|
@@ -218,8 +218,8 @@
|
|
|
218
218
|
verification (execute / archive / list the reuse library / read an archived file
|
|
219
219
|
verbatim / watch the background compile queue). Members use them too; they work in every mode.
|
|
220
220
|
- math_computation {op: probe|run|receipt|install, engine, mode: code|file|expr, …} — 先 probe 预检引擎/包/许可,再调引擎计算并把脚本与输出归档成可复核回执;缺引擎/缺包只报告与给安装指引/计划;shell 兜底不算归档。
|
|
221
|
-
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext
|
|
222
|
-
- Archive -> edit -> re-run: for mode:'code' the script original is at the receipt's scriptPath (Computation/<id>/script.<ext
|
|
221
|
+
- 归档→编辑→重跑:mode:'code' 的脚本原件在回执的 scriptPath(Computation/<id>/script.<ext>,**相对项目根**);**成员的文件工具是按会话 cwd 解析的**,所以打开它要用**绝对路径** `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来(回执两个字段都有);编辑后用 mode:'file' 指向它重跑,会写出一份**新回执/新 attempt**(含新的 scriptHash)。**旧回执对修改后的代码无效**——报告里必须引用与当前代码哈希一致的那份回执;工具会在 scriptChanged / scriptChangedDuringRun 为 true 时显式告警。
|
|
222
|
+
- 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.
|
|
223
223
|
- 替代必须声明(诚实性):当替代方案改变了**精确性或结论强度**时(精确符号解 → 数值近似、闭式解 → 采样/求积、改了精度/容差/假设、换了算法类),结论里**必须写明**,不得读起来像得到了原本(精确/所要求的)结果;拿不到精确结果就直说。
|
|
224
224
|
- Declare substitutions (honesty): when an alternative changes EXACTNESS or conclusion strength (exact symbolic solution -> numerical approximation, closed form -> sampling/quadrature, changed precision/tolerances/assumptions, a different algorithm class), the conclusion MUST say so explicitly and must not read as if the original (exact/requested) result had been obtained; if the exact result is unavailable, say so plainly.
|
|
225
225
|
A /v5 slash command mirrors these (configure|start|resume|pause|stop|status|report|members|message|meeting|hire|fire|add|remove|set|paper).
|