dsh-vibe-math 1.3.4 → 1.3.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
@@ -53,6 +53,10 @@
53
53
  > 🔧 **v1.3.3 审计修复**:非全票验证把平均概率写回源卡(兑现"留库附概率")、方法型验证标 `类型: 方法`(不再误标命题)、同进程 abort→resume 重建常驻、记录命题也触发自动同步会议、唤醒信号改用 `activityTimeoutMs`、`verdictMaxRounds`/`meetingKeepEvery` 可调且展示。详见 §18。
54
54
  >
55
55
  > 🔧 **v1.3.4 和谐修复**:重排 `框架图-v4.png` 消除文字/箭头重叠遮挡(子标签不再溢出框、`/compact` 移入独立的"上下文"能力块、底部"产物沉淀·断点续跑"居中排布);补齐 `agent.cordis.yml` 常驻描述中的"deadlines"一词(v4 并无截止机制),并移除 `实现方案.md` 中不存在的 `vibe_v4_inject`(实际用 `vibe_v4_message`)。
56
+ >
57
+ > 🔧 **v1.3.5 边界 A 落地 + 真实 `/compact`**:`maxParallel` 真正限流(在途上限),`activityTimeoutMs` 作为**心跳门控**(空闲超时才触发 CHECKPOINT 唤醒,推动收敛/停止而非无限烧 token);并用 DSH 真实 `ctx.compaction.compactIfNeeded(常驻 agent, 'pressure', signal)` 压缩常驻自身上下文(回退到自述指令)。详见 §19。
58
+ >
59
+ > 🔧 **v1.3.6 修复 v4 preset 选择后跳回原 preset**:v4 插件的 `apply` 读取 `ctx.subagents/agents/fs/tools/commands` 却**未声明 `inject`**,被 DSH 守卫以"未声明依赖"拒绝 → 组合无法挂载 → 选择后自动回退。已补 `export const inject = [...]`,并把心跳定时器从全局 `setTimeout/clearTimeout`(插件沙箱里不存在)改为 **`timer` 服务(`ctx.timeout`)**。
56
60
 
57
61
  ---
58
62
 
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 — FOUR agent presets in one install: vibe-math-v1 (classic pipeline), 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 four presets into the DSH preset root.",
4
- "version": "1.3.4",
4
+ "version": "1.3.6",
5
5
  "type": "module",
6
6
  "main": "installer.js",
