dsh-vibe-math 0.3.2 → 0.3.4

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
@@ -96,7 +96,7 @@ dsh plugin --profile <你的 profile> add github:ChongCyrus/Vibe-Mathematics
96
96
 
97
97
  > 静态 PNG 预览 + 可编辑 Mermaid 图源(GitHub 原生渲染);完整流程说明见 [docs/架构图.md](docs/架构图.md)。
98
98
 
99
- ![Vibe Math 架构图](示例图/框架图.png)
99
+ ![Vibe Math V1 架构图](示例图/框架图-v1.png)
100
100
 
101
101
  ```mermaid
102
102
  flowchart TB
@@ -153,6 +153,10 @@ flowchart TB
153
153
 
154
154
  ## 🧩 架构图 · v2(新架构 · 概率驱动)
155
155
 
156
+ > 静态 PNG 预览 + 可编辑 Mermaid 图源(GitHub 原生渲染);生成脚本见 [docs/generate_framework_diagram_v2.py](docs/generate_framework_diagram_v2.py)。
157
+
158
+ ![Vibe Math V2 架构图](示例图/框架图-v2.png)
159
+
156
160
  ```mermaid
157
161
  flowchart TB
158
162
  subgraph L1["👤 交互层"]
@@ -399,7 +403,9 @@ flowchart TB
399
403
  | `solverToolAllow` / `solverToolDeny` | `[]` | 求解器允许/禁止的工具(硬性 toolFilter) |
400
404
  | `verifierToolAllow` / `verifierToolDeny` | `[]` | 验证器允许/禁止的工具 |
401
405
  | `solverMaxToolCalls` / `verifierMaxToolCalls` | 0 | 每轮外部工具调用上限(0=不限,软性) |
402
- | `reportIntervalMs` | 30000 | 自动写进度报告的最小间隔 |
406
+ | `reportIntervalMs` | 0 | 0 = 仅事件驱动(有代理状态更新等事件才写报告);>0 = 定时自动写(毫秒) |
407
+ | `tickIntervalMs` | 2000 | 调度器心跳间隔(毫秒) |
408
+ | `activityLogCap` | 100 | 活动日志保留条数(report 最多显示 30 条) |
403
409
 
404
410
  ### v2(新架构)默认值
405
411
 
@@ -414,12 +420,16 @@ flowchart TB
414
420
  | `reportMode` | `file` | `file` = 写报告文件 / `push` = 推送主代理汇报 / `both` |
415
421
  | `promoteValueThreshold` | 0.7 | Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json |
416
422
  | `priorityAdjust` | `none` | `none` / `deadend-deprioritize`(全死路降优先级)/ `survival-map`(按存活率重算) |
423
+ | `proposPriorityAdjust` | `none` | 命题优先级动态调整:`none` / `progress-graded`(按定论接近度+证明/证伪材料量重算,越接近定论越优先验证) |
417
424
  | `provider` / `model` | 空 | 子代理模型(空 = 继承根代理) |
418
425
  | `solverPersona` / `verifierPersona` | 空 | 注入求解器/验证器的额外要求 |
419
426
  | `solverToolAllow` / `solverToolDeny` | `[]` | 求解器允许/禁止的工具 |
420
427
  | `verifierToolAllow` / `verifierToolDeny` | `[]` | 验证器允许/禁止的工具 |
421
428
  | `solverMaxToolCalls` / `verifierMaxToolCalls` | 0 | 每轮外部工具调用上限(0=不限) |
422
- | `reportIntervalMs` | 30000 | 进度汇报最小间隔(毫秒) |
429
+ | `reportIntervalMs` | 0 | 0 = 仅事件驱动(有状态更新才写/推);>0 = 定时自动汇报(毫秒) |
430
+ | `tickIntervalMs` | 2000 | 调度器心跳间隔(毫秒) |
431
+ | `activityLogCap` | 100 | 活动日志保留条数(report 最多显示 30 条) |
432
+ | `maxExplorerRetries` | 3 | explorer 拆方向失败的重派生上限 |
423
433
 
424
434
  ---
425
435
 
@@ -427,7 +437,7 @@ flowchart TB
427
437
 
428
438
  - **断点续跑**:所有状态落盘到 `VibeMath_State/*.json`,每个子代理都是 DSH 的 **continuable 持久会话**(对话由 DSH 自动保存)。重启后新开会话 → `vibe_math_resume` 即可续跑。v2 额外用**进程纪元**区分"同进程暂停→恢复"(保留存活子代理继续)与"跨进程重启"(清理陈旧任务)。
429
439
  - **中途人工干预**:`manual` 模式在关键节点(v1:brainstorm/solver 派发、验证裁决、晋升 Verified;v2:explorer/solver 派发、验证裁决)挂起决策;可随时 `set_mode auto` 切回自动;可对任意子代理 `message_agent` / `interrupt_agent`。
