dsh-vibe-math 2.4.1 → 2.5.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.
- package/README.en.md +14 -14
- package/README.md +15 -14
- package/cordis.patch.yml +33 -10
- package/docs/AUDIT-CHECKLIST.md +25 -6
- package/docs/COMPAT-AUDIT-ROUND3.md +16 -1
- package/docs/release-notes/RELEASE-NOTES-2.5.0.md +173 -0
- package/docs/test-timing.md +73 -40
- package/installer.js +225 -45
- package/package.json +3 -2
- package/preset-declaration.js +38 -7
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +67 -21
- package/prompt-corpus-v2/formal-verify-v2.json +116 -92
- package/prompt-corpus-v2/formal-verify-v2.md +240 -93
- package/prompt-corpus-v3/formal-verify-v3.json +81 -81
- package/prompt-corpus-v3/formal-verify-v3.md +288 -1111
- package/prompt-corpus-v4/formal-verify-v4.json +38 -8
- package/prompt-corpus-v4/formal-verify-v4.md +157 -25
- package/prompt-corpus-v5/prompt-corpus-v5.json +17 -17
- package/prompt-corpus-v5/prompt-corpus-v5.md +32 -26
- package/tests/audit-installer-compat.test.mjs +70 -4
- package/tests/audit-installer-policy.test.mjs +229 -4
- package/tests/audit-persona-sensitivity.mjs +11 -4
- package/tests/audit-persona-surface.test.mjs +5 -1
- package/tests/audit-v5-sensitivity.mjs +47 -6
- package/tests/e2e-v5-round2.test.mjs +354 -0
- package/tests/formal-verify-v2.test.mjs +54 -2
- package/tests/formal-verify-v3.test.mjs +63 -2
- package/tests/formal-verify-v4.test.mjs +120 -4
- package/tests/run-tests.mjs +104 -23
- package/vibe-math-v2/agent.cordis.yml +6 -0
- package/vibe-math-v2/vibe-math-v2.js +503 -99
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +20 -4
- package/vibe-math-v3/agent.cordis.yml +26 -10
- package/vibe-math-v3/vibe-math-v3.js +555 -175
- package/vibe-math-v3//345/256/236/347/216/260/346/226/271/346/241/210.md +3 -3
- package/vibe-math-v4/agent.cordis.yml +26 -6
- package/vibe-math-v4/vibe-math-v4.js +404 -51
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +91 -9
- package/vibe-math-v5/agent.cordis.yml +8 -4
- package/vibe-math-v5/vibe-math-v5.js +513 -105
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +73 -40
|
@@ -43,6 +43,50 @@ export function apply(ctx) {
|
|
|
43
43
|
// ================= per-session registry =================
|
|
44
44
|
const sessions = new Map() // rootAgentId -> Session
|
|
45
45
|
const childOwner = new Map() // childId -> rootAgentId (route subagent/end back to its session)
|
|
46
|
+
// childOwner 的**有界化**(审计 H6)。一条 childId→rootAgentId 的映射只在"这个 child 还可能再发
|
|
47
|
+
// subagent/end"时有价值;子代理结束后它就是垃圾,而这里以前明确说"不要回收"(为了修 verdict 收口),
|
|
48
|
+
// 代价是每个历史子代理永久留下一条记录、进程生命周期内无界增长。现在改为**引用 + 宽限期**:
|
|
49
|
+
// · 仍被某个任务(t.children)或 agentRegistry 引用的一律保留;
|
|
50
|
+
// · 没有引用、且距最近一次 subagent/end 已超过宽限期,才回收。
|
|
51
|
+
// 宽限期是必需的:辩论/续轮会在**同一个 child** 上再次 end(这正是当初"结束后立即删"导致
|
|
52
|
+
// 'problem solved after verdict 1' 失败的原因),所以不能只看"当前是否有引用"。
|
|
53
|
+
const CHILD_OWNER_GRACE_MS = 5 * 60 * 1000
|
|
54
|
+
const lastChildEndAt = new Map() // childId -> 最近一次 subagent/end 的时间
|
|
55
|
+
function pruneChildOwner() {
|
|
56
|
+
const keep = new Set()
|
|
57
|
+
for (const s of sessions.values()) {
|
|
58
|
+
try { for (const id of s.referencedChildIds()) keep.add(id) } catch (e) { /* 尽力而为 */ }
|
|
59
|
+
}
|
|
60
|
+
const cutoff = now() - CHILD_OWNER_GRACE_MS
|
|
61
|
+
for (const cid of Array.from(childOwner.keys())) {
|
|
62
|
+
if (keep.has(cid)) continue
|
|
63
|
+
const ended = lastChildEndAt.get(cid)
|
|
64
|
+
if (ended === undefined || ended > cutoff) continue
|
|
65
|
+
childOwner.delete(cid); lastChildEndAt.delete(cid)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// sessions 的**有界化**(审计 L18)。`sessions` 按 root agent id 记;正常路径下 DSH 的每个根会话
|
|
69
|
+
// 各一条,但 `getSession` 对**任何** agent 都会建一条,包括 `rootOf` 向上走失败(父代理已不在注册表里)
|
|
70
|
+
// 的子代理——那种会话永远没人再访问,却会带着 `running=true` 一直被 apply 级的 tick 循环唤醒。
|
|
71
|
+
// `agents.roots()`(契约:全部 live 顶层 agent)是权威判据:root 不再是顶层 live agent ⇒ 该会话是僵尸。
|
|
72
|
+
// 三重保守:主机没有 roots() / 它抛错 / 它返回空数组 ⇒ **一律不裁剪**(空 = 没有可用信息,而不是
|
|
73
|
+
// "一个会话都没有");仍在册子代理的会话也保留。因此测试 mock(roots() 恒返回 [])与旧宿主行为不变。
|
|
74
|
+
function pruneSessions() {
|
|
75
|
+
if (sessions.size === 0) return
|
|
76
|
+
if (typeof agents.roots !== 'function') return
|
|
77
|
+
let roots = []
|
|
78
|
+
try { roots = agents.roots() || [] } catch (e) { return }
|
|
79
|
+
if (!Array.isArray(roots) || roots.length === 0) return
|
|
80
|
+
const live = new Set()
|
|
81
|
+
for (let i = 0; i < roots.length; i++) { const id = sessionIdOf(roots[i]); if (id !== undefined) live.add(id) }
|
|
82
|
+
if (live.size === 0) return
|
|
83
|
+
for (const sid of Array.from(sessions.keys())) {
|
|
84
|
+
if (live.has(sid)) continue
|
|
85
|
+
const s = sessions.get(sid)
|
|
86
|
+
try { if (s && s.referencedChildIds().length > 0) continue } catch (e) { continue }
|
|
87
|
+
sessions.delete(sid)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
46
90
|
// Process epoch: PROCESS-level (one per apply, shared by every session), written to
|
|
47
91
|
// VibeMath_State/process_epoch.json at init; a DIFFERENT persisted epoch means a
|
|
48
92
|
// previous DSH process wrote this state (in-flight children are gone), while an
|
|
@@ -90,7 +134,7 @@ export function apply(ctx) {
|
|
|
90
134
|
directionsPerSolver: 1, // 每个 solver 提示词附带的方向数量(1 = 只看自己方向)
|
|
91
135
|
verifierCount: 3, // independent reviewers per verification
|
|
92
136
|
debateMaxRounds: 5, // debate round cap (spec example)
|
|
93
|
-
verdictMode: 'flat', // flat =
|
|
137
|
+
verdictMode: 'flat', // flat = 均衡机制(不一致时取各评审自报概率的均值)| forced = 强制裁决(按验证者历史准确率+布尔票置信度加权)
|
|
94
138
|
provider: '',
|
|
95
139
|
model: '',
|
|
96
140
|
solverPersona: '',
|
|
@@ -113,7 +157,7 @@ export function apply(ctx) {
|
|
|
113
157
|
priorityAdjust: 'none', // none | deadend-deprioritize | survival-map
|
|
114
158
|
proposPriorityAdjust: 'none', // none | progress-graded(按定论接近度+证明/证伪材料量动态调命题优先级)
|
|
115
159
|
tickIntervalMs: 2000, // 调度器心跳间隔(毫秒)
|
|
116
|
-
activityLogCap: 100, // 活动日志保留条数(report
|
|
160
|
+
activityLogCap: 100, // 活动日志保留条数(status/report 的 recentActivity 最多显示 ACTIVITY_REPORT_MAX = 30 条)
|
|
117
161
|
maxExplorerRetries: 3, // explorer 重派生上限(拆方向失败重试次数)
|
|
118
162
|
// ---- Lean 形式化验证(契约:docs/formal-verification.md §1,四架构同名同语义)----
|
|
119
163
|
formalVerify: 'off', // off = 无任何额外要求(真无操作)| encourage = 鼓励但不强制 | require = 强制 + 定论门禁
|
|
@@ -125,7 +169,8 @@ export function apply(ctx) {
|
|
|
125
169
|
let scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null } // activeCount 由 activeCount() 从 agentRegistry 推导,不再作为字段
|
|
126
170
|
let agentRegistry = {}
|
|
127
171
|
let decisionQueue = []
|
|
128
|
-
let verifierAccuracy = {}
|
|
172
|
+
let verifierAccuracy = {} // 稳定身份键('m:<provider>/<model>')→ { correct, total }
|
|
173
|
+
let pendingReviewScores = {} // 对象 id → [{key,result}]:等对象**后来**取得布尔定论时才计分
|
|
129
174
|
let tasks = {} // verify tasks keyed by 'verify:<rId>'
|
|
130
175
|
let activityLog = []
|
|
131
176
|
let lastReportWrite = 0
|
|
@@ -134,6 +179,16 @@ export function apply(ctx) {
|
|
|
134
179
|
let tickInFlight = false
|
|
135
180
|
let lastTickAt = 0
|
|
136
181
|
let explorerRetries = {}
|
|
182
|
+
// 状态/报告里最近活动最多显示多少条(**一个常量**):此前 buildReport 用 min(30, cap)、getStatus
|
|
183
|
+
// 硬编码 min(10, cap),同一个字段两个端点给出不同答案,而参数说明承诺的是 30(审计 M15)。
|
|
184
|
+
const ACTIVITY_REPORT_MAX = 30
|
|
185
|
+
// 至少 2 名独立评审才能出裁决(审计 M11):一票裁决会把单个验证者的判断写成"完全验证"的布尔结论。
|
|
186
|
+
const MIN_REVIEWERS = 2
|
|
187
|
+
// 中段裁决(0<正确概率<1)的**重验冷却**(审计 M12):中段值是"为真的概率",不是定论,对象不能
|
|
188
|
+
// 被永久搁置;但每个 tick 都重开一轮完整辩论会把调度器饿死(那正是当初加 `已验证` 单向闩锁的原因)。
|
|
189
|
+
// 冷却期过后重新入选,所以没有任何对象会被永久停在中间概率上。
|
|
190
|
+
const REVERIFY_COOLDOWN_MS = 5 * 60 * 1000
|
|
191
|
+
function reverifyDue(x) { const at = Number(x && x.最近验证时间) || 0; return (now() - at) >= REVERIFY_COOLDOWN_MS }
|
|
137
192
|
|
|
138
193
|
// ================= helpers =================
|
|
139
194
|
function textBlock(t) { return { type: 'text', text: String(t) } }
|
|
@@ -150,15 +205,57 @@ export function apply(ctx) {
|
|
|
150
205
|
function getPolicy() { const sp = sandboxPolicyOf(); if (!sp) { warnNoPolicyOnce(); return undefined } try { if (rootAgent && rootAgent.session) return sp.resolve({ session: rootAgent.session }) } catch (e) { warnNoPolicyOnce() } try { const p = sp.resolve({}); if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v2: falling back to sandboxPolicy.resolve({}) — the fence root is the host-configured workspace, not necessarily this session cwd') } return p } catch (e) { warnNoPolicyOnce() } return undefined }
|
|
151
206
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
152
207
|
|
|
208
|
+
// ================= capability / tool-name lists =================
|
|
209
|
+
// Tool names for the permission filter, taken from the names the host ACTUALLY
|
|
210
|
+
// registers (dsh-tool-web registers 'web_search'/'web_fetch'; 'web'/'fetch' are
|
|
211
|
+
// only presentation card/kind fields, not tool names), and split by platform
|
|
212
|
+
// because each preset's composition gates them:
|
|
213
|
+
// dsh-tool-bash disabled: process.platform === 'win32'
|
|
214
|
+
// dsh-tool-pwsh disabled: process.platform !== 'win32'
|
|
215
|
+
// dsh-tools' restrict() THROWS on any name outside its registered set, and the
|
|
216
|
+
// host applies the filter when establishing a continuable child
|
|
217
|
+
// (dsh-subagent: childCtx.tools.restrict(...)), so a stale name meant the child
|
|
218
|
+
// was never created at all.
|
|
219
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
220
|
+
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
221
|
+
// 这两个名字是否真的存在取决于**本次组合**:v2 自己的 agent.cordis.yml 把 tool-web 行设成
|
|
222
|
+
// fetch:false,于是 web_search 在、web_fetch 不在,而 filter 里只要有一个宿主不注册的名字,
|
|
223
|
+
// restrict() 就整条拒绝 → 子代理先失败一次再走 sanitizeToolFilter 重试,日志还会谎称"你的配置过期"
|
|
224
|
+
// (审计 M8/M9)。所以能力清单**向宿主的可见工具面查询**(tools.schemas,宿主自己在
|
|
225
|
+
// dsh-tools/lib/index.js 里就是这么用的),查不到/不可信时才退回静态候选表。
|
|
226
|
+
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
227
|
+
/**
|
|
228
|
+
* 本会话可见的工具名集合;null = 无法可信地确定(此时一律保留候选名,行为与改动前一致)。
|
|
229
|
+
* 自校验:这张表必须包含本插件自己注册的工具,否则说明这个 scope 视图不是子代理真正看到的
|
|
230
|
+
* 组合面(例如只返回全局层)——据它裁剪能力清单会**静默丢掉**用户要的工具,比多一次重试更糟。
|
|
231
|
+
*/
|
|
232
|
+
function composedToolNames() {
|
|
233
|
+
try {
|
|
234
|
+
if (!tools || typeof tools.schemas !== 'function' || !rootAgent) return null
|
|
235
|
+
const list = tools.schemas(rootAgent)
|
|
236
|
+
if (!Array.isArray(list) || list.length === 0) return null
|
|
237
|
+
const names = new Set()
|
|
238
|
+
for (let i = 0; i < list.length; i++) { const n = list[i] && list[i].name; if (n) names.add(String(n)) }
|
|
239
|
+
if (!names.has('vibe_math_status')) return null
|
|
240
|
+
return names
|
|
241
|
+
} catch (e) { return null }
|
|
242
|
+
}
|
|
243
|
+
/** 候选工具名 ∩ 本次组合真正注册的名字(不确定时原样返回候选)。 */
|
|
244
|
+
function composedToolList(candidates) {
|
|
245
|
+
const names = composedToolNames()
|
|
246
|
+
if (!names) return candidates.slice()
|
|
247
|
+
return candidates.filter(function (n) { return names.has(n) })
|
|
248
|
+
}
|
|
249
|
+
|
|
153
250
|
// ================= parameter schema =================
|
|
154
251
|
const PARAM_SCHEMA = [
|
|
155
252
|
{ name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
|
|
156
253
|
{ name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
|
|
157
254
|
{ name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
|
|
158
255
|
{ name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向(互不干扰);N>1 = 额外附带最多 N 个其他活跃方向摘要用于协调', suggestion: 1 },
|
|
159
|
-
{ name: 'verifierCount', type: 'integer', description: '
|
|
256
|
+
{ name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量(下限 2:一票不裁决)。并发预算紧张时单个对象同时最多占用 maxParallelThreshold-2 个槽位(至少 2),以保证另一个对象也凑得齐 2 票', suggestion: 3 },
|
|
160
257
|
{ name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
|
|
161
|
-
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat =
|
|
258
|
+
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致时取各评审自报概率的**均值**,绝不折叠成固定的 0.5);forced = 强制裁决(按验证者历史准确率+布尔票置信度加权)。准确率按 provider/model 这种稳定身份记,且只在对象**后来**取得布尔定论时才计分', suggestion: 'flat' },
|
|
162
259
|
{ name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
163
260
|
{ name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
164
261
|
{ name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
|
|
@@ -169,19 +266,19 @@ export function apply(ctx) {
|
|
|
169
266
|
{ name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
|
|
170
267
|
{ name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
|
|
171
268
|
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
172
|
-
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow
|
|
269
|
+
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow 列表时补入本次组合真正注册的网络工具);false=禁止网络工具', suggestion: '' },
|
|
173
270
|
{ name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关(同 solverAllowNetwork)', suggestion: '' },
|
|
174
|
-
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止
|
|
271
|
+
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止 ' + SCRIPT_TOOLS.join('/'), suggestion: '' },
|
|
175
272
|
{ name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关(同 solverAllowScripts)', suggestion: '' },
|
|
176
|
-
{ name: 'solverMaxToolCalls', type: 'integer', description: '
|
|
177
|
-
{ name: 'verifierMaxToolCalls', type: 'integer', description: '
|
|
273
|
+
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
274
|
+
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
178
275
|
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
|
|
179
276
|
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
|
|
180
277
|
{ name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
|
|
181
278
|
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
|
|
182
279
|
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
|
|
183
280
|
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
|
|
184
|
-
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 report
|
|
281
|
+
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 status/report 里 recentActivity 的细节量,两者最多显示 ' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },
|
|
185
282
|
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
|
|
186
283
|
{ name: 'formalVerify', type: 'enum', options: ['off', 'encourage', 'require'], description: 'Lean 形式化验证档位:off=不额外要求(默认,提示词里不出现 Lean);encourage=鼓励按实现难度自行形式化,一旦 Lean 通过则验证重点转为「忠实性审查」;require=同 encourage 且加门禁——对象的 formal.status 未达到 passed/blocked 前,真/假裁定记为未定论(原因 formal-required)并写入 Formal/TODO.md', suggestion: 'off' },
|
|
187
284
|
{ name: 'leanCommand', type: 'string', description: '要执行的 Lean 可执行文件(默认 lean;用 lake 时配合 leanArgs=["env","lean"])', suggestion: 'lean' },
|
|
@@ -263,6 +360,20 @@ export function apply(ctx) {
|
|
|
263
360
|
return { ok: outcome.exitCode === 0, exitCode: outcome.exitCode }
|
|
264
361
|
} catch (e) { return { ok: false, error: String((e && e.message) || e) } }
|
|
265
362
|
}
|
|
363
|
+
/**
|
|
364
|
+
* `runShell` 已经**明确告诉我们失败原因**('no-subprocess' / spawn 异常 / 非零退出码),但它的
|
|
365
|
+
* 返回值此前无人查看:没有 subprocess 服务的宿主上目录树根本不会建、归档证明的删除静默降级,而
|
|
366
|
+
* 日志里一个字都没有——"mkdir 失败"和"mkdir 成功"完全无法区分(审计 H5)。
|
|
367
|
+
* 这里把每个调用点的失败**如实记一次**(按 调用点+原因 去重,避免每 tick 刷屏)。
|
|
368
|
+
*/
|
|
369
|
+
const shellWarned = {}
|
|
370
|
+
function warnShellOnce(where, r) {
|
|
371
|
+
const why = (r && (r.error || (r.exitCode === undefined ? undefined : ('exit code ' + r.exitCode)))) || 'unknown failure'
|
|
372
|
+
const key = String(where) + '|' + why
|
|
373
|
+
if (shellWarned[key]) return
|
|
374
|
+
shellWarned[key] = true
|
|
375
|
+
console.error('vibe-math-v2: ' + where + ' failed (' + why + ')')
|
|
376
|
+
}
|
|
266
377
|
// 目录布局(docs/formal-verification.md §3):
|
|
267
378
|
// 项目内:Formal/(对象形式化工作文件 + Index.md + TODO.md)、Verified/Lean/(归档证明)
|
|
268
379
|
// 全局(**不在项目内**,跨项目复用):<VibeMath 根>/Formal/Lib/(可复用定义)、Formal/Proved/(已证引理)
|
|
@@ -270,9 +381,16 @@ export function apply(ctx) {
|
|
|
270
381
|
const base = frameworkRoot()
|
|
271
382
|
const dirs = ['qs', 'Propos', 'Reliable', 'Verified', 'Verified/Lean', 'Verification_logs', 'Progress_Logs', 'VibeMath_State', 'Formal']
|
|
272
383
|
const paths = [vibeRoot() + '/Projects', vibeRoot() + '/Formal/Lib', vibeRoot() + '/Formal/Proved'].concat(dirs.map(function (d) { return base + '/' + d }))
|
|
273
|
-
|
|
384
|
+
const r = await runShell(mkdirCmd(paths))
|
|
385
|
+
if (!r || !r.ok) warnShellOnce('ensureDirs (mkdir ' + base + ')', r)
|
|
386
|
+
return r
|
|
387
|
+
}
|
|
388
|
+
async function removeFile(rel) {
|
|
389
|
+
const base = frameworkRoot()
|
|
390
|
+
const r = await runShell(rmCmd(base + '/' + rel))
|
|
391
|
+
if (!r || !r.ok) warnShellOnce('removeFile(' + rel + ')', r)
|
|
392
|
+
return r
|
|
274
393
|
}
|
|
275
|
-
async function removeFile(rel) { const base = frameworkRoot(); return await runShell(rmCmd(base + '/' + rel)) }
|
|
276
394
|
|
|
277
395
|
// ================= settings =================
|
|
278
396
|
function sanitizeParams(obj) {
|
|
@@ -379,13 +497,30 @@ export function apply(ctx) {
|
|
|
379
497
|
* 从 registry 长度推导后,两者不可能不一致,"丢一次 end 就永久停摆"这一类故障从根上消失。
|
|
380
498
|
*/
|
|
381
499
|
function activeCount() { return Object.keys(agentRegistry).length }
|
|
500
|
+
/**
|
|
501
|
+
* 只保留**稳定身份键**('m:…')的准确率记录:老版本按一次性的 childId 记的那份数据是自指的
|
|
502
|
+
* (拿聚合值给同一批评审打分),既无意义又会随历史子代理数无界增长(审计 H4/M13)。
|
|
503
|
+
*/
|
|
504
|
+
function sanitizeAccuracy(va) {
|
|
505
|
+
const out = {}
|
|
506
|
+
if (!va || typeof va !== 'object' || Array.isArray(va)) return out
|
|
507
|
+
for (const k of Object.keys(va)) {
|
|
508
|
+
if (k.indexOf('m:') !== 0) continue
|
|
509
|
+
const rec = va[k]
|
|
510
|
+
if (!rec || typeof rec !== 'object') continue
|
|
511
|
+
const correct = Number(rec.correct); const total = Number(rec.total)
|
|
512
|
+
if (!Number.isFinite(correct) || !Number.isFinite(total) || total < 0) continue
|
|
513
|
+
out[k] = { correct: Math.max(0, correct), total: Math.max(0, total) }
|
|
514
|
+
}
|
|
515
|
+
return out
|
|
516
|
+
}
|
|
382
517
|
async function loadState() {
|
|
383
518
|
const s = await readJson('VibeMath_State/scheduler_state.json')
|
|
384
519
|
// 丢弃历史持久化的 activeCount:旧值可能已经漂移,绝不能覆盖推导值(见 activeCount())。
|
|
385
520
|
if (s) { const restored = Object.assign({}, s); delete restored.activeCount; scheduler = Object.assign({}, scheduler, restored) }
|
|
386
521
|
const r = await readJson('VibeMath_State/agent_registry.json'); if (r) agentRegistry = r
|
|
387
522
|
const dq = await readJson('VibeMath_State/decision_queue.json'); if (dq) decisionQueue = dq
|
|
388
|
-
const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = va
|
|
523
|
+
const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = sanitizeAccuracy(va)
|
|
389
524
|
const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk
|
|
390
525
|
const er = await readJson('VibeMath_State/explorer_retries.json'); if (er) explorerRetries = er
|
|
391
526
|
// 形式化记录(docs/formal-verification.md §4):v2 没有会话投影,这条状态必须自己持久化,
|
|
@@ -637,6 +772,9 @@ export function apply(ctx) {
|
|
|
637
772
|
* 调用会把 `objectId: 'r-pAlias'` 写进记录,之后(任务表已不在内存时)这个错误的锚点反而会覆盖
|
|
638
773
|
* 正确的后缀解析结果。
|
|
639
774
|
*/
|
|
775
|
+
// 已加载的对象 id 集合(qs.id + propos.id),供 formalObjectIdOf 的后缀歧义消解使用(审计 L19)。
|
|
776
|
+
// 只增不清:一次读库把对象记进来,即使该对象随后被删/重命名,最坏也只是多保留一个字符串。
|
|
777
|
+
const knownObjectIds = new Set()
|
|
640
778
|
function formalObjectIdOf(id) {
|
|
641
779
|
const t = safeId(String(id == null ? '' : id))
|
|
642
780
|
const task = tasks['verify:' + t]
|
|
@@ -647,7 +785,16 @@ export function apply(ctx) {
|
|
|
647
785
|
const rec = formalRecords()[t]
|
|
648
786
|
if (rec && rec.objectId && formalObjectIdOfIsOwner(rec.objectId)) return safeId(rec.objectId)
|
|
649
787
|
const m = /^r-(.+?)(?:-(?:s\d+|pf\d+|rf\d+))?$/.exec(t)
|
|
650
|
-
|
|
788
|
+
if (!m) return t
|
|
789
|
+
// 后缀剥离是**有损**映射:`r-pAmb-s1` 既可能是"对象 pAmb 的第 1 个解法",也可能是"对象 pAmb-s1"
|
|
790
|
+
// 本身(对象 id 允许以 `-sN`/`-pfN`/`-rfN` 结尾)。惰性匹配总是剥掉后缀,于是后一种情形会被
|
|
791
|
+
// 解析成同前缀的**邻居对象**——一次 defect 就会降级邻居、并撤回它自己的归档证明(审计 L19)。
|
|
792
|
+
// 这里只在"完整串确实是已加载的对象 id"时才优先完整串;否则保持原有的后缀剥离(legacy 记录没有
|
|
793
|
+
// objectId 时这是唯一线索)。knownObjectIds 由 getQs/getPropos 在每次读库时刷新,未读过库时为空
|
|
794
|
+
// ⇒ 行为与从前完全一致。
|
|
795
|
+
const full = t.slice(2)
|
|
796
|
+
if (m[1] !== full && knownObjectIds.has(full)) return full
|
|
797
|
+
return m[1]
|
|
651
798
|
}
|
|
652
799
|
/** 一个 id 能否作为"对象 id"落进记录:它不能再被解析成别的 id(对象 id 不以 `r-` 开头)。 */
|
|
653
800
|
function formalObjectIdOfIsOwner(v) {
|
|
@@ -1147,7 +1294,7 @@ export function apply(ctx) {
|
|
|
1147
1294
|
}
|
|
1148
1295
|
|
|
1149
1296
|
// ================= data layer: qs.json =================
|
|
1150
|
-
async function getQs() { const a = await readJson('qs/qs.json');
|
|
1297
|
+
async function getQs() { const a = await readJson('qs/qs.json'); const list = Array.isArray(a) ? a : []; for (const q of list) { if (q && q.id) knownObjectIds.add(safeId(String(q.id))) } return list }
|
|
1151
1298
|
async function writeQs(list) { await writeJson('qs/qs.json', list) }
|
|
1152
1299
|
async function findQ(qid) { const qs = await getQs(); return qs.find(function (q) { return q.id === qid }) }
|
|
1153
1300
|
|
|
@@ -1174,7 +1321,7 @@ export function apply(ctx) {
|
|
|
1174
1321
|
const fname = files[i]
|
|
1175
1322
|
const cat = fname.replace(/_Propos\.json$/i, '')
|
|
1176
1323
|
const list = await readProposCategory(cat)
|
|
1177
|
-
for (let j = 0; j < list.length; j++) { list[j]._category = cat; out.push(list[j]) }
|
|
1324
|
+
for (let j = 0; j < list.length; j++) { list[j]._category = cat; if (list[j] && list[j].id) knownObjectIds.add(safeId(String(list[j].id))); out.push(list[j]) }
|
|
1178
1325
|
}
|
|
1179
1326
|
return out
|
|
1180
1327
|
}
|
|
@@ -1212,7 +1359,7 @@ export function apply(ctx) {
|
|
|
1212
1359
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
1213
1360
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
1214
1361
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
1215
|
-
recentActivity: activityLog.slice(-Math.min(
|
|
1362
|
+
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)),
|
|
1216
1363
|
// Lean 形式化:可调档位与开关同处可读参数表(契约 §1),并附当前形式化记录概况。
|
|
1217
1364
|
formal: {
|
|
1218
1365
|
mode: formalMode(), required: formalRequired(),
|
|
@@ -1249,7 +1396,10 @@ export function apply(ctx) {
|
|
|
1249
1396
|
',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
|
|
1250
1397
|
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions.length + '。' +
|
|
1251
1398
|
'请调用 vibe_math_report 汇总当前进展及各代理状态,并用人话简要汇报(不打断用户,简短即可)。'
|
|
1252
|
-
|
|
1399
|
+
// 来源 kind 必须是**已声明**的:`MessageSourceMap` 是 merge-extensible 的联合,但没有共享的
|
|
1400
|
+
// catch-all `plugin` kind(dsh-llm message.d.ts),{kind:'plugin'} 是契约外形状。role 本来就是
|
|
1401
|
+
// 'user',正文也自带 "[Vibe Math V2] 进度更新" 的真署名,所以用核心声明的 {kind:'user'}。
|
|
1402
|
+
rootAgent.followup({ id: uuid(), role: 'user', content: [textBlock(text)], source: { kind: 'user' } })
|
|
1253
1403
|
lastPushReport = now()
|
|
1254
1404
|
} catch (e) {
|
|
1255
1405
|
console.error('vibe-math-v2: push report failed: ' + String((e && e.message) || e))
|
|
@@ -1259,28 +1409,13 @@ export function apply(ctx) {
|
|
|
1259
1409
|
// ================= child spawn / followup =================
|
|
1260
1410
|
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' }
|
|
1261
1411
|
function childAgentOptions() { const o = {}; try { if (rootAgent && rootAgent.options) { if (rootAgent.options.provider) o.provider = rootAgent.options.provider; if (rootAgent.options.model) o.model = rootAgent.options.model } } catch (e) {} if (params.provider) o.provider = params.provider; if (params.model) o.model = params.model; return o }
|
|
1262
|
-
// Tool names for the permission filter, taken from the names the host ACTUALLY
|
|
1263
|
-
// registers (dsh-tool-web registers 'web_search'/'web_fetch'; 'web'/'fetch' are
|
|
1264
|
-
// only presentation card/kind fields, not tool names), and split by platform
|
|
1265
|
-
// because each preset's composition gates them:
|
|
1266
|
-
// dsh-tool-bash disabled: process.platform === 'win32'
|
|
1267
|
-
// dsh-tool-pwsh disabled: process.platform !== 'win32'
|
|
1268
|
-
// dsh-tools' restrict() THROWS on any name outside its registered set, and the
|
|
1269
|
-
// host applies the filter when establishing a continuable child
|
|
1270
|
-
// (dsh-subagent: childCtx.tools.restrict(...)), so a stale name meant the child
|
|
1271
|
-
// was never created at all.
|
|
1272
|
-
const IS_WINDOWS = process.platform === 'win32'
|
|
1273
|
-
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
1274
|
-
// 'web_fetch' is only registered when the composition enables fetch (the v4
|
|
1275
|
-
// preset sets `fetch: false`), so it is a candidate that sanitizeToolFilter drops.
|
|
1276
|
-
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
1277
1412
|
/**
|
|
1278
1413
|
* Drop filter names this host does not register. `known` comes from the host's
|
|
1279
1414
|
* own rejection message, which lists every registered global tool, so this
|
|
1280
1415
|
* never guesses. Returns undefined when nothing usable remains.
|
|
1281
1416
|
*/
|
|
1282
1417
|
/** The host names the offending tools and then lists the registered ones. */
|
|
1283
|
-
function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; if (net === false) d = d.concat(
|
|
1418
|
+
function buildToolFilter(role) { const allow = role === 'solver' ? params.solverToolAllow : role === 'verifier' ? params.verifierToolAllow : undefined; const deny = role === 'solver' ? params.solverToolDeny : role === 'verifier' ? params.verifierToolDeny : undefined; const net = role === 'solver' ? params.solverAllowNetwork : role === 'verifier' ? params.verifierAllowNetwork : undefined; const scr = role === 'solver' ? params.solverAllowScripts : role === 'verifier' ? params.verifierAllowScripts : undefined; let a = Array.isArray(allow) ? allow.slice() : []; let d = Array.isArray(deny) ? deny.slice() : []; const netTools = composedToolList(NETWORK_TOOLS); const scrTools = composedToolList(SCRIPT_TOOLS); if (net === false) d = d.concat(netTools); else if (net === true && a.length > 0) a = a.concat(netTools); if (scr === false) d = d.concat(scrTools); else if (scr === true && a.length > 0) a = a.concat(scrTools); const f = {}; if (a.length > 0) f.allow = a; if (d.length > 0) f.deny = d; return (f.allow || f.deny) ? f : undefined }
|
|
1284
1419
|
async function spawnChild(label, promptText, meta) {
|
|
1285
1420
|
const request = { prompt: [textBlock(promptText)], parent: rootAgent, agentOptions: childAgentOptions() }
|
|
1286
1421
|
const tf = buildToolFilter(meta && meta.role); if (tf) request.toolFilter = tf
|
|
@@ -1319,7 +1454,10 @@ export function apply(ctx) {
|
|
|
1319
1454
|
throw e2
|
|
1320
1455
|
}
|
|
1321
1456
|
}
|
|
1322
|
-
|
|
1457
|
+
const registered = Object.assign({ createdAt: now() }, meta || {})
|
|
1458
|
+
// 验证者的**稳定身份**随 meta 一起登记:forced 模式的历史准确率必须能跨 child 复用(审计 H4)。
|
|
1459
|
+
if (registered.role === 'verifier' && !registered.verificationKey) registered.verificationKey = verifierIdentityKey()
|
|
1460
|
+
agentRegistry[started.childId] = registered
|
|
1323
1461
|
childOwner.set(started.childId, sessionId)
|
|
1324
1462
|
// 并发计数由 agentRegistry 推导,此处无需手工 +1(见 activeCount())。
|
|
1325
1463
|
await saveAll(); return started.childId
|
|
@@ -1389,9 +1527,9 @@ export function apply(ctx) {
|
|
|
1389
1527
|
if (netOn !== false) toolParts.push('web search / literature lookup')
|
|
1390
1528
|
if (scrOn !== false) toolParts.push('symbolic/numeric computation (running scripts)')
|
|
1391
1529
|
let t = '\nYOUR PERMISSIONS / CAPABILITIES:\n'
|
|
1392
|
-
t += '- Network tools (web search / fetch): ' + (netOn === false ? 'DISABLED for you' : 'available') + '; Script/shell tools (
|
|
1530
|
+
t += '- Network tools (web search / fetch): ' + (netOn === false ? 'DISABLED for you' : 'available') + '; Script/shell tools (' + SCRIPT_TOOLS.join('/') + '): ' + (scrOn === false ? 'DISABLED for you' : 'available') + ' (your actual tool list is enforced by the framework).\n'
|
|
1393
1531
|
t += toolParts.length > 0
|
|
1394
|
-
? ('- You may use external tools (' + toolParts.join(', ') + ') to assist; ' + ((maxCalls && Number(maxCalls) > 0) ? ('
|
|
1532
|
+
? ('- You may use external tools (' + toolParts.join(', ') + ') to assist; ' + ((maxCalls && Number(maxCalls) > 0) ? ('as a guideline, keep external tool calls to about ' + maxCalls + ' this round (advisory: the framework does not enforce a hard quota).\n') : 'no per-round limit by default.\n'))
|
|
1395
1533
|
: '- External tools: none enabled for you this round.\n'
|
|
1396
1534
|
t += '- You may READ any file under Verified/ as a known, trusted dependency (resolved facts).\n'
|
|
1397
1535
|
t += '- You should BASE your reasoning on the existing knowledge under Propos/ (propositions with proofs/refutations and probabilities) and Reliable/ (trusted references).\n'
|
|
@@ -1510,12 +1648,23 @@ export function apply(ctx) {
|
|
|
1510
1648
|
}
|
|
1511
1649
|
return '\n' + L.join('\n') + '\n'
|
|
1512
1650
|
}
|
|
1651
|
+
/**
|
|
1652
|
+
* 需求 5:依赖临时假设的结论必须把假设**显式**写出来("若 p_{q-tmp} 成立,则…")。记录在命题上
|
|
1653
|
+
* (`依赖假设`)之后,还要把它**送到验证者眼前**——否则验证者会在不知道前提未定的情况下裁决它。
|
|
1654
|
+
*/
|
|
1655
|
+
function assumptionNote(r) {
|
|
1656
|
+
if (!r || !r.依赖假设) return ''
|
|
1657
|
+
return '\nDEPENDENCY: this conclusion is stated UNDER the temporary assumption ' + r.依赖假设
|
|
1658
|
+
+ '(' + (r.依赖假设已证伪 ? '该假设**已被证伪**:依赖它的结论必须重新审视' : '该假设尚未定论,0 < 布尔估计 < 1') + ')。'
|
|
1659
|
+
+ ' The assumption is a SEPARATE object: a verdict on this conclusion is NOT a verdict on the assumption.\n'
|
|
1660
|
+
}
|
|
1513
1661
|
function verifierReviewPrompt(r) {
|
|
1514
1662
|
let target = ''
|
|
1515
1663
|
if (r.kind === 'proposition') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述
|
|
1516
1664
|
else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
|
|
1517
1665
|
else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
|
|
1518
1666
|
return verifierPersonaText() + 'You are a STRICT peer reviewer verifying one mathematical object. Check it multiple times.\n\nTARGET (r: ' + r.kind + '):\n' + target + '\n' +
|
|
1667
|
+
assumptionNote(r) +
|
|
1519
1668
|
knowledgeContextText() +
|
|
1520
1669
|
capabilitiesText('verifier') +
|
|
1521
1670
|
formalVerifySection(r) +
|
|
@@ -1529,6 +1678,7 @@ export function apply(ctx) {
|
|
|
1529
1678
|
else if (r.kind === 'prop-proof') target = 'PROPOSITION (id: ' + r.pId + '): ' + r.概述 + '\n' + r.side + ' PROCESS TO CHECK:\n' + r.process
|
|
1530
1679
|
else target = 'PROBLEM (id: ' + r.qid + '): ' + r.概述 + '\nSOLUTION TO CHECK:\n' + r.process
|
|
1531
1680
|
return verifierPersonaText() + 'You are one reviewer in a DEBATE ("交流群") about this object.\n\nTARGET:\n' + target + '\n' +
|
|
1681
|
+
assumptionNote(r) +
|
|
1532
1682
|
knowledgeContextText() +
|
|
1533
1683
|
capabilitiesText('verifier') +
|
|
1534
1684
|
formalVerifySection(r) +
|
|
@@ -1556,10 +1706,29 @@ export function apply(ctx) {
|
|
|
1556
1706
|
} catch (e) { console.error('vibe-math-v2: spawn reject mark failed: ' + String((e && e.message) || e)) }
|
|
1557
1707
|
return { spawned: false, rejected: true }
|
|
1558
1708
|
}
|
|
1559
|
-
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 } }
|
|
1709
|
+
if (node === 'verdict') { const overridden = resolution.action === 'override' && (resolution.verdict === 1 || resolution.verdict === 0); const v = overridden ? Number(resolution.verdict) : data.verdict; const applied = await settleVerdict(data.task, v); delete tasks[data.task.id]; return { verdict: v, overridden: overridden, applied: applied } }
|
|
1560
1710
|
return {}
|
|
1561
1711
|
}
|
|
1562
1712
|
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' } }
|
|
1713
|
+
/**
|
|
1714
|
+
* 清掉 gate 时**必须结清它指向的那个人工决策**(审计 M10)。start/resume/abort/切项目都会离开
|
|
1715
|
+
* 需要那次决策的运行,而决策若仍是 `pending`,它会永远留在 `vibe_math_list_decisions` 里反复出现——
|
|
1716
|
+
* 一个已经无人等待的节点看起来仍然等待人工输入。与 `dropStaleGate` 互补:那条处理"决策没了但 gate
|
|
1717
|
+
* 还在",这条处理"gate 没了但决策还在"。
|
|
1718
|
+
* 注意 `pauseScheduler` **不**清 gate:暂停期间那次决策仍然有效(人还可以 vibe_math_decide),
|
|
1719
|
+
* 只有真正离开它(resume/abort/重启/换项目)才作废;作废是终态,不会再回到 pending。
|
|
1720
|
+
*/
|
|
1721
|
+
function abandonGatedDecision(why) {
|
|
1722
|
+
const g = scheduler.gate
|
|
1723
|
+
scheduler.gate = null
|
|
1724
|
+
if (!g) return
|
|
1725
|
+
const d = decisionQueue.find(function (x) { return x.id === g.decisionId })
|
|
1726
|
+
if (d && d.status === 'pending') {
|
|
1727
|
+
d.status = 'resolved'
|
|
1728
|
+
d.resolution = { action: 'abandoned', reason: why }
|
|
1729
|
+
logActivity('gate', 'gated decision ' + g.node + '/' + d.id + ' 已作废(' + why + ')——不再挂起等待人工输入')
|
|
1730
|
+
}
|
|
1731
|
+
}
|
|
1563
1732
|
|
|
1564
1733
|
// ================= scheduler core =================
|
|
1565
1734
|
function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v2 tick error: ' + String((e && e.stack) || e)) }) }
|
|
@@ -1648,6 +1817,8 @@ export function apply(ctx) {
|
|
|
1648
1817
|
cardWritten = cardOk(card)
|
|
1649
1818
|
}
|
|
1650
1819
|
if (targetBE !== null && p.布尔估计 !== targetBE) { p.布尔估计 = targetBE; pChanged = true }
|
|
1820
|
+
// 这个对象**现在**取得了布尔定论:用它给此前那些还没被检验过的评审回溯计分(forced 权重)。
|
|
1821
|
+
if (targetBE !== null) await scorePendingReviews(p.id, targetBE)
|
|
1651
1822
|
if ((p.布尔估计 === 1 || p.布尔估计 === 0) && p.优先级 !== 'never') { p.优先级 = 'never'; pChanged = true }
|
|
1652
1823
|
if (cardWritten) pChanged = true
|
|
1653
1824
|
if (p.布尔估计 === 1 || p.布尔估计 === 0) {
|
|
@@ -1659,6 +1830,21 @@ export function apply(ctx) {
|
|
|
1659
1830
|
else if (String((qj.progress && typeof qj.progress === 'object' ? (qj.progress.来源命题 || '') : qj.progress) || '').indexOf(p.id) !== -1) { qj.已解决 = true; qj.优先级 = 'never'; closedPromoted = true }
|
|
1660
1831
|
}
|
|
1661
1832
|
}
|
|
1833
|
+
// 需求 5 第二半:「若该假设被证伪,则依赖它的主线结论需重新审视」。假设的布尔估计被判定为 0 时,
|
|
1834
|
+
// 把依赖它的结论标记出来并**重新放回验证候选**(清掉 已验 / 冷却时间戳),否则那句承诺只是一句
|
|
1835
|
+
// 空话——依赖关系记下了却没人据此行动(审计 M16)。
|
|
1836
|
+
if (p.布尔估计 === 0) {
|
|
1837
|
+
for (let j = 0; j < propos.length; j++) {
|
|
1838
|
+
const depP = propos[j]
|
|
1839
|
+
if (depP === p || depP.id === p.id) continue
|
|
1840
|
+
if (depP.依赖假设 !== p.id || depP.依赖假设已证伪) continue
|
|
1841
|
+
depP.依赖假设已证伪 = true
|
|
1842
|
+
depP.已验 = false
|
|
1843
|
+
depP.最近验证时间 = 0
|
|
1844
|
+
await upsertProposition(depP)
|
|
1845
|
+
logActivity('dependency', '假设 ' + p.id + ' 被证伪:依赖它的结论 ' + depP.id + ' 标记为需重新审视,已重新进入验证候选')
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1662
1848
|
if (pChanged) await upsertProposition(p)
|
|
1663
1849
|
}
|
|
1664
1850
|
if (closedPromoted) { await writeQs(qs); logActivity('update', 'promoted problems closed because their source proposition resolved') }
|
|
@@ -1733,8 +1919,18 @@ export function apply(ctx) {
|
|
|
1733
1919
|
return // one per tick is enough
|
|
1734
1920
|
}
|
|
1735
1921
|
}
|
|
1922
|
+
/**
|
|
1923
|
+
* 临时假设的「判断问题」是**辅助对象**(审计 C2 的种子解法让它可验证)。它的解法只在主线安静时
|
|
1924
|
+
* 入选验证:一个 q_sub 会新增两个问题,若它的验证与主线同时抢并发槽位,默认组合
|
|
1925
|
+
* (maxParallelThreshold=4 / verifierCount=3)下主线连 2 个验证器都派不出来——实测 e2e-business 的
|
|
1926
|
+
* "verifiers spawned for solution" 会从 3 掉到 1。辅助对象让主线先走;主线一旦没有别的可验证对象、
|
|
1927
|
+
* 也没有 explorer/solver 在跑,它立刻轮到(所以 p_{q-tmp} 不再永远停在 0.5)。
|
|
1928
|
+
*/
|
|
1929
|
+
function busySolving() { return Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && (m.role === 'explorer' || m.role === 'solver') }) }
|
|
1736
1930
|
async function processVerify() {
|
|
1737
|
-
const
|
|
1931
|
+
const all = await buildVerifyCandidates()
|
|
1932
|
+
let candidates = all.filter(function (c) { return !c.auxAssumption })
|
|
1933
|
+
if (candidates.length === 0 && !busySolving()) candidates = all.filter(function (c) { return c.auxAssumption })
|
|
1738
1934
|
for (let i = 0; i < candidates.length; i++) {
|
|
1739
1935
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
1740
1936
|
const c = candidates[i]
|
|
@@ -1742,7 +1938,7 @@ export function apply(ctx) {
|
|
|
1742
1938
|
if (tasks['verify:' + rId]) continue
|
|
1743
1939
|
const inflight = Object.keys(agentRegistry).some(function (cid) { const m = agentRegistry[cid]; return m && m.role === 'verifier' && m.rId === rId })
|
|
1744
1940
|
if (inflight) continue
|
|
1745
|
-
tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(
|
|
1941
|
+
tasks['verify:' + rId] = { id: 'verify:' + rId, type: 'verify', r: c, rId: rId, status: 'spawning', children: [], childResults: {}, history: [], round: 1, expectedCount: Math.max(MIN_REVIEWERS, params.verifierCount), createdAt: now() }
|
|
1746
1942
|
await saveAll()
|
|
1747
1943
|
return // one verification at a time keeps scheduling simple; tick will continue next pass
|
|
1748
1944
|
}
|
|
@@ -1756,31 +1952,49 @@ export function apply(ctx) {
|
|
|
1756
1952
|
const sols = q.解法列表 || []
|
|
1757
1953
|
for (let j = 0; j < sols.length; j++) {
|
|
1758
1954
|
const s = sols[j]
|
|
1759
|
-
if (s.正确概率 === 1 || s.正确概率 === 0
|
|
1955
|
+
if (s.正确概率 === 1 || s.正确概率 === 0) continue
|
|
1956
|
+
// 已验的条目只在**中段值**(0<p<1)且冷却期已过时重新入选(审计 M12:中间概率不是定论,
|
|
1957
|
+
// 对象不能被永久搁置;`最近验证时间` 由 settleVerdict 在中段裁决时写下)。
|
|
1958
|
+
if (s.已验 && !reverifyDue(s)) continue
|
|
1760
1959
|
if (!String(s.完整解法 || '').trim()) continue
|
|
1761
|
-
out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级) })
|
|
1960
|
+
out.push({ rId: 'r-' + q.id + '-s' + j, kind: 'problem-solution', qid: q.id, 概述: q.概述, process: s.完整解法 || '', idx: j, prob: Number(s.正确概率) || 0, priority: q.优先级 === 'never' ? 999 : Number(q.优先级), 判断命题: q.判断命题, auxAssumption: s.来源 === 'sub-question-assumption' })
|
|
1762
1961
|
}
|
|
1763
1962
|
}
|
|
1764
1963
|
const propos = await getPropos()
|
|
1765
1964
|
for (let i = 0; i < propos.length; i++) {
|
|
1766
1965
|
const p = propos[i]
|
|
1767
1966
|
if (p.布尔估计 === 1 || p.布尔估计 === 0 || p.优先级 === 'never') continue
|
|
1768
|
-
if (p.已验证) continue //
|
|
1967
|
+
if (p.已验证 && !reverifyDue(p)) continue // 收敛闸门:该命题已由「判断命题」解法裁决过;中段裁决只上锁到冷却期结束(见 settleVerdict 点5)
|
|
1769
1968
|
if (p.在问题清单) continue // 已晋升:其证明/证伪经晋升问题的解法验证,避免同一内容双重验证
|
|
1969
|
+
const dep = { 依赖假设: p.依赖假设, 依赖假设已证伪: p.依赖假设已证伪 }
|
|
1770
1970
|
const proofs = p.证明列表 || []; const refutes = p.证伪列表 || []
|
|
1771
1971
|
if (proofs.length === 0 && refutes.length === 0) {
|
|
1772
1972
|
if (String(p.id).indexOf('p-tmp-') === 0) continue // 临时假设由「判断下述命题是否成立:p_{q-tmp}」问题统一验证,避免裸命题验证双重路径
|
|
1773
|
-
out.push({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) })
|
|
1973
|
+
out.push(Object.assign({ rId: 'r-' + p.id, kind: 'proposition', pId: p.id, 概述: p.概述, prob: Number(p.布尔估计) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }, dep))
|
|
1774
1974
|
} else {
|
|
1775
|
-
for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || proofs[j].已验) continue; if (!String(proofs[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
1776
|
-
for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || refutes[j].已验) continue; if (!String(refutes[j].完整过程 || '').trim()) continue; out.push({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }) }
|
|
1975
|
+
for (let j = 0; j < proofs.length; j++) { if (proofs[j].正确概率 === 1 || proofs[j].正确概率 === 0 || (proofs[j].已验 && !reverifyDue(proofs[j]))) continue; if (!String(proofs[j].完整过程 || '').trim()) continue; out.push(Object.assign({ rId: 'r-' + p.id + '-pf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证明', process: proofs[j].完整过程 || '', idx: j, prob: Number(proofs[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }, dep)) }
|
|
1976
|
+
for (let j = 0; j < refutes.length; j++) { if (refutes[j].正确概率 === 1 || refutes[j].正确概率 === 0 || (refutes[j].已验 && !reverifyDue(refutes[j]))) continue; if (!String(refutes[j].完整过程 || '').trim()) continue; out.push(Object.assign({ rId: 'r-' + p.id + '-rf' + j, kind: 'prop-proof', pId: p.id, 概述: p.概述, side: '证伪', process: refutes[j].完整过程 || '', idx: j, prob: Number(refutes[j].正确概率) || 0, priority: p.优先级 === 'never' ? 999 : Number(p.优先级) }, dep)) }
|
|
1777
1977
|
}
|
|
1778
1978
|
}
|
|
1779
1979
|
out.sort(function (a, b) { if (a.priority !== b.priority) return a.priority - b.priority; return (b.prob || 0) - (a.prob || 0) })
|
|
1780
1980
|
return out
|
|
1781
1981
|
}
|
|
1982
|
+
/**
|
|
1983
|
+
* 每个验证对象最多**同时**占多少评审槽位(审计 M11 的资源侧)。
|
|
1984
|
+
*
|
|
1985
|
+
* 目标仍是 verifierCount 个独立评审,但并发预算必须给别的验证对象留下至少 MIN_REVIEWERS 个槽位:
|
|
1986
|
+
* 否则一个对象(例如某条引理的证明)就能把 maxParallelThreshold 吃光,后出现的对象(例如主问题的
|
|
1987
|
+
* 解法)只剩 1 票——而一票不允许裁决 ⇒ 它会被反复补派/搁置。容量宽裕时(maxParallelThreshold ≥
|
|
1988
|
+
* verifierCount + MIN_REVIEWERS)这就是 verifierCount,一个都不少。
|
|
1989
|
+
*/
|
|
1990
|
+
function reviewerCap() {
|
|
1991
|
+
const target = Math.max(MIN_REVIEWERS, Math.floor(Number(params.verifierCount) || MIN_REVIEWERS))
|
|
1992
|
+
const byThreshold = Math.max(MIN_REVIEWERS, (Number(params.maxParallelThreshold) || MIN_REVIEWERS) - MIN_REVIEWERS)
|
|
1993
|
+
return Math.max(MIN_REVIEWERS, Math.min(target, byThreshold))
|
|
1994
|
+
}
|
|
1782
1995
|
async function backfillVerifiers(t) {
|
|
1783
|
-
|
|
1996
|
+
const cap = reviewerCap()
|
|
1997
|
+
while (t.children.length < t.expectedCount && t.children.length < cap) {
|
|
1784
1998
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
1785
1999
|
const index = t.children.length
|
|
1786
2000
|
const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
|
|
@@ -1793,6 +2007,14 @@ export function apply(ctx) {
|
|
|
1793
2007
|
for (let i = 0; i < ids.length; i++) {
|
|
1794
2008
|
const t = tasks[ids[i]]
|
|
1795
2009
|
if (t.type !== 'verify') continue
|
|
2010
|
+
if (t.status === 'stalled') {
|
|
2011
|
+
// 票数补不足时任务被置为 stalled(见 finalizeVerification):冷却结束再重新派发,避免每 tick
|
|
2012
|
+
// 重建同一个对象、反复重试。任务本身保留,所以 processVerify 不会为同一个 rId 造出第二个任务。
|
|
2013
|
+
if (now() >= Number(t.retryAt || 0)) {
|
|
2014
|
+
t.status = 'spawning'; t.reviewerRespawn = 0; t.children = []; t.childResults = {}; t.round = 1
|
|
2015
|
+
logActivity('verify', t.rId + ' 冷却结束,重新派发验证器(凑齐 ≥' + MIN_REVIEWERS + ' 份独立评审)')
|
|
2016
|
+
} else continue
|
|
2017
|
+
}
|
|
1796
2018
|
if (t.status === 'paused') {
|
|
1797
2019
|
const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === t.round })
|
|
1798
2020
|
if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
|
|
@@ -1871,12 +2093,17 @@ export function apply(ctx) {
|
|
|
1871
2093
|
if (!parsed && !scheduler.running) { delete agentRegistry[childId]; return } // abort:不把方向标记为死路,保留待 resume
|
|
1872
2094
|
const status = (parsed && parsed.status) || statusFromStop(stopReason)
|
|
1873
2095
|
dir.round = meta.round
|
|
2096
|
+
// 需求 5 第二半的**记录**侧:本轮产出的引理若处在某条临时假设之下,就把假设 id 记在命题上
|
|
2097
|
+
// (`依赖假设`)。只用**本轮之前**已登记、且尚未被证伪的假设:本轮新报的 q_sub 不算(先有假设
|
|
2098
|
+
// 才有"依赖它的结论",同一轮里 lemma 可能先于该假设产生)。此前 assumeId 只被存进
|
|
2099
|
+
// dir.sub_questions 而从不被读取,"若 p_{q-tmp} 成立,则…"就只是一句写在提示词里的空话(审计 M16)。
|
|
2100
|
+
const activeAssumptions = await activeAssumptionsOf(dir)
|
|
1874
2101
|
if (parsed) {
|
|
1875
2102
|
if (parsed.routes) dir.routes = (dir.routes || []).concat(parsed.routes)
|
|
1876
2103
|
if (parsed.lessons) dir.lessons = (dir.lessons || []).concat(parsed.lessons)
|
|
1877
2104
|
if (parsed.dead_end_reason) dir.dead_end_reason = parsed.dead_end_reason
|
|
1878
2105
|
if (typeof parsed.survival_probability === 'number') dir.survival = clamp01(parsed.survival_probability)
|
|
1879
|
-
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 || '' }) } } }
|
|
2106
|
+
if (parsed.lemmas && parsed.lemmas.length) { for (let i = 0; i < parsed.lemmas.length; i++) { const lid = await addLemmaAsProposition(qid, parsed.lemmas[i], activeAssumptions.length ? activeAssumptions[activeAssumptions.length - 1] : undefined); if (lid) { dir.lemmas = dir.lemmas || []; dir.lemmas.push({ id: lid, title: parsed.lemmas[i].title || '' }) } } }
|
|
1880
2107
|
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) } } }
|
|
1881
2108
|
}
|
|
1882
2109
|
if (status === 'success') {
|
|
@@ -1923,7 +2150,22 @@ export function apply(ctx) {
|
|
|
1923
2150
|
await saveProgress(qid, prog)
|
|
1924
2151
|
}
|
|
1925
2152
|
function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
|
|
1926
|
-
|
|
2153
|
+
/**
|
|
2154
|
+
* 某个方向当前**在力**的临时假设(p_{q-tmp})id 列表。已被证伪(布尔估计=0)的假设不再"在力":
|
|
2155
|
+
* 依赖它的结论按需求 5 必须重新审视,而不是继续挂在它下面。
|
|
2156
|
+
*/
|
|
2157
|
+
async function activeAssumptionsOf(dir) {
|
|
2158
|
+
const out = []
|
|
2159
|
+
const subs = (dir && Array.isArray(dir.sub_questions)) ? dir.sub_questions : []
|
|
2160
|
+
for (let i = 0; i < subs.length; i++) {
|
|
2161
|
+
const id = subs[i] && subs[i].assumeId
|
|
2162
|
+
if (!id) continue
|
|
2163
|
+
const p = await findProposition(id)
|
|
2164
|
+
if (p && p.布尔估计 !== 0) out.push(id)
|
|
2165
|
+
}
|
|
2166
|
+
return out
|
|
2167
|
+
}
|
|
2168
|
+
async function addLemmaAsProposition(qid, lemma, assumeId) {
|
|
1927
2169
|
if (!lemma || !lemma.title) return
|
|
1928
2170
|
let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
|
|
1929
2171
|
if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
|
|
@@ -1936,8 +2178,11 @@ export function apply(ctx) {
|
|
|
1936
2178
|
'价值/关键性': clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
|
|
1937
2179
|
progress: { 来源: 'solver-lemma', 问题: qid, 说明: '由求解器针对问题 ' + qid + ' 的方向迭代产出。' }, 来源问题: qid,
|
|
1938
2180
|
}
|
|
2181
|
+
// 需求 5:「后续所得命题/结论中凡依赖该假设的,必须把假设作为前提显式写出」——这里把依赖**落库**
|
|
2182
|
+
// (`依赖假设` 字段),于是它可被 list_propositions / 验证提示词 / 证伪联动读取(审计 M16)。
|
|
2183
|
+
if (assumeId) p.依赖假设 = String(assumeId)
|
|
1939
2184
|
await upsertProposition(p)
|
|
1940
|
-
logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id)
|
|
2185
|
+
logActivity('proposition', 'lemma「' + lemma.title + '」→ ' + p.id + (assumeId ? '(依赖临时假设 ' + assumeId + ')' : ''))
|
|
1941
2186
|
return p.id
|
|
1942
2187
|
}
|
|
1943
2188
|
// 点5(q_sub 严格化):solver 报告子问题 q_sub 时,注册三个对象:
|
|
@@ -1960,7 +2205,18 @@ export function apply(ctx) {
|
|
|
1960
2205
|
const judgeId = qid + '-judge-' + shortId()
|
|
1961
2206
|
const assumeStatement = sq.assumption_statement || sq.assumption_title || ('对子问题「' + (sq.q_sub_title || sq.q_sub_statement) + '」的一种回答(临时假设)')
|
|
1962
2207
|
qs.push({ id: subId, 概述: sq.q_sub_statement, 已解决: false, 解法列表: [], 优先级: 1, progress: { 类型: 'sub-question', 来源问题: qid, 来源方向: dirId, 说明: '临时子问题:由问题 ' + qid + ' 方向 ' + dirId + ' 分支产生;求解主线在 p_{q-tmp}(' + assumeId + ')假设下推进。' } })
|
|
1963
|
-
|
|
2208
|
+
// ★ 判断问题必须**自带一条种子解法**,否则整条链是死的(审计 C2):buildVerifyCandidates 需要至少
|
|
2209
|
+
// 一条非终态的解法/条目才会为它选验证对象,而裸的 p-tmp-* 命题又被显式跳过(那条路我们故意不放宽:
|
|
2210
|
+
// 裸临时假设不该绕过"判断命题"直接裁决)。解法列表为空 ⇒ 没有任何东西可验证 ⇒ p_{q-tmp} 永远停在
|
|
2211
|
+
// 0.5、判断命题永不收口、终止规则(unsolved.length === 0)不可达。
|
|
2212
|
+
// 种子内容 = 这条临时假设本身("p_{q-tmp} 成立"这一候选回答),概率 0.5(>0 且 <1:它是待验证的
|
|
2213
|
+
// 候选,不是定论),并打上 `来源` 标记以示它是辅助对象(processVerify 据此让主线先占并发槽位)。
|
|
2214
|
+
qs.push({
|
|
2215
|
+
id: judgeId, 概述: '判断下述命题是否成立:' + assumeStatement, 已解决: false,
|
|
2216
|
+
解法列表: [{ 完整解法: assumeStatement, 正确概率: 0.5, 已验: false, 来源: 'sub-question-assumption', 验证记录: [] }],
|
|
2217
|
+
优先级: 1, 判断命题: assumeId,
|
|
2218
|
+
progress: { 类型: 'judge', 假设命题: assumeId, 说明: '由临时假设 p_{q-tmp}(' + assumeId + ')生成;它是对子问题 ' + subId + ' 的一种回答的命题化。解法列表里那条种子解法就是"该假设成立"这一候选回答,由验证器的裁决决定它成立(1)还是不成立(0)。' },
|
|
2219
|
+
})
|
|
1964
2220
|
await writeQs(qs)
|
|
1965
2221
|
const p = {
|
|
1966
2222
|
id: assumeId, 概述: assumeStatement, 布尔估计: 0.5,
|
|
@@ -1984,9 +2240,44 @@ export function apply(ctx) {
|
|
|
1984
2240
|
}
|
|
1985
2241
|
|
|
1986
2242
|
// ================= verification (验证器) =================
|
|
1987
|
-
|
|
2243
|
+
// 一票不算共识:≥MIN_REVIEWERS 份独立评审才可能达成/否决共识(审计 M11;与 v3 的最小票数同型)。
|
|
2244
|
+
function consensus(t) { const cids = Object.keys(t.childResults); if (cids.length < MIN_REVIEWERS) return false; const vs = cids.map(function (cid) { return t.childResults[cid].Result }); return vs.every(function (v) { return v === 1 }) || vs.every(function (v) { return v === 0 }) }
|
|
1988
2245
|
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') }
|
|
1989
|
-
|
|
2246
|
+
// 说明(审计 L17):这里曾有一个 `verifierWeight(cid, rigor)`,但全仓只有它的定义、没有任何调用点,
|
|
2247
|
+
// 且其公式(clamped ±0.2 rigor 加成、按 childId 取准确率)与 finalVerdict 里真正在用的
|
|
2248
|
+
// 稳定身份键 + 0.1 自信加成**并不相同**——留着它只会让人以为 forced 模式走的是那条公式。已删除。
|
|
2249
|
+
/**
|
|
2250
|
+
* 验证者的**稳定身份**(provider/model):forced 模式的"历史准确率"必须按这种跨子代理稳定的键记,
|
|
2251
|
+
* 按一次性的 childId 记等于永远 0.5(审计 H4)。子代理信息里本来没有这个字段,所以在这里算出来,
|
|
2252
|
+
* 由 spawnChild 写进 agentRegistry 的 meta,再随每次投票进入 childResults。
|
|
2253
|
+
*/
|
|
2254
|
+
function verifierIdentityKey() { const o = childAgentOptions(); return 'm:' + String(o.provider || '-') + '/' + String(o.model || '-') }
|
|
2255
|
+
const PENDING_SCORE_MAX_OBJECTS = 32
|
|
2256
|
+
/** 记下本轮各评审的投票,等**这个对象后来**取得布尔定论时再回溯计分(见 scorePendingReviews)。 */
|
|
2257
|
+
function recordReviewForScoring(objectId, key, result) {
|
|
2258
|
+
const id = String(objectId || '')
|
|
2259
|
+
if (!id) return
|
|
2260
|
+
let list = pendingReviewScores[id]
|
|
2261
|
+
if (!list) { list = []; pendingReviewScores[id] = list }
|
|
2262
|
+
list.push({ key: String(key || 'm:unknown'), result: clamp01(result) })
|
|
2263
|
+
if (list.length > 8) list.shift()
|
|
2264
|
+
const ids = Object.keys(pendingReviewScores)
|
|
2265
|
+
while (ids.length > PENDING_SCORE_MAX_OBJECTS) delete pendingReviewScores[ids.shift()]
|
|
2266
|
+
}
|
|
2267
|
+
/** 对象取得布尔定论(1/0)时,用它给**此前**那些还没被检验过的评审打分。绝不拿聚合值给同一批打分。 */
|
|
2268
|
+
async function scorePendingReviews(objectId, truth) {
|
|
2269
|
+
const id = String(objectId || '')
|
|
2270
|
+
const list = id ? pendingReviewScores[id] : undefined
|
|
2271
|
+
if (!list || list.length === 0) return
|
|
2272
|
+
delete pendingReviewScores[id]
|
|
2273
|
+
for (let i = 0; i < list.length; i++) {
|
|
2274
|
+
const acc = verifierAccuracy[list[i].key] || { correct: 0, total: 0 }
|
|
2275
|
+
acc.total += 1
|
|
2276
|
+
if (list[i].result === truth) acc.correct += 1
|
|
2277
|
+
verifierAccuracy[list[i].key] = acc
|
|
2278
|
+
}
|
|
2279
|
+
await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
|
|
2280
|
+
}
|
|
1990
2281
|
async function handleVerifier(childId, meta, output, stopReason) {
|
|
1991
2282
|
const rId = meta.rId
|
|
1992
2283
|
const parsed = parseJson(output)
|
|
@@ -1997,7 +2288,7 @@ export function apply(ctx) {
|
|
|
1997
2288
|
const Result = clamp01((parsed && parsed.Result != null) ? parsed.Result : 0.5)
|
|
1998
2289
|
const Reason = (parsed && parsed.Reason) || ''
|
|
1999
2290
|
let t = tasks['verify:' + rId]
|
|
2000
|
-
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(
|
|
2291
|
+
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(MIN_REVIEWERS, params.verifierCount), createdAt: now() }; tasks[t.id] = t }
|
|
2001
2292
|
if (!parsed && !scheduler.running) {
|
|
2002
2293
|
// abort:被中断的验证器没有产出,丢弃该子代理并清理任务簿记(任务在 resume 时由 processVerify 重建)
|
|
2003
2294
|
delete agentRegistry[childId]
|
|
@@ -2007,7 +2298,7 @@ export function apply(ctx) {
|
|
|
2007
2298
|
return
|
|
2008
2299
|
}
|
|
2009
2300
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
2010
|
-
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
|
|
2301
|
+
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, key: meta.verificationKey || verifierIdentityKey() }
|
|
2011
2302
|
delete agentRegistry[childId]
|
|
2012
2303
|
const allReported = t.children.length > 0 && t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === meta.round })
|
|
2013
2304
|
if (!allReported) { await saveAll(); return }
|
|
@@ -2042,14 +2333,40 @@ export function apply(ctx) {
|
|
|
2042
2333
|
await finalizeVerification(t)
|
|
2043
2334
|
}
|
|
2044
2335
|
}
|
|
2336
|
+
/**
|
|
2337
|
+
* 裁决前的**票数下限**(审计 M11):验证者掉线(followup 失败会把 childResults 里那一票删掉)时,
|
|
2338
|
+
* 只剩一票也会走 finalVerdict —— 一个"完全验证"的布尔结论就这样由单个评审写成。
|
|
2339
|
+
* 票数不足时**不产生任何裁决**:先补派验证器;补不足(未运行 / 并发被占满 / 已重试两次)就把任务置为
|
|
2340
|
+
* `stalled`,冷却期后重试(任务保留 ⇒ processVerify 不会为同一个 rId 另造任务,也不会每 tick 重试)。
|
|
2341
|
+
*/
|
|
2045
2342
|
async function finalizeVerification(t) {
|
|
2343
|
+
const reviews = Object.keys(t.childResults || {}).length
|
|
2344
|
+
if (reviews < MIN_REVIEWERS) {
|
|
2345
|
+
const tried = Number(t.reviewerRespawn || 0)
|
|
2346
|
+
if (scheduler.running && tried < 2 && reviewerCap() >= MIN_REVIEWERS) {
|
|
2347
|
+
t.reviewerRespawn = tried + 1
|
|
2348
|
+
logActivity('verify', t.rId + ' 只有 ' + reviews + ' 份有效评审(需要 ≥' + MIN_REVIEWERS + '),重新派发验证器补足(第 ' + t.reviewerRespawn + ' 次)')
|
|
2349
|
+
t.status = 'spawning'; t.children = []; t.childResults = {}; t.round = 1
|
|
2350
|
+
await saveAll()
|
|
2351
|
+
return
|
|
2352
|
+
}
|
|
2353
|
+
logActivity('verify', t.rId + ' 有效评审不足 ' + MIN_REVIEWERS + ' 份(' + reviews + '),本次**不产生裁决**;' + REVERIFY_COOLDOWN_MS / 60000 + ' 分钟后重试')
|
|
2354
|
+
t.status = 'stalled'; t.retryAt = now() + REVERIFY_COOLDOWN_MS
|
|
2355
|
+
t.children = []; t.childResults = {}
|
|
2356
|
+
await saveAll()
|
|
2357
|
+
return
|
|
2358
|
+
}
|
|
2046
2359
|
const verdict = finalVerdict(t)
|
|
2047
2360
|
if (params.mode === 'manual') {
|
|
2048
2361
|
const d = enqueueDecision('verdict', 'verdict for ' + t.rId + ' (debate finished) = ' + verdict, { rId: t.rId, verdict: verdict, task: JSON.parse(JSON.stringify(t)) })
|
|
2049
2362
|
scheduler.gate = { decisionId: d.id, node: 'verdict' }
|
|
2050
2363
|
t.status = 'awaiting-verdict'
|
|
2051
2364
|
} else {
|
|
2052
|
-
|
|
2365
|
+
// 任务本条仍丢弃(见 settleVerdict 的返回契约:v2 的严格终止依赖 tasks 为空,既有用例
|
|
2366
|
+
// formal-verify-v2 7/12 也依赖"搁置后这一轮就此结束"),但"裁定是否真的应用"必须留痕,
|
|
2367
|
+
// 否则任务表与 Verification_logs 对同一次裁定的说法不一致。
|
|
2368
|
+
const applied = await settleVerdict(t, verdict)
|
|
2369
|
+
logActivity('verdict', t.rId + ' 的裁决已' + (applied ? '应用' : '搁置(未应用到对象,任务本条随之关闭;形式化通过后重新验证)'))
|
|
2053
2370
|
delete tasks[t.id]
|
|
2054
2371
|
}
|
|
2055
2372
|
}
|
|
@@ -2062,7 +2379,10 @@ export function apply(ctx) {
|
|
|
2062
2379
|
let num = 0; let den = 0
|
|
2063
2380
|
const cids = Object.keys(t.childResults)
|
|
2064
2381
|
for (let i = 0; i < rs.length; i++) {
|
|
2065
|
-
|
|
2382
|
+
// 历史准确率按**稳定身份**取(见 verifierIdentityKey / settleVerdict):新模型的第一票用 0.5
|
|
2383
|
+
// 先验,此后按"后来的布尔定论"累积。
|
|
2384
|
+
const key = (t.childResults[cids[i]] && t.childResults[cids[i]].key) || 'm:unknown'
|
|
2385
|
+
const acc = verifierAccuracy[key] || { correct: 0, total: 0 }
|
|
2066
2386
|
const accRate = acc.total > 0 ? (acc.correct / acc.total) : 0.5
|
|
2067
2387
|
const confident = (rs[i].Result === 1 || rs[i].Result === 0) ? 0.1 : 0
|
|
2068
2388
|
const w = Math.max(0.05, Math.min(0.95, accRate + confident))
|
|
@@ -2070,7 +2390,12 @@ export function apply(ctx) {
|
|
|
2070
2390
|
}
|
|
2071
2391
|
return den > 0 ? Math.max(0.01, Math.min(0.99, num / den)) : 0.5
|
|
2072
2392
|
}
|
|
2073
|
-
|
|
2393
|
+
// flat = 均衡机制:不再"不一致就判 0.5"——各评审自报的 0<Result<1 是**它为真的概率**,
|
|
2394
|
+
// 取它们的均值(用户语义:只有精确的 1/0 是绝对真/假)。均值仍严格落在 (0,1),绝不当成绝对结论;
|
|
2395
|
+
// 这样 0.9 vs 1 这种高置信分歧得到的是 ≈0.95 而不是被折叠成 0.5(v3 用近共识规则修的就是这一点)。
|
|
2396
|
+
let sum = 0
|
|
2397
|
+
for (let i = 0; i < rs.length; i++) sum += Number(rs[i].Result) || 0
|
|
2398
|
+
return Math.max(0.01, Math.min(0.99, sum / rs.length))
|
|
2074
2399
|
}
|
|
2075
2400
|
/**
|
|
2076
2401
|
* `require` 门禁对**一次具体裁定**的判定(契约 §8)。
|
|
@@ -2090,26 +2415,44 @@ export function apply(ctx) {
|
|
|
2090
2415
|
}
|
|
2091
2416
|
return false
|
|
2092
2417
|
}
|
|
2418
|
+
/**
|
|
2419
|
+
* 应用一次裁定。
|
|
2420
|
+
*
|
|
2421
|
+
* 返回值(审计 L20):`true` = 裁定**真的落到了对象上**;`false` = 被 require 门禁搁置
|
|
2422
|
+
* (`formalVerdictDeferred`),对象一个字段都没改。调用方必须据此决定任务簿记:以前返回值没人看,
|
|
2423
|
+
* `delete tasks[...]` 无条件执行,任务表就"声称"这次裁定已应用,而唯一的证据只剩 Verification_logs/
|
|
2424
|
+
* 与 Formal/TODO.md。注意 v2 的严格终止(`Object.keys(tasks).length === 0`)与既有用例
|
|
2425
|
+
* (formal-verify-v2 用例 7/12:搁置后调度器停在那里,人形式化后再 resume)都要求**任务本条仍被丢弃**,
|
|
2426
|
+
* 所以这里只把"未应用"这一事实显式报给调用方,不改变调度契约。
|
|
2427
|
+
*/
|
|
2093
2428
|
async function settleVerdict(t, verdict) {
|
|
2094
2429
|
const v = clamp01(verdict)
|
|
2095
2430
|
const r = t.r
|
|
2096
2431
|
const cids = Object.keys(t.childResults)
|
|
2097
|
-
//
|
|
2432
|
+
// 验证者历史准确率(forced 加权用)。两条不变式(审计 H4/M13):
|
|
2433
|
+
// ① 键是**稳定身份**('m:<provider>/<model>'),不是一次性的 childId —— 按 childId 记的准确率
|
|
2434
|
+
// 在裁决时永远是 {0,0}(每次验证都新开一个 child,handleVerifier 又把它从 registry 删掉),
|
|
2435
|
+
// 于是 accRate 恒 0.5,"按历史准确率加权"根本不可能成立;
|
|
2436
|
+
// ② **只按后来的布尔定论计分**。拿本轮各评审的 Result 去和"同一批 Result 的聚合值"比较是自指:
|
|
2437
|
+
// non-unanimous 时没人是错的,flat 0.5 时凡 Result≠0.5 的人全被记错。所以这里先把本轮投票
|
|
2438
|
+
// 记进待计分表(pendingReviewScores),等这个对象**后来**真的取得 0/1 时再回溯计分。
|
|
2439
|
+
const scoreTarget = String(r.pId || r.判断命题 || r.qid || '')
|
|
2440
|
+
if ((v === 1 || v === 0) && scoreTarget) await scorePendingReviews(scoreTarget, v)
|
|
2098
2441
|
for (let i = 0; i < cids.length; i++) {
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
if (t.childResults[cids[i]].Result === v) acc.correct += 1
|
|
2102
|
-
verifierAccuracy[cids[i]] = acc
|
|
2442
|
+
const res = t.childResults[cids[i]] || {}
|
|
2443
|
+
recordReviewForScoring(scoreTarget, res.key, res.Result)
|
|
2103
2444
|
}
|
|
2104
|
-
await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
|
|
2105
2445
|
// debate transcript log
|
|
2106
|
-
|
|
2446
|
+
// 文件名来自 t.rId,而 rId 是**用户可写**的 q.id / p.id 拼出来的(buildVerifyCandidates 里
|
|
2447
|
+
// 'r-' + q.id + '-s' + j)。fs.resolve 只做规范化、并不拒绝 '..',所以 q.id = 'x/../../../../pwn'
|
|
2448
|
+
// 会让这次写落到项目树之外(审计 C1,已用 pathdemo2.cjs 复现)。路径段一律过 safeId。
|
|
2449
|
+
await writeJson('Verification_logs/' + safeId(String(t.rId)) + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
|
|
2107
2450
|
|
|
2108
2451
|
if (r.kind === 'proposition') {
|
|
2109
2452
|
const p = await findProposition(r.pId)
|
|
2110
2453
|
if (p) {
|
|
2111
2454
|
// require 门禁:裸命题的 1/0 裁定不生效(对象留在原库、布尔估计不变、无卡片)
|
|
2112
|
-
if (await formalVerdictDeferred(t, v, v === 1 || v === 0)) return
|
|
2455
|
+
if (await formalVerdictDeferred(t, v, v === 1 || v === 0)) return false
|
|
2113
2456
|
p.布尔估计 = v
|
|
2114
2457
|
if (v === 1) { p.证明列表 = p.证明列表 || []; p.证明列表.push({ 完整过程: strongestReason(t, 1), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
|
|
2115
2458
|
else if (v === 0) { p.证伪列表 = p.证伪列表 || []; p.证伪列表.push({ 完整过程: strongestReason(t, 0), 正确概率: 1, '支持信息/依据': '', 已验: true }); p.优先级 = 'never' }
|
|
@@ -2117,6 +2460,9 @@ export function apply(ctx) {
|
|
|
2117
2460
|
p.证明列表 = p.证明列表 || []; p.证伪列表 = p.证伪列表 || []
|
|
2118
2461
|
p.证明列表.push({ 完整过程: strongestReason(t, 1) || '根据辩论得到的支持性论证', 正确概率: v, '支持信息/依据': '', 已验: true })
|
|
2119
2462
|
p.证伪列表.push({ 完整过程: strongestReason(t, 0) || '根据辩论得到的反驳性论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
|
|
2463
|
+
// 中段值 = "为真的概率",不是定论:记下时间戳,冷却期过后这个命题可以**重新入选验证**
|
|
2464
|
+
// (审计 M12:以前它被永久搁置在中间概率上,没有任何回到验证的路径)。
|
|
2465
|
+
p.最近验证时间 = now()
|
|
2120
2466
|
}
|
|
2121
2467
|
await upsertProposition(p)
|
|
2122
2468
|
await writeVerifiedCardIfNeeded(p)
|
|
@@ -2125,7 +2471,7 @@ export function apply(ctx) {
|
|
|
2125
2471
|
const p = await findProposition(r.pId)
|
|
2126
2472
|
if (p) {
|
|
2127
2473
|
// require 门禁:一条证明/证伪被判定为 1(严格成立)时同样受门禁约束
|
|
2128
|
-
if (await formalVerdictDeferred(t, v, v === 1)) return
|
|
2474
|
+
if (await formalVerdictDeferred(t, v, v === 1)) return false
|
|
2129
2475
|
const list = r.side === '证明' ? (p.证明列表 = p.证明列表 || []) : (p.证伪列表 = p.证伪列表 || [])
|
|
2130
2476
|
const item = list[r.idx]
|
|
2131
2477
|
if (item) {
|
|
@@ -2133,12 +2479,20 @@ export function apply(ctx) {
|
|
|
2133
2479
|
item.已验 = true
|
|
2134
2480
|
if (v === 1) { item['支持信息/依据'] = strongestReason(t, 1) || item['支持信息/依据'] }
|
|
2135
2481
|
else if (v === 0) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2482
|
+
// 判 0 = "这份<证明/证伪>无效",**不是**"命题为假"(反过来说 side=证伪 时也不是"命题为真")。
|
|
2483
|
+
// 旧实现在这里往对侧列表推入一条 正确概率=1 的反条目,等于用"证明无效"伪造出一个布尔定论:
|
|
2484
|
+
// processStatusUpdates 随即写 布尔估计=0/1、优先级=never 和一张 结论:false/true 的 Verified
|
|
2485
|
+
// 卡片。这与仓库已修过的 defect 事故是同一类(机制本意是让验证更严,却伪造出假否定)。
|
|
2486
|
+
// 现在只把这一条记为无效(已验 / 正确概率 0),判据文字留在支持信息里,绝不伪造概率 1 的反条目。
|
|
2487
|
+
item['支持信息/依据'] = strongestReason(t, 0) || item['支持信息/依据']
|
|
2488
|
+
logActivity('verdict', t.rId + ' 判定' + r.side + '无效:' + r.pId + ' 的布尔估计保持不变(不因此认定命题为假/真)')
|
|
2138
2489
|
} else {
|
|
2139
2490
|
const other = r.side === '证明' ? (p.证伪列表 = p.证伪列表 || []) : (p.证明列表 = p.证明列表 || [])
|
|
2140
2491
|
other.push({ 完整过程: strongestReason(t, v >= 0.5 ? 0 : 1) || '辩论得出的相反方向论证', 正确概率: 1 - v, '支持信息/依据': '', 已验: true })
|
|
2141
2492
|
item['支持信息/依据'] = strongestReason(t, v >= 0.5 ? 1 : 0) || item['支持信息/依据']
|
|
2493
|
+
// 中段值:条目本身与新增的反向条目都是 (0,1) 的概率,冷却期过后可重新入选验证。
|
|
2494
|
+
item.最近验证时间 = now()
|
|
2495
|
+
other[other.length - 1].最近验证时间 = now()
|
|
2142
2496
|
}
|
|
2143
2497
|
}
|
|
2144
2498
|
await upsertProposition(p)
|
|
@@ -2150,11 +2504,13 @@ export function apply(ctx) {
|
|
|
2150
2504
|
if (sol) {
|
|
2151
2505
|
// require 门禁:v=1 意味着"这个问题的解法严格成立"(问题收口),必须被门禁拦住,
|
|
2152
2506
|
// 否则问题会被标成已解决、优先级 never,而 formal 记录仍是 none。
|
|
2153
|
-
if (await formalVerdictDeferred(t, v, v === 1)) return
|
|
2507
|
+
if (await formalVerdictDeferred(t, v, v === 1)) return false
|
|
2154
2508
|
sol.正确概率 = v
|
|
2155
2509
|
sol.已验 = true
|
|
2156
2510
|
sol.验证记录 = sol.验证记录 || []
|
|
2157
2511
|
sol.验证记录.push({ 结果: v, 时间: now(), 依据: strongestReason(t, v >= 0.5 ? 1 : 0) })
|
|
2512
|
+
// 中段值(0<v<1)= "这份解法为真的概率",不是定论:冷却期过后可重新入选验证(审计 M12)。
|
|
2513
|
+
if (v !== 1 && v !== 0) sol.最近验证时间 = now()
|
|
2158
2514
|
// 点3 回写联动:晋升问题的解法验证结果同步回源命题的证明/证伪条目(含内容比对防错位)
|
|
2159
2515
|
if (sol.来源命题 && (sol.来源列表 === '证明' || sol.来源列表 === '证伪')) {
|
|
2160
2516
|
const sp = await findProposition(sol.来源命题)
|
|
@@ -2184,15 +2540,39 @@ export function apply(ctx) {
|
|
|
2184
2540
|
logActivity('gate', t.rId + ' 对源命题 ' + ap.id + ' 的裁定 ' + v + ' 被 require 模式搁置为未定论(formal-required)')
|
|
2185
2541
|
} else {
|
|
2186
2542
|
ap.布尔估计 = v
|
|
2187
|
-
// 收敛闸门:本条路径只在 v=1/0 时才写入证明/证伪条目,中间裁决(flat
|
|
2188
|
-
//
|
|
2189
|
-
//
|
|
2190
|
-
//
|
|
2191
|
-
//
|
|
2192
|
-
//
|
|
2193
|
-
|
|
2194
|
-
if (v === 1) {
|
|
2195
|
-
|
|
2543
|
+
// 收敛闸门:本条路径只在 v=1/0 时才写入证明/证伪条目,中间裁决(flat 现在给出各评审
|
|
2544
|
+
// 自报概率的均值,forced 给出加权均值)会让 ap 停留在"中间布尔估计 + 两个列表皆空"
|
|
2545
|
+
// 的状态——而这正是 buildVerifyCandidates 认定"裸命题需要验证"的条件。若不留时间戳,
|
|
2546
|
+
// 该命题会在每个 tick 重新入选、重开一轮完整辩论;又因 processVerify 每 tick 只跑一个
|
|
2547
|
+
// 验证,其它对象被无限饿死,终止条件(所有问题已解决)永不可达。
|
|
2548
|
+
// ★ 但**中段值不是定论**(用户语义:只有 1/0 是绝对真/假):`已验证` 只对布尔裁决生效;
|
|
2549
|
+
// 中段裁决改记时间戳,冷却期过后重新入选——对象不会被永久停在中间概率上(审计 M12)。
|
|
2550
|
+
if (v === 1) {
|
|
2551
|
+
ap.证明列表 = ap.证明列表 || []
|
|
2552
|
+
ap.证明列表.push({ 完整过程: strongestReason(t, 1) || '判断问题解法验证通过', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true })
|
|
2553
|
+
ap.优先级 = 'never'
|
|
2554
|
+
ap.已验证 = true
|
|
2555
|
+
} else if (v === 0) {
|
|
2556
|
+
ap.证伪列表 = ap.证伪列表 || []
|
|
2557
|
+
ap.证伪列表.push({ 完整过程: strongestReason(t, 0) || '判断问题解法判定不成立', 正确概率: 1, '支持信息/依据': '经「判断下述命题是否成立」问题解法验证', 已验: true })
|
|
2558
|
+
ap.优先级 = 'never'
|
|
2559
|
+
ap.已验证 = true
|
|
2560
|
+
} else {
|
|
2561
|
+
ap.最近验证时间 = now()
|
|
2562
|
+
}
|
|
2563
|
+
// 需求 5 第二半的联动:假设被**确认**(v=1)时,它就是对子问题 q_sub 的那一种回答的
|
|
2564
|
+
// 定论——把这份回答作为子问题的解法交回,让 processStatusUpdates 按收口规则正常关闭
|
|
2565
|
+
// 子问题并写问题卡片。否则 q_sub 永远未解决,终止规则(unsolved.length === 0)依然不可达。
|
|
2566
|
+
if (v === 1 && ap.progress && ap.progress.子问题) {
|
|
2567
|
+
const subQ = qs.find(function (x) { return x.id === ap.progress.子问题 })
|
|
2568
|
+
if (subQ && !subQ.已解决) {
|
|
2569
|
+
subQ.解法列表 = subQ.解法列表 || []
|
|
2570
|
+
if (!subQ.解法列表.some(function (s2) { return s2.正确概率 === 1 })) {
|
|
2571
|
+
subQ.解法列表.push({ 完整解法: '【假设确认】' + ap.概述 + '(子问题的候选回答经「判断下述命题是否成立」问题验证为真)', 正确概率: 1, 已验: true, 来源: 'sub-question-assumption', 验证记录: [] })
|
|
2572
|
+
logActivity('subquestion', '子问题 ' + subQ.id + ' 由临时假设 ' + ap.id + ' 的确认回答收口(交由收口规则关闭并写卡片)')
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2196
2576
|
await upsertProposition(ap)
|
|
2197
2577
|
await writeVerifiedCardIfNeeded(ap)
|
|
2198
2578
|
logActivity('judge-sync', 'judge problem verdict ' + v + ' synced to proposition ' + ap.id)
|
|
@@ -2205,6 +2585,7 @@ export function apply(ctx) {
|
|
|
2205
2585
|
}
|
|
2206
2586
|
}
|
|
2207
2587
|
logActivity('verdict', t.rId + ' = ' + v + (v === 1 ? ' (fully verified)' : v === 0 ? ' (refuted)' : ' (uncertain)'))
|
|
2588
|
+
return true
|
|
2208
2589
|
}
|
|
2209
2590
|
function strongestReason(t, wantTrue) {
|
|
2210
2591
|
const cids = Object.keys(t.childResults)
|
|
@@ -2319,10 +2700,10 @@ export function apply(ctx) {
|
|
|
2319
2700
|
await saveAll()
|
|
2320
2701
|
return { ok: true }
|
|
2321
2702
|
}
|
|
2322
|
-
async function startScheduler() { const r = await init(true); if (!r.ok) return r; scheduler.running = true; scheduler.startedAt = now();
|
|
2323
|
-
async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; scheduler
|
|
2703
|
+
async function startScheduler() { const r = await init(true); if (!r.ok) return r; abandonGatedDecision('scheduler restarted'); scheduler.running = true; scheduler.startedAt = now(); logActivity('start', 'scheduler started for project ' + currentProject); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
2704
|
+
async function resumeScheduler() { const r = await init(false); if (!r.ok) return r; abandonGatedDecision('scheduler resumed (离开那次运行,挂起的节点不再等待)'); scheduler.running = true; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
2324
2705
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
2325
|
-
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); agentRegistry = {}; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
2706
|
+
async function abortScheduler() { scheduler.running = false; abandonGatedDecision('scheduler aborted'); const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptChild(ids[i]); agentRegistry = {}; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
2326
2707
|
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
2327
2708
|
async function autoResolvePending() {
|
|
2328
2709
|
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
@@ -2330,7 +2711,7 @@ export function apply(ctx) {
|
|
|
2330
2711
|
const d = pending[i]
|
|
2331
2712
|
try {
|
|
2332
2713
|
if (d.node === 'spawn') { await spawnChild(d.data.label, d.data.promptText, d.data.meta); d.status = 'resolved'; d.resolution = { action: 'approve', auto: true } }
|
|
2333
|
-
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 } }
|
|
2714
|
+
else if (d.node === 'verdict') { const applied = await settleVerdict(d.data.task, d.data.verdict); delete tasks[d.data.task.id]; d.status = 'resolved'; d.resolution = { action: 'approve', auto: true, applied: applied } }
|
|
2334
2715
|
} catch (e) {
|
|
2335
2716
|
// 副作用失败必须把该决策落到终态,否则它会永远保持 pending:此后每次切 auto 都在同一个
|
|
2336
2717
|
// 决策上重新抛错,而 gate 又指向它 —— 调度永久卡死。标为 resolved(auto-failed) 并让它过去,
|
|
@@ -2354,7 +2735,7 @@ export function apply(ctx) {
|
|
|
2354
2735
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
2355
2736
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
2356
2737
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
2357
|
-
recentActivity: activityLog.slice(-Math.min(
|
|
2738
|
+
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,
|
|
2358
2739
|
formal: {
|
|
2359
2740
|
mode: formalMode(), required: formalRequired(),
|
|
2360
2741
|
leanCommand: params.leanCommand, leanArgs: params.leanArgs, leanTimeoutMs: params.leanTimeoutMs,
|
|
@@ -2371,6 +2752,7 @@ export function apply(ctx) {
|
|
|
2371
2752
|
const exists = (await listDirsAt(vibeRoot(), 'Projects')).indexOf(slug) !== -1
|
|
2372
2753
|
if (!create && !exists) return { ok: false, message: 'project not found: ' + slug }
|
|
2373
2754
|
if (scheduler.running) await abortScheduler()
|
|
2755
|
+
else abandonGatedDecision('project switched')
|
|
2374
2756
|
currentProject = slug; await writeCurrentProject(); await ensureDirs()
|
|
2375
2757
|
if ((await readJson('qs/qs.json')) === undefined) await writeJson('qs/qs.json', [])
|
|
2376
2758
|
params = Object.assign({}, DEFAULT_PARAMS); scheduler = { running: false, startedAt: 0, lastCheckpoint: 0, gate: null }; agentRegistry = {}; decisionQueue = []; verifierAccuracy = {}; tasks = {}; explorerRetries = {}; activityLog = []; lastReportWrite = 0; lastPushReport = 0; reportDirty = false
|
|
@@ -2398,7 +2780,7 @@ export function apply(ctx) {
|
|
|
2398
2780
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
2399
2781
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
|
|
2400
2782
|
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 } })
|
|
2401
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
|
|
2783
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, verifierAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, solverAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, verifierAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); return { ok: true, params: params } })
|
|
2402
2784
|
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' } })
|
|
2403
2785
|
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() })
|
|
2404
2786
|
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') })
|
|
@@ -2407,7 +2789,7 @@ export function apply(ctx) {
|
|
|
2407
2789
|
const p = { id: args.id, 概述: args.概述, 布尔估计: clamp01(args.布尔估计 != null ? args.布尔估计 : 0.5), 细类型: (args.细类型 && typeof args.细类型 === 'object') ? args.细类型 : { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: (args.优先级 != null) ? args.优先级 : 1, '价值/关键性': clamp01(args['价值/关键性'] != null ? args['价值/关键性'] : 0.5), progress: { 来源: 'user', 说明: '用户手动添加。' } }
|
|
2408
2790
|
await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) }
|
|
2409
2791
|
})
|
|
2410
|
-
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), async function () { const all = await getPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 概述: p.概述, 布尔估计: p.布尔估计, 优先级: p.优先级, '价值/关键性': p['价值/关键性'], category: p._category } }) } })
|
|
2792
|
+
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category, 依赖假设).', objParams({}), async function () { const all = await getPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 概述: p.概述, 布尔估计: p.布尔估计, 优先级: p.优先级, '价值/关键性': p['价值/关键性'], category: p._category, 依赖假设: p.依赖假设, 依赖假设已证伪: p.依赖假设已证伪 } }) } })
|
|
2411
2793
|
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) })
|
|
2412
2794
|
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) })
|
|
2413
2795
|
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
|
|
@@ -2417,9 +2799,9 @@ export function apply(ctx) {
|
|
|
2417
2799
|
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' } })
|
|
2418
2800
|
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' } })
|
|
2419
2801
|
// ---- Lean 形式化验证(契约 §5)----
|
|
2420
|
-
registerTool('vibe_math_lean_run', 'Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), async function (args, agent) { return await leanRunTool(memberIdOf(agent), args) })
|
|
2421
|
-
registerTool('vibe_math_lean_archive', 'Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), async function (args, agent) { return await leanArchive(memberIdOf(agent), args) })
|
|
2422
|
-
registerTool('vibe_math_lean_lib', 'List (and by default rebuild) the Lean reuse library: this project\'s Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: { type: 'boolean' } }), async function (args) {
|
|
2802
|
+
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), async function (args, agent) { return await leanRunTool(memberIdOf(agent), args) })
|
|
2803
|
+
registerTool('vibe_math_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), async function (args, agent) { return await leanArchive(memberIdOf(agent), args) })
|
|
2804
|
+
registerTool('vibe_math_lean_lib', '(member) List (and by default rebuild) the Lean reuse library: this project\'s Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: { type: 'boolean' } }), async function (args) {
|
|
2423
2805
|
const noRefresh = !!(args && args.refresh === false)
|
|
2424
2806
|
const r = noRefresh ? { lib: null, proved: null, objects: Object.keys(formalRecords()).length } : await rebuildLeanLibIndexes()
|
|
2425
2807
|
return {
|
|
@@ -2468,6 +2850,16 @@ export function apply(ctx) {
|
|
|
2468
2850
|
// 每次工具调用前同步当前项目(按会话读 current.json;多会话互不干扰)
|
|
2469
2851
|
refreshProject: async function () { if (rootAgent) currentProject = await readCurrentProject() },
|
|
2470
2852
|
getRunning: function () { return scheduler.running },
|
|
2853
|
+
// childOwner 裁剪用:这个会话当前仍"可能再发 subagent/end"的 child(在册的 + 任务正在等的)。
|
|
2854
|
+
referencedChildIds: function () {
|
|
2855
|
+
const out = Object.keys(agentRegistry)
|
|
2856
|
+
const ids = Object.keys(tasks)
|
|
2857
|
+
for (let i = 0; i < ids.length; i++) {
|
|
2858
|
+
const t = tasks[ids[i]]
|
|
2859
|
+
if (t && Array.isArray(t.children)) for (let j = 0; j < t.children.length; j++) out.push(t.children[j])
|
|
2860
|
+
}
|
|
2861
|
+
return out
|
|
2862
|
+
},
|
|
2471
2863
|
// Lean 形式化验证(docs/formal-verification.md):状态与工具的内部入口,
|
|
2472
2864
|
// 供状态/报告与测试直接读取,不必绕过工具层。
|
|
2473
2865
|
formalMode: formalMode, formalOn: formalOn, formalRequired: formalRequired, formalGateOk: formalGateOk,
|
|
@@ -2503,13 +2895,13 @@ export function apply(ctx) {
|
|
|
2503
2895
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
|
|
2504
2896
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
|
|
2505
2897
|
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')
|
|
2506
|
-
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { type: 'boolean' }, verifierAllowNetwork: { type: 'boolean' }, solverAllowScripts: { type: 'boolean' }, verifierAllowScripts: { type: 'boolean' }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), 'vibe_math_set_params')
|
|
2898
|
+
registerTool('vibe_math_set_params', 'Update scheduler parameters (partial). Lean 形式化验证:formalVerify = off(默认,不额外要求)| encourage(按实现难度自行决定是否形式化;一旦 Lean 通过,验证转为对 Lean 陈述的「忠实性审查」)| require(同上,且加门禁:对象的 formal.status 未达到 passed/blocked 之前,真/假裁定记为未定论、原因 formal-required,并进入 Formal/TODO.md);leanCommand/leanArgs/leanTimeoutMs 控制 Lean 工具链的调用方式。', objParams({ maxParallelThreshold: { type: 'integer' }, solverMaxRounds: { type: 'integer' }, verifierCount: { type: 'integer' }, debateMaxRounds: { type: 'integer' }, verdictMode: { type: 'string', enum: ['flat', 'forced'] }, reportMode: { type: 'string', enum: ['file', 'push', 'both'] }, promoteValueThreshold: { type: 'number' }, priorityAdjust: { type: 'string', enum: ['none', 'deadend-deprioritize', 'survival-map'] }, proposPriorityAdjust: { type: 'string', enum: ['none', 'progress-graded'] }, provider: { type: 'string' }, model: { type: 'string' }, solverPersona: { type: 'string' }, verifierPersona: { type: 'string' }, explorerPersona: { type: 'string' }, knowledgeContext: { type: 'string' }, solverToolAllow: { type: 'array', items: { type: 'string' } }, solverToolDeny: { type: 'array', items: { type: 'string' } }, verifierToolAllow: { type: 'array', items: { type: 'string' } }, verifierToolDeny: { type: 'array', items: { type: 'string' } }, solverAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, verifierAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, solverAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, verifierAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] }, solverMaxToolCalls: { type: 'integer' }, verifierMaxToolCalls: { type: 'integer' }, reportIntervalMs: { type: 'integer' }, tickIntervalMs: { type: 'integer' }, activityLogCap: { type: 'integer' }, maxExplorerRetries: { type: 'integer' }, directionsPerSolver: { type: 'integer' }, formalVerify: { type: 'string', enum: ['off', 'encourage', 'require'] }, leanCommand: { type: 'string' }, leanArgs: { type: 'array', items: { type: 'string' } }, leanTimeoutMs: { type: 'integer' } }), 'vibe_math_set_params')
|
|
2507
2899
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
|
|
2508
2900
|
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')
|
|
2509
2901
|
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')
|
|
2510
2902
|
registerTool('vibe_math_add_problem', 'Add a problem to the current project qs/qs.json.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), 'vibe_math_add_problem')
|
|
2511
2903
|
registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), 'vibe_math_add_proposition')
|
|
2512
|
-
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category).', objParams({}), 'vibe_math_list_propositions')
|
|
2904
|
+
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category, 依赖假设).', objParams({}), 'vibe_math_list_propositions')
|
|
2513
2905
|
registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
|
|
2514
2906
|
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
|
|
2515
2907
|
registerTool('vibe_math_list_projects', 'List math projects.', objParams({}), 'vibe_math_list_projects')
|
|
@@ -2523,9 +2915,9 @@ export function apply(ctx) {
|
|
|
2523
2915
|
// 三个工具**无条件注册**:工具注册是静态的(动态注册会依赖运行时开关,破坏既有
|
|
2524
2916
|
// `ctx.effect` 纪律),而档位只决定"框架是否告诉成员它们存在"。off 档下它们照常工作,
|
|
2525
2917
|
// 人/代理主动调用时一样可用。
|
|
2526
|
-
registerTool('vibe_math_lean_run', 'Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), 'vibe_math_lean_run')
|
|
2527
|
-
registerTool('vibe_math_lean_archive', 'Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), 'vibe_math_lean_archive')
|
|
2528
|
-
registerTool('vibe_math_lean_lib', 'List (and by default rebuild) the Lean reuse library: this project\'s Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: { type: 'boolean' } }), 'vibe_math_lean_lib')
|
|
2918
|
+
registerTool('vibe_math_lean_run', '(member) Execute the Lean toolchain on one .lean file inside the VibeMath root and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.', objParams({ file: { type: 'string' }, target: { type: 'string' }, timeout_ms: { type: 'integer' } }, ['file']), 'vibe_math_lean_run')
|
|
2919
|
+
registerTool('vibe_math_lean_archive', '(member) Archive Lean code. kind="def": a REUSABLE definition/object/assumption → the global cross-project library (Formal/Lib). kind="lemma": a machine-checked lemma → Formal/Proved. kind="proof": the formal proof of a project object → Formal/<target>.lean, and (when the run passes) also Verified/Lean/<target>.lean, marking the object Lean-passed. kind="blocked": record an explicit, reasoned "cannot/not worth formalizing" decision (note required).', objParams({ kind: { type: 'string', enum: ['def', 'lemma', 'proof', 'blocked'] }, name: { type: 'string' }, target: { type: 'string' }, content: { type: 'string' }, from: { type: 'string' }, note: { type: 'string' }, run: { type: 'boolean' } }, ['kind']), 'vibe_math_lean_archive')
|
|
2920
|
+
registerTool('vibe_math_lean_lib', '(member) List (and by default rebuild) the Lean reuse library: this project\'s Formal/Index.md, plus the global cross-project Formal/Lib and Formal/Proved indexes. Look here BEFORE writing a new definition so you reuse instead of redefining.', objParams({ refresh: { type: 'boolean' } }), 'vibe_math_lean_lib')
|
|
2529
2921
|
|
|
2530
2922
|
// /vibe slash command (registered once; routed per session)
|
|
2531
2923
|
ctx.effect(() => commands.register({
|
|
@@ -2550,17 +2942,29 @@ export function apply(ctx) {
|
|
|
2550
2942
|
|
|
2551
2943
|
// subagent/end (registered once; routed to the owning session via childOwner)
|
|
2552
2944
|
ctx.on('subagent/end', function (info) {
|
|
2945
|
+
// 记下"这个 child 最近一次 end"的时间:裁剪 childOwner 的宽限期从这里算(见 pruneChildOwner)。
|
|
2946
|
+
// **不要**在这里直接回收那条映射:同一个 child 会因辩论/续轮再次 end,丢了映射这次事件就没人路由,
|
|
2947
|
+
// 实测后果是 verdict 收口失效("problem solved after verdict 1")。回收交给 pruneChildOwner 的
|
|
2948
|
+
// 引用 + 宽限期判断。
|
|
2949
|
+
lastChildEndAt.set(info.id, now())
|
|
2553
2950
|
const sid = childOwner.get(info.id)
|
|
2554
2951
|
const s = sid !== undefined ? sessions.get(sid) : undefined
|
|
2555
2952
|
if (s) s.onChildEnd(info).catch(function (e) { console.error('vibe-math-v2 onChildEnd reject: ' + String((e && e.stack) || e)) })
|
|
2556
|
-
// 注意:这里**不要**回收 childOwner 条目。这条映射在子代理 end 之后仍会被后续事件路由
|
|
2557
|
-
// 用到:曾试过在此处回收、也试过在 onChildEnd 末尾回收,两次都导致 e2e-regression 的
|
|
2558
|
-
// verdict 收口失效("problem solved after verdict 1")。代价是每个历史子代理留下一条
|
|
2559
|
-
// 小记录(有界增长,实测不影响功能),远小于"验证无法收口"的代价。
|
|
2560
2953
|
})
|
|
2561
2954
|
|
|
2562
2955
|
// tick timer (registered once; ticks every running session at its own pace)
|
|
2563
|
-
ctx.effect(() => {
|
|
2956
|
+
ctx.effect(() => {
|
|
2957
|
+
let beat = 0
|
|
2958
|
+
const t = setInterval(function () {
|
|
2959
|
+
for (const s of sessions.values()) { if (s.getRunning() && !s.tickInFlight && s.tickDue() && s.scheduler.gate === null) s.scheduleTick() }
|
|
2960
|
+
// childOwner / sessions 裁剪:每 30 拍(约 30s)一次,成本是"会话数 × 映射数"的一次扫描。
|
|
2961
|
+
if ((++beat % 30) === 0) {
|
|
2962
|
+
try { pruneChildOwner() } catch (e) { console.error('vibe-math-v2: pruneChildOwner failed: ' + String((e && e.message) || e)) }
|
|
2963
|
+
try { pruneSessions() } catch (e) { console.error('vibe-math-v2: pruneSessions failed: ' + String((e && e.message) || e)) }
|
|
2964
|
+
}
|
|
2965
|
+
}, 1000)
|
|
2966
|
+
return () => clearInterval(t)
|
|
2967
|
+
})
|
|
2564
2968
|
}
|
|
2565
2969
|
|
|
2566
2970
|
// ---- test seam: pure, stateless helpers --------------------------------
|