7
7
  "exports": {
@@ -3,6 +3,10 @@
3
3
  // unanimous-consensus verification / context compaction proxy / resume / human
4
4
  // intervention). It NEVER assigns tasks: residents message & meet and decide all
5
5
  // task allocation among themselves. Consumes HOST subagents/agents/fs/tools/commands.
6
+ // NOTE: must declare `inject` for every service read as a ctx property (the Guard
7
+ // rejects undeclared dependencies), and must use the `timer` Service (ctx.timeout),
8
+ // not global setTimeout/clearTimeout, which do not exist in the plugin runtime.
9
+ export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands', 'timer']
6
10
  export function apply(ctx) {
7
11
  const subagents = ctx.subagents
8
12
  const agents = ctx.agents
@@ -11,6 +15,7 @@ export function apply(ctx) {
11
15
  const commands = ctx.commands
12
16
  const subprocess = ctx.get('subprocess')
13
17
  const sandboxPolicy = ctx.get('sandboxPolicy')
18
+ const compaction = ctx.get('compaction') // @deepseek-ai/dsh-compaction (CompactionEngine); optional
14
19
 
15
20
  const sessions = new Map() // rootAgentId -> Session
16
21
  const childOwner = new Map() // childId -> rootAgentId
@@ -36,7 +41,7 @@ export function apply(ctx) {
36
41
  let problemText = '', problemId = 'problem', runId = 'run-' + shortId()
37
42
  let meetingState = null, verifyState = null, pendingVerify = null
38
43
  let busy = new Set(), wakeKind = new Map(), currentResident = ''
39
- let lastActivityAt = now(), artifactCount = 0, lastSyncMeetingAt = 0, persistedEpoch = ''
44
+ let lastActivityAt = now(), artifactCount = 0, lastSyncMeetingAt = 0, persistedEpoch = '', heartbeatDisposer = null
40
45
  const activityLogCap = 200
41
46
 
42
47
  // ---- utils ----
@@ -156,6 +161,7 @@ export function apply(ctx) {
156
161
  }
157
162
  async function wakeResident(r, promptText, kind){
158
163
  if(!r.childId) return false
164
+ clearHeartbeat()
159
165
  busy.add(r.rId); wakeKind.set(r.rId,kind||'normal'); currentResident=r.rId
160
166
  r.lastActiveAt=now(); r.rounds+=1; r.roundsSinceCompact+=1
161
167
  // context / /compact: if the resident reports high context (or reached the round proxy),
@@ -218,6 +224,7 @@ export function apply(ctx) {
218
224
  // ---- meeting ----
219
225
  async function startMeeting(agenda,type,targetId){
220
226
  if(meetingState) return {ok:false,message:'meeting already in progress'}
227
+ clearHeartbeat()
221
228
  meetingState={id:'mt-'+shortId(),agenda,type:type||'general',targetId:targetId||null,round:0,asked:[],inputs:{},transcript:[]}
222
229
  logActivity('meeting','start: '+agenda); await saveAll(); await scheduleNext(); return {ok:true,id:meetingState.id}
223
230
  }
@@ -252,6 +259,7 @@ export function apply(ctx) {
252
259
 
253
260
  // ---- verification (unanimous) ----
254
261
  async function beginVerify(pv){
262
+ clearHeartbeat()
255
263
  pendingVerify=null
256
264
  verifyState={targetId:pv.targetId,targetType:pv.targetType,stage:'independent',round:0,asked:[],verdicts:{},transcript:[]}
257
265
  logActivity('verify','debate begin: '+pv.targetId+' ('+pv.targetType+')'); await saveAll(); await scheduleNext()
@@ -323,19 +331,68 @@ export function apply(ctx) {
323
331
  }
324
332
  function guessTargetType(id){ if(/^p-/.test(id)) return 'proposition'; if(/^m-/.test(id)) return 'method'; if(/^s-/.test(id)) return 'subproblem'; return 'proposition' }
325
333
 
334
+ // ---- heartbeat / liveness helpers (boundary-A: event-driven + gated heartbeat) ----
335
+ // A checkpoint wake is NOT "keep working forever": it nudges the least-recently-active
336
+ // resident, after an idle timeout, to either make concrete progress or push the group
337
+ // toward a decision (meeting / verify / solved). This adds convergence pressure instead
338
+ // of infinite token-burning, matching the "framework never assigns work" philosophy.
339
+ function heartbeatPrompt(r){
340
+ return (params.residentPersona?params.residentPersona+'\n':'')
341
+ +'You are resident researcher '+r.rId+'. CHECKPOINT (idle): the group is waiting for direction.\n'
342
+ +'State in one line what you will do next. If you have nothing further to add, or you believe the '
343
+ +'problem is solved / close to solved, PROPOSE a meeting (propose_meeting), propose a verification '
344
+ +'(propose_verify), or set solved=true so the group can reach a decision — do NOT produce filler work.\n'
345
+ +'\nReply with ONLY a JSON object (```json fence):\n'
346
+ +'{"summary":"<what you do next or a declaration>","solved":false,"propose_verify":"<id|null>","propose_meeting":"<agenda|null>","claim_task":"<id|null>"}'
347
+ }
348
+ function clearHeartbeat(){ if(heartbeatDisposer!==null){ try{ heartbeatDisposer() }catch(e){} heartbeatDisposer=null } }
349
+ function armHeartbeat(){
350
+ clearHeartbeat()
351
+ const ms=Number(params.activityTimeoutMs)||120000
352
+ if(!(ms>0) || typeof ctx.timeout!=='function') return
353
+ heartbeatDisposer=ctx.timeout(()=>{ heartbeatDisposer=null; scheduleNext().catch(()=>{}) }, ms)
354
+ }
355
+ // Real DSH /compact of a resident's OWN session via ctx.compaction (if the host provides it);
356
+ // falling back silently to the resident self-summary directive when the service is absent.
357
+ async function realCompact(r){
358
+ if(!r || !r.childId) return
359
+ if(compaction===undefined || !compaction.compactIfNeeded) return
360
+ let agent
361
+ try { agent = agents.get(r.childId) } catch(e){ agent = undefined }
362
+ if(!agent || !agent.session) return
363
+ try {
364
+ const signal = makeSignal(params.activityTimeoutMs||60000)
365
+ const result = await compaction.compactIfNeeded(agent, 'pressure', signal)
366
+ if(result && (result.shadowedSeqs||[]).length>0){
367
+ r.roundsSinceCompact=0; r.needCompact=false; r.contextPct=Math.min(r.contextPct||15,25)
368
+ logActivity('compact', r.rId+' real /compact (shadowed '+result.shadowedSeqs.length+' items, ~'+String(result.shadowedTokenCount||0)+' tokens)')
369
+ }
370
+ } catch(e){ /* real compaction unavailable/failed; the soft directive already covers it */ }
371
+ }
372
+
326
373
  // ---- liveness / scheduling ----
327
374
  async function scheduleNext(){
328
- if(!running||autoDone) return
375
+ if(!running||autoDone){ clearHeartbeat(); return }
329
376
  if(phase==='brainstorm'){ await maybeFinishBrainstorm(); return }
330
377
  if(meetingState){ await continueMeetingRound(); return }
331
378
  if(verifyState){ await continueVerifyRound(); return }
332
379
  if(pendingVerify){ const pv=pendingVerify; await beginVerify(pv); return }
333
380
  // mailbox delivery
334
381
  const delivered=await deliverNextMailbox(); if(delivered) return
335
- // fairness / coordination wake of the least-recently-active resident
382
+ // maxParallel: don't start a new wake when the in-flight cap is reached
383
+ const mp=Number(params.maxParallel)||0
384
+ if(mp>0 && busy.size>=mp){ armHeartbeat(); return }
385
+ // heartbeat / coordination wake of the least-recently-active resident, ONLY after idle timeout
386
+ clearHeartbeat()
336
387
  let target=null, oldest=-1
337
388
  for(const [,r] of residents){ if(busy.has(r.rId)) continue; const idle=now()-r.lastActiveAt; if(idle>oldest){ oldest=idle; target=r } }
338
- if(target){ await wakeResident(target, await normalPrompt(target), 'normal'); await saveAll(); return }
389
+ const atOs=Number(params.activityTimeoutMs)||120000
390
+ if(target && oldest>=atOs){
391
+ // an idle timeout has elapsed: checkpoint wake (event-driven work is unchanged)
392
+ await wakeResident(target, await heartbeatPrompt(target), 'normal'); await saveAll(); return
393
+ }
394
+ // everyone is busy or not idle-enough: arm a heartbeat to re-check later (no infinite spin)
395
+ armHeartbeat()
339
396
  }
340
397
  async function maybeFinishBrainstorm(){
341
398
  const pending=[]; for(const [,r] of residents){ if(r.status==='brainstorm' && !r.insight) pending.push(r.rId) }
@@ -359,6 +416,7 @@ export function apply(ctx) {
359
416
  async function onResidentEnd(childId, info){
360
417
  const r=byChild(childId); if(!r) return
361
418
  busy.delete(r.rId)
419
+ realCompact(r).catch(()=>{}) // best-effort real DSH /compact of this resident while idle
362
420
  const output=blocksToText(info&&info.lastAssistantMessage)
363
421
  const parsed=parseReply(output)
364
422
  const kind=wakeKind.get(r.rId)||'normal'
@@ -398,7 +456,7 @@ export function apply(ctx) {
398
456
  if(residentCount) params.residentCount=Number(residentCount)||4
399
457
  running=true; autoDone=false; phase='brainstorm'
400
458
  await writeText('Problems/'+problemId+'.md','# 问题|'+problemId+'\n- ID: '+problemId+'\n- 类型: 问题\n- 状态: 求解中\n- 优先级: 1\n- 依赖: []\n\n## 陈述\n'+problemText+'\n')
401
- residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=null; residentSeq=0; artifactCount=0
459
+ residents=new Map(); mailboxes=new Map(); taskboard=[]; decisions=[]; meetings=[]; reports=[]; verifyState=null; meetingState=null; pendingVerify=null; residentSeq=0; artifactCount=0; clearHeartbeat()
402
460
  const dirs=Array.isArray(seedDirections)?seedDirections.slice(0,params.residentCount):[]
403
461
  for(let i=0;i<params.residentCount;i++){ const r=newResident(dirs[i]||''); await spawnResident(r) }
404
462
  await saveAll(); return {ok:true,message:'v4 started: '+params.residentCount+' resident(s) brainstorming',project:currentProject}
@@ -426,8 +484,8 @@ export function apply(ctx) {
426
484
  async function addMember(direction){ const r=newResident(direction||''); await spawnResident(r); return {ok:true,id:r.rId,direction:r.direction} }
427
485
  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); await saveAll(); return {ok:true} }
428
486
  function setParams(upd){ for(const k of Object.keys(upd||{})){ if(k in params) params[k]=upd[k] } return {ok:true} }
429
- async function initAbort(){ running=false; phase='idle'; autoDone=false; for(const [,r] of residents){ if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } r.childId=''; r.lastActiveAt=0; r.roundsSinceCompact=0 } await saveAll(); return {ok:true,message:'aborted'} }
430
- function setPause(){ running=false; return {ok:true,message:'paused'} }
487
+ async function initAbort(){ clearHeartbeat(); running=false; phase='idle'; autoDone=false; for(const [,r] of residents){ if(r.childId){ try{ subagents.interrupt(r.childId,{kind:'ancestor',agent:rootAgent}) }catch(e){} } r.childId=''; r.lastActiveAt=0; r.roundsSinceCompact=0 } await saveAll(); return {ok:true,message:'aborted'} }
488
+ function setPause(){ clearHeartbeat(); running=false; return {ok:true,message:'paused'} }
431
489
 
432
490
  return {
433
491
  sessionId, running:()=>running, autoDone:()=>autoDone, phase:()=>phase,
@@ -375,3 +375,26 @@ VibeMath/Projects/<project>/
375
375
 
376
376
  > **已知边界(未改)**:`maxParallel`/`activityTimeoutMs` 仍未实际限流/心跳门控(避免破坏"持续推进→收敛");`claim_write`/`release_write` 仍未落地为真锁;真实 DSH `/compact` API 为 TODO。
377
377
 
378
+ ---
379
+
380
+ ## 19. 边界 A 落地 + 真实 `/compact`(v1.3.5)
381
+
382
+ ### 边界 A:事件驱动 + `activityTimeoutMs` 心跳门控 + `maxParallel` 限流
383
+ 此前 `scheduleNext()` **无条件**唤醒"最久没动的人",导致"只要没达到全体一致 solved 就永远烧 token 推进"。现改为贴合哲学"框架绝不指派/促成"的做法:
384
+
385
+ - **事件驱动为主**:有信(邮箱)、任务板 open、验证提案、会议请求 → 才唤醒对应常驻(不变)。
386
+ - **心跳门控**:`scheduleNext` 只在"某常驻空闲时间 ≥ `activityTimeoutMs`"时,才唤醒"最久没动的人",并给它 **CHECKPOINT 提示词**:*"说明你下一步做什么;若已无产出/认为接近解决 → 提议开会/验证 或 声明 solved。"* 这样既防僵死,又施加**收敛/停止**压力,而非无限推进。
387
+ - **`maxParallel` 限流**:`scheduleNext` 唤醒前检查 `busy.size < maxParallel`,超限则改为 `armHeartbeat()` 等待(brainstorm 阶段的"各自独立想"仍一次全开,常态/会议/验证轮受控)。
388
+ - 新增 `heartbeatTimer` 心跳定时器:`scheduleNext` 无事可做且无人空闲到阈值时,用 `activityTimeoutMs` 定时重查;每次唤醒/开会/验证/start/pause/abort 都会 `clearHeartbeat()` 避免僵尸定时器。
389
+
390
+ > 行为变化:真实运行里若常驻都在推进(每轮 < `activityTimeoutMs`),心跳基本不触发;只有真正"无事可做"才触发一次 CHECKPOINT,推动收敛。测试中把 `activityTimeoutMs` 设小(如 40ms)以驱动自组织收敛。
391
+
392
+ ### 真实 DSH `/compact`
393
+ 找到了 DSH 真实压缩服务:**`ctx.compaction`**(`@deepseek-ai/dsh-compaction` 的 `CompactionEngine`,`agent.cordis.yml` 已加载 compaction-basic)。v4 现在对每个常驻做**真实压缩**:
394
+
395
+ - 在 `onResidentEnd`(常驻空闲)里调用 `ctx.agents.get(r.childId)` 取该常驻的 Agent,然后 `ctx.compaction.compactIfNeeded(agent, 'pressure', signal)`。
396
+ - `compactIfNeeded` 用 `ctx.tokenMeter` **真实测量**该常驻会话 token 量,超过其模型上下文窗口阈值时把旧历史折叠成总结节点——真正的 `/compact` 效果(而非仅"注入浓缩指令+记账复位")。
397
+ - 成功后复位 `roundsSinceCompact/needCompact` 并记 `logActivity`。**若宿主未提供 `ctx.compaction`(或模型未配 `contextWindow`)则静默回退**到现有"自述指令"等效层。
398
+
399
+ > 局限:v4 的 mock 测试里 `compaction` 为 undefined,故这条**只在真实 DSH 上生效**;需在真实运行体确认 `ctx.compaction` 可用、且会话为常驻自身 session、模型已配 `contextWindow`。部署前请在当前运行 DSH 上验证。`claim_write`/`release_write` 仍保留为占位(常驻专属目录无写冲突)。
400
+