430
- - **进度汇报**:定时 / 有变动时写 `Progress_Logs/report.json`;v2 的 `reportMode` 还能 `push` —— 调度器通过 `rootAgent.followup()` 唤醒主代理主动汇报。
440
+ - **进度汇报**:默认**事件驱动** —— 只有代理状态更新等事件发生时才会写 `Progress_Logs/report.json`(v2 的 `reportMode` `file`/`push`/`both`,`push` 通过 `rootAgent.followup()` 唤醒主代理主动汇报);只有把 `reportIntervalMs` 设为 >0 才启动定时自动汇报(间隔毫秒)。
431
441
 
432
442
  ---
433
443
 
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 — TWO agent presets in one install: vibe-math-v1 (classic pipeline: brainstorm → solver iteration → multi-verifier debate → Verified) and vibe-math-v2 (new probability-driven architecture: qs.json + Propos knowledge base + explorer→solver→review/debate verdict). Installing this bundle auto-installs both presets into the DSH preset root.",
4
- "version": "0.3.2",
4
+ "version": "0.3.4",
5
5
  "type": "module",
6
6
  "main": "installer.js",
7
7
  "exports": {
@@ -1,2 +1,2 @@
1
- name: Vibe Math
1
+ name: Vibe Math V1
2
2
  description: 多代理数学问题求解与验证框架(广度探索 → 深度迭代 → 交叉验证 → 知识沉淀)。具备标准模式的全部能力,并提供 vibe_math_* 工具集(20 个)与 /vibe 斜杠命令,用于数学问题的自动求解、多代理交叉验证、按项目隔离、断点续跑与人工/自动干预。
@@ -69,7 +69,9 @@ export function apply(ctx) {
69
69
  verifierToolDeny: [],
70
70
  solverMaxToolCalls: 0,
71
71
  verifierMaxToolCalls: 0,
72
- reportIntervalMs: 30000,
72
+ reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写报告);>0 = 定时自动写(毫秒)
73
+ tickIntervalMs: 2000, // 调度器心跳间隔(毫秒):多久扫描一次状态并推进
74
+ activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
73
75
  }
74
76
  let params = Object.assign({}, DEFAULT_PARAMS)
75
77
  let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
