dsh-vibe-math 2.0.16 → 2.0.17
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.17",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": "^22.19.0 || >=24.0.0"
|
|
@@ -405,17 +405,10 @@ export function apply(ctx) {
|
|
|
405
405
|
const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
|
|
406
406
|
if(allSpoke){ await finalizeMeeting(); return }
|
|
407
407
|
// only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
|
|
408
|
+
// NOTE: we deliberately do NOT flush mailboxes here — drafting an un-spoken resident into a normal
|
|
409
|
+
// mail round would delay the meeting and can starve the consensus past its watchdog if the mail
|
|
410
|
+
// backlog is large. Mail is delivered on scheduleNext passes when no consensus is in progress.
|
|
408
411
|
const order=st.order||ids
|
|
409
|
-
// Bounded mailbox flush BETWEEN meeting rounds: if an idle resident has queued messages, let it
|
|
410
|
-
// read/answer them before being drafted into the meeting, otherwise a long chain of meetings &
|
|
411
|
-
// verifies can starve the mailboxes for many minutes (test9: 33 msgs sat undelivered >12 min).
|
|
412
|
-
// The mail recipient completes as a normal round; its onResidentEnd re-enters scheduleNext → the
|
|
413
|
-
// meeting continues (watchdog still bounds it). Only flush when there IS an idle un-spoken target,
|
|
414
|
-
// so we never burn the meeting's own turn budget on unrelated mail.
|
|
415
|
-
if(order.find(x=>st.inputs[x]===undefined && !busy.has(x))){
|
|
416
|
-
const flushed=await deliverNextMailbox()
|
|
417
|
-
if(flushed){ await saveAll(); armHeartbeat(); return }
|
|
418
|
-
}
|
|
419
412
|
const id=order.find(x=>st.inputs[x]===undefined && !busy.has(x))
|
|
420
413
|
if(!id){ armHeartbeat(); return } // no idle un-spoken resident (a busy/hung one): re-check later
|
|
421
414
|
const r=residents.get(id)
|
|
@@ -425,6 +418,7 @@ export function apply(ctx) {
|
|
|
425
418
|
async function finalizeMeeting(){
|
|
426
419
|
if(finalizeLock) return // reentry guard: two onResidentEnd may both see allSpoke → only finalize once
|
|
427
420
|
finalizeLock='meeting'
|
|
421
|
+
let doSchedule=false
|
|
428
422
|
try {
|
|
429
423
|
const st=meetingState
|
|
430
424
|
const ids=Array.from(residents.keys()); const allSpoke=ids.length>0 && ids.every(id=>st.inputs[id]!==undefined)
|
|
@@ -443,13 +437,27 @@ export function apply(ctx) {
|
|
|
443
437
|
const allSolved = allSpoke && votes.length>0 && votes.every(v=>v===true)
|
|
444
438
|
logActivity('meeting', 'concluded'+(allSolved?' → ALL agree solved':' (no unanimous solved vote)'))
|
|
445
439
|
if(allSolved){ running=false; autoDone=true; phase='done'; clearHeartbeat(); logActivity('stop','all residents agree: problem solved'); await saveAll(); return }
|
|
446
|
-
meetingState=null; wakeKind.clear(); await saveAll()
|
|
447
|
-
|
|
440
|
+
meetingState=null; wakeKind.clear(); await saveAll()
|
|
441
|
+
doSchedule=true
|
|
442
|
+
} finally { finalizeLock=null } // release BEFORE scheduling so a chained verify/meeting is not swallowed
|
|
443
|
+
if(doSchedule) await scheduleNext()
|
|
448
444
|
}
|
|
449
445
|
|
|
450
446
|
// ---- verification (unanimous) ----
|
|
451
447
|
async function beginVerify(pv){
|
|
452
448
|
clearHeartbeat()
|
|
449
|
+
// 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; when the
|
|
451
|
+
// current X verify closes, beginVerify would run X end-to-end a SECOND time (test9: p-r3-04 was
|
|
452
|
+
// verified twice back-to-back). Drop it if X was closed within the dedup window.
|
|
453
|
+
const tgt=pv&&pv.targetId?String(pv.targetId):''
|
|
454
|
+
if(tgt){
|
|
455
|
+
const last=verifiedRecently.get(tgt)
|
|
456
|
+
if(last!==undefined && (now()-last) < recoverStallMs()){
|
|
457
|
+
logActivity('verify',tgt+' queued verify dropped at start (just verified at '+fmtTime(last)+')')
|
|
458
|
+
pendingVerify=null; await saveAll(); await scheduleNext(); return
|
|
459
|
+
}
|
|
460
|
+
}
|
|
453
461
|
pendingVerify=null
|
|
454
462
|
verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},history:{},transcript:[],at:now(),lastVerdictAt:now()}
|
|
455
463
|
markProgress();
|
|
@@ -468,11 +476,9 @@ export function apply(ctx) {
|
|
|
468
476
|
}
|
|
469
477
|
const ids=Array.from(residents.keys()); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
|
|
470
478
|
if(allVoted){ await finalizeVerify(); return }
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if(flushed){ await saveAll(); armHeartbeat(); return }
|
|
475
|
-
}
|
|
479
|
+
// NOTE: we deliberately do NOT flush mailboxes here — drafting an un-voted resident into a normal
|
|
480
|
+
// mail round would delay its verdict and can starve the verify past its watchdog when the backlog
|
|
481
|
+
// is large. Mail is delivered on scheduleNext passes when no consensus is in progress.
|
|
476
482
|
const id=ids.find(x=>vs.verdicts[x]===undefined && !busy.has(x))
|
|
477
483
|
if(!id){ armHeartbeat(); return } // no idle un-voted resident (a busy/hung one): re-check later
|
|
478
484
|
const r=residents.get(id)
|
|
@@ -482,6 +488,7 @@ export function apply(ctx) {
|
|
|
482
488
|
async function finalizeVerify(){
|
|
483
489
|
if(finalizeLock) return // reentry guard (two onResidentEnd may both see allVoted)
|
|
484
490
|
finalizeLock='verify'
|
|
491
|
+
let doSchedule=false
|
|
485
492
|
try {
|
|
486
493
|
const vs=verifyState; const expected=Array.from(residents.keys()).length
|
|
487
494
|
const allVoted = expected>0 && Object.keys(vs.verdicts).length>=expected
|
|
@@ -489,20 +496,23 @@ export function apply(ctx) {
|
|
|
489
496
|
// verdict is a PURE 0-1 probability; only ALL=1 (true) or ALL=0 (false) is a binary verdict.
|
|
490
497
|
const allTrue = allVoted && vals.every(x=>Number(x.prob)===1)
|
|
491
498
|
const allFalse = allVoted && vals.every(x=>Number(x.prob)===0)
|
|
492
|
-
if(allTrue||allFalse){ await closeVerify(vs,allTrue);
|
|
493
|
-
if(vs.round+1<params.verdictMaxRounds){
|
|
499
|
+
if(allTrue||allFalse){ await closeVerify(vs,allTrue); doSchedule=true }
|
|
500
|
+
else if(vs.round+1<params.verdictMaxRounds){
|
|
494
501
|
// Move to a REAL debate round: snapshot the current votes into history (so the next round's
|
|
495
502
|
// prompt can show others' previous stances), then CLEAR verdicts so every resident is asked to
|
|
496
503
|
// give a fresh independent judgement after seeing the debate. Without the clear, allVoted stays
|
|
497
504
|
// true and the debate rounds burn through with NOBODY being re-asked (a silent no-op).
|
|
498
505
|
vs.history=Object.assign({}, vs.verdicts); vs.verdicts={}
|
|
499
506
|
vs.lastVerdictAt=now() // fresh deadlock window for the re-vote round
|
|
500
|
-
vs.stage='debate'; vs.round+=1; vs.asked=[]; logActivity('verify',vs.targetId+' round '+vs.round+' → debate (re-vote after seeing others)'); await saveAll();
|
|
507
|
+
vs.stage='debate'; vs.round+=1; vs.asked=[]; logActivity('verify',vs.targetId+' round '+vs.round+' → debate (re-vote after seeing others)'); await saveAll(); doSchedule=true
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
const avg=vals.length? vals.reduce((a,x)=>a+(x.prob!=null?x.prob:0.5),0)/vals.length : 0.5
|
|
511
|
+
await writeDebateDoc(vs,false,avg); await rewriteSourceProb(vs.targetId, avg, vs.targetOwner); logActivity('verify',vs.targetId+' NOT unanimous → kept unverified (avg '+avg.toFixed(2)+')')
|
|
512
|
+
verifyState=null; wakeKind.clear(); await saveAll(); doSchedule=true
|
|
501
513
|
}
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
verifyState=null; wakeKind.clear(); await saveAll(); await scheduleNext()
|
|
505
|
-
} finally { finalizeLock=null }
|
|
514
|
+
} finally { finalizeLock=null } // release BEFORE scheduling (chained verifies must not be swallowed)
|
|
515
|
+
if(doSchedule) await scheduleNext()
|
|
506
516
|
}
|
|
507
517
|
async function closeVerify(vs,isTrue){
|
|
508
518
|
await writeDebateDoc(vs,true,isTrue?1:0)
|
|
@@ -511,7 +521,9 @@ export function apply(ctx) {
|
|
|
511
521
|
await rewriteSource(target,isTrue,vs.targetOwner)
|
|
512
522
|
verifiedRecently.set(target, now()) // dedup: block an immediate re-proposal of the same object
|
|
513
523
|
logActivity('verify',target+' → Verified ('+(isTrue?'真':'假')+') by unanimous consensus')
|
|
514
|
-
verifyState=null; wakeKind.clear(); await saveAll()
|
|
524
|
+
verifyState=null; wakeKind.clear(); await saveAll()
|
|
525
|
+
// scheduling is done by finalizeVerify AFTER it releases finalizeLock (so a chained verify is
|
|
526
|
+
// never swallowed by the still-held reentry lock)
|
|
515
527
|
}
|
|
516
528
|
// Queue a verify proposal UNLESS the same object was just verified (closed as 真/假). In parallel
|
|
517
529
|
// self-organization several residents may independently propose the same target while a verify is
|
|
@@ -586,8 +598,8 @@ export function apply(ctx) {
|
|
|
586
598
|
const esc=field.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')
|
|
587
599
|
// one-per-line: `- 状态: ...\n` OR inline: `; - 状态: ...;` / `- 状态: ...; - 概率:`
|
|
588
600
|
const re=new RegExp('(^|\\n|;\\s*)-\\s*'+esc+':[^;\\n]*','gm')
|
|
589
|
-
|
|
590
|
-
return text
|
|
601
|
+
const replaced=text.replace(re,'$1- '+field+': '+newValue)
|
|
602
|
+
return replaced===text ? text : replaced
|
|
591
603
|
}
|
|
592
604
|
// non-unanimous verification: keep the object in its library but write back the
|
|
593
605
|
// average probability (design §8: "留库附概率"), so the card reflects the consensus estimate.
|
|
@@ -849,7 +861,14 @@ export function apply(ctx) {
|
|
|
849
861
|
verify: verifyState?{target:verifyState.targetId,stage:verifyState.stage, voted:Object.keys(verifyState.verdicts).length+'/'+residents.size}:null,
|
|
850
862
|
pendingVerify: pendingVerify?pendingVerify.targetId:null,
|
|
851
863
|
meetings:meetings.length, recentActivity: activityLog.slice(-8) } }
|
|
852
|
-
async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r)
|
|
864
|
+
async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r)
|
|
865
|
+
// Mid-meeting additions must join the meeting's speaking order; otherwise allSpoke (over CURRENT
|
|
866
|
+
// residents) can never be true for the new member (not in the snapshot order) and the meeting is
|
|
867
|
+
// only ever released by the stuck watchdog instead of finalizing with everyone's input.
|
|
868
|
+
if(meetingState){ if(!Array.isArray(meetingState.order)) meetingState.order=Array.from(residents.keys()); if(!meetingState.order.includes(r.rId)) meetingState.order.push(r.rId) }
|
|
869
|
+
// Mid-verify additions are automatically asked to vote (continueVerifyRound recomputes ids from
|
|
870
|
+
// the live residents map), so no extra handling is needed there.
|
|
871
|
+
return {ok:true,id:r.rId,direction:r.direction} }
|
|
853
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)
|
|
854
873
|
// Reconcile in-progress coordination so a removed member cannot hang consensus or crash a round:
|
|
855
874
|
// drop its meeting speech / verify verdict / deferred-meeting / pending-verify if it owned them, and
|
|
@@ -561,9 +561,10 @@ VibeMath/Projects/<project>/
|
|
|
561
561
|
|
|
562
562
|
### 实战审计修复(test9)
|
|
563
563
|
- **源卡回写定位与格式兼容**:常驻用 fs 直写卡片时,文件名可能与卡内声明的 `- ID:` 不一致(如 `Propos/r-3/p-01.md` 声明 `ID: p-r3-01`),且元数据可能是单行 `; ` 分隔(`- ID: p-105; - 状态: 未定论; - 概率: 0.95; …`)。旧 `findSourceRel` 只按文件名匹配 → 找不到 → 回退到顶层 `Propos/<target>.md` 并对空串执行写回,产生 **0 字节垃圾文件**、真实源卡状态不更新;旧回写正则只匹配"每行一个字段"的格式,对单行 `; ` 分隔卡失效。修复:① 文件名快查后增加**按卡内 `- ID:` 扫描**各常驻库;② 找不到时**跳过写回并记日志**(不再造空文件);③ 回写正则兼容"行首字段"与"行内 `; ` 分隔字段"两种格式。
|
|
564
|
-
- **会议/验证终结幂等(并发重入)**:两个常驻的 `onResidentEnd` 同时看到 `allSpoke/allVoted` 时可能**并发执行两次 finalizeMeeting/finalizeVerify**,重复 `meetings.push`/`logDecision`/`propose_task`/`stop`(test9:同一次会议在 decisions/session 里出现两次、生成两条相同任务、重复 stop)。修复:会话级 `finalizeLock`
|
|
565
|
-
- **重复验证同一对象去重**:并行自组织下多个常驻可能各自对同一对象 `propose_verify`,导致对象被端到端验证两次(test9:p-r3-04 被 Verified 两次)。修复:`verifiedRecently` 记录刚定论对象;在去重窗口(`recoverStallMs()`)内忽略对同一对象的再次提议(`maybeQueueVerify
|
|
566
|
-
-
|
|
564
|
+
- **会议/验证终结幂等(并发重入)**:两个常驻的 `onResidentEnd` 同时看到 `allSpoke/allVoted` 时可能**并发执行两次 finalizeMeeting/finalizeVerify**,重复 `meetings.push`/`logDecision`/`propose_task`/`stop`(test9:同一次会议在 decisions/session 里出现两次、生成两条相同任务、重复 stop)。修复:会话级 `finalizeLock` 重入锁(第一次执行期间再次进入直接返回),且**锁在调用 `scheduleNext()` 之前释放**——否则锁会跨 await 误吞紧随其后的链式验证/会议终结(牵连修正)。
|
|
565
|
+
- **重复验证同一对象去重**:并行自组织下多个常驻可能各自对同一对象 `propose_verify`,导致对象被端到端验证两次(test9:p-r3-04 被 Verified 两次)。修复:`verifiedRecently` 记录刚定论对象;在去重窗口(`recoverStallMs()`)内忽略对同一对象的再次提议(`maybeQueueVerify`);`beginVerify` 启动时**再查一次**去重窗口(丢弃"验证进行中排进 pendingVerify、前一个刚关闭"的重复请求);扩展后仍可在窗口外重新提议。
|
|
566
|
+
- **会议进行中加入成员**:新成员不在会议发言快照 `order` 中会导致 `allSpoke`(按当前成员集判定)永不成立,会议只能靠看门狗超时放弃。修复:`addMember` 在会议进行中把新成员**追加进 `order`**(验证进行中则自动被要求投票,无需处理)。
|
|
567
|
+
- **邮箱被长会议/长验证链饿死(记录为设计边界,未在会议轮内打断)**:`scheduleNext` 只在"无 meeting/verify/pending"时投递邮箱;verify→meeting→verify 长链可能让邮箱消息长时间未送达(test9 收尾期有积压)。曾尝试在会议/验证轮间投递,但会把未投票/未发言的常驻拉去做普通轮,令验证在看门狗窗口内等不到票而**误放弃**——故回退,改为**仅在无进行中共识时投递**(消息持久化不丢失,下次非共识调度/恢复时送达)。
|
|
567
568
|
|
|
568
569
|
> 效果:一个卡死/坏掉的会议或验证最多阻塞 `activityTimeoutMs`×2 后自动释放,团队重新回到 A/B 分级保活,**不会永久停死**。仍符合"框架只促成、从不指派任务"(放弃只是终止一个无法推进的会议,把控制权交还团队的自组织循环)。
|
|
569
570
|
>
|