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
|
+
"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
|
-
//
|
|
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
|
-
|
|
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
|
+
|