@@ -152,7 +154,9 @@ export function apply(ctx) {
152
154
  { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
153
155
  { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
154
156
  { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
155
- { name: 'reportIntervalMs', type: 'integer', description: '自动写进度报告 report.json 的最小间隔(毫秒)', suggestion: 30000 },
157
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写 report.json);>0 = 定时自动汇报', suggestion: 0 },
158
+ { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
159
+ { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
156
160
  ]
157
161
 
158
162
  // ================= fs =================
@@ -178,7 +182,7 @@ export function apply(ctx) {
178
182
  // ================= settings file (JSONC) =================
179
183
  function sanitizeParams(obj) {
180
184
  const out = {}
181
- const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
185
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap']
182
186
  const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
183
187
  for (const k of Object.keys(DEFAULT_PARAMS)) {
184
188
  if (!(k in obj)) continue
@@ -231,7 +235,7 @@ export function apply(ctx) {
231
235
  async function saveAll() { await writeJson('VibeMath_State/params.json', params); await writeJson('VibeMath_State/scheduler_state.json', scheduler); await writeJson('VibeMath_State/agent_registry.json', agentRegistry); await writeJson('VibeMath_State/dependencies.json', dependencies); await writeJson('VibeMath_State/decision_queue.json', decisionQueue); await writeJson('VibeMath_State/tasks.json', tasks); await writeJson('VibeMath_State/solved_by_verified.json', solvedByVerified); await writeJson('VibeMath_State/promotion_queue.json', promotionQueue); await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy); scheduler.lastCheckpoint = now() }
232
236
 
233
237
  // ================= activity log / report =================
234
- function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length > 100) activityLog.shift(); reportDirty = true }
238
+ function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); const cap = Number(params.activityLogCap) || 100; if (activityLog.length > cap) activityLog.shift(); reportDirty = true }
235
239
  function buildReport() {
236
240
  const openTasks = Object.keys(tasks).filter(function (k) { const t = tasks[k]; return t.status === 'spawning' || t.status === 'debating' || t.status === 'awaiting-verdict' })
237
241
  return {
@@ -246,13 +250,14 @@ export function apply(ctx) {
246
250
  pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
247
251
  openTasks: openTasks.length,
248
252
  registeredAgents: Object.keys(agentRegistry).length,
249
- recentActivity: activityLog.slice(-30),
253
+ recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
250
254
  params: params,
251
255
  }
252
256
  }
253
257
  async function maybeWriteReport(force) {
254
- if (!force && !reportDirty) return
255
- if (!force && (now() - lastReportWrite) < (Number(params.reportIntervalMs) || 30000)) return
258
+ const interval = Number(params.reportIntervalMs) || 0
259
+ if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
260
+ if (!force && interval <= 0 && !reportDirty) return
256
261
  await writeJson('Progress_Logs/report.json', buildReport())
257
262
  lastReportWrite = now()
258
263
  reportDirty = false
@@ -307,6 +312,19 @@ export function apply(ctx) {
307
312
  async function resumeScheduler(agent) { const r = await init(agent); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
308
313
  async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
309
314
  async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); scheduler.activeCount = 0; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
315
+ // auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
316
+ async function autoResolvePending() {
317
+ const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
318
+ for (let i = 0; i < pending.length; i++) {
319
+ const d = pending[i]
320
+ try {
321
+ if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
322
+ else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
323
+ else if (d.node === 'promote') { await promoteUnit(d.data.objId); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
324
+ } catch (e) { console.error('vibe-math: auto-resolve decision failed: ' + String((e && e.message) || e)) }
325
+ }
326
+ if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
327
+ }
310
328
  async function getStatus() { return { ok: true, initialized: rootAgent !== undefined, running: scheduler.running, project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'), mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold, frameworkRoot: frameworkRoot(), pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length, openTasks: Object.keys(tasks).filter(function (k) { return tasks[k].status === 'spawning' || tasks[k].status === 'debating' || tasks[k].status === 'awaiting-verdict' }).length, registeredAgents: Object.keys(agentRegistry).length, recentActivity: activityLog.slice(-10), params: params } }
311
329
 
312
330
  // ================= projects =================
@@ -339,7 +357,7 @@ export function apply(ctx) {
339
357
  async function onChildEnd(info) { const meta = agentRegistry[info.id]; if (meta === undefined) return; scheduler.activeCount = Math.max(0, scheduler.activeCount - 1); const output = blocksToText(info.lastAssistantMessage); try { if (meta.role === 'brainstorm') await handleBrainstorm(info.id, meta, output); else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason); else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason); else if (meta.role === 'decider') await handleDecider(info.id, meta, output); else if (meta.role === 'derive') await handleDerive(info.id, meta, output) } catch (e) { console.error('vibe-math onChildEnd error: ' + String((e && e.stack) || e)) } await saveAll(); scheduleTick() }
340
358
  async function handleBrainstorm(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('brainstorm', 'problem ' + meta.qid + ' brainstorm returned no directions'); await saveAll(); return } brainstormRetries[meta.qid] = 0; const prog = []; for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || '', round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('brainstorm', 'problem ' + meta.qid + ' → ' + prog.length + ' directions'); await writeProgress(meta.qid, prog) }
341
359
  async function handleDerive(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('derive', 'problem ' + meta.qid + ' derived no new directions'); await saveAll(); return } const prog = await readProgress(meta.qid); for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || (d.motivation || ''), round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('derive', 'problem ' + meta.qid + ' derived ' + dirs.length + ' new directions'); await writeProgress(meta.qid, prog) }
342
- async function handleSolver(childId, meta, output, stopReason) { const qid = meta.qid; const direction = meta.direction; const parsed = parseJson(output); const prog = await readProgress(qid); const dir = prog.find(function (d) { return d.direction_id === direction }); if (!dir) { delete agentRegistry[childId]; return } const status = (parsed && parsed.status) || statusFromStop(stopReason); dir.round = meta.round; dir.status = status; if (parsed) { if (parsed.lemmas) dir.lemmas = parsed.lemmas; if (parsed.sub_routes) dir.sub_routes = parsed.sub_routes; if (parsed.aux_hypotheses) dir.aux_hypotheses = parsed.aux_hypotheses; if (typeof parsed.survival_probability === 'number') dir.survival_probability = parsed.survival_probability; if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason } if (status === 'success') { dir.status = 'success'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' success at round ' + meta.round); await writePending(qid, dir, parsed) } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) { dir.status = 'dead-end'; if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' dead-end: ' + dir.dead_end_reason) } else { const q = { id: qid, description: meta.description || '' }; await followupChild(childId, solverPrompt(q, dir, meta.round + 1)); agentRegistry[childId].round = meta.round + 1; dir.round = meta.round + 1 } if (parsed && parsed.aux_hypotheses && parsed.aux_hypotheses.length > 0) await handleAuxHypotheses(qid, parsed.aux_hypotheses); dir.updated_at = String(now()); await writeProgress(qid, prog) }
360
+ async function handleSolver(childId, meta, output, stopReason) { const qid = meta.qid; const direction = meta.direction; const parsed = parseJson(output); const prog = await readProgress(qid); const dir = prog.find(function (d) { return d.direction_id === direction }); if (!dir) { delete agentRegistry[childId]; return } if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } const status = (parsed && parsed.status) || statusFromStop(stopReason); dir.round = meta.round; dir.status = status; if (parsed) { if (parsed.lemmas) dir.lemmas = parsed.lemmas; if (parsed.sub_routes) dir.sub_routes = parsed.sub_routes; if (parsed.aux_hypotheses) dir.aux_hypotheses = parsed.aux_hypotheses; if (typeof parsed.survival_probability === 'number') dir.survival_probability = parsed.survival_probability; if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason } if (status === 'success') { dir.status = 'success'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' success at round ' + meta.round); await writePending(qid, dir, parsed) } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) { dir.status = 'dead-end'; if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' dead-end: ' + dir.dead_end_reason) } else { const q = { id: qid, description: meta.description || '' }; await followupChild(childId, solverPrompt(q, dir, meta.round + 1)); agentRegistry[childId].round = meta.round + 1; dir.round = meta.round + 1 } if (parsed && parsed.aux_hypotheses && parsed.aux_hypotheses.length > 0) await handleAuxHypotheses(qid, parsed.aux_hypotheses); dir.updated_at = String(now()); await writeProgress(qid, prog) }
343
361
  async function handleAuxHypotheses(qid, hyps) { const qs = await getQs(); for (let i = 0; i < hyps.length; i++) { const h = hyps[i]; if (!h || !h.title) continue; const subId = qid + '_sub_' + shortId(); qs.push({ id: subId, description: h.title + (h.statement ? ' - ' + h.statement : ''), priority: 1, status: 'unsolved', deps: [] }); dependencies[qid] = dependencies[qid] || []; dependencies[qid].push(subId); await writeQs(qs); logActivity('subquestion', qid + ' spawned sub-question ' + subId + ' (Aux_Hypothesis)') } }
