dsh-vibe-math 0.2.0 → 0.3.0

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.
@@ -0,0 +1,1046 @@
1
+ // Vibe Math V2 — host plugin implementing the NEW architecture spec
2
+ // ("新架构-vibe-math-实现方案.md"): probability-driven scheduling over a
3
+ // problem list (qs/qs.json) and a proposition knowledge base (Propos/),
4
+ // multi-agent independent review → debate → consensus verification, with
5
+ // checkpoint resume, manual intervention, and file/push progress reporting.
6
+ //
7
+ // Preset-local plugin, import-free (only node builtins reachable). Registers
8
+ // vibe_math_* tools, a /vibe slash command, and a background scheduler;
9
+ // provides NO service, so it sits loose in the preset.
10
+ //
11
+ // Data layout (per project, under <workspace>/VibeMath/Projects/<project>/):
12
+ // qs/qs.json — problems (概述/已解决/解法列表/优先级/progress)
13
+ // Propos/<分类>_Propos.json — propositions (概述/布尔估计/细类型/证明·证伪列表/优先级/价值·关键性/progress)
14
+ // Reliable/ — read-only trusted references (user drops files)
15
+ // Verified/ — resolved facts index (布尔估计=0/1 的命题、已解决问题)
16
+ // Verification_logs/ — debate transcripts per verification run
17
+ // Progress_Logs/report.json — periodic progress report
18
+ // VibeMath_State/ — scheduler private state (checkpoint/resume)
19
+ export const name = 'vibe-math-v2'
20
+ export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands']
21
+
22
+ export function apply(ctx) {
23
+ const subagents = ctx.subagents
24
+ const agents = ctx.agents
25
+ const fs = ctx.fs
26
+ const tools = ctx.tools
27
+ const commands = ctx.commands
28
+ const subprocess = ctx.get('subprocess')
29
+ const sandboxPolicy = ctx.get('sandboxPolicy')
30
+
31
+ let rootAgent = undefined
32
+ let currentProject = 'default'
33
+ const DEFAULT_PARAMS = {
34
+ mode: 'auto', // auto | manual
35
+ maxParallelThreshold: 4, // concurrency gate: active turns < this
36
+ solverMaxRounds: 3, // per-direction iteration cap (spec example)
37
+ verifierCount: 3, // independent reviewers per verification
38
+ debateMaxRounds: 5, // debate round cap (spec example)
39
+ verdictMode: 'flat', // flat = 均衡机制(0.5) | forced = 强制裁决(weighted)
40
+ provider: '',
41
+ model: '',
42
+ solverPersona: '',
43
+ verifierPersona: '',
44
+ solverToolAllow: [],
45
+ solverToolDeny: [],
46
+ verifierToolAllow: [],
47
+ verifierToolDeny: [],
48
+ solverMaxToolCalls: 0,
49
+ verifierMaxToolCalls: 0,
50
+ reportIntervalMs: 30000,
51
+ reportMode: 'file', // file | push | both
52
+ promoteValueThreshold: 0.7, // Propos → qs auto-promotion threshold (价值/关键性)
53
+ priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
54
+ }
55
+ let params = Object.assign({}, DEFAULT_PARAMS)
56
+ let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
57
+ let agentRegistry = {}
58
+ let decisionQueue = []
59
+ let verifierAccuracy = {}
60
+ let tasks = {} // verify tasks keyed by 'verify:<rId>'
61
+ let activityLog = []
62
+ let lastReportWrite = 0
63
+ let lastPushReport = 0
64
+ let reportDirty = false
65
+ let tickInFlight = false
66
+ let explorerRetries = {}
67
+
68
+ // ================= helpers =================
69
+ function textBlock(t) { return { type: 'text', text: String(t) } }
70
+ function now() { return Date.now() }
71
+ function uuid() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) s += '-'; else s += h[Math.floor(Math.random() * 16)] } return s }
72
+ function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
73
+ function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
74
+ function workspaceRoot() { try { if (rootAgent && rootAgent.session && rootAgent.session.header && rootAgent.session.header.cwd) return rootAgent.session.header.cwd } catch (e) {} if (sandboxPolicy && sandboxPolicy.workspaceRoot) return sandboxPolicy.workspaceRoot; return '.' }
75
+ function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
76
+ function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
77
+ function frameworkRoot() { return projectRoot(currentProject) }
78
+ function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
79
+ function getPolicy() { try { if (sandboxPolicy && rootAgent && rootAgent.session) return sandboxPolicy.resolve({ session: rootAgent.session }) } catch (e) {} try { if (sandboxPolicy) return sandboxPolicy.resolve({}) } catch (e) {} return undefined }
80
+ function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
81
+ function blocksToText(blocks) { if (!blocks) return ''; let out = ''; for (let i = 0; i < blocks.length; i++) { const b = blocks[i]; if (b && b.type === 'text' && typeof b.text === 'string') out += b.text + '\n' } return out.trim() }
82
+ function parseJson(text) {
83
+ if (typeof text !== 'string') return undefined
84
+ const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
85
+ const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
86
+ let m
87
+ while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
88
+ const whole = tryObj(text.trim()); if (whole !== undefined) return whole
89
+ let best = undefined; let bestLen = -1
90
+ for (let start = 0; start < text.length; start++) {
91
+ if (text[start] !== '{') continue
92
+ let depth = 0, inStr = false, esc = false, end = -1
93
+ for (let i = start; i < text.length; i++) {
94
+ const c = text[i]
95
+ if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
96
+ if (c === '"') { inStr = true; continue }
97
+ if (c === '{') depth++
98
+ else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
99
+ }
100
+ if (end === -1) continue
101
+ const obj = tryObj(text.slice(start, end + 1))
102
+ if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
103
+ }
104
+ return best
105
+ }
106
+ function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
107
+ function stripJsonComments(text) { let out = ''; let inStr = false; let inLine = false; let inBlock = false; let esc = false; for (let i = 0; i < text.length; i++) { const c = text[i]; const n = text[i + 1]; if (inLine) { if (c === '\n') { inLine = false; out += c } continue } if (inBlock) { if (c === '*' && n === '/') { inBlock = false; i++ } continue } if (inStr) { out += c; if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue } if (c === '"') { inStr = true; out += c; continue } if (c === '/' && n === '/') { inLine = true; i++; continue } if (c === '/' && n === '*') { inBlock = true; i++; continue } out += c } return out }
108
+
109
+ // ================= parameter schema =================
110
+ const PARAM_SCHEMA = [
111
+ { name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
112
+ { name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
113
+ { name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
114
+ { name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量', suggestion: 3 },
115
+ { name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
116
+ { name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致直接判 0.5);forced = 强制裁决(按历史准确率+严谨性加权)', suggestion: 'flat' },
117
+ { name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
118
+ { name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
119
+ { name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
120
+ { name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
121
+ { name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
122
+ { name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
123
+ { name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
124
+ { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
125
+ { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
126
+ { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
127
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报最小间隔(毫秒)', suggestion: 30000 },
128
+ { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
129
+ { name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
130
+ { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
131
+ ]
132
+
133
+ // ================= fs =================
134
+ async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
135
+ async function readText(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
136
+ async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
137
+ async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
138
+ async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
139
+ async function listFiles(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'file' }).map(function (e) { return e.name }) } catch (e) { return [] } }
140
+ async function listDirsAt(base, rel) { try { const t = await fs.resolve(rel, { cwd: base }); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
141
+ async function readTextAbs(path) { try { const t = await fs.resolve(path); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
142
+ async function writeTextAbs(path, content) { try { const t = await fs.resolve(path); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true } catch (e) { return false } }
143
+ async function readCurrentProject() { try { const t = await fs.resolve('current.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s === undefined) return 'default'; const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } catch (e) { return 'default' } }
144
+ async function writeCurrentProject() { try { const t = await fs.resolve('current.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) {} }
145
+
146
+ // ================= subprocess =================
147
+ function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
148
+ async function runShell(script, cwd) { if (subprocess === undefined) return { ok: false, error: 'no-subprocess' }; try { const handle = subprocess.spawn({ argv: ['powershell', '-NoProfile', '-NonInteractive', '-Command', script], cwd: cwd || workspaceRoot(), stdio: { stdin: 'ignore', stdout: 'inherit', stderr: 'inherit' }, graceMs: 20000 }); const outcome = await handle.done; return { ok: outcome.exitCode === 0, exitCode: outcome.exitCode } } catch (e) { return { ok: false, error: String((e && e.message) || e) } } }
149
+ async function ensureDirs() { const base = frameworkRoot(); const dirs = ['qs', 'Propos', 'Reliable', 'Verified', 'Verification_logs', 'Progress_Logs', 'VibeMath_State']; const paths = [vibeRoot() + '/Projects'].concat(dirs.map(function (d) { return base + '/' + d })); const list = paths.map(psQuote).join(','); return await runShell('New-Item -Force -ItemType Directory -Path ' + list + ' | Out-Null') }
150
+ async function removeFile(rel) { const base = frameworkRoot(); return await runShell('Remove-Item -Force -LiteralPath ' + psQuote(base + '/' + rel) + ' -ErrorAction SilentlyContinue') }
151
+
152
+ // ================= settings =================
153
+ function sanitizeParams(obj) {
154
+ const out = {}
155
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs']
156
+ const numFields = ['promoteValueThreshold']
157
+ const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
158
+ for (const k of Object.keys(DEFAULT_PARAMS)) {
159
+ if (!(k in obj)) continue
160
+ const v = obj[k]
161
+ if (intFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.floor(n) : DEFAULT_PARAMS[k] }
162
+ else if (numFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : DEFAULT_PARAMS[k] }
163
+ else if (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
164
+ else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
165
+ else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
166
+ else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
167
+ else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
168
+ else { out[k] = v }
169
+ }
170
+ return out
171
+ }
172
+ async function loadSettings() {
173
+ let text = await readText('vibe_math_setting.json')
174
+ if (text === undefined) text = await readTextAbs(vibeRoot() + '/vibe_math_setting.json')
175
+ if (text === undefined) return
176
+ const clean = stripJsonComments(text)
177
+ try {
178
+ const obj = JSON.parse(clean)
179
+ if (obj && typeof obj === 'object' && !Array.isArray(obj)) params = Object.assign({}, params, sanitizeParams(obj))
180
+ } catch (e) {
181
+ console.error('vibe-math-v2: invalid vibe_math_setting.json ignored: ' + String((e && e.message) || e))
182
+ }
183
+ }
184
+ function settingsTemplateFrom(src) {
185
+ const lines = []
186
+ lines.push('{')
187
+ lines.push(' // Vibe Math V2 默认参数配置(JSON with Comments,可加 // 注释)。')
188
+ lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
189
+ const keys = Object.keys(src).sort()
190
+ for (let i = 0; i < keys.length; i++) {
191
+ const k = keys[i]
192
+ const v = src[k]
193
+ const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
194
+ const desc = schema ? schema.description : ''
195
+ const comma = i === keys.length - 1 ? '' : ','
196
+ lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
197
+ lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
198
+ }
199
+ lines.push('}')
200
+ return lines.join('\n') + '\n'
201
+ }
202
+ function settingsTemplate() { return settingsTemplateFrom(params) }
203
+ async function saveSettings() { await writeText('vibe_math_setting.json', settingsTemplate()); return { ok: true, path: frameworkRoot() + '/vibe_math_setting.json' } }
204
+ async function createTemplate(where) { const isGlobal = where !== 'project'; const path = isGlobal ? (vibeRoot() + '/vibe_math_setting.json') : (frameworkRoot() + '/vibe_math_setting.json'); const content = settingsTemplateFrom(DEFAULT_PARAMS); const ok = isGlobal ? await writeTextAbs(path, content) : await writeText('vibe_math_setting.json', content); return { ok: ok, path: path, where: isGlobal ? 'global' : 'project' } }
205
+
206
+ // ================= persistence =================
207
+ async function loadState() {
208
+ const pj = await readJson('VibeMath_State/params.json'); if (pj) params = Object.assign({}, params, pj)
209
+ const s = await readJson('VibeMath_State/scheduler_state.json'); if (s) scheduler = Object.assign({}, scheduler, s)
210
+ const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r
211
+ const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq
212
+ const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va
213
+ const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk
214
+ const er = await readJson('VibeMath_State/explorer_retries.json'); if (er) explorerRetries = er
215
+ }
216
+ async function saveAll() {
217
+ await writeJson('VibeMath_State/params.json', params)
218
+ await writeJson('VibeMath_State/scheduler_state.json', scheduler)
219
+ await writeJson('VibeMath_State/agent_registry.json', agentRegistry)
220
+ await writeJson('VibeMath_State/decision_queue.json', decisionQueue)
221
+ await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
222
+ await writeJson('VibeMath_State/tasks.json', tasks)
223
+ await writeJson('VibeMath_State/explorer_retries.json', explorerRetries)
224
+ scheduler.lastCheckpoint = now()
225
+ }
226
+
227
+ // ================= data layer: qs.json =================
228
+ async function getQs() { const a = await readJson('qs/qs.json'); return Array.isArray(a) ? a : [] }
229
+ async function writeQs(list) { await writeJson('qs/qs.json', list) }
230
+ async function findQ(qid) { const qs = await getQs(); return qs.find(function (q) { return q.id === qid }) }
231
+
232
+ // progress is a JSON string inside the problem object
233
+ function parseProgress(q) { const p = safeJson((q && q.progress) || '', null); if (p && typeof p === 'object') return p; return { directions: [], experience: '' } }
234
+ async function saveProgress(qid, progObj) { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return; q.progress = JSON.stringify(progObj); await writeQs(qs) }
235
+
236
+ // ================= data layer: Propos =================
237
+ function categoryOf(p) { const t = (p && p.细类型) || {}; const keys = Object.keys(t); return (keys.length > 0 && typeof t[keys[0]] === 'object') ? keys[0] : '未分类' }
238
+ function proposFile(cat) { return 'Propos/' + String(cat) + '_Propos.json' }
239
+ async function proposFiles() { return await listFiles('Propos') }
240
+ async function readProposCategory(cat) { const a = await readJson(proposFile(cat)); return Array.isArray(a) ? a : [] }
241
+ async function getPropos() {
242
+ const out = []
243
+ const files = await proposFiles()
244
+ for (let i = 0; i < files.length; i++) {
245
+ const fname = files[i]
246
+ const cat = fname.replace(/_Propos\.json$/i, '')
247
+ const list = await readProposCategory(cat)
248
+ for (let j = 0; j < list.length; j++) { list[j]._category = cat; out.push(list[j]) }
249
+ }
250
+ return out
251
+ }
252
+ async function findProposition(pId) { const all = await getPropos(); return all.find(function (p) { return p.id === pId }) }
253
+ async function upsertProposition(p) {
254
+ const cat = p._category || categoryOf(p)
255
+ delete p._category
256
+ const list = await readProposCategory(cat)
257
+ const idx = list.findIndex(function (x) { return x.id === p.id })
258
+ if (idx !== -1) list[idx] = p; else list.push(p)
259
+ await writeJson(proposFile(cat), list)
260
+ return cat
261
+ }
262
+ async function deleteProposition(p) {
263
+ const cat = p._category || categoryOf(p)
264
+ const list = await readProposCategory(cat)
265
+ const next = list.filter(function (x) { return x.id !== p.id })
266
+ await writeJson(proposFile(cat), next)
267
+ }
268
+
269
+ // ================= data layer: Verified / Reliable =================
270
+ async function readVerifiedCategory(cat) { const a = await readJson('Verified/' + String(cat) + '_Verified.json'); return Array.isArray(a) ? a : [] }
271
+ async function reliableFiles() { return await listFiles('Reliable') }
272
+
273
+ // ================= reporting =================
274
+ function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); if (activityLog.length > 100) activityLog.shift(); reportDirty = true }
275
+ async function buildReport() {
276
+ const qs = await getQs()
277
+ const propos = await getPropos()
278
+ return {
279
+ ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
280
+ running: scheduler.running, mode: params.mode,
281
+ activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
282
+ problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
283
+ propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
284
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
285
+ registeredAgents: Object.keys(agentRegistry).length,
286
+ recentActivity: activityLog.slice(-30),
287
+ params: params,
288
+ }
289
+ }
290
+ async function maybeWriteReport(force) {
291
+ if (!force && !reportDirty) return
292
+ if (!force && (now() - lastReportWrite) < (Number(params.reportIntervalMs) || 30000)) return
293
+ await writeJson('Progress_Logs/report.json', await buildReport())
294
+ lastReportWrite = now(); reportDirty = false
295
+ }
296
+ async function maybePushReport(force) {
297
+ const mode = params.reportMode || 'file'
298
+ if (mode !== 'push' && mode !== 'both') return
299
+ // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
300
+ if (!force && (now() - lastPushReport) < (Number(params.reportIntervalMs) || 30000)) return
301
+ if (!rootAgent || typeof rootAgent.followup !== 'function') return
302
+ try {
303
+ const report = await buildReport()
304
+ const text = '[Vibe Math V2] 进度更新:当前项目 "' + currentProject + '" 运行中=' + report.running +
305
+ ',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
306
+ '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
307
+ '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
308
+ rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
309
+ lastPushReport = now(); reportDirty = false
310
+ } catch (e) {
311
+ console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
312
+ }
313
+ }
314
+
315
+ // ================= child spawn / followup =================
316
+ function pickProvider() { try { const names = subagents.list ? subagents.list() : []; if (names.indexOf('spawn') !== -1) return 'spawn'; if (names.indexOf('fork') !== -1) return 'fork' } catch (e) {} return 'spawn' }
317
+ function childAgentOptions() { const o = {}; try { if (rootAgent && rootAgent.options) { if (rootAgent.options.provider) o.provider = rootAgent.options.provider; if (rootAgent.options.model) o.model = rootAgent.options.model } } catch (e) {} if (params.provider) o.provider = params.provider; if (params.model) o.model = params.model; return o }
318
+ function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const f = {}; if (Array.isArray(allow) && allow.length > 0) f.allow = allow.slice(); if (Array.isArray(deny) && deny.length > 0) f.deny = deny.slice(); return (f.allow || f.deny) ? f : undefined }
319
+ async function spawnChild(label, promptText, meta) {
320
+ const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
321
+ const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf
322
+ let started
323
+ try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) }
324
+ catch (e) {
325
+ if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math-v2: startContinuable with toolFilter failed, retrying without it: ' + String((e && e.message) || e)); started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } else { throw e }
326
+ }
327
+ agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
328
+ scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
329
+ await saveAll(); return started.childId
330
+ }
331
+ async function followupChild(childId, promptText) { await subagents.followup(rootAgent, childId, [textBlock(promptText)], { source: { kind: 'user' }, signal: makeSignal(30000) }); scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1; await saveAll() }
332
+ async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
333
+
334
+ // ================= prompts =================
335
+ function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
336
+ function verifierPersonaText() { return params.verifierPersona ? (String(params.verifierPersona) + '\n\n') : '' }
337
+ function capabilitiesText(role) {
338
+ const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
339
+ let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
340
+ t += '- You may READ any file under Verified/ as a known, trusted dependency (resolved facts).\n'
341
+ t += '- You should BASE your reasoning on the existing knowledge under Propos/ (propositions with proofs/refutations and probabilities) and Reliable/ (trusted references).\n'
342
+ t += '- You may use external tools (web search, symbolic/numeric computation, literature lookup) to assist; '
343
+ t += (maxCalls && Number(maxCalls) > 0) ? ('call such external tools AT MOST ' + maxCalls + ' times this round.\n') : 'no per-round limit by default.\n'
344
+ t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer.\n'
345
+ t += '\nHOW TO READ EXISTING KNOWLEDGE (coarse scan → fine read):\n'
346
+ t += '- These are JSON files. A conclusion object carries summary-index fields (概述 / 布尔估计 / 优先级) and the full detail (证明列表 / 证伪列表 / 完整过程 / progress).\n'
347
+ t += '- COARSE SCAN first: use a read/grep tool to extract ONLY the summary index (概述, 布尔估计, 优先级, titles) to locate which files / objects look relevant — do NOT load full proofs yet.\n'
348
+ t += '- FINE READ after: once you identify a valuable object, read that file again and extract its full JSON (完整过程 / 证明 / 证伪 / progress) via the index you found.\n'
349
+ return t
350
+ }
351
+ function explorerPrompt(q) {
352
+ return 'You are a research mathematician orchestrating strategy for one problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.概述 + '\n\n' +
353
+ capabilitiesText('solver') +
354
+ '\nDo a first-stage METACOGNITIVE BRAINSTORM: decompose constraints, test boundary/extreme cases, map to similar known problems. ' +
355
+ 'Then propose 3-6 DIVERSE, mutually distinct solution directions (e.g. analytic method, constructive proof, contradiction, numeric approximation + limit passage, categorical abstraction, ...). ' +
356
+ 'Record each direction with its core assumption and an initial feasibility estimate.\n\n' +
357
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
358
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}]}'
359
+ }
360
+ function rederivePrompt(q, prog) {
361
+ const prior = prog.directions.map(function (d) {
362
+ return '- ' + d.id + '「' + d.title + '」status=' + d.status + ' round=' + d.round + ' survival=' + d.survival + (d.dead_end_reason ? ' [blocker: ' + d.dead_end_reason + ']' : '') +
363
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '')
364
+ }).join('\n')
365
+ return 'You are a research mathematician re-deriving strategy for a problem whose prior directions stalled or failed.\n\nPROBLEM (id: ' + q.id + '): ' + q.概述 + '\n\nPRIOR DIRECTIONS (with blockers):\n' + prior + '\n' +
366
+ capabilitiesText('solver') +
367
+ '\nQuantitatively analyze the historical progress, blocker causes, and feasibility decay of each prior direction. Discard directions already proven to be dead ends (unless a new tool/idea changes that). ' +
368
+ 'Then deeply DERIVE 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. ' +
369
+ 'Finally return the UNION of high-potential leftover directions and the brand-new directions as the new direction set M_q (drop dead ends).\n\n' +
370
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
371
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5,"motivation":"..."}]}'
372
+ }
373
+ function directionSummary(d) {
374
+ return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
375
+ ' survival=' + d.survival +
376
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '') +
377
+ (d.blockers && d.blockers.length ? ' | blockers: ' + d.blockers.join('; ') : '')
378
+ }
379
+ function solverPrompt(q, dir, round, progressText) {
380
+ let head = solverPersonaText() + 'You are a dedicated solver agent working ONE solution direction of a math problem (agent_self_iteration).\n\n'
381
+ head += 'PROBLEM (id: ' + q.id + '): ' + q.概述 + '\nDIRECTION: ' + dir.title + ' (method: ' + dir.method + '; core assumption: ' + dir.core_assumption + ')\nROUND: ' + round + ' of ' + params.solverMaxRounds + '\n'
382
+ if (round > 1 || (progressText && progressText.length)) head += '\nYOUR PRIOR PROGRESS / OTHER DIRECTIONS:\n' + progressText + '\n'
383
+ head += capabilitiesText('solver')
384
+ head += '\nStart from the last recorded node of direction ' + dir.id + ' (inherit progress, or branch a sub-route under it). Each round you MUST produce, even if incomplete:\n' +
385
+ '- new lemmas / intermediate conclusions WITH full proofs (these go to the Propos/ knowledge base);\n' +
386
+ '- each concrete sub-route tried, its progress overview, an EXPLICIT feasibility signal (e.g. "unremovable singularity", "conflicts with known theorem X"), and any blocker;\n' +
387
+ '- an updated survival probability for this direction.\n'
388
+ head += '\nIf you encounter an EXTREMELY complex auxiliary conjecture/sub-problem q_sub: list it in "sub_questions", TEMPORARILY ASSUME it holds, and continue the main line — every later proposition MUST then be stated as "若 <q_sub 标题> 成立,则:..." so the dependency is explicit.\n'
389
+ head += '\nIf you obtain a COMPLETE solution: adversarially self-check (construct counterexamples, test boundary conditions) BEFORE declaring success; put the full solution text in "solution".\n'
390
+ head += '\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
391
+ '{"status":"continue|success|dead-end","solution":"complete solution text, or null","solution_probability":0.85,"lemmas":[{"title":"...","statement":"...","proof":"...","细类型":{"分类名":{}},"布尔估计":0.6,"价值/关键性":0.5,"优先级":1}],"routes":[{"title":"...","progress":"...","feasibility_signal":"...","blocker":"..."}],"survival_probability":0.5,"dead_end_reason":"... or null","sub_questions":[{"title":"...","statement":"..."}]}'
392
+ return head
393
+ }
394
+ function verifierReviewPrompt(r) {
395
+ let target = ''
396
+ if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
397
+ else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
398
+ else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
399
+ return verifierPersonaText() + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + target + '\n' +
400
+ capabilitiesText('verifier') +
401
+ '\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
402
+ '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence; when Result=1 for a bare proposition, Reason must be a complete proof; when Result=0, Reason must be a rigorous complete refutation"}'
403
+ }
404
+ function verifierDebatePrompt(r, transcript) {
405
+ let target = ''
406
+ if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
407
+ else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
408
+ else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
409
+ return verifierPersonaText() + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + target + '\n' +
410
+ capabilitiesText('verifier') +
411
+ '\nOTHERS HAVE SAID SO FAR (轮流发言):\n' + transcript + '\n' +
412
+ '\nRespond to the others (agree / rebut / add new evidence). If you changed your Result because of them, state the reason explicitly.\n' +
413
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
414
+ '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"}'
415
+ }
416
+
417
+ // ================= decisions (manual/auto) =================
418
+ function enqueueDecision(node, contextText, data) { const d = { id: uuid(), node: node, context: contextText, data: data, status: 'pending', resolution: null, createdAt: now() }; decisionQueue.push(d); return d }
419
+ async function maybeGate(node, contextText, data, autoFn) { if (params.mode === 'auto') return await autoFn(data); const d = enqueueDecision(node, contextText, data); scheduler.gate = { decisionId: d.id, node: node }; logActivity('gate', node + ': ' + contextText); await saveAll(); return { gated: true, decisionId: d.id } }
420
+ async function applyDecision(node, data, resolution) {
421
+ if (node === 'spawn') {
422
+ if (resolution.action === 'approve') { await spawnChild(data.label, data.promptText, data.meta); return { spawned: true } }
423
+ try {
424
+ const meta = data.meta || {}
425
+ if (meta.role === 'explorer' && meta.qid) {
426
+ const q = await findQ(meta.qid)
427
+ if (q) { const prog = parseProgress(q); prog.directions.push({ id: 'd_' + shortId(), title: '用户拒绝派发', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], blockers: [], dead_end_reason: 'explorer 派发被用户拒绝' }); await saveProgress(meta.qid, prog) }
428
+ } else if (meta.role === 'solver' && meta.qid && meta.direction) {
429
+ const q = await findQ(meta.qid)
430
+ if (q) { const prog = parseProgress(q); const d = prog.directions.find(function (x) { return x.id === meta.direction }); if (d) { d.status = 'dead-end'; d.dead_end_reason = '求解器派发被用户拒绝' } await saveProgress(meta.qid, prog) }
431
+ }
432
+ } catch (e) { console.error('vibe-math-v2: spawn reject mark failed: ' + String((e && e.message) || e)) }
433
+ return { spawned: false, rejected: true }
434
+ }
435
+ if (node === 'verdict') { const overridden = resolution.action === 'override' && (resolution.verdict === 1 || resolution.verdict === 0); const v = overridden ? Number(resolution.verdict) : data.verdict; await settleVerdict(data.task, v); delete tasks[data.task.id]; return { verdict: v, overridden: overridden } }
436
+ return {}
437
+ }
438
+ async function resolveDecision(id, resolution) { const d = decisionQueue.find(function (x) { return x.id === id }); if (!d) return { ok: false, message: 'decision not found' }; if (d.status !== 'pending') return { ok: false, message: 'decision already resolved' }; d.status = 'resolved'; d.resolution = resolution; if (scheduler.gate && scheduler.gate.decisionId === id) scheduler.gate = null; logActivity('decide', id + ' resolved: ' + resolution.action + (resolution.verdict !== undefined ? ' ' + resolution.verdict : '')); await saveAll(); scheduleTick(); return { ok: true, message: 'decision resolved' } }
439
+
440
+ // ================= scheduler core =================
441
+ function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v2 tick error: ' + String((e && e.stack) || e)) }) }
442
+ async function tick() {
443
+ if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return
444
+ tickInFlight = true
445
+ try {
446
+ await processStatusUpdates()
447
+ await processPriorityAdjust()
448
+ await processPromote()
449
+ await processVerify()
450
+ await reconcileVerify()
451
+ await processSolve()
452
+ await maybeWriteReport(false)
453
+ await maybePushReport(false)
454
+ const qs = await getQs()
455
+ const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
456
+ if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
457
+ scheduler.running = false
458
+ logActivity('stop', 'all active problems solved (never-priority problems excluded) and no active agents/tasks — scheduler stopped (strict termination)')
459
+ await saveAll(); await maybeWriteReport(true); await maybePushReport(true)
460
+ } else if (Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0 && unsolved.length > 0) {
461
+ let allBlocked = true
462
+ for (let i = 0; i < unsolved.length; i++) {
463
+ const prog = parseProgress(unsolved[i])
464
+ const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
465
+ const hasActive = prog.directions.some(function (d) { return d.status === 'active' })
466
+ const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= 3
467
+ if (hasActive || !blocked) { allBlocked = false; break }
468
+ }
469
+ if (allBlocked) {
470
+ scheduler.running = false
471
+ logActivity('stall', 'no feasible direction remains for any unsolved problem — scheduler paused (stalled, NOT all solved)')
472
+ await saveAll(); await maybeWriteReport(true)
473
+ }
474
+ }
475
+ } finally { tickInFlight = false }
476
+ }
477
+ // note 4: probability-1 rules
478
+ async function processStatusUpdates() {
479
+ let changed = false
480
+ const qs = await getQs()
481
+ for (let i = 0; i < qs.length; i++) {
482
+ const q = qs[i]
483
+ if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) changed = true; q.已解决 = true; q.优先级 = 'never' }
484
+ }
485
+ if (changed) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
486
+ const propos = await getPropos()
487
+ for (let i = 0; i < propos.length; i++) {
488
+ const p = propos[i]
489
+ let pChanged = false
490
+ const proofOne = (p.证明列表 || []).some(function (x) { return x.正确概率 === 1 })
491
+ const refuteOne = (p.证伪列表 || []).some(function (x) { return x.正确概率 === 1 })
492
+ if (proofOne && p.布尔估计 !== 1) { p.布尔估计 = 1; pChanged = true }
493
+ else if (refuteOne && p.布尔估计 !== 0) { p.布尔估计 = 0; pChanged = true }
494
+ if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
495
+ if (p.布尔估计 === 1 || p.布尔估计 === 0) { if (await writeVerifiedCardIfNeeded(p)) pChanged = true }
496
+ if (pChanged) await upsertProposition(p)
497
+ }
498
+ }
499
+ async function processPriorityAdjust() {
500
+ const mode = params.priorityAdjust || 'none'
501
+ if (mode === 'none') return
502
+ const qs = await getQs()
503
+ let changed = false
504
+ for (let i = 0; i < qs.length; i++) {
505
+ const q = qs[i]
506
+ if (q.已解决 || q.优先级 === 'never') continue
507
+ const prog = parseProgress(q)
508
+ if (mode === 'deadend-deprioritize') {
509
+ if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
510
+ const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
511
+ }
512
+ } else if (mode === 'survival-map') {
513
+ if (prog.directions.length > 0) {
514
+ const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
515
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
516
+ if (q.优先级 !== target) { q.优先级 = target; changed = true }
517
+ }
518
+ }
519
+ }
520
+ if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
521
+ }
522
+ // note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
523
+ async function processPromote() {
524
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
525
+ const qs = await getQs()
526
+ const qDescriptions = qs.map(function (q) { return q.概述 })
527
+ const propos = await getPropos()
528
+ for (let i = 0; i < propos.length; i++) {
529
+ const p = propos[i]
530
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
531
+ if (Number(p['价值/关键性']) < Number(params.promoteValueThreshold)) continue
532
+ if (p.在问题清单) continue
533
+ if (qDescriptions.indexOf(p.概述) !== -1) continue
534
+ const qid = 'q-promoted-' + String(p.id).replace(/[^a-z0-9\-]/gi, '').slice(-12)
535
+ qs.push({ id: qid, 概述: p.概述, 已解决: false, 解法列表: [], 优先级: 1, progress: '由命题 ' + p.id + '(价值/关键性=' + p['价值/关键性'] + ')自动晋升;目标是证明/证伪该命题。' })
536
+ p.在问题清单 = true
537
+ await upsertProposition(p)
538
+ await writeQs(qs)
539
+ logActivity('promote', 'proposition ' + p.id + ' promoted to problem ' + qid)
540
+ return // one per tick is enough
541
+ }
542
+ }
543
+ async function processVerify() {
544
+ const candidates = await buildVerifyCandidates()
545
+ for (let i = 0; i < candidates.length; i++) {
546
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
547
+ const c = candidates[i]
548
+ const rId = c.rId
549
+ if (tasks['verify:' + rId]) continue
550
+ const inflight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'verifier' && m.rId === rId })
551
+ if (inflight) continue
552
+ tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }
553
+ await saveAll()
554
+ return // one verification at a time keeps scheduling simple; tick will continue next pass
555
+ }
556
+ }
557
+ async function buildVerifyCandidates() {
558
+ const out = []
559
+ const qs = await getQs()
560
+ for (let i = 0; i < qs.length; i++) {
561
+ const q = qs[i]
562
+ if (q.已解决 || q.优先级 === 'never') continue
563
+ const sols = q.解法列表 || []
564
+ for (let j = 0; j < sols.length; j++) {
565
+ const s = sols[j]
566
+ if (s.正确概率 === 1 || s.正确概率 === 0 || s.已验) continue
567
+ 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.优先级) })
568
+ }
569
+ }
570
+ const propos = await getPropos()
571
+ for (let i = 0; i < propos.length; i++) {
572
+ const p = propos[i]
573
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
574
+ const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
575
+ if (proofs.length === 0 && refutes.length === 0) {
576
+ out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
577
+ } else {
578
+ 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.优先级) }) }
579
+ 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.优先级) }) }
580
+ }
581
+ }
582
+ out.sort(function (a, b) { return a.priority - b.priority })
583
+ return out
584
+ }
585
+ async function backfillVerifiers(t) {
586
+ while (t.children.length < t.expectedCount) {
587
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
588
+ const index = t.children.length
589
+ const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
590
+ t.children.push(childId)
591
+ }
592
+ if (t.children.length >= t.expectedCount) t.status = 'debating'
593
+ }
594
+ async function reconcileVerify() {
595
+ const ids = Object.keys(tasks)
596
+ for (let i = 0; i < ids.length; i++) {
597
+ const t = tasks[ids[i]]
598
+ if (t.type !== 'verify' || t.status !== 'spawning') continue
599
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
600
+ await backfillVerifiers(t)
601
+ }
602
+ }
603
+ async function processSolve() {
604
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
605
+ const qs = await getQs()
606
+ const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' }).sort(function (a, b) { return (a.优先级 === 'never' ? 999 : Number(a.优先级)) - (b.优先级 === 'never' ? 999 : Number(b.优先级)) })
607
+ for (let i = 0; i < unsolved.length; i++) {
608
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
609
+ const q = unsolved[i]
610
+ const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
611
+ if (busy) continue
612
+ const prog = parseProgress(q)
613
+ const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
614
+ if (prog.directions.length === 0 || allExhausted) {
615
+ if ((explorerRetries[q.id] || 0) >= 3) {
616
+ 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 次未产出方向' })
617
+ await saveProgress(q.id, prog)
618
+ logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (3 failed attempts)')
619
+ continue
620
+ }
621
+ explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
622
+ const promptText = (prog.directions.length > 0) ? rederivePrompt(q, prog) : explorerPrompt(q)
623
+ const r = await maybeGate('spawn', 'explorer for problem ' + q.id, { label: 'explorer:' + q.id, promptText: promptText, meta: { role: 'explorer', qid: q.id } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } })
624
+ if (r && r.gated) return
625
+ } else {
626
+ // spawn solvers for each active direction
627
+ for (let j = 0; j < prog.directions.length; j++) {
628
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
629
+ const dir = prog.directions[j]
630
+ if (dir.status === 'success' || dir.status === 'dead-end') continue
631
+ const running = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && m.direction === dir.id && m.role === 'solver' })
632
+ if (running) continue
633
+ const progressText = prog.directions.map(directionSummary).join('\n')
634
+ const promptText = solverPrompt(q, dir, 1, progressText)
635
+ const r = await maybeGate('spawn', 'solver for problem ' + q.id + ' direction ' + dir.id, { label: 'solver:' + q.id + ':' + dir.id, promptText: promptText, meta: { role: 'solver', qid: q.id, direction: dir.id, round: 1, description: q.概述 } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } })
636
+ if (r && r.gated) return
637
+ }
638
+ }
639
+ }
640
+ }
641
+
642
+ // ================= solver (agent_self_iteration) handling =================
643
+ async function handleExplorer(childId, meta, output) {
644
+ delete agentRegistry[childId]
645
+ const parsed = parseJson(output)
646
+ const dirs = (parsed && parsed.directions) || []
647
+ if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions'); await saveAll(); return }
648
+ explorerRetries[meta.qid] = 0
649
+ const q = await findQ(meta.qid); if (!q) return
650
+ const prog = parseProgress(q)
651
+ prog.directions = dirs.map(function (d) {
652
+ return { id: d.id || ('d_' + shortId()), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || '', feasibility: clamp01(d.feasibility), status: 'active', round: 0, survival: clamp01(d.feasibility), routes: [], blockers: [], dead_end_reason: '' }
653
+ })
654
+ await saveProgress(meta.qid, prog)
655
+ logActivity('explorer', 'problem ' + meta.qid + ' → ' + prog.directions.length + ' directions')
656
+ }
657
+ async function handleSolver(childId, meta, output, stopReason) {
658
+ const qid = meta.qid; const dirId = meta.direction
659
+ const parsed = parseJson(output)
660
+ const q = await findQ(qid); if (!q) { delete agentRegistry[childId]; return }
661
+ const prog = parseProgress(q)
662
+ const dir = prog.directions.find(function (d) { return d.id === dirId })
663
+ if (!dir) { delete agentRegistry[childId]; return }
664
+ const status = (parsed && parsed.status) || statusFromStop(stopReason)
665
+ dir.round = meta.round
666
+ if (parsed) {
667
+ if (parsed.routes) dir.routes = (dir.routes || []).concat(parsed.routes)
668
+ if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
669
+ if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
670
+ if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) await addLemmaAsProposition(qid, parsed.lemmas[i]) }
671
+ if (parsed.sub_questions && parsed.sub_questions.length) { for (let i = 0; i < parsed.sub_questions.length; i++) await addSubQuestion(qid, dirId, parsed.sub_questions[i]) }
672
+ }
673
+ if (status === 'success') {
674
+ if (parsed && parsed.solution) {
675
+ dir.status = 'success'
676
+ delete agentRegistry[childId]
677
+ logActivity('solver', qid + '/' + dirId + ' success at round ' + meta.round)
678
+ await addSolution(qid, parsed.solution, parsed.solution_probability)
679
+ } else {
680
+ // claimed success without a solution text — treat as an incomplete round
681
+ if (meta.round >= params.solverMaxRounds) {
682
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || 'claimed success without solution at iteration cap'
683
+ delete agentRegistry[childId]
684
+ logActivity('solver', qid + '/' + dirId + ' dead-end (success without solution)')
685
+ } else {
686
+ const progressText = prog.directions.map(directionSummary).join('\n')
687
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
688
+ agentRegistry[childId].round = meta.round + 1
689
+ dir.round = meta.round + 1
690
+ }
691
+ }
692
+ } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) {
693
+ dir.status = 'dead-end'
694
+ if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'
695
+ delete agentRegistry[childId]
696
+ logActivity('solver', qid + '/' + dirId + ' dead-end: ' + dir.dead_end_reason)
697
+ } else {
698
+ const progressText = prog.directions.map(directionSummary).join('\n')
699
+ try {
700
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
701
+ agentRegistry[childId].round = meta.round + 1
702
+ dir.round = meta.round + 1
703
+ } catch (e) {
704
+ console.error('vibe-math-v2: solver followup failed: ' + String((e && e.message) || e))
705
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || '求解器续轮失败(followup 异常)'
706
+ delete agentRegistry[childId]
707
+ }
708
+ }
709
+ await saveProgress(qid, prog)
710
+ }
711
+ function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
712
+ async function addLemmaAsProposition(qid, lemma) {
713
+ if (!lemma || !lemma.title) return
714
+ const p = {
715
+ id: 'p-' + shortId(), 概述: lemma.statement || lemma.title,
716
+ 布尔估计: clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6),
717
+ 细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
718
+ 证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
719
+ 证伪列表: [], 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1,
720
+ '价值/关键性': clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
721
+ progress: '由求解器针对问题 ' + qid + ' 的方向迭代产出。', 来源问题: qid,
722
+ }
723
+ await upsertProposition(p)
724
+ logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
725
+ }
726
+ async function addSubQuestion(qid, dirId, sq) {
727
+ if (!sq || !sq.title) return
728
+ const qs = await getQs()
729
+ const subId = qid + '-sub-' + shortId()
730
+ qs.push({ id: subId, 概述: sq.statement || sq.title, 已解决: false, 解法列表: [], 优先级: 1, progress: '子问题:由问题 ' + qid + ' 方向 ' + dirId + ' 分支产生;主线临时假设其成立。' })
731
+ await writeQs(qs)
732
+ const q = qs.find(function (x) { return x.id === qid })
733
+ if (q) { const prog = parseProgress(q); const d = prog.directions.find(function (x) { return x.id === dirId }); if (d) { d.sub_questions = d.sub_questions || []; d.sub_questions.push(subId) } await saveProgress(qid, prog) }
734
+ logActivity('subquestion', qid + ' → ' + subId + '(' + sq.title + ')')
735
+ }
736
+ async function addSolution(qid, solutionText, prob) {
737
+ const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return
738
+ const p = clamp01(prob != null ? prob : 0.8)
739
+ const finalProb = p >= 1 ? 0.99 : (p <= 0 ? 0.01 : p) // must be < 1 (待验证器验证)
740
+ q.解法列表 = q.解法列表 || []
741
+ q.解法列表.push({ 完整解法: String(solutionText), 正确概率: finalProb, 来源: 'solver', 验证记录: [] })
742
+ await writeQs(qs)
743
+ logActivity('solution', 'problem ' + qid + ' got a candidate solution (probability ' + finalProb + ', awaiting verification)')
744
+ }
745
+
746
+ // ================= verification (验证器) =================
747
+ function consensus(t) { const vs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid].Result }); if (vs.length === 0) return false; return vs.every(function (v) { return v === 1 }) || vs.every(function (v) { return v === 0 }) }
748
+ function buildTranscript(t) { const parts = []; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const r = t.childResults[cids[i]]; parts.push('Reviewer ' + i + ': Result=' + r.Result + ' Reason=' + r.Reason) } return parts.join('\n') }
749
+ function verifierWeight(cid, rigor) { const acc = verifierAccuracy[cid] || { correct: 0, total: 0 }; const base = acc.total > 0 ? (acc.correct / acc.total) : 0.5; const bonus = (typeof rigor === 'number' && Number.isFinite(rigor)) ? Math.max(-0.2, Math.min(0.2, rigor)) : 0; return Math.max(0.05, Math.min(0.95, base + bonus)) }
750
+ async function handleVerifier(childId, meta, output, stopReason) {
751
+ const rId = meta.rId
752
+ const parsed = parseJson(output)
753
+ const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
754
+ const Reason = (parsed && parsed.Reason) || ''
755
+ let t = tasks['verify:' + rId]
756
+ 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 }
757
+ if (t.children.indexOf(childId) === -1) t.children.push(childId)
758
+ t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
759
+ delete agentRegistry[childId]
760
+ const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
761
+ if (!allReported) { await saveAll(); return }
762
+ await advanceVerification(t, meta.round)
763
+ await saveAll()
764
+ }
765
+ async function advanceVerification(t, round) {
766
+ if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
767
+ t.round = round + 1
768
+ const transcript = buildTranscript(t)
769
+ const nextChildren = []
770
+ for (let i = 0; i < t.children.length; i++) {
771
+ const cid = t.children[i]
772
+ try {
773
+ await followupChild(cid, verifierDebatePrompt(t.r, transcript))
774
+ agentRegistry[cid] = { role: 'verifier', rId: t.rId, round: round + 1, index: i }
775
+ nextChildren.push(cid)
776
+ } catch (e) {
777
+ console.error('vibe-math-v2: verifier followup failed: ' + String((e && e.message) || e))
778
+ delete agentRegistry[cid]
779
+ delete t.childResults[cid]
780
+ }
781
+ }
782
+ t.children = nextChildren
783
+ if (nextChildren.length === 0) await finalizeVerification(t)
784
+ } else {
785
+ await finalizeVerification(t)
786
+ }
787
+ }
788
+ async function finalizeVerification(t) {
789
+ const verdict = finalVerdict(t)
790
+ if (params.mode === 'manual') {
791
+ const d = enqueueDecision('verdict', 'verdict for ' + t.rId + ' (debate finished) = ' + verdict, { rId: t.rId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
792
+ scheduler.gate = { decisionId: d.id, node: 'verdict' }
793
+ t.status = 'awaiting-verdict'
794
+ } else {
795
+ await settleVerdict(t, verdict)
796
+ delete tasks[t.id]
797
+ }
798
+ }
799
+ function finalVerdict(t) {
800
+ const rs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid] })
801
+ if (rs.length === 0) return 0.5
802
+ if (rs.every(function (r) { return r.Result === 1 })) return 1
803
+ if (rs.every(function (r) { return r.Result === 0 })) return 0
804
+ if (params.verdictMode === 'forced') {
805
+ let num = 0; let den = 0
806
+ const cids = Object.keys(t.childResults)
807
+ for (let i = 0; i < rs.length; i++) {
808
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
809
+ const accRate = acc.total > 0 ? (acc.correct / acc.total) : 0.5
810
+ const confident = (rs[i].Result === 1 || rs[i].Result === 0) ? 0.1 : 0
811
+ const w = Math.max(0.05, Math.min(0.95, accRate + confident))
812
+ num += w * rs[i].Result; den += w
813
+ }
814
+ return den > 0 ? Math.max(0.01, Math.min(0.99, num / den)) : 0.5
815
+ }
816
+ return 0.5 // flat = 均衡机制
817
+ }
818
+ async function settleVerdict(t, verdict) {
819
+ const v = clamp01(verdict)
820
+ const r = t.r
821
+ const cids = Object.keys(t.childResults)
822
+ // update verifier historical accuracy (forced mode audit)
823
+ for (let i = 0; i < cids.length; i++) {
824
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
825
+ acc.total += 1
826
+ if (t.childResults[cids[i]].Result === v) acc.correct += 1
827
+ verifierAccuracy[cids[i]] = acc
828
+ }
829
+ await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
830
+ // debate transcript log
831
+ await writeJson('Verification_logs/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), at: now() })
832
+
833
+ if (r.kind === 'proposition') {
834
+ const p = await findProposition(r.pId)
835
+ if (p) {
836
+ p.布尔估计 = v
837
+ if (v === 1) { p.证明列表 = p.证明列表 || []; p.证明列表.push({ 完整过程: strongestReason(t, 1), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
838
+ else if (v === 0) { p.证伪列表 = p.证伪列表 || []; p.证伪列表.push({ 完整过程: strongestReason(t, 0), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
839
+ else {
840
+ p.证明列表 = p.证明列表 || []; p.证伪列表 = p.证伪列表 || []
841
+ p.证明列表.push({ 完整过程: strongestReason(t, 1) || '根据辩论得到的支持性论证', 正确概率: v, '支持信息/依据': '', 已验: true })
842
+ p.证伪列表.push({ 完整过程: strongestReason(t, 0) || '根据辩论得到的反驳性论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
843
+ }
844
+ await upsertProposition(p)
845
+ await writeVerifiedCardIfNeeded(p)
846
+ }
847
+ } else if (r.kind === 'prop-proof') {
848
+ const p = await findProposition(r.pId)
849
+ if (p) {
850
+ const list = r.side === '证明' ? (p.证明列表 = p.证明列表 || []) : (p.证伪列表 = p.证伪列表 || [])
851
+ const item = list[r.idx]
852
+ if (item) {
853
+ item.正确概率 = v
854
+ item.已验 = true
855
+ if (v === 1) { item['支持信息/依据'] = strongestReason(t, 1) || item['支持信息/依据'] }
856
+ else if (v === 0) {
857
+ const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
858
+ other.push({ 完整过程: strongestReason(t, 0) || '', 正确概率: 1, '支持信息/依据': '判定 ' + r.side + ' 错误后的反证', 已验: true })
859
+ } else {
860
+ const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
861
+ other.push({ 完整过程: strongestReason(t, v >= 0.5 ? 0 : 1) || '辩论得出的相反方向论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
862
+ item['支持信息/依据'] = strongestReason(t, v >= 0.5 ? 1 : 0) || item['支持信息/依据']
863
+ }
864
+ }
865
+ await upsertProposition(p)
866
+ await writeVerifiedCardIfNeeded(p)
867
+ }
868
+ } else if (r.kind === 'problem-solution') {
869
+ const qs = await getQs(); const q = qs.find(function (x) { return x.id === r.qid }); if (q) {
870
+ const sol = (q.解法列表 || [])[r.idx]
871
+ if (sol) {
872
+ sol.正确概率 = v
873
+ sol.已验 = true
874
+ sol.验证记录 = sol.验证记录 || []
875
+ sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
876
+ if (v === 1) { q.已解决 = true; q.优先级 = 'never' }
877
+ }
878
+ await writeQs(qs)
879
+ }
880
+ }
881
+ logActivity('verdict', t.rId + ' = ' + v + (v === 1 ? ' (fully verified)' : v === 0 ? ' (refuted)' : ' (uncertain)'))
882
+ }
883
+ function strongestReason(t, wantTrue) {
884
+ const cids = Object.keys(t.childResults)
885
+ let best = ''; let bestDist = -1
886
+ for (let i = 0; i < cids.length; i++) {
887
+ const res = t.childResults[cids[i]]
888
+ const dist = wantTrue ? res.Result : 1 - res.Result
889
+ if (dist > bestDist && res.Reason) { bestDist = dist; best = res.Reason }
890
+ }
891
+ return best
892
+ }
893
+ async function writeVerifiedCardIfNeeded(p) {
894
+ if (p.布尔估计 !== 1 && p.布尔估计 !== 0) return false
895
+ const cat = categoryOf(p)
896
+ const list = await readVerifiedCategory(cat)
897
+ if (list.some(function (c) { return c.id === p.id })) return false // idempotent
898
+ const card = {
899
+ id: p.id, 概述: p.概述, 类型: '命题', 结论: p.布尔估计 === 1, 概率: p.布尔估计,
900
+ 内容: (p.布尔估计 === 1 ? ((p.证明列表 || []).find(function (x) { return x.正确概率 === 1 }) || {}).完整过程 : ((p.证伪列表 || []).find(function (x) { return x.正确概率 === 1 }) || {}).完整过程) || '',
901
+ 来源: p.来源问题 || '', 时间: now(), 分类: cat,
902
+ }
903
+ list.push(card)
904
+ await writeJson('Verified/' + cat + '_Verified.json', list)
905
+ return true
906
+ }
907
+
908
+ // ================= child result dispatch =================
909
+ async function onChildEnd(info) {
910
+ const meta = agentRegistry[info.id]
911
+ if (meta === undefined) return
912
+ scheduler.activeCount = Math.max(0, scheduler.activeCount - 1)
913
+ const output = blocksToText(info.lastAssistantMessage)
914
+ try {
915
+ if (meta.role === 'explorer') await handleExplorer(info.id, meta, output)
916
+ else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason)
917
+ else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason)
918
+ } catch (e) { console.error('vibe-math-v2 onChildEnd error: ' + String((e && e.stack) || e)) }
919
+ await saveAll()
920
+ scheduleTick()
921
+ }
922
+
923
+ // ================= init / control =================
924
+ async function resolveRootAgent(agent) { if (rootAgent) return rootAgent; if (agent) { rootAgent = agent; return rootAgent } try { const roots = agents.roots ? agents.roots() : []; if (roots && roots.length > 0) { rootAgent = roots[0]; return rootAgent } } catch (e) {} return rootAgent }
925
+ async function init(agent) {
926
+ await resolveRootAgent(agent); if (!rootAgent) return { ok: false, message: 'no root agent available' }
927
+ currentProject = await readCurrentProject(); await ensureDirs()
928
+ if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
929
+ params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await loadState()
930
+ // In-flight children of a previous process are gone after restart: drop stale
931
+ // registrations so scheduling is not blocked by phantom entries. Completed work
932
+ // already lives in qs.json / Propos / progress; only the interrupted turn is lost.
933
+ if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
934
+ logActivity('resume', 'cleared ' + Object.keys(agentRegistry).length + ' stale agent(s) and ' + Object.keys(tasks).length + ' in-flight task(s) from previous process')
935
+ agentRegistry = {}; tasks = {}
936
+ }
937
+ scheduler.activeCount = 0; await saveAll()
938
+ return { ok: true }
939
+ }
940
+ async function startScheduler(agent) { const r = await init(agent); 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() } }
941
+ 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() } }
942
+ async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
943
+ 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 } }
944
+ async function getStatus() {
945
+ const qs = await getQs(); const propos = await getPropos()
946
+ return {
947
+ ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
948
+ project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
949
+ mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
950
+ frameworkRoot: frameworkRoot(),
951
+ problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
952
+ propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
953
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
954
+ registeredAgents: Object.keys(agentRegistry).length,
955
+ recentActivity: activityLog.slice(-10), params: params,
956
+ }
957
+ }
958
+
959
+ // ================= projects =================
960
+ async function setProject(slug, create) {
961
+ if (!rootAgent) return { ok: false, message: 'no root agent available' }
962
+ const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
963
+ if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
964
+ if (scheduler.running) await abortScheduler()
965
+ currentProject = slug; await writeCurrentProject(); await ensureDirs()
966
+ if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
967
+ params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; lastReportWrite = 0; lastPushReport = 0; reportDirty = false
968
+ await loadSettings(); await loadState(); await saveAll()
969
+ return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
970
+ }
971
+
972
+ // ================= events / timer =================
973
+ ctx.on('subagent/end', function (info) { onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) }) })
974
+ ctx.effect(() => { const t = setInterval(function () { scheduleTick() }, 2000); return () => clearInterval(t) })
975
+
976
+ // ================= tools =================
977
+ function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
978
+ function registerTool(name, description, parameters, executeFn) {
979
+ ctx.effect(() => tools.register({
980
+ name: name, description: description, parameters: parameters,
981
+ output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
982
+ execute: async function (args, exec) { try { const agent = (exec && exec.agent) || undefined; await resolveRootAgent(agent); if (rootAgent) currentProject = await readCurrentProject(); return JSON.stringify(await executeFn(args || {}, agent)) } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) } },
983
+ }))
984
+ }
985
+ registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), async function (args, agent) { return await startScheduler(agent) })
986
+ registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), async function (args, agent) { return await resumeScheduler(agent) })
987
+ registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
988
+ registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
989
+ registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { return await getStatus() })
990
+ 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() })
991
+ 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 } })
992
+ 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 } })
993
+ 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' } })
994
+ 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() })
995
+ 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') })
996
+ registerTool('vibe_math_add_problem', 'Add a problem to the current project qs/qs.json.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), async function (args) { const qs = await getQs(); if (qs.some(function (q) { return q.id === args.id })) return { ok: false, message: 'problem id already exists' }; qs.push({ id: args.id, 概述: args.description, 已解决: false, 解法列表: [], 优先级: args.priority || 0, progress: '' }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } })
997
+ registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), async function (args) {
998
+ const p = { id: args.id, 概述: args.概述, 布尔估计: clamp01(args.布尔估计 != null ? args.布尔估计 : 0.5), 细类型: (args.细类型 && typeof args.细类型 === 'object') ? args.细类型 : { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: (args.优先级 != null) ? args.优先级 : 1, '价值/关键性': clamp01(args['价值/关键性'] != null ? args['价值/关键性'] : 0.5), progress: '用户手动添加。' }
999
+ await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) }
1000
+ })
1001
+ registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), async function () { const all = await getPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 概述: p.概述, 布尔估计: p.布尔估计, 优先级: p.优先级, '价值/关键性': p['价值/关键性'], category: p._category } }) } })
1002
+ registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), async function (args) { const slug = slugify(args.name); return await setProject(slug, true) })
1003
+ registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), async function (args) { const slug = slugify(args.name); return await setProject(slug, false) })
1004
+ registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
1005
+ registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), async function () { return { ok: true, decisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }) } })
1006
+ registerTool('vibe_math_decide', 'Resolve a pending manual decision (verdict override uses verdict: 1|0).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id }); if (!d) return { ok: false, message: 'decision not found' }; if (d.status !== 'pending') return { ok: false, message: 'decision already resolved' }; const resolution = { action: args.action, verdict: args.verdict }; const applied = await applyDecision(d.node, d.data, resolution); const r = await resolveDecision(args.id, resolution); return Object.assign({ ok: true, applied: applied }, r) })
1007
+ registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), async function () { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round, rId: m.rId }) } return { ok: true, agents: out, count: out.length } })
1008
+ registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), async function (args) { if (!agentRegistry[args.childId]) return { ok: false, message: 'unknown childId' }; await followupChild(args.childId, args.message); return { ok: true, message: 'message delivered' } })
1009
+ registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok: true, message: 'interrupt requested' } })
1010
+
1011
+ // ================= slash command /vibe =================
1012
+ async function dispatchVibeCommand(cmd, args, agent) {
1013
+ if (cmd === 'start') return await startScheduler(agent)
1014
+ if (cmd === 'resume') return await resumeScheduler(agent)
1015
+ if (cmd === 'pause') return await pauseScheduler()
1016
+ if (cmd === 'abort') return await abortScheduler()
1017
+ if (cmd === 'status') return await getStatus()
1018
+ if (cmd === 'report') { await maybeWriteReport(true); return await buildReport() }
1019
+ if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); return { ok: true, mode: params.mode } }
1020
+ 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' } }
1021
+ if (cmd === 'save') return await saveSettings()
1022
+ if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
1023
+ if (cmd === 'add') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add <id> <description>' }; const qs = await getQs(); if (qs.some(function (q) { return q.id === id })) return { ok: false, message: 'problem id already exists' }; qs.push({ id: id, 概述: desc, 已解决: false, 解法列表: [], 优先级: 0, progress: '' }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } }
1024
+ if (cmd === 'project') {
1025
+ if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
1026
+ if (args[0] === 'new') return await setProject(slugify(args.slice(1).join(' ')), true)
1027
+ return await setProject(slugify(args[0]), false)
1028
+ }
1029
+ if (cmd === 'decisions') return { ok: true, decisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }) }
1030
+ if (cmd === 'agents') { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round }) } return { ok: true, agents: out } }
1031
+ return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <desc> | project [list|new <name>|<name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
1032
+ }
1033
+ ctx.effect(() => commands.register({
1034
+ name: 'vibe',
1035
+ description: 'control the Vibe Math V2 solver (start/pause/projects/setup/save/decisions/agents)',
1036
+ input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|project [list|new <name>|<name>]|decisions|agents]' },
1037
+ handler: async function (invocation) {
1038
+ const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
1039
+ const parts = line.length > 0 ? line.split(/\s+/) : []
1040
+ const cmd = parts[0] || ''
1041
+ const rest = parts.slice(1)
1042
+ const result = await dispatchVibeCommand(cmd, rest, invocation.agent)
1043
+ return { kind: 'success', text: JSON.stringify(result, null, 2) }
1044
+ },
1045
+ }))
1046
+ }