dsh-vibe-math 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,692 +0,0 @@
1
- // Vibe Math — permanent host plugin: multi-agent mathematical problem solving &
2
- // verification framework ("广度探索 → 深度迭代 → 交叉验证 → 知识沉淀").
3
- //
4
- // Preset-local plugin. Only node: builtins are imported (the harness's own
5
- // TypeScript sources are not reachable via ESM resolution). Registers 20 model
6
- // tools, /vibe slash commands, and a background scheduler; provides NO service,
7
- // so it sits loose in the preset.
8
- //
9
- // Projects: each math project lives in its own folder under
10
- // <workspace>/VibeMath/Projects/<project>/ with its own qs/Verified/... layout.
11
- // The current project is recorded in <workspace>/VibeMath/current.json.
12
- // Default parameters can be overridden by an (optionally commented) JSON file:
13
- // <project>/vibe_math_setting.json (fallback <workspace>/VibeMath/vibe_math_setting.json).
14
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
15
- import { homedir } from 'node:os'
16
- import { dirname, join } from 'node:path'
17
- import { fileURLToPath } from 'node:url'
18
-
19
- // Ensure the agent-preset form is installed under the DSH preset root, so a
20
- // market/bundle install also surfaces "Vibe Math V1" in the agent-preset picker.
21
- // No-op when the preset already exists or when running inside the preset itself.
22
- function ensurePresetInstalled(logger) {
23
- try {
24
- const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh')
25
- const presetDir = join(dshHome, '.agent-presets', 'vibe-math-v1')
26
- const here = dirname(fileURLToPath(import.meta.url))
27
- const files = ['agent.cordis.yml', 'preset.yml', 'vibe-math.js']
28
- if (files.every((f) => existsSync(join(presetDir, f)))) return false
29
- if (!files.every((f) => existsSync(join(here, f)))) return false
30
- mkdirSync(presetDir, { recursive: true })
31
- for (const f of files) writeFileSync(join(presetDir, f), readFileSync(join(here, f)))
32
- logger?.info?.('[vibe-math] agent preset installed to %s (visible in the preset picker for new sessions)', presetDir)
33
- return true
34
- } catch (err) {
35
- logger?.warn?.('[vibe-math] could not install agent preset: %s', String(err?.message ?? err))
36
- return false
37
- }
38
- }
39
-
40
- export const name = 'vibe-math'
41
- export const inject = ['subagents', 'agents', 'fs', 'tools', 'commands']
42
-
43
- // Standing mount: DSH mounts each agent preset ONCE per preset and joins every
44
- // session that names it to that SAME plugin instance (see @deepseek-ai/dsh-agent-presets).
45
- // This plugin must therefore isolate ALL per-session state itself, keyed by the
46
- // root agent (session) id — otherwise two sessions running the preset at the same
47
- // time (e.g. project A and project B) would share one rootAgent/scheduler/registry
48
- // and spawn children under the wrong parent session. Each session gets its own
49
- // Session instance below via makeSession(rootAgent, sessionId).
50
- export function apply(ctx) {
51
- ensurePresetInstalled(ctx.logger)
52
- const subagents = ctx.subagents
53
- const agents = ctx.agents
54
- const fs = ctx.fs
55
- const tools = ctx.tools
56
- const commands = ctx.commands
57
- const subprocess = ctx.get('subprocess')
58
- const sandboxPolicy = ctx.get('sandboxPolicy')
59
-
60
- // ================= per-session registry =================
61
- const sessions = new Map() // rootAgentId -> Session
62
- const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
63
-
64
- function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
65
- // Walk up the durable session lineage to the top-level (root) agent of this session,
66
- // so calls from a child agent (which inherits this preset) still route to its session.
67
- function rootOf(agent) {
68
- try {
69
- let cur = agent
70
- const seen = new Set()
71
- while (cur) {
72
- const id = cur.id
73
- if (seen.has(id)) return cur
74
- seen.add(id)
75
- const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
76
- if (parentId === undefined) return cur
77
- const parent = agents.get(parentId)
78
- if (!parent) return cur
79
- cur = parent
80
- }
81
- } catch (e) { /* fall through */ }
82
- return agent
83
- }
84
- function getSession(agent) {
85
- const root = rootOf(agent)
86
- const sid = sessionIdOf(root)
87
- if (sid === undefined) return undefined
88
- let s = sessions.get(sid)
89
- if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
90
- return s
91
- }
92
-
93
- // ================= per-session plugin body =================
94
- function makeSession(rootAgent, sessionId) {
95
- let currentProject = 'default'
96
- const DEFAULT_PARAMS = {
97
- mode: 'auto',
98
- maxParallelThreshold: 4,
99
- solverMaxRounds: 20,
100
- verifierCount: 3,
101
- debateMaxRounds: 5,
102
- verdictMode: 'direct-veto',
103
- provider: '',
104
- model: '',
105
- solverPersona: '',
106
- verifierPersona: '',
107
- solverToolAllow: [],
108
- solverToolDeny: [],
109
- verifierToolAllow: [],
110
- verifierToolDeny: [],
111
- solverMaxToolCalls: 0,
112
- verifierMaxToolCalls: 0,
113
- reportIntervalMs: 0, // 0 = 仅事件驱动(有代理状态更新等事件才写报告);>0 = 定时自动写(毫秒)
114
- tickIntervalMs: 2000, // 调度器心跳间隔(毫秒):多久扫描一次状态并推进
115
- activityLogCap: 100, // 活动日志保留条数(report.recentActivity 最多显示 30 条)
116
- }
117
- let params = Object.assign({}, DEFAULT_PARAMS)
118
- let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
119
- let agentRegistry = {}
120
- let dependencies = {}
121
- let decisionQueue = []
122
- let tasks = {}
123
- let tickInFlight = false
124
- let lastTickAt = 0
125
- let brainstormRetries = {}
126
- let deriveRetries = {}
127
- let solvedByVerified = {}
128
- let promotionQueue = {}
129
- let verifierAccuracy = {}
130
- let activityLog = []
131
- let lastReportWrite = 0
132
- let reportDirty = false
133
-
134
- // ================= helpers =================
135
- function textBlock(t) { return { type: 'text', text: String(t) } }
136
- function now() { return Date.now() }
137
- 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 }
138
- function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
139
- 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 '.' }
140
- function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
141
- function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
142
- function frameworkRoot() { return projectRoot(currentProject) }
143
- function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
144
- function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
145
- 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 }
146
- function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
147
- 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() }
148
- function parseJson(text) {
149
- if (typeof text !== 'string') return undefined
150
- const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
151
- // 1) prefer any ```json ... ``` code fence
152
- const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
153
- let m
154
- while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
155
- // 2) whole trimmed text as pure JSON
156
- const whole = tryObj(text.trim()); if (whole !== undefined) return whole
157
- // 3) fallback: scan EVERY balanced {…} block and return the LARGEST one that parses as a JSON object
158
- // (robust against prose containing braces like {2k+1}, which are not valid JSON)
159
- let best = undefined; let bestLen = -1
160
- for (let start = 0; start < text.length; start++) {
161
- if (text[start] !== '{') continue
162
- let depth = 0, inStr = false, esc = false, end = -1
163
- for (let i = start; i < text.length; i++) {
164
- const c = text[i]
165
- if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
166
- if (c === '"') { inStr = true; continue }
167
- if (c === '{') depth++
168
- else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
169
- }
170
- if (end === -1) continue
171
- const obj = tryObj(text.slice(start, end + 1))
172
- if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
173
- }
174
- return best
175
- }
176
- function csvEscape(v) { const s = String(v == null ? '' : v); return /[",\n\r]/.test(s) ? '"' + s.replace(/"/g, '""') + '"' : s }
177
- function csvRow(vals) { return vals.map(csvEscape).join(',') }
178
- function parseCsv(text) { const rows = []; let row = []; let field = ''; let q = false; for (let i = 0; i < text.length; i++) { const c = text[i]; if (q) { if (c === '"') { if (text[i + 1] === '"') { field += '"'; i++ } else q = false } else field += c } else if (c === '"') q = true; else if (c === ',') { row.push(field); field = '' } else if (c === '\n') { row.push(field); rows.push(row); row = []; field = '' } else if (c !== '\r') field += c } if (field !== '' || row.length > 0) { row.push(field); rows.push(row) } return rows }
179
- function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
180
- 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 }
181
-
182
- // ================= parameter schema (for setup + settings template) =================
183
- const PARAM_SCHEMA = [
184
- { name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
185
- { name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理数(并发门阈值)', suggestion: 4 },
186
- { name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数', suggestion: 20 },
187
- { name: 'verifierCount', type: 'integer', description: '每个验证单元的独立验证器数量(内部强制 ≥3)', suggestion: 3 },
188
- { name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
189
- { name: 'verdictMode', type: 'enum', options: ['direct-veto', 'weighted-vote'], description: 'direct-veto = 一票否决(严谨);weighted-vote = 证伪优先加权', suggestion: 'direct-veto' },
190
- { name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
191
- { name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
192
- { name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
193
- { name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
194
- { name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
195
- { name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表(如禁用写文件/控制工具)', suggestion: [] },
196
- { name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表(空 = 继承全部)', suggestion: [] },
197
- { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
198
- { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
199
- { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
200
- { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写 report.json);>0 = 定时自动汇报', suggestion: 0 },
201
- { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
202
- { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report.recentActivity 的细节量,报告最多显示 30 条)', suggestion: 100 },
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 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 } }
209
- 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 } }
210
- async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
211
- async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
212
- async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
213
- 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 [] } }
214
- 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 [] } }
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', 'Verified', 'Pending_Verification', 'Under_Verification', 'Temp', 'Temp_Validated', '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 atomicMove(srcRel, dstRel) { const base = frameworkRoot(); return await runShell('Move-Item -Force -LiteralPath ' + psQuote(base + '/' + srcRel) + ' -Destination ' + psQuote(base + '/' + dstRel)) }
229
- async function removeFile(rel) { const base = frameworkRoot(); return await runShell('Remove-Item -Force -LiteralPath ' + psQuote(base + '/' + rel) + ' -ErrorAction SilentlyContinue') }
230
-
231
- // ================= settings file (JSONC) =================
232
- function sanitizeParams(obj) {
233
- const out = {}
234
- const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap']
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 (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
241
- else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
242
- else if (k === 'verdictMode') { out[k] = (v === 'direct-veto' || v === 'weighted-vote') ? v : DEFAULT_PARAMS[k] }
243
- else { out[k] = v }
244
- }
245
- return out
246
- }
247
- async function loadSettings() {
248
- let text = await readText('vibe_math_setting.json')
249
- if (text === undefined) text = await readTextAbs(vibeRoot() + '/vibe_math_setting.json')
250
- if (text === undefined) return
251
- const clean = stripJsonComments(text)
252
- try {
253
- const obj = JSON.parse(clean)
254
- if (obj && typeof obj === 'object' && !Array.isArray(obj)) params = Object.assign({}, params, sanitizeParams(obj))
255
- } catch (e) {
256
- console.error('vibe-math: invalid vibe_math_setting.json ignored: ' + String((e && e.message) || e))
257
- }
258
- }
259
- function settingsTemplateFrom(src) {
260
- const lines = []
261
- lines.push('{')
262
- lines.push(' // Vibe Math 默认参数配置(JSON with Comments,可加 // 注释)。')
263
- lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
264
- lines.push(' // 本文件是参数的唯一持久化来源:vibe_math_set_params / set_mode 会立即写回此文件;全局文件仅作项目文件不存在时的回退默认。')
265
- const keys = Object.keys(src).sort()
266
- for (let i = 0; i < keys.length; i++) {
267
- const k = keys[i]
268
- const v = src[k]
269
- const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
270
- const desc = schema ? schema.description : ''
271
- const comma = i === keys.length - 1 ? '' : ','
272
- lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
273
- lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
274
- }
275
- lines.push('}')
276
- return lines.join('\n') + '\n'
277
- }
278
- function settingsTemplate() { return settingsTemplateFrom(params) }
279
- async function saveSettings() { await writeText('vibe_math_setting.json', settingsTemplate()); return { ok: true, path: frameworkRoot() + '/vibe_math_setting.json' } }
280
- 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' } }
281
-
282
- // ================= persistence =================
283
- async function loadState() { const s = await readJson('VibeMath_State/scheduler_state.json'); if (s) scheduler = Object.assign({}, scheduler, s); const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r; const d = await readJson('VibeMath_State/dependencies.json'); if (d) dependencies = d; const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq; const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk; const sv = await readJson('VibeMath_State/solved_by_verified.json'); if (sv) solvedByVerified = sv; const pq = await readJson('VibeMath_State/promotion_queue.json'); if (pq) promotionQueue = pq; const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va }
284
- async function saveAll() { await writeJson('VibeMath_State/scheduler_state.json', scheduler); await writeJson('VibeMath_State/agent_registry.json', agentRegistry); await writeJson('VibeMath_State/dependencies.json', dependencies); await writeJson('VibeMath_State/decision_queue.json', decisionQueue); await writeJson('VibeMath_State/tasks.json', tasks); await writeJson('VibeMath_State/solved_by_verified.json', solvedByVerified); await writeJson('VibeMath_State/promotion_queue.json', promotionQueue); await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy); scheduler.lastCheckpoint = now() }
285
- // 查询类工具(status/setup/report)汇报前重读设置文件:文件是唯一持久化源,可能在会话启动后被用户手改或由本进程外编辑更新。
286
- async function refreshParams() {
287
- params = Object.assign({}, DEFAULT_PARAMS)
288
- await loadSettings()
289
- await migrateLegacyParams()
290
- }
291
- // 一次性迁移(单文件化):旧版 params.json 合并进 vibe_math_setting.json 后删除。
292
- async function migrateLegacyParams() {
293
- const legacy = await readJson('VibeMath_State/params.json')
294
- if (!legacy || Object.keys(legacy).length === 0) return
295
- try {
296
- params = Object.assign({}, params, sanitizeParams(legacy))
297
- await saveSettings()
298
- await writeJson('VibeMath_State/params.json', {}) // 一次性守卫:即使删除失败,空对象也不再覆盖设置文件
299
- await removeFile('VibeMath_State/params.json')
300
- logActivity('params', 'legacy params.json merged into vibe_math_setting.json and removed')
301
- } catch (e) { console.error('vibe-math: params migration failed: ' + String((e && e.message) || e)) }
302
- }
303
-
304
- // ================= activity log / report =================
305
- 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 }
306
- function buildReport() {
307
- const openTasks = Object.keys(tasks).filter(function (k) { const t = tasks[k]; return t.status === 'spawning' || t.status === 'debating' || t.status === 'awaiting-verdict' })
308
- return {
309
- ok: true,
310
- at: now(),
311
- project: currentProject,
312
- frameworkRoot: frameworkRoot(),
313
- running: scheduler.running,
314
- mode: params.mode,
315
- activeCount: scheduler.activeCount,
316
- maxParallelThreshold: params.maxParallelThreshold,
317
- pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
318
- openTasks: openTasks.length,
319
- registeredAgents: Object.keys(agentRegistry).length,
320
- recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
321
- params: params,
322
- }
323
- }
324
- async function maybeWriteReport(force) {
325
- const interval = Number(params.reportIntervalMs) || 0
326
- if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
327
- if (!force && interval <= 0 && !reportDirty) return
328
- await writeJson('Progress_Logs/report.json', buildReport())
329
- lastReportWrite = now()
330
- reportDirty = false
331
- }
332
-
333
- // ================= qs & progress =================
334
- async function getQs() { const text = await readText('qs/qs.csv'); if (text === undefined) return []; const rows = parseCsv(text); const out = []; for (let i = 1; i < rows.length; i++) { const r = rows[i]; if (r.length < 4) continue; out.push({ id: (r[0] || '').trim(), description: r[1] || '', priority: parseInt(r[2], 10) || 0, status: (r[3] || 'unsolved').trim(), deps: safeJson(r[4], []) }) } return out }
335
- async function writeQs(rows) { const lines = ['id,description,priority,status,deps']; for (let i = 0; i < rows.length; i++) { const q = rows[i]; lines.push(csvRow([q.id, q.description, q.priority, q.status, JSON.stringify(q.deps || [])])) } await writeText('qs/qs.csv', lines.join('\n') + '\n') }
336
- const PHEADER = ['direction_id', 'title', 'method', 'core_assumption', 'round', 'status', 'survival_probability', 'dead_end_reason', 'lemmas_json', 'sub_routes_json', 'aux_hypotheses_json', 'updated_at']
337
- async function readProgress(qid) { const text = await readText('Progress_Logs/' + qid + '_progress.csv'); if (text === undefined) return []; const rows = parseCsv(text); const out = []; for (let i = 1; i < rows.length; i++) { const r = rows[i]; if (r.length < 12) continue; out.push({ direction_id: r[0], title: r[1], method: r[2], core_assumption: r[3], round: parseInt(r[4], 10) || 0, status: r[5] || 'active', survival_probability: parseFloat(r[6]) || 0, dead_end_reason: r[7] || '', lemmas: safeJson(r[8], []), sub_routes: safeJson(r[9], []), aux_hypotheses: safeJson(r[10], []), updated_at: r[11] || '' }) } return out }
338
- async function writeProgress(qid, dirs) { const lines = [csvRow(PHEADER)]; for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; lines.push(csvRow([d.direction_id, d.title || '', d.method || '', d.core_assumption || '', d.round || 0, d.status || 'active', d.survival_probability || 0, d.dead_end_reason || '', JSON.stringify(d.lemmas || []), JSON.stringify(d.sub_routes || []), JSON.stringify(d.aux_hypotheses || []), d.updated_at || String(now())])) } await writeText('Progress_Logs/' + qid + '_progress.csv', lines.join('\n') + '\n') }
339
-
340
- // ================= child spawn / followup =================
341
- 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' }
342
- 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 }
343
- function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const f = {}; if (Array.isArray(allow) && allow.length > 0) f.allow = allow.slice(); if (Array.isArray(deny) && deny.length > 0) f.deny = deny.slice(); return (f.allow || f.deny) ? f : undefined }
344
- async function spawnChild(label, promptText, meta) { const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }; const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf; let started; try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) } catch (e) { if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math: 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 } } agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {}); childOwner.set(started.childId, sessionId); scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1; await saveAll(); return started.childId }
345
- 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() }
346
- async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
347
-
348
- // ================= prompts =================
349
- function solverPersonaText() { return params.solverPersona ? (String(params.solverPersona) + '\n\n') : '' }
350
- function verifierPersonaText() { return params.verifierPersona ? (String(params.verifierPersona) + '\n\n') : '' }
351
- function capabilitiesText(role, qid) {
352
- const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
353
- let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
354
- t += '- You may READ any file under Verified/ as a known, trusted dependency.\n'
355
- if (role === 'solver' && qid) t += '- You may READ Progress_Logs/' + qid + '_progress.csv to inspect ALL directions\' progress, blockers, and prior results for this problem.\n'
356
- t += '- You may use external tools (web search, symbolic/numeric computation, literature lookup) to assist; '
357
- t += (maxCalls && Number(maxCalls) > 0) ? ('call such external tools AT MOST ' + maxCalls + ' times this round.\n') : 'no per-round limit by default.\n'
358
- t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer.\n'
359
- t += '\nHOW TO READ EXISTING KNOWLEDGE (coarse scan → fine read):\n'
360
- t += '- These files are CSV (RFC 4180); content cells are JSON-encoded strings. A conclusion/lemma object carries summary-index fields ("title", "statement") and the full detail ("proof").\n'
361
- t += '- COARSE SCAN first: use a read/grep tool to extract ONLY the summary index (e.g. the direction "title" column, or each object\'s "title"/"statement") to quickly locate which files / sub-objects look relevant or valuable — do NOT load full proofs yet.\n'
362
- t += '- FINE READ after: once you identify a valuable conclusion file or sub-object, read that file again and extract its full JSON content (e.g. "statement", "proof") via the index you just found.\n'
363
- return t
364
- }
365
- function brainstormPrompt(q) { return 'You are a research mathematician orchestrating strategy for one problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.description + '\n\nDo a first-stage metacognitive brainstorm: decompose constraints, test boundary/extreme cases, map to similar known problems. Then propose 3-6 DIVERSE, mutually distinct solution directions.\n' + capabilitiesText('solver', q.id) + '\n\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}]}' }
366
- function deriveDirectionsPrompt(q, prog) { const summary = prog.map(function (d) { return '- ' + d.direction_id + ': ' + d.title + ' (status ' + d.status + ', round ' + d.round + ', survival ' + d.survival_probability + ')' + (d.dead_end_reason ? ' [blocker: ' + d.dead_end_reason + ']' : '') }).join('\n'); return 'You are a research mathematician re-deriving strategy for a problem whose prior directions stalled or failed.\n\nPROBLEM (id: ' + q.id + '): ' + q.description + '\n\nPRIOR DIRECTIONS (with blockers):\n' + summary + '\n' + capabilitiesText('solver', q.id) + '\n\nBased on the pain points and blockers above, deeply derive 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. Avoid any direction equivalent to a failed one.\n\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n{"directions":[{"title":"...","method":"...","core_assumption":"...","motivation":"...","feasibility":0.5}]}' }
367
- function dirSummary(dir) { const lemmas = (dir.lemmas || []).map(function (l) { return l.title }).join('; '); const blockers = (dir.sub_routes || []).filter(function (s) { return s && s.blocker }).map(function (s) { return s.blocker }).join('; '); return 'round ' + dir.round + ', status ' + dir.status + ', survival ' + dir.survival_probability + ', lemmas: ' + lemmas + ', blockers: ' + blockers }
368
- function solverPrompt(q, dir, round) { let head = solverPersonaText() + 'You are a dedicated solver agent working ONE solution direction of a math problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.description + '\nDIRECTION: ' + dir.title + ' (method: ' + dir.method + '; assumption: ' + dir.core_assumption + ')\nROUND: ' + round + '\n'; if (round > 1) head += '\nYOUR PRIOR PROGRESS: ' + dirSummary(dir) + '\n'; head += capabilitiesText('solver', q.id) + '\n'; head += '\nAdvance the proof/computation for this direction. You MUST produce (even if incomplete):\n- new lemmas / intermediate conclusions WITH full proofs;\n- each concrete sub-route tried, its progress, an explicit feasibility signal, and any blocker;\n- an updated survival probability for this direction.\n\nIf you obtain a COMPLETE solution, adversarially self-check (construct counterexamples, test boundary conditions) BEFORE declaring success, and put the full solution text in "solution".\n\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose and no braces { } outside the JSON:\n{"status":"continue|success|dead-end","solution":"complete solution text, or null","lemmas":[{"title":"...","statement":"...","proof":"..."}],"findings":["..."],"sub_routes":[{"title":"...","progress":"...","feasibility_signal":"...","blocker":"..."}],"survival_probability":0.5,"dead_end_reason":"... or null","aux_hypotheses":[{"title":"...","statement":"..."}]}'; return head }
369
- function verifierReviewPrompt(unit) { return verifierPersonaText() + 'You are a STRICT peer reviewer verifying one mathematical claim. Assume its declared dependencies hold, then check whether the claim is correct.\n\nTARGET CLAIM (id: ' + unit.obj_id + '): ' + unit.title + '\nCONTENT:\n' + unit.content + '\n' + capabilitiesText('verifier') + '\n\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n{"verdict":"true|false|uncertain","reason":"detailed logic chain, potential counterexample, or challenge to a dependency","strictness":"strict|lenient"}' }
370
- function verifierDebatePrompt(unit, transcript) { return verifierPersonaText() + 'You are one reviewer in a debate ("交流群") about this claim.\n\nTARGET CLAIM (id: ' + unit.obj_id + '): ' + unit.title + '\nCONTENT:\n' + unit.content + '\n' + capabilitiesText('verifier') + '\n\nOTHERS HAVE SAID SO FAR:\n' + transcript + '\n\nRespond to the others (agree / rebut / add evidence). If you changed your mind, say why explicitly. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n{"verdict":"true|false|uncertain","reason":"...","strictness":"strict|lenient"}' }
371
- function deciderPrompt(qsList, verifiedContent) { return 'You are a triage agent. Given a newly VERIFIED mathematical result, decide whether it is a COMPLETE solution to any UNSOLVED problem in the list.\n\nUNSOLVED PROBLEMS:\n' + qsList + '\n\nVERIFIED RESULT:\n' + verifiedContent + '\n\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n{"solves_qid":"<problem id> or null"}' }
372
- function solutionText(parsed, dir) { if (parsed && parsed.solution) return String(parsed.solution); const parts = []; if (parsed && parsed.lemmas && parsed.lemmas.length) parts.push('Lemmas: ' + JSON.stringify(parsed.lemmas)); if (parsed && parsed.findings && parsed.findings.length) parts.push('Findings: ' + JSON.stringify(parsed.findings)); if (parts.length) return parts.join('\n'); return 'Direction "' + ((dir && dir.title) || '') + '" reported success (no explicit solution text).' }
373
- function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
374
-
375
- // ================= init / control =================
376
- async function init() {
377
- if (!rootAgent) return { ok: false, message: 'no root agent available' }; currentProject = await readCurrentProject(); await ensureDirs(); if ((await readText('qs/qs.csv')) === undefined) await writeText('qs/qs.csv', 'id,description,priority,status,deps\n'); params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState(); scheduler.activeCount = 0; await saveAll(); return { ok: true } }
378
- async function startScheduler() { const r = await init(); 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() } }
379
- async function resumeScheduler() { const r = await init(); 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() } }
380
- async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
381
- 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 } }
382
- // auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
383
- async function autoResolvePending() {
384
- const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
385
- for (let i = 0; i < pending.length; i++) {
386
- const d = pending[i]
387
- try {
388
- if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
389
- 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 } }
390
- else if (d.node === 'promote') { await promoteUnit(d.data.objId); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
391
- } catch (e) { console.error('vibe-math: auto-resolve decision failed: ' + String((e && e.message) || e)) }
392
- }
393
- if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
394
- }
395
- async function getStatus() { return { ok: true, initialized: rootAgent !== undefined, running: scheduler.running, project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'), mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold, frameworkRoot: frameworkRoot(), pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length, openTasks: Object.keys(tasks).filter(function (k) { return tasks[k].status === 'spawning' || tasks[k].status === 'debating' || tasks[k].status === 'awaiting-verdict' }).length, registeredAgents: Object.keys(agentRegistry).length, recentActivity: activityLog.slice(-10), params: params } }
396
-
397
- // ================= projects =================
398
- async function setProject(slug, create) {
399
- if (!rootAgent) return { ok: false, message: 'no root agent available' }
400
- const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
401
- if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
402
- if (scheduler.running) await abortScheduler()
403
- currentProject = slug
404
- await writeCurrentProject()
405
- await ensureDirs()
406
- if ((await readText('qs/qs.csv')) === undefined) await writeText('qs/qs.csv', 'id,description,priority,status,deps\n')
407
- params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; dependencies = {}; decisionQueue = []; tasks = {}; brainstormRetries = {}; deriveRetries = {}; solvedByVerified = {}; promotionQueue = {}; verifierAccuracy = {}; activityLog = []
408
- await loadSettings(); await migrateLegacyParams(); await loadState(); await saveAll()
409
- return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
410
- }
411
-
412
- // ================= decisions (manual/auto) =================
413
- 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 }
414
- 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 } }
415
- async function applyDecision(node, data, resolution) { if (node === 'spawn') { if (resolution.action === 'approve') { await spawnChild(data.label, data.promptText, data.meta); return { spawned: true } } return { spawned: false } } if (node === 'verdict') { const overridden = resolution.action === 'override' && (resolution.verdict === 'true' || resolution.verdict === 'false'); const v = overridden ? resolution.verdict : data.verdict; await settleVerdict(data.task, v); delete tasks['verify:' + data.unitId]; return { verdict: v, overridden: overridden } } if (node === 'promote') { if (resolution.action === 'approve') { await promoteUnit(data.objId); return { promoted: true } } return { promoted: false } } return {} }
416
- 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 ? ' ' + resolution.verdict : '')); await saveAll(); scheduleTick(); return { ok: true, message: 'decision resolved' } }
417
-
418
- // ================= scheduler =================
419
- function scheduleTick() { tick().catch(function (e) { console.error('vibe-math tick error: ' + String((e && e.stack) || e)) }) }
420
- async function tick() { if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return; tickInFlight = true; lastTickAt = now(); try { await processVerification(); await reconcileTasks(); await processPromotion(); await processDecider(); await processSolve(); await maybeWriteReport(false); const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' && !solvedByVerified[q.id] }); if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0) { scheduler.running = false; logActivity('stop', 'no unsolved problems, no active agents/tasks — scheduler stopped'); await saveAll(); await maybeWriteReport(true) } } finally { tickInFlight = false } }
421
- async function processSolve() { if (scheduler.activeCount >= params.maxParallelThreshold) return; const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' && !solvedByVerified[q.id] }).sort(function (a, b) { return a.priority - b.priority }); for (let i = 0; i < unsolved.length; i++) { if (scheduler.activeCount >= params.maxParallelThreshold) break; const q = unsolved[i]; const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'brainstorm' || m.role === 'solver' || m.role === 'derive') }); if (busy) continue; const prog = await readProgress(q.id); if (prog.length === 0) { if ((brainstormRetries[q.id] || 0) >= 3) { await writeProgress(q.id, [{ direction_id: 'd_' + shortId(), title: 'brainstorm failed', method: '', core_assumption: '', round: 0, status: 'dead-end', survival_probability: 0, dead_end_reason: 'brainstorm produced no directions after 3 attempts', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }]); continue } brainstormRetries[q.id] = (brainstormRetries[q.id] || 0) + 1; const label = 'brainstorm:' + q.id; const promptText = brainstormPrompt(q); const r = await maybeGate('spawn', 'brainstorm for problem ' + q.id, { label: label, promptText: promptText, meta: { role: 'brainstorm', qid: q.id } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (r && r.gated) return } else { for (let j = 0; j < prog.length; j++) { if (scheduler.activeCount >= params.maxParallelThreshold) break; const dir = prog[j]; if (dir.status === 'success' || dir.status === 'dead-end') continue; const running = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && m.direction === dir.direction_id && m.role === 'solver' }); if (running) continue; const label = 'solver:' + q.id + ':' + dir.direction_id; const promptText = solverPrompt(q, dir, Math.max(1, dir.round + 1)); const r = await maybeGate('spawn', 'solver for problem ' + q.id + ' direction ' + dir.direction_id, { label: label, promptText: promptText, meta: { role: 'solver', qid: q.id, description: q.description, direction: dir.direction_id, round: Math.max(1, dir.round + 1) } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (r && r.gated) return } if (scheduler.activeCount < params.maxParallelThreshold) { const activeDirs = prog.filter(function (d) { return d.status !== 'success' && d.status !== 'dead-end' }); if (activeDirs.length === 0 && (deriveRetries[q.id] || 0) < 3) { deriveRetries[q.id] = (deriveRetries[q.id] || 0) + 1; const dlabel = 'derive:' + q.id; const dprompt = deriveDirectionsPrompt(q, prog); const dr = await maybeGate('spawn', 'derive new directions for problem ' + q.id, { label: dlabel, promptText: dprompt, meta: { role: 'derive', qid: q.id, description: q.description } }, async function (d) { await spawnChild(d.label, d.promptText, d.meta); return { spawned: true } }); if (dr && dr.gated) return } } } } }
422
-
423
- // ================= child result handling =================
424
- async function onChildEnd(info) { const meta = agentRegistry[info.id]; if (meta === undefined) return; scheduler.activeCount = Math.max(0, scheduler.activeCount - 1); const output = blocksToText(info.lastAssistantMessage); try { if (meta.role === 'brainstorm') await handleBrainstorm(info.id, meta, output); else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason); else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason); else if (meta.role === 'decider') await handleDecider(info.id, meta, output); else if (meta.role === 'derive') await handleDerive(info.id, meta, output) } catch (e) { console.error('vibe-math onChildEnd error: ' + String((e && e.stack) || e)) } await saveAll(); scheduleTick() }
425
- async function handleBrainstorm(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('brainstorm', 'problem ' + meta.qid + ' brainstorm returned no directions'); await saveAll(); return } brainstormRetries[meta.qid] = 0; const prog = []; for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || '', round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('brainstorm', 'problem ' + meta.qid + ' → ' + prog.length + ' directions'); await writeProgress(meta.qid, prog) }
426
- async function handleDerive(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const dirs = (parsed && parsed.directions) || []; if (dirs.length === 0) { logActivity('derive', 'problem ' + meta.qid + ' derived no new directions'); await saveAll(); return } const prog = await readProgress(meta.qid); for (let i = 0; i < dirs.length; i++) { const d = dirs[i]; prog.push({ direction_id: 'd_' + shortId(), title: d.title || '', method: d.method || '', core_assumption: d.core_assumption || (d.motivation || ''), round: 0, status: 'active', survival_probability: (typeof d.feasibility === 'number') ? d.feasibility : 0.5, dead_end_reason: '', lemmas: [], sub_routes: [], aux_hypotheses: [], updated_at: String(now()) }) } logActivity('derive', 'problem ' + meta.qid + ' derived ' + dirs.length + ' new directions'); await writeProgress(meta.qid, prog) }
427
- async function handleSolver(childId, meta, output, stopReason) { const qid = meta.qid; const direction = meta.direction; const parsed = parseJson(output); const prog = await readProgress(qid); const dir = prog.find(function (d) { return d.direction_id === direction }); if (!dir) { delete agentRegistry[childId]; return } if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } const status = (parsed && parsed.status) || statusFromStop(stopReason); dir.round = meta.round; dir.status = status; if (parsed) { if (parsed.lemmas) dir.lemmas = parsed.lemmas; if (parsed.sub_routes) dir.sub_routes = parsed.sub_routes; if (parsed.aux_hypotheses) dir.aux_hypotheses = parsed.aux_hypotheses; if (typeof parsed.survival_probability === 'number') dir.survival_probability = parsed.survival_probability; if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason } if (status === 'success') { dir.status = 'success'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' success at round ' + meta.round); await writePending(qid, dir, parsed) } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) { dir.status = 'dead-end'; if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'; delete agentRegistry[childId]; logActivity('solver', qid + '/' + direction + ' dead-end: ' + dir.dead_end_reason) } else if (!scheduler.running) { delete agentRegistry[childId] } else { const q = { id: qid, description: meta.description || '' }; await followupChild(childId, solverPrompt(q, dir, meta.round + 1)); agentRegistry[childId].round = meta.round + 1; dir.round = meta.round + 1 } if (parsed && parsed.aux_hypotheses && parsed.aux_hypotheses.length > 0) await handleAuxHypotheses(qid, parsed.aux_hypotheses); dir.updated_at = String(now()); await writeProgress(qid, prog) }
428
- async function handleAuxHypotheses(qid, hyps) { const qs = await getQs(); for (let i = 0; i < hyps.length; i++) { const h = hyps[i]; if (!h || !h.title) continue; const subId = qid + '_sub_' + shortId(); qs.push({ id: subId, description: h.title + (h.statement ? ' - ' + h.statement : ''), priority: 1, status: 'unsolved', deps: [] }); dependencies[qid] = dependencies[qid] || []; dependencies[qid].push(subId); await writeQs(qs); logActivity('subquestion', qid + ' spawned sub-question ' + subId + ' (Aux_Hypothesis)') } }
429
- async function writePending(qid, dir, parsed) { const doc = { qid: qid, direction: dir.direction_id, solution: solutionText(parsed, dir), lemmas: (parsed && parsed.lemmas) || [], findings: (parsed && parsed.findings) || [], sub_routes: (parsed && parsed.sub_routes) || [], aux_hypotheses: (parsed && parsed.aux_hypotheses) || [], survival_probability: dir.survival_probability, created_at: String(now()) }; const id = uuid(); await writeText('Pending_Verification/' + id + '.csv', 'qid,direction,content_json,created_at\n' + csvRow([qid, dir.direction_id, JSON.stringify(doc), String(now())]) + '\n') }
430
-
431
- // ================= verification =================
432
- async function processVerification() {
433
- const files = await listFiles('Pending_Verification')
434
- for (let i = 0; i < files.length; i++) {
435
- const fname = files[i]
436
- const text = await readText('Pending_Verification/' + fname)
437
- if (text === undefined) continue
438
- const rows = parseCsv(text)
439
- const contentJson = rows.length > 1 ? safeJson(rows[1][2], null) : null
440
- if (!contentJson) { await removeFile('Pending_Verification/' + fname); continue }
441
- const units = decompose(contentJson)
442
- for (let j = 0; j < units.length; j++) {
443
- const unit = units[j]
444
- await writeText('Under_Verification/' + unit.obj_id + '_' + safeTitle(unit.title) + '.csv', 'obj_id,title,content_json,dependencies_json\n' + csvRow([unit.obj_id, unit.title, JSON.stringify(unit), JSON.stringify(unit.dependencies || [])]) + '\n')
445
- tasks['verify:' + unit.obj_id] = { id: 'verify:' + unit.obj_id, type: 'verify', unitId: unit.obj_id, unit: unit, status: 'spawning', children: [], childResults: {}, round: 1, expectedCount: Math.max(3, params.verifierCount), createdAt: now() }
446
- }
447
- await removeFile('Pending_Verification/' + fname)
448
- }
449
- const ids = Object.keys(tasks)
450
- for (let i = 0; i < ids.length; i++) {
451
- const t = tasks[ids[i]]
452
- if (t.type !== 'verify' || t.status !== 'spawning') continue
453
- if (scheduler.activeCount >= params.maxParallelThreshold) break
454
- await backfillVerifiers(t)
455
- }
456
- }
457
- function safeTitle(t) { return String(t || 'unit').replace(/[^a-zA-Z0-9_\-\u4e00-\u9fa5]/g, '_').slice(0, 60) }
458
- function decompose(doc) {
459
- const units = []
460
- const qid = doc.qid || ''
461
- const lemmas = doc.lemmas || []
462
- for (let i = 0; i < lemmas.length; i++) { const l = lemmas[i]; units.push({ obj_id: uuid(), qid: qid, isSolution: false, title: l.title || ('lemma-' + (i + 1)), content: JSON.stringify(l), dependencies: [] }) }
463
- const findings = doc.findings || []
464
- for (let i = 0; i < findings.length; i++) units.push({ obj_id: uuid(), qid: qid, isSolution: false, title: 'finding-' + (i + 1), content: String(findings[i]), dependencies: [] })
465
- const deps = (doc.aux_hypotheses || []).map(function (h) { return { title: h.title, assumed: true } })
466
- if (doc.solution) {
467
- units.push({ obj_id: uuid(), qid: qid, isSolution: true, title: 'solution-' + doc.direction, content: JSON.stringify({ solution: doc.solution, lemmas: lemmas, findings: findings }), dependencies: deps })
468
- } else if (units.length === 0) {
469
- units.push({ obj_id: uuid(), qid: qid, isSolution: true, title: 'solution-' + doc.direction, content: JSON.stringify({ solution: null, lemmas: lemmas, findings: findings }), dependencies: deps })
470
- }
471
- return units
472
- }
473
- async function backfillVerifiers(t) {
474
- while (t.children.length < t.expectedCount) {
475
- if (scheduler.activeCount >= params.maxParallelThreshold) break
476
- const index = t.children.length
477
- const childId = await spawnChild('verifier:' + t.unitId + ':' + index, verifierReviewPrompt(t.unit), { role: 'verifier', unitId: t.unitId, unit: t.unit, round: 1, index: index })
478
- t.children.push(childId)
479
- }
480
- if (t.children.length >= t.expectedCount) t.status = 'debating'
481
- }
482
- async function handleVerifier(childId, meta, output, stopReason) {
483
- const unitId = meta.unitId
484
- const parsed = parseJson(output)
485
- const verdict = (parsed && parsed.verdict) || 'uncertain'
486
- let t = tasks['verify:' + unitId]
487
- if (!t) { t = { id: 'verify:' + unitId, type: 'verify', unitId: unitId, unit: meta.unit || { obj_id: unitId, title: unitId, content: '' }, status: 'debating', children: [], childResults: {}, round: 1, expectedCount: Math.max(3, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
488
- if (!parsed && !scheduler.running) {
489
- // abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerification 重建)
490
- delete agentRegistry[childId]
491
- const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
492
- delete t.childResults[childId]
493
- if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
494
- return
495
- }
496
- if (t.children.indexOf(childId) === -1) t.children.push(childId)
497
- t.childResults[childId] = { verdict: verdict, reason: (parsed && parsed.reason) || '', strictness: (parsed && parsed.strictness) || 'lenient', round: meta.round }
498
- delete agentRegistry[childId]
499
- const allReported = t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
500
- if (!allReported) { await saveAll(); return }
501
- await advanceVerification(t, meta.round)
502
- await saveAll()
503
- }
504
- function unitFromTask(t) { return t.unit || { obj_id: t.unitId, title: t.unitId, content: '' } }
505
- function consensus(t) { const vs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid].verdict }); if (vs.length === 0) return false; return vs.every(function (v) { return v === 'true' }) || vs.every(function (v) { return v === 'false' }) }
506
- 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 + ': verdict=' + r.verdict + ' reason=' + r.reason) } return parts.join('\n') }
507
- function verifierWeight(cid, strictness) { const acc = verifierAccuracy[cid] || { correct: 0, total: 0 }; const base = acc.total > 0 ? (acc.correct / acc.total) : 0.5; const bonus = strictness === 'strict' ? 0.1 : strictness === 'lenient' ? -0.1 : 0; return Math.max(0, Math.min(1, base + bonus)) }
508
- function weightsOf(t) { const out = {}; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const cid = cids[i]; const r = t.childResults[cid]; out[cid] = { accuracy: verifierAccuracy[cid] ? (verifierAccuracy[cid].correct / verifierAccuracy[cid].total) : 0.5, strictness: r.strictness, weight: verifierWeight(cid, r.strictness) } } return out }
509
- function finalVerdict(t) { const rs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid] }); const falses = rs.filter(function (r) { return r.verdict === 'false' }); const trues = rs.filter(function (r) { return r.verdict === 'true' }); const uncerts = rs.filter(function (r) { return r.verdict === 'uncertain' }); if (params.verdictMode === 'weighted-vote') { if (falses.length > 0) return 'false'; if (trues.length > 0 && uncerts.length === 0) return 'true'; return 'false' } if (falses.length > 0) return 'false'; if (trues.length === rs.length && rs.length > 0) return 'true'; return 'false' }
510
- async function advanceVerification(t, round) {
511
- if (round < params.debateMaxRounds && !consensus(t)) {
512
- if (!scheduler.running || scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return } // 暂停/并发门:辩论挂起,resume 由 reconcileTasks 重推进
513
- t.round = round + 1
514
- const transcript = buildTranscript(t)
515
- for (let i = 0; i < t.children.length; i++) {
516
- const cid = t.children[i]
517
- await followupChild(cid, verifierDebatePrompt(unitFromTask(t), transcript))
518
- agentRegistry[cid] = { role: 'verifier', unitId: t.unitId, round: round + 1, index: i }
519
- }
520
- } else {
521
- const verdict = finalVerdict(t)
522
- if (params.mode === 'manual') {
523
- const d = enqueueDecision('verdict', 'verdict for unit ' + t.unitId + ' = ' + verdict, { unitId: t.unitId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
524
- scheduler.gate = { decisionId: d.id, node: 'verdict' }
525
- t.status = 'awaiting-verdict'
526
- } else {
527
- await settleVerdict(t, verdict)
528
- delete tasks[t.id]
529
- }
530
- }
531
- }
532
- async function reconcileTasks() {
533
- const ids = Object.keys(tasks)
534
- for (let i = 0; i < ids.length; i++) {
535
- const t = tasks[ids[i]]
536
- if (t.type !== 'verify') continue
537
- if (t.status === 'paused') {
538
- const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
539
- if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
540
- }
541
- if (t.status !== 'debating') continue
542
- const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
543
- if (!allReported) continue
544
- await advanceVerification(t, t.round)
545
- }
546
- }
547
- async function settleVerdict(t, verdict) { const unit = unitFromTask(t); const unitId = t.unitId; const cids = Object.keys(t.childResults); for (let i = 0; i < cids.length; i++) { const cid = cids[i]; const r = t.childResults[cid]; const acc = verifierAccuracy[cid] || { correct: 0, total: 0 }; acc.total += 1; if (r.verdict === verdict) acc.correct += 1; verifierAccuracy[cid] = acc } await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy); await writeText('Progress_Logs/verification_' + unitId + '.log', JSON.stringify({ verdict: verdict, results: t.childResults, weights: weightsOf(t), transcript: buildTranscript(t), at: now() }, null, 2)); if (verdict === 'true') { const deps = (unit.dependencies || []).map(function (d) { return (d && d.title) || String(d) }); const body = 'Dependencies (Assumed or Verified): ' + JSON.stringify(deps) + '\n---\nTarget Content: ' + unit.content; await writeText('Temp_Validated/' + unitId + '.csv', body); promotionQueue[unitId] = { qid: unit.qid || '', isSolution: !!(unit.isSolution), deps: deps }; await writeJson('VibeMath_State/promotion_queue.json', promotionQueue) } else { await writeText('Progress_Logs/falsified_' + unitId + '.log', JSON.stringify({ unit: (unit.title || unitId), reasons: Object.keys(t.childResults).map(function (cid) { return t.childResults[cid].reason }), direct_veto: params.verdictMode === 'direct-veto', at: now() }, null, 2)) } await removeFile('Under_Verification/' + unit.obj_id + '_' + safeTitle(unit.title) + '.csv'); logActivity('verdict', 'unit ' + unitId + ' = ' + verdict) }
548
-
549
- // ================= promotion =================
550
- async function processPromotion() { const files = await listFiles('Temp_Validated'); if (files.length === 0) return; const verifiedText = await verifiedContentSnapshot(); for (let i = 0; i < files.length; i++) { const fname = files[i]; const objId = fname.replace(/\.csv$/, ''); const rec = promotionQueue[objId] || { qid: '', isSolution: false, deps: [] }; const deps = rec.deps || []; const allSatisfied = deps.every(function (title) { return title === '' || verifiedText.indexOf(title) !== -1 }); if (!allSatisfied) continue; const r = await maybeGate('promote', 'promote validated unit ' + objId + ' to Verified', { objId: objId }, async function (d) { await promoteUnit(d.objId); return { promoted: true } }); if (r && r.gated) return } }
551
- async function verifiedContentSnapshot() { const files = await listFiles('Verified'); let text = ''; for (let i = 0; i < files.length; i++) { const c = await readText('Verified/' + files[i]); if (c !== undefined) text += c + '\n' } return text }
552
- async function promoteUnit(objId) { await atomicMove('Temp_Validated/' + objId + '.csv', 'Verified/' + objId + '.csv'); const rec = promotionQueue[objId]; if (rec && rec.isSolution && rec.qid) { solvedByVerified[rec.qid] = true; await writeJson('VibeMath_State/solved_by_verified.json', solvedByVerified) } delete promotionQueue[objId]; await writeJson('VibeMath_State/promotion_queue.json', promotionQueue); logActivity('promote', 'unit ' + objId + ' promoted to Verified') }
553
-
554
- // ================= decider =================
555
- async function processDecider() { if (scheduler.activeCount >= params.maxParallelThreshold) return; const qs = await getQs(); const unsolved = qs.filter(function (q) { return q.status !== 'solved' }); if (unsolved.length === 0) return; const verified = await listFiles('Verified'); const processed = (await readJson('VibeMath_State/decided_verified.json')) || []; for (let i = 0; i < verified.length; i++) { const vf = verified[i]; if (processed.indexOf(vf) !== -1) continue; const inflight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'decider' && m.verifiedFile === vf }); if (inflight) continue; if (scheduler.activeCount >= params.maxParallelThreshold) break; const content = await readText('Verified/' + vf); if (content === undefined) continue; const qsList = unsolved.map(function (q) { return q.id + ': ' + q.description }).join('\n'); await spawnChild('decider:' + vf, deciderPrompt(qsList, content.slice(0, 4000)), { role: 'decider', verifiedFile: vf }) } }
556
- async function handleDecider(childId, meta, output) { delete agentRegistry[childId]; const parsed = parseJson(output); const qid = parsed && parsed.solves_qid; const processed = (await readJson('VibeMath_State/decided_verified.json')) || []; if (processed.indexOf(meta.verifiedFile) === -1) processed.push(meta.verifiedFile); if (qid && qid !== 'null') { const qs = await getQs(); const q = qs.find(function (x) { return x.id === qid }); if (q && q.status !== 'solved') { q.status = 'solved'; solvedByVerified[qid] = true; await writeQs(qs); const newName = qid + '-的解法_' + shortId() + '.csv'; await atomicMove('Verified/' + meta.verifiedFile, 'Verified/' + newName); processed.push(newName); logActivity('decider', 'problem ' + qid + ' solved; Verified file renamed to ' + newName) } } await writeJson('VibeMath_State/decided_verified.json', processed) }
557
-
558
- // ================= events / timer =================
559
- // NOTE: subagent/end listener and the tick timer are registered ONCE at the
560
- // apply level (below), routing through childOwner/sessions — NOT here, because
561
- // the standing-mount plugin instance is shared by every session.
562
-
563
- // ================= tools =================
564
- function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
565
- const handlers = {}
566
- function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
567
-
568
- registerTool('vibe_math_start', 'Start (or restart) the Vibe Math scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
569
- registerTool('vibe_math_resume', 'Resume the scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
570
- registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
571
- registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
572
- registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
573
- registerTool('vibe_math_report', 'Return the full progress report (status + recent activity + params) and write it to Progress_Logs/report.json.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return buildReport() })
574
- 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 } })
575
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, args); await saveAll(); await saveSettings(); return { ok: true, params: params } })
576
- registerTool('vibe_math_setup', 'Return the interactive parameter schema (each param: name, type, current, default, description, options, suggestion) 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' } })
577
- 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() })
578
- 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') })
579
- registerTool('vibe_math_add_problem', 'Add a problem to the current project qs.csv.', 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, description: args.description, priority: args.priority || 0, status: 'unsolved', deps: [] }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } })
580
- 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) })
581
- 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) })
582
- registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
583
- 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 } }) } })
584
- registerTool('vibe_math_decide', 'Resolve a pending manual decision.', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'string', enum: ['true', 'false'] } }, ['id', 'action']), async function (args) { const resolution = { action: args.action, verdict: args.verdict }; const d = decisionQueue.find(function (x) { return x.id === args.id }); if (!d) return { ok: false, message: 'decision not found' }; if (args.action === 'override' && args.verdict === undefined) return { ok: false, message: 'override requires a verdict (true|false)' }; const applied = await applyDecision(d.node, d.data, resolution); const r = await resolveDecision(args.id, resolution); return Object.assign({ ok: true, applied: applied }, r) })
585
- 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, unitId: m.unitId }) } return { ok: true, agents: out, count: out.length } })
586
- 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' } })
587
- 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' } })
588
-
589
- // ================= slash command /vibe =================
590
- async function dispatchVibeCommand(cmd, args) {
591
- if (cmd === 'start') return await startScheduler()
592
- if (cmd === 'resume') return await resumeScheduler()
593
- if (cmd === 'pause') return await pauseScheduler()
594
- if (cmd === 'abort') return await abortScheduler()
595
- if (cmd === 'status') { await refreshParams(); return await getStatus() }
596
- if (cmd === 'report') { await refreshParams(); await maybeWriteReport(true); return buildReport() }
597
- 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 } }
598
- 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' } }
599
- if (cmd === 'save') return await saveSettings()
600
- if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
601
- 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, description: desc, priority: 0, status: 'unsolved', deps: [] }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } }
602
- if (cmd === 'project') {
603
- if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
604
- if (args[0] === 'new') return await setProject(slugify(args.slice(1).join(' ')), true)
605
- return await setProject(slugify(args[0]), false)
606
- }
607
- 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 } }) }
608
- 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 } }
609
- return { ok: false, usage: 'start | resume | pause | abort | status | report | mode <auto|manual> | setup | save | template [global|project] | add <id> <description> | project [list | new <name> | <name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
610
- }
611
-
612
- // ================= session surface =================
613
- return {
614
- sessionId: sessionId,
615
- scheduler: scheduler,
616
- tickInFlight: tickInFlight,
617
- scheduleTick: scheduleTick,
618
- onChildEnd: onChildEnd,
619
- dispatchVibeCommand: dispatchVibeCommand,
620
- handlers: handlers,
621
- // 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
622
- refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
623
- getRunning: function () { return scheduler.running },
624
- // 会话自己的心跳节流:timer 每 1s 询问是否到点;tick 执行时刷新 lastTickAt
625
- tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
626
- }
627
- }
628
-
629
- // ================= apply-level registrations (ONCE per preset) =================
630
- function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
631
- function registerTool(name, description, parameters, handlerName) {
632
- ctx.effect(() => tools.register({
633
- name: name, description: description, parameters: parameters,
634
- output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
635
- execute: async function (args, exec) {
636
- try {
637
- const s = getSession(exec && exec.agent)
638
- if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
639
- await s.refreshProject()
640
- return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
641
- } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
642
- },
643
- }))
644
- }
645
- registerTool('vibe_math_start', 'Start (or restart) the Vibe Math scheduler for the current project.', objParams({}), 'vibe_math_start')
646
- registerTool('vibe_math_resume', 'Resume the scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
647
- registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
648
- registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
649
- registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
650
- registerTool('vibe_math_report', 'Return the full progress report (status + recent activity + params) and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
651
- 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')
652
- registerTool('vibe_math_set_params', 'Update scheduler parameters (partial).', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['direct-veto', 'weighted-vote'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' } }), 'vibe_math_set_params')
653
- registerTool('vibe_math_setup', 'Return the interactive parameter schema (each param: name, type, current, default, description, options, suggestion) for guided configuration.', objParams({}), 'vibe_math_setup')
654
- 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')
655
- 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')
656
- registerTool('vibe_math_add_problem', 'Add a problem to the current project qs.csv.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), 'vibe_math_add_problem')
657
- registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
658
- registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
659
- registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
660
- registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
661
- registerTool('vibe_math_decide', 'Resolve a pending manual decision.', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'string', enum: ['true', 'false'] } }, ['id', 'action']), 'vibe_math_decide')
662
- registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
663
- 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')
664
- registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
665
-
666
- // /vibe slash command (registered once; routed per session)
667
- ctx.effect(() => commands.register({
668
- name: 'vibe',
669
- description: 'control the Vibe Math solver (start/pause/projects/setup/save/decisions/agents)',
670
- input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|project [list|new <name>|<name>]|decisions|agents]' },
671
- handler: async function (invocation) {
672
- const s = getSession(invocation && invocation.agent)
673
- if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
674
- const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
675
- const parts = line.length > 0 ? line.split(/\s+/) : []
676
- const cmd = parts[0] || ''
677
- const rest = parts.slice(1)
678
- const result = await s.dispatchVibeCommand(cmd, rest)
679
- return { kind: 'success', text: JSON.stringify(result, null, 2) }
680
- },
681
- }))
682
-
683
- // subagent/end (registered once; routed to the owning session via childOwner)
684
- ctx.on('subagent/end', function (info) {
685
- const sid = childOwner.get(info.id)
686
- const s = sid !== undefined ? sessions.get(sid) : undefined
687
- if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math onChildEnd reject: ' + String((e && e.stack) || e)) })
688
- })
689
-
690
- // tick timer (registered once; ticks every running session at its own pace)
691
- 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) })
692
- }