dsh-vibe-math 2.0.20 → 2.0.21
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.21",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -76,7 +76,12 @@ export function apply(ctx) {
|
|
|
76
76
|
// deadlocked and abandon it. A meeting round's own signal window is activityTimeoutMs, so a
|
|
77
77
|
// resident should speak within that; 2× that without ANY new input/verdict means the meeting/verify
|
|
78
78
|
// is stuck and must not keep the whole group blocked.
|
|
79
|
-
|
|
79
|
+
// Positive duration with a safe fallback: a NEGATIVE/NaN activityTimeoutMs or stallAutoMeetingMs
|
|
80
|
+
// (misconfigured via vibe_v4_set) would otherwise make recoverStallMs negative → every meeting/
|
|
81
|
+
// verify watchdog fires INSTANTLY (abandoning all consensus) and A-fill's idle window would never
|
|
82
|
+
// elapse (waking everyone every pass). Guard every duration read with this.
|
|
83
|
+
function posMs(v,def){ const n=Number(v); return (Number.isFinite(n)&&n>0)?n:(def||120000) }
|
|
84
|
+
function recoverStallMs(){ return posMs(params.activityTimeoutMs,120000) * 2 }
|
|
80
85
|
function pickProvider(){ try { const n=subagents.list?subagents.list():[]; if(n.indexOf('spawn')!==-1) return 'spawn'; if(n.indexOf('fork')!==-1) return 'fork' } catch(e){} return 'spawn' }
|
|
81
86
|
// Per-resident model/provider inheritance: when params.provider / params.model are set,
|
|
82
87
|
// the resident uses that exact route; when left '' the resident inherits the parent's
|
|
@@ -91,11 +96,20 @@ export function apply(ctx) {
|
|
|
91
96
|
if(allow.length===0 && deny.length===0) return undefined
|
|
92
97
|
const f={}; if(allow.length) f.allow=allow; if(deny.length) f.deny=deny; return f
|
|
93
98
|
}
|
|
94
|
-
function makeSignal(ms){ return AbortSignal.timeout(ms
|
|
99
|
+
function makeSignal(ms){ return AbortSignal.timeout(posMs(ms,30000)) }
|
|
95
100
|
function workspaceRoot(){ try { if(rootAgent&&rootAgent.session&&rootAgent.session.header&&rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch(e){} if(sandboxPolicy&&sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot; return '.' }
|
|
96
101
|
function vibeRoot(){ return (workspaceRoot()+'/VibeMath').replace(/\\/g,'/') }
|
|
97
102
|
function frameworkRoot(){ return vibeRoot()+'/Projects/'+currentProject }
|
|
98
103
|
function slugify(s){ const t=String(s==null?'':s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g,'-').replace(/^-+|-+$/g,''); return t||'project' }
|
|
104
|
+
// Object ids (verify targets, recorded cards) become FILE NAMES and DIRECTORY PATHS
|
|
105
|
+
// (Verified/命题/<id>.md, Shared/debates/<id>.md, Propos/<r>/<id>.md, source-card scans).
|
|
106
|
+
// A hostile/sloppy id containing path separators ('../../x') or Windows-forbidden chars would
|
|
107
|
+
// escape the project tree. Keep every harmless character (incl. Chinese) and replace only
|
|
108
|
+
// separators/control chars; strip leading/trailing dots/dashes so the name is never '.'/'..'.
|
|
109
|
+
function idSafe(s){
|
|
110
|
+
const t=String(s==null?'':s).trim().replace(/[\\/:*?"<>|\u0000-\u001f]+/g,'-').replace(/-{2,}/g,'-').replace(/^[.\-]+|[.\-]+$/g,'')
|
|
111
|
+
return t||'id'
|
|
112
|
+
}
|
|
99
113
|
function getPolicy(){ try { if(sandboxPolicy&&rootAgent&&rootAgent.session) return sandboxPolicy.resolve({session:rootAgent.session}) } catch(e){} try { if(sandboxPolicy) return sandboxPolicy.resolve({}) } catch(e){} return undefined }
|
|
100
114
|
function psQuote(p){ return "'"+String(p).replace(/'/g,"''")+"'" }
|
|
101
115
|
async function runShell(script,cwd){ if(subprocess===undefined) return {ok:false,error:'no-subprocess'}; try { const h=subprocess.spawn({argv:['powershell','-NoProfile','-NonInteractive','-Command',script],cwd:cwd||workspaceRoot(),stdio:{stdin:'ignore',stdout:'inherit',stderr:'inherit'},graceMs:20000}); const o=await h.done; return {ok:o.exitCode===0,exitCode:o.exitCode} } catch(e){ return {ok:false,error:String((e&&e.message)||e)} } }
|
|
@@ -322,9 +336,9 @@ export function apply(ctx) {
|
|
|
322
336
|
|
|
323
337
|
// ---- artifact writers (resident-facing) ----
|
|
324
338
|
async function publishProgress(rId,content){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; 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} }
|
|
325
|
-
async function recordProposition(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id
|
|
326
|
-
async function recordMethod(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id
|
|
327
|
-
async function recordSubproblem(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; const id=o.id
|
|
339
|
+
async function recordProposition(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; 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'} }
|
|
340
|
+
async function recordMethod(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; 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'} }
|
|
341
|
+
async function recordSubproblem(rId,o){ if(!rId||!residents.has(rId)) return {ok:false,message:'no such resident'} ; 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'} }
|
|
328
342
|
// auto-sync meeting: every meetingKeepEvery new artifacts, convene a general coordination meeting
|
|
329
343
|
function bumpArtifacts(){ artifactCount+=1; markProgress(); if(!meetingState && !verifyState && !pendingMeeting && Number(params.meetingKeepEvery)>0 && artifactCount % Number(params.meetingKeepEvery)===0){ startMeeting('定期同步:分工/进展/是否需要验证','general',null).catch(()=>{}) } }
|
|
330
344
|
function listResidents(){ return Array.from(residents.values()).map(r=>({id:r.rId,direction:r.direction,status:r.status,rounds:r.rounds,contextPct:r.contextPct,insight:r.insight?r.insight.slice(0,80):''})) }
|
|
@@ -370,9 +384,9 @@ export function apply(ctx) {
|
|
|
370
384
|
}
|
|
371
385
|
const mb=mailboxes.get(to)||[]; mb.push({from,at:now(),content}); mailboxes.set(to,mb); await saveAll(); logActivity('message',from+'→'+to+' (queued)'); return {ok:true}
|
|
372
386
|
}
|
|
373
|
-
async function broadcast(content){
|
|
387
|
+
async function broadcast(content, from){
|
|
374
388
|
let n=0
|
|
375
|
-
for(const [,r] of residents){ const res=await postMessage('facilitator',r.rId,content); if(res&&res.ok) n++ }
|
|
389
|
+
for(const [,r] of residents){ if(from && r.rId===from) continue; const res=await postMessage(from||'facilitator',r.rId,content); if(res&&res.ok) n++ }
|
|
376
390
|
logActivity('broadcast','to '+n+' resident(s)'); await saveAll(); return {ok:true,message:'broadcast to '+n+' resident(s)'}
|
|
377
391
|
}
|
|
378
392
|
// group conversation relay: when a resident "speaks" (input in its round), forward its
|
|
@@ -460,7 +474,13 @@ export function apply(ctx) {
|
|
|
460
474
|
const votes=Object.values(st.inputs).map(x=>x.voteSolved).filter(v=>typeof v==='boolean')
|
|
461
475
|
const allSolved = allSpoke && votes.length>0 && votes.every(v=>v===true)
|
|
462
476
|
logActivity('meeting', 'concluded'+(allSolved?' → ALL agree solved':' (no unanimous solved vote)'))
|
|
463
|
-
if(allSolved){
|
|
477
|
+
if(allSolved){
|
|
478
|
+
running=false; autoDone=true; phase='done'; clearHeartbeat()
|
|
479
|
+
// symmetric with initAbort: the STOP path must also release the coordination state, else
|
|
480
|
+
// status keeps reporting a phantom in-progress meeting (and stale wake kinds) forever.
|
|
481
|
+
meetingState=null; wakeKind.clear(); pendingMeeting=null; verifyState=null; pendingVerify=[]
|
|
482
|
+
logActivity('stop','all residents agree: problem solved'); await saveAll(); return
|
|
483
|
+
}
|
|
464
484
|
meetingState=null; wakeKind.clear(); await saveAll()
|
|
465
485
|
doSchedule=true
|
|
466
486
|
} finally { finalizeLock=null } // release BEFORE scheduling so a chained verify/meeting is not swallowed
|
|
@@ -557,8 +577,8 @@ export function apply(ctx) {
|
|
|
557
577
|
// duplicates collapse to one entry. A resident who genuinely extends the object later can still
|
|
558
578
|
// re-propose after the dedup window (recoverStallMs) has passed.
|
|
559
579
|
function maybeQueueVerify(target, proposer){
|
|
560
|
-
const t=
|
|
561
|
-
if(!t) return false
|
|
580
|
+
const t=idSafe(target) // sanitize BEFORE it becomes file names / dedup keys / status output
|
|
581
|
+
if(!t || t==='id') return false
|
|
562
582
|
const last=verifiedRecently.get(t)
|
|
563
583
|
if(last!==undefined && (now()-last) < recoverStallMs()){
|
|
564
584
|
logActivity('verify',t+' re-propose ignored (just verified at '+fmtTime(last)+')')
|
|
@@ -663,8 +683,8 @@ export function apply(ctx) {
|
|
|
663
683
|
function clearHeartbeat(){ if(heartbeatDisposer!==null){ try{ heartbeatDisposer() }catch(e){} heartbeatDisposer=null } }
|
|
664
684
|
function armHeartbeat(){
|
|
665
685
|
clearHeartbeat()
|
|
666
|
-
const ms=
|
|
667
|
-
if(
|
|
686
|
+
const ms=posMs(params.activityTimeoutMs,120000)
|
|
687
|
+
if(typeof ctx.timeout!=='function') return
|
|
668
688
|
heartbeatDisposer=ctx.timeout(()=>{ heartbeatDisposer=null; scheduleNext().catch(()=>{}) }, ms)
|
|
669
689
|
}
|
|
670
690
|
// Real DSH /compact of a resident's OWN session via ctx.compaction (if the host provides it);
|
|
@@ -708,7 +728,7 @@ export function apply(ctx) {
|
|
|
708
728
|
// meeting/verify/pending work is active AND no resident is currently working (so it never
|
|
709
729
|
// preempts an in-flight round).
|
|
710
730
|
if(phase==='active' && !meetingState && !verifyState && pendingVerify.length===0 && busy.size===0){
|
|
711
|
-
const stallMs=
|
|
731
|
+
const stallMs=posMs(params.stallAutoMeetingMs, posMs(params.activityTimeoutMs,120000)*3)
|
|
712
732
|
if(now()-lastProgressAt>=stallMs){
|
|
713
733
|
await startMeeting('团队较长时间没有新进展。请你们自行讨论:当前问题是否已解决、开放难点是什么、谁负责哪部分、下一步如何推进,并自主决定是否继续。框架只负责转达与记录,不替你们决定。','general',null)
|
|
714
734
|
return
|
|
@@ -721,7 +741,7 @@ export function apply(ctx) {
|
|
|
721
741
|
// On a FAILED wake we re-arm the heartbeat so a single follow-up error NEVER permanently stops
|
|
722
742
|
// the group (a successful wake re-drives scheduleNext through its own onResidentEnd, which re-arms).
|
|
723
743
|
clearHeartbeat()
|
|
724
|
-
const atOs=
|
|
744
|
+
const atOs=posMs(params.activityTimeoutMs,120000)
|
|
725
745
|
// `mp` (maxParallel) is already declared above in this function scope.
|
|
726
746
|
// Collect idle (not busy) residents sorted by idle time, oldest-first (round-robin fairness).
|
|
727
747
|
const idleCandidates = Array.from(residents.values())
|
|
@@ -764,7 +784,7 @@ export function apply(ctx) {
|
|
|
764
784
|
let delivered=false
|
|
765
785
|
for(const [to,msgs] of mailboxes){
|
|
766
786
|
if(msgs.length===0) continue
|
|
767
|
-
const r=residents.get(to); if(!r){ continue }
|
|
787
|
+
const r=residents.get(to); if(!r){ mailboxes.delete(to); continue } // stale recipient → drop the entry
|
|
768
788
|
if(busy.has(to)) continue // recipient busy → leave the message queued for a later pass
|
|
769
789
|
const mp=Number(params.maxParallel)||0
|
|
770
790
|
if(mp>0 && busy.size>=mp) break // concurrency cap reached → stop delivering more now
|
|
@@ -856,6 +876,7 @@ export function apply(ctx) {
|
|
|
856
876
|
if(!problemText) return {ok:false,message:'problem text required (pass problem, or use vibe_v4_configure first)'}
|
|
857
877
|
problemId=slugify(problemText.slice(0,40))||'problem'
|
|
858
878
|
if(residentCount) params.residentCount=Number(residentCount)||4
|
|
879
|
+
if(!(Number(params.residentCount)>=1)) params.residentCount=DEFAULT_PARAMS.residentCount // a 0/negative count (settings misconfig) would spawn nobody & idle forever
|
|
859
880
|
running=true; autoDone=false; phase='brainstorm'
|
|
860
881
|
await writeText('Problems/'+problemId+'.md','# 问题|'+problemId+'\n- ID: '+problemId+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n')
|
|
861
882
|
// A reused session may still have OLD residents in flight from a previous run (start is a FRESH
|
|
@@ -925,7 +946,12 @@ export function apply(ctx) {
|
|
|
925
946
|
pendingVerify: pendingVerify.length?pendingVerify[0].targetId:null,
|
|
926
947
|
parkedMeeting: pendingMeeting?pendingMeeting.agenda:null,
|
|
927
948
|
meetings:meetings.length, recentActivity: activityLog.slice(-8) } }
|
|
928
|
-
async function addMember(direction){
|
|
949
|
+
async function addMember(direction){
|
|
950
|
+
// Adding a member starts a REAL resident turn (spawnResident → brainstorm) — refuse unless the
|
|
951
|
+
// run is live: on a concluded (autoDone) or never-started/paused run the new member would work
|
|
952
|
+
// with nobody to coordinate (zombie work on a project the group already declared done).
|
|
953
|
+
if(!running || autoDone) return {ok:false,message:'no active run to join (start or resume first)'}
|
|
954
|
+
const r=newResident(direction||''); await spawnResident(r)
|
|
929
955
|
// Mid-meeting additions must join the meeting's speaking order; otherwise allSpoke (over CURRENT
|
|
930
956
|
// residents) can never be true for the new member (not in the snapshot order) and the meeting is
|
|
931
957
|
// only ever released by the stuck watchdog instead of finalizing with everyone's input.
|
|
@@ -994,6 +1020,9 @@ export function apply(ctx) {
|
|
|
994
1020
|
onResidentEnd, start, resume, status, report, addMember, removeMember, setParams,
|
|
995
1021
|
setPause, initAbort, postMessage, startMeeting, saveAll, broadcast, configure, loadSettings,
|
|
996
1022
|
currentResident:()=>currentResident,
|
|
1023
|
+
// safety kick: drive one scheduler pass (used when an end handler errored, so an exceptional
|
|
1024
|
+
// turn can never leave the group with no end-event and no heartbeat to continue it)
|
|
1025
|
+
nudge:()=>scheduleNext().catch(()=>{}),
|
|
997
1026
|
residentIdOf:(agent)=>{ const m=residentOfAgent(agent); if(m) return m; const c=currentResident; return (c && residents.has(c)) ? c : '' },
|
|
998
1027
|
useResident:(id)=>{ currentResident=id },
|
|
999
1028
|
publishProgress, recordProposition, recordMethod, recordSubproblem, listResidents, reportContext,
|
|
@@ -1034,7 +1063,7 @@ export function apply(ctx) {
|
|
|
1034
1063
|
registerTool('vibe_v4_set','Set V4 parameters. model/provider override resident LLM route (empty=inherit main); toolAllow/toolDeny restrict resident tools (arrays of tool names); residentPersona adds a persona line.',objParams({residentCount:{type:'integer'},compactAfterRounds:{type:'integer'},compactThreshold:{type:'integer'},meetingKeepEvery:{type:'integer'},maxParallel:{type:'integer'},activityTimeoutMs:{type:'integer'},verdictMaxRounds:{type:'integer'},stallAutoMeetingMs:{type:'integer'},provider:{type:'string'},model:{type:'string'},residentPersona:{type:'string'},toolAllow:{type:'array',items:{type:'string'}},toolDeny:{type:'array',items:{type:'string'}}}),(s,a)=>{ s.setParams(a); return {ok:true} })
|
|
1035
1064
|
// resident-facing tools: route to the CALLING resident (exec.agent.id === childId);
|
|
1036
1065
|
// fall back to the last-woken resident when called by the host/assistant.
|
|
1037
|
-
registerTool('vibe_v4_send_message','(resident) Send a message to another resident.',objParams({to:{type:'string'},content:{type:'string'}},['to','content']),(s,a,x)=>s.
|
|
1066
|
+
registerTool('vibe_v4_send_message','(resident) Send a message to another resident (to=all broadcasts to the whole team).',objParams({to:{type:'string'},content:{type:'string'}},['to','content']),(s,a,x)=>{ const from=s.residentIdOf(x); if(!from) return {ok:false,message:'no such resident'}; if(String(a.to)==='all') return s.broadcast(a.content, from); return s.postMessage(from,a.to,a.content) })
|
|
1038
1067
|
registerTool('vibe_v4_publish_progress','(resident) Append to your own progress markdown.',objParams({content:{type:'string'}},['content']),(s,a,x)=>s.publishProgress(s.residentIdOf(x),a.content))
|
|
1039
1068
|
registerTool('vibe_v4_record_proposition','(resident) Record a proposition to your library.',objParams({id:{type:'string'},title:{type:'string'},statement:{type:'string'},prob:{type:'number'},value:{type:'number'},motivation:{type:'string'}}),(s,a,x)=>s.recordProposition(s.residentIdOf(x),a))
|
|
1040
1069
|
registerTool('vibe_v4_record_method','(resident) Record a method/theory to your library.',objParams({id:{type:'string'},title:{type:'string'},type:{type:'string'},content:{type:'string'},notation:{type:'string'},value:{type:'number'},motivation:{type:'string'}}),(s,a,x)=>s.recordMethod(s.residentIdOf(x),a))
|
|
@@ -1077,6 +1106,6 @@ export function apply(ctx) {
|
|
|
1077
1106
|
|
|
1078
1107
|
ctx.on('subagent/end', function(info){
|
|
1079
1108
|
const sid=childOwner.get(info.id); const s=sid!==undefined?sessions.get(sid):undefined
|
|
1080
|
-
if(s) s.onResidentEnd(info.id, info).catch(e=>console.error('vibe-v4 end: '+String((e&&e.stack)||e)))
|
|
1109
|
+
if(s) s.onResidentEnd(info.id, info).catch(e=>{ console.error('vibe-v4 end: '+String((e&&e.stack)||e)); if(s.nudge) s.nudge() })
|
|
1081
1110
|
})
|
|
1082
1111
|
}
|
|
@@ -570,7 +570,23 @@ 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` 120/120(T1–T41,后续审计回归见 §27–§30)、v3 100/100、v2 business 18/regression 14、multisession 25/25。
|
|
574
|
+
|
|
575
|
+
## 30. 第六/七轮全面深度审计(npm v2.0.21,迭代至无新发现)
|
|
576
|
+
|
|
577
|
+
按"不断检查、修复直至没有新问题"的要求连续多轮审计:本轮修复 6 处 + 2 处加固(新增回归 T38–T41),其后两轮全量通读与一致性扫描**未再发现新问题**,审计收敛。
|
|
578
|
+
|
|
579
|
+
- **全票停会后协调态残留**:`finalizeMeeting` 的 allSolved 分支直接 return,留下 `meetingState`/`wakeKind`/pending 态 → status 永久谎报 `meetingInProgress:true`(v2.0.20 修了 initAbort 却漏了 stop 路径)。修复:stop 分支与 initAbort 对称清空全部协调态。**T38**。
|
|
580
|
+
- **已定论/未运行 run 上 addMember 仍开工**:`addMember` → `spawnResident` 会真的启动一轮头脑风暴——在已全票定论的项目上制造僵尸工作。修复:`!running || autoDone` 时拒绝。**T38**。
|
|
581
|
+
- **验证目标/记录 id 路径穿越**:`propose_verify` 或 record 工具传入含 `/`、`\`、`:` 等的 id 时,Verified 卡/辩论文档/来源回写路径可逃出项目树或写入他人目录。修复:`idSafe` 消毒(保留含中文在内的无害字符,仅替换分隔符/非法字符/控制字符,去首尾点与横线),在 `maybeQueueVerify`(唯一入队漏斗)与三个 record 工具处统一应用。**T39**。
|
|
582
|
+
- **resident 工具 `to=all` 声称支持实际报错**:toolList 宣称 `vibe_v4_send_message {to:all}` 可广播,但 resident 版直接 `residents.get('all')` → no such resident。修复:resident 调用 `to=all` 走 `broadcast(content, from)`,且**跳过自己**(host 广播不受影响)。**T40**。
|
|
583
|
+
- **负值/NaN 时长参数灾难**:`vibe_v4_set {activityTimeoutMs:-50}` 会让 `recoverStallMs()` 为负 → 会议/验证看门狗**立即放弃一切共识**;A-fill 空闲窗口永不流逝(每 pass 唤醒全员)。修复:`posMs(v,def)` 兜底所有时长读取(recoverStallMs、makeSignal、armHeartbeat、A-fill atOs、B 停滞阈值)。**T41**。
|
|
584
|
+
- **residentCount<=0 启动空转**:settings 误配 0/负值 → start 不生成任何常驻却 running=true。修复:start 时回落默认值。
|
|
585
|
+
- **加固**:`deliverNextMailbox` 顺带清理已不存在常驻的陈旧邮箱条目;`subagent/end` 处理异常时(理论上所有 IO 均已内捕,此为最后防线)调用会话 `nudge()` 补一针调度,杜绝"异常回合后无 end、无心跳"的静默停摆。
|
|
586
|
+
|
|
587
|
+
> 审计收敛声明:此后两轮对全文件(1111 行)逐函数通读 + 交叉扫描,仅剩以下**已记录设计边界**(均有意识保留、非缺陷):brainstorm 首轮 end 永不触发无看门狗(框架外故障);会议/验证"末位在途成员"不被心跳打断(避免误杀慢成员,真实 DSH 回合必有 end);暂停中显式 `postMessage` 仍投递(人的明确指令);默认工具全继承(T11 钉住,宿主级工具未写入 resident toolList);邮箱在超长共识链后送达(§27);`State` 与进程内共识态不跨进程持久化(§26 说明,重启后 A/B 保活自愈)。
|
|
588
|
+
>
|
|
589
|
+
> 全套测试仍全绿:`selfdrive-v4` 21/21、`e2e-v4-fixes` 120/120(T1–T41)、v3 100/100、v2 business 18/regression 14、multisession 25/25、selfdrive-v3 0 异常。
|
|
574
590
|
|
|
575
591
|
## 29. 第五轮全面深度审计(npm v2.0.20,遗留区域 + 未修改逻辑)
|
|
576
592
|
|