dsh-vibe-math 2.0.17 → 2.0.18
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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-vibe-math",
|
|
3
3
|
"description": "Multi-agent mathematical problem-solving & verification frameworks for DeepSeek Harness — THREE agent presets in one install: vibe-math-v2 (probability-driven: qs.json + Propos knowledge base + explorer→solver→review/debate verdict), vibe-math-v3 (THIRD-generation, recommended: paper-style Markdown knowledge base with Problems/Progress/Propos/Methods/Verified + planner-agent scheduling that decides the next N actions + universal theory/method invention library + agents write their own Markdown directly via a per-file write lock), and vibe-math-v4 (FOURTH-generation: persistent self-organizing resident subagents that message & meet to decide all tasks, verify only by unanimous consensus, /compact at a context threshold, and stop only when all agree the problem is solved). Installing this bundle auto-installs all three presets (v1 was removed at v2.0.0).",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.18",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -46,7 +46,7 @@ export function apply(ctx) {
|
|
|
46
46
|
let residents = new Map(), mailboxes = new Map(), taskboard = [], decisions = []
|
|
47
47
|
let meetings = [], reports = [], activityLog = []
|
|
48
48
|
let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
|
|
49
|
-
let meetingState = null, verifyState = null, pendingVerify =
|
|
49
|
+
let meetingState = null, verifyState = null, pendingVerify = [], pendingMeeting = null // pendingVerify: FIFO queue (several residents may independently propose different objects before any verify runs — a single slot silently DROPPED all but the last proposal)
|
|
50
50
|
let busy = new Set(), wakeKind = new Map(), currentResident = ''
|
|
51
51
|
let finalizeLock = null // 'meeting'|'verify' while a consensus finalize is running (reentry guard)
|
|
52
52
|
const verifiedRecently = new Map() // targetId -> timestamp when it was closed as Verified (dedup re-propose)
|
|
@@ -102,7 +102,22 @@ export function apply(ctx) {
|
|
|
102
102
|
async function fsTarget(rel){ return await fs.resolve(rel,{cwd:frameworkRoot()}) }
|
|
103
103
|
async function readText(rel){ try { const t=await fsTarget(rel); if(await fs.stat(t)===undefined) return undefined; return await fs.readText(t) } catch(e){ return undefined } }
|
|
104
104
|
async function writeText(rel,content){ try { const t=await fsTarget(rel); await fs.writeText(t,content,undefined,undefined,getPolicy()); return true } catch(e){ return false } }
|
|
105
|
-
|
|
105
|
+
// State files (taskboard/residents/session/mailboxes/decisions) are written by MANY concurrent
|
|
106
|
+
// flows (parallel resident turns + end handlers + tools). Two near-simultaneous writers of the
|
|
107
|
+
// SAME file each stringified their snapshot BEFORE their fs.writeText landed, so the writer with
|
|
108
|
+
// the OLDER snapshot could land LAST and silently erase the other's entry (e.g. two residents
|
|
109
|
+
// proposing tasks in the same tick → one task vanished from taskboard.json until the next save).
|
|
110
|
+
// Fix: serialize writes PER FILE, and defer JSON.stringify until the write actually runs (so the
|
|
111
|
+
// snapshot always reflects the newest in-memory state at execution time — late writers win with
|
|
112
|
+
// the FULL state, never with a stale subset).
|
|
113
|
+
const jsonQueues = new Map() // rel -> tail promise (per-session file write chain)
|
|
114
|
+
function writeJson(rel,obj){
|
|
115
|
+
const key='j:'+rel
|
|
116
|
+
const prev=jsonQueues.get(key)||Promise.resolve(true)
|
|
117
|
+
const run=prev.catch(()=>{}).then(async ()=>{ try { const t=await fsTarget(rel); await fs.writeText(t,JSON.stringify(obj,null,2),undefined,undefined,getPolicy()); return true } catch(e){ return false } })
|
|
118
|
+
jsonQueues.set(key,run.catch(()=>{}))
|
|
119
|
+
return run
|
|
120
|
+
}
|
|
106
121
|
async function readJson(rel){ const t=await readText(rel); if(t===undefined||t==='') return undefined; try { return JSON.parse(t) } catch(e){ return undefined } }
|
|
107
122
|
async function ensureDirs(){ const base=frameworkRoot(); const dirs=['Problems','Progress','Propos','Methods','Subproblems','Shared/meetings','Shared/debates','Verified/命题','Verified/问题','Reliable','Notes','State']; return await runShell('New-Item -Force -ItemType Directory -Path '+[vibeRoot()+'/Projects'].concat(dirs.map(d=>base+'/'+d)).map(psQuote).join(',')+' | Out-Null') }
|
|
108
123
|
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 } }
|
|
@@ -373,13 +388,17 @@ export function apply(ctx) {
|
|
|
373
388
|
// ---- meeting ----
|
|
374
389
|
async function startMeeting(agenda,type,targetId){
|
|
375
390
|
if(meetingState) return {ok:false,message:'meeting already in progress'}
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
391
|
+
// Park-and-resume (never lose a coordination request, never create a zombie): while a
|
|
392
|
+
// verification holds the floor, while the group is still brainstorming (members are busy in
|
|
393
|
+
// their first rounds — a meeting started there could not be serviced and the old code let its
|
|
394
|
+
// stall watchdog silently ABANDON it minutes later), or while the run is paused, the meeting
|
|
395
|
+
// request is parked in pendingMeeting (FIRST request wins) and starts as soon as the floor is
|
|
396
|
+
// free. A never-started session (no residents to talk) and a concluded run (autoDone) refuse
|
|
397
|
+
// instead — convening there previously created a meeting nobody could ever be woken into.
|
|
398
|
+
if(!running || autoDone || phase==='brainstorm' || verifyState || pendingVerify.length>0){
|
|
399
|
+
if(autoDone || (!running && residents.size===0)) return {ok:false,message:'run is not active (use vibe_v4_start or vibe_v4_resume first)'}
|
|
381
400
|
if(!pendingMeeting) pendingMeeting = { agenda, type:type||'general', targetId:targetId||null }
|
|
382
|
-
return {ok:true,deferred:true,during:'verify'}
|
|
401
|
+
return {ok:true,deferred:true,during: phase==='brainstorm'?'brainstorm':(!running?'paused':'verify')}
|
|
383
402
|
}
|
|
384
403
|
clearHeartbeat()
|
|
385
404
|
const ids=Array.from(residents.keys())
|
|
@@ -447,18 +466,18 @@ export function apply(ctx) {
|
|
|
447
466
|
async function beginVerify(pv){
|
|
448
467
|
clearHeartbeat()
|
|
449
468
|
// Re-check dedup at ACTUAL start, not just at propose time: a resident may propose object X while
|
|
450
|
-
// X is already being verified (it does not know). That proposal sits in pendingVerify;
|
|
451
|
-
// current X verify closes, beginVerify would run X end-to-end a SECOND time (test9:
|
|
452
|
-
// verified twice back-to-back). Drop it if X was closed within the dedup window.
|
|
469
|
+
// X is already being verified (it does not know). That proposal sits in the pendingVerify queue;
|
|
470
|
+
// when the current X verify closes, beginVerify would run X end-to-end a SECOND time (test9:
|
|
471
|
+
// p-r3-04 was verified twice back-to-back). Drop it if X was closed within the dedup window.
|
|
472
|
+
// (pv was already popped from the FIFO queue by scheduleNext — nothing else to clear here.)
|
|
453
473
|
const tgt=pv&&pv.targetId?String(pv.targetId):''
|
|
454
474
|
if(tgt){
|
|
455
475
|
const last=verifiedRecently.get(tgt)
|
|
456
476
|
if(last!==undefined && (now()-last) < recoverStallMs()){
|
|
457
477
|
logActivity('verify',tgt+' queued verify dropped at start (just verified at '+fmtTime(last)+')')
|
|
458
|
-
|
|
478
|
+
await saveAll(); await scheduleNext(); return
|
|
459
479
|
}
|
|
460
480
|
}
|
|
461
|
-
pendingVerify=null
|
|
462
481
|
verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now()}
|
|
463
482
|
markProgress();
|
|
464
483
|
logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
|
|
@@ -526,10 +545,12 @@ export function apply(ctx) {
|
|
|
526
545
|
// never swallowed by the still-held reentry lock)
|
|
527
546
|
}
|
|
528
547
|
// Queue a verify proposal UNLESS the same object was just verified (closed as 真/假). In parallel
|
|
529
|
-
// self-organization several residents may independently propose
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
//
|
|
548
|
+
// self-organization several residents may independently propose targets while a verify is already
|
|
549
|
+
// settling — sometimes the SAME object (test9: p-r3-04 was Verified twice back-to-back), sometimes
|
|
550
|
+
// DIFFERENT objects (e.g. a sync meeting where each member proposes its own target). pendingVerify
|
|
551
|
+
// is therefore a FIFO queue with per-target dedup: every distinct proposal is honored in order, and
|
|
552
|
+
// duplicates collapse to one entry. A resident who genuinely extends the object later can still
|
|
553
|
+
// re-propose after the dedup window (recoverStallMs) has passed.
|
|
533
554
|
function maybeQueueVerify(target, proposer){
|
|
534
555
|
const t=String(target||'').trim()
|
|
535
556
|
if(!t) return false
|
|
@@ -538,7 +559,8 @@ export function apply(ctx) {
|
|
|
538
559
|
logActivity('verify',t+' re-propose ignored (just verified at '+fmtTime(last)+')')
|
|
539
560
|
return false
|
|
540
561
|
}
|
|
541
|
-
pendingVerify
|
|
562
|
+
if(pendingVerify.some(p=>String(p.targetId)===t)) return true // already queued → keep ONE entry
|
|
563
|
+
pendingVerify.push({targetId:t,targetType:guessTargetType(t),proposer:proposer||'',at:now()})
|
|
542
564
|
return true
|
|
543
565
|
}
|
|
544
566
|
async function writeDebateDoc(vs,done,val){
|
|
@@ -666,9 +688,9 @@ export function apply(ctx) {
|
|
|
666
688
|
if(phase==='brainstorm'){ await maybeFinishBrainstorm(); return }
|
|
667
689
|
if(meetingState){ await continueMeetingRound(); return }
|
|
668
690
|
if(verifyState){ await continueVerifyRound(); return }
|
|
669
|
-
if(pendingVerify){ const pv=pendingVerify; await beginVerify(pv); return }
|
|
691
|
+
if(pendingVerify.length){ const pv=pendingVerify.shift(); await beginVerify(pv); return }
|
|
670
692
|
// A meeting requested while a verify held the floor is parked in pendingMeeting; once the
|
|
671
|
-
// verify has truly
|
|
693
|
+
// verify queue has truly drained (no verifyState / pendingVerify), resume it before anything else.
|
|
672
694
|
if(pendingMeeting){ const pm=pendingMeeting; pendingMeeting=null; await startMeeting(pm.agenda, pm.type, pm.targetId); return }
|
|
673
695
|
// mailbox delivery
|
|
674
696
|
const delivered=await deliverNextMailbox(); if(delivered) return
|
|
@@ -680,7 +702,7 @@ export function apply(ctx) {
|
|
|
680
702
|
// (framework convenes & records; residents decide — never assigns work). Only when no
|
|
681
703
|
// meeting/verify/pending work is active AND no resident is currently working (so it never
|
|
682
704
|
// preempts an in-flight round).
|
|
683
|
-
if(phase==='active' && !meetingState && !verifyState &&
|
|
705
|
+
if(phase==='active' && !meetingState && !verifyState && pendingVerify.length===0 && busy.size===0){
|
|
684
706
|
const stallMs=Number(params.stallAutoMeetingMs)||((Number(params.activityTimeoutMs)||120000)*3)
|
|
685
707
|
if(now()-lastProgressAt>=stallMs){
|
|
686
708
|
await startMeeting('团队较长时间没有新进展。请你们自行讨论:当前问题是否已解决、开放难点是什么、谁负责哪部分、下一步如何推进,并自主决定是否继续。框架只负责转达与记录,不替你们决定。','general',null)
|
|
@@ -769,21 +791,29 @@ export function apply(ctx) {
|
|
|
769
791
|
meetingState.inputs[r.rId]={input:parsed.input||parsed.summary||'',voteSolved:typeof parsed.voteSolved==='boolean'?parsed.voteSolved:null,propose_verify:parsed.propose_verify||null,propose_task:parsed.propose_task||null,task_desc:parsed.task_desc||'',claim_task:parsed.claim_task||null}
|
|
770
792
|
meetingState.lastInputAt=now()
|
|
771
793
|
if(parsed.propose_verify) maybeQueueVerify(parsed.propose_verify, r.rId)
|
|
772
|
-
await saveAll()
|
|
794
|
+
await saveAll()
|
|
795
|
+
// PAUSE/stop: record the in-flight input/verdict but do NOT start any NEW consensus wake —
|
|
796
|
+
// a paused run must stay paused (resume() refreshes the consensus clocks and re-drives).
|
|
797
|
+
if(!running || autoDone) return
|
|
798
|
+
await continueMeetingRound(); return
|
|
773
799
|
}
|
|
774
800
|
if((kind==='verif-ind'||kind==='verif-deb') && verifyState){
|
|
775
801
|
const v=(parsed&&parsed.vote)||{}
|
|
776
802
|
// verdict = 0-1 probability the object is TRUE (1=绝对真, 0=绝对假, 0.5=不确定);
|
|
777
|
-
// also accept legacy 'TRUE'/'FALSE' strings.
|
|
803
|
+
// also accept legacy 'TRUE'/'FALSE' strings AND quoted numeric strings ("0.9"), which LLMs
|
|
804
|
+
// occasionally emit — without this a confident "0.9" was silently misread as 0.5 (uncertainty).
|
|
778
805
|
let p
|
|
779
806
|
if(typeof v.verdict==='number'){ p=clamp01(v.verdict) }
|
|
780
807
|
else if(/^TRUE$/i.test(String(v.verdict))){ p=1 }
|
|
781
808
|
else if(/^FALSE$/i.test(String(v.verdict))){ p=0 }
|
|
809
|
+
else if(typeof v.verdict==='string' && v.verdict.trim()!=='' && Number.isFinite(Number(v.verdict))){ p=clamp01(Number(v.verdict)) }
|
|
782
810
|
else { p=clamp01(Number(v.confidence)) }
|
|
783
811
|
// verdict is a PURE 0-1 probability (a degree); no binary TRUE/FALSE classification.
|
|
784
812
|
verifyState.verdicts[r.rId]={prob:p,confidence:p,reason:String(v.reason||parsed.summary||'')}
|
|
785
813
|
verifyState.lastVerdictAt=now()
|
|
786
|
-
await saveAll()
|
|
814
|
+
await saveAll()
|
|
815
|
+
if(!running || autoDone) return // pause: freeze (resume refreshes the clocks and re-drives)
|
|
816
|
+
await continueVerifyRound(); return
|
|
787
817
|
}
|
|
788
818
|
// normal turn
|
|
789
819
|
if(r.status==='brainstorm'){ r.insight=parsed.summary||output; r.status='active' }
|
|
@@ -817,7 +847,11 @@ export function apply(ctx) {
|
|
|
817
847
|
if(residentCount) params.residentCount=Number(residentCount)||4
|
|
818
848
|
running=true; autoDone=false; phase='brainstorm'
|
|
819
849
|
await writeText('Problems/'+problemId+'.md','# 问题|'+problemId+'\n- ID: '+problemId+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n')
|
|
820
|
-
|
|
850
|
+
// A reused session may still have OLD residents in flight from a previous run (start is a FRESH
|
|
851
|
+
// run that reuses the same r-1.. library paths). Interrupt them BEFORE resetting, otherwise their
|
|
852
|
+
// still-running turns keep writing into the same per-resident files the new run is about to use.
|
|
853
|
+
for(const [,or] of residents){ if(or.childId){ try{ subagents.interrupt(or.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } }
|
|
854
|
+
residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=[]; residentSeq=0; artifactCount=0; clearHeartbeat()
|
|
821
855
|
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; lastSyncMeetingAt=0; finalizeLock=null; verifiedRecently.clear() // fresh run must NOT inherit stale concurrency/coordination state (busy/wakeKind/currentResident/pendingMeeting) from a previous run on the same reused session
|
|
822
856
|
lastActivityAt=now(); lastProgressAt=now() // fresh stall/activity clock for the new run (else B could fire immediately on a reused session)
|
|
823
857
|
const dirs=Array.isArray(seedDirections)?seedDirections.slice(0,params.residentCount):[]
|
|
@@ -826,6 +860,10 @@ export function apply(ctx) {
|
|
|
826
860
|
}
|
|
827
861
|
async function resume(){
|
|
828
862
|
currentProject=await readCurrentProject(); await ensureDirs(); await loadAll(); await loadSettings()
|
|
863
|
+
// A run the group CONCLUDED (unanimous voteSolved → autoDone) must not be silently revived into
|
|
864
|
+
// a zombie that keeps waking residents with no consensus that it should still run. The group
|
|
865
|
+
// decided it is done; continuing means a NEW run (vibe_v4_start / vibe_v4_configure).
|
|
866
|
+
if(autoDone) return {ok:false,message:'This run already concluded (all residents agreed solved). Start a fresh run with vibe_v4_start (vibe_v4_configure a new problem first if needed).'}
|
|
829
867
|
// After loadAll the residentSeq counter is still whatever THIS process had (0 on a fresh process),
|
|
830
868
|
// but persisted residents may already be r-1..r-N. Sync it to the max existing id so a later
|
|
831
869
|
// addMember never collides with an existing resident (it would silently overwrite it).
|
|
@@ -844,22 +882,27 @@ export function apply(ctx) {
|
|
|
844
882
|
const needRespawn = Array.from(residents.values()).some(r=>!r.childId)
|
|
845
883
|
if(needRespawn){
|
|
846
884
|
for(const [,r] of residents){ r.childId=''; r.status='brainstorm'; r.insight=''; r.roundsSinceCompact=0 }
|
|
847
|
-
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; pendingVerify=
|
|
885
|
+
busy=new Set(); wakeKind=new Map(); currentResident=''; pendingMeeting=null; pendingVerify=[]; verifyState=null; meetingState=null; finalizeLock=null; verifiedRecently.clear()
|
|
848
886
|
}
|
|
849
887
|
for(const [,r] of residents){ if(!r.childId){ await spawnResident(r) } }
|
|
850
888
|
if(!running){ running=true; autoDone=false; if(phase==='idle') phase='active' }
|
|
851
889
|
if(needRespawn && phase!=='brainstorm') phase='brainstorm' // let re-spawned residents re-bootstrap together
|
|
890
|
+
// A pause froze an in-progress meeting/verify with its watchdog clock still running: refresh the
|
|
891
|
+
// clocks so a resumed consensus gets a full fresh stall window instead of being abandoned the
|
|
892
|
+
// instant it is serviced again (a short pause must never silently kill a real discussion).
|
|
893
|
+
if(meetingState && meetingState.lastInputAt) meetingState.lastInputAt=now()
|
|
894
|
+
if(verifyState && verifyState.lastVerdictAt) verifyState.lastVerdictAt=now()
|
|
852
895
|
logActivity('resume','restarted'+(crossProcess?' (cross-process: re-spawned)':needRespawn?' (re-spawned)':'')); await saveAll(); await scheduleNext(); return {ok:true,message:'resumed',project:currentProject}
|
|
853
896
|
}
|
|
854
897
|
function status(){ return { ok:true, running, phase, autoDone, project:currentProject, residentCount:residents.size,
|
|
855
898
|
residents:listResidents(), busy:[...busy], taskboard:taskboard.length,
|
|
856
|
-
meetingInProgress: !!(meetingState), verifyInProgress: !!(verifyState), pendingVerify: pendingVerify?pendingVerify.targetId:null,
|
|
899
|
+
meetingInProgress: !!(meetingState), verifyInProgress: !!(verifyState), pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null, pendingVerifyCount: pendingVerify.length,
|
|
857
900
|
params:['residentCount','compactAfterRounds','compactThreshold','maxParallel','activityTimeoutMs','meetingKeepEvery','verdictMaxRounds','stallAutoMeetingMs','provider','model','residentPersona','toolAllow','toolDeny'].map(k=>k+'='+(Array.isArray(params[k])?params[k].join(','):params[k])).join(', ') } }
|
|
858
901
|
function report(){ return { ok:true, running, phase, autoDone, project:currentProject, problem:problemText,
|
|
859
902
|
residents:listResidents(), taskboard:taskboard.filter(t=>t.status!=='done'),
|
|
860
903
|
meeting: meetingState?{id:meetingState.id, agenda:meetingState.agenda, spoke:Object.keys(meetingState.inputs).length+'/'+residents.size}:null,
|
|
861
904
|
verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+residents.size}:null,
|
|
862
|
-
pendingVerify: pendingVerify?pendingVerify.targetId:null,
|
|
905
|
+
pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null,
|
|
863
906
|
meetings:meetings.length, recentActivity: activityLog.slice(-8) } }
|
|
864
907
|
async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r)
|
|
865
908
|
// Mid-meeting additions must join the meeting's speaking order; otherwise allSpoke (over CURRENT
|
|
@@ -869,14 +912,21 @@ export function apply(ctx) {
|
|
|
869
912
|
// Mid-verify additions are automatically asked to vote (continueVerifyRound recomputes ids from
|
|
870
913
|
// the live residents map), so no extra handling is needed there.
|
|
871
914
|
return {ok:true,id:r.rId,direction:r.direction} }
|
|
872
|
-
async function removeMember(id){ const r=residents.get(id); if(!r) return {ok:false}; 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)
|
|
915
|
+
async function removeMember(id){ const r=residents.get(id); if(!r) return {ok:false}; 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=''
|
|
873
916
|
// Reconcile in-progress coordination so a removed member cannot hang consensus or crash a round:
|
|
874
|
-
// drop its meeting speech / verify verdict /
|
|
917
|
+
// drop its meeting speech / verify verdict / pending-verify proposals if it owned them, and
|
|
875
918
|
// prune it from the meeting's speaking order so the find() there never selects a ghost.
|
|
876
919
|
if(meetingState){ delete meetingState.inputs[id]; meetingState.order=(meetingState.order||[]).filter(x=>x!==id) }
|
|
877
920
|
if(verifyState){ delete verifyState.verdicts[id] }
|
|
878
|
-
if(pendingVerify
|
|
879
|
-
await saveAll()
|
|
921
|
+
if(pendingVerify.length) pendingVerify = pendingVerify.filter(p=>p.proposer!==id)
|
|
922
|
+
await saveAll()
|
|
923
|
+
// Re-drive the scheduler right away. If the removed member was the ONLY turn in flight (e.g. the
|
|
924
|
+
// last unspoken meeting speaker / the last unvoted voter, interrupted mid-turn), NO subagent/end
|
|
925
|
+
// will ever arrive to trigger the next pass, and while a consensus is being serviced no heartbeat
|
|
926
|
+
// is armed either — without this kick the meeting/verify would freeze forever behind members that
|
|
927
|
+
// can already conclude. scheduleNext no-ops safely when the run is paused/stopped.
|
|
928
|
+
await scheduleNext()
|
|
929
|
+
return {ok:true} }
|
|
880
930
|
// Normalize one parameter value to its intended type so a string from /v4 set or configure
|
|
881
931
|
// becomes the right number/array. Keeps settings.json clean regardless of how it was set.
|
|
882
932
|
function normalizeParam(k, v){
|
|
@@ -570,7 +570,22 @@ VibeMath/Projects/<project>/
|
|
|
570
570
|
>
|
|
571
571
|
> 说明:无法对"某常驻真的拒绝/掉线"的情况达成全体一致时,看门狗会把该对象保留为"未定论/带概率",这是哲学上期望的诚实结果。`session.json` 仍不持久化 `meetingState`(进行中的会议不跨进程恢复),B 的停滞看门狗在重启后仍会触发,因此重启也能自愈。
|
|
572
572
|
>
|
|
573
|
-
> 全套测试仍全绿:`selfdrive-v4` 21/21、`e2e-v4-fixes`
|
|
573
|
+
> 全套测试仍全绿:`selfdrive-v4` 21/21、`e2e-v4-fixes` 80/80(含 T20 源卡回写 / T21 终结重入 / T22-23 验证去重 / T24-28 后续审计回归,见 §27)、v3 100/100、v2 business 18/regression 14、multisession 25/25。
|
|
574
|
+
|
|
575
|
+
## 27. 第三轮全面深度审计(npm v2.0.18,牵连对象/逻辑)
|
|
576
|
+
|
|
577
|
+
对 v2.0.16/17 修复做连带对象与逻辑审计,又发现并修复 6 处真实缺陷(每处均有新回归 T24–T28 钉住):
|
|
578
|
+
|
|
579
|
+
- **验证提议单槽丢失(pendingVerify 改 FIFO 队列)**:旧实现 `pendingVerify` 是单槽,后到覆盖先到——一次同步会议里多位常驻**各自提议不同对象**(或并行普通轮同时提议)时,只有最后一个对象的验证会被执行,前面的提议**静默丢失**(正是"框架忠实转达"哲学最不能丢的一环)。修复:改为 FIFO 队列 + 队内同目标去重(`maybeQueueVerify` 去重窗口后入队;`scheduleNext` 出队头启动;`beginVerify` 的"启动时二次去重"保留;`removeMember` 过滤其名下在队提议;status/report 显示队头 + `pendingVerifyCount`)。**T25**:一次会议两个成员各提议一个对象 → 两个都完整验证到 Verified/。
|
|
580
|
+
- **removeMember 后不再驱动调度 → 共识永久冻结**:会议/验证在服务中不武装心跳,靠"常驻 end 事件"推进。若被移除的常驻恰是**唯一在途**的那一个(如最后一个未投票者/未发言者,interrupt 后不会再发 end),其余常驻明明已齐票/齐发言却没人触发 finalize——没有看门狗能救(看门狗也在 end 驱动的轮次里),**永久冻结**。修复:`removeMember` 清理后立即 `await scheduleNext()`(顺带把 `currentResident` 复位)。**T26**:验证中移除在途未投票者(mock 永不发 end)→ 验证在剩余成员间照常达成全票、正常定论。
|
|
581
|
+
- **会议在 brainstorm/暂停期"假启动"后被看门狗误杀**:会议在 brainstorm 阶段(成员都在首轮思考)或暂停期间无法被服务,但它的看门狗时钟在创建时就开始走;几分钟后真正轮到它时已被判定卡死而**放弃**(主持人/常驻的会议请求被静默吞掉)。修复:`startMeeting` 在这两种情形(以及验证占场、暂停)一律**暂存到 `pendingMeeting`**(首条优先、不覆盖),等 brainstorm 完成/验证清空/恢复后再真正开始(时钟从真正开始时算);从未启动(无成员)与已全票定论(autoDone)的运行则**明确拒绝**——旧代码在那里开会只会造出一个谁也唤不醒的僵尸会议。
|
|
582
|
+
- **autoDone 后 resume 把已结束的 run 悄悄复活**:已全票定论(`autoDone`)的 run 被 resume 后会把 autoDone 清掉、继续唤醒常驻工作——违反"只有全组一致认为已解决才停止"的哲学(僵尸复活)。修复:`resume()` 在 autoDone 时拒绝并提示改用 `vibe_v4_start`/`vibe_v4_configure`。**T24**。
|
|
583
|
+
- **暂停(Pause)不冻结共识;恢复后看门狗误杀进行中的会议/验证**:旧 setPause 只停 A/B 自驱动,进行中的会议/验证在常驻 end 后仍继续**新唤醒**(暂停不暂停);而暂停期间时钟照走,恢复后若暂停超过看门狗窗口,进行中的会议/验证会被**立即放弃**。修复:end 处理器在 `!running` 时记录在途发言/投票后**不再启动新唤醒**(真暂停);`resume()` 对仍在进行的 meeting/verify **刷新看门狗时钟**后继续。**T28**:暂停期间在途会议发言被记录但无新唤醒;恢复后会议照常收尾(转录文件写出)。
|
|
584
|
+
- **writeJson 并发丢写(同文件延迟序列化)**:taskboard/session/residents 等 State 文件被并行轮、end 处理器、工具并发写;两个近同时写同一文件的流程各自在 fs.writeText 落地**之前**序列化快照,**旧快照后落地会覆盖新快照**(同一瞬间两位常驻各自 proposeTask → 其中一条从 taskboard.json 消失,直到下次保存才自愈)。修复:`writeJson` 按文件串行化 + **延迟到真正执行时才 JSON.stringify**(执行时取最新内存态,后写者永远带全量状态,不会用旧子集覆盖)。
|
|
585
|
+
- **连带小修**:verdict 为**引号数字串**(`"0.9"`,LLM 常见)时旧解析落到 confidence 缺省 → 静默记成 0.5 不确定;现按数字解析。**T27**。
|
|
586
|
+
- **start() 前中断旧常驻**:同一会话复用启动新 run 时,旧 run 仍在途的常驻会继续往**同名** `r-1..` 库路径写文件,与新 run 的 r-1.. 互相污染;start 重置前先 interrupt 旧常驻。
|
|
587
|
+
|
|
588
|
+
> 设计边界复查确认(未改):① 会议/验证轮次唤醒**不额外受 maxParallel 限制**——共识需要全员发言/投票;每个调度 pass 只唤醒一个未发言/未投票者,由各 end 事件串行驱动(残留的普通轮可能造成短暂并发,但不会无限累积);② 非一致(平均概率留库)的对象可被后续提议**再次端到端验证**(去重窗口只挡"刚定论为真/假"的对象)——重复提议者是在不知情时提议的,重验一轮不算错;③ 验证中途 `addMember` 的新成员会被要求投票(v2.0.17 注释行为,保持);④ 已定论 run 的成果随时可在磁盘读取,resume 拒绝只针对"自动复活"。
|
|
574
589
|
|
|
575
590
|
|
|
576
591
|
|