dsh-vibe-math 0.3.2 → 0.3.3

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
 
@@ -419,7 +425,10 @@ flowchart TB
419
425
  | `solverToolAllow` / `solverToolDeny` | `[]` | 求解器允许/禁止的工具 |
420
426
  | `verifierToolAllow` / `verifierToolDeny` | `[]` | 验证器允许/禁止的工具 |
421
427
  | `solverMaxToolCalls` / `verifierMaxToolCalls` | 0 | 每轮外部工具调用上限(0=不限) |
422
- | `reportIntervalMs` | 30000 | 进度汇报最小间隔(毫秒) |
428
+ | `reportIntervalMs` | 0 | 0 = 仅事件驱动(有状态更新才写/推);>0 = 定时自动汇报(毫秒) |
429
+ | `tickIntervalMs` | 2000 | 调度器心跳间隔(毫秒) |
430
+ | `activityLogCap` | 100 | 活动日志保留条数(report 最多显示 30 条) |
431
+ | `maxExplorerRetries` | 3 | explorer 拆方向失败的重派生上限 |
423
432
 
424
433
  ---
425
434
 
@@ -427,7 +436,7 @@ flowchart TB
427
436
 
428
437
  - **断点续跑**:所有状态落盘到 `VibeMath_State/*.json`,每个子代理都是 DSH 的 **continuable 持久会话**(对话由 DSH 自动保存)。重启后新开会话 → `vibe_math_resume` 即可续跑。v2 额外用**进程纪元**区分"同进程暂停→恢复"(保留存活子代理继续)与"跨进程重启"(清理陈旧任务)。
429
438
  - **中途人工干预**:`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()` 唤醒主代理主动汇报。
439
+ - **进度汇报**:默认**事件驱动** —— 只有代理状态更新等事件发生时才会写 `Progress_Logs/report.json`(v2 的 `reportMode` `file`/`push`/`both`,`push` 通过 `rootAgent.followup()` 唤醒主代理主动汇报);只有把 `reportIntervalMs` 设为 >0 才启动定时自动汇报(间隔毫秒)。
431
440
 
432
441
  ---
433
442
 
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.3",
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
@@ -458,7 +463,7 @@ export function apply(ctx) {
458
463
 
459
464
  // ================= events / timer =================
460
465
  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) })
466
+ ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
462
467
 
463
468
  // ================= tools =================
464
469
  function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
@@ -477,7 +482,7 @@ export function apply(ctx) {
477
482
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
478
483
  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
484
  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 } })
485
+ 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
486
  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
487
  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
488
  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') })
@@ -47,10 +47,13 @@ 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
+ tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
55
+ activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
56
+ maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
54
57
  }
55
58
  let params = Object.assign({}, DEFAULT_PARAMS)
56
59
  let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
@@ -128,10 +131,13 @@ export function apply(ctx) {
128
131
  { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
129
132
  { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
130
133
  { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
131
- { name: 'reportIntervalMs', type: 'integer', description: '进度汇报最小间隔(毫秒)', suggestion: 30000 },
134
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
132
135
  { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
133
136
  { name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
134
137
  { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
138
+ { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
139
+ { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
140
+ { name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
135
141
  ]
136
142
 
137
143
  // ================= fs =================
@@ -156,7 +162,7 @@ export function apply(ctx) {
156
162
  // ================= settings =================
157
163
  function sanitizeParams(obj) {
158
164
  const out = {}
159
- const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
165
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
160
166
  const numFields = ['promoteValueThreshold']
161
167
  const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
162
168
  for (const k of Object.keys(DEFAULT_PARAMS)) {
@@ -275,7 +281,7 @@ export function apply(ctx) {
275
281
  async function reliableFiles() { return await listFiles('Reliable') }
276
282
 
277
283
  // ================= reporting =================
278
- function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length > 100) activityLog.shift(); reportDirty = true }
284
+ 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
285
  async function buildReport() {
280
286
  const qs = await getQs()
281
287
  const propos = await getPropos()
@@ -287,21 +293,28 @@ export function apply(ctx) {
287
293
  propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
288
294
  pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
289
295
  registeredAgents: Object.keys(agentRegistry).length,
290
- recentActivity: activityLog.slice(-30),
296
+ recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
291
297
  params: params,
292
298
  }
293
299
  }
294
300
  async function maybeWriteReport(force) {
295
- if (!force && !reportDirty) return
296
- if (!force && (now() - lastReportWrite) < (Number(params.reportIntervalMs) || 30000)) return
301
+ const interval = Number(params.reportIntervalMs) || 0
302
+ if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
303
+ if (!force && interval <= 0 && !reportDirty) return
297
304
  await writeJson('Progress_Logs/report.json', await buildReport())
298
305
  lastReportWrite = now(); reportDirty = false
299
306
  }
300
307
  async function maybePushReport(force) {
301
308
  const mode = params.reportMode || 'file'
302
309
  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
310
+ const interval = Number(params.reportIntervalMs) || 0
311
+ if (interval > 0) {
312
+ // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
313
+ if (!force && (now() - lastPushReport) < interval) return
314
+ } else if (!force && !reportDirty) {
315
+ // event-driven: only push when an event happened since the last report
316
+ return
317
+ }
305
318
  if (!rootAgent || typeof rootAgent.followup !== 'function') return
306
319
  try {
307
320
  const report = await buildReport()
@@ -310,7 +323,7 @@ export function apply(ctx) {
310
323
  '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
311
324
  '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
312
325
  rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
313
- lastPushReport = now(); reportDirty = false
326
+ lastPushReport = now()
314
327
  } catch (e) {
315
328
  console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
316
329
  }
@@ -455,8 +468,8 @@ export function apply(ctx) {
455
468
  await processVerify()
456
469
  await reconcileVerify()
457
470
  await processSolve()
458
- await maybeWriteReport(false)
459
471
  await maybePushReport(false)
472
+ await maybeWriteReport(false)
460
473
  const qs = await getQs()
461
474
  const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
462
475
  if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
@@ -469,7 +482,7 @@ export function apply(ctx) {
469
482
  const prog = parseProgress(unsolved[i])
470
483
  const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
471
484
  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
485
+ const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= (Number(params.maxExplorerRetries) || 3)
473
486
  if (hasActive || !blocked) { allBlocked = false; break }
474
487
  }
475
488
  if (allBlocked) {
@@ -623,10 +636,11 @@ export function apply(ctx) {
623
636
  const prog = parseProgress(q)
624
637
  const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
625
638
  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 次未产出方向' })
639
+ const explorerCap = Number(params.maxExplorerRetries) || 3
640
+ if ((explorerRetries[q.id] || 0) >= explorerCap) {
641
+ 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
642
  await saveProgress(q.id, prog)
629
- logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (3 failed attempts)')
643
+ logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
630
644
  continue
631
645
  }
632
646
  explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
@@ -995,7 +1009,7 @@ export function apply(ctx) {
995
1009
 
996
1010
  // ================= events / timer =================
997
1011
  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) })
1012
+ ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, Math.max(200, Number(params.tickIntervalMs) || 2000)); return () => clearInterval(t) })
999
1013
 
1000
1014
  // ================= tools =================
1001
1015
  function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
@@ -1013,7 +1027,7 @@ export function apply(ctx) {
1013
1027
  registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
1014
1028
  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
1029
  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 } })
1030
+ 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' }, 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
1031
  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
1032
  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
1033
  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') })