dsh-vibe-math 2.7.1 → 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 +682 -690
- package/README.md +19 -27
- package/cordis.patch.yml +112 -16
- package/docs/AUDIT-CHECKLIST.md +744 -518
- package/docs/COMPAT-AUDIT-ROUND2.md +35 -9
- package/docs/COMPAT-AUDIT-ROUND3.md +154 -154
- package/docs/final-paper.md +172 -172
- package/docs/formal-verification.md +588 -588
- package/docs/math-computation.md +194 -163
- package/docs/parameter-schema.md +37 -0
- package/docs/release-notes/README.en.md +49 -0
- package/docs/release-notes/README.md +48 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.en.md +72 -0
- package/docs/release-notes/RELEASE-NOTES-2.7.2.md +72 -0
- 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 +126 -0
- package/docs/status-report-fields.md +167 -0
- package/docs/test-timing.md +157 -154
- package/installer.js +27 -17
- package/package.json +55 -3
- 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 +305 -113
- package/prompt-corpus-v2/formal-verify-v2.md +2399 -279
- package/prompt-corpus-v3/formal-verify-v3.json +134 -110
- package/prompt-corpus-v3/formal-verify-v3.md +409 -141
- 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 +513 -259
- 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 +107 -9
- 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 +6 -4
- 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 +15 -8
- package/tests/e2e-identity-a6.mutants.mjs +65 -0
- package/tests/e2e-v5-round2.test.mjs +566 -12
- package/tests/formal-verify-v2.test.mjs +91 -5
- package/tests/formal-verify-v3.mutants.mjs +117 -0
- package/tests/formal-verify-v3.test.mjs +85 -6
- package/tests/formal-verify-v4.mutants.mjs +152 -0
- package/tests/formal-verify-v4.test.mjs +628 -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 +396 -2
- 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 +229 -29
- package/vibe-math-v2/math-engines.js +12 -12
- package/vibe-math-v2/vibe-math-v2.js +458 -119
- 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 +229 -29
- package/vibe-math-v3/math-engines.js +12 -12
- package/vibe-math-v3/vibe-math-v3.js +482 -139
- package/vibe-math-v4/agent.cordis.yml +44 -8
- package/vibe-math-v4/math-computation.js +229 -29
- package/vibe-math-v4/math-engines.js +12 -12
- package/vibe-math-v4/vibe-math-v4.js +245 -52
- 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 +229 -29
- package/vibe-math-v5/math-engines.js +12 -12
- package/vibe-math-v5/vibe-math-v5.js +1307 -243
- package/vibe-math-v5//345/256/236/347/216/260/346/226/271/346/241/210.md +1728 -1655
- package/vibe-math-v5//346/236/266/346/236/204/345/233/276.md +693 -693
|
@@ -1,3 +1,118 @@
|
|
|
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: '每个求解方向的最大迭代轮数', suggestion: 3 },
|
|
16
|
+
{ name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向', suggestion: 1 },
|
|
17
|
+
{ name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量(实际下界为 2:少于 2 份独立评审一律不定论,见 minVotes)', suggestion: 3 },
|
|
18
|
+
{ name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
|
|
19
|
+
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: '裁决模式:flat = 均衡机制(分歧时 0.5);forced = 强制裁决(对每票等权取均值;严格 1/0 是绝对投票,其影响通过数值本身拉向端点。**不按"历史准确率"加权**——该统计量统计的是与本批裁决的一致度,没有后续真值可纠正);两者都先做近共识判定(同侧且均值≥0.85/≤0.15取均值,修复 v2 flat 误判)', suggestion: 'forced' },
|
|
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: 'plannerPersona', type: 'string', description: '注入规划代理提示词开头的人格/要求', suggestion: '' },
|
|
26
|
+
{ name: 'methodKeeperPersona', type: 'string', description: '注入方法整理代理提示词开头的人格/要求', suggestion: '' },
|
|
27
|
+
{ name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖)', suggestion: '' },
|
|
28
|
+
{ name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
|
|
29
|
+
{ name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
|
|
30
|
+
{ name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
|
|
31
|
+
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
32
|
+
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承;true=允许;false=禁止', suggestion: '' },
|
|
33
|
+
{ name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关', suggestion: '' },
|
|
34
|
+
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关', suggestion: '' },
|
|
35
|
+
{ name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关', suggestion: '' },
|
|
36
|
+
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
37
|
+
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
38
|
+
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动', suggestion: 0 },
|
|
39
|
+
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息;both = 两者', suggestion: 'file' },
|
|
40
|
+
{ name: 'promoteValueThreshold', type: 'number', description: '命题「价值/关键性」≥ 该值且未决(0,1) 时自动晋升为问题', suggestion: 0.7 },
|
|
41
|
+
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '问题优先级动态调整策略', suggestion: 'none' },
|
|
42
|
+
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整策略', suggestion: 'none' },
|
|
43
|
+
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒)', suggestion: 2000 },
|
|
44
|
+
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数(影响 status/report 里 recentActivity 的细节量,两者最多显示 ' + ACTIVITY_REPORT_MAX + ' 条)', suggestion: 100 },
|
|
45
|
+
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限', suggestion: 3 },
|
|
46
|
+
{ name: 'planningHorizon', type: 'integer', description: '规划代理一次计划的最多动作数("接下来 n 次")', suggestion: 3 },
|
|
47
|
+
{ name: 'plannerEnabled', type: 'boolean', description: 'false = 完全走内置启发式调度(规划代理禁用)', suggestion: true },
|
|
48
|
+
{ name: 'plannerProvider', type: 'string', description: '规划代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
49
|
+
{ name: 'plannerModel', type: 'string', description: '规划代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
50
|
+
{ name: 'planMinIntervalMs', type: 'integer', description: '每一次规划调用的最小间隔(毫秒);对**每一次**规划调用都生效(含空计划、含「空闲但仍有工作」,不再有空闲绕过)', suggestion: 30000 },
|
|
51
|
+
{ name: 'plannerMaxFails', type: 'integer', description: '规划代理连续失败达此值 → 自动降级启发式', suggestion: 3 },
|
|
52
|
+
{ name: 'methodKeepIntervalMs', type: 'integer', description: 'Method Keeper 定时整理间隔(0 = 事件驱动)', suggestion: 0 },
|
|
53
|
+
{ name: 'methodKeepEvery', type: 'integer', description: '每积累 N 个待沉淀发明/新命题触发一次整理', suggestion: 5 },
|
|
54
|
+
{ name: 'methodAutoPromote', type: 'boolean', description: '项目级方法自动晋升全局库(false = 人工门)', suggestion: false },
|
|
55
|
+
{ name: 'indexAutoRebuild', type: 'boolean', description: '每次写盘后自动重建索引(false = 手动 vibe_math_index)', suggestion: true },
|
|
56
|
+
{ name: 'projectLockTimeoutMs', type: 'integer', description: '项目锁等待超时(毫秒)', suggestion: 60000 },
|
|
57
|
+
{ name: 'fileLockTimeoutMs', type: 'integer', description: '文件写锁租约时长(毫秒,默认 60000,最小 1000):持有代理存活期间自动续租,持有者消失后最多这么久失效,避免长文写入被别人抢锁(审计 D2)', suggestion: 60000 },
|
|
58
|
+
{ name: 'formalVerify', type: 'enum', options: ['off', 'encourage', 'require'], description: 'Lean 形式化验证:off = 不额外要求(默认,提示词里不出现任何 Lean 内容);encourage = 鼓励按实现难度形式化,Lean 通过后验证转为忠实性审查;require = 同上并加门禁:真/假定论必须先达到 Lean 已通过 或 已记录显式阻塞原因,否则记为未定论(formal-required)并进「形式化待办」', suggestion: 'off' },
|
|
59
|
+
{ name: 'leanCommand', type: 'string', description: 'Lean 可执行文件(例:lean / lake;配合 leanArgs=[env,lean] 用 lake)', suggestion: 'lean' },
|
|
60
|
+
{ name: 'leanArgs', type: 'string[]', description: '插在 .lean 文件名之前的附加参数', suggestion: [] },
|
|
61
|
+
{ name: 'leanTimeoutMs', type: 'integer', description: '单次 Lean 运行的超时上限(毫秒,非正数回退默认);异步档下它同时是**每个后台作业**的预算(到时主动 terminate,作业记 timeout,对象留在 attempted)', suggestion: 120000 },
|
|
62
|
+
{ name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
|
|
63
|
+
{ name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
|
|
64
|
+
{ name: 'leanInitiative', type: 'enum', options: ['off', 'normal', 'eager'], description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
|
|
65
|
+
{ name: 'leanSearchPaths', type: 'string[]', description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
|
|
66
|
+
{ name: 'finalPaper', type: 'boolean', description: '收口(checkTermination 的完整收口分支:无未解决问题、无待验证对象、无任务/计划/门)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已定论命题/解法/方法整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
|
|
67
|
+
{ name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md(跳过编译);tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
|
|
68
|
+
{ name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
|
|
69
|
+
{ name: 'paperCompilePdf', type: 'boolean', description: '检测到 LaTeX 时是否编译 paper.pdf(-interaction=nonstopmode 跑两遍;失败先尝试修复:换引擎/去不支持宏包/最小模板)。false 或无 LaTeX 时只保留 tex+md 并记日志(不阻塞定稿)', suggestion: true },
|
|
70
|
+
{ name: 'paperLatexCommand', type: 'string', description: '指定 LaTeX 引擎可执行文件(空 = 按语言探测:中文 xelatex > latexmk > pdflatex > lualatex > tectonic;英文 pdflatex 优先)。解析不到时按"未检测到"降级', suggestion: '' },
|
|
71
|
+
// ---- 数学计算 math_computation(六参数冻结;描述与 prompts.md §1/§6 口径一致)----
|
|
72
|
+
{ name: 'mathComputation', type: 'enum', options: ['off', 'auto', 'on'], description: '数学计算总开关:off = 真无操作(提示词零提及);auto = 探测到 mathEngines 里任一允许引擎才工作;on = 同上(探测失败时工具仍返回可执行的安装指引,而不是假装可用)', suggestion: 'auto' },
|
|
73
|
+
{ name: 'mathMode', type: 'enum', options: ['typed', 'typed+shell'], description: '计算策略:typed+shell(默认)= 工具不可用时允许宿主 shell 兜底,但结论必须标注"未经工具归档(shell 路径)";typed = 只用工具路径(提示词里不出现 shell 兜底段,engine:"cli" 返回 REFUSED{reason:policy})', suggestion: 'typed+shell' },
|
|
74
|
+
{ name: 'mathEngines', type: 'string[]', description: '允许的引擎列表(默认 python|r|octave|julia|matlab|maple|wolfram|cli)。cli 默认开启且走同一套超时/输出上限/回执;从列表里移除某引擎即禁用(商业引擎只探测+许可,永不安装)', suggestion: MATH_PARAM_DEFAULTS.mathEngines.slice() },
|
|
75
|
+
{ name: 'mathTimeoutMs', type: 'integer', description: '单次数学计算的超时上限(毫秒,默认 60000,最小 1000);到时主动 terminate 并把回执标为 MATH_TIMEOUT', suggestion: 60000 },
|
|
76
|
+
{ name: 'mathPackages', type: 'string[]', description: '需要预检的包(默认空)。缺包只报告 + 给"用户自装指引"或"代理代装计划",不会执行脚本,也永不自动安装', suggestion: [] },
|
|
77
|
+
{ name: 'mathInstallScope', type: 'enum', options: ['user', 'system'], description: '安装作用域:user(默认,用户级目录);system 只对当次显式调用生效、永不记忆(不会写进状态文件)', suggestion: 'user' },
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
// 钉住"机读键集合"(现状 v2=49 / v3=63 个,不随 PARAM_SCHEMA 增删而漂移)。
|
|
81
|
+
const PARAM_PROPS_KEYS = ['mode', 'maxParallelThreshold', 'solverMaxRounds', 'verifierCount', 'debateMaxRounds', 'verdictMode', 'reportMode', 'promoteValueThreshold', 'priorityAdjust', 'proposPriorityAdjust', 'provider', 'model', 'solverPersona', 'verifierPersona', 'explorerPersona', 'plannerPersona', 'methodKeeperPersona', 'knowledgeContext', 'solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny', 'solverAllowNetwork', 'verifierAllowNetwork', 'solverAllowScripts', 'verifierAllowScripts', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'directionsPerSolver', 'planningHorizon', 'plannerEnabled', 'plannerProvider', 'plannerModel', 'planMinIntervalMs', 'plannerMaxFails', 'methodKeepIntervalMs', 'methodKeepEvery', 'methodAutoPromote', 'indexAutoRebuild', 'projectLockTimeoutMs', 'formalVerify', 'leanCommand', 'leanArgs', 'leanTimeoutMs', 'leanAsync', 'leanJobsMaxParallel', 'leanInitiative', 'leanSearchPaths', 'mathComputation', 'mathMode', 'mathEngines', 'mathTimeoutMs', 'mathPackages', 'mathInstallScope', 'finalPaper', 'paperFormat', 'paperLanguage', 'paperCompilePdf', 'paperLatexCommand']
|
|
82
|
+
// 源无法表达的条目:数组 items / oneOf / mode 的现状枚举顺序 —— 逐字沿用,并由
|
|
83
|
+
// "例外集合 == docs/parameter-schema.md" 断言钉住。
|
|
84
|
+
const PARAM_PROPS_EXTRA = {
|
|
85
|
+
solverToolAllow: { type: 'array', items: { type: 'string' } },
|
|
86
|
+
solverToolDeny: { type: 'array', items: { type: 'string' } },
|
|
87
|
+
verifierToolAllow: { type: 'array', items: { type: 'string' } },
|
|
88
|
+
verifierToolDeny: { type: 'array', items: { type: 'string' } },
|
|
89
|
+
leanArgs: { type: 'array', items: { type: 'string' } },
|
|
90
|
+
mathEngines: { type: 'array', items: { type: 'string' } },
|
|
91
|
+
mathPackages: { type: 'array', items: { type: 'string' } },
|
|
92
|
+
leanSearchPaths: { type: 'array', items: { type: 'string' } },
|
|
93
|
+
mode: { type: 'string', enum: ['manual', 'auto'] },
|
|
94
|
+
}
|
|
95
|
+
function paramProps() {
|
|
96
|
+
const props = {}
|
|
97
|
+
for (const name of PARAM_PROPS_KEYS) {
|
|
98
|
+
const e = PARAM_SCHEMA.find(function (x) { return x && x.name === name })
|
|
99
|
+
if (!e) continue
|
|
100
|
+
const enumVals = (Array.isArray(e.enum) && e.enum.length) ? e.enum : ((Array.isArray(e.options) && e.options.length) ? e.options : null)
|
|
101
|
+
const isEnum = e.type === 'enum' || !!enumVals
|
|
102
|
+
const p = isEnum ? { type: 'string', enum: (enumVals || []).slice() } : { type: e.type }
|
|
103
|
+
if (e.description) p.description = e.description
|
|
104
|
+
props[name] = p
|
|
105
|
+
}
|
|
106
|
+
// 例外表覆盖"源无法表达"的字段,但**保留源里的描述**(覆盖度 v2 49/49、v3 63/63)
|
|
107
|
+
for (const k of Object.keys(PARAM_PROPS_EXTRA)) {
|
|
108
|
+
const base = props[k] || {}
|
|
109
|
+
props[k] = Object.assign({}, PARAM_PROPS_EXTRA[k])
|
|
110
|
+
if (base.description) props[k].description = base.description
|
|
111
|
+
}
|
|
112
|
+
return props
|
|
113
|
+
}
|
|
114
|
+
// ================= end F-B =================
|
|
115
|
+
|
|
1
116
|
// Vibe Math V3 — host plugin implementing the THIRD-generation architecture
|
|
2
117
|
// ("vibe-math-v3/实现方案.md"): paper-style Markdown knowledge base + agent
|
|
3
118
|
// self-organizing scheduling + universal theory/method invention library.
|
|
@@ -112,6 +227,7 @@ export function apply(ctx) {
|
|
|
112
227
|
// 判据时最坏年龄仍有 2467ms,几乎顶到 2500ms 的租约上限。250ms 轮询下最坏租约年龄 ≈
|
|
113
228
|
// max(250ms, projectLockTimeoutMs/4),即上限的 1/4 再加一次轮询的抖动。
|
|
114
229
|
const LOCK_POLL_MS = 250
|
|
230
|
+
const ACTIVITY_PERSIST_MAX = 200 // P5:活动日志落盘上限(恢复时保留更多线索)
|
|
115
231
|
|
|
116
232
|
function sessionIdOf(agent) { try { return (agent && agent.id) ? String(agent.id) : undefined } catch (e) { return undefined } }
|
|
117
233
|
// Walk up the durable session lineage to the top-level (root) agent of this session,
|
|
@@ -191,6 +307,7 @@ export function apply(ctx) {
|
|
|
191
307
|
methodAutoPromote: false, // 项目级方法自动晋升全局库(false = 人工门)
|
|
192
308
|
indexAutoRebuild: true, // 每次写盘后自动重建索引(false = 手动 vibe_math_index)
|
|
193
309
|
projectLockTimeoutMs: 60000, // 项目锁等待超时
|
|
310
|
+
fileLockTimeoutMs: 60000, // 文件写锁租约时长(审计 D2;持有者活着则每 LOCK_POLL_MS 续租)
|
|
194
311
|
// ---- Lean 形式化验证(契约:docs/formal-verification.md,四架构同名同语义)----
|
|
195
312
|
formalVerify: 'off', // off | encourage | require(三档开关,见 实现方案.md §10.1)
|
|
196
313
|
leanCommand: 'lean', // 要执行的 Lean 可执行文件(例:'lake')
|
|
@@ -224,6 +341,8 @@ export function apply(ctx) {
|
|
|
224
341
|
// 长跑进程里无界增长并整表落盘。它已经不再参与任何加权(见 finalVerdict 的 M8 说明),
|
|
225
342
|
// 只是审计轨迹,所以按插入顺序保留最近 N 条即可。
|
|
226
343
|
const VERIFIER_ACC_KEEP = 200
|
|
344
|
+
// F2(对照 v2:221):status 与 report 的 recentActivity **共用**这一上限;参数 activityLogCap
|
|
345
|
+
// 只能把它调小、不能调大(旧实现 status 硬编码 10、report 硬编码 30,读者无从知道)。
|
|
227
346
|
function pruneVerifierAccuracy() {
|
|
228
347
|
const keys = Object.keys(verifierAccuracy)
|
|
229
348
|
for (let i = 0; i < keys.length - VERIFIER_ACC_KEEP; i++) delete verifierAccuracy[keys[i]]
|
|
@@ -276,87 +395,53 @@ export function apply(ctx) {
|
|
|
276
395
|
* 保证结果永不为 `.` / `..`、也不含分隔符。
|
|
277
396
|
*/
|
|
278
397
|
let warnedNoPolicy = false
|
|
398
|
+
// F-5:围栏漂移检测(只告警不拒写)。边界:若解析结果**不暴露**任何 root 字段,本比对无法运行。
|
|
399
|
+
function policyRootOf(p) {
|
|
400
|
+
if (!p || typeof p !== 'object') return ''
|
|
401
|
+
const cand = p.workspaceRoot || p.root || p.cwd || (p.config && p.config.workspaceRoot) || ''
|
|
402
|
+
return String(cand || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
|
403
|
+
}
|
|
404
|
+
let warnedFenceDrift = false
|
|
405
|
+
function warnFenceDriftOnce(actual, expected) {
|
|
406
|
+
if (warnedFenceDrift) return
|
|
407
|
+
warnedFenceDrift = true
|
|
408
|
+
console.error('vibe-math-v3' + ': 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')
|
|
409
|
+
}
|
|
410
|
+
function checkFenceRoot(p) {
|
|
411
|
+
const actual = policyRootOf(p)
|
|
412
|
+
const expected = String(workspaceRoot() || '').replace(/\\/g, '/').replace(/\/+$/, '')
|
|
413
|
+
if (actual && expected && actual !== expected) warnFenceDriftOnce(actual, expected)
|
|
414
|
+
return p
|
|
415
|
+
}
|
|
279
416
|
function warnNoPolicyOnce() { if (!warnedNoPolicy) { warnedNoPolicy = true; console.error('vibe-math-v3: sandboxPolicy unavailable; writes go out with no explicit policy') } }
|
|
280
417
|
// Sandbox fence for our own writes. The `resolve({})` fallback is a last resort and is
|
|
281
418
|
// deliberately reported (once): with no session it resolves the policy's CONFIGURED root
|
|
282
419
|
// (dsh-sandbox-policy: resolveWorkspaceRoot(config.workspaceRoot ?? process.cwd())),
|
|
283
420
|
// which is not necessarily this session's workspace — a silently different fence.
|
|
284
|
-
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-v3: 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 }
|
|
421
|
+
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-v3: 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 }
|
|
285
422
|
function makeSignal(ms) { return AbortSignal.timeout(ms || 30000) }
|
|
286
423
|
|
|
287
424
|
// ================= 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: '每个求解方向的最大迭代轮数', suggestion: 3 },
|
|
292
|
-
{ name: 'directionsPerSolver', type: 'integer', description: '每个 solver 提示词附带的其他活跃方向摘要数量:1 = 只看自己方向', suggestion: 1 },
|
|
293
|
-
{ name: 'verifierCount', type: 'integer', description: '每个验证对象的独立验证器数量(实际下界为 2:少于 2 份独立评审一律不定论,见 minVotes)', suggestion: 3 },
|
|
294
|
-
{ name: 'debateMaxRounds', type: 'integer', description: '验证辩论(交流群)最大轮数', suggestion: 5 },
|
|
295
|
-
{ name: 'verdictMode', type: 'enum', options: ['flat', 'forced'], description: '裁决模式:flat = 均衡机制(分歧时 0.5);forced = 强制裁决(对每票等权取均值;严格 1/0 是绝对投票,其影响通过数值本身拉向端点。**不按"历史准确率"加权**——该统计量统计的是与本批裁决的一致度,没有后续真值可纠正);两者都先做近共识判定(同侧且均值≥0.85/≤0.15取均值,修复 v2 flat 误判)', suggestion: 'forced' },
|
|
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: 'plannerPersona', type: 'string', description: '注入规划代理提示词开头的人格/要求', suggestion: '' },
|
|
302
|
-
{ name: 'methodKeeperPersona', type: 'string', description: '注入方法整理代理提示词开头的人格/要求', suggestion: '' },
|
|
303
|
-
{ name: 'knowledgeContext', type: 'string', description: '共享知识/数据模型说明(空 = 内置完整版;非空 = 覆盖)', suggestion: '' },
|
|
304
|
-
{ name: 'solverToolAllow', type: 'string[]', description: '求解器允许的工具名列表(空 = 继承全部工具)', suggestion: [] },
|
|
305
|
-
{ name: 'solverToolDeny', type: 'string[]', description: '求解器禁止的工具名列表', suggestion: [] },
|
|
306
|
-
{ name: 'verifierToolAllow', type: 'string[]', description: '验证器允许的工具名列表', suggestion: [] },
|
|
307
|
-
{ name: 'verifierToolDeny', type: 'string[]', description: '验证器禁止的工具名列表', suggestion: [] },
|
|
308
|
-
{ name: 'solverAllowNetwork', type: 'boolean', description: '求解器网络工具开关:空=继承;true=允许;false=禁止', suggestion: '' },
|
|
309
|
-
{ name: 'verifierAllowNetwork', type: 'boolean', description: '验证器网络工具开关', suggestion: '' },
|
|
310
|
-
{ name: 'solverAllowScripts', type: 'boolean', description: '求解器脚本工具开关', suggestion: '' },
|
|
311
|
-
{ name: 'verifierAllowScripts', type: 'boolean', description: '验证器脚本工具开关', suggestion: '' },
|
|
312
|
-
{ name: 'solverMaxToolCalls', type: 'integer', description: '求解器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
313
|
-
{ name: 'verifierMaxToolCalls', type: 'integer', description: '验证器每轮外部工具调用上限(0 = 不限)', suggestion: 0 },
|
|
314
|
-
{ name: 'reportIntervalMs', type: 'integer', description: '进度汇报间隔(毫秒):0 = 仅事件驱动', suggestion: 0 },
|
|
315
|
-
{ name: 'reportMode', type: 'enum', options: ['file', 'push', 'both'], description: 'file = 写报告文件;push = 推送消息;both = 两者', suggestion: 'file' },
|
|
316
|
-
{ name: 'promoteValueThreshold', type: 'number', description: '命题「价值/关键性」≥ 该值且未决(0,1) 时自动晋升为问题', suggestion: 0.7 },
|
|
317
|
-
{ name: 'priorityAdjust', type: 'enum', options: ['none', 'deadend-deprioritize', 'survival-map'], description: '问题优先级动态调整策略', suggestion: 'none' },
|
|
318
|
-
{ name: 'proposPriorityAdjust', type: 'enum', options: ['none', 'progress-graded'], description: '命题优先级动态调整策略', suggestion: 'none' },
|
|
319
|
-
{ name: 'tickIntervalMs', type: 'integer', description: '调度器心跳间隔(毫秒)', suggestion: 2000 },
|
|
320
|
-
{ name: 'activityLogCap', type: 'integer', description: '活动日志保留条数', suggestion: 100 },
|
|
321
|
-
{ name: 'maxExplorerRetries', type: 'integer', description: 'explorer 拆方向失败的重派生上限', suggestion: 3 },
|
|
322
|
-
{ name: 'planningHorizon', type: 'integer', description: '规划代理一次计划的最多动作数("接下来 n 次")', suggestion: 3 },
|
|
323
|
-
{ name: 'plannerEnabled', type: 'boolean', description: 'false = 完全走内置启发式调度(规划代理禁用)', suggestion: true },
|
|
324
|
-
{ name: 'plannerProvider', type: 'string', description: '规划代理模型 provider(空 = 继承根代理)', suggestion: '' },
|
|
325
|
-
{ name: 'plannerModel', type: 'string', description: '规划代理模型 id(空 = 继承根代理)', suggestion: '' },
|
|
326
|
-
{ name: 'planMinIntervalMs', type: 'integer', description: '每一次规划调用的最小间隔(毫秒);对**每一次**规划调用都生效(含空计划、含「空闲但仍有工作」,不再有空闲绕过)', suggestion: 30000 },
|
|
327
|
-
{ name: 'plannerMaxFails', type: 'integer', description: '规划代理连续失败达此值 → 自动降级启发式', suggestion: 3 },
|
|
328
|
-
{ name: 'methodKeepIntervalMs', type: 'integer', description: 'Method Keeper 定时整理间隔(0 = 事件驱动)', suggestion: 0 },
|
|
329
|
-
{ name: 'methodKeepEvery', type: 'integer', description: '每积累 N 个待沉淀发明/新命题触发一次整理', suggestion: 5 },
|
|
330
|
-
{ name: 'methodAutoPromote', type: 'boolean', description: '项目级方法自动晋升全局库(false = 人工门)', suggestion: false },
|
|
331
|
-
{ name: 'indexAutoRebuild', type: 'boolean', description: '每次写盘后自动重建索引(false = 手动 vibe_math_index)', suggestion: true },
|
|
332
|
-
{ name: 'projectLockTimeoutMs', type: 'integer', description: '项目锁等待超时(毫秒)', suggestion: 60000 },
|
|
333
|
-
{ name: 'formalVerify', type: 'enum', options: ['off', 'encourage', 'require'], description: 'Lean 形式化验证:off = 不额外要求(默认,提示词里不出现任何 Lean 内容);encourage = 鼓励按实现难度形式化,Lean 通过后验证转为忠实性审查;require = 同上并加门禁:真/假定论必须先达到 Lean 已通过 或 已记录显式阻塞原因,否则记为未定论(formal-required)并进「形式化待办」', suggestion: 'off' },
|
|
334
|
-
{ name: 'leanCommand', type: 'string', description: 'Lean 可执行文件(例:lean / lake;配合 leanArgs=[env,lean] 用 lake)', suggestion: 'lean' },
|
|
335
|
-
{ name: 'leanArgs', type: 'string[]', description: '插在 .lean 文件名之前的附加参数', suggestion: [] },
|
|
336
|
-
{ name: 'leanTimeoutMs', type: 'integer', description: '单次 Lean 运行的超时上限(毫秒,非正数回退默认);异步档下它同时是**每个后台作业**的预算(到时主动 terminate,作业记 timeout,对象留在 attempted)', suggestion: 120000 },
|
|
337
|
-
{ name: 'leanAsync', type: 'boolean', description: 'Lean 编译模式:true(默认)= 后台队列,vibe_math_lean_run / vibe_math_lean_archive{run:true} 立即返回 async.jobId 入队,成员不阻塞,结果由下一轮提示的【形式化结果】行与 vibe_math_lean_job 公告(**只有作业落地为 ok 才会置 passed 并写归档证明**);false = 完全同步 await(与旧行为逐字一致)', suggestion: true },
|
|
338
|
-
{ name: 'leanJobsMaxParallel', type: 'integer', description: '后台 Lean 编译的并发上限(默认 1 = 串行,保持可预测的资源占用;调大可并行编译多个作业)', suggestion: 1 },
|
|
339
|
-
{ name: 'leanInitiative', type: 'enum', options: ['off', 'normal', 'eager'], description: '日常流程中的形式化主动性:off(不主动,只在验证提示词按 formalVerify 的要求做)| normal(默认:顺手把有价值且可能复用的东西形式化)| eager(更主动:日常就主动把有价值的小引理/命题/定义形式化)。注意它与 formalVerify(验证时的要求强度:off|encourage|require)是**两件事**', suggestion: 'normal' },
|
|
340
|
-
{ name: 'leanSearchPaths', type: 'string[]', description: '额外 Lean 搜索路径(默认空数组 = 只用框架自动注入的 VibeMath 根)。非空时按顺序先注入这里给的路径、再注入自动根(去重);若 leanArgs 里已显式给了 --search-path/-R/--root,则完全尊重用户配置、不注入任何东西', suggestion: [] },
|
|
341
|
-
{ name: 'finalPaper', type: 'boolean', description: '收口(checkTermination 的完整收口分支:无未解决问题、无待验证对象、无任务/计划/门)时自动撰写最终论文:派遣一名专职「论文撰写」子代理,把已定论命题/解法/方法整理成 Paper/<项目>/{paper.md,paper.tex,paper.meta.json,paper.log.md}。false = 只关自动触发,/vibe paper 手动命令仍可用', suggestion: true },
|
|
342
|
-
{ name: 'paperFormat', type: 'enum', options: ['both', 'md', 'tex'], description: '论文产出格式:both = markdown + latex;md = 只写 paper.md(跳过编译);tex = 只写 paper.tex(tex 才会尝试编译 pdf)', suggestion: 'both' },
|
|
343
|
-
{ name: 'paperLanguage', type: 'enum', options: ['zh', 'en'], description: '论文语言:zh = 中文(LaTeX 用 ctexart,引擎优先 xelatex);en = 英文(article,引擎优先 pdflatex/latexmk)', suggestion: 'zh' },
|
|
344
|
-
{ name: 'paperCompilePdf', type: 'boolean', description: '检测到 LaTeX 时是否编译 paper.pdf(-interaction=nonstopmode 跑两遍;失败先尝试修复:换引擎/去不支持宏包/最小模板)。false 或无 LaTeX 时只保留 tex+md 并记日志(不阻塞定稿)', suggestion: true },
|
|
345
|
-
{ name: 'paperLatexCommand', type: 'string', description: '指定 LaTeX 引擎可执行文件(空 = 按语言探测:中文 xelatex > latexmk > pdflatex > lualatex > tectonic;英文 pdflatex 优先)。解析不到时按"未检测到"降级', suggestion: '' },
|
|
346
|
-
// ---- 数学计算 math_computation(六参数冻结;描述与 prompts.md §1/§6 口径一致)----
|
|
347
|
-
{ name: 'mathComputation', type: 'enum', options: ['off', 'auto', 'on'], description: '数学计算总开关:off = 真无操作(提示词零提及);auto = 探测到 mathEngines 里任一允许引擎才工作;on = 同上(探测失败时工具仍返回可执行的安装指引,而不是假装可用)', suggestion: 'auto' },
|
|
348
|
-
{ name: 'mathMode', type: 'enum', options: ['typed', 'typed+shell'], description: '计算策略:typed+shell(默认)= 工具不可用时允许宿主 shell 兜底,但结论必须标注"未经工具归档(shell 路径)";typed = 只用工具路径(提示词里不出现 shell 兜底段,engine:"cli" 返回 REFUSED{reason:policy})', suggestion: 'typed+shell' },
|
|
349
|
-
{ name: 'mathEngines', type: 'string[]', description: '允许的引擎列表(默认 python|r|octave|julia|matlab|maple|wolfram|cli)。cli 默认开启且走同一套超时/输出上限/回执;从列表里移除某引擎即禁用(商业引擎只探测+许可,永不安装)', suggestion: MATH_PARAM_DEFAULTS.mathEngines.slice() },
|
|
350
|
-
{ name: 'mathTimeoutMs', type: 'integer', description: '单次数学计算的超时上限(毫秒,默认 60000,最小 1000);到时主动 terminate 并把回执标为 MATH_TIMEOUT', suggestion: 60000 },
|
|
351
|
-
{ name: 'mathPackages', type: 'string[]', description: '需要预检的包(默认空)。缺包只报告 + 给"用户自装指引"或"代理代装计划",不会执行脚本,也永不自动安装', suggestion: [] },
|
|
352
|
-
{ name: 'mathInstallScope', type: 'enum', options: ['user', 'system'], description: '安装作用域:user(默认,用户级目录);system 只对当次显式调用生效、永不记忆(不会写进状态文件)', suggestion: 'user' },
|
|
353
|
-
]
|
|
354
425
|
|
|
355
426
|
// ================= fs (adapted to DSH 0.1.1: resolve returns {targetKey, displayPath}) =================
|
|
356
427
|
async function fsTarget(rel) { return await fs.resolve(rel, { cwd: frameworkRoot() }) }
|
|
357
428
|
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 } }
|
|
358
|
-
async function writeText(rel, content) {
|
|
359
|
-
|
|
429
|
+
async function writeText(rel, content) {
|
|
430
|
+
const t = await fsTarget(rel)
|
|
431
|
+
const r = await fs.writeText(t, content, undefined, undefined, getPolicy())
|
|
432
|
+
// P4:宿主若用返回值(而非抛异常)报告写失败,也要留痕——旧实现无条件 return true 会把它吞掉。
|
|
433
|
+
if (r && r.ok === false) noteStateWriteFailure(rel, 'host reported write failure' + (r.error ? (': ' + r.error) : ''))
|
|
434
|
+
return true
|
|
435
|
+
}
|
|
436
|
+
async function writeJson(rel, obj) {
|
|
437
|
+
if (!assertWritable(rel)) {
|
|
438
|
+
// P4:损坏守卫拒绝写入过去是**完全静默**的(返回 false 没人看)⇒ 会话以为已提交、磁盘还是旧内容。
|
|
439
|
+
noteStateWriteFailure(rel, 'corruption guard refused write')
|
|
440
|
+
logActivity('state', '拒绝写入(该 JSON 已存在但无法解析,先修/删它):' + rel)
|
|
441
|
+
return false
|
|
442
|
+
}
|
|
443
|
+
return await writeText(rel, JSON.stringify(obj, null, 2))
|
|
444
|
+
}
|
|
360
445
|
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 } }
|
|
361
446
|
/**
|
|
362
447
|
* Corruption guard. `readJson` cannot tell "no file yet" from "file present but
|
|
@@ -385,11 +470,11 @@ export function apply(ctx) {
|
|
|
385
470
|
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 } }
|
|
386
471
|
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 } }
|
|
387
472
|
async function readCurrentProject() {
|
|
388
|
-
|
|
389
|
-
|
|
473
|
+
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)) }
|
|
474
|
+
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' }
|
|
390
475
|
}
|
|
391
476
|
async function writeCurrentProject() {
|
|
392
|
-
|
|
477
|
+
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: 同上
|
|
393
478
|
}
|
|
394
479
|
|
|
395
480
|
// ================= subprocess =================
|
|
@@ -443,8 +528,27 @@ export function apply(ctx) {
|
|
|
443
528
|
// Progress_Logs/ 必须和别的骨架目录**一起**建出来(审计 L8):vibe_math_report 会写
|
|
444
529
|
// `Progress_Logs/report.json`,README 也把它列为布局的一部分,但此前它只靠 writeText 的隐式
|
|
445
530
|
// mkdir 兜底——在宿主不支持删除/创建的路径上(runShell 失败)报告目录就时有时无。
|
|
446
|
-
|
|
447
|
-
|
|
531
|
+
// F-4c:shell 兜底失败不能沉默(v2 有 warnShellOnce,v3 此前丢了 ⇒ mkdir/rm 失败无人知晓)。
|
|
532
|
+
const shellWarned = {}
|
|
533
|
+
function warnShellOnce(where, r) {
|
|
534
|
+
const why = (r && (r.error || (r.exitCode === undefined ? undefined : ('exit code ' + r.exitCode)))) || 'unknown failure'
|
|
535
|
+
const key = String(where) + '|' + why
|
|
536
|
+
if (shellWarned[key]) return
|
|
537
|
+
shellWarned[key] = true
|
|
538
|
+
console.error('vibe-math-v3: ' + where + ' failed (' + why + ')')
|
|
539
|
+
}
|
|
540
|
+
async function ensureDirs() {
|
|
541
|
+
const base = frameworkRoot(); const dirs = ['Problems', 'Progress', 'Progress_Logs', 'Propos', 'Methods', 'Verified/命题', 'Verified/问题', 'Verified/Lean', 'Formal', 'Formal/Jobs', 'Reliable', 'Notes', 'Logs/Verification', 'Logs/Plans', 'State', 'Computation']; const paths = [vibeRoot() + '/Projects', vibeRoot() + '/Methods', vibeRoot() + '/Formal/Lib', vibeRoot() + '/Formal/Proved'].concat(dirs.map(function (d) { return base + '/' + d })); return await runShell(mkdirCmd(paths))
|
|
542
|
+
const r = await runShell(mkdirCmd(paths))
|
|
543
|
+
if (!r || !r.ok) warnShellOnce('ensureDirs (mkdir ' + base + ')', r)
|
|
544
|
+
return r
|
|
545
|
+
}
|
|
546
|
+
async function removeFile(rel) {
|
|
547
|
+
const base = frameworkRoot()
|
|
548
|
+
const r = await runShell(rmCmd(base + '/' + rel))
|
|
549
|
+
if (!r || !r.ok) warnShellOnce('removeFile(' + rel + ')', r)
|
|
550
|
+
return r
|
|
551
|
+
}
|
|
448
552
|
|
|
449
553
|
// ================= 数学计算 math_computation:会话侧接线(共享模块,FREEZE §4/§5) =================
|
|
450
554
|
// 探测量(是否装了 python/r/…)是异步的,而提示词是同步构造的 ⇒ 会话级缓存 + TTL 刷新,
|
|
@@ -577,7 +681,7 @@ export function apply(ctx) {
|
|
|
577
681
|
// ================= settings =================
|
|
578
682
|
function sanitizeParams(obj) {
|
|
579
683
|
const out = {}
|
|
580
|
-
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'planningHorizon', 'planMinIntervalMs', 'plannerMaxFails', 'methodKeepIntervalMs', 'methodKeepEvery', 'projectLockTimeoutMs']
|
|
684
|
+
const intFields = ['maxParallelThreshold', 'solverMaxRounds', 'directionsPerSolver', 'verifierCount', 'debateMaxRounds', 'solverMaxToolCalls', 'verifierMaxToolCalls', 'reportIntervalMs', 'tickIntervalMs', 'activityLogCap', 'maxExplorerRetries', 'planningHorizon', 'planMinIntervalMs', 'plannerMaxFails', 'methodKeepIntervalMs', 'methodKeepEvery', 'projectLockTimeoutMs', 'fileLockTimeoutMs']
|
|
581
685
|
const numFields = ['promoteValueThreshold']
|
|
582
686
|
const arrayFields = ['solverToolAllow', 'solverToolDeny', 'verifierToolAllow', 'verifierToolDeny', 'leanArgs']
|
|
583
687
|
const boolFields = ['plannerEnabled', 'methodAutoPromote', 'indexAutoRebuild', 'finalPaper', 'paperCompilePdf', 'leanAsync']
|
|
@@ -918,6 +1022,7 @@ export function apply(ctx) {
|
|
|
918
1022
|
// 引理只在摘要给"索引"(id+标题),完整叙述在各方向文件 Progress/<qid>/<dirId>.md(由代理或调度器写入)
|
|
919
1023
|
if (d.lemmas && d.lemmas.length) { lines.push('**引理索引**:' + d.lemmas.map(function (l) { return '「' + l.title + '」(' + l.id + ')' }).join(';')) }
|
|
920
1024
|
lines.push('**完整叙述**:见 `Progress/' + qid + '/' + d.id + '.md`')
|
|
1025
|
+
lines.push(PAPER_PATH_NOTE)
|
|
921
1026
|
for (const j of (d.journal || [])) {
|
|
922
1027
|
lines.push('')
|
|
923
1028
|
lines.push('### 第 ' + j.round + ' 轮|' + (j.agent || '') + '|' + (j.at || ''))
|
|
@@ -926,6 +1031,31 @@ export function apply(ctx) {
|
|
|
926
1031
|
lines.push('')
|
|
927
1032
|
}
|
|
928
1033
|
await writeText('Progress/' + qid + '.md', lines.join('\n').trimEnd() + '\n')
|
|
1034
|
+
// 兜底(审计 F1):聚合索引在上面用 `**完整叙述**:见 Progress/<qid>/<d.id>.md` 指向**逐方向**
|
|
1035
|
+
// 研究日志。设计上这些文件由**代理**写(见 applyAgentWrites 的 Progress/<qid>/ 分支),但代理没写
|
|
1036
|
+
// 时索引就会指向不存在的文件。这里补一份**调度器维护**的版本:`ensureProgressDir` 建目录、
|
|
1037
|
+
// `directionMdText` 生成内容;带 scheduler-managed 标记的文件每轮刷新,**代理写的文件绝不动**。
|
|
1038
|
+
// D6:只在**确实要写**时建目录(旧实现在每轮 writeJournal 开头无条件跑一次 shell mkdir,
|
|
1039
|
+
// 即使所有方向文件都已存在且归代理所有);宿主没有 subprocess 时直接跳过(writeText 仍会落盘),
|
|
1040
|
+
// 这样无 shell 宿主的每一轮日志也不会留下一条 mkdir 失败记录。
|
|
1041
|
+
let journalDirReady = false
|
|
1042
|
+
for (const d of dirs) {
|
|
1043
|
+
const rel = 'Progress/' + qid + '/' + d.id + '.md'
|
|
1044
|
+
// 归属判定:带 `<!-- scheduler-managed -->` 首行的文件由**调度器**维护(每轮刷新);
|
|
1045
|
+
// 其它文件视为**代理**所有、绝不触碰(契约:聚合索引不覆盖各方向文件)。
|
|
1046
|
+
let exists = false, owned = false
|
|
1047
|
+
try {
|
|
1048
|
+
const txt = await readText(rel)
|
|
1049
|
+
exists = txt !== undefined
|
|
1050
|
+
owned = exists && String(txt).indexOf('<!-- scheduler-managed -->') === 0
|
|
1051
|
+
} catch (e) { exists = false; owned = false }
|
|
1052
|
+
if (exists && !owned) continue
|
|
1053
|
+
if (!journalDirReady) {
|
|
1054
|
+
journalDirReady = true
|
|
1055
|
+
try { if (subprocessOf()) await ensureProgressDir(qid) } catch (e) { /* 无 shell:不打扰这一轮 */ }
|
|
1056
|
+
}
|
|
1057
|
+
await writeText(rel, '<!-- scheduler-managed -->\n' + directionMdText(qid, d, true))
|
|
1058
|
+
}
|
|
929
1059
|
}
|
|
930
1060
|
// 代理直接写内容:模拟/落盘代理声称写的文件(__writes),真实环境代理用 write 工具自己写,此处为调度器兜底落盘
|
|
931
1061
|
async function applyAgentWrites(writes) {
|
|
@@ -1055,10 +1185,87 @@ export function apply(ctx) {
|
|
|
1055
1185
|
* 仍为 true、status 照常响应。计数还会写进 scheduler_state.json 一路带下去。
|
|
1056
1186
|
*/
|
|
1057
1187
|
function activeCount() { return Object.keys(agentRegistry).length }
|
|
1188
|
+
/** P2:状态提交的原子性 + 完整性标记。
|
|
1189
|
+
*
|
|
1190
|
+
* 可用能力只有 fs.resolve/stat/readText/writeText(**宿主没有 rename**)。所以提交协议是
|
|
1191
|
+
* 「**暂存 → 回读校验 → 提交 → 标记**」:
|
|
1192
|
+
* 1) 每个状态文件先写 `<rel>.tmp`,再回读确认**非空**(截断/失败写在这里就被抓住);
|
|
1193
|
+
* 2) 暂存成功才把它写进最终路径(`writeText(rel, …)`),失败则记入 `stateWriteFailures`;
|
|
1194
|
+
* 3) 全部落地后**最后**写 `State/commit.json`:`{seq, at, checkpoint, files, missing, mode}`
|
|
1195
|
+
* —— 它是"这次提交完整"的唯一标记,恢复时用它判断上一次提交是否撕裂。
|
|
1196
|
+
*
|
|
1197
|
+
* **不用 shell move**:宿主可能只提供受控/模拟的 subprocess(本轮实测:假 subprocess 返回 exit 0
|
|
1198
|
+
* 但不会真的移动文件 ⇒ "搬"成功、文件却不存在,状态直接丢失)。有鉴于此,这里坚持只走宿主 fs API;
|
|
1199
|
+
* 代价是没有 rename 级原子性,收益是任何宿主上都不会静默丢文件。
|
|
1200
|
+
*/
|
|
1201
|
+
let stateCommitSeq = 0
|
|
1202
|
+
let stateCommit = { seq: 0, at: 0, complete: false, mode: '', files: [], missing: [] }
|
|
1203
|
+
const stateWriteFailures = [] // P4:被拒绝/失败的落盘(status/report 可见)
|
|
1204
|
+
function noteStateWriteFailure(rel, error) {
|
|
1205
|
+
stateWriteFailures.push({ rel: String(rel), error: String(error || 'unknown'), at: now() })
|
|
1206
|
+
if (stateWriteFailures.length > 32) stateWriteFailures.shift()
|
|
1207
|
+
}
|
|
1208
|
+
async function relNonEmpty(rel) {
|
|
1209
|
+
try { const t = await fsTarget(rel); const st = await fs.stat(t); return !!(st && (st.size === undefined || st.size > 0)) } catch (e) { return false }
|
|
1210
|
+
}
|
|
1211
|
+
async function writeJsonAtomic(rel, obj) {
|
|
1212
|
+
// P4:原子路径也必须过损坏守卫(否则「存在但解析失败」的状态文件会被静默覆写)
|
|
1213
|
+
if (!assertWritable(rel)) {
|
|
1214
|
+
noteStateWriteFailure(rel, 'corruption guard refused write')
|
|
1215
|
+
logActivity('state', '拒绝写入(该 JSON 已存在但无法解析,先修/删它):' + rel)
|
|
1216
|
+
return 'refused'
|
|
1217
|
+
}
|
|
1218
|
+
const text = JSON.stringify(obj, null, 2)
|
|
1219
|
+
// 没有 shell 就没法清理暂存文件(宿主 fs 无 delete/rename)⇒ 直接写,避免留下 .tmp 垃圾与告警
|
|
1220
|
+
if (!subprocessOf()) {
|
|
1221
|
+
try { await writeText(rel, text) } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
1222
|
+
if (!(await relNonEmpty(rel))) noteStateWriteFailure(rel, 'written but empty/missing')
|
|
1223
|
+
return 'direct'
|
|
1224
|
+
}
|
|
1225
|
+
const tmp = rel + '.tmp'
|
|
1226
|
+
let staged = false
|
|
1227
|
+
try { await writeText(tmp, text); staged = await relNonEmpty(tmp) } catch (e) { staged = false }
|
|
1228
|
+
let mode = 'direct'
|
|
1229
|
+
if (staged) {
|
|
1230
|
+
try { await writeText(rel, text); mode = 'stage-commit' } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
1231
|
+
try { await removeFile(tmp) } catch (e) { /* 清理暂存文件失败不影响提交内容 */ }
|
|
1232
|
+
} else {
|
|
1233
|
+
try { await writeText(rel, text) } catch (e) { noteStateWriteFailure(rel, (e && e.message) || e) }
|
|
1234
|
+
}
|
|
1235
|
+
if (!(await relNonEmpty(rel))) noteStateWriteFailure(rel, 'written but empty/missing')
|
|
1236
|
+
return mode
|
|
1237
|
+
}
|
|
1238
|
+
async function commitState(files) {
|
|
1239
|
+
stateCommitSeq += 1
|
|
1240
|
+
const modes = []
|
|
1241
|
+
const missing = []
|
|
1242
|
+
for (let i = 0; i < files.length; i++) {
|
|
1243
|
+
const mode = await writeJsonAtomic(files[i].rel, files[i].obj)
|
|
1244
|
+
modes.push(mode)
|
|
1245
|
+
if (!(await relNonEmpty(files[i].rel))) missing.push(files[i].rel)
|
|
1246
|
+
}
|
|
1247
|
+
const commit = {
|
|
1248
|
+
seq: stateCommitSeq, at: now(), epoch: processEpoch,
|
|
1249
|
+
checkpoint: Number(scheduler.lastCheckpoint) || now(),
|
|
1250
|
+
files: files.map(function (x) { return x.rel }), missing: missing,
|
|
1251
|
+
mode: modes.indexOf('stage-commit') === -1 ? 'direct' : 'stage-commit',
|
|
1252
|
+
}
|
|
1253
|
+
await writeJsonAtomic('State/commit.json', commit)
|
|
1254
|
+
stateCommit = Object.assign({ complete: missing.length === 0 }, commit)
|
|
1255
|
+
return commit
|
|
1256
|
+
}
|
|
1058
1257
|
async function loadState() {
|
|
1059
1258
|
const s = await readJson('State/scheduler_state.json')
|
|
1060
1259
|
// 丢弃历史持久化的 activeCount:旧值可能已漂移,绝不能覆盖推导值。
|
|
1061
1260
|
if (s) { const restored = Object.assign({}, s); delete restored.activeCount; scheduler = Object.assign({}, scheduler, restored) }
|
|
1261
|
+
await loadCommitMarker()
|
|
1262
|
+
// P5:活动日志回读(旧实现只落 report 快照且从不回读)
|
|
1263
|
+
const al = await readJson('State/activity_log.json')
|
|
1264
|
+
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) }
|
|
1265
|
+
// P6:论文排队/回收计数恢复;在途撰写者跨进程不可恢复 ⇒ 清掉留痕
|
|
1266
|
+
const pj = await readJson('State/paper.json')
|
|
1267
|
+
if (pj && typeof pj === 'object') { if (pj.pending) paperPending = pj.pending; paperReaps = Number(pj.reaps) || 0 }
|
|
1268
|
+
if (paperInFlight) { logActivity('paper', '上一进程的在途撰写者 ' + paperInFlight + ' 不可恢复 ⇒ 交由 paper 锁/回收逻辑重新触发'); paperInFlight = ''; paperInFlightAt = 0 }
|
|
1062
1269
|
const r = await readJson('State/agents.json'); if (r) agentRegistry = r
|
|
1063
1270
|
const dq = await readJson('State/decision_queue.json'); if (dq) decisionQueue = dq
|
|
1064
1271
|
const va = await readJson('State/verifier_accuracy.json'); if (va) verifierAccuracy = va
|
|
@@ -1085,22 +1292,43 @@ export function apply(ctx) {
|
|
|
1085
1292
|
formalState = { records: clean, todo: Array.isArray(fm.todo) ? fm.todo.filter(function (t) { return t && t.id }) : [], libRuns: (fm.libRuns && typeof fm.libRuns === 'object') ? fm.libRuns : {} }
|
|
1086
1293
|
}
|
|
1087
1294
|
}
|
|
1295
|
+
/** P2:恢复时校验上一次提交的完整性(标记列出但缺失/为空的状态文件 = 撕裂提交)。 */
|
|
1296
|
+
async function loadCommitMarker() {
|
|
1297
|
+
const cm = await readJson('State/commit.json')
|
|
1298
|
+
if (!cm || typeof cm !== 'object') { stateCommit = { seq: 0, at: 0, complete: false, mode: '', files: [], missing: [] }; return stateCommit }
|
|
1299
|
+
const missing = []
|
|
1300
|
+
const files = Array.isArray(cm.files) ? cm.files : []
|
|
1301
|
+
for (let i = 0; i < files.length; i++) { if (!(await relNonEmpty(files[i]))) missing.push(files[i]) }
|
|
1302
|
+
stateCommitSeq = Number(cm.seq) || 0
|
|
1303
|
+
stateCommit = Object.assign({}, cm, { complete: missing.length === 0, missing: missing })
|
|
1304
|
+
if (missing.length && files.length) logActivity('state', '上一次状态提交不完整(缺失/为空:' + missing.join(', ') + ')⇒ 按逐文件容错读取,最后一次提交的部分字段可能丢失')
|
|
1305
|
+
return stateCommit
|
|
1306
|
+
}
|
|
1088
1307
|
async function saveAll() {
|
|
1089
|
-
|
|
1090
|
-
await writeJson('State/agents.json', agentRegistry)
|
|
1091
|
-
await writeJson('State/decision_queue.json', decisionQueue)
|
|
1092
|
-
await writeJson('State/verifier_accuracy.json', verifierAccuracy)
|
|
1093
|
-
await writeJson('State/tasks.json', tasks)
|
|
1094
|
-
await writeJson('State/explorer_retries.json', explorerRetries)
|
|
1095
|
-
await writeJson('State/plans.json', { queued: planQueue, epoch: processEpoch })
|
|
1096
|
-
await writeJson('State/method_log.json', methodLog)
|
|
1097
|
-
await writeJson('State/project_lock.json', projectLock)
|
|
1098
|
-
await writeJson('State/archived_journals.json', archivedJ)
|
|
1099
|
-
// 未启用且从未产生任何形式化记录时不落这份状态文件(off 保持真正的无操作)。
|
|
1100
|
-
if (formalOn() || Object.keys(formalState.records).length || formalState.todo.length || Object.keys(formalState.libRuns).length) await writeJson('State/formal.json', formalState)
|
|
1101
|
-
if (lastPlanSummary) await writeJson('State/last_plan.json', lastPlanSummary)
|
|
1308
|
+
// P2:checkpoint 先落内存再提交(旧顺序 ⇒ 磁盘 checkpoint 永远滞后一次)
|
|
1102
1309
|
scheduler.lastCheckpoint = now()
|
|
1103
|
-
|
|
1310
|
+
const files = [
|
|
1311
|
+
// P5:活动日志(有界)落盘;P6:论文排队/回收计数落盘
|
|
1312
|
+
// 注:这里**没有** v2 的 pending_review_scores.json——v3 的"历史准确率"按稳定身份键直接累计、
|
|
1313
|
+
// 没有"等对象后来取得布尔定论再回溯计分"的待计分表,因此它的缺席是**设计如此**,不是漏掉的产物。
|
|
1314
|
+
{ rel: 'State/activity_log.json', obj: activityLog.slice(-ACTIVITY_PERSIST_MAX) },
|
|
1315
|
+
{ rel: 'State/paper.json', obj: { pending: paperPending, reaps: paperReaps } },
|
|
1316
|
+
{ rel: 'State/scheduler_state.json', obj: scheduler },
|
|
1317
|
+
{ rel: 'State/agents.json', obj: agentRegistry },
|
|
1318
|
+
{ rel: 'State/decision_queue.json', obj: decisionQueue },
|
|
1319
|
+
{ rel: 'State/verifier_accuracy.json', obj: verifierAccuracy },
|
|
1320
|
+
{ rel: 'State/tasks.json', obj: tasks },
|
|
1321
|
+
{ rel: 'State/explorer_retries.json', obj: explorerRetries },
|
|
1322
|
+
{ rel: 'State/plans.json', obj: { queued: planQueue, epoch: processEpoch } },
|
|
1323
|
+
{ rel: 'State/method_log.json', obj: methodLog },
|
|
1324
|
+
{ rel: 'State/project_lock.json', obj: projectLock },
|
|
1325
|
+
{ rel: 'State/archived_journals.json', obj: archivedJ },
|
|
1326
|
+
]
|
|
1327
|
+
if (formalOn() || Object.keys(formalState.records).length || formalState.todo.length || Object.keys(formalState.libRuns).length) files.push({ rel: 'State/formal.json', obj: formalState })
|
|
1328
|
+
if (lastPlanSummary) files.push({ rel: 'State/last_plan.json', obj: lastPlanSummary })
|
|
1329
|
+
await commitState(files)
|
|
1330
|
+
}
|
|
1331
|
+
// 未启用且从未产生任何形式化记录时不落这份状态文件(off 保持真正的无操作)。
|
|
1104
1332
|
async function refreshParams() {
|
|
1105
1333
|
params = Object.assign({}, DEFAULT_PARAMS)
|
|
1106
1334
|
await loadSettings()
|
|
@@ -1122,20 +1350,24 @@ export function apply(ctx) {
|
|
|
1122
1350
|
function logActivity(event, detail) { activityLog.push({ at: now(), event: event, detail: String(detail || '') }); const cap = Number(params.activityLogCap) || 100; if (activityLog.length > cap) activityLog.shift(); reportDirty = true }
|
|
1123
1351
|
async function buildReport() {
|
|
1124
1352
|
return {
|
|
1125
|
-
ok: true, at: now(), project: currentProject, frameworkRoot: frameworkRoot(),
|
|
1353
|
+
ok: true, at: now(), project: currentProject, projectExists: await projectExistsOnDisk(), frameworkRoot: frameworkRoot(),
|
|
1126
1354
|
running: scheduler.running, mode: params.mode,
|
|
1127
1355
|
activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
1128
1356
|
problems: { total: problems.size, solved: allProblems().filter(function (q) { return q.状态 === '已解决' }).length },
|
|
1129
1357
|
propositions: { total: propos.size, resolved: allPropos().filter(function (p) { return p.概率 === 1 || p.概率 === 0 }).length },
|
|
1130
1358
|
verifyPending: (await buildVerifyCandidates()).length,
|
|
1131
1359
|
methods: { project: methods.size, global: globalMethods.size, pendingInventions: methodLog.pendingInventions.length },
|
|
1132
|
-
|
|
1360
|
+
// F-A(有意拆分):pendingDecisions 是**合同**(两面同形的数字);pendingDecisionItems 是**诊断**(明细),只出现在 report——status 面向代理自省、report 面向文件快照。改动此处需同步 docs/status-report-fields.md(该文档租约在共享层 owner)。
|
|
1361
|
+
pendingDecisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).length,
|
|
1362
|
+
pendingDecisionItems: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }),
|
|
1133
1363
|
registeredAgents: Object.keys(agentRegistry).length,
|
|
1134
1364
|
queuedPlanActions: planQueue.length,
|
|
1135
|
-
recentActivity: activityLog.slice(-Math.min(
|
|
1365
|
+
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)),
|
|
1136
1366
|
// Lean 形式化:模式 + 每个对象的状态 + 形式化待办(可读参数表在 params 里)。
|
|
1137
1367
|
formal: formalSummary(),
|
|
1138
1368
|
params: params,
|
|
1369
|
+
// F5:与 status 对齐,周期快照也能看到论文/编译状态。
|
|
1370
|
+
paper: await paperStatusView(),
|
|
1139
1371
|
}
|
|
1140
1372
|
}
|
|
1141
1373
|
async function maybeWriteReport(force) {
|
|
@@ -1152,7 +1384,7 @@ export function apply(ctx) {
|
|
|
1152
1384
|
lines.push('# 项目进展报告|' + currentProject + '|' + fmtTime())
|
|
1153
1385
|
lines.push('')
|
|
1154
1386
|
lines.push('## 总览')
|
|
1155
|
-
lines.push('- 运行中:' + scheduler.running + ';活跃子代理:' + activeCount() + '/' + params.maxParallelThreshold)
|
|
1387
|
+
lines.push('- 运行中:' + (scheduler.running ? '是' : '否') + ';活跃子代理:' + activeCount() + '/' + params.maxParallelThreshold)
|
|
1156
1388
|
lines.push('- 问题:' + allProblems().filter(function (q) { return q.状态 === '已解决' }).length + '/' + problems.size + ' 已解决;命题:' + allPropos().filter(function (p) { return p.概率 === 1 || p.概率 === 0 }).length + '/' + propos.size + ' 已定论')
|
|
1157
1389
|
lines.push('- 方法库:项目 ' + methods.size + ' 条,全局 ' + globalMethods.size + ' 条,待沉淀发明 ' + methodLog.pendingInventions.length + ' 条')
|
|
1158
1390
|
lines.push('- 待执行计划动作:' + planQueue.length + ' 条;待人工决策:' + decisionQueue.filter(function (d) { return d.status === 'pending' }).length + ' 条')
|
|
@@ -1160,7 +1392,9 @@ export function apply(ctx) {
|
|
|
1160
1392
|
lines.push('## 问题状态')
|
|
1161
1393
|
for (const q of allProblems()) {
|
|
1162
1394
|
const dirs = getDirState(q.id)
|
|
1163
|
-
|
|
1395
|
+
// F7:人读报告不把裸代码状态拼进中文;中文化后把代码放括号里备查。
|
|
1396
|
+
const DIR_STATUS_ZH = { active: '进行中', 'dead-end': '死路', success: '已成功', queued: '排队中' }
|
|
1397
|
+
const dirInfo = dirs.length ? '(方向:' + dirs.map(function (d) { return d.id + ':' + (DIR_STATUS_ZH[d.status] || d.status) + '(' + d.status + ')' }).join(', ') + ')' : ''
|
|
1164
1398
|
lines.push('- **' + q.id + '**:' + q.状态 + ',优先级 ' + q.优先级 + ',解法 ' + (q.solutions || []).length + ' 条' + dirInfo)
|
|
1165
1399
|
}
|
|
1166
1400
|
lines.push('')
|
|
@@ -1190,10 +1424,10 @@ export function apply(ctx) {
|
|
|
1190
1424
|
if (!rootAgent || typeof rootAgent.followup !== 'function') return
|
|
1191
1425
|
try {
|
|
1192
1426
|
const report = await buildReport()
|
|
1193
|
-
const text = '[Vibe Math V3] 进度更新:项目 "' + currentProject + '" 运行中=' + report.running +
|
|
1427
|
+
const text = '[Vibe Math V3] 进度更新:项目 "' + currentProject + '" 运行中=' + (report.running ? '是' : '否') +
|
|
1194
1428
|
',问题 ' + report.problems.solved + '/' + report.problems.total + ' 已解决,命题 ' + report.propositions.resolved + '/' + report.propositions.total + ' 已定论,' +
|
|
1195
|
-
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions
|
|
1196
|
-
'请调用 vibe_math_report
|
|
1429
|
+
'活跃代理轮数=' + report.activeCount + ',待人工决策=' + report.pendingDecisions + ',待执行计划=' + report.queuedPlanActions + '。' +
|
|
1430
|
+
'请调用 vibe_math_report 汇总当前进展,并用 vibe_math_list_agents 取各代理(id/角色/目标)状态,再用人话简要汇报(不打断用户,简短即可)。'
|
|
1197
1431
|
// 来源 kind 必须是**已声明**的:`MessageSourceMap` 是 merge-extensible 联合,但没有共享的
|
|
1198
1432
|
// catch-all `plugin` kind(审计 L5/dsh-llm message.d.ts),{kind:'plugin'} 是契约外形状。
|
|
1199
1433
|
// role 本来就是 'user',正文自带 "[Vibe Math V3] 进度更新" 的真署名,故用核心声明的 {kind:'user'}。
|
|
@@ -1205,7 +1439,14 @@ export function apply(ctx) {
|
|
|
1205
1439
|
}
|
|
1206
1440
|
|
|
1207
1441
|
// ================= child spawn / followup =================
|
|
1208
|
-
|
|
1442
|
+
// F-6a:宿主 list() 失败(或既无 spawn 也无 fork)时回退到假定值必须**留痕**,不能静默猜。
|
|
1443
|
+
let providerFallbackWarned = false
|
|
1444
|
+
function warnProviderFallback(why) {
|
|
1445
|
+
if (providerFallbackWarned) return
|
|
1446
|
+
providerFallbackWarned = true
|
|
1447
|
+
console.error('vibe-math-v3: pickProvider() falling back to \'spawn\': ' + why)
|
|
1448
|
+
}
|
|
1449
|
+
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' }
|
|
1209
1450
|
function childAgentOptions(role) {
|
|
1210
1451
|
const o = {}
|
|
1211
1452
|
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) {}
|
|
@@ -1225,11 +1466,8 @@ export function apply(ctx) {
|
|
|
1225
1466
|
// host applies the filter when establishing a continuable child
|
|
1226
1467
|
// (dsh-subagent: childCtx.tools.restrict(...)), so a stale name meant the child
|
|
1227
1468
|
// was never created at all.
|
|
1228
|
-
const IS_WINDOWS = process.platform === 'win32'
|
|
1229
|
-
const SCRIPT_TOOLS = IS_WINDOWS ? ['pwsh'] : ['bash']
|
|
1230
1469
|
// 'web_fetch' is only registered when the composition enables fetch (the v4
|
|
1231
1470
|
// preset sets `fetch: false`), so it is a candidate that sanitizeToolFilter drops.
|
|
1232
|
-
const NETWORK_TOOLS = ['web_search', 'web_fetch']
|
|
1233
1471
|
/**
|
|
1234
1472
|
* Drop filter names this host does not register. `known` comes from the host's
|
|
1235
1473
|
* own rejection message, which lists every registered global tool, so this
|
|
@@ -1309,7 +1547,28 @@ export function apply(ctx) {
|
|
|
1309
1547
|
// 并发计数由 agentRegistry 推导,无需手工 +1。
|
|
1310
1548
|
await saveAll()
|
|
1311
1549
|
}
|
|
1312
|
-
|
|
1550
|
+
/**
|
|
1551
|
+
* 中断一个**在册**子代理。审计 D1:旧实现 `try{…}catch(e){}` 把"没有这个 child / 宿主拒绝中断"
|
|
1552
|
+
* 都吞成静默成功(调用方拿到 undefined,还以为已经中断)。现在:空/未知 id ⇒ `code` +
|
|
1553
|
+
* `next{tool,hint}`,宿主抛错 ⇒ 保留错误文本并给替代出口;只有真的发出中断才 `ok:true`。
|
|
1554
|
+
*/
|
|
1555
|
+
/** F-6b:中断失败必须留痕(调用点此前丢掉 {ok:false},界面看起来一切正常)。 */
|
|
1556
|
+
async function interruptTraced(cid, why) {
|
|
1557
|
+
const r = await interruptChild(cid)
|
|
1558
|
+
if (r && r.ok === false) logActivity('interrupt', '中断失败(' + why + '):' + String(cid) + ' — ' + String(r.message || r.code || ''))
|
|
1559
|
+
return r
|
|
1560
|
+
}
|
|
1561
|
+
async function interruptChild(childId) {
|
|
1562
|
+
const id = String(childId == null ? '' : childId).trim()
|
|
1563
|
+
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 调用。' } }
|
|
1564
|
+
if (!Object.prototype.hasOwnProperty.call(agentRegistry, id)) return { ok: false, code: 'VIBE_MATH_CHILD_NOT_FOUND', message: 'interruptChild 失败:' + id + ' 不在本会话的在册子代理里(在册 ' + Object.keys(agentRegistry).length + ' 个)——可能已经结束,或不是本会话的子代理。', next: { kind: 'reason', tool: 'vibe_math_list_agents', hint: '用 vibe_math_list_agents 取当前在册 id;要停整个会话用 vibe_math_abort。' } }
|
|
1565
|
+
try {
|
|
1566
|
+
await subagents.interrupt(id, { kind: 'ancestor', agent: rootAgent })
|
|
1567
|
+
return { ok: true, childId: id }
|
|
1568
|
+
} catch (e) {
|
|
1569
|
+
return { ok: false, code: 'VIBE_MATH_INTERRUPT_FAILED', message: 'interruptChild 失败:宿主拒绝中断 ' + id + '(' + String((e && e.message) || e) + ')。', next: { kind: 'reason', tool: 'vibe_math_abort', hint: '宿主不接受单点中断时,用 vibe_math_abort 终止整个会话的子代理。' } }
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1313
1572
|
|
|
1314
1573
|
// ================= prompts =================
|
|
1315
1574
|
function personaText(key) { return params[key] ? (String(params[key]) + '\n\n') : '' }
|
|
@@ -1524,14 +1783,16 @@ export function apply(ctx) {
|
|
|
1524
1783
|
function plannerPrompt(brief) {
|
|
1525
1784
|
return personaText('plannerPersona') + 'You are the SCHEDULING PLANNER of a multi-agent mathematical research system. Your job: autonomously choose the OPTIMAL schedule — you may lay out the NEXT ' + params.planningHorizon + ' agent-task calls in one plan (they will be executed in order, beyond-capacity ones queued for later ticks).\n\n' +
|
|
1526
1785
|
'CURRENT STATE BRIEF (JSON):\n' + JSON.stringify(brief, null, 2) + '\n\n' +
|
|
1527
|
-
'ACTION VOCABULARY
|
|
1786
|
+
'ACTION VOCABULARY — the code ACCEPTS exactly these 6 actions: spawn / interrupt / promote are HARD-VALIDATED (invalid ones are dropped), while wait / continue / stop are ADVISORY ONLY (they are logged and have NO scheduling effect):\n' +
|
|
1528
1787
|
'- {"action":"spawn","role":"explorer","target":"<qid>","reason":"..."} — problem has no directions yet or all dead (re-derive).\n' +
|
|
1529
1788
|
'- {"action":"spawn","role":"solver","target":"<qid>","direction":"<dirId>","reason":"..."} — active direction, needs a solving round.\n' +
|
|
1530
1789
|
'- {"action":"spawn","role":"verifier","target":"<rId>","reason":"..."} — verify candidate (from verify_candidates); keep solving AND verifying balanced.\n' +
|
|
1531
1790
|
'- {"action":"spawn","role":"method-keeper","reason":"..."} — distill pending inventions / maintain the theory library.\n' +
|
|
1532
1791
|
'- {"action":"interrupt","childId":"<childId>","reason":"..."} — stop a running child (direction dead, superseded...).\n' +
|
|
1533
1792
|
'- {"action":"promote","target":"<pId>","reason":"..."} — high-value unresolved proposition → judge problem.\n' +
|
|
1534
|
-
'- {"action":"wait","target":"<id>","reason":"..."} — advisory:
|
|
1793
|
+
'- {"action":"wait","target":"<id>","reason":"..."} — advisory only (logged; no scheduling effect): you are waiting for a dependency.\n' +
|
|
1794
|
+
'- {"action":"continue","childId":"<childId>","reason":"..."} — advisory only (logged): continuation of an in-flight child is code-driven; this never re-dispatches anything.\n' +
|
|
1795
|
+
'- {"action":"stop","childId":"<childId>","reason":"..."} — advisory only (logged; it does NOT stop anyone). To actually stop a child use `interrupt` with a live childId.\n' +
|
|
1535
1796
|
'\nHARD RULES: never re-schedule verified objects; problems with 依赖未就绪 (依赖就绪=false) should wait unless you explicitly accept a temporary assumption; respect capacity (brief.free_slots); PREFER problems whose dependencies are ready and whose directions have the highest survival; DO NOT forget verification — unresolved solutions/proofs/refutations (verify_candidates) will never be checked unless you schedule a verifier; DO NOT assume a direction is already being worked just because it is shown "active" in a problem — check brief.problems[].running_solver_dirs and brief.active_agents: schedule a solver for a direction ONLY if that direction is NOT in running_solver_dirs (an "active" direction absent from running_solver_dirs is WAITING to be dispatched, not being worked); schedule at most ' + params.planningHorizon + ' actions.\n' +
|
|
1536
1797
|
'Respond with ONLY a single JSON object wrapped in a ```json code fence — no prose:\n' +
|
|
1537
1798
|
'{"summary":"one-line plan rationale","plan":[{"action":"...","role":"...","target":"...","direction":"...","childId":"...","reason":"..."}]}'
|
|
@@ -1546,7 +1807,7 @@ export function apply(ctx) {
|
|
|
1546
1807
|
mathWorkLine() +
|
|
1547
1808
|
(formalWorkLine() + (formalOn() ? '\n【方法沉淀 × Lean 形式化】除了方法卡,你沉淀的每个可复用对象 / 定义 / 假设都应当归档到全局 Lean 库(vibe_math_lean_archive kind=\'def\'),已成立的引理归档到 Proved/(kind=\'lemma\');归档时**连同定义与陈述一起写清**,方便后续直接 import。\n' : '')) +
|
|
1548
1809
|
'OUTPUT CONTRACT — pick ONE channel. Write method cards into Markdown; only the created IDs, which cards were used, and improvements cross the machine reply.\n' +
|
|
1549
|
-
'CHANNEL A (recommended, you can write files): write each method card into `Methods/<m-id>.md` (`# 方法|标题` + `- 标题/ID/类型/状态/可信断言/适用场景` + `## 核心内容`/`## 应用记录`/`## 改进历史`), then reply ONLY this metadata:\n' +
|
|
1810
|
+
'CHANNEL A (recommended, you can write files): write each method card into `Methods/<m-id>.md` — the `<m-id>` in the FILE NAME must be EXACTLY the id you list in `created`(调度器按 `created` 里的 id 去 `Methods/<id>.md` 找卡;不一致会被当成"已沉淀"而实际没有卡)(`# 方法|标题` + `- 标题/ID/类型/状态/可信断言/适用场景` + `## 核心内容`/`## 应用记录`/`## 改进历史`), then reply ONLY this metadata:\n' +
|
|
1550
1811
|
'{"meta":{"kind":"methods","used":[{"id":"m-...","效果":"...","建议":"..."}],"created":["m-xxx"],"improvements":[{"id":"m-...","改进内容":"...","原因":"..."}]}}\n' +
|
|
1551
1812
|
'CHANNEL B (your file tools are unavailable): put the method-card content into __writes and carry the same meta:\n' +
|
|
1552
1813
|
'{"__writes":[{"path":"Methods/<m-id>.md","content":"<# 方法|标题 + 锚点 + ## 核心内容... 完整卡面>"}],"meta":{"kind":"methods","used":[...],"created":["m-xxx"],"improvements":[...]}}'
|
|
@@ -1625,7 +1886,10 @@ export function apply(ctx) {
|
|
|
1625
1886
|
}
|
|
1626
1887
|
return {}
|
|
1627
1888
|
}
|
|
1628
|
-
async function resolveDecision(id, resolution) {
|
|
1889
|
+
async function resolveDecision(id, resolution) {
|
|
1890
|
+
const d = decisionQueue.find(function (x) { return x.id === id })
|
|
1891
|
+
if (!d) return { ok: false, code: 'VIBE_MATH_DECISION_NOT_FOUND', message: 'decision not found:' + String(id == null ? '' : id) + ' 不在人工决策队列里(队列现有 ' + decisionQueue.length + ' 个' + (decisionQueue.length ? (':' + decisionQueue.map(function (x) { return x.id }).join(', ')) : '') + ')——可能已被 vibe_math_decide 结清,或被 start/resume/abort/换项目作废(作废是终态,不会回到 pending)。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '先用 vibe_math_list_decisions 取当前 pending 的 id;若期望它还在,检查是否发生过 start/resume/abort/换项目。' } }
|
|
1892
|
+
if (d.status !== 'pending') return { ok: false, code: 'VIBE_MATH_DECISION_ALREADY_RESOLVED', message: 'decision already resolved:' + String(id) + ' 当前状态是 ' + String(d.status) + '——每次决策只生效一次。', next: { kind: 'reason', tool: 'vibe_math_list_decisions', hint: '用 vibe_math_list_decisions 看还有哪些 pending;要重新决策请等框架发起新的决策项。' } }; d.status = 'resolved'; d.resolution = resolution; if (scheduler.gate && scheduler.gate.decisionId === id) scheduler.gate = null; logActivity('decide', id + ' resolved: ' + resolution.action + (resolution.verdict !== undefined ? ' ' + resolution.verdict : '')); await saveAll(); scheduleTick(); return { ok: true, message: 'decision resolved' } }
|
|
1629
1893
|
|
|
1630
1894
|
// ================= scheduler core =================
|
|
1631
1895
|
function scheduleTick() { tick().catch(function (e) { console.error('vibe-math-v3 tick error: ' + String((e && e.stack) || e)) }) }
|
|
@@ -1877,7 +2141,7 @@ export function apply(ctx) {
|
|
|
1877
2141
|
return true
|
|
1878
2142
|
}
|
|
1879
2143
|
async function backfillVerifiers(t) {
|
|
1880
|
-
while (t.children.length < t.
|
|
2144
|
+
while (t.children.length < voteCount(t).expected) {
|
|
1881
2145
|
if (activeCount() >= params.maxParallelThreshold) break
|
|
1882
2146
|
const index = t.children.length
|
|
1883
2147
|
const childId = await spawnChild('verifier:' + t.rId + ':' + index, verifierReviewPrompt(t.r), { role: 'verifier', rId: t.rId, round: 1, index: index })
|
|
@@ -1891,7 +2155,7 @@ export function apply(ctx) {
|
|
|
1891
2155
|
const t = tasks[ids[i]]
|
|
1892
2156
|
if (t.type !== 'verify') continue
|
|
1893
2157
|
if (t.status === 'paused') {
|
|
1894
|
-
const allReported =
|
|
2158
|
+
const allReported = voteCount(t, t.round).quorum
|
|
1895
2159
|
if (allReported) { t.status = 'debating'; await advanceVerification(t, t.round); continue }
|
|
1896
2160
|
}
|
|
1897
2161
|
if (t.status !== 'spawning') continue
|
|
@@ -2334,6 +2598,21 @@ export function apply(ctx) {
|
|
|
2334
2598
|
await saveDirState(); await writeJournal(qid)
|
|
2335
2599
|
}
|
|
2336
2600
|
function statusFromStop(stopReason) { return (stopReason === 'completed' || stopReason === 'max-tokens') ? 'continue' : 'dead-end' }
|
|
2601
|
+
/**
|
|
2602
|
+
* P1:`add_proposition`/`/vibe add-proposition` 只用于**新建**。id 已存在时**拒绝**,绝不覆盖——
|
|
2603
|
+
* 旧实现 `propos.set(id, p)` + `saveProposition(p)` 会把已有卡的 陈述/证明列表/证伪列表 整条换掉
|
|
2604
|
+
* (静默丢数据)。自动路径(sync_meta 的引理)本来就有 `propos.has` + 文件存在双守卫,这里拉齐语义。
|
|
2605
|
+
*/
|
|
2606
|
+
function propositionIdConflict(id) {
|
|
2607
|
+
const hit = propos.get(id)
|
|
2608
|
+
if (!hit) return null
|
|
2609
|
+
return {
|
|
2610
|
+
code: 'PROPOSITION_ID_EXISTS',
|
|
2611
|
+
message: '命题 id "' + id + '" 已存在:add_proposition 只用于**新建**,不会覆盖已有卡(其 陈述/证明尝试/证伪尝试 一律保留)。',
|
|
2612
|
+
next: '换一个新 id 新建(例如 p-' + shortId() + ');要修改已有卡请写进它自己的 Propos/<分类>/<id>.md(代理直接写卡是允许的)。',
|
|
2613
|
+
existing: { id: hit.id, 标题: hit.标题, 概率: hit.概率, 状态: hit.状态 },
|
|
2614
|
+
}
|
|
2615
|
+
}
|
|
2337
2616
|
async function addLemmaAsProposition(qid, lemma) {
|
|
2338
2617
|
if (!lemma || !lemma.title) return
|
|
2339
2618
|
// M11:`prob`(提示词名)与 `布尔估计`(遗留扁平名)都接受;`分类` 与 `细类型` 同理。
|
|
@@ -2341,7 +2620,7 @@ export function apply(ctx) {
|
|
|
2341
2620
|
let be = clamp01(rawProb != null ? rawProb : 0.6)
|
|
2342
2621
|
if (be >= 1) be = 0.99; else if (be <= 0) be = 0.01
|
|
2343
2622
|
const p = {
|
|
2344
|
-
id: 'p-' +
|
|
2623
|
+
id: 'p-' + shortIdUnique(function (x) { return propos.has(x) }), 标题: lemma.title, 状态: '未定论', 概率: be,
|
|
2345
2624
|
优先级: (lemma.优先级 != null) ? lemma.优先级 : 1, 依赖: [], 价值关键性: clamp01(lemma['价值/关键性'] != null ? lemma['价值/关键性'] : 0.5),
|
|
2346
2625
|
分类: categoryOf({ 分类: lemmaCategory(lemma) || '未分类' }),
|
|
2347
2626
|
陈述: lemma.statement || lemma.title,
|
|
@@ -3756,16 +4035,36 @@ export function apply(ctx) {
|
|
|
3756
4035
|
* 下限取 max(2, expectedCount):verifierCount=1 不是"允许单票",只是"至少 2 个独立验证器"
|
|
3757
4036
|
* (与 createVerifyTask 的 `Math.max(2, ...)` 一致)。
|
|
3758
4037
|
*/
|
|
3759
|
-
|
|
3760
|
-
|
|
4038
|
+
/**
|
|
4039
|
+
* 票数/参与集的**单一来源**(审计 D4)。一个 verify 任务的参与集与"需要几票"只由**创建时的快照**
|
|
4040
|
+
* `t.expectedCount`(createVerifyTask 写入)与当前 round 决定——**绝不**读实时 `params.verifierCount`
|
|
4041
|
+
* 或实时名册:否则同一个计数会在 quorum 判据 / 裁决聚合 / 任务簿记之间给出不同答案,中途调参会让
|
|
4042
|
+
* 已经在进行中的表决永远凑不够票数而卡死(守卫见 formal-verify-v3 §5c)。
|
|
4043
|
+
*/
|
|
4044
|
+
function voteCount(t, round) {
|
|
4045
|
+
const tt = t || {}
|
|
4046
|
+
const expected = Math.max(2, Number(tt.expectedCount) || 0)
|
|
4047
|
+
const r = Number((round === undefined || round === null) ? (tt.round || 1) : round) || 1
|
|
4048
|
+
const participants = Array.isArray(tt.children) ? tt.children.slice() : []
|
|
4049
|
+
const results = tt.childResults || {}
|
|
4050
|
+
const reportedIds = Object.keys(results)
|
|
4051
|
+
const thisRoundIds = participants.filter(function (cid) { const x = results[cid]; return x && Number(x.round) === r })
|
|
4052
|
+
return {
|
|
4053
|
+
round: r, expected: expected, participants: participants,
|
|
4054
|
+
dispatched: participants.length, reported: reportedIds.length, reportedIds: reportedIds,
|
|
4055
|
+
reportedThisRound: thisRoundIds.length,
|
|
4056
|
+
quorum: participants.length > 0 && participants.length >= expected && thisRoundIds.length === participants.length,
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
function minVotes(t) { return voteCount(t).expected } // 快照值;**故意**不读实时 params.verifierCount
|
|
4060
|
+
function haveEnoughVotes(t) { const v = voteCount(t); return v.reported >= minVotes(t) } // 单一来源:minVotes 也只读快照
|
|
3761
4061
|
/** 所有已派出的验证器都报了本轮的票,**且**票数达到下限——否则不得推进/裁决。 */
|
|
3762
4062
|
function allReportedWithQuorum(t, round) {
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
return t.children.every(function (cid) { const r = t.childResults[cid]; return r && r.round === round })
|
|
4063
|
+
const v = voteCount(t, round)
|
|
4064
|
+
return v.dispatched > 0 && v.dispatched >= v.expected && v.quorum
|
|
3766
4065
|
}
|
|
3767
|
-
function consensus(t) { const vs =
|
|
3768
|
-
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') }
|
|
4066
|
+
function consensus(t) { const vs = voteCount(t).reportedIds.map(function (cid) { return t.childResults[cid].Result }); if (vs.length === 0) return false; return vs.every(function (v) { return v === 1 }) || vs.every(function (v) { return v === 0 }) }
|
|
4067
|
+
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') }
|
|
3769
4068
|
async function handleVerifier(childId, meta, output, stopReason) {
|
|
3770
4069
|
const rId = meta.rId
|
|
3771
4070
|
const parsed = parseJson(output)
|
|
@@ -3784,7 +4083,7 @@ export function apply(ctx) {
|
|
|
3784
4083
|
return
|
|
3785
4084
|
}
|
|
3786
4085
|
if (t.children.indexOf(childId) === -1) t.children.push(childId)
|
|
3787
|
-
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round }
|
|
4086
|
+
t.childResults[childId] = { Result: Result, Reason: Reason, round: meta.round, changed: (parsed && parsed.changed != null && String(parsed.changed).trim()) ? String(parsed.changed).trim() : null }
|
|
3788
4087
|
delete agentRegistry[childId]
|
|
3789
4088
|
// H5:推进/裁决的前置条件是"已凑齐下限票数且本轮都已回报",不是"目前存在的那几个孩子都报了"。
|
|
3790
4089
|
// 未凑齐时保持 spawning,由 reconcileVerify 在后续 tick 里补派验证器(不裁决、不定论)。
|
|
@@ -3838,7 +4137,7 @@ export function apply(ctx) {
|
|
|
3838
4137
|
}
|
|
3839
4138
|
// 近共识 + forced/flat 裁决(修复 v2 flat 高置信分歧误判:全部同侧且均值≥0.85/≤0.15 取均值)
|
|
3840
4139
|
function finalVerdict(t) {
|
|
3841
|
-
const rs =
|
|
4140
|
+
const rs = voteCount(t).reportedIds.map(function (cid) { return t.childResults[cid] })
|
|
3842
4141
|
if (rs.length === 0) return 0.5
|
|
3843
4142
|
if (rs.every(function (r) { return r.Result === 1 })) return 1
|
|
3844
4143
|
if (rs.every(function (r) { return r.Result === 0 })) return 0
|
|
@@ -3877,7 +4176,7 @@ export function apply(ctx) {
|
|
|
3877
4176
|
verifierAccuracy[cids[i]] = acc
|
|
3878
4177
|
}
|
|
3879
4178
|
pruneVerifierAccuracy()
|
|
3880
|
-
await writeJson('Logs/Verification/' + t.rId + '_' + Date.now() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
|
|
4179
|
+
await writeJson('Logs/Verification/' + t.rId + '_' + Date.now() + '_' + shortId() + '.json', { r: r, verdict: v, results: t.childResults, transcript: buildTranscript(t), history: t.history || [], at: now() })
|
|
3881
4180
|
// ── require 门禁(契约 §8)────────────────────────────────────────────────
|
|
3882
4181
|
// 判定为真(严格证明)或假(严格反驳)之前必须先有 `passed` 或 `blocked`。门禁放在**改
|
|
3883
4182
|
// 对象之前**:不这样就改写不出"不改变对象的既有权重/概率字段"(一旦先把 概率 写成 1,
|
|
@@ -4036,7 +4335,7 @@ export function apply(ctx) {
|
|
|
4036
4335
|
const cid = paperInFlight
|
|
4037
4336
|
paperInFlight = ''; paperInFlightAt = 0
|
|
4038
4337
|
if (cid) {
|
|
4039
|
-
try { await
|
|
4338
|
+
try { await interruptTraced(cid, 'paper writer re-dispatch') } catch (e) { /* best effort */ }
|
|
4040
4339
|
delete agentRegistry[cid]
|
|
4041
4340
|
paperAbandoned[cid] = true
|
|
4042
4341
|
}
|
|
@@ -4053,6 +4352,12 @@ export function apply(ctx) {
|
|
|
4053
4352
|
/** 证据索引(spec §3.9):只列**确实存在**的文件;排除 Paper/ 自己(避免自我污染)。
|
|
4054
4353
|
* formalVerify=off 必须真的是无操作(formal-verify-v3 的语料守卫断言"off 的提示词里零 Lean 文本"),
|
|
4055
4354
|
* 所以 off 档不把形式化路径写进材料。 */
|
|
4355
|
+
/**
|
|
4356
|
+
* 成员可见路径说明(跨预设审计 P0)。成员/子代理的**文件工具按会话 cwd 解析**相对路径,而下面列出的
|
|
4357
|
+
* 路径都是**项目根相对**的 ⇒ 必须显式说明"先拼绝对前缀";计算产物用回执里的绝对字段。一处常量、
|
|
4358
|
+
* 五处材料文本各自 append(绝不替换既有行)。
|
|
4359
|
+
*/
|
|
4360
|
+
const PAPER_PATH_NOTE = '(路径说明:成员/子代理的文件工具按**会话 cwd** 解析相对路径,因此上面列出的相对路径都必须先拼上**项目根的绝对前缀**再使用;计算产物请用回执里的绝对字段 `receipt.scriptAbs`,或把 `receipt.cwd` 与 `receipt.scriptPath` 拼起来。)'
|
|
4056
4361
|
async function paperEvidenceIndex() {
|
|
4057
4362
|
const formalNow = formalOn()
|
|
4058
4363
|
const out = []
|
|
@@ -4061,6 +4366,9 @@ export function apply(ctx) {
|
|
|
4061
4366
|
for (const p of allProblems()) out.push(problemRel(p))
|
|
4062
4367
|
for (const p of allPropos()) out.push(propositionRel(p))
|
|
4063
4368
|
for (const m of methods.values()) out.push(methodRel(m, false))
|
|
4369
|
+
// 审计 F2(v3 同型):Reliable/ 是用户提供的可信参考层,材料证据索引必须收它,
|
|
4370
|
+
// 否则被引用/被要求给出处的来源进不了论文材料。
|
|
4371
|
+
for (const f of await listFiles('Reliable')) out.push('Reliable/' + f)
|
|
4064
4372
|
const dirs = ['Verified/命题', 'Verified/问题', 'Logs/Verification', 'Logs/Plans', 'Progress'].concat(formalNow ? ['Verified/Lean', 'Formal'] : [])
|
|
4065
4373
|
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]) }
|
|
4066
4374
|
const seen = {}, uniq = []
|
|
@@ -4088,6 +4396,7 @@ export function apply(ctx) {
|
|
|
4088
4396
|
const ps = allPropos()
|
|
4089
4397
|
L.push('')
|
|
4090
4398
|
L.push('[PROPOSITIONS] (Propos/<分类>/<id>.md)')
|
|
4399
|
+
L.push(PAPER_PATH_NOTE)
|
|
4091
4400
|
if (ps.length === 0) L.push('- (none)')
|
|
4092
4401
|
for (let i = 0; i < ps.length; i++) {
|
|
4093
4402
|
const p = ps[i]
|
|
@@ -4098,6 +4407,7 @@ export function apply(ctx) {
|
|
|
4098
4407
|
}
|
|
4099
4408
|
L.push('')
|
|
4100
4409
|
L.push('[METHODS / ARTEFACTS] (Methods/ + 全局 VibeMath/Methods/)')
|
|
4410
|
+
L.push(PAPER_PATH_NOTE)
|
|
4101
4411
|
const ms = Array.from(methods.values()).concat(Array.from(globalMethods.values()))
|
|
4102
4412
|
if (ms.length === 0) L.push('- (none)')
|
|
4103
4413
|
for (let i = 0; i < ms.length; i++) {
|
|
@@ -4484,7 +4794,7 @@ export function apply(ctx) {
|
|
|
4484
4794
|
const prevEpoch = await readJson('State/process_epoch.json')
|
|
4485
4795
|
const stale = typeof prevEpoch === 'string' && prevEpoch !== processEpoch
|
|
4486
4796
|
if (fresh || stale) {
|
|
4487
|
-
if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await
|
|
4797
|
+
if (fresh) { const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptTraced(ids[i], 'batch clear (start/abort)') }
|
|
4488
4798
|
if (Object.keys(agentRegistry).length > 0 || Object.keys(tasks).length > 0) {
|
|
4489
4799
|
logActivity(fresh ? 'start' : 'resume', 'cleared ' + Object.keys(agentRegistry).length + ' agent(s) and ' + Object.keys(tasks).length + ' task(s) (' + (fresh ? 'restart' : 'stale from previous process') + ')')
|
|
4490
4800
|
agentRegistry = {}; tasks = {}
|
|
@@ -4551,7 +4861,7 @@ export function apply(ctx) {
|
|
|
4551
4861
|
async function startScheduler(override) { const r = await init(true); if (!r.ok) return r; const lock = await acquireProjectLock(override === true); if (!lock.ok) return lock; scheduler.running = true; scheduler.startedAt = now(); scheduler.gate = null; logActivity('start', 'scheduler started for project ' + currentProject + '(v3:md 知识库 + 规划代理调度 + 方法库)'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler started', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
4552
4862
|
async function resumeScheduler(override) { const r = await init(false); if (!r.ok) return r; const lock = await acquireProjectLock(override === true); if (!lock.ok) return lock; scheduler.running = true; scheduler.gate = null; logActivity('resume', 'scheduler resumed'); await saveAll(); await maybeWriteReport(true); scheduleTick(); return { ok: true, message: 'scheduler resumed', project: currentProject, frameworkRoot: frameworkRoot() } }
|
|
4553
4863
|
async function pauseScheduler() { scheduler.running = false; await releaseProjectLock(); logActivity('pause', 'scheduler paused'); await saveAll(); return { ok: true, message: 'scheduler paused' } }
|
|
4554
|
-
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await
|
|
4864
|
+
async function abortScheduler() { scheduler.running = false; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) await interruptTraced(ids[i], 'batch clear (start/abort)'); agentRegistry = {}; planQueue = []; paperInFlight = ''; paperInFlightAt = 0; paperReaps = 0; paperPending = null; await releaseProjectLock(); logActivity('abort', 'scheduler aborted, ' + ids.length + ' child(ren) interrupted'); await saveAll(); return { ok: true, message: 'scheduler aborted', interrupted: ids.length } }
|
|
4555
4865
|
async function autoResolvePending() {
|
|
4556
4866
|
const pending = decisionQueue.filter(function (d) { return d.status === 'pending' })
|
|
4557
4867
|
for (let i = 0; i < pending.length; i++) {
|
|
@@ -4572,10 +4882,16 @@ export function apply(ctx) {
|
|
|
4572
4882
|
}
|
|
4573
4883
|
if (pending.length > 0) { scheduler.gate = null; logActivity('mode', 'switched to auto — auto-resolved ' + pending.length + ' pending decision(s)'); await saveAll(); scheduleTick() }
|
|
4574
4884
|
}
|
|
4885
|
+
/** 审计 F7(v3 同型):`project` 是**当前会话**的项目名,`projects` 是磁盘上已存在的 slug 列表;
|
|
4886
|
+
* 两者不闭合时(新会话 project='default' 而磁盘上没有该目录)语义必须显式,不能靠调用方猜。 */
|
|
4887
|
+
async function projectExistsOnDisk() { return (await listDirsAt(vibeRoot(), 'Projects')).indexOf(currentProject) !== -1 }
|
|
4575
4888
|
async function getStatus() {
|
|
4576
4889
|
return {
|
|
4890
|
+
at: now(),
|
|
4577
4891
|
ok: true, initialized: rootAgent !== undefined, running: scheduler.running,
|
|
4578
|
-
project: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects'),
|
|
4892
|
+
project: currentProject, projectExists: await projectExistsOnDisk(), projects: await listDirsAt(vibeRoot(), 'Projects'),
|
|
4893
|
+
stateCommit: stateCommit,
|
|
4894
|
+
stateWriteFailures: { count: stateWriteFailures.length, last: stateWriteFailures[stateWriteFailures.length - 1] || null },
|
|
4579
4895
|
mode: params.mode, activeCount: activeCount(), maxParallelThreshold: params.maxParallelThreshold,
|
|
4580
4896
|
frameworkRoot: frameworkRoot(),
|
|
4581
4897
|
problems: { total: problems.size, solved: allProblems().filter(function (q) { return q.状态 === '已解决' }).length },
|
|
@@ -4587,8 +4903,14 @@ export function apply(ctx) {
|
|
|
4587
4903
|
queuedPlanActions: planQueue.length,
|
|
4588
4904
|
plannerEnabled: params.plannerEnabled, plannerFails: plannerFails,
|
|
4589
4905
|
formal: formalSummary(),
|
|
4590
|
-
recentActivity: activityLog.slice(-Math.min(
|
|
4906
|
+
recentActivity: activityLog.slice(-Math.min(ACTIVITY_REPORT_MAX, Number(params.activityLogCap) || 100)), params: params,
|
|
4591
4907
|
// 最终论文的可观测面(spec §2/§6)。
|
|
4908
|
+
// F3(对照 v5 的 leanNoticesScope/fieldScopes):逐组标注会话内存 vs 耐久来源。
|
|
4909
|
+
fieldScopes: {
|
|
4910
|
+
durable: ['project', 'projects', 'problems', 'propositions', 'stateCommit', 'formal', 'methods.project', 'methods.global', 'paper.finalizedAt', 'paper.artifacts', 'paper.compile'],
|
|
4911
|
+
session: ['stateWriteFailures', 'activeCount', 'pendingDecisions', 'registeredAgents', 'verifyPending', 'queuedPlanActions', 'plannerFails', 'methods.pendingInventions', 'recentActivity', 'paper.inFlight', 'paper.inFlightSince', 'paper.inFlightAgeMs', 'paper.reapedThisRun', 'paper.queued'],
|
|
4912
|
+
note: 'session = 本进程内存,重启后归零(看着永远健康是假象);durable = 落在项目树里(State/Formal/Progress/Verified 等),重启后仍在',
|
|
4913
|
+
},
|
|
4592
4914
|
paper: await paperStatusView(),
|
|
4593
4915
|
}
|
|
4594
4916
|
}
|
|
@@ -4678,7 +5000,7 @@ export function apply(ctx) {
|
|
|
4678
5000
|
registerTool('vibe_math_status', TOOL_DESC.vibe_math_status, objParams({}), async function () { await refreshParams(); return await getStatus() })
|
|
4679
5001
|
registerTool('vibe_math_report', TOOL_DESC.vibe_math_report, objParams({}), async function () { await refreshParams(); await maybeWriteReport(true); return await buildReport() })
|
|
4680
5002
|
registerTool('vibe_math_set_mode', TOOL_DESC.vibe_math_set_mode, 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 } })
|
|
4681
|
-
registerTool('vibe_math_set_params', TOOL_DESC.vibe_math_set_params, objParams(
|
|
5003
|
+
registerTool('vibe_math_set_params', TOOL_DESC.vibe_math_set_params, 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 } })
|
|
4682
5004
|
registerTool('vibe_math_setup', TOOL_DESC.vibe_math_setup, 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' } })
|
|
4683
5005
|
registerTool('vibe_math_save_settings', TOOL_DESC.vibe_math_save_settings, objParams({}), async function () { return await saveSettings() })
|
|
4684
5006
|
registerTool('vibe_math_template', TOOL_DESC.vibe_math_template, objParams({ where: { type: 'string', enum: ['global', 'project'] } }), async function (args) { return await createTemplate((args && args.where) || 'global') })
|
|
@@ -4687,7 +5009,7 @@ export function apply(ctx) {
|
|
|
4687
5009
|
// M9:内存键、对象 id、文件名必须是**同一个**归一化 id。此前内存键用原始 id、文件名用
|
|
4688
5010
|
// idSafe(id)、重载键又来自文件名 ⇒ 同一个对象分裂成两个身份(`p-1/2` 与 `p-1-2`)。
|
|
4689
5011
|
const id = idSafe(args.id)
|
|
4690
|
-
if (propos.has(id))
|
|
5012
|
+
if (propos.has(id)) { const cf = propositionIdConflict(id); logActivity('proposition', '拒绝覆盖已有命题:' + id); return Object.assign({ ok: false }, cf) }
|
|
4691
5013
|
const p = { id: id, 标题: id, 状态: '未定论', 概率: clamp01(args.概率 != null ? args.概率 : 0.5), 优先级: (args.优先级 != null) ? args.优先级 : 1, 依赖: [], 价值关键性: clamp01(args['价值/关键性'] != null ? args['价值/关键性'] : 0.5), 分类: args.分类 || '未分类', 陈述: String(args.概述 == null ? '' : args.概述), proofs: [], refutes: [], 来源问题: String(args.来源问题 || ''), 来源方向: String(args.来源方向 || ''), 在问题清单: false }
|
|
4692
5014
|
propos.set(p.id, p); await saveProposition(p); await rebuildIndex(); scheduleTick(); return { ok: true, proposition: p, file: propositionRel(p) }
|
|
4693
5015
|
})
|
|
@@ -4696,10 +5018,10 @@ export function apply(ctx) {
|
|
|
4696
5018
|
registerTool('vibe_math_set_project', TOOL_DESC.vibe_math_set_project, objParams({ name: { type: 'string' } }, ['name']), async function (args) { const slug = slugify(args.name); return await setProject(slug, false) })
|
|
4697
5019
|
registerTool('vibe_math_list_projects', TOOL_DESC.vibe_math_list_projects, objParams({}), async function () { return { ok: true, current: currentProject, projects: await listDirsAt(vibeRoot(), 'Projects') } })
|
|
4698
5020
|
registerTool('vibe_math_list_decisions', TOOL_DESC.vibe_math_list_decisions, objParams({}), async function () { return { ok: true, decisions: decisionQueue.filter(function (d) { return d.status === 'pending' }).map(function (d) { return { id: d.id, node: d.node, context: d.context } }) } })
|
|
4699
|
-
registerTool('vibe_math_decide', TOOL_DESC.vibe_math_decide, objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id });
|
|
5021
|
+
registerTool('vibe_math_decide', TOOL_DESC.vibe_math_decide, objParams({ id: { type: 'string' }, action: { type: 'string', enum: ['approve', 'reject', 'override'] }, verdict: { type: 'number' } }, ['id', 'action']), async function (args) { const d = decisionQueue.find(function (x) { return x.id === args.id }); const resolution = { action: args.action, verdict: args.verdict }; if (!d || d.status !== 'pending') return await resolveDecision(args.id, resolution); const applied = await applyDecision(d.node, d.data, resolution); const r = await resolveDecision(args.id, resolution); return Object.assign({ ok: true, applied: applied }, r) })
|
|
4700
5022
|
registerTool('vibe_math_list_agents', TOOL_DESC.vibe_math_list_agents, objParams({}), async function () { const out = []; const ids = Object.keys(agentRegistry); for (let i = 0; i < ids.length; i++) { const m = agentRegistry[ids[i]]; out.push({ childId: ids[i], role: m.role, qid: m.qid, direction: m.direction, round: m.round, rId: m.rId }) } return { ok: true, agents: out, count: out.length } })
|
|
4701
5023
|
registerTool('vibe_math_message_agent', TOOL_DESC.vibe_math_message_agent, objParams({ childId: { type: 'string' }, message: { type: 'string' } }, ['childId', 'message']), async function (args) { if (!agentRegistry[args.childId]) return { ok: false, message: 'unknown childId' }; await followupChild(args.childId, args.message); return { ok: true, message: 'message delivered' } })
|
|
4702
|
-
registerTool('vibe_math_interrupt_agent', TOOL_DESC.vibe_math_interrupt_agent, objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { await interruptChild(args.childId); return { ok:
|
|
5024
|
+
registerTool('vibe_math_interrupt_agent', TOOL_DESC.vibe_math_interrupt_agent, objParams({ childId: { type: 'string' } }, ['childId']), async function (args) { const r = await interruptChild(args.childId); return Object.assign({}, r, { ok: !!(r && r.ok) }) })
|
|
4703
5025
|
registerTool('vibe_math_plan', TOOL_DESC.vibe_math_plan, objParams({ force: { type: 'boolean' } }), async function (args) { if (args && args.force && scheduler.running && !scheduler.gate) { await callPlanner(); return { ok: true, message: 'planning triggered', queued: planQueue.length } } return { ok: true, queued: planQueue, lastPlan: lastPlanSummary } })
|
|
4704
5026
|
registerTool('vibe_math_index', TOOL_DESC.vibe_math_index, objParams({}), async function () { await loadKnowledgeBase(); const r = await rebuildIndex(); return { ok: true, index: r, project: currentProject } })
|
|
4705
5027
|
registerTool('vibe_math_method_add', TOOL_DESC.vibe_math_method_add, objParams({ id: { type: 'string' }, 标题: { type: 'string' }, 类型: { type: 'string' }, 核心内容: { type: 'string' }, 适用场景: { type: 'string' } }, ['id', '标题']), async function (args) { const id = idSafe(args.id); if (methods.has(id)) return { ok: false, message: 'method id already exists' }; const m = { id: id, 标题: args.标题, 类型: args.类型 || '方法', 状态: '经验', 可信断言: [], 上级体系: [], 子方法: [], 相关: [], 适用场景: args.适用场景 || '', 核心内容: args.核心内容 || '', 定义与记号: '', applications: [], improvements: [], 来源: 'user' }; methods.set(m.id, m); await saveMethod(m, false); await rebuildIndex(); return { ok: true, method: m, file: methodRel(m, false) } })
|
|
@@ -4751,13 +5073,34 @@ export function apply(ctx) {
|
|
|
4751
5073
|
function fileLockKey(target) {
|
|
4752
5074
|
return String(target || '').replace(/\\/g, '/')
|
|
4753
5075
|
}
|
|
5076
|
+
/** 文件写锁的租约时长(审计 D2):与项目锁同型——**持有者活着就续租**,持有者消失后最多这么久失效。
|
|
5077
|
+
* 旧实现只在 claim 时写一次 `at`、判定窗口固定 60s 且全仓没有续租点:成员写一份大文件超过 60s 后,
|
|
5078
|
+
* 另一个成员可以合法抢锁(锁恰好在它最该起作用的场景失效)。 */
|
|
5079
|
+
function fileLockTtl() { return Math.max(1000, Number(params.fileLockTimeoutMs) || 60000) }
|
|
5080
|
+
function fileLockDue() { return Math.max(250, fileLockTtl() / 4) }
|
|
5081
|
+
function fileLockHolderAlive(id) {
|
|
5082
|
+
const cid = String(id || '')
|
|
5083
|
+
if (!cid || cid === 'scheduler') return true
|
|
5084
|
+
if (agentRegistry[cid]) return true
|
|
5085
|
+
try { return String((rootAgent && rootAgent.id) || '') === cid } catch (e) { return false }
|
|
5086
|
+
}
|
|
5087
|
+
/** 每次锁轮询(LOCK_POLL_MS)里与 renewProjectLock 一同执行:只续租**活着**的持有者。 */
|
|
5088
|
+
async function renewFileLocks() {
|
|
5089
|
+
const ids = Object.keys(fileOwner)
|
|
5090
|
+
for (let i = 0; i < ids.length; i++) {
|
|
5091
|
+
const o = fileOwner[ids[i]]
|
|
5092
|
+
if (!o) continue
|
|
5093
|
+
if (!fileLockHolderAlive(o.childId)) continue // 持有者已消失 ⇒ 让租约自然过期(有界接管)
|
|
5094
|
+
if ((now() - (Number(o.at) || 0)) >= fileLockDue()) o.at = now()
|
|
5095
|
+
}
|
|
5096
|
+
}
|
|
4754
5097
|
async function claimWrite(target, agent) {
|
|
4755
5098
|
const childId = (agent && agent.id) ? String(agent.id) : 'scheduler'
|
|
4756
5099
|
const key = String(target || '').replace(/\\/g, '/')
|
|
4757
5100
|
if (!key) return { ok: false, message: 'target required' }
|
|
4758
5101
|
const lockKey = fileLockKey(key)
|
|
4759
5102
|
const owner = fileOwner[lockKey]
|
|
4760
|
-
if (owner && owner.childId !== childId && (now() - (owner.at || 0)) <
|
|
5103
|
+
if (owner && owner.childId !== childId && (now() - (owner.at || 0)) < fileLockTtl()) {
|
|
4761
5104
|
return { ok: false, busy: owner.childId, message: '文件 "' + key + '" 正被其他代理写入,请稍后(写锁)' }
|
|
4762
5105
|
}
|
|
4763
5106
|
// 确保目标父目录存在(如 Progress/<qid>/ 供方向文件写入)
|
|
@@ -4931,7 +5274,7 @@ export function apply(ctx) {
|
|
|
4931
5274
|
if (cmd === 'save') return await saveSettings()
|
|
4932
5275
|
if (cmd === 'template') return await createTemplate(args[0] === 'project' ? 'project' : 'global')
|
|
4933
5276
|
if (cmd === 'add') { const raw = args[0]; const desc = args.slice(1).join(' '); if (!raw || !desc) return { ok: false, message: 'usage: /vibe add <id> <description>' }; const id = idSafe(raw); if (problems.has(id)) return { ok: false, message: 'problem id already exists' }; problems.set(id, { id: id, 标题: id, 状态: '求解中', 优先级: 0, 依赖: [], 被依赖: [], 来源: '原始', 计划: '待调度', 陈述: desc, 来源与动机: '', solutions: [], 判断命题: '', 来源命题: '' }); await saveProblem(problems.get(id)); await rebuildIndex(); scheduleTick(); return { ok: true, message: 'problem added', file: problemRel(problems.get(id)) } }
|
|
4934
|
-
if (cmd === 'add-proposition') { const raw = args[0]; const desc = args.slice(1).join(' '); if (!raw || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const id = idSafe(raw); const p = { id: id, 标题: id, 状态: '未定论', 概率: 0.5, 优先级: 1, 依赖: [], 价值关键性: 0.5, 分类: '未分类', 陈述: desc, proofs: [], refutes: [], 来源问题: '', 在问题清单: false }; propos.set(p.id, p); await saveProposition(p); await rebuildIndex(); scheduleTick(); return { ok: true, proposition: p, file: propositionRel(p) } }
|
|
5277
|
+
if (cmd === 'add-proposition') { const raw = args[0]; const desc = args.slice(1).join(' '); if (!raw || !desc) return { ok: false, message: 'usage: /vibe add-proposition <id> <概述>' }; const id = idSafe(raw); const p1c = propositionIdConflict(id); if (p1c) { logActivity('proposition', '拒绝覆盖已有命题:' + id); return Object.assign({ ok: false }, p1c) } const p = { id: id, 标题: id, 状态: '未定论', 概率: 0.5, 优先级: 1, 依赖: [], 价值关键性: 0.5, 分类: '未分类', 陈述: desc, proofs: [], refutes: [], 来源问题: '', 在问题清单: false }; propos.set(p.id, p); await saveProposition(p); await rebuildIndex(); scheduleTick(); return { ok: true, proposition: p, file: propositionRel(p) } }
|
|
4935
5278
|
if (cmd === 'list-propositions') { const all = allPropos(); return { ok: true, count: all.length, propositions: all.map(function (p) { return { id: p.id, 标题: p.标题, 概率: p.概率, 状态: p.状态, 优先级: p.优先级, 分类: categoryOf(p) } }) } }
|
|
4936
5279
|
if (cmd === 'methods') { const all = Array.from(methods.values()); return { ok: true, count: all.length, methods: all.map(function (m) { return { id: m.id, 标题: m.标题, 类型: m.类型, 状态: m.状态 } }) } }
|
|
4937
5280
|
if (cmd === 'index') { await loadKnowledgeBase(); return await rebuildIndex() }
|
|
@@ -4960,7 +5303,7 @@ export function apply(ctx) {
|
|
|
4960
5303
|
scheduleTick: scheduleTick,
|
|
4961
5304
|
// Lease renewal for the apply-scope interval (see the ctx.effect below). Kept on the session so
|
|
4962
5305
|
// the timer needs no per-session context, and so a mock host WITHOUT ctx.timeout still renews.
|
|
4963
|
-
renewLockIfDue: renewProjectLock,
|
|
5306
|
+
renewLockIfDue: async function () { await renewProjectLock(); await renewFileLocks() },
|
|
4964
5307
|
onChildEnd: onChildEnd,
|
|
4965
5308
|
dispatchVibeCommand: dispatchVibeCommand,
|
|
4966
5309
|
handlers: handlers,
|
|
@@ -4979,10 +5322,6 @@ export function apply(ctx) {
|
|
|
4979
5322
|
mathProbeDue: mathProbeDue,
|
|
4980
5323
|
refreshMathProbe: refreshMathProbe,
|
|
4981
5324
|
mathProbe: function () { return mathProbe },
|
|
4982
|
-
// P2a:persona 两个文本块必须含的「归档→编辑→重跑」规则(文本取自共享模块常量,persona 不手抄)。
|
|
4983
|
-
mathArchiveWorkflowLine: MATH_ARCHIVE_WORKFLOW_LINE,
|
|
4984
|
-
// A 项:替代声明规则(文本由共享模块给出;persona 两块与每轮可用性行都必须带它)。
|
|
4985
|
-
mathSubstitutionRuleLine: MATH_SUBSTITUTION_RULE_LINE,
|
|
4986
5325
|
// childOwner 裁剪用(审计 L1):这个会话当前仍"可能再发 subagent/end"的 child
|
|
4987
5326
|
// = 在册子代理 + 任何任务正在等的那几个。
|
|
4988
5327
|
referencedChildIds: function () {
|
|
@@ -5021,7 +5360,7 @@ export function apply(ctx) {
|
|
|
5021
5360
|
registerTool('vibe_math_status', TOOL_DESC.vibe_math_status, objParams({}), 'vibe_math_status')
|
|
5022
5361
|
registerTool('vibe_math_report', TOOL_DESC.vibe_math_report, objParams({}), 'vibe_math_report')
|
|
5023
5362
|
registerTool('vibe_math_set_mode', TOOL_DESC.vibe_math_set_mode, objParams({ mode: { type: 'string', enum: ['manual', 'auto'] } }, ['mode']), 'vibe_math_set_mode')
|
|
5024
|
-
registerTool('vibe_math_set_params', TOOL_DESC.vibe_math_set_params, objParams(
|
|
5363
|
+
registerTool('vibe_math_set_params', TOOL_DESC.vibe_math_set_params, objParams(paramProps()), 'vibe_math_set_params')
|
|
5025
5364
|
registerTool('vibe_math_setup', TOOL_DESC.vibe_math_setup, objParams({}), 'vibe_math_setup')
|
|
5026
5365
|
registerTool('vibe_math_save_settings', TOOL_DESC.vibe_math_save_settings, objParams({}), 'vibe_math_save_settings')
|
|
5027
5366
|
registerTool('vibe_math_template', TOOL_DESC.vibe_math_template, objParams({ where: { type: 'string', enum: ['global', 'project'] } }), 'vibe_math_template')
|
|
@@ -5470,7 +5809,6 @@ function leanJobId(key, fingerprint) {
|
|
|
5470
5809
|
/** "已落地且通过"的**唯一**判据:state=settled 且 exit 0 且未超时未中断(内容哈希由 settle 校验)。 */
|
|
5471
5810
|
function leanJobSettledOk(rec) { return !!rec && rec.state === 'settled' && rec.exitCode === 0 && !rec.timedOut && !rec.interrupted }
|
|
5472
5811
|
/** 主动性档位(修订 §1):与"验证时的要求强度"(formalVerify)是两件事。 */
|
|
5473
|
-
const LEAN_INITIATIVE_MODES = ['off', 'normal', 'eager']
|
|
5474
5812
|
|
|
5475
5813
|
export const __testHelpers = {
|
|
5476
5814
|
uuid,
|
|
@@ -5555,7 +5893,7 @@ const TOOL_DESC = {
|
|
|
5555
5893
|
vibe_math_save_settings: 'Write the current params to vibe_math_setting.json (JSON with comments) as new defaults.',
|
|
5556
5894
|
vibe_math_template: 'Create a fresh vibe_math_setting.json template (with defaults + comments) in the workspace (global) or current project folder.',
|
|
5557
5895
|
vibe_math_add_problem: 'Add a problem to the current project (creates Problems/<id>.md).',
|
|
5558
|
-
vibe_math_add_proposition: 'Add a proposition to Propos/ (creates Propos/<分类>/<id>.md).',
|
|
5896
|
+
vibe_math_add_proposition: 'Add a NEW proposition to Propos/ (creates Propos/<分类>/<id>.md). The id must be NEW: an existing id is REFUSED with code PROPOSITION_ID_EXISTS (cards are never overwritten — 陈述/证明尝试/证伪尝试 are preserved); pick a fresh id (e.g. p-<8 hex>) or write into that card file directly.',
|
|
5559
5897
|
vibe_math_list_propositions: 'List propositions from Propos/ (summary index: id, 标题, 概率, 状态, 优先级, 分类).',
|
|
5560
5898
|
vibe_math_new_project: 'Create a new math project folder and switch to it.',
|
|
5561
5899
|
vibe_math_set_project: 'Switch the current math project.',
|
|
@@ -5570,7 +5908,7 @@ const TOOL_DESC = {
|
|
|
5570
5908
|
vibe_math_method_add: 'Manually add a method card to Methods/ (creates Methods/<id>.md).',
|
|
5571
5909
|
vibe_math_method_list: 'List methods from Methods/ (+ global VibeMath/Methods/): id, 标题, 类型, 状态, 可信断言, applications count.',
|
|
5572
5910
|
vibe_math_lock_status: 'Show the project lock occupancy.',
|
|
5573
|
-
vibe_math_claim_write: '(member) Acquire the write lock for one target file (relative to the project root). Call before writing a Markdown file directly; a file may only be written by ONE agent at a time. Returns the display path you may write (VibeMath/Projects/<project>/<target>) and a hint.',
|
|
5911
|
+
vibe_math_claim_write: '(member) Acquire the write lock for one target file (relative to the project root). Call before writing a Markdown file directly; a file may only be written by ONE agent at a time. The lock is a LEASE: it is renewed automatically while you are still alive, so a long write keeps it; if your process disappears the lease expires after fileLockTimeoutMs (default 60000) and another agent may take it. Returns the display path you may write (VibeMath/Projects/<project>/<target>) and a hint.',
|
|
5574
5912
|
vibe_math_release_write: '(member) Release the write lock for one target file (relative to the project root). Call after you finished writing it.',
|
|
5575
5913
|
vibe_math_sync_meta: '(member) After you write content into Markdown files, report ONLY lightweight scheduling metadata to keep the scheduler state in sync (content stays in the md files). meta.kind must be one of:\n- "directions": {qid, directions:[{id,title,method,core_assumption,feasibility}], methods_used:[{id,效果,建议}], new_inventions:[{类型,标题,内容描述,是否已入库}]}\n- "solver": {qid, dirId, round, survival, status:"continue|success|dead-end", dead_end_reason, lemmas:[{id,title,statement,proof,prob,价值/关键性,分类,优先级}], methods_used, new_inventions, solution_prob, solution_text, sub_questions:[{q_sub_title,q_sub_statement,assumption_title,assumption_statement}]}\n- "methods": {used:[{id,效果,建议}], created:[ids], improvements:[{id,改进内容,原因}]}',
|
|
5576
5914
|
vibe_math_lean_run: "(member) Execute the Lean toolchain on one .lean file inside the workspace and report the result. Never throws: a missing toolchain returns LEAN_NOT_FOUND, a non-zero exit returns the compiler output. Pass target=<object id> to also record the run against that object.",
|
|
@@ -5584,6 +5922,11 @@ const TOOL_DESC = {
|
|
|
5584
5922
|
|
|
5585
5923
|
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 }
|
|
5586
5924
|
|
|
5925
|
+
/// P8:框架分配 id 时先查重(shortId 是 Math.random 的 8 位 hex,碰撞概率极低但后果是静默覆盖)。
|
|
5926
|
+
function shortIdUnique(isTaken) {
|
|
5927
|
+
for (let i = 0; i < 8; i++) { const id = shortId(); if (!isTaken(id)) return id }
|
|
5928
|
+
return shortId()
|
|
5929
|
+
}
|
|
5587
5930
|
function shortId() { const h = '0123456789abcdef'; let s = ''; for (let i = 0; i < 8; i++) s += h[Math.floor(Math.random() * 16)]; return s }
|
|
5588
5931
|
|
|
5589
5932
|
function clamp01(v) { const n = Number(v); if (!Number.isFinite(n)) return 0.5; return Math.max(0, Math.min(1, n)) }
|