dsh-vibe-math 2.0.4 → 2.0.6

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.md CHANGED
@@ -40,7 +40,7 @@
40
40
  **一句话流水线**:起始产生 N 个**常驻子代理**(continuable,持久上下文)先各自头脑风暴、产出初始见解/方向 → 此后**所有任务安排由它们互相留言 + 集体开会自主决定**(框架只做消息总线/会议/任务板/产物沉淀,**绝不分配任务**);每个常驻把有价值的产物按**价值程度 / 动机用途计划 / 自身概率估计**沉淀到**自己**的 `Progress/<id>/`、`Propos/<id>/`、`Methods/<id>/`、`Subproblems/<id>/` 库,并**可互相阅读**;验证由它们**自行商议**发起,**仅当全体常驻一致(真或假)**才写入 `Verified/`,否则留库附概率;常驻上下文量达阈值(默认 66%)自动 `/compact`;**仅当全体一致认为原问题已解决**才停止;可随时人工干预/增开/关闭常驻,支持断点续跑。
41
41
 
42
42
  > 说明:V4 去掉 v3 的中央规划器与确定性角色(explorer/solver/verifier/planner/method-keeper),把"研究者"本身作为主体。详见 `vibe-math-v4/实现方案.md`。
43
- > 保活机制(分级保活 A+B):团伙空闲超 `activityTimeoutMs` 会收到**自驱动** CHECKPOINT(建议继续解决/发消息/提议任务,而非"是否要停止"),唤醒失败会自动重新武装心跳;若团队空闲且**无新产物**超过 `stallAutoMeetingMs`(默认 6 分钟),框架会自动召集一次同步会议让常驻们自行决定下一步——因此小组不会"跑完就永久停滞"(框架只促成、从不指派任务)。
43
+ > 保活机制(分级保活 A+B + 死锁看门狗):团伙空闲超 `activityTimeoutMs` 会收到**自驱动** CHECKPOINT(建议继续解决/发消息/提议任务,而非"是否要停止"),唤醒失败会自动重新武装心跳;若团队空闲且**无新产物**超过 `stallAutoMeetingMs`(默认 6 分钟),框架会自动召集一次同步会议让常驻们自行决定下一步;若某次**会议/验证卡死**(超过 2×`activityTimeoutMs` 仍无新的发言/投票),框架会自动**放弃该会议/验证**并回到正常自组织,避免一个坏掉的会议永久卡住整个团队——框架始终只促成、从不指派任务。
44
44
 
45
45
  ---