344
362
  async function writePending(qid, dir, parsed) { const doc = { qid: qid, direction: dir.direction_id, solution: solutionText(parsed, dir), lemmas: (parsed && parsed.lemmas) || [], findings: (parsed && parsed.findings) || [], sub_routes: (parsed && parsed.sub_routes) || [], aux_hypotheses: (parsed && parsed.aux_hypotheses) || [], survival_probability: dir.survival_probability, created_at: String(now()) }; const id = uuid(); await writeText('Pending_Verification/' + id + '.csv', 'qid,direction,content_json,created_at\n' + csvRow([qid, dir.direction_id, JSON.stringify(doc), String(now())]) + '\n') }
345
363
 
@@ -400,6 +418,14 @@ export function apply(ctx) {
400
418
  const verdict = (parsed && parsed.verdict) || 'uncertain'
401
419
  let t = tasks['verify:' + unitId]
402
420
  if (!t) { t = { id: 'verify:' + unitId, type: 'verify', unitId: unitId, unit: meta.unit || { obj_id: unitId, title: unitId, content: '' }, status: 'debating', children: [], childResults: {}, round: 1, expectedCount: Math.max(3, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
421
+ if (!parsed && !scheduler.running) {
422
+ // abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerification 重建)
423
+ delete agentRegistry[childId]
424
+ const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
425
+ delete t.childResults[childId]
426
+ if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
427
+ return
428
+ }
403
429
  if (t.children.indexOf(childId) === -1) t.children.push(childId)
404
430
  t.childResults[childId] = { verdict: verdict, reason: (parsed && parsed.reason) || '', strictness: (parsed && parsed.strictness) || 'lenient', round: meta.round }
405
431
  delete agentRegistry[childId]
@@ -458,7 +484,7 @@ export function apply(ctx) {
458
484
 
459
485
  // ================= events / timer =================
460
486
  ctx.on('subagent/end', function (info) { onChildEnd(info).catch(function (e) { console.error('vibe-math onChildEnd reject: ' + String((e && e.stack) || e)) }) })
461
- ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, 2000); return () => clearInterval(t) })
487
+ ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
462
488
 
463
489
  // ================= tools =================
464
490
  function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
@@ -476,8 +502,8 @@ export function apply(ctx) {
476
502
  registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
477
503
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
478
504
  registerTool('vibe_math_report', 'Return the full progress report (status + recent activity + params) and write it to Progress_Logs/report.json.', objParams({}), async function () { await maybeWriteReport(true); return buildReport() })
479
- registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); return { ok: true, mode: params.mode } })
480
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, args); await saveAll(); return { ok: true, params: params } })
505
+ registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
506
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, args); await saveAll(); return { ok: true, params: params } })
481
507
  registerTool('vibe_math_setup', 'Return the interactive parameter schema (each param: name, type, current, default, description, options, suggestion) for guided configuration.', objParams({}), async function () { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
482
508
  registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
483
509
  registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
@@ -499,7 +525,7 @@ export function apply(ctx) {
499
525
  if (cmd === 'abort') return await abortScheduler()
500
526
  if (cmd === 'status') return await getStatus()
501
527
  if (cmd === 'report') { await maybeWriteReport(true); return buildReport() }
502
- if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); return { ok: true, mode: params.mode } }
528
+ if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
503
529
  if (cmd === 'setup') { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } }
