dsh-vibe-math 2.7.2 → 2.7.3
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 +11 -10
- package/README.md +13 -12
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +229 -3
- package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
- package/docs/final-paper.md +2 -2
- package/docs/math-computation.md +13 -6
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +1 -0
- package/docs/release-notes/README.md +1 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +2 -2
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +3 -3
- package/docs/release-notes/RELEASE-NOTES-2.7.3.en.md +67 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.3.md +67 -0
- package/docs/release-notes/TEMPLATE.md +6 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +8 -5
- package/installer.js +27 -17
- package/package.json +250 -203
- package/prompt-corpus-persona/persona-corpus.json +9 -9
- package/prompt-corpus-persona/persona-corpus.md +225 -33
- package/prompt-corpus-v2/formal-verify-v2.json +295 -115
- package/prompt-corpus-v2/formal-verify-v2.md +2287 -283
- package/prompt-corpus-v3/formal-verify-v3.json +105 -105
- package/prompt-corpus-v3/formal-verify-v3.md +213 -143
- package/prompt-corpus-v4/formal-verify-v4.json +18 -18
- package/prompt-corpus-v4/formal-verify-v4.md +49 -44
- package/prompt-corpus-v5/prompt-corpus-v5.json +82 -82
- package/prompt-corpus-v5/prompt-corpus-v5.md +451 -207
- package/scripts/update-doc-counts.mjs +71 -0
- package/tests/audit-artifact-docs.mjs +72 -0
- package/tests/audit-artifact-docs.mutants.mjs +58 -0
- package/tests/audit-installer-assertions.mutants.mjs +63 -0
- package/tests/audit-installer-compat.test.mjs +52 -4
- package/tests/audit-installer-policy.test.mjs +41 -8
- package/tests/audit-math-computation-contract.mjs +64 -8
- package/tests/audit-math-computation-contract.mutants.mjs +106 -0
- package/tests/audit-math-computation-parity.mjs +164 -3
- package/tests/audit-math-computation-sensitivity.mjs +4 -2
- package/tests/audit-package-membership.mjs +57 -0
- package/tests/audit-package-membership.mutants.mjs +28 -0
- package/tests/audit-participant-set-parity.mjs +160 -0
- package/tests/audit-path-discipline.mjs +83 -0
- package/tests/audit-path-discipline.mutants.mjs +51 -0
- package/tests/audit-persona-sensitivity.mjs +14 -0
- package/tests/audit-persona-surface.mutants.mjs +69 -0
- package/tests/audit-persona-surface.test.mjs +63 -22
- package/tests/audit-prompt-duplication.mjs +143 -0
- package/tests/audit-prompt-duplication.mutants.mjs +50 -0
- package/tests/audit-prompt-invariants.mjs +43 -5
- package/tests/audit-readme-bilingual.test.mjs +152 -0
- package/tests/audit-readme-counts.mjs +95 -0
- package/tests/audit-readme-counts.mutants.mjs +52 -0
- package/tests/audit-registration.mjs +78 -0
- package/tests/audit-status-report-fields.mjs +172 -0
- package/tests/audit-status-report-fields.mutants.mjs +64 -0
- package/tests/audit-v3-registration-parity.mjs +156 -0
- package/tests/audit-v5-integrity.mjs +300 -13
- package/tests/audit-v5-lean-abstention.mjs +392 -0
- package/tests/audit-v5-lean-abstention.mutants.mjs +106 -0
- package/tests/audit-v5-prompt-duplication.mjs +88 -0
- package/tests/audit-v5-prompt-duplication.mutants.mjs +34 -0
- package/tests/audit-v5-sensitivity.mjs +12 -7
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +458 -13
- package/tests/formal-verify-v2.test.mjs +91 -43
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +85 -39
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +541 -9
- package/tests/formal-verify-v5.test.mjs +68 -9
- package/tests/helpers/doc-counts-format.mjs +41 -0
- package/tests/helpers/math-computation-fake-seam.mjs +4 -0
- package/tests/math-computation-a1.mutants.mjs +72 -0
- package/tests/math-computation-archive-rerun.mutants.mjs +65 -0
- package/tests/math-computation-discovery.mutants.mjs +76 -0
- package/tests/math-computation-shared.test.mjs +274 -5
- package/tests/math-computation-v2.test.mjs +767 -0
- package/tests/math-computation-v3.test.mjs +759 -0
- package/tests/math-computation-v4.mutants.mjs +133 -0
- package/tests/math-computation-v4.test.mjs +739 -0
- package/tests/prompt-v5-integrity.test.mjs +13 -2
- package/tests/run-tests.mjs +139 -9
- package/tests/run-tests.mutants.mjs +70 -0
- package/tests/selfdrive-v5.mjs +53 -1
- package/tests/selfdrive-v5.mutants.mjs +132 -0
- package/tests/v2-fix-probes.mutants.mjs +100 -0
- package/tests/v2-fix-probes.test.mjs +1039 -0
- package/tests/v2-list-agents-and-next-step.mutants.mjs +106 -0
- package/tests/v3-fix-probes.mutants.mjs +95 -0
- package/tests/v3-fix-probes.test.mjs +891 -0
- package/tests/v5-institute-fixes.mutants.mjs +190 -0
- package/vibe-math-v2/agent.cordis.yml +62 -6
- package/vibe-math-v2/math-computation.js +130 -41
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/vibe-math-v2.js +388 -113
- package/vibe-math-v2//345/256/236/347/216/260/346/226/271/346/241/210.md +5 -1
- package/vibe-math-v3/agent.cordis.yml +72 -6
- package/vibe-math-v3/math-computation.js +130 -41
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/vibe-math-v3.js +422 -126
- package/vibe-math-v4/agent.cordis.yml +44 -8
- package/vibe-math-v4/math-computation.js +130 -41
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/vibe-math-v4.js +196 -43
- package/vibe-math-v4//345/256/236/347/216/260/346/226/271/346/241/210.md +2 -0
- package/vibe-math-v5/agent.cordis.yml +46 -12
- package/vibe-math-v5/math-computation.js +130 -41
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1173 -218
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +80 -12
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +2 -2
|
@@ -1,3 +1,106 @@
|
|
|
1
|
+
// ================= F-B: 参数机读 schema 的单一真源(模块作用域) =================
|
|
2
|
+
// 工具面 `vibe_math_set_params` 的 `parameters` 由 PARAM_SCHEMA 派生,描述/类型只有一处真源
|
|
3
|
+
//(真源与例外说明见 docs/parameter-schema.md)。本块整体位于**模块作用域**,因为本文件定义了两处
|
|
4
|
+
// `objParams`(会话层 makeSession + apply 层)——只有模块作用域能被两层同时看见(rehearsal 曾因此
|
|
5
|
+
// 让 apply 层拿到只有例外项的残缺 schema)。下列常量与 PARAM_SCHEMA 是从 makeSession 内**逐字上移**的纯
|
|
6
|
+
// 常量(值不变、作用域上移),会话层原有引用照旧解析到它们。
|
|
7
|
+
const IS_WINDOWS = process.platform === 'win32'
|
|
8
|
+
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
9
|
+
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
10
|
+
const ACTIVITY_REPORT_MAX = 30
|
|
11
|
+
const LEAN_INITIATIVE_MODES = ['off', 'normal', 'eager']
|
|
12
|
+
const PARAM_SCHEMA = [
|
|
13
|
+
{ name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
|
|
14
|
+
{ name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
|
|
15
|
+
{ name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
|
|
16
|
+
{ name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向(互不干扰);N>1 = 额外附带最多 N 个其他活跃方向摘要用于协调', suggestion: 1 },
|
|
17
|
+
{ name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量(下限 2:一票不裁决)。并发预算紧张时单个对象同时最多占用 maxParallelThreshold-2 个槽位(至少 2),以保证另一个对象也凑得齐 2 票', suggestion: 3 },
|
|
18
|
+
{ name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
|
|
19
|
+
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致时取各评审自报概率的**均值**,绝不折叠成固定的 0.5);forced = 强制裁决(按验证者历史准确率+布尔票置信度加权)。准确率按 provider/model 这种稳定身份记,且只在对象**后来**取得布尔定论时才计分', suggestion: 'flat' },
|
|
20
|
+
{ name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
21
|
+
{ name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
22
|
+
{ name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
|
|
23
|
+
{ name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
|
|
24
|
+
{ name: 'explorerPersona', type: 'string', description: '注入每个 explorer/重派生提示词开头的人格/要求', suggestion: '' },
|
|
25
|
+
{ name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖,注入 explorer/solver/verifier 提示词)', suggestion: '' },
|
|
26
|
+
{ name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
|
|
27
|
+
{ name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
|
|
28
|
+
{ name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
|
|
29
|
+
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
30
|
+
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow 列表时补入本次组合真正注册的网络工具);false=禁止网络工具', suggestion: '' },
|
|
31
|
+
{ name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关(同 solverAllowNetwork)', suggestion: '' },
|
|
32
|
+
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止 ' + SCRIPT_TOOLS.join('/'), suggestion: '' },
|
|
33
|
+
{ name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关(同 solverAllowScripts)', suggestion: '' },
|
|
34
|
+
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
35
|
+
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
36
|
+
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
|
|
37
|
+
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
|
|
38
|
+
{ name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
|
|
39
|
+
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
|
|
40
|
+
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
|
|
41
|
+
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
|
|
42
|
+
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 status/report 里 recentActivity 的细节量,两者最多显示 ' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },
|
|
43
|
+
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
|
|
44
|
+
{ 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' },
|
|
45
|
+
{ name: 'leanCommand', type: 'string', description: '要执行的 Lean 可执行文件(默认 lean;用 lake 时配合 leanArgs=["env","lean"])', suggestion: 'lean' },
|
|
46
|
+
{ name: 'leanArgs', type: 'string[]', description: '插在 .lean 文件名之前的附加命令行参数(默认空)', suggestion: [] },
|
|
47
|
+
{ name: 'leanTimeoutMs', type: 'integer', description: '单次 Lean 运行的超时上限(毫秒,默认 120000,最小 1000);异步档下它同时是**每个后台编译作业**的预算(到时主动 terminate,作业记 timeout、对象留在 attempted)', suggestion: 120000 },
|
|
48
|
+
{ name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_lib / vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
|
|
49
|
+
{ name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
|
|
50
|
+
{ name: 'leanInitiative', type: 'string', enum: LEAN_INITIATIVE_MODES.slice(), description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
|
|
51
|
+
{ name: 'leanSearchPaths', type: 'array', items: { type: 'string' }, description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
|
|
52
|
+
{ name: 'finalPaper', type: 'boolean', description: '收口(严格终止)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已检验通过的命题/解法/成果整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
|
|
53
|
+
{ name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md;tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
|
|
54
|
+
{ name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
|
|
55
|
+
{ name: 'paperCompilePdf', type: 'boolean', description: '检测到 LaTeX 时是否编译 paper.pdf(-interaction=nonstopmode 跑两遍;失败先尝试修复:换引擎/去不支持宏包/最小模板)。false 或无 LaTeX 时只保留 tex+md 并记日志', suggestion: true },
|
|
56
|
+
{ name: 'paperLatexCommand', type: 'string', description: '指定 LaTeX 引擎可执行文件(空 = 按语言探测:中文 xelatex > latexmk > pdflatex > lualatex > tectonic;英文 pdflatex 优先)。该命令解析不到时按"未检测到"降级(只留 tex+md)', suggestion: '' },
|
|
57
|
+
// ---- 数学计算 math_computation(六参数冻结;描述与 prompts.md §1/§6 口径一致)----
|
|
58
|
+
{ name: 'mathComputation', type: 'enum', options: ['off', 'auto', 'on'], description: '数学计算总开关:off = 真无操作(提示词零提及);auto = 探测到 mathEngines 里任一允许引擎才工作;on = 同上(探测失败时工具仍返回可执行的安装指引,而不是假装可用)', suggestion: 'auto' },
|
|
59
|
+
{ name: 'mathMode', type: 'enum', options: ['typed', 'typed+shell'], description: '计算策略:typed+shell(默认)= 工具不可用时允许宿主 shell 兜底,但结论必须标注"未经工具归档(shell 路径)";typed = 只用工具路径(提示词里不出现 shell 兜底段,engine:"cli" 返回 REFUSED{reason:policy})', suggestion: 'typed+shell' },
|
|
60
|
+
{ name: 'mathEngines', type: 'string[]', description: '允许的引擎列表(默认 python|r|octave|julia|matlab|maple|wolfram|cli)。cli 默认开启且走同一套超时/输出上限/回执;从列表里移除某引擎即禁用(商业引擎只探测+许可,永不安装)', suggestion: MATH_PARAM_DEFAULTS.mathEngines.slice() },
|
|
61
|
+
{ name: 'mathTimeoutMs', type: 'integer', description: '单次数学计算的超时上限(毫秒,默认 60000,最小 1000);到时主动 terminate 并把回执标为 MATH_TIMEOUT', suggestion: 60000 },
|
|
62
|
+
{ name: 'mathPackages', type: 'string[]', description: '需要预检的包(默认空)。缺包只报告 + 给"用户自装指引"或"代理代装计划",不会执行脚本,也永不自动安装', suggestion: [] },
|
|
63
|
+
{ name: 'mathInstallScope', type: 'string', enum: ['user', 'system'], description: '安装作用域:user(默认,用户级目录);system 只对当次显式调用生效、永不记忆(不会写进状态文件)', suggestion: 'user' },
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
// 钉住"机读键集合"(现状 v2=49 / v3=63 个,不随 PARAM_SCHEMA 增删而漂移)。
|
|
67
|
+
const PARAM_PROPS_KEYS = ['maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'verdictMode', 'reportMode', 'promoteValueThreshold', 'priorityAdjust', 'proposPriorityAdjust', 'provider', 'model', 'solverPersona', 'verifierPersona', 'explorerPersona', 'knowledgeContext', 'solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny', 'solverAllowNetwork', 'verifierAllowNetwork', 'solverAllowScripts', 'verifierAllowScripts', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'directionsPerSolver', 'formalVerify', 'leanCommand', 'leanArgs', 'leanTimeoutMs', 'leanAsync', 'leanJobsMaxParallel', 'leanInitiative', 'leanSearchPaths', 'mathComputation', 'mathMode', 'mathEngines', 'mathTimeoutMs', 'mathPackages', 'mathInstallScope', 'finalPaper', 'paperFormat', 'paperLanguage', 'paperCompilePdf', 'paperLatexCommand']
|
|
68
|
+
// 源无法表达的条目:数组 items / oneOf / mode 的现状枚举顺序 —— 逐字沿用,并由
|
|
69
|
+
// "例外集合 == docs/parameter-schema.md" 断言钉住。
|
|
70
|
+
const PARAM_PROPS_EXTRA = {
|
|
71
|
+
solverToolAllow: { type: 'array', items: { type: 'string' } },
|
|
72
|
+
solverToolDeny: { type: 'array', items: { type: 'string' } },
|
|
73
|
+
verifierToolAllow: { type: 'array', items: { type: 'string' } },
|
|
74
|
+
verifierToolDeny: { type: 'array', items: { type: 'string' } },
|
|
75
|
+
leanArgs: { type: 'array', items: { type: 'string' } },
|
|
76
|
+
mathEngines: { type: 'array', items: { type: 'string' } },
|
|
77
|
+
mathPackages: { type: 'array', items: { type: 'string' } },
|
|
78
|
+
solverAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] },
|
|
79
|
+
verifierAllowNetwork: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] },
|
|
80
|
+
solverAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] },
|
|
81
|
+
verifierAllowScripts: { oneOf: [{ type: 'boolean' }, { type: 'string', enum: [''] }] },
|
|
82
|
+
}
|
|
83
|
+
function paramProps() {
|
|
84
|
+
const props = {}
|
|
85
|
+
for (const name of PARAM_PROPS_KEYS) {
|
|
86
|
+
const e = PARAM_SCHEMA.find(function (x) { return x && x.name === name })
|
|
87
|
+
if (!e) continue
|
|
88
|
+
const enumVals = (Array.isArray(e.enum) && e.enum.length) ? e.enum : ((Array.isArray(e.options) && e.options.length) ? e.options : null)
|
|
89
|
+
const isEnum = e.type === 'enum' || !!enumVals
|
|
90
|
+
const p = isEnum ? { type: 'string', enum: (enumVals || []).slice() } : { type: e.type }
|
|
91
|
+
if (e.description) p.description = e.description
|
|
92
|
+
props[name] = p
|
|
93
|
+
}
|
|
94
|
+
// 例外表覆盖"源无法表达"的字段,但**保留源里的描述**(覆盖度 v2 49/49、v3 63/63)
|
|
95
|
+
for (const k of Object.keys(PARAM_PROPS_EXTRA)) {
|
|
96
|
+
const base = props[k] || {}
|
|
97
|
+
props[k] = Object.assign({}, PARAM_PROPS_EXTRA[k])
|
|
98
|
+
if (base.description) props[k].description = base.description
|
|
99
|
+
}
|
|
100
|
+
return props
|
|
101
|
+
}
|
|
102
|
+
// ================= end F-B =================
|
|
103
|
+
|
|
1
104
|
// Vibe Math V2 — host plugin implementing the classic-architecture spec
|
|
2
105
|
// ("vibe-math-v2/实现方案.md"): probability-driven scheduling over a
|
|
3
106
|
// problem list (qs/qs.json) and a proposition knowledge base (Propos/),
|
|
@@ -218,7 +321,7 @@ export function apply(ctx) {
|
|
|
218
321
|
let explorerRetries = {}
|
|
219
322
|
// 状态/报告里最近活动最多显示多少条(**一个常量**):此前 buildReport 用 min(30, cap)、getStatus
|
|
220
323
|
// 硬编码 min(10, cap),同一个字段两个端点给出不同答案,而参数说明承诺的是 30(审计 M15)。
|
|
221
|
-
const
|
|
324
|
+
const ACTIVITY_PERSIST_MAX = 200 // P5:活动日志落盘上限(status/report 只显示 30 条,但恢复时保留更多线索)
|
|
222
325
|
// 至少 2 名独立评审才能出裁决(审计 M11):一票裁决会把单个验证者的判断写成"完全验证"的布尔结论。
|
|
223
326
|
const MIN_REVIEWERS = 2
|
|
224
327
|
// 中段裁决(0<正确概率<1)的**重验冷却**(审计 M12):中段值是"为真的概率",不是定论,对象不能
|
|
@@ -234,12 +337,30 @@ export function apply(ctx) {
|
|
|
234
337
|
function projectRoot(slug) { return vibeRoot() + '/Projects/' + slug }
|
|
235
338
|
function frameworkRoot() { return projectRoot(currentProject) }
|
|
236
339
|
let warnedNoPolicy = false
|
|
340
|
+
// F-5:围栏漂移检测(只告警不拒写)。边界:若解析结果**不暴露**任何 root 字段,本比对无法运行。
|
|
341
|
+
function policyRootOf(p) {
|
|
342
|
+
if (!p || typeof p !== 'object') return ''
|
|
343
|
+
const cand = p.workspaceRoot || p.root || p.cwd || (p.config && p.config.workspaceRoot) || ''
|
|
344
|
+
return String(cand || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
|
345
|
+
}
|
|
346
|
+
let warnedFenceDrift = false
|
|
347
|
+
function warnFenceDriftOnce(actual, expected) {
|
|
348
|
+
if (warnedFenceDrift) return
|
|
349
|
+
warnedFenceDrift = true
|
|
350
|
+
console.error('vibe-math-v2' + ': sandbox fence root differs from this session workspace (resolved="' + actual + '", session="' + expected + '") — writes may be fenced to the host-configured root; boundary: when the policy object exposes no root field this comparison cannot run')
|
|
351
|
+
}
|
|
352
|
+
function checkFenceRoot(p) {
|
|
353
|
+
const actual = policyRootOf(p)
|
|
354
|
+
const expected = String(workspaceRoot() || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
|
355
|
+
if (actual && expected && actual !== expected) warnFenceDriftOnce(actual, expected)
|
|
356
|
+
return p
|
|
357
|
+
}
|
|
237
358
|
function warnNoPolicyOnce() { if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v2: sandboxPolicy unavailable; writes go out with no explicit policy') } }
|
|
238
359
|
// Sandbox fence for our own writes. The `resolve({})` fallback is a last resort and is
|
|
239
360
|
// deliberately reported (once): with no session it resolves the policy's CONFIGURED root
|
|
240
361
|
// (dsh-sandbox-policy: resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd())),
|
|
241
362
|
// which is not necessarily this session's workspace — a silently different fence.
|
|
242
|
-
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 }
|
|
363
|
+
function getPolicy() { const sp = sandboxPolicyOf(); if (!sp) { warnNoPolicyOnce(); return undefined } try { if (rootAgent && rootAgent.session) return checkFenceRoot(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 checkFenceRoot(p) } catch (e) { warnNoPolicyOnce() } return undefined }
|
|
243
364
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
244
365
|
|
|
245
366
|
// ================= capability / tool-name lists =================
|
|
@@ -253,14 +374,11 @@ export function apply(ctx) {
|
|
|
253
374
|
// host applies the filter when establishing a continuable child
|
|
254
375
|
// (dsh-subagent: childCtx.tools.restrict(...)), so a stale name meant the child
|
|
255
376
|
// was never created at all.
|
|
256
|
-
const IS_WINDOWS = process.platform === 'win32'
|
|
257
|
-
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
258
377
|
// 这两个名字是否真的存在取决于**本次组合**:v2 自己的 agent.cordis.yml 把 tool-web 行设成
|
|
259
378
|
// fetch:false,于是 web_search 在、web_fetch 不在,而 filter 里只要有一个宿主不注册的名字,
|
|
260
379
|
// restrict() 就整条拒绝 → 子代理先失败一次再走 sanitizeToolFilter 重试,日志还会谎称"你的配置过期"
|
|
261
380
|
// (审计 M8/M9)。所以能力清单**向宿主的可见工具面查询**(tools.schemas,宿主自己在
|
|
262
381
|
// dsh-tools/lib/index.js 里就是这么用的),查不到/不可信时才退回静态候选表。
|
|
263
|
-
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
264
382
|
/**
|
|
265
383
|
* 本会话可见的工具名集合;null = 无法可信地确定(此时一律保留候选名,行为与改动前一致)。
|
|
266
384
|
* 自校验:这张表必须包含本插件自己注册的工具,否则说明这个 scope 视图不是子代理真正看到的
|
|
@@ -285,64 +403,17 @@ export function apply(ctx) {
|
|
|
285
403
|
}
|
|
286
404
|
|
|
287
405
|
// ================= parameter schema =================
|
|
288
|
-
const PARAM_SCHEMA = [
|
|
289
|
-
{ name: 'mode', type: 'enum', options: ['auto', 'manual'], description: 'auto = 无人值守自动通过关键节点;manual = 关键节点挂起人工决策', suggestion: 'auto' },
|
|
290
|
-
{ name: 'maxParallelThreshold', type: 'integer', description: '全局最大并发子代理轮数(新派发前须满足 active < 阈值)', suggestion: 4 },
|
|
291
|
-
{ name: 'solverMaxRounds', type: 'integer', description: '每个求解方向的最大迭代轮数(agent_self_iteration 上限)', suggestion: 3 },
|
|
292
|
-
{ name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向(互不干扰);N>1 = 额外附带最多 N 个其他活跃方向摘要用于协调', suggestion: 1 },
|
|
293
|
-
{ name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量(下限 2:一票不裁决)。并发预算紧张时单个对象同时最多占用 maxParallelThreshold-2 个槽位(至少 2),以保证另一个对象也凑得齐 2 票', suggestion: 3 },
|
|
294
|
-
{ name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
|
|
295
|
-
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: 'flat = 均衡机制(不一致时取各评审自报概率的**均值**,绝不折叠成固定的 0.5);forced = 强制裁决(按验证者历史准确率+布尔票置信度加权)。准确率按 provider/model 这种稳定身份记,且只在对象**后来**取得布尔定论时才计分', suggestion: 'flat' },
|
|
296
|
-
{ name: 'provider', type: 'string', description: '子代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
297
|
-
{ name: 'model', type: 'string', description: '子代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
298
|
-
{ name: 'solverPersona', type: 'string', description: '注入每个求解器提示词开头的人格/要求', suggestion: '' },
|
|
299
|
-
{ name: 'verifierPersona', type: 'string', description: '注入每个验证器提示词开头的人格/要求', suggestion: '' },
|
|
300
|
-
{ name: 'explorerPersona', type: 'string', description: '注入每个 explorer/重派生提示词开头的人格/要求', suggestion: '' },
|
|
301
|
-
{ name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖,注入 explorer/solver/verifier 提示词)', suggestion: '' },
|
|
302
|
-
{ name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
|
|
303
|
-
{ name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
|
|
304
|
-
{ name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
|
|
305
|
-
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
306
|
-
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承全部;true=允许(在已有 allow 列表时补入本次组合真正注册的网络工具);false=禁止网络工具', suggestion: '' },
|
|
307
|
-
{ name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关(同 solverAllowNetwork)', suggestion: '' },
|
|
308
|
-
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关:空=继承全部;true=允许(在已有 allow 列表时补入);false=禁止 ' + SCRIPT_TOOLS.join('/'), suggestion: '' },
|
|
309
|
-
{ name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关(同 solverAllowScripts)', suggestion: '' },
|
|
310
|
-
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
311
|
-
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用**建议**上限(0 = 不限)。框架只把它写进提示词,不计数、不强制', suggestion: 0 },
|
|
312
|
-
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动(有代理状态更新等事件才写/推报告);>0 = 同时按该间隔定时自动汇报', suggestion: 0 },
|
|
313
|
-
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息让主代理主动汇报;both = 两者都做', suggestion: 'file' },
|
|
314
|
-
{ name: 'promoteValueThreshold', type: 'number', description: 'Propos 中「价值/关键性」≥ 该值且未决(0,1) 的命题自动加入 qs.json', suggestion: 0.7 },
|
|
315
|
-
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '优先级动态调整策略:none=不自动调;deadend-deprioritize=方向全死路时降优先级;survival-map=按最高方向存活率重算(存活率高越优先)', suggestion: 'none' },
|
|
316
|
-
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整:none=不自动调;progress-graded=按「定论接近度(|布尔估计-0.5|)+ 证明/证伪材料量」重算,越接近定论越优先验证', suggestion: 'none' },
|
|
317
|
-
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒):多久扫描一次子代理状态并推进(越小越灵敏、越大越省资源)', suggestion: 2000 },
|
|
318
|
-
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 status/report 里 recentActivity 的细节量,两者最多显示 ' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },
|
|
319
|
-
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限(达到后该问题标记为方向耗尽)', suggestion: 3 },
|
|
320
|
-
{ 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' },
|
|
321
|
-
{ name: 'leanCommand', type: 'string', description: '要执行的 Lean 可执行文件(默认 lean;用 lake 时配合 leanArgs=["env","lean"])', suggestion: 'lean' },
|
|
322
|
-
{ name: 'leanArgs', type: 'string[]', description: '插在 .lean 文件名之前的附加命令行参数(默认空)', suggestion: [] },
|
|
323
|
-
{ name: 'leanTimeoutMs', type: 'integer', description: '单次 Lean 运行的超时上限(毫秒,默认 120000,最小 1000);异步档下它同时是**每个后台编译作业**的预算(到时主动 terminate,作业记 timeout、对象留在 attempted)', suggestion: 120000 },
|
|
324
|
-
{ name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_lib / vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
|
|
325
|
-
{ name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
|
|
326
|
-
{ name: 'leanInitiative', type: 'string', enum: LEAN_INITIATIVE_MODES.slice(), description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
|
|
327
|
-
{ name: 'leanSearchPaths', type: 'array', items: { type: 'string' }, description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
|
|
328
|
-
{ name: 'finalPaper', type: 'boolean', description: '收口(严格终止)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已检验通过的命题/解法/成果整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
|
|
329
|
-
{ name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md;tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
|
|
330
|
-
{ name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
|
|
331
|
-
{ name: 'paperCompilePdf', type: 'boolean', description: '检测到 LaTeX 时是否编译 paper.pdf(-interaction=nonstopmode 跑两遍;失败先尝试修复:换引擎/去不支持宏包/最小模板)。false 或无 LaTeX 时只保留 tex+md 并记日志', suggestion: true },
|
|
332
|
-
{ name: 'paperLatexCommand', type: 'string', description: '指定 LaTeX 引擎可执行文件(空 = 按语言探测:中文 xelatex > latexmk > pdflatex > lualatex > tectonic;英文 pdflatex 优先)。该命令解析不到时按"未检测到"降级(只留 tex+md)', suggestion: '' },
|
|
333
|
-
// ---- 数学计算 math_computation(六参数冻结;描述与 prompts.md §1/§6 口径一致)----
|
|
334
|
-
{ name: 'mathComputation', type: 'enum', options: ['off', 'auto', 'on'], description: '数学计算总开关:off = 真无操作(提示词零提及);auto = 探测到 mathEngines 里任一允许引擎才工作;on = 同上(探测失败时工具仍返回可执行的安装指引,而不是假装可用)', suggestion: 'auto' },
|
|
335
|
-
{ name: 'mathMode', type: 'enum', options: ['typed', 'typed+shell'], description: '计算策略:typed+shell(默认)= 工具不可用时允许宿主 shell 兜底,但结论必须标注"未经工具归档(shell 路径)";typed = 只用工具路径(提示词里不出现 shell 兜底段,engine:"cli" 返回 REFUSED{reason:policy})', suggestion: 'typed+shell' },
|
|
336
|
-
{ name: 'mathEngines', type: 'string[]', description: '允许的引擎列表(默认 python|r|octave|julia|matlab|maple|wolfram|cli)。cli 默认开启且走同一套超时/输出上限/回执;从列表里移除某引擎即禁用(商业引擎只探测+许可,永不安装)', suggestion: MATH_PARAM_DEFAULTS.mathEngines.slice() },
|
|
337
|
-
{ name: 'mathTimeoutMs', type: 'integer', description: '单次数学计算的超时上限(毫秒,默认 60000,最小 1000);到时主动 terminate 并把回执标为 MATH_TIMEOUT', suggestion: 60000 },
|
|
338
|
-
{ name: 'mathPackages', type: 'string[]', description: '需要预检的包(默认空)。缺包只报告 + 给"用户自装指引"或"代理代装计划",不会执行脚本,也永不自动安装', suggestion: [] },
|
|
339
|
-
{ name: 'mathInstallScope', type: 'string', enum: ['user', 'system'], description: '安装作用域:user(默认,用户级目录);system 只对当次显式调用生效、永不记忆(不会写进状态文件)', suggestion: 'user' },
|
|
340
|
-
]
|
|
341
406
|
|
|
342
407
|
// ================= fs =================
|
|
343
408
|
async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
|
|
344
409
|
async function readText(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
|
|
345
|
-
async function writeText(rel, content) {
|
|
410
|
+
async function writeText(rel, content) {
|
|
411
|
+
const t = await fsTarget(rel)
|
|
412
|
+
const r = await fs.writeText(t, content, undefined, undefined, getPolicy())
|
|
413
|
+
// P4:宿主若用返回值(而非抛异常)报告写失败,也要留痕——旧实现无条件 return true 会把它吞掉。
|
|
414
|
+
if (r && r.ok === false) noteStateWriteFailure(rel, 'host reported write failure' + (r.error ? (': ' + r.error) : ''))
|
|
415
|
+
return true
|
|
416
|
+
}
|
|
346
417
|
async function readJson(rel) { const t = await readText(rel); if (t === undefined || t === '') return undefined; try { return JSON.parse(t) } catch (e) { noteSuspect(rel); return undefined } }
|
|
347
418
|
/**
|
|
348
419
|
* Corruption guard. `readJson` cannot tell "no file yet" from "file present but
|
|
@@ -368,18 +439,26 @@ export function apply(ctx) {
|
|
|
368
439
|
console.error('vibe-math-v2: write to ' + rel + ' blocked (file is unparseable; see the earlier warning)')
|
|
369
440
|
return false
|
|
370
441
|
}
|
|
371
|
-
async function writeJson(rel, obj) {
|
|
442
|
+
async function writeJson(rel, obj) {
|
|
443
|
+
if (!assertWritable(rel)) {
|
|
444
|
+
// P4:损坏守卫拒绝写入过去是**完全静默**的(返回 false 没人看)⇒ 会话以为已提交、磁盘还是旧内容。
|
|
445
|
+
noteStateWriteFailure(rel, 'corruption guard refused write')
|
|
446
|
+
logActivity('state', '拒绝写入(该 JSON 已存在但无法解析,先修/删它):' + rel)
|
|
447
|
+
return false
|
|
448
|
+
}
|
|
449
|
+
return await writeText(rel, JSON.stringify(obj, null, 2))
|
|
450
|
+
}
|
|
372
451
|
async function listFiles(rel) { try { const t = await fsTarget(rel); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'file' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
373
452
|
async function listDirsAt(base, rel) { try { const t = await fs.resolve(rel, { cwd: base }); const s = await fs.stat(t); if (s === undefined) return []; const entries = await fs.listDir(t); return entries.filter(function (e) { return e && e.type === 'directory' }).map(function (e) { return e.name }) } catch (e) { return [] } }
|
|
374
453
|
async function readTextAbs(path) { try { const t = await fs.resolve(path); const s = await fs.stat(t); if (s === undefined) return undefined; return await fs.readText(t) } catch (e) { return undefined } }
|
|
375
454
|
async function writeTextAbs(path, content) { try { const t = await fs.resolve(path); await fs.writeText(t, content, undefined, undefined, getPolicy()); return true } catch (e) { return false } }
|
|
376
455
|
async function readCurrentProject() {
|
|
377
456
|
// 按会话隔离的 current 文件(多会话并行时互不覆盖);无则回退旧共享文件
|
|
378
|
-
|
|
379
|
-
|
|
457
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s !== undefined) { const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } } catch (e) { noteStateWriteFailure('current.' + safeId(sessionId) + '.json', 'read failed: ' + ((e && e.message) || e)) }
|
|
458
|
+
try { const t = await fs.resolve('current.json', { cwd: vibeRoot() }); const s = await fs.stat(t); if (s === undefined) return 'default'; const txt = await fs.readText(t); const j = safeJson(txt, null); const p = (j && j.project) ? String(j.project) : 'default'; return slugify(p) } catch (e) { noteStateWriteFailure('current.json', 'read failed: ' + ((e && e.message) || e)); return 'default' }
|
|
380
459
|
}
|
|
381
460
|
async function writeCurrentProject() {
|
|
382
|
-
|
|
461
|
+
try { const t = await fs.resolve('current.' + safeId(sessionId) + '.json', { cwd: vibeRoot() }); await fs.writeText(t, JSON.stringify({ project: currentProject }), undefined, undefined, getPolicy()) } catch (e) { noteStateWriteFailure('current.' + safeId(sessionId) + '.json', (e && e.message) || e) } // F-6c: 内存已切/磁盘未落必须可观察,否则重启丢失项目切换
|
|
383
462
|
}
|
|
384
463
|
|
|
385
464
|
// ================= subprocess =================
|
|
@@ -732,6 +811,75 @@ export function apply(ctx) {
|
|
|
732
811
|
}
|
|
733
812
|
return out
|
|
734
813
|
}
|
|
814
|
+
/** P2:状态提交的原子性 + 完整性标记。
|
|
815
|
+
*
|
|
816
|
+
* 可用能力只有 fs.resolve/stat/readText/writeText(**宿主没有 rename**)。所以提交协议是
|
|
817
|
+
* 「**暂存 → 回读校验 → 提交 → 标记**」:
|
|
818
|
+
* 1) 每个状态文件先写 `<rel>.tmp`,再回读确认**非空**(截断/失败写在这里就被抓住);
|
|
819
|
+
* 2) 暂存成功才把它写进最终路径(`writeText(rel, …)`),失败则记入 `stateWriteFailures`;
|
|
820
|
+
* 3) 全部落地后**最后**写 `VibeMath_State/commit.json`:`{seq, at, checkpoint, files, missing, mode}`
|
|
821
|
+
* —— 它是"这次提交完整"的唯一标记,恢复时用它判断上一次提交是否撕裂。
|
|
822
|
+
*
|
|
823
|
+
* **不用 shell move**:宿主可能只提供受控/模拟的 subprocess(本轮实测:假 subprocess 返回 exit 0
|
|
824
|
+
* 但不会真的移动文件 ⇒ "搬"成功、文件却不存在,状态直接丢失)。有鉴于此,这里坚持只走宿主 fs API;
|
|
825
|
+
* 代价是没有 rename 级原子性,收益是任何宿主上都不会静默丢文件。
|
|
826
|
+
*/
|
|
827
|
+
let stateCommitSeq = 0
|
|
828
|
+
let stateCommit = { seq: 0, at: 0, complete: false, mode: '', files: [], missing: [] }
|
|
829
|
+
const stateWriteFailures = [] // P4:被拒绝/失败的落盘(status/report 可见)
|
|
830
|
+
function noteStateWriteFailure(rel, error) {
|
|
831
|
+
stateWriteFailures.push({ rel: String(rel), error: String(error || 'unknown'), at: now() })
|
|
832
|
+
if (stateWriteFailures.length > 32) stateWriteFailures.shift()
|
|
833
|
+
}
|
|
834
|
+
async function relNonEmpty(rel) {
|
|
835
|
+
try { const t = await fsTarget(rel); const st = await fs.stat(t); return !!(st && (st.size === undefined || st.size > 0)) } catch (e) { return false }
|
|
836
|
+
}
|
|
837
|
+
async function writeJsonAtomic(rel, obj) {
|
|
838
|
+
// P4:原子路径也必须过损坏守卫(否则「存在但解析失败」的状态文件会被静默覆写)
|
|
839
|
+
if (!assertWritable(rel)) {
|
|
840
|
+
noteStateWriteFailure(rel, 'corruption guard refused write')
|
|
841
|
+
logActivity('state', '拒绝写入(该 JSON 已存在但无法解析,先修/删它):' + rel)
|
|
842
|
+
return 'refused'
|
|
843
|
+
}
|
|
844
|
+
const text = JSON.stringify(obj, null, 2)
|
|
845
|
+
// 没有 shell 就没法清理暂存文件(宿主 fs 无 delete/rename)⇒ 直接写,避免留下 .tmp 垃圾与告警
|
|
846
|
+
if (!subprocessOf()) {
|
|
847
|
+
try { await writeText(rel, text) } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
848
|
+
if (!(await relNonEmpty(rel))) noteStateWriteFailure(rel, 'written but empty/missing')
|
|
849
|
+
return 'direct'
|
|
850
|
+
}
|
|
851
|
+
const tmp = rel + '.tmp'
|
|
852
|
+
let staged = false
|
|
853
|
+
try { await writeText(tmp, text); staged = await relNonEmpty(tmp) } catch (e) { staged = false }
|
|
854
|
+
let mode = 'direct'
|
|
855
|
+
if (staged) {
|
|
856
|
+
try { await writeText(rel, text); mode = 'stage-commit' } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
857
|
+
try { await removeFile(tmp) } catch (e) { /* 清理暂存文件失败不影响提交内容 */ }
|
|
858
|
+
} else {
|
|
859
|
+
try { await writeText(rel, text) } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
860
|
+
}
|
|
861
|
+
if (!(await relNonEmpty(rel))) noteStateWriteFailure(rel, 'written but empty/missing')
|
|
862
|
+
return mode
|
|
863
|
+
}
|
|
864
|
+
async function commitState(files) {
|
|
865
|
+
stateCommitSeq += 1
|
|
866
|
+
const modes = []
|
|
867
|
+
const missing = []
|
|
868
|
+
for (let i = 0; i < files.length; i++) {
|
|
869
|
+
const mode = await writeJsonAtomic(files[i].rel, files[i].obj)
|
|
870
|
+
modes.push(mode)
|
|
871
|
+
if (!(await relNonEmpty(files[i].rel))) missing.push(files[i].rel)
|
|
872
|
+
}
|
|
873
|
+
const commit = {
|
|
874
|
+
seq: stateCommitSeq, at: now(), epoch: processEpoch,
|
|
875
|
+
checkpoint: Number(scheduler.lastCheckpoint) || now(),
|
|
876
|
+
files: files.map(function (x) { return x.rel }), missing: missing,
|
|
877
|
+
mode: modes.indexOf('stage-commit') === -1 ? 'direct' : 'stage-commit',
|
|
878
|
+
}
|
|
879
|
+
await writeJsonAtomic('VibeMath_State/commit.json', commit)
|
|
880
|
+
stateCommit = Object.assign({ complete: missing.length === 0 }, commit)
|
|
881
|
+
return commit
|
|
882
|
+
}
|
|
735
883
|
async function loadState() {
|
|
736
884
|
const s = await readJson('VibeMath_State/scheduler_state.json')
|
|
737
885
|
// 丢弃历史持久化的 activeCount:旧值可能已经漂移,绝不能覆盖推导值(见 activeCount())。
|
|
@@ -741,23 +889,55 @@ export function apply(ctx) {
|
|
|
741
889
|
const va = await readJson('VibeMath_State/verifier_accuracy.json'); if (va) verifierAccuracy = sanitizeAccuracy(va)
|
|
742
890
|
const tk = await readJson('VibeMath_State/tasks.json'); if (tk) tasks = tk
|
|
743
891
|
const er = await readJson('VibeMath_State/explorer_retries.json'); if (er) explorerRetries = er
|
|
892
|
+
// P3:恢复待计分样本(有上限、有溢出告警,见 recordReviewForScoring)
|
|
893
|
+
// P5:把磁盘上的活动日志并回内存(旧实现只落 report 快照且从不回读 ⇒ 恢复后 status 从零开始)
|
|
894
|
+
const al = await readJson('VibeMath_State/activity_log.json')
|
|
895
|
+
if (Array.isArray(al) && al.length) { const seen = {}; const merged = []; for (const e of al.concat(activityLog)) { const k = String(e && e.at) + '|' + String(e && e.detail); if (seen[k]) continue; seen[k] = true; merged.push(e) } activityLog = merged.slice(-ACTIVITY_PERSIST_MAX) }
|
|
896
|
+
// P6:论文排队/回收计数恢复;在途撰写者跨进程不可恢复 ⇒ 清掉留痕
|
|
897
|
+
const pj = await readJson('VibeMath_State/paper.json')
|
|
898
|
+
if (pj && typeof pj === 'object') { if (pj.pending) paperPending = pj.pending; paperReaps = Number(pj.reaps) || 0 }
|
|
899
|
+
if (paperInFlight) { logActivity('paper', '上一进程的在途撰写者 ' + paperInFlight + ' 不可恢复(child 随进程结束)⇒ 交由 paper 锁/回收逻辑重新触发'); paperInFlight = ''; paperInFlightAt = 0 }
|
|
900
|
+
const prs = await readJson('VibeMath_State/pending_review_scores.json')
|
|
901
|
+
if (prs && typeof prs === 'object' && !Array.isArray(prs)) pendingReviewScores = prs
|
|
744
902
|
// 形式化记录(docs/formal-verification.md §4):v2 没有会话投影,这条状态必须自己持久化,
|
|
745
903
|
// 否则一次 resume 就让 require 门禁失忆(已 passed 的对象被再当"未尝试")。
|
|
904
|
+
// P2:先校验上一次提交的完整性(不完整 ⇒ 留痕,再按逐文件容错读取)
|
|
905
|
+
await loadCommitMarker()
|
|
746
906
|
const fm = await readJson('VibeMath_State/formal.json')
|
|
747
907
|
formalState = {
|
|
748
908
|
records: (fm && fm.records && typeof fm.records === 'object' && !Array.isArray(fm.records)) ? fm.records : {},
|
|
749
909
|
todo: (fm && Array.isArray(fm.todo)) ? fm.todo : [],
|
|
750
910
|
}
|
|
751
911
|
}
|
|
912
|
+
/** P2:恢复时校验上一次提交的完整性(标记列出但缺失/为空的状态文件 = 撕裂提交)。 */
|
|
913
|
+
async function loadCommitMarker() {
|
|
914
|
+
const cm = await readJson('VibeMath_State/commit.json')
|
|
915
|
+
if (!cm || typeof cm !== 'object') { stateCommit = { seq: 0, at: 0, complete: false, mode: '', files: [], missing: [] }; return stateCommit }
|
|
916
|
+
const missing = []
|
|
917
|
+
const files = Array.isArray(cm.files) ? cm.files : []
|
|
918
|
+
for (let i = 0; i < files.length; i++) { if (!(await relNonEmpty(files[i]))) missing.push(files[i]) }
|
|
919
|
+
stateCommitSeq = Number(cm.seq) || 0
|
|
920
|
+
stateCommit = Object.assign({}, cm, { complete: missing.length === 0, missing: missing })
|
|
921
|
+
if (missing.length && files.length) logActivity('state', '上一次状态提交不完整(缺失/为空:' + missing.join(', ') + ')⇒ 按逐文件容错读取,最后一次提交的部分字段可能丢失')
|
|
922
|
+
return stateCommit
|
|
923
|
+
}
|
|
752
924
|
async function saveAll() {
|
|
753
|
-
|
|
754
|
-
await writeJson('VibeMath_State/agent_registry.json', agentRegistry)
|
|
755
|
-
await writeJson('VibeMath_State/decision_queue.json', decisionQueue)
|
|
756
|
-
await writeJson('VibeMath_State/verifier_accuracy.json', verifierAccuracy)
|
|
757
|
-
await writeJson('VibeMath_State/tasks.json', tasks)
|
|
758
|
-
await writeJson('VibeMath_State/explorer_retries.json', explorerRetries)
|
|
759
|
-
await writeJson('VibeMath_State/formal.json', { records: formalRecords(), todo: formalTodo() })
|
|
925
|
+
// P2:checkpoint 先落进内存再提交(旧顺序是提交完才改内存 ⇒ 磁盘上的 checkpoint 永远滞后一次)
|
|
760
926
|
scheduler.lastCheckpoint = now()
|
|
927
|
+
await commitState([
|
|
928
|
+
{ rel: 'VibeMath_State/scheduler_state.json', obj: scheduler },
|
|
929
|
+
{ rel: 'VibeMath_State/agent_registry.json', obj: agentRegistry },
|
|
930
|
+
{ rel: 'VibeMath_State/decision_queue.json', obj: decisionQueue },
|
|
931
|
+
{ rel: 'VibeMath_State/verifier_accuracy.json', obj: verifierAccuracy },
|
|
932
|
+
{ rel: 'VibeMath_State/tasks.json', obj: tasks },
|
|
933
|
+
{ rel: 'VibeMath_State/explorer_retries.json', obj: explorerRetries },
|
|
934
|
+
{ rel: 'VibeMath_State/formal.json', obj: { records: formalRecords(), todo: formalTodo() } },
|
|
935
|
+
// P3:待计分样本也必须落盘——旧实现只在内存,崩溃/恢复后这批评审永远不会被计分(准确率静默丢样本)
|
|
936
|
+
{ rel: 'VibeMath_State/pending_review_scores.json', obj: pendingReviewScores },
|
|
937
|
+
// P5:活动日志(有界)落盘;P6:论文排队/回收计数落盘(在途 childId 不落,见 loadState)
|
|
938
|
+
{ rel: 'VibeMath_State/activity_log.json', obj: activityLog.slice(-ACTIVITY_PERSIST_MAX) },
|
|
939
|
+
{ rel: 'VibeMath_State/paper.json', obj: { pending: paperPending, reaps: paperReaps } },
|
|
940
|
+
])
|
|
761
941
|
}
|
|
762
942
|
// 查询类工具(status/setup/report)汇报前重读设置文件:文件是唯一持久化源,可能在会话启动后被用户手改或由本进程外编辑更新。
|
|
763
943
|
async function refreshParams() {
|
|
@@ -1969,7 +2149,9 @@ export function apply(ctx) {
|
|
|
1969
2149
|
const rec = formalGateRecord(t)
|
|
1970
2150
|
if (formalGateOk(rec)) return false
|
|
1971
2151
|
const own = formalOf(t)
|
|
1972
|
-
const
|
|
2152
|
+
const statusZh = ({ none: '未尝试', attempted: '已尝试未通过', blocked: '已记录阻塞', passed: '已通过' })[String(rec.status || 'none')] || String(rec.status || 'none')
|
|
2153
|
+
const reason = 'formal-required:尚未取得 Lean 形式化通过,也没有显式阻塞记录(当前状态 ' + statusZh + ';代码 formal-required)' + (why ? '|' + why : '') +
|
|
2154
|
+
';下一步:把该对象形式化到 Lean 通过(vibe_math_lean_archive,kind=proof/lemma),或写入显式阻塞记录(kind=blocked)后重新提议该对象,即可再次表决'
|
|
1973
2155
|
const list = formalTodo().filter(function (x) { return x.id !== t })
|
|
1974
2156
|
list.push({ id: t, at: now(), why: reason })
|
|
1975
2157
|
// 不改动对象的既有权重/概率字段:只补一条 formal 记录(status 保持 none/attempted)。
|
|
@@ -2016,6 +2198,21 @@ export function apply(ctx) {
|
|
|
2016
2198
|
return out
|
|
2017
2199
|
}
|
|
2018
2200
|
async function findProposition(pId) { const all = await getPropos(); return all.find(function (p) { return p.id === pId }) }
|
|
2201
|
+
/**
|
|
2202
|
+
* P1:`add_proposition`/`/vibe add-proposition` 只用于**新建**。id 已存在时**拒绝**,绝不覆盖——
|
|
2203
|
+
* 旧实现是"按 id upsert",会把已有卡的 陈述/证明列表/证伪列表 换成全新的空列表(静默丢数据),
|
|
2204
|
+
* 而 sync_meta 的引理路径(v3 侧)本来就有 `propos.has` + 文件存在双守卫。这里把两者语义拉齐。
|
|
2205
|
+
*/
|
|
2206
|
+
function propositionIdConflict(id, all) {
|
|
2207
|
+
const hit = (all || []).filter(function (x) { return String(x.id) === String(id) })[0]
|
|
2208
|
+
if (!hit) return null
|
|
2209
|
+
return {
|
|
2210
|
+
code: 'PROPOSITION_ID_EXISTS',
|
|
2211
|
+
message: '命题 id "' + id + '" 已存在:add_proposition 只用于**新建**,不会覆盖已有卡(其 陈述/证明列表/证伪列表 一律保留)。',
|
|
2212
|
+
next: '换一个新 id 新建(例如 p-' + shortId() + ');要修改已有卡请用文件工具直接编辑 Propos/<分类>/<id>.md。',
|
|
2213
|
+
existing: { id: hit.id, 概述: hit.概述, 布尔估计: hit.布尔估计, category: hit._category },
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2019
2216
|
async function upsertProposition(p) {
|
|
2020
2217
|
const cat = p._category || categoryOf(p)
|
|
2021
2218
|
delete p._category
|
|
@@ -2025,13 +2222,6 @@ export function apply(ctx) {
|
|
|
2025
2222
|
await writeJson(proposFile(cat), list)
|
|
2026
2223
|
return cat
|
|
2027
2224
|
}
|
|
2028
|
-
async function deleteProposition(p) {
|
|
2029
|
-
const cat = p._category || categoryOf(p)
|
|
2030
|
-
const list = await readProposCategory(cat)
|
|
2031
|
-
const next = list.filter(function (x) { return x.id !== p.id })
|
|
2032
|
-
await writeJson(proposFile(cat), next)
|
|
2033
|
-
}
|
|
2034
|
-
|
|
2035
2225
|
// ================= data layer: Verified / Reliable =================
|
|
2036
2226
|
async function readVerifiedCategory(cat) { const a = await readJson('Verified/' + String(cat) + '_Verified.json'); return Array.isArray(a) ? a : [] }
|
|
2037
2227
|
async function reliableFiles() { return await listFiles('Reliable') }
|
|
@@ -2042,15 +2232,18 @@ export function apply(ctx) {
|
|
|
2042
2232
|
const qs = await getQs()
|
|
2043
2233
|
const propos = await getPropos()
|
|
2044
2234
|
return {
|
|
2045
|
-
ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
|
|
2235
|
+
ok: true, at: now(), project: currentProject, projectExists: await projectExistsOnDisk(), frameworkRoot: frameworkRoot(),
|
|
2236
|
+
stateWriteFailures: { count: stateWriteFailures.length, last: stateWriteFailures[stateWriteFailures.length - 1] || null },
|
|
2046
2237
|
running: scheduler.running, mode: params.mode,
|
|
2047
2238
|
activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
2048
2239
|
problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
|
|
2049
2240
|
propositions: { total: propos.length, resolved: propos.filter(function (p) { return p.布尔估计 === 1 || p.布尔估计 === 0 }).length },
|
|
2050
|
-
|
|
2241
|
+
// F-A(有意拆分):pendingDecisions 是**合同**(两面同形的数字);pendingDecisionItems 是**诊断**(明细),只出现在 report——status 面向代理自省、report 面向文件快照。改动此处需同步 docs/status-report-fields.md(该文档租约在共享层 owner)。
|
|
2242
|
+
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
2243
|
+
pendingDecisionItems: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
2051
2244
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
2052
2245
|
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
2053
|
-
verifyTasks:
|
|
2246
|
+
verifyTasks: verifyTasksView(tasks),
|
|
2054
2247
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)),
|
|
2055
2248
|
// Lean 形式化:可调档位与开关同处可读参数表(契约 §1),并附当前形式化记录概况。
|
|
2056
2249
|
formal: {
|
|
@@ -2058,9 +2251,12 @@ export function apply(ctx) {
|
|
|
2058
2251
|
leanCommand: params.leanCommand, leanArgs: params.leanArgs, leanTimeoutMs: params.leanTimeoutMs,
|
|
2059
2252
|
objects: Object.keys(formalRecords()).map(function (k) { const r = formalRecords()[k] || {}; return { target: k, status: r.status, file: r.file, proof: r.proof, note: r.note } }),
|
|
2060
2253
|
todo: formalTodo(),
|
|
2061
|
-
|
|
2254
|
+
// F6:四项路径基准不同 ⇒ 必须点名 base(project/proofs 相对 base,lib/proved 已绝对)。
|
|
2255
|
+
paths: { base: frameworkRoot() + '/', project: 'Formal/', proofs: 'Verified/Lean/', lib: vibeRoot() + '/Formal/Lib/', proved: vibeRoot() + '/Formal/Proved/', note: 'project/proofs 相对 base(项目根);lib/proved 已含绝对前缀' },
|
|
2062
2256
|
},
|
|
2063
2257
|
params: params,
|
|
2258
|
+
// F5:report/status 两面字段集对齐(旧实现只有 status 看得到论文/编译状态)。
|
|
2259
|
+
paper: await paperStatusView(),
|
|
2064
2260
|
}
|
|
2065
2261
|
}
|
|
2066
2262
|
async function maybeWriteReport(force) {
|
|
@@ -2086,8 +2282,8 @@ export function apply(ctx) {
|
|
|
2086
2282
|
const report = await buildReport()
|
|
2087
2283
|
const text = '[Vibe Math V2] 进度更新:当前项目 "' + currentProject + '" 运行中=' + report.running +
|
|
2088
2284
|
',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
|
|
2089
|
-
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions
|
|
2090
|
-
'请调用 vibe_math_report
|
|
2285
|
+
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions + '。' +
|
|
2286
|
+
'请调用 vibe_math_report 汇总当前进展,并用 vibe_math_list_agents 取各代理(id/角色/目标)状态,再用人话简要汇报(不打断用户,简短即可)。'
|
|
2091
2287
|
// 来源 kind 必须是**已声明**的:`MessageSourceMap` 是 merge-extensible 的联合,但没有共享的
|
|
2092
2288
|
// catch-all `plugin` kind(dsh-llm message.d.ts),{kind:'plugin'} 是契约外形状。role 本来就是
|
|
2093
2289
|
// 'user',正文也自带 "[Vibe Math V2] 进度更新" 的真署名,所以用核心声明的 {kind:'user'}。
|
|
@@ -2099,7 +2295,14 @@ export function apply(ctx) {
|
|
|
2099
2295
|
}
|
|
2100
2296
|
|
|
2101
2297
|
// ================= child spawn / followup =================
|
|
2102
|
-
|
|
2298
|
+
// F-6a:宿主 list() 失败(或既无 spawn 也无 fork)时回退到假定值必须**留痕**,不能静默猜。
|
|
2299
|
+
let providerFallbackWarned = false
|
|
2300
|
+
function warnProviderFallback(why) {
|
|
2301
|
+
if (providerFallbackWarned) return
|
|
2302
|
+
providerFallbackWarned = true
|
|
2303
|
+
console.error('vibe-math-v2: pickProvider() falling back to \'spawn\': ' + why)
|
|
2304
|
+
}
|
|
2305
|
+
function pickProvider() { let names = []; let listed = false; try { names = subagents.list ? subagents.list() : []; listed = true } catch (e) { warnProviderFallback('subagents.list() failed: ' + ((e && e.message) || e)) } if (names.indexOf('spawn') !== -1) return 'spawn'; if (names.indexOf('fork') !== -1) return 'fork'; if (listed) warnProviderFallback('host exposes neither spawn nor fork (list=' + JSON.stringify(names) + ')'); return 'spawn' }
|
|
2103
2306
|
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 }
|
|
2104
2307
|
/**
|
|
2105
2308
|
* Drop filter names this host does not register. `known` comes from the host's
|
|
@@ -2172,6 +2375,12 @@ export function apply(ctx) {
|
|
|
2172
2375
|
* 都吞成静默成功(调用方拿到 undefined,还以为已经中断)。现在:空/未知 id ⇒ `code` +
|
|
2173
2376
|
* `next{tool,hint}`,宿主抛错 ⇒ 保留错误文本并给替代出口;只有真的发出中断才 `ok:true`。
|
|
2174
2377
|
*/
|
|
2378
|
+
/** F-6b:中断失败必须留痕(调用点此前丢掉 {ok:false},界面看起来一切正常)。 */
|
|
2379
|
+
async function interruptTraced(cid, why) {
|
|
2380
|
+
const r = await interruptChild(cid)
|
|
2381
|
+
if (r && r.ok === false) logActivity('interrupt', '中断失败(' + why + '):' + String(cid) + ' — ' + String(r.message || r.code || ''))
|
|
2382
|
+
return r
|
|
2383
|
+
}
|
|
2175
2384
|
async function interruptChild(childId) {
|
|
2176
2385
|
const id = String(childId == null ? '' : childId).trim()
|
|
2177
2386
|
if (!id) return { ok: false, code: 'VIBE_MATH_INVALID_ARGUMENT', message: 'interruptChild 失败:childId 为空——无法确定要中断哪个子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 列出本会话在册子代理的 id,再带 childId 调用。' } }
|
|
@@ -2229,6 +2438,14 @@ export function apply(ctx) {
|
|
|
2229
2438
|
// 数学计算可用性行(prompts.md §2+§4):与"顺手形式化"并列注入;off 档为空串。
|
|
2230
2439
|
return '\n' + t + formalReplyNote() + notice + math + '\n'
|
|
2231
2440
|
}
|
|
2441
|
+
/**
|
|
2442
|
+
* F7:机器回执的**键名大小写**是契约的一部分,必须显式写在提示词里。
|
|
2443
|
+
* v2 各回执的对象类型不同(验证回执用 `Result`/`Reason` 首字母大写;计划/求解/方法卡回执用小写
|
|
2444
|
+
* `directions`/`lemmas`/`status`/`solution`/`solution_probability`),历史上只在示例里体现大小写——
|
|
2445
|
+
* 模型猜错键名会被解析器当成"缺字段"静默兜底(`Result` 缺失 ⇒ 0.5),所以这里统一说清:
|
|
2446
|
+
* **解析器按大小写精确匹配、不接受别名**(`parsed.Result` / `parsed.directions` 都是原样取键)。
|
|
2447
|
+
*/
|
|
2448
|
+
const REPLY_KEY_STYLE_LINE = '\nKEY CASE IS PART OF THE REPLY CONTRACT: the verifier reply uses `Result`/`Reason` (capitalised; the debate round adds `changed`), while every other reply uses lowercase keys exactly as shown (`directions`, `lemmas`, `status`, `solution`, `solution_probability`). The parser matches keys EXACTLY, case-sensitively, and accepts NO aliases (e.g. `result` is not read).\n'
|
|
2232
2449
|
function capabilitiesText(role) {
|
|
2233
2450
|
const maxCalls = role === 'solver' ? params.solverMaxToolCalls : params.verifierMaxToolCalls
|
|
2234
2451
|
const netOn = role === 'solver' ? params.solverAllowNetwork : params.verifierAllowNetwork
|
|
@@ -2244,6 +2461,7 @@ export function apply(ctx) {
|
|
|
2244
2461
|
t += '- You may READ any file under Verified/ as a known, trusted dependency (resolved facts).\n'
|
|
2245
2462
|
t += '- You should BASE your reasoning on the existing knowledge under Propos/ (propositions with proofs/refutations and probabilities) and Reliable/ (trusted references).\n'
|
|
2246
2463
|
t += '- You must NOT write files directly: return structured JSON only — the scheduler is the single writer.\n'
|
|
2464
|
+
t += REPLY_KEY_STYLE_LINE
|
|
2247
2465
|
t += '\nHOW TO READ EXISTING KNOWLEDGE (coarse scan → fine read):\n'
|
|
2248
2466
|
t += '- These are JSON files. A conclusion object carries summary-index fields (概述 / 布尔估计 / 优先级) and the full detail (证明列表 / 证伪列表 / 完整过程 / progress).\n'
|
|
2249
2467
|
t += '- COARSE SCAN first: use a read/grep tool to extract ONLY the summary index (概述, 布尔估计, 优先级, titles) to locate which files / objects look relevant — do NOT load full proofs yet.\n'
|
|
@@ -2714,7 +2932,7 @@ export function apply(ctx) {
|
|
|
2714
2932
|
}
|
|
2715
2933
|
async function backfillVerifiers(t) {
|
|
2716
2934
|
const cap = reviewerCap()
|
|
2717
|
-
const want = voteCount(t).expected // 快照目标(不再直接读 t.expectedCount,保持单一来源)
|
|
2935
|
+
const want = voteCount(t).expected // 快照目标(不再直接读 t.expectedCount,保持单一来源)
|
|
2718
2936
|
while (t.children.length < want && t.children.length < cap) {
|
|
2719
2937
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
2720
2938
|
const index = t.children.length
|
|
@@ -2890,8 +3108,9 @@ export function apply(ctx) {
|
|
|
2890
3108
|
if (!lemma || !lemma.title) return
|
|
2891
3109
|
let be = clamp01(lemma.布尔估计 != null ? lemma.布尔估计 : 0.6)
|
|
2892
3110
|
if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01 // 写入时概率必须 <1 且 >0(待验证器验证)
|
|
3111
|
+
const p8Ids = (await getPropos()).map(function (x) { return x.id })
|
|
2893
3112
|
const p = {
|
|
2894
|
-
id: 'p-' +
|
|
3113
|
+
id: 'p-' + shortIdUnique(function (x) { return p8Ids.indexOf(x) !== -1 }), 概述: lemma.statement || lemma.title,
|
|
2895
3114
|
布尔估计: be,
|
|
2896
3115
|
细类型: (lemma.细类型 && typeof lemma.细类型 === 'object') ? lemma.细类型 : { 未分类: {} },
|
|
2897
3116
|
证明列表: [{ 完整过程: lemma.proof || '', 正确概率: clamp01(0.7), '支持信息/依据': '' }],
|
|
@@ -2993,9 +3212,23 @@ export function apply(ctx) {
|
|
|
2993
3212
|
quorum: participants.length > 0 && participants.length >= expected && thisRoundIds.length === participants.length,
|
|
2994
3213
|
}
|
|
2995
3214
|
}
|
|
3215
|
+
/**
|
|
3216
|
+
* 待验证任务视图(v2 唯一的"在飞验证任务清单"入口)。**单一来源**:`status` 与 `report` 两条
|
|
3217
|
+
* 路径都必须调它——审计 6.5 之前这段投影被内联复制了两份,任何一边改字段就会让两个视图分叉
|
|
3218
|
+
* (与 D4 的教训同型)。字段语义:expected = 创建时快照票数,dispatched = 本轮派出的评审数,
|
|
3219
|
+
* reported = 本轮已报票数,quorum = 本轮是否齐票。
|
|
3220
|
+
*/
|
|
3221
|
+
function verifyTasksView(tasks) {
|
|
3222
|
+
return Object.keys(tasks || {}).filter(function (k) { return tasks[k] && tasks[k].type === 'verify' })
|
|
3223
|
+
.map(function (k) {
|
|
3224
|
+
const t = tasks[k]
|
|
3225
|
+
const v = voteCount(t)
|
|
3226
|
+
return { rId: t.rId, status: t.status, round: v.round, expected: v.expected, dispatched: v.dispatched, reported: v.reportedThisRound, quorum: v.quorum }
|
|
3227
|
+
})
|
|
3228
|
+
}
|
|
2996
3229
|
// 一票不算共识:≥MIN_REVIEWERS 份独立评审才可能达成/否决共识(审计 M11;与 v3 的最小票数同型)。
|
|
2997
3230
|
function consensus(t) { const v = voteCount(t); if (v.reportedIds.length < MIN_REVIEWERS) return false; const vs = v.reportedIds.map(function (cid) { return t.childResults[cid].Result }); return vs.every(function (x) { return x === 1 }) || vs.every(function (x) { return x === 0 }) }
|
|
2998
|
-
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') }
|
|
3231
|
+
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]]; const changedNote = (r.changed != null && String(r.changed).trim()) ? (' [changed: ' + String(r.changed).trim() + ']') : ''; parts.push('Reviewer ' + i + ': Result=' + r.Result + ' Reason=' + r.Reason + changedNote) } return parts.join('\n') }
|
|
2999
3232
|
// 说明(审计 L17):这里曾有一个 `verifierWeight(cid, rigor)`,但全仓只有它的定义、没有任何调用点,
|
|
3000
3233
|
// 且其公式(clamped ±0.2 rigor 加成、按 childId 取准确率)与 finalVerdict 里真正在用的
|
|
3001
3234
|
// 稳定身份键 + 0.1 自信加成**并不相同**——留着它只会让人以为 forced 模式走的是那条公式。已删除。
|
|
@@ -3005,7 +3238,11 @@ export function apply(ctx) {
|
|
|
3005
3238
|
* 由 spawnChild 写进 agentRegistry 的 meta,再随每次投票进入 childResults。
|
|
3006
3239
|
*/
|
|
3007
3240
|
function verifierIdentityKey() { const o = childAgentOptions(); return 'm:' + String(o.provider || '-') + '/' + String(o.model || '-') }
|
|
3008
|
-
const PENDING_SCORE_MAX_OBJECTS = 32
|
|
3241
|
+
const PENDING_SCORE_MAX_OBJECTS = 32 // 全局最多保留多少个对象的待计分样本(FIFO 丢弃,见 recordReviewForScoring)
|
|
3242
|
+
const PENDING_SCORE_CAP_PER_OBJECT = 8 // 每个对象最多保留多少条待计分样本
|
|
3243
|
+
const PENDING_SCORE_WARN_INTERVAL_MS = 5 * 60 * 1000 // 全局丢弃告警的最小间隔(避免每票刷屏)
|
|
3244
|
+
const scoreCapWarned = new Set()
|
|
3245
|
+
let scoreGlobalWarnedAt = 0
|
|
3009
3246
|
/** 记下本轮各评审的投票,等**这个对象后来**取得布尔定论时再回溯计分(见 scorePendingReviews)。 */
|
|
3010
3247
|
function recordReviewForScoring(objectId, key, result) {
|
|
3011
3248
|
const id = String(objectId || '')
|
|
@@ -3013,9 +3250,26 @@ export function apply(ctx) {
|
|
|
3013
3250
|
let list = pendingReviewScores[id]
|
|
3014
3251
|
if (!list) { list = []; pendingReviewScores[id] = list }
|
|
3015
3252
|
list.push({ key: String(key || 'm:unknown'), result: clamp01(result) })
|
|
3016
|
-
|
|
3253
|
+
// 有界保留(**故意**的截断,已文档化):每对象最多 PENDING_SCORE_CAP_PER_OBJECT 条待计分样本、
|
|
3254
|
+
// 全局最多 PENDING_SCORE_MAX_OBJECTS 个对象;超限丢弃**最旧**的(FIFO)。丢弃会写活动日志,
|
|
3255
|
+
// 避免"verifierAccuracy 随对象数悄悄漂移"而无人知晓(已累计的准确率不受影响)。
|
|
3256
|
+
if (list.length > PENDING_SCORE_CAP_PER_OBJECT) {
|
|
3257
|
+
list.shift()
|
|
3258
|
+
if (!scoreCapWarned.has(id)) {
|
|
3259
|
+
scoreCapWarned.add(id)
|
|
3260
|
+
if (scoreCapWarned.size > 64) scoreCapWarned.clear()
|
|
3261
|
+
logActivity('verify', '评审计分样本达上限(每对象 ' + PENDING_SCORE_CAP_PER_OBJECT + ' 条):' + id + ' 丢弃最旧一条(故意保留上限;已累计 verifierAccuracy 不受影响)')
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3017
3264
|
const ids = Object.keys(pendingReviewScores)
|
|
3018
|
-
|
|
3265
|
+
if (ids.length > PENDING_SCORE_MAX_OBJECTS) {
|
|
3266
|
+
let dropped = 0
|
|
3267
|
+
while (ids.length > PENDING_SCORE_MAX_OBJECTS) { delete pendingReviewScores[ids.shift()]; dropped++ }
|
|
3268
|
+
if (now() - scoreGlobalWarnedAt > PENDING_SCORE_WARN_INTERVAL_MS) {
|
|
3269
|
+
scoreGlobalWarnedAt = now()
|
|
3270
|
+
logActivity('verify', '评审计分样本的对象数达上限(' + PENDING_SCORE_MAX_OBJECTS + '):丢弃最旧的 ' + dropped + ' 个对象的待计分样本(故意保留上限;已累计 verifierAccuracy 不受影响)')
|
|
3271
|
+
}
|
|
3272
|
+
}
|
|
3019
3273
|
}
|
|
3020
3274
|
/** 对象取得布尔定论(1/0)时,用它给**此前**那些还没被检验过的评审打分。绝不拿聚合值给同一批打分。 */
|
|
3021
3275
|
async function scorePendingReviews(objectId, truth) {
|
|
@@ -3051,7 +3305,7 @@ export function apply(ctx) {
|
|
|
3051
3305
|
return
|
|
3052
3306
|
}
|
|
3053
3307
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
3054
|
-
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, key: meta.verificationKey || verifierIdentityKey() }
|
|
3308
|
+
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, key: meta.verificationKey || verifierIdentityKey(), changed: (parsed && parsed.changed != null && String(parsed.changed).trim()) ? String(parsed.changed).trim() : null }
|
|
3055
3309
|
delete agentRegistry[childId]
|
|
3056
3310
|
const allReported = voteCount(t, meta.round).allParticipantsReported
|
|
3057
3311
|
if (!allReported) { await saveAll(); return }
|
|
@@ -3200,7 +3454,7 @@ export function apply(ctx) {
|
|
|
3200
3454
|
// 文件名来自 t.rId,而 rId 是**用户可写**的 q.id / p.id 拼出来的(buildVerifyCandidates 里
|
|
3201
3455
|
// 'r-' + q.id + '-s' + j)。fs.resolve 只做规范化、并不拒绝 '..',所以 q.id = 'x/../../../../pwn'
|
|
3202
3456
|
// 会让这次写落到项目树之外(审计 C1,已用 pathdemo2.cjs 复现)。路径段一律过 safeId。
|
|
3203
|
-
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() })
|
|
3457
|
+
await writeJson('Verification_logs/' + safeId(String(t.rId)) + '_' + Date.now() + '_' + shortId() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
|
|
3204
3458
|
|
|
3205
3459
|
if (r.kind === 'proposition') {
|
|
3206
3460
|
const p = await findProposition(r.pId)
|
|
@@ -3488,7 +3742,7 @@ export function apply(ctx) {
|
|
|
3488
3742
|
const cid = paperInFlight
|
|
3489
3743
|
paperInFlight = ''; paperInFlightAt = 0
|
|
3490
3744
|
if (cid) {
|
|
3491
|
-
try { await
|
|
3745
|
+
try { await interruptTraced(cid, 'paper writer re-dispatch') } catch (e) { /* best effort */ }
|
|
3492
3746
|
delete agentRegistry[cid]
|
|
3493
3747
|
paperAbandoned[cid] = true
|
|
3494
3748
|
}
|
|
@@ -3508,19 +3762,24 @@ export function apply(ctx) {
|
|
|
3508
3762
|
/**
|
|
3509
3763
|
* 成员可见路径说明(跨预设审计 P0)。成员/子代理的**文件工具按会话 cwd 解析**相对路径,而下面列出的
|
|
3510
3764
|
* 路径都是**项目根相对**的 ⇒ 必须显式说明"先拼绝对前缀";计算产物用回执里的绝对字段。一处常量、
|
|
3511
|
-
*
|
|
3765
|
+
* 两处**成员可见**材料文本(`paperEvidenceIndex` 证据索引 + `buildPaperDigest` 摘要)各自 append(绝不替换既有行)。
|
|
3766
|
+
* 另三处(`paperCompile` / `paperEmitArtifacts` / `paperMissingArtifacts`)是**框架侧文件操作**,其路径从不进入
|
|
3767
|
+
* 成员可见文本,因此**不**追加本说明——这不是遗漏,而是按"是否需要成员照抄路径"分类的结果。
|
|
3512
3768
|
*/
|
|
3513
3769
|
const PAPER_PATH_NOTE = '(路径说明:成员/子代理的文件工具按**会话 cwd** 解析相对路径,因此上面列出的相对路径都必须先拼上**项目根的绝对前缀**再使用;计算产物请用回执里的绝对字段 `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来。)'
|
|
3514
3770
|
async function paperEvidenceIndex() {
|
|
3515
3771
|
const formalNow = formalOn()
|
|
3516
|
-
const out = [
|
|
3772
|
+
const out = []
|
|
3773
|
+
if (await paperPathExists('qs/qs.json')) out.push('qs/qs.json')
|
|
3517
3774
|
if (formalNow && await paperPathExists('VibeMath_State/formal.json')) out.push('VibeMath_State/formal.json')
|
|
3518
3775
|
for (const f of await listFiles('Propos')) out.push('Propos/' + f)
|
|
3519
3776
|
for (const f of await listFiles('Verified')) out.push('Verified/' + f)
|
|
3520
3777
|
if (formalNow) for (const f of await listFiles('Verified/Lean')) out.push('Verified/Lean/' + f)
|
|
3778
|
+
// 审计 F2:提示词要求「引用 Reliable/ 里的可信来源时要给出处」,材料证据索引也必须收它,
|
|
3779
|
+
// 否则被要求引用的那一层永远进不了论文材料(reliableFiles() 因此从死代码变成活代码)。
|
|
3780
|
+
for (const f of await reliableFiles()) out.push('Reliable/' + f)
|
|
3521
3781
|
const dirs = ['Verification_logs', 'Progress_Logs'].concat(formalNow ? ['Formal'] : [])
|
|
3522
3782
|
for (let i = 0; i < dirs.length; i++) { const files = await listFiles(dirs[i]); for (let j = 0; j < files.length; j++) out.push(dirs[i] + '/' + files[j]) }
|
|
3523
|
-
out.push(PAPER_PATH_NOTE)
|
|
3524
3783
|
return out.filter(function (x) { return x.indexOf('Paper/') !== 0 }).sort()
|
|
3525
3784
|
}
|
|
3526
3785
|
/** 汇总材料(spec §4):只含既有证据 + 未决/被否证的显式标注。不含时间戳 ⇒ 哈希稳定。 */
|
|
@@ -3587,7 +3846,7 @@ export function apply(ctx) {
|
|
|
3587
3846
|
PAPER_SKELETON.map(function (s, i) { return ' ' + (i + 1) + '. ' + s.key + ' — ' + s.spec }).join('\n') + '\n' +
|
|
3588
3847
|
'- A section with no evidence must be exactly 「' + PAPER_NO_EVIDENCE + '」 (do not pad it).\n' +
|
|
3589
3848
|
'- Markdown subset only: `#`/`##`/`###`, `- ` lists, `**bold**`, `*em*`, `` `code` ``, and inline math as `$...$`. No tables, images, footnotes or raw HTML.\n\n' +
|
|
3590
|
-
'OUTPUT CONTRACT — respond with ONLY one ```json code fence, no prose:\n' +
|
|
3849
|
+
'OUTPUT CONTRACT — respond with ONLY one ```json code fence, no prose:\n' + REPLY_KEY_STYLE_LINE +
|
|
3591
3850
|
'{"title":"<paper title>","abstract":"<original problem + main results>","sections":[{"name":"<one of the 9 headings>","body":"<markdown>"}, ...]}\n\n' +
|
|
3592
3851
|
'MATERIAL (evidence only — do not add anything beyond it):\n' + digest
|
|
3593
3852
|
}
|
|
@@ -3943,7 +4202,7 @@ export function apply(ctx) {
|
|
|
3943
4202
|
const prevEpoch = await readJson('VibeMath_State/process_epoch.json')
|
|
3944
4203
|
const stale = typeof prevEpoch === 'string' && prevEpoch !== processEpoch
|
|
3945
4204
|
if (fresh || stale) {
|
|
3946
|
-
if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await
|
|
4205
|
+
if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptTraced(ids[i], 'batch clear (start/abort)') }
|
|
3947
4206
|
if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
|
|
3948
4207
|
logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
|
|
3949
4208
|
agentRegistry = {}; tasks = {}
|
|
@@ -3967,7 +4226,7 @@ export function apply(ctx) {
|
|
|
3967
4226
|
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() } }
|
|
3968
4227
|
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() } }
|
|
3969
4228
|
async function pauseScheduler() { scheduler.running = false; logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
3970
|
-
async function abortScheduler() { scheduler.running = false; abandonGatedDecision('scheduler aborted'); const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await
|
|
4229
|
+
async function abortScheduler() { scheduler.running = false; abandonGatedDecision('scheduler aborted'); const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptTraced(ids[i], 'batch clear (start/abort)'); agentRegistry = {}; paperInFlight = ''; paperInFlightAt = 0; paperReaps = 0; paperPending = null; logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
3971
4230
|
// auto 模式语义 = 无人值守自动通过关键节点:切回 auto 时把仍挂起的人工决策按自动策略放行
|
|
3972
4231
|
async function autoResolvePending() {
|
|
3973
4232
|
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
@@ -3988,11 +4247,17 @@ export function apply(ctx) {
|
|
|
3988
4247
|
}
|
|
3989
4248
|
if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
|
|
3990
4249
|
}
|
|
4250
|
+
/** 审计 F7:`project` 是**当前会话**的项目名,`projects` 是磁盘上已存在的 slug 列表;
|
|
4251
|
+
* 两者不闭合时(新会话 project='default' 而磁盘上没有该目录)语义必须显式,不能靠调用方猜。 */
|
|
4252
|
+
async function projectExistsOnDisk() { return (await listDirsAt(vibeRoot(), 'Projects')).indexOf(currentProject) !== -1 }
|
|
3991
4253
|
async function getStatus() {
|
|
3992
4254
|
const qs = await getQs(); const propos = await getPropos()
|
|
3993
4255
|
return {
|
|
4256
|
+
at: now(),
|
|
3994
4257
|
ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
|
|
3995
|
-
project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
|
|
4258
|
+
project: currentProject, projectExists: await projectExistsOnDisk(), projects: await listDirsAt(vibeRoot(), 'Projects'),
|
|
4259
|
+
stateCommit: stateCommit,
|
|
4260
|
+
stateWriteFailures: { count: stateWriteFailures.length, last: stateWriteFailures[stateWriteFailures.length - 1] || null },
|
|
3996
4261
|
mode: params.mode, activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
3997
4262
|
frameworkRoot: frameworkRoot(),
|
|
3998
4263
|
problems: { total: qs.length, solved: qs.filter(function (q) { return q.已解决 }).length },
|
|
@@ -4000,16 +4265,24 @@ export function apply(ctx) {
|
|
|
4000
4265
|
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
4001
4266
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
4002
4267
|
// D4 审计面:每个在飞验证任务的参与集/票数都由 voteCount 投影出来(两个视图可对照)。
|
|
4003
|
-
verifyTasks:
|
|
4268
|
+
verifyTasks: verifyTasksView(tasks),
|
|
4004
4269
|
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,
|
|
4005
4270
|
formal: {
|
|
4006
4271
|
mode: formalMode(), required: formalRequired(),
|
|
4007
4272
|
leanCommand: params.leanCommand, leanArgs: params.leanArgs, leanTimeoutMs: params.leanTimeoutMs,
|
|
4008
4273
|
objects: Object.keys(formalRecords()).map(function (k) { const r = formalRecords()[k] || {}; return { target: k, status: r.status, file: r.file, proof: r.proof, note: r.note } }),
|
|
4009
4274
|
todo: formalTodo(),
|
|
4010
|
-
|
|
4275
|
+
// F6:四项路径基准不同 ⇒ 必须点名 base(project/proofs 相对 base,lib/proved 已绝对)。
|
|
4276
|
+
paths: { base: frameworkRoot() + '/', project: 'Formal/', proofs: 'Verified/Lean/', lib: vibeRoot() + '/Formal/Lib/', proved: vibeRoot() + '/Formal/Proved/', note: 'project/proofs 相对 base(项目根);lib/proved 已含绝对前缀' },
|
|
4011
4277
|
},
|
|
4012
4278
|
// 最终论文的可观测面(spec §2/§6):自动开关、在途/排队、定稿时间、产物与编译结果。
|
|
4279
|
+
// F3(对照 v5 的 leanNoticesScope/fieldScopes):本面把**会话内存**与**耐久**字段并排放在同一层,
|
|
4280
|
+
// 这里逐组标注来源,读者不必猜哪个重启会归零。
|
|
4281
|
+
fieldScopes: {
|
|
4282
|
+
durable: ['project', 'projects', 'problems', 'propositions', 'stateCommit', 'formal', 'paper.finalizedAt', 'paper.artifacts', 'paper.compile'],
|
|
4283
|
+
session: ['stateWriteFailures', 'activeCount', 'pendingDecisions', 'registeredAgents', 'verifyTasks', 'recentActivity', 'paper.inFlight', 'paper.inFlightSince', 'paper.inFlightAgeMs', 'paper.reapedThisRun', 'paper.queued'],
|
|
4284
|
+
note: 'session = 本进程内存,重启后归零(看着永远健康是假象);durable = 落在项目树里(State/Formal/Progress/Verified 等),重启后仍在',
|
|
4285
|
+
},
|
|
4013
4286
|
paper: await paperStatusView(),
|
|
4014
4287
|
}
|
|
4015
4288
|
}
|
|
@@ -4061,12 +4334,14 @@ export function apply(ctx) {
|
|
|
4061
4334
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
4062
4335
|
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() })
|
|
4063
4336
|
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 } })
|
|
4064
|
-
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 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(
|
|
4337
|
+
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 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), async function (args) { params = Object.assign({}, params, sanitizeParams(args)); await saveAll(); await saveSettings(); if (args && MATH_PARAM_NAMES.some(function (k) { return k in args })) { try { await refreshMathProbe(true) } catch (e) { /* 探测失败不影响参数保存 */ } } return { ok: true, params: params } })
|
|
4065
4338
|
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' } })
|
|
4066
4339
|
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() })
|
|
4067
4340
|
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') })
|
|
4068
4341
|
registerTool('vibe_math_add_problem', 'Add a problem to the current project qs/qs.json.', objParams({ id: { type: 'string' }, description: { type: 'string' }, priority: { type: 'integer' } }, ['id', 'description']), async function (args) { const qs = await getQs(); if (qs.some(function (q) { return q.id === args.id })) return { ok: false, message: 'problem id already exists' }; qs.push({ id: args.id, 概述: args.description, 已解决: false, 解法列表: [], 优先级: args.priority || 0, progress: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } })
|
|
4069
|
-
registerTool('vibe_math_add_proposition', 'Add a proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性).', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), async function (args) {
|
|
4342
|
+
registerTool('vibe_math_add_proposition', 'Add a NEW proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性). The id must be NEW: if it already exists the call is REFUSED with code PROPOSITION_ID_EXISTS (existing cards are never overwritten — their 证明列表/证伪列表 are preserved); pick a fresh id (e.g. p-<8 hex>) or edit the card file Propos/<分类>/<id>.md directly.', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), async function (args) {
|
|
4343
|
+
const p1Conflict = propositionIdConflict(args.id, await getPropos())
|
|
4344
|
+
if (p1Conflict) { logActivity('proposition', '拒绝覆盖已有命题:' + args.id); return Object.assign({ ok: false }, p1Conflict) }
|
|
4070
4345
|
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', 说明: '用户手动添加。' } }
|
|
4071
4346
|
await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) }
|
|
4072
4347
|
})
|
|
@@ -4114,7 +4389,7 @@ export function apply(ctx) {
|
|
|
4114
4389
|
if (cmd === 'save') return await saveSettings()
|
|
4115
4390
|
if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
|
|
4116
4391
|
if (cmd === 'add') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add <id> <description>' }; const qs = await getQs(); if (qs.some(function (q) { return q.id === id })) return { ok: false, message: 'problem id already exists' }; qs.push({ id: id, 概述: desc, 已解决: false, 解法列表: [], 优先级: 0, progress: { directions: [] } }); await writeQs(qs); scheduleTick(); return { ok: true, message: 'problem added' } }
|
|
4117
|
-
if (cmd === 'add-proposition') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const p = { id: id, 概述: desc, 布尔估计: 0.5, 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1, '价值/关键性': 0.5, progress: { 来源: 'user-vibe', 说明: '用户通过 /vibe 添加。' } }; await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) } }
|
|
4392
|
+
if (cmd === 'add-proposition') { const id = args[0]; const desc = args.slice(1).join(' '); if (!id || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const p1c = propositionIdConflict(id, await getPropos()); if (p1c) { logActivity('proposition', '拒绝覆盖已有命题:' + id); return Object.assign({ ok: false }, p1c) } const p = { id: id, 概述: desc, 布尔估计: 0.5, 细类型: { 未分类: {} }, 证明列表: [], 证伪列表: [], 优先级: 1, '价值/关键性': 0.5, progress: { 来源: 'user-vibe', 说明: '用户通过 /vibe 添加。' } }; await upsertProposition(p); scheduleTick(); return { ok: true, proposition: p, file: proposFile(categoryOf(p)) } }
|
|
4118
4393
|
if (cmd === 'list-propositions') { const all = await getPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 概述: p.概述, 布尔估计: p.布尔估计, 优先级: p.优先级, '价值/关键性': p['价值/关键性'], category: p._category } }) } }
|
|
4119
4394
|
if (cmd === 'project') {
|
|
4120
4395
|
if (args.length === 0 || args[0] === 'list') return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') }
|
|
@@ -4150,10 +4425,6 @@ export function apply(ctx) {
|
|
|
4150
4425
|
mathProbeDue: mathProbeDue,
|
|
4151
4426
|
refreshMathProbe: refreshMathProbe,
|
|
4152
4427
|
mathProbe: function () { return mathProbe },
|
|
4153
|
-
// P2a:persona 两个文本块必须含的「归档→编辑→重跑」规则(文本取自共享模块常量,persona 不手抄)。
|
|
4154
|
-
mathArchiveWorkflowLine: MATH_ARCHIVE_WORKFLOW_LINE,
|
|
4155
|
-
// A 项:替代声明规则(文本由共享模块给出;persona 两块与每轮可用性行都必须带它)。
|
|
4156
|
-
mathSubstitutionRuleLine: MATH_SUBSTITUTION_RULE_LINE,
|
|
4157
4428
|
leanQueueSize: function () { return leanQueue.length },
|
|
4158
4429
|
leanJobsPublic: function () { const out = []; for (const j of leanJobs.values()) out.push(leanJobPublic(j)); return out },
|
|
4159
4430
|
// childOwner 裁剪用:这个会话当前仍"可能再发 subagent/end"的 child(在册的 + 任务正在等的)。
|
|
@@ -4201,12 +4472,12 @@ export function apply(ctx) {
|
|
|
4201
4472
|
registerTool('vibe_math_status', 'Show scheduler status, params, active agents, projects, and recent activity.', objParams({}), 'vibe_math_status')
|
|
4202
4473
|
registerTool('vibe_math_report', 'Return the full progress report and write it to Progress_Logs/report.json.', objParams({}), 'vibe_math_report')
|
|
4203
4474
|
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')
|
|
4204
|
-
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 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(
|
|
4475
|
+
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 工具链的调用方式(框架会在用户 leanArgs 之后、文件名之前自动追加 `--search-path <VibeMath 根>`,用户已显式给出就不注入);leanAsync = true(默认,后台队列:入队即返回,只有作业落地 ok 才置 passed 并写归档证明)| false(同步 await 的旧语义)。leanJobsMaxParallel = 后台编译并发上限(默认 1 = 串行);leanInitiative = off|normal|eager 控制**日常的**形式化主动性(与 formalVerify 的验证要求强度是两件事:formalVerify=off 只关验证阶段,eager 仍注入日线);leanSearchPaths = 额外 Lean 搜索路径(默认空 = 只用自动注入的 VibeMath 根;非空时先注入它们、再注入自动根)。最终论文:finalPaper(默认 true;收口时自动派遣一名「论文撰写」子代理)/ paperFormat = both|md|tex / paperLanguage = zh|en / paperCompilePdf(检测到 LaTeX 时编译 paper.pdf)/ paperLatexCommand(指定引擎,空 = 自动探测),产物在 Paper/<项目>/。数学计算(工具 math_computation,回执落在 Computation/<id>/):mathComputation = off|auto|on(默认 auto;off 时提示词零提及)· mathMode = typed|typed+shell(默认 typed+shell;typed 时提示词不含 shell 兜底段且 cli 返回 REFUSED{reason:policy})· mathEngines = 允许的引擎列表(默认含 cli,cli 默认开启)· mathTimeoutMs(默认 60000,最小 1000;到时主动 terminate)· mathPackages(需预检的包,缺包只报告+给安装计划)· mathInstallScope = user|system(默认 user;system 只对当次显式调用生效、永不记忆)。', objParams(paramProps()), 'vibe_math_set_params')
|
|
4205
4476
|
registerTool('vibe_math_setup', 'Return the interactive parameter schema for guided configuration.', objParams({}), 'vibe_math_setup')
|
|
4206
4477
|
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')
|
|
4207
4478
|
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')
|
|
4208
4479
|
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')
|
|
4209
|
-
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')
|
|
4480
|
+
registerTool('vibe_math_add_proposition', 'Add a NEW proposition to Propos/ (with 概述, 布尔估计, 细类型, 优先级, 价值/关键性). The id must be NEW: if it already exists the call is REFUSED with code PROPOSITION_ID_EXISTS (existing cards are never overwritten — their 证明列表/证伪列表 are preserved); pick a fresh id (e.g. p-<8 hex>) or edit the card file Propos/<分类>/<id>.md directly.', objParams({ id: { type: 'string' }, 概述: { type: 'string' }, 布尔估计: { type: 'number' }, 优先级: { type: 'integer' }, '价值/关键性': { type: 'number' }, 细类型: { type: 'object' } }, ['id', '概述']), 'vibe_math_add_proposition')
|
|
4210
4481
|
registerTool('vibe_math_list_propositions', 'List propositions from Propos/ (summary index: id, 概述, 布尔估计, 优先级, 价值/关键性, category, 依赖假设).', objParams({}), 'vibe_math_list_propositions')
|
|
4211
4482
|
registerTool('vibe_math_new_project', 'Create a new math project folder and switch to it.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_new_project')
|
|
4212
4483
|
registerTool('vibe_math_set_project', 'Switch the current math project.', objParams({ name: { type: 'string' } }, ['name']), 'vibe_math_set_project')
|
|
@@ -4631,7 +4902,6 @@ function leanJobId(key, fingerprint) {
|
|
|
4631
4902
|
/** "已落地且通过"的**唯一**判据:state=settled 且 exit 0 且未超时未中断(内容哈希由 settle 校验)。 */
|
|
4632
4903
|
function leanJobSettledOk(rec) { return !!rec && rec.state === 'settled' && rec.exitCode === 0 && !rec.timedOut && !rec.interrupted }
|
|
4633
4904
|
/** 主动性档位(修订 §1):与"验证时的要求强度"(formalVerify)是两件事。 */
|
|
4634
|
-
const LEAN_INITIATIVE_MODES = ['off', 'normal', 'eager']
|
|
4635
4905
|
|
|
4636
4906
|
export const __testHelpers = {
|
|
4637
4907
|
uuid,
|
|
@@ -4685,6 +4955,11 @@ function now() { return Date.now() }
|
|
|
4685
4955
|
|
|
4686
4956
|
function uuid() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) s += '-'; else s += h[Math.floor(Math.random() * 16)] } return s }
|
|
4687
4957
|
|
|
4958
|
+
/// P8:框架分配 id 时先查重(shortId 是 Math.random 的 8 位 hex,碰撞概率极低但后果是静默覆盖)。
|
|
4959
|
+
function shortIdUnique(isTaken) {
|
|
4960
|
+
for (let i = 0; i < 8; i++) { const id = shortId(); if (!isTaken(id)) return id }
|
|
4961
|
+
return shortId()
|
|
4962
|
+
}
|
|
4688
4963
|
function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
|
|
4689
4964
|
|
|
4690
4965
|
function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
|