46
46
 
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",
4
+ "version": "2.0.6",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": "^22.19.0 || >=24.0.0"
@@ -70,6 +70,11 @@ export function apply(ctx) {
70
70
  // NOT advanced for stallAutoMeetingMs, so a group that is genuinely producing keeps working
71
71
  // and only a truly stalled group gets a coordination meeting to reboot itself.
72
72
  function markProgress(){ lastProgressAt = now(); }
73
+ // How long a meeting/verify may run without collecting a new input/verdict before we treat it as
74
+ // deadlocked and abandon it. A meeting round's own signal window is activityTimeoutMs, so a
75
+ // resident should speak within that; 2× that without ANY new input/verdict means the meeting/verify
76
+ // is stuck and must not keep the whole group blocked.
77
+ function recoverStallMs(){ return (Number(params.activityTimeoutMs)||120000) * 2 }
73
78
  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' }
74
79
  // Per-resident model/provider inheritance: when params.provider / params.model are set,
75
80
  // the resident uses that exact route; when left '' the resident inherits the parent's
@@ -132,7 +137,7 @@ export function apply(ctx) {
132
137
  function residentLibraries(){
133
138
  const base=frameworkRoot()
134
139
  return '你的资料库根目录:'+base+'/\n'
135
- +' Progress/<你>/progress.md —— 你的研究日志(叙述,可追加)。\n'
140
+ +' Progress/<你>/progress.md —— 你的研究日志(叙述,可追加。主要内容是尝试过的各方法、路线、历程、进度,当前研究进展/进度、将来的计划与打算,及各路线、过程中遇到的障碍及其原因,对各路线、方法的看法、可行性评估,自己研究过程中的一些有价值看法、感想、猜想、理解。以及其它各种你认为有价值的值得记录的事物、经验、方法/想法、创新等都可进行记录)。\n'
136
141
  +' Propos/<你>/<id>.md —— 你的命题/引理。格式:\n'
137
142
  +' - ID: p-<id>; - 状态: 未定论; - 概率: <0-1>; - 价值程度: <0-1>; - 动机用途计划: <为何重要/打算怎么用>\n'
138
143
  +' 然后 ## 陈述 <陈述>;## 证明尝试;## 证伪尝试。\n'
@@ -354,19 +359,29 @@ export function apply(ctx) {
354
359
  // who sees no one else's contribution"; a real discussion lets each member lead sometimes.
355
360
  const rot=Math.floor(Math.random()*Math.max(1,ids.length))
356
361
  const order=ids.slice(rot).concat(ids.slice(0,rot))
357
- meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order}
362
+ meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[],order,at:now(),lastInputAt:now()}
358
363
  markProgress();
359
364
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
360
365
  }
361
366
  async function continueMeetingRound(){
362
367
  if(!meetingState) return
363
- const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>meetingState.inputs[id]!==undefined)
368
+ const st=meetingState
369
+ // STUCK watchdog: a meeting that has been active but collected NO new input for a long while
370
+ // is deadlocked (e.g. an in-flight/hung resident, a run of failed wakes). Abandoning it returns
371
+ // the group to normal self-organization (A heartbeat / B auto-sync can then re-drive) instead of
372
+ // permanently blocking the whole group behind a broken meeting.
373
+ if(now()-(st.lastInputAt||st.at||now())>=recoverStallMs()){
374
+ meetingState=null; wakeKind.clear(); logActivity('meeting','abandoned (stuck: no resident spoke)')
375
+ await saveAll(); await scheduleNext(); return
376
+ }
377
+ const ids=Array.from(residents.keys()); const allSpoke=ids.every(id=>st.inputs[id]!==undefined)
364
378
  if(allSpoke){ await finalizeMeeting(); return }
365
379
  // only wake IDLE un-spoken residents (rotated order); in-flight ones re-trigger this on end.
366
- const order=meetingState.order||ids
367
- const id=order.find(x=>meetingState.inputs[x]===undefined && !busy.has(x)); if(!id) return
380
+ const order=st.order||ids
381
+ const id=order.find(x=>st.inputs[x]===undefined && !busy.has(x))
382
+ if(!id){ armHeartbeat(); return } // no idle un-spoken resident (a busy/hung one): re-check later
368
383
  const r=residents.get(id)
369
- const ok = await wakeResident(r, meetingPrompt(r,meetingState), 'meeting'); await saveAll()
384
+ const ok = await wakeResident(r, meetingPrompt(r,st), 'meeting'); await saveAll()
370
385
  if(!ok) armHeartbeat() // a failed meeting wake must NOT silently hang the meeting
371
386
  }
372
387
  async function finalizeMeeting(){
@@ -394,17 +409,27 @@ export function apply(ctx) {
394
409
  async function beginVerify(pv){
395
410
  clearHeartbeat()
396
411
  pendingVerify=null
397
- verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},transcript:[]}
412
+ verifyState={targetId:pv.targetId,targetType:pv.targetType,targetOwner:pv.proposer||'',stage:'independent',round:0,asked:[],verdicts:{},transcript:[],at:now(),lastVerdictAt:now()}
398
413
  markProgress();
399
414
  logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
400
415
  }