504
530
  if (cmd === 'save') return await saveSettings()
505
531
  if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
@@ -47,10 +47,14 @@ export function apply(ctx) {
47
47
  verifierToolDeny: [],
48
48
  solverMaxToolCalls: 0,
49
49
  verifierMaxToolCalls: 0,
50
- reportIntervalMs: 30000,
50
+ reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 定时自动汇报(毫秒)
51
51
  reportMode: 'file', // file | push | both
52
52
  promoteValueThreshold: 0.7, // Propos → qs auto-promotion threshold (价值/关键性)
53
53
  priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
54
+ proposPriorityAdjust: 'none', // none | progress-graded(按定论接近度+证明/证伪材料量动态调命题优先级)
55
+ tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
56
+ activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
57
+ maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
54
58
  }
55
59
  let params = Object.assign({}, DEFAULT_PARAMS)
56
60
  let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
@@ -128,10 +132,14 @@ export function apply(ctx) {
128
132
  { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
129
133
  { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
130
134
  { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
131
- { name: 'reportIntervalMs', type: 'integer', description: '进度汇报最小间隔(毫秒)', suggestion: 30000 },
135
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
132
136
  { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
133
137
  { name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
134
138
  { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
139
+ { name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
140
+ { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
141
+ { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
142
+ { name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
135
143
  ]
136
144
 
137
145
  // ================= fs =================
@@ -156,7 +164,7 @@ export function apply(ctx) {
156
164
  // ================= settings =================
157
165
  function sanitizeParams(obj) {
158
166
  const out = {}
159
- const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
167
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
160
168
  const numFields = ['promoteValueThreshold']
161
169
  const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
162
170
  for (const k of Object.keys(DEFAULT_PARAMS)) {
@@ -169,6 +177,7 @@ export function apply(ctx) {
169
177
  else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
170
178
  else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
171
179
  else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
180
+ else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
172
181
  else { out[k] = v }
173
182
  }
174
183
  return out
@@ -275,7 +284,7 @@ export function apply(ctx) {
275
284
  async function reliableFiles() { return await listFiles('Reliable') }
276
285
 
277
286
  // ================= reporting =================
278
- function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length > 100) activityLog.shift(); reportDirty = true }
287
+ function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); const cap = Number(params.activityLogCap) || 100; if (activityLog.length > cap) activityLog.shift(); reportDirty = true }
279
288
  async function buildReport() {
280
289
  const qs = await getQs()
281
290
  const propos = await getPropos()
@@ -287,21 +296,28 @@ export function apply(ctx) {
287
296
  propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
288
297
  pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
289
298
  registeredAgents: Object.keys(agentRegistry).length,
290
- recentActivity: activityLog.slice(-30),
299
+ recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
291
300
  params: params,
292
301
  }
293
302
  }
294
303
  async function maybeWriteReport(force) {
295
- if (!force && !reportDirty) return
296
- if (!force && (now() - lastReportWrite) < (Number(params.reportIntervalMs) || 30000)) return
304
+ const interval = Number(params.reportIntervalMs) || 0
305
+ if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
306
+ if (!force && interval <= 0 && !reportDirty) return
297
307
  await writeJson('Progress_Logs/report.json', await buildReport())
298
308
  lastReportWrite = now(); reportDirty = false
299
309
  }
300
310
  async function maybePushReport(force) {
301
311
  const mode = params.reportMode || 'file'
302
312
  if (mode !== 'push' && mode !== 'both') return
303
- // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
304
- if (!force && (now() - lastPushReport) < (Number(params.reportIntervalMs) || 30000)) return
313
+ const interval = Number(params.reportIntervalMs) || 0
314
+ if (interval > 0) {
315
+ // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
316
+ if (!force && (now() - lastPushReport) < interval) return
317
+ } else if (!force && !reportDirty) {
318
+ // event-driven: only push when an event happened since the last report
319
+ return
320
+ }
305
321
  if (!rootAgent || typeof rootAgent.followup !== 'function') return
306
322
  try {
307
323
  const report = await buildReport()
@@ -310,7 +326,7 @@ export function apply(ctx) {
310
326
  '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
311
327
  '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
312
328
  rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
313
- lastPushReport = now(); reportDirty = false
329
+ lastPushReport = now()
314
330
  } catch (e) {
315
331
  console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
316
332
  }
@@ -455,8 +471,8 @@ export function apply(ctx) {
455
471
  await processVerify()
456
472
  await reconcileVerify()
457
473
  await processSolve()
458
- await maybeWriteReport(false)
459
474
  await maybePushReport(false)
475
+ await maybeWriteReport(false)
460
476
  const qs = await getQs()
461
477
  const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
462
478
  if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
@@ -469,7 +485,7 @@ export function apply(ctx) {
469
485
  const prog = parseProgress(unsolved[i])
470
486
  const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
471
487
  const hasActive = prog.directions.some(function (d) { return d.status === 'active' })
472
- const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= 3
488
+ const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= (Number(params.maxExplorerRetries) || 3)
473
489
  if (hasActive || !blocked) { allBlocked = false; break }
474
490
  }
475
491
  if (allBlocked) {
@@ -482,14 +498,15 @@ export function apply(ctx) {
482
498
  }
483
499
  // note 4: probability-1 rules
484
500
  async function processStatusUpdates() {
485
- let changed = false
486
501
  const qs = await getQs()
502
+ let qsChanged = false
487
503
  for (let i = 0; i < qs.length; i++) {
488
504
  const q = qs[i]
489
- if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) changed = true; q.已解决 = true; q.优先级 = 'never' }
505
+ if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) qsChanged = true; q.已解决 = true; q.优先级 = 'never' }
490
506
  }
491
- if (changed) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
507
+ if (qsChanged) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
492
508
  const propos = await getPropos()
509
+ let closedPromoted = false
493
510
  for (let i = 0; i < propos.length; i++) {
494
511
  const p = propos[i]
495
512
  let pChanged = false
@@ -498,32 +515,61 @@ export function apply(ctx) {
498
515
  if (proofOne && p.布尔估计 !== 1) { p.布尔估计 = 1; pChanged = true }
499
516
  else if (refuteOne && p.布尔估计 !== 0) { p.布尔估计 = 0; pChanged = true }
500
517
  if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
501
- if (p.布尔估计 === 1 || p.布尔估计 === 0) { if (await writeVerifiedCardIfNeeded(p)) pChanged = true }
518
+ if (p.布尔估计 === 1 || p.布尔估计 === 0) {
519
+ if (await writeVerifiedCardIfNeeded(p)) pChanged = true
520
+ // 源命题已定论 → 关闭其晋升出的"僵尸"问题(避免永远未解决)
521
+ const srcMarker = '由命题 ' + p.id + '('
522
+ for (let j = 0; j < qs.length; j++) {
523
+ const qj = qs[j]
524
+ if (!qj.已解决 && String(qj.progress || '').indexOf(srcMarker) !== -1) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
525
+ }
526
+ }
502
527
  if (pChanged) await upsertProposition(p)
503
528
  }
529
+ if (closedPromoted) { await writeQs(qs); logActivity('update', 'promoted problems closed because their source proposition resolved') }
504
530
  }
505
531
  async function processPriorityAdjust() {
506
532
  const mode = params.priorityAdjust || 'none'
507
- if (mode === 'none') return
508
- const qs = await getQs()
509
- let changed = false
510
- for (let i = 0; i < qs.length; i++) {
511
- const q = qs[i]
512
- if (q.已解决 || q.优先级 === 'never') continue
513
- const prog = parseProgress(q)
514
- if (mode === 'deadend-deprioritize') {
515
- if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
516
- const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
517
- }
518
- } else if (mode === 'survival-map') {
519
- if (prog.directions.length > 0) {
520
- const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
521
- const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
522
- if (q.优先级 !== target) { q.优先级 = target; changed = true }
533
+ if (mode !== 'none') {
534
+ const qs = await getQs()
535
+ let changed = false
536
+ for (let i = 0; i < qs.length; i++) {
537
+ const q = qs[i]
538
+ if (q.已解决 || q.优先级 === 'never') continue
539
+ const prog = parseProgress(q)
540
+ if (mode === 'deadend-deprioritize') {
541
+ if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
542
+ const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
543
+ }
544
+ } else if (mode === 'survival-map') {
545
+ if (prog.directions.length > 0) {
546
+ const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
547
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
548
+ if (q.优先级 !== target) { q.优先级 = target; changed = true }
549
+ }
523
550
  }
524
551
  }
552
+ if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
553
+ }
554
+ const pMode = params.proposPriorityAdjust || 'none'
555
+ if (pMode === 'progress-graded') {
556
+ const propos = await getPropos()
557
+ const changedProps = []
558
+ for (let i = 0; i < propos.length; i++) {
559
+ const p = propos[i]
560
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
561
+ const closeness = Math.abs(Number(p.布尔估计) - 0.5)
562
+ const material = Math.min(5, (p.证明列表 || []).length + (p.证伪列表 || []).length)
563
+ const score = closeness * 1.2 + material * 0.08
564
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * score)))
565
+ const cur = Number(p.优先级)
566
+ if (Number.isFinite(cur) && cur !== target) { p.优先级 = target; changedProps.push(p) }
567
+ }
568
+ if (changedProps.length > 0) {
569
+ for (let i = 0; i < changedProps.length; i++) await upsertProposition(changedProps[i])
570
+ logActivity('priority', 'proposition priorities auto-adjusted (progress-graded)')
571
+ }
525
572
  }
526
- if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
527
573
  }
528
574
  // note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
529
575
  async function processPromote() {
@@ -570,7 +616,7 @@ export function apply(ctx) {
570
616
  for (let j = 0; j < sols.length; j++) {
571
617
  const s = sols[j]
572
618
  if (s.正确概率 === 1 || s.正确概率 === 0 || s.已验) continue
573
- out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
619
+ out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
574
620
  }
575
621
  }
576
622
  const propos = await getPropos()
@@ -579,13 +625,13 @@ export function apply(ctx) {
579
625
  if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
580
626
  const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
581
627
  if (proofs.length === 0 && refutes.length === 0) {
582
- out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
628
+ out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
583
629
  } else {
584
- for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
585
- for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
630
+ for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
631
+ for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
586
632
  }
587
633
  }
588
- out.sort(function (a, b) { return a.priority - b.priority })
634
+ out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
589
635
  return out
590
636
  }
591
637
  async function backfillVerifiers(t) {
@@ -623,10 +669,11 @@ export function apply(ctx) {
623
669
  const prog = parseProgress(q)
624
670
  const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
625
671
  if (prog.directions.length === 0 || allExhausted) {
626
- if ((explorerRetries[q.id] || 0) >= 3) {
627
- if (prog.directions.length === 0) prog.directions.push({ id: 'd_' + shortId(), title: 'explorer 失败', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], blockers: [], dead_end_reason: 'explorer 连续 3 次未产出方向' })
672
+ const explorerCap = Number(params.maxExplorerRetries) || 3
673
+ if ((explorerRetries[q.id] || 0) >= explorerCap) {
674
+ if (prog.directions.length === 0) prog.directions.push({ id: 'd_' + shortId(), title: 'explorer 失败', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], blockers: [], dead_end_reason: 'explorer 连续 ' + explorerCap + ' 次未产出方向' })
628
675
  await saveProgress(q.id, prog)
629
- logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (3 failed attempts)')
676
+ logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
630
677
  continue
631
678
  }
632
679
  explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
@@ -672,6 +719,7 @@ export function apply(ctx) {
672
719
  const prog = parseProgress(q)
673
720
  const dir = prog.directions.find(function (d) { return d.id === dirId })
674
721
  if (!dir) { delete agentRegistry[childId]; return }
722
+ if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } // abort:不把方向标记为死路,保留待 resume
675
723
  const status = (parsed && parsed.status) || statusFromStop(stopReason)
676
724
  dir.round = meta.round
677
725
  if (parsed) {
@@ -728,9 +776,11 @@ export function apply(ctx) {
728
776
  function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
729
777
  async function addLemmaAsProposition(qid, lemma) {
730
778
  if (!lemma || !lemma.title) return
779
+ let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
780
+ if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
731
781
  const p = {
732
782
  id: 'p-' + shortId(), 概述: lemma.statement || lemma.title,
733
- 布尔估计: clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6),
783
+ 布尔估计: be,
734
784
  细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
735
785
  证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
736
786
  证伪列表: [], 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1,
@@ -771,6 +821,14 @@ export function apply(ctx) {
771
821
  const Reason = (parsed && parsed.Reason) || ''
772
822
  let t = tasks['verify:' + rId]
773
823
  if (!t) { t = { id: 'verify:' + rId, type: 'verify', r: { kind: 'proposition', pId: rId, 概述: rId }, rId: rId, status: 'debating', children: [], childResults: {}, round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
824
+ if (!parsed && !scheduler.running) {
825
+ // abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerify 重建)
826
+ delete agentRegistry[childId]
827
+ const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
828
+ delete t.childResults[childId]
829
+ if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
830
+ return
831
+ }
774
832
  if (t.children.indexOf(childId) === -1) t.children.push(childId)
775
833
  t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
776
834
  delete agentRegistry[childId]
@@ -782,6 +840,7 @@ export function apply(ctx) {
782
840
  async function advanceVerification(t, round) {
783
841
  if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
784
842
  if (!scheduler.running) { t.status = 'paused'; return } // resume will re-advance this task
843
+ if (scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return } // 并发门:等有空闲槽位再辩论(reconcileVerify 会重推进)
785
844
  t.round = round + 1
786
845
  const transcript = buildTranscript(t)
787
846
  const nextChildren = []
@@ -891,7 +950,7 @@ export function apply(ctx) {
891
950
  sol.已验 = true
892
951
  sol.验证记录 = sol.验证记录 || []
893
952
  sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
894
- if (v === 1) { q.已解决 = true; q.优先级 = 'never' }
953
+ if (v === 1) { q.已解决 = true; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q, sol) }
895
954
  }
896
955
  await writeQs(qs)
897
956
  }
@@ -922,6 +981,16 @@ export function apply(ctx) {
922
981
  await writeJson('Verified/' + cat + '_Verified.json', list)
923
982
  return true
924
983
  }
984
+ async function writeVerifiedProblemCardIfNeeded(q, sol) {
985
+ if (!q || !q.已解决) return false
986
+ const cat = '问题'
987
+ const list = await readVerifiedCategory(cat)
988
+ if (list.some(function (c) { return c.id === q.id })) return false // idempotent
989
+ const card = { id: q.id, 概述: q.概述, 类型: '问题', 结论: true, 概率: 1, 内容: (sol && sol.完整解法) || '', 来源: q.id, 时间: now(), 分类: cat }
990
+ list.push(card)
991
+ await writeJson('Verified/' + cat + '_Verified.json', list)
992
+ return true
993
+ }
925
994
 
926
995
  // ================= child result dispatch =================
927
996
  async function onChildEnd(info) {
@@ -956,15 +1025,28 @@ export function apply(ctx) {
956
1025
  logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
957
1026
  agentRegistry = {}; tasks = {}
958
1027
  }
1028
+ scheduler.activeCount = 0 // 仅清空 registry/tasks 时归零;同进程 resume 保留存活计数(并发门才准确)
959
1029
  }
960
1030
  await writeJson('VibeMath_State/process_epoch.json', processEpoch)
961
- scheduler.activeCount = 0; await saveAll()
1031
+ await saveAll()
962
1032
  return { ok: true }
963
1033
  }
964
1034
  async function startScheduler(agent) { const r = await init(agent, true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
965
1035
  async function resumeScheduler(agent) { const r = await init(agent, false); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
966
1036
  async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
967
1037
  async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); scheduler.activeCount = 0; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
1038
+ // auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
1039
+ async function autoResolvePending() {
1040
+ const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
1041
+ for (let i = 0; i < pending.length; i++) {
1042
+ const d = pending[i]
1043
+ try {
1044
+ if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1045
+ else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1046
+ } catch (e) { console.error('vibe-math-v2: auto-resolve decision failed: ' + String((e && e.message) || e)) }
1047
+ }
1048
+ if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
1049
+ }
968
1050
  async function getStatus() {
969
1051
  const qs = await getQs(); const propos = await getPropos()
970
1052
  return {
@@ -976,7 +1058,7 @@ export function apply(ctx) {
976
1058
  propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
977
1059
  pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
978
1060
  registeredAgents: Object.keys(agentRegistry).length,
979
- recentActivity: activityLog.slice(-10), params: params,
1061
+ recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
980
1062
  }
981
1063
  }
982
1064
 
@@ -995,7 +1077,7 @@ export function apply(ctx) {
995
1077
 
996
1078
  // ================= events / timer =================
997
1079
  ctx.on('subagent/end', function (info) { onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) }) })
998
- ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, 2000); return () => clearInterval(t) })
1080
+ ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
999
1081
 
1000
1082
  // ================= tools =================
1001
1083
  function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
@@ -1012,8 +1094,8 @@ export function apply(ctx) {
1012
1094
  registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
1013
1095
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
1014
1096
  registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await maybeWriteReport(true); return await buildReport() })
1015
- registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); return { ok: true, mode: params.mode } })
1016
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); return { ok: true, params: params } })
1097
+ registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
1098
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); return { ok: true, params: params } })
1017
1099
  registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } })
1018
1100
  registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), async function () { return await saveSettings() })
1019
1101
  registerTool('vibe_math_template', 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.', objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
@@ -1040,7 +1122,7 @@ export function apply(ctx) {
1040
1122
  if (cmd === 'abort') return await abortScheduler()
1041
1123
  if (cmd === 'status') return await getStatus()
1042
1124
  if (cmd === 'report') { await maybeWriteReport(true); return await buildReport() }
1043
- if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); return { ok: true, mode: params.mode } }
1125
+ if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
1044
1126
  if (cmd === 'setup') { const list = PARAM_SCHEMA.map(function (p) { const out = Object.assign({}, p); out.current = params[p.name]; out.default = DEFAULT_PARAMS[p.name]; return out }); return { ok: true, parameters: list, saveTo: frameworkRoot() + '/vibe_math_setting.json' } }
1045
1127
  if (cmd === 'save') return await saveSettings()
1046
1128
  if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')