dsh-vibe-math 1.0.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2480 @@
1
+ // Vibe Math V3 — host plugin implementing the THIRD-generation architecture
2
+ // ("vibe-math-v3/实现方案.md"): paper-style Markdown knowledge base + agent
3
+ // self-organizing scheduling + universal theory/method invention library.
4
+ //
5
+ // What changed vs V2:
6
+ // 1. DATA LAYER IS MARKDOWN. Problems/ (问题清单 with 依赖/来源与动机/计划),
7
+ // Progress/ (研究日志, per-direction round narratives), Propos/ (结论/命题,
8
+ // category subfolders), Methods/ (通用理论发明库), Verified/ (absolute
9
+ // trust, scheduler-generated read-only copies), Notes/, Logs/, State/.
10
+ // Each object is ONE md file with a tiny "soft spec" anchor header
11
+ // (ID/类型/状态/概率/优先级/依赖/...) + free-form narrative body. The
12
+ // scheduler parses ONLY the anchor header + entry headings (### 解法 N|...)
13
+ // and never parses prose. ONLY Verified/ is absolutely trusted; everything
14
+ // else (incl. unverified method claims) is experiential reference.
15
+ // 2. SCHEDULING VIA PLANNER AGENT. Before new spawns the scheduler builds a
16
+ // state brief and calls a planner agent which returns up to
17
+ // `planningHorizon` actions (spawn solver/verifier/explorer/method-keeper,
18
+ // interrupt, promote, wait). The code validates every action against hard
19
+ // invariants and executes them in order; beyond-capacity actions queue
20
+ // across ticks. Planner failure falls back to the V2-style heuristic.
21
+ // 3. METHOD LIBRARY. Solvers/explorers report methods_used + new_inventions;
22
+ // the scheduler appends application records and queues inventions for the
23
+ // Method Keeper agent, which distills new method cards / improves existing
24
+ // ones into a reusable theory system (project-level + global Methods/).
25
+ //
26
+ // Data layout (per project, under <workspace>/VibeMath/Projects/<project>/):
27
+ // Problems/<id>.md — one problem per file (软规范锚点 + 陈述/来源与动机/解法候选)
28
+ // Progress/<id>.md — research journal per problem (directions & rounds, narrative)
29
+ // Propos/<分类>/<id>.md — one proposition per file (陈述/证明尝试/证伪尝试)
30
+ // Methods/<id>.md — method/theory cards (project level)
31
+ // Verified/命题/<id>.md — verified-true/false copies (scheduler generated, read-only)
32
+ // Verified/问题/<id>.md — solved problems with full verified solutions
33
+ // Reliable/ — user-provided trusted references (read-only)
34
+ // Notes/ — free notes (not scheduled)
35
+ // Logs/Verification/ — debate transcripts per verification run
36
+ // Logs/Plans/ — every planner plan + per-action outcomes (planning loop)
37
+ // State/ — scheduler private state (JSON, scheduler-only)
38
+ // Workspace level: VibeMath/Methods/ = GLOBAL theory library (cross-project),
39
+ // VibeMath/current.<sessionId>.json = per-session current project.
40
+ export const name = 'vibe-math-v3'
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
+ const subagents = ctx.subagents
52
+ const agents = ctx.agents
53
+ const fs = ctx.fs
54
+ const tools = ctx.tools
55
+ const commands = ctx.commands
56
+ const subprocess = ctx.get('subprocess')
57
+ const sandboxPolicy = ctx.get('sandboxPolicy')
58
+
59
+ // ================= per-session registry =================
60
+ const sessions = new Map() // rootAgentId -> Session
61
+ const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
62
+ const fileOwner = {} // 进程级写锁:fileKey -> { childId, sessionId, at } —— 防任何代理(跨会话)并发写同一 md 文件
63
+ // Process epoch: PROCESS-level (one per apply, shared by every session), written to
64
+ // State/process_epoch.json at init; a DIFFERENT persisted epoch means a previous DSH
65
+ // process wrote this state (in-flight children are gone), while an equal epoch means
66
+ // same-process pause→resume (children may still be alive). Kept at apply level so two
67
+ // sessions in one process never treat each other as a stale previous process.
68
+ const processEpoch = String(Date.now()) + '-' + Math.random().toString(36).slice(2, 8)
69
+
70
+ function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
71
+ // Walk up the durable session lineage to the top-level (root) agent of this session,
72
+ // so calls from a child agent (which inherits this preset) still route to its session.
73
+ function rootOf(agent) {
74
+ try {
75
+ let cur = agent
76
+ const seen = new Set()
77
+ while (cur) {
78
+ const id = cur.id
79
+ if (seen.has(id)) return cur
80
+ seen.add(id)
81
+ const parentId = (cur.session && cur.session.header) ? cur.session.header.parentSession : undefined
82
+ if (parentId === undefined) return cur
83
+ const parent = agents.get(parentId)
84
+ if (!parent) return cur
85
+ cur = parent
86
+ }
87
+ } catch (e) { /* fall through */ }
88
+ return agent
89
+ }
90
+ function getSession(agent) {
91
+ const root = rootOf(agent)
92
+ const sid = sessionIdOf(root)
93
+ if (sid === undefined) return undefined
94
+ let s = sessions.get(sid)
95
+ if (!s) { s = makeSession(root, sid); sessions.set(sid, s) }
96
+ return s
97
+ }
98
+
99
+ // ================= per-session plugin body =================
100
+ function makeSession(rootAgent, sessionId) {
101
+ let currentProject = 'default'
102
+ const DEFAULT_PARAMS = {
103
+ mode: 'auto', // auto | manual
104
+ maxParallelThreshold: 4, // concurrency gate: active turns < this
105
+ solverMaxRounds: 3, // per-direction iteration cap
106
+ directionsPerSolver: 1, // directions shown per solver prompt (1 = own only)
107
+ verifierCount: 3, // independent reviewers per verification
108
+ debateMaxRounds: 5, // debate round cap
109
+ verdictMode: 'forced', // flat = 均衡(0.5) | forced = 按历史准确率+严谨性加权;两者都先做近共识判定(同侧且均值≥0.85/≤0.15取均值)
110
+ provider: '',
111
+ model: '',
112
+ solverPersona: '',
113
+ verifierPersona: '',
114
+ explorerPersona: '',
115
+ plannerPersona: '', // 注入规划代理提示词开头的人格/要求
116
+ methodKeeperPersona: '', // 注入方法整理代理提示词开头的人格/要求
117
+ knowledgeContext: '', // 共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖)
118
+ solverToolAllow: [],
119
+ solverToolDeny: [],
120
+ verifierToolAllow: [],
121
+ verifierToolDeny: [],
122
+ solverAllowNetwork: '', // '' = 继承全部;true = 允许网络工具;false = 禁止
123
+ verifierAllowNetwork: '',
124
+ solverAllowScripts: '', // '' = 继承全部;true = 允许脚本工具;false = 禁止
125
+ verifierAllowScripts: '',
126
+ solverMaxToolCalls: 0,
127
+ verifierMaxToolCalls: 0,
128
+ reportIntervalMs: 0, // 0 = 仅事件驱动;>0 = 定时自动汇报(毫秒)
129
+ reportMode: 'file', // file | push | both
130
+ promoteValueThreshold: 0.7, // Propos → Problems auto-promotion threshold (价值/关键性)
131
+ priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
132
+ proposPriorityAdjust: 'none', // none | progress-graded
133
+ tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
134
+ activityLogCap: 100,
135
+ maxExplorerRetries: 3, // explorer 重派生上限
136
+ // ---- V3 new params ----
137
+ planningHorizon: 3, // 规划代理一次计划的最多动作数("接下来 n 次")
138
+ plannerEnabled: true, // false = 完全走内置启发式(规划代理禁用)
139
+ plannerProvider: '', // 规划代理模型 provider(空 = 继承)
140
+ plannerModel: '', // 规划代理模型 id(空 = 继承)
141
+ planMinIntervalMs: 30000, // 两次规划调用的最小间隔(系统空闲且有工作时忽略)
142
+ plannerMaxFails: 3, // 规划代理连续失败达此值 → 自动降级启发式
143
+ methodKeepIntervalMs: 0, // Method Keeper 定时整理间隔(0 = 事件驱动)
144
+ methodKeepEvery: 5, // 每积累 N 个待沉淀发明/新命题触发一次整理
145
+ methodAutoPromote: false, // 项目级方法自动晋升全局库(false = 人工门)
146
+ indexAutoRebuild: true, // 每次写盘后自动重建索引(false = 手动 vibe_math_index)
147
+ projectLockTimeoutMs: 60000, // 项目锁等待超时
148
+ }
149
+ let params = Object.assign({}, DEFAULT_PARAMS)
150
+ let scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }
151
+ let agentRegistry = {}
152
+ let decisionQueue = []
153
+ let verifierAccuracy = {}
154
+ let tasks = {} // verify tasks keyed by 'verify:<rId>'
155
+ let activityLog = []
156
+ let lastReportWrite = 0
157
+ let lastPushReport = 0
158
+ let reportDirty = false
159
+ let tickInFlight = false
160
+ let lastTickAt = 0
161
+ let explorerRetries = {}
162
+
163
+ // ---- V3: md knowledge base (in-memory machine view; persisted as md) ----
164
+ let problems = new Map() // id -> Problem
165
+ let propos = new Map() // id -> Proposition
166
+ let methods = new Map() // id -> Method (project-level)
167
+ let globalMethods = new Map() // id -> Method (workspace global VibeMath/Methods/)
168
+ let dirState = new Map() // qid -> [Direction] (machine view of Progress journal)
169
+ let methodLog = { pendingInventions: [], keepCount: 0, lastKeepAt: 0 }
170
+ let planQueue = [] // queued plan actions (beyond-capacity, cross-tick)
171
+ let plannerFails = 0
172
+ let lastPlanAt = 0
173
+ let lastPlanSummary = null // { at, summary, actions, outcomes } for the next brief
174
+ let projectLock = { sessionId: '', at: 0 }
175
+ let lastIndexWrite = 0 // State/index.json 写入节流(每 5s 至多一次;工具/init 强制时立即)
176
+ let archivedJ = {} // qid -> [md 段]:重派生时被替换方向的 journal 归档(论文式历史保留)
177
+
178
+ // ================= helpers =================
179
+ function textBlock(t) { return { type: 'text', text: String(t) } }
180
+ function now() { return Date.now() }
181
+ 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 }
182
+ function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
183
+ function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
184
+ function fmtTime(ts) { try { return new Date(ts || now()).toISOString().replace('T', ' ').slice(0, 19) } catch (e) { return String(ts || '') } }
185
+ 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 '.' }
186
+ function vibeRoot() { return (workspaceRoot() + '/VibeMath').replace(/\\/g, '/') }
187
+ function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
188
+ function frameworkRoot() { return projectRoot(currentProject) }
189
+ function slugify(s) { const t = String(s == null ? '' : s).trim().toLowerCase().replace(/[^a-z0-9_\-\u4e00-\u9fa5]+/g, '-').replace(/^-+|-+$/g, ''); return t || 'project' }
190
+ function safeId(s) { return String(s == null ? 'anon' : s).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80) || 'anon' }
191
+ 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 }
192
+ function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
193
+ 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() }
194
+ function parseJson(text) {
195
+ if (typeof text !== 'string') return undefined
196
+ const tryObj = function (s) { try { const v = JSON.parse(s); return (v && typeof v === 'object' && !Array.isArray(v)) ? v : undefined } catch (e) { return undefined } }
197
+ const fenceRe = /```(?:json)?[ \t]*([\s\S]*?)```/gi
198
+ let m
199
+ while ((m = fenceRe.exec(text)) !== null) { const obj = tryObj(m[1].trim()); if (obj !== undefined) return obj }
200
+ const whole = tryObj(text.trim()); if (whole !== undefined) return whole
201
+ let best = undefined; let bestLen = -1
202
+ for (let start = 0; start < text.length; start++) {
203
+ if (text[start] !== '{') continue
204
+ let depth = 0, inStr = false, esc = false, end = -1
205
+ for (let i = start; i < text.length; i++) {
206
+ const c = text[i]
207
+ if (inStr) { if (esc) esc = false; else if (c === '\\') esc = true; else if (c === '"') inStr = false; continue }
208
+ if (c === '"') { inStr = true; continue }
209
+ if (c === '{') depth++
210
+ else if (c === '}') { depth--; if (depth === 0) { end = i; break } }
211
+ }
212
+ if (end === -1) continue
213
+ const obj = tryObj(text.slice(start, end + 1))
214
+ if (obj !== undefined && (end - start + 1) > bestLen) { best = obj; bestLen = end - start + 1 }
215
+ }
216
+ return best
217
+ }
218
+ function safeJson(v, fb) { if (v == null || v === '') return fb; try { return JSON.parse(v) } catch (e) { return fb } }
219
+ 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 }
220
+
221
+ // ================= parameter schema =================
222
+ const PARAM_SCHEMA = [
223
+ { name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
224
+ { name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
225
+ { name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数', suggestion: 3 },
226
+ { name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向', suggestion: 1 },
227
+ { name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量', suggestion: 3 },
228
+ { name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
229
+ { name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: '裁决模式:flat = 均衡机制;forced = 强制裁决(按历史准确率+严谨性加权);两者都先做近共识判定(同侧且均值≥0.85/≤0.15取均值,修复 v2 flat 误判)', suggestion: 'forced' },
230
+ { name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
231
+ { name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
232
+ { name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
233
+ { name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
234
+ { name: 'explorerPersona', type: 'string', description: '注入每个 explorer/重派生提示词开头的人格/要求', suggestion: '' },
235
+ { name: 'plannerPersona', type: 'string', description: '注入规划代理提示词开头的人格/要求', suggestion: '' },
236
+ { name: 'methodKeeperPersona', type: 'string', description: '注入方法整理代理提示词开头的人格/要求', suggestion: '' },
237
+ { name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖)', suggestion: '' },
238
+ { name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
239
+ { name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
240
+ { name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
241
+ { name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
242
+ { name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承;true=允许;false=禁止', suggestion: '' },
243
+ { name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关', suggestion: '' },
244
+ { name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关', suggestion: '' },
245
+ { name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关', suggestion: '' },
246
+ { name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
247
+ { name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
248
+ { name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动', suggestion: 0 },
249
+ { name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息;both = 两者', suggestion: 'file' },
250
+ { name: 'promoteValueThreshold', type: 'number', description: '命题「价值/关键性」≥ 该值且未决(0,1) 时自动晋升为问题', suggestion: 0.7 },
251
+ { name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '问题优先级动态调整策略', suggestion: 'none' },
252
+ { name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整策略', suggestion: 'none' },
253
+ { name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒)', suggestion: 2000 },
254
+ { name: 'activityLogCap', type: 'integer', description: '活动日志保留条数', suggestion: 100 },
255
+ { name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限', suggestion: 3 },
256
+ { name: 'planningHorizon', type: 'integer', description: '规划代理一次计划的最多动作数("接下来 n 次")', suggestion: 3 },
257
+ { name: 'plannerEnabled', type: 'boolean', description: 'false = 完全走内置启发式调度(规划代理禁用)', suggestion: true },
258
+ { name: 'plannerProvider', type: 'string', description: '规划代理模型 provider(空 = 继承根代理)', suggestion: '' },
259
+ { name: 'plannerModel', type: 'string', description: '规划代理模型 id(空 = 继承根代理)', suggestion: '' },
260
+ { name: 'plannerPersona', type: 'string', description: '注入规划代理提示词开头的人格/要求', suggestion: '' },
261
+ { name: 'planMinIntervalMs', type: 'integer', description: '两次规划调用的最小间隔(毫秒);系统空闲且有工作时忽略', suggestion: 30000 },
262
+ { name: 'plannerMaxFails', type: 'integer', description: '规划代理连续失败达此值 → 自动降级启发式', suggestion: 3 },
263
+ { name: 'methodKeepIntervalMs', type: 'integer', description: 'Method Keeper 定时整理间隔(0 = 事件驱动)', suggestion: 0 },
264
+ { name: 'methodKeepEvery', type: 'integer', description: '每积累 N 个待沉淀发明/新命题触发一次整理', suggestion: 5 },
265
+ { name: 'methodAutoPromote', type: 'boolean', description: '项目级方法自动晋升全局库(false = 人工门)', suggestion: false },
266
+ { name: 'indexAutoRebuild', type: 'boolean', description: '每次写盘后自动重建索引(false = 手动 vibe_math_index)', suggestion: true },
267
+ { name: 'projectLockTimeoutMs', type: 'integer', description: '项目锁等待超时(毫秒)', suggestion: 60000 },
268
+ ]
269
+
270
+ // ================= fs (adapted to DSH 0.1.1: resolve returns {targetKey, displayPath}) =================
271
+ async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
272
+ 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 } }
273
+ async function writeText(rel, content) { const t = await fsTarget(rel); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true }
274
+ async function writeJson(rel, obj) { return await writeText(rel, JSON.stringify(obj, null, 2)) }
275
+ async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { return undefined } }
276
+ 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 [] } }
277
+ async function listDirs(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 === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
278
+ 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 [] } }
279
+ 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 } }
280
+ 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 } }
281
+ async function readCurrentProject() {
282
+ 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) {}
283
+ 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' }
284
+ }
285
+ async function writeCurrentProject() {
286
+ 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) {}
287
+ }
288
+
289
+ // ================= subprocess =================
290
+ function psQuote(p) { return "'" + String(p).replace(/'/g, "''") + "'" }
291
+ 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) } } }
292
+ async function ensureDirs() { const base = frameworkRoot(); const dirs = ['Problems', 'Progress', 'Propos', 'Methods', 'Verified/命题', 'Verified/问题', 'Reliable', 'Notes', 'Logs/Verification', 'Logs/Plans', 'State']; const paths = [vibeRoot() + '/Projects', vibeRoot() + '/Methods'].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') }
293
+ async function removeFile(rel) { const base = frameworkRoot(); return await runShell('Remove-Item -Force -LiteralPath ' + psQuote(base + '/' + rel) + ' -ErrorAction SilentlyContinue') }
294
+
295
+ // ================= settings =================
296
+ function sanitizeParams(obj) {
297
+ const out = {}
298
+ const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'planningHorizon', 'planMinIntervalMs', 'plannerMaxFails', 'methodKeepIntervalMs', 'methodKeepEvery', 'projectLockTimeoutMs']
299
+ const numFields = ['promoteValueThreshold']
300
+ const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny']
301
+ const boolFields = ['plannerEnabled', 'methodAutoPromote', 'indexAutoRebuild']
302
+ for (const k of Object.keys(DEFAULT_PARAMS)) {
303
+ if (!(k in obj)) continue
304
+ const v = obj[k]
305
+ if (intFields.indexOf(k) !== -1) { const n = Number(v); out[k] = Number.isFinite(n) ? Math.floor(n) : DEFAULT_PARAMS[k] }
306
+ 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] }
307
+ else if (arrayFields.indexOf(k) !== -1) { out[k] = Array.isArray(v) ? v.filter(function (x) { return typeof x === 'string' }) : DEFAULT_PARAMS[k] }
308
+ else if (boolFields.indexOf(k) !== -1) { out[k] = (v === true || v === false) ? v : DEFAULT_PARAMS[k] }
309
+ else if (k === 'mode') { out[k] = (v === 'manual' || v === 'auto') ? v : DEFAULT_PARAMS[k] }
310
+ else if (k === 'verdictMode') { out[k] = (v === 'flat' || v === 'forced') ? v : DEFAULT_PARAMS[k] }
311
+ else if (k === 'reportMode') { out[k] = (v === 'file' || v === 'push' || v === 'both') ? v : DEFAULT_PARAMS[k] }
312
+ else if (k === 'priorityAdjust') { out[k] = (v === 'none' || v === 'deadend-deprioritize' || v === 'survival-map') ? v : DEFAULT_PARAMS[k] }
313
+ else if (k === 'proposPriorityAdjust') { out[k] = (v === 'none' || v === 'progress-graded') ? v : DEFAULT_PARAMS[k] }
314
+ else if (k === 'solverAllowNetwork' || k === 'verifierAllowNetwork' || k === 'solverAllowScripts' || k === 'verifierAllowScripts') { out[k] = (v === true || v === false || v === '') ? v : DEFAULT_PARAMS[k] }
315
+ else { out[k] = v }
316
+ }
317
+ return out
318
+ }
319
+ async function loadSettings() {
320
+ let text = await readText('vibe_math_setting.json')
321
+ if (text === undefined) text = await readTextAbs(vibeRoot() + '/vibe_math_setting.json')
322
+ if (text === undefined) return
323
+ const clean = stripJsonComments(text)
324
+ try {
325
+ const obj = JSON.parse(clean)
326
+ if (obj && typeof obj === 'object' && !Array.isArray(obj)) params = Object.assign({}, params, sanitizeParams(obj))
327
+ } catch (e) {
328
+ console.error('vibe-math-v3: invalid vibe_math_setting.json ignored: ' + String((e && e.message) || e))
329
+ }
330
+ }
331
+ function settingsTemplateFrom(src) {
332
+ const lines = []
333
+ lines.push('{')
334
+ lines.push(' // Vibe Math V3 默认参数配置(JSON with Comments,可加 // 注释)。')
335
+ lines.push(' // 位置:<项目>/vibe_math_setting.json(全局回退:<工作区>/VibeMath/vibe_math_setting.json)。')
336
+ lines.push(' // 本文件是参数的唯一持久化来源:vibe_math_set_params / set_mode 会立即写回此文件;全局文件仅作项目文件不存在时的回退默认。')
337
+ const keys = Object.keys(src).sort()
338
+ for (let i = 0; i < keys.length; i++) {
339
+ const k = keys[i]
340
+ const v = src[k]
341
+ const schema = PARAM_SCHEMA.find(function (p) { return p.name === k })
342
+ const desc = schema ? schema.description : ''
343
+ const comma = i === keys.length - 1 ? '' : ','
344
+ lines.push(' // ' + k + (desc ? ' — ' + desc : ''))
345
+ lines.push(' ' + JSON.stringify(k) + ': ' + JSON.stringify(v) + comma)
346
+ }
347
+ lines.push('}')
348
+ return lines.join('\n') + '\n'
349
+ }
350
+ function settingsTemplate() { return settingsTemplateFrom(params) }
351
+ async function saveSettings() { await writeText('vibe_math_setting.json', settingsTemplate()); return { ok: true, path: frameworkRoot() + '/vibe_math_setting.json' } }
352
+ 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' } }
353
+
354
+ // ================= md soft-spec helpers =================
355
+ // 软规范:对象 md 头部锚点行(唯一强制部分)+ 正文自由叙述。调度器只解析
356
+ // 头部锚点与条目标题行(### 解法/证明/证伪 N|标题|概率X|状态Y),从不解析正文散文。
357
+ function splitHeader(text) {
358
+ const idx = String(text).search(/\n## /)
359
+ const head = idx === -1 ? String(text) : String(text).slice(0, idx)
360
+ const body = idx === -1 ? '' : String(text).slice(idx + 1)
361
+ return { head: head, body: body }
362
+ }
363
+ function parseAnchors(head) {
364
+ const anchors = {}
365
+ const re = /^-\s*([A-Za-z\u4e00-\u9fa5/]+)\s*:\s*(.*)$/gm
366
+ let m
367
+ while ((m = re.exec(head)) !== null) anchors[m[1].trim()] = m[2].trim()
368
+ return anchors
369
+ }
370
+ function anchorLine(k, v) { return '- ' + k + ': ' + String(v == null ? '' : v) }
371
+ // 解析条目标题行 + 其后正文,直到下一个 ### / ## 标题。返回 [{heading, text}]
372
+ function parseEntries(body, kindRe) {
373
+ const out = []
374
+ const lines = String(body).split('\n')
375
+ let cur = null
376
+ for (let i = 0; i < lines.length; i++) {
377
+ const line = lines[i]
378
+ const m = kindRe.exec(line)
379
+ if (m) {
380
+ if (cur) out.push(cur)
381
+ // 约定:m[1]=标题段,m[2]=概率段,m[3]=状态段(应用记录正则只有 m[1])
382
+ cur = { heading: line.trim(), title: (m[1] || '').trim(), prob: m[2] !== undefined ? Number(m[2]) : undefined, status: (m[3] || '').trim(), text: [] }
383
+ continue
384
+ }
385
+ if (/^#{2,3}\s/.test(line)) {
386
+ if (cur) { out.push(cur); cur = null }
387
+ continue
388
+ }
389
+ if (cur) cur.text.push(line)
390
+ }
391
+ if (cur) out.push(cur)
392
+ for (const e of out) e.text = e.text.join('\n').trim()
393
+ return out
394
+ }
395
+ function section(body, name) {
396
+ const re = new RegExp('^##\\s+' + name + '\\s*$', 'm')
397
+ const m = re.exec(String(body))
398
+ if (!m) return ''
399
+ const rest = String(body).slice(m.index + m[0].length)
400
+ const end = rest.search(/\n##\s/)
401
+ return (end === -1 ? rest : rest.slice(0, end)).trim()
402
+ }
403
+
404
+ // ---- compose / parse: Problem ----
405
+ function composeProblemMd(p) {
406
+ const lines = []
407
+ lines.push('# 问题|' + (p.标题 || p.id))
408
+ lines.push(anchorLine('ID', p.id))
409
+ lines.push(anchorLine('类型', '问题'))
410
+ lines.push(anchorLine('状态', p.状态 || '求解中'))
411
+ lines.push(anchorLine('优先级', p.优先级 == null ? 1 : p.优先级))
412
+ lines.push(anchorLine('依赖', JSON.stringify(p.依赖 || [])))
413
+ lines.push(anchorLine('被依赖', JSON.stringify(p.被依赖 || [])))
414
+ lines.push(anchorLine('来源', p.来源 || '原始'))
415
+ lines.push(anchorLine('计划', p.计划 || ''))
416
+ lines.push('')
417
+ lines.push('## 陈述')
418
+ lines.push(p.陈述 || '')
419
+ lines.push('')
420
+ if (p.来源与动机) { lines.push('## 来源与动机'); lines.push(p.来源与动机); lines.push('') }
421
+ lines.push('## 解法候选')
422
+ const sols = p.solutions || []
423
+ if (sols.length === 0) lines.push('(暂无解法候选)')
424
+ else for (let i = 0; i < sols.length; i++) {
425
+ const s = sols[i]
426
+ lines.push('### 解法 ' + (i + 1) + '|' + (s.title || '解法' + (i + 1)) + '|概率' + (s.prob != null ? s.prob : 0.5) + '|状态' + (s.status || '未定论'))
427
+ lines.push(s.text || '')
428
+ lines.push('')
429
+ }
430
+ return lines.join('\n').trimEnd() + '\n'
431
+ }
432
+ function parseProblemMd(id, text) {
433
+ const { head, body } = splitHeader(text)
434
+ const a = parseAnchors(head)
435
+ const sols = parseEntries(body, /^###\s*解法\s*\d+|(.*?)|概率([0-9.]+)|状态(.+)$/).map(function (e) { return { title: e.title, prob: clamp01(e.prob), status: e.status, text: e.text } })
436
+ return {
437
+ id: id, 标题: a['标题'] || id, 状态: a['状态'] || '求解中',
438
+ 优先级: (a['优先级'] === 'never' ? 'never' : (Number(a['优先级']) || 1)),
439
+ 依赖: safeJson(a['依赖'], []), 被依赖: safeJson(a['被依赖'], []),
440
+ 来源: a['来源'] || '原始', 计划: a['计划'] || '',
441
+ 陈述: section(body, '陈述'), 来源与动机: section(body, '来源与动机'),
442
+ solutions: sols, 判断命题: a['判断命题'] || '', 来源命题: a['来源命题'] || '',
443
+ }
444
+ }
445
+
446
+ // ---- compose / parse: Proposition ----
447
+ function composePropositionMd(p) {
448
+ const lines = []
449
+ lines.push('# 命题|' + (p.标题 || p.id))
450
+ lines.push(anchorLine('ID', p.id))
451
+ lines.push(anchorLine('类型', '命题'))
452
+ lines.push(anchorLine('状态', p.状态 || '未定论'))
453
+ lines.push(anchorLine('概率', p.概率 == null ? 0.5 : p.概率))
454
+ lines.push(anchorLine('优先级', p.优先级 == null ? 1 : p.优先级))
455
+ lines.push(anchorLine('依赖', JSON.stringify(p.依赖 || [])))
456
+ if (p.价值关键性 != null) lines.push(anchorLine('价值/关键性', p.价值关键性))
457
+ lines.push('')
458
+ lines.push('## 陈述')
459
+ lines.push(p.陈述 || '')
460
+ lines.push('')
461
+ lines.push('## 证明尝试')
462
+ const proofs = p.proofs || []
463
+ if (proofs.length === 0) lines.push('(暂无证明尝试)')
464
+ else for (let i = 0; i < proofs.length; i++) {
465
+ const s = proofs[i]
466
+ lines.push('### 证明 ' + (i + 1) + '|' + (s.title || '证明' + (i + 1)) + '|概率' + (s.prob != null ? s.prob : 0.5) + '|状态' + (s.status || '未定论'))
467
+ lines.push(s.text || '')
468
+ lines.push('')
469
+ }
470
+ lines.push('## 证伪尝试')
471
+ const refutes = p.refutes || []
472
+ if (refutes.length === 0) lines.push('(暂无证伪尝试)')
473
+ else for (let i = 0; i < refutes.length; i++) {
474
+ const s = refutes[i]
475
+ lines.push('### 证伪 ' + (i + 1) + '|' + (s.title || '证伪' + (i + 1)) + '|概率' + (s.prob != null ? s.prob : 0.5) + '|状态' + (s.status || '未定论'))
476
+ lines.push(s.text || '')
477
+ lines.push('')
478
+ }
479
+ return lines.join('\n').trimEnd() + '\n'
480
+ }
481
+ function parsePropositionMd(id, text) {
482
+ const { head, body } = splitHeader(text)
483
+ const a = parseAnchors(head)
484
+ const proofs = parseEntries(body, /^###\s*证明\s*\d+|(.*?)|概率([0-9.]+)|状态(.+)$/).map(function (e) { return { title: e.title, prob: clamp01(e.prob), status: e.status, text: e.text } })
485
+ const refutes = parseEntries(body, /^###\s*证伪\s*\d+|(.*?)|概率([0-9.]+)|状态(.+)$/).map(function (e) { return { title: e.title, prob: clamp01(e.prob), status: e.status, text: e.text } })
486
+ return {
487
+ id: id, 标题: a['标题'] || id, 状态: a['状态'] || '未定论',
488
+ 概率: clamp01(a['概率'] != null ? Number(a['概率']) : 0.5),
489
+ 优先级: (a['优先级'] === 'never' ? 'never' : (Number(a['优先级']) || 1)),
490
+ 依赖: safeJson(a['依赖'], []),
491
+ 价值关键性: a['价值/关键性'] != null ? clamp01(Number(a['价值/关键性'])) : 0.5,
492
+ 陈述: section(body, '陈述'), proofs: proofs, refutes: refutes,
493
+ 来源问题: a['来源问题'] || '', 来源方向: a['来源方向'] || '',
494
+ }
495
+ }
496
+
497
+ // ---- compose / parse: Method ----
498
+ function composeMethodMd(m) {
499
+ const lines = []
500
+ lines.push('# 方法|' + (m.标题 || m.id))
501
+ lines.push(anchorLine('ID', m.id))
502
+ lines.push(anchorLine('类型', m.类型 || '方法'))
503
+ lines.push(anchorLine('状态', m.状态 || '经验'))
504
+ lines.push(anchorLine('可信断言', JSON.stringify(m.可信断言 || [])))
505
+ lines.push(anchorLine('上级体系', JSON.stringify(m.上级体系 || [])))
506
+ lines.push(anchorLine('子方法', JSON.stringify(m.子方法 || [])))
507
+ lines.push(anchorLine('相关', JSON.stringify(m.相关 || [])))
508
+ lines.push(anchorLine('适用场景', m.适用场景 || ''))
509
+ lines.push('')
510
+ lines.push('## 核心内容')
511
+ lines.push(m.核心内容 || '')
512
+ lines.push('')
513
+ if (m.定义与记号) { lines.push('## 定义与记号'); lines.push(m.定义与记号); lines.push('') }
514
+ lines.push('## 应用记录')
515
+ const apps = m.applications || []
516
+ if (apps.length === 0) lines.push('(暂无应用记录)')
517
+ else for (let i = 0; i < apps.length; i++) {
518
+ lines.push('### 应用 ' + (i + 1) + '|' + (apps[i].at || fmtTime()) + '|问题 ' + (apps[i].问题 || '') + (apps[i].方向 ? ' 方向 ' + apps[i].方向 : ''))
519
+ lines.push(apps[i].text || '')
520
+ lines.push('')
521
+ }
522
+ lines.push('## 改进历史')
523
+ const imps = m.improvements || []
524
+ if (imps.length === 0) lines.push('(暂无改进记录)')
525
+ else for (let i = 0; i < imps.length; i++) {
526
+ lines.push('### v' + (imps[i].v || (i + 1)) + '(' + (imps[i].原因 || '') + ')')
527
+ lines.push(imps[i].text || '')
528
+ lines.push('')
529
+ }
530
+ return lines.join('\n').trimEnd() + '\n'
531
+ }
532
+ function parseMethodMd(id, text) {
533
+ const { head, body } = splitHeader(text)
534
+ const a = parseAnchors(head)
535
+ const apps = parseEntries(body, /^###\s*应用\s*\d+|(.*?)$/).map(function (e) { return { at: e.title || '', 问题: '', 方向: '', text: e.text } })
536
+ return {
537
+ id: id, 标题: a['标题'] || id, 类型: a['类型'] || '方法', 状态: a['状态'] || '经验',
538
+ 可信断言: safeJson(a['可信断言'], []), 上级体系: safeJson(a['上级体系'], []), 子方法: safeJson(a['子方法'], []), 相关: safeJson(a['相关'], []),
539
+ 适用场景: a['适用场景'] || '', 核心内容: section(body, '核心内容'), 定义与记号: section(body, '定义与记号'),
540
+ applications: apps, improvements: [], 来源: a['来源'] || '',
541
+ }
542
+ }
543
+
544
+ // ---- compose: Verified card ----
545
+ function composeVerifiedMd(card) {
546
+ const lines = []
547
+ lines.push('# 已验证|' + (card.标题 || card.id))
548
+ lines.push(anchorLine('ID', card.id))
549
+ lines.push(anchorLine('类型', card.类型 || '命题'))
550
+ lines.push(anchorLine('结论', card.结论 === true ? '真' : (card.结论 === false ? '假' : '')))
551
+ lines.push(anchorLine('概率', card.概率))
552
+ lines.push(anchorLine('分类', card.分类 || '未分类'))
553
+ lines.push(anchorLine('来源', card.来源 || ''))
554
+ lines.push(anchorLine('时间', fmtTime(card.时间)))
555
+ lines.push('')
556
+ lines.push('## 陈述')
557
+ lines.push(card.陈述 || '')
558
+ lines.push('')
559
+ lines.push('## 可信内容')
560
+ lines.push(card.内容 || '')
561
+ lines.push('')
562
+ return lines.join('\n').trimEnd() + '\n'
563
+ }
564
+
565
+ // ================= data layer =================
566
+ function categoryOf(p) { const cat = p.分类 || '未分类'; return String(cat).replace(/[\\/:*?"<>|]/g, '_') || '未分类' }
567
+ async function saveProblem(p) { await writeText('Problems/' + p.id + '.md', composeProblemMd(p)) }
568
+ async function saveProposition(p) { await writeText('Propos/' + categoryOf(p) + '/' + p.id + '.md', composePropositionMd(p)) }
569
+ async function saveMethod(m, global) { if (global) { await writeTextAbs(vibeRoot() + '/Methods/' + m.id + '.md', composeMethodMd(m)) } else { await writeText('Methods/' + m.id + '.md', composeMethodMd(m)) } }
570
+ async function saveVerified(card) { await writeText('Verified/' + (card.类型 === '问题' ? '问题' : '命题') + '/' + card.id + '.md', composeVerifiedMd(card)) }
571
+ async function ensureProgressDir(qid) { const base = frameworkRoot(); return await runShell('New-Item -Force -ItemType Directory -Path ' + psQuote(base + '/Progress/' + qid) + ' | Out-Null') }
572
+ // 单个方向的完整日志文本(供"每方向一个文件"与聚合复用)
573
+ function directionMdText(qid, d, standalone) {
574
+ const lines = []
575
+ if (standalone) { lines.push('# 研究方向日志|' + qid + ' / ' + d.id); lines.push('') }
576
+ lines.push('- 方向: ' + d.title)
577
+ lines.push('- 存活率: ' + (d.survival != null ? d.survival : '?') + ';状态: ' + d.status + (d.round ? ';轮次: ' + d.round : ''))
578
+ if (d.method) lines.push('- 方法: ' + d.method)
579
+ if (d.core_assumption) lines.push('- 核心假设: ' + d.core_assumption)
580
+ if (d.dead_end_reason) lines.push('- 死路原因: ' + d.dead_end_reason)
581
+ if (d.lemmas && d.lemmas.length) lines.push('- 引理索引: ' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join(';'))
582
+ lines.push('')
583
+ for (const j of (d.journal || [])) {
584
+ lines.push('### 第 ' + j.round + ' 轮|' + (j.agent || '') + '|' + (j.at || ''))
585
+ lines.push(j.prose || '')
586
+ lines.push('')
587
+ }
588
+ return lines.join('\n').trimEnd() + '\n'
589
+ }
590
+ // 研究日志 = 每方向一个独立文件(由代理直接写:Progress/<qid>/<dirId>.md,方向间无并发冲突)
591
+ // + 聚合索引(Progress/<qid>.md,由调度器从 dirState 汇总,不覆盖各方向文件)
592
+ async function writeJournal(qid) {
593
+ const dirs = dirState.get(qid) || []
594
+ const lines = []
595
+ lines.push('# 研究日志|' + qid)
596
+ lines.push('')
597
+ const arch = archivedJ[qid] || []
598
+ for (const seg of arch) { lines.push(seg); lines.push('') }
599
+ for (const d of dirs) {
600
+ lines.push('## 方向 ' + d.id + '|' + d.title + '|存活率' + (d.survival != null ? d.survival : '?') + '|状态' + d.status)
601
+ if (d.core_assumption) { lines.push(''); lines.push('**核心假设**:' + d.core_assumption) }
602
+ if (d.method) { lines.push('**方法**:' + d.method) }
603
+ if (d.dead_end_reason) { lines.push('**死路原因**:' + d.dead_end_reason) }
604
+ // 引理只在摘要给"索引"(id+标题),完整叙述在各方向文件 Progress/<qid>/<dirId>.md(由代理或调度器写入)
605
+ if (d.lemmas && d.lemmas.length) { lines.push('**引理索引**:' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join(';')) }
606
+ lines.push('**完整叙述**:见 `Progress/' + qid + '/' + d.id + '.md`')
607
+ for (const j of (d.journal || [])) {
608
+ lines.push('')
609
+ lines.push('### 第 ' + j.round + ' 轮|' + (j.agent || '') + '|' + (j.at || ''))
610
+ lines.push(j.prose || '')
611
+ }
612
+ lines.push('')
613
+ }
614
+ await writeText('Progress/' + qid + '.md', lines.join('\n').trimEnd() + '\n')
615
+ }
616
+ // 代理直接写内容:模拟/落盘代理声称写的文件(__writes),真实环境代理用 write 工具自己写,此处为调度器兜底落盘
617
+ async function applyAgentWrites(writes) {
618
+ if (!Array.isArray(writes)) return { ok: true, applied: 0 }
619
+ let applied = 0
620
+ for (const w of writes) {
621
+ if (!w || !w.path) continue
622
+ const safe = String(w.path).replace(/\\/g, '/').replace(/\.\./g, '')
623
+ if (!/^(Problems|Progress|Propos|Methods|Notes)\//.test(safe)) continue // 只允许知识库路径,防越界
624
+ const content = (w.content != null) ? String(w.content) : ''
625
+ // Progress/<qid>/<dir>.md 需要 Progress/<qid>/ 子目录
626
+ const m = /^Progress\/([^/]+)\//.exec(safe)
627
+ if (m) { const base = frameworkRoot(); await runShell('New-Item -Force -ItemType Directory -Path ' + psQuote(base + '/Progress/' + m[1]) + ' | Out-Null') }
628
+ const t = await fs.resolve(safe, { cwd: frameworkRoot() })
629
+ if (await fs.stat(t) !== undefined) { await fs.writeText(t, content, undefined, undefined, getPolicy()); applied += 1 }
630
+ else { await writeText(safe, content); applied += 1 }
631
+ }
632
+ return { ok: true, applied: applied }
633
+ }
634
+ // 把将被替换的旧方向归档为"已归档方向"段(保留论文式历史,重派生不丢记录)
635
+ async function archiveDirections(qid, oldDirs) {
636
+ const withJournal = oldDirs.filter(function (d) { return d.journal && d.journal.length > 0 })
637
+ if (withJournal.length === 0) return
638
+ const segs = []
639
+ for (const d of withJournal) {
640
+ const lines = []
641
+ lines.push('## 已归档方向 ' + d.id + '|' + d.title + '|状态' + (d.status || '') + '(共 ' + d.round + ' 轮)')
642
+ if (d.lessons && d.lessons.length) lines.push('**教训**:' + d.lessons.join(';'))
643
+ if (d.dead_end_reason) lines.push('**归档原因**:' + d.dead_end_reason)
644
+ for (const j of (d.journal || [])) {
645
+ lines.push('')
646
+ lines.push('### 第 ' + j.round + ' 轮|' + (j.agent || '') + '|' + (j.at || ''))
647
+ lines.push(j.prose || '')
648
+ }
649
+ segs.push(lines.join('\n'))
650
+ }
651
+ archivedJ[qid] = (archivedJ[qid] || []).concat(segs)
652
+ await writeJson('State/archived_journals.json', archivedJ)
653
+ }
654
+ function getDirState(qid) { if (!dirState.has(qid)) dirState.set(qid, []); return dirState.get(qid) }
655
+ async function saveDirState() { await writeJson('State/directions.json', Object.fromEntries(dirState)) }
656
+ async function rebuildIndex() {
657
+ const idx = {
658
+ at: now(), project: currentProject,
659
+ problems: Object.fromEntries(problems), propos: Object.fromEntries(propos),
660
+ methods: Object.fromEntries(methods), dirs: Object.fromEntries(dirState),
661
+ }
662
+ await writeJson('State/index.json', idx)
663
+ lastIndexWrite = now()
664
+ return { ok: true, problems: problems.size, propos: propos.size, methods: methods.size }
665
+ }
666
+ async function loadKnowledgeBase() {
667
+ problems = new Map(); propos = new Map(); methods = new Map(); globalMethods = new Map(); dirState = new Map()
668
+ try {
669
+ const pf = await listFiles('Problems')
670
+ for (const f of pf) {
671
+ if (!/\.md$/i.test(f)) continue
672
+ const id = f.replace(/\.md$/i, '')
673
+ const text = await readText('Problems/' + f)
674
+ if (text === undefined) continue
675
+ try { problems.set(id, parseProblemMd(id, text)) } catch (e) { console.error('vibe-math-v3: parse problem ' + id + ' failed: ' + String((e && e.message) || e)) }
676
+ }
677
+ const cats = await listDirs('Propos')
678
+ for (const cat of cats) {
679
+ const files = await listFiles('Propos/' + cat)
680
+ for (const f of files) {
681
+ if (!/\.md$/i.test(f)) continue
682
+ const id = f.replace(/\.md$/i, '')
683
+ const text = await readText('Propos/' + cat + '/' + f)
684
+ if (text === undefined) continue
685
+ try { const p = parsePropositionMd(id, text); p.分类 = cat; propos.set(id, p) } catch (e) { console.error('vibe-math-v3: parse proposition ' + id + ' failed: ' + String((e && e.message) || e)) }
686
+ }
687
+ }
688
+ const mf = await listFiles('Methods')
689
+ for (const f of mf) {
690
+ if (!/\.md$/i.test(f)) continue
691
+ const id = f.replace(/\.md$/i, '')
692
+ const text = await readText('Methods/' + f)
693
+ if (text === undefined) continue
694
+ try { methods.set(id, parseMethodMd(id, text)) } catch (e) { console.error('vibe-math-v3: parse method ' + id + ' failed: ' + String((e && e.message) || e)) }
695
+ }
696
+ // global methods (read-only visibility; writes go through promote)
697
+ try {
698
+ const gFiles = await listFilesAbs(vibeRoot() + '/Methods')
699
+ for (const f of gFiles) {
700
+ if (!/\.md$/i.test(f)) continue
701
+ const id = f.replace(/\.md$/i, '')
702
+ const text = await readTextAbs(vibeRoot() + '/Methods/' + f)
703
+ if (text === undefined) continue
704
+ try { globalMethods.set(id, parseMethodMd(id, text)) } catch (e) {}
705
+ }
706
+ } catch (e) {}
707
+ const dj = await readJson('State/directions.json')
708
+ if (dj && typeof dj === 'object') dirState = new Map(Object.entries(dj))
709
+ } catch (e) { console.error('vibe-math-v3: loadKnowledgeBase failed: ' + String((e && e.message) || e)) }
710
+ }
711
+ async function listFilesAbs(path) { try { const t = await fs.resolve(path); 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 [] } }
712
+ function allProblems() { return Array.from(problems.values()) }
713
+ function allPropos() { return Array.from(propos.values()) }
714
+ function depResolved(id) {
715
+ if (problems.has(id)) { const q = problems.get(id); return q.状态 === '已解决' || q.优先级 === 'never' }
716
+ if (propos.has(id)) { const p = propos.get(id); return p.概率 === 1 || p.概率 === 0 || p.优先级 === 'never' } // never = 主动弃权,视为依赖已满足,避免等待依赖死锁
717
+ return true // unknown dependency: treat as resolved (conservative)
718
+ }
719
+ function problemDepReady(q) { return (q.依赖 || []).every(function (d) { return depResolved(d) }) }
720
+ // 被依赖自动回填:X 依赖 Y → Y.被依赖 加入 X
721
+ async function syncDependencies() {
722
+ for (const q of allProblems()) { if (q.被依赖 && q.被依赖.length) q.被依赖 = [] }
723
+ for (const q of allProblems()) {
724
+ for (const d of (q.依赖 || [])) {
725
+ const t = problems.get(d)
726
+ if (t) { if (!t.被依赖) t.被依赖 = []; if (t.被依赖.indexOf(q.id) === -1) t.被依赖.push(q.id) }
727
+ }
728
+ }
729
+ }
730
+
731
+ // ================= persistence (scheduler state) =================
732
+ async function loadState() {
733
+ const s = await readJson('State/scheduler_state.json'); if (s) scheduler = Object.assign({}, scheduler, s)
734
+ const r = await readJson('State/agents.json'); if (r) agentRegistry = r
735
+ const dq = await readJson('State/decision_queue.json'); if (dq) decisionQueue = dq
736
+ const va = await readJson('State/verifier_accuracy.json'); if (va) verifierAccuracy = va
737
+ const tk = await readJson('State/tasks.json'); if (tk) tasks = tk
738
+ const er = await readJson('State/explorer_retries.json'); if (er) explorerRetries = er
739
+ const pq = await readJson('State/plans.json'); if (pq && Array.isArray(pq.queued)) planQueue = pq.queued
740
+ const ml = await readJson('State/method_log.json'); if (ml) methodLog = Object.assign({ pendingInventions: [], keepCount: 0, lastKeepAt: 0 }, ml)
741
+ const pl = await readJson('State/project_lock.json'); if (pl) projectLock = Object.assign({ sessionId: '', at: 0 }, pl)
742
+ const lp = await readJson('State/last_plan.json'); if (lp) lastPlanSummary = lp
743
+ const aj = await readJson('State/archived_journals.json'); if (aj && typeof aj === 'object') archivedJ = aj
744
+ }
745
+ async function saveAll() {
746
+ await writeJson('State/scheduler_state.json', scheduler)
747
+ await writeJson('State/agents.json', agentRegistry)
748
+ await writeJson('State/decision_queue.json', decisionQueue)
749
+ await writeJson('State/verifier_accuracy.json', verifierAccuracy)
750
+ await writeJson('State/tasks.json', tasks)
751
+ await writeJson('State/explorer_retries.json', explorerRetries)
752
+ await writeJson('State/plans.json', { queued: planQueue })
753
+ await writeJson('State/method_log.json', methodLog)
754
+ await writeJson('State/project_lock.json', projectLock)
755
+ await writeJson('State/archived_journals.json', archivedJ)
756
+ if (lastPlanSummary) await writeJson('State/last_plan.json', lastPlanSummary)
757
+ scheduler.lastCheckpoint = now()
758
+ }
759
+ async function refreshParams() {
760
+ params = Object.assign({}, DEFAULT_PARAMS)
761
+ await loadSettings()
762
+ await migrateLegacyParams()
763
+ }
764
+ async function migrateLegacyParams() {
765
+ const legacy = await readJson('State/params.json')
766
+ if (!legacy || Object.keys(legacy).length === 0) return
767
+ try {
768
+ params = Object.assign({}, params, sanitizeParams(legacy))
769
+ await saveSettings()
770
+ await writeJson('State/params.json', {})
771
+ await removeFile('State/params.json')
772
+ logActivity('params', 'legacy State/params.json merged into vibe_math_setting.json and removed')
773
+ } catch (e) { console.error('vibe-math-v3: params migration failed: ' + String((e && e.message) || e)) }
774
+ }
775
+
776
+ // ================= reporting =================
777
+ 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 }
778
+ async function buildReport() {
779
+ return {
780
+ ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
781
+ running: scheduler.running, mode: params.mode,
782
+ activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
783
+ problems: { total: problems.size, solved: allProblems().filter(function (q) { return q.状态 === '已解决' }).length },
784
+ propositions: { total: propos.size, resolved: allPropos().filter(function (p) { return p.概率 === 1 || p.概率 === 0 }).length },
785
+ verifyPending: (await buildVerifyCandidates()).length,
786
+ methods: { project: methods.size, global: globalMethods.size, pendingInventions: methodLog.pendingInventions.length },
787
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
788
+ registeredAgents: Object.keys(agentRegistry).length,
789
+ queuedPlanActions: planQueue.length,
790
+ recentActivity: activityLog.slice(-Math.min(30, Number(params.activityLogCap) || 100)),
791
+ params: params,
792
+ }
793
+ }
794
+ async function maybeWriteReport(force) {
795
+ const interval = Number(params.reportIntervalMs) || 0
796
+ if (!force && interval > 0 && (now() - lastReportWrite) < interval) return
797
+ if (!force && interval <= 0 && !reportDirty) return
798
+ await writeJson('Progress_Logs/report.json', await buildReport())
799
+ await writeNarrativeReport()
800
+ lastReportWrite = now(); reportDirty = false
801
+ }
802
+ async function writeNarrativeReport() {
803
+ // 论文式人读摘要(调度器编译,无需额外代理)
804
+ const lines = []
805
+ lines.push('# 项目进展报告|' + currentProject + '|' + fmtTime())
806
+ lines.push('')
807
+ lines.push('## 总览')
808
+ lines.push('- 运行中:' + scheduler.running + ';活跃子代理:' + scheduler.activeCount + '/' + params.maxParallelThreshold)
809
+ lines.push('- 问题:' + allProblems().filter(function (q) { return q.状态 === '已解决' }).length + '/' + problems.size + ' 已解决;命题:' + allPropos().filter(function (p) { return p.概率 === 1 || p.概率 === 0 }).length + '/' + propos.size + ' 已定论')
810
+ lines.push('- 方法库:项目 ' + methods.size + ' 条,全局 ' + globalMethods.size + ' 条,待沉淀发明 ' + methodLog.pendingInventions.length + ' 条')
811
+ lines.push('- 待执行计划动作:' + planQueue.length + ' 条;待人工决策:' + decisionQueue.filter(function (d) { return d.status === 'pending' }).length + ' 条')
812
+ lines.push('')
813
+ lines.push('## 问题状态')
814
+ for (const q of allProblems()) {
815
+ const dirs = getDirState(q.id)
816
+ const dirInfo = dirs.length ? '(方向:' + dirs.map(function (d) { return d.id + ':' + d.status }).join(', ') + ')' : ''
817
+ lines.push('- **' + q.id + '**:' + q.状态 + ',优先级 ' + q.优先级 + ',解法 ' + (q.solutions || []).length + ' 条' + dirInfo)
818
+ }
819
+ lines.push('')
820
+ lines.push('## 最近活动')
821
+ for (const e of activityLog.slice(-10)) lines.push('- ' + fmtTime(e.at) + ' [' + e.event + '] ' + e.detail)
822
+ lines.push('')
823
+ await writeText('Logs/报告.md', lines.join('\n'))
824
+ }
825
+ async function maybePushReport(force) {
826
+ const mode = params.reportMode || 'file'
827
+ if (mode !== 'push' && mode !== 'both') return
828
+ const interval = Number(params.reportIntervalMs) || 0
829
+ if (interval > 0) { if (!force && (now() - lastPushReport) < interval) return }
830
+ else if (!force && !reportDirty) return
831
+ if (!rootAgent || typeof rootAgent.followup !== 'function') return
832
+ try {
833
+ const report = await buildReport()
834
+ const text = '[Vibe Math V3] 进度更新:项目 "' + currentProject + '" 运行中=' + report.running +
835
+ ',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
836
+ '活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + ',待执行计划=' + report.queuedPlanActions + '。' +
837
+ '请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
838
+ rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'plugin', plugin: 'vibe-math-v3' } })
839
+ lastPushReport = now()
840
+ } catch (e) {
841
+ console.error('vibe-math-v3: push report failed: ' + String((e && e.message) || e))
842
+ }
843
+ }
844
+
845
+ // ================= child spawn / followup =================
846
+ 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' }
847
+ function childAgentOptions(role) {
848
+ const o = {}
849
+ 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) {}
850
+ const pv = role === 'planner' ? params.plannerProvider : params.provider
851
+ const md = role === 'planner' ? params.plannerModel : params.model
852
+ if (pv) o.provider = pv
853
+ if (md) o.model = md
854
+ return o
855
+ }
856
+ const NETWORK_TOOLS = ['web_search', 'web', 'fetch']
857
+ const SCRIPT_TOOLS = ['bash', 'pwsh']
858
+ function buildToolFilter(role) {
859
+ const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined
860
+ const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined
861
+ const net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined
862
+ const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined
863
+ let a = Array.isArray(allow) ? allow.slice() : []
864
+ let d = Array.isArray(deny) ? deny.slice() : []
865
+ if (net === false) d = d.concat(NETWORK_TOOLS); else if (net === true && a.length > 0) a = a.concat(NETWORK_TOOLS)
866
+ if (scr === false) d = d.concat(SCRIPT_TOOLS); else if (scr === true && a.length > 0) a = a.concat(SCRIPT_TOOLS)
867
+ const f = {}
868
+ if (a.length > 0) f.allow = a
869
+ if (d.length > 0) f.deny = d
870
+ return (f.allow || f.deny) ? f : undefined
871
+ }
872
+ async function spawnChild(label, promptText, meta) {
873
+ const role = meta && meta.role
874
+ const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions(role) }
875
+ const tf = buildToolFilter(role)
876
+ if (tf) request.toolFilter = tf
877
+ let started
878
+ try { started = await subagents.startContinuable({ provider: pickProvider(), label: label, request: request, signal: makeSignal(30000) }) }
879
+ catch (e) {
880
+ if (request.toolFilter) { delete request.toolFilter; console.error('vibe-math-v3: 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 }
881
+ }
882
+ agentRegistry[started.childId] = Object.assign({ createdAt: now() }, meta || {})
883
+ childOwner.set(started.childId, sessionId)
884
+ scheduler.activeCount = Math.max(0, scheduler.activeCount) + 1
885
+ await saveAll()
886
+ return started.childId
887
+ }
888
+ 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() }
889
+ async function interruptChild(childId) { try { subagents.interrupt(childId, { kind: 'ancestor', agent: rootAgent }) } catch (e) {} }
890
+
891
+ // ================= prompts =================
892
+ function personaText(key) { return params[key] ? (String(params[key]) + '\n\n') : '' }
893
+ function defaultKnowledgeContext() {
894
+ return 'KNOWLEDGE BASE & DATA MODEL (definition contract you MUST follow):\n' +
895
+ '\n1) TRUST LAYERS — the single most important rule:\n' +
896
+ '- Verified/ 中的内容 = 绝对可信(已被验证器判定为真/假并生成只读副本):可直接引用。\n' +
897
+ '- Propos/ 中 状态: 已验证·真/假 的命题 = 可信(以 Verified/ 副本为准)。\n' +
898
+ '- 其余一切(未定论命题、Progress/ 研究日志、Methods/ 中未验证断言、Notes/)= 经验性记录/参考,绝不能当作已成立事实引用。\n' +
899
+ '- 概率语义:1 = 绝对正确(可当已知事实);0 = 绝对错误;0 与 1 之间 = 未定论/待验证。\n' +
900
+ '\n2) OBJECT MODELS(md 卡片,软规范:头部锚点行 + 正文自由叙述):\n' +
901
+ '- 问题卡 Problems/<id>.md:{ ID, 类型:问题, 状态:原始|求解中|等待依赖|已解决|死路, 优先级, 依赖:[], 被依赖:[], 来源:原始|后生, 计划(由调度器按规划代理的计划自动更新:一句话说明下一轮安排), ## 陈述(完整问题陈述,每个记号/对象都要完整定义), ## 来源与动机(后生问题:产生流程/动机/如何回填主线), ## 解法候选(### 解法 N|标题|概率X|状态Y + 叙述式完整解法)}。\n' +
902
+ '- 命题卡 Propos/<分类>/<id>.md:{ ID, 类型:命题, 状态:未定论|已验证·真|已验证·假, 概率, 优先级, 依赖:[], ## 陈述(完整), ## 证明尝试(### 证明 N|…), ## 证伪尝试(### 证伪 N|…)}。\n' +
903
+ '- 方法卡 Methods/<id>.md:{ ID, 类型:方法, 状态:经验|应用验证|含已验证断言, 可信断言:[](只允许已进 Verified/ 的 ID), 上级体系/子方法/相关, 适用场景, ## 核心内容, ## 定义与记号, ## 应用记录, ## 改进历史 }。\n' +
904
+ '- 收口规则:某个解法/证明/证伪 概率=1 → 问题已解决 / 命题已验证(状态/概率锚点由调度器改写)。\n' +
905
+ '\n3) FOLDERS:Problems/ 问题清单;Progress/ 研究日志(每问题一个 md,按方向按轮续写);Propos/ 命题库;Methods/ 理论发明库;Verified/ 绝对可信(只读);Reliable/ 可信参考文献(只读);Notes/ 自由笔记;Logs/ 审计;State/ 调度器私有——不要读也不要改。\n' +
906
+ '\n4) METHOD LIBRARY RULES:开工前先查 Methods/(含全局 VibeMath/Methods/),有可复用方法/体系则引用其 ID;用后必须在 methods_used 上报(含效果与改进建议);本轮新发明/经验性总结必须在 new_inventions 上报(类型:理论体系|框架|工具|方法|思想|范式|技巧)——若与某张已有方法卡同类,在内容描述里注明"可并入 m-xxx"以便 Method Keeper 合并而非重复建卡。**重要区分**:methods_used 只能填**已存在方法卡的 ID**(形如 m-abc12345,来自 AVAILABLE METHODS 列表);你自己刚想出的新方法/新技巧不属于 methods_used,请如实填入 new_inventions(由 Method Keeper 蒸馏建卡);千万不要把方法名/标题文字当 id 填进 methods_used。\n' +
907
+ '\n5) OUTPUT REQUIREMENTS:完整性、不断章取义——任何输出的问题/命题/结论都要给出完整陈述并补全所依赖的对象/环境/背景定义;引用必须给出处(文件路径 + ID + 锚点/节),事实只引 Verified/;若结论依赖临时假设 p,必须显式写「若 <p 完整陈述> 成立,则:…」;只输出规定的 JSON(```json 围栏内),JSON 之外不写任何内容。' +
908
+ '\n\n6) WRITE-INTO-MD WORKFLOW(推荐,代理自组织直接写 md):框架允许你**直接写 Markdown**,把研究内容落到对应路径的 md 文件里,而不是全部塞进 JSON。做法(你拥有 file 工具):\n' +
909
+ '- 每个角色有明确的"归属文件":求解器写该方向的完整叙述到 `Progress/<问题id>/<方向id>.md`;新引理写一张完整命题卡到 `Propos/<分类>/<p-id>.md`(含锚点 `# 命题|标题`、`- ID/状态/概率/优先级` 与 `## 陈述`/`## 证明尝试`);方法整理代理写 `Methods/<m-id>.md`(锚点 `- ID/类型/状态/可信断言/适用场景` + `## 核心内容`/`## 应用记录`/`## 改进历史`)。\n' +
910
+ '- **并发写安全**:写任何文件前先 `vibe_math_claim_write({target:"<相对项目根的路径>"})` 申请写锁(同一文件同一时刻只允许一个代理写;若返回 busy 请稍后重试),写完 `vibe_math_release_write({target})`。不同方向是不同文件,天然不冲突。\n' +
911
+ '- 写完内容后,用 `vibe_math_sync_meta({meta:{kind:"solver|directions|methods", ...}})` 上报**轻量元数据**(方向状态/存活率/引理 id/方法卡 id/新发明清单),让调度器更新索引与调度——内容留在 md,元数据才进机读接口。\n' +
912
+ '- 若你所在环境无法写文件(工具不可用/被拒),再回退到"把内容放进下面的 JSON 字段"由调度器落盘。两种方式二选一即可,不要重复。'
913
+ }
914
+ function knowledgeContextText() { const k = params.knowledgeContext ? String(params.knowledgeContext) : defaultKnowledgeContext(); return k ? ('\n' + k + '\n') : '' }
915
+ function capabilitiesText(role) {
916
+ const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
917
+ const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
918
+ const scrOn = role === 'solver' ? params.solverAllowScripts : params.verifierAllowScripts
919
+ const toolParts = []
920
+ if (netOn !== false) toolParts.push('web search / literature lookup')
921
+ if (scrOn !== false) toolParts.push('symbolic/numeric computation (running scripts)')
922
+ let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
923
+ t += '- Network tools: ' + (netOn === false ? 'DISABLED for you' : 'available') + '; Script/shell tools: ' + (scrOn === false ? 'DISABLED for you' : 'available') + ' (your actual tool list is enforced by the framework).\n'
924
+ t += toolParts.length > 0
925
+ ? ('- 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'))
926
+ : '- External tools: none enabled for you this round.\n'
927
+ t += '- You may READ any file under Verified/ as a known, trusted dependency.\n'
928
+ t += '- You should BASE your reasoning on Propos/ (propositions with proofs/refutations and probabilities), Methods/ (reusable theories/tools), Reliable/ (trusted references), and Verified/.\n'
929
+ t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer (it composes the Markdown knowledge base from your report).\n'
930
+ t += '\nHOW TO READ EXISTING KNOWLEDGE: these are Markdown files. COARSE SCAN first: use read/grep on the anchor header lines (- ID/- 状态/- 概率/- 优先级/- 依赖) to locate relevant objects — do NOT load full prose yet. FINE READ after: read the full card for 陈述/证明/证伪/解法/核心内容 sections.\n'
931
+ return t
932
+ }
933
+ function methodsIndexText() {
934
+ const list = []
935
+ for (const m of methods.values()) list.push('- ' + m.id + '「' + m.标题 + '」(' + m.类型 + ', 状态=' + m.状态 + (m.可信断言 && m.可信断言.length ? ', 可信断言=' + m.可信断言.join(',') : '') + ')')
936
+ for (const m of globalMethods.values()) list.push('- ' + m.id + '「' + m.标题 + '」(全局, ' + m.类型 + ', 状态=' + m.状态 + ')')
937
+ // 方法库可能很大:只注入前 20 条索引,防止提示词膨胀(完整索引可读 Methods/ 目录)
938
+ const shown = list.slice(0, 20)
939
+ return shown.length ? ('\nAVAILABLE METHODS (Methods/, 含全局, 前 ' + shown.length + ' 条; 完整列表见 Methods/ 目录):\n' + shown.join('\n') + '\n') : ''
940
+ }
941
+ function explorerPrompt(q) {
942
+ return personaText('explorerPersona') + 'You are a research mathematician orchestrating strategy for one problem.\n\nPROBLEM (id: ' + q.id + '): ' + q.陈述 + '\n' +
943
+ knowledgeContextText() +
944
+ methodsIndexText() +
945
+ capabilitiesText('solver') +
946
+ '\nDo a first-stage METACOGNITIVE BRAINSTORM: decompose constraints, test boundary/extreme cases, map to similar known problems. First check the AVAILABLE METHODS list — if a listed method/system applies, plan to use it (you will reference its id in methods_used). ' +
947
+ 'Then propose 3-6 DIVERSE, mutually distinct solution directions (e.g. analytic method, constructive proof, contradiction, numeric approximation + limit passage, categorical abstraction, ...). ' +
948
+ 'Record each direction with its core assumption and an initial feasibility estimate. Every direction must be self-contained: title / method / core_assumption written completely, defining every object they mention — no 断章取义.\n\n' +
949
+ 'feasibility ∈ [0,1]. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
950
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5}],"methods_used":[{"id":"m-...","效果":"...","建议":"..."}],"new_inventions":[{"类型":"方法|工具|...","标题":"...","内容描述":"...","是否已入库":false}]}'
951
+ }
952
+ function rederivePrompt(q, prog) {
953
+ const prior = prog.map(function (d) {
954
+ return '- ' + d.id + '「' + d.title + '」status=' + d.status + ' round=' + d.round + ' survival=' + d.survival + (d.dead_end_reason ? ' [blocker: ' + d.dead_end_reason + ']' : '') +
955
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '')
956
+ }).join('\n')
957
+ return personaText('explorerPersona') + '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' +
958
+ knowledgeContextText() +
959
+ methodsIndexText() +
960
+ capabilitiesText('solver') +
961
+ '\nQuantitatively analyze the historical progress, blocker causes, and feasibility decay of each prior direction. Discard directions already proven dead ends (unless a new tool/idea changes that). ' +
962
+ 'Then deeply DERIVE 1-3 BRAND-NEW directions never tried before, each with a one-line motivation. Return the UNION of high-potential leftover directions and the brand-new directions (drop dead ends).\n\n' +
963
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
964
+ '{"directions":[{"id":"d1","title":"...","method":"...","core_assumption":"...","feasibility":0.5,"motivation":"..."}],"methods_used":[...],"new_inventions":[...]}'
965
+ }
966
+ function directionSummary(d) {
967
+ return 'id ' + d.id + '「' + d.title + '」method=' + d.method + ' | round=' + d.round + ' status=' + d.status +
968
+ ' survival=' + d.survival +
969
+ (d.lemmas && d.lemmas.length ? ' | lemmas: ' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join('; ') : '') +
970
+ (d.routes && d.routes.length ? ' | routes: ' + d.routes.map(function (r) { return r.title + '[' + (r.feasibility_signal || '') + ']' }).join('; ') : '') +
971
+ (d.lessons && d.lessons.length ? ' | lessons: ' + d.lessons.join('; ') : '') +
972
+ (d.blockers && d.blockers.length ? ' | blockers: ' + d.blockers.join('; ') : '')
973
+ }
974
+ function buildSolverContext(all, own, round, perSolver) {
975
+ const out = []
976
+ const n = Math.max(1, Number(perSolver) || 1)
977
+ let slots = n
978
+ if (round > 1) { out.push(own); slots -= 1 }
979
+ else slots -= 1
980
+ const others = all.filter(function (d) { return d.id !== own.id && d.status === 'active' })
981
+ for (let i = 0; i < others.length && slots > 0; i++) { out.push(others[i]); slots -= 1 }
982
+ return out.map(directionSummary).join('\n')
983
+ }
984
+ function solverPrompt(q, dir, round, progressText) {
985
+ let head = personaText('solverPersona') + 'You are a dedicated solver agent working ONE solution direction of a math problem (agent_self_iteration).\n\n'
986
+ head += 'PROBLEM (id: ' + q.id + '): ' + q.陈述 + '\nDIRECTION: ' + dir.title + ' (method: ' + dir.method + '; core assumption: ' + dir.core_assumption + ')\nROUND: ' + round + ' of ' + params.solverMaxRounds + '\n'
987
+ if (round > 1 || (progressText && progressText.length)) head += '\nYOUR PRIOR PROGRESS / OTHER DIRECTIONS:\n' + progressText + '\n'
988
+ head += knowledgeContextText()
989
+ head += methodsIndexText()
990
+ head += capabilitiesText('solver')
991
+ head += '\nStart from the last recorded node of direction ' + dir.id + ' (inherit progress, or branch a sub-route under it). Consult AVAILABLE METHODS first — reuse a listed method/system when it fits (report it in methods_used). Each round you MUST produce, even if incomplete:\n' +
992
+ '- new lemmas / intermediate conclusions WITH full proofs (they become Propos/ proposition cards);\n' +
993
+ '- 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' +
994
+ '- lessons learned from failed attempts;\n' +
995
+ '- an updated survival probability for this direction;\n' +
996
+ '- ANY new theory/tool/method/idea you invented or summarized this round in new_inventions (类型:理论体系|框架|工具|方法|思想|范式|技巧) — the Method Keeper will distill it into the theory library.'
997
+ 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 fully defined — 不断章取义), together with p_{q-tmp}: a PROPOSITION-class TEMPORARY ASSUMPTION answering q_sub. TEMPORARILY ASSUME p_{q-tmp} holds and continue the main line — every later proposition/conclusion depending on it MUST be stated as "若 <p_{q-tmp} 的完整陈述> 成立,则:..." (complete definitions).\n'
998
+ 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/ count as certain.\n'
999
+ 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'
1000
+ head += '\nRespond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1001
+ '{"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":"完整假设陈述(含所有定义)"}],"methods_used":[{"id":"<已有方法卡的ID,形如 m-abc12345,必须是 AVAILABLE METHODS 中出现的 id>","效果":"...","建议":"..."}],"new_inventions":[{"类型":"...","标题":"...","内容描述":"...","是否已入库":false}]}\n' +
1002
+ '区分规则:methods_used 只能填**已存在的方法卡 ID**(m-…,来自 AVAILABLE METHODS 列表)——引用你自己刚想出的新方法/新技巧不属于 methods_used,请如实填入 new_inventions(它会由 Method Keeper 蒸馏建卡);不要把方法名/标题当 id 填进 methods_used。'
1003
+ return head
1004
+ }
1005
+ function verifierTargetText(r) {
1006
+ if (r.kind === 'proposition') return 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
1007
+ if (r.kind === 'prop-proof') return 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
1008
+ return 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
1009
+ }
1010
+ function verifierReviewPrompt(r) {
1011
+ return personaText('verifierPersona') + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + verifierTargetText(r) + '\n' +
1012
+ knowledgeContextText() +
1013
+ capabilitiesText('verifier') +
1014
+ '\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' +
1015
+ '\nCitations: facts may only be cited from Verified/ (or Propos/ 状态: 已验证·真/假). Never cite an unverified or refuted object as a fact — if you need a sub-claim of a refuted card, re-derive it yourself.\n' +
1016
+ '\nIndependently output your initial review. Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1017
+ '{"Result":0.5,"Reason":"detailed logic chain, potential counterexample, or supporting evidence"}'
1018
+ }
1019
+ function verifierDebatePrompt(r, transcript) {
1020
+ return personaText('verifierPersona') + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + verifierTargetText(r) + '\n' +
1021
+ knowledgeContextText() +
1022
+ capabilitiesText('verifier') +
1023
+ '\nFULL DEBATE HISTORY SO FAR (每轮所有评审轮流发言的记录):\n' + transcript + '\n' +
1024
+ '\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. ' +
1025
+ 'Remember: formal/notation-level flaws in an otherwise correct proof should lower confidence only slightly — a mathematically correct argument is not "uncertain" because of typos; near-consensus is not a deadlock.\n' +
1026
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1027
+ '{"Result":0.5,"Reason":"updated logic chain / counterexample / proof / refutation","changed":"brief reason if you changed your Result, else null"}'
1028
+ }
1029
+ function plannerPrompt(brief) {
1030
+ return personaText('plannerPersona') + 'You are the SCHEDULING PLANNER of a multi-agent mathematical research system. Your job: autonomously choose the OPTIMAL schedule — you may lay out the NEXT ' + params.planningHorizon + ' agent-task calls in one plan (they will be executed in order, beyond-capacity ones queued for later ticks).\n\n' +
1031
+ 'CURRENT STATE BRIEF (JSON):\n' + JSON.stringify(brief, null, 2) + '\n\n' +
1032
+ 'ACTION VOCABULARY (code validates every action against hard invariants; invalid actions are dropped):\n' +
1033
+ '- {"action":"spawn","role":"explorer","target":"<qid>","reason":"..."} — problem has no directions yet or all dead (re-derive).\n' +
1034
+ '- {"action":"spawn","role":"solver","target":"<qid>","direction":"<dirId>","reason":"..."} — active direction, needs a solving round.\n' +
1035
+ '- {"action":"spawn","role":"verifier","target":"<rId>","reason":"..."} — verify candidate (from verify_candidates); keep solving AND verifying balanced.\n' +
1036
+ '- {"action":"spawn","role":"method-keeper","reason":"..."} — distill pending inventions / maintain the theory library.\n' +
1037
+ '- {"action":"interrupt","childId":"<childId>","reason":"..."} — stop a running child (direction dead, superseded...).\n' +
1038
+ '- {"action":"promote","target":"<pId>","reason":"..."} — high-value unresolved proposition → judge problem.\n' +
1039
+ '- {"action":"wait","target":"<id>","reason":"..."} — advisory: wait for a dependency.\n' +
1040
+ '\nHARD RULES: never re-schedule verified objects; problems with 依赖未就绪 (依赖就绪=false) should wait unless you explicitly accept a temporary assumption; respect capacity (brief.free_slots); PREFER problems whose dependencies are ready and whose directions have the highest survival; DO NOT forget verification — unresolved solutions/proofs/refutations (verify_candidates) will never be checked unless you schedule a verifier; schedule at most ' + params.planningHorizon + ' actions.\n' +
1041
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1042
+ '{"summary":"one-line plan rationale","plan":[{"action":"...","role":"...","target":"...","direction":"...","childId":"...","reason":"..."}]}'
1043
+ }
1044
+ function methodKeeperPrompt(digest) {
1045
+ return personaText('methodKeeperPersona') + 'You are the METHOD KEEPER of a mathematical research system. Your job: distill reusable THEORIES, FRAMEWORKS, TOOLS, METHODS, IDEAS (including experiential ones) invented during solving into the theory library, so future work can apply and extend them — like inventing group theory while solving an equation, or functional analysis while studying variational problems.\n\n' +
1046
+ knowledgeContextText() +
1047
+ '\nRECENT WORK DIGEST:\n' + digest + '\n\n' +
1048
+ 'For each pending invention decide: create a NEW method card, or fold it into an EXISTING method (as an improvement). Only list 可信断言 for claims already verified (ids from Verified/) — everything else stays 经验 (experiential). You may propose 上级体系/子方法 links to organize methods into systems.\n' +
1049
+ 'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
1050
+ '{"new_methods":[{"标题":"...","类型":"理论体系|框架|工具|方法|思想|范式|技巧","核心内容":"...","定义与记号":"...","适用场景":"...","上级体系":[],"子方法":[],"可信断言":[],"来源":"从哪些工作提炼"}],"improvements":[{"id":"m-...","改进内容":"...","原因":"..."}]}'
1051
+ }
1052
+
1053
+ // ================= decisions (manual/auto) =================
1054
+ 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 }
1055
+ 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 } }
1056
+ async function applyDecision(node, data, resolution) {
1057
+ if (node === 'spawn') {
1058
+ if (resolution.action === 'approve') { await spawnChild(data.label, data.promptText, data.meta); return { spawned: true } }
1059
+ try {
1060
+ const meta = data.meta || {}
1061
+ if (meta.role === 'explorer' && meta.qid) {
1062
+ const q = problems.get(meta.qid)
1063
+ if (q) { const dirs = getDirState(meta.qid); dirs.push({ id: 'd_' + shortId(), title: '用户拒绝派发', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], lessons: [], blockers: [], lemmas: [], journal: [], dead_end_reason: 'explorer 派发被用户拒绝' }); await saveDirState(); await writeJournal(meta.qid) }
1064
+ } else if (meta.role === 'solver' && meta.qid && meta.direction) {
1065
+ const dirs = getDirState(meta.qid); const d = dirs.find(function (x) { return x.id === meta.direction })
1066
+ if (d) { d.status = 'dead-end'; d.dead_end_reason = '求解器派发被用户拒绝' }
1067
+ await saveDirState(); await writeJournal(meta.qid)
1068
+ }
1069
+ } catch (e) { console.error('vibe-math-v3: spawn reject mark failed: ' + String((e && e.message) || e)) }
1070
+ return { spawned: false, rejected: true }
1071
+ }
1072
+ 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 } }
1073
+ if (node === 'plan') {
1074
+ if (resolution.action === 'approve') { planQueue = (data.plan || []).slice(); await applyPlanToProblemCards(planQueue); logActivity('plan', 'plan ' + data.planId + ' approved: ' + planQueue.length + ' action(s) queued') }
1075
+ else { planQueue = []; logActivity('plan', 'plan ' + data.planId + ' rejected by user') }
1076
+ return { planApproved: resolution.action === 'approve', queued: planQueue.length }
1077
+ }
1078
+ if (node === 'method-promote') {
1079
+ const m = methods.get(data.methodId)
1080
+ if (m && resolution.action === 'approve') { await promoteMethodToGlobal(m); logActivity('method', 'method ' + m.id + ' promoted to global library (user approved)') }
1081
+ else logActivity('method', 'method ' + data.methodId + ' promotion ' + (resolution.action === 'approve' ? '' : 'rejected'))
1082
+ return { promoted: resolution.action === 'approve' }
1083
+ }
1084
+ return {}
1085
+ }
1086
+ 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' } }
1087
+
1088
+ // ================= scheduler core =================
1089
+ function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v3 tick error: ' + String((e && e.stack) || e)) }) }
1090
+ async function tick() {
1091
+ if (tickInFlight) return; if (!rootAgent) return; if (!scheduler.running) return; if (scheduler.gate) return
1092
+ tickInFlight = true
1093
+ lastTickAt = now()
1094
+ try {
1095
+ await syncDependencies()
1096
+ await processStatusUpdates()
1097
+ await processPriorityAdjust()
1098
+ await autoDependencyStates()
1099
+ await processPromote()
1100
+ await reconcileVerify()
1101
+ await executePlanQueue()
1102
+ await maybePlan()
1103
+ await maybeMethodKeepFallback()
1104
+ await maybePushReport(false)
1105
+ await maybeWriteReport(false)
1106
+ // index 节流:每 5s 至多重写一次(工具调用/init/setProject 仍即时重建)
1107
+ if (params.indexAutoRebuild && (now() - lastIndexWrite) > 5000) await rebuildIndex()
1108
+ await checkTermination()
1109
+ } finally { tickInFlight = false }
1110
+ }
1111
+ // note: probability-1 rules (same semantics as v2, over md anchors)
1112
+ async function processStatusUpdates() {
1113
+ let changed = false
1114
+ for (const q of allProblems()) {
1115
+ if ((q.solutions || []).some(function (s) { return s.prob === 1 })) {
1116
+ const wasSolved = q.状态 === '已解决'
1117
+ q.状态 = '已解决'; q.优先级 = 'never'
1118
+ // 先置状态再写卡(writeVerifiedProblemCardIfNeeded 依赖 状态=已解决 才能生成卡)
1119
+ if (!wasSolved) { changed = true; await writeVerifiedProblemCardIfNeeded(q) }
1120
+ }
1121
+ }
1122
+ for (const p of allPropos()) {
1123
+ let pChanged = false
1124
+ const proofOne = (p.proofs || []).some(function (x) { return x.prob === 1 })
1125
+ const refuteOne = (p.refutes || []).some(function (x) { return x.prob === 1 })
1126
+ if (proofOne && p.概率 !== 1) { p.概率 = 1; p.状态 = '已验证·真'; pChanged = true }
1127
+ else if (refuteOne && p.概率 !== 0) { p.概率 = 0; p.状态 = '已验证·假'; pChanged = true }
1128
+ if ((p.概率 === 1 || p.概率 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
1129
+ if (p.概率 === 1 || p.概率 === 0) {
1130
+ if (await writeVerifiedPropositionCardIfNeeded(p)) pChanged = true
1131
+ // 关闭晋升/判断出的"僵尸"问题
1132
+ for (const q of allProblems()) {
1133
+ if (q.状态 === '已解决') continue
1134
+ if (q.判断命题 === p.id || (q.来源命题 === p.id) || ((q.来源 === 'promote' || q.来源 === 'judge') && q.来源与动机 && q.来源与动机.indexOf(p.id) !== -1)) { q.状态 = '已解决'; q.优先级 = 'never'; changed = true }
1135
+ }
1136
+ }
1137
+ if (pChanged) { await saveProposition(p); changed = true }
1138
+ }
1139
+ if (changed) { for (const q of allProblems()) await saveProblem(q); logActivity('update', 'status updates applied (probability-1 closures / verified cards)') }
1140
+ }
1141
+ async function writeVerifiedPropositionCardIfNeeded(p) {
1142
+ if (p.概率 !== 1 && p.概率 !== 0) return false
1143
+ const proofs1 = (p.proofs || []).filter(function (x) { return x.prob === 1 })
1144
+ const refutes1 = (p.refutes || []).filter(function (x) { return x.prob === 1 })
1145
+ const parts = []
1146
+ for (let i = 0; i < proofs1.length; i++) parts.push('【证明 #' + (i + 1) + '】' + (proofs1[i].text || ''))
1147
+ for (let i = 0; i < refutes1.length; i++) parts.push('【证伪 #' + (i + 1) + '】' + (refutes1[i].text || ''))
1148
+ const card = { id: p.id, 标题: p.标题, 类型: '命题', 结论: p.概率 === 1, 概率: p.概率, 陈述: p.陈述, 内容: parts.join('\n\n'), 分类: categoryOf(p), 来源: p.来源问题 || '', 时间: now() }
1149
+ return await writeVerifiedCardIfChanged(card)
1150
+ }
1151
+ async function writeVerifiedProblemCardIfNeeded(q) {
1152
+ if (q.状态 !== '已解决') return false
1153
+ const sols1 = (q.solutions || []).filter(function (s) { return s.prob === 1 })
1154
+ const parts = []
1155
+ for (let i = 0; i < sols1.length; i++) parts.push('【解法 #' + (i + 1) + '】' + (sols1[i].text || ''))
1156
+ const card = { id: q.id, 标题: q.标题, 类型: '问题', 结论: true, 概率: 1, 陈述: q.陈述, 内容: parts.join('\n\n'), 分类: '问题', 来源: q.id, 时间: now() }
1157
+ return await writeVerifiedCardIfChanged(card)
1158
+ }
1159
+ // 幂等写卡:内容未变化则不重写(时间戳行不参与比较,避免每 tick 重写与日志刷屏)
1160
+ async function writeVerifiedCardIfChanged(card) {
1161
+ const rel = 'Verified/' + (card.类型 === '问题' ? '问题' : '命题') + '/' + card.id + '.md'
1162
+ const existing = await readText(rel)
1163
+ const md = composeVerifiedMd(card)
1164
+ const strip = function (s) { return String(s).split('\n').filter(function (l) { return l.indexOf('- 时间:') !== 0 }).join('\n').trim() }
1165
+ if (existing !== undefined && strip(existing) === strip(md)) return false
1166
+ await saveVerified(card)
1167
+ return true
1168
+ }
1169
+ async function processPriorityAdjust() {
1170
+ const mode = params.priorityAdjust || 'none'
1171
+ if (mode !== 'none') {
1172
+ let changed = false
1173
+ for (const q of allProblems()) {
1174
+ if (q.状态 === '已解决' || q.优先级 === 'never') continue
1175
+ const dirs = getDirState(q.id)
1176
+ if (mode === 'deadend-deprioritize') {
1177
+ if (dirs.length > 0 && dirs.every(function (d) { return d.status === 'dead-end' })) { const cur = Number(q.优先级); if (Number.isFinite(cur) && cur < 10) { q.优先级 = 10; changed = true } }
1178
+ } else if (mode === 'survival-map') {
1179
+ if (dirs.length > 0) { const maxSurv = Math.max.apply(null, dirs.map(function (d) { return Number(d.survival) || 0 })); const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * maxSurv))); if (q.优先级 !== target) { q.优先级 = target; changed = true } }
1180
+ }
1181
+ }
1182
+ if (changed) { for (const q of allProblems()) await saveProblem(q); logActivity('priority', 'problem priorities auto-adjusted (' + mode + ')') }
1183
+ }
1184
+ const pMode = params.proposPriorityAdjust || 'none'
1185
+ if (pMode === 'progress-graded') {
1186
+ const changedProps = []
1187
+ for (const p of allPropos()) {
1188
+ if (p.概率 === 1 || p.概率 === 0 || p.优先级 === 'never') continue
1189
+ const closeness = Math.abs(Number(p.概率) - 0.5)
1190
+ const material = Math.min(5, (p.proofs || []).length + (p.refutes || []).length)
1191
+ const score = closeness * 1.2 + material * 0.08
1192
+ const target = Math.round(Math.max(0, Math.min(10, 10 - 10 * score)))
1193
+ const cur = Number(p.优先级)
1194
+ if (Number.isFinite(cur) && cur !== target) { p.优先级 = target; changedProps.push(p) }
1195
+ }
1196
+ if (changedProps.length > 0) { for (const p of changedProps) await saveProposition(p); logActivity('priority', 'proposition priorities auto-adjusted (progress-graded)') }
1197
+ }
1198
+ }
1199
+ // 自动依赖状态:依赖未就绪 → 等待依赖;依赖就绪 → 回到求解中
1200
+ async function autoDependencyStates() {
1201
+ let changed = false
1202
+ for (const q of allProblems()) {
1203
+ if (q.状态 === '已解决' || q.优先级 === 'never') continue
1204
+ const ready = problemDepReady(q)
1205
+ if (!ready && q.状态 !== '等待依赖') { q.状态 = '等待依赖'; changed = true }
1206
+ else if (ready && q.状态 === '等待依赖') { q.状态 = '求解中'; changed = true }
1207
+ }
1208
+ if (changed) { for (const q of allProblems()) await saveProblem(q) }
1209
+ }
1210
+ // note 3 + value: promote high-value unresolved propositions into Problems (judge problem)
1211
+ async function processPromote() {
1212
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
1213
+ for (const p of allPropos()) {
1214
+ if (p.概率 === 1 || p.概率 === 0 || p.优先级 === 'never') continue
1215
+ if (Number(p.价值关键性) < Number(params.promoteValueThreshold)) continue
1216
+ if (p.在问题清单) continue
1217
+ const qDescs = allProblems().map(function (q) { return q.陈述 })
1218
+ if (qDescs.indexOf('判断下述命题是否成立:' + p.陈述) !== -1) continue
1219
+ const qid = 'q-promoted-' + String(p.id).replace(/[^a-z0-9\-]/gi, '').slice(-12)
1220
+ const sols = []
1221
+ const proofs = p.proofs || []; const refutes = p.refutes || []
1222
+ for (let j = 0; j < proofs.length; j++) { const it = proofs[j]; sols.push({ title: '【证明】' + (it.title || ('证明' + (j + 1))), prob: clamp01(it.prob != null ? it.prob : 0.5), status: '未定论', text: '【证明】' + (it.text || '') }) }
1223
+ for (let j = 0; j < refutes.length; j++) { const it = refutes[j]; sols.push({ title: '【证伪】' + (it.title || ('证伪' + (j + 1))), prob: clamp01(it.prob != null ? it.prob : 0.5), status: '未定论', text: '【证伪】' + (it.text || '') }) }
1224
+ problems.set(qid, {
1225
+ id: qid, 标题: '判断命题:' + p.标题, 状态: '求解中', 优先级: 1,
1226
+ 依赖: [], 被依赖: [], 来源: 'promote',
1227
+ 计划: '证明或证伪源命题 ' + p.id + '(解法列表中的【证明】/【证伪】条目即原命题的证明/证伪材料,验证结果会回写源命题)。',
1228
+ 陈述: '判断下述命题是否成立:' + p.陈述,
1229
+ 来源与动机: '由命题 ' + p.id + '(价值/关键性=' + p.价值关键性 + ')自动晋升,目标:证明或证伪该命题;验证结果回写源命题。',
1230
+ solutions: sols, 判断命题: p.id, 来源命题: p.id,
1231
+ })
1232
+ p.在问题清单 = true
1233
+ await saveProblem(problems.get(qid))
1234
+ await saveProposition(p)
1235
+ logActivity('promote', 'proposition ' + p.id + ' promoted to problem ' + qid + '(' + sols.length + ' 条证明/证伪转为解法)')
1236
+ return // one per tick
1237
+ }
1238
+ }
1239
+
1240
+ // ================= verify candidates (like v2) =================
1241
+ async function buildVerifyCandidates() {
1242
+ const out = []
1243
+ for (const q of allProblems()) {
1244
+ if (q.状态 === '已解决' || q.优先级 === 'never') continue
1245
+ const sols = q.solutions || []
1246
+ for (let j = 0; j < sols.length; j++) {
1247
+ const s = sols[j]
1248
+ if (s.prob === 1 || s.prob === 0 || s.status === '已验') continue
1249
+ if (!String(s.text || '').trim()) continue
1250
+ out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.陈述, process: s.text || '', idx: j, prob: Number(s.prob) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
1251
+ }
1252
+ }
1253
+ for (const p of allPropos()) {
1254
+ if (p.概率 === 1 || p.概率 === 0 || p.优先级 === 'never') continue
1255
+ if (p.在问题清单) continue
1256
+ const proofs = p.proofs || []; const refutes = p.refutes || []
1257
+ if (proofs.length === 0 && refutes.length === 0) {
1258
+ if (String(p.id).indexOf('p-tmp-') === 0) continue
1259
+ out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.陈述, prob: Number(p.概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
1260
+ } else {
1261
+ for (let j = 0; j < proofs.length; j++) { if (proofs[j].prob === 1 || proofs[j].prob === 0 || proofs[j].status === '已验') continue; if (!String(proofs[j].text || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.陈述, side: '证明', process: proofs[j].text || '', idx: j, prob: Number(proofs[j].prob) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
1262
+ for (let j = 0; j < refutes.length; j++) { if (refutes[j].prob === 1 || refutes[j].prob === 0 || refutes[j].status === '已验') continue; if (!String(refutes[j].text || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.陈述, side: '证伪', process: refutes[j].text || '', idx: j, prob: Number(refutes[j].prob) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
1263
+ }
1264
+ }
1265
+ out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
1266
+ return out
1267
+ }
1268
+ function verifyTaskBusy(rId) { if (tasks['verify:' + rId]) return true; return Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'verifier' && m.rId === rId }) }
1269
+ async function createVerifyTask(c) {
1270
+ const rId = c.rId
1271
+ if (verifyTaskBusy(rId)) return false
1272
+ 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() }
1273
+ logActivity('verify', 'verification task created for ' + rId)
1274
+ await saveAll()
1275
+ return true
1276
+ }
1277
+ async function backfillVerifiers(t) {
1278
+ while (t.children.length < t.expectedCount) {
1279
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
1280
+ const index = t.children.length
1281
+ const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
1282
+ t.children.push(childId)
1283
+ }
1284
+ if (t.children.length >= t.expectedCount) t.status = 'debating'
1285
+ }
1286
+ async function reconcileVerify() {
1287
+ const ids = Object.keys(tasks)
1288
+ for (let i = 0; i < ids.length; i++) {
1289
+ const t = tasks[ids[i]]
1290
+ if (t.type !== 'verify') continue
1291
+ if (t.status === 'paused') {
1292
+ const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
1293
+ if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
1294
+ }
1295
+ if (t.status !== 'spawning') continue
1296
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
1297
+ await backfillVerifiers(t)
1298
+ await saveAll() // 持久化 children(resume 时任务簿记更准确)
1299
+ }
1300
+ }
1301
+
1302
+ // ================= planner (需求 3) =================
1303
+ function hasSchedulableWork() {
1304
+ if (scheduler.activeCount >= params.maxParallelThreshold) return false
1305
+ for (const q of allProblems()) {
1306
+ if (q.状态 === '已解决' || q.优先级 === 'never') continue
1307
+ if (q.状态 === '等待依赖') continue
1308
+ const dirs = getDirState(q.id)
1309
+ const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
1310
+ if (busy) continue
1311
+ const allExhausted = dirs.length > 0 && dirs.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
1312
+ if (dirs.length === 0 || allExhausted) { if ((explorerRetries[q.id] || 0) < (Number(params.maxExplorerRetries) || 3)) return true }
1313
+ else if (dirs.some(function (d) { return d.status === 'active' && !Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && m.direction === d.id && m.role === 'solver' }) })) return true
1314
+ }
1315
+ return false
1316
+ }
1317
+ async function buildBrief() {
1318
+ const qsList = []
1319
+ for (const q of allProblems()) {
1320
+ if (q.状态 === '已解决' || q.优先级 === 'never') continue
1321
+ const dirs = getDirState(q.id)
1322
+ qsList.push({
1323
+ id: q.id, 状态: q.状态, 优先级: q.优先级, 依赖: q.依赖, 依赖就绪: problemDepReady(q),
1324
+ 方向数: dirs.length,
1325
+ 活跃方向: dirs.filter(function (d) { return d.status === 'active' }).map(function (d) { return d.id }),
1326
+ 最高存活率: dirs.length ? Math.max.apply(null, dirs.map(function (d) { return Number(d.survival) || 0 })) : null,
1327
+ 解法数: (q.solutions || []).length,
1328
+ })
1329
+ }
1330
+ const cands = (await buildVerifyCandidates()).slice(0, 10).map(function (c) { return { rId: c.rId, kind: c.kind, target: c.pId || c.qid, prob: c.prob, priority: c.priority } })
1331
+ return {
1332
+ at: now(), horizon: params.planningHorizon,
1333
+ free_slots: Math.max(0, params.maxParallelThreshold - scheduler.activeCount),
1334
+ maxParallelThreshold: params.maxParallelThreshold,
1335
+ problems: qsList,
1336
+ verify_candidates: cands,
1337
+ active_agents: Object.keys(agentRegistry).map(function (cid) { const m = agentRegistry[cid]; return { childId: cid, role: m.role, target: m.qid || m.rId || '', direction: m.direction || '', round: m.round || '' } }),
1338
+ methods: Array.from(methods.values()).map(function (m) { return { id: m.id, 标题: m.标题, 状态: m.状态 } }).slice(0, 20),
1339
+ pending_inventions: methodLog.pendingInventions.length,
1340
+ last_plan: lastPlanSummary ? { at: lastPlanSummary.at, summary: lastPlanSummary.summary, outcomes: lastPlanSummary.outcomes } : null,
1341
+ recent_events: activityLog.slice(-8),
1342
+ }
1343
+ }
1344
+ async function maybePlan() {
1345
+ if (!params.plannerEnabled) { await fallbackScheduler(); return }
1346
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
1347
+ // 规划代理在途时不再重复调用
1348
+ const plannerInFlight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'planner' })
1349
+ if (plannerInFlight) return
1350
+ // 有"可调度工作"= 有待解问题方向可推进 或 有验证候选 或 有待沉淀发明 或 有待执行计划。
1351
+ // 修复:仅剩验证候选(如所有问题已解决但 Propos 里仍有未验证命题/解法)时也必须触发规划,
1352
+ // 否则 planner 永远不会被调用、验证永不进行。
1353
+ const verifyWork = (await buildVerifyCandidates()).length > 0
1354
+ if (!hasSchedulableWork() && !verifyWork && methodLog.pendingInventions.length === 0 && planQueue.length === 0) { await maybeMethodKeepFallback(); return }
1355
+ // 冷却:系统空闲且有工作时忽略冷却(避免 30s 空转)
1356
+ const cooldown = Number(params.planMinIntervalMs) || 0
1357
+ const hasInflight = Object.keys(agentRegistry).length > 0 || planQueue.length > 0
1358
+ if (cooldown > 0 && hasInflight && (now() - lastPlanAt) < cooldown) return
1359
+ await callPlanner()
1360
+ }
1361
+ async function callPlanner() {
1362
+ const brief = await buildBrief()
1363
+ lastPlanAt = now()
1364
+ const planId = 'plan-' + shortId()
1365
+ const promptText = plannerPrompt(brief)
1366
+ try {
1367
+ await spawnChild('planner:' + planId, promptText, { role: 'planner', planId: planId, brief: brief })
1368
+ logActivity('plan', 'planner ' + planId + ' called with ' + brief.problems.length + ' problem(s), ' + brief.verify_candidates.length + ' verify candidate(s)')
1369
+ return { spawned: true }
1370
+ } catch (e) {
1371
+ console.error('vibe-math-v3: planner spawn failed: ' + String((e && e.message) || e))
1372
+ plannerFails = Math.min(999, plannerFails + 1)
1373
+ if (plannerFails >= (Number(params.plannerMaxFails) || 3)) { params.plannerEnabled = false; logActivity('plan', 'planner disabled after ' + plannerFails + ' consecutive failures — heuristic mode') }
1374
+ await fallbackScheduler()
1375
+ return { spawned: false, error: String((e && e.message) || e) }
1376
+ }
1377
+ }
1378
+ async function handlePlanner(childId, meta, output) {
1379
+ delete agentRegistry[childId]
1380
+ const parsed = parseJson(output)
1381
+ const actions = (parsed && Array.isArray(parsed.plan)) ? parsed.plan : []
1382
+ const summary = (parsed && parsed.summary) ? String(parsed.summary) : ''
1383
+ const planId = meta.planId || ('plan-' + shortId())
1384
+ if (actions.length === 0) {
1385
+ plannerFails += 1
1386
+ logActivity('plan', 'planner ' + planId + ' returned no usable plan (' + String(output || '').slice(0, 200) + ')')
1387
+ if (plannerFails >= (Number(params.plannerMaxFails) || 3)) { params.plannerEnabled = false; logActivity('plan', 'planner disabled after ' + plannerFails + ' consecutive failures — heuristic mode') }
1388
+ await fallbackScheduler()
1389
+ return
1390
+ }
1391
+ plannerFails = 0
1392
+ const validated = await validatePlan(actions)
1393
+ lastPlanSummary = { at: now(), planId: planId, summary: summary, actions: validated.length, outcomes: [] }
1394
+ await writeJson('Logs/Plans/' + planId + '.json', { at: now(), planId: planId, summary: summary, raw: actions, validated: validated, project: currentProject })
1395
+ logActivity('plan', 'planner ' + planId + ' → ' + validated.length + '/' + actions.length + ' valid action(s): ' + validated.map(function (a) { return a.action + (a.role ? ':' + a.role : '') + (a.target ? ':' + a.target : '') }).join(', '))
1396
+ if (validated.length === 0) {
1397
+ plannerFails += 1
1398
+ await fallbackScheduler()
1399
+ return
1400
+ }
1401
+ // manual: 计划审批门在 planner 结果到达后挂起(审批的是真实计划);auto: 直接入队
1402
+ if (params.mode === 'manual') {
1403
+ const d = enqueueDecision('plan', 'planner ' + planId + ' 计划 ' + validated.length + ' 个动作,是否放行?', { planId: planId, plan: validated, brief: meta.brief })
1404
+ scheduler.gate = { decisionId: d.id, node: 'plan' }
1405
+ await saveAll()
1406
+ return
1407
+ }
1408
+ planQueue = validated
1409
+ await applyPlanToProblemCards(validated)
1410
+ await saveAll()
1411
+ }
1412
+ async function validatePlan(actions) {
1413
+ const out = []
1414
+ const seen = {}
1415
+ const allowed = { spawn: 1, interrupt: 1, promote: 1, wait: 1, continue: 1, stop: 1 }
1416
+ for (let i = 0; i < actions.length && out.length < (Number(params.planningHorizon) || 3); i++) {
1417
+ const a = actions[i]
1418
+ if (!a || typeof a !== 'object') continue
1419
+ const act = String(a.action || '')
1420
+ if (!allowed[act]) continue
1421
+ const key = act + ':' + (a.role || '') + ':' + (a.target || '') + ':' + (a.direction || '') + ':' + (a.childId || '')
1422
+ if (seen[key]) continue
1423
+ seen[key] = true
1424
+ if (act === 'spawn') {
1425
+ const role = String(a.role || '')
1426
+ if (role === 'explorer') {
1427
+ const q = problems.get(String(a.target || ''))
1428
+ if (!q || q.状态 === '已解决' || q.优先级 === 'never') continue
1429
+ const dirs = getDirState(q.id)
1430
+ const allExhausted = dirs.length > 0 && dirs.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
1431
+ if (!(dirs.length === 0 || allExhausted)) continue
1432
+ const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'explorer' && m.qid === q.id })
1433
+ if (busy) continue
1434
+ if ((explorerRetries[q.id] || 0) >= (Number(params.maxExplorerRetries) || 3)) continue
1435
+ } else if (role === 'solver') {
1436
+ const q = problems.get(String(a.target || ''))
1437
+ if (!q || q.状态 === '已解决' || q.优先级 === 'never' || q.状态 === '等待依赖') continue
1438
+ const dir = (getDirState(q.id) || []).find(function (d) { return d.id === String(a.direction || '') })
1439
+ if (!dir || dir.status !== 'active') continue
1440
+ 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' })
1441
+ if (running) continue
1442
+ } else if (role === 'verifier') {
1443
+ const rId = String(a.target || '')
1444
+ if (!rId) continue
1445
+ if (verifyTaskBusy(rId)) continue
1446
+ const cands = await buildVerifyCandidates()
1447
+ if (!cands.some(function (c) { return c.rId === rId })) continue
1448
+ } else if (role === 'method-keeper') {
1449
+ // 无目标;允许(由代码兜底去重:一次只允许一个 method-keeper)
1450
+ const running = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'method-keeper' })
1451
+ if (running) continue
1452
+ } else continue
1453
+ out.push({ action: 'spawn', role: role, target: String(a.target || ''), direction: a.direction || '', reason: String(a.reason || '') })
1454
+ } else if (act === 'interrupt') {
1455
+ const cid = String(a.childId || '')
1456
+ if (!agentRegistry[cid]) continue
1457
+ out.push({ action: 'interrupt', childId: cid, reason: String(a.reason || '') })
1458
+ } else if (act === 'promote') {
1459
+ const p = propos.get(String(a.target || ''))
1460
+ if (!p || p.概率 === 1 || p.概率 === 0 || p.优先级 === 'never' || p.在问题清单) continue
1461
+ if (Number(p.价值关键性) < Number(params.promoteValueThreshold)) continue
1462
+ out.push({ action: 'promote', target: String(a.target || ''), reason: String(a.reason || '') })
1463
+ } else if (act === 'wait' || act === 'continue' || act === 'stop') {
1464
+ out.push(Object.assign({ action: act }, a.childId ? { childId: String(a.childId) } : {}, a.target ? { target: String(a.target) } : {}, { reason: String(a.reason || '') }))
1465
+ }
1466
+ }
1467
+ return out
1468
+ }
1469
+ // 计划审批/入队后:把 planner 的下一步安排回写到问题卡的「计划」锚点(软规范:- 计划: 一句话说明下一轮安排)
1470
+ async function applyPlanToProblemCards(actions) {
1471
+ for (let i = 0; i < (actions || []).length; i++) {
1472
+ const a = actions[i]
1473
+ const tid = a.target || ''
1474
+ if (!tid || !problems.has(tid)) continue
1475
+ const q = problems.get(tid)
1476
+ const desc = a.reason ? String(a.reason) : ('下一步:' + (a.role ? a.role : a.action) + (a.direction ? ' ' + a.direction : ''))
1477
+ if (q.计划 !== desc) { q.计划 = desc; await saveProblem(q) }
1478
+ }
1479
+ }
1480
+ async function executePlanQueue() {
1481
+ while (planQueue.length > 0 && scheduler.activeCount < params.maxParallelThreshold) {
1482
+ const a = planQueue.shift()
1483
+ try {
1484
+ await executePlanAction(a)
1485
+ if (lastPlanSummary) lastPlanSummary.outcomes.push({ action: a.action, role: a.role || '', target: a.target || '', ok: true })
1486
+ } catch (e) {
1487
+ console.error('vibe-math-v3: plan action failed: ' + String((e && e.message) || e))
1488
+ if (lastPlanSummary) lastPlanSummary.outcomes.push({ action: a.action, role: a.role || '', target: a.target || '', ok: false, error: String((e && e.message) || e) })
1489
+ }
1490
+ }
1491
+ }
1492
+ async function executePlanAction(a) {
1493
+ if (a.action === 'spawn') {
1494
+ if (a.role === 'explorer') {
1495
+ const q = problems.get(a.target)
1496
+ if (!q) return
1497
+ const dirs = getDirState(q.id)
1498
+ const promptText = dirs.length > 0 ? rederivePrompt(q, dirs) : explorerPrompt(q)
1499
+ explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
1500
+ await spawnChild('explorer:' + q.id, promptText, { role: 'explorer', qid: q.id })
1501
+ logActivity('explorer', 'problem ' + q.id + ' explorer spawned (plan)')
1502
+ } else if (a.role === 'solver') {
1503
+ const q = problems.get(a.target)
1504
+ if (!q) return
1505
+ const dir = (getDirState(q.id) || []).find(function (d) { return d.id === a.direction })
1506
+ if (!dir) return
1507
+ const progressText = buildSolverContext(getDirState(q.id), dir, 1, params.directionsPerSolver)
1508
+ await spawnChild('solver:' + q.id + ':' + dir.id, solverPrompt(q, dir, 1, progressText), { role: 'solver', qid: q.id, direction: dir.id, round: 1, description: q.陈述 })
1509
+ logActivity('solver', 'problem ' + q.id + ' direction ' + dir.id + ' solver spawned (plan)')
1510
+ } else if (a.role === 'verifier') {
1511
+ const cands = await buildVerifyCandidates()
1512
+ const c = cands.find(function (x) { return x.rId === a.target })
1513
+ if (c) await createVerifyTask(c)
1514
+ } else if (a.role === 'method-keeper') {
1515
+ await spawnMethodKeeper('plan')
1516
+ }
1517
+ } else if (a.action === 'interrupt') {
1518
+ await interruptChild(a.childId)
1519
+ logActivity('interrupt', 'plan: interrupted ' + a.childId + ' (' + a.reason + ')')
1520
+ } else if (a.action === 'promote') {
1521
+ const p = propos.get(a.target)
1522
+ if (p) { p.在问题清单 = true; await saveProposition(p); await promoteProposition(a.target); logActivity('promote', 'plan: proposition ' + a.target + ' promoted') }
1523
+ } else if (a.action === 'wait') {
1524
+ logActivity('plan', 'wait advisory: ' + a.target + ' (' + a.reason + ')')
1525
+ } else if (a.action === 'continue') {
1526
+ logActivity('plan', 'continue advisory: ' + (a.childId || a.target) + ' (in-flight continuation is code-driven)')
1527
+ } else if (a.action === 'stop') {
1528
+ logActivity('plan', 'stop advisory: ' + (a.reason || ''))
1529
+ }
1530
+ }
1531
+ // 晋升(供计划/回退共用;processPromote 已有单步逻辑,这里拆出可复用函数)
1532
+ async function promoteProposition(pId) {
1533
+ const p = propos.get(pId)
1534
+ if (!p) return
1535
+ const qid = 'q-promoted-' + String(p.id).replace(/[^a-z0-9\-]/gi, '').slice(-12)
1536
+ if (problems.has(qid)) return
1537
+ const sols = []
1538
+ for (let j = 0; j < (p.proofs || []).length; j++) { const it = p.proofs[j]; sols.push({ title: '【证明】' + (it.title || ('证明' + (j + 1))), prob: clamp01(it.prob != null ? it.prob : 0.5), status: '未定论', text: '【证明】' + (it.text || '') }) }
1539
+ for (let j = 0; j < (p.refutes || []).length; j++) { const it = p.refutes[j]; sols.push({ title: '【证伪】' + (it.title || ('证伪' + (j + 1))), prob: clamp01(it.prob != null ? it.prob : 0.5), status: '未定论', text: '【证伪】' + (it.text || '') }) }
1540
+ problems.set(qid, {
1541
+ id: qid, 标题: '判断命题:' + p.标题, 状态: '求解中', 优先级: 1, 依赖: [], 被依赖: [], 来源: 'promote',
1542
+ 计划: '证明或证伪源命题 ' + p.id + '(验证结果回写源命题)。', 陈述: '判断下述命题是否成立:' + p.陈述,
1543
+ 来源与动机: '由命题 ' + p.id + '(价值/关键性=' + p.价值关键性 + ')晋升,验证结果回写源命题。',
1544
+ solutions: sols, 判断命题: p.id, 来源命题: p.id,
1545
+ })
1546
+ await saveProblem(problems.get(qid))
1547
+ }
1548
+
1549
+ // ================= fallback heuristic (planner disabled/failed) =================
1550
+ async function fallbackScheduler() {
1551
+ // 1) promote one
1552
+ await processPromote()
1553
+ // 2) verify candidates
1554
+ if (scheduler.activeCount < params.maxParallelThreshold) {
1555
+ const cands = await buildVerifyCandidates()
1556
+ for (let i = 0; i < cands.length; i++) {
1557
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
1558
+ const c = cands[i]
1559
+ if (verifyTaskBusy(c.rId)) continue
1560
+ await createVerifyTask(c)
1561
+ return // one per tick keeps scheduling simple
1562
+ }
1563
+ }
1564
+ // 3) solve: explorer / solver spawns (manual → gate)
1565
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
1566
+ const unsolved = allProblems().filter(function (q) { return !(q.状态 === '已解决' || q.优先级 === 'never' || q.状态 === '等待依赖') }).sort(function (a, b) { return (a.优先级 === 'never' ? 999 : Number(a.优先级)) - (b.优先级 === 'never' ? 999 : Number(b.优先级)) })
1567
+ for (let i = 0; i < unsolved.length; i++) {
1568
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
1569
+ const q = unsolved[i]
1570
+ const busy = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.qid === q.id && (m.role === 'explorer' || m.role === 'solver') })
1571
+ if (busy) continue
1572
+ const dirs = getDirState(q.id)
1573
+ const allExhausted = dirs.length > 0 && dirs.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
1574
+ if (dirs.length === 0 || allExhausted) {
1575
+ const explorerCap = Number(params.maxExplorerRetries) || 3
1576
+ if ((explorerRetries[q.id] || 0) >= explorerCap) {
1577
+ if (dirs.length === 0) dirs.push({ id: 'd_' + shortId(), title: 'explorer 失败', method: '', core_assumption: '', feasibility: 0, status: 'dead-end', round: 0, survival: 0, routes: [], lessons: [], blockers: [], lemmas: [], journal: [], dead_end_reason: 'explorer 连续 ' + explorerCap + ' 次未产出方向' })
1578
+ await saveDirState(); await writeJournal(q.id)
1579
+ logActivity('explorer', 'problem ' + q.id + ' explorer exhausted (' + explorerCap + ' failed attempts)')
1580
+ continue
1581
+ }
1582
+ explorerRetries[q.id] = (explorerRetries[q.id] || 0) + 1
1583
+ const promptText = dirs.length > 0 ? rederivePrompt(q, dirs) : explorerPrompt(q)
1584
+ 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 } })
1585
+ if (r && r.gated) return
1586
+ continue
1587
+ }
1588
+ for (let j = 0; j < dirs.length; j++) {
1589
+ if (scheduler.activeCount >= params.maxParallelThreshold) break
1590
+ const dir = dirs[j]
1591
+ if (dir.status === 'success' || dir.status === 'dead-end') continue
1592
+ 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' })
1593
+ if (running) continue
1594
+ const progressText = buildSolverContext(dirs, dir, 1, params.directionsPerSolver)
1595
+ const promptText = solverPrompt(q, dir, 1, progressText)
1596
+ 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 } })
1597
+ if (r && r.gated) return
1598
+ }
1599
+ }
1600
+ }
1601
+
1602
+ // ================= solver / explorer handling =================
1603
+ async function handleExplorer(childId, meta, output) {
1604
+ delete agentRegistry[childId]
1605
+ const parsed = parseJson(output)
1606
+ // 新协议(代理直接写 md + sync_meta):__writes 落盘,meta.kind==='directions' 走元数据同步
1607
+ if (parsed && ((Array.isArray(parsed.__writes) && parsed.__writes.length) || (parsed.meta && parsed.meta.kind === 'directions'))) {
1608
+ await applyAgentWrites(parsed.__writes)
1609
+ if (parsed.meta && parsed.meta.kind === 'directions') await syncMeta(parsed.meta, { id: childId })
1610
+ await saveAll(); return
1611
+ }
1612
+ const dirs = (parsed && parsed.directions) || []
1613
+ await consumeMethodFeedback(parsed, { qid: meta.qid })
1614
+ if (dirs.length === 0) { logActivity('explorer', 'problem ' + meta.qid + ' returned no directions (output head: ' + String(output || '').slice(0, 200) + ')'); await saveAll(); return }
1615
+ explorerRetries[meta.qid] = 0
1616
+ const q = problems.get(meta.qid); if (!q) return
1617
+ const list = dirs.map(function (d) {
1618
+ 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: [], lessons: [], blockers: [], lemmas: [], journal: [], dead_end_reason: '' }
1619
+ })
1620
+ // 重派生替换方向前:把旧方向的 journal 归档到日志(论文式历史保留)
1621
+ const oldDirs = dirState.get(meta.qid)
1622
+ if (oldDirs && oldDirs.length > 0) await archiveDirections(meta.qid, oldDirs)
1623
+ dirState.set(meta.qid, list)
1624
+ await saveDirState(); await writeJournal(meta.qid)
1625
+ logActivity('explorer', 'problem ' + meta.qid + ' → ' + list.length + ' directions')
1626
+ }
1627
+ async function handleSolver(childId, meta, output, stopReason) {
1628
+ const qid = meta.qid; const dirId = meta.direction
1629
+ const parsed = parseJson(output)
1630
+ // 新协议(代理直接写 md + sync_meta):__writes 落盘,meta.kind==='solver' 走元数据同步
1631
+ if (parsed && ((Array.isArray(parsed.__writes) && parsed.__writes.length) || (parsed.meta && parsed.meta.kind === 'solver'))) {
1632
+ delete agentRegistry[childId]
1633
+ await applyAgentWrites(parsed.__writes)
1634
+ if (parsed.meta && parsed.meta.kind === 'solver') await syncMeta(parsed.meta, { id: childId })
1635
+ await saveAll(); return
1636
+ }
1637
+ const q = problems.get(qid); if (!q) { delete agentRegistry[childId]; return }
1638
+ const dirs = getDirState(qid)
1639
+ const dir = dirs.find(function (d) { return d.id === dirId })
1640
+ if (!dir) { delete agentRegistry[childId]; return }
1641
+ if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return }
1642
+ const status = (parsed && parsed.status) || statusFromStop(stopReason)
1643
+ dir.round = meta.round
1644
+ if (parsed) {
1645
+ if (parsed.routes) dir.routes = (dir.routes || []).concat(parsed.routes)
1646
+ if (parsed.lessons) dir.lessons = (dir.lessons || []).concat(parsed.lessons)
1647
+ if (parsed.blockers) dir.blockers = (dir.blockers || []).concat(parsed.blockers)
1648
+ if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
1649
+ if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
1650
+ 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 || '' }) } } }
1651
+ 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) } } }
1652
+ // journal narrative (论文式续写:把本轮叙述追加进研究日志)
1653
+ const prose = []
1654
+ if (parsed.routes && parsed.routes.length) prose.push('**本轮子路线**:' + parsed.routes.map(function (r) { return r.title + '(' + (r.progress || '') + ';可行性信号:' + (r.feasibility_signal || '—') + (r.blocker ? ';阻碍:' + r.blocker : '') + ')' }).join(';'))
1655
+ if (parsed.lessons && parsed.lessons.length) prose.push('**教训**:' + parsed.lessons.join(';'))
1656
+ if (parsed.lemmas && parsed.lemmas.length) prose.push('**新引理**:' + parsed.lemmas.map(function (l) { return l.title + ':' + (l.statement || '') + '(证明:' + (l.proof || '') + ')' }).join(';'))
1657
+ if (parsed.dead_end_reason) prose.push('**死路原因**:' + parsed.dead_end_reason)
1658
+ if (parsed.solution) prose.push('**完整解法**:' + parsed.solution)
1659
+ if (prose.length) { dir.journal = dir.journal || []; dir.journal.push({ round: meta.round, at: fmtTime(), agent: 'solver:' + qid + ':' + dirId, prose: prose.join('\n') }) }
1660
+ await consumeMethodFeedback(parsed, { qid: qid, dirId: dirId })
1661
+ }
1662
+ if (status === 'success') {
1663
+ if (parsed && parsed.solution) {
1664
+ dir.status = 'success'
1665
+ delete agentRegistry[childId]
1666
+ logActivity('solver', qid + '/' + dirId + ' success at round ' + meta.round)
1667
+ await addSolution(qid, parsed.solution, parsed.solution_probability)
1668
+ } else {
1669
+ if (meta.round >= params.solverMaxRounds) {
1670
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || 'claimed success without solution at iteration cap'
1671
+ delete agentRegistry[childId]
1672
+ logActivity('solver', qid + '/' + dirId + ' dead-end (success without solution)')
1673
+ } else {
1674
+ const progressText = buildSolverContext(dirs, dir, meta.round + 1, params.directionsPerSolver)
1675
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
1676
+ agentRegistry[childId].round = meta.round + 1
1677
+ dir.round = meta.round + 1
1678
+ }
1679
+ }
1680
+ } else if (status === 'dead-end' || meta.round >= params.solverMaxRounds) {
1681
+ dir.status = 'dead-end'
1682
+ if (!dir.dead_end_reason) dir.dead_end_reason = (status === 'dead-end' && !parsed) ? 'solver ended abnormally (' + stopReason + ')' : 'iteration cap reached'
1683
+ delete agentRegistry[childId]
1684
+ logActivity('solver', qid + '/' + dirId + ' dead-end: ' + dir.dead_end_reason)
1685
+ } else {
1686
+ const progressText = buildSolverContext(dirs, dir, meta.round + 1, params.directionsPerSolver)
1687
+ if (!scheduler.running) {
1688
+ delete agentRegistry[childId]
1689
+ } else {
1690
+ try {
1691
+ await followupChild(childId, solverPrompt(q, dir, meta.round + 1, progressText))
1692
+ agentRegistry[childId].round = meta.round + 1
1693
+ dir.round = meta.round + 1
1694
+ } catch (e) {
1695
+ console.error('vibe-math-v3: solver followup failed: ' + String((e && e.message) || e))
1696
+ dir.status = 'dead-end'; dir.dead_end_reason = dir.dead_end_reason || '求解器续轮失败(followup 异常)'
1697
+ delete agentRegistry[childId]
1698
+ }
1699
+ }
1700
+ }
1701
+ await saveDirState(); await writeJournal(qid)
1702
+ }
1703
+ function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
1704
+ async function addLemmaAsProposition(qid, lemma) {
1705
+ if (!lemma || !lemma.title) return
1706
+ let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
1707
+ if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01
1708
+ const p = {
1709
+ id: 'p-' + shortId(), 标题: lemma.title, 状态: '未定论', 概率: be,
1710
+ 优先级: (lemma.优先级 != null) ? lemma.优先级 : 1, 依赖: [], 价值关键性: clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
1711
+ 分类: categoryOf({ 分类: Object.keys(lemma.细类型 || { 未分类: {} })[0] || '未分类' }),
1712
+ 陈述: lemma.statement || lemma.title,
1713
+ proofs: [{ title: lemma.title + '(证明)', prob: clamp01(0.7), status: '未定论', text: lemma.proof || '' }],
1714
+ refutes: [], 来源问题: qid, 在问题清单: false,
1715
+ }
1716
+ propos.set(p.id, p)
1717
+ await saveProposition(p)
1718
+ logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
1719
+ return p.id
1720
+ }
1721
+ // 点5 严格化:solver 报告子问题 q_sub 时注册三个对象(q_sub / 判断问题 / p-tmp 假设)
1722
+ async function addSubQuestion(qid, dirId, sq) {
1723
+ if (!sq || !sq.q_sub_statement) return undefined
1724
+ const dirs = getDirState(qid)
1725
+ const d = dirs.find(function (x) { return x.id === dirId })
1726
+ 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 }
1727
+ const subId = qid + '-sub-' + shortId()
1728
+ const assumeId = 'p-tmp-' + shortId()
1729
+ const judgeId = qid + '-judge-' + shortId()
1730
+ const assumeStatement = sq.assumption_statement || sq.assumption_title || ('对子问题「' + (sq.q_sub_title || sq.q_sub_statement) + '」的一种回答(临时假设)')
1731
+ const parentQ = problems.get(qid)
1732
+ const parentTitle = parentQ ? parentQ.标题 : qid
1733
+ problems.set(subId, {
1734
+ id: subId, 标题: (sq.q_sub_title || '子问题') + '(' + parentTitle + ' 分支)', 状态: '求解中', 优先级: 1, 依赖: [], 被依赖: [], 来源: '后生',
1735
+ 计划: '独立求解后回填主线 ' + qid + ' 方向 ' + dirId + ';若 p_{q-tmp}(' + assumeId + ')被证伪,需重新审视依赖它的主线结论。',
1736
+ 陈述: sq.q_sub_statement,
1737
+ 来源与动机: '由问题 ' + qid + '(' + parentTitle + ')方向 ' + dirId + ' 在求解第 ' + ((d && d.round) || 1) + ' 轮分支产生;动机:想利用该子问题的结果来推进主线问题的求解;拟在解决后将结果回填到 ' + qid + ' 的 ' + dirId + ' 方向。',
1738
+ solutions: [], 判断命题: '', 来源命题: '',
1739
+ })
1740
+ problems.set(judgeId, {
1741
+ id: judgeId, 标题: '判断命题:' + (sq.assumption_title || assumeId), 状态: '求解中', 优先级: 1, 依赖: [], 被依赖: [], 来源: '后生',
1742
+ 计划: '判定 p_{q-tmp}(' + assumeId + ')是否成立;结果回写该命题。',
1743
+ 陈述: '判断下述命题是否成立:' + assumeStatement,
1744
+ 来源与动机: '由临时假设 p_{q-tmp}(' + assumeId + ')生成,它是对子问题 ' + subId + ' 的一种回答的命题化;判定结果回写命题 ' + assumeId + '。',
1745
+ solutions: [], 判断命题: assumeId, 来源命题: '',
1746
+ })
1747
+ const p = {
1748
+ id: assumeId, 标题: sq.assumption_title || '临时假设 ' + assumeId, 状态: '未定论', 概率: 0.5, 优先级: 1, 依赖: [],
1749
+ 价值关键性: 0.5, 分类: '未分类', 陈述: assumeStatement, proofs: [], refutes: [],
1750
+ 来源问题: qid, 来源方向: dirId, 在问题清单: false,
1751
+ }
1752
+ propos.set(p.id, p)
1753
+ for (const id of [subId, judgeId]) await saveProblem(problems.get(id))
1754
+ await saveProposition(p)
1755
+ logActivity('subquestion', qid + ' → q_sub ' + subId + ' + 判断问题 ' + judgeId + ' + 临时假设 ' + assumeId)
1756
+ return { subId: subId, judgeId: judgeId, assumeId: assumeId, statement: sq.q_sub_statement }
1757
+ }
1758
+ async function addSolution(qid, solutionText, prob) {
1759
+ const q = problems.get(qid); if (!q) return
1760
+ const p = clamp01(prob != null ? prob : 0.8)
1761
+ const finalProb = p >= 1 ? 0.99 : (p <= 0 ? 0.01 : p)
1762
+ q.solutions = q.solutions || []
1763
+ q.solutions.push({ title: '解法 ' + (q.solutions.length + 1), prob: finalProb, status: '未定论', text: String(solutionText) })
1764
+ await saveProblem(q)
1765
+ logActivity('solution', 'problem ' + qid + ' got a candidate solution (probability ' + finalProb + ', awaiting verification)')
1766
+ }
1767
+
1768
+ // ================= method library (需求 2) =================
1769
+ // ctx = { qid, dirId }:应用记录带上"用在哪"(问题/方向),方法卡的可追溯性更好
1770
+ async function consumeMethodFeedback(parsed, ctx) {
1771
+ if (!parsed) return
1772
+ ctx = ctx || {}
1773
+ if (Array.isArray(parsed.methods_used)) {
1774
+ for (const mu of parsed.methods_used) {
1775
+ if (!mu || !mu.id) continue
1776
+ const m = methods.get(mu.id) || globalMethods.get(mu.id)
1777
+ if (!m) { logActivity('method', 'methods_used referenced unknown method ' + mu.id); continue }
1778
+ if (methods.has(mu.id)) {
1779
+ m.applications = m.applications || []
1780
+ m.applications.push({ at: fmtTime(), 问题: ctx.qid || '', 方向: ctx.dirId || '', text: (mu.效果 || '') + (mu.建议 ? ';建议:' + mu.建议 : '') })
1781
+ await saveMethod(m, false)
1782
+ logActivity('method', 'application record appended to ' + mu.id + (ctx.qid ? ' (问题 ' + ctx.qid + (ctx.dirId ? ' 方向 ' + ctx.dirId : '') + ')' : ''))
1783
+ }
1784
+ }
1785
+ }
1786
+ if (Array.isArray(parsed.new_inventions)) {
1787
+ for (const inv of parsed.new_inventions) {
1788
+ if (!inv || !inv.标题) continue
1789
+ methodLog.pendingInventions.push(Object.assign({ at: now(), 来源: parsed.__source || (ctx.qid ? ('问题 ' + ctx.qid + (ctx.dirId ? ' 方向 ' + ctx.dirId : '')) : '') }, inv))
1790
+ logActivity('method', 'new invention queued: ' + inv.类型 + '「' + inv.标题 + '」')
1791
+ }
1792
+ }
1793
+ }
1794
+ function methodKeepDue() {
1795
+ if (Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'method-keeper' })) return false
1796
+ if (methodLog.pendingInventions.length === 0) return false
1797
+ const interval = Number(params.methodKeepIntervalMs) || 0
1798
+ if (interval > 0 && (now() - methodLog.lastKeepAt) >= interval) return true
1799
+ const every = Number(params.methodKeepEvery) || 0
1800
+ if (every > 0 && methodLog.pendingInventions.length >= every) return true
1801
+ return false
1802
+ }
1803
+ async function maybeMethodKeepFallback() {
1804
+ if (!methodKeepDue()) return
1805
+ if (scheduler.activeCount >= params.maxParallelThreshold) return
1806
+ await spawnMethodKeeper('fallback')
1807
+ }
1808
+ async function spawnMethodKeeper(why) {
1809
+ const digest = await buildMethodDigest()
1810
+ const promptText = methodKeeperPrompt(digest)
1811
+ await spawnChild('method-keeper', promptText, { role: 'method-keeper', why: why })
1812
+ logActivity('method', 'method keeper spawned (' + why + '), pending inventions: ' + methodLog.pendingInventions.length)
1813
+ }
1814
+ async function buildMethodDigest() {
1815
+ // 精简摘要:只给标题/类型/来源 + 一句摘要,避免 pending 全文压垮 Method Keeper
1816
+ const lines = []
1817
+ lines.push('- 待沉淀发明 ' + methodLog.pendingInventions.length + ' 条(仅列标题/类型/来源):')
1818
+ for (const inv of methodLog.pendingInventions.slice(-16)) {
1819
+ const desc = String(inv.内容描述 || '').slice(0, 60)
1820
+ lines.push(' * [' + (inv.类型 || '') + '] ' + (inv.标题 || '') + (inv.来源 ? '(' + inv.来源 + ')' : '') + (desc ? ':' + desc + '…' : ''))
1821
+ }
1822
+ const recentProps = allPropos().slice(-6).map(function (p) { return p.id + '「' + p.标题 + '」概率=' + p.概率 })
1823
+ if (recentProps.length) lines.push('- 最近命题:' + recentProps.join(';'))
1824
+ const recentDirs = []
1825
+ for (const [qid, dirs] of dirState) {
1826
+ for (const d of dirs.slice(-1)) if (d.lessons && d.lessons.length) recentDirs.push(qid + '/' + d.id + '「' + d.title + '」教训摘要:' + d.lessons.join(';').slice(0, 80))
1827
+ }
1828
+ if (recentDirs.length) { lines.push('- 最近方向教训(摘要):'); lines.push.apply(lines, recentDirs) }
1829
+ return lines.join('\n')
1830
+ }
1831
+ async function handleMethodKeeper(childId, meta, output) {
1832
+ delete agentRegistry[childId]
1833
+ const parsed = parseJson(output)
1834
+ // 新协议(Method Keeper 直接写方法卡 md + sync_meta):__writes 落盘,meta.kind==='methods' 登记
1835
+ if (parsed && ((Array.isArray(parsed.__writes) && parsed.__writes.length) || (parsed.meta && parsed.meta.kind === 'methods'))) {
1836
+ await applyAgentWrites(parsed.__writes)
1837
+ if (parsed.meta && parsed.meta.kind === 'methods') await syncMeta(parsed.meta, { id: childId })
1838
+ if (parsed.meta && Array.isArray(parsed.meta.created) && parsed.meta.created.length > 0) methodLog.pendingInventions = []
1839
+ await saveAll(); return
1840
+ }
1841
+ if (!parsed) { logActivity('method', 'method keeper returned nothing usable'); await saveAll(); return }
1842
+ let created = 0
1843
+ if (Array.isArray(parsed.new_methods)) {
1844
+ for (const nm of parsed.new_methods) {
1845
+ if (!nm || !nm.标题) continue
1846
+ const id = 'm-' + shortId()
1847
+ const m = {
1848
+ id: id, 标题: nm.标题, 类型: nm.类型 || '方法', 状态: '经验',
1849
+ 可信断言: Array.isArray(nm.可信断言) ? nm.可信断言.filter(function (x) { return propos.get(x) && (propos.get(x).概率 === 1 || propos.get(x).概率 === 0) }) : [],
1850
+ 上级体系: Array.isArray(nm.上级体系) ? nm.上级体系 : [], 子方法: Array.isArray(nm.子方法) ? nm.子方法 : [], 相关: [],
1851
+ 适用场景: nm.适用场景 || '', 核心内容: nm.核心内容 || '', 定义与记号: nm.定义与记号 || '',
1852
+ applications: [], improvements: [{ v: 1, 原因: '初始沉淀', text: '由 Method Keeper 从近期工作提炼' }], 来源: nm.来源 || '',
1853
+ }
1854
+ methods.set(id, m)
1855
+ await saveMethod(m, false)
1856
+ created += 1
1857
+ logActivity('method', 'new method card ' + id + '「' + nm.标题 + '」(' + m.类型 + ') created')
1858
+ }
1859
+ }
1860
+ if (Array.isArray(parsed.improvements)) {
1861
+ for (const imp of parsed.improvements) {
1862
+ if (!imp || !imp.id) continue
1863
+ const m = methods.get(imp.id)
1864
+ if (!m) { logActivity('method', 'improvement referenced unknown method ' + imp.id); continue }
1865
+ m.improvements = m.improvements || []
1866
+ m.improvements.push({ v: m.improvements.length + 1, 原因: imp.原因 || '', text: imp.改进内容 || '' })
1867
+ await saveMethod(m, false)
1868
+ logActivity('method', 'method ' + imp.id + ' improved (v' + m.improvements.length + ')')
1869
+ }
1870
+ }
1871
+ // 消费已沉淀的发明:Method Keeper 有有效输出(新建或改进)即视为已处理本轮 pending。
1872
+ // 修复:仅返回 improvements 而无可新建方法时也必须清空,否则 pending 永不归零导致反复整理。
1873
+ const hasOutput = (Array.isArray(parsed.new_methods) && parsed.new_methods.length > 0) || (Array.isArray(parsed.improvements) && parsed.improvements.length > 0)
1874
+ // 兜底建卡:Method Keeper 未产出(输出不合规/空对象/模型未能蒸馏)时,把 pending 发明直接建成草稿方法卡,
1875
+ // 保证"发明不因一次梳理失败而永久滞留";草稿卡状态=经验、来源=草稿沉淀,后续可被 Method Keeper 再整理。
1876
+ let fallback = 0
1877
+ if (!hasOutput && methodLog.pendingInventions.length > 0) {
1878
+ for (const inv of methodLog.pendingInventions.slice(-20)) {
1879
+ if (!inv || !inv.标题) continue
1880
+ const id = 'm-' + shortId()
1881
+ const m = {
1882
+ id: id, 标题: String(inv.标题), 类型: inv.类型 || '方法', 状态: '经验',
1883
+ 可信断言: [], 上级体系: [], 子方法: [], 相关: [],
1884
+ 适用场景: '', 核心内容: String(inv.内容描述 || ''), 定义与记号: '',
1885
+ applications: [], improvements: [{ v: 1, 原因: '草稿沉淀', text: 'Method Keeper 未产出,按发明清单兜底建卡(来源:' + (inv.来源 || '') + ')' }], 来源: '草稿沉淀(' + (inv.来源 || '') + ')',
1886
+ }
1887
+ if (!methods.has(id)) { methods.set(id, m); await saveMethod(m, false); fallback += 1 }
1888
+ }
1889
+ if (fallback > 0) logActivity('method', 'method keeper 未产出,兜底建 ' + fallback + ' 张草稿方法卡(防发明滞留)')
1890
+ }
1891
+ if (hasOutput || fallback > 0) methodLog.pendingInventions = []
1892
+ methodLog.keepCount += 1
1893
+ methodLog.lastKeepAt = now()
1894
+ await saveAll()
1895
+ logActivity('method', 'method keeper round done: ' + created + ' new, ' + (parsed.improvements || []).length + ' improved')
1896
+ }
1897
+ async function promoteMethodToGlobal(m) {
1898
+ const gm = Object.assign({}, m, { 来源: (m.来源 || '') + (m.来源 ? ';' : '') + 'promoted from project ' + currentProject })
1899
+ globalMethods.set(m.id, gm)
1900
+ await saveMethod(gm, true)
1901
+ m.状态 = m.状态 || '经验'
1902
+ await saveMethod(m, false)
1903
+ }
1904
+ async function maybePromoteMethods() {
1905
+ // 方法晋升:应用记录 ≥ 3 且尚未入全局库时触发。
1906
+ // methodAutoPromote=true → 自动晋升;否则 manual 模式下挂「方法晋升门」(method-promote 决策)。
1907
+ for (const m of methods.values()) {
1908
+ if ((m.applications || []).length < 3 || globalMethods.has(m.id)) continue
1909
+ if (params.methodAutoPromote) {
1910
+ await promoteMethodToGlobal(m)
1911
+ logActivity('method', 'method ' + m.id + ' auto-promoted to global library (3+ applications)')
1912
+ } else if (params.mode === 'manual') {
1913
+ const alreadyPending = decisionQueue.some(function (d) { return d.status === 'pending' && d.node === 'method-promote' && d.data.methodId === m.id })
1914
+ if (alreadyPending) continue
1915
+ const d = enqueueDecision('method-promote', '方法 ' + m.id + '「' + m.标题 + '」已有 ' + (m.applications || []).length + ' 次应用,是否晋升到全局方法库(VibeMath/Methods/)供跨项目复用?', { methodId: m.id })
1916
+ if (!scheduler.gate) scheduler.gate = { decisionId: d.id, node: 'method-promote' }
1917
+ logActivity('method', 'method-promote gate: ' + m.id + ' awaiting user decision')
1918
+ await saveAll()
1919
+ }
1920
+ }
1921
+ }
1922
+
1923
+ // ================= verification (验证器) =================
1924
+ 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 }) }
1925
+ 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') }
1926
+ async function handleVerifier(childId, meta, output, stopReason) {
1927
+ const rId = meta.rId
1928
+ const parsed = parseJson(output)
1929
+ const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
1930
+ const Reason = (parsed && parsed.Reason) || ''
1931
+ let t = tasks['verify:' + rId]
1932
+ 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 }
1933
+ if (!parsed && !scheduler.running) {
1934
+ delete agentRegistry[childId]
1935
+ const ix = t.children.indexOf(childId); if (ix !== -1) t.children.splice(ix, 1)
1936
+ delete t.childResults[childId]
1937
+ if (t.children.length === 0 && t.id && tasks[t.id]) delete tasks[t.id]
1938
+ return
1939
+ }
1940
+ if (t.children.indexOf(childId) === -1) t.children.push(childId)
1941
+ t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
1942
+ delete agentRegistry[childId]
1943
+ const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
1944
+ if (!allReported) { await saveAll(); return }
1945
+ await advanceVerification(t, meta.round)
1946
+ await saveAll()
1947
+ }
1948
+ async function advanceVerification(t, round) {
1949
+ if (round < params.debateMaxRounds && !consensus(t) && t.children.length > 0) {
1950
+ if (!scheduler.running) { t.status = 'paused'; return }
1951
+ if (scheduler.activeCount >= params.maxParallelThreshold) { t.status = 'paused'; return }
1952
+ t.round = round + 1
1953
+ const roundTranscript = buildTranscript(t)
1954
+ t.history = t.history || []
1955
+ t.history.push('Round ' + round + ':\n' + roundTranscript)
1956
+ const transcript = t.history.join('\n\n')
1957
+ const nextChildren = []
1958
+ for (let i = 0; i < t.children.length; i++) {
1959
+ const cid = t.children[i]
1960
+ try {
1961
+ await followupChild(cid, verifierDebatePrompt(t.r, transcript))
1962
+ agentRegistry[cid] = { role: 'verifier', rId: t.rId, round: round + 1, index: i }
1963
+ nextChildren.push(cid)
1964
+ } catch (e) {
1965
+ console.error('vibe-math-v3: verifier followup failed: ' + String((e && e.message) || e))
1966
+ delete agentRegistry[cid]
1967
+ delete t.childResults[cid]
1968
+ }
1969
+ }
1970
+ t.children = nextChildren
1971
+ if (nextChildren.length === 0) await finalizeVerification(t)
1972
+ } else {
1973
+ await finalizeVerification(t)
1974
+ }
1975
+ }
1976
+ async function finalizeVerification(t) {
1977
+ const verdict = finalVerdict(t)
1978
+ if (params.mode === 'manual') {
1979
+ const d = enqueueDecision('verdict', 'verdict for ' + t.rId + ' (debate finished) = ' + verdict, { rId: t.rId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
1980
+ scheduler.gate = { decisionId: d.id, node: 'verdict' }
1981
+ t.status = 'awaiting-verdict'
1982
+ } else {
1983
+ await settleVerdict(t, verdict)
1984
+ delete tasks[t.id]
1985
+ }
1986
+ }
1987
+ // 近共识 + forced/flat 裁决(修复 v2 flat 高置信分歧误判:全部同侧且均值≥0.85/≤0.15 取均值)
1988
+ function finalVerdict(t) {
1989
+ const rs = Object.keys(t.childResults).map(function (cid) { return t.childResults[cid] })
1990
+ if (rs.length === 0) return 0.5
1991
+ if (rs.every(function (r) { return r.Result === 1 })) return 1
1992
+ if (rs.every(function (r) { return r.Result === 0 })) return 0
1993
+ // 近共识:全部结果在同一侧(全 ≥0.5 或全 ≤0.5)且均值达到阈值 → 取均值
1994
+ const allHigh = rs.every(function (r) { return r.Result >= 0.5 })
1995
+ const allLow = rs.every(function (r) { return r.Result <= 0.5 })
1996
+ if (allHigh || allLow) {
1997
+ let sum = 0; for (let i = 0; i < rs.length; i++) sum += rs[i].Result
1998
+ const mean = sum / rs.length
1999
+ if (mean >= 0.85 || mean <= 0.15) return Math.max(0.01, Math.min(0.99, mean))
2000
+ }
2001
+ if (params.verdictMode === 'forced') {
2002
+ let num = 0; let den = 0
2003
+ const cids = Object.keys(t.childResults)
2004
+ for (let i = 0; i < rs.length; i++) {
2005
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
2006
+ const accRate = acc.total > 0 ? (acc.correct / acc.total) : 0.5
2007
+ const confident = (rs[i].Result === 1 || rs[i].Result === 0) ? 0.1 : 0
2008
+ const w = Math.max(0.05, Math.min(0.95, accRate + confident))
2009
+ num += w * rs[i].Result; den += w
2010
+ }
2011
+ return den > 0 ? Math.max(0.01, Math.min(0.99, num / den)) : 0.5
2012
+ }
2013
+ return 0.5
2014
+ }
2015
+ async function settleVerdict(t, verdict) {
2016
+ const v = clamp01(verdict)
2017
+ const r = t.r
2018
+ const cids = Object.keys(t.childResults)
2019
+ for (let i = 0; i < cids.length; i++) {
2020
+ const acc = verifierAccuracy[cids[i]] || { correct: 0, total: 0 }
2021
+ acc.total += 1
2022
+ if (t.childResults[cids[i]].Result === v) acc.correct += 1
2023
+ verifierAccuracy[cids[i]] = acc
2024
+ }
2025
+ await writeJson('Logs/Verification/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
2026
+ if (r.kind === 'proposition') {
2027
+ const p = propos.get(r.pId)
2028
+ if (p) {
2029
+ p.概率 = v
2030
+ if (v === 1) { p.状态 = '已验证·真'; p.proofs = p.proofs || []; p.proofs.push({ title: '验证证明', prob: 1, status: '已验', text: strongestReason(t, 1) || '' }); p.优先级 = 'never' }
2031
+ else if (v === 0) { p.状态 = '已验证·假'; p.refutes = p.refutes || []; p.refutes.push({ title: '验证证伪', prob: 1, status: '已验', text: strongestReason(t, 0) || '' }); p.优先级 = 'never' }
2032
+ else {
2033
+ p.状态 = '未定论'
2034
+ p.proofs = p.proofs || []; p.refutes = p.refutes || []
2035
+ p.proofs.push({ title: '辩论支持论证', prob: v, status: '已验', text: strongestReason(t, 1) || '根据辩论得到的支持性论证' })
2036
+ p.refutes.push({ title: '辩论反驳论证', prob: 1 - v, status: '已验', text: strongestReason(t, 0) || '根据辩论得到的反驳性论证' })
2037
+ }
2038
+ await saveProposition(p)
2039
+ if (p.概率 === 1 || p.概率 === 0) await writeVerifiedPropositionCardIfNeeded(p)
2040
+ }
2041
+ } else if (r.kind === 'prop-proof') {
2042
+ const p = propos.get(r.pId)
2043
+ if (p) {
2044
+ const list = r.side === '证明' ? (p.proofs = p.proofs || []) : (p.refutes = p.refutes || [])
2045
+ const item = list[r.idx]
2046
+ if (item) {
2047
+ item.prob = v
2048
+ item.status = '已验'
2049
+ if (v === 1) { item.title = item.title || (r.side + '(已验证)') }
2050
+ else if (v === 0) {
2051
+ const other = r.side === '证明' ? (p.refutes = p.refutes || []) : (p.proofs = p.proofs || [])
2052
+ other.push({ title: (r.side === '证明' ? '证伪' : '证明') + '(反证)', prob: 1, status: '已验', text: strongestReason(t, 0) || '' })
2053
+ } else {
2054
+ const other = r.side === '证明' ? (p.refutes = p.refutes || []) : (p.proofs = p.proofs || [])
2055
+ other.push({ title: (r.side === '证明' ? '证伪' : '证明') + '(辩论论证)', prob: 1 - v, status: '已验', text: strongestReason(t, v >= 0.5 ? 0 : 1) || '辩论得出的相反方向论证' })
2056
+ item.title = item.title || ''
2057
+ }
2058
+ }
2059
+ await saveProposition(p)
2060
+ if (p.概率 === 1 || p.概率 === 0) await writeVerifiedPropositionCardIfNeeded(p)
2061
+ }
2062
+ } else if (r.kind === 'problem-solution') {
2063
+ const q = problems.get(r.qid)
2064
+ if (q) {
2065
+ const sol = (q.solutions || [])[r.idx]
2066
+ if (sol) {
2067
+ sol.prob = v
2068
+ sol.status = '已验'
2069
+ // 点3 回写联动:晋升问题的解法验证结果同步回源命题
2070
+ if (q.来源命题 && propos.has(q.来源命题)) {
2071
+ const sp = propos.get(q.来源命题)
2072
+ const isProof = String(sol.title || '').indexOf('【证明】') === 0
2073
+ const list = isProof ? (sp.proofs = sp.proofs || []) : (sp.refutes = sp.refutes || [])
2074
+ // 内容比对定位源条目(sol.text 去掉【证明/证伪】前缀),避免同概率条目错配
2075
+ const solText = String(sol.text || '').replace(/^【(证明|证伪)】/, '').trim()
2076
+ const srcIdx = list.findIndex(function (x) { return x.status === '未定论' && String(x.text || '').trim() === solText })
2077
+ if (srcIdx !== -1) { list[srcIdx].prob = v; list[srcIdx].status = '已验' }
2078
+ else list.push({ title: '晋升验证回写', prob: v, status: '已验', text: strongestReason(t, v >= 0.5 ? 1 : 0) || sol.text })
2079
+ await saveProposition(sp)
2080
+ if (sp.概率 === 1 || sp.概率 === 0) await writeVerifiedPropositionCardIfNeeded(sp)
2081
+ }
2082
+ // 判断问题联动:「判断下述命题是否成立:X」的解法验证 → X 命题收口
2083
+ if (q.判断命题 && propos.has(q.判断命题)) {
2084
+ const ap = propos.get(q.判断命题)
2085
+ ap.概率 = v
2086
+ if (v === 1) { ap.状态 = '已验证·真'; ap.proofs = ap.proofs || []; ap.proofs.push({ title: '判断问题验证通过', prob: 1, status: '已验', text: strongestReason(t, 1) || '经判断问题解法验证' }); ap.优先级 = 'never' }
2087
+ else if (v === 0) { ap.状态 = '已验证·假'; ap.refutes = ap.refutes || []; ap.refutes.push({ title: '判断问题判定不成立', prob: 1, status: '已验', text: strongestReason(t, 0) || '经判断问题解法验证' }); ap.优先级 = 'never' }
2088
+ await saveProposition(ap)
2089
+ if (ap.概率 === 1 || ap.概率 === 0) await writeVerifiedPropositionCardIfNeeded(ap)
2090
+ }
2091
+ if (v === 1) { q.状态 = '已解决'; q.优先级 = 'never'; await writeVerifiedProblemCardIfNeeded(q) }
2092
+ }
2093
+ await saveProblem(q)
2094
+ }
2095
+ }
2096
+ logActivity('verdict', t.rId + ' = ' + v + (v === 1 ? ' (fully verified)' : v === 0 ? ' (refuted)' : ' (uncertain)'))
2097
+ }
2098
+ function strongestReason(t, wantTrue) {
2099
+ const cids = Object.keys(t.childResults)
2100
+ let best = ''; let bestDist = -1
2101
+ for (let i = 0; i < cids.length; i++) {
2102
+ const res = t.childResults[cids[i]]
2103
+ const dist = wantTrue ? res.Result : 1 - res.Result
2104
+ if (dist > bestDist && res.Reason) { bestDist = dist; best = res.Reason }
2105
+ }
2106
+ return best
2107
+ }
2108
+
2109
+ // ================= child result dispatch =================
2110
+ async function onChildEnd(info) {
2111
+ // 子代理结束/中断:自动释放它持有的所有写锁(防锁残留导致文件被永久锁住)
2112
+ const endedId = String(info.id)
2113
+ for (const k of Object.keys(fileOwner)) { if (String(fileOwner[k].childId) === endedId) delete fileOwner[k] }
2114
+ const meta = agentRegistry[info.id]
2115
+ if (meta === undefined) return
2116
+ scheduler.activeCount = Math.max(0, scheduler.activeCount - 1)
2117
+ const output = blocksToText(info.lastAssistantMessage)
2118
+ try {
2119
+ if (meta.role === 'explorer') await handleExplorer(info.id, meta, output)
2120
+ else if (meta.role === 'solver') await handleSolver(info.id, meta, output, info.stopReason)
2121
+ else if (meta.role === 'verifier') await handleVerifier(info.id, meta, output, info.stopReason)
2122
+ else if (meta.role === 'planner') await handlePlanner(info.id, meta, output)
2123
+ else if (meta.role === 'method-keeper') await handleMethodKeeper(info.id, meta, output)
2124
+ } catch (e) { console.error('vibe-math-v3 onChildEnd error: ' + String((e && e.stack) || e)) }
2125
+ await saveAll()
2126
+ await maybePromoteMethods()
2127
+ scheduleTick()
2128
+ }
2129
+
2130
+ // ================= init / control =================
2131
+ async function init(fresh) {
2132
+ if (!rootAgent) return { ok: false, message: 'no root agent available' }
2133
+ currentProject = await readCurrentProject(); await ensureDirs()
2134
+ params = Object.assign({}, DEFAULT_PARAMS); await loadSettings(); await migrateLegacyParams(); await loadState(); await loadKnowledgeBase()
2135
+ const prevEpoch = await readJson('State/process_epoch.json')
2136
+ const stale = typeof prevEpoch === 'string' && prevEpoch !== processEpoch
2137
+ if (fresh || stale) {
2138
+ if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]) }
2139
+ if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
2140
+ logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
2141
+ agentRegistry = {}; tasks = {}
2142
+ }
2143
+ scheduler.activeCount = 0
2144
+ }
2145
+ await writeJson('State/process_epoch.json', processEpoch)
2146
+ await saveAll()
2147
+ if (params.indexAutoRebuild) await rebuildIndex()
2148
+ return { ok: true }
2149
+ }
2150
+ async function acquireProjectLock() {
2151
+ const timeout = Number(params.projectLockTimeoutMs) || 60000
2152
+ if (projectLock.sessionId && projectLock.sessionId !== sessionId && (now() - projectLock.at) < timeout) {
2153
+ return { ok: false, message: '项目 "' + currentProject + '" 正被会话 ' + projectLock.sessionId + ' 占用(锁超时 ' + timeout + 'ms)' }
2154
+ }
2155
+ projectLock = { sessionId: sessionId, at: now() }
2156
+ await saveAll()
2157
+ return { ok: true }
2158
+ }
2159
+ async function releaseProjectLock() {
2160
+ if (projectLock.sessionId === sessionId) { projectLock = { sessionId: '', at: 0 }; await saveAll() }
2161
+ }
2162
+ async function startScheduler() { const r = await init(true); if (!r.ok) return r; const lock = await acquireProjectLock(); if (!lock.ok) return lock; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject + '(v3:md 知识库 + 规划代理调度 + 方法库)'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
2163
+ async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; const lock = await acquireProjectLock(); if (!lock.ok) return lock; 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() } }
2164
+ async function pauseScheduler() { scheduler.running = false; await releaseProjectLock(); logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
2165
+ 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; planQueue = []; await releaseProjectLock(); logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
2166
+ async function autoResolvePending() {
2167
+ const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
2168
+ for (let i = 0; i < pending.length; i++) {
2169
+ const d = pending[i]
2170
+ try {
2171
+ if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
2172
+ 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 } }
2173
+ else if (d.node === 'plan') { planQueue = (d.data.plan || []).slice(); await applyPlanToProblemCards(planQueue); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
2174
+ else if (d.node === 'method-promote') { const m = methods.get(d.data.methodId); if (m) await promoteMethodToGlobal(m); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
2175
+ } catch (e) { console.error('vibe-math-v3: auto-resolve decision failed: ' + String((e && e.message) || e)) }
2176
+ }
2177
+ if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
2178
+ }
2179
+ async function getStatus() {
2180
+ return {
2181
+ ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
2182
+ project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
2183
+ mode: params.mode, activeCount: scheduler.activeCount, maxParallelThreshold: params.maxParallelThreshold,
2184
+ frameworkRoot: frameworkRoot(),
2185
+ problems: { total: problems.size, solved: allProblems().filter(function (q) { return q.状态 === '已解决' }).length },
2186
+ propositions: { total: propos.size, resolved: allPropos().filter(function (p) { return p.概率 === 1 || p.概率 === 0 }).length },
2187
+ verifyPending: (await buildVerifyCandidates()).length,
2188
+ methods: { project: methods.size, global: globalMethods.size, pendingInventions: methodLog.pendingInventions.length },
2189
+ pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
2190
+ registeredAgents: Object.keys(agentRegistry).length,
2191
+ queuedPlanActions: planQueue.length,
2192
+ plannerEnabled: params.plannerEnabled, plannerFails: plannerFails,
2193
+ recentActivity: activityLog.slice(-Math.min(10, Number(params.activityLogCap) || 100)), params: params,
2194
+ }
2195
+ }
2196
+ async function checkTermination() {
2197
+ const unsolved = allProblems().filter(function (q) { return !(q.状态 === '已解决' || q.优先级 === 'never') })
2198
+ if (unsolved.length === 0 && Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0 && planQueue.length === 0) {
2199
+ scheduler.running = false
2200
+ await releaseProjectLock()
2201
+ logActivity('stop', 'all active problems solved (never-priority excluded) and no active agents/tasks/plans — scheduler stopped (strict termination)')
2202
+ await saveAll(); await maybeWriteReport(true); await maybePushReport(true)
2203
+ } else if (Object.keys(agentRegistry).length === 0 && Object.keys(tasks).length === 0 && planQueue.length === 0 && unsolved.length > 0) {
2204
+ let allBlocked = true
2205
+ for (let i = 0; i < unsolved.length; i++) {
2206
+ const q = unsolved[i]
2207
+ if (q.状态 === '等待依赖') continue
2208
+ const dirs = getDirState(q.id)
2209
+ const exhaustedAll = dirs.length > 0 && dirs.every(function (d) { return d.status === 'dead-end' || d.status === 'success' })
2210
+ const hasActive = dirs.some(function (d) { return d.status === 'active' })
2211
+ const blocked = (dirs.length === 0 || exhaustedAll) && (explorerRetries[q.id] || 0) >= (Number(params.maxExplorerRetries) || 3)
2212
+ if (hasActive || !blocked) { allBlocked = false; break }
2213
+ }
2214
+ if (allBlocked) {
2215
+ scheduler.running = false
2216
+ await releaseProjectLock()
2217
+ logActivity('stall', 'no feasible direction remains for any unsolved problem — scheduler paused (stalled, NOT all solved)')
2218
+ await saveAll(); await maybeWriteReport(true)
2219
+ }
2220
+ }
2221
+ }
2222
+
2223
+ // ================= projects =================
2224
+ async function setProject(slug, create) {
2225
+ if (!rootAgent) return { ok: false, message: 'no root agent available' }
2226
+ const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
2227
+ if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
2228
+ if (scheduler.running) await abortScheduler()
2229
+ currentProject = slug; await writeCurrentProject(); await ensureDirs()
2230
+ params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, activeCount: 0, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; planQueue = []; plannerFails = 0; methodLog = { pendingInventions: [], keepCount: 0, lastKeepAt: 0 }; projectLock = { sessionId: '', at: 0 }; lastReportWrite = 0; lastPushReport = 0; reportDirty = false; lastPlanSummary = null; archivedJ = {}; lastIndexWrite = 0
2231
+ await loadSettings(); await migrateLegacyParams(); await loadState(); await loadKnowledgeBase(); await saveAll()
2232
+ if (params.indexAutoRebuild) await rebuildIndex()
2233
+ return { ok: true, project: slug, frameworkRoot: frameworkRoot() }
2234
+ }
2235
+
2236
+ // ================= events / timer (registered at apply level, see bottom) =================
2237
+
2238
+ // ================= tools =================
2239
+ function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
2240
+ const handlers = {}
2241
+ function registerTool(name, description, parameters, executeFn) { handlers[name] = executeFn }
2242
+ registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V3 scheduler for the current project.', objParams({}), async function () { return await startScheduler() })
2243
+ registerTool('vibe_math_resume', 'Resume the Vibe Math V3 scheduler after a checkpoint/restart.', objParams({}), async function () { return await resumeScheduler() })
2244
+ registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), async function () { return await pauseScheduler() })
2245
+ registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), async function () { return await abortScheduler() })
2246
+ registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
2247
+ registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json + Logs/报告.md.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
2248
+ 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 } })
2249
+ 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' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { 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' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
2250
+ 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' } })
2251
+ 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() })
2252
+ 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') })
2253
+ registerTool('vibe_math_add_problem', 'Add a problem to the current project (creates Problems/<id>.md).', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' }, dependencies: { type: 'array', items: { type: 'string' } } }, ['id', 'description']), async function (args) { if (problems.has(args.id)) return { ok: false, message: 'problem id already exists' }; problems.set(args.id, { id: args.id, 标题: args.id, 状态: '求解中', 优先级: args.priority || 0, 依赖: Array.isArray(args.dependencies) ? args.dependencies : [], 被依赖: [], 来源: '原始', 计划: '待调度', 陈述: args.description, 来源与动机: '', solutions: [], 判断命题: '', 来源命题: '' }); await saveProblem(problems.get(args.id)); await syncDependencies(); await rebuildIndex(); scheduleTick(); return { ok: true, message: 'problem added', file: 'Problems/' + args.id + '.md' } })
2254
+ registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (creates Propos/<分类>/<id>.md).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 概率: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 分类: { type: 'string' } }, ['id', '概述']), async function (args) {
2255
+ if (propos.has(args.id)) return { ok: false, message: 'proposition id already exists' }
2256
+ const p = { id: args.id, 标题: args.id, 状态: '未定论', 概率: clamp01(args.概率 != null ? args.概率 : 0.5), 优先级: (args.优先级 != null) ? args.优先级 : 1, 依赖: [], 价值关键性: clamp01(args['价值/关键性'] != null ? args['价值/关键性'] : 0.5), 分类: args.分类 || '未分类', 陈述: args.概述, proofs: [], refutes: [], 来源问题: '', 在问题清单: false }
2257
+ propos.set(p.id, p); await saveProposition(p); await rebuildIndex(); scheduleTick(); return { ok: true, proposition: p, file: 'Propos/' + categoryOf(p) + '/' + p.id + '.md' }
2258
+ })
2259
+ registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 标题, 概率, 状态, 优先级, 分类).', objParams({}), async function () { const all = allPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 标题: p.标题, 概率: p.概率, 状态: p.状态, 优先级: p.优先级, 分类: categoryOf(p) } }) } })
2260
+ 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) })
2261
+ 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) })
2262
+ registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
2263
+ 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 } }) } })
2264
+ registerTool('vibe_math_decide', 'Resolve a pending manual decision (plan: approve|reject; verdict: override with verdict 1|0; spawn: approve|reject; method-promote: approve|reject).', 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) })
2265
+ 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 } })
2266
+ 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' } })
2267
+ 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' } })
2268
+ registerTool('vibe_math_plan', 'Show the queued plan / last plan result, or force a planning round.', objParams({ force: { type: 'boolean' } }), async function (args) { if (args && args.force && scheduler.running && !scheduler.gate) { await callPlanner(); return { ok: true, message: 'planning triggered', queued: planQueue.length } } return { ok: true, queued: planQueue, lastPlan: lastPlanSummary } })
2269
+ registerTool('vibe_math_index', 'Rebuild the machine index (State/index.json) from the Markdown knowledge base.', objParams({}), async function () { await loadKnowledgeBase(); const r = await rebuildIndex(); return { ok: true, index: r, project: currentProject } })
2270
+ registerTool('vibe_math_method_add', 'Manually add a method card to Methods/ (creates Methods/<id>.md).', objParams({ id: { type: 'string' }, 标题: { type: 'string' }, 类型: { type: 'string' }, 核心内容: { type: 'string' }, 适用场景: { type: 'string' } }, ['id', '标题']), async function (args) { if (methods.has(args.id)) return { ok: false, message: 'method id already exists' }; const m = { id: args.id, 标题: args.标题, 类型: args.类型 || '方法', 状态: '经验', 可信断言: [], 上级体系: [], 子方法: [], 相关: [], 适用场景: args.适用场景 || '', 核心内容: args.核心内容 || '', 定义与记号: '', applications: [], improvements: [], 来源: 'user' }; methods.set(m.id, m); await saveMethod(m, false); await rebuildIndex(); return { ok: true, method: m, file: 'Methods/' + m.id + '.md' } })
2271
+ registerTool('vibe_math_method_list', 'List methods from Methods/ (+ global VibeMath/Methods/): id, 标题, 类型, 状态, 可信断言, applications count.', objParams({}), async function () { const all = Array.from(methods.values()); const g = Array.from(globalMethods.values()); return { ok: true, count: all.length, globalCount: g.length, methods: all.map(function (m) { return { id: m.id, 标题: m.标题, 类型: m.类型, 状态: m.状态, 可信断言: m.可信断言 || [], applications: (m.applications || []).length, global: false } }).concat(g.map(function (m) { return { id: m.id, 标题: m.标题, 类型: m.类型, 状态: m.状态, 可信断言: m.可信断言 || [], applications: (m.applications || []).length, global: true } })) } })
2272
+ registerTool('vibe_math_lock_status', 'Show the project lock occupancy.', objParams({}), async function () { return { ok: true, project: currentProject, lock: projectLock } })
2273
+ registerTool('vibe_math_claim_write', 'Acquire the write lock for one target file (relative to the project root). Call before writing a Markdown file directly; a file may only be written by ONE agent at a time. Returns the display path you may write (VibeMath/Projects/<project>/<target>) and a hint.', objParams({ target: { type: 'string' } }, ['target']), async function (args, agent) { return await claimWrite(String(args.target || ''), agent) })
2274
+ registerTool('vibe_math_release_write', 'Release the write lock for one target file (relative to the project root). Call after you finished writing it.', objParams({ target: { type: 'string' } }, ['target']), async function (args, agent) { return await releaseWrite(String(args.target || ''), agent) })
2275
+ registerTool('vibe_math_sync_meta', 'Report lightweight scheduling metadata after you wrote content to Markdown files (direction status/survival, registered lemma ids, methods_used/new_inventions, new method cards). Content itself stays in the md files; this only keeps the scheduler index/state in sync.', objParams({ meta: { type: 'object' } }, ['meta']), async function (args, agent) { return await syncMeta(args.meta || {}, agent) })
2276
+
2277
+ // ---- 代理直接写 md 的写锁 + 轻元数据同步(任务2:代理自组织写各自对应路径的 md,避免并发写同一文件) ----
2278
+ async function claimWrite(target, agent) {
2279
+ const childId = (agent && agent.id) ? String(agent.id) : 'scheduler'
2280
+ const key = String(target || '').replace(/\\/g, '/')
2281
+ if (!key) return { ok: false, message: 'target required' }
2282
+ const owner = fileOwner[key]
2283
+ if (owner && owner.childId !== childId && (now() - (owner.at || 0)) < 60000) {
2284
+ return { ok: false, busy: owner.childId, message: '文件 "' + key + '" 正被其他代理写入,请稍后(写锁)' }
2285
+ }
2286
+ fileOwner[key] = { childId: childId, sessionId: sessionId, at: now() }
2287
+ logActivity('write-lock', 'claim ' + key + ' by ' + childId)
2288
+ return { ok: true, key: key, path: frameworkRoot() + '/' + key, hint: '现在可写入 ' + frameworkRoot() + '/' + key + ';写完请 release_write' }
2289
+ }
2290
+ async function releaseWrite(target, agent) {
2291
+ const childId = (agent && agent.id) ? String(agent.id) : 'scheduler'
2292
+ const key = String(target || '').replace(/\\/g, '/')
2293
+ const owner = fileOwner[key]
2294
+ if (owner && owner.childId !== childId) return { ok: false, message: '写锁不属于此代理,无法释放' }
2295
+ delete fileOwner[key]
2296
+ logActivity('write-lock', 'release ' + key + ' by ' + childId)
2297
+ return { ok: true, key: key }
2298
+ }
2299
+ // 元数据同步:代理把内容写进 md 后,用极简字段让调度器更新索引/状态(content 不进 JSON)
2300
+ async function syncMeta(meta, agent) {
2301
+ if (!meta || typeof meta !== 'object') return { ok: false, message: 'meta object required' }
2302
+ const kind = String(meta.kind || '')
2303
+ if (kind === 'directions') {
2304
+ // explorer 写好了方向定义:更新 dirState 元数据(id/title/存活率/状态)
2305
+ const qid = String(meta.qid || '')
2306
+ if (qid && Array.isArray(meta.directions)) {
2307
+ const list = (meta.directions || []).map(function (d) {
2308
+ const old = (getDirState(qid) || []).find(function (x) { return x.id === d.id })
2309
+ return { id: d.id || ('d_' + shortId()), title: d.title || '', method: d.method || old?.method || '', core_assumption: d.core_assumption || old?.core_assumption || '', feasibility: clamp01(d.feasibility != null ? d.feasibility : (old ? old.survival : 0.5)), status: 'active', round: old ? old.round : 0, survival: clamp01(d.survival != null ? d.survival : (old ? old.survival : 0.5)), routes: old?.routes || [], lessons: old?.lessons || [], blockers: old?.blockers || [], lemmas: old?.lemmas || [], journal: old?.journal || [], dead_end_reason: '' }
2310
+ })
2311
+ dirState.set(qid, list)
2312
+ await saveDirState(); await writeJournal(qid)
2313
+ await consumeMethodFeedback(meta, { qid: qid })
2314
+ logActivity('explorer', 'problem ' + qid + ' → ' + list.length + ' directions (meta sync)')
2315
+ }
2316
+ return { ok: true }
2317
+ }
2318
+ if (kind === 'solver') {
2319
+ const qid = String(meta.qid || ''); const dirId = String(meta.dirId || '')
2320
+ const q = problems.get(qid)
2321
+ const dirs = getDirState(qid)
2322
+ const dir = dirs.find(function (d) { return d.id === dirId })
2323
+ if (q && dir) {
2324
+ if (typeof meta.survival === 'number') dir.survival = clamp01(meta.survival)
2325
+ if (meta.status) dir.status = String(meta.status)
2326
+ if (meta.dead_end_reason) dir.dead_end_reason = String(meta.dead_end_reason)
2327
+ if (meta.round) dir.round = Number(meta.round)
2328
+ // 引理注册(id 由代理在命题卡里自定)
2329
+ if (Array.isArray(meta.lemmas)) {
2330
+ for (const l of meta.lemmas) {
2331
+ if (!l || (!l.id && !l.title)) continue
2332
+ const pid = l.id || ('p-' + shortId())
2333
+ if (!propos.has(pid)) {
2334
+ const pn = { id: pid, 标题: l.title || pid, 状态: '未定论', 概率: clamp01(l.prob != null ? l.prob : 0.6), 优先级: l.优先级 != null ? l.优先级 : 1, 依赖: [], 价值关键性: clamp01(l['价值/关键性'] != null ? l['价值/关键性'] : 0.5), 分类: l.分类 || '未分类', 陈述: l.statement || l.title || '', proofs: [], refutes: [], 来源问题: qid, 在问题清单: false }
2335
+ propos.set(pid, pn)
2336
+ // 若代理已直接写了该命题卡,保留其内容(不覆盖);否则写一张标准卡兜底(保证可被索引/验证)
2337
+ const rel = 'Propos/' + categoryOf(pn) + '/' + pid + '.md'
2338
+ if ((await readText(rel)) === undefined) await saveProposition(pn)
2339
+ }
2340
+ if (!(dir.lemmas || []).some(function (x) { return x.id === pid })) { dir.lemmas = dir.lemmas || []; dir.lemmas.push({ id: pid, title: l.title || pid }) }
2341
+ }
2342
+ }
2343
+ // 解法上报(prob 由代理写进 Problems/<qid>.md;这里只登记)
2344
+ if (meta.solution_prob != null && meta.solution_text) {
2345
+ q.solutions = q.solutions || []
2346
+ const p = clamp01(meta.solution_prob)
2347
+ q.solutions.push({ title: '解法 ' + (q.solutions.length + 1), prob: p >= 1 ? 0.99 : (p <= 0 ? 0.01 : p), status: '未定论', text: String(meta.solution_text).slice(0, 2000) })
2348
+ }
2349
+ await saveProblem(q); await saveDirState(); await writeJournal(qid)
2350
+ await consumeMethodFeedback(meta, { qid: qid, dirId: dirId })
2351
+ logActivity('solver', qid + '/' + dirId + ' meta sync (status=' + (meta.status || '') + ', survival=' + dir.survival + ')')
2352
+ }
2353
+ return { ok: true }
2354
+ }
2355
+ if (kind === 'methods') {
2356
+ if (Array.isArray(meta.used)) for (const mu of meta.used) await consumeMethodFeedback({ methods_used: mu ? [mu] : [] }, { qid: '', dirId: '' })
2357
+ if (Array.isArray(meta.created)) for (const mid of meta.created) { if (!methods.has(mid)) { methods.set(mid, { id: mid, 标题: mid, 类型: '方法', 状态: '经验', 可信断言: [], 上级体系: [], 子方法: [], 相关: [], 适用场景: '', 核心内容: '', 定义与记号: '', applications: [], improvements: [], 来源: 'agent-written' }); await saveMethod(methods.get(mid), false) } }
2358
+ return { ok: true }
2359
+ }
2360
+ return { ok: false, message: 'unknown meta kind: ' + kind }
2361
+ }
2362
+
2363
+ // ================= slash command /vibe =================
2364
+ async function dispatchVibeCommand(cmd, args) {
2365
+ if (cmd === 'start') return await startScheduler()
2366
+ if (cmd === 'resume') return await resumeScheduler()
2367
+ if (cmd === 'pause') return await pauseScheduler()
2368
+ if (cmd === 'abort') return await abortScheduler()
2369
+ if (cmd === 'status') { await refreshParams(); return await getStatus() }
2370
+ if (cmd === 'report') { await refreshParams(); await maybeWriteReport(true); return await buildReport() }
2371
+ 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 } }
2372
+ 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' } }
2373
+ if (cmd === 'save') return await saveSettings()
2374
+ if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
2375
+ 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>' }; if (problems.has(id)) return { ok: false, message: 'problem id already exists' }; problems.set(id, { id: id, 标题: id, 状态: '求解中', 优先级: 0, 依赖: [], 被依赖: [], 来源: '原始', 计划: '待调度', 陈述: desc, 来源与动机: '', solutions: [], 判断命题: '', 来源命题: '' }); await saveProblem(problems.get(id)); await rebuildIndex(); scheduleTick(); return { ok: true, message: 'problem added', file: 'Problems/' + id + '.md' } }
2376
+ 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, 标题: id, 状态: '未定论', 概率: 0.5, 优先级: 1, 依赖: [], 价值关键性: 0.5, 分类: '未分类', 陈述: desc, proofs: [], refutes: [], 来源问题: '', 在问题清单: false }; propos.set(p.id, p); await saveProposition(p); await rebuildIndex(); scheduleTick(); return { ok: true, proposition: p, file: 'Propos/' + categoryOf(p) + '/' + p.id + '.md' } }
2377
+ if (cmd === 'list-propositions') { const all = allPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 标题: p.标题, 概率: p.概率, 状态: p.状态, 优先级: p.优先级, 分类: categoryOf(p) } }) } }
2378
+ if (cmd === 'methods') { const all = Array.from(methods.values()); return { ok: true, count: all.length, methods: all.map(function (m) { return { id: m.id, 标题: m.标题, 类型: m.类型, 状态: m.状态 } }) } }
2379
+ if (cmd === 'index') { await loadKnowledgeBase(); return await rebuildIndex() }
2380
+ if (cmd === 'plan') { return { ok: true, queued: planQueue, lastPlan: lastPlanSummary } }
2381
+ if (cmd === 'lock') { return { ok: true, project: currentProject, lock: projectLock } }
2382
+ if (cmd === 'project') {
2383
+ if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
2384
+ if (args[0] === 'new') return await setProject(slugify(args.slice(1).join(' ')), true)
2385
+ return await setProject(slugify(args[0]), false)
2386
+ }
2387
+ 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 } }) }
2388
+ 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 } }
2389
+ 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 | methods | index | plan | lock | project [list|new <name>|<name>] | decisions | agents', message: 'unknown /vibe subcommand: ' + (cmd || '(empty)') }
2390
+ }
2391
+
2392
+ // ================= session surface =================
2393
+ return {
2394
+ sessionId: sessionId,
2395
+ scheduler: scheduler,
2396
+ tickInFlight: tickInFlight,
2397
+ scheduleTick: scheduleTick,
2398
+ onChildEnd: onChildEnd,
2399
+ dispatchVibeCommand: dispatchVibeCommand,
2400
+ handlers: handlers,
2401
+ refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
2402
+ getRunning: function () { return scheduler.running },
2403
+ tickDue: function () { const iv = Math.max(200, Number(params.tickIntervalMs) || 2000); return (now() - lastTickAt) >= iv },
2404
+ }
2405
+ }
2406
+
2407
+ // ================= apply-level registrations (ONCE per preset) =================
2408
+ function objParams(props, required) { return { type: 'object', properties: props, additionalProperties: false, required: required || [] } }
2409
+ function registerTool(name, description, parameters, handlerName) {
2410
+ ctx.effect(() => tools.register({
2411
+ name: name, description: description, parameters: parameters,
2412
+ output: { schema: { type: 'string' }, render: (_args, value) => [{ type: 'text', text: String(value) }] },
2413
+ execute: async function (args, exec) {
2414
+ try {
2415
+ const s = getSession(exec && exec.agent)
2416
+ if (!s) return JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' })
2417
+ await s.refreshProject()
2418
+ return JSON.stringify(await s.handlers[handlerName](args || {}, exec && exec.agent))
2419
+ } catch (e) { return JSON.stringify({ ok: false, error: String((e && e.message) || e) }) }
2420
+ },
2421
+ }))
2422
+ }
2423
+ registerTool('vibe_math_start', 'Start (or restart) the Vibe Math V3 scheduler for the current project.', objParams({}), 'vibe_math_start')
2424
+ registerTool('vibe_math_resume', 'Resume the Vibe Math V3 scheduler after a checkpoint/restart.', objParams({}), 'vibe_math_resume')
2425
+ registerTool('vibe_math_pause', 'Pause the scheduler (in-flight children finish their current turn).', objParams({}), 'vibe_math_pause')
2426
+ registerTool('vibe_math_abort', 'Abort the scheduler and interrupt all active children.', objParams({}), 'vibe_math_abort')
2427
+ registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
2428
+ registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json + Logs/报告.md.', objParams({}), 'vibe_math_report')
2429
+ 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')
2430
+ 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' }, plannerPersona: { type: 'string' }, methodKeeperPersona: { 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' }, planningHorizon: { type: 'integer' }, plannerEnabled: { type: 'boolean' }, plannerProvider: { type: 'string' }, plannerModel: { type: 'string' }, planMinIntervalMs: { type: 'integer' }, plannerMaxFails: { type: 'integer' }, methodKeepIntervalMs: { type: 'integer' }, methodKeepEvery: { type: 'integer' }, methodAutoPromote: { type: 'boolean' }, indexAutoRebuild: { type: 'boolean' }, projectLockTimeoutMs: { type: 'integer' } }), 'vibe_math_set_params')
2431
+ registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
2432
+ 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')
2433
+ 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')
2434
+ registerTool('vibe_math_add_problem', 'Add a problem to the current project (creates Problems/<id>.md).', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' }, dependencies: { type: 'array', items: { type: 'string' } } }, ['id', 'description']), 'vibe_math_add_problem')
2435
+ registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (creates Propos/<分类>/<id>.md).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 概率: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 分类: { type: 'string' } }, ['id', '概述']), 'vibe_math_add_proposition')
2436
+ registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index).', objParams({}), 'vibe_math_list_propositions')
2437
+ registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
2438
+ registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
2439
+ registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
2440
+ registerTool('vibe_math_list_decisions', 'List pending manual decisions.', objParams({}), 'vibe_math_list_decisions')
2441
+ registerTool('vibe_math_decide', 'Resolve a pending manual decision (plan: approve|reject; verdict: override with verdict 1|0; spawn: approve|reject; method-promote: approve|reject).', objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), 'vibe_math_decide')
2442
+ registerTool('vibe_math_list_agents', 'List tracked sub-agents (child sessions).', objParams({}), 'vibe_math_list_agents')
2443
+ 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')
2444
+ registerTool('vibe_math_interrupt_agent', 'Interrupt a tracked child agent.', objParams({ childId: { type: 'string' } }, ['childId']), 'vibe_math_interrupt_agent')
2445
+ registerTool('vibe_math_plan', 'Show the queued plan / last plan result, or force a planning round.', objParams({ force: { type: 'boolean' } }), 'vibe_math_plan')
2446
+ registerTool('vibe_math_index', 'Rebuild the machine index (State/index.json) from the Markdown knowledge base.', objParams({}), 'vibe_math_index')
2447
+ registerTool('vibe_math_method_add', 'Manually add a method card to Methods/ (creates Methods/<id>.md).', objParams({ id: { type: 'string' }, 标题: { type: 'string' }, 类型: { type: 'string' }, 核心内容: { type: 'string' }, 适用场景: { type: 'string' } }, ['id', '标题']), 'vibe_math_method_add')
2448
+ registerTool('vibe_math_method_list', 'List methods from Methods/ (+ global VibeMath/Methods/).', objParams({}), 'vibe_math_method_list')
2449
+ registerTool('vibe_math_lock_status', 'Show the project lock occupancy.', objParams({}), 'vibe_math_lock_status')
2450
+ registerTool('vibe_math_claim_write', 'Acquire the write lock for one target file (relative to the project root). Call before writing a Markdown file directly.', objParams({ target: { type: 'string' } }, ['target']), 'vibe_math_claim_write')
2451
+ registerTool('vibe_math_release_write', 'Release the write lock for one target file (relative to the project root).', objParams({ target: { type: 'string' } }, ['target']), 'vibe_math_release_write')
2452
+ registerTool('vibe_math_sync_meta', 'Report lightweight scheduling metadata after writing content to Markdown files.', objParams({ meta: { type: 'object' } }, ['meta']), 'vibe_math_sync_meta')
2453
+
2454
+ // /vibe slash command (registered once; routed per session)
2455
+ ctx.effect(() => commands.register({
2456
+ name: 'vibe',
2457
+ description: 'control the Vibe Math V3 solver (start/pause/projects/setup/save/decisions/agents/methods/index/plan/lock)',
2458
+ input: { hint: '[start|resume|pause|abort|status|report|mode <auto|manual>|setup|save|template [global|project]|add <id> <desc>|add-proposition <id> <概述>|list-propositions|methods|index|plan|lock|project [list|new <name>|<name>]|decisions|agents]' },
2459
+ handler: async function (invocation) {
2460
+ const s = getSession(invocation && invocation.agent)
2461
+ if (!s) return { kind: 'success', text: JSON.stringify({ ok: false, error: 'no vibe-math session for this agent' }) }
2462
+ const line = String(invocation && invocation.rawInput ? invocation.rawInput : '').trim()
2463
+ const parts = line.length > 0 ? line.split(/\s+/) : []
2464
+ const cmd = parts[0] || ''
2465
+ const rest = parts.slice(1)
2466
+ const result = await s.dispatchVibeCommand(cmd, rest)
2467
+ return { kind: 'success', text: JSON.stringify(result, null, 2) }
2468
+ },
2469
+ }))
2470
+
2471
+ // subagent/end (registered once; routed to the owning session via childOwner)
2472
+ ctx.on('subagent/end', function (info) {
2473
+ const sid = childOwner.get(info.id)
2474
+ const s = sid !== undefined ? sessions.get(sid) : undefined
2475
+ if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v3 onChildEnd reject: ' + String((e && e.stack) || e)) })
2476
+ })
2477
+
2478
+ // tick timer (registered once; ticks every running session at its own pace)
2479
+ 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) })
2480
+ }