401
416
  async function continueVerifyRound(){
402
417
  if(!verifyState) return
403
- const ids=Array.from(residents.keys()); const allVoted=ids.every(id=>verifyState.verdicts[id]!==undefined)
418
+ const vs=verifyState
419
+ // STUCK watchdog: a verification that has been active but collected no new verdict for a long
420
+ // while is deadlocked (e.g. an in-flight/hung resident). Abandoning it keeps the object as an
421
+ // unverified probability (no unanimous consensus was reachable) and returns the group to normal
422
+ // scheduling instead of blocking the whole team behind a broken verification.
423
+ if(now()-(vs.lastVerdictAt||vs.at||now())>=recoverStallMs()){
424
+ verifyState=null; wakeKind.clear(); logActivity('verify',vs.targetId+' abandoned (stuck: no unanimous verdict reachable)')
425
+ await saveAll(); await scheduleNext(); return
426
+ }
427
+ const ids=Array.from(residents.keys()); const allVoted=ids.every(id=>vs.verdicts[id]!==undefined)
404
428
  if(allVoted){ await finalizeVerify(); return }
405
- const id=ids.find(x=>verifyState.verdicts[x]===undefined && !busy.has(x)); if(!id) return
429
+ const id=ids.find(x=>vs.verdicts[x]===undefined && !busy.has(x))
430
+ if(!id){ armHeartbeat(); return } // no idle un-voted resident (a busy/hung one): re-check later
406
431
  const r=residents.get(id)
407
- const ok = await wakeResident(r, verifyPrompt(r,verifyState), verifyState.stage==='independent'?'verif-ind':'verif-deb'); await saveAll()
432
+ const ok = await wakeResident(r, verifyPrompt(r,vs), vs.stage==='independent'?'verif-ind':'verif-deb'); await saveAll()
408
433
  if(!ok) armHeartbeat() // a failed verify wake must NOT silently hang the verification
409
434
  }
