dsh-vibe-math 0.3.18 → 0.3.19

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.
@@ -1,1449 +1,1452 @@
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
- // Standing mount: DSH mounts each agent preset ONCE per preset and joins every
23
- // session that names it to that SAME plugin instance (see @deepseek-ai/dsh-agent-presets).
24
- // This plugin must therefore isolate ALL per-session state itself, keyed by the
25
- // root agent (session) id — otherwise two sessions running the preset at the same
26
- // time (e.g. project A and project B) would share one rootAgent/scheduler/registry
27
- // and spawn children under the wrong parent session. Each session gets its own
28
- // Session instance below via makeSession(rootAgent, sessionId).
29
- export function apply(ctx) {
30
- const subagents = ctx.subagents
31
- const agents = ctx.agents
32
- const fs = ctx.fs
33
- const tools = ctx.tools
34
- const commands = ctx.commands
35
- const subprocess = ctx.get('subprocess')
36
- const sandboxPolicy = ctx.get('sandboxPolicy')
37
-
38
- // ================= per-session registry =================
39
- const sessions = new Map() // rootAgentId -> Session
40
- const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
41
-
42
- function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
43
- // Walk up the durable session lineage to the top-level (root) agent of this session,
44
- // so calls from a child agent (which inherits this preset) still route to its session.
45
- function rootOf(agent) {
46
- try {
47
- let cur = agent
48
- const seen = new Set()
49
- while (cur) {
50
- const id = cur.id
51
- if (seen.has(id)) return cur
52
- seen.add(id)
53
- const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
54
- if (parentId === undefined) return cur
55
- const parent = agents.get(parentId)
56
- if (!parent) return cur
57
- cur = parent
58
- }
59
- } catch (e) { /* fall through */ }
60
- return agent
61
- }
62
- function getSession(agent) {
63
- const root = rootOf(agent)
64
- const sid = sessionIdOf(root)
65
- if (sid === undefined) return undefined
66
- let s = sessions.get(sid)
67
- if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
68
- return s
69
- }
70
-
71
- // ================= per-session plugin body =================
72
- function makeSession(rootAgent, sessionId) {
73
- let currentProject = 'default'
74
- const DEFAULT_PARAMS = {
75
- mode: 'auto', // auto | manual
76
- maxParallelThreshold: 4, // concurrency gate: active turns < this
77
- solverMaxRounds: 3, // per-direction iteration cap (spec example)
78
- directionsPerSolver: 1, // 每个 solver 提示词附带的方向数量(1 = 只看自己方向)
79
- verifierCount: 3, // independent reviewers per verification
80
- debateMaxRounds: 5, // debate round cap (spec example)
81
- verdictMode: 'flat', // flat = 均衡机制(0.5) | forced = 强制裁决(weighted)
82
- provider: '',
83
- model: '',
84
- solverPersona: '',
85
- verifierPersona: '',
86
- explorerPersona: '', // 注入每个 explorer/rederive 提示词开头的人格/要求
87
- knowledgeContext: '', // 共享知识/数据模型说明(空 = 使用内置完整版;非空 = 覆盖)
88
- solverToolAllow: [],
89
- solverToolDeny: [],
90
- verifierToolAllow: [],
91
- verifierToolDeny: [],
92
- solverAllowNetwork: '', // '' = 继承全部;true = 允许网络工具;false = 禁止
93
- verifierAllowNetwork: '',
94
- solverAllowScripts: '', // '' = 继承全部;true = 允许脚本工具;false = 禁止
95
- verifierAllowScripts: '',
96
- solverMaxToolCalls: 0,
97
- verifierMaxToolCalls: 0,
98
- reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 定时自动汇报(毫秒)
99
- reportMode: 'file', // file | push | both
100
- promoteValueThreshold: 0.7, // Propos → qs auto-promotion threshold (价值/关键性)
101
- priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
102
- proposPriorityAdjust: 'none', // none | progress-graded(按定论接近度+证明/证伪材料量动态调命题优先级)
103
- tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
104
- activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
105
- maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
106
- }
107
- let params = Object.assign({}, DEFAULT_PARAMS)
108
- let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
109
- let agentRegistry = {}
110
- let decisionQueue = []
111
- let verifierAccuracy = {}
112
- let tasks = {} // verify tasks keyed by 'verify:<rId>'
113
- let activityLog = []
114
- let lastReportWrite = 0
115
- let lastPushReport = 0
116
- let reportDirty = false
117
- let tickInFlight = false
118
- let lastTickAt = 0
119
- let explorerRetries = {}
120
- // Process epoch: written to state at init; a DIFFERENT persisted epoch means a
121
- // previous DSH process wrote this state (in-flight children are gone), while an
122
- // equal epoch means same-process pause→resume (children may still be alive).
123
- const processEpoch = String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8)
124
-
125
- // ================= helpers =================
126
- function textBlock(t) { return { type: 'text', text: String(t) } }
127
- function now() { return Date.now() }
128
- 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 }
129
- function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
130
- function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
131
- 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 '.' }
132
- function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
133
- function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
134
- function frameworkRoot() { return projectRoot(currentProject) }
135
- function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
136
- function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
137
- 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 }
138
- function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
139
- 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() }
140
- function parseJson(text) {
141
- if (typeof text !== 'string') return undefined
142
- const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
143
- const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
144
- let m
145
- while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
146
- const whole = tryObj(text.trim()); if (whole !== undefined) return whole
147
- let best = undefined; let bestLen = -1
148
- for (let start = 0; start < text.length; start++) {
149
- if (text[start] !== '{') continue
150
- let depth = 0, inStr = false, esc = false, end = -1
151
- for (let i = start; i < text.length; i++) {
152
- const c = text[i]
153
- if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
154
- if (c === '"') { inStr = true; continue }
155
- if (c === '{') depth++
156
- else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
157
- }
158
- if (end === -1) continue
159
- const obj = tryObj(text.slice(start, end + 1))
160
- if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
161
- }
162
- return best
163
- }
164
- function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
165
- 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 }
166
-
167
- // ================= parameter schema =================
168
- const PARAM_SCHEMA = [
169
- { name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
170
- { name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
171
- { name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
172
- { name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向(互不干扰);N>1 = 额外附带最多 N 个其他活跃方向摘要用于协调', suggestion: 1 },
173
- { name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量', suggestion: 3 },
174
- { name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
175
- { name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致直接判 0.5);forced = 强制裁决(按历史准确率+严谨性加权)', suggestion: 'flat' },
176
- { name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
177
- { name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
178
- { name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
179
- { name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
180
- { name: 'explorerPersona', type: 'string', description: '注入每个 explorer/重派生提示词开头的人格/要求', suggestion: '' },
181
- { name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖,注入 explorer/solver/verifier 提示词)', suggestion: '' },
182
- { name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
183
- { name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
184
- { name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
185
- { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
186
- { name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow 列表时补入网络工具);false=禁止 web_search/web/fetch', suggestion: '' },
187
- { name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关(同 solverAllowNetwork)', suggestion: '' },
188
- { name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止 bash/pwsh', suggestion: '' },
189
- { name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关(同 solverAllowScripts)', suggestion: '' },
190
- { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
191
- { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
192
- { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
193
- { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
194
- { name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
195
- { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
196
- { name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
197
- { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
198
- { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
199
- { name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
200
- ]
201
-
202
- // ================= fs =================
203
- async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
204
- 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 } }
205
- async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
206
- async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
207
- async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
208
- 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 [] } }
209
- 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 [] } }
210
- 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 } }
211
- 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 } }
212
- async function readCurrentProject() {
213
- // 按会话隔离的 current 文件(多会话并行时互不覆盖);无则回退旧共享文件
214
- try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s !== undefined) { 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) {}
215
- 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' }
216
- }
217
- async function writeCurrentProject() {
218
- try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) {}
219
- }
220
-
221
- // ================= subprocess =================
222
- function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
223
- 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) } } }
224
- 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') }
225
- async function removeFile(rel) { const base = frameworkRoot(); return await runShell('Remove-Item -Force -LiteralPath ' + psQuote(base + '/' + rel) + ' -ErrorAction SilentlyContinue') }
226
-
227
- // ================= settings =================
228
- function sanitizeParams(obj) {
229
- const out = {}
230
- const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
231
- const numFields = ['promoteValueThreshold']
232
- const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
233
- for (const k of Object.keys(DEFAULT_PARAMS)) {
234
- if (!(k in obj)) continue
235
- const v = obj[k]
236
- if (intFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.floor(n) : DEFAULT_PARAMS[k] }
237
- 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] }
238
- else if (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
239
- else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
240
- else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
241
- else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
242
- else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
243
- else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
244
- else if (k === 'solverAllowNetwork' || k === 'verifierAllowNetwork' || k === 'solverAllowScripts' || k === 'verifierAllowScripts') { out[k] = (v === true || v === false || v === '') ? v : DEFAULT_PARAMS[k] }
245
- else { out[k] = v }
246
- }
247
- return out
248
- }
249
- async function loadSettings() {
250
- let text = await readText('vibe_math_setting.json')
251
- if (text === undefined) text = await readTextAbs(vibeRoot() + '/vibe_math_setting.json')
252
- if (text === undefined) return
253
- const clean = stripJsonComments(text)
254
- try {
255
- const obj = JSON.parse(clean)
256
- if (obj && typeof obj === 'object' && !Array.isArray(obj)) params = Object.assign({}, params, sanitizeParams(obj))
257
- } catch (e) {
258
- console.error('vibe-math-v2: invalid vibe_math_setting.json ignored: ' + String((e && e.message) || e))
259
- }
260
- }
261
- function settingsTemplateFrom(src) {
262
- const lines = []
263
- lines.push('{')
264
- lines.push(' // Vibe Math V2 默认参数配置(JSON with Comments,可加 // 注释)。')
265
- lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
266
- lines.push(' // 本文件是参数的唯一持久化来源:vibe_math_set_params / set_mode 会立即写回此文件;全局文件仅作项目文件不存在时的回退默认。')
267
- const keys = Object.keys(src).sort()
268
- for (let i = 0; i < keys.length; i++) {
269
- const k = keys[i]
270
- const v = src[k]
271
- const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
272
- const desc = schema ? schema.description : ''
273
- const comma = i === keys.length - 1 ? '' : ','
274
- lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
275
- lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
276
- }
277
- lines.push('}')
278
- return lines.join('\n') + '\n'
279
- }
280
- function settingsTemplate() { return settingsTemplateFrom(params) }
281
- async function saveSettings() { await writeText('vibe_math_setting.json', settingsTemplate()); return { ok: true, path: frameworkRoot() + '/vibe_math_setting.json' } }
282
- 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' } }
283
-
284
- // ================= persistence =================
285
- async function loadState() {
286
- const s = await readJson('VibeMath_State/scheduler_state.json'); if (s) scheduler = Object.assign({}, scheduler, s)
287
- const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r
288
- const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq
289
- const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va
290
- const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk
291
- const er = await readJson('VibeMath_State/explorer_retries.json'); if (er) explorerRetries = er
292
- }
293
- async function saveAll() {
294
- await writeJson('VibeMath_State/scheduler_state.json', scheduler)
295
- await writeJson('VibeMath_State/agent_registry.json', agentRegistry)
296
- await writeJson('VibeMath_State/decision_queue.json', decisionQueue)
297
- await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
298
- await writeJson('VibeMath_State/tasks.json', tasks)
299
- await writeJson('VibeMath_State/explorer_retries.json', explorerRetries)
300
- scheduler.lastCheckpoint = now()
301
- }
302
- // 查询类工具(status/setup/report)汇报前重读设置文件:文件是唯一持久化源,可能在会话启动后被用户手改或由本进程外编辑更新。
303
- async function refreshParams() {
304
- params = Object.assign({}, DEFAULT_PARAMS)
305
- await loadSettings()
306
- await migrateLegacyParams()
307
- }
308
- // 一次性迁移(单文件化):旧版 VibeMath_State/params.json 中的运行时参数合并进 vibe_math_setting.json 后删除。
309
- async function migrateLegacyParams() {
310
- const legacy = await readJson('VibeMath_State/params.json')
311
- if (!legacy || Object.keys(legacy).length === 0) return
312
- try {
313
- params = Object.assign({}, params, sanitizeParams(legacy))
314
- await saveSettings()
315
- await writeJson('VibeMath_State/params.json', {}) // 一次性守卫:即使删除失败,空对象也不再覆盖设置文件
316
- await removeFile('VibeMath_State/params.json')
317
- logActivity('params', 'legacy params.json merged into vibe_math_setting.json and removed')
318
- } catch (e) { console.error('vibe-math-v2: params migration failed: ' + String((e && e.message) || e)) }
319
- }
320
-
321
- // ================= data layer: qs.json =================
322
- async function getQs() { const a = await readJson('qs/qs.json'); return Array.isArray(a) ? a : [] }
323
- async function writeQs(list) { await writeJson('qs/qs.json', list) }
324
- async function findQ(qid) { const qs = await getQs(); return qs.find(function (q) { return q.id === qid }) }
325
-
326
- // progress:结构化 JSON 对象(旧数据可能是 JSON 字符串,两者兼容解析)。
327
- // 注意:必须保证返回对象含 directions 数组(晋升/判断/子问题等 progress 可能只有来源/说明等字段)。
328
- function parseProgress(q) {
329
- const raw = (q && q.progress) || null
330
- let p = null
331
- if (raw && typeof raw === 'object' && !Array.isArray(raw)) p = raw
332
- else p = safeJson(raw, null)
333
- if (!p || typeof p !== 'object' || Array.isArray(p)) return { directions: [], experience: '' }
334
- if (!Array.isArray(p.directions)) p.directions = []
335
- return p
336
- }
337
- async function saveProgress(qid, progObj) { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return; q.progress = progObj; await writeQs(qs) }
338
-
339
- // ================= data layer: Propos =================
340
- function categoryOf(p) { const t = (p && p.细类型) || {}; const keys = Object.keys(t); return (keys.length > 0 && typeof t[keys[0]] === 'object') ? keys[0] : '未分类' }
341
- function proposFile(cat) { return 'Propos/' + String(cat) + '_Propos.json' }
342
- async function proposFiles() { return await listFiles('Propos') }
343
- async function readProposCategory(cat) { const a = await readJson(proposFile(cat)); return Array.isArray(a) ? a : [] }
344
- async function getPropos() {
345
- const out = []
346
- const files = await proposFiles()
347
- for (let i = 0; i < files.length; i++) {
348
- const fname = files[i]
349
- const cat = fname.replace(/_Propos\.json$/i, '')
350
- const list = await readProposCategory(cat)
351
- for (let j = 0; j < list.length; j++) { list[j]._category = cat; out.push(list[j]) }
352
- }
353
- return out
354
- }
355
- async function findProposition(pId) { const all = await getPropos(); return all.find(function (p) { return p.id === pId }) }
356
- async function upsertProposition(p) {
357
- const cat = p._category || categoryOf(p)
358
- delete p._category
359
- const list = await readProposCategory(cat)
360
- const idx = list.findIndex(function (x) { return x.id === p.id })
361
- if (idx !== -1) list[idx] = p; else list.push(p)
362
- await writeJson(proposFile(cat), list)
363
- return cat
364
- }
365
- async function deleteProposition(p) {
366
- const cat = p._category || categoryOf(p)
367
- const list = await readProposCategory(cat)
368
- const next = list.filter(function (x) { return x.id !== p.id })
369
- await writeJson(proposFile(cat), next)
370
- }
371
-
372
- // ================= data layer: Verified / Reliable =================
373
- async function readVerifiedCategory(cat) { const a = await readJson('Verified/' + String(cat) + '_Verified.json'); return Array.isArray(a) ? a : [] }
374
- async function reliableFiles() { return await listFiles('Reliable') }
375
-
376
- // ================= reporting =================
377
- 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 }
378
- async function buildReport() {
379
- const qs = await getQs()
380
- const propos = await getPropos()
381
- return {
382
- ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
383
- running: scheduler.running, mode: params.mode,
384
- activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
385
- problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
386
- propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
387
- pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
388
- registeredAgents: Object.keys(agentRegistry).length,
389
- recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
390
- params: params,
391
- }
392
- }
393
- async function maybeWriteReport(force) {
394
- const interval = Number(params.reportIntervalMs) || 0
395
- if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
396
- if (!force && interval <= 0 && !reportDirty) return
397
- await writeJson('Progress_Logs/report.json', await buildReport())
398
- lastReportWrite = now(); reportDirty = false
399
- }
400
- async function maybePushReport(force) {
401
- const mode = params.reportMode || 'file'
402
- if (mode !== 'push' && mode !== 'both') return
403
- const interval = Number(params.reportIntervalMs) || 0
404
- if (interval > 0) {
405
- // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
406
- if (!force && (now() - lastPushReport) < interval) return
407
- } else if (!force && !reportDirty) {
408
- // event-driven: only push when an event happened since the last report
409
- return
410
- }
411
- if (!rootAgent || typeof rootAgent.followup !== 'function') return
412
- try {
413
- const report = await buildReport()
414
- const text = '[Vibe Math V2] 进度更新:当前项目 "' + currentProject + '" 运行中=' + report.running +
415
- ',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
416
- '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
417
- '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
418
- rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
419
- lastPushReport = now()
420
- } catch (e) {
421
- console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
422
- }
423
- }
424
-
425
- // ================= child spawn / followup =================
426
- 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' }
427
- 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 }
428
- const NETWORK_TOOLS = ['web_search', 'web', 'fetch']
429
- const SCRIPT_TOOLS = ['bash', 'pwsh']
430
- 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 net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; if (net === false) d = d.concat(NETWORK_TOOLS); else if (net === true && a.length > 0) a = a.concat(NETWORK_TOOLS); if (scr === false) d = d.concat(SCRIPT_TOOLS); else if (scr === true && a.length > 0) a = a.concat(SCRIPT_TOOLS); const f = {}; if (a.length > 0) f.allow = a; if (d.length > 0) f.deny = d; return (f.allow || f.deny) ? f : undefined }
431
- async function spawnChild(label, promptText, meta) {
432
- const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
433
- const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf
434
- let started
435
- try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) }
436
- catch (e) {
437
- 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 }
438
- }
439
- agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
440
- childOwner.set(started.childId, sessionId)
441
- scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
442
- await saveAll(); return started.childId
443
- }
444
- 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() }
445
- async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
446
-
447
- // ================= prompts =================
448
- function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
449
- function verifierPersonaText() { return params.verifierPersona ? (String(params.verifierPersona) + '\n\n') : '' }
450
- function explorerPersonaText() { return params.explorerPersona ? (String(params.explorerPersona) + '\n\n') : '' }
451
- // 共享知识/数据模型说明(点6):完整解释各对象/属性含义、概率语义、文件夹用途、输出要求。
452
- // 空参数 = 使用内置完整版;非空 = 由用户覆盖(点8)。
453
- function defaultKnowledgeContext() {
454
- return 'KNOWLEDGE BASE & DATA MODEL (definition contract you MUST follow):\n' +
455
- '\n1) PROBABILITY SEMANTICS — the single most important rule:\n' +
456
- '- 正确概率 / 布尔估计 ∈ [0,1]。\n' +
457
- '- 1 = 绝对正确(已被证明且验证通过):你可以把它当作已知事实/可信结论直接用于推理。\n' +
458
- '- 0 = 绝对错误(已被证伪且验证通过)。\n' +
459
- '- 0 与 1 之间的任何值 = 未定论/待验证:只能作为参考证据,绝不能当作已成立的事实引用。\n' +
460
- '- Verified/ 中的卡片概率恒为 1 或 0,内容可信、可直接引用。\n' +
461
- '\n2) OBJECT MODELS (按实现方案):\n' +
462
- '- 问题 PROBLEM(qs/qs.json):{ id, 概述(完整问题陈述,所提到的每个对象/记号都要给出完整定义), 已解决(bool), 解法列表:[{ 完整解法(详细步骤), 正确概率, 已验 }], 优先级(整数,越小越优先调度;"never"=永不调度), progress(历史:已试方向、各方向路线、阻碍及原因、教训、可行性评估)}。\n' +
463
- '- 命题 PROPOSITION(Propos/<分类>_Propos.json):{ id, 概述(完整陈述), 布尔估计(该命题为真的概率), 细类型(分类 JSON), 证明列表:[{ 完整过程(完整证明), 正确概率, 支持信息/依据 }], 证伪列表:[{ 完整过程(完整证伪), 正确概率, 支持信息/依据 }], 优先级, 价值/关键性(0-1,重要性), progress(过往尝试与教训)}。\n' +
464
- '- 收口规则:问题的某个解法 正确概率=1 → 问题已解决;命题的证明/证伪条目 正确概率=1 → 命题布尔估计=1/0(已定论)。\n' +
465
- '\n3) FOLDERS (per project, VibeMath/Projects/<project>/):\n' +
466
- '- qs/qs.json:问题清单——求解与验证的唯一问题来源。\n' +
467
- '- Propos/<分类>_Propos.json:命题知识库(已有认知)。\n' +
468
- '- Reliable/:可信参考文献(只读)。\n' +
469
- '- Verified/<分类>_Verified.json:定论事实索引——布尔估计=0/1 的命题卡片与已解决问题卡片;内容可信、可直接使用。\n' +
470
- '- Verification_logs/:辩论记录。Progress_Logs/:进度与报告。VibeMath_State/:调度器私有状态——不要读也不要改。\n' +
471
- '\n4) OUTPUT REQUIREMENTS (你输出的每个对象必须满足):\n' +
472
- '- 完整性、不断章取义:任何你写出的问题/命题/结论都要给出完整陈述,并把它所依赖的对象、环境、背景、定义全部补全(例如提到某个序列/函数/定理时给出其完整定义与假设)。\n' +
473
- '- 引用溯源:若你引用了 qs/qs.json、Propos/、Verified/、Reliable/ 中已有的命题/引理/结论/解法,必须给出出处——具体文件路径(相对项目根,如 Propos/数论_Propos.json 或 Verified/未分类_Verified.json)+ 对象 id 或 JSON 路径(如 .证明列表[0] 或 .directions[1]);没有出处的引用一律不允许。你自己新提出的结论则必须自带完整定义,不得引用未定义的内容。\n' +
474
- '- 若结论依赖某个临时假设 p,必须显式写成「若 <p 的完整陈述> 成立,则:...」(同样要定义完整)。\n' +
475
- '- 只输出规定的 JSON(放在 ```json 代码围栏内),JSON 之外不写任何内容。\n' +
476
- '- 示例(完整问题 概述):"设 {a_n} 为非负实数序列(n≥1),满足:对任意正整数 n 都存在 i,j 使 |a_i − a_j| = 1/n^p(p>0 为实参数)。判断:p 在什么范围内保证级数 ∑_{n=1}^∞ a_n 发散?" —— 每个记号(序列、参数、级数)都在句内定义完整,读它的人无需再查背景。\n' +
477
- '- 示例(完整命题 概述):"设函数 f:[0,1]→R 连续,则 f 在 [0,1] 上有界(连续性按 ε-δ 定义,有界性按标准实数分析定义)。" —— 概念与对象定义完整,不引用未定义的记号。\n'
478
- }
479
- function knowledgeContextText() { const k = params.knowledgeContext ? String(params.knowledgeContext) : defaultKnowledgeContext(); return k ? ('\n' + k + '\n') : '' }
480
- function capabilitiesText(role) {
481
- const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
482
- const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
483
- const scrOn = role === 'solver' ? params.solverAllowScripts : params.verifierAllowScripts
484
- const toolParts = []
485
- if (netOn !== false) toolParts.push('web search / literature lookup')
486
- if (scrOn !== false) toolParts.push('symbolic/numeric computation (running scripts)')
487
- let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
488
- t += '- Network tools (web search / fetch): ' + (netOn === false ? 'DISABLED for you' : 'available') + '; Script/shell tools (bash/pwsh): ' + (scrOn === false ? 'DISABLED for you' : 'available') + ' (your actual tool list is enforced by the framework).\n'
489
- t += toolParts.length > 0
490
- ? ('- You may use external tools (' + toolParts.join(', ') + ') to assist; ' + ((maxCalls && Number(maxCalls) > 0) ? ('call such external tools AT MOST ' + maxCalls + ' times this round.\n') : 'no per-round limit by default.\n'))
491
- : '- External tools: none enabled for you this round.\n'
492
- t += '- You may READ any file under Verified/ as a known, trusted dependency (resolved facts).\n'
493
- t += '- You should BASE your reasoning on the existing knowledge under Propos/ (propositions with proofs/refutations and probabilities) and Reliable/ (trusted references).\n'
494
- t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer.\n'
495
- t += '\nHOW TO READ EXISTING KNOWLEDGE (coarse scan → fine read):\n'
496
- t += '- These are JSON files. A conclusion object carries summary-index fields (概述 / 布尔估计 / 优先级) and the full detail (证明列表 / 证伪列表 / 完整过程 / progress).\n'
497
- 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'
498
- 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'
499
- return t
500
- }
501
- function explorerPrompt(q) {
502
- return explorerPersonaText() + 'You are a research mathematician orchestrating strategy for one problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.概述 + '\n\n' +
503
- knowledgeContextText() +
504
- capabilitiesText('solver') +
505
- '\nDo a first-stage METACOGNITIVE BRAINSTORM: decompose constraints, test boundary/extreme cases, map to similar known problems. ' +
506
- 'Then propose 3-6 DIVERSE, mutually distinct solution directions (e.g. analytic method, constructive proof, contradiction, numeric approximation + limit passage, categorical abstraction, ...). ' +
507
- 'Record each direction with its core assumption and an initial feasibility estimate.\n\n' +
508
- 'feasibility ∈ [0,1]: your estimate of the probability this direction leads to a full solution. Every direction must be self-contained and unambiguous: title / method / core_assumption written completely, defining every object they mention — no 断章取义, no undefined symbols.\n\n' +
509
- 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
510
- '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}]}'
511
- }
512
- function rederivePrompt(q, prog) {
513
- const prior = prog.directions.map(function (d) {
514
- return '- ' + d.id + '「' + d.title + '」status=' + d.status + ' round=' + d.round + ' survival=' + d.survival + (d.dead_end_reason ? ' [blocker: ' + d.dead_end_reason + ']' : '') +
515
- (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '')
516
- }).join('\n')
517
- return explorerPersonaText() + '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' +
518
- knowledgeContextText() +
519
- capabilitiesText('solver') +
520
- '\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). ' +
521
- 'Then deeply DERIVE 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. ' +
522
- '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' +
523
- 'feasibility ∈ [0,1] as above. Every returned direction (kept or new) must be self-contained and unambiguous, with complete definitions — no 断章取义.\n\n' +
524
- 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
525
- '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5,"motivation":"..."}]}'
526
- }
527
- function directionSummary(d) {
528
- return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
529
- ' survival=' + d.survival +
530
- (d.lemmas && d.lemmas.length ? ' | lemmas: ' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join('; ') : '') +
531
- (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '') +
532
- (d.lessons && d.lessons.length ? ' | lessons: ' + d.lessons.join('; ') : '') +
533
- (d.blockers && d.blockers.length ? ' | blockers: ' + d.blockers.join('; ') : '')
534
- }
535
- // 每个 solver 可见的方向总数 = directionsPerSolver(默认 1 = 只看自己方向,互不干扰)。
536
- // round 1 时自己的方向由 DIRECTION 行给出、占用 1 个名额;round>1 时自己的历史进度摘要占用 1 个名额;
537
- // 其余名额填充其他活跃方向摘要(n=3 → 自己 + 最多 2 个其他方向)。
538
- function buildSolverContext(all, own, round, perSolver) {
539
- const out = []
540
- const n = Math.max(1, Number(perSolver) || 1)
541
- let slots = n
542
- if (round > 1) { out.push(own); slots -= 1 }
543
- else slots -= 1 // round 1:自己的方向已由 DIRECTION 行给出
544
- const others = all.filter(function (d) { return d.id !== own.id && d.status === 'active' })
545
- for (let i = 0; i < others.length && slots > 0; i++) { out.push(others[i]); slots -= 1 }
546
- return out.map(directionSummary).join('\n')
547
- }
548
- function solverPrompt(q, dir, round, progressText) {
549
- let head = solverPersonaText() + 'You are a dedicated solver agent working ONE solution direction of a math problem (agent_self_iteration).\n\n'
550
- head += 'PROBLEM (id: ' + q.id + '): ' + q.概述 + '\nDIRECTION: ' + dir.title + ' (method: ' + dir.method + '; core assumption: ' + dir.core_assumption + ')\nROUND: ' + round + ' of ' + params.solverMaxRounds + '\n'
551
- if (round > 1 || (progressText && progressText.length)) head += '\nYOUR PRIOR PROGRESS / OTHER DIRECTIONS:\n' + progressText + '\n'
552
- head += knowledgeContextText()
553
- head += capabilitiesText('solver')
554
- 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' +
555
- '- new lemmas / intermediate conclusions WITH full proofs (these go to the Propos/ knowledge base);\n' +
556
- '- 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' +
557
- '- lessons learned from failed attempts (what to avoid, what did not work and why);\n' +
558
- '- an updated survival probability for this direction.\n'
559
- head += '\nIf you encounter an EXTREMELY complex auxiliary conjecture/sub-problem q_sub: list it in "sub_questions" as a PROBLEM-class object with its COMPLETE statement (every object/definition/notation it mentions must be fully defined — never quote partially, 不断章取义), together with p_{q-tmp}: a PROPOSITION-class TEMPORARY ASSUMPTION that is one possible answer to q_sub. TEMPORARILY ASSUME p_{q-tmp} holds and continue the main line — every later proposition/conclusion that depends on this assumption MUST be stated as "若 <p_{q-tmp} 的完整陈述> 成立,则:..." (with complete definitions). The scheduler registers q_sub and the problem "判断下述命题是否成立:p_{q-tmp}" in the problem list, and p_{q-tmp} in the proposition base.\n'
560
- head += '\nIMPORTANT — PROBABILITY RULES FOR NEW RESULTS: any 布尔估计 / solution_probability / survival_probability you output for NEW results must be strictly BETWEEN 0 and 1 (they await independent verifier confirmation). NEVER mark your own fresh lemma or solution as 1 or 0 — that is the verifiers\' job. Only facts already recorded in Verified/ (or 正确概率=1 entries you READ from files) count as certain.\n'
561
- head += '- Each lemma you output must carry a COMPLETE statement ("statement") and a COMPLETE proof ("proof"): define every object/notation it uses — no 断章取义, no undefined symbols. If a lemma/conclusion references or is derived from existing knowledge (Propos/Verified/Reliable/qs files), state the source file path + object id / JSON path inside the statement — no unsourced references.\n'
562
- 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'
563
- head += '\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
564
- '{"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":"..."}],"lessons":["..."],"survival_probability":0.5,"dead_end_reason":"... or null","sub_questions":[{"q_sub_title":"...","q_sub_statement":"完整问题陈述(含所有对象/定义)","assumption_title":"p_{q-tmp} 标题","assumption_statement":"完整假设陈述(含所有定义)"}]}'
565
- return head
566
- }
567
- function verifierReviewPrompt(r) {
568
- let target = ''
569
- if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
570
- else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
571
- else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
572
- return verifierPersonaText() + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + target + '\n' +
573
- knowledgeContextText() +
574
- capabilitiesText('verifier') +
575
- '\nResult ∈ [0,1] = your probability that the TARGET is CORRECT: 1 ONLY when you are fully certain (for a bare proposition: Reason must be a complete proof; for a proof/refutation/solution: you verified every step and Reason confirms the whole chain); 0 ONLY when you are certain it is wrong (Reason must be a rigorous complete refutation / pinpoint the fatal flaw); otherwise a value strictly between 0 and 1.\n' +
576
- '\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
577
- '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"}'
578
- }
579
- function verifierDebatePrompt(r, transcript) {
580
- let target = ''
581
- if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
582
- else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
583
- else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
584
- return verifierPersonaText() + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + target + '\n' +
585
- knowledgeContextText() +
586
- capabilitiesText('verifier') +
587
- '\nFULL DEBATE HISTORY SO FAR (每轮所有评审轮流发言的记录):\n' + transcript + '\n' +
588
- '\nRespond to the others (agree / rebut / add new evidence, referencing earlier rounds if needed). If you changed your Result because of them, state the reason explicitly.\n' +
589
- 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
590
- '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"}'
591
- }
592
-
593
- // ================= decisions (manual/auto) =================
594
- 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 }
595
- 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 } }
596
- async function applyDecision(node, data, resolution) {
597
- if (node === 'spawn') {
598
- if (resolution.action === 'approve') { await spawnChild(data.label, data.promptText, data.meta); return { spawned: true } }
599
- try {
600
- const meta = data.meta || {}
601
- if (meta.role === 'explorer' && meta.qid) {
602
- const q = await findQ(meta.qid)
603
- 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) }
604
- } else if (meta.role === 'solver' && meta.qid && meta.direction) {
605
- const q = await findQ(meta.qid)
606
- 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) }
607
- }
608
- } catch (e) { console.error('vibe-math-v2: spawn reject mark failed: ' + String((e && e.message) || e)) }
609
- return { spawned: false, rejected: true }
610
- }
611
- 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 } }
612
- return {}
613
- }
614
- 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' } }
615
-
616
- // ================= scheduler core =================
617
- function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v2 tick error: ' + String((e && e.stack) || e)) }) }
618
- async function tick() {
619
- if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return
620
- tickInFlight = true
621
- lastTickAt = now()
622
- try {
623
- await processStatusUpdates()
624
- await processPriorityAdjust()
625
- await processPromote()
626
- await processVerify()
627
- await reconcileVerify()
628
- await processSolve()
629
- await maybePushReport(false)
630
- await maybeWriteReport(false)
631
- const qs = await getQs()
632
- const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
633
- if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
634
- scheduler.running = false
635
- logActivity('stop', 'all active problems solved (never-priority problems excluded) and no active agents/tasks — scheduler stopped (strict termination)')
636
- await saveAll(); await maybeWriteReport(true); await maybePushReport(true)
637
- } else if (Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0 && unsolved.length > 0) {
638
- let allBlocked = true
639
- for (let i = 0; i < unsolved.length; i++) {
640
- const prog = parseProgress(unsolved[i])
641
- const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
642
- const hasActive = prog.directions.some(function (d) { return d.status === 'active' })
643
- const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= (Number(params.maxExplorerRetries) || 3)
644
- if (hasActive || !blocked) { allBlocked = false; break }
645
- }
646
- if (allBlocked) {
647
- scheduler.running = false
648
- logActivity('stall', 'no feasible direction remains for any unsolved problem — scheduler paused (stalled, NOT all solved)')
649
- await saveAll(); await maybeWriteReport(true)
650
- }
651
- }
652
- } finally { tickInFlight = false }
653
- }
654
- // note 4: probability-1 rules
655
- async function processStatusUpdates() {
656
- const qs = await getQs()
657
- let qsChanged = false
658
- for (let i = 0; i < qs.length; i++) {
659
- const q = qs[i]
660
- if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) { qsChanged = true; await writeVerifiedProblemCardIfNeeded(q) } q.已解决 = true; q.优先级 = 'never' }
661
- }
662
- if (qsChanged) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
663
- const propos = await getPropos()
664
- let closedPromoted = false
665
- for (let i = 0; i < propos.length; i++) {
666
- const p = propos[i]
667
- let pChanged = false
668
- const proofOne = (p.证明列表 || []).some(function (x) { return x.正确概率 === 1 })
669
- const refuteOne = (p.证伪列表 || []).some(function (x) { return x.正确概率 === 1 })
670
- if (proofOne && p.布尔估计 !== 1) { p.布尔估计 = 1; pChanged = true }
671
- else if (refuteOne && p.布尔估计 !== 0) { p.布尔估计 = 0; pChanged = true }
672
- if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
673
- if (p.布尔估计 === 1 || p.布尔估计 === 0) {
674
- if (await writeVerifiedCardIfNeeded(p)) pChanged = true
675
- // 源命题已定论 → 关闭其晋升出的"僵尸"问题(优先用 判断命题 字段;兼容旧文本标记数据)
676
- for (let j = 0; j < qs.length; j++) {
677
- const qj = qs[j]
678
- if (qj.已解决) continue
679
- if (qj.判断命题 === p.id) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
680
- else if (String((qj.progress && typeof qj.progress === 'object' ? (qj.progress.来源命题 || '') : qj.progress) || '').indexOf(p.id) !== -1) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
681
- }
682
- }
683
- if (pChanged) await upsertProposition(p)
684
- }
685
- if (closedPromoted) { await writeQs(qs); logActivity('update', 'promoted problems closed because their source proposition resolved') }
686
- }
687
- async function processPriorityAdjust() {
688
- const mode = params.priorityAdjust || 'none'
689
- if (mode !== 'none') {
690
- const qs = await getQs()
691
- let changed = false
692
- for (let i = 0; i < qs.length; i++) {
693
- const q = qs[i]
694
- if (q.已解决 || q.优先级 === 'never') continue
695
- const prog = parseProgress(q)
696
- if (mode === 'deadend-deprioritize') {
697
- if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
698
- const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
699
- }
700
- } else if (mode === 'survival-map') {
701
- if (prog.directions.length > 0) {
702
- const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
703
- const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
704
- if (q.优先级 !== target) { q.优先级 = target; changed = true }
705
- }
706
- }
707
- }
708
- if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
709
- }
710
- const pMode = params.proposPriorityAdjust || 'none'
711
- if (pMode === 'progress-graded') {
712
- const propos = await getPropos()
713
- const changedProps = []
714
- for (let i = 0; i < propos.length; i++) {
715
- const p = propos[i]
716
- if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
717
- const closeness = Math.abs(Number(p.布尔估计) - 0.5)
718
- const material = Math.min(5, (p.证明列表 || []).length + (p.证伪列表 || []).length)
719
- const score = closeness * 1.2 + material * 0.08
720
- const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * score)))
721
- const cur = Number(p.优先级)
722
- if (Number.isFinite(cur) && cur !== target) { p.优先级 = target; changedProps.push(p) }
723
- }
724
- if (changedProps.length > 0) {
725
- for (let i = 0; i < changedProps.length; i++) await upsertProposition(changedProps[i])
726
- logActivity('priority', 'proposition priorities auto-adjusted (progress-graded)')
727
- }
728
- }
729
- }
730
- // note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
731
- async function processPromote() {
732
- if (scheduler.activeCount >= params.maxParallelThreshold) return
733
- const qs = await getQs()
734
- const qDescriptions = qs.map(function (q) { return q.概述 })
735
- const propos = await getPropos()
736
- for (let i = 0; i < propos.length; i++) {
737
- const p = propos[i]
738
- if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
739
- if (Number(p['价值/关键性']) < Number(params.promoteValueThreshold)) continue
740
- if (p.在问题清单) continue
741
- if (qDescriptions.indexOf(p.概述) !== -1) continue
742
- if (qDescriptions.indexOf('判断下述命题是否成立:' + p.概述) !== -1) continue
743
- const qid = 'q-promoted-' + String(p.id).replace(/[^a-z0-9\-]/gi, '').slice(-12)
744
- // 点3:证明/证伪列表 → 解法列表(条目前加【证明】/【证伪】前缀),保留概率/已验并记录来源以便回写联动
745
- const sols = []
746
- const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
747
- for (let j = 0; j < proofs.length; j++) { const it = proofs[j]; sols.push({ 完整解法: '【证明】' + (it.完整过程 || ''), 正确概率: clamp01(it.正确概率 != null ? it.正确概率 : 0.5), 已验: !!it.已验, 来源: '由命题晋升(证明#' + j + ')', 来源命题: p.id, 来源列表: '证明', 来源索引: j, 验证记录: [] }) }
748
- for (let j = 0; j < refutes.length; j++) { const it = refutes[j]; sols.push({ 完整解法: '【证伪】' + (it.完整过程 || ''), 正确概率: clamp01(it.正确概率 != null ? it.正确概率 : 0.5), 已验: !!it.已验, 来源: '由命题晋升(证伪#' + j + ')', 来源命题: p.id, 来源列表: '证伪', 来源索引: j, 验证记录: [] }) }
749
- qs.push({ id: qid, 概述: '判断下述命题是否成立:' + p.概述, 已解决: false, 解法列表: sols, 优先级: 1, 判断命题: p.id, 细类型: (p.细类型 && typeof p.细类型 === 'object') ? p.细类型 : {}, '价值/关键性': p['价值/关键性'], progress: { 来源: 'promote', 来源命题: p.id, 说明: '由命题 ' + p.id + '(价值/关键性=' + p['价值/关键性'] + ')自动晋升;目标:证明或证伪该命题(解法列表中的【证明】/【证伪】条目即原命题的证明/证伪材料,验证结果会回写源命题)。' } })
750
- p.在问题清单 = true
751
- await upsertProposition(p)
752
- await writeQs(qs)
753
- logActivity('promote', 'proposition ' + p.id + ' promoted to problem ' + qid + '(' + sols.length + ' 条证明/证伪转为解法)')
754
- return // one per tick is enough
755
- }
756
- }
757
- async function processVerify() {
758
- const candidates = await buildVerifyCandidates()
759
- for (let i = 0; i < candidates.length; i++) {
760
- if (scheduler.activeCount >= params.maxParallelThreshold) break
761
- const c = candidates[i]
762
- const rId = c.rId
763
- if (tasks['verify:' + rId]) continue
764
- const inflight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'verifier' && m.rId === rId })
765
- if (inflight) continue
766
- tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }
767
- await saveAll()
768
- return // one verification at a time keeps scheduling simple; tick will continue next pass
769
- }
770
- }
771
- async function buildVerifyCandidates() {
772
- const out = []
773
- const qs = await getQs()
774
- for (let i = 0; i < qs.length; i++) {
775
- const q = qs[i]
776
- if (q.已解决 || q.优先级 === 'never') continue
777
- const sols = q.解法列表 || []
778
- for (let j = 0; j < sols.length; j++) {
779
- const s = sols[j]
780
- if (s.正确概率 === 1 || s.正确概率 === 0 || s.已验) continue
781
- if (!String(s.完整解法 || '').trim()) continue
782
- out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
783
- }
784
- }
785
- const propos = await getPropos()
786
- for (let i = 0; i < propos.length; i++) {
787
- const p = propos[i]
788
- if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
789
- if (p.在问题清单) continue // 已晋升:其证明/证伪经晋升问题的解法验证,避免同一内容双重验证
790
- const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
791
- if (proofs.length === 0 && refutes.length === 0) {
792
- if (String(p.id).indexOf('p-tmp-') === 0) continue // 临时假设由「判断下述命题是否成立:p_{q-tmp}」问题统一验证,避免裸命题验证双重路径
793
- out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
794
- } else {
795
- for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; if (!String(proofs[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
796
- for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; if (!String(refutes[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
797
- }
798
- }
799
- out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
800
- return out
801
- }
802
- async function backfillVerifiers(t) {
803
- while (t.children.length < t.expectedCount) {
804
- if (scheduler.activeCount >= params.maxParallelThreshold) break
805
- const index = t.children.length
806
- const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
807
- t.children.push(childId)
808
- }
809
- if (t.children.length >= t.expectedCount) t.status = 'debating'
810
- }
811
- async function reconcileVerify() {
812
- const ids = Object.keys(tasks)
813
- for (let i = 0; i < ids.length; i++) {
814
- const t = tasks[ids[i]]
815
- if (t.type !== 'verify') continue
816
- if (t.status === 'paused') {
817
- const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
818
- if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
819
- }
820
- if (t.status !== 'spawning') continue
821
- if (scheduler.activeCount >= params.maxParallelThreshold) break
822
- await backfillVerifiers(t)
823
- }
824
- }
825
- async function processSolve() {
826
- if (scheduler.activeCount >= params.maxParallelThreshold) return
827
- const qs = await getQs()
828
- 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.优先级)) })
829
- for (let i = 0; i < unsolved.length; i++) {
830
- if (scheduler.activeCount >= params.maxParallelThreshold) break
831
- const q = unsolved[i]
832
- const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
833
- if (busy) continue
834
- const prog = parseProgress(q)
835
- const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
836
- if (prog.directions.length === 0 || allExhausted) {
837
- const explorerCap = Number(params.maxExplorerRetries) || 3
838
- if ((explorerRetries[q.id] || 0) >= explorerCap) {
839
- 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 + ' 次未产出方向' })
840
- await saveProgress(q.id, prog)
841
- logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
842
- continue
843
- }
844
- explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
845
- const promptText = (prog.directions.length > 0) ? rederivePrompt(q, prog) : explorerPrompt(q)
846
- 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 } })
847
- if (r && r.gated) return
848
- } else {
849
- // spawn solvers for each active direction
850
- for (let j = 0; j < prog.directions.length; j++) {
851
- if (scheduler.activeCount >= params.maxParallelThreshold) break
852
- const dir = prog.directions[j]
853
- if (dir.status === 'success' || dir.status === 'dead-end') continue
854
- 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' })
855
- if (running) continue
856
- const progressText = buildSolverContext(prog.directions, dir, 1, params.directionsPerSolver)
857
- const promptText = solverPrompt(q, dir, 1, progressText)
858
- 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 } })
859
- if (r && r.gated) return
860
- }
861
- }
862
- }
863
- }
864
-
865
- // ================= solver (agent_self_iteration) handling =================
866
- async function handleExplorer(childId, meta, output) {
867
- delete agentRegistry[childId]
868
- const parsed = parseJson(output)
869
- const dirs = (parsed && parsed.directions) || []
870
- if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions (output head: ' + String(output || '').slice(0, 200) + ')'); await saveAll(); return }
871
- explorerRetries[meta.qid] = 0
872
- const q = await findQ(meta.qid); if (!q) return
873
- const prog = parseProgress(q)
874
- prog.directions = dirs.map(function (d) {
875
- 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: '' }
876
- })
877
- await saveProgress(meta.qid, prog)
878
- logActivity('explorer', 'problem ' + meta.qid + ' → ' + prog.directions.length + ' directions')
879
- }
880
- async function handleSolver(childId, meta, output, stopReason) {
881
- const qid = meta.qid; const dirId = meta.direction
882
- const parsed = parseJson(output)
883
- const q = await findQ(qid); if (!q) { delete agentRegistry[childId]; return }
884
- const prog = parseProgress(q)
885
- const dir = prog.directions.find(function (d) { return d.id === dirId })
886
- if (!dir) { delete agentRegistry[childId]; return }
887
- if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } // abort:不把方向标记为死路,保留待 resume
888
- const status = (parsed && parsed.status) || statusFromStop(stopReason)
889
- dir.round = meta.round
890
- if (parsed) {
891
- if (parsed.routes) dir.routes = (dir.routes || []).concat(parsed.routes)
892
- if (parsed.lessons) dir.lessons = (dir.lessons || []).concat(parsed.lessons)
893
- if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
894
- if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
895
- if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) { const lid = await addLemmaAsProposition(qid, parsed.lemmas[i]); if (lid) { dir.lemmas = dir.lemmas || []; dir.lemmas.push({ id: lid, title: parsed.lemmas[i].title || '' }) } } }
896
- if (parsed.sub_questions && parsed.sub_questions.length) { for (let i = 0; i < parsed.sub_questions.length; i++) { const sq = parsed.sub_questions[i]; if (sq && sq.q_sub_statement && dir.sub_questions && dir.sub_questions.some(function (x) { return x.statement === sq.q_sub_statement })) continue; const rec = await addSubQuestion(qid, dirId, sq); if (rec) { dir.sub_questions = dir.sub_questions || []; dir.sub_questions.push(rec) } } }
897
- }
898
- if (status === 'success') {
899
- if (parsed && parsed.solution) {
900
- dir.status = 'success'
901
- delete agentRegistry[childId]
902
- logActivity('solver', qid + '/' + dirId + ' success at round ' + meta.round)
903
- await addSolution(qid, parsed.solution, parsed.solution_probability)
904
- } else {
905
- // claimed success without a solution text — treat as an incomplete round
906
- if (meta.round >= params.solverMaxRounds) {
907
- dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || 'claimed success without solution at iteration cap'
908
- delete agentRegistry[childId]
909
- logActivity('solver', qid + '/' + dirId + ' dead-end (success without solution)')
910
- } else {
911
- const progressText = buildSolverContext(prog.directions, dir, meta.round + 1, params.directionsPerSolver)
912
- await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
913
- agentRegistry[childId].round = meta.round + 1
914
- dir.round = meta.round + 1
915
- }
916
- }
917
- } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) {
918
- dir.status = 'dead-end'
919
- if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'
920
- delete agentRegistry[childId]
921
- logActivity('solver', qid + '/' + dirId + ' dead-end: ' + dir.dead_end_reason)
922
- } else {
923
- const progressText = buildSolverContext(prog.directions, dir, meta.round + 1, params.directionsPerSolver)
924
- if (!scheduler.running) {
925
- // paused/aborted: stop the follow-up chain; keep the direction active for resume
926
- delete agentRegistry[childId]
927
- } else {
928
- try {
929
- await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
930
- agentRegistry[childId].round = meta.round + 1
931
- dir.round = meta.round + 1
932
- } catch (e) {
933
- console.error('vibe-math-v2: solver followup failed: ' + String((e && e.message) || e))
934
- dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || '求解器续轮失败(followup 异常)'
935
- delete agentRegistry[childId]
936
- }
937
- }
938
- }
939
- await saveProgress(qid, prog)
940
- }
941
- function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
942
- async function addLemmaAsProposition(qid, lemma) {
943
- if (!lemma || !lemma.title) return
944
- let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
945
- if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
946
- const p = {
947
- id: 'p-' + shortId(), 概述: lemma.statement || lemma.title,
948
- 布尔估计: be,
949
- 细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
950
- 证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
951
- 证伪列表: [], 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1,
952
- '价值/关键性': clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
953
- progress: { 来源: 'solver-lemma', 问题: qid, 说明: '由求解器针对问题 ' + qid + ' 的方向迭代产出。' }, 来源问题: qid,
954
- }
955
- await upsertProposition(p)
956
- logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
957
- return p.id
958
- }
959
- // 点5(q_sub 严格化):solver 报告子问题 q_sub 时,注册三个对象:
960
- // 1) q_sub 本身(问题类,完整陈述)入 qs.json;
961
- // 2) p_{q-tmp}(命题类临时假设:对 q_sub 的某种回答)入 Propos/;
962
- // 3) 「判断下述命题是否成立:p_{q-tmp}」(问题类)入 qs.json。
963
- // 返回 {subId,judgeId,assumeId},由 handleSolver 在最终保存 progress 时记入方向(避免旧 progress 覆盖丢记录)。
964
- async function addSubQuestion(qid, dirId, sq) {
965
- if (!sq || !sq.q_sub_statement) return undefined
966
- const q = await findQ(qid)
967
- if (q) {
968
- const prog = parseProgress(q)
969
- const d = prog.directions.find(function (x) { return x.id === dirId })
970
- // 去重:同一方向已注册过相同陈述的 q_sub 则跳过(避免多轮重复上报产生重复问题/假设)
971
- if (d && d.sub_questions && d.sub_questions.some(function (x) { return x.statement === sq.q_sub_statement })) { logActivity('subquestion', 'duplicate q_sub skipped for ' + qid + '/' + dirId); return undefined }
972
- }
973
- const qs = await getQs()
974
- const subId = qid + '-sub-' + shortId()
975
- const assumeId = 'p-tmp-' + shortId()
976
- const judgeId = qid + '-judge-' + shortId()
977
- const assumeStatement = sq.assumption_statement || sq.assumption_title || ('对子问题「' + (sq.q_sub_title || sq.q_sub_statement) + '」的一种回答(临时假设)')
978
- qs.push({ id: subId, 概述: sq.q_sub_statement, 已解决: false, 解法列表: [], 优先级: 1, progress: { 类型: 'sub-question', 来源问题: qid, 来源方向: dirId, 说明: '临时子问题:由问题 ' + qid + ' 方向 ' + dirId + ' 分支产生;求解主线在 p_{q-tmp}(' + assumeId + ')假设下推进。' } })
979
- qs.push({ id: judgeId, 概述: '判断下述命题是否成立:' + assumeStatement, 已解决: false, 解法列表: [], 优先级: 1, 判断命题: assumeId, progress: { 类型: 'judge', 假设命题: assumeId, 说明: '由临时假设 p_{q-tmp}(' + assumeId + ')生成;它是对子问题 ' + subId + ' 的一种回答的命题化。' } })
980
- await writeQs(qs)
981
- const p = {
982
- id: assumeId, 概述: assumeStatement, 布尔估计: 0.5,
983
- 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1,
984
- '价值/关键性': 0.5,
985
- progress: { 类型: 'temporary-assumption', 来源问题: qid, 子问题: subId, 说明: '临时假设 p_{q-tmp}:由问题 ' + qid + ' 方向 ' + dirId + ' 在求解中临时假设其成立以推进主线;若该假设被证伪,则依赖它的主线结论需重新审视。' },
986
- 来源问题: qid,
987
- }
988
- await upsertProposition(p)
989
- logActivity('subquestion', qid + ' → q_sub ' + subId + ' + 判断问题 ' + judgeId + ' + 临时假设 ' + assumeId)
990
- return { subId: subId, judgeId: judgeId, assumeId: assumeId, statement: sq.q_sub_statement }
991
- }
992
- async function addSolution(qid, solutionText, prob) {
993
- const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return
994
- const p = clamp01(prob != null ? prob : 0.8)
995
- const finalProb = p >= 1 ? 0.99 : (p <= 0 ? 0.01 : p) // must be < 1 (待验证器验证)
996
- q.解法列表 = q.解法列表 || []
997
- q.解法列表.push({ 完整解法: String(solutionText), 正确概率: finalProb, 来源: 'solver', 验证记录: [] })
998
- await writeQs(qs)
999
- logActivity('solution', 'problem ' + qid + ' got a candidate solution (probability ' + finalProb + ', awaiting verification)')
1000
- }
1001
-
1002
- // ================= verification (验证器) =================
1003
- 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 }) }
1004
- 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') }
1005
- 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)) }
1006
- async function handleVerifier(childId, meta, output, stopReason) {
1007
- const rId = meta.rId
1008
- const parsed = parseJson(output)
1009
- const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
1010
- const Reason = (parsed && parsed.Reason) || ''
1011
- let t = tasks['verify:' + rId]
1012
- if (!t) { t = { id: 'verify:' + rId, type: 'verify', r: { kind: 'proposition', pId: rId, 概述: rId }, rId: rId, status: 'debating', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
1013
- if (!parsed && !scheduler.running) {
1014
- // abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerify 重建)
1015
- delete agentRegistry[childId]
1016
- const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
1017
- delete t.childResults[childId]
1018
- if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
1019
- return
1020
- }
1021
- if (t.children.indexOf(childId) === -1) t.children.push(childId)
1022
- t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
1023
- delete agentRegistry[childId]
1024
- const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
1025
- if (!allReported) { await saveAll(); return }
1026
- await advanceVerification(t, meta.round)
1027
- await saveAll()
1028
- }
1029
- async function advanceVerification(t, round) {
1030
- if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
1031
- if (!scheduler.running) { t.status = 'paused'; return } // resume will re-advance this task
1032
- if (scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return } // 并发门:等有空闲槽位再辩论(reconcileVerify 会重推进)
1033
- t.round = round + 1
1034
- const roundTranscript = buildTranscript(t)
1035
- t.history = t.history || []
1036
- t.history.push('Round ' + round + ':\n' + roundTranscript)
1037
- const transcript = t.history.join('\n\n')
1038
- const nextChildren = []
1039
- for (let i = 0; i < t.children.length; i++) {
1040
- const cid = t.children[i]
1041
- try {
1042
- await followupChild(cid, verifierDebatePrompt(t.r, transcript))
1043
- agentRegistry[cid] = { role: 'verifier', rId: t.rId, round: round + 1, index: i }
1044
- nextChildren.push(cid)
1045
- } catch (e) {
1046
- console.error('vibe-math-v2: verifier followup failed: ' + String((e && e.message) || e))
1047
- delete agentRegistry[cid]
1048
- delete t.childResults[cid]
1049
- }
1050
- }
1051
- t.children = nextChildren
1052
- if (nextChildren.length === 0) await finalizeVerification(t)
1053
- } else {
1054
- await finalizeVerification(t)
1055
- }
1056
- }
1057
- async function finalizeVerification(t) {
1058
- const verdict = finalVerdict(t)
1059
- if (params.mode === 'manual') {
1060
- const d = enqueueDecision('verdict', 'verdict for ' + t.rId + ' (debate finished) = ' + verdict, { rId: t.rId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
1061
- scheduler.gate = { decisionId: d.id, node: 'verdict' }
1062
- t.status = 'awaiting-verdict'
1063
- } else {
1064
- await settleVerdict(t, verdict)
1065
- delete tasks[t.id]
1066
- }
1067
- }
1068
- function finalVerdict(t) {
1069
- const rs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid] })
1070
- if (rs.length === 0) return 0.5
1071
- if (rs.every(function (r) { return r.Result === 1 })) return 1
1072
- if (rs.every(function (r) { return r.Result === 0 })) return 0
1073
- if (params.verdictMode === 'forced') {
1074
- let num = 0; let den = 0
1075
- const cids = Object.keys(t.childResults)
1076
- for (let i = 0; i < rs.length; i++) {
1077
- const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
1078
- const accRate = acc.total > 0 ? (acc.correct / acc.total) : 0.5
1079
- const confident = (rs[i].Result === 1 || rs[i].Result === 0) ? 0.1 : 0
1080
- const w = Math.max(0.05, Math.min(0.95, accRate + confident))
1081
- num += w * rs[i].Result; den += w
1082
- }
1083
- return den > 0 ? Math.max(0.01, Math.min(0.99, num / den)) : 0.5
1084
- }
1085
- return 0.5 // flat = 均衡机制
1086
- }
1087
- async function settleVerdict(t, verdict) {
1088
- const v = clamp01(verdict)
1089
- const r = t.r
1090
- const cids = Object.keys(t.childResults)
1091
- // update verifier historical accuracy (forced mode audit)
1092
- for (let i = 0; i < cids.length; i++) {
1093
- const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
1094
- acc.total += 1
1095
- if (t.childResults[cids[i]].Result === v) acc.correct += 1
1096
- verifierAccuracy[cids[i]] = acc
1097
- }
1098
- await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
1099
- // debate transcript log
1100
- await writeJson('Verification_logs/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
1101
-
1102
- if (r.kind === 'proposition') {
1103
- const p = await findProposition(r.pId)
1104
- if (p) {
1105
- p.布尔估计 = v
1106
- if (v === 1) { p.证明列表 = p.证明列表 || []; p.证明列表.push({ 完整过程: strongestReason(t, 1), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
1107
- else if (v === 0) { p.证伪列表 = p.证伪列表 || []; p.证伪列表.push({ 完整过程: strongestReason(t, 0), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
1108
- else {
1109
- p.证明列表 = p.证明列表 || []; p.证伪列表 = p.证伪列表 || []
1110
- p.证明列表.push({ 完整过程: strongestReason(t, 1) || '根据辩论得到的支持性论证', 正确概率: v, '支持信息/依据': '', 已验: true })
1111
- p.证伪列表.push({ 完整过程: strongestReason(t, 0) || '根据辩论得到的反驳性论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
1112
- }
1113
- await upsertProposition(p)
1114
- await writeVerifiedCardIfNeeded(p)
1115
- }
1116
- } else if (r.kind === 'prop-proof') {
1117
- const p = await findProposition(r.pId)
1118
- if (p) {
1119
- const list = r.side === '证明' ? (p.证明列表 = p.证明列表 || []) : (p.证伪列表 = p.证伪列表 || [])
1120
- const item = list[r.idx]
1121
- if (item) {
1122
- item.正确概率 = v
1123
- item.已验 = true
1124
- if (v === 1) { item['支持信息/依据'] = strongestReason(t, 1) || item['支持信息/依据'] }
1125
- else if (v === 0) {
1126
- const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
1127
- other.push({ 完整过程: strongestReason(t, 0) || '', 正确概率: 1, '支持信息/依据': '判定 ' + r.side + ' 错误后的反证', 已验: true })
1128
- } else {
1129
- const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
1130
- other.push({ 完整过程: strongestReason(t, v >= 0.5 ? 0 : 1) || '辩论得出的相反方向论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
1131
- item['支持信息/依据'] = strongestReason(t, v >= 0.5 ? 1 : 0) || item['支持信息/依据']
1132
- }
1133
- }
1134
- await upsertProposition(p)
1135
- await writeVerifiedCardIfNeeded(p)
1136
- }
1137
- } else if (r.kind === 'problem-solution') {
1138
- const qs = await getQs(); const q = qs.find(function (x) { return x.id === r.qid }); if (q) {
1139
- const sol = (q.解法列表 || [])[r.idx]
1140
- if (sol) {
1141
- sol.正确概率 = v
1142
- sol.已验 = true
1143
- sol.验证记录 = sol.验证记录 || []
1144
- sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
1145
- // 点3 回写联动:晋升问题的解法验证结果同步回源命题的证明/证伪条目(含内容比对防错位)
1146
- if (sol.来源命题 && (sol.来源列表 === '证明' || sol.来源列表 === '证伪')) {
1147
- const sp = await findProposition(sol.来源命题)
1148
- if (sp) {
1149
- const slist = sol.来源列表 === '证明' ? (sp.证明列表 || []) : (sp.证伪列表 || [])
1150
- const item = slist[sol.来源索引]
1151
- if (item && item.完整过程 === String(sol.完整解法 || '').replace(/^【(证明|证伪)】/, '')) {
1152
- item.正确概率 = v; item.已验 = true
1153
- await upsertProposition(sp)
1154
- logActivity('promote-sync', 'promoted solution verdict ' + v + ' synced to proposition ' + sp.id + ' ' + sol.来源列表 + '#' + sol.来源索引)
1155
- }
1156
- }
1157
- }
1158
- // 点5 联动:「判断下述命题是否成立:X」问题的**新解法**验证结果 → X 命题收口
1159
- // (转移条目走上面来源联动 + processStatusUpdates 按证明/证伪侧向收口;这里只处理无来源的新解法)
1160
- if (q.判断命题 && !sol.来源列表) {
1161
- const ap = await findProposition(q.判断命题)
1162
- if (ap) {
1163
- ap.布尔估计 = v
1164
- if (v === 1) { ap.证明列表 = ap.证明列表 || []; ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1165
- else if (v === 0) { ap.证伪列表 = ap.证伪列表 || []; ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1166
- await upsertProposition(ap)
1167
- await writeVerifiedCardIfNeeded(ap)
1168
- logActivity('judge-sync', 'judge problem verdict ' + v + ' synced to proposition ' + ap.id)
1169
- }
1170
- }
1171
- if (v === 1) { q.已解决 = true; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q) }
1172
- }
1173
- await writeQs(qs)
1174
- }
1175
- }
1176
- logActivity('verdict', t.rId + ' = ' + v + (v === 1 ? ' (fully verified)' : v === 0 ? ' (refuted)' : ' (uncertain)'))
1177
- }
1178
- function strongestReason(t, wantTrue) {
1179
- const cids = Object.keys(t.childResults)
1180
- let best = ''; let bestDist = -1
1181
- for (let i = 0; i < cids.length; i++) {
1182
- const res = t.childResults[cids[i]]
1183
- const dist = wantTrue ? res.Result : 1 - res.Result
1184
- if (dist > bestDist && res.Reason) { bestDist = dist; best = res.Reason }
1185
- }
1186
- return best
1187
- }
1188
- // 点4:Verified 卡片 = 每对象一卡,内容 = 该对象当前所有正确概率=1 的证明/证伪完整过程;
1189
- // 新 1-概率条目出现时自动更新(内容不变则不写)。幂等键 = 对象 id。
1190
- async function writeVerifiedCardIfNeeded(p) {
1191
- if (p.布尔估计 !== 1 && p.布尔估计 !== 0) return false
1192
- const cat = categoryOf(p)
1193
- const list = await readVerifiedCategory(cat)
1194
- const idx = list.findIndex(function (c) { return c.id === p.id })
1195
- const proofs1 = (p.证明列表 || []).filter(function (x) { return x.正确概率 === 1 })
1196
- const refutes1 = (p.证伪列表 || []).filter(function (x) { return x.正确概率 === 1 })
1197
- const parts = []
1198
- for (let i = 0; i < proofs1.length; i++) parts.push('【证明 #' + (i + 1) + '】' + (proofs1[i].完整过程 || ''))
1199
- for (let i = 0; i < refutes1.length; i++) parts.push('【证伪 #' + (i + 1) + '】' + (refutes1[i].完整过程 || ''))
1200
- const card = {
1201
- id: p.id, 概述: p.概述, 类型: '命题', 结论: p.布尔估计 === 1, 概率: p.布尔估计,
1202
- 内容: parts.join('\n'), 证明条数: proofs1.length, 证伪条数: refutes1.length,
1203
- 来源: p.来源问题 || '', 时间: now(), 分类: cat,
1204
- }
1205
- if (idx === -1) list.push(card)
1206
- else { if (list[idx].内容 === card.内容 && list[idx].概率 === card.概率) return false; list[idx] = card }
1207
- await writeJson('Verified/' + cat + '_Verified.json', list)
1208
- return idx === -1
1209
- }
1210
- async function writeVerifiedProblemCardIfNeeded(q) {
1211
- if (!q || !q.已解决) return false
1212
- const cat = '问题'
1213
- const list = await readVerifiedCategory(cat)
1214
- const idx = list.findIndex(function (c) { return c.id === q.id })
1215
- const sols1 = (q.解法列表 || []).filter(function (s) { return s.正确概率 === 1 })
1216
- const parts = []
1217
- for (let i = 0; i < sols1.length; i++) parts.push('【解法 #' + (i + 1) + '】' + (sols1[i].完整解法 || ''))
1218
- const card = { id: q.id, 概述: q.概述, 类型: '问题', 结论: true, 概率: 1, 内容: parts.join('\n'), 解法条数: sols1.length, 来源: q.id, 时间: now(), 分类: cat }
1219
- if (idx === -1) list.push(card)
1220
- else { if (list[idx].内容 === card.内容) return false; list[idx] = card }
1221
- await writeJson('Verified/' + cat + '_Verified.json', list)
1222
- return idx === -1
1223
- }
1224
-
1225
- // ================= child result dispatch =================
1226
- async function onChildEnd(info) {
1227
- const meta = agentRegistry[info.id]
1228
- if (meta === undefined) return
1229
- scheduler.activeCount = Math.max(0, scheduler.activeCount - 1)
1230
- const output = blocksToText(info.lastAssistantMessage)
1231
- try {
1232
- if (meta.role === 'explorer') await handleExplorer(info.id, meta, output)
1233
- else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason)
1234
- else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason)
1235
- } catch (e) { console.error('vibe-math-v2 onChildEnd error: ' + String((e && e.stack) || e)) }
1236
- await saveAll()
1237
- scheduleTick()
1238
- }
1239
-
1240
- // ================= init / control =================
1241
- async function init(fresh) {
1242
- if (!rootAgent) return { ok: false, message: 'no root agent available' }
1243
- currentProject = await readCurrentProject(); await ensureDirs()
1244
- if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
1245
- params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState()
1246
- // Distinguish same-process continue from cross-process restart via processEpoch:
1247
- // equal epoch = same process (pause→resume; children may still be alive), different
1248
- // epoch = previous process wrote this state (in-flight children are gone).
1249
- const prevEpoch = await readJson('VibeMath_State/process_epoch.json')
1250
- const stale = typeof prevEpoch === 'string' && prevEpoch !== processEpoch
1251
- if (fresh || stale) {
1252
- if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]) }
1253
- if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
1254
- logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
1255
- agentRegistry = {}; tasks = {}
1256
- }
1257
- scheduler.activeCount = 0 // 仅清空 registry/tasks 时归零;同进程 resume 保留存活计数(并发门才准确)
1258
- }
1259
- await writeJson('VibeMath_State/process_epoch.json', processEpoch)
1260
- await saveAll()
1261
- return { ok: true }
1262
- }
1263
- async function startScheduler() { const r = await init(true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
1264
- async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
1265
- async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
1266
- 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 } }
1267
- // auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
1268
- async function autoResolvePending() {
1269
- const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
1270
- for (let i = 0; i < pending.length; i++) {
1271
- const d = pending[i]
1272
- try {
1273
- if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1274
- else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1275
- } catch (e) { console.error('vibe-math-v2: auto-resolve decision failed: ' + String((e && e.message) || e)) }
1276
- }
1277
- if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
1278
- }
1279
- async function getStatus() {
1280
- const qs = await getQs(); const propos = await getPropos()
1281
- return {
1282
- ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
1283
- project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
1284
- mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
1285
- frameworkRoot: frameworkRoot(),
1286
- problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
1287
- propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
1288
- pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
1289
- registeredAgents: Object.keys(agentRegistry).length,
1290
- recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
1291
- }
1292
- }
1293
-
1294
- // ================= projects =================
1295
- async function setProject(slug, create) {
1296
- if (!rootAgent) return { ok: false, message: 'no root agent available' }
1297
- const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
1298
- if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
1299
- if (scheduler.running) await abortScheduler()
1300
- currentProject = slug; await writeCurrentProject(); await ensureDirs()
1301
- if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
1302
- 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
1303
- await loadSettings(); await migrateLegacyParams(); await loadState(); await saveAll()
1304
- return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
1305
- }
1306
-
1307
- // ================= events / timer =================
1308
- // NOTE: subagent/end listener and the tick timer are registered ONCE at the
1309
- // apply level (below), routing through childOwner/sessions — NOT here, because
1310
- // the standing-mount plugin instance is shared by every session.
1311
-
1312
- // ================= tools =================
1313
- function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
1314
- const handlers = {}
1315
- function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
1316
- registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
1317
- registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
1318
- registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
1319
- registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
1320
- registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
1321
- registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
1322
- registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
1323
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { 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' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
1324
- registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { await refreshParams(); 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' } })
1325
- 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() })
1326
- 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') })
1327
- 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: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } })
1328
- 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) {
1329
- 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: { 来源: 'user', 说明: '用户手动添加。' } }
1330
- await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) }
1331
- })
1332
- 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 } }) } })
1333
- 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) })
1334
- 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) })
1335
- registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
1336
- 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 } }) } })
1337
- 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) })
1338
- 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 } })
1339
- 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' } })
1340
- 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' } })
1341
-
1342
- // ================= slash command /vibe =================
1343
- async function dispatchVibeCommand(cmd, args) {
1344
- if (cmd === 'start') return await startScheduler()
1345
- if (cmd === 'resume') return await resumeScheduler()
1346
- if (cmd === 'pause') return await pauseScheduler()
1347
- if (cmd === 'abort') return await abortScheduler()
1348
- if (cmd === 'status') { await refreshParams(); return await getStatus() }
1349
- if (cmd === 'report') { await refreshParams(); await maybeWriteReport(true); return await buildReport() }
1350
- if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
1351
- if (cmd === 'setup') { await refreshParams(); 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' } }
1352
- if (cmd === 'save') return await saveSettings()
1353
- if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
1354
- 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: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } }
1355
- if (cmd === 'add-proposition') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const p = { id: id, 概述: desc, 布尔估计: 0.5, 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1, '价值/关键性': 0.5, progress: { 来源: 'user-vibe', 说明: '用户通过 /vibe 添加。' } }; await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) } }
1356
- if (cmd === 'list-propositions') { 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 } }) } }
1357
- if (cmd === 'project') {
1358
- if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
1359
- if (args[0] === 'new') return await setProject(slugify(args.slice(1).join(' ')), true)
1360
- return await setProject(slugify(args[0]), false)
1361
- }
1362
- 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 } }) }
1363
- 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 } }
1364
- return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <desc> | add-proposition <id> <概述> | list-propositions | project [list|new <name>|<name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
1365
- }
1366
-
1367
- // ================= session surface =================
1368
- return {
1369
- sessionId: sessionId,
1370
- scheduler: scheduler,
1371
- tickInFlight: tickInFlight,
1372
- scheduleTick: scheduleTick,
1373
- onChildEnd: onChildEnd,
1374
- dispatchVibeCommand: dispatchVibeCommand,
1375
- handlers: handlers,
1376
- // 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
1377
- refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
1378
- getRunning: function () { return scheduler.running },
1379
- // 会话自己的心跳节流:timer 每 1s 询问是否到点;tick 执行时刷新 lastTickAt
1380
- tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
1381
- }
1382
- }
1383
-
1384
- // ================= apply-level registrations (ONCE per preset) =================
1385
- function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
1386
- function registerTool(name, description, parameters, handlerName) {
1387
- ctx.effect(() => tools.register({
1388
- name: name, description: description, parameters: parameters,
1389
- output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
1390
- execute: async function (args, exec) {
1391
- try {
1392
- const s = getSession(exec && exec.agent)
1393
- if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
1394
- await s.refreshProject()
1395
- return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
1396
- } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
1397
- },
1398
- }))
1399
- }
1400
- registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), 'vibe_math_start')
1401
- registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
1402
- registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
1403
- registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
1404
- registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
1405
- registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
1406
- registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), 'vibe_math_set_mode')
1407
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { 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' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' } }), 'vibe_math_set_params')
1408
- registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
1409
- registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
1410
- 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'] } }), 'vibe_math_template')
1411
- 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']), 'vibe_math_add_problem')
1412
- 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', '概述']), 'vibe_math_add_proposition')
1413
- registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), 'vibe_math_list_propositions')
1414
- registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
1415
- registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
1416
- registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
1417
- registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
1418
- 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']), 'vibe_math_decide')
1419
- registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
1420
- registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), 'vibe_math_message_agent')
1421
- registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
1422
-
1423
- // /vibe slash command (registered once; routed per session)
1424
- ctx.effect(() => commands.register({
1425
- name: 'vibe',
1426
- description: 'control the Vibe Math V2 solver (start/pause/projects/setup/save/decisions/agents/propositions)',
1427
- input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|project [list|new <name>|<name>]|decisions|agents]' },
1428
- handler: async function (invocation) {
1429
- const s = getSession(invocation && invocation.agent)
1430
- if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
1431
- const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
1432
- const parts = line.length > 0 ? line.split(/\s+/) : []
1433
- const cmd = parts[0] || ''
1434
- const rest = parts.slice(1)
1435
- const result = await s.dispatchVibeCommand(cmd, rest)
1436
- return { kind: 'success', text: JSON.stringify(result, null, 2) }
1437
- },
1438
- }))
1439
-
1440
- // subagent/end (registered once; routed to the owning session via childOwner)
1441
- ctx.on('subagent/end', function (info) {
1442
- const sid = childOwner.get(info.id)
1443
- const s = sid !== undefined ? sessions.get(sid) : undefined
1444
- if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) })
1445
- })
1446
-
1447
- // tick timer (registered once; ticks every running session at its own pace)
1448
- ctx.effect(() => { const t = setInterval(function () { for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() } }, 1000); return () => clearInterval(t) })
1449
- }
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
+ // Standing mount: DSH mounts each agent preset ONCE per preset and joins every
23
+ // session that names it to that SAME plugin instance (see @deepseek-ai/dsh-agent-presets).
24
+ // This plugin must therefore isolate ALL per-session state itself, keyed by the
25
+ // root agent (session) id — otherwise two sessions running the preset at the same
26
+ // time (e.g. project A and project B) would share one rootAgent/scheduler/registry
27
+ // and spawn children under the wrong parent session. Each session gets its own
28
+ // Session instance below via makeSession(rootAgent, sessionId).
29
+ export function apply(ctx) {
30
+ const subagents = ctx.subagents
31
+ const agents = ctx.agents
32
+ const fs = ctx.fs
33
+ const tools = ctx.tools
34
+ const commands = ctx.commands
35
+ const subprocess = ctx.get('subprocess')
36
+ const sandboxPolicy = ctx.get('sandboxPolicy')
37
+
38
+ // ================= per-session registry =================
39
+ const sessions = new Map() // rootAgentId -> Session
40
+ const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
41
+ // Process epoch: PROCESS-level (one per apply, shared by every session), written to
42
+ // VibeMath_State/process_epoch.json at init; a DIFFERENT persisted epoch means a
43
+ // previous DSH process wrote this state (in-flight children are gone), while an
44
+ // equal epoch means same-process pause→resume (children may still be alive).
45
+ // Kept at apply level (not per-session) to match the 0.3.17 semantics: two sessions
46
+ // in the same process must never treat each other as a stale previous process.
47
+ const processEpoch = String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8)
48
+
49
+ function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
50
+ // Walk up the durable session lineage to the top-level (root) agent of this session,
51
+ // so calls from a child agent (which inherits this preset) still route to its session.
52
+ function rootOf(agent) {
53
+ try {
54
+ let cur = agent
55
+ const seen = new Set()
56
+ while (cur) {
57
+ const id = cur.id
58
+ if (seen.has(id)) return cur
59
+ seen.add(id)
60
+ const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
61
+ if (parentId === undefined) return cur
62
+ const parent = agents.get(parentId)
63
+ if (!parent) return cur
64
+ cur = parent
65
+ }
66
+ } catch (e) { /* fall through */ }
67
+ return agent
68
+ }
69
+ function getSession(agent) {
70
+ const root = rootOf(agent)
71
+ const sid = sessionIdOf(root)
72
+ if (sid === undefined) return undefined
73
+ let s = sessions.get(sid)
74
+ if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
75
+ return s
76
+ }
77
+
78
+ // ================= per-session plugin body =================
79
+ function makeSession(rootAgent, sessionId) {
80
+ let currentProject = 'default'
81
+ const DEFAULT_PARAMS = {
82
+ mode: 'auto', // auto | manual
83
+ maxParallelThreshold: 4, // concurrency gate: active turns < this
84
+ solverMaxRounds: 3, // per-direction iteration cap (spec example)
85
+ directionsPerSolver: 1, // 每个 solver 提示词附带的方向数量(1 = 只看自己方向)
86
+ verifierCount: 3, // independent reviewers per verification
87
+ debateMaxRounds: 5, // debate round cap (spec example)
88
+ verdictMode: 'flat', // flat = 均衡机制(0.5) | forced = 强制裁决(weighted)
89
+ provider: '',
90
+ model: '',
91
+ solverPersona: '',
92
+ verifierPersona: '',
93
+ explorerPersona: '', // 注入每个 explorer/rederive 提示词开头的人格/要求
94
+ knowledgeContext: '', // 共享知识/数据模型说明(空 = 使用内置完整版;非空 = 覆盖)
95
+ solverToolAllow: [],
96
+ solverToolDeny: [],
97
+ verifierToolAllow: [],
98
+ verifierToolDeny: [],
99
+ solverAllowNetwork: '', // '' = 继承全部;true = 允许网络工具;false = 禁止
100
+ verifierAllowNetwork: '',
101
+ solverAllowScripts: '', // '' = 继承全部;true = 允许脚本工具;false = 禁止
102
+ verifierAllowScripts: '',
103
+ solverMaxToolCalls: 0,
104
+ verifierMaxToolCalls: 0,
105
+ reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 定时自动汇报(毫秒)
106
+ reportMode: 'file', // file | push | both
107
+ promoteValueThreshold: 0.7, // Propos → qs auto-promotion threshold (价值/关键性)
108
+ priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
109
+ proposPriorityAdjust: 'none', // none | progress-graded(按定论接近度+证明/证伪材料量动态调命题优先级)
110
+ tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
111
+ activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
112
+ maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
113
+ }
114
+ let params = Object.assign({}, DEFAULT_PARAMS)
115
+ let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
116
+ let agentRegistry = {}
117
+ let decisionQueue = []
118
+ let verifierAccuracy = {}
119
+ let tasks = {} // verify tasks keyed by 'verify:<rId>'
120
+ let activityLog = []
121
+ let lastReportWrite = 0
122
+ let lastPushReport = 0
123
+ let reportDirty = false
124
+ let tickInFlight = false
125
+ let lastTickAt = 0
126
+ let explorerRetries = {}
127
+
128
+ // ================= helpers =================
129
+ function textBlock(t) { return { type: 'text', text: String(t) } }
130
+ function now() { return Date.now() }
131
+ 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 }
132
+ function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
133
+ function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
134
+ 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 '.' }
135
+ function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
136
+ function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
137
+ function frameworkRoot() { return projectRoot(currentProject) }
138
+ function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
139
+ function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
140
+ 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 }
141
+ function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
142
+ 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() }
143
+ function parseJson(text) {
144
+ if (typeof text !== 'string') return undefined
145
+ const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
146
+ const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
147
+ let m
148
+ while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
149
+ const whole = tryObj(text.trim()); if (whole !== undefined) return whole
150
+ let best = undefined; let bestLen = -1
151
+ for (let start = 0; start < text.length; start++) {
152
+ if (text[start] !== '{') continue
153
+ let depth = 0, inStr = false, esc = false, end = -1
154
+ for (let i = start; i < text.length; i++) {
155
+ const c = text[i]
156
+ if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
157
+ if (c === '"') { inStr = true; continue }
158
+ if (c === '{') depth++
159
+ else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
160
+ }
161
+ if (end === -1) continue
162
+ const obj = tryObj(text.slice(start, end + 1))
163
+ if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
164
+ }
165
+ return best
166
+ }
167
+ function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
168
+ 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 }
169
+
170
+ // ================= parameter schema =================
171
+ const PARAM_SCHEMA = [
172
+ { name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
173
+ { name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
174
+ { name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
175
+ { name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向(互不干扰);N>1 = 额外附带最多 N 个其他活跃方向摘要用于协调', suggestion: 1 },
176
+ { name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量', suggestion: 3 },
177
+ { name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
178
+ { name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致直接判 0.5);forced = 强制裁决(按历史准确率+严谨性加权)', suggestion: 'flat' },
179
+ { name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
180
+ { name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
181
+ { name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
182
+ { name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
183
+ { name: 'explorerPersona', type: 'string', description: '注入每个 explorer/重派生提示词开头的人格/要求', suggestion: '' },
184
+ { name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖,注入 explorer/solver/verifier 提示词)', suggestion: '' },
185
+ { name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
186
+ { name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
187
+ { name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
188
+ { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
189
+ { name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow 列表时补入网络工具);false=禁止 web_search/web/fetch', suggestion: '' },
190
+ { name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关(同 solverAllowNetwork)', suggestion: '' },
191
+ { name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止 bash/pwsh', suggestion: '' },
192
+ { name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关(同 solverAllowScripts)', suggestion: '' },
193
+ { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
194
+ { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
195
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
196
+ { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
197
+ { name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
198
+ { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
199
+ { name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
200
+ { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
201
+ { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
202
+ { name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
203
+ ]
204
+
205
+ // ================= fs =================
206
+ async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
207
+ 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 } }
208
+ async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
209
+ async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
210
+ async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
211
+ 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 [] } }
212
+ 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 [] } }
213
+ 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 } }
214
+ 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 } }
215
+ async function readCurrentProject() {
216
+ // 按会话隔离的 current 文件(多会话并行时互不覆盖);无则回退旧共享文件
217
+ try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s !== undefined) { 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) {}
218
+ 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' }
219
+ }
220
+ async function writeCurrentProject() {
221
+ try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) {}
222
+ }
223
+
224
+ // ================= subprocess =================
225
+ function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
226
+ 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) } } }
227
+ 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') }
228
+ async function removeFile(rel) { const base = frameworkRoot(); return await runShell('Remove-Item -Force -LiteralPath ' + psQuote(base + '/' + rel) + ' -ErrorAction SilentlyContinue') }
229
+
230
+ // ================= settings =================
231
+ function sanitizeParams(obj) {
232
+ const out = {}
233
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries']
234
+ const numFields = ['promoteValueThreshold']
235
+ const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
236
+ for (const k of Object.keys(DEFAULT_PARAMS)) {
237
+ if (!(k in obj)) continue
238
+ const v = obj[k]
239
+ if (intFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.floor(n) : DEFAULT_PARAMS[k] }
240
+ 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] }
241
+ else if (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
242
+ else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
243
+ else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
244
+ else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
245
+ else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
246
+ else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
247
+ else if (k === 'solverAllowNetwork' || k === 'verifierAllowNetwork' || k === 'solverAllowScripts' || k === 'verifierAllowScripts') { out[k] = (v === true || v === false || v === '') ? v : DEFAULT_PARAMS[k] }
248
+ else { out[k] = v }
249
+ }
250
+ return out
251
+ }
252
+ async function loadSettings() {
253
+ let text = await readText('vibe_math_setting.json')
254
+ if (text === undefined) text = await readTextAbs(vibeRoot() + '/vibe_math_setting.json')
255
+ if (text === undefined) return
256
+ const clean = stripJsonComments(text)
257
+ try {
258
+ const obj = JSON.parse(clean)
259
+ if (obj && typeof obj === 'object' && !Array.isArray(obj)) params = Object.assign({}, params, sanitizeParams(obj))
260
+ } catch (e) {
261
+ console.error('vibe-math-v2: invalid vibe_math_setting.json ignored: ' + String((e && e.message) || e))
262
+ }
263
+ }
264
+ function settingsTemplateFrom(src) {
265
+ const lines = []
266
+ lines.push('{')
267
+ lines.push(' // Vibe Math V2 默认参数配置(JSON with Comments,可加 // 注释)。')
268
+ lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
269
+ lines.push(' // 本文件是参数的唯一持久化来源:vibe_math_set_params / set_mode 会立即写回此文件;全局文件仅作项目文件不存在时的回退默认。')
270
+ const keys = Object.keys(src).sort()
271
+ for (let i = 0; i < keys.length; i++) {
272
+ const k = keys[i]
273
+ const v = src[k]
274
+ const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
275
+ const desc = schema ? schema.description : ''
276
+ const comma = i === keys.length - 1 ? '' : ','
277
+ lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
278
+ lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
279
+ }
280
+ lines.push('}')
281
+ return lines.join('\n') + '\n'
282
+ }
283
+ function settingsTemplate() { return settingsTemplateFrom(params) }
284
+ async function saveSettings() { await writeText('vibe_math_setting.json', settingsTemplate()); return { ok: true, path: frameworkRoot() + '/vibe_math_setting.json' } }
285
+ 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' } }
286
+
287
+ // ================= persistence =================
288
+ async function loadState() {
289
+ const s = await readJson('VibeMath_State/scheduler_state.json'); if (s) scheduler = Object.assign({}, scheduler, s)
290
+ const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r
291
+ const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq
292
+ const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va
293
+ const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk
294
+ const er = await readJson('VibeMath_State/explorer_retries.json'); if (er) explorerRetries = er
295
+ }
296
+ async function saveAll() {
297
+ await writeJson('VibeMath_State/scheduler_state.json', scheduler)
298
+ await writeJson('VibeMath_State/agent_registry.json', agentRegistry)
299
+ await writeJson('VibeMath_State/decision_queue.json', decisionQueue)
300
+ await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
301
+ await writeJson('VibeMath_State/tasks.json', tasks)
302
+ await writeJson('VibeMath_State/explorer_retries.json', explorerRetries)
303
+ scheduler.lastCheckpoint = now()
304
+ }
305
+ // 查询类工具(status/setup/report)汇报前重读设置文件:文件是唯一持久化源,可能在会话启动后被用户手改或由本进程外编辑更新。
306
+ async function refreshParams() {
307
+ params = Object.assign({}, DEFAULT_PARAMS)
308
+ await loadSettings()
309
+ await migrateLegacyParams()
310
+ }
311
+ // 一次性迁移(单文件化):旧版 VibeMath_State/params.json 中的运行时参数合并进 vibe_math_setting.json 后删除。
312
+ async function migrateLegacyParams() {
313
+ const legacy = await readJson('VibeMath_State/params.json')
314
+ if (!legacy || Object.keys(legacy).length === 0) return
315
+ try {
316
+ params = Object.assign({}, params, sanitizeParams(legacy))
317
+ await saveSettings()
318
+ await writeJson('VibeMath_State/params.json', {}) // 一次性守卫:即使删除失败,空对象也不再覆盖设置文件
319
+ await removeFile('VibeMath_State/params.json')
320
+ logActivity('params', 'legacy params.json merged into vibe_math_setting.json and removed')
321
+ } catch (e) { console.error('vibe-math-v2: params migration failed: ' + String((e && e.message) || e)) }
322
+ }
323
+
324
+ // ================= data layer: qs.json =================
325
+ async function getQs() { const a = await readJson('qs/qs.json'); return Array.isArray(a) ? a : [] }
326
+ async function writeQs(list) { await writeJson('qs/qs.json', list) }
327
+ async function findQ(qid) { const qs = await getQs(); return qs.find(function (q) { return q.id === qid }) }
328
+
329
+ // progress:结构化 JSON 对象(旧数据可能是 JSON 字符串,两者兼容解析)。
330
+ // 注意:必须保证返回对象含 directions 数组(晋升/判断/子问题等 progress 可能只有来源/说明等字段)。
331
+ function parseProgress(q) {
332
+ const raw = (q && q.progress) || null
333
+ let p = null
334
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) p = raw
335
+ else p = safeJson(raw, null)
336
+ if (!p || typeof p !== 'object' || Array.isArray(p)) return { directions: [], experience: '' }
337
+ if (!Array.isArray(p.directions)) p.directions = []
338
+ return p
339
+ }
340
+ async function saveProgress(qid, progObj) { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return; q.progress = progObj; await writeQs(qs) }
341
+
342
+ // ================= data layer: Propos =================
343
+ function categoryOf(p) { const t = (p && p.细类型) || {}; const keys = Object.keys(t); return (keys.length > 0 && typeof t[keys[0]] === 'object') ? keys[0] : '未分类' }
344
+ function proposFile(cat) { return 'Propos/' + String(cat) + '_Propos.json' }
345
+ async function proposFiles() { return await listFiles('Propos') }
346
+ async function readProposCategory(cat) { const a = await readJson(proposFile(cat)); return Array.isArray(a) ? a : [] }
347
+ async function getPropos() {
348
+ const out = []
349
+ const files = await proposFiles()
350
+ for (let i = 0; i < files.length; i++) {
351
+ const fname = files[i]
352
+ const cat = fname.replace(/_Propos\.json$/i, '')
353
+ const list = await readProposCategory(cat)
354
+ for (let j = 0; j < list.length; j++) { list[j]._category = cat; out.push(list[j]) }
355
+ }
356
+ return out
357
+ }
358
+ async function findProposition(pId) { const all = await getPropos(); return all.find(function (p) { return p.id === pId }) }
359
+ async function upsertProposition(p) {
360
+ const cat = p._category || categoryOf(p)
361
+ delete p._category
362
+ const list = await readProposCategory(cat)
363
+ const idx = list.findIndex(function (x) { return x.id === p.id })
364
+ if (idx !== -1) list[idx] = p; else list.push(p)
365
+ await writeJson(proposFile(cat), list)
366
+ return cat
367
+ }
368
+ async function deleteProposition(p) {
369
+ const cat = p._category || categoryOf(p)
370
+ const list = await readProposCategory(cat)
371
+ const next = list.filter(function (x) { return x.id !== p.id })
372
+ await writeJson(proposFile(cat), next)
373
+ }
374
+
375
+ // ================= data layer: Verified / Reliable =================
376
+ async function readVerifiedCategory(cat) { const a = await readJson('Verified/' + String(cat) + '_Verified.json'); return Array.isArray(a) ? a : [] }
377
+ async function reliableFiles() { return await listFiles('Reliable') }
378
+
379
+ // ================= reporting =================
380
+ 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 }
381
+ async function buildReport() {
382
+ const qs = await getQs()
383
+ const propos = await getPropos()
384
+ return {
385
+ ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
386
+ running: scheduler.running, mode: params.mode,
387
+ activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
388
+ problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
389
+ propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
390
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
391
+ registeredAgents: Object.keys(agentRegistry).length,
392
+ recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
393
+ params: params,
394
+ }
395
+ }
396
+ async function maybeWriteReport(force) {
397
+ const interval = Number(params.reportIntervalMs) || 0
398
+ if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
399
+ if (!force && interval <= 0 && !reportDirty) return
400
+ await writeJson('Progress_Logs/report.json', await buildReport())
401
+ lastReportWrite = now(); reportDirty = false
402
+ }
403
+ async function maybePushReport(force) {
404
+ const mode = params.reportMode || 'file'
405
+ if (mode !== 'push' && mode !== 'both') return
406
+ const interval = Number(params.reportIntervalMs) || 0
407
+ if (interval > 0) {
408
+ // heartbeat: push on interval (or force), independent of reportDirty so 'both' mode works
409
+ if (!force && (now() - lastPushReport) < interval) return
410
+ } else if (!force && !reportDirty) {
411
+ // event-driven: only push when an event happened since the last report
412
+ return
413
+ }
414
+ if (!rootAgent || typeof rootAgent.followup !== 'function') return
415
+ try {
416
+ const report = await buildReport()
417
+ const text = '[Vibe Math V2] 进度更新:当前项目 "' + currentProject + '" 运行中=' + report.running +
418
+ ',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
419
+ '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
420
+ '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
421
+ rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v2' } })
422
+ lastPushReport = now()
423
+ } catch (e) {
424
+ console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
425
+ }
426
+ }
427
+
428
+ // ================= child spawn / followup =================
429
+ 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' }
430
+ 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 }
431
+ const NETWORK_TOOLS = ['web_search', 'web', 'fetch']
432
+ const SCRIPT_TOOLS = ['bash', 'pwsh']
433
+ 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 net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; if (net === false) d = d.concat(NETWORK_TOOLS); else if (net === true && a.length > 0) a = a.concat(NETWORK_TOOLS); if (scr === false) d = d.concat(SCRIPT_TOOLS); else if (scr === true && a.length > 0) a = a.concat(SCRIPT_TOOLS); const f = {}; if (a.length > 0) f.allow = a; if (d.length > 0) f.deny = d; return (f.allow || f.deny) ? f : undefined }
434
+ async function spawnChild(label, promptText, meta) {
435
+ const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
436
+ const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf
437
+ let started
438
+ try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) }
439
+ catch (e) {
440
+ 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 }
441
+ }
442
+ agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
443
+ childOwner.set(started.childId, sessionId)
444
+ scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
445
+ await saveAll(); return started.childId
446
+ }
447
+ 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() }
448
+ async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
449
+
450
+ // ================= prompts =================
451
+ function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
452
+ function verifierPersonaText() { return params.verifierPersona ? (String(params.verifierPersona) + '\n\n') : '' }
453
+ function explorerPersonaText() { return params.explorerPersona ? (String(params.explorerPersona) + '\n\n') : '' }
454
+ // 共享知识/数据模型说明(点6):完整解释各对象/属性含义、概率语义、文件夹用途、输出要求。
455
+ // 空参数 = 使用内置完整版;非空 = 由用户覆盖(点8)。
456
+ function defaultKnowledgeContext() {
457
+ return 'KNOWLEDGE BASE & DATA MODEL (definition contract you MUST follow):\n' +
458
+ '\n1) PROBABILITY SEMANTICS — the single most important rule:\n' +
459
+ '- 正确概率 / 布尔估计 ∈ [0,1]。\n' +
460
+ '- 1 = 绝对正确(已被证明且验证通过):你可以把它当作已知事实/可信结论直接用于推理。\n' +
461
+ '- 0 = 绝对错误(已被证伪且验证通过)。\n' +
462
+ '- 0 与 1 之间的任何值 = 未定论/待验证:只能作为参考证据,绝不能当作已成立的事实引用。\n' +
463
+ '- Verified/ 中的卡片概率恒为 1 或 0,内容可信、可直接引用。\n' +
464
+ '\n2) OBJECT MODELS (按实现方案):\n' +
465
+ '- 问题 PROBLEM(qs/qs.json):{ id, 概述(完整问题陈述,所提到的每个对象/记号都要给出完整定义), 已解决(bool), 解法列表:[{ 完整解法(详细步骤), 正确概率, 已验 }], 优先级(整数,越小越优先调度;"never"=永不调度), progress(历史:已试方向、各方向路线、阻碍及原因、教训、可行性评估)}。\n' +
466
+ '- 命题 PROPOSITION(Propos/<分类>_Propos.json):{ id, 概述(完整陈述), 布尔估计(该命题为真的概率), 细类型(分类 JSON), 证明列表:[{ 完整过程(完整证明), 正确概率, 支持信息/依据 }], 证伪列表:[{ 完整过程(完整证伪), 正确概率, 支持信息/依据 }], 优先级, 价值/关键性(0-1,重要性), progress(过往尝试与教训)}。\n' +
467
+ '- 收口规则:问题的某个解法 正确概率=1 → 问题已解决;命题的证明/证伪条目 正确概率=1 → 命题布尔估计=1/0(已定论)。\n' +
468
+ '\n3) FOLDERS (per project, VibeMath/Projects/<project>/):\n' +
469
+ '- qs/qs.json:问题清单——求解与验证的唯一问题来源。\n' +
470
+ '- Propos/<分类>_Propos.json:命题知识库(已有认知)。\n' +
471
+ '- Reliable/:可信参考文献(只读)。\n' +
472
+ '- Verified/<分类>_Verified.json:定论事实索引——布尔估计=0/1 的命题卡片与已解决问题卡片;内容可信、可直接使用。\n' +
473
+ '- Verification_logs/:辩论记录。Progress_Logs/:进度与报告。VibeMath_State/:调度器私有状态——不要读也不要改。\n' +
474
+ '\n4) OUTPUT REQUIREMENTS (你输出的每个对象必须满足):\n' +
475
+ '- 完整性、不断章取义:任何你写出的问题/命题/结论都要给出完整陈述,并把它所依赖的对象、环境、背景、定义全部补全(例如提到某个序列/函数/定理时给出其完整定义与假设)。\n' +
476
+ '- 引用溯源:若你引用了 qs/qs.json、Propos/、Verified/、Reliable/ 中已有的命题/引理/结论/解法,必须给出出处——具体文件路径(相对项目根,如 Propos/数论_Propos.json 或 Verified/未分类_Verified.json)+ 对象 id 或 JSON 路径(如 .证明列表[0] 或 .directions[1]);没有出处的引用一律不允许。你自己新提出的结论则必须自带完整定义,不得引用未定义的内容。\n' +
477
+ '- 若结论依赖某个临时假设 p,必须显式写成「若 <p 的完整陈述> 成立,则:...」(同样要定义完整)。\n' +
478
+ '- 只输出规定的 JSON(放在 ```json 代码围栏内),JSON 之外不写任何内容。\n' +
479
+ '- 示例(完整问题 概述):"设 {a_n} 为非负实数序列(n≥1),满足:对任意正整数 n 都存在 i,j 使 |a_i − a_j| = 1/n^p(p>0 为实参数)。判断:p 在什么范围内保证级数 ∑_{n=1}^∞ a_n 发散?" —— 每个记号(序列、参数、级数)都在句内定义完整,读它的人无需再查背景。\n' +
480
+ '- 示例(完整命题 概述):"设函数 f:[0,1]→R 连续,则 f 在 [0,1] 上有界(连续性按 ε-δ 定义,有界性按标准实数分析定义)。" —— 概念与对象定义完整,不引用未定义的记号。\n'
481
+ }
482
+ function knowledgeContextText() { const k = params.knowledgeContext ? String(params.knowledgeContext) : defaultKnowledgeContext(); return k ? ('\n' + k + '\n') : '' }
483
+ function capabilitiesText(role) {
484
+ const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
485
+ const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
486
+ const scrOn = role === 'solver' ? params.solverAllowScripts : params.verifierAllowScripts
487
+ const toolParts = []
488
+ if (netOn !== false) toolParts.push('web search / literature lookup')
489
+ if (scrOn !== false) toolParts.push('symbolic/numeric computation (running scripts)')
490
+ let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
491
+ t += '- Network tools (web search / fetch): ' + (netOn === false ? 'DISABLED for you' : 'available') + '; Script/shell tools (bash/pwsh): ' + (scrOn === false ? 'DISABLED for you' : 'available') + ' (your actual tool list is enforced by the framework).\n'
492
+ t += toolParts.length > 0
493
+ ? ('- You may use external tools (' + toolParts.join(', ') + ') to assist; ' + ((maxCalls && Number(maxCalls) > 0) ? ('call such external tools AT MOST ' + maxCalls + ' times this round.\n') : 'no per-round limit by default.\n'))
494
+ : '- External tools: none enabled for you this round.\n'
495
+ t += '- You may READ any file under Verified/ as a known, trusted dependency (resolved facts).\n'
496
+ t += '- You should BASE your reasoning on the existing knowledge under Propos/ (propositions with proofs/refutations and probabilities) and Reliable/ (trusted references).\n'
497
+ t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer.\n'
498
+ t += '\nHOW TO READ EXISTING KNOWLEDGE (coarse scan → fine read):\n'
499
+ t += '- These are JSON files. A conclusion object carries summary-index fields (概述 / 布尔估计 / 优先级) and the full detail (证明列表 / 证伪列表 / 完整过程 / progress).\n'
500
+ 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'
501
+ 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'
502
+ return t
503
+ }
504
+ function explorerPrompt(q) {
505
+ return explorerPersonaText() + 'You are a research mathematician orchestrating strategy for one problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.概述 + '\n\n' +
506
+ knowledgeContextText() +
507
+ capabilitiesText('solver') +
508
+ '\nDo a first-stage METACOGNITIVE BRAINSTORM: decompose constraints, test boundary/extreme cases, map to similar known problems. ' +
509
+ 'Then propose 3-6 DIVERSE, mutually distinct solution directions (e.g. analytic method, constructive proof, contradiction, numeric approximation + limit passage, categorical abstraction, ...). ' +
510
+ 'Record each direction with its core assumption and an initial feasibility estimate.\n\n' +
511
+ 'feasibility ∈ [0,1]: your estimate of the probability this direction leads to a full solution. Every direction must be self-contained and unambiguous: title / method / core_assumption written completely, defining every object they mention — no 断章取义, no undefined symbols.\n\n' +
512
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
513
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}]}'
514
+ }
515
+ function rederivePrompt(q, prog) {
516
+ const prior = prog.directions.map(function (d) {
517
+ return '- ' + d.id + '「' + d.title + '」status=' + d.status + ' round=' + d.round + ' survival=' + d.survival + (d.dead_end_reason ? ' [blocker: ' + d.dead_end_reason + ']' : '') +
518
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '')
519
+ }).join('\n')
520
+ return explorerPersonaText() + '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' +
521
+ knowledgeContextText() +
522
+ capabilitiesText('solver') +
523
+ '\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). ' +
524
+ 'Then deeply DERIVE 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. ' +
525
+ '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' +
526
+ 'feasibility ∈ [0,1] as above. Every returned direction (kept or new) must be self-contained and unambiguous, with complete definitions — no 断章取义.\n\n' +
527
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
528
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5,"motivation":"..."}]}'
529
+ }
530
+ function directionSummary(d) {
531
+ return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
532
+ ' survival=' + d.survival +
533
+ (d.lemmas && d.lemmas.length ? ' | lemmas: ' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join('; ') : '') +
534
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '') +
535
+ (d.lessons && d.lessons.length ? ' | lessons: ' + d.lessons.join('; ') : '') +
536
+ (d.blockers && d.blockers.length ? ' | blockers: ' + d.blockers.join('; ') : '')
537
+ }
538
+ // 每个 solver 可见的方向总数 = directionsPerSolver(默认 1 = 只看自己方向,互不干扰)。
539
+ // round 1 时自己的方向由 DIRECTION 行给出、占用 1 个名额;round>1 时自己的历史进度摘要占用 1 个名额;
540
+ // 其余名额填充其他活跃方向摘要(n=3 → 自己 + 最多 2 个其他方向)。
541
+ function buildSolverContext(all, own, round, perSolver) {
542
+ const out = []
543
+ const n = Math.max(1, Number(perSolver) || 1)
544
+ let slots = n
545
+ if (round > 1) { out.push(own); slots -= 1 }
546
+ else slots -= 1 // round 1:自己的方向已由 DIRECTION 行给出
547
+ const others = all.filter(function (d) { return d.id !== own.id && d.status === 'active' })
548
+ for (let i = 0; i < others.length && slots > 0; i++) { out.push(others[i]); slots -= 1 }
549
+ return out.map(directionSummary).join('\n')
550
+ }
551
+ function solverPrompt(q, dir, round, progressText) {
552
+ let head = solverPersonaText() + 'You are a dedicated solver agent working ONE solution direction of a math problem (agent_self_iteration).\n\n'
553
+ head += 'PROBLEM (id: ' + q.id + '): ' + q.概述 + '\nDIRECTION: ' + dir.title + ' (method: ' + dir.method + '; core assumption: ' + dir.core_assumption + ')\nROUND: ' + round + ' of ' + params.solverMaxRounds + '\n'
554
+ if (round > 1 || (progressText && progressText.length)) head += '\nYOUR PRIOR PROGRESS / OTHER DIRECTIONS:\n' + progressText + '\n'
555
+ head += knowledgeContextText()
556
+ head += capabilitiesText('solver')
557
+ 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' +
558
+ '- new lemmas / intermediate conclusions WITH full proofs (these go to the Propos/ knowledge base);\n' +
559
+ '- 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' +
560
+ '- lessons learned from failed attempts (what to avoid, what did not work and why);\n' +
561
+ '- an updated survival probability for this direction.\n'
562
+ head += '\nIf you encounter an EXTREMELY complex auxiliary conjecture/sub-problem q_sub: list it in "sub_questions" as a PROBLEM-class object with its COMPLETE statement (every object/definition/notation it mentions must be fully defined — never quote partially, 不断章取义), together with p_{q-tmp}: a PROPOSITION-class TEMPORARY ASSUMPTION that is one possible answer to q_sub. TEMPORARILY ASSUME p_{q-tmp} holds and continue the main line — every later proposition/conclusion that depends on this assumption MUST be stated as "若 <p_{q-tmp} 的完整陈述> 成立,则:..." (with complete definitions). The scheduler registers q_sub and the problem "判断下述命题是否成立:p_{q-tmp}" in the problem list, and p_{q-tmp} in the proposition base.\n'
563
+ head += '\nIMPORTANT — PROBABILITY RULES FOR NEW RESULTS: any 布尔估计 / solution_probability / survival_probability you output for NEW results must be strictly BETWEEN 0 and 1 (they await independent verifier confirmation). NEVER mark your own fresh lemma or solution as 1 or 0 — that is the verifiers\' job. Only facts already recorded in Verified/ (or 正确概率=1 entries you READ from files) count as certain.\n'
564
+ head += '- Each lemma you output must carry a COMPLETE statement ("statement") and a COMPLETE proof ("proof"): define every object/notation it uses — no 断章取义, no undefined symbols. If a lemma/conclusion references or is derived from existing knowledge (Propos/Verified/Reliable/qs files), state the source file path + object id / JSON path inside the statement — no unsourced references.\n'
565
+ 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'
566
+ head += '\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n' +
567
+ '{"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":"..."}],"lessons":["..."],"survival_probability":0.5,"dead_end_reason":"... or null","sub_questions":[{"q_sub_title":"...","q_sub_statement":"完整问题陈述(含所有对象/定义)","assumption_title":"p_{q-tmp} 标题","assumption_statement":"完整假设陈述(含所有定义)"}]}'
568
+ return head
569
+ }
570
+ function verifierReviewPrompt(r) {
571
+ let target = ''
572
+ if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
573
+ else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
574
+ else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
575
+ return verifierPersonaText() + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + target + '\n' +
576
+ knowledgeContextText() +
577
+ capabilitiesText('verifier') +
578
+ '\nResult ∈ [0,1] = your probability that the TARGET is CORRECT: 1 ONLY when you are fully certain (for a bare proposition: Reason must be a complete proof; for a proof/refutation/solution: you verified every step and Reason confirms the whole chain); 0 ONLY when you are certain it is wrong (Reason must be a rigorous complete refutation / pinpoint the fatal flaw); otherwise a value strictly between 0 and 1.\n' +
579
+ '\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
580
+ '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"}'
581
+ }
582
+ function verifierDebatePrompt(r, transcript) {
583
+ let target = ''
584
+ if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
585
+ else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
586
+ else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
587
+ return verifierPersonaText() + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + target + '\n' +
588
+ knowledgeContextText() +
589
+ capabilitiesText('verifier') +
590
+ '\nFULL DEBATE HISTORY SO FAR (每轮所有评审轮流发言的记录):\n' + transcript + '\n' +
591
+ '\nRespond to the others (agree / rebut / add new evidence, referencing earlier rounds if needed). If you changed your Result because of them, state the reason explicitly.\n' +
592
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
593
+ '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"}'
594
+ }
595
+
596
+ // ================= decisions (manual/auto) =================
597
+ 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 }
598
+ 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 } }
599
+ async function applyDecision(node, data, resolution) {
600
+ if (node === 'spawn') {
601
+ if (resolution.action === 'approve') { await spawnChild(data.label, data.promptText, data.meta); return { spawned: true } }
602
+ try {
603
+ const meta = data.meta || {}
604
+ if (meta.role === 'explorer' && meta.qid) {
605
+ const q = await findQ(meta.qid)
606
+ 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) }
607
+ } else if (meta.role === 'solver' && meta.qid && meta.direction) {
608
+ const q = await findQ(meta.qid)
609
+ 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) }
610
+ }
611
+ } catch (e) { console.error('vibe-math-v2: spawn reject mark failed: ' + String((e && e.message) || e)) }
612
+ return { spawned: false, rejected: true }
613
+ }
614
+ 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 } }
615
+ return {}
616
+ }
617
+ 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' } }
618
+
619
+ // ================= scheduler core =================
620
+ function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v2 tick error: ' + String((e && e.stack) || e)) }) }
621
+ async function tick() {
622
+ if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return
623
+ tickInFlight = true
624
+ lastTickAt = now()
625
+ try {
626
+ await processStatusUpdates()
627
+ await processPriorityAdjust()
628
+ await processPromote()
629
+ await processVerify()
630
+ await reconcileVerify()
631
+ await processSolve()
632
+ await maybePushReport(false)
633
+ await maybeWriteReport(false)
634
+ const qs = await getQs()
635
+ const unsolved = qs.filter(function (q) { return !q.已解决 && q.优先级 !== 'never' })
636
+ if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) {
637
+ scheduler.running = false
638
+ logActivity('stop', 'all active problems solved (never-priority problems excluded) and no active agents/tasks — scheduler stopped (strict termination)')
639
+ await saveAll(); await maybeWriteReport(true); await maybePushReport(true)
640
+ } else if (Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0 && unsolved.length > 0) {
641
+ let allBlocked = true
642
+ for (let i = 0; i < unsolved.length; i++) {
643
+ const prog = parseProgress(unsolved[i])
644
+ const exhaustedAll = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
645
+ const hasActive = prog.directions.some(function (d) { return d.status === 'active' })
646
+ const blocked = (prog.directions.length === 0 || exhaustedAll) && (explorerRetries[unsolved[i].id] || 0) >= (Number(params.maxExplorerRetries) || 3)
647
+ if (hasActive || !blocked) { allBlocked = false; break }
648
+ }
649
+ if (allBlocked) {
650
+ scheduler.running = false
651
+ logActivity('stall', 'no feasible direction remains for any unsolved problem — scheduler paused (stalled, NOT all solved)')
652
+ await saveAll(); await maybeWriteReport(true)
653
+ }
654
+ }
655
+ } finally { tickInFlight = false }
656
+ }
657
+ // note 4: probability-1 rules
658
+ async function processStatusUpdates() {
659
+ const qs = await getQs()
660
+ let qsChanged = false
661
+ for (let i = 0; i < qs.length; i++) {
662
+ const q = qs[i]
663
+ if (q.解法列表 && q.解法列表.some(function (s) { return s.正确概率 === 1 })) { if (!q.已解决) { qsChanged = true; await writeVerifiedProblemCardIfNeeded(q) } q.已解决 = true; q.优先级 = 'never' }
664
+ }
665
+ if (qsChanged) { await writeQs(qs); logActivity('update', 'problems marked solved by probability-1 solutions') }
666
+ const propos = await getPropos()
667
+ let closedPromoted = false
668
+ for (let i = 0; i < propos.length; i++) {
669
+ const p = propos[i]
670
+ let pChanged = false
671
+ const proofOne = (p.证明列表 || []).some(function (x) { return x.正确概率 === 1 })
672
+ const refuteOne = (p.证伪列表 || []).some(function (x) { return x.正确概率 === 1 })
673
+ if (proofOne && p.布尔估计 !== 1) { p.布尔估计 = 1; pChanged = true }
674
+ else if (refuteOne && p.布尔估计 !== 0) { p.布尔估计 = 0; pChanged = true }
675
+ if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
676
+ if (p.布尔估计 === 1 || p.布尔估计 === 0) {
677
+ if (await writeVerifiedCardIfNeeded(p)) pChanged = true
678
+ // 源命题已定论 → 关闭其晋升出的"僵尸"问题(优先用 判断命题 字段;兼容旧文本标记数据)
679
+ for (let j = 0; j < qs.length; j++) {
680
+ const qj = qs[j]
681
+ if (qj.已解决) continue
682
+ if (qj.判断命题 === p.id) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
683
+ else if (String((qj.progress && typeof qj.progress === 'object' ? (qj.progress.来源命题 || '') : qj.progress) || '').indexOf(p.id) !== -1) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
684
+ }
685
+ }
686
+ if (pChanged) await upsertProposition(p)
687
+ }
688
+ if (closedPromoted) { await writeQs(qs); logActivity('update', 'promoted problems closed because their source proposition resolved') }
689
+ }
690
+ async function processPriorityAdjust() {
691
+ const mode = params.priorityAdjust || 'none'
692
+ if (mode !== 'none') {
693
+ const qs = await getQs()
694
+ let changed = false
695
+ for (let i = 0; i < qs.length; i++) {
696
+ const q = qs[i]
697
+ if (q.已解决 || q.优先级 === 'never') continue
698
+ const prog = parseProgress(q)
699
+ if (mode === 'deadend-deprioritize') {
700
+ if (prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' })) {
701
+ const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true }
702
+ }
703
+ } else if (mode === 'survival-map') {
704
+ if (prog.directions.length > 0) {
705
+ const maxSurv = Math.max.apply(null, prog.directions.map(function (d) { return Number(d.survival) || 0 }))
706
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv)))
707
+ if (q.优先级 !== target) { q.优先级 = target; changed = true }
708
+ }
709
+ }
710
+ }
711
+ if (changed) { await writeQs(qs); logActivity('priority', 'priorities auto-adjusted (' + mode + ')') }
712
+ }
713
+ const pMode = params.proposPriorityAdjust || 'none'
714
+ if (pMode === 'progress-graded') {
715
+ const propos = await getPropos()
716
+ const changedProps = []
717
+ for (let i = 0; i < propos.length; i++) {
718
+ const p = propos[i]
719
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
720
+ const closeness = Math.abs(Number(p.布尔估计) - 0.5)
721
+ const material = Math.min(5, (p.证明列表 || []).length + (p.证伪列表 || []).length)
722
+ const score = closeness * 1.2 + material * 0.08
723
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * score)))
724
+ const cur = Number(p.优先级)
725
+ if (Number.isFinite(cur) && cur !== target) { p.优先级 = target; changedProps.push(p) }
726
+ }
727
+ if (changedProps.length > 0) {
728
+ for (let i = 0; i < changedProps.length; i++) await upsertProposition(changedProps[i])
729
+ logActivity('priority', 'proposition priorities auto-adjusted (progress-graded)')
730
+ }
731
+ }
732
+ }
733
+ // note 3 + user 价值 field: promote high-value unresolved propositions into qs.json
734
+ async function processPromote() {
735
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
736
+ const qs = await getQs()
737
+ const qDescriptions = qs.map(function (q) { return q.概述 })
738
+ const propos = await getPropos()
739
+ for (let i = 0; i < propos.length; i++) {
740
+ const p = propos[i]
741
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
742
+ if (Number(p['价值/关键性']) < Number(params.promoteValueThreshold)) continue
743
+ if (p.在问题清单) continue
744
+ if (qDescriptions.indexOf(p.概述) !== -1) continue
745
+ if (qDescriptions.indexOf('判断下述命题是否成立:' + p.概述) !== -1) continue
746
+ const qid = 'q-promoted-' + String(p.id).replace(/[^a-z0-9\-]/gi, '').slice(-12)
747
+ // 点3:证明/证伪列表 → 解法列表(条目前加【证明】/【证伪】前缀),保留概率/已验并记录来源以便回写联动
748
+ const sols = []
749
+ const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
750
+ for (let j = 0; j < proofs.length; j++) { const it = proofs[j]; sols.push({ 完整解法: '【证明】' + (it.完整过程 || ''), 正确概率: clamp01(it.正确概率 != null ? it.正确概率 : 0.5), 已验: !!it.已验, 来源: '由命题晋升(证明#' + j + ')', 来源命题: p.id, 来源列表: '证明', 来源索引: j, 验证记录: [] }) }
751
+ for (let j = 0; j < refutes.length; j++) { const it = refutes[j]; sols.push({ 完整解法: '【证伪】' + (it.完整过程 || ''), 正确概率: clamp01(it.正确概率 != null ? it.正确概率 : 0.5), 已验: !!it.已验, 来源: '由命题晋升(证伪#' + j + ')', 来源命题: p.id, 来源列表: '证伪', 来源索引: j, 验证记录: [] }) }
752
+ qs.push({ id: qid, 概述: '判断下述命题是否成立:' + p.概述, 已解决: false, 解法列表: sols, 优先级: 1, 判断命题: p.id, 细类型: (p.细类型 && typeof p.细类型 === 'object') ? p.细类型 : {}, '价值/关键性': p['价值/关键性'], progress: { 来源: 'promote', 来源命题: p.id, 说明: '由命题 ' + p.id + '(价值/关键性=' + p['价值/关键性'] + ')自动晋升;目标:证明或证伪该命题(解法列表中的【证明】/【证伪】条目即原命题的证明/证伪材料,验证结果会回写源命题)。' } })
753
+ p.在问题清单 = true
754
+ await upsertProposition(p)
755
+ await writeQs(qs)
756
+ logActivity('promote', 'proposition ' + p.id + ' promoted to problem ' + qid + '(' + sols.length + ' 条证明/证伪转为解法)')
757
+ return // one per tick is enough
758
+ }
759
+ }
760
+ async function processVerify() {
761
+ const candidates = await buildVerifyCandidates()
762
+ for (let i = 0; i < candidates.length; i++) {
763
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
764
+ const c = candidates[i]
765
+ const rId = c.rId
766
+ if (tasks['verify:' + rId]) continue
767
+ const inflight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'verifier' && m.rId === rId })
768
+ if (inflight) continue
769
+ tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }
770
+ await saveAll()
771
+ return // one verification at a time keeps scheduling simple; tick will continue next pass
772
+ }
773
+ }
774
+ async function buildVerifyCandidates() {
775
+ const out = []
776
+ const qs = await getQs()
777
+ for (let i = 0; i < qs.length; i++) {
778
+ const q = qs[i]
779
+ if (q.已解决 || q.优先级 === 'never') continue
780
+ const sols = q.解法列表 || []
781
+ for (let j = 0; j < sols.length; j++) {
782
+ const s = sols[j]
783
+ if (s.正确概率 === 1 || s.正确概率 === 0 || s.已验) continue
784
+ if (!String(s.完整解法 || '').trim()) continue
785
+ out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
786
+ }
787
+ }
788
+ const propos = await getPropos()
789
+ for (let i = 0; i < propos.length; i++) {
790
+ const p = propos[i]
791
+ if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
792
+ if (p.在问题清单) continue // 已晋升:其证明/证伪经晋升问题的解法验证,避免同一内容双重验证
793
+ const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
794
+ if (proofs.length === 0 && refutes.length === 0) {
795
+ if (String(p.id).indexOf('p-tmp-') === 0) continue // 临时假设由「判断下述命题是否成立:p_{q-tmp}」问题统一验证,避免裸命题验证双重路径
796
+ out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
797
+ } else {
798
+ for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; if (!String(proofs[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
799
+ for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; if (!String(refutes[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
800
+ }
801
+ }
802
+ out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
803
+ return out
804
+ }
805
+ async function backfillVerifiers(t) {
806
+ while (t.children.length < t.expectedCount) {
807
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
808
+ const index = t.children.length
809
+ const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
810
+ t.children.push(childId)
811
+ }
812
+ if (t.children.length >= t.expectedCount) t.status = 'debating'
813
+ }
814
+ async function reconcileVerify() {
815
+ const ids = Object.keys(tasks)
816
+ for (let i = 0; i < ids.length; i++) {
817
+ const t = tasks[ids[i]]
818
+ if (t.type !== 'verify') continue
819
+ if (t.status === 'paused') {
820
+ const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
821
+ if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
822
+ }
823
+ if (t.status !== 'spawning') continue
824
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
825
+ await backfillVerifiers(t)
826
+ }
827
+ }
828
+ async function processSolve() {
829
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
830
+ const qs = await getQs()
831
+ 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.优先级)) })
832
+ for (let i = 0; i < unsolved.length; i++) {
833
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
834
+ const q = unsolved[i]
835
+ const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
836
+ if (busy) continue
837
+ const prog = parseProgress(q)
838
+ const allExhausted = prog.directions.length > 0 && prog.directions.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
839
+ if (prog.directions.length === 0 || allExhausted) {
840
+ const explorerCap = Number(params.maxExplorerRetries) || 3
841
+ if ((explorerRetries[q.id] || 0) >= explorerCap) {
842
+ 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 + ' 次未产出方向' })
843
+ await saveProgress(q.id, prog)
844
+ logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
845
+ continue
846
+ }
847
+ explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
848
+ const promptText = (prog.directions.length > 0) ? rederivePrompt(q, prog) : explorerPrompt(q)
849
+ 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 } })
850
+ if (r && r.gated) return
851
+ } else {
852
+ // spawn solvers for each active direction
853
+ for (let j = 0; j < prog.directions.length; j++) {
854
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
855
+ const dir = prog.directions[j]
856
+ if (dir.status === 'success' || dir.status === 'dead-end') continue
857
+ 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' })
858
+ if (running) continue
859
+ const progressText = buildSolverContext(prog.directions, dir, 1, params.directionsPerSolver)
860
+ const promptText = solverPrompt(q, dir, 1, progressText)
861
+ 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 } })
862
+ if (r && r.gated) return
863
+ }
864
+ }
865
+ }
866
+ }
867
+
868
+ // ================= solver (agent_self_iteration) handling =================
869
+ async function handleExplorer(childId, meta, output) {
870
+ delete agentRegistry[childId]
871
+ const parsed = parseJson(output)
872
+ const dirs = (parsed && parsed.directions) || []
873
+ if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions (output head: ' + String(output || '').slice(0, 200) + ')'); await saveAll(); return }
874
+ explorerRetries[meta.qid] = 0
875
+ const q = await findQ(meta.qid); if (!q) return
876
+ const prog = parseProgress(q)
877
+ prog.directions = dirs.map(function (d) {
878
+ 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: '' }
879
+ })
880
+ await saveProgress(meta.qid, prog)
881
+ logActivity('explorer', 'problem ' + meta.qid + ' → ' + prog.directions.length + ' directions')
882
+ }
883
+ async function handleSolver(childId, meta, output, stopReason) {
884
+ const qid = meta.qid; const dirId = meta.direction
885
+ const parsed = parseJson(output)
886
+ const q = await findQ(qid); if (!q) { delete agentRegistry[childId]; return }
887
+ const prog = parseProgress(q)
888
+ const dir = prog.directions.find(function (d) { return d.id === dirId })
889
+ if (!dir) { delete agentRegistry[childId]; return }
890
+ if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } // abort:不把方向标记为死路,保留待 resume
891
+ const status = (parsed && parsed.status) || statusFromStop(stopReason)
892
+ dir.round = meta.round
893
+ if (parsed) {
894
+ if (parsed.routes) dir.routes = (dir.routes || []).concat(parsed.routes)
895
+ if (parsed.lessons) dir.lessons = (dir.lessons || []).concat(parsed.lessons)
896
+ if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
897
+ if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
898
+ if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) { const lid = await addLemmaAsProposition(qid, parsed.lemmas[i]); if (lid) { dir.lemmas = dir.lemmas || []; dir.lemmas.push({ id: lid, title: parsed.lemmas[i].title || '' }) } } }
899
+ if (parsed.sub_questions && parsed.sub_questions.length) { for (let i = 0; i < parsed.sub_questions.length; i++) { const sq = parsed.sub_questions[i]; if (sq && sq.q_sub_statement && dir.sub_questions && dir.sub_questions.some(function (x) { return x.statement === sq.q_sub_statement })) continue; const rec = await addSubQuestion(qid, dirId, sq); if (rec) { dir.sub_questions = dir.sub_questions || []; dir.sub_questions.push(rec) } } }
900
+ }
901
+ if (status === 'success') {
902
+ if (parsed && parsed.solution) {
903
+ dir.status = 'success'
904
+ delete agentRegistry[childId]
905
+ logActivity('solver', qid + '/' + dirId + ' success at round ' + meta.round)
906
+ await addSolution(qid, parsed.solution, parsed.solution_probability)
907
+ } else {
908
+ // claimed success without a solution text — treat as an incomplete round
909
+ if (meta.round >= params.solverMaxRounds) {
910
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || 'claimed success without solution at iteration cap'
911
+ delete agentRegistry[childId]
912
+ logActivity('solver', qid + '/' + dirId + ' dead-end (success without solution)')
913
+ } else {
914
+ const progressText = buildSolverContext(prog.directions, dir, meta.round + 1, params.directionsPerSolver)
915
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
916
+ agentRegistry[childId].round = meta.round + 1
917
+ dir.round = meta.round + 1
918
+ }
919
+ }
920
+ } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) {
921
+ dir.status = 'dead-end'
922
+ if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'
923
+ delete agentRegistry[childId]
924
+ logActivity('solver', qid + '/' + dirId + ' dead-end: ' + dir.dead_end_reason)
925
+ } else {
926
+ const progressText = buildSolverContext(prog.directions, dir, meta.round + 1, params.directionsPerSolver)
927
+ if (!scheduler.running) {
928
+ // paused/aborted: stop the follow-up chain; keep the direction active for resume
929
+ delete agentRegistry[childId]
930
+ } else {
931
+ try {
932
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
933
+ agentRegistry[childId].round = meta.round + 1
934
+ dir.round = meta.round + 1
935
+ } catch (e) {
936
+ console.error('vibe-math-v2: solver followup failed: ' + String((e && e.message) || e))
937
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || '求解器续轮失败(followup 异常)'
938
+ delete agentRegistry[childId]
939
+ }
940
+ }
941
+ }
942
+ await saveProgress(qid, prog)
943
+ }
944
+ function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
945
+ async function addLemmaAsProposition(qid, lemma) {
946
+ if (!lemma || !lemma.title) return
947
+ let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
948
+ if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
949
+ const p = {
950
+ id: 'p-' + shortId(), 概述: lemma.statement || lemma.title,
951
+ 布尔估计: be,
952
+ 细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
953
+ 证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
954
+ 证伪列表: [], 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1,
955
+ '价值/关键性': clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
956
+ progress: { 来源: 'solver-lemma', 问题: qid, 说明: '由求解器针对问题 ' + qid + ' 的方向迭代产出。' }, 来源问题: qid,
957
+ }
958
+ await upsertProposition(p)
959
+ logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
960
+ return p.id
961
+ }
962
+ // 点5(q_sub 严格化):solver 报告子问题 q_sub 时,注册三个对象:
963
+ // 1) q_sub 本身(问题类,完整陈述)入 qs.json;
964
+ // 2) p_{q-tmp}(命题类临时假设:对 q_sub 的某种回答)入 Propos/;
965
+ // 3) 「判断下述命题是否成立:p_{q-tmp}」(问题类)入 qs.json。
966
+ // 返回 {subId,judgeId,assumeId},由 handleSolver 在最终保存 progress 时记入方向(避免旧 progress 覆盖丢记录)。
967
+ async function addSubQuestion(qid, dirId, sq) {
968
+ if (!sq || !sq.q_sub_statement) return undefined
969
+ const q = await findQ(qid)
970
+ if (q) {
971
+ const prog = parseProgress(q)
972
+ const d = prog.directions.find(function (x) { return x.id === dirId })
973
+ // 去重:同一方向已注册过相同陈述的 q_sub 则跳过(避免多轮重复上报产生重复问题/假设)
974
+ if (d && d.sub_questions && d.sub_questions.some(function (x) { return x.statement === sq.q_sub_statement })) { logActivity('subquestion', 'duplicate q_sub skipped for ' + qid + '/' + dirId); return undefined }
975
+ }
976
+ const qs = await getQs()
977
+ const subId = qid + '-sub-' + shortId()
978
+ const assumeId = 'p-tmp-' + shortId()
979
+ const judgeId = qid + '-judge-' + shortId()
980
+ const assumeStatement = sq.assumption_statement || sq.assumption_title || ('对子问题「' + (sq.q_sub_title || sq.q_sub_statement) + '」的一种回答(临时假设)')
981
+ qs.push({ id: subId, 概述: sq.q_sub_statement, 已解决: false, 解法列表: [], 优先级: 1, progress: { 类型: 'sub-question', 来源问题: qid, 来源方向: dirId, 说明: '临时子问题:由问题 ' + qid + ' 方向 ' + dirId + ' 分支产生;求解主线在 p_{q-tmp}(' + assumeId + ')假设下推进。' } })
982
+ qs.push({ id: judgeId, 概述: '判断下述命题是否成立:' + assumeStatement, 已解决: false, 解法列表: [], 优先级: 1, 判断命题: assumeId, progress: { 类型: 'judge', 假设命题: assumeId, 说明: '由临时假设 p_{q-tmp}(' + assumeId + ')生成;它是对子问题 ' + subId + ' 的一种回答的命题化。' } })
983
+ await writeQs(qs)
984
+ const p = {
985
+ id: assumeId, 概述: assumeStatement, 布尔估计: 0.5,
986
+ 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1,
987
+ '价值/关键性': 0.5,
988
+ progress: { 类型: 'temporary-assumption', 来源问题: qid, 子问题: subId, 说明: '临时假设 p_{q-tmp}:由问题 ' + qid + ' 方向 ' + dirId + ' 在求解中临时假设其成立以推进主线;若该假设被证伪,则依赖它的主线结论需重新审视。' },
989
+ 来源问题: qid,
990
+ }
991
+ await upsertProposition(p)
992
+ logActivity('subquestion', qid + ' → q_sub ' + subId + ' + 判断问题 ' + judgeId + ' + 临时假设 ' + assumeId)
993
+ return { subId: subId, judgeId: judgeId, assumeId: assumeId, statement: sq.q_sub_statement }
994
+ }
995
+ async function addSolution(qid, solutionText, prob) {
996
+ const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (!q) return
997
+ const p = clamp01(prob != null ? prob : 0.8)
998
+ const finalProb = p >= 1 ? 0.99 : (p <= 0 ? 0.01 : p) // must be < 1 (待验证器验证)
999
+ q.解法列表 = q.解法列表 || []
1000
+ q.解法列表.push({ 完整解法: String(solutionText), 正确概率: finalProb, 来源: 'solver', 验证记录: [] })
1001
+ await writeQs(qs)
1002
+ logActivity('solution', 'problem ' + qid + ' got a candidate solution (probability ' + finalProb + ', awaiting verification)')
1003
+ }
1004
+
1005
+ // ================= verification (验证器) =================
1006
+ 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 }) }
1007
+ 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') }
1008
+ 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)) }
1009
+ async function handleVerifier(childId, meta, output, stopReason) {
1010
+ const rId = meta.rId
1011
+ const parsed = parseJson(output)
1012
+ const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
1013
+ const Reason = (parsed && parsed.Reason) || ''
1014
+ let t = tasks['verify:' + rId]
1015
+ if (!t) { t = { id: 'verify:' + rId, type: 'verify', r: { kind: 'proposition', pId: rId, 概述: rId }, rId: rId, status: 'debating', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(2, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
1016
+ if (!parsed && !scheduler.running) {
1017
+ // abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerify 重建)
1018
+ delete agentRegistry[childId]
1019
+ const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
1020
+ delete t.childResults[childId]
1021
+ if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
1022
+ return
1023
+ }
1024
+ if (t.children.indexOf(childId) === -1) t.children.push(childId)
1025
+ t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
1026
+ delete agentRegistry[childId]
1027
+ const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
1028
+ if (!allReported) { await saveAll(); return }
1029
+ await advanceVerification(t, meta.round)
1030
+ await saveAll()
1031
+ }
1032
+ async function advanceVerification(t, round) {
1033
+ if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
1034
+ if (!scheduler.running) { t.status = 'paused'; return } // resume will re-advance this task
1035
+ if (scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return } // 并发门:等有空闲槽位再辩论(reconcileVerify 会重推进)
1036
+ t.round = round + 1
1037
+ const roundTranscript = buildTranscript(t)
1038
+ t.history = t.history || []
1039
+ t.history.push('Round ' + round + ':\n' + roundTranscript)
1040
+ const transcript = t.history.join('\n\n')
1041
+ const nextChildren = []
1042
+ for (let i = 0; i < t.children.length; i++) {
1043
+ const cid = t.children[i]
1044
+ try {
1045
+ await followupChild(cid, verifierDebatePrompt(t.r, transcript))
1046
+ agentRegistry[cid] = { role: 'verifier', rId: t.rId, round: round + 1, index: i }
1047
+ nextChildren.push(cid)
1048
+ } catch (e) {
1049
+ console.error('vibe-math-v2: verifier followup failed: ' + String((e && e.message) || e))
1050
+ delete agentRegistry[cid]
1051
+ delete t.childResults[cid]
1052
+ }
1053
+ }
1054
+ t.children = nextChildren
1055
+ if (nextChildren.length === 0) await finalizeVerification(t)
1056
+ } else {
1057
+ await finalizeVerification(t)
1058
+ }
1059
+ }
1060
+ async function finalizeVerification(t) {
1061
+ const verdict = finalVerdict(t)
1062
+ if (params.mode === 'manual') {
1063
+ const d = enqueueDecision('verdict', 'verdict for ' + t.rId + ' (debate finished) = ' + verdict, { rId: t.rId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
1064
+ scheduler.gate = { decisionId: d.id, node: 'verdict' }
1065
+ t.status = 'awaiting-verdict'
1066
+ } else {
1067
+ await settleVerdict(t, verdict)
1068
+ delete tasks[t.id]
1069
+ }
1070
+ }
1071
+ function finalVerdict(t) {
1072
+ const rs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid] })
1073
+ if (rs.length === 0) return 0.5
1074
+ if (rs.every(function (r) { return r.Result === 1 })) return 1
1075
+ if (rs.every(function (r) { return r.Result === 0 })) return 0
1076
+ if (params.verdictMode === 'forced') {
1077
+ let num = 0; let den = 0
1078
+ const cids = Object.keys(t.childResults)
1079
+ for (let i = 0; i < rs.length; i++) {
1080
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
1081
+ const accRate = acc.total > 0 ? (acc.correct / acc.total) : 0.5
1082
+ const confident = (rs[i].Result === 1 || rs[i].Result === 0) ? 0.1 : 0
1083
+ const w = Math.max(0.05, Math.min(0.95, accRate + confident))
1084
+ num += w * rs[i].Result; den += w
1085
+ }
1086
+ return den > 0 ? Math.max(0.01, Math.min(0.99, num / den)) : 0.5
1087
+ }
1088
+ return 0.5 // flat = 均衡机制
1089
+ }
1090
+ async function settleVerdict(t, verdict) {
1091
+ const v = clamp01(verdict)
1092
+ const r = t.r
1093
+ const cids = Object.keys(t.childResults)
1094
+ // update verifier historical accuracy (forced mode audit)
1095
+ for (let i = 0; i < cids.length; i++) {
1096
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
1097
+ acc.total += 1
1098
+ if (t.childResults[cids[i]].Result === v) acc.correct += 1
1099
+ verifierAccuracy[cids[i]] = acc
1100
+ }
1101
+ await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
1102
+ // debate transcript log
1103
+ await writeJson('Verification_logs/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
1104
+
1105
+ if (r.kind === 'proposition') {
1106
+ const p = await findProposition(r.pId)
1107
+ if (p) {
1108
+ p.布尔估计 = v
1109
+ if (v === 1) { p.证明列表 = p.证明列表 || []; p.证明列表.push({ 完整过程: strongestReason(t, 1), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
1110
+ else if (v === 0) { p.证伪列表 = p.证伪列表 || []; p.证伪列表.push({ 完整过程: strongestReason(t, 0), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
1111
+ else {
1112
+ p.证明列表 = p.证明列表 || []; p.证伪列表 = p.证伪列表 || []
1113
+ p.证明列表.push({ 完整过程: strongestReason(t, 1) || '根据辩论得到的支持性论证', 正确概率: v, '支持信息/依据': '', 已验: true })
1114
+ p.证伪列表.push({ 完整过程: strongestReason(t, 0) || '根据辩论得到的反驳性论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
1115
+ }
1116
+ await upsertProposition(p)
1117
+ await writeVerifiedCardIfNeeded(p)
1118
+ }
1119
+ } else if (r.kind === 'prop-proof') {
1120
+ const p = await findProposition(r.pId)
1121
+ if (p) {
1122
+ const list = r.side === '证明' ? (p.证明列表 = p.证明列表 || []) : (p.证伪列表 = p.证伪列表 || [])
1123
+ const item = list[r.idx]
1124
+ if (item) {
1125
+ item.正确概率 = v
1126
+ item.已验 = true
1127
+ if (v === 1) { item['支持信息/依据'] = strongestReason(t, 1) || item['支持信息/依据'] }
1128
+ else if (v === 0) {
1129
+ const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
1130
+ other.push({ 完整过程: strongestReason(t, 0) || '', 正确概率: 1, '支持信息/依据': '判定 ' + r.side + ' 错误后的反证', 已验: true })
1131
+ } else {
1132
+ const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
1133
+ other.push({ 完整过程: strongestReason(t, v >= 0.5 ? 0 : 1) || '辩论得出的相反方向论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
1134
+ item['支持信息/依据'] = strongestReason(t, v >= 0.5 ? 1 : 0) || item['支持信息/依据']
1135
+ }
1136
+ }
1137
+ await upsertProposition(p)
1138
+ await writeVerifiedCardIfNeeded(p)
1139
+ }
1140
+ } else if (r.kind === 'problem-solution') {
1141
+ const qs = await getQs(); const q = qs.find(function (x) { return x.id === r.qid }); if (q) {
1142
+ const sol = (q.解法列表 || [])[r.idx]
1143
+ if (sol) {
1144
+ sol.正确概率 = v
1145
+ sol.已验 = true
1146
+ sol.验证记录 = sol.验证记录 || []
1147
+ sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
1148
+ // 点3 回写联动:晋升问题的解法验证结果同步回源命题的证明/证伪条目(含内容比对防错位)
1149
+ if (sol.来源命题 && (sol.来源列表 === '证明' || sol.来源列表 === '证伪')) {
1150
+ const sp = await findProposition(sol.来源命题)
1151
+ if (sp) {
1152
+ const slist = sol.来源列表 === '证明' ? (sp.证明列表 || []) : (sp.证伪列表 || [])
1153
+ const item = slist[sol.来源索引]
1154
+ if (item && item.完整过程 === String(sol.完整解法 || '').replace(/^【(证明|证伪)】/, '')) {
1155
+ item.正确概率 = v; item.已验 = true
1156
+ await upsertProposition(sp)
1157
+ logActivity('promote-sync', 'promoted solution verdict ' + v + ' synced to proposition ' + sp.id + ' ' + sol.来源列表 + '#' + sol.来源索引)
1158
+ }
1159
+ }
1160
+ }
1161
+ // 点5 联动:「判断下述命题是否成立:X」问题的**新解法**验证结果 → X 命题收口
1162
+ // (转移条目走上面来源联动 + processStatusUpdates 按证明/证伪侧向收口;这里只处理无来源的新解法)
1163
+ if (q.判断命题 && !sol.来源列表) {
1164
+ const ap = await findProposition(q.判断命题)
1165
+ if (ap) {
1166
+ ap.布尔估计 = v
1167
+ if (v === 1) { ap.证明列表 = ap.证明列表 || []; ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1168
+ else if (v === 0) { ap.证伪列表 = ap.证伪列表 || []; ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true }); ap.优先级 = 'never' }
1169
+ await upsertProposition(ap)
1170
+ await writeVerifiedCardIfNeeded(ap)
1171
+ logActivity('judge-sync', 'judge problem verdict ' + v + ' synced to proposition ' + ap.id)
1172
+ }
1173
+ }
1174
+ if (v === 1) { q.已解决 = true; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q) }
1175
+ }
1176
+ await writeQs(qs)
1177
+ }
1178
+ }
1179
+ logActivity('verdict', t.rId + ' = ' + v + (v === 1 ? ' (fully verified)' : v === 0 ? ' (refuted)' : ' (uncertain)'))
1180
+ }
1181
+ function strongestReason(t, wantTrue) {
1182
+ const cids = Object.keys(t.childResults)
1183
+ let best = ''; let bestDist = -1
1184
+ for (let i = 0; i < cids.length; i++) {
1185
+ const res = t.childResults[cids[i]]
1186
+ const dist = wantTrue ? res.Result : 1 - res.Result
1187
+ if (dist > bestDist && res.Reason) { bestDist = dist; best = res.Reason }
1188
+ }
1189
+ return best
1190
+ }
1191
+ // 点4:Verified 卡片 = 每对象一卡,内容 = 该对象当前所有正确概率=1 的证明/证伪完整过程;
1192
+ // 新 1-概率条目出现时自动更新(内容不变则不写)。幂等键 = 对象 id。
1193
+ async function writeVerifiedCardIfNeeded(p) {
1194
+ if (p.布尔估计 !== 1 && p.布尔估计 !== 0) return false
1195
+ const cat = categoryOf(p)
1196
+ const list = await readVerifiedCategory(cat)
1197
+ const idx = list.findIndex(function (c) { return c.id === p.id })
1198
+ const proofs1 = (p.证明列表 || []).filter(function (x) { return x.正确概率 === 1 })
1199
+ const refutes1 = (p.证伪列表 || []).filter(function (x) { return x.正确概率 === 1 })
1200
+ const parts = []
1201
+ for (let i = 0; i < proofs1.length; i++) parts.push('【证明 #' + (i + 1) + '】' + (proofs1[i].完整过程 || ''))
1202
+ for (let i = 0; i < refutes1.length; i++) parts.push('【证伪 #' + (i + 1) + '】' + (refutes1[i].完整过程 || ''))
1203
+ const card = {
1204
+ id: p.id, 概述: p.概述, 类型: '命题', 结论: p.布尔估计 === 1, 概率: p.布尔估计,
1205
+ 内容: parts.join('\n'), 证明条数: proofs1.length, 证伪条数: refutes1.length,
1206
+ 来源: p.来源问题 || '', 时间: now(), 分类: cat,
1207
+ }
1208
+ if (idx === -1) list.push(card)
1209
+ else { if (list[idx].内容 === card.内容 && list[idx].概率 === card.概率) return false; list[idx] = card }
1210
+ await writeJson('Verified/' + cat + '_Verified.json', list)
1211
+ return idx === -1
1212
+ }
1213
+ async function writeVerifiedProblemCardIfNeeded(q) {
1214
+ if (!q || !q.已解决) return false
1215
+ const cat = '问题'
1216
+ const list = await readVerifiedCategory(cat)
1217
+ const idx = list.findIndex(function (c) { return c.id === q.id })
1218
+ const sols1 = (q.解法列表 || []).filter(function (s) { return s.正确概率 === 1 })
1219
+ const parts = []
1220
+ for (let i = 0; i < sols1.length; i++) parts.push('【解法 #' + (i + 1) + '】' + (sols1[i].完整解法 || ''))
1221
+ const card = { id: q.id, 概述: q.概述, 类型: '问题', 结论: true, 概率: 1, 内容: parts.join('\n'), 解法条数: sols1.length, 来源: q.id, 时间: now(), 分类: cat }
1222
+ if (idx === -1) list.push(card)
1223
+ else { if (list[idx].内容 === card.内容) return false; list[idx] = card }
1224
+ await writeJson('Verified/' + cat + '_Verified.json', list)
1225
+ return idx === -1
1226
+ }
1227
+
1228
+ // ================= child result dispatch =================
1229
+ async function onChildEnd(info) {
1230
+ const meta = agentRegistry[info.id]
1231
+ if (meta === undefined) return
1232
+ scheduler.activeCount = Math.max(0, scheduler.activeCount - 1)
1233
+ const output = blocksToText(info.lastAssistantMessage)
1234
+ try {
1235
+ if (meta.role === 'explorer') await handleExplorer(info.id, meta, output)
1236
+ else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason)
1237
+ else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason)
1238
+ } catch (e) { console.error('vibe-math-v2 onChildEnd error: ' + String((e && e.stack) || e)) }
1239
+ await saveAll()
1240
+ scheduleTick()
1241
+ }
1242
+
1243
+ // ================= init / control =================
1244
+ async function init(fresh) {
1245
+ if (!rootAgent) return { ok: false, message: 'no root agent available' }
1246
+ currentProject = await readCurrentProject(); await ensureDirs()
1247
+ if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
1248
+ params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState()
1249
+ // Distinguish same-process continue from cross-process restart via processEpoch:
1250
+ // equal epoch = same process (pause→resume; children may still be alive), different
1251
+ // epoch = previous process wrote this state (in-flight children are gone).
1252
+ const prevEpoch = await readJson('VibeMath_State/process_epoch.json')
1253
+ const stale = typeof prevEpoch === 'string' && prevEpoch !== processEpoch
1254
+ if (fresh || stale) {
1255
+ if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]) }
1256
+ if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
1257
+ logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
1258
+ agentRegistry = {}; tasks = {}
1259
+ }
1260
+ scheduler.activeCount = 0 // 仅清空 registry/tasks 时归零;同进程 resume 保留存活计数(并发门才准确)
1261
+ }
1262
+ await writeJson('VibeMath_State/process_epoch.json', processEpoch)
1263
+ await saveAll()
1264
+ return { ok: true }
1265
+ }
1266
+ async function startScheduler() { const r = await init(true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
1267
+ async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
1268
+ async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
1269
+ 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 } }
1270
+ // auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
1271
+ async function autoResolvePending() {
1272
+ const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
1273
+ for (let i = 0; i < pending.length; i++) {
1274
+ const d = pending[i]
1275
+ try {
1276
+ if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1277
+ else if (d.node === 'verdict') { await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
1278
+ } catch (e) { console.error('vibe-math-v2: auto-resolve decision failed: ' + String((e && e.message) || e)) }
1279
+ }
1280
+ if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
1281
+ }
1282
+ async function getStatus() {
1283
+ const qs = await getQs(); const propos = await getPropos()
1284
+ return {
1285
+ ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
1286
+ project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
1287
+ mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
1288
+ frameworkRoot: frameworkRoot(),
1289
+ problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
1290
+ propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
1291
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
1292
+ registeredAgents: Object.keys(agentRegistry).length,
1293
+ recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
1294
+ }
1295
+ }
1296
+
1297
+ // ================= projects =================
1298
+ async function setProject(slug, create) {
1299
+ if (!rootAgent) return { ok: false, message: 'no root agent available' }
1300
+ const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
1301
+ if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
1302
+ if (scheduler.running) await abortScheduler()
1303
+ currentProject = slug; await writeCurrentProject(); await ensureDirs()
1304
+ if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
1305
+ 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
1306
+ await loadSettings(); await migrateLegacyParams(); await loadState(); await saveAll()
1307
+ return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
1308
+ }
1309
+
1310
+ // ================= events / timer =================
1311
+ // NOTE: subagent/end listener and the tick timer are registered ONCE at the
1312
+ // apply level (below), routing through childOwner/sessions — NOT here, because
1313
+ // the standing-mount plugin instance is shared by every session.
1314
+
1315
+ // ================= tools =================
1316
+ function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
1317
+ const handlers = {}
1318
+ function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
1319
+ registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
1320
+ registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
1321
+ registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
1322
+ registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
1323
+ registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
1324
+ registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
1325
+ registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), async function (args) { params.mode = args.mode; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } })
1326
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { 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' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
1327
+ registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), async function () { await refreshParams(); 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' } })
1328
+ 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() })
1329
+ 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') })
1330
+ 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: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } })
1331
+ 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) {
1332
+ 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: { 来源: 'user', 说明: '用户手动添加。' } }
1333
+ await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) }
1334
+ })
1335
+ 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 } }) } })
1336
+ 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) })
1337
+ 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) })
1338
+ registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
1339
+ 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 } }) } })
1340
+ 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) })
1341
+ 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 } })
1342
+ 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' } })
1343
+ 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' } })
1344
+
1345
+ // ================= slash command /vibe =================
1346
+ async function dispatchVibeCommand(cmd, args) {
1347
+ if (cmd === 'start') return await startScheduler()
1348
+ if (cmd === 'resume') return await resumeScheduler()
1349
+ if (cmd === 'pause') return await pauseScheduler()
1350
+ if (cmd === 'abort') return await abortScheduler()
1351
+ if (cmd === 'status') { await refreshParams(); return await getStatus() }
1352
+ if (cmd === 'report') { await refreshParams(); await maybeWriteReport(true); return await buildReport() }
1353
+ if (cmd === 'mode') { params.mode = (args[0] === 'manual') ? 'manual' : 'auto'; await saveAll(); await saveSettings(); if (params.mode === 'auto') await autoResolvePending(); return { ok: true, mode: params.mode } }
1354
+ if (cmd === 'setup') { await refreshParams(); 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' } }
1355
+ if (cmd === 'save') return await saveSettings()
1356
+ if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
1357
+ 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: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } }
1358
+ if (cmd === 'add-proposition') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const p = { id: id, 概述: desc, 布尔估计: 0.5, 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1, '价值/关键性': 0.5, progress: { 来源: 'user-vibe', 说明: '用户通过 /vibe 添加。' } }; await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) } }
1359
+ if (cmd === 'list-propositions') { 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 } }) } }
1360
+ if (cmd === 'project') {
1361
+ if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
1362
+ if (args[0] === 'new') return await setProject(slugify(args.slice(1).join(' ')), true)
1363
+ return await setProject(slugify(args[0]), false)
1364
+ }
1365
+ 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 } }) }
1366
+ 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 } }
1367
+ return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <desc> | add-proposition <id> <概述> | list-propositions | project [list|new <name>|<name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
1368
+ }
1369
+
1370
+ // ================= session surface =================
1371
+ return {
1372
+ sessionId: sessionId,
1373
+ scheduler: scheduler,
1374
+ tickInFlight: tickInFlight,
1375
+ scheduleTick: scheduleTick,
1376
+ onChildEnd: onChildEnd,
1377
+ dispatchVibeCommand: dispatchVibeCommand,
1378
+ handlers: handlers,
1379
+ // 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
1380
+ refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
1381
+ getRunning: function () { return scheduler.running },
1382
+ // 会话自己的心跳节流:timer 每 1s 询问是否到点;tick 执行时刷新 lastTickAt
1383
+ tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
1384
+ }
1385
+ }
1386
+
1387
+ // ================= apply-level registrations (ONCE per preset) =================
1388
+ function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
1389
+ function registerTool(name, description, parameters, handlerName) {
1390
+ ctx.effect(() => tools.register({
1391
+ name: name, description: description, parameters: parameters,
1392
+ output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
1393
+ execute: async function (args, exec) {
1394
+ try {
1395
+ const s = getSession(exec && exec.agent)
1396
+ if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
1397
+ await s.refreshProject()
1398
+ return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
1399
+ } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
1400
+ },
1401
+ }))
1402
+ }
1403
+ registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V2 scheduler for the current project.', objParams({}), 'vibe_math_start')
1404
+ registerTool('vibe_math_resume', 'Resume the Vibe Math V2 scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
1405
+ registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
1406
+ registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
1407
+ registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
1408
+ registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
1409
+ registerTool('vibe_math_set_mode', 'Switch between manual and auto (preset) mode. Switching to auto auto-resolves any pending manual decisions.', objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), 'vibe_math_set_mode')
1410
+ registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { 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' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' } }), 'vibe_math_set_params')
1411
+ registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
1412
+ registerTool('vibe_math_save_settings', 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.', objParams({}), 'vibe_math_save_settings')
1413
+ 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'] } }), 'vibe_math_template')
1414
+ 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']), 'vibe_math_add_problem')
1415
+ 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', '概述']), 'vibe_math_add_proposition')
1416
+ registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), 'vibe_math_list_propositions')
1417
+ registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
1418
+ registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
1419
+ registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
1420
+ registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
1421
+ 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']), 'vibe_math_decide')
1422
+ registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
1423
+ registerTool('vibe_math_message_agent', 'Send a message to a tracked child agent (next turn).', objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), 'vibe_math_message_agent')
1424
+ registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
1425
+
1426
+ // /vibe slash command (registered once; routed per session)
1427
+ ctx.effect(() => commands.register({
1428
+ name: 'vibe',
1429
+ description: 'control the Vibe Math V2 solver (start/pause/projects/setup/save/decisions/agents/propositions)',
1430
+ input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|project [list|new <name>|<name>]|decisions|agents]' },
1431
+ handler: async function (invocation) {
1432
+ const s = getSession(invocation && invocation.agent)
1433
+ if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
1434
+ const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
1435
+ const parts = line.length > 0 ? line.split(/\s+/) : []
1436
+ const cmd = parts[0] || ''
1437
+ const rest = parts.slice(1)
1438
+ const result = await s.dispatchVibeCommand(cmd, rest)
1439
+ return { kind: 'success', text: JSON.stringify(result, null, 2) }
1440
+ },
1441
+ }))
1442
+
1443
+ // subagent/end (registered once; routed to the owning session via childOwner)
1444
+ ctx.on('subagent/end', function (info) {
1445
+ const sid = childOwner.get(info.id)
1446
+ const s = sid !== undefined ? sessions.get(sid) : undefined
1447
+ if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) })
1448
+ })
1449
+
1450
+ // tick timer (registered once; ticks every running session at its own pace)
1451
+ ctx.effect(() => { const t = setInterval(function () { for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() } }, 1000); return () => clearInterval(t) })
1452
+ }