410
435
  async function finalizeVerify(){
@@ -522,8 +547,9 @@ export function apply(ctx) {
522
547
  // B) stall auto-sync meeting (分级保活 B): the group has been idle with NO progress for
523
548
  // stallAutoMeetingMs → convene a sync meeting so the residents coordinate their next move
524
549
  // (framework convenes & records; residents decide — never assigns work). Only when no
525
- // meeting/verify/pending work is already active.
526
- if(phase==='active' && !meetingState && !verifyState && !pendingVerify){
550
+ // meeting/verify/pending work is active AND no resident is currently working (so it never
551
+ // preempts an in-flight round).
552
+ if(phase==='active' && !meetingState && !verifyState && !pendingVerify && busy.size===0){
527
553
  const stallMs=Number(params.stallAutoMeetingMs)||((Number(params.activityTimeoutMs)||120000)*3)
528
554
  if(now()-lastProgressAt>=stallMs){
529
555
  await startMeeting('团队较长时间没有新进展。请你们自行讨论:当前问题是否已解决、开放难点是什么、谁负责哪部分、下一步如何推进,并自主决定是否继续。框架只负责转达与记录,不替你们决定。','general',null)
@@ -531,8 +557,9 @@ export function apply(ctx) {
531
557
  }
532
558
  }
533
559
  // A) heartbeat / coordination: wake the least-recently-active resident after an idle timeout
534
- // to SELF-DRIVE (continue solving / message / propose task / meeting / verify). On a failed
535
- // wake we re-arm the heartbeat so a single follow-up error NEVER permanently stops the group.
560
+ // to SELF-DRIVE (continue solving / message / propose task / meeting / verify). On a FAILED
561
+ // wake we re-arm the heartbeat so a single follow-up error NEVER permanently stops the group
562
+ // (a successful wake re-drives scheduleNext through its own onResidentEnd, which re-arms).
536
563
  clearHeartbeat()
537
564
  let target=null, oldest=-1
538
565
  for(const [,r] of residents){ if(busy.has(r.rId)) continue; const idle=now()-r.lastActiveAt; if(idle>oldest){ oldest=idle; target=r } }
@@ -576,6 +603,7 @@ export function apply(ctx) {
576
603
  const kind=wakeKind.get(r.rId)||'normal'
577
604
  if(kind==='meeting' && meetingState){
578
605
  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}
606
+ meetingState.lastInputAt=now()
579
607
  if(parsed.propose_verify) pendingVerify={targetId:parsed.propose_verify,targetType:guessTargetType(parsed.propose_verify),proposer:r.rId,at:now()}
580
608
  await saveAll(); await continueMeetingRound(); return
581
609
  }
@@ -590,6 +618,7 @@ export function apply(ctx) {
590
618
  else { p=clamp01(Number(v.confidence)) }
591
619
  // verdict is a PURE 0-1 probability (a degree); no binary TRUE/FALSE classification.
592
620
  verifyState.verdicts[r.rId]={prob:p,confidence:p,reason:String(v.reason||parsed.summary||'')}
621
+ verifyState.lastVerdictAt=now()
593
622
  await saveAll(); await continueVerifyRound(); return
594
623
  }
595
624
  // normal turn
@@ -537,6 +537,24 @@ VibeMath/Projects/<project>/
537
537
  >
538
538
  > 新增参数:`stallAutoMeetingMs`(`vibe_v4_set`/`status` 可见)。新增测试:T14(停滞自动会议)、T15(自驱动心跳提示词非旧的"说下一步");`selfdrive-v4` 21/21、`e2e-v4-fixes` 39/39。
539
539
 
540
+ ---
541
+
542
+ ## 26. 会议/验证死锁看门狗(v1.4.3)
543
+
544
+ 一次真实 run(测试6)证实 A+B 之后仍会出现"三个常驻都停":**B 定时同步会议确实被触发了,但会议本身卡死**——`continueMeetingRound` 唤醒某常驻、其发言未被正确记录(或某常驻的唤醒一直失败),导致该常驻被反复唤醒(rounds 递增但会议不前进),而**会议一旦处于卡死状态,`scheduleNext` 只走 `meetingState → continueMeetingRound` 分支,A 心跳、邮箱投递、B 定时会议全部被挡住**,整个团队就永久停在一个坏掉的会议后面。
545
+
546
+ ### 死锁看门狗
547
+ - `meetingState` 增加 `lastInputAt`、`verifyState` 增加 `lastVerdictAt`(创建时=now,每当收到新发言/投票时刷新)。
548
+ - `continueMeetingRound` / `continueVerifyRound` 顶部加看门狗:若 `now()-lastInputAt/lastVerdictAt >= recoverStallMs()`(= `activityTimeoutMs`×2,默认 4 分钟),判定该会议/验证**卡死**,**放弃**(`meetingState/verifyState=null`,记 `abandoned (stuck)`),随后 `scheduleNext()` 回到正常自组织(A/B 继续驱动)。
549
+ - 同时:**找不到空闲的未发言/未投票常驻时不再静默 `return`,改为 `armHeartbeat()`**(稍后重查),并在发现"没有空闲者"时也武装心跳,杜绝"一直等待一个永不空闲的常驻"造成的隐性挂起。
550
+ - 分级保活 B 增加 `busy.size===0` 守卫:任何常驻正在工作时不触发,避免预抢占在途轮次。
551
+
552
+ > 效果:一个卡死/坏掉的会议或验证最多阻塞 `activityTimeoutMs`×2 后自动释放,团队重新回到 A/B 分级保活,**不会永久停死**。仍符合"框架只促成、从不指派任务"(放弃只是终止一个无法推进的会议,把控制权交还团队的自组织循环)。
553
+ >
554
+ > 说明:无法对"某常驻真的拒绝/掉线"的情况达成全体一致时,看门狗会把该对象保留为"未定论/带概率",这是哲学上期望的诚实结果。`session.json` 仍不持久化 `meetingState`(进行中的会议不跨进程恢复),B 的停滞看门狗在重启后仍会触发,因此重启也能自愈。
555
+ >
556
+ > 全套测试仍全绿:`selfdrive-v4` 21/21、`e2e-v4-fixes` 39/39、v3 100/100、v2 business 18/regression 14、multisession 25/25。
557
+
540
558
 
541
559
 
542
560